diff --git a/app/src/screens/SessionsScreen.tsx b/app/src/screens/SessionsScreen.tsx index 2eb8634..6d23cf9 100644 --- a/app/src/screens/SessionsScreen.tsx +++ b/app/src/screens/SessionsScreen.tsx @@ -2,9 +2,11 @@ import React, { useCallback, useState } from 'react'; import { RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native'; import { useFocusEffect } from '@react-navigation/native'; import { ps } from '@/api/client'; +import { useTheme } from '@/theme/ThemeContext'; import type { ActiveSession, PastSession } from 'parksmarter-client'; export function SessionsScreen() { + const { colors } = useTheme(); const [active, setActive] = useState([]); const [past, setPast] = useState([]); const [refreshing, setRefreshing] = useState(false); @@ -31,31 +33,32 @@ export function SessionsScreen() { return ( } > - Active + Active {active.length === 0 ? ( - No active sessions. + No active sessions. ) : ( active.map((s, i) => ( - - {s.ZoneName ?? 'Session'} - + + {s.ZoneName ?? 'Session'} + {s.SpaceName ?? s.Space ?? ''} · ends {s.EndTimeDisplay ?? s.EndTime ?? ''} )) )} - History + History {past.length === 0 ? ( - No past sessions. + No past sessions. ) : ( past.map((s, i) => ( - - {s.ZoneName ?? 'Session'} - + + {s.ZoneName ?? 'Session'} + {s.StartTime ?? ''} · {s.Amount != null ? `$${s.Amount}` : ''} @@ -67,9 +70,8 @@ export function SessionsScreen() { const styles = StyleSheet.create({ header: { fontSize: 18, fontWeight: '700', marginBottom: 8 }, - empty: { color: '#888', marginBottom: 8 }, - card: { backgroundColor: '#f4f4f4', borderRadius: 10, padding: 14, marginBottom: 10 }, - activeCard: { backgroundColor: '#e8f5e9' }, + empty: { marginBottom: 8 }, + card: { borderRadius: 10, padding: 14, marginBottom: 10 }, zone: { fontSize: 16, fontWeight: '600' }, - meta: { color: '#777', fontSize: 13, marginTop: 2 }, + meta: { fontSize: 13, marginTop: 2 }, }); diff --git a/parksmarter-client/src/client.ts b/parksmarter-client/src/client.ts index 4454bc4..176b1c5 100644 --- a/parksmarter-client/src/client.ts +++ b/parksmarter-client/src/client.ts @@ -598,13 +598,20 @@ export class ParkSmarterClient { MeterTypeId: params.meterTypeId, }; if (params.bleEncBytes) body.BleEncBytes = params.bleEncBytes; - return this.unwrap( - this.http.request({ - method: 'POST', - path: '/api/Session', - body, - }), - ); + return this.http + .request({ method: 'POST', path: '/api/Session', body }) + .then((res) => { + // The server returns HTTP 200 even when the charge is declined, signalling + // failure via Response.Status:"Error" (+ OriginalErrorMessage like "DECLINED"). + const env = res.data?.Response as { Status?: string; Message?: string } | undefined; + if (env?.Status === 'Error') { + throw new Error( + (env.Message || 'Payment failed.') + + (res.data?.OriginalErrorMessage ? ` (${res.data.OriginalErrorMessage})` : ''), + ); + } + return res.data; + }); } /** GET /api/ParkingSession — currently active sessions. */ diff --git a/parksmarter-client/src/types.ts b/parksmarter-client/src/types.ts index 488d298..368261d 100644 --- a/parksmarter-client/src/types.ts +++ b/parksmarter-client/src/types.ts @@ -452,8 +452,29 @@ export interface StartParkingSessionParams { bleEncBytes?: string; } +/** + * POST /api/Session. CONFIRMED via live capture. Returns HTTP 200 even on a + * declined charge — check `Response.Status === 'Error'` (the client does this and + * throws). `OriginalErrorMessage` carries the gateway reason (e.g. "DECLINED"). + */ export interface StartParkingSessionResponse { - BleEncBytes?: string; + Response?: ResponseEnvelope; + BleEncBytes?: string | null; + PBPParkingSessionId?: number; + OriginalErrorMessage?: string; + StartTime?: string | null; + EndTime?: string | null; + TimePurchased?: string | null; + ZoneID?: number | string | null; + Zone?: string | null; + SpaceID?: number | string | null; + Space?: string | null; + VehicleNumber?: string | null; + Lat?: number | null; + Long?: number | null; + TransactionFee?: string | number | null; + Amount?: string | number | null; + AmountCharged?: string | number | null; [key: string]: unknown; }