Files
ContentGen_FE/src/app/[locale]/(dashboard)/dashboard/page.tsx
Harun CAN 45a540c530
Some checks failed
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled
main
2026-03-29 12:44:02 +03:00

242 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}