feat(source2): lock dark mode, sans-serif typography, and all recent updates

This commit is contained in:
rafif committed 2026-08-20 23:02:00 +07:00
1 parent dd4c8f6876
commit 9f24b56e97
259 files changed
+15596 -8966

No files matched your search

+44 -50
View File
@@ -2,13 +2,14 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Loader2, Users, Plus } from "lucide-react";
import { getAdminUsers, getViewAsHeaders, startViewAsUser, type ManagedUser } from "@/lib/admin-api";
import { Loader2, Plus } from "lucide-react";
import { getAdminUsers, getViewAsHeaders, startViewAsUser, unlockUser, type ManagedUser } from "@/lib/admin-api";
import { HelpTrigger } from "@/components/help/HelpTrigger";
import ExternalAccountModal from "@/components/admin/ExternalAccountModal";
import { getColumns } from "./columns";
import CompanyCard, { CompanyGroup } from "./CompanyCard";
import { EmptyState } from "./EmptyState";
export default function UserAccountsPage() {
const router = useRouter();
@@ -23,10 +24,9 @@ export default function UserAccountsPage() {
const [modalUser, setModalUser] = useState<ManagedUser | null>(null);
const [targetCompany, setTargetCompany] = useState<string | null>(null);
const [viewAsLoading, setViewAsLoading] = useState<string | null>(null);
// Registry agents untuk lookup label agent (digunakan pada View-As banner)
const [unlockLoading, setUnlockLoading] = useState<string | number | null>(null);
const [agentRegistry, setAgentRegistry] = useState<Record<string, string>>({});
const loadData = async () => {
setIsLoading(true);
setError(null);
@@ -55,7 +55,6 @@ export default function UserAccountsPage() {
router.push("/");
} else {
loadData();
// Juga load agent registry untuk label lookup (digunakan pada banner View-As)
fetch("/api/dashboard/agents/list", { headers: getViewAsHeaders() })
.then(r => r.json())
.then(json => {
@@ -78,11 +77,8 @@ export default function UserAccountsPage() {
});
}, [router]);
if (!mounted) return null;
// User Accounts = HANYA akun customer/client (COMPANY_* roles)
// Role teknikal/operasional (SOC_ANALYST, EXECUTIVE, ENGINEER, dll) ada di halaman Agents
const COMPANY_ROLES = ['COMPANY_ADMIN', 'COMPANY_OPERATOR', 'COMPANY_VIEWER'];
const companyUsers = managedUsers.filter(u => COMPANY_ROLES.includes(u.role || '') && u.company_name);
@@ -114,21 +110,18 @@ export default function UserAccountsPage() {
};
const handleViewAsUser = async (user: ManagedUser) => {
// View-As berdasarkan agent_uuids pertama yang di-assign ke user target
const agentUuids = user.agent_uuids || [];
if (agentUuids.length === 0) {
alert(`User "${user.account_name || user.username}" has no assigned agents. Cannot enter View-As mode.`);
return;
}
const targetAgentUuid = agentUuids[0];
// Gunakan label agent dari registry (bukan UUID) untuk banner yang informatif
const agentLabel = agentRegistry[targetAgentUuid] || targetAgentUuid;
const userName = user.account_name || user.username;
const userRole = user.role;
setViewAsLoading(user.username);
try {
// startViewAsUser menyimpan: agent_label (label agent), user_name, user_role, view_as_type='user'
await startViewAsUser(targetAgentUuid, agentLabel, userName, userRole);
await startViewAsUser(targetAgentUuid, agentLabel, userName, userRole, user.id);
router.refresh();
window.location.reload();
} catch (err: any) {
@@ -138,28 +131,55 @@ export default function UserAccountsPage() {
}
};
const handleUnlockUser = async (user: ManagedUser) => {
setUnlockLoading(user.id);
try {
const res = await unlockUser(user.id);
if (res.ok) {
await loadData();
} else {
alert('Failed to unlock user: ' + (res.error || 'Unknown error'));
}
} catch (err: any) {
alert('Failed to unlock user: ' + err.message);
} finally {
setUnlockLoading(null);
}
};
const columns = getColumns({
role,
currentUsername,
viewAsLoading,
unlockLoading: unlockLoading !== null ? String(unlockLoading) : null,
onEditClick: handleEditClick,
onViewAsClick: (role === "SUPER_ADMIN" || role === "COMPANY_ADMIN") ? handleViewAsUser : undefined,
onUnlockClick: (role === "SUPER_ADMIN" || role === "COMPANY_ADMIN" || role === "TENANT_ADMIN") ? handleUnlockUser : undefined,
});
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-semibold tracking-tight text-white flex items-center gap-3">
User Accounts Directory
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-amber-500" />}
</h2>
<p className="text-sm text-muted-foreground mt-1">
<div className="space-y-6 pb-24 sm:pb-10 animate-fade-in">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-3 sm:justify-start sm:gap-4 w-full">
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold tracking-tight text-white flex items-center gap-2 sm:gap-3 truncate">
<span>User Accounts Directory</span>
{isLoading && <Loader2 className="w-4 h-4 sm:w-5 sm:h-5 animate-spin text-amber-500 flex-shrink-0" />}
</h2>
{/* Mobile Actions: Inline next to Title */}
<div className="flex items-center gap-1.5 flex-shrink-0 sm:hidden">
<HelpTrigger pageId="user-accounts" />
</div>
</div>
<p className="text-xs sm:text-sm text-slate-400 mt-1 leading-normal sm:leading-relaxed">
Manage company tenant user access, roles, and device ownership delegation.
</p>
</div>
<HelpTrigger pageId="user-accounts" />
{/* Laptop/Tablet Actions: Floats on the right */}
<div className="hidden sm:flex items-center gap-2 sm:gap-3 flex-shrink-0">
<HelpTrigger pageId="user-accounts" />
</div>
</div>
{error && (
@@ -175,32 +195,7 @@ export default function UserAccountsPage() {
) : (
<div className="space-y-8">
{companies.length === 0 ? (
<div className="max-w-md mx-auto py-12 px-6">
<div className="relative overflow-hidden bg-white/[0.02] backdrop-blur-md border border-white/10 rounded-3xl p-8 text-center shadow-2xl shadow-black/50">
{/* Ambient glow highlight */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-48 h-48 bg-amber-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="relative w-16 h-16 rounded-full bg-amber-500/10 border border-amber-500/20 flex items-center justify-center mx-auto mb-5 text-amber-400 shadow-lg shadow-amber-500/5 animate-pulse-subtle">
<Users className="w-8 h-8" />
</div>
<h3 className="relative text-white font-bold text-lg mb-1.5">No Company Accounts</h3>
<p className="relative text-sm text-slate-400 mb-6 max-w-xs mx-auto">
Create the first company tenant admin account to get started.
</p>
{role === "SUPER_ADMIN" && (
<button
type="button"
onClick={() => handleAddUserClick("")}
className="relative inline-flex items-center gap-2 px-5 py-2.5 bg-gradient-to-r from-amber-600 to-amber-700 hover:from-amber-500 hover:to-amber-600 text-white rounded-xl text-xs font-semibold shadow-md shadow-amber-600/10 hover:shadow-lg transition-all cursor-pointer"
>
<Plus className="w-4 h-4" />
Add Company Admin
</button>
)}
</div>
</div>
<EmptyState role={role} onAddUserClick={handleAddUserClick} />
) : (
companies.map((company) => (
<CompanyCard
@@ -214,11 +209,11 @@ export default function UserAccountsPage() {
)}
{role === "SUPER_ADMIN" && companies.length > 0 && (
<div className="flex justify-end pt-2">
<div className="flex justify-end pt-2 pb-6 sm:pb-0">
<button
type="button"
onClick={() => handleAddUserClick("")}
className="flex items-center gap-2 px-5 py-2.5 text-xs font-semibold bg-amber-600 hover:bg-amber-700 text-white rounded-xl transition-all shadow-md shadow-amber-600/10 hover:shadow-lg cursor-pointer"
className="w-full sm:w-auto flex items-center justify-center gap-2 px-5 py-3 text-xs font-semibold bg-amber-600 hover:bg-amber-700 text-white rounded-xl transition-all shadow-lg shadow-amber-600/20 cursor-pointer"
>
<Plus className="w-4 h-4" />
Add New Company Admin
@@ -228,7 +223,6 @@ export default function UserAccountsPage() {
</div>
)}
{/* Account Creation / Edit Modal */}
<ExternalAccountModal
isOpen={isModalOpen}
onClose={() => {