File size: 8,491 Bytes
3418204
 
 
 
 
 
691d646
3418204
 
8c88a89
3418204
f6d34ff
 
3418204
 
f6d34ff
be17a46
f6d34ff
3418204
8c88a89
 
3418204
 
f6d34ff
3418204
 
 
f6d34ff
3418204
8c88a89
 
3418204
 
 
 
f6d34ff
3418204
 
 
 
 
 
 
 
 
 
 
8c88a89
3418204
 
 
 
 
 
8c88a89
3418204
 
 
 
 
8c88a89
3418204
 
 
 
 
 
8c88a89
3418204
 
01be06d
 
 
8c88a89
01be06d
 
3418204
7f6afc2
8c88a89
 
7f6afc2
 
3418204
f6d34ff
8c88a89
3418204
 
7f6afc2
 
8c88a89
7f6afc2
3418204
 
 
 
 
 
 
 
 
 
 
 
 
 
01be06d
 
 
3418204
 
 
01be06d
3418204
 
 
 
 
 
 
 
 
 
691d646
3418204
 
 
 
 
 
 
 
 
 
 
691d646
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3418204
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
@page "/login"
@layout Layout.EmptyLayout
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
@inject NavigationManager Navigation
@inject AuthenticationStateProvider AuthStateProvider
@inject MenuAuthorizationService MenuAuthorization
@inject IJSRuntime JS

<PageTitle>@AppLocalization.Text("common.signIn", "Sign In") - Taskify</PageTitle>

<div class="flex min-h-screen items-center justify-center bg-slate-50 px-4 py-10 sm:px-6 lg:px-8">
    <div class="w-full max-w-md space-y-7 rounded-xl border border-slate-200 bg-white p-8 shadow-sm">
        <div>
            <div class="flex justify-center">
                <span class="flex h-12 w-12 items-center justify-center rounded-xl bg-violet-600 text-white shadow-sm">
                    <i data-lucide="clipboard-check" class="h-6 w-6"></i>
                </span>
            </div>
            <h2 class="mt-5 text-center text-2xl font-bold text-slate-900">@AppLocalization.Text("common.signIn", "Sign In") - Taskify</h2>
            <p class="mt-2 text-center text-sm text-slate-500">@AppLocalization.PageDescription("dashboard", "Continue to your work dashboard.")</p>
        </div>

        <form method="post" action="/account/login" class="space-y-5">
            <AntiforgeryToken />
            <input type="hidden" name="ReturnUrl" value="@ReturnUrl" />

            <div class="space-y-4">
                <div>
                    <label for="usernameOrEmail" class="block text-sm font-medium text-slate-700 mb-1">@AppLocalization.Text("common.user", "Username or Email")</label>
                    <input id="usernameOrEmail" name="UsernameOrEmail" required placeholder="@AppLocalization.Text("common.searchPlaceholder", "Enter username or email")"
                           class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent transition-all" />
                </div>
                <div class="relative">
                    <label for="password" class="block text-sm font-medium text-slate-700 mb-1">Password</label>
                    <input id="password" name="Password" type="@(showPassword ? "text" : "password")" required placeholder="Password"
                           class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent transition-all pr-10" />
                    <button type="button" @onclick="TogglePassword" class="absolute right-3 top-[28px] text-slate-400 hover:text-slate-600 focus:outline-none">
                        <i data-lucide="@(showPassword ? "eye-off" : "eye")" class="h-5 w-5"></i>
                    </button>
                </div>
            </div>

            <div class="flex items-center justify-between">
                <div class="flex items-center">
                    <input id="rememberMe" name="RememberMe" type="checkbox" value="true"
                           class="h-4 w-4 rounded border-slate-350 text-slate-900 focus:ring-slate-900 focus:outline-none" />
                    <label for="rememberMe" class="ml-2 block text-sm text-slate-600 font-medium">@AppLocalization.Text("common.rememberMe", "Remember me")</label>
                </div>
            </div>

            @if (ShowUnavailableError)
            {
                <div class="p-3 rounded-lg bg-amber-50 border border-amber-100">
                    <p class="text-sm text-amber-800 text-center">@AppLocalization.Text("result.apiUnavailable", "Unable to reach the API server. Start TaskTrackingSystem.WebApi and try again.")</p>
                </div>
            }
            else if (ShowError)
            {
                <div class="p-3 rounded-lg bg-red-50 border border-red-100">
                    <p class="text-sm text-red-600 text-center">@ResultMessages.InvalidCredentials</p>
                </div>
            }

            @if (ShowLoggedOutMessage)
            {
                <div class="p-3 rounded-lg bg-emerald-50 border border-emerald-100">
                    <p class="text-sm text-emerald-700 text-center">@AppLocalization.Text("result.signedOut", "You have been signed out successfully.")</p>
                </div>
            }
            else if (ShowResetMessage)
            {
                <div class="p-3 rounded-lg bg-emerald-50 border border-emerald-100">
                    <p class="text-sm text-emerald-700 text-center">@AppLocalization.Text("result.passwordUpdated", "Password updated successfully. Please sign in with your new password.")</p>
                </div>
            }

            <p class="text-center text-sm text-slate-600">
                @AppLocalization.Text("auth.needAccount", "Need an account?")
                <a href="/register" class="font-medium text-slate-900 hover:underline">@AppLocalization.Text("auth.register", "Register")</a>
            </p>

            <div>
                <button type="submit" class="group relative flex w-full justify-center rounded-lg bg-violet-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-violet-700">
                    @AppLocalization.Text("common.signIn", "Sign In")
                </button>
            </div>

            <p class="text-center text-sm text-slate-600">
                <a href="/reset-password" class="font-medium text-slate-900 hover:underline">@AppLocalization.Text("auth.resetPassword", "Reset password")</a>
            </p>
        </form>
    </div>
