Files
marmarislocal/app/[locale]/admin/collections/[id]/CollectionForm.tsx
T

293 lines
12 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 { useState } from 'react'
import { useRouter } from 'next/navigation'
import { createOrUpdateCollectionAction } from '@/app/actions'
import { ArrowLeft, Save } from 'lucide-react'
import { Link } from '@/i18n/routing'
interface Option {
value: string
label: string
}
interface FormProps {
collection: any | null
listingOptions: Option[]
}
export default function CollectionForm({ collection, listingOptions }: FormProps) {
const router = useRouter()
const [activeTab, setActiveTab] = useState<'tr' | 'en' | 'ru'>('tr')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [success, setSuccess] = useState(false)
// Selected Listings
const [selectedListings, setSelectedListings] = useState<string[]>(collection?.listingIds || [])
const [listingSearch, setListingSearch] = useState('')
const handleListingToggle = (id: string) => {
setSelectedListings(prev =>
prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
)
}
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setLoading(true)
setError('')
setSuccess(false)
const formData = new FormData(e.currentTarget)
if (collection) {
formData.append('id', collection.id)
}
formData.append('listingIds', selectedListings.join(','))
try {
const res = await createOrUpdateCollectionAction(formData)
if (res.error) {
setError(res.error)
} else {
setSuccess(true)
setTimeout(() => {
router.push('/admin/collections')
router.refresh()
}, 1500)
}
} catch (err) {
setError('İşlem sırasında bir hata oluştu.')
} finally {
setLoading(false)
}
}
const filteredListings = listingOptions.filter(opt =>
opt.label.toLowerCase().includes(listingSearch.toLowerCase())
)
return (
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="bg-bougainvillea/10 border border-bougainvillea/25 text-bougainvillea p-4 rounded-xl text-xs font-semibold">
{error}
</div>
)}
{success && (
<div className="bg-turquoise/10 border border-turquoise/25 text-turquoise p-4 rounded-xl text-xs font-bold">
Seçki başarıyla kaydedildi! Yönlendiriliyorsunuz...
</div>
)}
{/* Language tabs */}
<div className="border-b border-pine/8">
<div className="flex gap-2">
{(['tr', 'en', 'ru'] as const).map((lang) => {
const label = lang === 'tr' ? '🇹🇷 Türkçe' : lang === 'en' ? '🇬🇧 English' : '🇷🇺 Русский'
const isTabActive = activeTab === lang
return (
<button
key={lang}
type="button"
onClick={() => setActiveTab(lang)}
className={`py-3 px-4 font-heading font-bold text-xs border-b-2 transition lowercase ${
isTabActive
? 'border-turquoise text-turquoise'
: 'border-transparent text-shutter hover:text-pine'
}`}
>
{label}
</button>
)
})}
</div>
</div>
{/* Localized inputs */}
<div className="space-y-4">
{activeTab === 'tr' && (
<div className="space-y-4">
<div className="space-y-1.5">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">Seçki Başlığı (TR) *</label>
<input
type="text"
name="titleTr"
required
defaultValue={collection?.titleTr || ''}
className="w-full bg-stone border border-pine/10 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-turquoise outline-none text-ink"
placeholder="Örn: Aile Dostu Restoranlar"
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">Açıklama (TR) *</label>
<textarea
name="descriptionTr"
required
rows={3}
defaultValue={collection?.descriptionTr || ''}
className="w-full bg-stone border border-pine/10 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-turquoise outline-none text-ink"
placeholder="Bu seçkinin odağını ve amacını açıklayın..."
/>
</div>
</div>
)}
{activeTab === 'en' && (
<div className="space-y-4">
<div className="space-y-1.5">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">Seçki Başlığı (EN) *</label>
<input
type="text"
name="titleEn"
required
defaultValue={collection?.titleEn || ''}
className="w-full bg-stone border border-pine/10 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-turquoise outline-none text-ink"
placeholder="Örn: Family Friendly Restaurants"
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">Açıklama (EN) *</label>
<textarea
name="descriptionEn"
required
rows={3}
defaultValue={collection?.descriptionEn || ''}
className="w-full bg-stone border border-pine/10 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-turquoise outline-none text-ink"
placeholder="Describe this collection's focus..."
/>
</div>
</div>
)}
{activeTab === 'ru' && (
<div className="space-y-4">
<div className="space-y-1.5">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">Seçki Başlığı (RU) *</label>
<input
type="text"
name="titleRu"
required
defaultValue={collection?.titleRu || ''}
className="w-full bg-stone border border-pine/10 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-turquoise outline-none text-ink"
placeholder="Örn: Семейные рестораны"
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">Açıklama (RU) *</label>
<textarea
name="descriptionRu"
required
rows={3}
defaultValue={collection?.descriptionRu || ''}
className="w-full bg-stone border border-pine/10 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-turquoise outline-none text-ink"
placeholder="Описание подборки..."
/>
</div>
</div>
)}
</div>
<hr className="border-dashed border-pine/8" />
{/* Global fields */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Slug */}
<div className="space-y-1.5">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">URL Slug *</label>
<input
type="text"
name="slug"
required
defaultValue={collection?.slug || ''}
className="w-full bg-stone border border-pine/10 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-turquoise outline-none text-ink font-mono"
placeholder="aile-dostu-restoranlar"
/>
</div>
{/* Cover Image Upload */}
<div className="space-y-1.5">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">Kapak Görseli</label>
<input
type="file"
name="coverImageFile"
accept="image/*"
className="w-full bg-stone border border-pine/10 rounded-xl px-4 py-3 text-sm focus:ring-1 focus:ring-turquoise outline-none text-ink file:mr-3 file:py-1 file:px-2.5 file:rounded-lg file:border-0 file:text-xs file:font-semibold file:bg-pine file:text-stone hover:file:opacity-90 cursor-pointer"
/>
{collection?.coverImage && (
<div className="mt-2 text-xs flex items-center gap-2">
<span className="text-shutter/65">Mevcut Görsel:</span>
<a href={collection.coverImage} target="_blank" rel="noopener noreferrer" className="text-turquoise hover:underline font-mono truncate max-w-xs">{collection.coverImage}</a>
<input type="hidden" name="coverImageUrl" value={collection.coverImage} />
</div>
)}
</div>
</div>
{/* Listing multi-select panel */}
<div className="space-y-2 border-t border-dashed border-pine/8 pt-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<label className="block text-xs font-mono uppercase tracking-wider text-shutter">Seçkide Yer Alan Mekanlar * ({selectedListings.length} seçildi)</label>
<input
type="text"
value={listingSearch}
onChange={(e) => setListingSearch(e.target.value)}
className="bg-stone border border-pine/10 rounded-xl px-3 py-1.5 text-xs outline-none focus:ring-1 focus:ring-turquoise w-full sm:w-64 placeholder:text-shutter/60"
placeholder="Mekan ara..."
/>
</div>
<div className="border border-pine/10 bg-stone/20 rounded-2xl max-h-52 overflow-y-auto p-4 grid grid-cols-1 sm:grid-cols-2 gap-2">
{filteredListings.length === 0 ? (
<span className="text-xs text-shutter/60 col-span-2 py-4 text-center font-mono">Aradığınız mekan bulunamadı.</span>
) : (
filteredListings.map((opt) => {
const isChecked = selectedListings.includes(opt.value)
return (
<label
key={opt.value}
className={`flex items-center gap-2.5 p-2 rounded-xl border text-xs cursor-pointer select-none transition ${
isChecked ? 'bg-turquoise/5 border-turquoise/35 text-pine font-bold' : 'border-transparent text-ink/75 hover:bg-stone/50'
}`}
>
<input
type="checkbox"
checked={isChecked}
onChange={() => handleListingToggle(opt.value)}
className="accent-turquoise rounded w-3.5 h-3.5 shrink-0"
/>
<span className="truncate leading-none">{opt.label}</span>
</label>
)
})
)}
</div>
</div>
{/* Buttons */}
<div className="flex gap-4 pt-4 border-t border-dashed border-pine/8">
<Link
href="/admin/collections"
className="inline-flex items-center gap-1.5 px-5 py-3 border border-pine/20 rounded-xl text-xs font-bold text-pine hover:bg-stone/30 transition duration-150"
>
<ArrowLeft className="w-4 h-4" />
İptal
</Link>
<button
type="submit"
disabled={loading}
className="inline-flex items-center gap-1.5 px-5 py-3 bg-turquoise hover:bg-turquoise/90 disabled:opacity-75 text-paper rounded-xl text-xs font-bold shadow-sm transition duration-150"
>
<Save className="w-4 h-4" />
{loading ? 'Kaydediliyor...' : 'Seçkiyi Kaydet'}
</button>
</div>
</form>
)
}