generated from fahricansecer/boilerplate-fe
Some checks failed
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled
242 lines
8.8 KiB
TypeScript
242 lines
8.8 KiB
TypeScript
"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>
|
||
);
|
||
}
|