| 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 { |
| |
| match: Match; |
| |
| onAccept?: () => void; |
| |
| onMessage?: () => void; |
| } |
|
|
| |
| |
| |
| |
| |
| 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, |
| }, |
| }); |
|
|