feat: enable user profile avatar upload and display across header and settings.
This commit is contained in:
@@ -4,6 +4,8 @@ import { useState, useEffect, useRef } from 'react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useSession, signOut } from 'next-auth/react';
|
import { useSession, signOut } from 'next-auth/react';
|
||||||
|
import { agentsService } from '@/services/agents.service';
|
||||||
|
import { uploadService } from '@/services/upload.service';
|
||||||
|
|
||||||
const navLinks = [
|
const navLinks = [
|
||||||
{ label: 'Education', href: '/education' },
|
{ label: 'Education', href: '/education' },
|
||||||
@@ -15,6 +17,7 @@ export function CommonHeader() {
|
|||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||||
const profileMenuRef = useRef<HTMLDivElement>(null);
|
const profileMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [profileImage, setProfileImage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Close dropdown when clicking outside
|
// Close dropdown when clicking outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -33,9 +36,34 @@ export function CommonHeader() {
|
|||||||
};
|
};
|
||||||
}, [showProfileMenu]);
|
}, [showProfileMenu]);
|
||||||
|
|
||||||
|
// Fetch profile image from backend
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchProfileImage = async () => {
|
||||||
|
try {
|
||||||
|
const profile = await agentsService.getMyProfile();
|
||||||
|
if (profile.avatar) {
|
||||||
|
try {
|
||||||
|
const avatarUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
|
||||||
|
setProfileImage(avatarUrl);
|
||||||
|
} catch {
|
||||||
|
setProfileImage(profile.avatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch profile image:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
fetchProfileImage();
|
||||||
|
}
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
const userName = session?.user?.name;
|
const userName = session?.user?.name;
|
||||||
const userEmail = session?.user?.email;
|
const userEmail = session?.user?.email;
|
||||||
const userRole = (session?.user as any)?.role;
|
const userRole = (session?.user as any)?.role;
|
||||||
|
// Use fetched profile image, fallback to session image
|
||||||
|
const userImage = profileImage || session?.user?.image;
|
||||||
|
|
||||||
// Determine dashboard link based on user role
|
// Determine dashboard link based on user role
|
||||||
const dashboardLink = userRole === 'AGENT' ? '/agent/dashboard' : '/user/dashboard';
|
const dashboardLink = userRole === 'AGENT' ? '/agent/dashboard' : '/user/dashboard';
|
||||||
@@ -90,14 +118,22 @@ export function CommonHeader() {
|
|||||||
className="flex items-center gap-3 hover:opacity-80 transition-opacity cursor-pointer"
|
className="flex items-center gap-3 hover:opacity-80 transition-opacity cursor-pointer"
|
||||||
>
|
>
|
||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div className="w-[35px] h-[35px] rounded-full overflow-hidden border-2 border-[#e58625]">
|
<div className="w-[35px] h-[35px] rounded-full overflow-hidden border-2 border-[#e58625] bg-gray-100">
|
||||||
<Image
|
{userImage ? (
|
||||||
src="/assets/icons/user-placeholder-icon.svg"
|
<img
|
||||||
alt="Profile"
|
src={userImage}
|
||||||
width={35}
|
alt="Profile"
|
||||||
height={35}
|
className="w-full h-full object-cover"
|
||||||
className="w-full h-full object-cover"
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<Image
|
||||||
|
src="/assets/icons/user-placeholder-icon.svg"
|
||||||
|
alt="Profile"
|
||||||
|
width={35}
|
||||||
|
height={35}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Greeting Text */}
|
{/* Greeting Text */}
|
||||||
<div className="flex flex-col text-left">
|
<div className="flex flex-col text-left">
|
||||||
@@ -118,14 +154,22 @@ export function CommonHeader() {
|
|||||||
|
|
||||||
{/* User Profile Section */}
|
{/* User Profile Section */}
|
||||||
<div className="flex items-center gap-3 px-4 py-4 border-b border-black/10">
|
<div className="flex items-center gap-3 px-4 py-4 border-b border-black/10">
|
||||||
<div className="w-[42px] h-[42px] rounded-full overflow-hidden flex-shrink-0">
|
<div className="w-[42px] h-[42px] rounded-full overflow-hidden flex-shrink-0 bg-gray-100">
|
||||||
<Image
|
{userImage ? (
|
||||||
src="/assets/icons/user-placeholder-icon.svg"
|
<img
|
||||||
alt="Profile"
|
src={userImage}
|
||||||
width={42}
|
alt="Profile"
|
||||||
height={42}
|
className="w-full h-full object-cover"
|
||||||
className="w-full h-full object-cover"
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<Image
|
||||||
|
src="/assets/icons/user-placeholder-icon.svg"
|
||||||
|
alt="Profile"
|
||||||
|
width={42}
|
||||||
|
height={42}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="font-fractul font-medium text-[16px] leading-[20px] text-black">
|
<p className="font-fractul font-medium text-[16px] leading-[20px] text-black">
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { useSession, signIn } from 'next-auth/react';
|
||||||
|
import { uploadService } from '@/services/upload.service';
|
||||||
|
import { agentsService } from '@/services/agents.service';
|
||||||
|
|
||||||
interface ProfileSettingsFormProps {
|
interface ProfileSettingsFormProps {
|
||||||
initialData?: {
|
initialData?: {
|
||||||
@@ -10,6 +13,7 @@ interface ProfileSettingsFormProps {
|
|||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
location: string;
|
location: string;
|
||||||
|
avatar?: string;
|
||||||
};
|
};
|
||||||
onSave?: (data: {
|
onSave?: (data: {
|
||||||
fullName: string;
|
fullName: string;
|
||||||
@@ -22,34 +26,234 @@ interface ProfileSettingsFormProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ProfileSettingsForm({
|
export function ProfileSettingsForm({
|
||||||
initialData = {
|
initialData,
|
||||||
fullName: 'Brain Neeland',
|
|
||||||
career: 'Real Estate Agent',
|
|
||||||
email: 'Brain.Neeland1234@gmail.com',
|
|
||||||
phone: '+917483849544',
|
|
||||||
location: 'New York',
|
|
||||||
},
|
|
||||||
onSave,
|
onSave,
|
||||||
descriptionText = 'This information will be displayed on your public profile page.',
|
descriptionText = 'This information will be displayed on your public profile page.',
|
||||||
}: ProfileSettingsFormProps) {
|
}: ProfileSettingsFormProps) {
|
||||||
const [formData, setFormData] = useState(initialData);
|
const { data: session, update: updateSession } = useSession();
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
fullName: initialData?.fullName || '',
|
||||||
|
career: initialData?.career || '',
|
||||||
|
email: initialData?.email || '',
|
||||||
|
phone: initialData?.phone || '',
|
||||||
|
location: initialData?.location || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Fetch profile data on mount
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchProfile = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const profile = await agentsService.getMyProfile();
|
||||||
|
|
||||||
|
setFormData({
|
||||||
|
fullName: `${profile.firstName || ''} ${profile.lastName || ''}`.trim(),
|
||||||
|
career: profile.agentType?.name || 'Real Estate Agent',
|
||||||
|
email: profile.email || session?.user?.email || '',
|
||||||
|
phone: profile.phone || '',
|
||||||
|
location: profile.serviceAreas?.[0] || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle avatar URL - if it's an S3 key, get presigned URL
|
||||||
|
if (profile.avatar) {
|
||||||
|
try {
|
||||||
|
const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
|
||||||
|
setAvatarUrl(presignedUrl);
|
||||||
|
} catch {
|
||||||
|
// If getting presigned URL fails, the avatar might be a full URL
|
||||||
|
setAvatarUrl(profile.avatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch profile:', err);
|
||||||
|
// Use session data as fallback
|
||||||
|
if (session?.user) {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
fullName: session.user?.name || prev.fullName,
|
||||||
|
email: session.user?.email || prev.email,
|
||||||
|
}));
|
||||||
|
if (session.user.image) {
|
||||||
|
setAvatarUrl(session.user.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
fetchProfile();
|
||||||
|
}
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
const handleChange = (field: string, value: string) => {
|
const handleChange = (field: string, value: string) => {
|
||||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
|
setError(null);
|
||||||
|
setSuccessMessage(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleUploadClick = () => {
|
||||||
if (onSave) {
|
fileInputRef.current?.click();
|
||||||
onSave(formData);
|
};
|
||||||
} else {
|
|
||||||
console.log('Saving profile settings:', formData);
|
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Upload the avatar
|
||||||
|
const uploadedFile = await uploadService.uploadAvatar(file, (progress) => {
|
||||||
|
setUploadProgress(progress.percentage);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the profile with the new avatar key
|
||||||
|
await agentsService.updateProfile({ avatar: uploadedFile.url });
|
||||||
|
|
||||||
|
// Get the presigned URL for display
|
||||||
|
const presignedUrl = await uploadService.getPresignedDownloadUrl(uploadedFile.url);
|
||||||
|
setAvatarUrl(presignedUrl);
|
||||||
|
|
||||||
|
// Update the session to reflect the new avatar
|
||||||
|
await updateSession({
|
||||||
|
...session,
|
||||||
|
user: {
|
||||||
|
...session?.user,
|
||||||
|
image: presignedUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Update profile to remove avatar
|
||||||
|
await agentsService.updateProfile({ avatar: null });
|
||||||
|
setAvatarUrl(null);
|
||||||
|
|
||||||
|
// Update session
|
||||||
|
await updateSession({
|
||||||
|
...session,
|
||||||
|
user: {
|
||||||
|
...session?.user,
|
||||||
|
image: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
await agentsService.updateProfile({
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
email: formData.email,
|
||||||
|
phone: formData.phone,
|
||||||
|
serviceAreas: formData.location ? [formData.location] : [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update session with new name
|
||||||
|
await updateSession({
|
||||||
|
...session,
|
||||||
|
user: {
|
||||||
|
...session?.user,
|
||||||
|
name: formData.fullName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onSave) {
|
||||||
|
onSave(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 = () => {
|
const handleCancel = () => {
|
||||||
setFormData(initialData);
|
// Reset to initial data or session data
|
||||||
|
if (session?.user) {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
fullName: session.user?.name || prev.fullName,
|
||||||
|
email: session.user?.email || prev.email,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
setError(null);
|
||||||
|
setSuccessMessage(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="border border-[#00293d]/20 rounded-[15px] bg-white p-8 flex items-center justify-center min-h-[400px]">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[#e58625]"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border border-[#00293d]/20 rounded-[15px] bg-white p-8">
|
<div className="border border-[#00293d]/20 rounded-[15px] bg-white p-8">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -62,27 +266,62 @@ export function ProfileSettingsForm({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Error/Success Messages */}
|
||||||
|
{error && (
|
||||||
|
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-[10px] text-red-600 text-sm font-serif">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{successMessage && (
|
||||||
|
<div className="mb-4 p-3 bg-green-50 border border-green-200 rounded-[10px] text-green-600 text-sm font-serif">
|
||||||
|
{successMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Profile Photo Section */}
|
{/* Profile Photo Section */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-[60px] h-[60px] rounded-full overflow-hidden border border-[#00293D]/20 flex-shrink-0">
|
<div className="w-[60px] h-[60px] rounded-full overflow-hidden border border-[#00293D]/20 flex-shrink-0 bg-gray-100">
|
||||||
<Image
|
{avatarUrl ? (
|
||||||
src="/assets/icons/user-placeholder-icon.svg"
|
<img
|
||||||
alt="Profile"
|
src={avatarUrl}
|
||||||
width={60}
|
alt="Profile"
|
||||||
height={60}
|
className="w-full h-full object-cover"
|
||||||
className="w-full h-full object-cover"
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<Image
|
||||||
|
src="/assets/icons/user-placeholder-icon.svg"
|
||||||
|
alt="Profile"
|
||||||
|
width={60}
|
||||||
|
height={60}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-serif font-medium text-[14px] text-[#00293D] mb-2">
|
<p className="font-serif font-medium text-[14px] text-[#00293D] mb-2">
|
||||||
Public Picture
|
Public Picture
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<button className="px-4 py-1.5 bg-[#e58625] text-white rounded-[20px] font-serif text-[12px] hover:bg-[#d47920] transition-colors">
|
<input
|
||||||
Upload Now
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept="image/jpeg,image/png,image/gif"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleUploadClick}
|
||||||
|
disabled={isUploading}
|
||||||
|
className="px-4 py-1.5 bg-[#e58625] text-white rounded-[20px] font-serif text-[12px] hover:bg-[#d47920] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{isUploading ? `Uploading ${uploadProgress}%` : 'Upload Now'}
|
||||||
</button>
|
</button>
|
||||||
<button className="px-4 py-1.5 border border-[#00293D]/30 text-[#00293D] rounded-[20px] font-serif text-[12px] hover:bg-[#00293d]/5 transition-colors">
|
<button
|
||||||
|
onClick={handleDeleteAvatar}
|
||||||
|
disabled={isUploading || !avatarUrl}
|
||||||
|
className="px-4 py-1.5 border border-[#00293D]/30 text-[#00293D] rounded-[20px] font-serif text-[12px] hover:bg-[#00293d]/5 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,15 +414,17 @@ export function ProfileSettingsForm({
|
|||||||
<div className="mt-8 flex justify-end gap-3">
|
<div className="mt-8 flex justify-end gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
className="px-8 py-3 border border-[#00293D]/30 text-[#00293D] rounded-[10px] font-serif font-medium text-[14px] hover:bg-[#00293d]/5 transition-colors"
|
disabled={isSaving}
|
||||||
|
className="px-8 py-3 border border-[#00293D]/30 text-[#00293D] rounded-[10px] font-serif font-medium text-[14px] hover:bg-[#00293d]/5 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="px-8 py-3 bg-[#e58625] text-white rounded-[10px] font-serif font-medium text-[14px] hover:bg-[#d47920] transition-colors"
|
disabled={isSaving}
|
||||||
|
className="px-8 py-3 bg-[#e58625] text-white rounded-[10px] font-serif font-medium text-[14px] hover:bg-[#d47920] transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Save Changes
|
{isSaving ? 'Saving...' : 'Save Changes'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { agentsService } from '@/services/agents.service';
|
||||||
|
import { uploadService } from '@/services/upload.service';
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -11,19 +15,50 @@ interface NavItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface SettingsSidebarProps {
|
interface SettingsSidebarProps {
|
||||||
profileImage?: string;
|
|
||||||
name?: string;
|
|
||||||
title?: string;
|
|
||||||
basePath?: '/agent/settings' | '/user/settings';
|
basePath?: '/agent/settings' | '/user/settings';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSidebar({
|
export function SettingsSidebar({
|
||||||
profileImage = '/assets/icons/user-placeholder-icon.svg',
|
|
||||||
name = 'Brain Neeland',
|
|
||||||
title = 'Top Real Estate Agent',
|
|
||||||
basePath = '/agent/settings',
|
basePath = '/agent/settings',
|
||||||
}: SettingsSidebarProps) {
|
}: SettingsSidebarProps) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
const [profileData, setProfileData] = useState({
|
||||||
|
name: session?.user?.name || 'User',
|
||||||
|
title: 'Real Estate Agent',
|
||||||
|
avatarUrl: session?.user?.image || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchProfile = async () => {
|
||||||
|
try {
|
||||||
|
const profile = await agentsService.getMyProfile();
|
||||||
|
|
||||||
|
let avatarUrl = session?.user?.image || null;
|
||||||
|
if (profile.avatar) {
|
||||||
|
try {
|
||||||
|
avatarUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
|
||||||
|
} catch {
|
||||||
|
avatarUrl = profile.avatar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setProfileData({
|
||||||
|
name: `${profile.firstName || ''} ${profile.lastName || ''}`.trim() || session?.user?.name || 'User',
|
||||||
|
title: profile.agentType?.name || 'Real Estate Agent',
|
||||||
|
avatarUrl,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch profile for sidebar:', err);
|
||||||
|
// Keep session data as fallback
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
fetchProfile();
|
||||||
|
}
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
{
|
{
|
||||||
@@ -53,21 +88,29 @@ export function SettingsSidebar({
|
|||||||
{/* Profile Card */}
|
{/* Profile Card */}
|
||||||
<div className="bg-white rounded-[15px] border border-[#00293D]/20 p-5">
|
<div className="bg-white rounded-[15px] border border-[#00293D]/20 p-5">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-[70px] h-[70px] rounded-full overflow-hidden border-2 border-[#00293D]/20 flex-shrink-0">
|
<div className="w-[70px] h-[70px] rounded-full overflow-hidden border-2 border-[#00293D]/20 flex-shrink-0 bg-gray-100">
|
||||||
<Image
|
{profileData.avatarUrl ? (
|
||||||
src={profileImage}
|
<img
|
||||||
alt={name}
|
src={profileData.avatarUrl}
|
||||||
width={70}
|
alt={profileData.name}
|
||||||
height={70}
|
className="w-full h-full object-cover"
|
||||||
className="w-full h-full object-cover"
|
/>
|
||||||
/>
|
) : (
|
||||||
|
<Image
|
||||||
|
src="/assets/icons/user-placeholder-icon.svg"
|
||||||
|
alt={profileData.name}
|
||||||
|
width={70}
|
||||||
|
height={70}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-fractul font-bold text-[16px] leading-[19px] text-[#00293D]">
|
<h3 className="font-fractul font-bold text-[16px] leading-[19px] text-[#00293D]">
|
||||||
{name}
|
{profileData.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="font-serif font-normal text-[13px] leading-[17px] text-[#00293D]/60 mt-1">
|
<p className="font-serif font-normal text-[13px] leading-[17px] text-[#00293D]/60 mt-1">
|
||||||
{title}
|
{profileData.title}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user