From 6992891ae3e8eaf591138b703f930ed2eafad1f0 Mon Sep 17 00:00:00 2001 From: Kenan KARAER Date: Sun, 25 Jan 2026 02:03:27 +0300 Subject: [PATCH] =?UTF-8?q?Site=20i=C3=A7erik=20y=C3=B6netimi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(dashboard)/dashboard/settings/actions.ts | 42 ----- app/(dashboard)/dashboard/settings/page.tsx | 58 +++++-- app/layout.tsx | 8 +- components/dashboard/settings-tabs.tsx | 40 ++++- components/dashboard/sidebar.tsx | 12 +- components/dashboard/site-settings-form.tsx | 163 ------------------ components/dashboard/users-table.tsx | 69 ++++++++ drop_site_settings.sql | 2 + lib/site-settings.ts | 8 - 9 files changed, 154 insertions(+), 248 deletions(-) delete mode 100644 app/(dashboard)/dashboard/settings/actions.ts delete mode 100644 components/dashboard/site-settings-form.tsx create mode 100644 components/dashboard/users-table.tsx create mode 100644 drop_site_settings.sql delete mode 100644 lib/site-settings.ts diff --git a/app/(dashboard)/dashboard/settings/actions.ts b/app/(dashboard)/dashboard/settings/actions.ts deleted file mode 100644 index f26c8da..0000000 --- a/app/(dashboard)/dashboard/settings/actions.ts +++ /dev/null @@ -1,42 +0,0 @@ -"use server" - -import { createClient } from "@/lib/supabase-server" -import { revalidatePath } from "next/cache" - -export async function updateSiteSettings(data: { - site_title: string - site_description: string - contact_email: string - contact_phone: string - currency: string -}) { - const supabase = createClient() - - // Check admin is already handled by RLS on database level, but we can double check here - const { data: { user } } = await supabase.auth.getUser() - if (!user) return { error: "Oturum açmanız gerekiyor." } - - // We update the single row where id is likely 1 or just the first row - // Since we initialized it with one row, we can just update match on something true or fetch id first. - // Easier: Update all rows (there should only be one) or fetch the specific ID first. - - // Let's first get the ID just to be precise - const { data: existing } = await supabase.from('site_settings').select('id').single() - - if (!existing) { - return { error: "Ayarlar bulunamadı." } - } - - const { error } = await supabase - .from('site_settings') - .update(data) - .eq('id', existing.id) - - if (error) { - return { error: "Ayarlar güncellenemedi: " + error.message } - } - - revalidatePath("/dashboard/settings") - revalidatePath("/") // Revalidate home as it might use these settings - return { success: true } -} diff --git a/app/(dashboard)/dashboard/settings/page.tsx b/app/(dashboard)/dashboard/settings/page.tsx index f5d1745..7ee1968 100644 --- a/app/(dashboard)/dashboard/settings/page.tsx +++ b/app/(dashboard)/dashboard/settings/page.tsx @@ -1,28 +1,62 @@ import { createClient } from "@/lib/supabase-server" import { SettingsTabs } from "@/components/dashboard/settings-tabs" import { getSmsSettings } from "@/lib/sms/actions" +import { SiteContent } from "@/types/cms" export default async function SettingsPage() { const supabase = createClient() - // Fetch site settings - const { data: siteSettings } = await supabase - .from('site_settings') - .select('*') - .single() - - // Fetch SMS settings (server-side call to our action/db) - // Note: getSmsSettings is an action that checks for admin, which is fine. - // However, since we are in a server component with a Supabase client, we could also fetch directly if we had admin client. - // But let's use the clean action or direct logic. - // Actually, calling server action from server component is fine, but we need to handle the return structure. + // Fetch SMS settings const smsResponse = await getSmsSettings() const smsSettings = smsResponse.data || null + // Fetch Users (Profiles) + const { data: profiles } = await supabase + .from("profiles") + .select("*") + .order("created_at", { ascending: false }) + + // Fetch Site Contents (CMS) + const { data: contents } = await supabase + .from('site_contents') + .select('*') + .order('key') + + // Define default contents for CMS + const DEFAULT_CONTENTS: SiteContent[] = [ + // General + { key: 'site_title', value: 'ParaKasa', type: 'text', section: 'general' }, + { key: 'site_description', value: '', type: 'long_text', section: 'general' }, + { key: 'site_logo', value: '', type: 'image_url', section: 'general' }, + + // Contact + { key: 'contact_phone', value: '', type: 'text', section: 'contact' }, + { key: 'contact_email', value: '', type: 'text', section: 'contact' }, + { key: 'contact_address', value: '', type: 'long_text', section: 'contact' }, + { key: 'social_instagram', value: '', type: 'text', section: 'contact' }, + { key: 'social_youtube', value: '', type: 'text', section: 'contact' }, + { key: 'social_tiktok', value: '', type: 'text', section: 'contact' }, + { key: 'contact_map_embed', value: '', type: 'html', section: 'contact' }, + ] + + // Merge default contents with existing contents + const mergedContents = [...(contents as SiteContent[] || [])] + const existingKeys = new Set(mergedContents.map(c => c.key)) + + DEFAULT_CONTENTS.forEach(item => { + if (!existingKeys.has(item.key)) { + mergedContents.push(item) + } + }) + return (

Ayarlar

- +
) } diff --git a/app/layout.tsx b/app/layout.tsx index e5e36e9..4e0c429 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,14 +6,14 @@ import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); const outfit = Outfit({ subsets: ["latin"], variable: "--font-outfit" }); -import { getSiteSettings } from "@/lib/site-settings"; +import { getSiteContents } from "@/lib/data"; export async function generateMetadata() { - const settings = await getSiteSettings(); + const settings = await getSiteContents(); return { - title: settings?.site_title || "ParaKasa - Premium Çelik Kasalar", - description: settings?.site_description || "Eviniz ve iş yeriniz için en yüksek güvenlikli çelik kasa ve para sayma çözümleri.", + title: settings.site_title || "ParaKasa - Premium Çelik Kasalar", + description: settings.site_description || "Eviniz ve iş yeriniz için en yüksek güvenlikli çelik kasa ve para sayma çözümleri.", }; } diff --git a/components/dashboard/settings-tabs.tsx b/components/dashboard/settings-tabs.tsx index 5afe841..24be725 100644 --- a/components/dashboard/settings-tabs.tsx +++ b/components/dashboard/settings-tabs.tsx @@ -1,32 +1,56 @@ "use client" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { SiteSettingsForm, SettingsFormValues } from "@/components/dashboard/site-settings-form" import { SmsSettingsForm } from "@/components/dashboard/sms-settings-form" import { AppearanceForm } from "@/components/dashboard/appearance-form" +import { UsersTable } from "@/components/dashboard/users-table" +import { ContentForm } from "@/components/dashboard/content-form" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" +import { SiteContent } from "@/types/cms" interface SettingsTabsProps { - siteSettings: Partial | null smsSettings: { username: string header: string } | null + users: any[] + contents: SiteContent[] } -export function SettingsTabs({ siteSettings, smsSettings }: SettingsTabsProps) { +export function SettingsTabs({ smsSettings, users, contents }: SettingsTabsProps) { return ( - - - Genel + + + İçerik Yönetimi + Kullanıcılar SMS / Bildirimler Görünüm Güvenlik - - + +
+
+

