From 0fe49b5c96a5af5b0cfaba28728eb65f22b34640 Mon Sep 17 00:00:00 2001 From: Kenan KARAER Date: Sun, 25 Jan 2026 01:46:12 +0300 Subject: [PATCH] =?UTF-8?q?web=20sitesi=20y=C3=B6netimi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/cms/content/actions.ts | 45 +++++ .../dashboard/cms/content/page.tsx | 61 ++++++ app/(public)/contact/page.tsx | 187 +++++------------- components/contact/contact-form.tsx | 137 +++++++++++++ components/dashboard/content-form.tsx | 128 ++++++++++++ components/dashboard/sidebar.tsx | 7 +- components/layout/footer.tsx | 38 +++- components/layout/navbar.tsx | 35 +++- lib/data.ts | 18 ++ package-lock.json | 10 + package.json | 1 + supabase_migration_tiktok.sql | 9 + supabase_migration_youtube.sql | 9 + supabase_schema_site_contents.sql | 39 ++++ types/cms.ts | 6 + 15 files changed, 575 insertions(+), 155 deletions(-) create mode 100644 app/(dashboard)/dashboard/cms/content/actions.ts create mode 100644 app/(dashboard)/dashboard/cms/content/page.tsx create mode 100644 components/contact/contact-form.tsx create mode 100644 components/dashboard/content-form.tsx create mode 100644 supabase_migration_tiktok.sql create mode 100644 supabase_migration_youtube.sql create mode 100644 supabase_schema_site_contents.sql create mode 100644 types/cms.ts diff --git a/app/(dashboard)/dashboard/cms/content/actions.ts b/app/(dashboard)/dashboard/cms/content/actions.ts new file mode 100644 index 0000000..f6873dd --- /dev/null +++ b/app/(dashboard)/dashboard/cms/content/actions.ts @@ -0,0 +1,45 @@ +'use server' + +import { createClient } from "@/lib/supabase-server" +import { SiteContent } from "@/types/cms" +import { revalidatePath } from "next/cache" + +export async function updateSiteContent(contents: SiteContent[]) { + const supabase = await createClient() + + try { + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + return { success: false, error: "Oturum açmanız gerekiyor" } + } + + // Upsert each content item + // Since we might have many items, we can do this in parallel or a single upsert if the structure allows + // Supabase upsert accepts an array + const { error } = await supabase + .from('site_contents') + .upsert( + contents.map(item => ({ + key: item.key, + value: item.value, + type: item.type, + section: item.section, + updated_at: new Date().toISOString() + })) + ) + + if (error) { + console.error('CMS Update Error:', error) + return { success: false, error: "Güncelleme sırasında bir hata oluştu: " + error.message } + } + + revalidatePath('/dashboard/cms/content') + revalidatePath('/') // Revalidate home page as it likely uses these settings + + return { success: true } + } catch (error) { + console.error('CMS Update Error:', error) + return { success: false, error: "Bir hata oluştu" } + } +} diff --git a/app/(dashboard)/dashboard/cms/content/page.tsx b/app/(dashboard)/dashboard/cms/content/page.tsx new file mode 100644 index 0000000..dfbcd5e --- /dev/null +++ b/app/(dashboard)/dashboard/cms/content/page.tsx @@ -0,0 +1,61 @@ +import { createClient } from "@/lib/supabase-server" +import { ContentForm } from "@/components/dashboard/content-form" +import { SiteContent } from "@/types/cms" + +export default async function ContentPage() { + const supabase = await createClient() + + const { data: contents } = await supabase + .from('site_contents') + .select('*') + .order('key') + + // Define default contents that should exist + 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 ( +
+
+

İçerik Yönetimi

+
+
+
+
+
+
+

+ Site başlığı, sloganlar, iletişim bilgileri ve logoları buradan yönetebilirsiniz. +

+
+ +
+
+
+
+
+ ) +} diff --git a/app/(public)/contact/page.tsx b/app/(public)/contact/page.tsx index 81b1bbd..52907fc 100644 --- a/app/(public)/contact/page.tsx +++ b/app/(public)/contact/page.tsx @@ -1,50 +1,11 @@ -"use client" +import { getSiteContents } from "@/lib/data" +import { ContactForm } from "@/components/contact/contact-form" +import { Mail, MapPin, Phone, Instagram, Youtube } from "lucide-react" +import { FaTiktok } from "react-icons/fa" +import Link from "next/link" -import { useState } from "react" -import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Textarea } from "@/components/ui/textarea" -import { Card, CardContent } from "@/components/ui/card" -import { Mail, MapPin, Phone, Loader2, CheckCircle } from "lucide-react" -import { contactFormSchema, ContactFormValues } from "@/lib/schemas" -import { submitContactForm } from "@/lib/actions/contact" -import { toast } from "sonner" - -export default function ContactPage() { - const [isSubmitting, setIsSubmitting] = useState(false) - const [isSuccess, setIsSuccess] = useState(false) - - const form = useForm({ - resolver: zodResolver(contactFormSchema), - defaultValues: { - name: "", - surname: "", - email: "", - phone: "", - subject: "", - message: "", - }, - }) - - async function onSubmit(data: ContactFormValues) { - setIsSubmitting(true) - try { - const response = await submitContactForm(data) - if (response.success) { - setIsSuccess(true) - form.reset() - toast.success("Mesajınız başarıyla gönderildi.") - } else { - toast.error("Hata: " + response.error) - } - } catch { - toast.error("Bir hata oluştu.") - } finally { - setIsSubmitting(false) - } - } +export default async function ContactPage() { + const siteSettings = await getSiteContents() return (
@@ -63,9 +24,8 @@ export default function ContactPage() {

Merkez Ofis & Showroom

-

- Organize Sanayi Bölgesi, 12. Cadde No: 45
- Başakşehir, İstanbul +

+ {siteSettings.contact_address || "Organize Sanayi Bölgesi, 12. Cadde No: 45\nBaşakşehir, İstanbul"}

@@ -73,110 +33,55 @@ export default function ContactPage() {

Telefon

-

+90 (212) 555 00 00

+

+ {siteSettings.contact_phone || "+90 (212) 555 00 00"} +

E-posta

-

info@parakasa.com

+

+ {siteSettings.contact_email || "info@parakasa.com"} +

+
+
+ +
+

Sosyal Medya

+
+ {siteSettings.social_instagram && ( + + + + )} + {siteSettings.social_youtube && ( + + + + )} + {siteSettings.social_tiktok && ( + + + + )}
-
-
- Harita (Google Maps Embed) + {siteSettings.contact_map_embed ? ( +
+ ) : ( +
+ Harita henüz eklenmemiş.
-
+ )}
- - - {isSuccess ? ( -
- -

Mesajınız Alındı!

-

- En kısa sürede size dönüş yapacağız. -

- -
- ) : ( -
-
-
- - - {form.formState.errors.name &&

{form.formState.errors.name.message}

} -
-
- - - {form.formState.errors.surname &&

{form.formState.errors.surname.message}

} -
-
-
-
- - - {form.formState.errors.email &&

{form.formState.errors.email.message}

} -
-
- -
-
- 🇹🇷 - +90 -
-
- { - let value = e.target.value.replace(/\D/g, ''); // Remove non-digits - if (value.startsWith('90')) value = value.slice(2); // Remove leading 90 if user types it - - // Format: (5XX) XXX XX XX - let formattedValue = ''; - if (value.length > 0) formattedValue += '(' + value.substring(0, 3); - if (value.length > 3) formattedValue += ') ' + value.substring(3, 6); - if (value.length > 6) formattedValue += ' ' + value.substring(6, 8); - if (value.length > 8) formattedValue += ' ' + value.substring(8, 10); - - e.target.value = formattedValue; - return e; - } - })} - /> -
- {form.formState.errors.phone &&

{form.formState.errors.phone.message}

} -
-
-
- - - {form.formState.errors.subject &&

{form.formState.errors.subject.message}

} -
-
- -