Checkpoint SHD dashboard design system
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { createSession, getSessionUser } from '$lib/server/auth/session';
|
||||
import { hashPassword, verifyPassword } from '$lib/server/auth/password';
|
||||
import { getDatabase } from '$lib/server/database';
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
const user = await getSessionUser(cookies);
|
||||
if (!user) redirect(303, '/sign-in');
|
||||
const database = await getDatabase();
|
||||
const [builds, guides, favorites, ratings] = await Promise.all([
|
||||
(await database.prepare('SELECT id, title, slug, visibility, updated_at FROM builds WHERE owner_id = ? ORDER BY updated_at DESC')).all(user.id),
|
||||
(await database.prepare("SELECT id, title, slug, visibility, updated_at FROM builds WHERE owner_id = ? AND guide_body IS NOT NULL AND length(trim(guide_body)) > 0 ORDER BY updated_at DESC")).all(user.id),
|
||||
(await database.prepare('SELECT builds.id, builds.title, builds.slug, users.username AS author FROM favorites JOIN builds ON builds.id = favorites.build_id JOIN users ON users.id = builds.owner_id WHERE favorites.user_id = ? ORDER BY favorites.created_at DESC')).all(user.id),
|
||||
(await database.prepare('SELECT builds.id, builds.title, builds.slug, users.username AS author, ratings.value FROM ratings JOIN builds ON builds.id = ratings.build_id JOIN users ON users.id = builds.owner_id WHERE ratings.user_id = ? ORDER BY builds.updated_at DESC')).all(user.id)
|
||||
]);
|
||||
return { account: { builds, guides, favorites, ratings }, chooseUsername: url.searchParams.has('choose_username'), discordLinked: url.searchParams.has('discord_linked'), oauthError: url.searchParams.has('oauth_error') };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
changeUsername: async ({ request, cookies }) => {
|
||||
const user = await getSessionUser(cookies);
|
||||
if (!user) redirect(303, '/sign-in');
|
||||
const username = String((await request.formData()).get('username') ?? '').normalize('NFKC').trim();
|
||||
if (username.length < 3 || username.length > 24 || !/^[\p{L}\p{N}_-]+$/u.test(username)) return fail(400, { usernameError: 'Use 3–24 letters, numbers, underscores, or hyphens.' });
|
||||
if (['admin','administrator','moderator','shd','support'].includes(username.toLowerCase())) return fail(400, { usernameError: 'That username is reserved.' });
|
||||
const database = await getDatabase();
|
||||
if (await (await database.prepare('SELECT id FROM users WHERE username = ? COLLATE NOCASE AND id <> ?')).get(username, user.id)) return fail(409, { usernameError: 'That username is already in use.' });
|
||||
await (await database.prepare('UPDATE users SET username = ? WHERE id = ?')).run(username, user.id);
|
||||
return { usernameChanged: true };
|
||||
},
|
||||
changePassword: async ({ request, cookies }) => {
|
||||
const user = await getSessionUser(cookies);
|
||||
if (!user) redirect(303, '/sign-in');
|
||||
const form = await request.formData();
|
||||
const current = String(form.get('currentPassword') ?? ''), next = String(form.get('newPassword') ?? ''), confirm = String(form.get('confirmPassword') ?? '');
|
||||
if (next.length < 12 || next.length > 128) return fail(400, { passwordError: 'Use between 12 and 128 characters.' });
|
||||
if (next !== confirm) return fail(400, { passwordError: 'The new passwords do not match.' });
|
||||
const database = await getDatabase();
|
||||
const credential = await (await database.prepare('SELECT password_hash FROM users WHERE id = ?')).get(user.id);
|
||||
if (typeof credential?.password_hash !== 'string' || !(await verifyPassword(credential.password_hash, current))) return fail(400, { passwordError: 'Your current password is incorrect.' });
|
||||
await (await database.prepare('UPDATE users SET password_hash = ? WHERE id = ?')).run(await hashPassword(next), user.id);
|
||||
await (await database.prepare('DELETE FROM sessions WHERE user_id = ?')).run(user.id);
|
||||
await createSession(cookies, user.id);
|
||||
return { passwordChanged: true };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">let { data, form } = $props(); const user = $derived(data.user!);</script>
|
||||
<svelte:head><title>{user.username} — SHD.build</title></svelte:head>
|
||||
<main class="page"><div class="page-head"><div><p class="eyebrow">AGENT PROFILE</p><h1>{user.username}</h1><span class="accent">{user.role.toUpperCase()}</span></div><div><b>{data.account.builds.length}</b> builds · <b>{data.account.guides.length}</b> guides · <b>{data.account.favorites.length}</b> favorites</div></div>
|
||||
{#if data.chooseUsername}<p class="notice">Choose the unique SHD.build username other agents will see.</p>{/if}{#if data.discordLinked}<p class="success">Discord account linked successfully.</p>{/if}{#if data.oauthError}<p class="error">Discord linking could not be completed.</p>{/if}
|
||||
<div class="grid" style="grid-template-columns:1fr 380px">
|
||||
<section class="grid"><div class="panel"><div class="page-head"><div><p class="eyebrow">LOADOUT ARCHIVE</p><h2>My builds</h2></div><a class="secondary" href="/builder">NEW BUILD +</a></div>{#if data.account.builds.length}{#each data.account.builds as build}<div class="table-row"><b>{build.title}</b><span>{build.visibility}</span><span>{new Date(String(build.updated_at)).toLocaleDateString()}</span><span></span><button>OPEN</button></div>{/each}{:else}<p class="muted">Your saved and published loadouts will appear here.</p>{/if}</div><div class="panel"><h2>Favorites and ratings</h2><p class="muted">{data.account.favorites.length} favorited · {data.account.ratings.length} rated</p></div></section>
|
||||
<aside class="panel"><p class="eyebrow">ACCOUNT SETTINGS</p><h2>Username</h2><form method="POST" action="?/changeUsername"><label class="field">PUBLIC USERNAME<input name="username" value={user.username} required /></label>{#if form?.usernameError}<p class="error">{form.usernameError}</p>{/if}<div class="form-actions"><button class="primary">UPDATE</button></div></form><hr /><h2>Sign-in methods</h2><a class="secondary" href="/auth/discord?mode=link">LINK DISCORD</a><hr /><h2>Change password</h2><form method="POST" action="?/changePassword"><label class="field">CURRENT PASSWORD<input name="currentPassword" type="password" required /></label><label class="field">NEW PASSWORD<input name="newPassword" type="password" minlength="12" required /></label><label class="field">CONFIRM PASSWORD<input name="confirmPassword" type="password" minlength="12" required /></label>{#if form?.passwordError}<p class="error">{form.passwordError}</p>{/if}<div class="form-actions"><button class="primary">UPDATE PASSWORD</button></div></form></aside>
|
||||
</div></main>
|
||||
Reference in New Issue
Block a user