StartSession: current-policy awareness (Free / No Parking / paid)

Read the CurrentSlot policy: when the meter's current time-of-day window is Free
or No Parking, show that status instead of a purchase flow (matches the official
app). Confirmed via API: DSB3 9AM-5PM is RateType "Hour" $4/hr with no free grace;
the "2 hours free" is a city grace period outside the ParkSmarter payment system.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hank 2026-07-06 11:48:35 -07:00
parent f044a9a511
commit 80812d25cf

View file

@ -51,6 +51,13 @@ export function StartSessionScreen() {
const { zone } = useRoute<SessionRoute>().params; const { zone } = useRoute<SessionRoute>().params;
const space = zone.Spaces?.[0]; const space = zone.Spaces?.[0];
// The policy in effect right now (CurrentSlot). Drives whether paid parking is
// even applicable — some windows are Free or No Parking (per the meter's schedule).
const currentPolicy = space?.Policies?.find((p) => p.CurrentSlot);
const currentType = (currentPolicy?.RateType ?? '').toLowerCase();
const currentlyFree = currentType.includes('free');
const noParkingNow = currentType.includes('no parking');
const [vehicles, setVehicles] = useState<VehicleDetail[]>([]); const [vehicles, setVehicles] = useState<VehicleDetail[]>([]);
const [cards, setCards] = useState<CreditCardDetail[]>([]); const [cards, setCards] = useState<CreditCardDetail[]>([]);
const [vehicleId, setVehicleId] = useState<number | undefined>(); const [vehicleId, setVehicleId] = useState<number | undefined>();
@ -169,6 +176,32 @@ export function StartSessionScreen() {
} }
}; };
// When the current time-of-day policy is Free or No Parking, paid parking
// doesn't apply — show the status instead of a purchase flow (matches the app).
if (currentlyFree || noParkingNow) {
return (
<View style={[styles.center, { backgroundColor: colors.bg, padding: 24 }]}>
<Text style={[styles.zone, { color: colors.text }]}>{zone.ZoneName}</Text>
<View
style={[
styles.freeBanner,
{ backgroundColor: currentlyFree ? '#e8f5e9' : '#fdecea', marginTop: 16, alignSelf: 'stretch' },
]}
>
<Text style={{ color: currentlyFree ? '#2e7d32' : '#c0392b', fontWeight: '700', fontSize: 16 }}>
{currentlyFree ? 'Parking is currently free' : 'No parking allowed right now'}
</Text>
{currentPolicy?.MessageText && currentPolicy.MessageText !== 'null' ? (
<Text style={{ color: colors.subtext, marginTop: 6 }}>{currentPolicy.MessageText}</Text>
) : null}
{currentPolicy?.DisplayString ? (
<Text style={{ color: colors.subtext, marginTop: 4 }}>{currentPolicy.DisplayString}</Text>
) : null}
</View>
</View>
);
}
if (loading && !ladder.length) { if (loading && !ladder.length) {
return ( return (
<View style={[styles.center, { backgroundColor: colors.bg }]}> <View style={[styles.center, { backgroundColor: colors.bg }]}>