diff --git a/src/auth/RequireAuth.tsx b/src/auth/RequireAuth.tsx
new file mode 100644
index 0000000..040de56
--- /dev/null
+++ b/src/auth/RequireAuth.tsx
@@ -0,0 +1,43 @@
+import { Navigate, useLocation } from "react-router-dom";
+import { useAuth } from "./useAuth";
+import type { ReactNode } from "react";
+
+/**
+ * Route guard. Wrap any subtree that requires a logged-in operator:
+ *
+ * }>
+ * } />
+ * ...
+ *
+ *
+ * Three branches, in priority order:
+ *
+ * 1. `status === "loading"` → render a centered "Loading…"
+ * placeholder so we don't bounce the user to /login before the
+ * cookie has had a chance to prove itself via /api/auth/me.
+ * 2. `status === "unauthenticated"` → redirect to
+ * `/login?next=`. The Login page reads
+ * `next` and bounces the operator back here on success.
+ * 3. otherwise → render children (status is
+ * "authenticated").
+ */
+export function RequireAuth({ children }: { children: ReactNode }) {
+ const { status } = useAuth();
+ const location = useLocation();
+ if (status === "loading") {
+ return (
+
+ Loading…
+
+ );
+ }
+ if (status === "unauthenticated") {
+ return (
+
+ );
+ }
+ return <>{children}>;
+}