Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 587b80b5

Přidáno uživatelem Dominik Chlouba před téměř 4 roky(ů)

Bug fixes, creating and updating user, role seed
refs #8763 @0.5h

Zobrazit rozdíly:

docs/Leuze.Core.Application.Identity.xml
57 57
            
58 58
            </summary>
59 59
        </member>
60
        <member name="M:Leuze.Core.Application.Identity.ApplicationRole.#ctor(System.String)">
60
        <member name="M:Leuze.Core.Application.Identity.ApplicationRole.#ctor(System.String,System.Boolean)">
61 61
            <summary>
62 62
            
63 63
            </summary>
64 64
            <param name="name"></param>
65
            <param name="isDefault"></param>
65 66
        </member>
66 67
        <member name="P:Leuze.Core.Application.Identity.ApplicationRole.Permissions">
67 68
            <summary>
......
96 97
            </summary>
97 98
            <param name="name"></param>
98 99
        </member>
100
        <member name="P:Leuze.Core.Application.Identity.ApplicationRole.IsDefault">
101
            <summary>
102
            
103
            </summary>
104
        </member>
99 105
        <member name="T:Leuze.Core.Application.Identity.ApplicationRoleClaim">
100 106
            <summary>
101 107
            
......
129 135
            
130 136
            </summary>
131 137
        </member>
138
        <member name="M:Leuze.Core.Application.Identity.ApplicationUser.ChangeEmail(System.String)">
139
            <summary>
140
            
141
            </summary>
142
            <param name="email"></param>
143
        </member>
132 144
        <member name="T:Leuze.Core.Application.Identity.ApplicationUserClaim">
133 145
            <summary>
134 146
            
src/Core/Application/Leuze.Core.Application.Identity/ApplicationRole.cs
19 19
        /// 
20 20
        /// </summary>
21 21
        /// <param name="name"></param>
22
        public ApplicationRole(string name) => (Name, NormalizedName) = (name, name.ToUpper());
22
        /// <param name="isDefault"></param>
23
        public ApplicationRole(string name, bool isDefault) => (Name, NormalizedName, IsDefault) = (name, name.ToUpper(), isDefault);
23 24

  
24 25
        private List<ApplicationPermission> _permissions = new List<ApplicationPermission>();
25 26

  
......
57 58
        /// </summary>
58 59
        /// <param name="name"></param>
59 60
        public void ChangeName(string name) => (Name, NormalizedName) = (name, name.ToUpper());
61

  
62
        /// <summary>
63
        /// 
64
        /// </summary>
65
        public bool IsDefault { get; private set; }
66

  
60 67
    }
61 68
}
src/Core/Application/Leuze.Core.Application.Identity/ApplicationUser.cs
36 36
        /// </summary>
37 37
        public IReadOnlyCollection<ApplicationRole> Roles => _roles.AsReadOnly();
38 38

  
39
        /// <summary>
40
        /// 
41
        /// </summary>
42
        /// <param name="email"></param>
43
        public void ChangeEmail(string email) => (UserName, Email) = (email, email);
39 44
    }
40 45
}
src/Core/Application/Leuze.Core.Application.UI/App.razor
3 3
<CascadingAuthenticationState>
4 4
    <Router AppAssembly="@typeof(CoreUIExtension).Assembly" AdditionalAssemblies="LoadAdditionalAssemblies">
5 5
        <Found Context="routeData">
6
            <AuthorizeRouteView RouteData="@routeData"/>
6
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
7
                <NotAuthorized>
8
                    <RedirectToLogin />
9
                </NotAuthorized>
10
                <Authorizing>
11
                    <p>Just checking with the boss you can come in.</p>
12
                </Authorizing>
13
            </AuthorizeRouteView>
7 14
        </Found>
8 15
        <NotFound>
9
            <LayoutView Layout="@typeof(MainLayout)">
16
            <LayoutView Layout="@typeof(EmptyLayout)">
10 17
                <p>Sorry, there's nothing at this address.</p>
11 18
            </LayoutView>
12 19
        </NotFound>
src/Core/Application/Leuze.Core.Application.UI/Areas/Identity/IdentityHostingStartup.cs
1
using Microsoft.AspNetCore.Hosting;
2
using Microsoft.Extensions.DependencyInjection;
3

  
4
[assembly: HostingStartup(typeof(Leuze.Core.Application.UI.Areas.Identity.IdentityHostingStartup))]
5
namespace Leuze.Core.Application.UI.Areas.Identity
6
{
7
    /// <summary>
8
    /// 
9
    /// </summary>
10
    public class IdentityHostingStartup : IHostingStartup
11
    {
12
        /// <summary>
13
        /// 
14
        /// </summary>
15
        /// <param name="builder"></param>
16
        public void Configure(IWebHostBuilder builder)
17
        {
18
            builder.ConfigureServices((context, services) => services.AddLeuzeCore(context.Configuration, context.HostingEnvironment));
19
            builder.Configure((app) => app.UseLeuzeCore());
20
        }
21
    }
22
}
src/Core/Application/Leuze.Core.Application.UI/Areas/Identity/Pages/Account/Login.cshtml
1
@page
2
@model LoginModel
3

  
4
@{
5
    ViewData["Title"] = "Log in";
6
}
7

  
8
<style>
9
    .login_leuze_logo {
10
        height: 45px;
11
        position: absolute;
12
        top: 60px;
13
        left: 120px;
14
    }
15

  
16
    .login_leuze_robot {
17
        height: 450px;
18
        position: absolute;
19
        bottom: 0;
20
        left: 120px;
21
    }
22

  
23
    @@media and (max-width: 1900px) {
24
        .login_leuze_logo {
25
            height: 35px;
26
        }
27
        .login_leuze_robot {
28
            height: 275px;
29
        }
30
    }
31

  
32
    @@media (max-width: 1400px) {
33
        .login_leuze_logo {
34
            left: auto;
35
            position: relative;
36
        }
37
        .login_leuze_robot {
38
            display: none;
39
        }
40
    }
