Files
RealCV/src/RealCV.Web/Components/Pages/Account/Register.razor

235 lines
13 KiB
Plaintext
Raw Normal View History

@page "/account/register"
@using RealCV.Web.Components.Layout
@layout MainLayout
@rendermode InteractiveServer
@using Microsoft.AspNetCore.Identity
@using RealCV.Infrastructure.Identity
@inject UserManager<ApplicationUser> UserManager
@inject SignInManager<ApplicationUser> SignInManager
@inject NavigationManager NavigationManager
<PageTitle>Register - RealCV</PageTitle>
<div class="auth-container">
<!-- Left side - Form -->
<div class="auth-form-side">
<div class="auth-form-wrapper">
<h1 class="auth-title">Create account</h1>
<p class="auth-subtitle">Start verifying UK-based CVs in minutes</p>
@if (!string.IsNullOrEmpty(_errorMessage))
{
<div class="alert alert-danger d-flex align-items-center" role="alert">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="me-2 flex-shrink-0" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
</svg>
<span>@_errorMessage</span>
</div>
}
<EditForm Model="_model" OnValidSubmit="HandleRegister" FormName="register">
<DataAnnotationsValidator />
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<div class="input-group-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/>
</svg>
<InputText id="email" class="form-control form-control-lg" @bind-Value="_model.Email"
placeholder="name@example.com" autocomplete="email" />
</div>
<ValidationMessage For="() => _model.Email" class="text-danger small mt-1" />
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<div class="input-group-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
</svg>
<InputText id="password" type="password" class="form-control form-control-lg"
@bind-Value="_model.Password" placeholder="Create a password" autocomplete="new-password" />
</div>
<ValidationMessage For="() => _model.Password" class="text-danger small mt-1" />
<div class="form-text">At least 12 characters with uppercase, lowercase, number, and symbol.</div>
</div>
<div class="mb-4">
<label for="confirmPassword" class="form-label">Confirm password</label>
<div class="input-group-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"/>
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
</svg>
<InputText id="confirmPassword" type="password" class="form-control form-control-lg"
@bind-Value="_model.ConfirmPassword" placeholder="Confirm your password" autocomplete="new-password" />
</div>
<ValidationMessage For="() => _model.ConfirmPassword" class="text-danger small mt-1" />
</div>
<div class="mb-4">
<div class="form-check">
<InputCheckbox id="agreeToTerms" class="form-check-input" @bind-Value="_model.AgreeToTerms" />
<label class="form-check-label" for="agreeToTerms">
I agree to the <a href="/terms" target="_blank" class="text-decoration-none">Terms of Service</a>
and have read the <a href="/privacy" target="_blank" class="text-decoration-none">Privacy Policy</a>
</label>
</div>
<ValidationMessage For="() => _model.AgreeToTerms" class="text-danger small mt-1" />
</div>
<div class="d-grid mb-4">
<button type="submit" class="btn btn-primary btn-lg" disabled="@_isLoading">
@if (_isLoading)
{
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<span>Creating account...</span>
}
else
{
<span>Create Account</span>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="ms-2" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/>
</svg>
}
</button>
</div>
</EditForm>
<div class="auth-divider">
<span>Already have an account?</span>
</div>
<div class="text-center">
<a href="/account/login" class="btn btn-outline-secondary btn-lg w-100">
Sign in
</a>
</div>
</div>
</div>
<!-- Right side - Branding -->
<div class="auth-brand-side">
<div class="auth-brand-content">
<div class="auth-brand-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"/>
<path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/>
</svg>
</div>
<h2 class="auth-brand-title">Start Your Free Trial</h2>
<p class="auth-brand-text">
Get 3 free CV verifications to experience the power of AI-driven credential analysis.
</p>
<div class="auth-features">
<div class="auth-feature">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7z"/>
</svg>
<span>AI-powered verification in seconds</span>
</div>
<div class="auth-feature">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7z"/>
</svg>
<span>Company legitimacy checks</span>
</div>
<div class="auth-feature">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7z"/>
</svg>
<span>Qualification & timeline analysis</span>
</div>
<div class="auth-feature">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7z"/>
</svg>
<span>Detailed PDF reports</span>
</div>
</div>
<div class="auth-testimonial">
<blockquote>
"We reduced bad hires by 40% in the first quarter using RealCV."
</blockquote>
<cite>- Recruitment Manager, Financial Services</cite>
</div>
</div>
</div>
</div>
@code {
private RegisterModel _model = new();
private bool _isLoading;
private string? _errorMessage;
private async Task HandleRegister()
{
_isLoading = true;
_errorMessage = null;
try
{
if (_model.Password != _model.ConfirmPassword)
{
_errorMessage = "Passwords do not match.";
_isLoading = false;
return;
}
var user = new ApplicationUser
{
UserName = _model.Email,
Email = _model.Email,
Plan = Domain.Enums.UserPlan.Free,
ChecksUsedThisMonth = 0,
TermsAcceptedAt = DateTime.UtcNow
};
var result = await UserManager.CreateAsync(user, _model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false);
NavigationManager.NavigateTo("/dashboard", forceLoad: true);
}
else
{
var errors = result.Errors.Select(e => e.Description);
_errorMessage = string.Join(" ", errors);
}
}
catch (Exception)
{
_errorMessage = "An unexpected error occurred. Please try again.";
}
finally
{
_isLoading = false;
}
}
private sealed class RegisterModel
{
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Email is required")]
[System.ComponentModel.DataAnnotations.EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; } = string.Empty;
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Password is required")]
[System.ComponentModel.DataAnnotations.MinLength(12, ErrorMessage = "Password must be at least 12 characters")]
public string Password { get; set; } = string.Empty;
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Please confirm your password")]
[System.ComponentModel.DataAnnotations.Compare(nameof(Password), ErrorMessage = "Passwords do not match")]
public string ConfirmPassword { get; set; } = string.Empty;
[System.ComponentModel.DataAnnotations.Range(typeof(bool), "true", "true", ErrorMessage = "You must agree to the Terms of Service")]
public bool AgreeToTerms { get; set; }
}
}