diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte
index 11e850d..5983750 100644
--- a/src/lib/components/layout/Sidebar.svelte
+++ b/src/lib/components/layout/Sidebar.svelte
@@ -58,6 +58,11 @@
label: 'Gallery',
icon: 'M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z'
},
+ {
+ href: '/feature-requests',
+ label: 'Requests',
+ icon: 'M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z'
+ },
{
href: '/settings',
label: 'Settings',
diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts
index 2921b49..645eae1 100644
--- a/src/lib/server/db/schema.ts
+++ b/src/lib/server/db/schema.ts
@@ -339,3 +339,16 @@ export const todos = pgTable(
check('todos_priority_check', sql`${table.priority} IN (0, 1, 2, 3)`)
]
);
+
+// ─── Feature Requests ───────────────────────────────────────────────
+
+export const featureRequests = pgTable('feature_requests', {
+ id: uuid('id').defaultRandom().primaryKey(),
+ title: text('title').notNull(),
+ description: text('description'),
+ status: text('status').notNull().default('open'),
+ votes: integer('votes').notNull().default(0),
+ createdBy: text('created_by'),
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
+});
diff --git a/src/routes/(app)/feature-requests/+page.server.ts b/src/routes/(app)/feature-requests/+page.server.ts
new file mode 100644
index 0000000..737b28a
--- /dev/null
+++ b/src/routes/(app)/feature-requests/+page.server.ts
@@ -0,0 +1,59 @@
+import type { PageServerLoad, Actions } from './$types';
+import { db } from '$lib/server/db/index.js';
+import { featureRequests } from '$lib/server/db/schema.js';
+import { eq, desc, count } from 'drizzle-orm';
+import { fail } from '@sveltejs/kit';
+
+export const load: PageServerLoad = async () => {
+ const requests = await db
+ .select()
+ .from(featureRequests)
+ .orderBy(desc(featureRequests.votes), desc(featureRequests.createdAt));
+
+ return { requests };
+};
+
+export const actions: Actions = {
+ create: async ({ request, locals }) => {
+ const formData = await request.formData();
+ const title = (formData.get('title') as string)?.trim();
+ const description = (formData.get('description') as string)?.trim();
+ if (!title) return fail(400, { error: 'Title is required' });
+
+ await db.insert(featureRequests).values({
+ title,
+ description: description || null,
+ createdBy: locals.user?.displayName ?? locals.user?.email ?? 'Unknown'
+ });
+
+ return { created: true };
+ },
+
+ vote: async ({ request }) => {
+ const formData = await request.formData();
+ const id = formData.get('id') as string;
+
+ const [req] = await db.select({ votes: featureRequests.votes }).from(featureRequests).where(eq(featureRequests.id, id));
+ if (req) {
+ await db.update(featureRequests).set({ votes: req.votes + 1 }).where(eq(featureRequests.id, id));
+ }
+
+ return { voted: true };
+ },
+
+ updateStatus: async ({ request }) => {
+ const formData = await request.formData();
+ const id = formData.get('id') as string;
+ const status = formData.get('status') as string;
+
+ await db.update(featureRequests).set({ status, updatedAt: new Date() }).where(eq(featureRequests.id, id));
+ return { statusUpdated: true };
+ },
+
+ delete: async ({ request }) => {
+ const formData = await request.formData();
+ const id = formData.get('id') as string;
+ await db.delete(featureRequests).where(eq(featureRequests.id, id));
+ return { deleted: true };
+ }
+};
diff --git a/src/routes/(app)/feature-requests/+page.svelte b/src/routes/(app)/feature-requests/+page.svelte
new file mode 100644
index 0000000..1eba639
--- /dev/null
+++ b/src/routes/(app)/feature-requests/+page.svelte
@@ -0,0 +1,129 @@
+
+
+
Suggest and vote on new features.
+No feature requests yet. Be the first to suggest one!
+{req.description}
+ {/if} +