401 auto re-login, My Location via native GPS, edge-to-edge, brain-car icon

- Client: onUnauthorized hook; 401 clears the token and (in-app) bounces to sign-in
- Map: "My location" uses the native UserLocation fix + flyTo; safe-area insets on
  the floating status bar / controls
- Enable edgeToEdgeEnabled (Android 15 forces it) so headers/back-arrow sit correctly
- App icon: brain-car launcher + adaptive icon

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hank 2026-07-06 09:58:23 -07:00
parent b31cb78592
commit 65e9118806
9 changed files with 66 additions and 8 deletions

View file

@ -43,6 +43,8 @@ export interface ParkSmarterClientOptions {
/** Provide a fetch impl for React Native / Node < 18 / tests. */
fetchImpl?: typeof fetch;
uuid?: () => string;
/** Called when any authenticated request returns 401 (token cleared automatically). */
onUnauthorized?: () => void;
}
function resolveEnvironment(
@ -66,6 +68,7 @@ export class ParkSmarterClient {
timeoutMs: options.timeoutMs,
fetchImpl: options.fetchImpl,
uuid: options.uuid,
onUnauthorized: options.onUnauthorized,
});
}

View file

@ -101,6 +101,11 @@ export interface HttpClientConfig {
fetchImpl?: typeof fetch;
/** Override UUID generation. Defaults to crypto.randomUUID when available. */
uuid?: () => string;
/**
* Called whenever an authenticated request comes back 401. Use it to clear the
* stored token and route the user back to sign-in. Fired before the error throws.
*/
onUnauthorized?: () => void;
}
function defaultUuid(): string {
@ -135,6 +140,7 @@ export class HttpClient {
timeoutMs: config.timeoutMs ?? 30000,
fetchImpl: config.fetchImpl ?? globalThis.fetch?.bind(globalThis),
uuid: config.uuid ?? defaultUuid,
onUnauthorized: config.onUnauthorized ?? (() => {}),
};
if (!this.cfg.fetchImpl) {
throw new Error(
@ -244,6 +250,11 @@ export class HttpClient {
}
if (!res.ok) {
if (res.status === 401) {
// Token invalid/rotated/expired — let the app clear it and re-auth.
await this.cfg.tokens.setAuthToken(null);
this.cfg.onUnauthorized();
}
throw new ParkSmarterApiError(res.status, data ?? text, requestId);
}