From 36cc748d847b971d4be1116ccb9d27c232b75ea0 Mon Sep 17 00:00:00 2001 From: Jamie Brunton Date: Sun, 4 Jan 2026 20:26:54 +0000 Subject: [PATCH] Move match card from header to message list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- apps/mobile/src/features/chat/ChatScreen.tsx | 1 + .../features/chat/components/ChatHeader.tsx | 382 +++--------------- .../features/chat/components/ChatMessages.tsx | 8 + .../features/chat/components/MatchCard.tsx | 245 +++++++++++ .../src/features/chat/components/index.ts | 1 + 5 files changed, 317 insertions(+), 320 deletions(-) create mode 100644 apps/mobile/src/features/chat/components/MatchCard.tsx diff --git a/apps/mobile/src/features/chat/ChatScreen.tsx b/apps/mobile/src/features/chat/ChatScreen.tsx index 3222c9d..2e8aeb1 100644 --- a/apps/mobile/src/features/chat/ChatScreen.tsx +++ b/apps/mobile/src/features/chat/ChatScreen.tsx @@ -288,6 +288,7 @@ export const ChatScreen: React.FC = () => { void; } export const ChatHeader: React.FC = ({ partner, - user, - match, insets, onBack, }) => { - const isYesYes = match?.match_type === 'yes_yes'; - - // Modern reanimated keyboard hook - const keyboard = useAnimatedKeyboard(); - - const containerAnimatedStyle = useAnimatedStyle(() => { - // Collapse when keyboard height exceeds 50px - const animationValue = interpolate( - keyboard.height.value, - [0, 60], - [0, 1], - Extrapolation.CLAMP - ); - - return { - opacity: interpolate(animationValue, [0, 1], [1, 0]), - maxHeight: interpolate(animationValue, [0, 1], [500, 0]), // Assuming card max height - marginTop: interpolate(animationValue, [0, 1], [spacing.sm, 0]), - transform: [ - { scale: interpolate(animationValue, [0, 1], [1, 0.95]) } - ], - overflow: 'hidden', - }; - }); - return ( - <> - + {/* Gradient background */} + + {/* Top silk highlight */} + + + - {/* Gradient background */} - - {/* Top silk highlight */} - - - - - - - - - - {partner?.avatar_url ? ( - - - {/* Avatar ring */} - - - ) : ( - - - {partner?.name?.[0]?.toUpperCase() || "P"} - - - )} - - CONVERSATION WITH - {partner?.name || "Partner"} - + + + - - - {/* Bottom border */} - - - - {/* Premium Match Card */} - {match?.question && ( - - - {/* Card gradient background */} - + {partner?.avatar_url ? ( + + - - {/* Top glow for YES+YES */} - {isYesYes && ( - - )} - - {/* Match Label */} - - {isYesYes ? "PERFECT MATCH" : "SOFT MATCH"} - - - {/* Decorative Separator */} - - - {/* Question and Responses - Show who answered what */} - {match.responses && match.responses.length > 0 ? ( - - {(() => { - // Sort responses by created_at to determine who answered first - const sortedResponses = [...match.responses].sort( - (a: any, b: any) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() - ); - const isTwoPart = !!match.question.partner_text; - - return sortedResponses.map((response: any, index: number) => { - const isUser = response.user_id === user?.id; - const isYes = response.answer === 'yes'; - const name = isUser ? 'You' : (response.profiles?.name?.split(' ')[0] || 'Partner'); - - // First responder sees main text, second sees partner_text - const questionText = isTwoPart && index === 1 - ? match.question.partner_text - : match.question.text; - - return ( - - {/* Name and Answer Badge */} - - {name} - {isYes ? ( - - - YES - - ) : ( - - - MAYBE - - )} - - {/* Question they saw */} - - "{questionText}" - - - ); - }); - })()} - - ) : ( - - "{match.question.text}" - - )} - - {/* Premium border */} - + {/* Avatar ring */} + - - )} - + ) : ( + + + {partner?.name?.[0]?.toUpperCase() || "P"} + + + )} + + CONVERSATION WITH + {partner?.name || "Partner"} + + + + + + {/* Bottom border */} + + ); }; @@ -333,116 +187,4 @@ const styles = StyleSheet.create({ height: 1, backgroundColor: `${ACCENT_RGBA}0.15)`, }, - matchCardContainer: { - marginHorizontal: spacing.md, - // Vertical margins are handled by animation - borderRadius: radius.lg, - overflow: 'hidden', - }, - matchCard: { - // Margins moved to container - borderRadius: radius.lg, - paddingVertical: spacing.md, - paddingHorizontal: spacing.md, - alignItems: 'center', - overflow: 'hidden', - }, - matchCardGlow: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - height: 60, - }, - matchLabel: { - ...typography.caption2, - fontWeight: '700', - letterSpacing: 2, - color: colors.premium.rose, - marginBottom: spacing.xs, - }, - matchLabelYesYes: { - color: ACCENT, - }, - matchQuestionText: { - ...typography.subhead, - color: colors.text, - textAlign: 'center', - fontStyle: 'italic', - lineHeight: 20, - }, - responsesContainer: { - width: '100%', - gap: spacing.sm, - }, - responseCard: { - backgroundColor: 'rgba(255, 255, 255, 0.03)', - borderRadius: radius.md, - padding: spacing.sm, - borderWidth: 1, - borderColor: 'rgba(255, 255, 255, 0.06)', - }, - responseHeader: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: spacing.xs, - }, - responseName: { - ...typography.caption1, - fontWeight: '600', - color: colors.textSecondary, - textTransform: 'uppercase', - letterSpacing: 1, - }, - answerBadge: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: spacing.sm, - paddingVertical: 2, - gap: 3, - borderRadius: radius.full, - }, - answerBadgeText: { - ...typography.caption2, - fontWeight: '700', - color: colors.text, - letterSpacing: 0.5, - }, - answerBadgeMaybe: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: spacing.sm, - paddingVertical: 2, - gap: 3, - backgroundColor: 'rgba(243, 156, 18, 0.15)', - borderWidth: 1, - borderColor: 'rgba(243, 156, 18, 0.3)', - borderRadius: radius.full, - }, - answerBadgeTextMaybe: { - ...typography.caption2, - fontWeight: '700', - color: colors.warning, - letterSpacing: 0.5, - }, - responseQuestionText: { - ...typography.footnote, - color: colors.text, - fontStyle: 'italic', - lineHeight: 18, - }, - matchCardBorder: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - borderRadius: radius.lg, - borderWidth: 1, - borderColor: `rgba(232, 164, 174, 0.2)`, - }, - matchCardBorderYesYes: { - borderColor: `${ACCENT_RGBA}0.25)`, - }, }); diff --git a/apps/mobile/src/features/chat/components/ChatMessages.tsx b/apps/mobile/src/features/chat/components/ChatMessages.tsx index 7c1baf3..51aeb3b 100644 --- a/apps/mobile/src/features/chat/components/ChatMessages.tsx +++ b/apps/mobile/src/features/chat/components/ChatMessages.tsx @@ -8,6 +8,7 @@ import { MessageContent } from './MessageContent'; import { DateSeparator } from './DateSeparator'; import { UploadProgress } from './UploadProgress'; import { TypingIndicator } from './TypingIndicator'; +import { MatchCard } from './MatchCard'; import { DecorativeSeparator } from '../../../components/ui'; import { colors, spacing, typography } from '../../../theme'; import { Database } from '../../../types/supabase'; @@ -18,6 +19,7 @@ type Message = Database['public']['Tables']['messages']['Row']; interface ChatMessagesProps { messages: Message[]; userId: string | undefined; + match: any; uploadStatus: UploadStatus | null; partnerTyping: boolean; onImagePress: (uri: string) => void; @@ -44,6 +46,7 @@ function isSameDay(date1: Date, date2: Date): boolean { export const ChatMessages: React.FC = ({ messages, userId, + match, uploadStatus, partnerTyping, onImagePress, @@ -107,6 +110,11 @@ export const ChatMessages: React.FC = ({ {partnerTyping && } } + ListFooterComponent={ + + + + } ListEmptyComponent={ = ({ match, user }) => { + if (!match?.question) return null; + + const isYesYes = match?.match_type === 'yes_yes'; + + return ( + + + {/* Card gradient background */} + + + {/* Top glow for YES+YES */} + {isYesYes && ( + + )} + + {/* Match Label */} + + {isYesYes ? "PERFECT MATCH" : "SOFT MATCH"} + + + {/* Decorative Separator */} + + + {/* Question and Responses - Show who answered what */} + {match.responses && match.responses.length > 0 ? ( + + {(() => { + // Sort responses by created_at to determine who answered first + const sortedResponses = [...match.responses].sort( + (a: any, b: any) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() + ); + const isTwoPart = !!match.question.partner_text; + + return sortedResponses.map((response: any, index: number) => { + const isUser = response.user_id === user?.id; + const isYes = response.answer === 'yes'; + const name = isUser ? 'You' : (response.profiles?.name?.split(' ')[0] || 'Partner'); + + // First responder sees main text, second sees partner_text + const questionText = isTwoPart && index === 1 + ? match.question.partner_text + : match.question.text; + + return ( + + {/* Name and Answer Badge */} + + {name} + {isYes ? ( + + + YES + + ) : ( + + + MAYBE + + )} + + {/* Question they saw */} + + "{questionText}" + + + ); + }); + })()} + + ) : ( + + "{match.question.text}" + + )} + + {/* Premium border */} + + + + ); +}; + +const styles = StyleSheet.create({ + matchCardContainer: { + marginHorizontal: spacing.md, + marginTop: spacing.md, + borderRadius: radius.lg, + overflow: 'hidden', + }, + matchCard: { + borderRadius: radius.lg, + paddingVertical: spacing.md, + paddingHorizontal: spacing.md, + alignItems: 'center', + overflow: 'hidden', + }, + matchCardGlow: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + height: 60, + }, + matchLabel: { + ...typography.caption2, + fontWeight: '700', + letterSpacing: 2, + color: colors.premium.rose, + marginBottom: spacing.xs, + }, + matchLabelYesYes: { + color: ACCENT, + }, + matchQuestionText: { + ...typography.subhead, + color: colors.text, + textAlign: 'center', + fontStyle: 'italic', + lineHeight: 20, + }, + responsesContainer: { + width: '100%', + gap: spacing.sm, + }, + responseCard: { + backgroundColor: 'rgba(255, 255, 255, 0.03)', + borderRadius: radius.md, + padding: spacing.sm, + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.06)', + }, + responseHeader: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: spacing.xs, + }, + responseName: { + ...typography.caption1, + fontWeight: '600', + color: colors.textSecondary, + textTransform: 'uppercase', + letterSpacing: 1, + }, + answerBadge: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: spacing.sm, + paddingVertical: 2, + gap: 3, + borderRadius: radius.full, + }, + answerBadgeText: { + ...typography.caption2, + fontWeight: '700', + color: colors.text, + letterSpacing: 0.5, + }, + answerBadgeMaybe: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: spacing.sm, + paddingVertical: 2, + gap: 3, + backgroundColor: 'rgba(243, 156, 18, 0.15)', + borderWidth: 1, + borderColor: 'rgba(243, 156, 18, 0.3)', + borderRadius: radius.full, + }, + answerBadgeTextMaybe: { + ...typography.caption2, + fontWeight: '700', + color: colors.warning, + letterSpacing: 0.5, + }, + responseQuestionText: { + ...typography.footnote, + color: colors.text, + fontStyle: 'italic', + lineHeight: 18, + }, + matchCardBorder: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + borderRadius: radius.lg, + borderWidth: 1, + borderColor: `rgba(232, 164, 174, 0.2)`, + }, + matchCardBorderYesYes: { + borderColor: `${ACCENT_RGBA}0.25)`, + }, +}); diff --git a/apps/mobile/src/features/chat/components/index.ts b/apps/mobile/src/features/chat/components/index.ts index 389b992..bdaed5d 100644 --- a/apps/mobile/src/features/chat/components/index.ts +++ b/apps/mobile/src/features/chat/components/index.ts @@ -14,3 +14,4 @@ export { MessageBubble } from './MessageBubble'; export type { MessageBubbleProps } from './MessageBubble'; export { ChatHeader } from './ChatHeader'; export { ChatMessages } from './ChatMessages'; +export { MatchCard } from './MatchCard';