+
Manage my network
- {/* Invitations Card */}
-
- {/* Invitations Header */}
-
-
- Invitations ({invitations.length})
-
-
+ {/* Loading State */}
+ {loading && (
+
+ )}
- {/* Invitations List */}
-
- {invitations.map((invitation) => (
-
- ))}
+ {/* Error State */}
+ {error && !loading && (
+
+
+
+
{error}
+
+
+
-
+ )}
+
+ {!loading && !error && (
+ <>
+ {/* Pending Connection Requests Card */}
+
+ {/* Requests Header */}
+
+
+ Pending Requests ({counts.pending})
+
+
+
+ {/* Empty State for Pending */}
+ {pendingRequests.length === 0 && (
+
+
+
+
+
+
No pending requests
+
+ New connection requests will appear here.
+
+
+
+ )}
+
+ {/* Pending Requests List */}
+ {pendingRequests.length > 0 && (
+
+ {pendingRequests.map((request) => (
+
+ ))}
+
+ )}
+
+
+ {/* My Connections Card */}
+
+ {/* Connections Header */}
+
+
+ My Connections ({counts.accepted})
+
+
+
+ {/* Empty State for Connections */}
+ {connections.length === 0 && (
+
+
+
+
+
+
No connections yet
+
+ Accept connection requests to grow your network.
+
+
+
+ )}
+
+ {/* Connections List */}
+ {connections.length > 0 && (
+
+ {connections.map((connection) => (
+
+ ))}
+
+ )}
+
+ >
+ )}
);
diff --git a/src/app/(user)/user/profile/[id]/page.tsx b/src/app/(user)/user/profile/[id]/page.tsx
index 2739e76..c3871e5 100644
--- a/src/app/(user)/user/profile/[id]/page.tsx
+++ b/src/app/(user)/user/profile/[id]/page.tsx
@@ -3,6 +3,7 @@
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,9 +15,11 @@ import {
StatusButtons,
ContactInfo,
} from '@/components/profile';
+import { ConnectRequestModal } from '@/components/modals';
import { agentsService, AgentProfile, FieldValueResponse } from '@/services/agents.service';
-import { getProxyImageUrl } from '@/lib/imageProxy';
-import { mapFieldValuesToExperience, mapFieldValuesToSpecializationFields, ExperienceData, SpecializationFieldsData } from '@/utils/profileDataMapper';
+import { connectionRequestsService, ConnectionStatus } from '@/services/connection-requests.service';
+import { uploadService } from '@/services/upload.service';
+import { mapFieldValuesToExperience, mapFieldValuesToSpecializationFields, mapFieldValuesToProfileCard, ExperienceData, SpecializationFieldsData, ProfileCardData } from '@/utils/profileDataMapper';
// Mock data for sections not yet available from API
const mockData = {
@@ -65,20 +68,44 @@ const defaultSpecializationFieldsData: SpecializationFieldsData = {
fields: [],
};
+// Default profile card data when no field values are available
+const defaultProfileCardData: ProfileCardData = {
+ bio: '',
+ expertise: [],
+ serviceAreas: [],
+ city: null,
+ state: null,
+};
+
export default function AgentProfileView() {
const params = useParams();
const id = params.id as string;
+ const { data: session } = useSession();
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 [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [imageError, setImageError] = useState(false);
+ const [avatarUrl, setAvatarUrl] = useState(null);
+
+ // Connect modal state
+ const [showConnectModal, setShowConnectModal] = useState(false);
+ const [connectionStatus, setConnectionStatus] = useState(null);
+ const [connectionRequestId, setConnectionRequestId] = useState(null);
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
+ // 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('/');
+ };
+
// Fetch agent profile and field values on mount
useEffect(() => {
const fetchData = async () => {
@@ -104,6 +131,24 @@ export default function AgentProfileView() {
// Map field values to specialization fields data (only fields from "Specialization" section)
const mappedSpecializationFields = mapFieldValuesToSpecializationFields(fieldValuesResponse.fieldValues);
setSpecializationFieldsData(mappedSpecializationFields);
+
+ // Map field values to profile card data (bio, expertise, location)
+ const mappedProfileCard = mapFieldValuesToProfileCard(fieldValuesResponse.fieldValues);
+ setProfileCardData(mappedProfileCard);
+
+ // If avatar is an S3 key, fetch presigned URL
+ if (profile.avatar && isS3Key(profile.avatar)) {
+ try {
+ const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
+ setAvatarUrl(presignedUrl);
+ } catch (avatarErr) {
+ console.error('Failed to get avatar URL:', avatarErr);
+ // Don't fail the whole page, just use default image
+ }
+ } else if (profile.avatar) {
+ // It's already a full URL or local path
+ setAvatarUrl(profile.avatar);
+ }
} catch (err) {
console.error('Failed to fetch profile:', err);
setError('Failed to load profile data');
@@ -115,24 +160,65 @@ export default function AgentProfileView() {
fetchData();
}, [id]);
+ // Fetch connection status when user is logged in
+ useEffect(() => {
+ const fetchConnectionStatus = async () => {
+ if (!id || !session) return;
+
+ try {
+ const statusResponse = await connectionRequestsService.getConnectionStatus(id);
+ setConnectionStatus(statusResponse?.status || null);
+ setConnectionRequestId(statusResponse?.id || null);
+ } catch (err) {
+ console.error('Failed to fetch connection status:', err);
+ // Non-critical error, don't show to user
+ }
+ };
+
+ fetchConnectionStatus();
+ }, [id, session]);
+
+ // Handle connection request success
+ const handleConnectionSuccess = () => {
+ setConnectionStatus('PENDING');
+ };
+
+ // Handle unlink/disconnect
+ const handleUnlink = async () => {
+ if (!connectionRequestId) return;
+
+ try {
+ await connectionRequestsService.cancelRequest(connectionRequestId);
+ setConnectionStatus(null);
+ setConnectionRequestId(null);
+ } catch (err) {
+ console.error('Failed to unlink connection:', err);
+ }
+ };
+
const getProfileImageUrl = () => {
// If image failed to load, return default
if (imageError) {
return defaultImage;
}
+ // If we have a presigned URL from S3, use it
+ if (avatarUrl) {
+ return avatarUrl;
+ }
+
// Check for null, undefined, or empty string
if (!agentProfile?.avatar || agentProfile.avatar.trim() === '') {
return defaultImage;
}
- // S3 URLs need to go through the backend proxy
- if (agentProfile.avatar.startsWith('http')) {
- return getProxyImageUrl(agentProfile.avatar);
+ // For relative paths (local assets), return as-is
+ if (agentProfile.avatar.startsWith('/')) {
+ return agentProfile.avatar;
}
- // For relative paths (local assets), return as-is
- return agentProfile.avatar;
+ // Default fallback
+ return defaultImage;
};
// Format member since date
@@ -208,8 +294,13 @@ export default function AgentProfileView() {