41
</style>
42
<style>
43
    .login_form {
44
        width: 300px;
45
        height: 357px;
46
        padding-left: 200px;
47
        border-left: 1px solid #EDEDED;
48
        position: absolute;
49
        top: calc(50% - 178.5px);
50
        right: 200px;
51
    }
52
    .login_row {
53
        width: 100%;
54
        margin-bottom: 24px;
55
    }
56
    .login_label {
57
        width: 100%;
58
        color: #9D9D9D;
59
        font-size: 14px;
60
        text-transform: uppercase;
61
        font-weight: 700;
62
        font-family: 'Arial';
63
    }
64
    .login_input {
65
        width: calc(100% - 24px);
66
        padding: 10px 12px;
67
        color: #111111;
68
        font-size: 16px;
69
        font-weight: 700;
70
        font-family: 'Arial';
71
        border-radius: 8px;
72
        border: 1px solid #DADADA;
73
        outline: none;
74
        margin-top: 6px;
75
    }
76
    .login_button {
77
        width: 100%;
78
        padding: 12px;
79
        color: #E30613;
80
        font-size: 14px;
81
        font-weight: 700;
82
        font-family: 'Arial';
83
        border-radius: 8px;
84
        border: 2px solid #E30613;
85
        text-transform: uppercase;
86
        outline: none;
87
        background-color: white;
88
        margin-top: 12px;
89
    }
90
    .login_or {
91
        width: 100%;
92
        color: #DADADA;
93
        font-size: 14px;
94
        font-weight: 700;
95
        font-family: 'Arial';
96
        text-transform: uppercase;
97
        position: relative;
98
        text-align: center;
99
        margin: 24px 0;
100
    }
101
        .login_or::before {
102
            content: '';
103
            width: 100%;
104
            height: 1px;
105
            background-color: #EDEDED;
106
            left: 0;
107
            top: 10px;
108
            position: absolute;
109
        }
110
    .login_orText {
111
        background-color: white;
112
        width: fit-content;
113
        margin: 0 auto;
114
        position: relative;
115
        padding: 0 12px;
116
    }
117
    .login_adWrapper {
118
        width: 100%;
119
        text-align: center;
120
    }
121
    .login_microsoft {
122
        width: 24px;
123
        height: 24px;
124
    }
125
    @@media (max-width: 1400px) {
126
        .login_form {
127
            width: 400px;
128
            right: calc(50% - 200px);
129
            border-left: none;
130
            padding-left: 0;
131
        }
132
    }
133

  
134
    @@media (max-width: 450px) {
135
        .login_form {
136
            width: 90%;
137
            position: relative;
138
            top: 0;
139
            right: 5%;
140
            left: 5%;
141
            margin-top: 150px;
142
            padding-bottom: 24px;
143
            height: auto;
144
        }
145
    }
146
</style>
147

  
148
<img class="login_leuze_logo" src="/Resources/Icons/logo.svg" />
149
<img class="login_leuze_robot" src="/Resources/Icons/login-robot.svg" />
150

  
151
<form id="account" method="post" class="login_form">
152
    <div class="login_row">
153
        <label class="login_label" asp-for="Input.Email">Přihlašovací jméno</label>
154
        <input class="login_input" type="email" asp-for="Input.Email" />
155
    </div>
156
    <div class="login_row">
157
        <label class="login_label" asp-for="Input.Password">Heslo</label>
158
        <input class="login_input" type="password" asp-for="Input.Password" />
159
    </div>
160
    <p>@Model.ErrorMessage</p>
161
    <button type="submit" class="login_button">Přihlásit se</button>
162
    <div class="login_or">
163
        <div class="login_orText">nebo</div>
164
    </div>
165
    <div class="login_adWrapper">
166
        <a href="/MicrosoftIdentity/Account/SignIn">
167
            <img class="login_microsoft" src="/Resources/Icons/microsoft-logo.svg" />
168
        </a>
169
    </div>
170
</form>
src/Core/Application/Leuze.Core.Application.UI/Areas/Identity/Pages/Account/Login.cshtml.cs
1
using System;
2
using System.Collections.Generic;
3
using System.ComponentModel.DataAnnotations;
4
using System.Linq;
5
using System.Threading.Tasks;
6
using Leuze.Core.Application.Identity;
7
using Microsoft.AspNetCore.Authentication;
8
using Microsoft.AspNetCore.Authorization;
9
using Microsoft.AspNetCore.Identity;
10
using Microsoft.AspNetCore.Mvc;
11
using Microsoft.AspNetCore.Mvc.RazorPages;
12
using Microsoft.Extensions.Logging;
13

  
14
namespace Leuze.Core.Application.UI.Areas.Pages.Account
15
{
16
    /// <summary>
17
    /// 
18
    /// </summary>
19
    [AllowAnonymous]
20
    public class LoginModel : PageModel
21
    {
22
        private readonly UserManager<ApplicationUser> _userManager;
23
        private readonly SignInManager<ApplicationUser> _signInManager;
24
        private readonly ILogger<LoginModel> _logger;
25

  
26
        /// <summary>
27
        /// 
28
        /// </summary>
29
        /// <param name="signInManager"></param>
30
        /// <param name="logger"></param>
31
        /// <param name="userManager"></param>
32
        public LoginModel(SignInManager<ApplicationUser> signInManager,
33
            ILogger<LoginModel> logger,
34
            UserManager<ApplicationUser> userManager)
35
            => (_userManager, _signInManager, _logger) = (userManager, signInManager, logger);
36

  
37
        /// <summary>
38
        /// 
39
        /// </summary>
40
        [BindProperty]
41
        public InputModel Input { get; set; } = new();
42

  
43
        /// <summary>
44
        /// 
45
        /// </summary>
46
        public string ReturnUrl { get; set; } = null!;
47

  
48
        /// <summary>
49
        /// 
50
        /// </summary>
51
        [TempData]
52
        public string ErrorMessage { get; set; } = null!;
53

  
54
        /// <summary>
55
        /// 
56
        /// </summary>
57
        public class InputModel
58
        {
59
            /// <summary>
60
            /// 
61
            /// </summary>
62
            [Required]
63
            [EmailAddress]
64
            public string Email { get; set; } = string.Empty;
65

  
66
            /// <summary>
67
            /// 
68
            /// </summary>
69
            [Required]
70
            [DataType(DataType.Password)]
71
            public string Password { get; set; } = string.Empty;
72
        }
73

  
74
        /// <summary>
75
        /// 
76
        /// </summary>
77
        /// <param name="returnUrl"></param>
78
        /// <returns></returns>
79
        public async Task OnGetAsync(string returnUrl = null!)
80
        {
81
            if (!string.IsNullOrEmpty(ErrorMessage))
82
            {
83
                ModelState.AddModelError(string.Empty, ErrorMessage);
84
            }
85

  
86
            returnUrl ??= Url.Content("~/")!;
87

  
88
            // Clear the existing external cookie to ensure a clean login process
89
            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
90

  
91
            ReturnUrl = returnUrl;
92
        }
93

  
94
        /// <summary>
95
        /// 
96
        /// </summary>
97
        /// <param name="returnUrl"></param>
98
        /// <returns></returns>
99
        public async Task<IActionResult> OnPostAsync(string? returnUrl = null!)
100
        {
101
            returnUrl ??= Url.Content("~/")!;
102

  
103
            if (ModelState.IsValid)
104
            {
105
                var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, true, false);
106
                if (result.Succeeded)
107
                {
108
                    _logger.LogInformation("User logged in.");
109
                    return LocalRedirect(returnUrl);
110
                }
111
                else
112
                {
113
                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
114
                    ErrorMessage = "Invalid email or password";
115
                    return Page();
116
                }
117
            }
118

  
119
            // If we got this far, something failed, redisplay form
120
            return Page();
121
        }
122
    }
