'use client'; import { useEffect, useState } from 'react'; import { useRouter, useParams } from 'next/navigation'; import { usersService, User, getErrorMessage, uploadService, agentTypesService, AgentType, VerificationStatus, VerificationDocument, } from '@/services'; export default function UserDetailPage() { const router = useRouter(); const params = useParams(); const userId = params.id as string; const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(''); const [avatarUrl, setAvatarUrl] = useState(null); // Agent type editing const [agentTypes, setAgentTypes] = useState([]); const [selectedAgentTypeId, setSelectedAgentTypeId] = useState(''); const [isUpdatingAgentType, setIsUpdatingAgentType] = useState(false); const [updateSuccess, setUpdateSuccess] = useState(''); // Verification const [verificationDocuments, setVerificationDocuments] = useState([]); const [isLoadingDocuments, setIsLoadingDocuments] = useState(false); const [verificationNote, setVerificationNote] = useState(''); const [isUpdatingVerification, setIsUpdatingVerification] = useState(false); // Helper function to check if avatar is an S3 key const isS3Key = (avatar: string | null | undefined): boolean => { if (!avatar) return false; return !avatar.startsWith('http') && !avatar.startsWith('/'); }; useEffect(() => { fetchUser(); fetchAgentTypes(); }, [userId]); // Fetch verification documents when user is loaded and is an agent useEffect(() => { if (user && user.role === 'AGENT') { fetchVerificationDocuments(); } }, [user?.id, user?.role]); const fetchUser = async () => { setIsLoading(true); setError(''); try { const userData = await usersService.getUserById(userId); setUser(userData); // Set initial agent type if (userData.agentProfile?.agentTypeId) { setSelectedAgentTypeId(userData.agentProfile.agentTypeId); } // Fetch avatar URL if needed const avatar = userData.profile?.avatar; if (avatar) { if (isS3Key(avatar)) { try { const presignedUrl = await uploadService.getPresignedDownloadUrl(avatar); setAvatarUrl(presignedUrl); } catch (err) { console.error('Failed to get avatar URL:', err); } } else { setAvatarUrl(avatar); } } } catch (err) { const errorMessage = getErrorMessage(err); setError(errorMessage); if (errorMessage.includes('Unauthorized')) { router.push('/login'); } } finally { setIsLoading(false); } }; const fetchAgentTypes = async () => { try { const types = await agentTypesService.getAll(); setAgentTypes(types); } catch (err) { console.error('Failed to fetch agent types:', err); } }; const fetchVerificationDocuments = async () => { if (!user || user.role !== 'AGENT') return; setIsLoadingDocuments(true); try { const docs = await usersService.getVerificationDocuments(userId); // Fetch presigned URLs for each document const docsWithUrls = await Promise.all( docs.map(async (doc) => { try { // The S3 key is stored in 'id' field from the upload service // or in 'key' field if explicitly set. Fall back to id if key is missing. const fileKey = doc.key || doc.id; if (!fileKey || fileKey.startsWith('http')) { // If no key or already a URL, return document as-is return doc; } const url = await uploadService.getPresignedDownloadUrl(fileKey); return { ...doc, url }; } catch { return doc; } }) ); setVerificationDocuments(docsWithUrls); } catch (err) { console.error('Failed to fetch verification documents:', err); } finally { setIsLoadingDocuments(false); } }; const handleVerification = async (status: VerificationStatus) => { if (!user) return; setIsUpdatingVerification(true); setError(''); setUpdateSuccess(''); try { await usersService.updateVerification(user.id, { status, note: verificationNote || undefined, }); setUpdateSuccess( status === 'APPROVED' ? 'Agent verification approved successfully' : 'Agent verification rejected' ); setVerificationNote(''); // Refresh user data await fetchUser(); // Clear success message after 3 seconds setTimeout(() => setUpdateSuccess(''), 3000); } catch (err) { setError(getErrorMessage(err)); } finally { setIsUpdatingVerification(false); } }; const getVerificationStatusBadge = (status: VerificationStatus | undefined) => { switch (status) { case 'APPROVED': return ( Approved ); case 'REJECTED': return ( Rejected ); case 'PENDING_REVIEW': return ( Pending Review ); default: return ( Not Submitted ); } }; const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const handleUpdateAgentType = async () => { if (!selectedAgentTypeId || !user) return; setIsUpdatingAgentType(true); setError(''); setUpdateSuccess(''); try { await usersService.updateAgentType(user.id, selectedAgentTypeId); setUpdateSuccess('Agent type updated successfully'); // Refresh user data await fetchUser(); // Clear success message after 3 seconds setTimeout(() => setUpdateSuccess(''), 3000); } catch (err) { setError(getErrorMessage(err)); } finally { setIsUpdatingAgentType(false); } }; const formatDate = (dateString: string | null | undefined) => { if (!dateString) return 'N/A'; return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', }); }; if (isLoading) { return (
); } if (error && !user) { return (

{error}

); } if (!user) { return (

User not found

); } return (
{/* Back Button */} {/* Error/Success Messages */} {error && (

{error}

)} {updateSuccess && (

{updateSuccess}

)} {/* User Header */}
{/* Avatar */}
{avatarUrl ? ( ) : (
{user.profile?.firstName?.[0] || user.email[0].toUpperCase()}
)}
{/* Name & Email */}

{user.profile?.firstName} {user.profile?.lastName}

{user.email}

{/* Role & Status Badges */}
{user.role} {user.status}
{/* Basic Information */}

Basic Information

Provider

{user.authProvider === 'LOCAL' ? 'Email' : user.authProvider}

Email Verified

{user.emailVerified ? ( <> Verified ) : ( <> Not Verified )}

Joined

{formatDate(user.createdAt)}

Last Login

{user.lastLoginAt ? formatDate(user.lastLoginAt) : 'Never'}

{/* Contact Information */}

Contact Information

Phone

{user.profile?.phone || 'Not provided'}

City

{user.profile?.city || 'Not provided'}

State

{user.profile?.state || 'Not provided'}

Country

{user.profile?.country || 'Not provided'}

{/* Agent Details (only for AGENT role) */} {user.role === 'AGENT' && user.agentProfile && (

Agent Details

{/* Agent Details Grid */}
{/* Agent Type - Editable */}

Agent Type

{selectedAgentTypeId && selectedAgentTypeId !== user.agentProfile?.agentTypeId && ( )}

Slug

{user.agentProfile.slug || 'Not set'}

Company

{user.agentProfile.companyName || 'Not provided'}

License Number

{user.agentProfile.licenseNumber || 'Not provided'}

Years of Experience

{user.agentProfile.yearsOfExperience ?? 'Not provided'}

Profile Status

{user.agentProfile.profileCompleteness}%{' '} {user.agentProfile.isProfileComplete ? 'Complete' : 'Incomplete'}

{/* Headline & Bio */} {(user.agentProfile.headline || user.agentProfile.bio) && (
{user.agentProfile.headline && (

Headline

{user.agentProfile.headline}

)} {user.agentProfile.bio && (

Bio

{user.agentProfile.bio}

)}
)}
)} {/* Verification Section (only for AGENT role) */} {user.role === 'AGENT' && user.agentProfile && (

Verification Status

{getVerificationStatusBadge(user.agentProfile.verificationStatus as VerificationStatus)}
{/* Verification Info */} {user.agentProfile.isVerified && user.agentProfile.verifiedAt && (

Verified on {formatDate(user.agentProfile.verifiedAt)}

)} {user.agentProfile.verificationStatus === 'REJECTED' && user.agentProfile.verificationNote && (

Rejection Note:

{user.agentProfile.verificationNote}

)} {/* Uploaded Documents */}

Uploaded Documents

{isLoadingDocuments ? (
) : verificationDocuments.length > 0 ? (
{verificationDocuments.map((doc, index) => (

{doc.name}

{formatFileSize(doc.size)}

{doc.url && ( Download )}
))}
) : (

No documents uploaded yet

)}
{/* Admin Actions */} {user.agentProfile.verificationStatus !== 'APPROVED' && (

Admin Actions

{/* Note Input */}