diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/server/db/index.ts | 10 | ||||
| -rw-r--r-- | src/lib/server/db/schema.ts | 8 | ||||
| -rw-r--r-- | src/routes/+page.server.ts | 53 | ||||
| -rw-r--r-- | src/routes/+page.svelte | 95 |
4 files changed, 164 insertions, 2 deletions
diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts new file mode 100644 index 0000000..cfbbe66 --- /dev/null +++ b/src/lib/server/db/index.ts @@ -0,0 +1,10 @@ +import { drizzle } from 'drizzle-orm/libsql'; +import { createClient } from '@libsql/client'; +import { env } from '$env/dynamic/private'; +import * as schema from './schema'; + +const client = createClient({ + url: env.DATABASE_URL ?? 'file:./data.db', +}); + +export const db = drizzle(client, { schema }); diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts new file mode 100644 index 0000000..a8527fd --- /dev/null +++ b/src/lib/server/db/schema.ts @@ -0,0 +1,8 @@ +import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; + +export const groceries = sqliteTable('groceries', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + checked: integer('checked', { mode: 'boolean' }).notNull().default(false), + createdAt: text('created_at').notNull(), +}); diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..e6f3b0f --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,53 @@ +import { fail } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { groceries } from '$lib/server/db/schema'; +import { eq, desc } from 'drizzle-orm'; +import type { Actions, PageServerLoad } from './$types'; + +export const load: PageServerLoad = async () => { + const items = await db + .select() + .from(groceries) + .where(eq(groceries.checked, false)) + .orderBy(desc(groceries.createdAt)); + + return { items }; +}; + +export const actions: Actions = { + add: async ({ request }) => { + const data = await request.formData(); + const name = data.get('name')?.toString().trim(); + + if (!name) { + return fail(400, { error: 'Name is required' }); + } + + await db.insert(groceries).values({ + name, + createdAt: new Date().toISOString(), + }); + + return { success: true }; + }, + + toggle: async ({ request }) => { + const data = await request.formData(); + const id = Number(data.get('id')); + + const [item] = await db + .select() + .from(groceries) + .where(eq(groceries.id, id)); + if (!item) { + return fail(404, { error: 'Item not found' }); + } + + await db + .update(groceries) + .set({ checked: !item.checked }) + .where(eq(groceries.id, id)); + + return { success: true }; + }, +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 1b6e963..fc56140 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,6 +1,97 @@ <script> import '../app.css'; + import { enhance } from '$app/forms'; + + let { data, form } = $props(); + let name = $state(''); </script> -<h1>Home</h1> -<p>Welcome! Groceries and chores coming soon.</p> +<h1>Groceries</h1> + +<form class="add-form" method="POST" action="?/add" use:enhance> + <input + type="text" + name="name" + bind:value={name} + placeholder="Add an item..." + autocomplete="off" + /> + <button type="submit">Add</button> +</form> + +{#if form?.error} + <p class="error">{form.error}</p> +{/if} + +<hr /> + +<form method="POST" action="?/toggle" use:enhance> + <ul class="item-list"> + {#each data?.items ?? [] as item (item.id)} + <li> + <button name="id" value={item.id} type="submit"> + {item.name} + </button> + </li> + {/each} + </ul> +</form> + +<style> + .add-form { + display: flex; + gap: 0.5rem; + margin-bottom: 1rem; + } + + input { + flex: 1; + padding: 0.75rem; + border: 1px solid #ccc; + border-radius: 0.5rem; + font-size: 1rem; + } + + .add-form button[type='submit'] { + padding: 0.75rem 1.25rem; + border: none; + border-radius: 0.5rem; + background: #333; + color: #fff; + font-size: 1rem; + } + + .error { + color: #d00; + margin-top: -0.5rem; + margin-bottom: 1rem; + } + + hr { + border: none; + border-top: 1px solid #ddd; + margin: 1.5rem 0; + } + + .item-list { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.25rem; + } + + .item-list li button { + width: 100%; + text-align: left; + padding: 0.75rem; + border: 1px solid #e0e0e0; + border-radius: 0.5rem; + background: #fff; + font-size: 1rem; + transition: background 0.15s; + } + + .item-list li button:hover { + background: #f5f5f5; + } +</style> |
