diff --git a/src/app/(agent)/agent/dashboard/layout.tsx b/src/app/(agent)/agent/dashboard/layout.tsx new file mode 100644 index 0000000..d264754 --- /dev/null +++ b/src/app/(agent)/agent/dashboard/layout.tsx @@ -0,0 +1,35 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Agent Dashboard", + description: + "Manage your professional profile, availability, testimonials, connection requests, and client interactions on RE-Quest.", + + robots: { + index: false, + follow: false, + }, + + openGraph: { + title: "Agent Dashboard", + description: + "Manage your professional profile, availability, testimonials, connection requests, and client interactions on RE-Quest.", + siteName: "RE-Quest", + type: "website", + }, + + twitter: { + card: "summary", + title: "Agent Dashboard", + description: + "Manage your professional profile, availability, testimonials, connection requests, and client interactions on RE-Quest.", + }, +}; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} diff --git a/src/app/(agent)/agent/edit/layout.tsx b/src/app/(agent)/agent/edit/layout.tsx new file mode 100644 index 0000000..91bef28 --- /dev/null +++ b/src/app/(agent)/agent/edit/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Edit Profile", + description: + "Update your professional profile, experience, certifications, availability, and contact information on RE-Quest.", + + robots: { + index: false, + follow: false, + }, +}; + +export default function EditLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} diff --git a/src/app/(agent)/agent/message/layout.tsx b/src/app/(agent)/agent/message/layout.tsx new file mode 100644 index 0000000..fbece08 --- /dev/null +++ b/src/app/(agent)/agent/message/layout.tsx @@ -0,0 +1,28 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Messages", + description: + "View and manage your conversations, client inquiries, and professional communications on RE-Quest.", + + robots: { + index: false, + follow: false, + }, + + openGraph: { + title: "Messages", + description: + "View and manage your conversations, client inquiries, and professional communications on RE-Quest.", + siteName: "RE-Quest", + type: "website", + }, +}; + +export default function MessageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} diff --git a/src/app/(agent)/agent/network/layout.tsx b/src/app/(agent)/agent/network/layout.tsx new file mode 100644 index 0000000..62c8c1f --- /dev/null +++ b/src/app/(agent)/agent/network/layout.tsx @@ -0,0 +1,35 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Network", + description: + "Manage your professional connections, connection requests, and networking opportunities on RE-Quest.", + + robots: { + index: false, + follow: false, + }, + + openGraph: { + title: "Network", + description: + "Manage your professional connections, connection requests, and networking opportunities on RE-Quest.", + siteName: "RE-Quest", + type: "website", + }, + + twitter: { + card: "summary", + title: "Network", + description: + "Manage your professional connections, connection requests, and networking opportunities on RE-Quest.", + }, +}; + +export default function NetworkLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} diff --git a/src/app/(agent)/agent/notifications/layout.tsx b/src/app/(agent)/agent/notifications/layout.tsx new file mode 100644 index 0000000..7872b0d --- /dev/null +++ b/src/app/(agent)/agent/notifications/layout.tsx @@ -0,0 +1,35 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Notifications", + description: + "View and manage your notifications, updates, connection requests, and account activity on RE-Quest.", + + robots: { + index: false, + follow: false, + }, + + openGraph: { + title: "Notifications", + description: + "View and manage your notifications, updates, connection requests, and account activity on RE-Quest.", + siteName: "RE-Quest", + type: "website", + }, + + twitter: { + card: "summary", + title: "Notifications", + description: + "View and manage your notifications, updates, connection requests, and account activity on RE-Quest.", + }, +}; + +export default function NotificationsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} diff --git a/src/app/(agent)/agent/settings/layout.tsx b/src/app/(agent)/agent/settings/layout.tsx new file mode 100644 index 0000000..46af141 --- /dev/null +++ b/src/app/(agent)/agent/settings/layout.tsx @@ -0,0 +1,35 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Settings", + description: + "Manage your account settings, privacy preferences, notifications, billing information, password, and testimonials on RE-Quest.", + + robots: { + index: false, + follow: false, + }, + + openGraph: { + title: "Settings", + description: + "Manage your account settings, privacy preferences, notifications, billing information, password, and testimonials on RE-Quest.", + siteName: "RE-Quest", + type: "website", + }, + + twitter: { + card: "summary", + title: "Settings", + description: + "Manage your account settings, privacy preferences, notifications, billing information, password, and testimonials on RE-Quest.", + }, +}; + +export default function SettingsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} diff --git a/src/app/(auth)/forgot-password/layout.tsx b/src/app/(auth)/forgot-password/layout.tsx new file mode 100644 index 0000000..c76b745 --- /dev/null +++ b/src/app/(auth)/forgot-password/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Forgot Password", + description: + "Reset your RE-Quest account password securely and regain access to your account.", + robots: { + index: false, + follow: false, + }, +}; + +export default function ForgotPasswordLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(auth)/login/layout.tsx b/src/app/(auth)/login/layout.tsx new file mode 100644 index 0000000..2bdec43 --- /dev/null +++ b/src/app/(auth)/login/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Login", + description: + "Sign in to your RE-Quest account to connect with real estate professionals and manage your profile.", + robots: { + index: false, + follow: false, + }, +}; + +export default function LoginLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(auth)/reset-password/layout.tsx b/src/app/(auth)/reset-password/layout.tsx new file mode 100644 index 0000000..c1eef79 --- /dev/null +++ b/src/app/(auth)/reset-password/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Reset Password", + description: + "Create a new password for your RE-Quest account and securely continue using the platform.", + robots: { + index: false, + follow: false, + }, +}; + +export default function ResetPasswordLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(auth)/signup/layout.tsx b/src/app/(auth)/signup/layout.tsx new file mode 100644 index 0000000..4796dc9 --- /dev/null +++ b/src/app/(auth)/signup/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Create Account", + description: + "Create a RE-Quest account to connect with trusted real estate professionals and explore opportunities.", + robots: { + index: false, + follow: false, + }, +}; + +export default function SignupLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(auth)/verify-email/layout.tsx b/src/app/(auth)/verify-email/layout.tsx new file mode 100644 index 0000000..bce6dd1 --- /dev/null +++ b/src/app/(auth)/verify-email/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Verify Email", + description: + "Verify your email address to activate and secure your RE-Quest account.", + robots: { + index: false, + follow: false, + }, +}; + +export default function VerifyEmailLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(user)/user/dashboard/layout.tsx b/src/app/(user)/user/dashboard/layout.tsx new file mode 100644 index 0000000..4443a91 --- /dev/null +++ b/src/app/(user)/user/dashboard/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "User Dashboard", + description: + "Access your RE-Quest dashboard to manage your profile, messages, notifications, and account activity.", + + robots: { + index: false, + follow: false, + }, +}; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(user)/user/message/layout.tsx b/src/app/(user)/user/message/layout.tsx new file mode 100644 index 0000000..9c273bc --- /dev/null +++ b/src/app/(user)/user/message/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Messages", + description: + "View and manage conversations with real estate professionals through RE-Quest messaging.", + + robots: { + index: false, + follow: false, + }, +}; + +export default function MessageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(user)/user/notifications/layout.tsx b/src/app/(user)/user/notifications/layout.tsx new file mode 100644 index 0000000..3230c41 --- /dev/null +++ b/src/app/(user)/user/notifications/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Notifications", + description: + "Stay updated with connection requests, messages, profile activity, and important alerts on RE-Quest.", + + robots: { + index: false, + follow: false, + }, +}; + +export default function NotificationsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(user)/user/profile/[id]/layout.tsx b/src/app/(user)/user/profile/[id]/layout.tsx new file mode 100644 index 0000000..1b4de76 --- /dev/null +++ b/src/app/(user)/user/profile/[id]/layout.tsx @@ -0,0 +1,25 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Professional Profile", + description: + "View verified real estate professionals, agents, and lenders on RE-Quest.", + openGraph: { + title: "Professional Profile", + description: + "View verified real estate professionals, agents, and lenders on RE-Quest.", + type: "profile", + siteName: "RE-Quest", + }, + alternates: { + canonical: "https://re-quest.com/user/profile", + }, +}; + +export default function ProfileLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/(user)/user/profile/[id]/page.tsx b/src/app/(user)/user/profile/[id]/page.tsx index b6fcc07..47d22df 100644 --- a/src/app/(user)/user/profile/[id]/page.tsx +++ b/src/app/(user)/user/profile/[id]/page.tsx @@ -1,9 +1,9 @@ -'use client'; +"use client"; -import { useState, useEffect } from 'react'; -import Image from 'next/image'; -import { useParams } from 'next/navigation'; -import { useSession } from 'next-auth/react'; +import { useState, useEffect } from "react"; +import Image from "next/image"; +import { useParams } from "next/navigation"; +import { useSession } from "next-auth/react"; // Import shared components import { @@ -14,43 +14,68 @@ import { TestimonialsSection, StatusButtons, ContactInfo, -} from '@/components/profile'; -import { ConnectRequestModal } from '@/components/modals'; -import { MobileBackButton } from '@/components/layout/MobileBackButton'; -import { agentsService, AgentProfile, FieldValueResponse } from '@/services/agents.service'; -import { connectionRequestsService, ConnectionStatus } from '@/services/connection-requests.service'; -import { uploadService } from '@/services/upload.service'; -import { testimonialsService, Testimonial } from '@/services/testimonials.service'; -import { mapFieldValuesToExperience, mapFieldValuesToSpecializationFields, mapFieldValuesToProfileCard, mapFieldValuesToContactInfo, mapFieldValuesToAvailability, mapFieldValuesToWorkEnvironment, mapFieldValuesToPersonalTagline, ExperienceData, SpecializationFieldsData, ProfileCardData, ContactInfoData, AvailabilityData, WorkEnvironmentData, PersonalTaglineData } from '@/utils/profileDataMapper'; +} from "@/components/profile"; +import { ConnectRequestModal } from "@/components/modals"; +import { MobileBackButton } from "@/components/layout/MobileBackButton"; +import { + agentsService, + AgentProfile, + FieldValueResponse, +} from "@/services/agents.service"; +import { + connectionRequestsService, + ConnectionStatus, +} from "@/services/connection-requests.service"; +import { uploadService } from "@/services/upload.service"; +import { + testimonialsService, + Testimonial, +} from "@/services/testimonials.service"; +import { + mapFieldValuesToExperience, + mapFieldValuesToSpecializationFields, + mapFieldValuesToProfileCard, + mapFieldValuesToContactInfo, + mapFieldValuesToAvailability, + mapFieldValuesToWorkEnvironment, + mapFieldValuesToPersonalTagline, + ExperienceData, + SpecializationFieldsData, + ProfileCardData, + ContactInfoData, + AvailabilityData, + WorkEnvironmentData, + PersonalTaglineData, +} from "@/utils/profileDataMapper"; // Default work environment data const defaultWorkEnvironmentData: WorkEnvironmentData = { - label: 'Preferred Work Environment', - content: '', + label: "Preferred Work Environment", + content: "", }; // Default personal tagline data const defaultPersonalTaglineData: PersonalTaglineData = { - label: 'Personal Tagline', - content: '', + label: "Personal Tagline", + content: "", }; // Default experience data when no field values are available const defaultExperience: ExperienceData = { - years: '-', - yearsLabel: 'Years in Experience', - contracts: '-', - contractsLabel: 'Number of contracts closed', + years: "-", + yearsLabel: "Years in Experience", + contracts: "-", + contractsLabel: "Number of contracts closed", licensingAreas: [], - licensingAreasLabel: 'Licensing & Areas', + licensingAreasLabel: "Licensing & Areas", expertiseYears: [], - expertiseYearsLabel: 'Areas in expertise & Years', + expertiseYearsLabel: "Areas in expertise & Years", certifications: [], - certificationsLabel: 'Certifications', - agencyName: '', - agencyDesignation: '', + certificationsLabel: "Certifications", + agencyName: "", + agencyDesignation: "", agencyShowDesignation: false, - agencySectionLabel: 'Real Estate Agency & Designation', + agencySectionLabel: "Real Estate Agency & Designation", }; // Default specialization fields data when no field values are available @@ -60,7 +85,7 @@ const defaultSpecializationFieldsData: SpecializationFieldsData = { // Default profile card data when no field values are available const defaultProfileCardData: ProfileCardData = { - bio: '', + bio: "", expertise: [], serviceAreas: [], city: null, @@ -75,9 +100,9 @@ const defaultContactInfoData: ContactInfoData = { // Default availability data when no field values are available const defaultAvailabilityData: AvailabilityData = { - type: '', + type: "", schedule: [], - label: 'Availability', + label: "Availability", }; export default function AgentProfileView() { @@ -87,31 +112,46 @@ export default function AgentProfileView() { const [agentProfile, setAgentProfile] = useState(null); const [fieldValues, setFieldValues] = useState([]); - const [experienceData, setExperienceData] = useState(defaultExperience); - const [specializationFieldsData, setSpecializationFieldsData] = useState(defaultSpecializationFieldsData); - const [profileCardData, setProfileCardData] = useState(defaultProfileCardData); - const [contactInfoData, setContactInfoData] = useState(defaultContactInfoData); - const [availabilityData, setAvailabilityData] = useState(defaultAvailabilityData); - const [workEnvironmentData, setWorkEnvironmentData] = useState(defaultWorkEnvironmentData); - const [personalTaglineData, setPersonalTaglineData] = useState(defaultPersonalTaglineData); + const [experienceData, setExperienceData] = + useState(defaultExperience); + const [specializationFieldsData, setSpecializationFieldsData] = + useState(defaultSpecializationFieldsData); + const [profileCardData, setProfileCardData] = useState( + defaultProfileCardData, + ); + const [contactInfoData, setContactInfoData] = useState( + defaultContactInfoData, + ); + const [availabilityData, setAvailabilityData] = useState( + defaultAvailabilityData, + ); + const [workEnvironmentData, setWorkEnvironmentData] = + useState(defaultWorkEnvironmentData); + const [personalTaglineData, setPersonalTaglineData] = + useState(defaultPersonalTaglineData); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [imageError, setImageError] = useState(false); const [imageLoaded, setImageLoaded] = useState(false); const [avatarUrl, setAvatarUrl] = useState(null); - const [testimonials, setTestimonials] = useState<{ id: string; text: string; author: string; role: string; rating: number }[]>([]); + const [testimonials, setTestimonials] = useState< + { id: string; text: string; author: string; role: string; rating: number }[] + >([]); // Connect modal state const [showConnectModal, setShowConnectModal] = useState(false); - const [connectionStatus, setConnectionStatus] = useState(null); - const [connectionRequestId, setConnectionRequestId] = useState(null); + const [connectionStatus, setConnectionStatus] = + useState(null); + const [connectionRequestId, setConnectionRequestId] = useState( + null, + ); // Helper to check if avatar is an S3 key (not a full URL or local path) const isS3Key = (avatar: string | null | undefined): boolean => { if (!avatar) return false; // S3 keys don't start with http or / - return !avatar.startsWith('http') && !avatar.startsWith('/'); + return !avatar.startsWith("http") && !avatar.startsWith("/"); }; // Fetch agent profile and field values on mount @@ -124,41 +164,58 @@ export default function AgentProfileView() { setError(null); // Fetch profile, field values, and testimonials in parallel - const [profile, fieldValuesResponse, testimonialsData] = await Promise.all([ - agentsService.getAgentById(id), - agentsService.getFieldValuesByAgentId(id), - testimonialsService.getAgentTestimonials(id).catch(() => [] as Testimonial[]), - ]); + const [profile, fieldValuesResponse, testimonialsData] = + await Promise.all([ + agentsService.getAgentById(id), + agentsService.getFieldValuesByAgentId(id), + testimonialsService + .getAgentTestimonials(id) + .catch(() => [] as Testimonial[]), + ]); setAgentProfile(profile); setFieldValues(fieldValuesResponse.fieldValues); // Map field values to experience data structure - const mappedExperience = mapFieldValuesToExperience(fieldValuesResponse.fieldValues); + const mappedExperience = mapFieldValuesToExperience( + fieldValuesResponse.fieldValues, + ); setExperienceData(mappedExperience); // Map field values to specialization fields data (only fields from "Specialization" section) - const mappedSpecializationFields = mapFieldValuesToSpecializationFields(fieldValuesResponse.fieldValues); + const mappedSpecializationFields = mapFieldValuesToSpecializationFields( + fieldValuesResponse.fieldValues, + ); setSpecializationFieldsData(mappedSpecializationFields); // Map field values to profile card data (bio, expertise, location) - const mappedProfileCard = mapFieldValuesToProfileCard(fieldValuesResponse.fieldValues); + const mappedProfileCard = mapFieldValuesToProfileCard( + fieldValuesResponse.fieldValues, + ); setProfileCardData(mappedProfileCard); // Map field values to contact info data (email, phone) - const mappedContactInfo = mapFieldValuesToContactInfo(fieldValuesResponse.fieldValues); + const mappedContactInfo = mapFieldValuesToContactInfo( + fieldValuesResponse.fieldValues, + ); setContactInfoData(mappedContactInfo); // Map field values to availability data - const mappedAvailability = mapFieldValuesToAvailability(fieldValuesResponse.fieldValues); + const mappedAvailability = mapFieldValuesToAvailability( + fieldValuesResponse.fieldValues, + ); setAvailabilityData(mappedAvailability); // Map field values to work environment data - const mappedWorkEnv = mapFieldValuesToWorkEnvironment(fieldValuesResponse.fieldValues); + const mappedWorkEnv = mapFieldValuesToWorkEnvironment( + fieldValuesResponse.fieldValues, + ); setWorkEnvironmentData(mappedWorkEnv); // Map field values to personal tagline data - const mappedTagline = mapFieldValuesToPersonalTagline(fieldValuesResponse.fieldValues); + const mappedTagline = mapFieldValuesToPersonalTagline( + fieldValuesResponse.fieldValues, + ); setPersonalTaglineData(mappedTagline); // Map testimonials for TestimonialsSection @@ -169,29 +226,33 @@ export default function AgentProfileView() { author: t.authorName, role: t.authorRole, rating: t.rating, - })) + })), ); // If avatar is an S3 key, fetch presigned URL if (profile.avatar && isS3Key(profile.avatar)) { try { - const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar); + const presignedUrl = await uploadService.getPresignedDownloadUrl( + profile.avatar, + ); setAvatarUrl(presignedUrl); } catch (avatarErr) { - console.error('Failed to get avatar URL:', avatarErr); + console.error("Failed to get avatar URL:", avatarErr); } } else if (profile.avatar) { setAvatarUrl(profile.avatar); } } catch (err: any) { - console.error('Failed to fetch profile:', err); + console.error("Failed to fetch profile:", err); const status = err?.response?.status; if (status === 403) { - setError('This profile is private. You don\u2019t have permission to view it.'); + setError( + "This profile is private. You don\u2019t have permission to view it.", + ); } else if (status === 404) { - setError('Profile not found.'); + setError("Profile not found."); } else { - setError('Failed to load profile data'); + setError("Failed to load profile data"); } } finally { setLoading(false); @@ -207,11 +268,12 @@ export default function AgentProfileView() { if (!id || !session) return; try { - const statusResponse = await connectionRequestsService.getConnectionStatus(id); + const statusResponse = + await connectionRequestsService.getConnectionStatus(id); setConnectionStatus(statusResponse?.status || null); setConnectionRequestId(statusResponse?.id || null); } catch (err) { - console.error('Failed to fetch connection status:', err); + console.error("Failed to fetch connection status:", err); // Non-critical error, don't show to user } }; @@ -221,7 +283,7 @@ export default function AgentProfileView() { // Handle connection request success const handleConnectionSuccess = () => { - setConnectionStatus('PENDING'); + setConnectionStatus("PENDING"); }; // Handle unlink/disconnect @@ -233,7 +295,7 @@ export default function AgentProfileView() { setConnectionStatus(null); setConnectionRequestId(null); } catch (err) { - console.error('Failed to unlink connection:', err); + console.error("Failed to unlink connection:", err); } }; @@ -249,12 +311,12 @@ export default function AgentProfileView() { } // Check for null, undefined, or empty string - if (!agentProfile?.avatar || agentProfile.avatar.trim() === '') { + if (!agentProfile?.avatar || agentProfile.avatar.trim() === "") { return null; } // For relative paths (local assets), return as-is - if (agentProfile.avatar.startsWith('/')) { + if (agentProfile.avatar.startsWith("/")) { return agentProfile.avatar; } @@ -264,12 +326,15 @@ export default function AgentProfileView() { // Format member since date const formatMemberSince = (dateString?: string) => { - if (!dateString) return 'Member'; + if (!dateString) return "Member"; try { const date = new Date(dateString); - return date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); + return date.toLocaleDateString("en-US", { + month: "long", + year: "numeric", + }); } catch { - return 'Member'; + return "Member"; } }; @@ -279,35 +344,64 @@ export default function AgentProfileView() {
-

