Update MudBlazor integration, improve captcha handling, and upgrade project dependencies

This commit is contained in:
masoodafar-web
2025-11-14 09:32:19 +03:30
parent cce59612fa
commit 07ea8f0f47
15 changed files with 456 additions and 395 deletions
+2 -3
View File
@@ -10,7 +10,7 @@
<MudText Typo="Typo.h2" Align="Align.Center" Class="mb-3">
آماده شنیدن صدای شما هستیم
</MudText>
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary mb-6" MaxWidth="600px">
<MudText Typo="Typo.body1" Align="Align.Center" Class="mud-text-secondary mb-6" Style="max-width:600px">
سوالات، پیشنهادات یا انتقادات خود را با ما در میان بگذارید. تیم ما آماده پاسخگویی به شماست.
</MudText>
</MudStack>
@@ -94,7 +94,7 @@
<MudItem xs="12">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Start">
<MudCheckBox T="bool" @bind-Checked="_contactForm.AcceptTerms" />
<MudCheckBox @bind-Value="_contactForm.AcceptTerms" />
<MudText Typo="Typo.body2" Class="mud-text-secondary">
با ارسال این فرم،
<MudLink Href="/privacy" Target="_blank">سیاست حفظ حریم خصوصی</MudLink>
@@ -234,4 +234,3 @@
</MudContainer>
</section>
</MudStack>
@@ -193,7 +193,7 @@ else
</MudAvatar>
<div>
<MudText Typo="Typo.body2" >@(review.UserName)</MudText>
<MudRating ReadOnly="true" Value="review.Rating" Size="Size.Small" />
<MudRating ReadOnly="true" Value="@review.Rating" Size="Size.Small" />
</div>
<MudSpacer />
<MudText Typo="Typo.caption" Class="mud-text-secondary">@(review.Date)</MudText>
@@ -8,7 +8,7 @@ namespace FrontOffice.Main.Pages.Profile.Components;
public partial class AddAddressDialog : ComponentBase
{
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
[CascadingParameter] private IDialogReference MudDialog { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
private MudForm? _form;
@@ -41,5 +41,5 @@ public partial class AddAddressDialog : ComponentBase
}
}
private void Cancel() => MudDialog.Cancel();
private void Cancel() => MudDialog.Close(DialogResult.Cancel());
}
@@ -9,8 +9,9 @@ namespace FrontOffice.Main.Pages.Profile.Components;
public partial class EditAddressDialog : ComponentBase
{
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
[CascadingParameter] private IDialogReference MudDialog { get; set; } = default!; // updated type
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
// removed duplicate Snackbar injection; provided by Razor partial via _Imports
[Parameter] public GetAllUserAddressByFilterResponseModel? Model { get; set; }
@@ -51,5 +52,5 @@ public partial class EditAddressDialog : ComponentBase
}
}
private void Cancel() => MudDialog.Cancel();
private void Cancel() => MudDialog.Close(DialogResult.Cancel());
}
+78 -116
View File
@@ -7,15 +7,14 @@
<MudGrid Spacing="4" Justify="Justify.Center">
<MudItem xs="12" md="5">
<MudStack Spacing="2" Class="mb-6">
<MudChip T="string"
Color="Color.Secondary"
Variant="Variant.Filled"
Class="mb-2">ثبت‌نام سه مرحله‌ای</MudChip>
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled" Class="mb-2">ثبت‌نام سه
مرحله‌ای</MudChip>
<MudText Typo="Typo.h3" Class="mb-2">
فقط در چند دقیقه حساب خود را فعال کنید
</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary">
اطلاعات اولیه را وارد کنید، مشخصات هویتی را تکمیل کنید و بعد از مطالعه قوانین و دانلود قرارداد، درخواست خود را ارسال کنید.
اطلاعات اولیه را وارد کنید، مشخصات هویتی را تکمیل کنید و بعد از مطالعه قوانین و دانلود قرارداد،
درخواست خود را ارسال کنید.
</MudText>
</MudStack>
@@ -42,15 +41,16 @@
<MudAvatar Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Large" />
<MudText Typo="Typo.h4">درخواست شما ثبت شد</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary">
تیم ما پس از بررسی اطلاعات با شما تماس خواهد گرفت. می‌توانید از طریق داشبورد وضعیت ثبت‌نام را دنبال کنید.
تیم ما پس از بررسی اطلاعات با شما تماس خواهد گرفت. می‌توانید از طریق داشبورد وضعیت ثبت‌نام
را دنبال کنید.
</MudText>
<MudStack Row="true" Spacing="2" Justify="Justify.Center">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Main.MainPage))">بازگشت به صفحه اصلی</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Profile.Index))">مشاهده پروفایل</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Main.MainPage))">بازگشت به صفحه
اصلی</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Profile.Index))">مشاهده پروفایل
</MudButton>
</MudStack>
</MudStack>
}
@@ -59,7 +59,8 @@
<MudStack Spacing="3">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h4">ویزارد ثبت‌نام</MudText>
<MudChip Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">۳ مرحله</MudChip>
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined" Size="Size.Small">۳ مرحله
</MudChip>
</MudStack>
@if (_isSubmitting)
@@ -67,119 +68,80 @@
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
}
<MudStepper @bind-ActiveIndex="_activeStep"
Elevation="0"
DisableClick="true"
Class="mb-4">
<MudStep Label="تایید موبایل" Icon="@Icons.Material.Filled.Smartphone">
<MudForm @ref="_stepOneForm">
<MudTextField Label="شماره موبایل"
Placeholder="مثال: 09121234567"
InputType="InputType.Tel"
Immediate="true"
MaxLength="11"
Variant="Variant.Outlined"
@bind-Value="_model.MobileNumber"
For="@(() => _model.MobileNumber)"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Outlined.Phone" />
<MudStepper @bind-ActiveIndex="_activeStep" Elevation="0" DisableClick="true" Class="mb-4">
<ChildContent>
<MudStep Label="تأیید موبایل" Icon="@Icons.Material.Filled.Smartphone">
@* Inline AuthDialog with captcha enabled *@
<AuthDialog @ref="_authDialog" InlineMode="true" EnableCaptcha="true" HideCancelButton="true" OnLoginSuccess="@(async () => { OnPhoneVerified(); })" />
</MudStep>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mt-4">
<MudPaper Elevation="1" Class="captcha-box d-flex align-center justify-center">
<MudText Typo="Typo.h5">@_captchaCode</MudText>
<MudStep Label="اطلاعات هویتی" Icon="@Icons.Material.Filled.Badge">
<MudForm @ref="_stepTwoForm">
<MudTextField Label="نام" Variant="Variant.Outlined" Immediate="true"
@bind-Value="_model.FirstName" For="@(() => _model.FirstName)" />
<MudTextField Label="نام خانوادگی" Variant="Variant.Outlined" Immediate="true"
@bind-Value="_model.LastName" For="@(() => _model.LastName)" />
<MudTextField Label="کد ملی" Variant="Variant.Outlined" Immediate="true"
MaxLength="10" @bind-Value="_model.NationalCode"
For="@(() => _model.NationalCode)" InputType="InputType.Number" />
</MudForm>
</MudStep>
<MudStep Label="قوانین و قرارداد" Icon="@Icons.Material.Filled.Rule">
<MudForm @ref="_stepThreeForm">
<MudAlert Variant="Variant.Outlined" Severity="Severity.Info" Class="mb-3">
لطفاً قوانین و شرایط همکاری را با دقت مطالعه کنید و در صورت موافقت، تیک
تایید را فعال نمایید. همچنین می‌توانید نسخه‌ی قرارداد را دانلود و ذخیره
کنید.
</MudAlert>
<MudPaper Elevation="0" Class="terms-box pa-4 mb-3">
<MudText Typo="Typo.subtitle2" Class="mb-2">بخشی از قوانین:</MudText>
<MudList T="string" Dense="true">
<MudListItem T="string">استفاده از اطلاعات کاربری صرفاً برای ثبت نام و
احراز هویت مجاز است.</MudListItem>
<MudListItem T="string">تمامی فعالیت‌ها مطابق قوانین جمهوری اسلامی ایران
انجام می‌شود.</MudListItem>
<MudListItem T="string">مسئولیت صحت اطلاعات وارد شده بر عهده متقاضی است.
</MudListItem>
</MudList>
</MudPaper>
<MudButton Variant="Variant.Text"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Refresh"
Disabled="_isSubmitting"
OnClick="GenerateCaptcha">تازه‌سازی کد</MudButton>
</MudStack>
<MudTextField Label="کد کپچا"
Placeholder="کد نمایش داده شده"
Immediate="true"
Variant="Variant.Outlined"
@bind-Value="_model.CaptchaInput"
For="@(() => _model.CaptchaInput)" />
</MudForm>
</MudStep>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-2">
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Download" Disabled="_isSubmitting"
OnClick="DownloadContract">
دانلود قرارداد نمونه
</MudButton>
<MudText Typo="Typo.caption" Class="mud-text-secondary">فرمت: فایل متنی
</MudText>
</MudStack>
<MudStep Label="اطلاعات هویتی" Icon="@Icons.Material.Filled.Badge">
<MudForm @ref="_stepTwoForm">
<MudTextField Label="نام"
Variant="Variant.Outlined"
Immediate="true"
@bind-Value="_model.FirstName"
For="@(() => _model.FirstName)" />
<MudTextField Label="نام خانوادگی"
Variant="Variant.Outlined"
Immediate="true"
@bind-Value="_model.LastName"
For="@(() => _model.LastName)" />
<MudTextField Label="کد ملی"
Variant="Variant.Outlined"
Immediate="true"
MaxLength="10"
@bind-Value="_model.NationalCode"
For="@(() => _model.NationalCode)"
InputType="InputType.Number" />
</MudForm>
</MudStep>
<MudStep Label="قوانین و قرارداد" Icon="@Icons.Material.Filled.Rule">
<MudForm @ref="_stepThreeForm">
<MudAlert Variant="Variant.Outlined"
Severity="Severity.Info"
Class="mb-3">
لطفاً قوانین و شرایط همکاری را با دقت مطالعه کنید و در صورت موافقت، تیک تایید را فعال نمایید. همچنین می‌توانید نسخه‌ی قرارداد را دانلود و ذخیره کنید.
</MudAlert>
<MudPaper Elevation="0" Class="terms-box pa-4 mb-3">
<MudText Typo="Typo.subtitle2" Class="mb-2">بخشی از قوانین:</MudText>
<MudList Dense="true">
<MudListItem>استفاده از اطلاعات کاربری صرفاً برای ثبت نام و احراز هویت مجاز است.</MudListItem>
<MudListItem>تمامی فعالیت‌ها مطابق قوانین جمهوری اسلامی ایران انجام می‌شود.</MudListItem>
<MudListItem>مسئولیت صحت اطلاعات وارد شده بر عهده متقاضی است.</MudListItem>
</MudList>
</MudPaper>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-2">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Download"
Disabled="_isSubmitting"
OnClick="DownloadContract">
دانلود قرارداد نمونه
</MudButton>
<MudText Typo="Typo.caption" Class="mud-text-secondary">فرمت: فایل متنی</MudText>
</MudStack>
<MudCheckBox @bind-Checked="_model.AcceptTerms"
Color="Color.Success"
For="@(() => _model.AcceptTerms)"
Label="قوانین و مقررات را مطالعه کرده‌ام و می‌پذیرم" />
</MudForm>
</MudStep>
<MudCheckBox @bind-Checked="_model.AcceptTerms" Color="Color.Success"
For="@(() => _model.AcceptTerms)"
Label="قوانین و مققرات را مطالعه کرده‌ام و می‌پذیرم" />
</MudForm>
</MudStep>
</ChildContent>
<ActionContent Context="stepper">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudButton Variant="Variant.Text" Color="Color.Secondary"
Disabled="_activeStep == 0 || _isSubmitting"
OnClick="@(async () => { await GoBack(stepper); })">مرحله قبل</MudButton>
<MudSpacer />
<MudButton Variant="Variant.Filled" Color="Color.Primary"
Disabled="_isSubmitting"
OnClick="@(async () => { await GoNextAsync(stepper); })">
@_nextButtonText
</MudButton>
</MudStack>
</ActionContent>
</MudStepper>
<MudDivider Class="my-2" />
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudButton Variant="Variant.Text"
Color="Color.Secondary"
Disabled="_activeStep == 0 || _isSubmitting"
OnClick="GoBack">مرحله قبل</MudButton>
<MudSpacer />
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Disabled="_isSubmitting"
OnClick="GoNextAsync">
@_nextButtonText
</MudButton>
</MudStack>
</MudStack>
}
</MudPaper>
</MudItem>
</MudGrid>
</MudContainer>
</section>
</section>
@@ -1,4 +1,8 @@
using System.ComponentModel.DataAnnotations;
using FrontOffice.BFF.User.Protobuf.Protos.User;
using FrontOffice.Main.Shared;
using FrontOffice.Main.Utilities;
using Google.Protobuf.WellKnownTypes;
using Microsoft.AspNetCore.Components;
using MudBlazor;
@@ -6,14 +10,16 @@ namespace FrontOffice.Main.Pages;
public partial class RegisterWizard
{
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
private readonly RegistrationModel _model = new();
private MudForm? _stepOneForm;
private MudForm? _stepTwoForm;
private MudForm? _stepThreeForm;
private int _activeStep;
private bool _isSubmitting;
private bool _completed;
private string _captchaCode = string.Empty;
private AuthDialog _authDialog;
private UpdateUserRequest _updateUserRequest = new();
private string _nextButtonText => _activeStep switch
{
@@ -25,25 +31,18 @@ public partial class RegisterWizard
protected override void OnInitialized()
{
base.OnInitialized();
GenerateCaptcha();
}
private void GenerateCaptcha()
{
var random = Guid.NewGuid().ToString("N")[..6].ToUpperInvariant();
_captchaCode = random;
_model.CaptchaInput = string.Empty;
}
private void GoBack()
private async Task GoBack(MudStepper mudStepper)
{
if (_activeStep == 0 || _isSubmitting)
return;
_activeStep--;
await mudStepper.PreviousStepAsync();
}
private async Task GoNextAsync()
private async Task GoNextAsync(MudStepper mudStepper)
{
if (_isSubmitting)
return;
@@ -51,18 +50,31 @@ public partial class RegisterWizard
switch (_activeStep)
{
case 0:
if (!await ValidateStepAsync(_stepOneForm))
return;
if (!string.Equals(_model.CaptchaInput?.Trim(), _captchaCode, StringComparison.OrdinalIgnoreCase))
if (_authDialog._currentStep == AuthDialog.AuthStep.Phone)
{
Snackbar.Add("کد کپچا صحیح نیست.", Severity.Warning);
await _authDialog.SendOtpAsync();
return;
}
_activeStep = 1;
else
{
var verifyOtp = await _authDialog.VerifyOtpAsync();
if (!verifyOtp)
return;
_activeStep = 1;
}
break;
case 1:
if (!await ValidateStepAsync(_stepTwoForm))
return;
var saveResult = await SavePersonalInfo();
if (!saveResult)
return;
_activeStep = 2;
break;
case 2:
@@ -71,6 +83,8 @@ public partial class RegisterWizard
await SubmitAsync();
break;
}
await mudStepper.NextStepAsync();
}
private async Task<bool> ValidateStepAsync(MudForm? form)
@@ -102,15 +116,49 @@ public partial class RegisterWizard
Navigation.NavigateTo("docs/sample-contract.txt", true);
}
private void OnPhoneVerified()
{
// Move to next step after phone verification success
// _activeStep = 1;
// StateHasChanged();
}
private async Task<bool> SavePersonalInfo()
{
if (_stepTwoForm is null) return false;
await _stepTwoForm.Validate();
if (!_stepTwoForm.IsValid) return false;
try
{
// _updateUserRequest.AvatarPath="test";
// _updateUserRequest.BirthDate=Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc));
// _updateUserRequest.EmailNotifications = true;
// _updateUserRequest.PushNotifications = true;
// _updateUserRequest.SmsNotifications = true;
//
_updateUserRequest.FirstName = _model.FirstName;
_updateUserRequest.LastName = _model.LastName;
_updateUserRequest.NationalCode = _model.NationalCode.PersianToEnglish();
await UserContract.UpdateUserAsync(request: _updateUserRequest);
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
return true;
}
catch (Exception ex)
{
Snackbar.Add($"خطا در ذخیره اطلاعات: {ex.Message}", Severity.Error);
return false;
}
finally
{
await InvokeAsync(StateHasChanged);
}
}
private sealed class RegistrationModel
{
[Required(ErrorMessage = "شماره موبایل الزامی است.")]
[RegularExpression(@"^09\d{9}$", ErrorMessage = "شماره موبایل معتبر نیست.")]
public string? MobileNumber { get; set; }
[Required(ErrorMessage = "کد کپچا را وارد کنید.")]
public string? CaptchaInput { get; set; }
[Required(ErrorMessage = "نام الزامی است.")]
[StringLength(50, ErrorMessage = "حداکثر ۵۰ کاراکتر")]
public string? FirstName { get; set; }
@@ -120,10 +168,10 @@ public partial class RegisterWizard
public string? LastName { get; set; }
[Required(ErrorMessage = "کد ملی الزامی است.")]
[RegularExpression(@"^\d{10}$", ErrorMessage = "کدملی باید ۱۰ رقم باشد.")]
[RegularExpression("^\\d{10}$", ErrorMessage = "کدملی باید ۱۰ رقم باشد.")]
public string? NationalCode { get; set; }
[Range(typeof(bool), "true", "true", ErrorMessage = "برای ادامه باید قوانین را تایید کنید.")]
public bool AcceptTerms { get; set; }
}
}
}
+4 -3
View File
@@ -15,7 +15,8 @@
<component type="typeof(HeadOutlet)" render-mode="Server" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<!-- Ensure latest MudBlazor CSS is used (cache-busting) -->
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" asp-append-version="true" />
</head>
<body>
<component type="typeof(App)" render-mode="Server" />
@@ -31,8 +32,9 @@
<a class="dismiss">🗙</a>
</div>
<!-- Load MudBlazor JS before Blazor to avoid early JS interop calls failing; add cache-busting -->
<script src="_content/MudBlazor/MudBlazor.min.js" asp-append-version="true"></script>
<script src="_framework/blazor.server.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script>
// elementId: id نوار (مثلاً "top")
// containerSelector: کانتینری که اسکرول می‌خوره؛ برای MudLayout معمولا ".mud-main-content"
@@ -73,4 +75,3 @@
</script>
</body>
</html>