Login: fixed-length 4-digit PIN that auto-submits on the 4th digit (no submit button to scroll to on small iPhone screens) and clears on a wrong PIN. Compact, vertically-centered keypad so it fits without scrolling. Operator tracking: after PIN auth, staff enter their name (new /operator screen, persisted per device). The name is sent as X-Operator on every authed request and recorded on each check-in/undo/ice audit entry (new Operator column), so logs show who did what. Shown in the scanner header and the admin audit view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
64 lines
1.8 KiB
TypeScript
64 lines
1.8 KiB
TypeScript
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
|
import {
|
|
login as apiLogin,
|
|
logout as apiLogout,
|
|
getToken,
|
|
getOperator,
|
|
setOperator as apiSetOperator,
|
|
onAuthCleared,
|
|
} from "./api";
|
|
|
|
interface AuthState {
|
|
ready: boolean; // finished the initial load
|
|
signedIn: boolean; // has a valid PIN token
|
|
operator: string; // gate staff name (empty until set)
|
|
signIn: (pin: string) => Promise<void>;
|
|
setOperator: (name: 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);
|
|
const [operator, setOperatorState] = useState("");
|
|
|
|
useEffect(() => {
|
|
Promise.all([getToken(), getOperator()]).then(([t, op]) => {
|
|
setSignedIn(!!t);
|
|
setOperatorState(op ?? "");
|
|
setReady(true);
|
|
});
|
|
// Keep state in sync when the token is cleared elsewhere (401 handling).
|
|
onAuthCleared(() => {
|
|
setSignedIn(false);
|
|
setOperatorState("");
|
|
});
|
|
return () => onAuthCleared(null);
|
|
}, []);
|
|
|
|
const signIn = async (pin: string) => {
|
|
await apiLogin(pin);
|
|
setSignedIn(true);
|
|
};
|
|
const setOperator = async (name: string) => {
|
|
await apiSetOperator(name);
|
|
setOperatorState(name);
|
|
};
|
|
const signOut = async () => {
|
|
await apiLogout();
|
|
setSignedIn(false);
|
|
setOperatorState("");
|
|
};
|
|
|
|
return (
|
|
<Ctx.Provider value={{ ready, signedIn, operator, signIn, setOperator, 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;
|
|
}
|