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 + ); }