Initial commit

This commit is contained in:
Trey
2026-03-20 18:49:48 -05:00
commit dfa1697fef
197 changed files with 29298 additions and 0 deletions

View File

@@ -0,0 +1,903 @@
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<string[]>([]);
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 (
<div style={styles.overlay(isClosing)} onClick={handleClose}>
<div style={styles.backdrop} />
<div style={styles.modal(isClosing)} onClick={(e) => e.stopPropagation()}>
{loading ? (
<div style={styles.loadingContainer}>
<div style={styles.loadingSpinner} />
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
</div>
) : error ? (
<div style={styles.errorContainer}>
<div style={styles.errorIcon}>
<svg viewBox="0 0 24 24" fill="none" stroke="#ef4444" strokeWidth="1.5" style={{ width: '32px', height: '32px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
<h3 style={styles.errorTitle}>Failed to load profile</h3>
<p style={styles.errorText}>{error.message}</p>
<button
style={styles.actionButton('secondary')}
onClick={handleClose}
>
Close
</button>
</div>
) : profile ? (
<>
{/* Scrollable wrapper */}
<div style={styles.scrollableContent}>
{/* Photo Section */}
<div style={styles.photoSection}>
{currentPhotoUrl ? (
<ProxiedImage
src={currentPhotoUrl}
alt={profile.imaginaryName}
style={styles.photo}
/>
) : (
<div style={{ ...styles.photo, background: '#1a1a1f', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.2)" strokeWidth="1" style={{ width: '64px', height: '64px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
</svg>
</div>
)}
<div style={styles.photoGradient} />
{/* Close button */}
<button
style={styles.closeButton}
onClick={handleClose}
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(0,0,0,0.7)'}
onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(0,0,0,0.5)'}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ width: '20px', height: '20px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
{/* Photo navigation */}
{photos.length > 1 && (
<>
<div style={styles.photoIndicators}>
{photos.map((_: any, idx: number) => (
<div
key={idx}
style={styles.photoIndicator(idx === currentPhotoIndex)}
onClick={() => setCurrentPhotoIndex(idx)}
/>
))}
</div>
<button
style={styles.navButton('left')}
onClick={prevPhoto}
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(0,0,0,0.7)'}
onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(0,0,0,0.5)'}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ width: '20px', height: '20px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
</button>
<button
style={styles.navButton('right')}
onClick={nextPhoto}
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(0,0,0,0.7)'}
onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(0,0,0,0.5)'}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ width: '20px', height: '20px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</button>
<div style={styles.photoCounter}>
{currentPhotoIndex + 1} / {photos.length}
</div>
</>
)}
{/* Header overlay */}
<div style={styles.headerOverlay}>
<div style={styles.nameRow}>
<h2 style={styles.name}>{profile.imaginaryName}</h2>
<span style={styles.age}>{profile.age}</span>
{profile.isMajestic && (
<div style={styles.badge('majestic')} title="Majestic Member">
<svg viewBox="0 0 24 24" fill="#fff" style={{ width: '18px', height: '18px' }}>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
</div>
)}
{profile.verificationStatus && (
<div style={styles.badge('verified')} title="Verified">
<svg viewBox="0 0 24 24" fill="#fff" style={{ width: '18px', height: '18px' }}>
<path fillRule="evenodd" d="M8.603 3.799A4.49 4.49 0 0112 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 013.498 1.307 4.491 4.491 0 011.307 3.497A4.49 4.49 0 0121.75 12a4.49 4.49 0 01-1.549 3.397 4.491 4.491 0 01-1.307 3.497 4.491 4.491 0 01-3.497 1.307A4.49 4.49 0 0112 21.75a4.49 4.49 0 01-3.397-1.549 4.49 4.49 0 01-3.498-1.306 4.491 4.491 0 01-1.307-3.498A4.49 4.49 0 012.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 011.307-3.497 4.49 4.49 0 013.497-1.307zm7.007 6.387a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z" clipRule="evenodd" />
</svg>
</div>
)}
</div>
<div style={styles.detailsRow}>
<span>{typeof profile.gender === 'string' ? profile.gender : ''}</span>
<div style={styles.detailDot} />
<span>{typeof profile.sexuality === 'string' ? profile.sexuality : ''}</span>
{profile.distance && (
<>
<div style={styles.detailDot} />
<span>{Math.round(profile.distance.mi)} mi away</span>
</>
)}
</div>
{/* Likes you badge */}
{theyLikedMe && (
<div style={styles.interactionBadge}>
<svg viewBox="0 0 24 24" fill="currentColor" style={{ width: '14px', height: '14px' }}>
<path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
</svg>
Likes you
</div>
)}
{/* Scroll hint */}
<div style={styles.scrollHint}>
<span>Scroll for more</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ width: '16px', height: '16px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
</div>
<style>{`@keyframes bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(5px); } 60% { transform: translateY(3px); } }`}</style>
</div>
</div>
{/* Content */}
<div style={styles.content}>
{/* Back button when viewing partner */}
{profileHistory.length > 0 && (
<button
style={styles.backButton}
onClick={handleGoBack}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
e.currentTarget.style.borderColor = 'rgba(255,255,255,0.15)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
e.currentTarget.style.borderColor = 'rgba(255,255,255,0.1)';
}}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ width: '16px', height: '16px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
Back to previous profile
</button>
)}
{/* Bio */}
{profile.bio && (
<div style={styles.bioSection}>
<p style={styles.bioText}>{profile.bio}</p>
</div>
)}
{/* Hidden Bio */}
{profile.hasHiddenBio && profile.hiddenBio && (
<div style={{ ...styles.section, padding: '16px', background: 'rgba(190,49,68,0.1)', borderRadius: '16px', border: '1px solid rgba(190,49,68,0.2)' }}>
<p style={styles.sectionLabel}>Private Note</p>
<p style={{ ...styles.bioText, fontSize: '14px' }}>{profile.hiddenBio}</p>
</div>
)}
{/* Connection Goals */}
{profile.connectionGoals?.length > 0 && (
<div style={styles.section}>
<p style={styles.sectionLabel}>Looking For</p>
<div style={styles.tagsContainer}>
{profile.connectionGoals.filter((g: any) => typeof g === 'string').map((goal: string) => (
<span key={goal} style={styles.tag('primary')}>
{goal.replace(/_/g, ' ')}
</span>
))}
</div>
</div>
)}
{/* Desires */}
{profile.desires?.length > 0 && (
<div style={styles.section}>
<p style={styles.sectionLabel}>Desires</p>
<div style={styles.tagsContainer}>
{profile.desires.filter((d: any) => typeof d === 'string').map((desire: string) => (
<span key={desire} style={styles.tag('default')}>
{desire.replace(/_/g, ' ')}
</span>
))}
</div>
</div>
)}
{/* Interests */}
{profile.interests?.length > 0 && (
<div style={styles.section}>
<p style={styles.sectionLabel}>Interests</p>
<div style={styles.tagsContainer}>
{profile.interests.filter((i: any) => typeof i === 'string').map((interest: string) => (
<span key={interest} style={styles.tag('success')}>
{interest}
</span>
))}
</div>
</div>
)}
{/* Partner */}
{profile.constellation?.length > 0 && (
<div style={styles.section}>
<p style={styles.sectionLabel}>Partner</p>
{profile.constellation.map((partner: any) => (
<div
key={partner.partnerId}
style={styles.partnerCard}
onClick={() => 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 && (
<ProxiedImage
src={partner.partnerProfile.photos[0].pictureUrls.medium || partner.partnerProfile.photos[0].pictureUrls.small}
alt={partner.partnerProfile.imaginaryName}
style={styles.partnerPhoto}
/>
)}
<div>
<p style={styles.partnerName}>{partner.partnerProfile?.imaginaryName}</p>
{partner.partnerLabel && typeof partner.partnerLabel === 'string' && (
<p style={styles.partnerLabel}>{partner.partnerLabel}</p>
)}
</div>
<div style={styles.partnerChevron}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ width: '20px', height: '20px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</div>
</div>
))}
</div>
)}
{/* Last seen */}
{profile.lastSeen && (
<p style={{ ...styles.sectionLabel, marginTop: '16px', marginBottom: 0 }}>
Last active {new Date(profile.lastSeen).toLocaleDateString()}
</p>
)}
</div>
</div>
{/* Action Bar */}
<div style={styles.actionBar}>
<button
style={{
...styles.actionButton('secondary'),
opacity: disliking ? 0.7 : 1,
}}
onClick={() => dislikeProfile()}
disabled={disliking}
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.08)'}
onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.05)'}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ width: '20px', height: '20px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
{disliking ? 'Passing...' : 'Pass'}
</button>
{/* Ping button */}
<button
style={{
...styles.actionButton('ping'),
opacity: availablePings > 0 ? 1 : 0.5,
}}
onClick={() => availablePings > 0 && setShowPingModal(true)}
title={availablePings > 0 ? `Send a ping (${availablePings} available)` : 'No pings available'}
onMouseEnter={(e) => {
if (availablePings > 0) e.currentTarget.style.background = 'rgba(245,158,11,0.25)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(245,158,11,0.15)';
}}
>
<svg viewBox="0 0 24 24" fill="currentColor" style={{ width: '20px', height: '20px' }}>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
</button>
<button
style={{
...styles.actionButton('primary'),
opacity: liking ? 0.7 : 1,
}}
onClick={() => likeProfile()}
disabled={liking}
onMouseEnter={(e) => {
if (!liking) e.currentTarget.style.transform = 'scale(1.02)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'scale(1)';
}}
>
<svg viewBox="0 0 24 24" fill="currentColor" style={{ width: '20px', height: '20px' }}>
<path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
</svg>
{liking ? 'Liking...' : 'Like'}
</button>
</div>
{/* Ping Modal */}
{showPingModal && (
<PingModal
profileName={profile.imaginaryName}
availablePings={availablePings}
sending={sendingPing}
onSend={handleSendPing}
onClose={() => setShowPingModal(false)}
/>
)}
</>
) : null}
</div>
</div>
);
}