mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 20:06:57 +02:00
feat: add logout button and delete account to settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
09f2d3c928
commit
31b3cc78b4
@@ -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>
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
Reference in New Issue
Block a user