"use client"; import { useEffect, useRef, useState } from "react"; import Image from "next/image"; import { Blocks, Brain, Download, Globe2, ImageIcon, Key, Languages, Monitor, Moon, Palette, Save, Shield, Sun, Trash2, Upload, User, } from "lucide-react"; import { loadSettings, commitI18nImportAction, createLocaleAction, exportI18nAction, previewI18nImportAction, removeBrandingAsset, resetUiTranslationAction, saveAiSettings, saveColorMode, saveGeneralSettings, saveLanguagePreference, saveUiTranslationAction, setDefaultLocaleAction, updateLocaleStatusAction, updatePassword, updateProfile, } from "./actions"; import { Button, Card, CardContent, Input, Label, RadioGroup, RadioGroupItem, Textarea, } from "poyraz-ui/atoms"; import { toast } from "poyraz-ui/molecules"; import { applyColorMode } from "@/components/theme/color-mode-sync"; import { LocalizedFields, type LocalizedFieldValues } from "@/components/i18n/localized-fields"; import { isColorMode, type ColorMode } from "@/lib/color-mode"; import { contentTranslationRegistry } from "@/lib/i18n/content"; type AiProvider = "groq" | "ollama" | "openai" | "gemini"; type BrandingAsset = "lightLogo" | "darkLogo" | "favicon"; type LocaleStatus = "draft" | "active" | "archived" | "test"; type LocaleRecord = { code: string; name: string; nativeName: string; status: LocaleStatus; fallbackLocale: string | null; textDirection: "ltr" | "rtl"; builtIn: boolean; sortOrder: number; }; type TranslationRow = { locale: string; namespace: string; key: string; value: string }; type ContentTranslationRow = { entityType: string; entityId: string; field: string; locale: string; value: string }; type ReferenceKey = { key: string; namespace: string; translationKey: string; tr: string; en: string; parityOk: boolean; }; type TranslationCompletion = { locale: string; translated: number; total: number; percent: number; missingKeys: string[]; }; const colorModeOptions = [ { value: "light", label: "Açık", description: "Her zaman aydınlık renk paletini kullanır.", icon: Sun, }, { value: "dark", label: "Koyu", description: "Her zaman koyu renk paletini kullanır.", icon: Moon, }, { value: "system", label: "Sistem", description: "Cihazınızın görünüm tercihini otomatik takip eder.", icon: Monitor, }, ] satisfies Array<{ value: ColorMode; label: string; description: string; icon: typeof Sun; }>; export default function SettingsPage() { const [activeTab, setActiveTab] = useState("Genel"); // Profile States const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [avatarUrl, setAvatarUrl] = useState(""); // Security States const formRef = useRef(null); // AI States const [aiProvider, setAiProvider] = useState("gemini"); const [apiKey, setApiKey] = useState(""); const [hasApiKey, setHasApiKey] = useState(false); const [colorMode, setColorMode] = useState("system"); const [isSavingColorMode, setIsSavingColorMode] = useState(false); const [workspaceName, setWorkspaceName] = useState("Neta"); const [metaTitle, setMetaTitle] = useState("Neta"); const [shortName, setShortName] = useState("Neta"); const [primaryColor, setPrimaryColor] = useState("#C81E1E"); const [assetUrls, setAssetUrls] = useState>({ lightLogo: "", darkLogo: "", favicon: "", }); const [pendingAssetUrls, setPendingAssetUrls] = useState>({ lightLogo: "", darkLogo: "", favicon: "", }); const [customAssets, setCustomAssets] = useState>({ lightLogo: false, darkLogo: false, favicon: false, }); const [isSavingBranding, setIsSavingBranding] = useState(false); const [language, setLanguage] = useState("tr"); const [locales, setLocales] = useState([]); const [defaultLocale, setDefaultLocale] = useState("tr"); const [catalogVersion, setCatalogVersion] = useState(1); const [translations, setTranslations] = useState([]); const [brandingContentTranslations, setBrandingContentTranslations] = useState({}); const [referenceKeys, setReferenceKeys] = useState([]); const [completion, setCompletion] = useState([]); const [newLocale, setNewLocale] = useState({ code: "fr", name: "French", nativeName: "Français", fallbackLocale: "en", textDirection: "ltr" as "ltr" | "rtl", }); const [selectedLocale, setSelectedLocale] = useState("fr"); const [selectedNamespace, setSelectedNamespace] = useState("navigation"); const [translationSearch, setTranslationSearch] = useState(""); const [editingValues, setEditingValues] = useState>({}); const [importJson, setImportJson] = useState(""); const [importPreview, setImportPreview] = useState(""); const assetObjectUrlRefs = useRef>>({}); const tabs = [ { name: "Genel", icon: Palette }, { name: "Diller ve çeviriler", icon: Languages }, { name: "Profile & Account", icon: User }, { name: "AI Preferences", icon: Brain }, { name: "Security", icon: Shield }, ]; useEffect(() => { let isActive = true; const fetchData = async () => { const settings = await loadSettings(); if (!isActive) return; setFirstName(settings.firstName); setLastName(settings.lastName); setAvatarUrl(settings.avatarUrl); setAiProvider(settings.aiProvider); setHasApiKey(settings.hasApiKey); setColorMode(settings.colorMode); setWorkspaceName(settings.workspaceName); setMetaTitle(settings.metaTitle); setShortName(settings.shortName); setPrimaryColor(settings.primaryColor); setAssetUrls({ lightLogo: settings.lightLogoUrl, darkLogo: settings.darkLogoUrl, favicon: settings.faviconUrl, }); setCustomAssets({ lightLogo: settings.hasCustomLightLogo, darkLogo: settings.hasCustomDarkLogo, favicon: settings.hasCustomFavicon, }); setLanguage(settings.language); setLocales(settings.i18n.locales); setDefaultLocale(settings.i18n.defaultLocale); setCatalogVersion(settings.i18n.catalogVersion); setTranslations(settings.i18n.translations); setReferenceKeys(settings.i18n.referenceKeys); setCompletion(settings.i18n.completion); setBrandingContentTranslations(toLocalizedValues(settings.contentTranslations.branding)); setSelectedLocale(settings.i18n.locales.find((locale) => !locale.builtIn)?.code ?? "en"); }; void fetchData(); return () => { isActive = false; }; }, []); useEffect(() => { const objectUrls = assetObjectUrlRefs.current; return () => { for (const objectUrl of Object.values(objectUrls)) { if (objectUrl) URL.revokeObjectURL(objectUrl); } }; }, []); const handleProfileAction = async (formData: FormData) => { const response = await updateProfile(formData); if (response?.error) { toast.error(`Hata: ${response.error}`); } else { toast.success("Profil güncellendi!"); const avatar = formData.get("avatar"); if (avatar instanceof File && avatar.size > 0) window.location.reload(); } }; const handlePasswordAction = async (formData: FormData) => { const response = await updatePassword(formData); if (response?.error) { toast.error(`Hata: ${response.error}`); } else { toast.success("Şifre güncellendi!"); formRef.current?.reset(); } }; const handleSaveAI = async () => { const response = await saveAiSettings(aiProvider, apiKey); if (response.error) { toast.error(response.error); return; } setHasApiKey(Boolean(response.hasApiKey)); setApiKey(""); toast.success("Yapay Zeka ayarları kaydedildi!"); }; const handleColorModeChange = async (value: string) => { if (!isColorMode(value) || value === colorMode || isSavingColorMode) return; const previousColorMode = colorMode; setColorMode(value); applyColorMode(value); setIsSavingColorMode(true); try { const response = await saveColorMode(value); if (response.error) { setColorMode(previousColorMode); applyColorMode(previousColorMode); toast.error(response.error); return; } toast.success("Görünüm tercihi kaydedildi."); } finally { setIsSavingColorMode(false); } }; const handleBrandingAssetChange = ( asset: BrandingAsset, event: React.ChangeEvent, ) => { const previousObjectUrl = assetObjectUrlRefs.current[asset]; if (previousObjectUrl) URL.revokeObjectURL(previousObjectUrl); const file = event.target.files?.[0]; const objectUrl = file ? URL.createObjectURL(file) : ""; assetObjectUrlRefs.current[asset] = objectUrl || undefined; setPendingAssetUrls((current) => ({ ...current, [asset]: objectUrl })); }; const handleGeneralSettingsAction = async (formData: FormData) => { setIsSavingBranding(true); try { const response = await saveGeneralSettings(formData); if (response.error) { toast.error(response.error); return; } toast.success("Genel görünüm ve marka ayarları güncellendi."); window.location.reload(); } finally { setIsSavingBranding(false); } }; const handleRemoveBrandingAsset = async (asset: BrandingAsset) => { setIsSavingBranding(true); try { const response = await removeBrandingAsset(asset); if (response.error) { toast.error(response.error); return; } toast.success("Marka görseli kaldırıldı."); window.location.reload(); } finally { setIsSavingBranding(false); } }; const refreshI18nSettings = async () => { const settings = await loadSettings(); setLanguage(settings.language); setLocales(settings.i18n.locales); setDefaultLocale(settings.i18n.defaultLocale); setCatalogVersion(settings.i18n.catalogVersion); setTranslations(settings.i18n.translations); setReferenceKeys(settings.i18n.referenceKeys); setCompletion(settings.i18n.completion); }; const handleLanguagePreferenceChange = async (value: string) => { setLanguage(value); const response = await saveLanguagePreference(value); if (response.error) { toast.error(response.error); return; } toast.success("Arayüz dili kaydedildi."); window.location.reload(); }; const handleCreateLocale = async () => { const response = await createLocaleAction(newLocale); if (response.error) { toast.error(response.error); return; } toast.success("Dil eklendi."); setSelectedLocale(response.locale?.code ?? newLocale.code); await refreshI18nSettings(); }; const handleUpdateLocaleStatus = async (code: string, status: LocaleStatus) => { const response = await updateLocaleStatusAction(code, status); if (response.error) { toast.error(response.error); return; } toast.success("Dil durumu güncellendi."); await refreshI18nSettings(); }; const handleSetDefaultLocale = async (code: string) => { const response = await setDefaultLocaleAction(code); if (response.error) { toast.error(response.error); return; } toast.success("Varsayılan dil güncellendi."); await refreshI18nSettings(); }; const handleSaveTranslation = async (reference: ReferenceKey) => { const value = editingValues[reference.key] ?? getTranslationValue(translations, selectedLocale, reference); const response = await saveUiTranslationAction({ locale: selectedLocale, namespace: reference.namespace, key: reference.translationKey, value, }); if (response.error) { toast.error(response.error); return; } toast.success("Çeviri kaydedildi."); await refreshI18nSettings(); }; const handleResetTranslation = async (reference: ReferenceKey) => { const response = await resetUiTranslationAction({ locale: selectedLocale, namespace: reference.namespace, key: reference.translationKey, }); if (response.error) { toast.error(response.error); return; } toast.success("Override sıfırlandı."); setEditingValues((current) => ({ ...current, [reference.key]: "" })); await refreshI18nSettings(); }; const handleExportI18n = async () => { const response = await exportI18nAction(); if (response.error || !response.package) { toast.error(response.error ?? "Export oluşturulamadı."); return; } setImportJson(JSON.stringify(response.package, null, 2)); toast.success("Çeviri paketi aşağıdaki alana yazıldı."); }; const handlePreviewImport = async () => { const response = await previewI18nImportAction(importJson); if (response.error || !response.preview) { setImportPreview(""); toast.error(response.error ?? "Import paketi okunamadı."); return; } setImportPreview(`${response.preview.localeCount} dil, ${response.preview.translationCount} çeviri, default: ${response.preview.defaultLocale}`); }; const handleCommitImport = async () => { const response = await commitI18nImportAction(importJson); if (response.error) { toast.error(response.error); return; } toast.success("Çeviri paketi içe aktarıldı."); setImportPreview(""); await refreshI18nSettings(); }; const filteredReferenceKeys = referenceKeys.filter((reference) => { const namespaceMatches = selectedNamespace === "all" || reference.namespace === selectedNamespace; const query = translationSearch.trim().toLowerCase(); const textMatches = !query || [reference.key, reference.tr, reference.en] .some((value) => value.toLowerCase().includes(query)); return namespaceMatches && textMatches; }); return (

Ayarlar

{/* Settings Sidebar */}
{tabs.map((tab) => { const Icon = tab.icon; return ( ) })}
{/* Settings Content Area */}
{activeTab === "Genel" && (

Genel görünüm ve marka

Web ve mobil istemcilerde kullanılan workspace kimliğini, marka görsellerini ve tema tercihlerini yönetin.

setWorkspaceName(event.target.value)} minLength={1} maxLength={120} required />

Firma, freelance marka veya çalışma alanı adınız.

setMetaTitle(event.target.value)} minLength={1} maxLength={80} required />

Sekme başlıklarında ve uygulama metadata bilgisinde kullanılır.

setShortName(event.target.value)} minLength={1} maxLength={24} required />

