feat: add logout button and delete account to settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Frederic Cabassut
2026-03-09 02:19:47 +01:00
co-authored by Claude Opus 4.6
parent 7255169da8
commit 4d5ea35204
2 changed files with 110 additions and 1 deletions
+79 -1
View File
@@ -30,6 +30,12 @@
let activeSessions = $state<SessionInfo[]>([]);
let sessionsLoaded = $state(false);
// Delete account state
let deletePassword = $state('');
let deleteMsg = $state('');
let deleteError = $state(false);
let showDeleteConfirm = $state(false);
async function loadSessions() {
try {
const res = await fetch('/api/auth/sessions');
@@ -80,6 +86,32 @@
return new Date(d).toLocaleString();
}
async function deleteAccount() {
deleteMsg = '';
try {
const res = await fetch('/api/auth/account', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: deletePassword })
});
if (res.ok) {
window.location.href = '/login';
} else {
const d = await res.json();
deleteMsg = d.message || 'Failed to delete account';
deleteError = true;
}
} catch {
deleteMsg = 'Connection error';
deleteError = true;
}
}
async function handleLogout() {
await fetch('/api/auth/logout', { method: 'POST' });
window.location.href = '/login';
}
async function saveProfile() {
profileMsg = '';
try {
@@ -220,7 +252,7 @@
</section>
<!-- Active Sessions -->
<section class="rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<section class="mb-6 rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<div class="mb-4 flex items-center justify-between">
<h2 class="text-lg font-semibold text-[var(--text)]">Active Sessions</h2>
{#if activeSessions.length > 1}
@@ -265,3 +297,49 @@
</div>
{/if}
</section>
<!-- Log Out & Delete Account -->
<section class="rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<div class="flex items-center justify-between">
<button
onclick={handleLogout}
class="rounded-sm border border-[var(--border-subtle)] px-4 py-2 text-sm font-medium text-[var(--text)] hover:border-[var(--primary)] hover:text-[var(--primary)]"
data-testid="logout-btn"
>
Log out
</button>
<button
onclick={() => (showDeleteConfirm = !showDeleteConfirm)}
class="rounded-sm px-4 py-2 text-sm font-medium text-[var(--destructive)] hover:underline"
>
Delete account
</button>
</div>
{#if showDeleteConfirm}
<div class="mt-4 rounded-sm border border-[var(--error-border)] bg-[var(--error-bg)] p-4">
<p class="mb-3 text-sm text-[var(--error-text)]">
This will permanently delete your account and all your notes. This cannot be undone.
</p>
{#if deleteMsg}
<div class="mb-3 text-sm text-[var(--error-text)]">{deleteMsg}</div>
{/if}
<div class="flex items-center gap-3">
<input
type="password"
bind:value={deletePassword}
placeholder="Enter your password to confirm"
class="flex-1 rounded-sm border border-[var(--error-border)] bg-[var(--bg-base)] px-3 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--destructive)]"
/>
<button
onclick={deleteAccount}
class="rounded-sm bg-[var(--destructive)] px-4 py-2 text-sm font-medium text-white hover:opacity-90"
>
Confirm delete
</button>
</div>
</div>
{/if}
</section>
+31
View File
@@ -0,0 +1,31 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types.js';
import { getUserId } from '$lib/server/api-utils.js';
import { verifyPassword, deleteUser, deleteSession, getUser } from '$lib/server/auth.js';
export const DELETE: RequestHandler = async ({ request, cookies, ...event }) => {
const userId = getUserId(event);
const user = getUser(userId);
if (!user) throw error(404, 'User not found');
const body = await request.json();
// OAuth users don't have passwords — allow deletion without password
if (user.authProvider === 'password') {
if (!body.password) throw error(400, 'Password is required');
const verified = await verifyPassword(user.email, body.password);
if (!verified) throw error(401, 'Invalid password');
}
// Delete user and all associated data
await deleteUser(userId);
// Clear session cookie
const token = cookies.get('session');
if (token) {
await deleteSession(token);
cookies.delete('session', { path: '/' });
}
return json({ success: true });
};