diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 0a5afa1..66a22b4 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -12,7 +12,6 @@ import { SettingsPage } from "./pages/Settings.js";
import { BookingConfirmedPage } from "./pages/BookingConfirmed.js";
import { BookingCancelledPage } from "./pages/BookingCancelled.js";
import { BookingErrorPage } from "./pages/BookingError.js";
-import { SetupWizard } from "./pages/SetupWizard.jsx";
import { CustomerPortal } from "./portal/CustomerPortal.js";
import { DevLoginSelector, getDevUser } from "./pages/DevLoginSelector.js";
import { DevSessionIndicator } from "./components/DevSessionIndicator.js";
@@ -20,61 +19,6 @@ import { BrandingProvider, useBranding } from "./BrandingContext.js";
import { GlobalSearch } from "./components/GlobalSearch.js";
import { useSession, signIn } from "./lib/auth-client.js";
-function LoginPage() {
- const [isLoading, setIsLoading] = useState(false);
-
- const handleLogin = async () => {
- setIsLoading(true);
- await signIn.social({ provider: "authentik", callbackURL: window.location.origin });
- };
-
- return (
-
-
-
GroomBook
-
- Sign in to continue
-
-
-
-
- );
-}
-
const NAV_LINKS = [
{ to: "/admin", label: "Appointments" },
{ to: "/admin/clients", label: "Clients" },
@@ -190,7 +134,6 @@ function AdminLayout() {
export function App() {
const location = useLocation();
const [authDisabled, setAuthDisabled] = useState(null);
- const [needsSetup, setNeedsSetup] = useState(null);
const { data: rawSession, isPending: rawSessionLoading } = useSession();
// In dev mode (authDisabled=true), session state is irrelevant - skip useSession result
const session = authDisabled ? null : rawSession;
@@ -203,19 +146,6 @@ export function App() {
.catch(() => setAuthDisabled(false));
}, []);
- // After session is confirmed, check if setup is needed
- useEffect(() => {
- if (authDisabled === null || sessionLoading) return;
- // Skip if no authenticated session (will redirect to login or dev selector)
- if (!authDisabled && !session) return;
- if (authDisabled && !getDevUser()) return;
-
- fetch("/api/setup/status")
- .then((r) => r.json())
- .then((data) => setNeedsSetup(data.needsSetup === true))
- .catch(() => setNeedsSetup(false));
- }, [authDisabled, session, sessionLoading]);
-
// Public booking redirect pages — no auth or portal chrome needed
if (location.pathname === "/booking/confirmed") {
return ;
@@ -227,39 +157,24 @@ export function App() {
return ;
}
- // Setup wizard — standalone, no admin chrome
- if (location.pathname === "/setup") {
- return (
-
-
-
- );
- }
-
- // Still loading auth state or setup check (skip setup check in dev mode)
+ // Still loading auth state
if (authDisabled === null || sessionLoading) return null;
- // Dev mode: show login selector (no setup check needed in dev mode)
+ // Dev mode: show login selector
if (authDisabled && location.pathname === "/login") {
return ;
}
- // Dev mode: use dev login selector (no setup check needed in dev mode)
- if (authDisabled && !getDevUser()) {
+ // Dev mode: use dev login selector for non-admin routes
+ // Allow /admin/* access in dev mode even without stored dev user (skipLogin flow)
+ if (authDisabled && !getDevUser() && !location.pathname.startsWith("/admin")) {
return ;
}
- // Production: need setup check
- if (needsSetup === null) return null;
-
// Production mode: if no session, redirect to Authentik sign-in
if (!authDisabled && !session) {
- return ;
- }
-
- // Redirect to setup wizard if needed
- if (needsSetup) {
- return ;
+ signIn.social({ provider: "authentik" });
+ return null;
}
return (
diff --git a/apps/web/src/__tests__/App.test.tsx b/apps/web/src/__tests__/App.test.tsx
index ea5aea8..0f728fb 100644
--- a/apps/web/src/__tests__/App.test.tsx
+++ b/apps/web/src/__tests__/App.test.tsx
@@ -122,7 +122,47 @@ describe("App navigation", () => {
});
describe("Dev login selector", () => {
- it("redirects to /login when auth is disabled and no user selected", async () => {
+ it("renders /admin routes without redirect when auth is disabled and no user selected", async () => {
+ // authDisabled=true, no dev-user in localStorage
+ // /admin/* routes should render without requiring a stored dev user
+ global.fetch = vi.fn((url: string) => {
+ if (url === "/api/dev/config") {
+ return Promise.resolve({
+ ok: true,
+ json: async () => ({ authDisabled: true }),
+ } as Response);
+ }
+ if (url === "/api/branding") {
+ return Promise.resolve({
+ ok: true,
+ json: async () => ({
+ businessName: "GroomBook",
+ primaryColor: "#4f8a6f",
+ accentColor: "#8b7355",
+ logoBase64: null,
+ logoMimeType: null,
+ }),
+ } as Response);
+ }
+ return Promise.resolve({ ok: true, json: async () => [] } as Response);
+ }) as unknown as typeof fetch;
+
+ render(
+
+
+
+ );
+
+ // Should render admin nav (skipLogin flow: /admin accessible without stored dev user)
+ const nav = await screen.findByRole("navigation");
+ expect(
+ within(nav).getByText((_, el) => el?.tagName === "STRONG" && /Groom\s*Book/.test(el.textContent ?? ""))
+ ).toBeInTheDocument();
+ });
+
+ it("redirects non-/admin routes to /login when auth is disabled and no user selected", async () => {
+ // authDisabled=true, no dev-user in localStorage
+ // non-/admin/* routes should redirect to /login
global.fetch = vi.fn((url: string) => {
if (url === "/api/dev/config") {
return Promise.resolve({
@@ -151,17 +191,11 @@ describe("Dev login selector", () => {
}),
} as Response);
}
- if (url === "/api/auth/get-session") {
- return Promise.resolve({
- ok: true,
- json: async () => ({ user: null }),
- } as Response);
- }
return Promise.resolve({ ok: true, json: async () => [] } as Response);
}) as unknown as typeof fetch;
render(
-
+
);