mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Move match card from header to message list
🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
1 parent
474bbb6829
commit
36cc748d84
5 files changed
+317
-320
No files matched your search
@@ -288,6 +288,7 @@ export const ChatScreen: React.FC = () => {
|
|||||||
<ChatMessages
|
<ChatMessages
|
||||||
messages={messages}
|
messages={messages}
|
||||||
userId={user?.id}
|
userId={user?.id}
|
||||||
|
match={match}
|
||||||
uploadStatus={uploadStatus}
|
uploadStatus={uploadStatus}
|
||||||
partnerTyping={partnerTyping}
|
partnerTyping={partnerTyping}
|
||||||
onImagePress={setFullScreenImage}
|
onImagePress={setFullScreenImage}
|
||||||
|
|||||||
@@ -3,18 +3,10 @@ import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
|||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import Animated, {
|
import Animated, { FadeIn } from 'react-native-reanimated';
|
||||||
FadeIn,
|
|
||||||
FadeInDown,
|
|
||||||
useAnimatedKeyboard,
|
|
||||||
useAnimatedStyle,
|
|
||||||
interpolate,
|
|
||||||
Extrapolation
|
|
||||||
} from 'react-native-reanimated';
|
|
||||||
import { EdgeInsets } from 'react-native-safe-area-context';
|
import { EdgeInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
import { DecorativeSeparator } from '../../../components/ui';
|
import { colors, gradients, spacing, typography } from '../../../theme';
|
||||||
import { colors, gradients, spacing, radius, typography } from '../../../theme';
|
|
||||||
import { Profile } from '../../../types';
|
import { Profile } from '../../../types';
|
||||||
|
|
||||||
// Premium color palette for Chat
|
// Premium color palette for Chat
|
||||||
@@ -24,219 +16,81 @@ const ACCENT_RGBA = 'rgba(212, 175, 55, ';
|
|||||||
interface ChatHeaderProps {
|
interface ChatHeaderProps {
|
||||||
partner: Profile | null;
|
partner: Profile | null;
|
||||||
user: { id: string } | null;
|
user: { id: string } | null;
|
||||||
match: any; // Using any for match as it has joined properties (question, responses)
|
match: any; // Kept for compatibility but unused
|
||||||
insets: EdgeInsets;
|
insets: EdgeInsets;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatHeader: React.FC<ChatHeaderProps> = ({
|
export const ChatHeader: React.FC<ChatHeaderProps> = ({
|
||||||
partner,
|
partner,
|
||||||
user,
|
|
||||||
match,
|
|
||||||
insets,
|
insets,
|
||||||
onBack,
|
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 (
|
return (
|
||||||
<>
|
<Animated.View
|
||||||
<Animated.View
|
entering={FadeIn.duration(300)}
|
||||||
entering={FadeIn.duration(300)}
|
style={[styles.header, { paddingTop: insets.top + spacing.sm }]}
|
||||||
style={[styles.header, { paddingTop: insets.top + spacing.sm }]}
|
>
|
||||||
|
{/* Gradient background */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={['rgba(22, 33, 62, 0.8)', 'rgba(13, 13, 26, 0.6)']}
|
||||||
|
style={StyleSheet.absoluteFill}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 0, y: 1 }}
|
||||||
|
/>
|
||||||
|
{/* Top silk highlight */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={[`${ACCENT_RGBA}0.1)`, 'transparent']}
|
||||||
|
style={styles.headerSilkHighlight}
|
||||||
|
start={{ x: 0.5, y: 0 }}
|
||||||
|
end={{ x: 0.5, y: 1 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onBack}
|
||||||
|
style={styles.backButton}
|
||||||
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
{/* Gradient background */}
|
<View style={styles.backButtonInner}>
|
||||||
<LinearGradient
|
<Ionicons name="chevron-back" size={20} color={ACCENT} />
|
||||||
colors={['rgba(22, 33, 62, 0.8)', 'rgba(13, 13, 26, 0.6)']}
|
|
||||||
style={StyleSheet.absoluteFill}
|
|
||||||
start={{ x: 0, y: 0 }}
|
|
||||||
end={{ x: 0, y: 1 }}
|
|
||||||
/>
|
|
||||||
{/* Top silk highlight */}
|
|
||||||
<LinearGradient
|
|
||||||
colors={[`${ACCENT_RGBA}0.1)`, 'transparent']}
|
|
||||||
style={styles.headerSilkHighlight}
|
|
||||||
start={{ x: 0.5, y: 0 }}
|
|
||||||
end={{ x: 0.5, y: 1 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={onBack}
|
|
||||||
style={styles.backButton}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
>
|
|
||||||
<View style={styles.backButtonInner}>
|
|
||||||
<Ionicons name="chevron-back" size={20} color={ACCENT} />
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<View style={styles.headerCenter}>
|
|
||||||
{partner?.avatar_url ? (
|
|
||||||
<View style={styles.headerAvatarContainer}>
|
|
||||||
<Image
|
|
||||||
source={{ uri: partner.avatar_url }}
|
|
||||||
style={styles.headerAvatar}
|
|
||||||
cachePolicy="disk"
|
|
||||||
transition={200}
|
|
||||||
/>
|
|
||||||
{/* Avatar ring */}
|
|
||||||
<View style={styles.avatarRing} />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<LinearGradient
|
|
||||||
colors={gradients.primary as [string, string]}
|
|
||||||
style={styles.headerAvatarGradient}
|
|
||||||
start={{ x: 0, y: 0 }}
|
|
||||||
end={{ x: 1, y: 1 }}
|
|
||||||
>
|
|
||||||
<Text style={styles.headerAvatarText}>
|
|
||||||
{partner?.name?.[0]?.toUpperCase() || "P"}
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
)}
|
|
||||||
<View style={styles.headerTextContainer}>
|
|
||||||
<Text style={styles.headerLabel}>CONVERSATION WITH</Text>
|
|
||||||
<Text style={styles.headerTitle}>{partner?.name || "Partner"}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
<View style={styles.headerSpacer} />
|
<View style={styles.headerCenter}>
|
||||||
|
{partner?.avatar_url ? (
|
||||||
{/* Bottom border */}
|
<View style={styles.headerAvatarContainer}>
|
||||||
<View style={styles.headerBorderBottom} />
|
<Image
|
||||||
</Animated.View>
|
source={{ uri: partner.avatar_url }}
|
||||||
|
style={styles.headerAvatar}
|
||||||
{/* Premium Match Card */}
|
cachePolicy="disk"
|
||||||
{match?.question && (
|
transition={200}
|
||||||
<Animated.View
|
|
||||||
entering={FadeInDown.delay(100).duration(400).springify()}
|
|
||||||
style={[styles.matchCardContainer, containerAnimatedStyle]}
|
|
||||||
>
|
|
||||||
<View style={styles.matchCard}>
|
|
||||||
{/* Card gradient background */}
|
|
||||||
<LinearGradient
|
|
||||||
colors={isYesYes
|
|
||||||
? ['rgba(212, 175, 55, 0.12)', 'rgba(184, 134, 11, 0.08)']
|
|
||||||
: ['rgba(232, 164, 174, 0.1)', 'rgba(22, 33, 62, 0.6)']
|
|
||||||
}
|
|
||||||
style={StyleSheet.absoluteFill}
|
|
||||||
start={{ x: 0, y: 0 }}
|
|
||||||
end={{ x: 1, y: 1 }}
|
|
||||||
/>
|
/>
|
||||||
|
{/* Avatar ring */}
|
||||||
{/* Top glow for YES+YES */}
|
<View style={styles.avatarRing} />
|
||||||
{isYesYes && (
|
|
||||||
<LinearGradient
|
|
||||||
colors={[`${ACCENT_RGBA}0.15)`, 'transparent']}
|
|
||||||
style={styles.matchCardGlow}
|
|
||||||
start={{ x: 0.5, y: 0 }}
|
|
||||||
end={{ x: 0.5, y: 1 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Match Label */}
|
|
||||||
<Text style={[styles.matchLabel, isYesYes && styles.matchLabelYesYes]}>
|
|
||||||
{isYesYes ? "PERFECT MATCH" : "SOFT MATCH"}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{/* Decorative Separator */}
|
|
||||||
<DecorativeSeparator
|
|
||||||
variant={isYesYes ? "gold" : "rose"}
|
|
||||||
width="60%"
|
|
||||||
marginVertical={spacing.md}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Question and Responses - Show who answered what */}
|
|
||||||
{match.responses && match.responses.length > 0 ? (
|
|
||||||
<View style={styles.responsesContainer}>
|
|
||||||
{(() => {
|
|
||||||
// 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 (
|
|
||||||
<View key={response.user_id} style={styles.responseCard}>
|
|
||||||
{/* Name and Answer Badge */}
|
|
||||||
<View style={styles.responseHeader}>
|
|
||||||
<Text style={styles.responseName}>{name}</Text>
|
|
||||||
{isYes ? (
|
|
||||||
<LinearGradient
|
|
||||||
colors={isYesYes
|
|
||||||
? gradients.premiumGold as [string, string]
|
|
||||||
: [colors.success, '#27ae60']
|
|
||||||
}
|
|
||||||
style={styles.answerBadge}
|
|
||||||
start={{ x: 0, y: 0 }}
|
|
||||||
end={{ x: 1, y: 1 }}
|
|
||||||
>
|
|
||||||
<Ionicons name="checkmark" size={10} color={colors.text} />
|
|
||||||
<Text style={styles.answerBadgeText}>YES</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
) : (
|
|
||||||
<View style={styles.answerBadgeMaybe}>
|
|
||||||
<Ionicons name="help" size={10} color={colors.warning} />
|
|
||||||
<Text style={styles.answerBadgeTextMaybe}>MAYBE</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
{/* Question they saw */}
|
|
||||||
<Text style={styles.responseQuestionText}>
|
|
||||||
"{questionText}"
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})()}
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<Text style={styles.matchQuestionText}>
|
|
||||||
"{match.question.text}"
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Premium border */}
|
|
||||||
<View style={[
|
|
||||||
styles.matchCardBorder,
|
|
||||||
isYesYes && styles.matchCardBorderYesYes
|
|
||||||
]} pointerEvents="none" />
|
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
) : (
|
||||||
)}
|
<LinearGradient
|
||||||
</>
|
colors={gradients.primary as [string, string]}
|
||||||
|
style={styles.headerAvatarGradient}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<Text style={styles.headerAvatarText}>
|
||||||
|
{partner?.name?.[0]?.toUpperCase() || "P"}
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
)}
|
||||||
|
<View style={styles.headerTextContainer}>
|
||||||
|
<Text style={styles.headerLabel}>CONVERSATION WITH</Text>
|
||||||
|
<Text style={styles.headerTitle}>{partner?.name || "Partner"}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.headerSpacer} />
|
||||||
|
|
||||||
|
{/* Bottom border */}
|
||||||
|
<View style={styles.headerBorderBottom} />
|
||||||
|
</Animated.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -333,116 +187,4 @@ const styles = StyleSheet.create({
|
|||||||
height: 1,
|
height: 1,
|
||||||
backgroundColor: `${ACCENT_RGBA}0.15)`,
|
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)`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
@@ -8,6 +8,7 @@ import { MessageContent } from './MessageContent';
|
|||||||
import { DateSeparator } from './DateSeparator';
|
import { DateSeparator } from './DateSeparator';
|
||||||
import { UploadProgress } from './UploadProgress';
|
import { UploadProgress } from './UploadProgress';
|
||||||
import { TypingIndicator } from './TypingIndicator';
|
import { TypingIndicator } from './TypingIndicator';
|
||||||
|
import { MatchCard } from './MatchCard';
|
||||||
import { DecorativeSeparator } from '../../../components/ui';
|
import { DecorativeSeparator } from '../../../components/ui';
|
||||||
import { colors, spacing, typography } from '../../../theme';
|
import { colors, spacing, typography } from '../../../theme';
|
||||||
import { Database } from '../../../types/supabase';
|
import { Database } from '../../../types/supabase';
|
||||||
@@ -18,6 +19,7 @@ type Message = Database['public']['Tables']['messages']['Row'];
|
|||||||
interface ChatMessagesProps {
|
interface ChatMessagesProps {
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
userId: string | undefined;
|
userId: string | undefined;
|
||||||
|
match: any;
|
||||||
uploadStatus: UploadStatus | null;
|
uploadStatus: UploadStatus | null;
|
||||||
partnerTyping: boolean;
|
partnerTyping: boolean;
|
||||||
onImagePress: (uri: string) => void;
|
onImagePress: (uri: string) => void;
|
||||||
@@ -44,6 +46,7 @@ function isSameDay(date1: Date, date2: Date): boolean {
|
|||||||
export const ChatMessages: React.FC<ChatMessagesProps> = ({
|
export const ChatMessages: React.FC<ChatMessagesProps> = ({
|
||||||
messages,
|
messages,
|
||||||
userId,
|
userId,
|
||||||
|
match,
|
||||||
uploadStatus,
|
uploadStatus,
|
||||||
partnerTyping,
|
partnerTyping,
|
||||||
onImagePress,
|
onImagePress,
|
||||||
@@ -107,6 +110,11 @@ export const ChatMessages: React.FC<ChatMessagesProps> = ({
|
|||||||
{partnerTyping && <TypingIndicator />}
|
{partnerTyping && <TypingIndicator />}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
ListFooterComponent={
|
||||||
|
<View style={{ paddingBottom: spacing.md }}>
|
||||||
|
<MatchCard match={match} user={{ id: userId || '' }} />
|
||||||
|
</View>
|
||||||
|
}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={FadeIn.delay(200).duration(400)}
|
entering={FadeIn.delay(200).duration(400)}
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import { DecorativeSeparator } from '../../../components/ui';
|
||||||
|
import { colors, gradients, spacing, radius, typography } from '../../../theme';
|
||||||
|
|
||||||
|
// Premium color palette
|
||||||
|
const ACCENT = colors.premium.gold;
|
||||||
|
const ACCENT_RGBA = 'rgba(212, 175, 55, ';
|
||||||
|
|
||||||
|
interface MatchCardProps {
|
||||||
|
match: any;
|
||||||
|
user: { id: string } | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MatchCard: React.FC<MatchCardProps> = ({ match, user }) => {
|
||||||
|
if (!match?.question) return null;
|
||||||
|
|
||||||
|
const isYesYes = match?.match_type === 'yes_yes';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeInDown.delay(100).duration(400).springify()}
|
||||||
|
style={styles.matchCardContainer}
|
||||||
|
>
|
||||||
|
<View style={styles.matchCard}>
|
||||||
|
{/* Card gradient background */}
|
||||||
|
<LinearGradient
|
||||||
|
colors={isYesYes
|
||||||
|
? ['rgba(212, 175, 55, 0.12)', 'rgba(184, 134, 11, 0.08)']
|
||||||
|
: ['rgba(232, 164, 174, 0.1)', 'rgba(22, 33, 62, 0.6)']
|
||||||
|
}
|
||||||
|
style={StyleSheet.absoluteFill}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Top glow for YES+YES */}
|
||||||
|
{isYesYes && (
|
||||||
|
<LinearGradient
|
||||||
|
colors={[`${ACCENT_RGBA}0.15)`, 'transparent']}
|
||||||
|
style={styles.matchCardGlow}
|
||||||
|
start={{ x: 0.5, y: 0 }}
|
||||||
|
end={{ x: 0.5, y: 1 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Match Label */}
|
||||||
|
<Text style={[styles.matchLabel, isYesYes && styles.matchLabelYesYes]}>
|
||||||
|
{isYesYes ? "PERFECT MATCH" : "SOFT MATCH"}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* Decorative Separator */}
|
||||||
|
<DecorativeSeparator
|
||||||
|
variant={isYesYes ? "gold" : "rose"}
|
||||||
|
width="60%"
|
||||||
|
marginVertical={spacing.md}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Question and Responses - Show who answered what */}
|
||||||
|
{match.responses && match.responses.length > 0 ? (
|
||||||
|
<View style={styles.responsesContainer}>
|
||||||
|
{(() => {
|
||||||
|
// 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 (
|
||||||
|
<View key={response.user_id} style={styles.responseCard}>
|
||||||
|
{/* Name and Answer Badge */}
|
||||||
|
<View style={styles.responseHeader}>
|
||||||
|
<Text style={styles.responseName}>{name}</Text>
|
||||||
|
{isYes ? (
|
||||||
|
<LinearGradient
|
||||||
|
colors={isYesYes
|
||||||
|
? gradients.premiumGold as [string, string]
|
||||||
|
: [colors.success, '#27ae60']
|
||||||
|
}
|
||||||
|
style={styles.answerBadge}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 1, y: 1 }}
|
||||||
|
>
|
||||||
|
<Ionicons name="checkmark" size={10} color={colors.text} />
|
||||||
|
<Text style={styles.answerBadgeText}>YES</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
) : (
|
||||||
|
<View style={styles.answerBadgeMaybe}>
|
||||||
|
<Ionicons name="help" size={10} color={colors.warning} />
|
||||||
|
<Text style={styles.answerBadgeTextMaybe}>MAYBE</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
{/* Question they saw */}
|
||||||
|
<Text style={styles.responseQuestionText}>
|
||||||
|
"{questionText}"
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})()}
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<Text style={styles.matchQuestionText}>
|
||||||
|
"{match.question.text}"
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Premium border */}
|
||||||
|
<View style={[
|
||||||
|
styles.matchCardBorder,
|
||||||
|
isYesYes && styles.matchCardBorderYesYes
|
||||||
|
]} pointerEvents="none" />
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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)`,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -14,3 +14,4 @@ export { MessageBubble } from './MessageBubble';
|
|||||||
export type { MessageBubbleProps } from './MessageBubble';
|
export type { MessageBubbleProps } from './MessageBubble';
|
||||||
export { ChatHeader } from './ChatHeader';
|
export { ChatHeader } from './ChatHeader';
|
||||||
export { ChatMessages } from './ChatMessages';
|
export { ChatMessages } from './ChatMessages';
|
||||||
|
export { MatchCard } from './MatchCard';
|
||||||
Reference in new issue
Block a user