CampgroundTickets/app/app/_layout.tsx
Hank 0e8fe3bb9a 4-digit auto-submit PIN + operator name in audit logs
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>
2026-07-08 16:45:23 +00:00

59 lines
1.6 KiB
TypeScript

import { useEffect } from "react";
import { View, ActivityIndicator } from "react-native";
import { Stack, useRouter, useSegments } from "expo-router";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
import { AuthProvider, useAuth } from "../lib/auth";
import { theme } from "../lib/theme";
export default function RootLayout() {
return (
<SafeAreaProvider>
<StatusBar style="light" />
<AuthProvider>
<AuthGate />
</AuthProvider>
</SafeAreaProvider>
);
}
function AuthGate() {
const { ready, signedIn, operator } = useAuth();
const router = useRouter();
const segments = useSegments();
useEffect(() => {
if (!ready) return;
const route = segments[0];
const onLogin = route === "login";
const onOperator = route === "operator";
if (!signedIn) {
if (!onLogin) router.replace("/login");
return;
}
// Signed in via PIN — require an operator name before using the app.
if (!operator) {
if (!onOperator) router.replace("/operator");
return;
}
if (onLogin || onOperator) router.replace("/");
}, [ready, signedIn, operator, segments, router]);
if (!ready) {
return (
<View style={{ flex: 1, backgroundColor: theme.bg, alignItems: "center", justifyContent: "center" }}>
<ActivityIndicator color={theme.successBright} size="large" />
</View>
);
}
return (
<Stack
screenOptions={{
headerShown: false,
contentStyle: { backgroundColor: theme.bg },
animation: "fade",
}}
/>
);
}