generated from fahricansecer/boilerplate-fe
main
Some checks failed
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled
Some checks failed
UI Deploy (Next-Auth Support) 🎨 / build-and-deploy (push) Has been cancelled
This commit is contained in:
241
src/app/[locale]/(dashboard)/dashboard/page.tsx
Normal file
241
src/app/[locale]/(dashboard)/dashboard/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user