123
}
src/Core/Application/Leuze.Core.Application.UI/Areas/Identity/Pages/Shared/_Layout.cshtml
1
<!DOCTYPE html>
2
<html>
3
<head>
4
    <meta charset="utf-8" />
5
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
    <title>@ViewData["Title"] - BlazorLogin</title>
7
</head>
8
<body>
9
    <div class="container">
10
        <main role="main" class="pb-3">
11
            @RenderBody()
12
        </main>
13
    </div>
14
    @RenderSection("Scripts", required: false)
15
</body>
16
</html>
src/Core/Application/Leuze.Core.Application.UI/Areas/Identity/Pages/_ViewImports.cshtml
1
@using Microsoft.AspNetCore.Identity
2
@using Leuze.Core.Application.UI.Areas.Pages.Account
3
@using Leuze.Core.Application.UI.Areas.Pages
4
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
src/Core/Application/Leuze.Core.Application.UI/Areas/Identity/Pages/_ViewStart.cshtml
1

2
@{
3
    Layout = "/Areas/Identity/Pages/Shared/_Layout.cshtml";
4
}
src/Core/Application/Leuze.Core.Application.UI/Components/Login/Form.razor
1

1
@inject IMediator _mediator
2
@inject NavigationManager _navManager
3

  
2 4
<Styled @bind-Classname="@_form">
3
    width: 504px;
5
    width: 300px;
4 6
    height: 357px;
5 7
    padding-left: 200px;
6 8
    border-left: 1px solid #EDEDED;
......
37 39
    font-family: 'Arial';
38 40
</Styled>
39 41
<Styled @bind-Classname="@_input">
40
    width: 100%;
42
    width: calc(100% - 24px);
41 43
    padding: 10px 12px;
42 44
    color: #111111;
43 45
    font-size: 16px;
......
46 48
    border-radius: 8px;
47 49
    border: 1px solid #DADADA;
48 50
    outline: none;
51
    margin-top: 6px;
49 52
</Styled>
50 53
<Styled @bind-Classname="@_button">
51 54
    width: 100%;
......
97 100
    height: 24px;
98 101
</Styled>
99 102

  
100
<form class="@_form">
103
<form @onsubmit="Login" class="@_form">
101 104
    <div class="@_row">
102 105
        <label class="@_label">Přihlašovací jméno</label>
103
        <input class="@_input" />
106
        <input class="@_input" type="email" @bind-value="Email" />
104 107
    </div>
105 108
    <div class="@_row">
106 109
        <label class="@_label">Heslo</label>
107
        <input class="@_input" />
110
        <input class="@_input" type="password" @bind-value="Password" />
111
    </div>
112
    <p>@Error</p>
113
    <button @onclick="Login" class="@_button" @onkeypress:preventDefault>Přihlásit se</button>
114
    <div class="@_or">
115
        <div class="@_orText">nebo</div>
116
    </div>
117
    <div class="@_adWrapper">
118
        <a href="MicrosoftIdentity/Account/SignIn">
119
            <img class="@_microsoft" src="/Resources/Icons/microsoft-logo.svg" />
120
        </a>
108 121
    </div>
109
    <button class="@_button">Přihlásit se</button>
110
   
111
    
112
        <div class="@_or">
113
            <div class="@_orText">nebo</div>
114
        </div>
115
        <div class="@_adWrapper">
116
            <a href="MicrosoftIdentity/Account/SignIn">
117
                <img class="@_microsoft" src="/Resources/Icons/microsoft-logo.svg" />
118
            </a>
119
        </div>
120
    
