fix: surface saveUserPrefsAction errors, convert empty strings to null
- saveUserPrefsAction now returns {ok, error} instead of void
- empty string values converted to null before writing (Appwrite nullable attrs)
- getUserPrefs treats empty strings as absent (str() helper)
- ThemeCustomizer savePrefs wrapper shows toast on failure
- manually cleared corrupted colorTheme/tweakcnTheme empty strings from DB
This commit is contained in:
@@ -9,6 +9,7 @@ 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 { toast } from 'sonner'
|
||||
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'
|
||||
@@ -58,6 +59,12 @@ export function ThemeCustomizer({ open, onOpenChange, initialPrefs }: ThemeCusto
|
||||
})
|
||||
}, [sidebarConfig.variant, sidebarConfig.collapsible, sidebarConfig.side])
|
||||
|
||||
const savePrefs = (update: Parameters<typeof saveUserPrefsAction>[0]) => {
|
||||
saveUserPrefsAction(update).then((res) => {
|
||||
if (!res.ok) toast.error("Tercihler kaydedilemedi: " + res.error)
|
||||
})
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
setSelectedTheme("default")
|
||||
setSelectedTweakcnTheme("")
|
||||
@@ -68,7 +75,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 saveUserPrefsAction({ colorTheme: "default", tweakcnTheme: "", radius: "0.5rem", sidebarVariant: "inset", sidebarCollapsible: "offcanvas", sidebarSide: "left" })
|
||||
savePrefs({ colorTheme: "default", tweakcnTheme: "", radius: "0.5rem", sidebarVariant: "inset", sidebarCollapsible: "offcanvas", sidebarSide: "left" })
|
||||
}
|
||||
|
||||
const handleImport = (themeData: ImportedTheme) => {
|
||||
@@ -165,20 +172,20 @@ export function ThemeCustomizer({ open, onOpenChange, initialPrefs }: ThemeCusto
|
||||
setSelectedTheme(value)
|
||||
setSelectedTweakcnTheme("")
|
||||
saveLocalThemePrefs({ colorTheme: value, tweakcnTheme: "" })
|
||||
void saveUserPrefsAction({ colorTheme: value, tweakcnTheme: "" })
|
||||
savePrefs({ colorTheme: value, tweakcnTheme: "" })
|
||||
}}
|
||||
selectedTweakcnTheme={selectedTweakcnTheme}
|
||||
setSelectedTweakcnTheme={(value) => {
|
||||
setSelectedTweakcnTheme(value)
|
||||
setSelectedTheme("")
|
||||
saveLocalThemePrefs({ tweakcnTheme: value, colorTheme: "" })
|
||||
void saveUserPrefsAction({ tweakcnTheme: value, colorTheme: "" })
|
||||
savePrefs({ tweakcnTheme: value, colorTheme: "" })
|
||||
}}
|
||||
selectedRadius={selectedRadius}
|
||||
setSelectedRadius={(value) => {
|
||||
setSelectedRadius(value)
|
||||
saveLocalThemePrefs({ radius: value })
|
||||
void saveUserPrefsAction({ radius: value })
|
||||
savePrefs({ radius: value })
|
||||
}}
|
||||
setImportedTheme={setImportedTheme}
|
||||
onImportClick={handleImportClick}
|
||||
|
||||
@@ -30,11 +30,12 @@ export async function getUserPrefs(): Promise<UserPrefs> {
|
||||
if (result.rows.length === 0) return {};
|
||||
|
||||
const row = result.rows[0] as Record<string, unknown>;
|
||||
const str = (v: unknown) => (v && typeof v === "string" ? v : undefined);
|
||||
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,
|
||||
colorTheme: str(row.colorTheme),
|
||||
tweakcnTheme: str(row.tweakcnTheme),
|
||||
radius: str(row.radius),
|
||||
sidebarVariant: (row.sidebarVariant as UserPrefs["sidebarVariant"]) ?? undefined,
|
||||
sidebarCollapsible: (row.sidebarCollapsible as UserPrefs["sidebarCollapsible"]) ?? undefined,
|
||||
sidebarSide: (row.sidebarSide as UserPrefs["sidebarSide"]) ?? undefined,
|
||||
@@ -44,18 +45,21 @@ export async function getUserPrefs(): Promise<UserPrefs> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveUserPrefsAction(update: Partial<UserPrefs>): Promise<void> {
|
||||
export async function saveUserPrefsAction(
|
||||
update: Partial<UserPrefs>,
|
||||
): Promise<{ ok: boolean; error?: string }> {
|
||||
try {
|
||||
const { account } = await createSessionClient();
|
||||
const user = await account.get();
|
||||
const { tablesDB } = createAdminClient();
|
||||
|
||||
// Sadece tanımlı (undefined olmayan) alanları yaz
|
||||
// undefined → skip, "" → null (Appwrite rejects empty strings on nullable attrs)
|
||||
const clean: Record<string, unknown> = {};
|
||||
for (const [k, v] of Object.entries(update)) {
|
||||
if (v !== undefined) clean[k] = v;
|
||||
if (v === undefined) continue;
|
||||
clean[k] = v === "" ? null : v;
|
||||
}
|
||||
if (Object.keys(clean).length === 0) return;
|
||||
if (Object.keys(clean).length === 0) return { ok: true };
|
||||
|
||||
const existing = await tablesDB.listRows({
|
||||
databaseId: DATABASE_ID,
|
||||
@@ -85,7 +89,11 @@ export async function saveUserPrefsAction(update: Partial<UserPrefs>): Promise<v
|
||||
clean,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// best-effort
|
||||
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.error("[saveUserPrefsAction]", msg);
|
||||
return { ok: false, error: msg };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user