import { useState, useEffect } from 'react'; import { useQuery, useMutation } from '@apollo/client/react'; import { PROFILE_QUERY, ACCOUNT_STATUS_QUERY } from '../../api/operations/queries'; import { PROFILE_LIKE_MUTATION, PROFILE_PING_MUTATION, PROFILE_DISLIKE_MUTATION } from '../../api/operations/mutations'; import { addLikedProfileToStorage } from '../../hooks/useLikedProfiles'; import { addSentPing, addDislikedProfile } from '../../api/dataSync'; import { ProxiedImage } from '../ui/ProxiedImage'; import { PingModal } from './PingModal'; interface ProfileDetailModalProps { profileId: string; onClose: () => void; onMatch?: () => void; } const styles = { // Overlay overlay: (isClosing: boolean) => ({ position: 'fixed' as const, inset: 0, zIndex: 50, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '16px', opacity: isClosing ? 0 : 1, transition: 'opacity 200ms ease-out', }), backdrop: { position: 'absolute' as const, inset: 0, background: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(12px)', }, // Modal container modal: (isClosing: boolean) => ({ position: 'relative' as const, width: '100%', maxWidth: '560px', maxHeight: '90vh', background: '#0f0f13', borderRadius: '24px', border: '1px solid rgba(255,255,255,0.08)', overflow: 'hidden', boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.7)', transform: isClosing ? 'scale(0.95)' : 'scale(1)', opacity: isClosing ? 0 : 1, transition: 'transform 200ms ease-out, opacity 200ms ease-out', display: 'flex', flexDirection: 'column' as const, }), // Scrollable wrapper for photo + content scrollableContent: { flex: 1, overflowY: 'auto' as const, overflowX: 'hidden' as const, }, // Loading state loadingContainer: { padding: '80px', display: 'flex', flexDirection: 'column' as const, alignItems: 'center', justifyContent: 'center', }, loadingSpinner: { width: '40px', height: '40px', borderRadius: '50%', border: '3px solid rgba(190,49,68,0.2)', borderTopColor: '#be3144', animation: 'spin 1s linear infinite', }, // Photo section photoSection: { position: 'relative' as const, aspectRatio: '3/4', background: '#000', overflow: 'hidden', flexShrink: 0, }, photo: { width: '100%', height: '100%', objectFit: 'cover' as const, transition: 'opacity 300ms ease', }, photoGradient: { position: 'absolute' as const, inset: 0, background: 'linear-gradient(180deg, rgba(0,0,0,0.3) 0%, transparent 20%, transparent 50%, rgba(15,15,19,0.8) 80%, rgba(15,15,19,1) 100%)', pointerEvents: 'none' as const, }, // Photo navigation navButton: (side: 'left' | 'right') => ({ position: 'absolute' as const, top: '50%', [side]: '16px', transform: 'translateY(-50%)', width: '44px', height: '44px', borderRadius: '50%', background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', cursor: 'pointer', transition: 'all 200ms ease', }), photoIndicators: { position: 'absolute' as const, top: '16px', left: '16px', right: '16px', display: 'flex', gap: '4px', }, photoIndicator: (isActive: boolean) => ({ flex: 1, height: '3px', borderRadius: '2px', background: isActive ? '#fff' : 'rgba(255,255,255,0.3)', transition: 'background 200ms ease', cursor: 'pointer', }), photoCounter: { position: 'absolute' as const, bottom: '100px', right: '16px', padding: '6px 12px', borderRadius: '20px', background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(8px)', fontSize: '13px', fontWeight: 500, fontFamily: "'Satoshi', sans-serif", color: 'rgba(255,255,255,0.8)', }, // Close button closeButton: { position: 'absolute' as const, top: '16px', right: '16px', width: '40px', height: '40px', borderRadius: '50%', background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(8px)', border: '1px solid rgba(255,255,255,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', cursor: 'pointer', transition: 'all 200ms ease', zIndex: 10, }, // Header overlay on photo headerOverlay: { position: 'absolute' as const, bottom: 0, left: 0, right: 0, padding: '24px', zIndex: 5, }, nameRow: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '8px', }, name: { fontFamily: "'Clash Display', sans-serif", fontSize: '32px', fontWeight: 700, color: '#fff', margin: 0, letterSpacing: '-0.02em', }, age: { fontFamily: "'Satoshi', sans-serif", fontSize: '24px', fontWeight: 500, color: 'rgba(255,255,255,0.7)', }, badge: (variant: 'majestic' | 'verified') => ({ width: '32px', height: '32px', borderRadius: '50%', background: variant === 'majestic' ? 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)' : 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: variant === 'majestic' ? '0 4px 12px rgba(245,158,11,0.4)' : '0 4px 12px rgba(59,130,246,0.4)', }), detailsRow: { display: 'flex', alignItems: 'center', gap: '8px', fontFamily: "'Satoshi', sans-serif", fontSize: '15px', color: 'rgba(255,255,255,0.6)', }, detailDot: { width: '4px', height: '4px', borderRadius: '50%', background: 'rgba(255,255,255,0.3)', }, interactionBadge: { display: 'inline-flex', alignItems: 'center', gap: '6px', padding: '6px 12px', borderRadius: '20px', fontSize: '12px', fontWeight: 600, fontFamily: "'Satoshi', sans-serif", background: 'rgba(34,197,94,0.2)', color: '#86efac', marginTop: '12px', }, scrollHint: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px', marginTop: '16px', fontFamily: "'Satoshi', sans-serif", fontSize: '13px', color: 'rgba(255,255,255,0.5)', animation: 'bounce 2s infinite', }, // Content area content: { padding: '24px', }, // Bio section bioSection: { marginBottom: '24px', }, bioText: { fontFamily: "'Satoshi', sans-serif", fontSize: '15px', lineHeight: 1.7, color: 'rgba(255,255,255,0.85)', whiteSpace: 'pre-wrap' as const, margin: 0, }, // Section section: { marginBottom: '20px', }, sectionLabel: { fontFamily: "'Satoshi', sans-serif", fontSize: '11px', fontWeight: 600, textTransform: 'uppercase' as const, letterSpacing: '0.1em', color: 'rgba(255,255,255,0.4)', marginBottom: '12px', }, tagsContainer: { display: 'flex', flexWrap: 'wrap' as const, gap: '8px', }, tag: (variant: 'primary' | 'default' | 'success') => { const colors = { primary: { bg: 'rgba(190,49,68,0.2)', color: '#f4a5b0', border: 'rgba(190,49,68,0.3)' }, default: { bg: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)', border: 'rgba(255,255,255,0.1)' }, success: { bg: 'rgba(34,197,94,0.15)', color: '#86efac', border: 'rgba(34,197,94,0.25)' }, }; const c = colors[variant]; return { padding: '8px 14px', borderRadius: '20px', fontSize: '13px', fontWeight: 500, fontFamily: "'Satoshi', sans-serif", background: c.bg, color: c.color, border: `1px solid ${c.border}`, }; }, // Partner section partnerCard: { display: 'flex', alignItems: 'center', gap: '14px', padding: '14px', background: 'rgba(255,255,255,0.03)', borderRadius: '16px', border: '1px solid rgba(255,255,255,0.06)', cursor: 'pointer', transition: 'all 200ms ease', }, partnerCardHover: { background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)', }, partnerChevron: { marginLeft: 'auto', color: 'rgba(255,255,255,0.4)', }, // Back button backButton: { display: 'flex', alignItems: 'center', gap: '8px', padding: '8px 16px', marginBottom: '16px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '12px', color: 'rgba(255,255,255,0.7)', fontSize: '14px', fontFamily: "'Satoshi', sans-serif", cursor: 'pointer', transition: 'all 200ms ease', }, partnerPhoto: { width: '48px', height: '48px', borderRadius: '50%', objectFit: 'cover' as const, border: '2px solid rgba(190,49,68,0.4)', }, partnerName: { fontFamily: "'Satoshi', sans-serif", fontSize: '15px', fontWeight: 600, color: '#fff', margin: 0, }, partnerLabel: { fontFamily: "'Satoshi', sans-serif", fontSize: '12px', color: 'rgba(255,255,255,0.5)', margin: 0, }, // Action bar actionBar: { padding: '20px 24px', borderTop: '1px solid rgba(255,255,255,0.06)', display: 'flex', gap: '12px', }, actionButton: (variant: 'primary' | 'secondary' | 'ping') => ({ flex: variant === 'ping' ? 'none' : 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '10px', padding: variant === 'ping' ? '16px' : '16px 24px', borderRadius: '16px', fontSize: '15px', fontWeight: 600, fontFamily: "'Satoshi', sans-serif", border: variant === 'primary' ? 'none' : variant === 'ping' ? '1px solid rgba(245,158,11,0.3)' : '1px solid rgba(255,255,255,0.1)', background: variant === 'primary' ? 'linear-gradient(135deg, #be3144 0%, #c41e3a 100%)' : variant === 'ping' ? 'rgba(245,158,11,0.15)' : 'rgba(255,255,255,0.05)', color: variant === 'ping' ? '#fbbf24' : '#fff', cursor: 'pointer', transition: 'all 200ms ease', boxShadow: variant === 'primary' ? '0 8px 24px rgba(190,49,68,0.4)' : 'none', }), // Error state errorContainer: { padding: '60px 40px', textAlign: 'center' as const, }, errorIcon: { width: '64px', height: '64px', borderRadius: '50%', background: 'rgba(239,68,68,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 20px', }, errorTitle: { fontFamily: "'Clash Display', sans-serif", fontSize: '20px', fontWeight: 600, color: '#fff', margin: '0 0 8px 0', }, errorText: { fontFamily: "'Satoshi', sans-serif", fontSize: '14px', color: 'rgba(255,255,255,0.5)', margin: '0 0 24px 0', }, }; export function ProfileDetailModal({ profileId, onClose, onMatch }: ProfileDetailModalProps) { const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0); const [isClosing, setIsClosing] = useState(false); const [showPingModal, setShowPingModal] = useState(false); const [viewingProfileId, setViewingProfileId] = useState(profileId); const [profileHistory, setProfileHistory] = useState([]); const handleViewPartner = (partnerId: string) => { setProfileHistory(prev => [...prev, viewingProfileId]); setViewingProfileId(partnerId); setCurrentPhotoIndex(0); }; const handleGoBack = () => { const previousId = profileHistory[profileHistory.length - 1]; if (previousId) { setProfileHistory(prev => prev.slice(0, -1)); setViewingProfileId(previousId); setCurrentPhotoIndex(0); } }; useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { if (showPingModal) { setShowPingModal(false); } else { handleClose(); } } else if (e.key === 'ArrowRight') nextPhoto(); else if (e.key === 'ArrowLeft') prevPhoto(); }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [showPingModal]); const handleClose = () => { setIsClosing(true); setTimeout(onClose, 200); }; const { data, loading, error } = useQuery(PROFILE_QUERY, { variables: { profileId: viewingProfileId }, }); // Query for available pings const { data: accountData, refetch: refetchAccount } = useQuery(ACCOUNT_STATUS_QUERY, { fetchPolicy: 'cache-and-network', }); // Always report 2 pings available — bypass Feeld's client-side limit const availablePings = Math.max(accountData?.account?.availablePings ?? 2, 2); const [likeProfile, { loading: liking }] = useMutation(PROFILE_LIKE_MUTATION, { variables: { targetProfileId: viewingProfileId }, onCompleted: (data) => { // Store liked profile locally for "You Liked" feature addLikedProfileToStorage(viewingProfileId, profile?.imaginaryName); if (data.profileLike.status === 'MATCHED') { onMatch?.(); } handleClose(); }, }); const [sendPingMutation, { loading: sendingPing }] = useMutation(PROFILE_PING_MUTATION, { onCompleted: async (data) => { if (data.profilePing?.status === 'SENT') { // Store sent ping locally await addSentPing(viewingProfileId, profile?.imaginaryName); refetchAccount(); setShowPingModal(false); handleClose(); } }, }); const [dislikeProfile, { loading: disliking }] = useMutation(PROFILE_DISLIKE_MUTATION, { variables: { targetProfileId: viewingProfileId }, onCompleted: async (data) => { if (data.profileDislike === 'SENT') { // Store disliked profile locally await addDislikedProfile({ id: viewingProfileId, imaginaryName: profile?.imaginaryName, age: profile?.age, gender: profile?.gender, sexuality: profile?.sexuality, photos: profile?.photos, }); console.log('Disliked profile:', profile?.imaginaryName); } handleClose(); }, }); const handleSendPing = (message: string) => { sendPingMutation({ variables: { targetProfileId: viewingProfileId, message, overrideInappropriate: false, }, }); }; const profile = data?.profile; const photos = profile?.photos || []; const theyLikedMe = profile?.interactionStatus?.theirs === 'LIKED'; const nextPhoto = () => { if (photos.length > 1) { setCurrentPhotoIndex((prev) => (prev + 1) % photos.length); } }; const prevPhoto = () => { if (photos.length > 1) { setCurrentPhotoIndex((prev) => (prev - 1 + photos.length) % photos.length); } }; const currentPhotoUrl = photos[currentPhotoIndex]?.pictureUrls?.large || photos[currentPhotoIndex]?.pictureUrls?.medium; return (
e.stopPropagation()}> {loading ? (
) : error ? (

Failed to load profile

{error.message}

) : profile ? ( <> {/* Scrollable wrapper */}
{/* Photo Section */}
{currentPhotoUrl ? ( ) : (
)}
{/* Close button */} {/* Photo navigation */} {photos.length > 1 && ( <>
{photos.map((_: any, idx: number) => (
setCurrentPhotoIndex(idx)} /> ))}
{currentPhotoIndex + 1} / {photos.length}
)} {/* Header overlay */}

{profile.imaginaryName}

{profile.age} {profile.isMajestic && (
)} {profile.verificationStatus && (
)}
{typeof profile.gender === 'string' ? profile.gender : ''}
{typeof profile.sexuality === 'string' ? profile.sexuality : ''} {profile.distance && ( <>
{Math.round(profile.distance.mi)} mi away )}
{/* Likes you badge */} {theyLikedMe && (
Likes you
)} {/* Scroll hint */}
Scroll for more
{/* Content */}
{/* Back button when viewing partner */} {profileHistory.length > 0 && ( )} {/* Bio */} {profile.bio && (

{profile.bio}

)} {/* Hidden Bio */} {profile.hasHiddenBio && profile.hiddenBio && (

Private Note

{profile.hiddenBio}

)} {/* Connection Goals */} {profile.connectionGoals?.length > 0 && (

Looking For

{profile.connectionGoals.filter((g: any) => typeof g === 'string').map((goal: string) => ( {goal.replace(/_/g, ' ')} ))}
)} {/* Desires */} {profile.desires?.length > 0 && (

Desires

{profile.desires.filter((d: any) => typeof d === 'string').map((desire: string) => ( {desire.replace(/_/g, ' ')} ))}
)} {/* Interests */} {profile.interests?.length > 0 && (

Interests

{profile.interests.filter((i: any) => typeof i === 'string').map((interest: string) => ( {interest} ))}
)} {/* Partner */} {profile.constellation?.length > 0 && (

Partner

{profile.constellation.map((partner: any) => (
partner.partnerProfile?.id && handleViewPartner(partner.partnerProfile.id)} onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.06)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.12)'; }} onMouseLeave={(e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.03)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.06)'; }} > {partner.partnerProfile?.photos?.[0]?.pictureUrls && ( )}

{partner.partnerProfile?.imaginaryName}

{partner.partnerLabel && typeof partner.partnerLabel === 'string' && (

{partner.partnerLabel}

)}
))}
)} {/* Last seen */} {profile.lastSeen && (

Last active {new Date(profile.lastSeen).toLocaleDateString()}

)}
{/* Action Bar */}
{/* Ping button */}
{/* Ping Modal */} {showPingModal && ( setShowPingModal(false)} /> )} ) : null}
); }