121 122
</form>
122 123

  
123 124
@code {
......
131 132
    private string _adWrapper = null!;
132 133
    private string _microsoft = null!;
133 134

  
134
    /// <summary>
135
    /// 
136
    /// </summary>
137
    //public List<AuthenticationScheme> ExternalLogins { get; set; } = new();
135
    private string Email { get; set; } = string.Empty;
136
    private string Password { get; set; } = string.Empty;
137

  
138
    private string Error { get; set; } = string.Empty;
138 139

  
139
    /// <summary>
140
    /// 
141
    /// </summary>
142
    /// <returns></returns>
143
   /* protected override async Task OnInitializedAsync()
140
    private async Task Login()
144 141
    {
145
        ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
146
    }*/
142
        var result = await _mediator.Send(new VerifyLocalUser.Command(Email, Password));
143

  
144
        if (!result.IsSuccess) Error = result.Errors.First();
145
        else _navManager.NavigateTo("/");
146

  
147
    }
148

  
147 149
}
src/Core/Application/Leuze.Core.Application.UI/Leuze.Core.Application.UI.csproj
22 22
  </PropertyGroup>
23 23

  
24 24
  <ItemGroup>
25
    <PackageReference Include="Blazored.Toast" Version="3.1.2" />
25 26
    <PackageReference Include="BlazorStyled" Version="3.1.0" />
26 27
    <PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
28
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
27 29
  </ItemGroup>
28 30

  
29 31
  <ItemGroup>
src/Core/Application/Leuze.Core.Application.UI/Pages/Login.razor
1
@page "/Account/Login"
1
@page "/Identity/Account/ResetPassword"
2 2
@attribute [AllowAnonymous]
3 3
@layout EmptyLayout
4 4
<Styled>
src/Core/Application/Leuze.Core.Application.UI/Pages/Testing/User.razor
1
@page "/Testing"
2
@attribute [AllowAnonymous]
3
@layout EmptyLayout
4
@inject IMediator _mediator
5
<h3>Testovací GUI pro tvorbu lokálních uživatelů</h3>
6

  
7
<button @onclick="CreateUser">Vytvořit uživatele test@test.cz</button>
8
<br/>
9
Token: @Token
10
<br/>
11
<button @onclick="SetPassword">Nastavit heslo Test123*</button>
12

  
13
@code {
14

  
15
    protected string Token { get; set; }
16

  
17
    protected async Task CreateUser()
18
    {
19
        var result = await _mediator.Send(new CreateLocalUser.Command("Testovací Uživatel", "test@test.cz", null!, new List<Guid>()));
20
        Token = result.Result!.Token;
21
    }
22

  
23
    protected async Task SetPassword()
24
    {
25
        await _mediator.Send(new ResetLocalUserPassword.Command("test@test.cz", Token, "Test123*"));
26
    }
27

  
28
}
src/Core/Application/Leuze.Core.Application.UI/Pages/Users/Components/UserAside.razor
1
@inject IMediator _mediator
2
@inject IToastService toastService
3

  
4
<aside class="@_aside side_dialog">
5
    <div class="dialog_header">
6
        <div>Nový uživatel</div>
7
        <button @onclick="Close" type="button">X</button>
8
    </div>
9
    <div class="dialog_body">
10
        @if (UserId != default(Guid) && Loading)
11
        {
12
            <div>Načítám uživatele</div>
13
        }
14
        else
15
        {
16
            <div class="input_row">
17
                <label class="input_label">Jméno a příjmení<span style="color:#ff0000">*</span></label>
18
                <input type="text" placeholder="Jméno a příjmení" disabled="@IsAdUser" @bind-value="Name" />
19
            </div>
20
            <div class="input_row">
21
                <label class="input_label">E-mailová adresa<span style="color:#ff0000">*</span></label>
22
                <input type="email" placeholder="E-mail" @bind-value="Email" />
23
            </div>
24
            <div class="section_title">Role<span style="color:#ff0000">*</span></div>
25
            <div class="input_row">
26
                @if (Roles is null)
27
                {
28
                    <div>Načítám role</div>
29
                }
30
                else
31
                {
32
                    @foreach (var role in Roles)
33
                    {
34
                        <label class="container" @onclick="() => SetRole(role.Id)">
35
                            @role.Name
36
                            <input type="checkbox" checked="@(Role == role.Id)">
37
                            <span class="checkmark"></span>
38
                        </label>
39
                    }
40
                    @if (Roles.Count == 0)
41
                    {
42
                        <div>Nenalezeny žádné role</div>
43
                    }
44
                }
45
            </div>
46
            <div class="section_title">Nadřízený</div>
47
            <div class="input_row">
48
                @if (Users is null)
49
                {
50
                    <div>Načítám uživatele</div>
51
                }
52
                else
53
                {
54
                    <select @onchange="(e) => SetUser((e.Value != null && (string)e.Value != string.Empty) ? Guid.Parse((string)e.Value) : default(Guid))">
55
                        <option value="" selected="@(Senior == default(Guid))">Vyberte uživatele</option>
56

  
57
                        @foreach (var user in Users)
58
                        {
59
                            <option value="@user.Id" selected="@(Senior == user.Id)">@user.Name</option>
60
                        }
61
                    </select>
62
                }
63
            </div>
64
        }
65
    </div>
66
    <div class="button_container">
67
        <button @onclick="FireAction" disabled="@(IsValidModel() || Sent)">@(UserId == default(Guid) ? "Vytvořit" : "Uložit")</button>
68
        <button @onclick="Close">Zrušit</button>
69
    </div>
70
</aside>
71

  
72
<Styled @bind-Classname="@_aside">
73
    right: @(Show ? "0" : "-525")px;
74
</Styled>
75
<Styled>
76
    .side_dialog {
77
    width: 500px;
78
    height: 100%;
79
    position: fixed;
80
    background-color: #FFFFFF;
81
    z-index: 1;
82
    top: 0;
83
    right: -525px;
84
    transition: .25s right;
85
    -webkit-box-shadow: 0 0 6px 0 rgba(0,0,0,0.1);
86
    -moz-box-shadow: 0 0 6px 0 rgba(0,0,0,0.1);
87
    box-shadow: 0 0 6px 0 rgba(0,0,0,0.1);
88
    }
89
    .dialog_body {
90
    background-color: #FFFFFF;
91
    width: calc(100% - 72px);
92
    height: calc(100% - 216px);
93
    padding: 36px;
94
    }
