Files
edh-stats/frontend/public/profile.html
Michael Skrynski 779dfd173c Refactor change-password and add delete account
- Harden /change-password with JWT guard and per-hour rate limit
- Validate current and new passwords, and update password on success
- Replace previous password-change flow with a streamlined
  delete-account path
- Implement /me DELETE to permanently remove user data and respond with
  success
- Add frontend delete account flow: profile.js handles deletion, modal
  UI, and token-based API call
- Extend profile.html with a Danger Zone and a Delete Account modal
- Update register.html: link to Terms of Service now points to /tos.html
  and opens in a new tab
2026-01-18 15:29:10 +01:00

423 lines
15 KiB
HTML

<!doctype html>
<html lang="en" class="h-full bg-gray-50">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Profile - EDH Stats Tracker</title>
<meta name="description" content="Edit your profile and account settings" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body class="h-full flex flex-col" x-data="profileManager()">
<!-- Navigation Header -->
<header class="bg-slate-900 text-white shadow-lg">
<nav class="container mx-auto px-4 py-4">
<div class="flex justify-between items-center">
<div class="flex items-center space-x-4">
<h1 class="text-2xl font-bold font-mtg">EDH Stats</h1>
<div class="hidden md:flex space-x-6">
<a
href="/dashboard.html"
class="text-white hover:text-edh-accent transition-colors"
>Dashboard</a
>
<a
href="/commanders.html"
class="text-white hover:text-edh-accent transition-colors"
>Commanders</a
>
<a
href="/games.html"
class="text-white hover:text-edh-accent transition-colors"
>Game Log</a
>
<a
href="/stats.html"
class="text-white hover:text-edh-accent transition-colors"
>Statistics</a
>
</div>
</div>
<div class="flex items-center space-x-4">
<!-- User Menu -->
<div class="relative" x-data="{ userMenuOpen: false }">
<button
@click="userMenuOpen = !userMenuOpen"
class="flex items-center space-x-2 hover:text-edh-accent"
>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
></path>
</svg>
<span x-text="currentUser?.username"></span>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
></path>
</svg>
</button>
<div
x-show="userMenuOpen"
@click.away="userMenuOpen = false"
x-transition
class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50"
>
<a
href="/profile.html"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>Profile</a
>
<hr class="my-1" />
<button
@click="logout()"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>
Logout
</button>
</div>
</div>
<!-- Mobile Menu Button -->
<button @click="mobileMenuOpen = !mobileMenuOpen" class="md:hidden">
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
></path>
</svg>
</button>
</div>
</div>
<!-- Mobile Menu -->
<div
x-show="mobileMenuOpen"
x-transition
class="md:hidden mt-4 pt-4 border-t border-edh-secondary"
>
<div class="flex flex-col space-y-2">
<a
href="/dashboard.html"
class="text-white hover:text-edh-accent transition-colors py-2"
>Dashboard</a
>
<a
href="/commanders.html"
class="text-white hover:text-edh-accent transition-colors py-2"
>Commanders</a
>
<a
href="/games.html"
class="text-white hover:text-edh-accent transition-colors py-2"
>Log Game</a
>
<a
href="/stats.html"
class="text-white hover:text-edh-accent transition-colors py-2"
>Statistics</a
>
</div>
</div>
</nav>
</header>
<!-- Main Content -->
<main class="flex-1 container mx-auto px-4 py-8 max-w-2xl">
<div class="mb-8">
<a href="/dashboard.html" class="text-edh-accent hover:text-edh-primary"
>← Back to Dashboard</a
>
</div>
<h1 class="text-4xl font-bold mb-8">Profile Settings</h1>
<!-- Edit Username Section -->
<div class="card mb-8">
<h2 class="text-2xl font-semibold mb-6">Edit Username</h2>
<form @submit.prevent="handleUpdateUsername">
<div class="mb-6">
<label for="username" class="form-label">Username</label>
<input
id="username"
type="text"
x-model="formData.username"
@input="validateUsername()"
:class="errors.username ? 'border-red-500 focus:ring-red-500' : ''"
class="form-input"
placeholder="Enter your new username"
/>
<p
x-show="errors.username"
x-text="errors.username"
class="form-error"
></p>
</div>
<div
x-show="successMessage.username"
class="mb-6 p-4 bg-green-50 rounded-lg border border-green-200"
>
<p class="text-green-800" x-text="successMessage.username"></p>
</div>
<div
x-show="serverError.username"
class="mb-6 p-4 bg-red-50 rounded-lg border border-red-200"
>
<p class="text-red-800" x-text="serverError.username"></p>
</div>
<button
type="submit"
:disabled="submitting.username"
class="btn btn-primary"
>
<span x-show="!submitting.username">Update Username</span>
<span x-show="submitting.username">Updating...</span>
</button>
</form>
</div>
<!-- Change Password Section -->
<div class="card">
<h2 class="text-2xl font-semibold mb-6">Change Password</h2>
<form @submit.prevent="handleChangePassword">
<!-- Current Password -->
<div class="mb-6">
<label for="currentPassword" class="form-label"
>Current Password</label
>
<input
id="currentPassword"
type="password"
x-model="formData.currentPassword"
@input="validateCurrentPassword()"
:class="errors.currentPassword ? 'border-red-500 focus:ring-red-500' : ''"
class="form-input"
placeholder="Enter your current password"
/>
<p
x-show="errors.currentPassword"
x-text="errors.currentPassword"
class="form-error"
></p>
</div>
<!-- New Password -->
<div class="mb-6">
<label for="newPassword" class="form-label">New Password</label>
<input
id="newPassword"
type="password"
x-model="formData.newPassword"
@input="validateNewPassword()"
:class="errors.newPassword ? 'border-red-500 focus:ring-red-500' : ''"
class="form-input"
placeholder="Enter your new password"
/>
<p
x-show="errors.newPassword"
x-text="errors.newPassword"
class="form-error"
></p>
<p class="text-xs text-gray-500 mt-2">
Password must be at least 8 characters and contain uppercase,
lowercase, and numbers
</p>
</div>
<!-- Confirm New Password -->
<div class="mb-6">
<label for="confirmPassword" class="form-label"
>Confirm New Password</label
>
<input
id="confirmPassword"
type="password"
x-model="formData.confirmPassword"
@input="validateConfirmPassword()"
:class="errors.confirmPassword ? 'border-red-500 focus:ring-red-500' : ''"
class="form-input"
placeholder="Confirm your new password"
/>
<p
x-show="errors.confirmPassword"
x-text="errors.confirmPassword"
class="form-error"
></p>
</div>
<div
x-show="successMessage.password"
class="mb-6 p-4 bg-green-50 rounded-lg border border-green-200"
>
<p class="text-green-800" x-text="successMessage.password"></p>
</div>
<div
x-show="serverError.password"
class="mb-6 p-4 bg-red-50 rounded-lg border border-red-200"
>
<p class="text-red-800" x-text="serverError.password"></p>
</div>
<button
type="submit"
:disabled="submitting.password"
class="btn btn-primary"
>
<span x-show="!submitting.password">Change Password</span>
<span x-show="submitting.password">Updating...</span>
</button>
</form>
</div>
<!-- Delete Account Section -->
<div class="card mt-8 border-red-200 bg-red-50">
<h2 class="text-2xl font-semibold mb-4 text-red-900">Danger Zone</h2>
<div class="mb-6 p-4 bg-red-100 rounded-lg border border-red-300">
<div class="flex items-start">
<svg class="w-5 h-5 text-red-600 mr-3 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<div>
<h3 class="font-semibold text-red-900">Delete Account</h3>
<p class="text-sm text-red-800 mt-1">
This action is permanent and cannot be undone. All your game records, commanders, and statistics will be deleted.
</p>
</div>
</div>
</div>
<button
@click="showDeleteConfirm = true"
type="button"
class="btn bg-red-600 hover:bg-red-700 text-white"
>
Delete Account
</button>
</div>
</main>
<!-- Delete Account Confirmation Modal -->
<div
x-show="showDeleteConfirm"
x-transition
class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4"
@click.self="showDeleteConfirm = false"
>
<div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
<div class="flex items-center justify-center w-10 h-10 mx-auto bg-red-100 rounded-full mb-4">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4v2m0 5v-6m0-6V3m0 0h3m-3 0H9" />
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-900 text-center mb-4">
Delete Account?
</h3>
<p class="text-gray-600 text-center mb-4">
Are you absolutely sure? This will permanently delete:
</p>
<ul class="text-sm text-gray-600 space-y-2 mb-6 pl-4">
<li class="flex items-start">
<span class="text-red-600 mr-2"></span>
<span>Your account and all personal data</span>
</li>
<li class="flex items-start">
<span class="text-red-600 mr-2"></span>
<span>All game records and statistics</span>
</li>
<li class="flex items-start">
<span class="text-red-600 mr-2"></span>
<span>All commanders and associated data</span>
</li>
</ul>
<form @submit.prevent="handleDeleteAccount">
<div class="mb-4">
<label for="deleteConfirmText" class="form-label text-center block">
Type <span class="font-semibold">delete my account</span> to confirm:
</label>
<input
id="deleteConfirmText"
type="text"
x-model="deleteConfirmText"
placeholder="delete my account"
class="form-input mt-2"
/>
</div>
<div
x-show="serverError.deleteAccount"
class="mb-4 p-3 bg-red-50 rounded-lg border border-red-200"
>
<p class="text-red-800 text-sm" x-text="serverError.deleteAccount"></p>
</div>
<div class="flex gap-3">
<button
type="button"
@click="showDeleteConfirm = false; deleteConfirmText = ''"
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 font-medium"
>
Cancel
</button>
<button
type="submit"
:disabled="deleteConfirmText !== 'delete my account' || submitting.deleteAccount"
:class="{ 'opacity-50 cursor-not-allowed': deleteConfirmText !== 'delete my account' || submitting.deleteAccount }"
class="flex-1 px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 font-medium disabled:hover:bg-red-600"
>
<span x-show="!submitting.deleteAccount">Delete Account</span>
<span x-show="submitting.deleteAccount">Deleting...</span>
</button>
</div>
</form>
</div>
</div>
<!-- Scripts -->
<script
defer
src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"
></script>
<script src="/js/auth-guard.js"></script>
<script src="/js/app.js"></script>
<script src="/js/profile.js"></script>
<script src="/js/footer-loader.js"></script>
</body>
</html>