</div>

@code {
    [SupplyParameterFromQuery]
    private string? ReturnUrl { get; set; }

    [SupplyParameterFromQuery(Name = "error")]
    private string? Error { get; set; }

    [SupplyParameterFromQuery(Name = "loggedOut")]
    private string? LoggedOut { get; set; }

    [SupplyParameterFromQuery(Name = "reset")]
    private string? Reset { get; set; }

    private bool ShowError => string.Equals(Error, "invalid", StringComparison.OrdinalIgnoreCase);
    private bool ShowUnavailableError => string.Equals(Error, "unavailable", StringComparison.OrdinalIgnoreCase);
    private bool ShowLoggedOutMessage => string.Equals(LoggedOut, "true", StringComparison.OrdinalIgnoreCase);
    private bool ShowResetMessage => string.Equals(Reset, "true", StringComparison.OrdinalIgnoreCase);

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await JS.InvokeVoidAsync("initIcons");

        if (firstRender)
        {
            var authState = await AuthStateProvider.GetAuthenticationStateAsync();
            if (authState.User.Identity?.IsAuthenticated == true)
            {
                Navigation.NavigateTo(await ResolveLandingPageAsync(authState.User), forceLoad: true);
            }
        }
    }

    private bool showPassword = false;

    private async Task TogglePassword()
    {
        showPassword = !showPassword;
        await JS.InvokeVoidAsync("initIcons");
    }

    private async Task<string> ResolveLandingPageAsync(System.Security.Claims.ClaimsPrincipal user)
    {
        try
        {
            var menus = await MenuAuthorization.GetUserMenusAsync(user);
            var landing = FindFirstDashboardMenu(menus);
            if (!string.IsNullOrWhiteSpace(landing))
            {
                return landing;
            }
        }
        catch
        {
        }

        return "/dashboard";
    }

    private static string? FindFirstDashboardMenu(IEnumerable<TaskTrackingSystem.Shared.Models.Menu.MenuDto> menus)
    {
        foreach (var menu in menus)
        {
            var found = FindFirstDashboardMenu(menu);
            if (!string.IsNullOrWhiteSpace(found))
            {
                return found;
            }
        }

        return null;
    }

    private static string? FindFirstDashboardMenu(TaskTrackingSystem.Shared.Models.Menu.MenuDto menu)
    {
        if (!string.IsNullOrWhiteSpace(menu.MenuUrl) &&
            menu.MenuUrl.StartsWith("/dashboard", StringComparison.OrdinalIgnoreCase))
        {
            return menu.MenuUrl;
        }

        foreach (var subMenu in menu.SubMenus)
        {
            var found = FindFirstDashboardMenu(subMenu);
            if (!string.IsNullOrWhiteSpace(found))
            {
                return found;
            }
        }

        return null;
    }
}