Site İçerik Yönetimi

+

+ Site genel ayarları, iletişim bilgileri ve logolar. +

+
+ +
+
+ + +
+
+

Kullanıcı Yönetimi

+

+ Sistemdeki kayıtlı kullanıcıları ve rollerini yönetin. +

+
+ +
diff --git a/components/dashboard/sidebar.tsx b/components/dashboard/sidebar.tsx index 65596eb..9a7e3a5 100644 --- a/components/dashboard/sidebar.tsx +++ b/components/dashboard/sidebar.tsx @@ -3,7 +3,7 @@ import Link from "next/link" import { usePathname } from "next/navigation" import { cn } from "@/lib/utils" -import { LayoutDashboard, Package, ShoppingCart, Users, Settings, Globe, Tags, FileText } from "lucide-react" +import { LayoutDashboard, Package, ShoppingCart, Settings, Globe, Tags } from "lucide-react" const sidebarItems = [ { @@ -26,16 +26,6 @@ const sidebarItems = [ href: "/dashboard/categories", icon: Tags, }, - { - title: "Kullanıcılar", - href: "/dashboard/users", - icon: Users, - }, - { - title: "İçerik Yönetimi", - href: "/dashboard/cms/content", - icon: FileText, - }, { title: "Ayarlar", href: "/dashboard/settings", diff --git a/components/dashboard/site-settings-form.tsx b/components/dashboard/site-settings-form.tsx deleted file mode 100644 index 7f4d8a5..0000000 --- a/components/dashboard/site-settings-form.tsx +++ /dev/null @@ -1,163 +0,0 @@ -"use client" - -import { useState } from "react" -import { useRouter } from "next/navigation" -import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" -import * as z from "zod" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Textarea } from "@/components/ui/textarea" -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { toast } from "sonner" -import { Loader2 } from "lucide-react" -import { updateSiteSettings } from "@/app/(dashboard)/dashboard/settings/actions" - -const settingsSchema = z.object({ - site_title: z.string().min(2, "Site başlığı en az 2 karakter olmalıdır."), - site_description: z.string(), - contact_email: z.literal("").or(z.string().email("Geçerli bir e-posta adresi giriniz.")), - contact_phone: z.string(), - currency: z.string(), -}) - -export type SettingsFormValues = z.infer - -interface SiteSettingsFormProps { - initialData: Partial | null -} - -export function SiteSettingsForm({ initialData }: SiteSettingsFormProps) { - const router = useRouter() - const [loading, setLoading] = useState(false) - - const form = useForm({ - resolver: zodResolver(settingsSchema), - defaultValues: { - site_title: initialData?.site_title || "ParaKasa", - site_description: initialData?.site_description || "", - contact_email: initialData?.contact_email || "", - contact_phone: initialData?.contact_phone || "", - currency: initialData?.currency || "TRY", - }, - }) - - const onSubmit = async (data: SettingsFormValues) => { - setLoading(true) - try { - const result = await updateSiteSettings(data) - - if (result.error) { - toast.error(result.error) - return - } - - toast.success("Site ayarları güncellendi.") - router.refresh() - } catch { - toast.error("Bir sorun oluştu.") - } finally { - setLoading(false) - } - } - - return ( - - - Genel Ayarlar - Web sitesinin genel yapılandırma ayarları. - - -
- - ( - - Site Başlığı - - - - Tarayıcı sekmesinde görünen ad. - - - )} - /> - - ( - - Site Açıklaması - -