95
    .side_dialog .button_container {
96
    width: calc(100% - 72px);
97
    padding: 12px 36px;
98
    height: 60px;
99
    background-color: #F8F8F8;
100
    display: grid;
101
    grid-template-columns: repeat(2, 1fr);
102
    column-gap: 24px;
103
    align-items: center;
104
    }
105
    .side_dialog .button_container button {
106
    border: 1px solid #111111;
107
    border-radius: 4px;
108
    color: #111111;
109
    padding: 12px 16px;
110
    background-color: #F8F8F8;
111
    font-size: 14px;
112
    font-weight: 400;
113
    cursor: pointer;
114
    transition: .25s background-color, .25s color, .25s border;
115
    }
116
    .side_dialog .button_container button:hover {
117
    background-color: #EEEEEE;
118
    border: 1px solid #EEEEEE;
119
    color: #111111;
120
    }
121
    .side_dialog .button_container button:first-child {
122
    border: 1px solid #E30613;
123
    color: #E30613;
124
    }
125
    .side_dialog .button_container button:first-child:hover {
126
    background-color: #E30613;
127
    color: #FFFFFF;
128
    }
129
    .side_dialog .dialog_header {
130
    height: 36px;
131
    padding: 12px 36px;
132
    width: calc(100% - 72px);
133
    border-bottom: 1px solid #F8F8F8;
134
    display: flex;
135
    justify-content: space-between;
136
    align-items: center;
137
    }
138
    .side_dialog .dialog_header div {
139
    font-size: 20px;
140
    }
141
    .side_dialog .dialog_header button {
142
    border: none;
143
    background: none;
144
    cursor: pointer;
145
    }
146

  
147
    .input_row {
148
    margin-bottom: 24px;
149
    }
150
    .input_row input:not([type="checkbox"]) {
151
    width: calc(100% - 34px);
152
    padding: 12px 16px;
153
    border-radius: 4px;
154
    border: 1px solid #C8C8C8;
155
    font-size: 14px;
156
    }
157
    .input_row label.input_label {
158
    font-size: 16px;
159
    font-weight: 700;
160
    color: #111111;
161
    padding-bottom: 4px;
162
    display: block;
163
    }
164
    .section_title {
165
    font-size: 16px;
166
    font-weight: 700;
167
    color: #111111;
168
    padding-bottom: 4px;
169
    display: block;
170
    }
171
    .container {
172
    display: block;
173
    position: relative;
174
    padding-left: 25px;
175
    margin-bottom: 12px;
176
    cursor: pointer;
177
    font-size: 16px;
178
    -webkit-user-select: none;
179
    -moz-user-select: none;
180
    -ms-user-select: none;
181
    user-select: none;
182
    }
183
    .container input {
184
    position: absolute;
185
    opacity: 0;
186
    cursor: pointer;
187
    height: 0;
188
    width: 0;
189
    }
190
    .checkmark {
191
    position: absolute;
192
    top: 2px;
193
    left: 0;
194
    height: 15px;
195
    width: 15px;
196
    background-color: #eee;
197
    border-radius: 50%;
198
    }
199
    .container:hover input ~ .checkmark {
200
    background-color: #ccc;
201
    }
202
    .container input:checked ~ .checkmark {
203
    background-color: #E30613;
204
    }
205
    .checkmark:after {
206
    content: "";
207
    position: absolute;
208
    display: none;
209
    }
210
    .container input:checked ~ .checkmark:after {
211
    display: block;
212
    }
213
    .container .checkmark:after {
214
    top: 5px;
215
    left: 5px;
216
    width: 5px;
217
    height: 5px;
218
    border-radius: 50%;
219
    background: white;
220
    }
221
</Styled>
222

  
223
@code {
224
    private string _aside;
225

  
226
    private bool Sent { get; set; } = false;
227

  
228
    private bool Loading { get; set; } = true;
229

  
230
    private List<RoleItem> Roles { get; set; } = null!;
231

  
232
    private List<UserShortDto> Users { get; set; } = null!;
233

  
234
    private string Name { get; set; } = string.Empty;
235

  
236
    private string Email { get; set; } = string.Empty;
237

  
238
    private Guid Role { get; set; } = default(Guid);
239

  
240
    private Guid Senior { get; set; } = default(Guid);
241

  
242
    private bool IsAdUser { get; set; } = false;
243

  
244
    [Parameter]
245
    public Guid UserId { get; set; } = default(Guid);
246

  
247
    [Parameter]
248
    public bool Show { get; set; } = false;
249

  
250
    [Parameter]
251
    public EventCallback ToggleUserAside { get; set; }
252

  
253
    [Parameter]
254
    public EventCallback RefetchUsers { get; set; }
255

  
256
    protected override async Task OnInitializedAsync()
257
    {
258
        await base.OnInitializedAsync();
259
        await FetchRoles();
260
        await FetchUsers();
261
    }
262

  
263
    protected override async Task OnParametersSetAsync()
264
    {
265
        await base.OnParametersSetAsync();
266
        await FetchUser();
267
    }
268

  
269
    private async Task Close()
270
    {
271
        ResetValues();
272
        await ToggleUserAside.InvokeAsync();
273
    }
274

  
275
    private void ResetValues()
276
    {
277
        Name = string.Empty;
278
        Email = string.Empty;
279
        Role = default(Guid);
280
    }
281

  
282
    private async Task FetchUser()
283
    {
284
        var result = await _mediator.Send(new GetUserDetail.Query(UserId));
285
        if (result.IsSuccess && result.Result is not null)
286
        {
287
            Name = result.Result.User.Name;
288
            Email = result.Result.User.Email;
289
            Role = result.Result.User.Role.Id;
290
            Loading = false;
291
        }
292
    }
293

  
294
    private async Task FetchUsers()
295
    {
296
        var result = await _mediator.Send(new GetUsersList.Query(false));
297
        if (result.IsSuccess && result.Result is not null)
298
        {
299
            if (UserId == default(Guid)) Users = result.Result.List;
300
            else Users = result.Result.List.Where(o => o.Id != UserId).ToList();
301
        }
302
    }
303

  
304
    private async Task FetchRoles()
305
    {
306
        var result = await _mediator.Send(new GetRolesList.Query());
307
        if (result.IsSuccess && result.Result is not null) Roles = result.Result.List;
308
    }
309

  
310
    private void SetRole(Guid id) => Role = id;
311

  
312
    private void SetUser(Guid id) => Senior = id;
313

  
314
    private bool IsValidModel() => Email == string.Empty || Role == default(Guid) || Name == string.Empty;
315

  
316
    private async Task FireAction()
317
    {
318
        if (UserId == default(Guid))
319
        {
320
            var result = await _mediator.Send(new CreateLocalUser.Command(Name, Email, Senior == default(Guid) ? null! : Senior, new List<Guid>() { Role }));
321
            if (result.IsSuccess)
322
            {
323
                toastService.ShowSuccess("Uživatel úspěšně vytvořen");
324
            }
325
            else toastService.ShowError("Uživatele se nepodařilo vytvořit");
326
        }
327
        else
328
        {
329
            var result = await _mediator.Send(new UpdateUser.Command(UserId, Name, Email, Senior == default(Guid) ? null! : Senior, new List<Guid>() { Role }));
330
            if (result.IsSuccess)
331
            {
332
                toastService.ShowSuccess("Uživatel úspěšně vytvořen");
333
            }
334
            else toastService.ShowError("Uživatele se nepodařilo uložit");
335
        }
336
        ResetValues();
337
        await ToggleUserAside.InvokeAsync();
338
        await RefetchUsers.InvokeAsync();
339
    }
340

  
341
}
src/Core/Application/Leuze.Core.Application.UI/Pages/Users/List.razor
1
@page "/Users"
2
@attribute [Authorize]
3
@layout MainLayout
4
@inject IMediator _mediator
5
<div class="section_header">
6
    <h2>Správa uživatelů</h2>
