Enhance user routing and layout management. Added support for user-specific routes, including dynamic user ID handling and updated component imports. Improved type definitions and user data mapping in hooks for better user experience.

This commit is contained in:
eewing
2026-02-18 15:59:16 -06:00
parent 1af7aeb7d9
commit 5776a1fdb1
22 changed files with 274 additions and 28 deletions
+9 -3
View File
@@ -1,9 +1,15 @@
<script lang="ts">
import { browser } from '$app/environment';
import { Button } from '$lib/components/ui/button';
import { sites, siteSlugs } from '$lib/sites';
import { sites, siteSlugs, type SiteSlug } from '$lib/sites';
let { data }: { data: Record<string, unknown> } = $props();
let { data }: { data: App.PageData } = $props();
const visibleSlugs = $derived(
data.user
? (siteSlugs.filter((slug) => data.user && data.user[slug] === true) as SiteSlug[])
: []
);
</script>
{#if browser}
@@ -18,7 +24,7 @@
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-4 p-4 sm:gap-6 sm:p-6">
<h1 class="text-2xl font-semibold">Base</h1>
<div class="flex w-full max-w-xs flex-col gap-3">
{#each siteSlugs as slug}
{#each visibleSlugs as slug}
{@const site = sites[slug]}
<a href="/{slug}" class="block">
<Button class="h-16 w-full min-h-16" variant="outline">{site.title}</Button>
+9 -4
View File
@@ -1,15 +1,20 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import { sites, siteSlugs } from '$lib/sites';
import { sites, siteSlugs, type SiteSlug } from '$lib/sites';
let { data: _data }: { data: Record<string, unknown> } = $props();
// `_data` is accepted to mirror Stackq's pattern (unused for now).
let { data }: { data: App.PageData } = $props();
const visibleSlugs = $derived(
data.user
? (siteSlugs.filter((slug) => data.user && data.user[slug] === true) as SiteSlug[])
: []
);
</script>
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-4 p-4 sm:gap-6 sm:p-6">
<h1 class="w-full max-w-xs shrink-0 text-center text-2xl font-semibold">Base</h1>
<div class="flex w-full max-w-xs shrink-0 flex-col gap-3">
{#each siteSlugs as slug}
{#each visibleSlugs as slug}
{@const site = sites[slug]}
<a href="/{slug}" class="block">
<Button class="h-16 min-h-16 w-full" variant="outline">
+8
View File
@@ -0,0 +1,8 @@
import type { PageServerLoad } from './$types';
import { mapUserRecord } from '$lib/user-from-record';
export const load: PageServerLoad = async ({ locals }) => {
const list = await locals.pb.collection('users').getFullList();
const users = list.map((r) => mapUserRecord(r as Record<string, unknown>));
return { users };
};
+46
View File
@@ -0,0 +1,46 @@
<script lang="ts">
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import type { UserFromRecord } from '$lib/user-from-record';
let { data }: { data: { users: UserFromRecord[] } } = $props();
</script>
<svelte:head>
<title>Users — Base</title>
</svelte:head>
<main class="flex min-h-0 flex-1 flex-col gap-4 p-4 sm:gap-6 sm:p-6">
<h1 class="text-2xl font-semibold">Users</h1>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{#each data.users as user (user.id)}
<Card.Root class="flex flex-col">
<Card.Header class="flex flex-row items-center gap-3">
<div class="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm font-medium">
{user.name
? String(user.name).slice(0, 2).toUpperCase()
: user.email
? String(user.email).slice(0, 2).toUpperCase()
: '?'}
</div>
<div class="min-w-0 flex-1">
<Card.Title class="truncate">{user.name ?? user.email ?? user.id}</Card.Title>
{#if user.email && user.name}
<Card.Description class="truncate text-xs">{user.email}</Card.Description>
{/if}
</div>
</Card.Header>
<Card.Content class="flex-1">
<p class="text-muted-foreground text-sm">
Prism {user.prism ? '✓' : '—'} · TasGrid {user.tasgrid ? '✓' : '—'} · Stackq {user.stackq ? '✓' : '—'} · HRM {user.hrm ? '✓' : '—'}
</p>
</Card.Content>
<Card.Footer>
<a href="/users/{user.id}" class="block">
<Button variant="outline" class="w-full">View</Button>
</a>
</Card.Footer>
</Card.Root>
{/each}
</div>
</main>
+14
View File
@@ -0,0 +1,14 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { mapUserRecord } from '$lib/user-from-record';
export const load: PageServerLoad = async ({ params, locals }) => {
const id = params.id;
if (!id) throw error(404, 'User not found');
try {
const record = (await locals.pb.collection('users').getOne(id)) as Record<string, unknown>;
return { user: mapUserRecord(record) };
} catch {
throw error(404, 'User not found');
}
};
+35
View File
@@ -0,0 +1,35 @@
<script lang="ts">
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import type { UserFromRecord } from '$lib/user-from-record';
let { data }: { data: { user: UserFromRecord } } = $props();
const user = data.user;
</script>
<svelte:head>
<title>{user.name ?? user.email ?? 'User'} — Base</title>
</svelte:head>
<main class="flex min-h-0 flex-1 flex-col gap-4 p-4 sm:gap-6 sm:p-6">
<a href="/users" class="self-start">
<Button variant="ghost" size="sm">← Users</Button>
</a>
<Card.Root class="max-w-md">
<Card.Header>
<Card.Title>{user.name ?? user.email ?? user.id}</Card.Title>
{#if user.email}
<Card.Description>{user.email}</Card.Description>
{/if}
</Card.Header>
<Card.Content class="flex flex-col gap-2">
<p class="text-muted-foreground text-sm">
<span class="font-medium text-foreground">Access:</span><br />
Prism {user.prism ? '✓' : '—'} · TasGrid {user.tasgrid ? '✓' : '—'} · Stackq {user.stackq ? '✓' : '—'} · HRM {user.hrm ? '✓' : '—'}
</p>
<p class="text-muted-foreground text-sm">
<span class="font-medium text-foreground">Dark mode:</span> {user.darkmode ? 'On' : 'Off'}
</p>
</Card.Content>
</Card.Root>
</main>