Restructure route organization

This commit is contained in:
NeilClack
2026-05-03 20:59:43 -05:00
parent e563a66dc7
commit 3d1d713308
15 changed files with 0 additions and 0 deletions
@@ -0,0 +1,26 @@
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { prisma } from '$lib/db';
import { canAccessAdmin } from '$lib/svelte-admin/roles';
export const load: PageServerLoad = async ({ params, parent }) => {
const parentData = typeof parent === 'function' ? await parent() : {};
const userRoleId = (parentData as { userRoleId?: number }).userRoleId ?? null;
if (userRoleId != null && !canAccessAdmin(userRoleId)) {
throw error(403, 'not authorized');
}
const proclamation = await prisma.proclamation.findUnique({
where: { id: params.id },
include: {
solfeggioFrequency: true,
category: { include: { parent: true } }
}
});
if (!proclamation) {
throw error(404, 'Proclamation not found');
}
return { proclamation };
};
@@ -0,0 +1,45 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
const categoryBreadcrumb = $derived(
data.proclamation.category.parent
? `${data.proclamation.category.parent.name} / ${data.proclamation.category.name}`
: data.proclamation.category.name
);
</script>
<div class="mx-auto flex h-full min-h-0 w-full max-w-3xl flex-col gap-6 p-6">
<a
href="/proclamations"
class="text-sm text-blue-600 hover:text-blue-800 hover:underline"
>
&larr; Back to Proclamations
</a>
<div>
<h1 class="text-2xl font-semibold text-gray-900">{data.proclamation.name}</h1>
<p class="mt-1 text-sm text-gray-500">{categoryBreadcrumb}</p>
</div>
<div class="flex flex-wrap gap-4">
<div class="rounded-lg border border-purple-200 bg-purple-50 px-4 py-2">
<p class="text-xs font-medium text-purple-600">Frequency</p>
<p class="text-sm font-semibold text-purple-900">
{data.proclamation.solfeggioFrequency.value} Hz &mdash;
{data.proclamation.solfeggioFrequency.name}
</p>
</div>
<div class="rounded-lg border border-gray-200 bg-gray-50 px-4 py-2">
<p class="text-xs font-medium text-gray-500">Price</p>
<p class="text-sm font-semibold text-gray-900">
${data.proclamation.price.toFixed(2)}
</p>
</div>
</div>
<div class="prose max-w-none rounded-lg border border-gray-200 bg-white p-6">
{@html data.proclamation.content}
</div>
</div>
@@ -0,0 +1,66 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { isHttpError } from '@sveltejs/kit';
import { mockProclamation } from '$lib/test-utils';
vi.mock('$lib/db', () => ({
prisma: {
proclamation: { findUnique: vi.fn() }
}
}));
import { prisma } from '$lib/db';
import { load } from './+page.server';
const findUnique = prisma.proclamation.findUnique as ReturnType<typeof vi.fn>;
describe('(protected)/admin/proclamations/[id] page server load', () => {
beforeEach(() => {
findUnique.mockReset();
});
it('throws 404 when proclamation is not found', async () => {
findUnique.mockResolvedValue(null);
const err = await Promise.resolve(
load({ params: { id: 'nonexistent' } } as any)
).catch((e: unknown) => e);
expect(isHttpError(err, 404)).toBe(true);
});
it('returns proclamation when found', async () => {
const proclamation = mockProclamation();
findUnique.mockResolvedValue(proclamation);
const result = (await load({ params: { id: 'proc-1' } } as any)) as any;
expect(result.proclamation).toEqual(proclamation);
});
it('queries by params.id with frequency and category (including parent)', async () => {
findUnique.mockResolvedValue(mockProclamation());
await load({ params: { id: 'proc-1' } } as any);
expect(findUnique).toHaveBeenCalledWith({
where: { id: 'proc-1' },
include: {
solfeggioFrequency: true,
category: { include: { parent: true } }
}
});
});
it('returns proclamation with parent category when present', async () => {
const proclamation = mockProclamation({
category: {
id: 2,
name: 'Inner Healing',
parentId: 1,
parent: { id: 1, name: 'Healing', parentId: null }
}
});
findUnique.mockResolvedValue(proclamation);
const result = (await load({ params: { id: 'proc-1' } } as any)) as any;
expect(result.proclamation.category.parent.name).toBe('Healing');
});
});