Loading profile...

+

+ Loading profile... +

); } - const isPermissionError = error?.includes('permission') || error?.includes('private'); + const isPermissionError = + error?.includes("permission") || error?.includes("private"); if (error || !agentProfile) { return (
-
+
{isPermissionError ? ( - - + + ) : ( - - + + )}

- {isPermissionError ? 'Profile Not Available' : 'Unable to Load Profile'} + {isPermissionError + ? "Profile Not Available" + : "Unable to Load Profile"}

-

{error || 'Profile not found'}

+

+ {error || "Profile not found"} +

); } + const profileImage = getProfileImageUrl(); + const profileSchema = { + "@context": "https://schema.org", + "@type": "Person", + + name: `${agentProfile.firstName} ${agentProfile.lastName}`.trim(), + + jobTitle: agentProfile.agentType?.name || "Real Estate Professional", + + description: + profileCardData.bio || + agentProfile.bio || + "Verified real estate professional on RE-Quest.", + + image: profileImage + ? profileImage.startsWith("http") + ? profileImage + : `https://re-quest.com${profileImage}` + : undefined, + + email: agentProfile.email || agentProfile.user?.email || undefined, + + telephone: agentProfile.phone || contactInfoData.phone || undefined, + + url: `https://re-quest.com/user/profile/${id}`, + mainEntityOfPage: { + "@type": "WebPage", + "@id": `https://re-quest.com/user/profile/${id}`, + }, + address: { + "@type": "PostalAddress", + addressLocality: profileCardData.city || undefined, + + addressRegion: profileCardData.state || undefined, + }, + + memberOf: { + "@type": "Organization", + name: "RE-Quest", + url: "https://re-quest.com", + }, + + sameAs: [`https://re-quest.com/user/profile/${id}`], + }; return ( -
- - {/* Main Layout - Responsive: Column on mobile, Row on desktop */} -
- {/* Left Sidebar - Status & Contact */} -
- {/* Profile Image */} -
-
- {/* Shimmer while loading, initials only on error/no image */} - {imageError || !getProfileImageUrl() ? ( -
- {agentProfile?.firstName?.[0]?.toUpperCase() || '?'} -
- ) : !imageLoaded ? ( -
- ) : null} - {getProfileImageUrl() && ( - <> - {/* eslint-disable-next-line @next/next/no-img-element */} - { if (el?.complete) { if (el.naturalWidth > 0) setImageLoaded(true); else setImageError(true); } }} - src={getProfileImageUrl()!} - alt="Profile" - className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`} - onLoad={() => setImageLoaded(true)} - onError={() => setImageError(true)} - /> - - )} - {/* Gradient Overlay */} -
-
-
- - {/* Status Buttons - Public view shows availability status with Connect/Pending/Unlink button */} - setShowConnectModal(true)} - onUnlinkClick={handleUnlink} - /> - - {/* Contact Info */} - -
- - {/* Right Content - Profile Info + Experience + All Sections */} -
- {/* Profile Card - No edit button for user view */} - 0 ? profileCardData.expertise : (agentProfile.specializations || [])} - showEditButton={false} - messageHref={`/user/message?agentProfileId=${agentProfile.id}`} - connectionStatus={connectionStatus} - isAvailable={agentProfile.isAvailable ?? true} - onConnectClick={() => setShowConnectModal(true)} - onUnlinkClick={handleUnlink} - /> - - {/* Experience Section - Dynamic data from profile fields */} - - - {/* Info Cards Section */} -
- - } - content={ -
-
- {availabilityData.schedule.length > 0 ? ( - availabilityData.schedule.map((item, index) => ( -

{item}

- )) - ) : ( -

Not specified

- )} -
-
- } - /> - - } - content={ - workEnvironmentData.content ? ( -

{workEnvironmentData.content}

- ) : ( -

Not specified

- ) - } - /> - - } - content={ - personalTaglineData.content ? ( -

“{personalTaglineData.content.length > 150 ? personalTaglineData.content.substring(0, 150) + '...' : personalTaglineData.content}”

- ) : ( -

Not specified

- ) - } - /> -
- - {/* Specialization Section */} - - - {/* Testimonials Section */} - -
-
- - {/* Connect Request Modal */} - setShowConnectModal(false)} - agentProfileId={id} - agentName={`${agentProfile.firstName || ''} ${agentProfile.lastName || ''}`.trim() || 'Agent'} - existingStatus={connectionStatus} - onSuccess={handleConnectionSuccess} + <> +