feat: Split profile name into first and last name fields, introduce event-based profile updates for the header, and add S3 deletion for avatars.
This commit is contained in:
@@ -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<HTMLInputElement>(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 */}
|
||||
<div className="space-y-5">
|
||||
{/* Row 1: Full Name & Career (Career only for agents) */}
|
||||
<div className={`grid grid-cols-1 ${(session?.user as any)?.role === 'AGENT' ? 'sm:grid-cols-2' : ''} gap-5`}>
|
||||
{/* Row 1: First Name & Last Name */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
<div>
|
||||
<label className="block text-[12px] font-medium text-[#00293D]/70 font-serif mb-2">
|
||||
Full Name
|
||||
First Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.fullName}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[12px] font-medium text-[#00293D]/70 font-serif mb-2">
|
||||
Last Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.lastName}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
{(session?.user as any)?.role === 'AGENT' && (
|
||||
<div>
|
||||
<label className="block text-[12px] font-medium text-[#00293D]/70 font-serif mb-2">
|
||||
Career
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.career}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Career (only for agents) */}
|
||||
{(session?.user as any)?.role === 'AGENT' && (
|
||||
<div>
|
||||
<label className="block text-[12px] font-medium text-[#00293D]/70 font-serif mb-2">
|
||||
Career
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.career}
|
||||
onChange={(e) => 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]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Row 2: Email & Phone */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user