refactor: convert wishlist creation from API to form action
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user