diff --git a/drizzle/relations.ts b/drizzle/relations.ts new file mode 100644 index 0000000..2bea2d5 --- /dev/null +++ b/drizzle/relations.ts @@ -0,0 +1,58 @@ +import { relations } from "drizzle-orm/relations"; +import { wishlists, items, user, savedWishlists, reservations, session, account } from "./schema"; + +export const itemsRelations = relations(items, ({one, many}) => ({ + wishlist: one(wishlists, { + fields: [items.wishlistId], + references: [wishlists.id] + }), + reservations: many(reservations), +})); + +export const wishlistsRelations = relations(wishlists, ({one, many}) => ({ + items: many(items), + user: one(user, { + fields: [wishlists.userId], + references: [user.id] + }), + savedWishlists: many(savedWishlists), +})); + +export const userRelations = relations(user, ({many}) => ({ + wishlists: many(wishlists), + savedWishlists: many(savedWishlists), + sessions: many(session), + accounts: many(account), +})); + +export const savedWishlistsRelations = relations(savedWishlists, ({one}) => ({ + user: one(user, { + fields: [savedWishlists.userId], + references: [user.id] + }), + wishlist: one(wishlists, { + fields: [savedWishlists.wishlistId], + references: [wishlists.id] + }), +})); + +export const reservationsRelations = relations(reservations, ({one}) => ({ + item: one(items, { + fields: [reservations.itemId], + references: [items.id] + }), +})); + +export const sessionRelations = relations(session, ({one}) => ({ + user: one(user, { + fields: [session.userId], + references: [user.id] + }), +})); + +export const accountRelations = relations(account, ({one}) => ({ + user: one(user, { + fields: [account.userId], + references: [user.id] + }), +})); \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts new file mode 100644 index 0000000..2114757 --- /dev/null +++ b/drizzle/schema.ts @@ -0,0 +1,137 @@ +import { pgTable, foreignKey, text, numeric, boolean, timestamp, unique, primaryKey } from "drizzle-orm/pg-core" +import { sql } from "drizzle-orm" + + + +export const items = pgTable("items", { + id: text().primaryKey().notNull(), + wishlistId: text("wishlist_id").notNull(), + title: text().notNull(), + description: text(), + link: text(), + imageUrl: text("image_url"), + price: numeric({ precision: 10, scale: 2 }), + currency: text().default('DKK'), + color: text(), + order: numeric().default('0').notNull(), + isReserved: boolean("is_reserved").default(false).notNull(), + createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(), +}, (table) => [ + foreignKey({ + columns: [table.wishlistId], + foreignColumns: [wishlists.id], + name: "items_wishlist_id_wishlists_id_fk" + }).onDelete("cascade"), +]); + +export const wishlists = pgTable("wishlists", { + id: text().primaryKey().notNull(), + userId: text("user_id"), + title: text().notNull(), + description: text(), + ownerToken: text("owner_token").notNull(), + publicToken: text("public_token").notNull(), + isFavorite: boolean("is_favorite").default(false).notNull(), + color: text(), + endDate: timestamp("end_date", { mode: 'string' }), + createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp("updated_at", { mode: 'string' }).defaultNow().notNull(), + theme: text().default('none'), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "wishlists_user_id_user_id_fk" + }).onDelete("set null"), + unique("wishlists_owner_token_unique").on(table.ownerToken), + unique("wishlists_public_token_unique").on(table.publicToken), +]); + +export const savedWishlists = pgTable("saved_wishlists", { + id: text().primaryKey().notNull(), + userId: text("user_id").notNull(), + wishlistId: text("wishlist_id").notNull(), + isFavorite: boolean("is_favorite").default(false).notNull(), + createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(), + ownerToken: text("owner_token"), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "saved_wishlists_user_id_user_id_fk" + }).onDelete("cascade"), + foreignKey({ + columns: [table.wishlistId], + foreignColumns: [wishlists.id], + name: "saved_wishlists_wishlist_id_wishlists_id_fk" + }).onDelete("cascade"), +]); + +export const user = pgTable("user", { + id: text().primaryKey().notNull(), + name: text(), + email: text(), + emailVerified: timestamp({ mode: 'string' }), + image: text(), + password: text(), + username: text(), + dashboardTheme: text("dashboard_theme").default('none'), +}, (table) => [ + unique("user_email_unique").on(table.email), + unique("user_username_unique").on(table.username), +]); + +export const reservations = pgTable("reservations", { + id: text().primaryKey().notNull(), + itemId: text("item_id").notNull(), + reserverName: text("reserver_name"), + createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(), +}, (table) => [ + foreignKey({ + columns: [table.itemId], + foreignColumns: [items.id], + name: "reservations_item_id_items_id_fk" + }).onDelete("cascade"), +]); + +export const session = pgTable("session", { + sessionToken: text().primaryKey().notNull(), + userId: text().notNull(), + expires: timestamp({ mode: 'string' }).notNull(), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "session_userId_user_id_fk" + }).onDelete("cascade"), +]); + +export const verificationToken = pgTable("verificationToken", { + identifier: text().notNull(), + token: text().notNull(), + expires: timestamp({ mode: 'string' }).notNull(), +}, (table) => [ + primaryKey({ columns: [table.identifier, table.token], name: "verificationToken_identifier_token_pk"}), +]); + +export const account = pgTable("account", { + userId: text().notNull(), + type: text().notNull(), + provider: text().notNull(), + providerAccountId: text().notNull(), + refreshToken: text("refresh_token"), + accessToken: text("access_token"), + expiresAt: numeric("expires_at"), + tokenType: text("token_type"), + scope: text(), + idToken: text("id_token"), + sessionState: text("session_state"), +}, (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [user.id], + name: "account_userId_user_id_fk" + }).onDelete("cascade"), + primaryKey({ columns: [table.provider, table.providerAccountId], name: "account_provider_providerAccountId_pk"}), +]); diff --git a/src/lib/components/dashboard/LocalWishlistsSection.svelte b/src/lib/components/dashboard/LocalWishlistsSection.svelte index 1328a39..ba96c01 100644 --- a/src/lib/components/dashboard/LocalWishlistsSection.svelte +++ b/src/lib/components/dashboard/LocalWishlistsSection.svelte @@ -43,15 +43,25 @@ items: [] // We don't have item data in localStorage })); }); + + // Description depends on authentication status + const sectionDescription = $derived(() => { + if (isAuthenticated) { + return t.dashboard.localWishlistsAuthDescription || "Wishlists stored in your browser that haven't been claimed yet."; + } + return t.dashboard.localWishlistsDescription || "Wishlists stored in your browser. Sign in to save them permanently."; + }); -{#if localWishlists.length > 0} - + {#snippet actions(wishlist, unlocked)}
- {#if isAuthenticated} - - {/if} + {#if isAuthenticated} diff --git a/src/routes/dashboard/+page.server.ts b/src/routes/dashboard/+page.server.ts index 358e14e..ff67458 100644 --- a/src/routes/dashboard/+page.server.ts +++ b/src/routes/dashboard/+page.server.ts @@ -17,6 +17,11 @@ export const load: PageServerLoad = async (event) => { }; } + // Fetch user with theme + const user = await db.query.users.findFirst({ + where: eq(users.id, session.user.id) + }); + const userWishlists = await db.query.wishlists.findMany({ where: eq(wishlists.userId, session.user.id), with: { @@ -57,7 +62,7 @@ export const load: PageServerLoad = async (event) => { })); return { - user: session.user, + user: user, wishlists: userWishlists, savedWishlists: savedWithAccess, isAuthenticated: true diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index 3eba68c..c02fc1d 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -11,6 +11,30 @@ let { data }: { data: PageData } = $props(); + // For anonymous users, get theme from localStorage + function getInitialTheme() { + if (data.isAuthenticated) { + return data.user?.dashboardTheme || 'none'; + } else { + // Anonymous user - get from localStorage + if (typeof window !== 'undefined') { + return localStorage.getItem('dashboardTheme') || 'none'; + } + return 'none'; + } + } + + let currentTheme = $state(getInitialTheme()); + + // Save to localStorage when theme changes for anonymous users + function handleThemeUpdate(theme: string | null) { + currentTheme = theme || 'none'; + + if (!data.isAuthenticated && typeof window !== 'undefined') { + localStorage.setItem('dashboardTheme', currentTheme); + } + } + const t = $derived(languageStore.t); // Only owned wishlists for "My Wishlists" @@ -34,11 +58,13 @@ }); - + diff --git a/src/routes/wishlist/[token]/edit/+page.svelte b/src/routes/wishlist/[token]/edit/+page.svelte index 2f09791..dbc2d39 100644 --- a/src/routes/wishlist/[token]/edit/+page.svelte +++ b/src/routes/wishlist/[token]/edit/+page.svelte @@ -22,6 +22,7 @@ let addFormElement = $state(null); let editFormElement = $state(null); let searchQuery = $state(""); + let currentTheme = $state(data.wishlist.theme || 'none'); let items = $state([]); @@ -105,9 +106,14 @@ items = newItems; await handleReorder(newItems); } + + async function handleThemeUpdate(theme: string | null) { + currentTheme = theme || 'none'; + await wishlistUpdates.updateTheme(theme); + } - +