generated from fahricansecer/boilerplate-fe
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Search,
|
||||
Star,
|
||||
Copy,
|
||||
Eye,
|
||||
TrendingUp,
|
||||
Clock,
|
||||
Sparkles,
|
||||
Filter,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Template {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
language: string;
|
||||
usageCount: number;
|
||||
rating: number;
|
||||
duration: number;
|
||||
style: string;
|
||||
featured: boolean;
|
||||
}
|
||||
|
||||
const mockTemplates: Template[] = [
|
||||
{
|
||||
id: "t1",
|
||||
title: "Evrenin Gizemli Boşlukları",
|
||||
description: "Uzaydaki devasa boşlukları ve karanlık maddeyi keşfet",
|
||||
category: "Bilim",
|
||||
language: "tr",
|
||||
usageCount: 342,
|
||||
rating: 4.8,
|
||||
duration: 45,
|
||||
style: "CINEMATIC",
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
id: "t2",
|
||||
title: "5 Mind-Blowing Physics Facts",
|
||||
description: "Quantum mechanics to relativity in 60 seconds",
|
||||
category: "Education",
|
||||
language: "en",
|
||||
usageCount: 1205,
|
||||
rating: 4.9,
|
||||
duration: 60,
|
||||
style: "EDUCATIONAL",
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
id: "t3",
|
||||
title: "Mitolojik Yaratıklar",
|
||||
description: "Antik medeniyetlerin efsanevi canlıları",
|
||||
category: "Tarih",
|
||||
language: "tr",
|
||||
usageCount: 189,
|
||||
rating: 4.6,
|
||||
duration: 50,
|
||||
style: "STORYTELLING",
|
||||
featured: false,
|
||||
},
|
||||
{
|
||||
id: "t4",
|
||||
title: "Secretos del Océano Profundo",
|
||||
description: "Criaturas bioluminiscentes y volcanes submarinos",
|
||||
category: "Ciencia",
|
||||
language: "es",
|
||||
usageCount: 567,
|
||||
rating: 4.7,
|
||||
duration: 55,
|
||||
style: "DOCUMENTARY",
|
||||
featured: false,
|
||||
},
|
||||
{
|
||||
id: "t5",
|
||||
title: "AI Tüm Meslekleri Yok Edecek mi?",
|
||||
description: "Yapay zekanın iş dünyasına etkisi ve gelecek senaryoları",
|
||||
category: "Teknoloji",
|
||||
language: "tr",
|
||||
usageCount: 891,
|
||||
rating: 4.5,
|
||||
duration: 60,
|
||||
style: "NEWS",
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
id: "t6",
|
||||
title: "Die Geheimnisse der Pyramiden",
|
||||
description: "Ägyptische Pyramiden und ihre versteckten Kammern",
|
||||
category: "Geschichte",
|
||||
language: "de",
|
||||
usageCount: 234,
|
||||
rating: 4.4,
|
||||
duration: 40,
|
||||
style: "DOCUMENTARY",
|
||||
featured: false,
|
||||
},
|
||||
];
|
||||
|
||||
const categories = ["Tümü", "Bilim", "Education", "Tarih", "Teknoloji", "Ciencia", "Geschichte"];
|
||||
const sortOptions = [
|
||||
{ id: "popular", label: "En Popüler", icon: TrendingUp },
|
||||
{ id: "newest", label: "En Yeni", icon: Clock },
|
||||
{ id: "rating", label: "En İyi Puan", icon: Star },
|
||||
];
|
||||
|
||||
const flagEmoji: Record<string, string> = {
|
||||
tr: "🇹🇷",
|
||||
en: "🇺🇸",
|
||||
es: "🇪🇸",
|
||||
de: "🇩🇪",
|
||||
fr: "🇫🇷",
|
||||
};
|
||||
|
||||
const stagger = {
|
||||
hidden: { opacity: 0 },
|
||||
show: { opacity: 1, transition: { staggerChildren: 0.06 } },
|
||||
};
|
||||
|
||||
const fadeUp = {
|
||||
hidden: { opacity: 0, y: 16, scale: 0.97 },
|
||||
show: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.5, ease: [0.16, 1, 0.3, 1] as const } },
|
||||
};
|
||||
|
||||
export default function TemplatesPage() {
|
||||
const [search, setSearch] = useState("");
|
||||
const [activeCategory, setActiveCategory] = useState("Tümü");
|
||||
const [activeSort, setActiveSort] = useState("popular");
|
||||
|
||||
const filtered = mockTemplates.filter((t) => {
|
||||
if (activeCategory !== "Tümü" && t.category !== activeCategory) return false;
|
||||
if (search && !t.title.toLowerCase().includes(search.toLowerCase())) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto space-y-6">
|
||||
{/* ── Başlık ── */}
|
||||
<div>
|
||||
<h1 className="font-[family-name:var(--font-display)] text-2xl md:text-3xl font-bold">
|
||||
<Sparkles size={24} className="inline mr-2 text-violet-400" />
|
||||
Şablon Galerisi
|
||||
</h1>
|
||||
<p className="text-sm text-[var(--color-text-muted)] mt-1">
|
||||
Topluluk şablonlarını keşfet, tek tıkla kendi projene klonla
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* ── Arama + Filtreler ── */}
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="relative flex-1">
|
||||
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-ghost)]" />
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Şablon ara..."
|
||||
className="w-full pl-9 pr-4 py-2.5 rounded-xl bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)] text-sm text-[var(--color-text-primary)] placeholder:text-[var(--color-text-ghost)] focus:outline-none focus:border-violet-500/40 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-1.5">
|
||||
{sortOptions.map((opt) => {
|
||||
const Icon = opt.icon;
|
||||
return (
|
||||
<button
|
||||
key={opt.id}
|
||||
onClick={() => setActiveSort(opt.id)}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium transition-all",
|
||||
activeSort === opt.id
|
||||
? "bg-violet-500/12 text-violet-400 border border-violet-500/25"
|
||||
: "text-[var(--color-text-muted)] border border-[var(--color-border-faint)] hover:border-[var(--color-border-default)]"
|
||||
)}
|
||||
>
|
||||
<Icon size={13} />
|
||||
<span className="hidden sm:inline">{opt.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Kategori Tabs */}
|
||||
<div className="flex items-center gap-1.5 overflow-x-auto pb-1 scrollbar-none">
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setActiveCategory(cat)}
|
||||
className={cn(
|
||||
"px-3.5 py-1.5 rounded-full text-xs font-medium whitespace-nowrap transition-all",
|
||||
activeCategory === cat
|
||||
? "bg-violet-500/15 text-violet-400 border border-violet-500/25"
|
||||
: "text-[var(--color-text-muted)] border border-[var(--color-border-faint)] hover:border-[var(--color-border-default)]"
|
||||
)}
|
||||
>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* ── Grid ── */}
|
||||
<motion.div
|
||||
variants={stagger}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
{filtered.map((template) => (
|
||||
<motion.div key={template.id} variants={fadeUp}>
|
||||
<div className="group card-surface overflow-hidden hover:border-violet-500/20">
|
||||
{/* Cover */}
|
||||
<div className="relative h-36 bg-gradient-to-br from-[var(--color-bg-elevated)] to-[var(--color-bg-surface)] flex items-center justify-center overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-violet-500/5 to-cyan-500/5" />
|
||||
<span className="text-4xl opacity-70">{flagEmoji[template.language] || "🌍"}</span>
|
||||
{template.featured && (
|
||||
<span className="absolute top-3 left-3 badge badge-violet text-[9px]">
|
||||
✨ Öne Çıkan
|
||||
</span>
|
||||
)}
|
||||
<div className="absolute top-3 right-3 flex items-center gap-1 badge badge-amber text-[10px]">
|
||||
<Star size={10} className="fill-amber-400" />
|
||||
{template.rating}
|
||||
</div>
|
||||
{/* Hover overlay */}
|
||||
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-3">
|
||||
<button className="w-10 h-10 rounded-xl bg-white/10 backdrop-blur flex items-center justify-center text-white hover:bg-white/20 transition-colors">
|
||||
<Eye size={18} />
|
||||
</button>
|
||||
<button className="w-10 h-10 rounded-xl bg-violet-500/80 backdrop-blur flex items-center justify-center text-white hover:bg-violet-500 transition-colors">
|
||||
<Copy size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="p-4">
|
||||
<h3 className="text-sm font-semibold line-clamp-1 group-hover:text-violet-300 transition-colors">
|
||||
{template.title}
|
||||
</h3>
|
||||
<p className="text-xs text-[var(--color-text-muted)] mt-1 line-clamp-2">
|
||||
{template.description}
|
||||
</p>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<div className="flex items-center gap-2 text-[10px] text-[var(--color-text-ghost)]">
|
||||
<span>{template.duration}s</span>
|
||||
<span>•</span>
|
||||
<span>{template.usageCount} kullanım</span>
|
||||
</div>
|
||||
<button className="flex items-center gap-1 text-[11px] font-medium text-violet-400 hover:text-violet-300 transition-colors">
|
||||
<Copy size={12} />
|
||||
Klonla
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{filtered.length === 0 && (
|
||||
<div className="text-center py-16">
|
||||
<p className="text-[var(--color-text-muted)]">Aramanızla eşleşen şablon bulunamadı</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user