From 2ff71d74bb522e950785b55cc6befddc3850032c Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 19 Apr 2026 16:43:41 -0400 Subject: [PATCH] refactor(web): align auth surfaces with the install wizard Extract the wizard's INPUT_CLASS, PRIMARY_BUTTON_CLASS, SECONDARY_BUTTON_CLASS, and ErrorMessage into apps/fabro-web/app/components/ui.tsx so auth-login, setup, and the install wizard share one source of truth. - auth-login: raise the heading to text-2xl, swap white-on-teal for navy-on-teal, replace the bordered dev-token input with the outline pattern, use the ErrorMessage pill for invalid tokens, associate the input with a label, and shrink the GitHub mark to size-4 per the icons guideline - setup: replace the nested bg-overlay cards with a numbered
    matching the wizard's welcome layout, raise the heading, switch the primary button to navy-on-teal - install-app: re-import the shared primitives instead of holding local duplicates Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/fabro-web/app/components/ui.tsx | 23 +++++ apps/fabro-web/app/install-app.tsx | 25 ++--- apps/fabro-web/app/routes/auth-login.tsx | 117 ++++++++++++++--------- apps/fabro-web/app/routes/setup.tsx | 91 +++++++++++------- 4 files changed, 158 insertions(+), 98 deletions(-) create mode 100644 apps/fabro-web/app/components/ui.tsx diff --git a/apps/fabro-web/app/components/ui.tsx b/apps/fabro-web/app/components/ui.tsx new file mode 100644 index 000000000..d50ff61cf --- /dev/null +++ b/apps/fabro-web/app/components/ui.tsx @@ -0,0 +1,23 @@ +// Shared UI primitives. The install wizard set the visual baseline; this file +// exposes the primary button, secondary button, input, and inline error +// treatments so the auth and in-app surfaces can match. + +export const INPUT_CLASS = + "block w-full rounded-lg bg-panel-alt px-3.5 py-2.5 text-base text-fg outline-1 -outline-offset-1 outline-white/10 placeholder:text-fg-muted focus:outline-2 focus:-outline-offset-1 focus:outline-teal-500 sm:text-sm"; + +export const PRIMARY_BUTTON_CLASS = + "inline-flex items-center justify-center gap-2 rounded-lg bg-teal-500 px-4 py-2 text-sm font-medium text-navy-950 transition-colors hover:bg-teal-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-500 disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-teal-500"; + +export const SECONDARY_BUTTON_CLASS = + "inline-flex items-center justify-center gap-2 rounded-lg bg-transparent px-3.5 py-2 text-sm font-medium text-fg-2 outline-1 -outline-offset-1 outline-white/10 hover:bg-overlay hover:text-fg focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-teal-500"; + +export function ErrorMessage({ message }: { message: string }) { + return ( +

    + {message} +

    + ); +} diff --git a/apps/fabro-web/app/install-app.tsx b/apps/fabro-web/app/install-app.tsx index 1b625ceaa..240f90de7 100644 --- a/apps/fabro-web/app/install-app.tsx +++ b/apps/fabro-web/app/install-app.tsx @@ -37,6 +37,12 @@ import { consumeInstallTokenFromUrl, shouldConsumeInstallGithubErrorForPath, } from "./mode"; +import { + ErrorMessage, + INPUT_CLASS, + PRIMARY_BUTTON_CLASS, + SECONDARY_BUTTON_CLASS, +} from "./components/ui"; const INSTALL_STEPS = [ { id: "welcome", label: "Welcome", href: "/install/welcome" }, @@ -1416,17 +1422,6 @@ function Spinner({ className = "" }: { className?: string }) { ); } -function ErrorMessage({ message }: { message: string }) { - return ( -

    - {message} -

    - ); -} - function defaultProviderSelection(): ProviderSelection { return Object.fromEntries( INSTALL_PROVIDERS.map((provider) => [provider.id, { apiKey: "" }]), @@ -1509,11 +1504,3 @@ function submitGithubManifest( form.submit(); } -const INPUT_CLASS = - "block w-full rounded-lg bg-panel-alt px-3.5 py-2.5 text-base text-fg outline-1 -outline-offset-1 outline-white/10 placeholder:text-fg-muted focus:outline-2 focus:-outline-offset-1 focus:outline-teal-500 sm:text-sm"; - -const PRIMARY_BUTTON_CLASS = - "inline-flex items-center justify-center gap-2 rounded-lg bg-teal-500 px-4 py-2 text-sm font-medium text-navy-950 transition-colors hover:bg-teal-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-500 disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-teal-500"; - -const SECONDARY_BUTTON_CLASS = - "inline-flex items-center justify-center gap-2 rounded-lg bg-transparent px-3.5 py-2 text-sm font-medium text-fg-2 outline-1 -outline-offset-1 outline-white/10 hover:bg-overlay hover:text-fg focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-teal-500"; diff --git a/apps/fabro-web/app/routes/auth-login.tsx b/apps/fabro-web/app/routes/auth-login.tsx index e3f0ad278..09096f73a 100644 --- a/apps/fabro-web/app/routes/auth-login.tsx +++ b/apps/fabro-web/app/routes/auth-login.tsx @@ -1,6 +1,11 @@ import { useState } from "react"; import { useNavigate } from "react-router"; import { AuthLayout } from "../components/auth-layout"; +import { + ErrorMessage, + INPUT_CLASS, + PRIMARY_BUTTON_CLASS, +} from "../components/ui"; import { getAuthConfig, loginDevToken } from "../api"; export async function loader() { @@ -23,16 +28,16 @@ export default function AuthLogin({ loaderData }: any) { await loginDevToken(token); navigate("/runs"); } catch { - setError("Invalid dev token"); + setError("Invalid dev token."); } } return ( -

    +

    Sign in to Fabro

    -

    +

    {hasGitHub ? "Authenticate with your GitHub account to continue." : "Paste your dev token to continue."} @@ -41,7 +46,7 @@ export default function AuthLogin({ loaderData }: any) { {hasGitHub ? ( Sign in with GitHub @@ -55,34 +60,65 @@ export default function AuthLogin({ loaderData }: any) { onSubmit={handleSubmit} /> ) : hasDevToken ? ( -

    - setToken(event.target.value)} - placeholder="fabro_dev_..." - className="w-full rounded-lg border border-line-strong bg-panel px-4 py-2.5 text-sm text-fg outline-none focus:border-teal-500" - /> - -

    - Paste the dev token from your terminal or{" "} - cat ~/.fabro/dev-token -

    - {error ? ( -

    {error}

    - ) : null} - + ) : null}
    ); } +function DevTokenForm({ + token, + setToken, + error, + onSubmit, + showLocation = false, +}: { + token: string; + setToken: (v: string) => void; + error: string | null; + onSubmit: (e: React.FormEvent) => void; + showLocation?: boolean; +}) { + return ( +
    +
    + + setToken(event.target.value)} + placeholder="fabro_dev_…" + className={`${INPUT_CLASS} font-mono`} + autoComplete="off" + autoCapitalize="off" + spellCheck={false} + /> +
    + {error ? : null} + + {showLocation ? ( +

    + Paste the dev token from your terminal or{" "} + cat ~/.fabro/dev-token. +

    + ) : null} + + ); +} + function DevTokenCollapsible({ token, setToken, @@ -101,7 +137,7 @@ function DevTokenCollapsible({ {open ? ( -
    - setToken(event.target.value)} - placeholder="fabro_dev_..." - className="w-full rounded-lg border border-line-strong bg-panel px-4 py-2.5 text-sm text-fg outline-none focus:border-teal-500" +
    + - - {error ? ( -

    {error}

    - ) : null} - +
    ) : null} ); @@ -146,7 +173,7 @@ function DevTokenCollapsible({ function GitHubMark() { return ( - + ); diff --git a/apps/fabro-web/app/routes/setup.tsx b/apps/fabro-web/app/routes/setup.tsx index a159ddb76..816ac9af9 100644 --- a/apps/fabro-web/app/routes/setup.tsx +++ b/apps/fabro-web/app/routes/setup.tsx @@ -1,45 +1,68 @@ import { AuthLayout } from "../components/auth-layout"; +import { PRIMARY_BUTTON_CLASS } from "../components/ui"; + +const steps = [ + { + title: "Open a terminal on the server host", + body: ( +

    + Run{" "} + fabro install on the same + host that runs the Fabro server. +

    + ), + }, + { + title: "Choose GitHub App setup", + body: ( +

    + The CLI opens GitHub, exchanges the manifest code, and writes the + required settings and secrets locally. +

    + ), + }, + { + title: "Restart the server, then return to sign in", + body: ( +

    + Once the server comes back up, you can authenticate from the browser. +

    + ), + }, +]; export default function Setup() { return ( -

    +

    Set up Fabro

    -

    - Run the installer on the same host that runs the Fabro server to - register a GitHub App and write local configuration. +

    + Run the installer on the server host to register a GitHub App and write + local configuration.

    -
    -
    -

    - 1. Open a terminal on the server host -

    -
    -            fabro install
    -          
    -
    -
    -

    - 2. Choose GitHub App setup -

    -

    - The CLI opens GitHub, exchanges the manifest code, and writes the - required settings and secrets locally. -

    -
    -
    -

    - 3. Restart the server, then return to sign in -

    -
    - Continue to sign in - -
    -
    +
      + {steps.map((step, index) => ( +
    1. + +
      +

      {step.title}

      +
      {step.body}
      +
      +
    2. + ))} +
    + + Continue to sign in + ); }