Mobil uygulama ve ana ekrana ekleme alanlarında kullanılan kısa ad.

Tarayıcı ikonu

Favicon, web manifest ve mobil instance metadata alanlarında kullanılır.

Bir renk seçin; vurgu, focus ve yumuşak yüzey tonları otomatik türetilir.

setPrimaryColor(event.target.value.toUpperCase())} aria-label="Ana renk seçici" className="h-11 w-16 shrink-0 cursor-pointer p-1" /> setPrimaryColor(event.target.value.toUpperCase())} pattern="^#[0-9A-Fa-f]{6}$" maxLength={7} placeholder="#C81E1E" required className="font-mono uppercase" />

Portal metinleri

Müşteri portalında kullanılacak karşılama ve footer metinlerini aktif dillere göre girin.

locale.status !== "archived")} fields={contentTranslationRegistry.branding} values={brandingContentTranslations} />

Tema görünümü

Arayüzün açık, koyu veya cihazınızla uyumlu görünmesini seçin.

{colorModeOptions.map((option) => { const Icon = option.icon; const selected = colorMode === option.value; return ( ); })}

{isSavingColorMode ? "Görünüm tercihi kaydediliyor…" : "Değişiklik tüm sayfalara anında uygulanır."}

)} {activeTab === "Diller ve çeviriler" && (

Diller ve çeviriler

Instance dillerini, kişisel arayüz dilini ve katalog override metinlerini yönetin.

Catalog v{catalogVersion}

Aktif diller

{locales.map((locale) => { const localeCompletion = completion.find((item) => item.locale === locale.code); return (
{locale.nativeName} {locale.code}

{locale.name} · {locale.status} · fallback: {locale.fallbackLocale ?? "-"}

{locale.builtIn ? ( Built-in ) : null}
{localeCompletion?.percent ?? 0}% tamamlandı {localeCompletion?.translated ?? 0}/{localeCompletion?.total ?? 0}
{locale.status !== "active" ? ( ) : null} {locale.status === "active" ? ( ) : null} {!locale.builtIn && locale.status !== "archived" ? ( ) : null}
); })}

Yeni dil ekle

setNewLocale((current) => ({ ...current, code: event.target.value }))} placeholder="fr" /> setNewLocale((current) => ({ ...current, name: event.target.value }))} placeholder="French" /> setNewLocale((current) => ({ ...current, nativeName: event.target.value }))} placeholder="Français" />
setTranslationSearch(event.target.value)} placeholder="navigation.projects" />
{filteredReferenceKeys.slice(0, 60).map((reference) => { const currentValue = getTranslationValue(translations, selectedLocale, reference); const draftValue = editingValues[reference.key] ?? currentValue; const isMissing = !currentValue; return (
{reference.key} {isMissing ? Eksik : null}

TR: {reference.tr}

EN: {reference.en}