zebflow 0.7.9

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
import { useState } from "zeb";
import ChromeHeader from "@/pages/home/components/chrome-header";
import Button from "@/components/ui/button";
import Card from "@/components/ui/card";
import CardContent from "@/components/ui/card-content";
import CardDescription from "@/components/ui/card-description";
import CardTitle from "@/components/ui/card-title";
import Field from "@/components/ui/field";
import Input from "@/components/ui/input";

export const page = {
  html: {
    lang: "en",
  },
  body: {
    className: "min-h-screen bg-zinc-50 text-gray-900 font-sans",
  },
  navigation: "history",
};

export function getPage(input) {
  return {
    head: {
      title: input?.seo?.title ?? "Profile",
      description: input?.seo?.description ?? "User profile",
    },
  };
}

export default function Page(input) {
  const user = input?.user ?? {};
  const initialIdentity = input?.effective_git_identity ?? {};
  const [gitName, setGitName] = useState(user?.git_name ?? "");
  const [gitEmail, setGitEmail] = useState(user?.git_email ?? "");
  const [identity, setIdentity] = useState(initialIdentity);
  const [busy, setBusy] = useState(false);
  const [message, setMessage] = useState("");
  const [tone, setTone] = useState("muted");
  const api = input?.profile_api ?? "/api/profile";

  const save = async (event) => {
    event.preventDefault();
    setBusy(true);
    setMessage("");
    setTone("muted");
    try {
      const response = await fetch(api, {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
        },
        body: JSON.stringify({
          git_name: gitName,
          git_email: gitEmail,
        }),
      });
      if (response.status === 401) {
        window.location.href = "/login";
        return;
      }
      const data = await response.json().catch(() => ({}));
      if (!response.ok || data?.ok === false) {
        const err = data?.error?.message ?? data?.error ?? "Failed to save profile";
        setMessage(String(err));
        setTone("error");
        return;
      }
      setIdentity(data?.effective_git_identity ?? {});
      setMessage("Profile saved.");
      setTone("ok");
    } catch (_) {
      setMessage("Failed to save profile.");
      setTone("error");
    } finally {
      setBusy(false);
    }
  };

  const messageClass =
    tone === "ok" ? "text-emerald-700" : tone === "error" ? "text-red-600" : "text-gray-500";
  const source = identity?.source === "user_profile" ? "User profile" : "Generated fallback";

  return (
    <>
      <ChromeHeader />
      <main className="pb-16 pt-24">
        <section className="mx-auto max-w-3xl px-6">
          <header className="mb-8 border-b border-gray-200 pb-4">
            <h1 className="text-3xl font-black tracking-tighter text-gray-900">Profile</h1>
            <p className="mt-2 text-sm text-gray-500">
              Signed in as <span className="font-medium text-gray-700">{input?.owner}</span>.
            </p>
            {input?.app_version ? (
              <p className="mt-1 text-[0.7rem] tracking-wide text-gray-400">v{input.app_version}</p>
            ) : null}
          </header>

          <div className="grid gap-5 md:grid-cols-[1.15fr_0.85fr]">
            <Card>
              <CardContent className="py-5">
                <CardTitle className="text-lg">Git identity</CardTitle>
                <CardDescription className="mt-1">
                  Used for commits, sync rebase, and project history authored from this login.
                </CardDescription>
                <form className="mt-5 flex flex-col gap-4" onSubmit={save}>
                  <Field label="Committer name" id="profile-git-name">
                    <Input
                      id="profile-git-name"
                      value={gitName}
                      onInput={(event) => setGitName(event.currentTarget.value)}
                      placeholder="Your Name"
                      autoComplete="name"
                    />
                  </Field>
                  <Field label="Committer email" id="profile-git-email">
                    <Input
                      id="profile-git-email"
                      type="email"
                      value={gitEmail}
                      onInput={(event) => setGitEmail(event.currentTarget.value)}
                      placeholder="you@example.com"
                      autoComplete="email"
                    />
                  </Field>
                  <div className="flex flex-wrap items-center gap-3 pt-1">
                    <Button type="submit" variant="primary" disabled={busy}>
                      {busy ? "Saving..." : "Save profile"}
                    </Button>
                    <Button as="a" href="/home" variant="outline">
                      Back to home
                    </Button>
                    {message ? <span className={`text-sm ${messageClass}`}>{message}</span> : null}
                  </div>
                </form>
              </CardContent>
            </Card>

            <Card>
              <CardContent className="py-5">
                <CardTitle className="text-lg">Effective identity</CardTitle>
                <div className="mt-4 space-y-3 text-sm">
                  <div>
                    <div className="text-xs font-medium uppercase tracking-wide text-gray-400">Name</div>
                    <div className="mt-1 break-words font-medium text-gray-900">{identity?.name ?? ""}</div>
                  </div>
                  <div>
                    <div className="text-xs font-medium uppercase tracking-wide text-gray-400">Email</div>
                    <div className="mt-1 break-words font-medium text-gray-900">{identity?.email ?? ""}</div>
                  </div>
                  <div>
                    <div className="text-xs font-medium uppercase tracking-wide text-gray-400">Source</div>
                    <div className="mt-1 font-medium text-gray-900">{source}</div>
                  </div>
                </div>
              </CardContent>
            </Card>
          </div>
        </section>
      </main>
    </>
  );
}