"use client"; import { Activity, ArrowLeft, Calendar, Database, Mail, Save, UserPlus, Users, } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useSession } from "next-auth/react"; import { useCallback, useEffect, useId, useState } from "react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useToast } from "@/hooks/use-toast"; type ToastFunction = (props: { title: string; description: string; variant?: "default" | "destructive"; }) => void; interface CompanyManagementState { company: Company | null; setCompany: (company: Company | null) => void; isLoading: boolean; setIsLoading: (loading: boolean) => void; isSaving: boolean; setIsSaving: (saving: boolean) => void; editData: Partial; setEditData: ( data: Partial | ((prev: Partial) => Partial) ) => void; originalData: Partial; setOriginalData: (data: Partial) => void; showInviteUser: boolean; setShowInviteUser: (show: boolean) => void; inviteData: { name: string; email: string; role: string }; setInviteData: ( data: | { name: string; email: string; role: string } | ((prev: { name: string; email: string; role: string }) => { name: string; email: string; role: string; }) ) => void; showUnsavedChangesDialog: boolean; setShowUnsavedChangesDialog: (show: boolean) => void; pendingNavigation: string | null; setPendingNavigation: (navigation: string | null) => void; } interface User { id: string; name: string; email: string; role: string; createdAt: string; invitedBy: string | null; invitedAt: string | null; } interface Company { id: string; name: string; email: string; status: string; maxUsers: number; createdAt: string; updatedAt: string; users: User[]; _count: { sessions: number; imports: number; }; } /** * Custom hook for company management state */ function useCompanyManagementState() { const [company, setCompany] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [editData, setEditData] = useState>({}); const [originalData, setOriginalData] = useState>({}); const [showInviteUser, setShowInviteUser] = useState(false); const [inviteData, setInviteData] = useState({ name: "", email: "", role: "USER", }); const [showUnsavedChangesDialog, setShowUnsavedChangesDialog] = useState(false); const [pendingNavigation, setPendingNavigation] = useState( null ); return { company, setCompany, isLoading, setIsLoading, isSaving, setIsSaving, editData, setEditData, originalData, setOriginalData, showInviteUser, setShowInviteUser, inviteData, setInviteData, showUnsavedChangesDialog, setShowUnsavedChangesDialog, pendingNavigation, setPendingNavigation, }; } /** * Custom hook for form IDs */ function useCompanyFormIds() { const companyNameFieldId = useId(); const companyEmailFieldId = useId(); const maxUsersFieldId = useId(); const inviteNameFieldId = useId(); const inviteEmailFieldId = useId(); return { companyNameFieldId, companyEmailFieldId, maxUsersFieldId, inviteNameFieldId, inviteEmailFieldId, }; } /** * Custom hook for data validation and comparison */ function useDataComparison( editData: Partial, originalData: Partial ) { const hasUnsavedChanges = useCallback(() => { const normalizeValue = (value: string | number | null | undefined) => { if (value === null || value === undefined || value === "") { return ""; } return value; }; const normalizedEditData = { name: normalizeValue(editData.name), email: normalizeValue(editData.email), status: normalizeValue(editData.status), maxUsers: editData.maxUsers || 0, }; const normalizedOriginalData = { name: normalizeValue(originalData.name), email: normalizeValue(originalData.email), status: normalizeValue(originalData.status), maxUsers: originalData.maxUsers || 0, }; return ( JSON.stringify(normalizedEditData) !== JSON.stringify(normalizedOriginalData) ); }, [editData, originalData]); return { hasUnsavedChanges }; } /** * Custom hook for company data fetching */ function useCompanyData( params: { id: string | string[] }, toast: ToastFunction, state: CompanyManagementState ) { const { setCompany, setEditData, setOriginalData, setIsLoading } = state; const [hasFetched, setHasFetched] = useState(false); const fetchCompany = useCallback(async () => { if (hasFetched) return; try { const response = await fetch(`/api/platform/companies/${params.id}`); if (response.ok) { const data = await response.json(); setCompany(data); const companyData = { name: data.name, email: data.email, status: data.status, maxUsers: data.maxUsers, }; setEditData(companyData); setOriginalData(companyData); setHasFetched(true); } else { const errorText = await response.text(); const errorMessage = `Failed to load company data (${response.status}: ${response.statusText})`; console.error("Failed to fetch company - HTTP Error:", { status: response.status, statusText: response.statusText, response: errorText, url: response.url, }); toast({ title: "Error", description: errorMessage, variant: "destructive", }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; console.error("Failed to fetch company - Network/Parse Error:", { message: errorMessage, error: error, stack: error instanceof Error ? error.stack : undefined, url: `/api/platform/companies/${params.id}`, }); toast({ title: "Error", description: `Failed to load company data: ${errorMessage}`, variant: "destructive", }); } finally { setIsLoading(false); } }, [ params.id, hasFetched, toast, setCompany, setEditData, setOriginalData, setIsLoading, ]); return { fetchCompany }; } /** * Custom hook for navigation handling */ function useNavigationControl( router: { push: (url: string) => void }, params: { id: string | string[] }, hasUnsavedChanges: () => boolean, state: CompanyManagementState ) { const { setPendingNavigation, setShowUnsavedChangesDialog } = state; const handleNavigation = useCallback( (url: string) => { if (url.includes(`/platform/companies/${params.id}`)) { router.push(url); return; } if (hasUnsavedChanges()) { setPendingNavigation(url); setShowUnsavedChangesDialog(true); } else { router.push(url); } }, [ router, params.id, hasUnsavedChanges, setPendingNavigation, setShowUnsavedChangesDialog, ] ); return { handleNavigation }; } /** * Helper function to render company information card */ function renderCompanyInfoCard( state: CompanyManagementState, canEdit: boolean, companyNameFieldId: string, companyEmailFieldId: string, maxUsersFieldId: string, hasUnsavedChanges: () => boolean, handleSave: () => Promise ) { return ( Company Information
state.setEditData((prev) => ({ ...prev, name: e.target.value, })) } disabled={!canEdit} />
state.setEditData((prev) => ({ ...prev, email: e.target.value, })) } disabled={!canEdit} />
{ const value = e.target.value; const parsedValue = Number.parseInt(value, 10); // Validate input: must be a positive number const maxUsers = !Number.isNaN(parsedValue) && parsedValue > 0 ? parsedValue : 1; // Default to 1 for invalid/negative values state.setEditData((prev) => ({ ...prev, maxUsers, })); }} disabled={!canEdit} />
{canEdit && hasUnsavedChanges() && (
)}
); } /** * Helper function to render users tab content */ function renderUsersTab(state: CompanyManagementState, canEdit: boolean) { return ( Users ({state.company?.users.length || 0}) {canEdit && ( )}
{state.company?.users.map((user) => (
{user.name?.charAt(0) || user.email.charAt(0).toUpperCase()}
{user.name || "No name"}
{user.email}
{user.role}
Joined {new Date(user.createdAt).toLocaleDateString()}
))} {(state.company?.users.length || 0) === 0 && (
No users found. Invite the first user to get started.
)}
); } export default function CompanyManagement() { const { data: session, status } = useSession(); const router = useRouter(); const params = useParams(); const { toast } = useToast(); const state = useCompanyManagementState(); const { companyNameFieldId, companyEmailFieldId, maxUsersFieldId, inviteNameFieldId, inviteEmailFieldId, } = useCompanyFormIds(); const { hasUnsavedChanges } = useDataComparison( state.editData, state.originalData ); const { fetchCompany } = useCompanyData( { id: params.id as string }, toast, state ); const { handleNavigation } = useNavigationControl( router, { id: params.id as string }, hasUnsavedChanges, state ); useEffect(() => { if (status === "loading") return; if (!session?.user?.isPlatformUser) { router.push("/platform/login"); return; } fetchCompany(); }, [status, session?.user?.isPlatformUser, fetchCompany, router]); const handleSave = async () => { state.setIsSaving(true); try { const response = await fetch(`/api/platform/companies/${params.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(state.editData), }); if (response.ok) { const updatedCompany = await response.json(); state.setCompany(updatedCompany); const companyData = { name: updatedCompany.name, email: updatedCompany.email, status: updatedCompany.status, maxUsers: updatedCompany.maxUsers, }; state.setOriginalData(companyData); toast({ title: "Success", description: "Company updated successfully", }); } else { throw new Error("Failed to update company"); } } catch (_error) { toast({ title: "Error", description: "Failed to update company", variant: "destructive", }); } finally { state.setIsSaving(false); } }; const handleStatusChange = async (newStatus: string) => { const statusAction = newStatus === "SUSPENDED" ? "suspend" : "activate"; try { const response = await fetch(`/api/platform/companies/${params.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status: newStatus }), }); if (response.ok) { state.setCompany((prev) => prev ? { ...prev, status: newStatus } : null ); state.setEditData((prev) => ({ ...prev, status: newStatus })); toast({ title: "Success", description: `Company ${statusAction}d successfully`, }); } else { throw new Error(`Failed to ${statusAction} company`); } } catch (_error) { toast({ title: "Error", description: `Failed to ${statusAction} company`, variant: "destructive", }); } }; const confirmNavigation = () => { if (state.pendingNavigation) { router.push(state.pendingNavigation); state.setPendingNavigation(null); } state.setShowUnsavedChangesDialog(false); }; const cancelNavigation = () => { state.setPendingNavigation(null); state.setShowUnsavedChangesDialog(false); }; const handleInviteUser = async () => { try { const response = await fetch( `/api/platform/companies/${params.id}/users`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(state.inviteData), } ); if (response.ok) { state.setShowInviteUser(false); state.setInviteData({ name: "", email: "", role: "USER" }); // Refresh company data to show new user const updatedResponse = await fetch( `/api/platform/companies/${params.id}` ); if (updatedResponse.ok) { const updatedData = await updatedResponse.json(); state.setCompany(updatedData); } toast({ title: "Success", description: "User invited successfully", }); } else { throw new Error("Failed to invite user"); } } catch (_error) { toast({ title: "Error", description: "Failed to invite user", variant: "destructive", }); } }; // Protect against browser back/forward and other navigation useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (hasUnsavedChanges()) { e.preventDefault(); e.returnValue = ""; } }; const handlePopState = (e: PopStateEvent) => { if (hasUnsavedChanges()) { const confirmLeave = window.confirm( "You have unsaved changes. Are you sure you want to leave this page?" ); if (!confirmLeave) { window.history.pushState(null, "", window.location.href); e.preventDefault(); } } }; window.addEventListener("beforeunload", handleBeforeUnload); window.addEventListener("popstate", handlePopState); return () => { window.removeEventListener("beforeunload", handleBeforeUnload); window.removeEventListener("popstate", handlePopState); }; }, [hasUnsavedChanges]); const getStatusBadgeVariant = (status: string) => { switch (status) { case "ACTIVE": return "default"; case "TRIAL": return "secondary"; case "SUSPENDED": return "destructive"; case "ARCHIVED": return "outline"; default: return "default"; } }; if (status === "loading" || state.isLoading) { return (
Loading company details...
); } if (!session?.user?.isPlatformUser || !state.company) { return null; } const canEdit = session.user.platformRole === "SUPER_ADMIN"; return (

{state.company.name}

{state.company.status}

Company Management

{canEdit && ( )}
Overview Users Settings Analytics {/* Stats Overview */}
Total Users
{state.company.users.length}

of {state.company.maxUsers} maximum

Total Sessions
{state.company._count.sessions}
Data Imports
{state.company._count.imports}
Created
{new Date(state.company.createdAt).toLocaleDateString()}
{/* Company Info */} {renderCompanyInfoCard( state, canEdit, companyNameFieldId, companyEmailFieldId, maxUsersFieldId, hasUnsavedChanges, handleSave )}
{renderUsersTab(state, canEdit)} Danger Zone {canEdit && ( <>

Suspend Company

Temporarily disable access to this company

Suspend Company Are you sure you want to suspend this company? This will disable access for all users. Cancel handleStatusChange("SUSPENDED")} > Suspend
{state.company.status === "SUSPENDED" && (

Reactivate Company

Restore access to this company

)} )}
Analytics
Analytics dashboard coming soon...
{/* Invite User Dialog */} {state.showInviteUser && (
Invite User
state.setInviteData((prev) => ({ ...prev, name: e.target.value, })) } placeholder="User's full name" />
state.setInviteData((prev) => ({ ...prev, email: e.target.value, })) } placeholder="user@example.com" />
)} {/* Unsaved Changes Dialog */} Unsaved Changes You have unsaved changes that will be lost if you leave this page. Are you sure you want to continue? Stay on Page Leave Without Saving
); }