u
This commit is contained in:
@@ -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<string>(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
<PageTitle>ورود | تأیید شماره موبایل</PageTitle>
|
||||
|
||||
<MudPaper Class="d-flex flex-column align-center justify-center min-vh-100 pa-4">
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudCard Class="pa-6" Style="max-width:420px;width:100%;">
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h5" Class="mb-1" Align="Align.Center">ورود به حساب کاربری</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-4" Align="Align.Center">لطفاً شماره موبایل خود را وارد کنید تا رمز پویا ارسال شود.</MudText>
|
||||
|
||||
<MudForm @ref="_form" Model="_model">
|
||||
<MudTextField @bind-Value="_model.PhoneNumber"
|
||||
For="@(() => _model.PhoneNumber)"
|
||||
<MudForm @ref="_form" Model="_request" Validation="@(_requestValidator.ValidateValue)">
|
||||
<MudTextField @bind-Value="_request.Mobile"
|
||||
For="@(() => _request.Mobile)"
|
||||
Label="شماره موبایل"
|
||||
InputType="InputType.Text"
|
||||
Variant="Variant.Outlined"
|
||||
@@ -21,10 +21,8 @@
|
||||
Class="mb-2" />
|
||||
|
||||
<MudCheckBox T="bool"
|
||||
@bind-Value="_model.AcceptTerms"
|
||||
For="@(() => _model.AcceptTerms)"
|
||||
Label="شرایط و قوانین را میپذیرم"
|
||||
Class="mb-2" />
|
||||
Label="شرایط و قوانین را میپذیرم"
|
||||
Class="mb-2" />
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_errorMessage))
|
||||
{
|
||||
@@ -42,14 +40,9 @@
|
||||
</MudForm>
|
||||
</MudCardContent>
|
||||
<MudCardActions Class="justify-center">
|
||||
<MudText Typo="Typo.caption" Align="Align.Center" Class="px-2">
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="px-2">
|
||||
با ورود، شرایط استفاده و سیاست حفظ حریم خصوصی را میپذیرید.
|
||||
</MudText>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudPaper>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</MudStack>
|
||||
@@ -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<string>(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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PageTitle>تأیید رمز پویا</PageTitle>
|
||||
|
||||
<MudPaper Class="d-flex flex-column align-center justify-center min-vh-100 pa-4">
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudCard Class="pa-6" Style="max-width:420px;width:100%;">
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h5" Class="mb-1" Align="Align.Center">تأیید رمز پویا</MudText>
|
||||
@@ -15,20 +15,20 @@
|
||||
<MudText Typo="Typo.body2" Class="mb-4" Align="Align.Center">کد ارسالشده را وارد کنید.</MudText>
|
||||
}
|
||||
|
||||
<MudForm @ref="_form" Model="_model">
|
||||
<MudTextField @bind-Value="_model.Code"
|
||||
For="@(() => _model.Code)"
|
||||
<MudForm @ref="_form" Model="_request" Validation="@(_requestValidator.ValidateValue)">
|
||||
<MudTextField @bind-Value="_request.Code"
|
||||
For="@(() => _request.Code)"
|
||||
Label="رمز پویا"
|
||||
InputType="InputType.Text"
|
||||
Variant="Variant.Outlined"
|
||||
Immediate="true"
|
||||
Required="true"
|
||||
RequiredError="وارد کردن رمز پویا الزامی است."
|
||||
HelperText="کد ۵ یا ۶ رقمی"
|
||||
HelperText="کد ۶ رقمی"
|
||||
Class="mb-2"
|
||||
MaxLength="6" />
|
||||
|
||||
<MudText Typo="Typo.caption" Align="Align.Center" Class="mb-2">
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mb-2">
|
||||
تلاش باقیمانده: @_attemptsLeft از @MaxVerificationAttempts
|
||||
</MudText>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
@if (_resendRemaining > 0)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Align="Align.Center">
|
||||
<MudText Typo="Typo.body1" Align="Align.Center">
|
||||
امکان ارسال مجدد تا @_resendRemaining ثانیه دیگر
|
||||
</MudText>
|
||||
}
|
||||
@@ -78,8 +78,7 @@
|
||||
</MudForm>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudPaper>
|
||||
|
||||
</MudStack>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AppSettings>();
|
||||
UrlUtility.DownloadUrl = appSettings.DownloadUrl;
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user