diff --git a/src/app/dashboard/users/[id]/page.tsx b/src/app/dashboard/users/[id]/page.tsx new file mode 100644 index 0000000..947964e --- /dev/null +++ b/src/app/dashboard/users/[id]/page.tsx @@ -0,0 +1,431 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter, useParams } from 'next/navigation'; +import { + usersService, + User, + getErrorMessage, + uploadService, + agentTypesService, + AgentType, +} 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(''); + + // 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]); + + 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 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} +

+
+ )} +
+ )} +
+
+ )} +
+ ); +} diff --git a/src/app/dashboard/users/page.tsx b/src/app/dashboard/users/page.tsx index e4abbb0..67343ce 100644 --- a/src/app/dashboard/users/page.tsx +++ b/src/app/dashboard/users/page.tsx @@ -199,7 +199,11 @@ export default function UsersPage() { {users.map((user) => ( - + router.push(`/dashboard/users/${user.id}`)} + >
diff --git a/src/services/users.service.ts b/src/services/users.service.ts index 13188ad..dd8337b 100644 --- a/src/services/users.service.ts +++ b/src/services/users.service.ts @@ -1,12 +1,34 @@ import api, { ApiResponse } from './api'; // Types +export interface AgentType { + id: string; + name: string; + slug: string; + description?: string; +} + +export interface AgentProfileDetails { + id: string; + slug: string; + bio?: string | null; + headline?: string | null; + companyName?: string | null; + licenseNumber?: string | null; + yearsOfExperience?: number | null; + isProfileComplete: boolean; + profileCompleteness: number; + agentTypeId?: string | null; + agentType?: AgentType | null; +} + export interface User { id: string; email: string; role: string; status: string; emailVerified: boolean; + emailVerifiedAt?: string | null; authProvider: string; createdAt: string; lastLoginAt: string | null; @@ -19,6 +41,7 @@ export interface User { state: string | null; country: string | null; } | null; + agentProfile?: AgentProfileDetails | null; } export interface UsersListResponse { @@ -74,6 +97,11 @@ class UsersService { const response = await api.patch>(`${this.basePath}/${id}/status`, { status }); return response.data.data; } + + async updateAgentType(userId: string, agentTypeId: string): Promise<{ id: string; agentTypeId: string; agentType: AgentType; message: string }> { + const response = await api.patch>(`${this.basePath}/${userId}/agent-type`, { agentTypeId }); + return response.data.data; + } } export const usersService = new UsersService();