From cda94b7621c5c6fb2b8d4ed288b3c0c0a79546b9 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Wed, 8 Apr 2026 12:16:18 -0400 Subject: [PATCH] athlete page layout update --- src/pages/AthleteDetail.jsx | 379 ++++++++++++++++++++++++++++-------- 1 file changed, 293 insertions(+), 86 deletions(-) diff --git a/src/pages/AthleteDetail.jsx b/src/pages/AthleteDetail.jsx index 1e75a9d..0192147 100644 --- a/src/pages/AthleteDetail.jsx +++ b/src/pages/AthleteDetail.jsx @@ -6,132 +6,339 @@ import Avatar from '../components/Avatar.jsx'; import SportBadge from '../components/SportBadge.jsx'; import { fmtName } from '../utils/formatName.js'; +const SOCIAL_ICONS = { facebook: '๐Ÿ“˜', instagram: '๐Ÿ“ท', bluesky: '๐Ÿฆ‹', snapchat: '๐Ÿ‘ป' }; + +// Pick the most display-worthy highlight stats for the top strip. +// Skips 0-value stats so irrelevant positional stats don't show dashes. +function getHighlights(statDefs, stats, max = 6) { + return statDefs + .filter(d => d.type !== 'text' && stats[d.key] && Number(stats[d.key]) !== 0) + .slice(0, max); +} + export default function AthleteDetail() { const { id } = useParams(); const { getUserById, deleteUser, auth } = useStore(); const navigate = useNavigate(); const isLoggedIn = auth?.isLoggedIn; + const [activeSport, setActiveSport] = useState(null); + const [activeTab, setActiveTab] = useState('stats'); + const user = getUserById(id); if (!user) return (
-
๐ŸŸ๏ธ
-
Athlete not found
- Back to Athletes +
๐ŸŸ๏ธ
+

Athlete not found