7
    <button type="button" @onclick="() => SetUserAsideId(default(Guid))">
8
        <img src="/Resources/Icons/plus.svg" class="plus_icon" />
9
        <span>Přidat uživatele</span>
10
    </button>
11
</div>
12
@if (Users is not null)
13
{
14
    <table class="actions" style="width:100%">
15
        <tr>
16
            <th>Jméno</th>
17
            <th>Email</th>
18
            <th>Role</th>
19
            <th>Typ</th>
20
            <th>Akce</th>
21
        </tr>
22
        @foreach (var user in Users)
23
        {
24
    <tr>
25
        <td>@user.Name</td>
26
        <td>@user.Email</td>
27
        <td>@user.Role</td>
28
        <td>@(user.IsAdUser ? "AD" : "Lokální")</td>
29
        <td>
30
            <div @onclick="() => SetUserAsideId(user.Id)">Upravit</div>
31
        </td>
32
    </tr>
33
        }
34
    </table>
35
    @if (Users.Count > 0)
36
    {
37
        <div class="pagination">
38
            <div class="left">
39
                <button class="pagination_button"><img class="pagination_icon" src="/Resources/Icons/chevron-double-left.svg"/></button>
40
                <button class="pagination_button"><img class="pagination_icon" src="/Resources/Icons/chevron-left.svg" /></button>
41
                <input type="number" @bind-value="PageNumber" />
42
                <button class="pagination_button"><img class="pagination_icon" src="/Resources/Icons/chevron-right.svg" /></button>
43
                <button class="pagination_button"><img class="pagination_icon" src="/Resources/Icons/chevron-double-right.svg" /></button>
44
            </div>
45
            <div class="right">Zobrazeno <b>@Users.Count z @Total</b></div>
46
        </div>
47
    }
48
}
49
else
50
{
51
    <div>Loading...</div>
52
}
53
<UserAside Show="ShowUserAside" ToggleUserAside="@ToggleUserAside" UserId="UserAsideId" RefetchUsers="FetchUsers"/>
54

  
55
<Styled>
56
    .section_header {
57
    display: flex;
58
    justify-content: space-between;
59
    align-items: center;
60
    }
61
    .section_header h2 {
62
    color: #111111;
63
    font-size: 36px;
64
    margin: 0;
65
    }
66
    .section_header button {
67
    border-radius: 4px;
68
    border: none;
69
    background-color: #E30613;
70
    padding: 12px 16px;
71
    color: #FFFFFF;
72
    display: flex;
73
    align-items: center;
74
    cursor: pointer;
75
    transition: .25s opacity;
76
    outline: none;
77
    }
78
    .section_header button:hover {
79
    opacity: .6;
80
    }
81
    .section_header button .plus_icon {
82
    width: 18px;
83
    height: 18px;
84
    margin-right: 16px;
85
    }
86
    .section_header button span {
87
    position: relative;
88
    top: 1px;
89
    }
90
</Styled>
91
<Styled>
92
    table {
93
    border-collapse: collapse;
94
    margin-top: 60px !important;
95
    }
96
    table tr th { text-align: left; }
97
    table.actions tr th:last-child, table.actions tr td:last-child { text-align: right; }
98
    table tr {
99
    border-bottom: 1px solid #EDEDED;
100
    }
