@page "/settings" @attribute [Authorize] @using Microsoft.AspNetCore.Identity @using Microsoft.EntityFrameworkCore @inject UserManager UserManager @inject SignInManager SignInManager @inject AuthenticationStateProvider AuthStateProvider @inject ApplicationDbContext DbContext @inject NavigationManager Navigation @inject AppState AppState @rendermode InteractiveServer Settings

Settings beta

@* ── Change Password ── *@
Change Password
@if (passwordSuccess) {
Password changed successfully.
} @if (!string.IsNullOrEmpty(passwordError)) {
@passwordError
}
@* ── Reset Data ── *@
Reset Data

This will permanently delete all your accounts, transactions and categories, giving you a clean slate. Your user account and password will be kept.

@if (resetSuccess) {
All data has been reset successfully.
} @if (!string.IsNullOrEmpty(resetError)) {
@resetError
} @if (!confirmingReset) { } else {
Are you sure? This cannot be undone. Type I UNDERSTAND and enter your password to confirm.
}
@* ── Delete Account ── *@
Delete Account

This will permanently delete your account and all associated data (accounts, transactions, and categories). This action cannot be undone.

@if (!string.IsNullOrEmpty(deleteError)) {
@deleteError
} @if (!confirmingDelete) { } else {
Are you sure? Type your password to confirm.
}
@code { private static readonly string appVersion = typeof(Program).Assembly.GetName().Version?.ToString(3) ?? "0.0.0"; private ChangePasswordForm passwordForm = new(); private bool passwordSuccess; private string? passwordError; private bool confirmingDelete; private string deletePassword = string.Empty; private string? deleteError; private bool confirmingReset; private string resetConfirmation = string.Empty; private string resetPassword = string.Empty; private string? resetError; private bool resetSuccess; private async Task ChangePassword() { passwordSuccess = false; passwordError = null; var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var userIdClaim = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (userIdClaim is null) return; var user = await UserManager.FindByIdAsync(userIdClaim); if (user is null) return; var result = await UserManager.ChangePasswordAsync(user, passwordForm.CurrentPassword, passwordForm.NewPassword); if (result.Succeeded) { passwordSuccess = true; passwordForm = new ChangePasswordForm(); } else { passwordError = string.Join(" ", result.Errors.Select(e => e.Description)); } } private async Task DeleteAccount() { deleteError = null; var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var userIdClaim = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (userIdClaim is null) return; var user = await UserManager.FindByIdAsync(userIdClaim); if (user is null) return; var passwordValid = await UserManager.CheckPasswordAsync(user, deletePassword); if (!passwordValid) { deleteError = "Incorrect password."; return; } // Delete all user data explicitly in correct dependency order await DbContext.Transactions .Where(t => t.Account.UserId == userIdClaim) .ExecuteDeleteAsync(); await DbContext.Accounts .Where(a => a.UserId == userIdClaim) .ExecuteDeleteAsync(); await DbContext.Categories .Where(c => c.UserId == userIdClaim) .ExecuteDeleteAsync(); // Delete identity user await UserManager.DeleteAsync(user); await SignInManager.SignOutAsync(); Navigation.NavigateTo("/login", forceLoad: true); } private void CancelDelete() { confirmingDelete = false; deletePassword = string.Empty; deleteError = null; } private async Task ResetData() { resetError = null; resetSuccess = false; var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var userIdClaim = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (userIdClaim is null) return; var user = await UserManager.FindByIdAsync(userIdClaim); if (user is null) return; if (!string.Equals(resetConfirmation?.Trim(), "I UNDERSTAND", StringComparison.Ordinal)) { resetError = "Please type 'I UNDERSTAND' exactly to confirm."; return; } var passwordValid = await UserManager.CheckPasswordAsync(user, resetPassword); if (!passwordValid) { resetError = "Incorrect password."; return; } await DbContext.Transactions .Where(t => t.Account.UserId == userIdClaim) .ExecuteDeleteAsync(); await DbContext.Accounts .Where(a => a.UserId == userIdClaim) .ExecuteDeleteAsync(); await DbContext.Categories .Where(c => c.UserId == userIdClaim) .ExecuteDeleteAsync(); confirmingReset = false; resetConfirmation = string.Empty; resetPassword = string.Empty; resetSuccess = true; AppState.NotifyAccountsChanged(); } private void CancelReset() { confirmingReset = false; resetConfirmation = string.Empty; resetPassword = string.Empty; resetError = null; } private sealed class ChangePasswordForm { [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Current password is required")] public string CurrentPassword { get; set; } = string.Empty; [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "New password is required")] [System.ComponentModel.DataAnnotations.MinLength(8, ErrorMessage = "Password must be at least 8 characters")] public string NewPassword { get; set; } = string.Empty; [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Please confirm your new password")] [System.ComponentModel.DataAnnotations.Compare(nameof(NewPassword), ErrorMessage = "Passwords do not match")] public string ConfirmNewPassword { get; set; } = string.Empty; } }