- Add checkbox requiring users to agree to Terms of Service and Privacy Policy - Add TermsAcceptedAt field to ApplicationUser to track acceptance - Link checkbox to actual /terms and /privacy pages - Remove passive text that was using dead # links 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
235 lines
13 KiB
Plaintext
235 lines
13 KiB
Plaintext
@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; }
|
|
}
|
|
}
|