101
    table tr:first-child { border-bottom: 1px solid #9D9D9D55; }
102
    table tr th, table tr td {
103
    padding: 12px 8px;
104
    }
105
    table tr:not(:first-child):hover {
106
    background-color: #EDEDED55;
107
    }
108
</Styled>
109
<Styled>
110
    input::-webkit-outer-spin-button,
111
    input::-webkit-inner-spin-button {
112
    -webkit-appearance: none;
113
    margin: 0;
114
    }
115
    input[type=number] {
116
    -moz-appearance: textfield;
117
    }
118
    .pagination {
119
    width: 100%;
120
    display: flex;
121
    justify-content: space-between;
122
    align-items: center;
123
    margin-top: 36px;
124
    }
125
    .pagination .left {
126
    display: flex;
127
    align-items: center;
128
    }
129
    .pagination button {
130
    width: 34px;
131
    height: 34px;
132
    border: 1px solid #EEEEEE;
133
    border-radius: 4px;
134
    background-color: #FFFFFF;
135
    cursor: pointer;
136
    outline: none;
137
    margin-right: 6px;
138
    display: flex;
139
    justify-content: center;
140
    align-items: center;
141
    }
142
    .pagination input {
143
    width: 30px;
144
    height: 30px;
145
    padding: 1px 12px;
146
    outline: none;
147
    margin-right: 6px;
148
    border: 1px solid #EEEEEE;
149
    border-radius: 4px;
150
    }
151
    .pagination_icon {
152
    width: 18px;
153
    height: 18px;
154
    }
155
</Styled>
156

  
157
@code {
158

  
159
    private Guid UserAsideId { get; set; } = default(Guid);
160

  
161
    private List<UserDto> Users { get; set; } = null!;
162

  
163
    private int PageNumber { get; set; } = 1;
164

  
165
    private int PageSize { get; set; } = 12;
166

  
167
    private int Total { get; set; } = 0;
168

  
169
    private bool ShowUserAside { get; set; } = false;
170

  
171
    protected override async Task OnInitializedAsync()
172
    {
173
        await base.OnInitializedAsync();
174
        await FetchUsers();
175
    }
176

  
177
    private void ToggleUserAside() => ShowUserAside = !ShowUserAside;
178

  
179
    private void SetUserAsideId(Guid id)
180
    {
181
        UserAsideId = id;
182
        ToggleUserAside();
183
        StateHasChanged();
184
    }
185

  
186
    private async Task FetchUsers()
187
    {
188
        var result = await _mediator.Send(new GetFilteredUsers.Query(PageNumber, PageSize));
189

  
190
        if (result.IsSuccess && result.Result is not null)
191
        {
192
            Users = result.Result.List;
193
            Total = result.Result.Total;
194
        }
195
    }
196

  
197
}
src/Core/Application/Leuze.Core.Application.UI/Pages/_Host.cshtml
6 6
}
7 7

  
8 8
<!DOCTYPE html>
9
<html lang="en">
9
<html lang="cs">
10 10
<head>
11 11
    <meta charset="utf-8" />
12 12
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
13
    <title>Leuze.App</title>
13
    <title>Leuze</title>
14 14
    <base href="~/" />
15
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
16 15
    <link href="css/site.css" rel="stylesheet" />
16
    <link href="_content/Blazored.Toast/blazored-toast.min.css" rel="stylesheet" />
17 17
    <link href="Leuze.App.styles.css" rel="stylesheet" />
18 18
</head>
19 19
<body>
20 20
    <component type="typeof(App)" render-mode="ServerPrerendered" />
21

  
22
    <div id="blazor-error-ui">
23
        <environment include="Staging,Production">
24
            An error has occurred. This application may no longer respond until reloaded.
25
        </environment>
26
        <environment include="Development">
27
            An unhandled exception has occurred. See browser dev tools for details.
28
        </environment>
29
        <a href="" class="reload">Reload</a>
30
        <a class="dismiss">🗙</a>
31
    </div>
32

  
33 21
    <script src="_framework/blazor.server.js"></script>
34 22
</body>
35 23
</html>
src/Core/Application/Leuze.Core.Application.UI/Shared/Components/RedirectToLogin.cs
1
using Microsoft.AspNetCore.Components;
2

  
3
namespace Leuze.Core.Application.UI.Shared.Components
4
{
5
    public class RedirectToLogin : ComponentBase
6
    {
7
        [Inject]
8
        protected NavigationManager NavigationManager { get; set; }
9

  
10
        protected override void OnInitialized()
11
        {
12
            NavigationManager.NavigateTo("/Identity/Account/Login");
13
        }
14
    }
15
}
src/Core/Application/Leuze.Core.Application.UI/Shared/MainLayout.razor
1 1
@inherits LayoutComponentBase
2 2
@inject NavigationManager _navManager
3 3
@inject AuthenticationStateProvider AuthenticationStateProvider
4
<div class="top-row px-4">
5
    <img src="Resources/Icons/logo.svg" alt="logo" style="width:111px;height:28px;" />
4
@using Blazored.Toast.Configuration
5

  
6
<BlazoredToasts Position="ToastPosition.BottomRight"
7
                Timeout="10"
8
                IconType="IconType.FontAwesome"
9
                SuccessIcon="fa fa-thumbs-up"
10
                ErrorIcon="fa fa-bug" />
11
<header class="@_header">
12
    <img class="leuze_logo" src="/Resources/Icons/logo.svg" />
6 13
    <div>
7
        @Name
14
        <span class="username">@Name</span>
15
        <img class="user_icon" src="/Resources/Icons/user-circle.svg" />
8 16
        <a href="/MicrosoftIdentity/Account/SignOut">
9
            <img src="Resources/Icons/sign-out.svg" style="width:18px;height:20px;" />
17
            <img class="signout_icon" src="/Resources/Icons/sign-out.svg" />
10 18
        </a>
11 19
    </div>
12
</div>
13

  
20
</header>
21
<NavMenu />
22
<main class="@_main">@Body</main>
14 23

  
15
<div class="page">
16
    <div class="sidebar">
17
        <NavMenu />
18
    </div>
19

  
20
    <div class="main">
21
        <div class="content px-4">
22
            @Body
23
        </div>
24
    </div>
25
</div>
24
<Styled @bind-Classname="@_header">
25
    width: calc(100% - 84px);
26
    height: 71px;
27
    background-color: #FFFFFF;
28
    border-bottom: 1px solid #EDEDED;
29
    padding: 0 48px 0 36px;
30
    position: fixed;
31
    top: 0;
32
    left: 0;
33
    display: flex;
34
    justify-content: space-between;
