Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 2x 23x 19x 19x | import { Link } from "@tanstack/react-router";
import type { PublishReadiness } from "../../hooks/queries/usePublishReadiness";
import { MARKETPLACE_URL } from "../../lib/env";
import { Button } from "../ui/button";
import { Card } from "../ui/card";
import { Separator } from "../ui/separator";
import { buildPublishPhases, PhaseRail } from "./PhaseRail";
/**
* Today, first run. Plan ยง1.3; `.context/mockups/today-first-run.html`.
*
* ONE BAND, ONE ACTION, AND AN HONEST STATEMENT OF WHAT IT UNLOCKS.
*
* What this replaces, exactly: four zeros, an empty list, a 0% checklist of
* six chores, and an amber "pending review" banner whose only message was
* "Contact admin for more details." Every one of those is gone.
*
* The banner is gone because **there is no review**. A pro who clears the bar
* is published automatically, by the server, with no human in the loop
* (`maybeAutoPublishPro`). The banner was not merely unhelpful โ it was false.
*
* WHY THE COPY IS ALLOWED TO PROMISE THAT
* "goes live the moment it is saved" is only honest because the bar is
* objective and the transition is automatic. The promise and the phase rail
* under it therefore read from the same server response that gates the
* publish, so the sentence and the state can never drift apart.
*
* `photoMinimum` COMES FROM THE SERVER, IN THE PROSE TOO
* "One room with three photos" is not a sentence with a 3 in it โ it is
* `photoMinimum` spelled out. Change the bar in
* `apps/api/src/lib/publish-readiness.ts` and this paragraph changes with it.
*
* TYPE
* Display MD (`clamp(1.375rem, 2.2vw, 1.75rem)` โ `text-2xl sm:text-3xl`, the
* nearest steps on the token scale) is the biggest ramp the shell allows, and
* an empty state is one of the only two places it is allowed. Display LG and
* XL stay out of the shell entirely; auth gets the rest.
*/
/** `three` reads better than `3` in prose; past five, print the figure. */
const SPELLED = ["zero", "one", "two", "three", "four", "five"] as const;
export function spellMinimum(count: number): string {
return SPELLED[count] ?? String(count);
}
export interface FirstRunCardProps {
readiness: PublishReadiness | null | undefined;
}
export function FirstRunCard({ readiness }: FirstRunCardProps) {
const minimum = readiness?.photoMinimum;
return (
<Card className="max-w-3xl">
<div className="px-6 pt-10 pb-7 sm:px-7">
<h2 className="text-2xl font-bold tracking-tight text-foreground-default sm:text-3xl">
Add your first project
</h2>
<p className="mt-3 max-w-[62ch] text-base text-foreground-muted">
{minimum === undefined
? "One room with a few photos is all it takes."
: `One room with ${spellMinimum(minimum)} photos is all it takes.`}{" "}
Your studio goes live on the marketplace, your own website and search{" "}
<strong className="font-semibold text-foreground-default">
the moment it is saved
</strong>{" "}
— no waiting, no approval. About four minutes.
</p>
<div className="mt-6 flex flex-wrap items-center gap-2.5">
<Button asChild>
<Link to="/projects">Add a project</Link>
</Button>
<Button asChild variant="outline">
{/*
* An example of the thing being asked for, on the surface it
* will appear on. There is no example project inside the
* portal to point at, and inventing one would be a fixture
* pretending to be the product.
*/}
<a
href={`${MARKETPLACE_URL}/projects`}
target="_blank"
rel="noopener noreferrer"
>
See an example <span aria-hidden="true">▸</span>
<span className="sr-only">(opens the public marketplace)</span>
</a>
</Button>
</div>
<Separator className="my-6" />
<PhaseRail phases={buildPublishPhases(readiness)} />
</div>
</Card>
);
}
|