refactor: convert wishlist creation from API to form action

This commit is contained in:
Rasmus Q
2026-03-16 14:37:33 +00:00
parent 563ee5699b
commit 6d3a418525
3 changed files with 70 additions and 92 deletions

View File

@@ -1,4 +1,9 @@
import type { PageServerLoad } from './$types';
import type { PageServerLoad, Actions } from './$types';
import { db } from '$lib/server/db';
import { wishlists } from '$lib/db/schema';
import { createId } from '@paralleldrive/cuid2';
import { fail } from '@sveltejs/kit';
import { wishlistSchema } from '$lib/server/validation';
export const load: PageServerLoad = async (event) => {
const session = await event.locals.auth();
@@ -6,3 +11,39 @@ export const load: PageServerLoad = async (event) => {
session
};
};
export const actions: Actions = {
createWishlist: async ({ request, locals }) => {
const formData = await request.formData();
const rawData = Object.fromEntries(formData);
const result = wishlistSchema.safeParse(rawData);
if (!result.success) {
return fail(400, { success: false, error: result.error.errors.map(e => e.message).join(', ') });
}
const session = await locals.auth();
const userId = session?.user?.id || null;
const ownerToken = createId();
const publicToken = createId();
const [wishlist] = await db
.insert(wishlists)
.values({
...result.data,
ownerToken,
publicToken,
userId
})
.returning();
return {
success: true,
ownerToken,
publicToken,
title: wishlist.title,
createdAt: wishlist.createdAt
};
}
};

View File

@@ -14,11 +14,12 @@
import { LanguageToggle } from '$lib/components/ui/language-toggle';
import { goto } from '$app/navigation';
import ColorPicker from '$lib/components/ui/ColorPicker.svelte';
import type { PageData } from './$types';
import type { PageData, ActionData } from './$types';
import { languageStore } from '$lib/stores/language.svelte';
import { addLocalWishlist } from '$lib/utils/localWishlists';
import { enhance } from '$app/forms';
let { data }: { data: PageData } = $props();
let { data, form }: { data: PageData; form: ActionData } = $props();
const t = $derived(languageStore.t);
@@ -26,40 +27,6 @@
let description = $state('');
let color = $state<string | null>(null);
let isCreating = $state(false);
async function createWishlist() {
if (!title.trim()) return;
isCreating = true;
try {
const response = await fetch('/api/wishlists', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, description, color })
});
if (response.ok) {
const result = await response.json();
const { ownerToken, publicToken, title: wishlistTitle, createdAt } = result;
// If user is not authenticated, save to localStorage
if (!data.session?.user) {
addLocalWishlist({
ownerToken,
publicToken,
title: wishlistTitle,
createdAt
});
}
goto(`/wishlist/${ownerToken}/edit`);
}
} catch (error) {
console.error('Failed to create wishlist:', error);
} finally {
isCreating = false;
}
}
</script>
<div class="min-h-screen flex items-center justify-center p-4">
@@ -84,9 +51,28 @@
</CardHeader>
<CardContent>
<form
onsubmit={(e) => {
e.preventDefault();
createWishlist();
method="POST"
action="?/createWishlist"
use:enhance={() => {
isCreating = true;
return async ({ result, update }) => {
if (result.type === 'success' && result.data) {
const data = result.data as { ownerToken: string; publicToken: string; title: string; createdAt: string };
// If user is not authenticated, save to localStorage
if (!data?.session?.user) {
addLocalWishlist({
ownerToken: data.ownerToken,
publicToken: data.publicToken,
title: data.title,
createdAt: new Date(data.createdAt)
});
}
goto(`/wishlist/${data.ownerToken}/edit`);
} else {
await update();
isCreating = false;
}
};
}}
class="space-y-4"
>
@@ -94,6 +80,7 @@
<Label for="title">{t.form.wishlistTitle}</Label>
<Input
id="title"
name="title"
bind:value={title}
placeholder={t.form.wishlistTitlePlaceholder}
required
@@ -103,6 +90,7 @@
<Label for="description">{t.form.descriptionOptional}</Label>
<Textarea
id="description"
name="description"
bind:value={description}
placeholder={t.form.descriptionPlaceholder}
rows={3}
@@ -113,6 +101,7 @@
<Label for="color">{t.form.wishlistColor}</Label>
<ColorPicker bind:color size="sm" />
</div>
<input type="hidden" name="color" value={color || ''} />
</div>
<Button type="submit" class="w-full" disabled={isCreating || !title.trim()}>
{isCreating ? t.wishlist.creating : t.wishlist.createWishlist}

View File

@@ -1,52 +0,0 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { db } from '$lib/server/db';
import { wishlists } from '$lib/db/schema';
import { createId } from '@paralleldrive/cuid2';
import { sanitizeString, sanitizeColor } from '$lib/server/validation';
export const POST: RequestHandler = async ({ request, locals }) => {
const body = await request.json();
let title: string | null;
let description: string | null;
let color: string | null;
try {
title = sanitizeString(body.title, 200);
description = sanitizeString(body.description, 2000);
color = sanitizeColor(body.color);
} catch {
return json({ error: 'Invalid input' }, { status: 400 });
}
if (!title) {
return json({ error: 'Title is required' }, { status: 400 });
}
const session = await locals.auth();
const userId = session?.user?.id || null;
const ownerToken = createId();
const publicToken = createId();
const [wishlist] = await db
.insert(wishlists)
.values({
title,
description,
color,
ownerToken,
publicToken,
userId
})
.returning();
return json({
ownerToken,
publicToken,
id: wishlist.id,
title: wishlist.title,
createdAt: wishlist.createdAt
});
};