[TSM.ID].[11031972] PXE : Platform X Ecosystem I [118 Module -LIVE-]

This commit is contained in:
TSM.ID
2026-05-25 03:50:05 +07:00
commit e820143b3c
673 changed files with 101320 additions and 0 deletions
+750
View File
@@ -0,0 +1,750 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useOmni } from "@/components/OmniSyncProvider";
interface Tenant {
id: string;
name: string;
licenseNumber?: string;
licenses?: string | Record<string, string>;
brandColor?: string;
platformName?: string;
securityTier?: string;
}
interface User {
id: string;
email: string;
role: string;
licenses?: string | Record<string, string>;
byokEnabled?: boolean;
byokKey?: string;
createdAt: string;
}
interface Feature {
key: string;
name: string;
module?: string;
}
interface Package {
id: string;
name: string;
price: string;
features: string;
}
export default function AdminDashboard() {
const {} = useOmni();
const [tenant, setTenant] = useState<Tenant | null>(null);
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [systemFeatures, setSystemFeatures] = useState<Feature[]>([]);
const [activeTab, setActiveTab] = useState('users');
const [debugError, setDebugError] = useState<string | null>(null);
const [showAddUserModal, setShowAddUserModal] = useState(false);
const [showLimitsModal, setShowLimitsModal] = useState(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [userLicenses, setUserLicenses] = useState<Record<string, string>>({});
const [newEmail, setNewEmail] = useState("");
const [newPassword, setNewPassword] = useState("");
const [packages, setPackages] = useState<Package[]>([]);
const [checkoutLoading, setCheckoutLoading] = useState(false);
// BYOK States
const [orgByokEnabled, setOrgByokEnabled] = useState(false);
const [orgByokKey, setOrgByokKey] = useState("");
const [userByokEnabled, setUserByokEnabled] = useState(false);
const [userByokKey, setUserByokKey] = useState("");
// Quantum Toast System
const [toast, setToast] = useState<{message: string, type: 'success' | 'error'} | null>(null);
const showToast = (message: string, type: 'success' | 'error' = 'success') => {
setToast({ message, type });
setTimeout(() => setToast(null), 4000);
};
const router = useRouter();
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', { method: 'POST' });
window.location.href = '/';
} catch (e) {
console.error("Logout failed", e);
window.location.href = '/';
}
};
const fetchData = async () => {
try {
const resp = await fetch("/api/admin", { cache: "no-store" });
const data = await resp.json();
if (data.error) {
window.location.href = window.location.pathname.replace('/admin', '/dashboard');
} else {
setTenant(data.tenant);
setOrgByokEnabled(data.tenant.byokEnabled || false);
setOrgByokKey(data.tenant.byokKey || "");
setUsers(data.users);
setSystemFeatures(data.systemFeatures || []);
// Fetch Packages for Billing
const pResp = await fetch("/api/superadmin/packages");
const pData = await pResp.json();
setPackages(pData.packages || []);
}
} catch (e: any) {
console.error(e);
setDebugError(e.message || "Unknown error occurred");
} finally {
setLoading(false);
}
};
useEffect(() => {
const init = async () => {
await fetchData();
};
init();
}, []);
const handleAddUser = async (e: React.FormEvent) => {
e.preventDefault();
try {
const resp = await fetch("/api/admin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "add_user", email: newEmail, password: newPassword, role: "user" })
});
const data = await resp.json();
if (data.success) {
setUsers([...users, data.user]);
setShowAddUserModal(false);
setNewEmail(""); setNewPassword("");
showToast("Employee added successfully.");
} else {
showToast("Failed: " + data.error, "error");
}
} catch (_e) {
showToast("Server error.", "error");
}
};
const handleOpenLimits = (u: User) => {
setSelectedUser(u);
try {
let parsed: Record<string, string> = {};
if (typeof u.licenses === 'string') {
const js = JSON.parse(u.licenses);
if (Array.isArray(js)) js.forEach((k: string) => parsed[k] = "GRANTED");
else parsed = js;
} else if (u.licenses && typeof u.licenses === 'object') {
if (Array.isArray(u.licenses)) u.licenses.forEach((k: string) => parsed[k] = "GRANTED");
else parsed = u.licenses;
}
setUserLicenses(parsed);
setUserByokEnabled(u.byokEnabled || false);
setUserByokKey(u.byokKey || "");
} catch(_e) { setUserLicenses({}); }
setShowLimitsModal(true);
};
const handleSaveLimits = async () => {
try {
const resp = await fetch("/api/admin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "update_user_licenses",
targetUserId: selectedUser?.id,
newLicenses: userLicenses,
byokEnabled: userByokEnabled,
byokKey: userByokKey
})
});
if (resp.ok) {
showToast("User limits applied successfully.");
setShowLimitsModal(false);
fetchData();
try {
const bc = new BroadcastChannel('omni_channel');
bc.postMessage({ type: 'REFRESH_QUANTUM_TOKEN' });
} catch(e){}
} else {
const d = await resp.json();
showToast(d.error || "Error saving limits.", "error");
}
} catch (_e) {
showToast("Error saving limits. Periksa koneksi Anda.", "error");
}
};
const handleSaveOrgByok = async () => {
try {
const resp = await fetch("/api/admin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "update_tenant_byok",
byokEnabled: orgByokEnabled,
byokKey: orgByokKey
})
});
if (resp.ok) {
showToast("Organizational BYOK applied successfully.");
fetchData();
} else {
const d = await resp.json();
showToast("Error: " + d.error, "error");
}
} catch (_e) {
showToast("Error saving BYOK.", "error");
}
};
const handleCheckout = async (packageId: string) => {
setCheckoutLoading(true);
try {
const resp = await fetch("/api/admin/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ packageId })
});
const data = await resp.json();
if (data.url) {
window.location.assign(data.url);
} else {
showToast("Checkout Error: " + (data.error || "Unknown error"), "error");
}
} catch (_e) {
showToast("Payment gateway connection failed.", "error");
} finally {
setCheckoutLoading(false);
}
};
if (loading) return <div className="min-h-screen bg-[#f4f5f9] flex items-center justify-center text-blue-600 font-medium">Loading Admin Workspace...</div>;
if (debugError) return <div className="min-h-screen bg-[#f4f5f9] flex items-center justify-center text-red-600 font-medium">Error: {debugError}</div>;
if (!tenant) return <div className="min-h-screen bg-[#f4f5f9] flex items-center justify-center text-red-600 font-medium">Error: Tenant not found</div>;
return (
<div className="min-h-screen bg-[#f4f5f9] text-gray-900 font-sans pb-20 relative">
{/* Quantum Toast Notification System */}
{toast && (
<div className={`fixed top-20 right-8 z-[100] px-6 py-4 rounded-xl shadow-2xl backdrop-blur-xl border flex items-center gap-4 animate-in slide-in-from-right-8 fade-in duration-300 ${toast.type === 'success' ? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-700' : 'bg-red-500/10 border-red-500/30 text-red-700'}`}>
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${toast.type === 'success' ? 'bg-emerald-500 text-white' : 'bg-red-500 text-white'}`}>
{toast.type === 'success' ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path></svg>
) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12"></path></svg>
)}
</div>
<div>
<div className="text-xs font-black uppercase tracking-widest opacity-80 mb-0.5">{toast.type === 'success' ? 'System Success' : 'System Alert'}</div>
<div className="text-sm font-medium">{toast.message}</div>
</div>
</div>
)}
{/* HEADER ZOOM-LIKE */}
<header className="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-8 sticky top-0 z-40 shadow-sm">
<div className="flex items-center gap-3">
<svg className="w-7 h-7 text-blue-600" fill="currentColor" viewBox="0 0 24 24"><path d="M4 4h10a2 2 0 0 1 2 2v3.5l4-3v11l-4-3V18a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"></path></svg>
<h1 className="text-xl font-bold tracking-tight text-gray-800">Workspace Admin <span className="text-sm font-normal text-gray-500 ml-2">{tenant.name}</span></h1>
</div>
<div className="flex items-center gap-4">
<a href="/admin/telemetry" className="px-3 py-1.5 bg-gradient-to-r from-[#25D366] to-[#0b5cff] text-white text-xs font-bold rounded-md hover:opacity-90 transition-opacity flex items-center gap-2 shadow-sm">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
TENANT COMMAND CENTER
</a>
<button onClick={() => router.push('/dashboard')} className="text-sm text-gray-600 hover:text-gray-900 font-medium transition-colors">Go to App</button>
<button onClick={handleLogout} className="text-sm text-blue-600 hover:text-blue-800 font-semibold transition-colors">Sign Out</button>
</div>
</header>
<main className="max-w-7xl mx-auto px-6 py-8 flex flex-col md:flex-row gap-8">
{/* SIDEBAR */}
<div className="w-full md:w-64 shrink-0">
<div className="bg-white rounded-xl border border-gray-200 shadow-sm p-4 sticky top-24">
<div className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-3 px-2">Account Info</div>
<div className="mb-6 px-2">
<div className="font-bold text-gray-900">{tenant.name}</div>
<div className="text-xs text-gray-500 font-mono mt-1">LIC: {tenant.licenseNumber || 'PENDING'}</div>
{/* Security & Network Feature Badges */}
{(() => {
let lic: Record<string, string> = {};
try { lic = typeof tenant.licenses === 'string' ? JSON.parse(tenant.licenses || '{}') : (tenant.licenses || {}); } catch {}
const feats = [
{ key: 'tls_global_ca', label: "\ud83c\udf10 Let's Encrypt", cls: 'bg-emerald-50 text-emerald-700 border-emerald-200' },
{ key: 'tls_sovereign', label: '\ud83d\udee1\ufe0f Private CA (X)', cls: 'bg-cyan-50 text-cyan-700 border-cyan-200' },
{ key: 'ipv6_dual_stack', label: '\ud83c\udf10 IPv6 Dual-Stack', cls: 'bg-violet-50 text-violet-700 border-violet-200' },
];
const visible = feats.filter(f => lic[f.key] && lic[f.key] !== 'HIDDEN');
if (visible.length === 0) return null;
return (
<div className="mt-3 space-y-1.5">
{visible.map(f => {
const state = lic[f.key];
if (state === 'GRANTED') return (
<div key={f.key} className={`flex items-center gap-1.5 text-[10px] font-bold px-2 py-1 rounded-md border ${f.cls}`}>
<span className="relative flex h-1.5 w-1.5"><span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span><span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-emerald-500"></span></span>
{f.label}
</div>
);
if (state === 'UPSELL') return (
<button key={f.key} onClick={() => setActiveTab('billing')} className="flex items-center gap-1.5 text-[10px] font-bold px-2 py-1 rounded-md bg-amber-50 text-amber-600 border border-amber-200 hover:bg-amber-100 transition-colors w-full text-left">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd"></path></svg>
{f.label} <span className="ml-auto text-[8px] uppercase tracking-wider">Upgrade</span>
</button>
);
return null;
})}
</div>
);
})()}
</div>
<nav className="space-y-1">
<button
onClick={() => setActiveTab('users')}
className={`w-full text-left px-3 py-2 rounded-md font-medium text-sm transition-all ${activeTab === 'users' ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`}
>
User Management
</button>
<button
onClick={() => setActiveTab('billing')}
className={`w-full text-left px-3 py-2 rounded-md font-medium text-sm transition-all ${activeTab === 'billing' ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`}
>
Billing & Plans
</button>
<button
onClick={() => setActiveTab('security')}
className={`w-full text-left px-3 py-2 rounded-md font-medium text-sm transition-all ${activeTab === 'security' ? 'bg-blue-50 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`}
>
Security & BYOK
</button>
</nav>
</div>
</div>
{/* MAIN CONTENT */}
<div className="flex-1 space-y-6">
{activeTab === 'users' && (
<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="px-6 py-5 border-b border-gray-200 bg-gray-50 flex justify-between items-center">
<h2 className="text-lg font-semibold text-gray-800">Users ({users.length})</h2>
<button
onClick={() => setShowAddUserModal(true)}
className="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors shadow-sm"
>
+ Add User
</button>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="border-b border-gray-200 bg-white">
<th className="py-3 px-6 text-gray-500 font-semibold text-xs uppercase tracking-wider">Email / ID</th>
<th className="py-3 px-6 text-gray-500 font-semibold text-xs uppercase tracking-wider">Role</th>
<th className="py-3 px-6 text-gray-500 font-semibold text-xs uppercase tracking-wider">Joined Date</th>
<th className="py-3 px-6 text-right text-gray-500 font-semibold text-xs uppercase tracking-wider">Action</th>
</tr>
</thead>
<tbody className="text-sm">
{users.map((u, i) => (
<tr key={i} className="border-b border-gray-100 hover:bg-gray-50 transition-colors">
<td className="py-4 px-6 text-gray-900 font-medium">{u.email}</td>
<td className="py-4 px-6">
<span className={`px-2.5 py-1 rounded-md text-xs font-semibold ${u.role === 'admin' ? 'bg-purple-100 text-purple-700' : 'bg-gray-100 text-gray-600'}`}>
{u.role.toUpperCase()}
</span>
</td>
<td className="py-4 px-6 text-gray-500">{new Date(u.createdAt).toLocaleDateString('en-US')}</td>
<td className="py-4 px-6 text-right">
<button
onClick={() => handleOpenLimits(u)}
className="px-3 py-1.5 bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 hover:text-blue-600 rounded text-xs font-semibold transition-colors focus:ring-2 focus:ring-blue-500"
>
Feature Limits
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{activeTab === 'billing' && (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="bg-white rounded-xl border border-gray-200 shadow-sm p-8">
<h2 className="text-2xl font-bold text-gray-900 mb-2">Billing & Subscription</h2>
<p className="text-gray-500 text-sm mb-8">Choose the right plan to unlock Quantum XCU modules for your team.</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{packages.map(pkg => (
<div key={pkg.id} className="border border-gray-200 rounded-2xl p-6 hover:border-blue-500 transition-all group relative overflow-hidden">
<div className="font-bold text-lg text-gray-900 mb-1">{pkg.name}</div>
<div className="text-2xl font-black text-blue-600 mb-4">{pkg.price}</div>
<ul className="text-sm text-gray-500 space-y-2 mb-8">
{JSON.parse(pkg.features || '[]').slice(0, 4).map((f: string, idx: number) => (
<li key={idx} className="flex items-center gap-2">
<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}
</li>
))}
</ul>
<button
disabled={checkoutLoading}
onClick={() => handleCheckout(pkg.id)}
className="w-full py-3 bg-blue-600 text-white rounded-xl font-bold hover:bg-blue-700 transition-all disabled:opacity-50"
>
{checkoutLoading ? "Connecting..." : "Upgrade Now"}
</button>
</div>
))}
</div>
</div>
</div>
)}
{activeTab === 'security' && (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="bg-white rounded-xl border border-gray-200 shadow-sm p-8">
<div className="flex items-center gap-4 mb-8">
<div className="w-12 h-12 bg-blue-600 rounded-2xl flex items-center justify-center text-white shadow-lg">
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"></path></svg>
</div>
<div>
<h2 className="text-2xl font-bold text-gray-900">Quantum Security Suite</h2>
<p className="text-gray-500 text-sm">Manage organizational cryptographic sovereignty and BYOK settings.</p>
</div>
</div>
{/* TLS SOVEREIGN MODULE - ?? la Carte */}
{(() => {
let tlsState = 'HIDDEN';
try {
const lic = typeof tenant.licenses === 'string' ? JSON.parse(tenant.licenses || '{}') : (tenant.licenses || {});
tlsState = lic['tls_sovereign'] || 'HIDDEN';
} catch {}
if (tlsState === 'HIDDEN') return null;
return (
<div className={`mb-8 rounded-2xl border overflow-hidden ${
tlsState === 'GRANTED'
? 'bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 border-slate-700/50'
: 'bg-gradient-to-br from-amber-50 to-orange-50 border-amber-200'
}`}>
<div className="p-6">
<div className="flex items-center gap-4 mb-4">
<div className={`w-12 h-12 rounded-2xl flex items-center justify-center shadow-lg ${
tlsState === 'GRANTED'
? 'bg-gradient-to-br from-cyan-500 to-blue-600 shadow-cyan-500/20'
: 'bg-gradient-to-br from-amber-400 to-orange-500 shadow-amber-500/20'
}`}>
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
</div>
<div>
<h3 className={`text-lg font-bold ${
tlsState === 'GRANTED' ? 'text-white' : 'text-amber-900'
}`}>TLS Sovereign Module</h3>
<p className={`text-xs ${
tlsState === 'GRANTED' ? 'text-slate-400' : 'text-amber-700'
}`}>?? la Carte ??? Sovereign Communication Infrastructure</p>
</div>
</div>
{tlsState === 'GRANTED' ? (
<div className="space-y-4">
<div className="flex items-center gap-3">
<div className={`px-3 py-1.5 rounded-lg text-xs font-bold flex items-center gap-2 ${
tenant.securityTier === 'SOVEREIGN'
? 'bg-cyan-500/10 text-cyan-400 border border-cyan-500/20'
: tenant.securityTier === 'CLIENT_CA'
? 'bg-amber-500/10 text-amber-400 border border-amber-500/20'
: 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'
}`}>
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
{tenant.securityTier === 'SOVEREIGN' ? '\ud83d\udee1\ufe0f SOVEREIGN \u2014 Private CA (X)' : tenant.securityTier === 'CLIENT_CA' ? '\ud83c\udfdb\ufe0f CLIENT CA \u2014 Custom PKI' : '\ud83c\udf10 STANDARD \u2014 Let\'s Encrypt'}
</div>
<span className="relative flex h-2.5 w-2.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
</span>
<span className="text-[10px] text-emerald-400 font-bold uppercase">Active</span>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="bg-white/5 rounded-xl p-3 border border-white/10">
<div className="text-[10px] text-slate-500 font-bold uppercase mb-1">Mode</div>
<div className="text-sm text-white font-bold">{tenant.securityTier === 'SOVEREIGN' ? 'Private CA (Offline-Ready)' : tenant.securityTier === 'CLIENT_CA' ? 'Client PKI (Custom CA)' : 'Let\'s Encrypt (Global)'}</div>
</div>
<div className="bg-white/5 rounded-xl p-3 border border-white/10">
<div className="text-[10px] text-slate-500 font-bold uppercase mb-1">Validity</div>
<div className="text-sm text-white font-bold">{tenant.securityTier === 'SOVEREIGN' ? '30 Years (RSA-4096)' : tenant.securityTier === 'CLIENT_CA' ? 'Per CA Policy' : 'Auto-Renew (90 Days)'}</div>
</div>
</div>
<p className="text-[11px] text-slate-500 mt-2">
{tenant.securityTier === 'SOVEREIGN'
? 'Organisasi Anda menggunakan Private CA dari X. Hubungi Supreme Admin untuk panduan install sertifikat di device.'
: tenant.securityTier === 'CLIENT_CA'
? 'Organisasi Anda menggunakan CA sendiri (custom PKI). Sertifikat dan key di-manage oleh infrastruktur PKI internal organisasi.'
: 'Koneksi menggunakan sertifikat Let\'s Encrypt yang dipercaya semua browser. Untuk beralih ke Private CA, hubungi Supreme Admin.'
}
</p>
</div>
) : (
/* UPSELL STATE */
<div className="space-y-4">
<p className="text-sm text-amber-800 leading-relaxed">
Tingkatkan ke <strong>TLS Sovereign</strong> untuk mendapatkan infrastruktur komunikasi yang sepenuhnya tertutup
dengan Private CA, zero-trust security, dan kemampuan operasi offline tanpa bergantung internet global.
</p>
<div className="grid grid-cols-3 gap-3">
<div className="bg-white rounded-xl p-3 border border-amber-200 text-center">
<svg className="w-6 h-6 text-amber-600 mx-auto mb-1" fill="currentColor" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
<div className="text-[10px] font-bold text-amber-800">Private CA</div>
<div className="text-[9px] text-amber-600">RSA-4096</div>
</div>
<div className="bg-white rounded-xl p-3 border border-amber-200 text-center">
<svg className="w-6 h-6 text-amber-600 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M18.364 5.636a9 9 0 010 12.728M5.636 18.364a9 9 0 010-12.728" /></svg>
<div className="text-[10px] font-bold text-amber-800">Offline-Ready</div>
<div className="text-[9px] text-amber-600">Zero Internet</div>
</div>
<div className="bg-white rounded-xl p-3 border border-amber-200 text-center">
<svg className="w-6 h-6 text-amber-600 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
<div className="text-[10px] font-bold text-amber-800">30 Years</div>
<div className="text-[9px] text-amber-600">Certificate Life</div>
</div>
</div>
<button className="w-full py-3 bg-gradient-to-r from-amber-500 to-orange-500 text-white rounded-xl font-bold text-sm hover:shadow-lg hover:shadow-amber-500/20 transition-all hover:scale-[1.01] flex items-center justify-center gap-2">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
Upgrade ke TLS Sovereign
</button>
</div>
)}
</div>
</div>
);
})()}
<div className="bg-blue-50/50 border border-blue-100 rounded-2xl p-8">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-6">
<div className="flex-1">
<h3 className="text-lg font-bold text-blue-900 mb-2 uppercase tracking-wide">Organizational BYOK</h3>
<p className="text-sm text-blue-700 leading-relaxed max-w-xl">
Aktifkan kedaulatan data total dengan menggunakan kunci enkripsi milik Anda sendiri.
Kunci ini akan digunakan untuk mengenkripsi semua transmisi video, audio, dan chat dalam organisasi Anda.
</p>
</div>
<div className="flex flex-col gap-4 w-full md:w-72">
<label className="flex items-center gap-3 cursor-pointer bg-white px-4 py-3 rounded-2xl border border-blue-200 shadow-sm transition-all hover:border-blue-400">
<input
type="checkbox"
checked={orgByokEnabled}
onChange={e => setOrgByokEnabled(e.target.checked)}
className="w-5 h-5 text-blue-600 rounded-lg focus:ring-blue-500"
/>
<span className="text-xs font-black text-gray-700 uppercase tracking-widest">Enable BYOK Mode</span>
</label>
{orgByokEnabled && (
<div className="space-y-2 animate-in fade-in slide-in-from-top-2 duration-300">
<label className="text-[10px] font-black text-blue-600 uppercase ml-1">Custom Quantum Key</label>
<input
type="text"
placeholder="Enter Encryption Key..."
value={orgByokKey}
onChange={e => setOrgByokKey(e.target.value)}
className="w-full text-xs font-mono bg-white border border-blue-200 rounded-2xl px-4 py-3 outline-none focus:ring-2 focus:ring-blue-500 shadow-sm"
/>
</div>
)}
</div>
</div>
<div className="mt-8 pt-8 border-t border-blue-100 flex justify-end">
<button
onClick={handleSaveOrgByok}
className="px-8 py-3 bg-blue-600 text-white rounded-2xl font-black text-xs uppercase tracking-[0.2em] hover:bg-blue-700 transition-all shadow-lg hover:shadow-blue-500/20 active:scale-95"
>
Deploy Security Policy
</button>
</div>
</div>
<div className="mt-12">
<div className="flex items-center gap-2 text-emerald-600 mb-4">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"></path></svg>
<span className="text-xs font-black uppercase tracking-widest">Quantum Health Status: Protected</span>
</div>
<p className="text-[10px] text-gray-400 font-medium max-w-2xl italic">
Catatan: Setiap perubahan pada kebijakan keamanan akan dicatat dalam Quantum Ledger dan memerlukan waktu sinkronisasi hingga 50ms ke seluruh node global.
</p>
</div>
</div>
</div>
)}
</div>
</main>
{/* ADD USER MODAL */}
{showAddUserModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="bg-white p-8 rounded-xl shadow-2xl w-[400px]">
<h2 className="text-xl font-bold mb-6 text-gray-900">Add New User</h2>
<form onSubmit={handleAddUser}>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
<input type="email" value={newEmail} onChange={(e) => setNewEmail(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500" required />
</div>
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-1">Temporary Password</label>
<input type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500" required minLength={6} />
</div>
<div className="flex gap-3 justify-end">
<button type="button" onClick={() => setShowAddUserModal(false)} className="px-4 py-2 bg-white border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50">Cancel</button>
<button type="submit" className="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700">Add User</button>
</div>
</form>
</div>
</div>
)}
{/* USER LIMITS MODAL (BYOK) */}
{showLimitsModal && selectedUser && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="bg-white w-full max-w-2xl rounded-xl shadow-2xl overflow-hidden flex flex-col max-h-[90vh]">
<div className="px-6 py-4 border-b border-gray-200 bg-gray-50">
<h2 className="text-xl font-bold text-gray-900">User Feature Limits</h2>
<p className="text-sm text-gray-500 mt-1">Manage module access for <strong className="text-gray-800">{selectedUser.email}</strong>.</p>
</div>
<div className="p-6 overflow-y-auto flex-1">
{/* USER LEVEL BYOK SECTION */}
<div className="mb-10 bg-emerald-50/50 p-6 rounded-2xl border border-emerald-100">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div className="flex-1">
<h3 className="text-sm font-black text-emerald-900 uppercase tracking-widest mb-1">User BYOK Sovereignty</h3>
<p className="text-[10px] text-emerald-700 font-medium">Berikan kedaulatan enkripsi individual kepada user ini. Kunci ini akan melampaui kunci organisasi.</p>
</div>
<div className="flex flex-col gap-2 w-full md:w-64">
<label className="flex items-center gap-2 cursor-pointer bg-white px-3 py-2 rounded-xl border border-emerald-200">
<input type="checkbox" checked={userByokEnabled} onChange={e => setUserByokEnabled(e.target.checked)} className="w-4 h-4 text-emerald-600 rounded" />
<span className="text-[10px] font-bold text-gray-700">Enable Individual BYOK</span>
</label>
{userByokEnabled && (
<input
type="text"
placeholder="Individual Quantum Key..."
value={userByokKey}
onChange={e => setUserByokKey(e.target.value)}
className="text-[10px] font-mono bg-white border border-emerald-200 rounded-xl px-3 py-2 outline-none focus:ring-2 focus:ring-emerald-500"
/>
)}
</div>
</div>
</div>
<div className="space-y-4">
{(() => {
// Get tenant's granted features
let tenantGranted: Record<string, string> = {};
try {
const raw = tenant?.licenses;
if (typeof raw === 'string') {
const js = JSON.parse(raw);
if (Array.isArray(js)) js.forEach((k: string) => tenantGranted[k] = "GRANTED");
else tenantGranted = js;
} else if (raw && typeof raw === 'object') {
if (Array.isArray(raw)) raw.forEach((k: string) => tenantGranted[k] = "GRANTED");
else tenantGranted = raw;
}
} catch(_e) {}
// Render features by category
return ['JVC', 'JC', 'XCU', 'XTM', 'IAM'].map(mod => {
const filtered = systemFeatures.filter(f => f.module === mod || (!f.module && mod === 'IAM'));
if (filtered.length === 0) return null;
return (
<div key={mod} className="mb-8 last:mb-0">
<div className="text-[10px] font-black text-gray-400 mb-3 tracking-[0.2em] border-l-4 border-blue-500 pl-3 uppercase">{mod} CAPABILITIES</div>
<div className="space-y-3">
{filtered.map((feat: Feature) => {
const tStatus = tenantGranted[feat.key] || 'HIDDEN';
if (tStatus === 'HIDDEN') return null; // Invisible to tenant completely
if (tStatus === 'UPSELL') {
return (
<div key={feat.key} className="flex justify-between items-center bg-gray-50 p-4 rounded-lg border border-gray-200 opacity-70">
<div>
<div className="font-bold text-gray-600 flex items-center gap-2 text-sm">
<svg className="w-4 h-4 text-gray-500" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd"></path></svg>
{feat.name}
</div>
<div className="text-[10px] text-gray-500 font-mono mt-0.5">{feat.key}</div>
</div>
<div
onClick={() => setActiveTab('billing')}
className="text-[10px] font-bold text-blue-600 px-3 py-1 bg-blue-50 border border-blue-200 rounded cursor-pointer hover:bg-blue-100 transition-colors uppercase tracking-wider"
>
Upgrade Plan
</div>
</div>
);
}
// If GRANTED by Superadmin
const currentVal = userLicenses[feat.key] || 'GRANTED';
return (
<div key={feat.key} className="flex justify-between items-center bg-white p-4 rounded-lg border border-gray-200">
<div>
<div className="font-bold text-gray-900 text-sm">{feat.name}</div>
<div className="text-[10px] text-gray-500 font-mono mt-0.5">{feat.key}</div>
</div>
<select
value={currentVal}
onChange={(e) => setUserLicenses({...userLicenses, [feat.key]: e.target.value})}
className={`text-[10px] font-bold px-3 py-1.5 rounded outline-none border focus:ring-2 focus:ring-blue-500 ${
currentVal === 'GRANTED' ? 'bg-green-50 text-green-700 border-green-200' : 'bg-red-50 text-red-700 border-red-200'
}`}
>
<option value="GRANTED"> ALLOWED</option>
<option value="HIDDEN">🚫 BLOCKED</option>
</select>
</div>
);
})}
</div>
</div>
);
});
})()}
</div>
</div>
<div className="px-6 py-4 border-t border-gray-200 bg-gray-50 flex justify-end gap-3">
<button onClick={() => setShowLimitsModal(false)} className="px-4 py-2 bg-white border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50">Cancel</button>
<button onClick={handleSaveLimits} className="px-6 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700">Save Limits</button>
</div>
</div>
</div>
)}
</div>
);
}