first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:46:42 +03:00
commit 2a5b661443
202 changed files with 49770 additions and 0 deletions

View File

@@ -0,0 +1,582 @@
"use client"
import { useState, useEffect } from "react"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
FormDescription,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" // Assume these exist or need verify
import { Setting } from "@/types/setting"
import { settingService } from "@/services/settingService"
import { toast } from "sonner"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const formSchema = z.object({
title: z.string().min(2, "Başlık en az 2 karakter olmalı"),
slogan: z.string().optional(),
url: z.string().url("Geçerli bir URL giriniz").optional().or(z.literal("")),
email: z.string().email("Geçerli bir e-posta giriniz").optional().or(z.literal("")),
phone: z.string().optional(),
address: z.string().optional(),
copyright: z.string().optional(),
map_embed: z.string().optional(),
meta_title: z.string().optional(),
meta_description: z.string().optional(),
// Social
facebook: z.string().url().optional().or(z.literal("")),
x: z.string().url().optional().or(z.literal("")),
instagram: z.string().url().optional().or(z.literal("")),
whatsapp: z.string().optional(), // clean number usually
linkedin: z.string().url().optional().or(z.literal("")),
pinterest: z.string().url().optional().or(z.literal("")),
// Config
is_active: z.boolean().default(false),
// Images W Logo
w_logo: z.any().optional(),
w_width: z.coerce.number().min(1).default(100),
w_height: z.coerce.number().min(1).default(100),
w_quality: z.coerce.number().min(1).max(100).default(85),
w_format: z.string().default("avif"),
// Images B Logo
b_logo: z.any().optional(),
b_width: z.coerce.number().min(1).default(100),
b_height: z.coerce.number().min(1).default(100),
b_quality: z.coerce.number().min(1).max(100).default(85),
b_format: z.string().default("avif"),
})
interface SettingDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
setting?: Setting | null
onSuccess?: () => void
}
export function SettingDialog({ open, onOpenChange, setting, onSuccess }: SettingDialogProps) {
const [loading, setLoading] = useState(false)
const [wPreview, setWPreview] = useState<string | null>(null)
const [bPreview, setBPreview] = useState<string | null>(null)
const form = useForm<z.infer<typeof formSchema>>({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolver: zodResolver(formSchema) as any,
defaultValues: {
title: "",
slogan: "",
url: "",
email: "",
phone: "",
address: "",
copyright: "",
map_embed: "",
meta_title: "",
meta_description: "",
facebook: "",
x: "",
instagram: "",
whatsapp: "",
linkedin: "",
pinterest: "",
is_active: false,
w_width: 100,
w_height: 100,
w_quality: 85,
w_format: "avif",
b_width: 100,
b_height: 100,
b_quality: 85,
b_format: "avif",
},
})
useEffect(() => {
if (setting) {
form.reset({
title: setting.title,
slogan: setting.slogan || "",
url: setting.url || "",
email: setting.email || "",
phone: setting.phone || "",
address: setting.address || "",
copyright: setting.copyright || "",
map_embed: setting.map_embed || "",
meta_title: setting.meta_title || "",
meta_description: setting.meta_description || "",
facebook: setting.facebook || "",
x: setting.x || "",
instagram: setting.instagram || "",
whatsapp: setting.whatsapp || "",
linkedin: setting.linkedin || "",
pinterest: setting.pinterest || "",
is_active: !!setting.is_active,
w_width: setting.w_width || 100,
w_height: setting.w_height || 100,
w_quality: setting.w_quality || 85,
w_format: setting.w_format || "avif",
b_width: setting.b_width || 100,
b_height: setting.b_height || 100,
b_quality: setting.b_quality || 85,
b_format: setting.b_format || "avif",
})
// Set previews
// Assuming backend serves images from a static path, need env URL
const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"
if (setting.w_logo) {
// Check if it's already a full URL or relative
setWPreview(setting.w_logo.startsWith("http") ? setting.w_logo : `${apiUrl}${setting.w_logo}`)
}
if (setting.b_logo) {
setBPreview(setting.b_logo.startsWith("http") ? setting.b_logo : `${apiUrl}${setting.b_logo}`)
}
} else {
form.reset({
title: "",
slogan: "",
url: "",
email: "",
phone: "",
address: "",
copyright: "",
map_embed: "",
meta_title: "",
meta_description: "",
facebook: "",
x: "",
instagram: "",
whatsapp: "",
linkedin: "",
pinterest: "",
is_active: false,
w_width: 100,
w_height: 100,
w_quality: 85,
w_format: "avif",
b_width: 100,
b_height: 100,
b_quality: 85,
b_format: "avif",
})
setWPreview(null)
setBPreview(null)
}
}, [setting, form, open])
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>, fieldName: "w_logo" | "b_logo") => {
const file = e.target.files?.[0]
if (file) {
form.setValue(fieldName, file)
const reader = new FileReader()
reader.onloadend = () => {
if (fieldName === "w_logo") setWPreview(reader.result as string)
else setBPreview(reader.result as string)
}
reader.readAsDataURL(file)
}
}
const onSubmit = async (values: z.infer<typeof formSchema>) => {
setLoading(true)
const formData = new FormData()
// Append basic fields
Object.entries(values).forEach(([key, value]) => {
if (key !== "w_logo" && key !== "b_logo") {
formData.append(key, String(value))
}
})
// Append images if they are files
if (values.w_logo instanceof File) {
formData.append("w_logo", values.w_logo)
}
if (values.b_logo instanceof File) {
formData.append("b_logo", values.b_logo)
}
try {
if (setting) {
await settingService.updateSetting(setting.ID, formData)
toast.success("Ayar başarıyla güncellendi")
} else {
await settingService.createSetting(formData)
toast.success("Ayar başarıyla oluşturuldu")
}
onOpenChange(false)
if (onSuccess) onSuccess()
} catch (error: unknown) {
console.error("Setting save error:", error)
toast.error((error as Error).message || "Bir hata oluştu")
} finally {
setLoading(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[800px] h-[90vh] overflow-y-auto flex flex-col">
<DialogHeader>
<DialogTitle>{setting ? "Ayar Düzenle" : "Yeni Ayar Ekle"}</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto py-2">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<Tabs defaultValue="general" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="general">Genel</TabsTrigger>
<TabsTrigger value="contact">İletişim</TabsTrigger>
<TabsTrigger value="social">Sosyal Medya</TabsTrigger>
<TabsTrigger value="images">Görseller</TabsTrigger>
</TabsList>
{/* GENERAL TAB */}
<TabsContent value="general" className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Site Başlığı</FormLabel>
<FormControl>
<Input placeholder="Site Başlığı" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="slogan"
render={({ field }) => (
<FormItem>
<FormLabel>Slogan</FormLabel>
<FormControl>
<Input placeholder="Slogan" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="meta_title"
render={({ field }) => (
<FormItem>
<FormLabel>Meta Başlığı</FormLabel>
<FormControl>
<Input placeholder="SEO için Başlık" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="meta_description"
render={({ field }) => (
<FormItem>
<FormLabel>Meta ıklama</FormLabel>
<FormControl>
<Input placeholder="SEO için Açıklama" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="is_active"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm bg-destructive/10">
<div className="space-y-0.5">
<FormLabel className="font-bold text-destructive">Aktif Ayar</FormLabel>
<FormDescription>
Bu ayarı aktif yaparsanız, diğer tüm ayarlar otomatik olarak pasif duruma geçer.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</TabsContent>
{/* CONTACT TAB */}
<TabsContent value="contact" className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>E-posta</FormLabel>
<FormControl>
<Input placeholder="ornek@site.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Telefon</FormLabel>
<FormControl>
<Input placeholder="+90 555 ..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="url"
render={({ field }) => (
<FormItem>
<FormLabel>Site URL</FormLabel>
<FormControl>
<Input placeholder="https://site.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="address"
render={({ field }) => (
<FormItem>
<FormLabel>Adres</FormLabel>
<FormControl>
<Textarea placeholder="Adres bilgisi" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="map_embed"
render={({ field }) => (
<FormItem>
<FormLabel>Harita Embed Kodu (Iframe)</FormLabel>
<FormControl>
<Textarea placeholder='<iframe src="..." ...></iframe>' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="copyright"
render={({ field }) => (
<FormItem>
<FormLabel>Telif Hakkı Metni</FormLabel>
<FormControl>
<Input placeholder="© 2024 Tüm hakları saklıdır." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</TabsContent>
{/* SOCIAL TAB */}
<TabsContent value="social" className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<FormField control={form.control} name="facebook" render={({ field }) => (
<FormItem><FormLabel>Facebook</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="x" render={({ field }) => (
<FormItem><FormLabel>X (Twitter)</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="instagram" render={({ field }) => (
<FormItem><FormLabel>Instagram</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="linkedin" render={({ field }) => (
<FormItem><FormLabel>LinkedIn</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="pinterest" render={({ field }) => (
<FormItem><FormLabel>Pinterest</FormLabel><FormControl><Input placeholder="URL" {...field} /></FormControl><FormMessage /></FormItem>
)} />
<FormField control={form.control} name="whatsapp" render={({ field }) => (
<FormItem><FormLabel>Whatsapp</FormLabel><FormControl><Input placeholder="Numara" {...field} /></FormControl><FormMessage /></FormItem>
)} />
</div>
</TabsContent>
{/* IMAGES TAB */}
<TabsContent value="images" className="space-y-4 pt-4">
{/* White Logo Config */}
<div className="border p-4 rounded-md space-y-4">
<h3 className="font-semibold text-lg">Beyaz Yazılı Logo (w_logo)</h3>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="w_width"
render={({ field }) => (
<FormItem><FormLabel>Genişlik</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="w_height"
render={({ field }) => (
<FormItem><FormLabel>Yükseklik</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="w_quality"
render={({ field }) => (
<FormItem><FormLabel>Kalite</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="w_format"
render={({ field }) => (
<FormItem>
<FormLabel>Format</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger><SelectValue placeholder="Format" /></SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="avif">AVIF</SelectItem>
<SelectItem value="webp">WebP</SelectItem>
<SelectItem value="png">PNG</SelectItem>
<SelectItem value="jpeg">JPEG</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormItem>
<FormLabel>Logo Dosyası</FormLabel>
<FormControl>
<Input type="file" accept="image/*" onChange={(e) => handleImageChange(e, "w_logo")} />
</FormControl>
{wPreview && (
<div className="mt-2 w-full h-20 bg-gray-100 rounded flex items-center justify-center overflow-hidden border">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={wPreview} alt="W Logo Preview" className="h-full object-contain" />
</div>
)}
</FormItem>
</div>
{/* Black Logo Config */}
<div className="border p-4 rounded-md space-y-4">
<h3 className="font-semibold text-lg">Siyah Yazılı Logo (b_logo)</h3>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="b_width"
render={({ field }) => (
<FormItem><FormLabel>Genişlik</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="b_height"
render={({ field }) => (
<FormItem><FormLabel>Yükseklik</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="b_quality"
render={({ field }) => (
<FormItem><FormLabel>Kalite</FormLabel><FormControl><Input type="number" {...field} /></FormControl><FormMessage /></FormItem>
)}
/>
<FormField
control={form.control}
name="b_format"
render={({ field }) => (
<FormItem>
<FormLabel>Format</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger><SelectValue placeholder="Format" /></SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="avif">AVIF</SelectItem>
<SelectItem value="webp">WebP</SelectItem>
<SelectItem value="png">PNG</SelectItem>
<SelectItem value="jpeg">JPEG</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormItem>
<FormLabel>Logo Dosyası</FormLabel>
<FormControl>
<Input type="file" accept="image/*" onChange={(e) => handleImageChange(e, "b_logo")} />
</FormControl>
{bPreview && (
<div className="mt-2 w-full h-20 bg-gray-100 rounded flex items-center justify-center overflow-hidden border">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={bPreview} alt="B Logo Preview" className="h-full object-contain" />
</div>
)}
</FormItem>
</div>
</TabsContent>
</Tabs>
<DialogFooter>
<Button type="submit" disabled={loading}>
{loading ? "Kaydediliyor..." : "Kaydet"}
</Button>
</DialogFooter>
</form>
</Form>
</div>
</DialogContent>
</Dialog>
)
}