feat: add dashboard layout with navigation and auth guard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
29
web/src/lib/api/auth.remote.ts
Normal file
29
web/src/lib/api/auth.remote.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import { form, getRequestEvent, query } from '$app/server';
|
||||||
|
import { auth } from '$lib/server/auth';
|
||||||
|
import { signupSchema, loginSchema } from '$lib/schema/auth';
|
||||||
|
|
||||||
|
export const signup = form(signupSchema, async (user) => {
|
||||||
|
await auth.api.signUpEmail({ body: user });
|
||||||
|
redirect(307, '/dashboard');
|
||||||
|
});
|
||||||
|
|
||||||
|
export const login = form(loginSchema, async (user) => {
|
||||||
|
const { request } = getRequestEvent();
|
||||||
|
await auth.api.signInEmail({ body: user, headers: request.headers });
|
||||||
|
redirect(303, '/dashboard');
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signout = form(async () => {
|
||||||
|
const { request } = getRequestEvent();
|
||||||
|
await auth.api.signOut({ headers: request.headers });
|
||||||
|
redirect(303, '/login');
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getUser = query(async () => {
|
||||||
|
const { locals } = getRequestEvent();
|
||||||
|
if (!locals.user) {
|
||||||
|
redirect(307, '/login');
|
||||||
|
}
|
||||||
|
return locals.user;
|
||||||
|
});
|
||||||
7
web/src/routes/+layout.server.ts
Normal file
7
web/src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||||
|
return {
|
||||||
|
user: locals.user ?? null
|
||||||
|
};
|
||||||
|
};
|
||||||
9
web/src/routes/+page.server.ts
Normal file
9
web/src/routes/+page.server.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
if (locals.user) {
|
||||||
|
redirect(307, '/dashboard');
|
||||||
|
}
|
||||||
|
redirect(307, '/login');
|
||||||
|
};
|
||||||
1
web/src/routes/+page.svelte
Normal file
1
web/src/routes/+page.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<p>Redirecting...</p>
|
||||||
11
web/src/routes/dashboard/+layout.server.ts
Normal file
11
web/src/routes/dashboard/+layout.server.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||||
|
if (!locals.user) {
|
||||||
|
redirect(307, '/login');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
user: locals.user
|
||||||
|
};
|
||||||
|
};
|
||||||
29
web/src/routes/dashboard/+layout.svelte
Normal file
29
web/src/routes/dashboard/+layout.svelte
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { signout } from '$lib/api/auth.remote';
|
||||||
|
|
||||||
|
let { children, data } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex min-h-screen">
|
||||||
|
<aside class="flex w-64 flex-col border-r border-neutral-200 bg-neutral-50 p-6">
|
||||||
|
<h1 class="mb-8 text-xl font-bold">DroidClaw</h1>
|
||||||
|
<nav class="flex flex-col gap-2">
|
||||||
|
<a href="/dashboard" class="rounded px-3 py-2 hover:bg-neutral-200">Overview</a>
|
||||||
|
<a href="/dashboard/devices" class="rounded px-3 py-2 hover:bg-neutral-200">Devices</a>
|
||||||
|
<a href="/dashboard/api-keys" class="rounded px-3 py-2 hover:bg-neutral-200">API Keys</a>
|
||||||
|
<a href="/dashboard/settings" class="rounded px-3 py-2 hover:bg-neutral-200">Settings</a>
|
||||||
|
</nav>
|
||||||
|
<div class="mt-auto pt-8">
|
||||||
|
<p class="mb-2 text-sm text-neutral-500">{data.user.email}</p>
|
||||||
|
<form {...signout}>
|
||||||
|
<button type="submit" class="text-sm text-neutral-500 hover:text-neutral-800">
|
||||||
|
Sign out
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main class="flex-1 p-8">
|
||||||
|
{@render children?.()}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
30
web/src/routes/dashboard/+page.svelte
Normal file
30
web/src/routes/dashboard/+page.svelte
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { data } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h2 class="mb-6 text-2xl font-bold">Dashboard</h2>
|
||||||
|
<p class="text-neutral-600">Welcome back, {data.user.name}.</p>
|
||||||
|
|
||||||
|
<div class="mt-8 grid grid-cols-3 gap-6">
|
||||||
|
<a
|
||||||
|
href="/dashboard/devices"
|
||||||
|
class="rounded-lg border border-neutral-200 p-6 hover:border-neutral-400"
|
||||||
|
>
|
||||||
|
<h3 class="font-semibold">Devices</h3>
|
||||||
|
<p class="mt-1 text-sm text-neutral-500">Manage connected phones</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/dashboard/api-keys"
|
||||||
|
class="rounded-lg border border-neutral-200 p-6 hover:border-neutral-400"
|
||||||
|
>
|
||||||
|
<h3 class="font-semibold">API Keys</h3>
|
||||||
|
<p class="mt-1 text-sm text-neutral-500">Create keys for your devices</p>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/dashboard/settings"
|
||||||
|
class="rounded-lg border border-neutral-200 p-6 hover:border-neutral-400"
|
||||||
|
>
|
||||||
|
<h3 class="font-semibold">Settings</h3>
|
||||||
|
<p class="mt-1 text-sm text-neutral-500">Configure LLM provider</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user