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:
+46
-19
@@ -1,20 +1,22 @@
|
|||||||
// App shell — loads the session, applies the entry-routing rule (SD-0001 §6.5), and renders
|
// 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
|
// the matching screen. The rule is exhaustive: no state lands nowhere (BUC-4). The verify
|
||||||
// signed-in placeholder; the real create-storefront/admin screens land in SLICE-3.
|
// response carries the same shape as /me, so onAuthed feeds the session directly.
|
||||||
import { useEffect, useState } from "react";
|
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 { routeFor, type SessionState } from "./routing";
|
||||||
|
import Admin from "./screens/Admin";
|
||||||
|
import CreateStorefront from "./screens/CreateStorefront";
|
||||||
import Landing from "./screens/Landing";
|
import Landing from "./screens/Landing";
|
||||||
import SignIn from "./screens/SignIn";
|
import SignIn from "./screens/SignIn";
|
||||||
import SignedIn from "./screens/SignedIn";
|
|
||||||
|
|
||||||
type Door = "signup" | "login";
|
type Door = "signup" | "login";
|
||||||
|
type Welcome = "new" | "back" | null;
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [session, setSession] = useState<SessionState | null>(null);
|
const [session, setSession] = useState<SessionState | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [door, setDoor] = useState<Door | null>(null);
|
const [door, setDoor] = useState<Door | null>(null);
|
||||||
const [justCreated, setJustCreated] = useState(false);
|
const [welcome, setWelcome] = useState<Welcome>(null);
|
||||||
|
|
||||||
async function reload() {
|
async function reload() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -26,7 +28,15 @@ export default function App() {
|
|||||||
void reload();
|
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 });
|
const route = routeFor(session ?? { account: null, storefront: null });
|
||||||
|
|
||||||
@@ -35,13 +45,11 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<SignIn
|
<SignIn
|
||||||
door={door}
|
door={door}
|
||||||
onBack={() => {
|
onBack={() => setDoor(null)}
|
||||||
setDoor(null);
|
|
||||||
}}
|
|
||||||
onAuthed={(result: VerifyResult) => {
|
onAuthed={(result: VerifyResult) => {
|
||||||
setJustCreated(result.created);
|
setWelcome(result.created ? "new" : "back");
|
||||||
setDoor(null);
|
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")} />;
|
return <Landing onGetStarted={() => setDoor("signup")} onLogIn={() => setDoor("login")} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// route is "create-storefront" or "admin" — both are SLICE-3 screens; SLICE-2 shows the
|
const email = session!.account!.email;
|
||||||
// signed-in placeholder so sign-out (PUC-9) is reachable.
|
|
||||||
|
function signedOut() {
|
||||||
|
setWelcome(null);
|
||||||
|
setDoor(null);
|
||||||
|
setSession(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route === "create-storefront") {
|
||||||
return (
|
return (
|
||||||
<SignedIn
|
<CreateStorefront
|
||||||
email={session!.account!.email}
|
email={email}
|
||||||
created={justCreated}
|
welcome={welcome}
|
||||||
onSignedOut={() => {
|
onCreated={(sf: StorefrontResult) => {
|
||||||
setJustCreated(false);
|
setWelcome(null);
|
||||||
void reload();
|
setSession({ account: { email }, storefront: sf });
|
||||||
}}
|
}}
|
||||||
|
onAlreadyOwns={() => void reload()}
|
||||||
|
onSignedOut={signedOut}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Admin
|
||||||
|
storefrontName={session!.storefront!.name}
|
||||||
|
email={email}
|
||||||
|
welcome={welcome}
|
||||||
|
onSignedOut={signedOut}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// Admin shell (SD-0001 §5.4) — the storefront's stable home; honestly empty this release
|
||||||
|
// (PUC-8; PUC-9 sign-out). Renders from /me alone: storefront name + signed-in email. No
|
||||||
|
// zeroed metric tiles, no locked-feature teasers (OHM: Agency & Anti-Manipulation).
|
||||||
|
// Visuals per the ui/designs export (hf-admin).
|
||||||
|
import { logout } from "../api";
|
||||||
|
import { AccountChip, Banner, Eyebrow, Screen, TopBar } from "../ui/kit";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storefrontName: string;
|
||||||
|
email: string;
|
||||||
|
welcome: "new" | "back" | null;
|
||||||
|
onSignedOut: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Admin({ storefrontName, email, welcome, onSignedOut }: Props) {
|
||||||
|
async function signOut() {
|
||||||
|
await logout();
|
||||||
|
onSignedOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Screen plain>
|
||||||
|
<TopBar
|
||||||
|
left={
|
||||||
|
<span className="storeid">
|
||||||
|
<img src="/brand/mark-tile.svg" width={24} height={24} alt="" />
|
||||||
|
<span className="storeid__col">
|
||||||
|
<span className="storeid__name">{storefrontName}</span>
|
||||||
|
<span className="storeid__sub">ecomm storefront</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
right={<AccountChip email={email} onSignOut={signOut} />}
|
||||||
|
/>
|
||||||
|
<main className="screen__main">
|
||||||
|
<div className="empty">
|
||||||
|
{welcome && (
|
||||||
|
<div style={{ marginBottom: 24, width: "100%" }}>
|
||||||
|
<Banner tone="info" title={welcome === "new" ? "Welcome to ecomm" : "Welcome back"}>
|
||||||
|
{welcome === "new"
|
||||||
|
? "A new account was created for this email."
|
||||||
|
: "Signed in to your existing account."}
|
||||||
|
</Banner>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="empty__seal" aria-hidden="true">
|
||||||
|
<img src="/brand/mark-mono-gold.svg" width={36} height={36} alt="" />
|
||||||
|
</div>
|
||||||
|
<Eyebrow>Your storefront</Eyebrow>
|
||||||
|
<h1>{storefrontName}</h1>
|
||||||
|
<p className="empty__copy">
|
||||||
|
There's nothing to manage yet — and that's a finished state, not a missing one.
|
||||||
|
Catalog, orders, and settings will appear here as ecomm grows.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// SLICE-2 placeholder for the signed-in surfaces (create-storefront, admin) that land in
|
|
||||||
// SLICE-3. It honestly states what's next and makes PUC-9 sign-out reachable now. No
|
|
||||||
// fabricated capability (INV-9).
|
|
||||||
import { logout } from "../api";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
email: string;
|
|
||||||
created: boolean;
|
|
||||||
onSignedOut: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SignedIn({ email, created, onSignedOut }: Props) {
|
|
||||||
async function signOut() {
|
|
||||||
await logout();
|
|
||||||
onSignedOut();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main>
|
|
||||||
<header>
|
|
||||||
<strong>ecomm</strong>
|
|
||||||
<span>{email}</span>
|
|
||||||
<button type="button" onClick={signOut}>
|
|
||||||
Sign out
|
|
||||||
</button>
|
|
||||||
</header>
|
|
||||||
<section>
|
|
||||||
<h1>{created ? "Welcome to ecomm" : "Welcome back"}</h1>
|
|
||||||
<p>You're signed in as {email}.</p>
|
|
||||||
<p>Creating your storefront arrives in the next slice. Nothing else is needed yet.</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user