From 35ae58c0df7e37e6207fbf42fb455696dd731155 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Sun, 1 Feb 2026 00:59:37 +0530 Subject: [PATCH] feat: Split profile name into first and last name fields, introduce event-based profile updates for the header, and add S3 deletion for avatars. --- src/components/layout/CommonHeader.tsx | 81 +++++++----- .../settings/ProfileSettingsForm.tsx | 119 ++++++++++++------ 2 files changed, 135 insertions(+), 65 deletions(-) diff --git a/src/components/layout/CommonHeader.tsx b/src/components/layout/CommonHeader.tsx index ea125f8..78ad5e6 100644 --- a/src/components/layout/CommonHeader.tsx +++ b/src/components/layout/CommonHeader.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useCallback } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { useSession, signOut } from 'next-auth/react'; @@ -19,6 +19,7 @@ export function CommonHeader() { const [showProfileMenu, setShowProfileMenu] = useState(false); const profileMenuRef = useRef(null); const [profileImage, setProfileImage] = useState(null); + const [profileName, setProfileName] = useState(null); // Close dropdown when clicking outside useEffect(() => { @@ -37,40 +38,62 @@ export function CommonHeader() { }; }, [showProfileMenu]); - // Fetch profile image from backend based on user role - useEffect(() => { - const fetchProfileImage = async () => { - try { - const role = (session?.user as any)?.role; - let avatar: string | null = null; + // Fetch profile data (image and name) from backend based on user role + const fetchProfileData = useCallback(async () => { + try { + const role = (session?.user as any)?.role; + let avatar: string | null = null; + let name: string | null = null; - if (role === 'AGENT') { - const profile = await agentsService.getMyProfile(); - avatar = profile.avatar; - } else if (role === 'USER') { - const profile = await usersService.getMyProfile(); - avatar = profile.avatar; - } - - if (avatar) { - try { - const avatarUrl = await uploadService.getPresignedDownloadUrl(avatar); - setProfileImage(avatarUrl); - } catch { - setProfileImage(avatar); - } - } - } catch (err) { - console.error('Failed to fetch profile image:', err); + if (role === 'AGENT') { + const profile = await agentsService.getMyProfile(); + avatar = profile.avatar; + name = profile.firstName ? `${profile.firstName} ${profile.lastName || ''}`.trim() : null; + } else if (role === 'USER') { + const profile = await usersService.getMyProfile(); + avatar = profile.avatar; + name = profile.firstName ? `${profile.firstName} ${profile.lastName || ''}`.trim() : null; } - }; - if (session) { - fetchProfileImage(); + if (name) { + setProfileName(name); + } + + if (avatar) { + try { + const avatarUrl = await uploadService.getPresignedDownloadUrl(avatar); + setProfileImage(avatarUrl); + } catch { + setProfileImage(avatar); + } + } + } catch (err) { + console.error('Failed to fetch profile data:', err); } }, [session]); - const userName = session?.user?.name; + useEffect(() => { + if (session) { + fetchProfileData(); + } + }, [session, fetchProfileData]); + + // Listen for profile update events to refresh the header + useEffect(() => { + const handleProfileUpdate = () => { + if (session) { + fetchProfileData(); + } + }; + + window.addEventListener('profile-updated', handleProfileUpdate); + return () => { + window.removeEventListener('profile-updated', handleProfileUpdate); + }; + }, [session, fetchProfileData]); + + // Use fetched profile name, fallback to session name + const userName = profileName || session?.user?.name; const userEmail = session?.user?.email; const userRole = (session?.user as any)?.role; // Use fetched profile image, fallback to session image diff --git a/src/components/settings/ProfileSettingsForm.tsx b/src/components/settings/ProfileSettingsForm.tsx index 3f5f2a8..dc8f462 100644 --- a/src/components/settings/ProfileSettingsForm.tsx +++ b/src/components/settings/ProfileSettingsForm.tsx @@ -9,7 +9,8 @@ import { usersService } from '@/services/users.service'; interface ProfileSettingsFormProps { initialData?: { - fullName: string; + firstName: string; + lastName: string; career: string; email: string; phone: string; @@ -17,7 +18,8 @@ interface ProfileSettingsFormProps { avatar?: string; }; onSave?: (data: { - fullName: string; + firstName: string; + lastName: string; career: string; email: string; phone: string; @@ -35,7 +37,8 @@ export function ProfileSettingsForm({ const fileInputRef = useRef(null); const [formData, setFormData] = useState({ - fullName: initialData?.fullName || '', + firstName: initialData?.firstName || '', + lastName: initialData?.lastName || '', career: initialData?.career || '', email: initialData?.email || '', phone: initialData?.phone || '', @@ -60,7 +63,8 @@ export function ProfileSettingsForm({ if (role === 'AGENT') { const profile = await agentsService.getMyProfile(); setFormData({ - fullName: `${profile.firstName || ''} ${profile.lastName || ''}`.trim(), + firstName: profile.firstName || '', + lastName: profile.lastName || '', career: profile.agentType?.name || 'Real Estate Agent', email: profile.email || session?.user?.email || '', phone: profile.phone || '', @@ -78,7 +82,8 @@ export function ProfileSettingsForm({ } else if (role === 'USER') { const profile = await usersService.getMyProfile(); setFormData({ - fullName: `${profile.firstName || ''} ${profile.lastName || ''}`.trim(), + firstName: profile.firstName || '', + lastName: profile.lastName || '', career: '', // Users don't have career/agent type email: profile.email || session?.user?.email || '', phone: profile.phone || '', @@ -98,9 +103,11 @@ export function ProfileSettingsForm({ console.error('Failed to fetch profile:', err); // Use session data as fallback if (session?.user) { + const nameParts = (session.user?.name || '').split(' '); setFormData(prev => ({ ...prev, - fullName: session.user?.name || prev.fullName, + firstName: nameParts[0] || prev.firstName, + lastName: nameParts.slice(1).join(' ') || prev.lastName, email: session.user?.email || prev.email, })); if (session.user.image) { @@ -190,6 +197,9 @@ export function ProfileSettingsForm({ image: presignedUrl, }, }); + + // Dispatch event to notify header to refresh profile data + window.dispatchEvent(new Event('profile-updated')); } catch { // If presigned URL fails, keep the local preview (don't revoke it) console.warn('Could not get presigned URL, using local preview'); @@ -216,7 +226,27 @@ export function ProfileSettingsForm({ const role = (session?.user as any)?.role; - // Update profile to remove avatar based on role + // Get current avatar key from profile to delete from S3 + let avatarKey: string | null = null; + if (role === 'AGENT') { + const profile = await agentsService.getMyProfile(); + avatarKey = profile.avatar; + } else { + const profile = await usersService.getMyProfile(); + avatarKey = profile.avatar; + } + + // Delete from S3 if avatar exists + if (avatarKey) { + try { + await uploadService.deleteFile(avatarKey); + } catch (s3Error) { + console.warn('Could not delete from S3:', s3Error); + // Continue even if S3 delete fails - still clear database reference + } + } + + // Update profile to remove avatar reference from database if (role === 'AGENT') { await agentsService.updateProfile({ avatar: null }); } else { @@ -233,6 +263,9 @@ export function ProfileSettingsForm({ }, }); + // Dispatch event to notify header to refresh profile data + window.dispatchEvent(new Event('profile-updated')); + setSuccessMessage('Profile picture removed successfully!'); } catch (err) { console.error('Delete failed:', err); @@ -249,16 +282,11 @@ export function ProfileSettingsForm({ const role = (session?.user as any)?.role; - // Split full name into first and last name - const nameParts = formData.fullName.trim().split(' '); - const firstName = nameParts[0] || ''; - const lastName = nameParts.slice(1).join(' ') || ''; - // Update profile based on role (email is not editable - tied to auth) if (role === 'AGENT') { await agentsService.updateProfile({ - firstName, - lastName, + firstName: formData.firstName, + lastName: formData.lastName, phone: formData.phone, serviceAreas: formData.location ? [formData.location] : [], }); @@ -266,8 +294,8 @@ export function ProfileSettingsForm({ // Parse location into city, state, country for users const locationParts = formData.location.split(',').map(s => s.trim()); await usersService.updateProfile({ - firstName, - lastName, + firstName: formData.firstName, + lastName: formData.lastName, phone: formData.phone, city: locationParts[0] || undefined, state: locationParts[1] || undefined, @@ -276,14 +304,18 @@ export function ProfileSettingsForm({ } // Update session with new name + const fullName = `${formData.firstName} ${formData.lastName}`.trim(); await updateSession({ ...session, user: { ...session?.user, - name: formData.fullName, + name: fullName, }, }); + // Dispatch event to notify header and other components to refresh profile data + window.dispatchEvent(new Event('profile-updated')); + if (onSave) { onSave(formData); } @@ -300,9 +332,11 @@ export function ProfileSettingsForm({ const handleCancel = () => { // Reset to initial data or session data if (session?.user) { + const nameParts = (session.user?.name || '').split(' '); setFormData(prev => ({ ...prev, - fullName: session.user?.name || prev.fullName, + firstName: nameParts[0] || prev.firstName, + lastName: nameParts.slice(1).join(' ') || prev.lastName, email: session.user?.email || prev.email, })); } @@ -398,34 +432,47 @@ export function ProfileSettingsForm({ {/* Form Fields */}
- {/* Row 1: Full Name & Career (Career only for agents) */} -
+ {/* Row 1: First Name & Last Name */} +
handleChange('fullName', e.target.value)} + value={formData.firstName} + onChange={(e) => handleChange('firstName', e.target.value)} + className="w-full h-[44px] px-4 border border-[#00293D]/20 rounded-[10px] text-[14px] font-serif text-[#00293D] focus:outline-none focus:border-[#E58625]" + /> +
+
+ + handleChange('lastName', e.target.value)} className="w-full h-[44px] px-4 border border-[#00293D]/20 rounded-[10px] text-[14px] font-serif text-[#00293D] focus:outline-none focus:border-[#E58625]" />
- {(session?.user as any)?.role === 'AGENT' && ( -
- - handleChange('career', e.target.value)} - className="w-full h-[44px] px-4 border border-[#00293D]/20 rounded-[10px] text-[14px] font-serif text-[#00293D] focus:outline-none focus:border-[#E58625]" - /> -
- )}
+ {/* Career (only for agents) */} + {(session?.user as any)?.role === 'AGENT' && ( +
+ + handleChange('career', e.target.value)} + className="w-full sm:w-1/2 h-[44px] px-4 border border-[#00293D]/20 rounded-[10px] text-[14px] font-serif text-[#00293D] focus:outline-none focus:border-[#E58625]" + /> +
+ )} + {/* Row 2: Email & Phone */}