main
Some checks failed
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled

This commit is contained in:
Harun CAN
2026-03-29 12:44:02 +03:00
parent fe9aff3fec
commit 45a540c530
26 changed files with 10706 additions and 86 deletions

View File

@@ -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>
);
}