- Client: logRequests option + redacted request/response logging (setLogRequests to toggle at runtime); enabled via app extra.debugHttp for on-device 401 debugging - Dark mode (persisted) via ThemeProvider + React Navigation theme - New "Account" tab: Profile (view), Vehicles (full CRUD), Payment methods (view-only), About, dark-mode + request-logging toggles, sign out Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
import React, { useCallback, useState } from 'react';
|
|
import { ActivityIndicator, FlatList, StyleSheet, Text, View } from 'react-native';
|
|
import { useFocusEffect } from '@react-navigation/native';
|
|
import { ps } from '@/api/client';
|
|
import { useTheme } from '@/theme/ThemeContext';
|
|
import type { CreditCardDetail } from 'parksmarter-client';
|
|
|
|
export function PaymentMethodsScreen() {
|
|
const { colors } = useTheme();
|
|
const [cards, setCards] = useState<CreditCardDetail[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
setLoading(true);
|
|
ps.getUserDetail()
|
|
.then((u) => setCards(u.CreditCardDetails ?? []))
|
|
.catch(() => setCards([]))
|
|
.finally(() => setLoading(false));
|
|
}, []),
|
|
);
|
|
|
|
if (loading) {
|
|
return (
|
|
<View style={[styles.center, { backgroundColor: colors.bg }]}>
|
|
<ActivityIndicator color={colors.primary} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<FlatList
|
|
style={{ backgroundColor: colors.bg }}
|
|
contentContainerStyle={{ padding: 16 }}
|
|
data={cards}
|
|
keyExtractor={(c, i) => String(c.CCID ?? i)}
|
|
ListHeaderComponent={
|
|
<Text style={[styles.note, { color: colors.subtext }]}>
|
|
View only — add/edit coming later.
|
|
</Text>
|
|
}
|
|
ListEmptyComponent={
|
|
<Text style={[styles.note, { color: colors.subtext }]}>No saved cards.</Text>
|
|
}
|
|
renderItem={({ item }) => (
|
|
<View style={[styles.card, { backgroundColor: colors.card }]}>
|
|
<View style={styles.rowBetween}>
|
|
<Text style={[styles.brand, { color: colors.text }]}>
|
|
{item.CCAlias || 'Card'}
|
|
</Text>
|
|
{String(item.CCDefault) === 'true' ? (
|
|
<Text style={[styles.badge, { color: colors.primary }]}>Default</Text>
|
|
) : null}
|
|
</View>
|
|
<Text style={[styles.num, { color: colors.text }]}>
|
|
•••• •••• •••• {item.CCLastFour ?? '••••'}
|
|
</Text>
|
|
<Text style={[styles.meta, { color: colors.subtext }]}>
|
|
Exp {item.CCExpDate ?? '--/--'}
|
|
{item.CCZip ? ` · ZIP ${item.CCZip}` : ''}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
center: { flex: 1, alignItems: 'center', justifyContent: 'center' },
|
|
note: { fontSize: 13, marginBottom: 12 },
|
|
card: { borderRadius: 12, padding: 16, marginBottom: 12 },
|
|
rowBetween: { flexDirection: 'row', justifyContent: 'space-between' },
|
|
brand: { fontSize: 16, fontWeight: '700' },
|
|
badge: { fontSize: 12, fontWeight: '700' },
|
|
num: { fontSize: 18, letterSpacing: 2, marginTop: 10, fontVariant: ['tabular-nums'] },
|
|
meta: { fontSize: 13, marginTop: 6 },
|
|
});
|