From 2352b7ca1753cdd54ca4aae75778ff7fbcbf3d9c Mon Sep 17 00:00:00 2001 From: MeysamMoghaddam <65253484+MeysamMoghaddam@users.noreply.github.com> Date: Sun, 28 Sep 2025 05:36:45 +0330 Subject: [PATCH] u --- src/FrontOffice.Main/App.razor.cs | 37 +++++++++------ src/FrontOffice.Main/Pages/Auth/Phone.razor | 23 ++++----- .../Pages/Auth/Phone.razor.cs | 46 +++++------------- src/FrontOffice.Main/Pages/Auth/Verify.razor | 17 ++++--- .../Pages/Auth/Verify.razor.cs | 41 +++++----------- src/FrontOffice.Main/Program.cs | 5 ++ .../CustomFluentValidationLanguageManager.cs | 47 +++++++++++++++++++ 7 files changed, 116 insertions(+), 100 deletions(-) create mode 100644 src/FrontOffice.Main/Utilities/CustomFluentValidationLanguageManager.cs diff --git a/src/FrontOffice.Main/App.razor.cs b/src/FrontOffice.Main/App.razor.cs index 50432ad..583afbd 100644 --- a/src/FrontOffice.Main/App.razor.cs +++ b/src/FrontOffice.Main/App.razor.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using Blazored.LocalStorage; +using Blazored.LocalStorage; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; @@ -15,22 +13,23 @@ public partial class App private async Task HandleNavigationAsync(NavigationContext context) { - var relativePath = Navigation.ToBaseRelativePath(context.Path); - if (IsAuthPath(relativePath)) + var normalizedPath = NormalizePath(context.Path); + if (IsAuthPath(normalizedPath)) { return; } var token = await LocalStorage.GetItemAsync(TokenStorageKey); - if (string.IsNullOrWhiteSpace(token)) + if (!string.IsNullOrWhiteSpace(token)) { - var normalized = NormalizePath(relativePath); - var redirectQuery = string.IsNullOrEmpty(normalized) || normalized == "/" - ? string.Empty - : $"?redirect={Uri.EscapeDataString(normalized)}"; - - Navigation.NavigateTo(RouteConstants.Auth.Phone + redirectQuery, forceLoad: true); + return; } + + var redirect = string.IsNullOrEmpty(normalizedPath) || normalizedPath == "/" + ? string.Empty + : $"?redirect={Uri.EscapeDataString(normalizedPath)}"; + + Navigation.NavigateTo(RouteConstants.Auth.Phone + redirect, forceLoad: true); } private static bool IsAuthPath(string? path) @@ -40,6 +39,11 @@ public partial class App return false; } + if (Uri.TryCreate(path, UriKind.Absolute, out var absolute)) + { + path = absolute.PathAndQuery; + } + path = path.TrimStart('/'); return path.StartsWith("auth", StringComparison.OrdinalIgnoreCase); } @@ -51,7 +55,12 @@ public partial class App return string.Empty; } - var normalized = path.StartsWith('/') ? path : "/" + path; - return normalized; + if (Uri.TryCreate(path, UriKind.Absolute, out var absolute)) + { + path = absolute.PathAndQuery; + } + + return path.StartsWith('/') ? path : "/" + path; } } + diff --git a/src/FrontOffice.Main/Pages/Auth/Phone.razor b/src/FrontOffice.Main/Pages/Auth/Phone.razor index 24eeca7..a6eeef5 100644 --- a/src/FrontOffice.Main/Pages/Auth/Phone.razor +++ b/src/FrontOffice.Main/Pages/Auth/Phone.razor @@ -2,15 +2,15 @@ ورود | تأیید شماره موبایل - + ورود به حساب کاربری لطفاً شماره موبایل خود را وارد کنید تا رمز پویا ارسال شود. - - + + Label="شرایط و قوانین را می‌پذیرم" + Class="mb-2" /> @if (!string.IsNullOrWhiteSpace(_errorMessage)) { @@ -42,14 +40,9 @@ - + با ورود، شرایط استفاده و سیاست حفظ حریم خصوصی را می‌پذیرید. - - - - - - + \ 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 9f4f4fa..68b9584 100644 --- a/src/FrontOffice.Main/Pages/Auth/Phone.razor.cs +++ b/src/FrontOffice.Main/Pages/Auth/Phone.razor.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using Blazored.LocalStorage; +using Blazored.LocalStorage; using FrontOffice.BFF.User.Protobuf.Protos.User; using FrontOffice.BFF.User.Protobuf.Validator; using FrontOffice.Main.Utilities; @@ -17,10 +16,10 @@ public partial class Phone : IDisposable private const string TokenStorageKey = "auth:token"; private const string OtpPurpose = "Login"; - private static readonly CreateNewOtpTokenRequestValidator RequestValidator = new(); - - private readonly PhoneInputModel _model = new(); + private CreateNewOtpTokenRequestValidator _requestValidator = new(); + private CreateNewOtpTokenRequest _request = new(); private MudForm? _form; + private bool _isBusy; private string? _errorMessage; private string? _redirect; @@ -31,6 +30,7 @@ public partial class Phone : IDisposable protected override async Task OnInitializedAsync() { + _request.Purpose = OtpPurpose; var uri = Navigation.ToAbsoluteUri(Navigation.Uri); var query = QueryHelpers.ParseQuery(uri.Query); if (query.TryGetValue("redirect", out var redirectValues)) @@ -41,7 +41,7 @@ public partial class Phone : IDisposable var storedPhone = await LocalStorage.GetItemAsync(PhoneStorageKey); if (!string.IsNullOrWhiteSpace(storedPhone)) { - _model.PhoneNumber = storedPhone; + _request.Mobile = storedPhone; } if (string.IsNullOrWhiteSpace(_redirect)) @@ -58,15 +58,11 @@ public partial class Phone : IDisposable { _errorMessage = null; if (_form is null) - { return; - } - + await _form.Validate(); if (!_form.IsValid) - { - return; - } + return; _isBusy = true; _sendCts?.Cancel(); @@ -75,13 +71,7 @@ public partial class Phone : IDisposable try { - var request = new CreateNewOtpTokenRequest - { - Mobile = _model.PhoneNumber, - Purpose = OtpPurpose - }; - - var validationResult = RequestValidator.Validate(request); + var validationResult = _requestValidator.Validate(_request); if (!validationResult.IsValid) { _errorMessage = string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct()); @@ -92,11 +82,11 @@ public partial class Phone : IDisposable CreateNewOtpTokenResponse response; if (metadata is not null) { - response = await UserClient.CreateNewOtpTokenAsync(request, metadata, cancellationToken: _sendCts.Token); + response = await UserClient.CreateNewOtpTokenAsync(_request, metadata, cancellationToken: _sendCts.Token); } else { - response = await UserClient.CreateNewOtpTokenAsync(request, cancellationToken: _sendCts.Token); + response = await UserClient.CreateNewOtpTokenAsync(_request, cancellationToken: _sendCts.Token); } if (response?.Success != true) @@ -107,7 +97,7 @@ public partial class Phone : IDisposable return; } - await LocalStorage.SetItemAsync(PhoneStorageKey, _model.PhoneNumber); + await LocalStorage.SetItemAsync(PhoneStorageKey, _request.Mobile); if (!string.IsNullOrWhiteSpace(_redirect)) { await LocalStorage.SetItemAsync(RedirectStorageKey, _redirect); @@ -117,7 +107,7 @@ public partial class Phone : IDisposable await LocalStorage.RemoveItemAsync(RedirectStorageKey); } - var target = $"{RouteConstants.Auth.Verify}?phone={Uri.EscapeDataString(_model.PhoneNumber)}"; + var target = $"{RouteConstants.Auth.Verify}?phone={Uri.EscapeDataString(_request.Mobile)}"; if (!string.IsNullOrEmpty(_redirect)) { target += "&redirect=" + Uri.EscapeDataString(_redirect); @@ -167,14 +157,4 @@ public partial class Phone : IDisposable _sendCts?.Dispose(); _sendCts = null; } - - private sealed class PhoneInputModel - { - [Required(ErrorMessage = "???? ???? ????? ?????? ?????? ???.")] - [RegularExpression(@"^09\\d{9}$", ErrorMessage = "????? ?????? ????? ????.")] - public string PhoneNumber { get; set; } = string.Empty; - - [Range(typeof(bool), "true", "true", ErrorMessage = "????? ????? ? ?????? ????? ???.")] - public bool AcceptTerms { get; set; } - } } diff --git a/src/FrontOffice.Main/Pages/Auth/Verify.razor b/src/FrontOffice.Main/Pages/Auth/Verify.razor index c99b845..5ecccb9 100644 --- a/src/FrontOffice.Main/Pages/Auth/Verify.razor +++ b/src/FrontOffice.Main/Pages/Auth/Verify.razor @@ -2,7 +2,7 @@ تأیید رمز پویا - + تأیید رمز پویا @@ -15,20 +15,20 @@ کد ارسال‌شده را وارد کنید. } - - + - + تلاش باقی‌مانده: @_attemptsLeft از @MaxVerificationAttempts @@ -62,7 +62,7 @@ @if (_resendRemaining > 0) { - + امکان ارسال مجدد تا @_resendRemaining ثانیه دیگر } @@ -78,8 +78,7 @@ - - + diff --git a/src/FrontOffice.Main/Pages/Auth/Verify.razor.cs b/src/FrontOffice.Main/Pages/Auth/Verify.razor.cs index 6a03459..972542b 100644 --- a/src/FrontOffice.Main/Pages/Auth/Verify.razor.cs +++ b/src/FrontOffice.Main/Pages/Auth/Verify.razor.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using Blazored.LocalStorage; +using Blazored.LocalStorage; using FrontOffice.BFF.User.Protobuf.Protos.User; using FrontOffice.BFF.User.Protobuf.Validator; using FrontOffice.Main.Utilities; @@ -19,10 +18,10 @@ public partial class Verify : IDisposable private const string RedirectStorageKey = "auth:redirect"; private const string TokenStorageKey = "auth:token"; - private static readonly VerifyOtpTokenRequestValidator VerifyRequestValidator = new(); - - private readonly OtpInputModel _model = new(); + private VerifyOtpTokenRequestValidator _requestValidator = new(); + private VerifyOtpTokenRequest _request = new(); private MudForm? _form; + private bool _isBusy; private string? _phoneNumber; private string? _redirect; @@ -40,6 +39,7 @@ public partial class Verify : IDisposable protected override async Task OnInitializedAsync() { + _request.Purpose = OtpPurpose; var uri = Navigation.ToAbsoluteUri(Navigation.Uri); var query = QueryHelpers.ParseQuery(uri.Query); if (query.TryGetValue("redirect", out var redirectValues)) @@ -84,15 +84,11 @@ public partial class Verify : IDisposable _infoMessage = null; if (_form is null) - { - return; - } + return; await _form.Validate(); if (!_form.IsValid) - { return; - } if (IsVerificationLocked) { @@ -112,14 +108,8 @@ public partial class Verify : IDisposable try { - var request = new VerifyOtpTokenRequest - { - Mobile = _phoneNumber, - Purpose = OtpPurpose, - Code = _model.Code - }; - - var validationResult = VerifyRequestValidator.Validate(request); + _request.Mobile = _phoneNumber; + var validationResult = _requestValidator.Validate(_request); if (!validationResult.IsValid) { _errorMessage = string.Join(" ", validationResult.Errors.Select(e => e.ErrorMessage).Distinct()); @@ -130,11 +120,11 @@ public partial class Verify : IDisposable VerifyOtpTokenResponse response; if (metadata is not null) { - response = await UserClient.VerifyOtpTokenAsync(request, metadata, cancellationToken: cancellationToken); + response = await UserClient.VerifyOtpTokenAsync(_request, metadata, cancellationToken: cancellationToken); } else { - response = await UserClient.VerifyOtpTokenAsync(request, cancellationToken: cancellationToken); + response = await UserClient.VerifyOtpTokenAsync(_request, cancellationToken: cancellationToken); } if (response is null) @@ -158,7 +148,7 @@ public partial class Verify : IDisposable await LocalStorage.RemoveItemAsync(RedirectStorageKey); _attemptsLeft = MaxVerificationAttempts; - _model.Code = string.Empty; + _request.Code = string.Empty; var target = !string.IsNullOrWhiteSpace(_redirect) ? _redirect : RouteConstants.Main.MainPage; if (!string.IsNullOrWhiteSpace(target)) @@ -274,7 +264,7 @@ public partial class Verify : IDisposable : response.Message; _attemptsLeft = MaxVerificationAttempts; - _model.Code = string.Empty; + _request.Code = string.Empty; StartResendCountdown(); } catch (RpcException rpcEx) @@ -392,11 +382,4 @@ public partial class Verify : IDisposable _resendTimer?.Dispose(); _resendTimer = null; } - - private sealed class OtpInputModel - { - [Required(ErrorMessage = "???? ???? ??? ???? ?????? ???.")] - [RegularExpression(@"^\\d{5,6}$", ErrorMessage = "??? ???? ???? ? ?? ? ??? ????.")] - public string Code { get; set; } = string.Empty; - } } diff --git a/src/FrontOffice.Main/Program.cs b/src/FrontOffice.Main/Program.cs index 36af858..2421db0 100644 --- a/src/FrontOffice.Main/Program.cs +++ b/src/FrontOffice.Main/Program.cs @@ -1,3 +1,4 @@ +using FluentValidation; using FrontOffice.Main.Utilities; var builder = WebApplication.CreateBuilder(args); @@ -16,7 +17,11 @@ builder.Services.AddCommonServices(); builder.Services.AddGrpcServices(builder.Configuration); #endregion +#region FluentValidation +ValidatorOptions.Global.LanguageManager = new CustomFluentValidationLanguageManager(); + +#endregion var appSettings = builder.Configuration.Get(); UrlUtility.DownloadUrl = appSettings.DownloadUrl; diff --git a/src/FrontOffice.Main/Utilities/CustomFluentValidationLanguageManager.cs b/src/FrontOffice.Main/Utilities/CustomFluentValidationLanguageManager.cs new file mode 100644 index 0000000..0af6089 --- /dev/null +++ b/src/FrontOffice.Main/Utilities/CustomFluentValidationLanguageManager.cs @@ -0,0 +1,47 @@ +using System.Globalization; +using FluentValidation.Resources; + +namespace FrontOffice.Main.Utilities; + +public class CustomFluentValidationLanguageManager : LanguageManager +{ + public CustomFluentValidationLanguageManager() + { + + } + + public override string GetString(string key, CultureInfo culture = null) => key switch + { + "EmailValidator" => ".مقدار وارد شده قالب صحیح یک ایمیل را ندارد", + "GreaterThanOrEqualValidator" => "مقدار باید بیشتر یا مساوی '{ComparisonValue}' باشد.", + "GreaterThanValidator" => "مقدار باید بیشتر از '{ComparisonValue}' باشد.", + "LengthValidator" => "مقدار باید حداقل {MinLength} و حداکثر {MaxLength} کاراکتر داشته باشد. اما مقدار وارد شده {TotalLength} کاراکتر دارد.", + "MinimumLengthValidator" => "مقدار باید بزرگتر یا برابر با {MinLength} کاراکتر باشد. شما تعداد {TotalLength} کاراکتر را وارد کردید", + "MaximumLengthValidator" => "مقدار باید کمتر یا مساوی {MaxLength} باشد. {TotalLength} را وارد کردید", + "LessThanOrEqualValidator" => "مقدار باید کمتر یا مساوی '{ComparisonValue}' باشد.", + "LessThanValidator" => "مقدار باید کمتر از '{ComparisonValue}' باشد.", + "NotEmptyValidator" => "وارد کردن این فیلد ضروری است.", + "NotEqualValidator" => "نباید برابر با '{ComparisonValue}' باشد.", + "NotNullValidator" => "وارد کردن این فیلد ضروری است.", + "PredicateValidator" => "شرط تعیین شده برای این فیلد برقرار نیست.", + "AsyncPredicateValidator" => "شرط تعیین شده برای این فیلد برقرار نیست.", + "RegularExpressionValidator" => "مقدار دارای قالب صحیح نیست.", + "EqualValidator" => "مقادیر وارد شده برای و '{ComparisonValue}' یکسان نیستند.", + "ExactLengthValidator" => "مقدار باید دقیقا {MaxLength} کاراکتر باشد اما مقدار وارد شده {TotalLength} کاراکتر دارد.", + "InclusiveBetweenValidator" => "مقدار باید بین {From} و {To} باشد. اما مقدار وارد شده ({PropertyValue}) در این محدوده نیست.", + "ExclusiveBetweenValidator" => "مقدار باید بیشتر از {From} و کمتر از {To} باشد. اما مقدار وارد شده ({PropertyValue}) در این محدوده نیست.", + "CreditCardValidator" => "مقدار وارد شده معتبر نیست.", + "ScalePrecisionValidator" => "مقدار مقدار نباید بیش از {ExpectedPrecision} رقم، شامل {ExpectedScale} رقم اعشار داشته باشد. مقدار وارد شده {Digits} رقم و {ActualScale} رقم اعشار دارد.", + "EmptyValidator" => "مقدار باید خالی باشد.", + "NullValidator" => "مقدار باید خالی باشد.", + "EnumValidator" => "مقدار '{PropertyValue}' در لیست مقادیر قابل قبول برای نمی باشد.", + // Additional fallback messages used by clientside validation integration. + "Length_Simple" => "مقدار باید حداقل {MinLength} و حداکثر {MaxLength} کاراکتر داشته باشد.", + "MinimumLength_Simple" => "مقدار باید بزرگتر یا برابر با {MinLength} کاراکتر باشد.", + "MaximumLength_Simple" => "مقدار باید کمتر یا مساوی {MaxLength} باشد.", + "ExactLength_Simple" => "مقدار باید دقیقا {MaxLength} کاراکتر.", + "InclusiveBetween_Simple" => "مقدار باید بین {From} و {To} باشد.", + _ => null, + }; + +} \ No newline at end of file