Files
moy-qr/app/admin/(dashboard)/settings/SettingsForm.tsx
T

181 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useActionState, useState, useRef } from 'react'
import { saveSettings } from './actions'
import Image from 'next/image'
import { Upload, X, Loader2, Save } from 'lucide-react'
export default function SettingsForm({
defaultRestaurantName,
defaultLocation,
defaultLogoUrl,
defaultGoogleReviewUrl,
}: {
defaultRestaurantName: string
defaultLocation: string
defaultLogoUrl: string
defaultGoogleReviewUrl?: string
}) {
const [state, action, isPending] = useActionState(saveSettings, undefined)
const [logoUrl, setLogoUrl] = useState(defaultLogoUrl)
const [uploading, setUploading] = useState(false)
const [uploadError, setUploadError] = useState('')
const fileInputRef = useRef<HTMLInputElement>(null)
async function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0]
if (!file) return
if (file.size > 5 * 1024 * 1024) {
setUploadError("Dosya boyutu 5MB'ı geçemez.")
return
}
setUploadError('')
setUploading(true)
try {
const fd = new FormData()
fd.append('file', file)
const res = await fetch('/api/upload', { method: 'POST', body: fd })
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Yükleme başarısız')
setLogoUrl(data.url)
} catch (err: any) {
setUploadError(err.message || 'Logo yüklenirken hata oluştu.')
} finally {
setUploading(false)
}
}
return (
<form action={action} className="space-y-8">
{state?.success && (
<div className="bg-emerald-50 text-emerald-700 px-4 py-3 rounded-xl text-sm font-medium border border-emerald-100/50">
{state.message}
</div>
)}
{state?.error && (
<div className="bg-red-50 text-red-700 px-4 py-3 rounded-xl text-sm font-medium border border-red-100/50">
{state.error}
</div>
)}
{/* Logo Upload */}
<div>
<label className="block text-sm font-semibold text-slate-700 mb-3">
Site Logosu
</label>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-6">
{/* Preview */}
<div className="relative w-48 h-24 rounded-2xl overflow-hidden border border-slate-200 bg-slate-900 shadow-inner flex items-center justify-center flex-shrink-0 group">
{logoUrl ? (
<>
<Image
src={logoUrl}
alt="Logo önizleme"
fill
className="object-contain p-3"
/>
<button
type="button"
onClick={() => { setLogoUrl(''); if (fileInputRef.current) fileInputRef.current.value = '' }}
className="absolute top-2 right-2 bg-red-500/90 text-white rounded-full p-1 hover:bg-red-600 transition-colors opacity-0 group-hover:opacity-100 backdrop-blur-sm shadow-sm"
>
<X size={14} />
</button>
</>
) : (
<span className="text-xs text-slate-400 text-center px-4 font-medium">Logo yok<br />(varsayılan kullanılır)</span>
)}
</div>
{/* Upload button */}
<div className="flex flex-col gap-2.5 justify-center">
<button
type="button"
onClick={() => fileInputRef.current?.click()}
disabled={uploading}
className="flex items-center gap-2 px-5 py-2.5 border-2 border-dashed border-slate-300 rounded-xl text-sm font-medium text-slate-600 hover:border-blue-400 hover:text-blue-600 transition-colors disabled:opacity-50 hover:bg-blue-50/50"
>
{uploading
? <><Loader2 size={18} className="animate-spin" /> Yükleniyor...</>
: <><Upload size={18} /> Yeni Logo Seç</>
}
</button>
<p className="text-xs text-slate-400 font-medium ml-1">Önerilen format: Şeffaf arka planlı PNG veya SVG (Maks. 5MB)</p>
{uploadError && <p className="text-xs text-red-600 font-medium ml-1">{uploadError}</p>}
</div>
</div>
<input
ref={fileInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleFileChange}
/>
<input type="hidden" name="logo_url" value={logoUrl} />
</div>
<hr className="border-slate-100" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Restaurant Name */}
<div>
<label className="block text-sm font-semibold text-slate-700 mb-1.5">
Restoran / Mekan Adı
</label>
<input
type="text"
name="restaurant_name"
defaultValue={defaultRestaurantName}
required
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all"
/>
</div>
{/* Location */}
<div>
<label className="block text-sm font-semibold text-slate-700 mb-1.5">
Konum Bilgisi
</label>
<input
type="text"
name="location"
defaultValue={defaultLocation}
required
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all"
/>
</div>
</div>
{/* Google Review Url */}
<div>
<label className="block text-sm font-semibold text-slate-700 mb-1.5">
Google Yorum Linki
</label>
<input
type="url"
name="google_review_url"
defaultValue={defaultGoogleReviewUrl}
placeholder="https://g.page/r/..."
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 focus:bg-white outline-none text-sm transition-all"
/>
<p className="text-xs text-slate-500 mt-2 font-medium ml-1">Eğer buraya link eklerseniz, ziyaretçilere her 30 dakikada bir işletmenizi değerlendirmelerini isteyen bir açılır pencere (popup) gösterilir.</p>
</div>
<div className="pt-4 flex justify-end">
<button
type="submit"
disabled={isPending || uploading}
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-8 rounded-xl transition-all shadow-sm hover:shadow hover:-translate-y-0.5 disabled:opacity-70 disabled:hover:translate-y-0 disabled:shadow-none flex items-center gap-2"
>
<Save size={18} />
{isPending ? 'Kaydediliyor...' : 'Değişiklikleri Kaydet'}
</button>
</div>
</form>
)
}