From 0365e200509b7b5d8ec91525a909eb66b8a6443c Mon Sep 17 00:00:00 2001 From: Studio-18 Date: Sun, 25 Jan 2026 21:55:21 -0800 Subject: [PATCH 1/6] Update Find Players UI components --- src/components/players/BestMatchCTA.tsx | 107 +++++ src/components/players/BestMatchCard.tsx | 295 ++++++++++++ src/components/players/BestMatchesPanel.tsx | 161 +++++++ src/components/players/MyProfileQuickView.tsx | 449 ++++++++++++++++++ src/components/players/PlayersFilterBar.tsx | 48 ++ src/pages/FindPlayersPage.tsx | 178 ++++++- 6 files changed, 1235 insertions(+), 3 deletions(-) create mode 100644 src/components/players/BestMatchCTA.tsx create mode 100644 src/components/players/BestMatchCard.tsx create mode 100644 src/components/players/BestMatchesPanel.tsx create mode 100644 src/components/players/MyProfileQuickView.tsx diff --git a/src/components/players/BestMatchCTA.tsx b/src/components/players/BestMatchCTA.tsx new file mode 100644 index 00000000..6620cf22 --- /dev/null +++ b/src/components/players/BestMatchCTA.tsx @@ -0,0 +1,107 @@ +import React from "react"; + +interface BestMatchCTAProps { + onClick: () => void; + isMobile: boolean; +} + +const BestMatchCTA: React.FC = ({ onClick, isMobile }) => { + return ( +
+
+
+ + + +
+ +
+

+ Find your perfect tennis partner +

+

+ We'll match you based on skill, availability & location +

+
+
+ + +
+ ); +}; + +export default BestMatchCTA; diff --git a/src/components/players/BestMatchCard.tsx b/src/components/players/BestMatchCard.tsx new file mode 100644 index 00000000..e4ab6ed4 --- /dev/null +++ b/src/components/players/BestMatchCard.tsx @@ -0,0 +1,295 @@ +import React from "react"; + +interface Player { + id?: string; + name: string; + photo?: string; + avatarUrl?: string; + ntrp: string; + isVerified?: boolean; + court?: string; + courts?: string[]; + matchScore: number; + matchReasons: string[]; +} + +interface BestMatchCardProps { + player: Player; + onConnect: () => void; + onViewProfile: () => void; + isMobile: boolean; +} + +const BestMatchCard: React.FC = ({ + player, + onConnect, + onViewProfile, + isMobile, +}) => { + return ( +
+
+ {player.matchScore}% + match +
+ + {isMobile ? ( + <> +
+ {player.name} +
+
+ {player.name} + + NTRP {player.ntrp} + + {player.isVerified && ( + + + + + )} +
+
+ + + + + {player.court || player.courts?.[0]} +
+
+
+ +
+ {player.matchReasons?.map((reason, ridx) => ( + + + + + {reason} + + ))} +
+ +
+ + +
+ + ) : ( + <> + {player.name} + +
+
+ {player.name} + + NTRP {player.ntrp} + + {player.isVerified && ( + + + + + )} +
+ +
+ {player.matchReasons?.map((reason, ridx) => ( + + + + + {reason} + + ))} +
+ +
+ + + + + {player.court || player.courts?.[0]} +
+
+ +
+ + +
+ + )} +
+ ); +}; + +export default BestMatchCard; diff --git a/src/components/players/BestMatchesPanel.tsx b/src/components/players/BestMatchesPanel.tsx new file mode 100644 index 00000000..d3b70352 --- /dev/null +++ b/src/components/players/BestMatchesPanel.tsx @@ -0,0 +1,161 @@ +import React from "react"; +import BestMatchCard from "./BestMatchCard"; + +interface Player { + id?: string; + name: string; + photo?: string; + avatarUrl?: string; + ntrp: string; + isVerified?: boolean; + court?: string; + courts?: string[]; + matchScore: number; + matchReasons: string[]; +} + +interface BestMatchesPanelProps { + matches: Player[]; + onClose: () => void; + onConnect: (player: Player) => void; + onViewProfile: (player: Player) => void; + isMobile: boolean; +} + +const BestMatchesPanel: React.FC = ({ + matches, + onClose, + onConnect, + onViewProfile, + isMobile, +}) => { + return ( +
+
+
+
+ + + +
+
+

+ Your Best Matches +

+

+ {isMobile ? "Matched by skill, availability & location" : "Players matched by skill level, availability, location & play style"} +

+
+
+ + +
+ +
+ {matches.map((player, idx) => ( + onConnect(player)} + onViewProfile={() => onViewProfile(player)} + isMobile={isMobile} + /> + ))} +
+ +
+ Want better matches? + +
+
+ ); +}; + +export default BestMatchesPanel; diff --git a/src/components/players/MyProfileQuickView.tsx b/src/components/players/MyProfileQuickView.tsx new file mode 100644 index 00000000..5ea7a58e --- /dev/null +++ b/src/components/players/MyProfileQuickView.tsx @@ -0,0 +1,449 @@ +import React from "react"; + +interface MyProfileQuickViewProps { + user: { + name: string; + photo?: string; + avatarUrl?: string; + ntrp: string; + isVerified?: boolean; + verificationCount?: number; + tagline?: string; + bio?: string; + availability?: string[]; + courts?: string[]; + }; + onEdit: () => void; + onRequestVerification: () => void; + isMobile: boolean; +} + +const MyProfileQuickView: React.FC = ({ + user, + onEdit, + onRequestVerification, + isMobile, +}) => { + const isVerified = user?.isVerified ?? false; + const verificationCount = user?.verificationCount ?? 2; + const verificationsNeeded = 3; + + return ( +
+
+ {user.name} + +
+
+

+ {user.name} +

+ +
+ + NTRP {user.ntrp} + + + {isVerified && ( + + + + + Verified rating + + )} +
+
+ +

+ {user.tagline || user.bio} +

+ + {!isVerified && ( +
+
+
+ + + +
+ +
+ + Get your rating verified + + + {verificationCount} of {verificationsNeeded} player confirmations + +
+ + {!isMobile && ( +
+
+
+
+
+ )} +
+ + +
+ )} + +
+
+ + AVAILABILITY + +
+ {user.availability?.map((slot, idx) => ( + + {slot} + + ))} +
+
+ +
+ + LOCAL COURTS + +
+ {user.courts?.map((court, idx) => ( +
+ + + + + {court} +
+ ))} +
+
+
+
+
+ +
+ +
+
+ ); +}; + +export default MyProfileQuickView; diff --git a/src/components/players/PlayersFilterBar.tsx b/src/components/players/PlayersFilterBar.tsx index 9ad7c18c..da773fc6 100644 --- a/src/components/players/PlayersFilterBar.tsx +++ b/src/components/players/PlayersFilterBar.tsx @@ -20,6 +20,12 @@ type PlayersFilterBarProps = { genderOptions: string[]; selectedGender: string; onGenderChange: (value: string) => void; + playTypeOptions?: string[]; + selectedPlayType?: string; + onPlayTypeChange?: (value: string) => void; + availabilityOptions?: string[]; + selectedAvailability?: string; + onAvailabilityChange?: (value: string) => void; verifiedOnly: boolean; onVerifiedOnlyChange: (value: boolean) => void; }; @@ -40,6 +46,12 @@ const PlayersFilterBar = ({ genderOptions, selectedGender, onGenderChange, + playTypeOptions, + selectedPlayType, + onPlayTypeChange, + availabilityOptions, + selectedAvailability, + onAvailabilityChange, verifiedOnly, onVerifiedOnlyChange, }: PlayersFilterBarProps) => { @@ -105,6 +117,24 @@ const PlayersFilterBar = ({
+ {playTypeOptions && selectedPlayType && onPlayTypeChange ? ( +
+ +
+ ) : null} +
onAvailabilityChange(event.target.value)} + > + {availabilityOptions.map((option) => ( + + ))} + +
+ ) : null} +