+ โ† Back to Athletes
); const displaySport = activeSport || user.primarySport || user.sports?.[0]; + const sportMeta = SPORTS[displaySport]; + const sportColor = sportMeta?.color || 'var(--accent)'; const stats = user.sportStats?.[displaySport] || {}; const statDefs = SPORT_STATS_DEFS[displaySport] || []; + const numericDefs = statDefs.filter(d => d.type !== 'text'); + const position = stats.position; + const highlights = getHighlights(statDefs, stats); + + const tabs = ['stats', 'biometrics', ...(isLoggedIn ? ['contact'] : [])]; + const tabLabels = { stats: 'Stats', biometrics: 'Biometrics', contact: 'Contact' }; function handleDelete() { if (confirm(`Delete ${user.name}?`)) { deleteUser(id); navigate('/athletes'); } } - const socialIcons = { facebook: '๐Ÿ“˜', instagram: '๐Ÿ“ท', bluesky: '๐Ÿฆ‹', snapchat: '๐Ÿ‘ป' }; + function fmtStat(val, type) { + if (val === undefined || val === null || val === 0 || val === '') return 'โ€”'; + if (type === 'decimal') return Number(val).toFixed(2); + return val; + } return (
- {/* Header card */} -
-
- -
-

- {isLoggedIn ? user.name : fmtName(user)} -

-
{user.city}, {user.country} ยท Joined {user.joinDate}
-
- {user.sports?.map(s => )} + + {/* โ”€โ”€ Hero โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} +
+ {/* Color accent bar across the top */} +
+ +
+
+ + {/* Avatar */} +
+ +
+ + {/* Player info */} +
+ {/* Position chip */} + {position && ( +
+ {position} +
+ )} + + {/* Name */} +

+ {isLoggedIn ? user.name : fmtName(user)} +

+ + {/* Sport badges */} +
+ {user.sports?.map(s => )} +
+ + {/* Location + join date */} +
+ {[user.city, user.country].filter(Boolean).join(', ')} + {user.joinDate && ยท Member since {user.joinDate}} +
+ + {/* Bio */} + {user.bio && ( +

+ "{user.bio}" +

+ )} +
+ + {/* Action buttons */} +
+ Edit +
- {user.bio &&

"{user.bio}"

} -
-
- Edit -
- - {/* Social media โ€” logged-in only */} - {isLoggedIn && user.socials && Object.values(user.socials).some(Boolean) && ( -
- {Object.entries(user.socials).filter(([, handle]) => handle).map(([platform, handle]) => ( -
- {socialIcons[platform] || '๐Ÿ”—'} - {handle} -
- ))} -
- )}
- {/* Contact โ€” logged-in only */} - {isLoggedIn && ( -
-
Contact
-
-
Email
{user.email}
-
Phone
{user.phone}
+ {/* โ”€โ”€ Key stats highlight strip โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} + {highlights.length > 0 && ( +
+ {highlights.map(def => ( +
+
+ {def.type === 'decimal' ? Number(stats[def.key]).toFixed(2) : stats[def.key]} +
+
+ {def.label} +
+
+ ))} +
+ )} + + {/* โ”€โ”€ Sport selector (multi-sport athletes) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} + {user.sports?.length > 1 && ( +
+ {user.sports.map(s => ( + + ))} +
+ )} + + {/* โ”€โ”€ Content tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} +
+ {tabs.map(t => ( + + ))} +
+ + {/* โ”€โ”€ Stats tab โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} + {activeTab === 'stats' && ( +
+ {/* Table header row */} +
+ + {sportMeta?.emoji} {sportMeta?.name} โ€” Season Stats + + {position && ( + + {position} + + )} +
+ + {numericDefs.length > 0 ? ( +
+ + + + {numericDefs.map(def => ( + + ))} + + + + + {numericDefs.map(def => ( + + ))} + + +
h.key === def.key) ? sportColor : undefined, + }}> + {def.label} +
h.key === def.key) ? 800 : 500, + fontSize: highlights.some(h => h.key === def.key) ? 16 : 14, + color: highlights.some(h => h.key === def.key) ? sportColor : 'var(--text-primary)', + }}> + {fmtStat(stats[def.key], def.type)} +
+
+ ) : ( +
+ No stats recorded yet +
+ )} +
+ )} + + {/* โ”€โ”€ Biometrics tab โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} + {activeTab === 'biometrics' && ( +
+
+ Physical Measurements +
+
+ {BIOMETRIC_FIELDS.map(f => { + const val = user.biometrics?.[f.key]; + if (!val && val !== 0) return null; + return ( +
+
{f.label}
+
+ {f.type === 'decimal' ? Number(val).toFixed(1) : val} +
+
+ ); + })}
)} - {/* Biometrics */} -
-
Biometrics
-
- {BIOMETRIC_FIELDS.map(f => { - const val = user.biometrics?.[f.key]; - if (!val && val !== 0) return null; - return ( -
-
{f.label}
-
- {f.type === 'decimal' ? Number(val).toFixed(1) : val} -
+ {/* โ”€โ”€ Contact tab (logged-in only) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */} + {activeTab === 'contact' && isLoggedIn && ( +
+
+
+ Contact Details +
+
+
+ Email +
{user.email || 'โ€”'}
- ); - })} -
-
- - {/* Sport stats */} -
-
Sport Statistics
- - {user.sports?.length > 1 && ( -
- {user.sports.map(s => ( - - ))} +
+ Phone +
{user.phone || 'โ€”'}
+
+
- )} -
- {statDefs.map(def => { - const val = stats[def.key]; - if (val === undefined || val === null) return null; - const display = def.type === 'decimal' ? Number(val).toFixed(2) : String(val); - if (display === '0' && def.type !== 'text') return null; - return ( -
-
{def.label}
-
{display}
+ {user.socials && Object.values(user.socials).some(Boolean) && ( +
+
+ Social Media
- ); - })} +
+ {Object.entries(user.socials).filter(([, handle]) => handle).map(([platform, handle]) => ( +
+ {SOCIAL_ICONS[platform] || '๐Ÿ”—'} +
+
{platform}
+
{handle}
+
+
+ ))} +
+
+ )}
+ )} + +
+ โ† Back to Athletes
- โ† Back to Athletes
); }