35
    align-items: center;
36
    & > div {
37
    display: flex;
38
    justify-content: space-between;
39
    align-items: center;
40
    }
41
    & .leuze_logo {
42
    height: 28.24px;
43
    }
44
    & .username {
45
    color: #111111;
46
    font-size: 18px;
47
    }
48
    & .user_icon {
49
    width: 48px;
50
    height: 48px;
51
    margin: 0 48px 0 16px;
52
    }
53
    & .signout_icon {
54
    width: 26px;
55
    height: 22px;
56
    }
57
</Styled>
58
<Styled @bind-Classname="@_main">
59
    width: calc(100% - 420px);
60
    min-height: calc(100% - 168px);
61
    background-color: transparent;
62
    position: absolute;
63
    top: 72px;
64
    left: 276px;
65
    padding: 48px 72px;
66
</Styled>
26 67

  
27
@code {        
68
@code {
69
    private string _main = null!;
70
    private string _header = null!;
28 71

  
29 72
    private string Name { get; set; } = string.Empty;
30 73

  
src/Core/Application/Leuze.Core.Application.UI/Shared/NavMenu.razor
1
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
2
    <ul class="nav flex-column">
3
        <li class="nav-item">
4
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> 
5
                <img src="/Resources/Icons/analytics.svg" width="13" height="11"/> Přehled
6
            </NavLink>
7
        </li>
8
        <li class="nav-item">
9
            <NavLink class="nav-link" href="goals">
10
                <img src="/Resources/Icons/clipboard-list-check.svg" width="13" height="11" /> Správa cílů
11
            </NavLink>
12
        </li>
13
        <li class="nav-item">
14
            <NavLink class="nav-link" href="benefits">
15
                <img src="/Resources/Icons/award.svg" width="13" height="11" /> Benefity
16
            </NavLink>
17
        </li>
18
        <li class="nav-item">
19
            <NavLink class="nav-link" href="users">
20
                <img src="/Resources/Icons/user.svg" width="13" height="11" /> Uživatelé
21
            </NavLink>
22
        </li>
23
        <li class="nav-item">
24
            <NavLink class="nav-link" href="authorization">
25
                <img src="/Resources/Icons/balance-scale.svg" width="13" height="11" /> Role a práva
26
            </NavLink>
27
        </li>
28
    </ul>
29
</div>
1
<aside class="@_aside">
2
    <nav class="@_nav">
3
        <NavLink href="/dashboard">
4
            <div class="item">
5
                <img src="/Resources/Icons/analytics.svg" class="item_icon nav_icon_1" />
6
                <span>Přehled</span>
7
            </div>
8
        </NavLink>
9
        <NavLink href="/goals">
10
            <div class="item">
11
                <img src="/Resources/Icons/clipboard-list-check.svg" class="item_icon nav_icon_2" />
12
                <span>Správa cílů</span>
13
            </div>
14
        </NavLink>
15
        <NavLink href="/users">
16
            <div class="item">
17
                <img src="/Resources/Icons/user.svg" class="item_icon nav_icon_3" />
18
                <span>Uživatelé</span>
19
            </div>
20
        </NavLink>
21
</nav>
22
</aside>
30 23

  
31
@code {
32
    private bool collapseNavMenu = true;
33

  
34
    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
35

  
36
    private void ToggleNavMenu()
37
    {
38
        collapseNavMenu = !collapseNavMenu;
24
<Styled @bind-Classname="@_aside">
25
    width: 275px;
26
    height: calc(100% - 72px);
27
    position: fixed;
28
    left: 0;
29
    top: 72px;
30
    background-color: #FFFFFF;
31
    border-right: 1px solid #EDEDED;
32
</Styled>
33
<Styled @bind-Classname="@_nav">
34
    margin-top: 36px;
35
    & .item {
36
    padding: 20px 36px;
37
    width: calc(100% - 76px);
38
    border-left: 4px solid #FFFFFF;
39
    display: flex;
40
    align-items: center;
41
    background-color: #FFFFFF;
42
    transition: .25s background-color, .25s border-left-color;
43
    cursor: pointer;
44
    }
45
    & .item:hover {
46
    background-color: #FFFAFA;
47
    border-left: 4px solid #E30613;
48
    }
49
    & .item_active {
50
    background-color: #FFFAFA;
51
    border-left: 4px solid #E30613;
52
    }
53
    & .item > span {
54
    color: #111111;
55
    font-size: 18px;
56
    margin-left: 16px;
57
    position: relative;
58
    top: 2px;
39 59
    }
40
}
60
</Styled>
61
<Styled>
62
    .nav_icon_1 {
63
    width: 20px;
64
    height: 18px;
65
    }
66
    .nav_icon_2 {
67
    width: 20px;
68
    height: 26px;
69
    }
70
    .nav_icon_3 {
71
    width: 20px;
72
    height: 22px;
73
    }
74
</Styled>
75

  
76
@code {
77
    private string _aside = null!;
78
    private string _nav = null!;
79
} 
src/Core/Application/Leuze.Core.Application.UI/_Imports.razor
10 10
@using BlazorStyled
11 11
@using Microsoft.AspNetCore.Authentication
12 12
@using Microsoft.AspNetCore.Identity
13
@using Leuze.Core.Application.Identity 
14
@using Leuze.Core.Application.UI.Components.Login
13
@using Leuze.Core.Application.Identity
14
@using Leuze.Core.Application.UI.Components.Login
15
@using MediatR
16
@using Leuze.Core.Application.CQRS.Users.Commands
17
@using Leuze.Core.Application.CQRS.Auth.Commands
18
@using Leuze.Core.Domain.Domains.Users.DTOs
19
@using Leuze.Core.Application.CQRS.Users.Queries
20
@using Leuze.Core.Application.UI.Shared.Components
21
@using Leuze.Core.Application.UI.Pages.Users.Components
... Rozdílový soubor je zkrácen, protože jeho délka přesahuje max. limit.

Také k dispozici: Unified diff