mobileapp / src /components /ride /MatchCard.tsx
Antaram Dev Bot
feat: complete ANTARAM.ORG ride-sharing app frontend
5c876be
import React from 'react';
import { StyleSheet, View } from 'react-native';
import {
useTheme,
Card,
Text,
Avatar,
Chip,
Button,
} from 'react-native-paper';
import type { Match } from '../../types/match';
import { formatFare } from '../../utils/fareCalculator';
export interface MatchCardProps {
/** The match / match-with-user object */
match: Match;
/** Callback when the Accept button is pressed */
onAccept?: () => void;
/** Callback when the Message button is pressed */
onMessage?: () => void;
}
/**
* Match result card showing another user's ride offer/request.
* Displays avatar, name, rating, vehicle type, pickup distance,
* overlap %, fare range, and action buttons.
*/
export function MatchCard({ match, onAccept, onMessage }: MatchCardProps) {
const theme = useTheme();
const displayName = match.riderName ?? match.passengerName ?? 'User';
const rating = match.riderRating ?? match.passengerRating ?? 0;
const avatar = match.riderAvatar ?? match.passengerAvatar;
return (
<Card
style={[styles.card, { backgroundColor: theme.colors.surface }]}
mode="elevated"
elevation={1}
>
<Card.Content style={styles.content}>
{/* User info row */}
<View style={styles.userRow}>
<Avatar.Text
size={44}
label={displayName.charAt(0).toUpperCase()}
source={avatar ? { uri: avatar } : undefined}
style={{ backgroundColor: theme.colors.primaryContainer }}
color={theme.colors.onPrimaryContainer}
/>
<View style={styles.userInfo}>
<Text
variant="titleSmall"
style={{ color: theme.colors.onSurface }}
>
{displayName}
</Text>
<View style={styles.ratingRow}>
<Text
variant="bodySmall"
style={{ color: theme.colors.onSurfaceVariant }}
>
⭐ {rating > 0 ? rating.toFixed(1) : 'New'}
</Text>
</View>
</View>
</View>
{/* Stats chips */}
<View style={styles.chipsRow}>
{match.overlapPercentage != null && (
<Chip
mode="outlined"
compact
style={styles.chip}
textStyle={{ fontSize: 12 }}
>
{Math.round(match.overlapPercentage)}% match
</Chip>
)}
{match.detourDistanceKm != null && (
<Chip
mode="outlined"
compact
style={styles.chip}
textStyle={{ fontSize: 12 }}
icon="map-marker-distance"
>
{match.detourDistanceKm.toFixed(1)} km detour
</Chip>
)}
{match.fareAgreed != null && (
<Chip
mode="flat"
compact
style={[
styles.chip,
{ backgroundColor: theme.colors.tertiaryContainer },
]}
textStyle={{
fontSize: 12,
color: theme.colors.onTertiaryContainer,
}}
>
{formatFare(match.fareAgreed)}
</Chip>
)}
</View>
{/* Action buttons */}
<View style={styles.actionsRow}>
<Button
mode="outlined"
onPress={onMessage}
icon="chat-outline"
style={styles.messageButton}
contentStyle={styles.buttonContent}
labelStyle={styles.buttonLabel}
>
Message
</Button>
<Button
mode="contained"
onPress={onAccept}
icon="check"
style={styles.acceptButton}
contentStyle={styles.buttonContent}
labelStyle={styles.buttonLabel}
>
Accept
</Button>
</View>
</Card.Content>
</Card>
);
}
const styles = StyleSheet.create({
card: {
borderRadius: 12,
marginVertical: 4,
},
content: {
gap: 12,
paddingVertical: 14,
paddingHorizontal: 16,
},
userRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
userInfo: {
flex: 1,
gap: 2,
},
ratingRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
},
chipsRow: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
},
chip: {
height: 30,
},
actionsRow: {
flexDirection: 'row',
justifyContent: 'flex-end',
gap: 8,
},
messageButton: {
borderRadius: 20,
},
acceptButton: {
borderRadius: 20,
},
buttonContent: {
paddingHorizontal: 12,
paddingVertical: 4,
},
buttonLabel: {
fontSize: 13,
},
});