From d1473f977630fce7941f26bd7ca61f62266d2319 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 6 Jul 2026 12:38:38 -0700 Subject: [PATCH] Scanner: parse processQR URLs, manual Zone ID entry, zone-name fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kiosk QR decoded to a bare https://www.parksmarter.com/home/processQR (no zone) — even the official app rejects code-less URL QRs. Now: parse processQR?code= URLs, detect bare app-link QRs and prompt for the printed Zone ID, and add manual Zone ID entry. Lookups try getMetersByScannerCode then getMetersByZoneName (DSB13 maps to zone 113150). Fixes scanning real stickers, which previously passed the URL to the API. Co-Authored-By: Claude Fable 5 --- app/src/screens/ScanScreen.tsx | 167 ++++++++++++++++++++++++++++----- 1 file changed, 146 insertions(+), 21 deletions(-) diff --git a/app/src/screens/ScanScreen.tsx b/app/src/screens/ScanScreen.tsx index 9fa6580..1901a4e 100644 --- a/app/src/screens/ScanScreen.tsx +++ b/app/src/screens/ScanScreen.tsx @@ -1,5 +1,14 @@ import React, { useCallback, useRef, useState } from 'react'; -import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { + ActivityIndicator, + Alert, + Modal, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from 'react-native'; import { Camera, useCameraDevice, @@ -14,38 +23,98 @@ import type { RootStackParamList } from '@/navigation/RootNavigator'; type Nav = NativeStackNavigationProp; /** - * Scan a kiosk QR code (its `ScannerCode`), look the meter up via the API, and - * jump to the detail screen where it can be saved/shared. - * Uses on-device VisionCamera code scanning — no Google Play Services. + * Interpret a scanned code. Kiosk QR codes come in two flavors: + * - a raw scanner/zone code (e.g. "DSB13") -> use directly + * - a ParkSmarter/ips-txt2pay URL. If it has a query with the code + * (…/processQR?code=DSB13) we extract it; a bare app-link URL (this older + * sticker: …/processQR) carries no zone, so we ask for the printed Zone ID. */ +export function parseScannedCode(raw: string): { code?: string; isAppLink?: boolean } { + const v = raw.trim(); + if (!/^https?:\/\//i.test(v)) return { code: v }; + if (v.length > 255) return {}; + const lower = v.toLowerCase(); + if (!(lower.includes('parksmarter.com') || lower.includes('ips-txt2pay'))) { + return { isAppLink: true }; + } + const q = v.split('?')[1]; + if (!q) return { isAppLink: true }; + for (const pair of q.split('&')) { + const [k, val] = pair.split('='); + if (val && /^(code|zone|zoneid|m|meter)$/i.test(k)) { + return { code: decodeURIComponent(val) }; + } + } + const bare = q.split('&')[0]; + if (bare && !bare.includes('=')) return { code: decodeURIComponent(bare) }; + return { isAppLink: true }; +} + export function ScanScreen() { const navigation = useNavigation