aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/server/db/index.ts10
-rw-r--r--src/lib/server/db/schema.ts8
-rw-r--r--src/routes/+page.server.ts53
-rw-r--r--src/routes/+page.svelte95
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>