|
1 | | -@layout EmptyLayout |
| 1 | +@layout EmptyLayout |
2 | 2 | @page "/" |
3 | 3 | @implements IDisposable |
4 | 4 |
|
|
11 | 11 | @inject IJSRuntime JSRuntime; |
12 | 12 | @inject PreloadService preloadService |
13 | 13 |
|
14 | | -<style> |
15 | | - .form-signin { |
16 | | - width: 100%; |
17 | | - max-width: 330px; |
18 | | - padding: 15px; |
19 | | - margin: auto; |
20 | | - } |
21 | | -
|
22 | | - .buttons { |
23 | | - padding-top: inherit; |
24 | | - } |
25 | | -</style> |
26 | 14 | <Preload LoadingText="Loading..." /> |
27 | 15 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/intl-tel-input@23.0.12/build/css/intlTelInput.css"> |
| 16 | +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"> |
| 17 | + |
| 18 | +<PageTitle>Login - Telegram File Manager</PageTitle> |
| 19 | + |
| 20 | +<div class="login-page"> |
| 21 | + <div class="login-card"> |
| 22 | + @if (Model != null && Model.type != null) |
| 23 | + { |
| 24 | + <!-- Logo --> |
| 25 | + <div class="logo-container"> |
| 26 | + <img src="https://upload.wikimedia.org/wikipedia/commons/8/82/Telegram_logo.svg" alt="Telegram logo" /> |
| 27 | + </div> |
28 | 28 |
|
29 | | -<PageTitle>Login</PageTitle> |
| 29 | + <!-- Title based on step --> |
| 30 | + @switch (Model.type) |
| 31 | + { |
| 32 | + case "phone": |
| 33 | + <h1 class="login-title">Welcome</h1> |
| 34 | + <p class="login-subtitle">Enter your phone number to get started</p> |
| 35 | + break; |
| 36 | + case "vc": |
| 37 | + <h1 class="login-title">Verification</h1> |
| 38 | + <p class="login-subtitle">Enter the code sent to your phone</p> |
| 39 | + break; |
| 40 | + case "pass": |
| 41 | + <h1 class="login-title">Two-Factor Auth</h1> |
| 42 | + <p class="login-subtitle">Enter your 2FA password</p> |
| 43 | + break; |
| 44 | + case "ok": |
| 45 | + <h1 class="login-title">Welcome Back!</h1> |
| 46 | + <p class="login-subtitle">You are successfully authenticated</p> |
| 47 | + break; |
| 48 | + } |
30 | 49 |
|
31 | | -@if (Model != null && Model.type != null) |
32 | | -{ |
33 | | -<div class="container form-signin"> |
34 | | - <div class="text-center"> |
35 | | - <img src="https://static.vecteezy.com/system/resources/previews/023/986/534/large_2x/telegram-logo-telegram-logo-transparent-telegram-icon-transparent-free-free-png.png" alt="Telegram logo" style="max-width: -webkit-fill-available;" /> |
| 50 | + <!-- Step indicator --> |
| 51 | + <div class="step-indicator"> |
| 52 | + <div class="step @(Model.type == "phone" ? "active" : "completed")"></div> |
| 53 | + <div class="step @(Model.type == "vc" ? "active" : (Model.type == "pass" || Model.type == "ok" ? "completed" : ""))"></div> |
| 54 | + <div class="step @(Model.type == "pass" ? "active" : (Model.type == "ok" ? "completed" : ""))"></div> |
| 55 | + <div class="step @(Model.type == "ok" ? "active completed" : "")"></div> |
| 56 | + </div> |
36 | 57 |
|
37 | | - <EditForm class="form-signin mb-4" Model="Model" OnSubmit="Submit" FormName="LoginModel"> |
| 58 | + <EditForm class="login-form" Model="Model" OnSubmit="Submit" FormName="LoginModel"> |
38 | 59 | @switch (Model.type) |
39 | 60 | { |
40 | 61 | case "phone": |
41 | 62 | <div class="form-group"> |
42 | | - <label for="phone">Teléfono</label> |
43 | | - <InputText class="form-control" id="phone" type="text" @bind-Value="Model!.value" aria-describedby="phone" maxlength="12" data-mask="+00.000.00.00.00" placeholder="Teléfono" /> |
| 63 | + <label for="phone"><i class="bi bi-telephone"></i> Phone Number</label> |
| 64 | + <InputText class="form-control" id="phone" type="text" @bind-Value="Model!.value" |
| 65 | + aria-describedby="phone" maxlength="15" placeholder="+34 600 000 000" /> |
44 | 66 | </div> |
45 | 67 | break; |
46 | 68 | case "vc": |
47 | 69 | <div class="form-group"> |
48 | | - <label for="code">Código</label> |
49 | | - <InputText class="form-control" id="code" maxlength="5" type="text" @bind-Value="Model!.value" /> |
| 70 | + <label for="code"><i class="bi bi-shield-lock"></i> Verification Code</label> |
| 71 | + <InputText class="form-control" id="code" maxlength="5" type="text" |
| 72 | + @bind-Value="Model!.value" placeholder="12345" autocomplete="one-time-code" /> |
50 | 73 | </div> |
51 | 74 | break; |
52 | 75 | case "pass": |
53 | 76 | <div class="form-group"> |
54 | | - <label for="pass">Contraseña</label> |
55 | | - <InputText class="form-control" id="pass" type="password" @bind-Value="Model!.value" /> |
| 77 | + <label for="pass"><i class="bi bi-key"></i> Password</label> |
| 78 | + <InputText class="form-control" id="pass" type="password" |
| 79 | + @bind-Value="Model!.value" placeholder="Enter your 2FA password" /> |
56 | 80 | </div> |
57 | 81 | break; |
58 | 82 | case "ok": |
59 | | - <span> Authenticado </span> |
| 83 | + <div class="text-center"> |
| 84 | + <div class="success-badge"> |
| 85 | + <i class="bi bi-check-circle-fill"></i> |
| 86 | + <span>Authenticated</span> |
| 87 | + </div> |
| 88 | + </div> |
60 | 89 | break; |
61 | 90 | } |
62 | | - <div class="form-group buttons"> |
63 | 91 |
|
| 92 | + <div class="button-group"> |
64 | 93 | @if (Model.type == "ok") |
65 | 94 | { |
66 | | - <button class="btn btn-info" @onclick="Salir" type="button">Desconectar</button> |
| 95 | + <button class="btn-danger-custom" @onclick="Salir" type="button"> |
| 96 | + <i class="bi bi-box-arrow-right"></i> Disconnect |
| 97 | + </button> |
67 | 98 | } |
68 | 99 | else |
69 | 100 | { |
70 | | - <button class="btn btn-info" type="submit">Send</button> |
| 101 | + <button class="btn-primary-custom" type="submit"> |
| 102 | + @if (Model.type == "phone") |
| 103 | + { |
| 104 | + <span>Continue</span> |
| 105 | + <i class="bi bi-arrow-right"></i> |
| 106 | + } |
| 107 | + else |
| 108 | + { |
| 109 | + <span>Verify</span> |
| 110 | + <i class="bi bi-check-lg"></i> |
| 111 | + } |
| 112 | + </button> |
71 | 113 | @if (Model.type != null && Model.type != "phone") |
72 | 114 | { |
73 | | - <button class="btn btn-warning" @onclick="Cancel">Cancel</button> |
| 115 | + <button class="btn-secondary-custom" type="button" @onclick="Cancel"> |
| 116 | + Cancel |
| 117 | + </button> |
74 | 118 | } |
75 | 119 | } |
76 | 120 | </div> |
| 121 | + |
77 | 122 | @if (!string.IsNullOrEmpty(imageString)) |
78 | 123 | { |
79 | | - <img src="data:image;base64, @imageString" /> |
| 124 | + <div class="qr-container"> |
| 125 | + <img src="data:image;base64, @imageString" alt="QR Code" /> |
| 126 | + </div> |
80 | 127 | } |
81 | | - |
82 | 128 | </EditForm> |
83 | | - |
84 | | - |
85 | | - |
86 | | - </div> |
87 | | - </div> |
88 | | -} else |
89 | | -{ |
90 | | - <div class="container form-signin"> |
91 | | - <h3>Checking user data</h3> |
92 | | - <div class="text-center"> |
93 | | - <img src="https://static.vecteezy.com/system/resources/previews/023/986/534/large_2x/telegram-logo-telegram-logo-transparent-telegram-icon-transparent-free-free-png.png" alt="Telegram logo" style="max-width: -webkit-fill-available;" /> |
94 | | - </div> |
95 | | - </div> |
96 | | -} |
97 | 129 |
|
98 | | - |
99 | | -@* |
100 | | -
|
101 | | -<SurveyPrompt Title="How is Blazor working for you?" /> *@ |
| 130 | + <div class="login-footer"> |
| 131 | + Telegram File Manager |
| 132 | + </div> |
| 133 | + } |
| 134 | + else |
| 135 | + { |
| 136 | + <!-- Loading/Checking state --> |
| 137 | + <div class="loading-container"> |
| 138 | + <div class="loading-spinner"> |
| 139 | + <img class="loading-logo" src="https://upload.wikimedia.org/wikipedia/commons/8/82/Telegram_logo.svg" alt="Telegram" /> |
| 140 | + </div> |
| 141 | + <p class="loading-text"> |
| 142 | + Checking session |
| 143 | + <span class="loading-dots"> |
| 144 | + <span></span> |
| 145 | + <span></span> |
| 146 | + <span></span> |
| 147 | + </span> |
| 148 | + </p> |
| 149 | + <p class="loading-subtext">Please wait while we verify your credentials</p> |
| 150 | + </div> |
| 151 | + } |
| 152 | + </div> |
| 153 | +</div> |
102 | 154 |
|
103 | 155 | @code { |
104 | | - // [SupplyParameterFromForm] |
105 | 156 | public LoginModel? Model { get; set; } = null; |
106 | 157 | private CancellationToken cTokenQr { get; set; } |
107 | 158 | CancellationTokenSource source { get; set; } |
108 | 159 | private string imageString { get; set; } |
| 160 | + private string lastFocusedType = null; |
109 | 161 |
|
110 | 162 | protected override async Task OnInitializedAsync() |
111 | 163 | { |
|
122 | 174 | } |
123 | 175 | } |
124 | 176 |
|
125 | | - |
126 | 177 | protected override async Task OnAfterRenderAsync(bool firstRender) |
127 | 178 | { |
128 | | - if (Model.type != null && Model.type == "phone") |
129 | | - await JSRuntime.InvokeVoidAsync("loadCountries"); |
| 179 | + if (Model?.type != null && Model.type != "ok" && Model.type != lastFocusedType) |
| 180 | + { |
| 181 | + lastFocusedType = Model.type; |
| 182 | + |
| 183 | + if (Model.type == "phone") |
| 184 | + { |
| 185 | + await JSRuntime.InvokeVoidAsync("loadCountries"); |
| 186 | + await Task.Delay(100); // Small delay to ensure intl-tel-input is initialized |
| 187 | + } |
130 | 188 |
|
| 189 | + await FocusCurrentInput(); |
| 190 | + } |
131 | 191 | } |
132 | 192 |
|
| 193 | + private async Task FocusCurrentInput() |
| 194 | + { |
| 195 | + try |
| 196 | + { |
| 197 | + string elementId = Model?.type switch |
| 198 | + { |
| 199 | + "phone" => "phone", |
| 200 | + "vc" => "code", |
| 201 | + "pass" => "pass", |
| 202 | + _ => null |
| 203 | + }; |
133 | 204 |
|
| 205 | + if (elementId != null) |
| 206 | + { |
| 207 | + await JSRuntime.InvokeVoidAsync("focusElement", elementId); |
| 208 | + } |
| 209 | + } |
| 210 | + catch |
| 211 | + { |
| 212 | + // Element might not be rendered yet |
| 213 | + } |
| 214 | + } |
134 | 215 |
|
135 | 216 | private async void Submit() |
136 | 217 | { |
137 | 218 | if (Model.type == "phone") |
138 | 219 | { |
139 | 220 | var phone = await JSRuntime.InvokeAsync<string>("getNumber"); |
140 | 221 | Model.type = await ts.checkAuth(phone, Model.type == "phone") ?? "phone"; |
141 | | - } else |
| 222 | + } |
| 223 | + else |
142 | 224 | { |
143 | 225 | Model.type = await ts.checkAuth(Model.value, Model.type == "phone"); |
144 | 226 | } |
145 | 227 |
|
146 | 228 | Model.value = ""; |
147 | 229 | StateHasChanged(); |
148 | 230 | await isLogin(); |
149 | | - // Logger.LogInformation("Id = {Id}", Model?.Id); |
150 | 231 | } |
151 | 232 |
|
152 | 233 | private async Task isLogin() |
153 | 234 | { |
154 | | - if (Model.type == "ok") |
| 235 | + if (Model.type == "ok") |
155 | 236 | { |
156 | 237 | if (source != null) |
157 | 238 | { |
|
161 | 242 | } |
162 | 243 | } |
163 | 244 | NavManager.NavigateTo("/fetchdata"); |
164 | | - } else |
165 | | - { |
166 | | - // source = new CancellationTokenSource(); |
167 | | - // cTokenQr = source.Token; |
168 | | - // var user = await ts.CallQrGenerator(GenerateQR, cTokenQr, true); |
169 | | - // if (user != null) |
170 | | - // { |
171 | | - // NavManager.NavigateTo("/fetchdata"); |
172 | | - // } |
173 | 245 | } |
174 | 246 | } |
175 | 247 |
|
|
191 | 263 | Model.value = ""; |
192 | 264 | Model.type = "phone"; |
193 | 265 | StateHasChanged(); |
194 | | - // Logger.LogInformation("Id = {Id}", Model?.Id); |
195 | 266 | } |
196 | 267 |
|
197 | 268 | private async void Cancel() |
|
0 commit comments