feat: implement dynamic categories, admin category CRUD, fix routing and cleanup

This commit is contained in:
AyrisAI
2026-07-13 13:51:48 +03:00
parent 2f2dafcfb9
commit 5b44c78396
54 changed files with 3619 additions and 473 deletions
+118
View File
@@ -0,0 +1,118 @@
'use client'
import { useEffect, useState } from 'react'
import { useSearchParams } from 'next/navigation'
import ListingCard from '@/components/ListingCard'
import { Heart, Share2, ClipboardCheck } from 'lucide-react'
interface Props {
allListings: any[]
locale: string
}
export default function SavedClient({ allListings, locale }: Props) {
const searchParams = useSearchParams()
const [savedIds, setSavedIds] = useState<string[]>([])
const [copied, setCopied] = useState(false)
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
const paramIds = searchParams.get('ids')
if (paramIds) {
// Load shared favorites
const ids = paramIds.split(',').filter(Boolean)
setSavedIds(ids)
// Save shared list to local storage as well
localStorage.setItem('savedListingIds', JSON.stringify(ids))
} else {
// Load personal favorites from localStorage
try {
const stored = localStorage.getItem('savedListingIds')
if (stored) {
setSavedIds(JSON.parse(stored))
}
} catch (e) {
console.error('Error reading localStorage:', e)
}
}
}, [searchParams])
const handleShare = () => {
if (savedIds.length === 0) return
const shareUrl = `${window.location.origin}${window.location.pathname}?ids=${savedIds.join(',')}`
navigator.clipboard.writeText(shareUrl).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
})
}
// Filter listings matching saved IDs
const filteredListings = allListings.filter(l => savedIds.includes(l.id))
if (!isClient) {
return (
<div className="py-20 text-center font-mono text-xs text-shutter animate-pulse">
liste yükleniyor...
</div>
)
}
return (
<div className="space-y-8">
{/* Title / Action bar */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-b border-dashed border-pine/8 pb-6">
<div>
<span className="text-[10px] font-mono tracking-widest text-shutter uppercase bg-paper px-3 py-1.5 rounded-full border border-pine/5">
kişisel rehberiniz
</span>
<h1 className="text-3xl font-heading font-extrabold text-pine lowercase mt-2.5">
kaydedilen <span className="text-turquoise">mekanlar</span>
</h1>
</div>
{savedIds.length > 0 && (
<button
onClick={handleShare}
className="inline-flex items-center gap-2 bg-turquoise hover:bg-turquoise/90 text-paper text-xs font-bold py-3 px-5 rounded-xl shadow-sm transition active:scale-95 duration-150 shrink-0 self-start"
>
{copied ? (
<>
<ClipboardCheck className="w-4 h-4 text-paper" />
Paylaşım Linki Kopyalandı!
</>
) : (
<>
<Share2 className="w-4 h-4 text-paper" />
Listeyi Paylaş (URL Kopyala)
</>
)}
</button>
)}
</div>
{/* Grid List */}
{filteredListings.length === 0 ? (
<div className="bg-paper border border-pine/8 rounded-3xl p-12 text-center space-y-4 max-w-xl mx-auto">
<div className="w-12 h-12 rounded-full bg-stone/50 border border-pine/5 flex items-center justify-center mx-auto text-shutter">
<Heart className="w-5 h-5 fill-transparent" />
</div>
<div>
<h3 className="font-heading font-bold text-pine lowercase text-sm">listeniz henüz boş</h3>
<p className="text-ink/65 text-xs mt-1.5 max-w-sm mx-auto leading-relaxed">
Mekan kartlarındaki kalp simgesine tıklayarak beğendiğiniz yerleri buraya ekleyebilir ve daha sonra hızlıca erişebilirsiniz.
</p>
</div>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredListings.map((listing) => (
<ListingCard key={listing.id} listing={listing} />
))}
</div>
)}
</div>
)
}