diff --git a/src/app/(dashboard)/dashboard-shell.tsx b/src/app/(dashboard)/dashboard-shell.tsx index b873a27..749a2b0 100644 --- a/src/app/(dashboard)/dashboard-shell.tsx +++ b/src/app/(dashboard)/dashboard-shell.tsx @@ -9,7 +9,7 @@ import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar"; import { ThemeCustomizer, ThemeCustomizerTrigger } from "@/components/theme-customizer"; import { PrefsInitializer } from "@/components/theme-customizer/prefs-initializer"; import { useSidebarConfig } from "@/hooks/use-sidebar-config"; -import type { ThemePrefs } from "@/lib/appwrite/theme-prefs-actions"; +import type { UserPrefs as ThemePrefs } from "@/lib/appwrite/user-prefs-actions"; export type ShellUser = { id: string; diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index 27be959..c61c4b2 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -2,8 +2,8 @@ import { redirect } from "next/navigation"; import { getActiveContext } from "@/lib/appwrite/active-context"; import { getLogoUrl } from "@/lib/appwrite/storage"; -import { createSessionClient } from "@/lib/appwrite/server"; -import type { ThemePrefs } from "@/lib/appwrite/theme-prefs-actions"; +import { getUserPrefs } from "@/lib/appwrite/user-prefs-actions"; +import type { UserPrefs as ThemePrefs } from "@/lib/appwrite/user-prefs-actions"; import { DashboardShell } from "./dashboard-shell"; export default async function DashboardLayout({ @@ -14,15 +14,7 @@ export default async function DashboardLayout({ const ctx = await getActiveContext(); if (!ctx) redirect("/onboarding"); - let themePrefs: ThemePrefs = {}; - try { - const { account } = await createSessionClient(); - const raw = await account.getPrefs(); - // getPrefs returns an Appwrite prototype object — must be a plain object for Server→Client prop - themePrefs = JSON.parse(JSON.stringify(raw)) as ThemePrefs; - } catch { - // use defaults if prefs unavailable - } + const themePrefs: ThemePrefs = await getUserPrefs(); const company = { id: ctx.tenantId, diff --git a/src/components/theme-customizer/index.tsx b/src/components/theme-customizer/index.tsx index 519f20a..c719243 100644 --- a/src/components/theme-customizer/index.tsx +++ b/src/components/theme-customizer/index.tsx @@ -9,8 +9,8 @@ import { useThemeManager } from '@/hooks/use-theme-manager' import { useTheme } from '@/hooks/use-theme' import { useSidebarConfig } from '@/contexts/sidebar-context' import { tweakcnThemes } from '@/config/theme-data' -import { saveThemePrefsAction } from '@/lib/appwrite/theme-prefs-actions' -import type { ThemePrefs } from '@/lib/appwrite/theme-prefs-actions' +import { saveUserPrefsAction } from '@/lib/appwrite/user-prefs-actions' +import type { UserPrefs as ThemePrefs } from '@/lib/appwrite/user-prefs-actions' import { getLocalThemePrefs, saveLocalThemePrefs } from '@/lib/local-theme-prefs' import { ThemeTab } from './theme-tab' import { LayoutTab } from './layout-tab' @@ -68,7 +68,7 @@ export function ThemeCustomizer({ open, onOpenChange, initialPrefs }: ThemeCusto applyRadius("0.5rem") updateSidebarConfig({ variant: "inset", collapsible: "offcanvas", side: "left" }) saveLocalThemePrefs({ colorTheme: "default", tweakcnTheme: "", radius: "0.5rem", sidebarVariant: "inset", sidebarCollapsible: "offcanvas", sidebarSide: "left" }) - void saveThemePrefsAction({ colorTheme: "default", tweakcnTheme: "", radius: "0.5rem", sidebarVariant: "inset", sidebarCollapsible: "offcanvas", sidebarSide: "left" }) + void saveUserPrefsAction({ colorTheme: "default", tweakcnTheme: "", radius: "0.5rem", sidebarVariant: "inset", sidebarCollapsible: "offcanvas", sidebarSide: "left" }) } const handleImport = (themeData: ImportedTheme) => { @@ -165,20 +165,20 @@ export function ThemeCustomizer({ open, onOpenChange, initialPrefs }: ThemeCusto setSelectedTheme(value) setSelectedTweakcnTheme("") saveLocalThemePrefs({ colorTheme: value, tweakcnTheme: "" }) - void saveThemePrefsAction({ colorTheme: value, tweakcnTheme: "" }) + void saveUserPrefsAction({ colorTheme: value, tweakcnTheme: "" }) }} selectedTweakcnTheme={selectedTweakcnTheme} setSelectedTweakcnTheme={(value) => { setSelectedTweakcnTheme(value) setSelectedTheme("") saveLocalThemePrefs({ tweakcnTheme: value, colorTheme: "" }) - void saveThemePrefsAction({ tweakcnTheme: value, colorTheme: "" }) + void saveUserPrefsAction({ tweakcnTheme: value, colorTheme: "" }) }} selectedRadius={selectedRadius} setSelectedRadius={(value) => { setSelectedRadius(value) saveLocalThemePrefs({ radius: value }) - void saveThemePrefsAction({ radius: value }) + void saveUserPrefsAction({ radius: value }) }} setImportedTheme={setImportedTheme} onImportClick={handleImportClick} diff --git a/src/components/theme-customizer/prefs-initializer.tsx b/src/components/theme-customizer/prefs-initializer.tsx index d3c9fbd..adc6232 100644 --- a/src/components/theme-customizer/prefs-initializer.tsx +++ b/src/components/theme-customizer/prefs-initializer.tsx @@ -6,7 +6,7 @@ import { useSidebarConfig } from "@/contexts/sidebar-context"; import { useTheme } from "@/hooks/use-theme"; import { useThemeManager } from "@/hooks/use-theme-manager"; import { tweakcnThemes } from "@/config/theme-data"; -import type { ThemePrefs } from "@/lib/appwrite/theme-prefs-actions"; +import type { UserPrefs as ThemePrefs } from "@/lib/appwrite/user-prefs-actions"; import { getLocalThemePrefs } from "@/lib/local-theme-prefs"; export function PrefsInitializer({ prefs }: { prefs: ThemePrefs }) { diff --git a/src/lib/appwrite/schema.ts b/src/lib/appwrite/schema.ts index eef4010..16b286a 100644 --- a/src/lib/appwrite/schema.ts +++ b/src/lib/appwrite/schema.ts @@ -29,6 +29,7 @@ export const TABLES = { leadActivities: "lead_activities", passwordResets: "password_resets", attachments: "attachments", + userPreferences: "user_preferences", } as const; export type TableId = (typeof TABLES)[keyof typeof TABLES]; diff --git a/src/lib/appwrite/theme-prefs-actions.ts b/src/lib/appwrite/theme-prefs-actions.ts deleted file mode 100644 index c5d20d0..0000000 --- a/src/lib/appwrite/theme-prefs-actions.ts +++ /dev/null @@ -1,23 +0,0 @@ -"use server"; - -import { createSessionClient } from "@/lib/appwrite/server"; - -export interface ThemePrefs { - theme?: "dark" | "light" | "system"; - colorTheme?: string; - tweakcnTheme?: string; - radius?: string; - sidebarVariant?: "sidebar" | "floating" | "inset"; - sidebarCollapsible?: "offcanvas" | "icon" | "none"; - sidebarSide?: "left" | "right"; -} - -export async function saveThemePrefsAction(update: Partial): Promise { - try { - const { account } = await createSessionClient(); - const existing = await account.getPrefs>(); - await account.updatePrefs({ ...existing, ...update }); - } catch { - // best-effort — UI still works without persistence - } -} diff --git a/src/lib/appwrite/user-prefs-actions.ts b/src/lib/appwrite/user-prefs-actions.ts new file mode 100644 index 0000000..e3eb364 --- /dev/null +++ b/src/lib/appwrite/user-prefs-actions.ts @@ -0,0 +1,91 @@ +"use server"; + +import { ID, Permission, Query, Role } from "node-appwrite"; + +import { createAdminClient, createSessionClient } from "./server"; +import { DATABASE_ID, TABLES } from "./schema"; + +export interface UserPrefs { + theme?: "dark" | "light" | "system"; + colorTheme?: string; + tweakcnTheme?: string; + radius?: string; + sidebarVariant?: "sidebar" | "floating" | "inset"; + sidebarCollapsible?: "offcanvas" | "icon" | "none"; + sidebarSide?: "left" | "right"; +} + +export async function getUserPrefs(): Promise { + try { + const { account } = await createSessionClient(); + const user = await account.get(); + const { tablesDB } = createAdminClient(); + + const result = await tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.userPreferences, + queries: [Query.equal("userId", user.$id), Query.limit(1)], + }); + + if (result.rows.length === 0) return {}; + + const row = result.rows[0] as Record; + return { + theme: (row.theme as UserPrefs["theme"]) ?? undefined, + colorTheme: (row.colorTheme as string) ?? undefined, + tweakcnTheme: (row.tweakcnTheme as string) ?? undefined, + radius: (row.radius as string) ?? undefined, + sidebarVariant: (row.sidebarVariant as UserPrefs["sidebarVariant"]) ?? undefined, + sidebarCollapsible: (row.sidebarCollapsible as UserPrefs["sidebarCollapsible"]) ?? undefined, + sidebarSide: (row.sidebarSide as UserPrefs["sidebarSide"]) ?? undefined, + }; + } catch { + return {}; + } +} + +export async function saveUserPrefsAction(update: Partial): Promise { + try { + const { account } = await createSessionClient(); + const user = await account.get(); + const { tablesDB } = createAdminClient(); + + // Sadece tanımlı (undefined olmayan) alanları yaz + const clean: Record = {}; + for (const [k, v] of Object.entries(update)) { + if (v !== undefined) clean[k] = v; + } + if (Object.keys(clean).length === 0) return; + + const existing = await tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.userPreferences, + queries: [Query.equal("userId", user.$id), Query.limit(1)], + }); + + const perms = [ + Permission.read(Role.user(user.$id)), + Permission.update(Role.user(user.$id)), + Permission.delete(Role.user(user.$id)), + ]; + + if (existing.rows.length === 0) { + await tablesDB.createRow( + DATABASE_ID, + TABLES.userPreferences, + ID.unique(), + { userId: user.$id, ...clean }, + perms, + ); + } else { + await tablesDB.updateRow( + DATABASE_ID, + TABLES.userPreferences, + existing.rows[0].$id, + clean, + ); + } + } catch { + // best-effort + } +}