228 lines
14 KiB
TypeScript
228 lines
14 KiB
TypeScript
import Image from "next/image";
|
||
import { logout } from "./login/actions";
|
||
import { getApps } from "./apps/actions";
|
||
import { getAppStatus } from "./asc/actions";
|
||
import {
|
||
LayoutDashboard,
|
||
Smartphone,
|
||
Settings,
|
||
Bell,
|
||
CloudUpload,
|
||
Plus,
|
||
ExternalLink,
|
||
Search,
|
||
CheckCircle2,
|
||
Clock,
|
||
XCircle,
|
||
RefreshCw,
|
||
LogOut,
|
||
SlidersHorizontal
|
||
} from "lucide-react";
|
||
import Link from "next/link";
|
||
|
||
const statusMap: Record<string, { label: string, color: string, bg: string }> = {
|
||
READY_FOR_SALE: { label: "Yayında", color: "text-emerald-700 dark:text-emerald-400", bg: "bg-emerald-100 dark:bg-emerald-900/30" },
|
||
PREPARE_FOR_SUBMISSION: { label: "Hazırlanıyor", color: "text-blue-700 dark:text-blue-400", bg: "bg-blue-100 dark:bg-blue-900/30" },
|
||
WAITING_FOR_REVIEW: { label: "İnceleme Bekliyor", color: "text-amber-700 dark:text-amber-400", bg: "bg-amber-100 dark:bg-amber-900/30" },
|
||
IN_REVIEW: { label: "İncelemede", color: "text-purple-700 dark:text-purple-400", bg: "bg-purple-100 dark:bg-purple-900/30" },
|
||
REJECTED: { label: "Reddedildi", color: "text-red-700 dark:text-red-400", bg: "bg-red-100 dark:bg-red-900/30" },
|
||
METADATA_REJECTED: { label: "Metadata Reddi", color: "text-red-700 dark:text-red-400", bg: "bg-red-100 dark:bg-red-900/30" },
|
||
PENDING_DEVELOPER_RELEASE: { label: "Onay Bekliyor", color: "text-cyan-700 dark:text-cyan-400", bg: "bg-cyan-100 dark:bg-cyan-900/30" },
|
||
PROCESSING_FOR_APP_STORE: { label: "İşleniyor", color: "text-zinc-600 dark:text-zinc-400", bg: "bg-zinc-100 dark:bg-zinc-800" },
|
||
};
|
||
|
||
export default async function Home() {
|
||
const allApps = await getApps();
|
||
|
||
const appsWithStatus = await Promise.all(allApps.map(async (app) => {
|
||
if (app.platform === 'ios' && app.appleId) {
|
||
try {
|
||
const { data } = await getAppStatus(app.appleId);
|
||
const latestVersion = data?.data?.[0];
|
||
const state = latestVersion?.attributes?.appStoreState;
|
||
const versionNumber = latestVersion?.attributes?.versionString;
|
||
return { ...app, apiStatus: state, version: versionNumber };
|
||
} catch (err) {
|
||
return { ...app, apiStatus: 'ERROR' };
|
||
}
|
||
}
|
||
return { ...app, apiStatus: 'UNKNOWN' };
|
||
}));
|
||
|
||
const inReviewCount = appsWithStatus.filter(a => a.apiStatus === 'IN_REVIEW' || a.apiStatus === 'WAITING_FOR_REVIEW').length;
|
||
const liveCount = appsWithStatus.filter(a => a.apiStatus === 'READY_FOR_SALE').length;
|
||
const rejectedCount = appsWithStatus.filter(a => a.apiStatus === 'REJECTED' || a.apiStatus === 'METADATA_REJECTED').length;
|
||
|
||
return (
|
||
<div className="flex h-screen bg-slate-50 dark:bg-black font-sans text-slate-900 dark:text-slate-100">
|
||
{/* Sidebar */}
|
||
<aside className="w-64 bg-white dark:bg-zinc-950 border-r border-slate-200 dark:border-zinc-900 flex flex-col shrink-0 shadow-sm">
|
||
<div className="p-6 border-b border-slate-100 dark:border-zinc-900 flex items-center gap-3">
|
||
<div className="w-7 h-7 bg-slate-900 dark:bg-white rounded-md flex items-center justify-center">
|
||
<span className="text-white dark:text-black font-bold text-sm">A</span>
|
||
</div>
|
||
<span className="font-bold text-lg tracking-tight uppercase tracking-wider">AppAdmin</span>
|
||
</div>
|
||
|
||
<nav className="flex-1 p-4 space-y-1">
|
||
<Link href="/" className="flex items-center gap-2.5 px-3 py-2 bg-slate-900 dark:bg-white text-white dark:text-black rounded-lg text-sm font-semibold transition-all">
|
||
<LayoutDashboard size={18} /> Dashboard
|
||
</Link>
|
||
<Link href="/apps" className="flex items-center gap-2.5 px-3 py-2 text-slate-600 dark:text-zinc-400 hover:bg-slate-50 dark:hover:bg-zinc-900 hover:text-slate-900 dark:hover:text-white rounded-lg text-sm font-medium">
|
||
<Smartphone size={18} /> Uygulamalar
|
||
</Link>
|
||
<Link href="/notifications" className="flex items-center gap-2.5 px-3 py-2 text-slate-600 dark:text-zinc-400 hover:bg-slate-50 dark:hover:bg-zinc-900 hover:text-slate-900 dark:hover:text-white rounded-lg text-sm font-medium">
|
||
<Bell size={18} /> Bildirimler
|
||
</Link>
|
||
</nav>
|
||
|
||
<div className="p-4 border-t border-slate-100 dark:border-zinc-900">
|
||
<form action={logout}>
|
||
<button className="flex items-center gap-2.5 px-3 py-2 w-full text-slate-500 dark:text-zinc-400 hover:text-red-600 dark:hover:text-red-400 transition-colors text-sm font-medium">
|
||
<LogOut size={18} /> Çıkış Yap
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</aside>
|
||
|
||
{/* Main Content */}
|
||
<main className="flex-1 overflow-y-auto">
|
||
<header className="h-16 bg-white/80 dark:bg-zinc-950/80 backdrop-blur-md border-b border-slate-200 dark:border-zinc-900 flex items-center justify-between px-8 sticky top-0 z-10">
|
||
<div className="flex items-center gap-3">
|
||
<h2 className="text-md font-bold text-slate-900 dark:text-white uppercase tracking-wider">Genel Bakış</h2>
|
||
<span className="px-2 py-0.5 bg-emerald-50 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400 text-[10px] font-bold rounded-full border border-emerald-100 dark:border-emerald-800 uppercase tracking-widest">
|
||
Live
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center gap-4">
|
||
<div className="relative group">
|
||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-slate-900 transition-colors" size={16} />
|
||
<input className="pl-9 pr-4 py-1.5 bg-slate-50 dark:bg-zinc-900 border border-slate-200 dark:border-zinc-800 rounded-lg text-sm w-64 text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-slate-400 dark:focus:ring-zinc-700 transition-all" placeholder="Uygulama ara..." />
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div className="p-8 space-y-8">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
<StatCard title="Uygulamalar" value={allApps.length.toString()} icon={<Smartphone size={20}/>} color="bg-blue-600" />
|
||
<StatCard title="İnceleme" value={inReviewCount.toString()} icon={<Clock size={20}/>} color="bg-amber-500" />
|
||
<StatCard title="Reddedilen" value={rejectedCount.toString()} icon={<XCircle size={20}/>} color="bg-red-500" />
|
||
<StatCard title="Yayında" value={liveCount.toString()} icon={<CheckCircle2 size={20}/>} color="bg-emerald-500" />
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
|
||
<div className="xl:col-span-2 space-y-6">
|
||
<div className="flex items-center justify-between border-b border-slate-200 dark:border-zinc-900 pb-4">
|
||
<h3 className="text-lg font-bold text-slate-900 dark:text-white uppercase tracking-wider">Uygulamalar</h3>
|
||
<Link href="/apps" className="flex items-center gap-1.5 px-4 py-1.5 bg-slate-900 dark:bg-white text-white dark:text-black rounded-lg text-xs font-bold hover:opacity-90 transition-all">
|
||
<Plus size={14} /> Yeni Ekle
|
||
</Link>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
{appsWithStatus.map((app) => {
|
||
const statusInfo = statusMap[app.apiStatus || ''] || { label: "Bilinmiyor", color: "text-slate-500 dark:text-zinc-400", bg: "bg-slate-100 dark:bg-zinc-900" };
|
||
|
||
return (
|
||
<div key={app.id} className="bg-white dark:bg-zinc-950 p-6 rounded-xl border border-slate-200 dark:border-zinc-900 hover:shadow-sm hover:border-slate-300 dark:hover:border-zinc-800 transition-all group">
|
||
<div className="flex justify-between items-start mb-6">
|
||
<div className="w-11 h-11 bg-slate-50 dark:bg-zinc-900 rounded-lg flex items-center justify-center border border-slate-100 dark:border-zinc-800 overflow-hidden">
|
||
{app.platform === 'ios' ? <Image src="/next.svg" alt="iOS" width={22} height={22} className="dark:invert p-0.5" /> : <Smartphone size={22} className="text-slate-400" />}
|
||
</div>
|
||
<div className={`px-2.5 py-0.5 rounded-md text-[10px] font-bold uppercase tracking-widest border border-slate-100 dark:border-zinc-800 ${statusInfo.bg} ${statusInfo.color}`}>
|
||
{statusInfo.label}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-0.5">
|
||
<div className="flex items-baseline gap-2">
|
||
<h4 className="font-bold text-md text-slate-900 dark:text-white truncate max-w-[140px]">{app.name}</h4>
|
||
{(app as any).version && <span className="text-[10px] font-mono text-slate-400">v{(app as any).version}</span>}
|
||
</div>
|
||
<p className="text-[10px] text-slate-400 font-mono truncate">{app.bundleId}</p>
|
||
</div>
|
||
|
||
<div className="mt-6 flex items-center justify-between pt-4 border-t border-slate-50 dark:border-zinc-900/50">
|
||
<div className="flex gap-4">
|
||
{/* Remote Config Butonu (Yeni) */}
|
||
<Link href={`/apps/${app.id}/config`} className="flex items-center gap-1.5 px-2 py-1 bg-slate-50 dark:bg-zinc-900 border border-slate-200 dark:border-zinc-800 rounded text-[10px] font-bold uppercase tracking-wider text-slate-600 dark:text-slate-400 hover:text-blue-600 transition-all">
|
||
<SlidersHorizontal size={12} /> Config
|
||
</Link>
|
||
{/* Edit Butonu */}
|
||
<Link href={`/apps/${app.id}/edit`} className="flex items-center gap-1.5 px-2 py-1 bg-slate-50 dark:bg-zinc-900 border border-slate-200 dark:border-zinc-800 rounded text-[10px] font-bold uppercase tracking-wider text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition-all">
|
||
<Settings size={12} /> Edit
|
||
</Link>
|
||
</div>
|
||
<a href={`https://apps.apple.com/app/id${app.appleId}`} target="_blank" className="text-slate-400 hover:text-blue-600 transition-colors">
|
||
<ExternalLink size={18} />
|
||
</a>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="xl:col-span-1 space-y-8">
|
||
<div className="bg-white dark:bg-zinc-950 p-6 rounded-xl border border-slate-200 dark:border-zinc-900">
|
||
<h3 className="text-sm font-bold mb-5 text-slate-400 uppercase tracking-widest">Sistem Durumu</h3>
|
||
<div className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs text-slate-600 dark:text-zinc-400 font-semibold uppercase tracking-wider">Apple Connect API</span>
|
||
<span className="flex items-center gap-1.5 text-emerald-600 text-[10px] font-bold uppercase tracking-widest">
|
||
<div className="w-1.5 h-1.5 bg-emerald-500 rounded-full" /> Bağlı
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs text-slate-600 dark:text-zinc-400 font-semibold uppercase tracking-wider">PostgreSQL DB</span>
|
||
<span className="flex items-center gap-1.5 text-emerald-600 text-[10px] font-bold uppercase tracking-widest">
|
||
<div className="w-1.5 h-1.5 bg-emerald-500 rounded-full" /> Aktif
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white dark:bg-zinc-950 p-6 rounded-xl border border-slate-200 dark:border-zinc-900">
|
||
<h3 className="text-sm font-bold mb-5 flex items-center justify-between text-slate-400 uppercase tracking-widest">
|
||
Hızlı İşlemler
|
||
<RefreshCw size={14} className="hover:rotate-180 transition-all duration-500 cursor-pointer" />
|
||
</h3>
|
||
<div className="space-y-2">
|
||
<QuickActionButton label="Duyuru Yayınla" icon={<Bell size={16}/>} />
|
||
<QuickActionButton label="Yenile" icon={<CloudUpload size={16}/>} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function StatCard({ title, value, icon, color }: { title: string, value: string, icon: any, color: string }) {
|
||
return (
|
||
<div className="bg-white dark:bg-zinc-950 p-6 rounded-xl border border-slate-200 dark:border-zinc-900">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-[0.1em]">{title}</span>
|
||
<div className={`p-1.5 rounded-md text-white ${color} shadow-sm`}>
|
||
{icon}
|
||
</div>
|
||
</div>
|
||
<div className="text-3xl font-bold text-slate-900 dark:text-white tabular-nums">{value}</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function QuickActionButton({ label, icon }: { label: string, icon: any }) {
|
||
return (
|
||
<button className="w-full flex items-center justify-between p-3 rounded-lg border border-slate-50 dark:border-zinc-900 hover:bg-slate-50 dark:hover:bg-zinc-900 hover:border-slate-100 dark:hover:border-zinc-800 transition-all text-xs font-bold text-slate-700 dark:text-zinc-300">
|
||
<div className="flex items-center gap-3">
|
||
{icon}
|
||
{label}
|
||
</div>
|
||
<Plus size={14} className="text-slate-300" />
|
||
</button>
|
||
)
|
||
}
|