Files
blazor-oms/Features/Account/Register/Components/RegisterPage.razor
T
2026-07-21 13:38:38 +07:00

280 lines
8.5 KiB
Plaintext

@page "/account/register"
@layout AuthenticationLayout
@using Indotalent.Shared.Consts
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
@inject ISnackbar Snackbar
@inject NavigationManager Navigation
@inject IConfiguration Configuration
<PageTitle>Sign Up - @GlobalConsts.AppInitial</PageTitle>
<style>
/* ===== PAGE HEADER ===== */
.page-header {
text-align: center;
margin-bottom: 2rem;
}
.page-header h2 {
font-size: 1.5rem;
font-weight: 700;
color: #1E293B;
margin: 0 0 0.25rem 0;
}
.page-header p {
font-size: 0.875rem;
color: #94A3B8;
margin: 0;
}
/* ===== FORM LABEL ===== */
.form-label {
display: block;
font-size: 0.8125rem;
font-weight: 600;
color: #475569;
margin-bottom: 0.375rem;
}
/* ===== SUBMIT BUTTON ===== */
.btn-submit {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1.75rem;
background: #8B5CF6;
color: #ffffff;
font-size: 0.875rem;
font-weight: 600;
border-radius: 0.5rem;
transition: all 0.2s;
cursor: pointer;
border: none;
width: 100%;
font-family: "Poppins", sans-serif;
height: 48px;
}
.btn-submit:hover:not(:disabled) {
background: #7C3AED;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
}
.btn-submit:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* ===== SPINNER ANIMATION ===== */
@@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
}
/* ===== FOOTER ===== */
.auth-link {
color: #8B5CF6;
font-size: 0.875rem;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: color 0.2s;
}
.auth-link:hover {
color: #7C3AED;
text-decoration: underline;
}
.auth-footer {
margin-top: 1.5rem;
text-align: center;
font-size: 0.875rem;
color: #64748B;
}
</style>
<div class="page-header">
<h2>Sign Up</h2>
<p>Please register your account</p>
</div>
<MudForm @ref="form" @bind-IsValid="@success" Validation="@(new Func<EditContext, Task<bool>>(ValidateForm))">
<div style="display: flex; flex-direction: column; gap: 1rem;">
<div>
<label class="form-label" for="fullname-field">Full Name</label>
<MudTextField T="string"
@bind-Value="model.FullName"
Variant="Variant.Outlined"
Placeholder="Enter your full name"
Margin="Margin.Dense"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Person"
Required="true"
For="@(() => model.FullName)"
id="fullname-field" />
</div>
<div>
<label class="form-label" for="email-field">Email Address</label>
<MudTextField T="string"
@bind-Value="model.Email"
Variant="Variant.Outlined"
Placeholder="Enter your email"
Margin="Margin.Dense"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Email"
Required="true"
For="@(() => model.Email)"
id="email-field" />
</div>
<div>
<label class="form-label" for="password-field">Password</label>
<MudTextField T="string"
@bind-Value="model.Password"
Variant="Variant.Outlined"
Placeholder="Create a password"
Margin="Margin.Dense"
InputType="@(showPassword ? InputType.Text : InputType.Password)"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lock"
AdornmentEndIcon="@(showPassword ? Icons.Material.Filled.Visibility : Icons.Material.Filled.VisibilityOff)"
OnAdornmentEndClick="() => showPassword = !showPassword"
Required="true"
For="@(() => model.Password)"
id="password-field" />
</div>
<button class="btn-submit" type="button" disabled="@(!success || isProcessing)" @onclick="HandleRegister">
@if (isProcessing)
{
<svg class="spinner" style="width: 1rem; height: 1rem;" fill="none" viewBox="0 0 24 24">
<circle style="opacity: 0.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
<path style="opacity: 0.75;" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
</svg>
<span>Processing...</span>
}
else
{
<span>Create Account</span>
}
</button>
</div>
</MudForm>
<div class="auth-footer">
Already have an account?
<a class="auth-link" href="/account/login">Sign In</a>
</div>
@code {
private MudForm form = default!;
private bool success;
private bool isProcessing;
private bool showPassword;
private RegisterModel model = new();
private class RegisterModel
{
public string FullName { get; set; } = "";
public string Email { get; set; } = "";
public string Password { get; set; } = "";
}
private async Task<bool> ValidateForm(EditContext context)
{
return await Task.FromResult(true);
}
private async Task HandleRegister()
{
await form.Validate();
if (!success) return;
isProcessing = true;
StateHasChanged();
var result = await JSRuntime.InvokeAsync<ApiJSRuntimeResponse>("apiAccountRegister", model.FullName, model.Email, model.Password);
if (result.Status == 200)
{
var requireConfirmation = Configuration.GetValue<bool>("IdentitySettings:SignIn:RequireConfirmedAccount");
if (requireConfirmation)
{
Snackbar.Add("Registration successful! Please check your email to confirm your account.", Severity.Info);
}
else
{
Snackbar.Add("Registration successful! You can now sign in.", Severity.Success);
}
await Task.Delay(500);
Navigation.NavigateTo("/account/login");
}
else
{
Snackbar.Add(result.Title ?? "Registration failed", Severity.Error);
}
isProcessing = false;
StateHasChanged();
}
private class ApiJSRuntimeResponse
{
public int? Status { get; set; }
public string? Title { get; set; }
}
}
<script>
window.apiAccountRegister = async (fullName, email, password) => {
try {
const response = await fetch('/api/account/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ fullName, email, password })
});
const data = await response.json();
if (response.ok) {
return { status: 200, title: 'Success account registration' };
} else {
let errorMessage = data.title || data.message || 'Registration failed';
if (data.errors) {
if (Array.isArray(data.errors) && data.errors.length > 0) {
errorMessage = data.errors[0];
} else if (typeof data.errors === 'object') {
const firstKey = Object.keys(data.errors)[0];
if (firstKey) {
errorMessage = data.errors[firstKey][0];
}
}
}
return { status: response.status, title: errorMessage };
}
} catch (error) {
return { status: 500, title: 'Server / Network error' };
}
};
</script>