From 6f8573fe162a4c3662a9d8d00e7dfe9fe2ea2e0b Mon Sep 17 00:00:00 2001 From: seaznCode Date: Sat, 1 Nov 2025 18:47:21 +0100 Subject: [PATCH] feat: Add UserDetailModal component and update API for user status management - Implemented UserDetailModal component for displaying and editing user details. - Added functionality for archiving/unarchiving users and toggling admin verification. - Enhanced user profile editing capabilities with form inputs for personal and company profiles. - Introduced loading and error handling states for better user experience. - Updated API utility to include a new endpoint for updating user status. - Modified DetailedUserInfo interface to accommodate new user role 'super_admin'. --- src/app/admin/user-management/page.tsx | 31 +- src/app/components/UserDetailModal.tsx | 1115 ++++++++------------ src/app/components/UserDetailModal_Old.tsx | 860 +++++++++++++++ src/app/utils/api.ts | 13 +- 4 files changed, 1353 insertions(+), 666 deletions(-) create mode 100644 src/app/components/UserDetailModal_Old.tsx diff --git a/src/app/admin/user-management/page.tsx b/src/app/admin/user-management/page.tsx index 850676a..4bb7798 100644 --- a/src/app/admin/user-management/page.tsx +++ b/src/app/admin/user-management/page.tsx @@ -13,7 +13,7 @@ import { AdminAPI } from '../../utils/api' import useAuthStore from '../../store/authStore' type UserType = 'personal' | 'company' -type UserStatus = 'active' | 'pending' | 'disabled' | 'inactive' +type UserStatus = 'active' | 'pending' | 'disabled' | 'inactive' | 'suspended' | 'archived' type UserRole = 'user' | 'admin' interface User { @@ -99,12 +99,13 @@ export default function AdminUserManagementPage() { const fullName = u.user_type === 'company' ? companyName : `${firstName} ${lastName}` // Map backend status to frontend status - // Backend status can be: 'pending', 'active', 'suspended', 'inactive', etc. + // Backend status can be: 'pending', 'active', 'suspended', 'inactive', 'archived' // is_admin_verified: 1 = verified by admin, 0 = not verified - const userStatus: UserStatus = u.status === 'inactive' ? 'inactive' : + const userStatus: UserStatus = u.status === 'archived' ? 'archived' : + u.status === 'inactive' ? 'inactive' : + u.status === 'suspended' ? 'suspended' : u.is_admin_verified === 1 ? 'active' : u.status === 'pending' ? 'pending' : - u.status === 'suspended' ? 'disabled' : 'pending' // default fallback return ( @@ -173,10 +174,11 @@ export default function AdminUserManagementPage() { ] const rows = filtered.map(u => { // Map backend to friendly values - const userStatus: UserStatus = u.status === 'inactive' ? 'inactive' : + const userStatus: UserStatus = u.status === 'archived' ? 'archived' : + u.status === 'inactive' ? 'inactive' : + u.status === 'suspended' ? 'suspended' : u.is_admin_verified === 1 ? 'active' : - u.status === 'pending' ? 'pending' : - u.status === 'suspended' ? 'disabled' : 'pending' + u.status === 'pending' ? 'pending' : 'pending' return [ u.id, u.email, @@ -220,8 +222,10 @@ export default function AdminUserManagementPage() { const statusBadge = (s: UserStatus) => s==='active' ? badge('Active','green') : s==='pending' ? badge('Pending','amber') + : s==='suspended' ? badge('Suspended','rose') + : s==='archived' ? badge('Archived','gray') : s==='inactive' ? badge('Inactive','gray') - : badge('Disabled','rose') + : badge('Unknown','gray') const typeBadge = (t: UserType) => t==='personal' ? badge('Personal','blue') : badge('Company','purple') @@ -291,7 +295,7 @@ export default function AdminUserManagementPage() { setFStatus(e.target.value as any)} - className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:ring-2 focus:ring-indigo-500 focus:border-transparent" > {STATUSES.map(s => )} @@ -314,7 +318,7 @@ export default function AdminUserManagementPage() { setEditedProfile({...editedProfile, first_name: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> - -
- - setEditedProfile({...editedProfile, last_name: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, phone: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, date_of_birth: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, address: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, city: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, zip_code: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, country: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
- - )} - - {userDetails.companyProfile && ( - <> -
- - setEditedProfile({...editedProfile, company_name: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, tax_id: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, registration_number: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, phone: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, address: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, city: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, zip_code: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
-
- - setEditedProfile({...editedProfile, country: e.target.value})} - className="w-full rounded border-gray-300 px-2 py-1 text-sm" - /> -
- - )} - - ) : ( - // View mode - show readonly data - <> - {userDetails.personalProfile && ( -
-
- Name: - - {userDetails.personalProfile.first_name} {userDetails.personalProfile.last_name} - -
- {userDetails.personalProfile.phone && ( -
- Phone: - {userDetails.personalProfile.phone} -
- )} - {userDetails.personalProfile.date_of_birth && ( -
- Date of Birth: - {formatDate(userDetails.personalProfile.date_of_birth)} -
- )} - {userDetails.personalProfile.address && ( -
- Address: - - {userDetails.personalProfile.address}, {userDetails.personalProfile.zip_code} {userDetails.personalProfile.city}, {userDetails.personalProfile.country} - -
- )} -
- )} - - {userDetails.companyProfile && ( -
-
- Company Name: - {userDetails.companyProfile.company_name} -
- {userDetails.companyProfile.tax_id && ( -
- Tax ID: - {userDetails.companyProfile.tax_id} -
- )} - {userDetails.companyProfile.registration_number && ( -
- Registration Number: - {userDetails.companyProfile.registration_number} -
- )} - {userDetails.companyProfile.phone && ( -
- Phone: - {userDetails.companyProfile.phone} -
- )} - {userDetails.companyProfile.address && ( -
- Address: - - {userDetails.companyProfile.address}, {userDetails.companyProfile.zip_code} {userDetails.companyProfile.city}, {userDetails.companyProfile.country} - -
- )} -
- )} - - )} )} - {/* Documents */} - {(userDetails.documents.length > 0 || userDetails.contracts.length > 0 || userDetails.idDocuments.length > 0) && ( -
-
- -

Documents

+ {/* Company Profile Information */} + {userDetails.user.user_type === 'company' && userDetails.companyProfile && ( +
+
+

+ + Company Information +

+
+
+
+
Company Name
+
{userDetails.companyProfile.company_name || 'N/A'}
+
+
+
Registration Number
+
{userDetails.companyProfile.registration_number || 'N/A'}
+
+
+
Tax ID
+
{userDetails.companyProfile.tax_id || 'N/A'}
+
+
+
+ + Phone +
+
{userDetails.companyProfile.phone || 'N/A'}
+
+
+
+ + Address +
+
+ {userDetails.companyProfile.address || 'N/A'} + {userDetails.companyProfile.city && <>, {userDetails.companyProfile.city}} + {userDetails.companyProfile.zip_code && <>, {userDetails.companyProfile.zip_code}} + {userDetails.companyProfile.country && <>, {userDetails.companyProfile.country}} +
+
+
+
+
+ )} - {/* Regular Documents */} - {userDetails.documents.length > 0 && ( -
-
Uploaded Documents
-
- {userDetails.documents.map((doc) => ( -
-
- {doc.file_name} - ({formatFileSize(doc.file_size)}) -
- {formatDate(doc.uploaded_at)} -
- ))} + {/* Account Status */} + {userDetails.userStatus && ( +
+
+

+ + Registration Progress +

+
+
+
+
+ {userDetails.userStatus.email_verified === 1 ? ( + + ) : ( + + )} + Email Verified +
+
+ {userDetails.userStatus.profile_completed === 1 ? ( + + ) : ( + + )} + Profile Completed +
+
+ {userDetails.userStatus.documents_uploaded === 1 ? ( + + ) : ( + + )} + Documents Uploaded +
+
+ {userDetails.userStatus.contract_signed === 1 ? ( + + ) : ( + + )} + Contract Signed
- )} +
+
+ )} - {/* Contracts */} - {userDetails.contracts.length > 0 && ( -
-
Contracts
-
- {userDetails.contracts.map((contract) => ( -
-
- {contract.file_name} - ({formatFileSize(contract.file_size)}) -
- {formatDate(contract.uploaded_at)} -
- ))} -
-
- )} - - {/* ID Documents */} - {userDetails.idDocuments.length > 0 && ( -
-
ID Documents
-
- {userDetails.idDocuments.map((idDoc) => ( -
-
- - {idDoc.document_type} - {formatDate(idDoc.uploaded_at)} -
-
- {idDoc.frontUrl && ( + {/* Documents Section */} + {(userDetails.documents.length > 0 || userDetails.contracts.length > 0 || userDetails.idDocuments.length > 0) && ( +
+
+

+ + Documents ({userDetails.documents.length + userDetails.contracts.length + userDetails.idDocuments.length}) +

+
+
+ {/* Regular Documents */} + {userDetails.documents.length > 0 && ( +
+
Uploaded Documents
+
+ {userDetails.documents.map((doc) => ( +
+
+
-

Front:

- ID Front +
{doc.file_name}
+
{formatFileSize(doc.file_size)}
- )} - {idDoc.backUrl && ( +
+ {formatDate(doc.uploaded_at)} +
+ ))} +
+
+ )} + + {/* Contracts */} + {userDetails.contracts.length > 0 && ( +
+
Contracts
+
+ {userDetails.contracts.map((contract) => ( +
+
+
-

Back:

- ID Back +
{contract.file_name}
+
{formatFileSize(contract.file_size)}
+
+
+ {formatDate(contract.uploaded_at)} +
+ ))} +
+
+ )} + + {/* ID Documents */} + {userDetails.idDocuments.length > 0 && ( +
+
ID Documents
+
+ {userDetails.idDocuments.map((idDoc) => ( +
+
+ + {idDoc.document_type} + {formatDate(idDoc.uploaded_at)} +
+ {(idDoc.frontUrl || idDoc.backUrl) && ( +
+ {idDoc.frontUrl && ( +
+

Front

+ ID Front +
+ )} + {idDoc.backUrl && ( +
+

Back

+ ID Back +
+ )}
)}
-
- ))} + ))} +
-
- )} + )} +
)} {/* Permissions */} {userDetails.permissions.length > 0 && ( -
-
- -

Permissions

+
+
+

+ + Permissions ({userDetails.permissions.length}) +

-
- {userDetails.permissions.map((permission) => ( -
-
{permission.name}
- {permission.description && ( -
{permission.description}
- )} -
- ))} +
+
+ {userDetails.permissions.map((perm) => ( +
+ {perm.is_active ? ( + + ) : ( + + )} +
+
{perm.name}
+ {perm.description && ( +
{perm.description}
+ )} +
+
+ ))} +
)} -
- )} -
-
-
- {showArchiveConfirm ? ( - // Archive/Unarchive Confirmation Dialog -
-
- -

- {userDetails?.userStatus?.status === 'inactive' ? 'Unarchive User' : 'Archive User'} -

-
-

- {userDetails?.userStatus?.status === 'inactive' - ? 'Are you sure you want to unarchive this user? This will reactivate their account.' - : 'Are you sure you want to archive this user? This action will disable their account but preserve all their data.'} -

-
- - -
-
- ) : ( - // Normal action buttons -
- {isEditing ? ( - <> - - - - ) : ( - <> - - - {userDetails?.userStatus && ( - - )} - - - + {/* Action Buttons */} +
- - )} -
- )} +
+
+ ) : null} +
@@ -857,4 +668,4 @@ export default function UserDetailModal({ isOpen, onClose, userId, onUserUpdated ) -} \ No newline at end of file +} diff --git a/src/app/components/UserDetailModal_Old.tsx b/src/app/components/UserDetailModal_Old.tsx new file mode 100644 index 0000000..f21ccd8 --- /dev/null +++ b/src/app/components/UserDetailModal_Old.tsx @@ -0,0 +1,860 @@ +'use client' + +import { Fragment, useState, useEffect } from 'react' +import { Dialog, Transition } from '@headlessui/react' +import { + XMarkIcon, + UserIcon, + DocumentTextIcon, + ShieldCheckIcon, + CalendarIcon, + EnvelopeIcon, + PhoneIcon, + MapPinIcon, + BuildingOfficeIcon, + IdentificationIcon, + CheckCircleIcon, + XCircleIcon, + PencilSquareIcon, + TrashIcon, + ExclamationTriangleIcon +} 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 +} + +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 [isEditing, setIsEditing] = useState(false) + const [saving, setSaving] = useState(false) + const [archiving, setArchiving] = useState(false) + const [showArchiveConfirm, setShowArchiveConfirm] = useState(false) + const [editedProfile, setEditedProfile] = useState(null) + const token = useAuthStore(state => state.accessToken) + + useEffect(() => { + if (isOpen && userId && token) { + fetchUserDetails() + setIsEditing(false) + setShowArchiveConfirm(false) + setEditedProfile(null) + } + }, [isOpen, userId, token]) + + const fetchUserDetails = async () => { + if (!userId || !token) return + + setLoading(true) + setError(null) + + try { + const response = await AdminAPI.getDetailedUserInfo(token, userId) + if (response.success) { + setUserDetails(response) + // Initialize edited profile with current data + if (response.personalProfile) { + setEditedProfile(response.personalProfile) + } else if (response.companyProfile) { + setEditedProfile(response.companyProfile) + } + } 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 handleArchiveUser = async () => { + if (!userId || !token) return + + setArchiving(true) + setError(null) + + try { + const isCurrentlyInactive = userDetails?.userStatus?.status === 'inactive' + + if (isCurrentlyInactive) { + // Unarchive user + const response = await AdminAPI.unarchiveUser(token, userId) + if (response.success) { + onClose() + if (onUserUpdated) { + onUserUpdated() + } + } else { + throw new Error(response.message || 'Failed to unarchive user') + } + } else { + // Archive user + const response = await AdminAPI.archiveUser(token, userId) + if (response.success) { + onClose() + if (onUserUpdated) { + onUserUpdated() + } + } else { + throw new Error(response.message || 'Failed to archive user') + } + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to archive/unarchive user' + setError(errorMessage) + console.error('UserDetailModal.handleArchiveUser error:', err) + } finally { + setArchiving(false) + setShowArchiveConfirm(false) + } + } + + const handleSaveProfile = async () => { + if (!userId || !token || !editedProfile || !userDetails) return + + setSaving(true) + setError(null) + + try { + const userType = userDetails.user.user_type + const response = await AdminAPI.updateUserProfile(token, userId, editedProfile, userType) + if (response.success) { + // Refresh user details + await fetchUserDetails() + setIsEditing(false) + if (onUserUpdated) { + onUserUpdated() + } + } else { + throw new Error(response.message || 'Failed to update user profile') + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to update user profile' + setError(errorMessage) + console.error('UserDetailModal.handleSaveProfile error:', err) + } finally { + setSaving(false) + } + } + + const handleToggleAdminVerification = async () => { + if (!userId || !token || !userDetails) return + + setSaving(true) + setError(null) + + try { + const newVerificationStatus = userDetails.userStatus?.is_admin_verified === 1 ? 0 : 1 + // Note: You'll need to implement this API method + const response = await AdminAPI.updateUserVerification(token, userId, newVerificationStatus) + if (response.success) { + // Refresh user details + 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 formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('de-DE', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) + } + + 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 StatusBadge = ({ status, verified }: { status: boolean, verified?: boolean }) => { + if (verified) { + return ( + + + Verified + + ) + } + return ( + + {status ? : } + {status ? 'Complete' : 'Incomplete'} + + ) + } + + return ( + + + +
+ + +
+
+ + +
+ +
+ + {/* Scrollable Content Area */} +
+
+ + User Details + {isEditing && ( + + + Edit Mode + + )} + + + {loading && ( +
+
+ Loading user details... +
+ )} + + {error && ( +
+
{error}
+
+ )} + + {userDetails && ( +
+ {/* Basic User Info */} +
+
+ +

Basic Information

+
+
+
+ Email: + {userDetails.user.email} +
+
+ Type: + {userDetails.user.user_type} +
+
+ Role: + {userDetails.user.role} +
+
+ Created: + {formatDate(userDetails.user.created_at)} +
+ {userDetails.user.last_login_at && ( +
+ Last Login: + {formatDate(userDetails.user.last_login_at)} +
+ )} +
+
+ + {/* Verification Status */} + {userDetails.userStatus && ( +
+
+ +

Verification Status

+
+
+
+ Email + +
+
+ Profile + +
+
+ Documents + +
+
+ Contract + +
+
+ Admin Verified + +
+
+
+ )} + + {/* Profile Information */} + {(userDetails.personalProfile || userDetails.companyProfile) && ( +
+
+ {userDetails.user.user_type === 'personal' ? ( + + ) : ( + + )} +

Profile Information

+
+ + {isEditing && editedProfile ? ( + // Edit mode - show input fields +
+ {userDetails.personalProfile && ( + <> +
+ + setEditedProfile({...editedProfile, first_name: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, last_name: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, phone: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, date_of_birth: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, address: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, city: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, zip_code: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, country: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+ + )} + + {userDetails.companyProfile && ( + <> +
+ + setEditedProfile({...editedProfile, company_name: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, tax_id: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, registration_number: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, phone: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, address: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, city: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, zip_code: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+
+ + setEditedProfile({...editedProfile, country: e.target.value})} + className="w-full rounded border-gray-300 px-2 py-1 text-sm" + /> +
+ + )} +
+ ) : ( + // View mode - show readonly data + <> + {userDetails.personalProfile && ( +
+
+ Name: + + {userDetails.personalProfile.first_name} {userDetails.personalProfile.last_name} + +
+ {userDetails.personalProfile.phone && ( +
+ Phone: + {userDetails.personalProfile.phone} +
+ )} + {userDetails.personalProfile.date_of_birth && ( +
+ Date of Birth: + {formatDate(userDetails.personalProfile.date_of_birth)} +
+ )} + {userDetails.personalProfile.address && ( +
+ Address: + + {userDetails.personalProfile.address}, {userDetails.personalProfile.zip_code} {userDetails.personalProfile.city}, {userDetails.personalProfile.country} + +
+ )} +
+ )} + + {userDetails.companyProfile && ( +
+
+ Company Name: + {userDetails.companyProfile.company_name} +
+ {userDetails.companyProfile.tax_id && ( +
+ Tax ID: + {userDetails.companyProfile.tax_id} +
+ )} + {userDetails.companyProfile.registration_number && ( +
+ Registration Number: + {userDetails.companyProfile.registration_number} +
+ )} + {userDetails.companyProfile.phone && ( +
+ Phone: + {userDetails.companyProfile.phone} +
+ )} + {userDetails.companyProfile.address && ( +
+ Address: + + {userDetails.companyProfile.address}, {userDetails.companyProfile.zip_code} {userDetails.companyProfile.city}, {userDetails.companyProfile.country} + +
+ )} +
+ )} + + )} +
+ )} + + {/* Documents */} + {(userDetails.documents.length > 0 || userDetails.contracts.length > 0 || userDetails.idDocuments.length > 0) && ( +
+
+ +

Documents

+
+ + {/* Regular Documents */} + {userDetails.documents.length > 0 && ( +
+
Uploaded Documents
+
+ {userDetails.documents.map((doc) => ( +
+
+ {doc.file_name} + ({formatFileSize(doc.file_size)}) +
+ {formatDate(doc.uploaded_at)} +
+ ))} +
+
+ )} + + {/* Contracts */} + {userDetails.contracts.length > 0 && ( +
+
Contracts
+
+ {userDetails.contracts.map((contract) => ( +
+
+ {contract.file_name} + ({formatFileSize(contract.file_size)}) +
+ {formatDate(contract.uploaded_at)} +
+ ))} +
+
+ )} + + {/* ID Documents */} + {userDetails.idDocuments.length > 0 && ( +
+
ID Documents
+
+ {userDetails.idDocuments.map((idDoc) => ( +
+
+ + {idDoc.document_type} + {formatDate(idDoc.uploaded_at)} +
+
+ {idDoc.frontUrl && ( +
+

Front:

+ ID Front +
+ )} + {idDoc.backUrl && ( +
+

Back:

+ ID Back +
+ )} +
+
+ ))} +
+
+ )} +
+ )} + + {/* Permissions */} + {userDetails.permissions.length > 0 && ( +
+
+ +

Permissions

+
+
+ {userDetails.permissions.map((permission) => ( +
+
{permission.name}
+ {permission.description && ( +
{permission.description}
+ )} +
+ ))} +
+
+ )} +
+ )} +
+
+ +
+ {showArchiveConfirm ? ( + // Archive/Unarchive Confirmation Dialog +
+
+ +

+ {userDetails?.userStatus?.status === 'inactive' ? 'Unarchive User' : 'Archive User'} +

+
+

+ {userDetails?.userStatus?.status === 'inactive' + ? 'Are you sure you want to unarchive this user? This will reactivate their account.' + : 'Are you sure you want to archive this user? This action will disable their account but preserve all their data.'} +

+
+ + +
+
+ ) : ( + // Normal action buttons +
+ {isEditing ? ( + <> + + + + ) : ( + <> + + + {userDetails?.userStatus && ( + + )} + + + + + + )} +
+ )} +
+ + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/utils/api.ts b/src/app/utils/api.ts index 153dffe..a11cf21 100644 --- a/src/app/utils/api.ts +++ b/src/app/utils/api.ts @@ -45,6 +45,7 @@ export const API_ENDPOINTS = { ADMIN_UNARCHIVE_USER: '/api/admin/unarchive-user/:id', ADMIN_UPDATE_USER_VERIFICATION: '/api/admin/update-verification/:id', ADMIN_UPDATE_USER_PROFILE: '/api/admin/update-user-profile/:id', + ADMIN_UPDATE_USER_STATUS: '/api/admin/update-user-status/:id', } // API Helper Functions @@ -333,6 +334,16 @@ export class AdminAPI { } return response.json() } + + static async updateUserStatus(token: string, userId: string, status: string) { + const endpoint = API_ENDPOINTS.ADMIN_UPDATE_USER_STATUS.replace(':id', userId) + const response = await ApiClient.patch(endpoint, { status }, token) + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to update user status') + } + return response.json() + } } // Response Types @@ -375,7 +386,7 @@ export interface DetailedUserInfo { id: number email: string user_type: 'personal' | 'company' - role: 'user' | 'admin' + role: 'user' | 'admin' | 'super_admin' created_at: string last_login_at: string | null }