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>
This commit is contained in:
Hank 2026-07-06 10:14:24 -07:00
parent 65e9118806
commit 2f9f0f604a
11 changed files with 707 additions and 8 deletions

View file

@ -0,0 +1,102 @@
import React, {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
DarkTheme as NavDark,
DefaultTheme as NavLight,
type Theme as NavTheme,
} from '@react-navigation/native';
export type ThemeMode = 'light' | 'dark';
export interface Colors {
bg: string;
card: string;
text: string;
subtext: string;
border: string;
primary: string;
danger: string;
}
const light: Colors = {
bg: '#ffffff',
card: '#f4f4f4',
text: '#111111',
subtext: '#666666',
border: '#e0e0e0',
primary: '#1e6f5c',
danger: '#c0392b',
};
const dark: Colors = {
bg: '#0e1513',
card: '#1b2422',
text: '#f2f2f2',
subtext: '#9aa5a1',
border: '#2c3a36',
primary: '#3fbf9c',
danger: '#ef6a5c',
};
interface ThemeState {
mode: ThemeMode;
colors: Colors;
navTheme: NavTheme;
setMode: (m: ThemeMode) => void;
toggle: () => void;
}
const ThemeContext = createContext<ThemeState | null>(null);
const STORAGE_KEY = 'ps_theme_mode';
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [mode, setModeState] = useState<ThemeMode>('light');
useEffect(() => {
AsyncStorage.getItem(STORAGE_KEY).then((v) => {
if (v === 'light' || v === 'dark') setModeState(v);
});
}, []);
const setMode = (m: ThemeMode) => {
setModeState(m);
void AsyncStorage.setItem(STORAGE_KEY, m);
};
const value = useMemo<ThemeState>(() => {
const colors = mode === 'dark' ? dark : light;
const base = mode === 'dark' ? NavDark : NavLight;
const navTheme: NavTheme = {
...base,
colors: {
...base.colors,
background: colors.bg,
card: colors.bg,
text: colors.text,
border: colors.border,
primary: colors.primary,
},
};
return {
mode,
colors,
navTheme,
setMode,
toggle: () => setMode(mode === 'dark' ? 'light' : 'dark'),
};
}, [mode]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
export function useTheme(): ThemeState {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}