Settings page shows account info and a change password form. Validates current password, minimum 8 chars, confirmation match. Added Settings link in sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { users } from '$lib/server/db/schema.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import { verifyPassword, hashPassword } from '$lib/server/auth/password.js';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
return { user: locals.user! };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
changePassword: async ({ request, locals }) => {
|
||||
const formData = await request.formData();
|
||||
const currentPassword = formData.get('currentPassword') as string;
|
||||
const newPassword = formData.get('newPassword') as string;
|
||||
const confirmPassword = formData.get('confirmPassword') as string;
|
||||
|
||||
if (!currentPassword || !newPassword || !confirmPassword) {
|
||||
return fail(400, { error: 'All fields are required' });
|
||||
}
|
||||
|
||||
if (newPassword.length < 8) {
|
||||
return fail(400, { error: 'New password must be at least 8 characters' });
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
return fail(400, { error: 'New passwords do not match' });
|
||||
}
|
||||
|
||||
const [user] = await db
|
||||
.select({ passwordHash: users.passwordHash })
|
||||
.from(users)
|
||||
.where(eq(users.id, locals.user!.id));
|
||||
|
||||
if (!user) return fail(400, { error: 'User not found' });
|
||||
|
||||
const valid = await verifyPassword(user.passwordHash, currentPassword);
|
||||
if (!valid) {
|
||||
return fail(400, { error: 'Current password is incorrect' });
|
||||
}
|
||||
|
||||
const newHash = await hashPassword(newPassword);
|
||||
await db
|
||||
.update(users)
|
||||
.set({ passwordHash: newHash, updatedAt: new Date() })
|
||||
.where(eq(users.id, locals.user!.id));
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
|
||||
let { data, form } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Settings - B4L Repair</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="mx-auto max-w-lg">
|
||||
<h1 class="mb-6 text-2xl font-bold text-gray-900 dark:text-white">Settings</h1>
|
||||
|
||||
<!-- Account info -->
|
||||
<div class="mb-6 rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
|
||||
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Account</h2>
|
||||
<dl class="space-y-2 text-sm">
|
||||
<div>
|
||||
<dt class="text-gray-500 dark:text-gray-400">Email</dt>
|
||||
<dd class="text-gray-900 dark:text-white">{data.user.email}</dd>
|
||||
</div>
|
||||
{#if data.user.displayName}
|
||||
<div>
|
||||
<dt class="text-gray-500 dark:text-gray-400">Display Name</dt>
|
||||
<dd class="text-gray-900 dark:text-white">{data.user.displayName}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Change password -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Change Password</h2>
|
||||
|
||||
{#if form?.error}
|
||||
<div class="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-700 dark:bg-red-900/30 dark:text-red-300">
|
||||
{form.error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if form?.success}
|
||||
<div class="mb-4 rounded-md bg-green-50 p-3 text-sm text-green-700 dark:bg-green-900/30 dark:text-green-300">
|
||||
Password changed successfully.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form method="POST" action="?/changePassword" use:enhance class="space-y-4">
|
||||
<div>
|
||||
<label for="currentPassword" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Current Password</label>
|
||||
<input type="password" id="currentPassword" name="currentPassword" required autocomplete="current-password"
|
||||
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="newPassword" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">New Password</label>
|
||||
<input type="password" id="newPassword" name="newPassword" required minlength="8" autocomplete="new-password"
|
||||
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="confirmPassword" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Confirm New Password</label>
|
||||
<input type="password" id="confirmPassword" name="confirmPassword" required minlength="8" autocomplete="new-password"
|
||||
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
||||
</div>
|
||||
<button type="submit" class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
|
||||
Change Password
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user