'use client'; import { useState, useRef, useEffect } from 'react'; import Image from 'next/image'; import { useSession, signIn } from 'next-auth/react'; import { uploadService } from '@/services/upload.service'; import { agentsService } from '@/services/agents.service'; import { usersService } from '@/services/users.service'; interface ProfileSettingsFormProps { initialData?: { firstName: string; lastName: string; career: string; email: string; phone: string; location: string; avatar?: string; }; onSave?: (data: { firstName: string; lastName: string; career: string; email: string; phone: string; location: string; }) => void; descriptionText?: string; } export function ProfileSettingsForm({ initialData, onSave, descriptionText = 'This information will be displayed on your public profile page.', }: ProfileSettingsFormProps) { const { data: session, update: updateSession } = useSession(); const fileInputRef = useRef(null); const [formData, setFormData] = useState({ firstName: initialData?.firstName || '', lastName: initialData?.lastName || '', career: initialData?.career || '', email: initialData?.email || '', phone: initialData?.phone || '', location: initialData?.location || '', }); const [avatarUrl, setAvatarUrl] = useState(null); const [avatarLoaded, setAvatarLoaded] = useState(false); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); // Reset avatar loaded state when URL changes (e.g., after upload) useEffect(() => { setAvatarLoaded(false); }, [avatarUrl]); // Store original data for cancel/reset functionality const [originalData, setOriginalData] = useState(null); // Track if initial profile fetch is done to avoid re-showing loading spinner const hasFetchedRef = useRef(false); // Fetch profile data on mount based on user role useEffect(() => { const fetchProfile = async () => { try { // Only show loading spinner on initial fetch, not on session-triggered re-fetches if (!hasFetchedRef.current) { setIsLoading(true); } const role = (session?.user as any)?.role; if (role === 'AGENT') { const profile = await agentsService.getMyProfile(); const profileData = { firstName: profile.firstName || '', lastName: profile.lastName || '', career: profile.agentType?.name || 'Real Estate Agent', email: profile.email || session?.user?.email || '', phone: profile.phone || '', location: profile.serviceAreas?.[0] || '', }; setFormData(profileData); setOriginalData(profileData); // Store original for cancel if (profile.avatar) { try { const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar); setAvatarUrl(presignedUrl); } catch { setAvatarUrl(profile.avatar); } } } else if (role === 'USER') { const profile = await usersService.getMyProfile(); const profileData = { firstName: profile.firstName || '', lastName: profile.lastName || '', career: '', // Users don't have career/agent type email: profile.email || session?.user?.email || '', phone: profile.phone || '', location: [profile.city, profile.state, profile.country].filter(Boolean).join(', '), }; setFormData(profileData); setOriginalData(profileData); // Store original for cancel if (profile.avatar) { try { const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar); setAvatarUrl(presignedUrl); } catch { setAvatarUrl(profile.avatar); } } } } catch (err) { console.error('Failed to fetch profile:', err); // Use session data as fallback if (session?.user) { const nameParts = (session.user?.name || '').split(' '); setFormData(prev => ({ ...prev, firstName: nameParts[0] || prev.firstName, lastName: nameParts.slice(1).join(' ') || prev.lastName, email: session.user?.email || prev.email, })); if (session.user.image) { setAvatarUrl(session.user.image); } } } finally { setIsLoading(false); hasFetchedRef.current = true; } }; if (session) { // Skip re-fetching on session changes after initial load (e.g. after updateSession in handleSave) if (hasFetchedRef.current) return; fetchProfile(); } }, [session]); const handleChange = (field: string, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); setError(null); setSuccessMessage(null); }; const handleUploadClick = () => { fileInputRef.current?.click(); }; const handleFileChange = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; // Validate file type const validTypes = ['image/jpeg', 'image/png', 'image/gif']; if (!validTypes.includes(file.type)) { setError('Please upload a JPEG, PNG, or GIF image.'); return; } // Validate file size (2MB max) const maxSize = 2 * 1024 * 1024; if (file.size > maxSize) { setError('File size must be less than 2MB.'); return; } try { setIsUploading(true); setError(null); setUploadProgress(0); // Show local preview immediately for instant feedback const localPreviewUrl = URL.createObjectURL(file); setAvatarUrl(localPreviewUrl); const role = (session?.user as any)?.role; // Upload the avatar based on role let uploadedFile; if (role === 'AGENT') { uploadedFile = await uploadService.uploadAvatar(file, (progress) => { setUploadProgress(progress.percentage); }); // Update the agent profile with the new avatar key await agentsService.updateProfile({ avatar: uploadedFile.url }); } else { uploadedFile = await uploadService.uploadUserAvatar(file, (progress) => { setUploadProgress(progress.percentage); }); // Update the user profile with the new avatar key await usersService.updateProfile({ avatar: uploadedFile.url }); } // Small delay to ensure S3 has processed the upload await new Promise(resolve => setTimeout(resolve, 500)); // Get the presigned URL for display try { const presignedUrl = await uploadService.getPresignedDownloadUrl(uploadedFile.url); setAvatarUrl(presignedUrl); // Clean up local preview URL only after successful presigned URL URL.revokeObjectURL(localPreviewUrl); // 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'); // Still dispatch event to refresh header window.dispatchEvent(new Event('profile-updated')); } setSuccessMessage('Profile picture updated successfully!'); } catch (err) { console.error('Upload failed:', err); setError('Failed to upload profile picture. Please try again.'); } finally { setIsUploading(false); setUploadProgress(0); // Reset file input if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; const handleDeleteAvatar = async () => { try { setIsUploading(true); setError(null); const role = (session?.user as any)?.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 { await usersService.updateProfile({ avatar: null }); } setAvatarUrl(null); // 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); setError('Failed to remove profile picture. Please try again.'); } finally { setIsUploading(false); } }; const handleSave = async () => { try { setIsSaving(true); setError(null); const role = (session?.user as any)?.role; // Update profile based on role (email is not editable - tied to auth) if (role === 'AGENT') { await agentsService.updateProfile({ firstName: formData.firstName, lastName: formData.lastName, phone: formData.phone, serviceAreas: formData.location ? [formData.location] : [], }); } else { // Parse location into city, state, country for users const locationParts = formData.location.split(',').map(s => s.trim()); await usersService.updateProfile({ firstName: formData.firstName, lastName: formData.lastName, phone: formData.phone, city: locationParts[0] || undefined, state: locationParts[1] || undefined, country: locationParts[2] || undefined, }); } // Update session with new name const fullName = `${formData.firstName} ${formData.lastName}`.trim(); await updateSession({ ...session, user: { ...session?.user, name: fullName, }, }); // Dispatch event to notify header and other components to refresh profile data window.dispatchEvent(new Event('profile-updated')); if (onSave) { onSave(formData); } // Update original data so Cancel will reset to the newly saved values setOriginalData(formData); setSuccessMessage('Profile settings saved successfully!'); } catch (err) { console.error('Save failed:', err); setError('Failed to save profile settings. Please try again.'); } finally { setIsSaving(false); } }; const handleCancel = () => { // Reset to original fetched data (including phone) if (originalData) { setFormData(originalData); } else if (session?.user) { // Fallback to session data if original not available const nameParts = (session.user?.name || '').split(' '); setFormData(prev => ({ ...prev, firstName: nameParts[0] || prev.firstName, lastName: nameParts.slice(1).join(' ') || prev.lastName, email: session.user?.email || prev.email, })); } setError(null); setSuccessMessage(null); }; if (isLoading) { return (
); } return (
{/* Header */}

Public Profile

{descriptionText}

{/* Error/Success Messages */} {error && (
{error}
)} {successMessage && (
{successMessage}
)} {/* Profile Photo Section */}
{(!avatarUrl || !avatarLoaded) && ( Profile )} {avatarUrl && ( Profile setAvatarLoaded(true)} /> )}

Public Picture

Supported formats: JPEG, PNG, GIF Max file size: 2MB

{/* Form Fields */}
{/* Row 1: First Name & Last Name */}
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]" />
{/* 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 */}
handleChange('phone', 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]" />
{/* Row 3: Location */}
Location
handleChange('location', e.target.value)} className="w-full sm:w-1/2 h-[44px] pl-10 pr-4 border border-[#00293D]/20 rounded-[10px] text-[14px] font-serif text-[#00293D] focus:outline-none focus:border-[#E58625]" />
{/* Action Buttons */}
); }