diff --git a/src/FrontOffice.Main/Pages/Auth/Phone.razor b/src/FrontOffice.Main/Pages/Auth/Phone.razor
index 375b176..a6eeef5 100644
--- a/src/FrontOffice.Main/Pages/Auth/Phone.razor
+++ b/src/FrontOffice.Main/Pages/Auth/Phone.razor
@@ -6,7 +6,7 @@
ورود به حساب کاربری
- لطفاً شماره موبایل خود را وارد کنید تا رمز پویا برایتان ارسال شود.
+ لطفاً شماره موبایل خود را وارد کنید تا رمز پویا ارسال شود.
- @if (_remainingAttempts.HasValue)
- {
-
- تعداد تلاشهای باقیمانده: @_remainingAttempts
-
- }
-
- @if (_cooldownSeconds > 0)
- {
-
- امکان درخواست مجدد تا @_cooldownSeconds ثانیه دیگر فعال میشود.
-
- }
-
- @if (!string.IsNullOrWhiteSpace(_infoMessage))
- {
- @_infoMessage
- }
-
@if (!string.IsNullOrWhiteSpace(_errorMessage))
{
@_errorMessage
@@ -52,8 +32,7 @@
ارسال رمز پویا
@@ -66,4 +45,4 @@
-
+
\ No newline at end of file
diff --git a/src/FrontOffice.Main/Pages/Auth/Phone.razor.cs b/src/FrontOffice.Main/Pages/Auth/Phone.razor.cs
index eef8291..68b9584 100644
--- a/src/FrontOffice.Main/Pages/Auth/Phone.razor.cs
+++ b/src/FrontOffice.Main/Pages/Auth/Phone.razor.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Blazored.LocalStorage;
+using Blazored.LocalStorage;
using FrontOffice.BFF.User.Protobuf.Protos.User;
using FrontOffice.BFF.User.Protobuf.Validator;
using FrontOffice.Main.Utilities;
@@ -20,18 +16,13 @@ public partial class Phone : IDisposable
private const string TokenStorageKey = "auth:token";
private const string OtpPurpose = "Login";
- private readonly CreateNewOtpTokenRequestValidator _requestValidator = new();
- private readonly CreateNewOtpTokenRequest _request = new();
-
+ private CreateNewOtpTokenRequestValidator _requestValidator = new();
+ private CreateNewOtpTokenRequest _request = new();
private MudForm? _form;
+
private bool _isBusy;
- private bool _acceptTerms;
private string? _errorMessage;
- private string? _infoMessage;
private string? _redirect;
- private int? _remainingAttempts;
- private int _cooldownSeconds;
- private Timer? _cooldownTimer;
private CancellationTokenSource? _sendCts;
[Inject] private ILocalStorageService LocalStorage { get; set; } = default!;
@@ -66,18 +57,12 @@ public partial class Phone : IDisposable
private async Task SendOtpAsync()
{
_errorMessage = null;
- _infoMessage = null;
-
if (_form is null)
- {
return;
- }
-
+
await _form.Validate();
if (!_form.IsValid)
- {
- return;
- }
+ return;
_isBusy = true;
_sendCts?.Cancel();
@@ -94,21 +79,24 @@ public partial class Phone : IDisposable
}
var metadata = await BuildAuthMetadataAsync();
- var response = metadata is not null
- ? await UserClient.CreateNewOtpTokenAsync(_request, metadata, cancellationToken: _sendCts.Token)
- : await UserClient.CreateNewOtpTokenAsync(_request, cancellationToken: _sendCts.Token);
+ CreateNewOtpTokenResponse response;
+ if (metadata is not null)
+ {
+ response = await UserClient.CreateNewOtpTokenAsync(_request, metadata, cancellationToken: _sendCts.Token);
+ }
+ else
+ {
+ response = await UserClient.CreateNewOtpTokenAsync(_request, cancellationToken: _sendCts.Token);
+ }
if (response?.Success != true)
{
- _errorMessage = !string.IsNullOrWhiteSpace(response?.Message)
- ? response!.Message
- : "ارسال رمز پویا با خطا روبهرو شد. لطفاً دوباره تلاش کنید.";
- ApplyServerState(response, false);
+ _errorMessage = string.IsNullOrWhiteSpace(response?.Message)
+ ? "ارسال رمز پویا با خطا مواجه شد. لطفاً دوباره تلاش کنید."
+ : response!.Message;
return;
}
- ApplyServerState(response, true);
-
await LocalStorage.SetItemAsync(PhoneStorageKey, _request.Mobile);
if (!string.IsNullOrWhiteSpace(_redirect))
{
@@ -131,7 +119,7 @@ public partial class Phone : IDisposable
{
_errorMessage = !string.IsNullOrWhiteSpace(rpcEx.Status.Detail)
? rpcEx.Status.Detail
- : "ارسال رمز پویا با خطا روبهرو شد. لطفاً دوباره تلاش کنید.";
+ : "ارسال رمز پویا با خطا مواجه شد. لطفاً دوباره تلاش کنید.";
}
catch (OperationCanceledException)
{
@@ -149,54 +137,6 @@ public partial class Phone : IDisposable
}
}
- private void ApplyServerState(CreateNewOtpTokenResponse? response, bool isSuccess)
- {
- if (response is null)
- {
- return;
- }
-
- _infoMessage = isSuccess
- ? (string.IsNullOrWhiteSpace(response.Message) ? "رمز پویا ارسال شد." : response.Message)
- : null;
-
- if (response.RemainingAttempts >= 0)
- {
- _remainingAttempts = response.RemainingAttempts;
- }
-
- if (response.RemainingSeconds > 0)
- {
- StartCooldown(response.RemainingSeconds);
- }
- else if (isSuccess)
- {
- StopCooldown();
- }
- }
-
- private void StartCooldown(int seconds)
- {
- StopCooldown();
- _cooldownSeconds = seconds;
- _cooldownTimer = new Timer(_ =>
- {
- var remaining = Interlocked.Decrement(ref _cooldownSeconds);
- if (remaining <= 0)
- {
- StopCooldown();
- }
- _ = InvokeAsync(StateHasChanged);
- }, null, 1000, 1000);
- }
-
- private void StopCooldown()
- {
- _cooldownTimer?.Dispose();
- _cooldownTimer = null;
- _cooldownSeconds = 0;
- }
-
private async Task BuildAuthMetadataAsync()
{
var token = await LocalStorage.GetItemAsync(TokenStorageKey);
@@ -215,6 +155,6 @@ public partial class Phone : IDisposable
{
_sendCts?.Cancel();
_sendCts?.Dispose();
- StopCooldown();
+ _sendCts = null;
}
}
diff --git a/src/FrontOffice.Main/Pages/Auth/Verify.razor b/src/FrontOffice.Main/Pages/Auth/Verify.razor
index 05ba045..5ecccb9 100644
--- a/src/FrontOffice.Main/Pages/Auth/Verify.razor
+++ b/src/FrontOffice.Main/Pages/Auth/Verify.razor
@@ -5,14 +5,14 @@
- کد ارسالشده را وارد کنید
+ تأیید رمز پویا
@if (!string.IsNullOrWhiteSpace(_phoneNumber))
{
- کد ارسالشده به @_phoneNumber را وارد نمایید.
+ کد ارسالشده به @_phoneNumber را وارد کنید.
}
else
{
- کد پیامک شده را وارد نمایید.
+ کد ارسالشده را وارد کنید.
}
@@ -24,19 +24,17 @@
Immediate="true"
Required="true"
RequiredError="وارد کردن رمز پویا الزامی است."
- HelperText="کد ۵ یا ۶ رقمی"
+ HelperText="کد ۶ رقمی"
Class="mb-2"
MaxLength="6" />
-
- تلاشهای باقیمانده: @_attemptsLeft
+
+ تلاش باقیمانده: @_attemptsLeft از @MaxVerificationAttempts
- @if (_resendSeconds > 0)
+ @if (!string.IsNullOrWhiteSpace(_errorMessage))
{
-
- امکان ارسال مجدد تا @_resendSeconds ثانیه دیگر فعال میشود.
-
+ @_errorMessage
}
@if (!string.IsNullOrWhiteSpace(_infoMessage))
@@ -44,19 +42,13 @@
@_infoMessage
}
- @if (!string.IsNullOrWhiteSpace(_errorMessage))
- {
- @_errorMessage
- }
-
- تأیید و ادامه
+ تأیید و ورود
-
- ارسال دوباره رمز
-
+ @if (_resendRemaining > 0)
+ {
+
+ امکان ارسال مجدد تا @_resendRemaining ثانیه دیگر
+
+ }
+ else
+ {
+
+ ارسال مجدد رمز پویا
+
+ }
+
+
+
diff --git a/src/FrontOffice.Main/Pages/Auth/Verify.razor.cs b/src/FrontOffice.Main/Pages/Auth/Verify.razor.cs
index b3089ab..972542b 100644
--- a/src/FrontOffice.Main/Pages/Auth/Verify.razor.cs
+++ b/src/FrontOffice.Main/Pages/Auth/Verify.razor.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Blazored.LocalStorage;
+using Blazored.LocalStorage;
using FrontOffice.BFF.User.Protobuf.Protos.User;
using FrontOffice.BFF.User.Protobuf.Validator;
using FrontOffice.Main.Utilities;
@@ -22,17 +18,17 @@ public partial class Verify : IDisposable
private const string RedirectStorageKey = "auth:redirect";
private const string TokenStorageKey = "auth:token";
- private readonly VerifyOtpTokenRequestValidator _requestValidator = new();
- private readonly VerifyOtpTokenRequest _request = new();
-
+ private VerifyOtpTokenRequestValidator _requestValidator = new();
+ private VerifyOtpTokenRequest _request = new();
private MudForm? _form;
+
private bool _isBusy;
private string? _phoneNumber;
private string? _redirect;
private string? _errorMessage;
private string? _infoMessage;
private Timer? _resendTimer;
- private int _resendSeconds;
+ private int _resendRemaining;
private int _attemptsLeft = MaxVerificationAttempts;
private CancellationTokenSource? _operationCts;
@@ -79,7 +75,7 @@ public partial class Verify : IDisposable
}
}
- ResetResendCountdown();
+ StartResendCountdown();
}
private async Task VerifyOtpAsync()
@@ -88,19 +84,15 @@ public partial class Verify : IDisposable
_infoMessage = null;
if (_form is null)
- {
- return;
- }
+ return;
await _form.Validate();
if (!_form.IsValid)
- {
return;
- }
if (IsVerificationLocked)
{
- _errorMessage = "تعداد تلاشهای مجاز شما به پایان رسیده است. لطفاً رمز جدید دریافت کنید.";
+ _errorMessage = "تعداد تلاشهای مجاز به پایان رسیده است. لطفاً رمز جدید دریافت کنید.";
return;
}
@@ -125,9 +117,15 @@ public partial class Verify : IDisposable
}
var metadata = await BuildAuthMetadataAsync();
- var response = metadata is not null
- ? await UserClient.VerifyOtpTokenAsync(_request, metadata, cancellationToken: cancellationToken)
- : await UserClient.VerifyOtpTokenAsync(_request, cancellationToken: cancellationToken);
+ VerifyOtpTokenResponse response;
+ if (metadata is not null)
+ {
+ response = await UserClient.VerifyOtpTokenAsync(_request, metadata, cancellationToken: cancellationToken);
+ }
+ else
+ {
+ response = await UserClient.VerifyOtpTokenAsync(_request, cancellationToken: cancellationToken);
+ }
if (response is null)
{
@@ -135,21 +133,36 @@ public partial class Verify : IDisposable
return;
}
- ApplyVerificationState(response);
-
if (response.Success)
{
- await PersistTokenAsync(response.Token);
+ if (!string.IsNullOrWhiteSpace(response.Token))
+ {
+ await LocalStorage.SetItemAsync(TokenStorageKey, response.Token);
+ }
+ else
+ {
+ await LocalStorage.RemoveItemAsync(TokenStorageKey);
+ }
+
await LocalStorage.RemoveItemAsync(PhoneStorageKey);
await LocalStorage.RemoveItemAsync(RedirectStorageKey);
+ _attemptsLeft = MaxVerificationAttempts;
+ _request.Code = string.Empty;
+
var target = !string.IsNullOrWhiteSpace(_redirect) ? _redirect : RouteConstants.Main.MainPage;
- Navigation.NavigateTo(target ?? RouteConstants.Main.MainPage, forceLoad: true);
+ if (!string.IsNullOrWhiteSpace(target))
+ {
+ Navigation.NavigateTo(target, forceLoad: true);
+ }
+ return;
}
+
+ RegisterFailedAttempt(string.IsNullOrWhiteSpace(response.Message) ? "کد نادرست است." : response.Message);
}
catch (RpcException rpcEx)
{
- await HandleVerifyRpcFailureAsync(rpcEx);
+ await HandleVerificationFailureAsync(rpcEx);
}
catch (OperationCanceledException)
{
@@ -166,58 +179,19 @@ public partial class Verify : IDisposable
}
}
- private void ApplyVerificationState(VerifyOtpTokenResponse response)
- {
- if (response.RemainingSeconds > 0)
- {
- StartResendCountdown(response.RemainingSeconds);
- }
- else
- {
- ResetResendCountdown();
- }
-
- if (response.RemainingAttempts >= 0)
- {
- _attemptsLeft = response.RemainingAttempts;
- }
-
- if (response.Success)
- {
- _infoMessage = string.IsNullOrWhiteSpace(response.Message)
- ? "ورود با موفقیت انجام شد."
- : response.Message;
- }
- else
- {
- var baseMessage = string.IsNullOrWhiteSpace(response.Message)
- ? "کد وارد شده صحیح نیست."
- : response.Message;
-
- _errorMessage = _attemptsLeft > 0
- ? $"{baseMessage} {_attemptsLeft} تلاش باقیمانده است."
- : $"{baseMessage} تعداد تلاشهای مجاز شما پایان یافته است. لطفاً رمز جدید دریافت کنید.";
- }
- }
-
- private async Task HandleVerifyRpcFailureAsync(RpcException rpcEx)
+ private async Task HandleVerificationFailureAsync(RpcException rpcEx)
{
switch (rpcEx.Status.StatusCode)
{
case StatusCode.PermissionDenied:
case StatusCode.InvalidArgument:
- ApplyVerificationState(new VerifyOtpTokenResponse
- {
- Success = false,
- Message = string.IsNullOrWhiteSpace(rpcEx.Status.Detail) ? "کد وارد شده صحیح نیست." : rpcEx.Status.Detail,
- RemainingAttempts = _attemptsLeft - 1
- });
+ RegisterFailedAttempt(!string.IsNullOrWhiteSpace(rpcEx.Status.Detail)
+ ? rpcEx.Status.Detail
+ : "کد نادرست است.");
break;
case StatusCode.Unauthenticated:
await ResetAuthenticationAsync();
- _errorMessage = string.IsNullOrWhiteSpace(rpcEx.Status.Detail)
- ? "نشست کاربری منقضی شده است. لطفاً دوباره وارد شوید."
- : rpcEx.Status.Detail;
+ _errorMessage = "نشست کاربری منقضی شده است. لطفاً دوباره وارد شوید.";
Navigation.NavigateTo(RouteConstants.Auth.Phone, forceLoad: true);
break;
case StatusCode.DeadlineExceeded:
@@ -232,9 +206,23 @@ public partial class Verify : IDisposable
}
}
+ private void RegisterFailedAttempt(string baseMessage)
+ {
+ _attemptsLeft = Math.Max(0, _attemptsLeft - 1);
+
+ if (_attemptsLeft > 0)
+ {
+ _errorMessage = $"{baseMessage} {_attemptsLeft} تلاش باقی مانده است.";
+ }
+ else
+ {
+ _errorMessage = $"{baseMessage} تلاشهای مجاز شما به پایان رسیده است. لطفاً رمز جدید دریافت کنید.";
+ }
+ }
+
private async Task ResendOtpAsync()
{
- if (_resendSeconds > 0 || _isBusy || string.IsNullOrWhiteSpace(_phoneNumber))
+ if (_resendRemaining > 0 || _isBusy || string.IsNullOrWhiteSpace(_phoneNumber))
{
return;
}
@@ -253,28 +241,35 @@ public partial class Verify : IDisposable
};
var metadata = await BuildAuthMetadataAsync();
- var response = metadata is not null
- ? await UserClient.CreateNewOtpTokenAsync(request, metadata, cancellationToken: cancellationToken)
- : await UserClient.CreateNewOtpTokenAsync(request, cancellationToken: cancellationToken);
-
- if (response?.Success != true)
+ CreateNewOtpTokenResponse response;
+ if (metadata is not null)
{
- _errorMessage = !string.IsNullOrWhiteSpace(response?.Message)
- ? response!.Message
- : "ارسال مجدد رمز با خطا مواجه شد.";
+ response = await UserClient.CreateNewOtpTokenAsync(request, metadata, cancellationToken: cancellationToken);
}
else
{
- _infoMessage = string.IsNullOrWhiteSpace(response.Message)
- ? "رمز جدید ارسال شد."
- : response.Message;
+ response = await UserClient.CreateNewOtpTokenAsync(request, cancellationToken: cancellationToken);
}
- ApplyResendState(response);
+ if (response?.Success != true)
+ {
+ _errorMessage = string.IsNullOrWhiteSpace(response?.Message)
+ ? "ارسال مجدد رمز پویا با خطا مواجه شد."
+ : response!.Message;
+ return;
+ }
+
+ _infoMessage = string.IsNullOrWhiteSpace(response.Message)
+ ? "کد جدید ارسال شد."
+ : response.Message;
+
+ _attemptsLeft = MaxVerificationAttempts;
+ _request.Code = string.Empty;
+ StartResendCountdown();
}
catch (RpcException rpcEx)
{
- await HandleResendRpcFailureAsync(rpcEx);
+ await HandleResendFailureAsync(rpcEx);
}
catch (OperationCanceledException)
{
@@ -291,52 +286,50 @@ public partial class Verify : IDisposable
}
}
- private async Task HandleResendRpcFailureAsync(RpcException rpcEx)
+ private async Task HandleResendFailureAsync(RpcException rpcEx)
{
switch (rpcEx.Status.StatusCode)
{
case StatusCode.Unauthenticated:
await ResetAuthenticationAsync();
- _errorMessage = string.IsNullOrWhiteSpace(rpcEx.Status.Detail)
- ? "نشست کاربری منقضی شده است. لطفاً دوباره وارد شوید."
- : rpcEx.Status.Detail;
+ _errorMessage = "نشست کاربری منقضی شده است. لطفاً دوباره وارد شوید.";
Navigation.NavigateTo(RouteConstants.Auth.Phone, forceLoad: true);
break;
default:
_errorMessage = !string.IsNullOrWhiteSpace(rpcEx.Status.Detail)
? rpcEx.Status.Detail
- : "ارسال مجدد رمز با خطا مواجه شد.";
+ : "ارسال مجدد رمز پویا با خطا مواجه شد.";
break;
}
}
- private void ApplyResendState(CreateNewOtpTokenResponse? response)
+ private async Task ChangePhoneAsync()
{
- if (response?.RemainingAttempts >= 0)
- {
- _attemptsLeft = response.RemainingAttempts;
- }
-
- if (response?.RemainingSeconds > 0)
- {
- StartResendCountdown(response.RemainingSeconds);
- }
- else
- {
- StartResendCountdown(DefaultResendCooldown);
- }
+ await LocalStorage.RemoveItemAsync(PhoneStorageKey);
+ NavigateBackToPhone();
}
- private void StartResendCountdown(int seconds)
+ private void NavigateBackToPhone()
{
- _resendSeconds = seconds;
+ var target = RouteConstants.Auth.Phone;
+ if (!string.IsNullOrWhiteSpace(_redirect))
+ {
+ target += "?redirect=" + Uri.EscapeDataString(_redirect);
+ }
+
+ Navigation.NavigateTo(target, forceLoad: false);
+ }
+
+ private void StartResendCountdown(int seconds = DefaultResendCooldown)
+ {
+ _resendRemaining = seconds;
_resendTimer?.Dispose();
_resendTimer = new Timer(_ =>
{
- var remaining = Interlocked.Decrement(ref _resendSeconds);
+ var remaining = Interlocked.Add(ref _resendRemaining, -1);
if (remaining <= 0)
{
- Interlocked.Exchange(ref _resendSeconds, 0);
+ Interlocked.Exchange(ref _resendRemaining, 0);
_resendTimer?.Dispose();
_resendTimer = null;
}
@@ -345,13 +338,6 @@ public partial class Verify : IDisposable
}, null, 1000, 1000);
}
- private void ResetResendCountdown()
- {
- _resendTimer?.Dispose();
- _resendTimer = null;
- _resendSeconds = 0;
- }
-
private async Task BuildAuthMetadataAsync()
{
var token = await LocalStorage.GetItemAsync(TokenStorageKey);
@@ -366,24 +352,6 @@ public partial class Verify : IDisposable
};
}
- private async Task PersistTokenAsync(string? token)
- {
- if (!string.IsNullOrWhiteSpace(token))
- {
- await LocalStorage.SetItemAsync(TokenStorageKey, token);
- }
- else
- {
- await LocalStorage.RemoveItemAsync(TokenStorageKey);
- }
- }
-
- private async Task ChangePhoneAsync()
- {
- await LocalStorage.RemoveItemAsync(PhoneStorageKey);
- NavigateBackToPhone();
- }
-
private async Task ResetAuthenticationAsync()
{
await LocalStorage.RemoveItemAsync(TokenStorageKey);
@@ -391,17 +359,6 @@ public partial class Verify : IDisposable
await LocalStorage.RemoveItemAsync(RedirectStorageKey);
}
- private void NavigateBackToPhone()
- {
- var target = RouteConstants.Auth.Phone;
- if (!string.IsNullOrWhiteSpace(_redirect))
- {
- target += "?redirect=" + Uri.EscapeDataString(_redirect);
- }
-
- Navigation.NavigateTo(target, forceLoad: false);
- }
-
private CancellationToken PrepareOperationToken()
{
_operationCts?.Cancel();
@@ -420,6 +377,9 @@ public partial class Verify : IDisposable
{
_operationCts?.Cancel();
_operationCts?.Dispose();
+ _operationCts = null;
+
_resendTimer?.Dispose();
+ _resendTimer = null;
}
}