Fix login hang after PIN + enrich Banquet mode
Some checks failed
Build Android APK / build-apk (push) Failing after 57m31s
Some checks failed
Build Android APK / build-apk (push) Failing after 57m31s
- Auth: introduce a shared AuthProvider/useAuth so entering the PIN updates reactive state and the layout's gate navigates immediately (previously it cached the token check at startup, so login appeared to hang until a manual refresh). login/scanner now use signIn/signOut. - Banquet: donor lookup now returns lifetime giving, last-12-months giving (computed from dated transactions), member/donor status, bear name, and tags. The banquet result screen shows status badge + lifetime and last-year figures. Tickets are irrelevant in banquet mode. - Admin: fix "‹ Scanner" back link wrapping (remove fixed width). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
dc39c41428
commit
1ca2c38fbf
7 changed files with 268 additions and 90 deletions
|
|
@ -59,9 +59,17 @@ export async function login(pin: string): Promise<void> {
|
|||
await saveToken(token);
|
||||
}
|
||||
|
||||
// Lets the auth provider react when the token is cleared (e.g. on a 401), so
|
||||
// UI state stays in sync with storage.
|
||||
let onCleared: (() => void) | null = null;
|
||||
export function onAuthCleared(cb: (() => void) | null): void {
|
||||
onCleared = cb;
|
||||
}
|
||||
|
||||
export async function logout(): Promise<void> {
|
||||
cachedToken = null;
|
||||
await clearToken();
|
||||
onCleared?.();
|
||||
}
|
||||
|
||||
async function authed<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
|
|
@ -128,9 +136,14 @@ export interface DonorLookup {
|
|||
found: boolean;
|
||||
email: string;
|
||||
name: string;
|
||||
bearName: string;
|
||||
lifetime: number;
|
||||
lastYear: number;
|
||||
online: number;
|
||||
offline: number;
|
||||
total: number;
|
||||
isMember: boolean;
|
||||
tags: string[];
|
||||
status: string;
|
||||
source: "master" | "transactions" | "none";
|
||||
}
|
||||
|
||||
|
|
|
|||
43
app/lib/auth.tsx
Normal file
43
app/lib/auth.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
||||
import { login as apiLogin, logout as apiLogout, getToken, onAuthCleared } from "./api";
|
||||
|
||||
interface AuthState {
|
||||
ready: boolean; // finished the initial token load
|
||||
signedIn: boolean;
|
||||
signIn: (pin: string) => Promise<void>;
|
||||
signOut: () => Promise<void>;
|
||||
}
|
||||
|
||||
const Ctx = createContext<AuthState | null>(null);
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [ready, setReady] = useState(false);
|
||||
const [signedIn, setSignedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getToken().then((t) => {
|
||||
setSignedIn(!!t);
|
||||
setReady(true);
|
||||
});
|
||||
// Keep state in sync when the token is cleared elsewhere (401 handling).
|
||||
onAuthCleared(() => setSignedIn(false));
|
||||
return () => onAuthCleared(null);
|
||||
}, []);
|
||||
|
||||
const signIn = async (pin: string) => {
|
||||
await apiLogin(pin);
|
||||
setSignedIn(true);
|
||||
};
|
||||
const signOut = async () => {
|
||||
await apiLogout();
|
||||
setSignedIn(false);
|
||||
};
|
||||
|
||||
return <Ctx.Provider value={{ ready, signedIn, signIn, signOut }}>{children}</Ctx.Provider>;
|
||||
}
|
||||
|
||||
export function useAuth(): AuthState {
|
||||
const c = useContext(Ctx);
|
||||
if (!c) throw new Error("useAuth must be used within AuthProvider");
|
||||
return c;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue