diff --git a/app/page.tsx b/app/page.tsx
index ab99a5b..3d1b436 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
+import { createPortal } from "react-dom";
import dynamic from "next/dynamic";
import { CHAIN_OPTIONS, type EnvKey } from "@/lib/config";
@@ -53,6 +54,27 @@ const INITIAL_ENV: Record = {
NEXT_PUBLIC_SITE_URL: "",
};
+// Walkthrough shown in the "Connect Vercel" dialog. Drop the screenshots into
+// public/vercel-guide/ with these exact names. The red highlight is already
+// baked into these screenshots, so no `highlight` overlay is set here.
+const VERCEL_GUIDE_STEPS: GuideStep[] = [
+ {
+ image: "/vercel-guide/step-1-add-integration.png",
+ alt: "Vercel integration page with the Add Integration button highlighted",
+ caption: 'Click "Add Integration"',
+ },
+ {
+ image: "/vercel-guide/step-2-select-team-install.png",
+ alt: "Vercel install dialog with the team selector highlighted",
+ caption: 'Select your Vercel team, then click "Install"',
+ },
+ {
+ image: "/vercel-guide/step-3-configure.png",
+ alt: "Vercel installation overview with the Configure button highlighted",
+ caption: 'Click "Configure" to come back and finish setup',
+ },
+];
+
// Dev-only screen preview (?screen=review): seeds the state a screen needs
// so it can be styled without going through OAuth. No flows are simulated.
const previewTarget =
@@ -288,6 +310,18 @@ export default function Home() {
useEffect(() => {
if (previewTarget || screen !== "building" || !deployment) return;
+ // Tolerate transient failures, but give up after ~30s of consecutive
+ // ones (expired session, deleted deployment) instead of spinning forever.
+ let failures = 0;
+ const stall = () => {
+ failures += 1;
+ if (failures >= 10) {
+ setError(
+ "Lost track of the build — open the build logs on Vercel to follow it.",
+ );
+ setScreen("error");
+ }
+ };
const poll = async () => {
try {
const res = await fetch("/api/status", {
@@ -295,16 +329,23 @@ export default function Home() {
body: JSON.stringify({ deploymentId: deployment.uid }),
});
const data = await res.json();
- if (data.deployment) {
- setDeployment(data.deployment);
- if (data.deployment.readyState === "READY") setScreen("done");
- if (data.deployment.readyState === "ERROR") {
- setError("Build failed. Check the inspector for details.");
- setScreen("error");
- }
+ if (!res.ok || !data.deployment) {
+ stall();
+ return;
+ }
+ failures = 0;
+ setDeployment(data.deployment);
+ if (data.deployment.readyState === "READY") setScreen("done");
+ if (data.deployment.readyState === "ERROR") {
+ setError("Build failed. Check the inspector for details.");
+ setScreen("error");
+ }
+ if (data.deployment.readyState === "CANCELED") {
+ setError("Build was canceled. Check the inspector for details.");
+ setScreen("error");
}
} catch {
- // transient network error — keep polling
+ stall();
}
};
pollRef.current = setInterval(poll, 3000);
@@ -365,7 +406,8 @@ export default function Home() {
installHref="/api/vercel/install"
questionNumber={1}
question="First, connect Vercel"
- hint="We deploy your site to your own Vercel account."
+ hint="Connection 1 of 2 — you'll connect GitHub next. We deploy your site to your own Vercel account."
+ steps={VERCEL_GUIDE_STEPS}
/>
)}
{screen === "github" && (
@@ -375,7 +417,8 @@ export default function Home() {
installHref="/api/github/install"
questionNumber={me?.vercel.connected ? 2 : 1}
question="Now, connect GitHub"
- hint="So we can fork the template into your account."
+ hint="Connection 2 of 2 — the last one. So we can fork the template into your account."
+ confirmHint={'On GitHub, click the green "Authorize" button to continue.'}
/>
)}
{screen === "vercel-bridge" && (
@@ -653,8 +696,9 @@ function WelcomeScreen({
>
Builder DAO
{" "}
- front-ends. Connect your accounts, point us at your DAO, and we ship
- a fully-configured site to your Vercel — no code, no copy-paste.
+ front-ends. Connect your Vercel and GitHub accounts, point us at your
+ DAO, and we ship a fully-configured site to your Vercel — no code, no
+ copy-paste.
@@ -697,6 +741,61 @@ function Step({
);
}
+// A single step shown inside the connect dialog. `image` is a path under
+// /public; `highlight` draws a red box over the screenshot (percent values so
+// it scales with the image). Tweak the highlight numbers after dropping in the
+// real screenshots — no need to re-edit the image itself.
+type GuideStep = {
+ image: string;
+ alt: string;
+ caption: string;
+ highlight?: { top: string; left: string; width: string; height: string };
+};
+
+function GuideImage({
+ src,
+ alt,
+ highlight,
+}: {
+ src: string;
+ alt: string;
+ highlight?: GuideStep["highlight"];
+}) {
+ const [failed, setFailed] = useState(false);
+ return (
+
);
}
@@ -1306,8 +1560,8 @@ function QuestionHeader({
return (
{number !== undefined && (
-
- {number} →
+
+ Step {number}
)}
diff --git a/public/vercel-guide/README.md b/public/vercel-guide/README.md
new file mode 100644
index 0000000..2f61347
--- /dev/null
+++ b/public/vercel-guide/README.md
@@ -0,0 +1,23 @@
+# Vercel connect walkthrough screenshots
+
+These images show up inside the **Connect Vercel** dialog (the popup that
+appears when you click the "Connect Vercel" card).
+
+Save the three screenshots with these exact names:
+
+| File | What it shows |
+| --------------------------------- | ---------------------------------------------------------------- |
+| `step-1-add-integration.png` | The integration page with the **Add Integration** button |
+| `step-2-select-team-install.png` | The install dialog with the **team selector** / Install button |
+| `step-3-configure.png` | The installation overview with the **Configure** button |
+
+Notes:
+
+- These screenshots already have the red highlight box drawn in, so the code
+ does **not** add an overlay (`VERCEL_GUIDE_STEPS` in `app/page.tsx` has no
+ `highlight` field). If you swap in clean screenshots later, you can add a
+ `highlight` with percent coordinates to draw the box in code instead.
+- Images are shown at a 16:10 ratio with `object-cover`. Crop roughly to that
+ shape so nothing important gets clipped.
+- Until a file exists, the dialog shows a "Screenshot pending" placeholder in
+ its slot — the rest of the flow still works.
diff --git a/public/vercel-guide/step-1-add-integration.png b/public/vercel-guide/step-1-add-integration.png
new file mode 100644
index 0000000..bd924fc
Binary files /dev/null and b/public/vercel-guide/step-1-add-integration.png differ
diff --git a/public/vercel-guide/step-2-select-team-install.png b/public/vercel-guide/step-2-select-team-install.png
new file mode 100644
index 0000000..0abc216
Binary files /dev/null and b/public/vercel-guide/step-2-select-team-install.png differ
diff --git a/public/vercel-guide/step-3-configure.png b/public/vercel-guide/step-3-configure.png
new file mode 100644
index 0000000..fe5f143
Binary files /dev/null and b/public/vercel-guide/step-3-configure.png differ