generated from fahricansecer/boilerplate-fe
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
FolderOpen,
|
||||
PlayCircle,
|
||||
CheckCircle2,
|
||||
Coins,
|
||||
Plus,
|
||||
ArrowUpRight,
|
||||
Clock,
|
||||
Sparkles,
|
||||
TrendingUp,
|
||||
Loader2,
|
||||
Twitter,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { DashboardCharts } from "@/components/dashboard/dashboard-charts";
|
||||
import { RecentProjects } from "@/components/dashboard/recent-projects";
|
||||
import { TweetImportCard } from "@/components/dashboard/tweet-import-card";
|
||||
import { useDashboardStats, useCreditBalance } from "@/hooks/use-api";
|
||||
|
||||
const stagger = {
|
||||
hidden: { opacity: 0 },
|
||||
show: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.08 },
|
||||
},
|
||||
};
|
||||
|
||||
const fadeUp = {
|
||||
hidden: { opacity: 0, y: 16 },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: [0.16, 1, 0.3, 1] as const } },
|
||||
};
|
||||
|
||||
// Mock-mode: Backend yokken statik veri kullan
|
||||
const isMockMode = process.env.NEXT_PUBLIC_ENABLE_MOCK_MODE === "true";
|
||||
|
||||
const MOCK_STATS = {
|
||||
totalProjects: 12,
|
||||
completedVideos: 8,
|
||||
activeRenderJobs: 2,
|
||||
totalCreditsUsed: 27,
|
||||
creditsRemaining: 47,
|
||||
};
|
||||
|
||||
function getStatCards(data?: typeof MOCK_STATS, creditBalance?: { balance: number; monthlyLimit: number }) {
|
||||
const stats = data ?? MOCK_STATS;
|
||||
const credits = creditBalance ?? { balance: stats.creditsRemaining, monthlyLimit: 50 };
|
||||
|
||||
return [
|
||||
{
|
||||
label: "Toplam Proje",
|
||||
value: String(stats.totalProjects),
|
||||
change: `${stats.completedVideos} tamamlandı`,
|
||||
icon: FolderOpen,
|
||||
gradient: "from-violet-500/12 to-violet-600/5",
|
||||
iconBg: "bg-violet-500/12",
|
||||
iconColor: "text-violet-400",
|
||||
},
|
||||
{
|
||||
label: "Devam Eden",
|
||||
value: String(stats.activeRenderJobs),
|
||||
change: "İşleniyor",
|
||||
icon: PlayCircle,
|
||||
gradient: "from-cyan-500/12 to-cyan-600/5",
|
||||
iconBg: "bg-cyan-500/12",
|
||||
iconColor: "text-cyan-400",
|
||||
},
|
||||
{
|
||||
label: "Tamamlanan",
|
||||
value: String(stats.completedVideos),
|
||||
change: "Bu ay",
|
||||
icon: CheckCircle2,
|
||||
gradient: "from-emerald-500/12 to-emerald-600/5",
|
||||
iconBg: "bg-emerald-500/12",
|
||||
iconColor: "text-emerald-400",
|
||||
},
|
||||
{
|
||||
label: "Kalan Kredi",
|
||||
value: String(credits.balance),
|
||||
change: `${credits.monthlyLimit} üzerinden`,
|
||||
icon: Coins,
|
||||
gradient: "from-amber-500/12 to-amber-600/5",
|
||||
iconBg: "bg-amber-500/12",
|
||||
iconColor: "text-amber-400",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
// Real API hook'ları — mock modunda çağrılmaz
|
||||
const statsQuery = useDashboardStats();
|
||||
const creditQuery = useCreditBalance();
|
||||
|
||||
// Mock/Real veri birleştir
|
||||
const isLoading = !isMockMode && (statsQuery.isLoading || creditQuery.isLoading);
|
||||
const statsData = isMockMode ? MOCK_STATS : (statsQuery.data as typeof MOCK_STATS | undefined);
|
||||
const creditData = isMockMode ? undefined : (creditQuery.data as { balance: number; monthlyLimit: number } | undefined);
|
||||
|
||||
const statCards = getStatCards(statsData, creditData);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
variants={stagger}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="space-y-6 max-w-7xl mx-auto"
|
||||
>
|
||||
{/* ── Başlık + CTA ── */}
|
||||
<motion.div variants={fadeUp} className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="font-[family-name:var(--font-display)] text-2xl md:text-3xl font-bold tracking-tight">
|
||||
Hoş geldin 👋
|
||||
</h1>
|
||||
<p className="text-sm text-[var(--color-text-muted)] mt-1">
|
||||
AI ile videolarını oluşturmaya devam et
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/dashboard/projects/new"
|
||||
className="btn-primary flex items-center gap-2 text-sm"
|
||||
>
|
||||
<Plus size={16} />
|
||||
<span className="hidden sm:inline">Yeni Proje</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
{/* ── İstatistik Kartları ── */}
|
||||
<motion.div variants={fadeUp} className="grid grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4">
|
||||
{isLoading ? (
|
||||
<div className="col-span-4 flex items-center justify-center py-12">
|
||||
<Loader2 size={28} className="animate-spin text-violet-400" />
|
||||
</div>
|
||||
) : (
|
||||
statCards.map((stat) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<div
|
||||
key={stat.label}
|
||||
className={`card-surface p-4 md:p-5 bg-gradient-to-br ${stat.gradient}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className={`w-9 h-9 rounded-xl ${stat.iconBg} flex items-center justify-center`}>
|
||||
<Icon size={18} className={stat.iconColor} />
|
||||
</div>
|
||||
<ArrowUpRight size={14} className="text-[var(--color-text-ghost)]" />
|
||||
</div>
|
||||
<div className="font-[family-name:var(--font-display)] text-2xl md:text-3xl font-bold">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-1">
|
||||
<span className="text-xs text-[var(--color-text-muted)]">{stat.label}</span>
|
||||
<span className="text-[10px] text-[var(--color-text-ghost)]">{stat.change}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{/* ── Hızlı Eylemler ── */}
|
||||
<motion.div variants={fadeUp} className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<Link
|
||||
href="/dashboard/projects/new"
|
||||
className="group card-surface p-4 flex items-center gap-4 hover:border-violet-500/30"
|
||||
>
|
||||
<div className="w-11 h-11 rounded-xl bg-gradient-to-br from-violet-500 to-violet-700 flex items-center justify-center shrink-0 shadow-lg group-hover:shadow-violet-500/20 transition-shadow">
|
||||
<Sparkles size={20} className="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">AI ile Video Oluştur</h3>
|
||||
<p className="text-xs text-[var(--color-text-muted)] mt-0.5">Bir konu gir, gerisini AI halletsin</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/dashboard/templates"
|
||||
className="group card-surface p-4 flex items-center gap-4 hover:border-cyan-500/30"
|
||||
>
|
||||
<div className="w-11 h-11 rounded-xl bg-gradient-to-br from-cyan-500 to-cyan-700 flex items-center justify-center shrink-0 shadow-lg group-hover:shadow-cyan-500/20 transition-shadow">
|
||||
<TrendingUp size={20} className="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">Şablon Keşfet</h3>
|
||||
<p className="text-xs text-[var(--color-text-muted)] mt-0.5">Topluluk şablonlarını kullan</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/dashboard/projects"
|
||||
className="group card-surface p-4 flex items-center gap-4 hover:border-emerald-500/30"
|
||||
>
|
||||
<div className="w-11 h-11 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-700 flex items-center justify-center shrink-0 shadow-lg group-hover:shadow-emerald-500/20 transition-shadow">
|
||||
<Clock size={20} className="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">Devam Eden İşler</h3>
|
||||
<p className="text-xs text-[var(--color-text-muted)] mt-0.5">İşlenen videolarını takip et</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="#tweet-import"
|
||||
className="group card-surface p-4 flex items-center gap-4 hover:border-sky-500/30"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById('tweet-import')?.scrollIntoView({ behavior: 'smooth' });
|
||||
}}
|
||||
>
|
||||
<div className="w-11 h-11 rounded-xl bg-gradient-to-br from-sky-500 to-sky-700 flex items-center justify-center shrink-0 shadow-lg group-hover:shadow-sky-500/20 transition-shadow">
|
||||
<Twitter size={20} className="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">Tweet → Video</h3>
|
||||
<p className="text-xs text-[var(--color-text-muted)] mt-0.5">Viral tweet'leri videoya dönüştür</p>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
{/* ── Tweet Import + Grafikler ── */}
|
||||
<motion.div variants={fadeUp} className="grid grid-cols-1 lg:grid-cols-5 gap-4">
|
||||
<div id="tweet-import" className="lg:col-span-2">
|
||||
<TweetImportCard
|
||||
onProjectCreated={(id) => {
|
||||
window.location.href = `/dashboard/projects/${id}`;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="lg:col-span-3">
|
||||
<DashboardCharts />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* ── Son Projeler ── */}
|
||||
<motion.div variants={fadeUp}>
|
||||
<RecentProjects />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Check,
|
||||
X,
|
||||
Zap,
|
||||
Crown,
|
||||
Rocket,
|
||||
Sparkles,
|
||||
ArrowRight,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const plans = [
|
||||
{
|
||||
id: "free",
|
||||
name: "Free",
|
||||
icon: Sparkles,
|
||||
monthlyPrice: 0,
|
||||
yearlyPrice: 0,
|
||||
credits: 3,
|
||||
description: "AI video üretimini keşfet",
|
||||
color: "emerald",
|
||||
gradient: "from-emerald-500/15 to-emerald-600/5",
|
||||
borderActive: "border-emerald-500/30",
|
||||
buttonClass: "btn-ghost",
|
||||
buttonLabel: "Mevcut Plan",
|
||||
features: [
|
||||
{ label: "3 kredi / ay", included: true },
|
||||
{ label: "720p video kalitesi", included: true },
|
||||
{ label: "Max 30 saniye", included: true },
|
||||
{ label: "5 proje limiti", included: true },
|
||||
{ label: "Temel şablonlar", included: true },
|
||||
{ label: "Öncelikli kuyruk", included: false },
|
||||
{ label: "Marka kaldırma", included: false },
|
||||
{ label: "API erişimi", included: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pro",
|
||||
name: "Pro",
|
||||
icon: Zap,
|
||||
monthlyPrice: 19,
|
||||
yearlyPrice: 190,
|
||||
credits: 50,
|
||||
description: "İçerik üreticileri için güçlü araçlar",
|
||||
color: "violet",
|
||||
gradient: "from-violet-500/20 to-violet-600/8",
|
||||
borderActive: "border-violet-500/40",
|
||||
buttonClass: "btn-primary",
|
||||
buttonLabel: "Pro'ya Yükselt",
|
||||
recommended: true,
|
||||
features: [
|
||||
{ label: "50 kredi / ay", included: true },
|
||||
{ label: "1080p video kalitesi", included: true },
|
||||
{ label: "Max 120 saniye", included: true },
|
||||
{ label: "50 proje limiti", included: true },
|
||||
{ label: "Tüm şablonlar", included: true },
|
||||
{ label: "Öncelikli kuyruk", included: true },
|
||||
{ label: "Marka kaldırma", included: true },
|
||||
{ label: "API erişimi", included: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "business",
|
||||
name: "Business",
|
||||
icon: Crown,
|
||||
monthlyPrice: 49,
|
||||
yearlyPrice: 490,
|
||||
credits: -1,
|
||||
description: "Ajanslar ve profesyonel ekipler",
|
||||
color: "cyan",
|
||||
gradient: "from-cyan-500/15 to-cyan-600/5",
|
||||
borderActive: "border-cyan-500/30",
|
||||
buttonClass: "btn-primary",
|
||||
buttonLabel: "Business'a Yükselt",
|
||||
features: [
|
||||
{ label: "Sınırsız kredi", included: true },
|
||||
{ label: "1080p video kalitesi", included: true },
|
||||
{ label: "Max 180 saniye", included: true },
|
||||
{ label: "Sınırsız proje", included: true },
|
||||
{ label: "Tüm şablonlar + Özel", included: true },
|
||||
{ label: "Öncelikli kuyruk", included: true },
|
||||
{ label: "Marka kaldırma", included: true },
|
||||
{ label: "API erişimi", included: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const fadeUp = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1] as const } },
|
||||
};
|
||||
|
||||
export default function PricingPage() {
|
||||
const [isYearly, setIsYearly] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto space-y-10 py-4">
|
||||
{/* ── Başlık ── */}
|
||||
<motion.div
|
||||
variants={fadeUp}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="text-center space-y-3"
|
||||
>
|
||||
<h1 className="font-[family-name:var(--font-display)] text-3xl md:text-4xl font-bold tracking-tight">
|
||||
Planını Seç, Üretmeye Başla
|
||||
</h1>
|
||||
<p className="text-[var(--color-text-muted)] text-sm md:text-base max-w-md mx-auto">
|
||||
Her plan ücretsiz deneme ile başlar. İstediğin zaman yükselt veya iptal et.
|
||||
</p>
|
||||
|
||||
{/* Aylık / Yıllık Toggle */}
|
||||
<div className="flex items-center justify-center gap-3 pt-2">
|
||||
<span className={cn("text-sm", !isYearly ? "text-[var(--color-text-primary)]" : "text-[var(--color-text-muted)]")}>
|
||||
Aylık
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setIsYearly(!isYearly)}
|
||||
className={cn(
|
||||
"relative w-14 h-7 rounded-full transition-colors",
|
||||
isYearly ? "bg-violet-500" : "bg-[var(--color-bg-elevated)]"
|
||||
)}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute top-0.5 w-6 h-6 rounded-full bg-white shadow-md"
|
||||
animate={{ left: isYearly ? "calc(100% - 1.625rem)" : "0.125rem" }}
|
||||
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
||||
/>
|
||||
</button>
|
||||
<span className={cn("text-sm", isYearly ? "text-[var(--color-text-primary)]" : "text-[var(--color-text-muted)]")}>
|
||||
Yıllık
|
||||
</span>
|
||||
{isYearly && (
|
||||
<motion.span
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="badge badge-emerald text-[10px]"
|
||||
>
|
||||
%17 tasarruf
|
||||
</motion.span>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* ── Plan Kartları ── */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-5">
|
||||
{plans.map((plan, i) => {
|
||||
const Icon = plan.icon;
|
||||
const price = isYearly ? plan.yearlyPrice : plan.monthlyPrice;
|
||||
const period = isYearly ? "/yıl" : "/ay";
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={plan.id}
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: i * 0.1, duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={cn(
|
||||
"relative card-surface p-6 flex flex-col bg-gradient-to-br",
|
||||
plan.gradient,
|
||||
plan.recommended && "glow-violet md:-translate-y-2"
|
||||
)}
|
||||
>
|
||||
{plan.recommended && (
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
|
||||
<span className="badge bg-violet-500 text-white text-[10px] px-3 py-1 shadow-lg shadow-violet-500/30">
|
||||
⚡ Önerilen
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className={cn(
|
||||
"w-10 h-10 rounded-xl flex items-center justify-center",
|
||||
plan.color === "violet" && "bg-violet-500/15 text-violet-400",
|
||||
plan.color === "emerald" && "bg-emerald-500/15 text-emerald-400",
|
||||
plan.color === "cyan" && "bg-cyan-500/15 text-cyan-400"
|
||||
)}>
|
||||
<Icon size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-[family-name:var(--font-display)] text-lg font-bold">{plan.name}</h3>
|
||||
<p className="text-[11px] text-[var(--color-text-muted)]">{plan.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fiyat */}
|
||||
<div className="mb-5">
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="font-[family-name:var(--font-display)] text-4xl font-bold">
|
||||
${price}
|
||||
</span>
|
||||
{price > 0 && (
|
||||
<span className="text-sm text-[var(--color-text-muted)]">{period}</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-[var(--color-text-ghost)] mt-1">
|
||||
{plan.credits === -1 ? "Sınırsız video üretimi" : `${plan.credits} kredi dahil`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<ul className="space-y-2.5 flex-1 mb-6">
|
||||
{plan.features.map((feat) => (
|
||||
<li key={feat.label} className="flex items-center gap-2.5 text-sm">
|
||||
{feat.included ? (
|
||||
<Check size={14} className="text-emerald-400 shrink-0" />
|
||||
) : (
|
||||
<X size={14} className="text-[var(--color-text-ghost)] shrink-0" />
|
||||
)}
|
||||
<span className={cn(
|
||||
feat.included ? "text-[var(--color-text-secondary)]" : "text-[var(--color-text-ghost)]"
|
||||
)}>
|
||||
{feat.label}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* CTA */}
|
||||
<button className={cn("w-full py-3 rounded-xl font-semibold text-sm flex items-center justify-center gap-2", plan.buttonClass)}>
|
||||
{plan.buttonLabel}
|
||||
{price > 0 && <ArrowRight size={14} />}
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* ── Trust ── */}
|
||||
<motion.div
|
||||
variants={fadeUp}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="text-center space-y-2 pt-4"
|
||||
>
|
||||
<p className="text-xs text-[var(--color-text-ghost)]">
|
||||
🔒 Güvenli ödeme • İstediğin zaman iptal • 7 gün para iade garantisi
|
||||
</p>
|
||||
<p className="text-[10px] text-[var(--color-text-ghost)]">
|
||||
Stripe ile güvenli ödeme altyapısı
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
Sparkles,
|
||||
Languages,
|
||||
Clock,
|
||||
Palette,
|
||||
Monitor,
|
||||
Smartphone,
|
||||
Square,
|
||||
Loader2,
|
||||
Check,
|
||||
Wand2,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const steps = ["Konu & Dil", "Stil & Süre", "AI Senaryo"];
|
||||
|
||||
const languages = [
|
||||
{ code: "tr", label: "Türkçe", flag: "🇹🇷" },
|
||||
{ code: "en", label: "English", flag: "🇺🇸" },
|
||||
{ code: "es", label: "Español", flag: "🇪🇸" },
|
||||
{ code: "de", label: "Deutsch", flag: "🇩🇪" },
|
||||
{ code: "fr", label: "Français", flag: "🇫🇷" },
|
||||
{ code: "ar", label: "العربية", flag: "🇸🇦" },
|
||||
{ code: "pt", label: "Português", flag: "🇧🇷" },
|
||||
{ code: "ja", label: "日本語", flag: "🇯🇵" },
|
||||
];
|
||||
|
||||
const videoStyles = [
|
||||
{ id: "CINEMATIC", label: "Sinematik", emoji: "🎬", desc: "Film kalitesinde görseller" },
|
||||
{ id: "DOCUMENTARY", label: "Belgesel", emoji: "📹", desc: "Bilimsel ve ciddi ton" },
|
||||
{ id: "EDUCATIONAL", label: "Eğitim", emoji: "📚", desc: "Öğretici ve açıklayıcı" },
|
||||
{ id: "STORYTELLING", label: "Hikaye", emoji: "📖", desc: "Anlatı odaklı, sürükleyici" },
|
||||
{ id: "NEWS", label: "Haber", emoji: "📰", desc: "Güncel ve bilgilendirici" },
|
||||
{ id: "ARTISTIC", label: "Sanatsal", emoji: "🎨", desc: "Yaratıcı ve sıra dışı" },
|
||||
];
|
||||
|
||||
const aspectRatios = [
|
||||
{ id: "PORTRAIT_9_16", label: "9:16", icon: Smartphone, desc: "Shorts / Reels" },
|
||||
{ id: "SQUARE_1_1", label: "1:1", icon: Square, desc: "Instagram" },
|
||||
{ id: "LANDSCAPE_16_9", label: "16:9", icon: Monitor, desc: "YouTube" },
|
||||
];
|
||||
|
||||
export default function NewProjectPage() {
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
|
||||
// Form state
|
||||
const [topic, setTopic] = useState("");
|
||||
const [language, setLanguage] = useState("tr");
|
||||
const [style, setStyle] = useState("CINEMATIC");
|
||||
const [duration, setDuration] = useState(60);
|
||||
const [aspectRatio, setAspectRatio] = useState("PORTRAIT_9_16");
|
||||
|
||||
const canProceed = currentStep === 0 ? topic.trim().length >= 5 : true;
|
||||
|
||||
const handleGenerate = async () => {
|
||||
setIsGenerating(true);
|
||||
// API çağrısı burada yapılacak
|
||||
setTimeout(() => setIsGenerating(false), 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
{/* Geri + Başlık */}
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Link
|
||||
href="/dashboard/projects"
|
||||
className="w-9 h-9 rounded-xl flex items-center justify-center text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-elevated)] transition-colors"
|
||||
>
|
||||
<ArrowLeft size={18} />
|
||||
</Link>
|
||||
<div>
|
||||
<h1 className="font-[family-name:var(--font-display)] text-xl font-bold">Yeni Proje</h1>
|
||||
<p className="text-xs text-[var(--color-text-muted)]">AI ile video oluştur</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step Indicator */}
|
||||
<div className="flex items-center gap-2 mb-8">
|
||||
{steps.map((step, i) => (
|
||||
<div key={step} className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => i < currentStep && setCurrentStep(i)}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-medium transition-all",
|
||||
i === currentStep
|
||||
? "bg-violet-500/15 text-violet-400 border border-violet-500/25"
|
||||
: i < currentStep
|
||||
? "bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 cursor-pointer"
|
||||
: "text-[var(--color-text-ghost)] border border-[var(--color-border-faint)]"
|
||||
)}
|
||||
>
|
||||
{i < currentStep ? (
|
||||
<Check size={12} />
|
||||
) : (
|
||||
<span className="w-4 h-4 rounded-full border border-current flex items-center justify-center text-[10px]">
|
||||
{i + 1}
|
||||
</span>
|
||||
)}
|
||||
<span className="hidden sm:inline">{step}</span>
|
||||
</button>
|
||||
{i < steps.length - 1 && (
|
||||
<div className={cn(
|
||||
"w-6 h-px",
|
||||
i < currentStep ? "bg-emerald-500/40" : "bg-[var(--color-border-faint)]"
|
||||
)} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Step Content */}
|
||||
<AnimatePresence mode="wait">
|
||||
{currentStep === 0 && (
|
||||
<motion.div
|
||||
key="step-0"
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="space-y-6"
|
||||
>
|
||||
{/* Konu */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[var(--color-text-secondary)] mb-2 block">
|
||||
<Sparkles size={14} className="inline mr-1.5 text-violet-400" />
|
||||
Videonun Konusu
|
||||
</label>
|
||||
<textarea
|
||||
value={topic}
|
||||
onChange={(e) => setTopic(e.target.value)}
|
||||
placeholder="Örn: Boötes Boşluğu — evrendeki en büyük boşluk ve gizemi..."
|
||||
className="w-full h-32 px-4 py-3 rounded-xl bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)] text-[var(--color-text-primary)] text-sm placeholder:text-[var(--color-text-ghost)] focus:outline-none focus:border-violet-500/40 focus:ring-1 focus:ring-violet-500/20 transition-all resize-none"
|
||||
/>
|
||||
<p className="text-[11px] text-[var(--color-text-ghost)] mt-1.5">
|
||||
Ne kadar detaylı yazarsan, AI o kadar iyi senaryo üretir ({topic.length} karakter)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Dil Seçimi */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[var(--color-text-secondary)] mb-3 block">
|
||||
<Languages size={14} className="inline mr-1.5 text-cyan-400" />
|
||||
Video Dili
|
||||
</label>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{languages.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
onClick={() => setLanguage(lang.code)}
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-1 py-3 px-2 rounded-xl text-xs transition-all",
|
||||
language === lang.code
|
||||
? "bg-violet-500/12 border border-violet-500/30 text-violet-300"
|
||||
: "bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)] text-[var(--color-text-muted)] hover:border-[var(--color-border-default)]"
|
||||
)}
|
||||
>
|
||||
<span className="text-lg">{lang.flag}</span>
|
||||
<span className="font-medium">{lang.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{currentStep === 1 && (
|
||||
<motion.div
|
||||
key="step-1"
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="space-y-6"
|
||||
>
|
||||
{/* Video Stili */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[var(--color-text-secondary)] mb-3 block">
|
||||
<Palette size={14} className="inline mr-1.5 text-violet-400" />
|
||||
Video Stili
|
||||
</label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{videoStyles.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
onClick={() => setStyle(s.id)}
|
||||
className={cn(
|
||||
"flex flex-col items-start gap-1 p-3 rounded-xl text-left transition-all",
|
||||
style === s.id
|
||||
? "bg-violet-500/12 border border-violet-500/30 glow-violet"
|
||||
: "bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)] hover:border-[var(--color-border-default)]"
|
||||
)}
|
||||
>
|
||||
<span className="text-xl mb-0.5">{s.emoji}</span>
|
||||
<span className="text-sm font-medium">{s.label}</span>
|
||||
<span className="text-[10px] text-[var(--color-text-ghost)]">{s.desc}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Süre */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[var(--color-text-secondary)] mb-3 block">
|
||||
<Clock size={14} className="inline mr-1.5 text-cyan-400" />
|
||||
Hedef Süre: <span className="text-violet-400">{duration}s</span>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min={15}
|
||||
max={180}
|
||||
step={5}
|
||||
value={duration}
|
||||
onChange={(e) => setDuration(Number(e.target.value))}
|
||||
className="w-full h-1.5 rounded-full bg-[var(--color-bg-elevated)] appearance-none cursor-pointer
|
||||
[&::-webkit-slider-thumb]:appearance-none
|
||||
[&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5
|
||||
[&::-webkit-slider-thumb]:rounded-full
|
||||
[&::-webkit-slider-thumb]:bg-violet-500
|
||||
[&::-webkit-slider-thumb]:shadow-[0_0_12px_rgba(139,92,246,0.4)]
|
||||
[&::-webkit-slider-thumb]:cursor-grab"
|
||||
/>
|
||||
<div className="flex justify-between text-[10px] text-[var(--color-text-ghost)] mt-1">
|
||||
<span>15s</span>
|
||||
<span>60s</span>
|
||||
<span>120s</span>
|
||||
<span>180s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* En-Boy Oranı */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[var(--color-text-secondary)] mb-3 block">
|
||||
En-Boy Oranı
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
{aspectRatios.map((ar) => {
|
||||
const Icon = ar.icon;
|
||||
return (
|
||||
<button
|
||||
key={ar.id}
|
||||
onClick={() => setAspectRatio(ar.id)}
|
||||
className={cn(
|
||||
"flex-1 flex flex-col items-center gap-1.5 py-3 rounded-xl text-xs transition-all",
|
||||
aspectRatio === ar.id
|
||||
? "bg-violet-500/12 border border-violet-500/30 text-violet-300"
|
||||
: "bg-[var(--color-bg-surface)] border border-[var(--color-border-faint)] text-[var(--color-text-muted)] hover:border-[var(--color-border-default)]"
|
||||
)}
|
||||
>
|
||||
<Icon size={20} />
|
||||
<span className="font-semibold">{ar.label}</span>
|
||||
<span className="text-[10px] text-[var(--color-text-ghost)]">{ar.desc}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{currentStep === 2 && (
|
||||
<motion.div
|
||||
key="step-2"
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="space-y-6"
|
||||
>
|
||||
{/* Özet */}
|
||||
<div className="card-surface p-5 space-y-4">
|
||||
<h3 className="font-[family-name:var(--font-display)] text-base font-semibold">Proje Özeti</h3>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<span className="text-[var(--color-text-muted)] text-xs">Konu</span>
|
||||
<p className="font-medium mt-0.5 line-clamp-2">{topic || "—"}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[var(--color-text-muted)] text-xs">Dil</span>
|
||||
<p className="font-medium mt-0.5">
|
||||
{languages.find((l) => l.code === language)?.flag}{" "}
|
||||
{languages.find((l) => l.code === language)?.label}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[var(--color-text-muted)] text-xs">Stil</span>
|
||||
<p className="font-medium mt-0.5">
|
||||
{videoStyles.find((s) => s.id === style)?.emoji}{" "}
|
||||
{videoStyles.find((s) => s.id === style)?.label}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[var(--color-text-muted)] text-xs">Süre / Oran</span>
|
||||
<p className="font-medium mt-0.5">{duration}s • {aspectRatios.find((a) => a.id === aspectRatio)?.label}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Generate butonu */}
|
||||
<button
|
||||
onClick={handleGenerate}
|
||||
disabled={isGenerating}
|
||||
className={cn(
|
||||
"w-full py-4 rounded-xl font-semibold text-base flex items-center justify-center gap-2 transition-all",
|
||||
isGenerating
|
||||
? "bg-violet-500/20 text-violet-300 cursor-wait"
|
||||
: "btn-primary text-lg"
|
||||
)}
|
||||
>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
<span>AI Senaryo Üretiliyor...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Wand2 size={20} />
|
||||
<span>AI ile Senaryo Üret</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<p className="text-center text-[11px] text-[var(--color-text-ghost)]">
|
||||
Bu işlem 1 kredi kullanır • Tahmini süre: ~15 saniye
|
||||
</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
<div className="flex items-center justify-between mt-8 pt-4 border-t border-[var(--color-border-faint)]">
|
||||
<button
|
||||
onClick={() => setCurrentStep((s) => Math.max(0, s - 1))}
|
||||
disabled={currentStep === 0}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 text-sm font-medium transition-colors",
|
||||
currentStep === 0
|
||||
? "text-[var(--color-text-ghost)] cursor-not-allowed"
|
||||
: "text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]"
|
||||
)}
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
Geri
|
||||
</button>
|
||||
{currentStep < steps.length - 1 && (
|
||||
<button
|
||||
onClick={() => setCurrentStep((s) => Math.min(steps.length - 1, s + 1))}
|
||||
disabled={!canProceed}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 px-5 py-2 rounded-xl text-sm font-semibold transition-all",
|
||||
canProceed
|
||||
? "btn-primary"
|
||||
: "bg-[var(--color-bg-elevated)] text-[var(--color-text-ghost)] cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
İleri
|
||||
<ArrowRight size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Plus, Search, Filter, Grid3X3, List } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { RecentProjects } from "@/components/dashboard/recent-projects";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const statusFilters = [
|
||||
{ id: "all", label: "Tümü" },
|
||||
{ id: "DRAFT", label: "Taslak" },
|
||||
{ id: "RENDERING", label: "İşleniyor" },
|
||||
{ id: "COMPLETED", label: "Tamamlanan" },
|
||||
{ id: "FAILED", label: "Başarısız" },
|
||||
];
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const [activeFilter, setActiveFilter] = useState("all");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto space-y-6">
|
||||
{/* Başlık */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="font-[family-name:var(--font-display)] text-2xl font-bold">Projeler</h1>
|
||||
<p className="text-sm text-[var(--color-text-muted)] mt-0.5">
|
||||
Tüm video projelerini yönet
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/dashboard/projects/new" className="btn-primary flex items-center gap-2 text-sm">
|
||||
<Plus size={16} />
|
||||
<span>Yeni Proje</span>
|
||||
</Link>
|
||||
</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={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Proje 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>
|
||||
|
||||
{/* Status Tabs */}
|
||||
<div className="flex items-center gap-1.5 overflow-x-auto pb-1 scrollbar-none">
|
||||
{statusFilters.map((filter) => (
|
||||
<button
|
||||
key={filter.id}
|
||||
onClick={() => setActiveFilter(filter.id)}
|
||||
className={cn(
|
||||
"px-3.5 py-1.5 rounded-full text-xs font-medium whitespace-nowrap transition-all",
|
||||
activeFilter === filter.id
|
||||
? "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)]"
|
||||
)}
|
||||
>
|
||||
{filter.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Proje Listesi */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
|
||||
>
|
||||
<RecentProjects />
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
User,
|
||||
CreditCard,
|
||||
Bell,
|
||||
Palette,
|
||||
Globe,
|
||||
Shield,
|
||||
LogOut,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const sections = [
|
||||
{ id: "profile", label: "Profil", icon: User, desc: "Ad, e-posta ve avatar" },
|
||||
{ id: "billing", label: "Abonelik & Fatura", icon: CreditCard, desc: "Plan, kredi ve ödeme bilgileri" },
|
||||
{ id: "notifications", label: "Bildirimler", icon: Bell, desc: "E-posta ve push bildirimleri" },
|
||||
{ id: "appearance", label: "Görünüm", icon: Palette, desc: "Tema ve dil tercihleri" },
|
||||
{ id: "language", label: "Dil", icon: Globe, desc: "Varsayılan video ve arayüz dili" },
|
||||
{ id: "security", label: "Güvenlik", icon: Shield, desc: "Şifre ve iki faktörlü doğrulama" },
|
||||
];
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [activeSection, setActiveSection] = useState("profile");
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto space-y-6">
|
||||
<div>
|
||||
<h1 className="font-[family-name:var(--font-display)] text-2xl font-bold">Ayarlar</h1>
|
||||
<p className="text-sm text-[var(--color-text-muted)] mt-0.5">Hesap ve uygulama ayarlarını yönet</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{sections.map((section) => {
|
||||
const Icon = section.icon;
|
||||
return (
|
||||
<motion.button
|
||||
key={section.id}
|
||||
onClick={() => setActiveSection(section.id)}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-4 p-4 rounded-xl text-left transition-all",
|
||||
activeSection === section.id
|
||||
? "bg-violet-500/8 border border-violet-500/20"
|
||||
: "card-surface hover:border-[var(--color-border-default)]"
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
"w-10 h-10 rounded-xl flex items-center justify-center shrink-0",
|
||||
activeSection === section.id
|
||||
? "bg-violet-500/15 text-violet-400"
|
||||
: "bg-[var(--color-bg-elevated)] text-[var(--color-text-muted)]"
|
||||
)}>
|
||||
<Icon size={18} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold">{section.label}</h3>
|
||||
<p className="text-xs text-[var(--color-text-muted)] mt-0.5">{section.desc}</p>
|
||||
</div>
|
||||
<ChevronRight size={16} className="text-[var(--color-text-ghost)]" />
|
||||
</motion.button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Çıkış */}
|
||||
<button className="w-full flex items-center gap-4 p-4 rounded-xl text-left bg-rose-500/5 border border-rose-500/15 text-rose-400 hover:bg-rose-500/10 transition-colors">
|
||||
<div className="w-10 h-10 rounded-xl bg-rose-500/10 flex items-center justify-center">
|
||||
<LogOut size={18} />
|
||||
</div>
|
||||
<span className="text-sm font-semibold">Çıkış Yap</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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