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) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
const session = await event.locals.auth();
|
const session = await event.locals.auth();
|
||||||
@@ -6,3 +11,39 @@ export const load: PageServerLoad = async (event) => {
|
|||||||
session
|
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 { LanguageToggle } from '$lib/components/ui/language-toggle';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import ColorPicker from '$lib/components/ui/ColorPicker.svelte';
|
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 { languageStore } from '$lib/stores/language.svelte';
|
||||||
import { addLocalWishlist } from '$lib/utils/localWishlists';
|
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);
|
const t = $derived(languageStore.t);
|
||||||
|
|
||||||
@@ -26,40 +27,6 @@
|
|||||||
let description = $state('');
|
let description = $state('');
|
||||||
let color = $state<string | null>(null);
|
let color = $state<string | null>(null);
|
||||||
let isCreating = $state(false);
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="min-h-screen flex items-center justify-center p-4">
|
<div class="min-h-screen flex items-center justify-center p-4">
|
||||||
@@ -84,9 +51,28 @@
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form
|
<form
|
||||||
onsubmit={(e) => {
|
method="POST"
|
||||||
e.preventDefault();
|
action="?/createWishlist"
|
||||||
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"
|
class="space-y-4"
|
||||||
>
|
>
|
||||||
@@ -94,6 +80,7 @@
|
|||||||
<Label for="title">{t.form.wishlistTitle}</Label>
|
<Label for="title">{t.form.wishlistTitle}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
|
name="title"
|
||||||
bind:value={title}
|
bind:value={title}
|
||||||
placeholder={t.form.wishlistTitlePlaceholder}
|
placeholder={t.form.wishlistTitlePlaceholder}
|
||||||
required
|
required
|
||||||
@@ -103,6 +90,7 @@
|
|||||||
<Label for="description">{t.form.descriptionOptional}</Label>
|
<Label for="description">{t.form.descriptionOptional}</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="description"
|
id="description"
|
||||||
|
name="description"
|
||||||
bind:value={description}
|
bind:value={description}
|
||||||
placeholder={t.form.descriptionPlaceholder}
|
placeholder={t.form.descriptionPlaceholder}
|
||||||
rows={3}
|
rows={3}
|
||||||
@@ -113,6 +101,7 @@
|
|||||||
<Label for="color">{t.form.wishlistColor}</Label>
|
<Label for="color">{t.form.wishlistColor}</Label>
|
||||||
<ColorPicker bind:color size="sm" />
|
<ColorPicker bind:color size="sm" />
|
||||||
</div>
|
</div>
|
||||||
|
<input type="hidden" name="color" value={color || ''} />
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" class="w-full" disabled={isCreating || !title.trim()}>
|
<Button type="submit" class="w-full" disabled={isCreating || !title.trim()}>
|
||||||
{isCreating ? t.wishlist.creating : t.wishlist.createWishlist}
|
{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