Files
multiverse/jumpa-iam/app/supreme-admin/billing/page.tsx
T

259 lines
14 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
interface Pkg {
id: string;
name: string;
price: string;
features: string;
isActive: boolean;
}
interface Tenant {
id: string;
name: string;
licenseNumber?: string;
packageId?: string;
isActive: boolean;
createdAt: string;
}
import { useDictionary } from "@/lib/dictionary";
import { useOmni } from "@/components/OmniSyncProvider";
export default function BillingManagementPage() {
const { t } = useDictionary();
const { currency, setCurrency, formatCurrency, locale, setLocale, theme, setTheme } = useOmni();
const [packages, setPackages] = useState<Pkg[]>([]);
const [tenants, setTenants] = useState<Tenant[]>([]);
const [loading, setLoading] = useState(true);
const [stats, setStats] = useState({ totalRevenue: 150000000, activeTenants: 0, growth: "+12.5%" });
useEffect(() => {
(async () => {
try {
const [pkgResp, eyeResp] = await Promise.all([
fetch("/api/superadmin/packages"),
fetch("/api/superadmin/supreme-dashboard")
]);
const pkgData = await pkgResp.json();
const eyeData = await eyeResp.json();
setPackages(pkgData.packages || []);
setTenants(eyeData.matrix || []);
// Calculate stats
const activeCount = (eyeData.matrix || []).filter((t: Tenant) => t.isActive).length;
setStats(prev => ({ ...prev, activeTenants: activeCount }));
} catch (_e) {
console.error("Failed to load billing data");
} finally {
setLoading(false);
}
})();
}, []);
const handleKillSwitch = async (id: string, name: string) => {
if (!confirm(`ACTIVATE KILL-SWITCH: Deactivate [${name}] immediately?`)) return;
try {
const res = await fetch('/api/superadmin/tenants', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'update', id, isActive: false })
});
if (res.ok) {
setTenants(prev => prev.map(t => t.id === id ? { ...t, isActive: false } : t));
}
} catch (_e) {}
};
const handleReactivate = async (id: string) => {
try {
const res = await fetch('/api/superadmin/tenants', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'update', id, isActive: true })
});
if (res.ok) {
setTenants(prev => prev.map(t => t.id === id ? { ...t, isActive: true } : t));
}
} catch (_e) {}
};
if (loading) return <div className="min-h-screen bg-[#050b14] flex items-center justify-center text-white font-mono animate-pulse">SYNCHRONIZING BILLING MATRIX...</div>;
return (
<div className="min-h-screen bg-[#050b14] p-6 md:p-12 text-white overflow-x-hidden relative">
{/* BACKGROUND DECORATION */}
<div className="absolute top-0 right-0 w-[500px] h-[500px] bg-blue-500/10 rounded-full blur-[150px] -z-10"></div>
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-purple-500/10 rounded-full blur-[150px] -z-10"></div>
<div className="max-w-7xl mx-auto space-y-10">
{/* HEADER */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-amber-500 rounded-lg flex items-center justify-center shadow-[0_0_20px_rgba(245,158,11,0.4)]">
<svg className="w-6 h-6 text-black" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</div>
<h1 className="text-4xl font-black tracking-tighter bg-linear-to-r from-amber-400 to-amber-600 bg-clip-text text-transparent">XCU {t('Billing.title')}</h1>
</div>
<p className="text-gray-400 font-medium">Quantum Revenue & Subscription Orchestration Node.</p>
</div>
<div className="flex items-center gap-2">
{/* SETTINGS MATRIX */}
<div className="flex items-center gap-2 p-1.5 glass-panel rounded-xl border-white/5 bg-black/20">
<button onClick={() => setLocale(locale === 'id' ? 'en' : 'id')} className="px-3 py-1.5 rounded-lg bg-white/5 hover:bg-white/10 text-[10px] font-black uppercase tracking-widest border border-white/5 transition-all">
{locale === 'id' ? 'ID 🇮🇩' : 'EN 🇺🇸'}
</button>
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} className="p-1.5 rounded-lg bg-white/5 hover:bg-white/10 border border-white/5 transition-all text-amber-500">
{theme === 'dark' ? (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"></path></svg>
) : (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path></svg>
)}
</button>
<select value={currency} onChange={(e) => setCurrency(e.target.value as any)} className="bg-transparent text-[10px] font-black uppercase tracking-widest border-none focus:ring-0 cursor-pointer text-emerald-500">
<option value="Rp" className="bg-[#050b14]">IDR</option>
<option value="USD" className="bg-[#050b14]">USD</option>
<option value="Crypto" className="bg-[#050b14]">XCU</option>
</select>
</div>
<Link href="/supreme-admin" className="px-6 py-3 glass-panel border border-white/10 hover:border-white/20 rounded-2xl font-bold transition-all hover:bg-white/5 flex items-center gap-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
{t('Common.home')}
</Link>
</div>
</div>
{/* STATS GRID */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
{[
{ label: "ESTIMATED REVENUE", value: formatCurrency(stats.totalRevenue), color: "text-emerald-400", sub: "Calculated Real-time" },
{ label: "ACTIVE TENANTS", value: stats.activeTenants.toString(), color: "text-blue-400", sub: stats.growth + " this month" },
{ label: "SYSTEM UPTIME", value: "99.999%", color: "text-purple-400", sub: "0ms Latency Logic" }
].map((s, i) => (
<div key={i} className="glass-panel p-6 rounded-3xl border border-white/5 bg-white/2 overflow-hidden relative group">
<div className="absolute inset-0 bg-linear-to-br from-white/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div className="text-[10px] font-black text-gray-500 tracking-[0.2em] mb-2">{s.label}</div>
<div className={`text-3xl font-black ${s.color} tracking-tight`}>{s.value}</div>
<div className="text-[10px] text-gray-500 mt-2">{s.sub}</div>
</div>
))}
</div>
{/* ACTIVE PACKAGES */}
<div className="space-y-6">
<h2 className="text-xl font-black tracking-widest text-gray-400 border-l-4 border-amber-500 pl-4">LIVE SUBSCRIPTION PLANS</h2>
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6">
{packages.map(pkg => {
const numericPrice = parseInt(pkg.price.replace(/[^0-9]/g, '') || '0');
return (
<div key={pkg.id} className="glass-panel p-8 rounded-[2rem] border border-white/10 relative overflow-hidden group hover:border-amber-500/50 transition-all">
<div className="absolute -top-10 -right-10 w-32 h-32 bg-amber-500/5 rounded-full blur-3xl group-hover:bg-amber-500/10 transition-colors"></div>
<h3 className="text-xl font-extrabold text-white mb-1">{pkg.name}</h3>
<div className="text-2xl font-black text-amber-500 mb-6">{numericPrice === 0 ? 'PARTNER FREE' : formatCurrency(numericPrice)}</div>
<div className="space-y-3 mb-8">
{JSON.parse(pkg.features || '[]').slice(0, 3).map((f: string, idx: number) => (
<div key={idx} className="flex items-center gap-2 text-xs text-gray-400">
<svg className="w-4 h-4 text-emerald-500" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd"></path></svg>
{f.replace(/\./g, ' ').split(' ').slice(-1)[0].toUpperCase()}
</div>
))}
{JSON.parse(pkg.features || '[]').length > 3 && (
<div className="text-[10px] text-gray-600 font-bold">+{JSON.parse(pkg.features || '[]').length - 3} MORE CAPABILITIES</div>
)}
</div>
<div className="pt-4 border-t border-white/5">
<div className="text-[10px] font-bold text-gray-500 uppercase tracking-widest">Status: <span className="text-emerald-500">PROVISIONED</span></div>
</div>
</div>
)})}
</div>
</div>
{/* TENANT TABLE */}
<div className="space-y-6">
<h2 className="text-xl font-black tracking-widest text-gray-400 border-l-4 border-blue-500 pl-4">TENANT SUBSCRIPTION REGISTRY</h2>
<div className="glass-panel rounded-[2rem] border border-white/5 overflow-hidden">
<table className="w-full text-left">
<thead>
<tr className="bg-white/5 text-[10px] font-black text-gray-400 uppercase tracking-[0.2em]">
<th className="px-8 py-6">Identity</th>
<th className="px-8 py-6">License Number</th>
<th className="px-8 py-6">Provision Date</th>
<th className="px-8 py-6">Status</th>
<th className="px-8 py-6 text-right">Emergency Control</th>
</tr>
</thead>
<tbody className="divide-y divide-white/5">
{tenants.map(t => (
<tr key={t.id} className="hover:bg-white/[0.02] transition-colors group">
<td className="px-8 py-6">
<div className="text-sm font-bold text-white">{t.name}</div>
<div className="text-[10px] text-gray-500 font-mono mt-1">UID: {t.id.substring(0, 8)}...</div>
</td>
<td className="px-8 py-6">
<span className="text-[10px] font-mono bg-blue-500/10 text-blue-400 px-3 py-1 rounded-full border border-blue-500/20">
{t.licenseNumber || "N/A"}
</span>
</td>
<td className="px-8 py-6 text-xs text-gray-400 font-mono">
{new Date(t.createdAt).toLocaleDateString()}
</td>
<td className="px-8 py-6">
{t.isActive ? (
<div className="flex items-center gap-2 text-[10px] font-black text-emerald-400 uppercase tracking-widest">
<div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse shadow-[0_0_10px_rgba(16,185,129,0.5)]"></div>
ACTIVE
</div>
) : (
<div className="flex items-center gap-2 text-[10px] font-black text-red-500 uppercase tracking-widest">
<div className="w-2 h-2 bg-red-500 rounded-full"></div>
SUSPENDED
</div>
)}
</td>
<td className="px-8 py-6 text-right">
{t.isActive ? (
<button
onClick={() => handleKillSwitch(t.id, t.name)}
className="text-[10px] font-black text-red-500 border border-red-500/30 px-4 py-2 rounded-xl hover:bg-red-500 hover:text-white transition-all shadow-[0_0_10px_rgba(239,68,68,0)] hover:shadow-[0_0_15px_rgba(239,68,68,0.5)] uppercase tracking-widest"
>
Kill Switch
</button>
) : (
<button
onClick={() => handleReactivate(t.id)}
className="text-[10px] font-black text-emerald-500 border border-emerald-500/30 px-4 py-2 rounded-xl hover:bg-emerald-500 hover:text-white transition-all uppercase tracking-widest"
>
Restore Access
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* FOOTER WATERMARK */}
<div className="mt-20 text-center pb-10">
<div className="text-[10px] font-black text-gray-700 tracking-[0.5em] uppercase">JUMPA.ID ULTRA BILLING SYSTEM V2.4</div>
<div className="text-[8px] text-gray-800 mt-2 font-mono">ENCRYPTED QUANTUM HANDSHAKE: SUCCESS</div>
</div>
</div>
);
}