'use client' import { Fragment, useState, useEffect } from 'react' import { Dialog, Transition, Listbox } from '@headlessui/react' import { XMarkIcon, UserIcon, DocumentTextIcon, ShieldCheckIcon, CalendarIcon, EnvelopeIcon, PhoneIcon, MapPinIcon, BuildingOfficeIcon, IdentificationIcon, CheckCircleIcon, XCircleIcon, ChevronUpDownIcon, CheckIcon } from '@heroicons/react/24/outline' import { AdminAPI, DetailedUserInfo } from '../utils/api' import useAuthStore from '../store/authStore' interface UserDetailModalProps { isOpen: boolean onClose: () => void userId: string | null onUserUpdated?: () => void } type UserStatus = 'inactive' | 'pending' | 'active' | 'suspended' | 'archived' type ContractFileItem = { key: string filename: string documentId?: number | null contract_type?: 'contract' | 'gdpr' | string | null } const STATUS_OPTIONS: { value: UserStatus; label: string; color: string }[] = [ { value: 'pending', label: 'Pending', color: 'amber' }, { value: 'active', label: 'Active', color: 'green' }, { value: 'suspended', label: 'Suspended', color: 'rose' }, { value: 'archived', label: 'Archived', color: 'gray' }, { value: 'inactive', label: 'Inactive', color: 'gray' } ] export default function UserDetailModal({ isOpen, onClose, userId, onUserUpdated }: UserDetailModalProps) { const [userDetails, setUserDetails] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [saving, setSaving] = useState(false) const [selectedStatus, setSelectedStatus] = useState('pending') const token = useAuthStore(state => state.accessToken) // Contract preview state (lazy-loaded, per contract type) const [activePreviewTab, setActivePreviewTab] = useState<'contract' | 'gdpr'>('contract') const [previewState, setPreviewState] = useState({ contract: { loading: false, html: null as string | null, error: null as string | null, warning: null as string | null }, gdpr: { loading: false, html: null as string | null, error: null as string | null, warning: null as string | null }, }) const [contractFiles, setContractFiles] = useState<{ contract: ContractFileItem[]; gdpr: ContractFileItem[] }>({ contract: [], gdpr: [] }) const [docsLoading, setDocsLoading] = useState(false) const [moveLoading, setMoveLoading] = useState>({}) const [selectedFile, setSelectedFile] = useState<{ contract?: string; gdpr?: string }>({}) const missingIdOrContract = !!userDetails?.userStatus && ( userDetails.userStatus.documents_uploaded !== 1 || userDetails.userStatus.contract_signed !== 1 ) const storageMissing = !!userDetails?.storageStatus && ( userDetails.storageStatus.idDocumentsPresent === false || userDetails.storageStatus.contractPresent === false ) const canVerifyByStatus = !!(userDetails?.userStatus && userDetails.userStatus.email_verified === 1 && userDetails.userStatus.profile_completed === 1 && userDetails.userStatus.documents_uploaded === 1 && userDetails.userStatus.contract_signed === 1) const canVerify = storageMissing ? (canVerifyByStatus && !storageMissing) : canVerifyByStatus useEffect(() => { if (isOpen && userId && token) { fetchUserDetails() loadContractFiles() } }, [isOpen, userId, token]) useEffect(() => { if (!isOpen || !userId || !token || !userDetails) return loadContractFiles() // eslint-disable-next-line react-hooks/exhaustive-deps }, [userDetails]) useEffect(() => { if (!isOpen) return setActivePreviewTab('contract') setPreviewState({ contract: { loading: false, html: null, error: null, warning: null }, gdpr: { loading: false, html: null, error: null, warning: null } }) setContractFiles({ contract: [], gdpr: [] }) setSelectedFile({}) }, [isOpen, userId]) useEffect(() => { if (userDetails?.userStatus?.status) { setSelectedStatus(userDetails.userStatus.status as UserStatus) } }, [userDetails]) const fetchUserDetails = async () => { if (!userId || !token) return setLoading(true) setError(null) try { const response = await AdminAPI.getDetailedUserInfo(token, userId) if (response.success) { setUserDetails(response) } else { throw new Error(response.message || 'Failed to fetch user details') } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to fetch user details' setError(errorMessage) console.error('UserDetailModal.fetchUserDetails error:', err) } finally { setLoading(false) } } const handleStatusChange = async (newStatus: UserStatus) => { if (!userId || !token || newStatus === selectedStatus) return setSaving(true) setError(null) try { const response = await AdminAPI.updateUserStatus(token, userId, newStatus) if (response.success) { setSelectedStatus(newStatus) await fetchUserDetails() if (onUserUpdated) { onUserUpdated() } } else { throw new Error(response.message || 'Failed to update user status') } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to update user status' setError(errorMessage) console.error('UserDetailModal.handleStatusChange error:', err) } finally { setSaving(false) } } const handleToggleAdminVerification = async () => { if (!userId || !token || !userDetails?.userStatus) return setSaving(true) setError(null) try { const newValue = userDetails.userStatus.is_admin_verified === 1 ? 0 : 1 const response = await AdminAPI.updateUserVerification(token, userId, newValue) if (response.success) { await fetchUserDetails() if (onUserUpdated) { onUserUpdated() } } else { throw new Error(response.message || 'Failed to update verification status') } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to update verification status' setError(errorMessage) console.error('UserDetailModal.handleToggleAdminVerification error:', err) } finally { setSaving(false) } } const loadContractPreview = async (contractType: 'contract' | 'gdpr', documentId?: number, objectKey?: string) => { if (!userId || !token || !userDetails) return setPreviewState((prev) => ({ ...prev, [contractType]: { ...prev[contractType], loading: true, error: null, warning: null } })) try { const result = await AdminAPI.getContractPreviewHtml(token, String(userId), userDetails.user.user_type, contractType, documentId, objectKey) setPreviewState((prev) => ({ ...prev, [contractType]: { loading: false, html: result.html, error: null, warning: result.warning || null } })) } catch (e: any) { console.error('UserDetailModal.loadContractPreview error:', e) setPreviewState((prev) => ({ ...prev, [contractType]: { loading: false, html: null, error: e?.message || 'Failed to load contract preview', warning: e?.warning || null } })) } } const loadContractFiles = async () => { if (!userId || !token) return setDocsLoading(true) try { const result = await AdminAPI.listContractFiles(token, String(userId), userDetails?.user?.user_type) const contract = Array.isArray(result?.contract) ? result.contract : [] const gdpr = Array.isArray(result?.gdpr) ? result.gdpr : [] setContractFiles({ contract, gdpr }) setSelectedFile((prev) => ({ contract: prev.contract || contract[0]?.key || undefined, gdpr: prev.gdpr || gdpr[0]?.key || undefined })) } catch (e) { console.error('UserDetailModal.loadContractFiles error:', e) setContractFiles({ contract: [], gdpr: [] }) } finally { setDocsLoading(false) } } const moveContractDoc = async (documentId: number | undefined, targetType: 'contract' | 'gdpr', filename?: string | null, objectKey?: string) => { if (!userId || !token) return const label = targetType === 'gdpr' ? 'GDPR' : 'Contract' const name = filename ? `\n\nFile: ${filename}` : '' const ok = window.confirm(`Move this document to ${label}?${name}`) if (!ok) return const loadingKey = objectKey || String(documentId || '') setMoveLoading((prev) => ({ ...prev, [loadingKey]: true })) try { await AdminAPI.moveContractDocument(token, String(userId), documentId, targetType, objectKey) await loadContractFiles() } catch (e) { console.error('UserDetailModal.moveContractDoc error:', e) } finally { setMoveLoading((prev) => ({ ...prev, [loadingKey]: false })) } } const formatDate = (dateString: string | undefined | null) => { if (!dateString) return 'N/A' return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) } const getStatusColor = (status: UserStatus) => { const option = STATUS_OPTIONS.find(opt => opt.value === status) return option?.color || 'gray' } const getStatusBadgeClass = (color: string) => { const colorMap: Record = { amber: 'bg-amber-100 text-amber-800 border-amber-200', green: 'bg-green-100 text-green-800 border-green-200', rose: 'bg-rose-100 text-rose-800 border-rose-200', gray: 'bg-gray-100 text-gray-800 border-gray-200' } return colorMap[color] || colorMap.gray } if (!isOpen) return null return (
{/* Close Button */}
{/* Scrollable Content Area */}
{loading ? (
) : error ? (
) : userDetails ? (
{/* Header Section with User Info & Status */}
{userDetails.user.user_type === 'company' ? ( ) : ( )}

{userDetails.user.user_type === 'personal' ? `${userDetails.personalProfile?.first_name || ''} ${userDetails.personalProfile?.last_name || ''}`.trim() : userDetails.companyProfile?.company_name || 'Unknown'}

{userDetails.user.email}

{userDetails.user.user_type === 'personal' ? 'Personal' : 'Company'} {userDetails.user.role === 'super_admin' ? 'Super Admin' : userDetails.user.role}
{/* Status Badge */} {userDetails.userStatus && (
Current Status
{userDetails.userStatus.status.charAt(0).toUpperCase() + userDetails.userStatus.status.slice(1)}
)}
{/* Admin Controls Section */}

Admin Controls

{missingIdOrContract && (
ID documents or a signed contract are missing for this user. The user’s verification status should be checked.
)} {storageMissing && (
ID documents or a signed contract are missing from object storage. The user’s verification status should be checked.
)} {missingIdOrContract && (
ID documents or a signed contract are missing for this user. The user’s verification status should be checked.
)}
{/* Status Dropdown */}
{STATUS_OPTIONS.find(opt => opt.value === selectedStatus)?.label || selectedStatus} {STATUS_OPTIONS.map((option) => ( `relative cursor-pointer select-none py-2 pl-10 pr-4 ${ active ? 'bg-indigo-100 text-indigo-900' : 'text-gray-900' }` } value={option.value} > {({ selected }) => ( <> {option.label} {selected && ( )} )} ))}
{/* Admin Verification Toggle */}
{userDetails?.userStatus && (

{canVerify ? 'All steps completed. You can verify this user.' : 'User has not yet completed all required steps.'}

)}
{/* Contract Preview (admin verify flow) */}
Contract Preview
{(['contract','gdpr'] as const).map((tab) => ( ))}
{(() => { const files = contractFiles[activePreviewTab] || [] const selectedKey = selectedFile[activePreviewTab] || files[0]?.key const selectedItem = files.find((f) => f.key === selectedKey) || files[0] const moveTarget = activePreviewTab === 'contract' ? 'gdpr' : 'contract' const isMoving = selectedItem?.key ? !!moveLoading[selectedItem.key] : false return (
Files in {activePreviewTab.toUpperCase()}
{docsLoading && (
Loading files…
)} {!docsLoading && files.length === 0 && (
No files found in this folder.
)} {!docsLoading && files.length > 0 && ( <> {files.length > 1 && (
{files.map((f) => ( ))}
)} {selectedItem && (
Selected: {selectedItem.filename}
{files.length >= 1 && ( )}
)} )}
) })()} {previewState[activePreviewTab].warning && (
{previewState[activePreviewTab].warning}
)} {previewState[activePreviewTab].error && (
{previewState[activePreviewTab].error}
)} {previewState[activePreviewTab].loading && (
Loading preview…
)} {!previewState[activePreviewTab].loading && previewState[activePreviewTab].html && (