diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 04d7ccf..1e6c4f5 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -59,3 +59,21 @@ export async function verifyCode( export async function logout(): Promise { await fetch("/api/auth/logout", { method: "POST", credentials: "include" }); } + +export interface StorefrontResult { + id: number; + name: string; +} + +export async function createStorefront( + name: string, +): Promise<{ ok: true; storefront: StorefrontResult } | { ok: false; error: ApiError }> { + const resp = await fetch("/api/storefronts", { + method: "POST", + headers: { "content-type": "application/json" }, + credentials: "include", + body: JSON.stringify(name.trim() ? { name: name.trim() } : {}), + }); + if (resp.ok) return { ok: true, storefront: (await resp.json()) as StorefrontResult }; + return { ok: false, error: await errorOf(resp) }; +} diff --git a/frontend/src/screens/CreateStorefront.tsx b/frontend/src/screens/CreateStorefront.tsx new file mode 100644 index 0000000..4b521ef --- /dev/null +++ b/frontend/src/screens/CreateStorefront.tsx @@ -0,0 +1,92 @@ +// Create storefront (SD-0001 §5.3) — a storefront-less Merchant establishes their one +// storefront (PUC-4/5; PUC-7 defense-in-depth on 409). Visuals per the ui/designs export +// (hf-storefront). The welcome banner carries PUC-3's honest copy from the verify step. +import { useState } from "react"; +import { createStorefront, logout, type StorefrontResult } from "../api"; +import { AccountChip, AuthCard, Banner, Eyebrow, Field, Footer, PrimaryButton, Screen, TopBar, Wordmark } from "../ui/kit"; + +interface Props { + email: string; + welcome: "new" | "back" | null; + onCreated: (storefront: StorefrontResult) => void; + onAlreadyOwns: () => void; + onSignedOut: () => void; +} + +export default function CreateStorefront({ email, welcome, onCreated, onAlreadyOwns, onSignedOut }: Props) { + const [name, setName] = useState(""); + const [busy, setBusy] = useState(false); + const [alreadyOwns, setAlreadyOwns] = useState(false); + const [error, setError] = useState(null); + + async function signOut() { + await logout(); + onSignedOut(); + } + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setBusy(true); + setError(null); + const res = await createStorefront(name); + setBusy(false); + if (res.ok) { + onCreated(res.storefront); + return; + } + if (res.error.code === "already_owns_storefront") setAlreadyOwns(true); + else setError(res.error.message); + } + + return ( + + } right={} /> +
+ +
+
+ One storefront, fully yours +

Create your storefront

+

+ This is the one thing to do right now. It costs nothing and commits you to nothing. +

+
+ {welcome && ( + + {welcome === "new" + ? "A new account was created for this email." + : "Signed in to your existing account."} + + )} + {alreadyOwns && ( + + ecomm is one storefront per account today.{" "} + + + )} + {error && {error} Try again.} + setName(ev.target.value), + }} + /> +
+ {busy ? "Creating…" : "Create storefront"} +

You can rename, configure, or close your storefront whenever you like.

+
+ +
+
+