Files
app-admin/app/page.tsx
2026-03-24 15:46:27 +03:00

228 lines
14 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.
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>
)
}