feat(slice-3): Admin shell + complete entry routing (§5.4/§6.5, PUC-6/8/9)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 22:41:38 -07:00
parent 10d22938f1
commit 25ac540171
3 changed files with 106 additions and 54 deletions
+47 -20
View File
@@ -1,20 +1,22 @@
// App shell — loads the session, applies the entry-routing rule (SD-0001 §6.5), and renders
// the matching screen. SLICE-2 covers the unauthenticated doors (Landing/SignIn) and a
// signed-in placeholder; the real create-storefront/admin screens land in SLICE-3.
// the matching screen. The rule is exhaustive: no state lands nowhere (BUC-4). The verify
// response carries the same shape as /me, so onAuthed feeds the session directly.
import { useEffect, useState } from "react";
import { getMe, type VerifyResult } from "./api";
import { getMe, type StorefrontResult, type VerifyResult } from "./api";
import { routeFor, type SessionState } from "./routing";
import Admin from "./screens/Admin";
import CreateStorefront from "./screens/CreateStorefront";
import Landing from "./screens/Landing";
import SignIn from "./screens/SignIn";
import SignedIn from "./screens/SignedIn";
type Door = "signup" | "login";
type Welcome = "new" | "back" | null;
export default function App() {
const [session, setSession] = useState<SessionState | null>(null);
const [loading, setLoading] = useState(true);
const [door, setDoor] = useState<Door | null>(null);
const [justCreated, setJustCreated] = useState(false);
const [welcome, setWelcome] = useState<Welcome>(null);
async function reload() {
setLoading(true);
@@ -26,7 +28,15 @@ export default function App() {
void reload();
}, []);
if (loading) return <main>Loading</main>;
if (loading) {
return (
<div className="screen screen--plain">
<main className="screen__main">
<p className="note">Loading</p>
</main>
</div>
);
}
const route = routeFor(session ?? { account: null, storefront: null });
@@ -35,13 +45,11 @@ export default function App() {
return (
<SignIn
door={door}
onBack={() => {
setDoor(null);
}}
onBack={() => setDoor(null)}
onAuthed={(result: VerifyResult) => {
setJustCreated(result.created);
setWelcome(result.created ? "new" : "back");
setDoor(null);
void reload();
setSession({ account: result.account, storefront: result.storefront });
}}
/>
);
@@ -49,16 +57,35 @@ export default function App() {
return <Landing onGetStarted={() => setDoor("signup")} onLogIn={() => setDoor("login")} />;
}
// route is "create-storefront" or "admin" — both are SLICE-3 screens; SLICE-2 shows the
// signed-in placeholder so sign-out (PUC-9) is reachable.
const email = session!.account!.email;
function signedOut() {
setWelcome(null);
setDoor(null);
setSession(null);
}
if (route === "create-storefront") {
return (
<CreateStorefront
email={email}
welcome={welcome}
onCreated={(sf: StorefrontResult) => {
setWelcome(null);
setSession({ account: { email }, storefront: sf });
}}
onAlreadyOwns={() => void reload()}
onSignedOut={signedOut}
/>
);
}
return (
<SignedIn
email={session!.account!.email}
created={justCreated}
onSignedOut={() => {
setJustCreated(false);
void reload();
}}
<Admin
storefrontName={session!.storefront!.name}
email={email}
welcome={welcome}
onSignedOut={signedOut}
/>
);
}