BigBrainParking/app/src/screens/PaymentMethodsScreen.tsx
Hank 2f9f0f604a Add request logging mode, dark mode, Account hub, profile, vehicle CRUD, cards view
- 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>
2026-07-06 10:14:24 -07:00

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 },
});