Sign-up buttons say Sign up everywhere; only the sign-up page says registration takes an invite
The landing page, the header, login, a missed page, the invite page, the docs header, settings and support no longer talk about requesting access or being invite-only. The sign-up page (and the GitHub sign-up step that is part of it) still explains invites and the request form. The deploy smoke test checks the landing page by its headline instead.
11 files+27−440/11 viewed
| 1 | 1 | --- | |
| 2 | 2 | // The header's right side: where people go from the docs, then the source. | |
| 3 | 3 | import { GitBranch } from '@lucide/astro'; | |
| 4 | − | ||
| 5 | − | // g1t.sh is invite-only for now. A build with PUBLIC_G1T_REGISTRATION=open | |
| 6 | − | // (an installation that lets anyone register) says Sign up again. | |
| 7 | − | const inviteOnly = (import.meta.env.PUBLIC_G1T_REGISTRATION ?? 'invite') !== 'open'; | |
| 8 | 4 | --- | |
| 9 | 5 | ||
| 10 | 6 | <nav class="g1t-nav" aria-label="g1t"> | |
| 11 | 7 | <a href="https://g1t.sh/">g1t.sh</a> | |
| 12 | − | <a href="https://g1t.sh/register" class="g1t-nav-cta">{inviteOnly ? 'Request access' : 'Sign up'}</a> | |
| 8 | + | <a href="https://g1t.sh/register" class="g1t-nav-cta">Sign up</a> | |
| 13 | 9 | <a href="https://g1t.sh/flagon-io/g1t" aria-label="Source on g1t" title="Source on g1t" class="g1t-nav-icon"> | |
| 14 | 10 | <GitBranch /> | |
| 15 | 11 | </a> |
| 71 | 71 | <section id="invites" className="scroll-mt-20"> | |
| 72 | 72 | <p className="text-sm text-muted"> | |
| 73 | 73 | {overview.mode === "invite" | |
| 74 | − | ? "g1t is invite-only for now. Each invite lets one person make an account. " | |
| 74 | + | ? "Each invite lets one person make an account. " | |
| 75 | 75 | : "Anyone can make an account, but an invite still says who sent it. "} | |
| 76 | 76 | A revoked or expired invite that was never used comes back to you. | |
| 77 | 77 | </p> |
| 8 | 8 | import { DeployArt, HeroArt, Live, PlanArt, QueueArt, TeamArt, WhyArt } from "./art"; | |
| 9 | 9 | import { ProviderMark } from "./model-providers"; | |
| 10 | 10 | import { ButtonLink, CopyLine } from "./ui"; | |
| 11 | − | import { HAVE_AN_INVITE } from "../lib/invites"; | |
| 12 | − | import { useInviteOnly } from "../lib/registration"; | |
| 13 | 11 | ||
| 14 | 12 | const DOCS = "https://docs.g1t.sh"; | |
| 15 | 13 | ||
| ⋯ | |||
| 213 | 211 | * It lists no one's repositories; those are a click away under Explore. | |
| 214 | 212 | */ | |
| 215 | 213 | export function Landing() { | |
| 216 | − | const inviteOnly = useInviteOnly(); | |
| 217 | 214 | return ( | |
| 218 | 215 | <main className="overflow-x-clip"> | |
| 219 | 216 | {/* The opening. */} | |
| ⋯ | |||
| 244 | 241 | </p> | |
| 245 | 242 | <div className="mt-9 flex animate-fade-up flex-wrap items-center justify-center gap-3"> | |
| 246 | 243 | <ButtonLink to="/register" variant="primary" large> | |
| 247 | − | {inviteOnly ? "Request access" : "Start for free"} | |
| 244 | + | Start for free | |
| 248 | 245 | <ArrowRight size={15} /> | |
| 249 | 246 | </ButtonLink> | |
| 250 | − | {inviteOnly ? ( | |
| 251 | − | <ButtonLink to={HAVE_AN_INVITE} variant="quiet" large> | |
| 252 | − | Have an invite? | |
| 253 | − | </ButtonLink> | |
| 254 | − | ) : ( | |
| 255 | − | <ButtonLink to="#how" variant="quiet" large> | |
| 256 | − | See how it works | |
| 257 | − | </ButtonLink> | |
| 258 | − | )} | |
| 247 | + | <ButtonLink to="#how" variant="quiet" large> | |
| 248 | + | See how it works | |
| 249 | + | </ButtonLink> | |
| 259 | 250 | </div> | |
| 260 | 251 | <p className="mx-auto mt-5 max-w-xl animate-fade-up text-sm text-fg-soft/60 text-balance"> | |
| 261 | − | {inviteOnly | |
| 262 | − | ? "g1t is invite-only while we open it up. The forge is free, with no card; agents and checks start with a trial after a card check, and deployments come with the g1t plan." | |
| 263 | − | : "The forge is free, with no card. Agents and checks start with a trial after a card check; deployments come with the g1t plan."} | |
| 252 | + | The forge is free, with no card. Agents and checks start with a trial after a card check; | |
| 253 | + | deployments come with the g1t plan. | |
| 264 | 254 | </p> | |
| 265 | 255 | </div> | |
| 266 | 256 | <Live className="relative mx-auto max-w-6xl px-2 pt-6 pb-2 sm:px-4"> | |
| ⋯ | |||
| 626 | 616 | </p> | |
| 627 | 617 | <div className="mt-8 flex flex-wrap justify-center gap-3"> | |
| 628 | 618 | <ButtonLink to="/register" variant="primary" large> | |
| 629 | − | {inviteOnly ? "Request access" : "Create your workspace"} | |
| 619 | + | Create your workspace | |
| 630 | 620 | <ArrowRight size={15} /> | |
| 631 | 621 | </ButtonLink> | |
| 632 | − | {inviteOnly && ( | |
| 633 | − | <ButtonLink to={HAVE_AN_INVITE} variant="quiet" large> | |
| 634 | − | Have an invite? | |
| 635 | − | </ButtonLink> | |
| 636 | − | )} | |
| 637 | 622 | <ButtonLink to={`${DOCS}/quickstart/`} variant="quiet" large> | |
| 638 | 623 | Read the quickstart | |
| 639 | 624 | </ButtonLink> | |
| 26 | 26 | const CODE = "g1t-k7m2-q9xd-4hpw-abcd-0123-4567-89ef-ghjk"; | |
| 27 | 27 | ||
| 28 | 28 | test("while invite-only, nobody is offered a plain sign-up", () => { | |
| 29 | − | assert.deepEqual(signUpCopy(true), { primary: "Request access", secondary: "Have an invite?" }); | |
| 30 | − | assert.deepEqual(signUpCopy(false), { primary: "Sign up", secondary: null }); | |
| 29 | + | // Sign up everywhere; only the sign-up page says registration takes an invite. | |
| 30 | + | assert.deepEqual(signUpCopy(), { primary: "Sign up", secondary: null }); | |
| 31 | 31 | assert.equal(HAVE_AN_INVITE, "/register#invite"); | |
| 32 | 32 | }); | |
| 33 | 33 |
| 16 | 16 | return `mailto:${INVITES_CONTACT}?subject=${encodeURIComponent(subject)}`; | |
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | − | /** What the sign-up buttons say. While invite-only, nobody can just sign up. */ | |
| 20 | − | export function signUpCopy(inviteOnly: boolean): { primary: string; secondary: string | null } { | |
| 21 | − | return inviteOnly ? { primary: "Request access", secondary: "Have an invite?" } : { primary: "Sign up", secondary: null }; | |
| 19 | + | /** | |
| 20 | + | * What the sign-up buttons say: Sign up, whether or not registration is | |
| 21 | + | * invite-only. Only the sign-up page itself says how to get in (an invite, | |
| 22 | + | * or a request for one), so nothing else reads as a waiting room. | |
| 23 | + | */ | |
| 24 | + | export function signUpCopy(): { primary: string; secondary: string | null } { | |
| 25 | + | return { primary: "Sign up", secondary: null }; | |
| 22 | 26 | } | |
| 23 | 27 | ||
| 24 | 28 | /** The /register address that opens on the invite-code field. */ |
| 12 | 12 | return root?.inviteOnly ?? true; | |
| 13 | 13 | } | |
| 14 | 14 | ||
| 15 | − | /** What the sign-up buttons say on this page. */ | |
| 15 | + | /** What the sign-up buttons say on this page: Sign up, in every registration mode. */ | |
| 16 | 16 | export function useSignUpCopy() { | |
| 17 | − | return signUpCopy(useInviteOnly()); | |
| 17 | + | return signUpCopy(); | |
| 18 | 18 | } |
| 85 | 85 | export async function loader({ context, params, request }: Route.LoaderArgs) { | |
| 86 | 86 | const user = getViewer(context); | |
| 87 | 87 | const chosen = readCookie(request.headers.get("cookie"), WORKSPACE_COOKIE); | |
| 88 | − | // What sign-up buttons say: Request access while g1t is invite-only. | |
| 88 | + | // Whether sign-up takes an invite: only the sign-up page says so. | |
| 89 | 89 | const [shell, mode] = await Promise.all([ | |
| 90 | 90 | // An account still confirming its address sees only the pages that | |
| 91 | 91 | // allows (lib/confirm-gate.ts), in the visitor's frame. | |
| ⋯ | |||
| 271 | 271 | { label: "Sign up", to: "/register", icon: <Plus size={15} /> }, | |
| 272 | 272 | ]; | |
| 273 | 273 | ||
| 274 | − | /** Sign up, or Request access while g1t is invite-only. */ | |
| 274 | + | /** Sign up: the sign-up page says whether it takes an invite. */ | |
| 275 | 275 | function SignUpButton() { | |
| 276 | 276 | const copy = useSignUpCopy(); | |
| 277 | 277 | return <ButtonLink to="/register">{copy.primary}</ButtonLink>; | |
| 182 | 182 | signingUp ? "Make your account below and you get" : "Accepting gives you" | |
| 183 | 183 | } the ${invite.repository.role} role on ${invite.repository.name}.`; | |
| 184 | 184 | } | |
| 185 | − | return "g1t is where people and agents ship software together: plan in issues, assign work to agents like teammates, and land it through checks that hold. It is invite-only for now; this invite gets you in."; | |
| 185 | + | return "g1t is where people and agents ship software together: plan in issues, assign work to agents like teammates, and land it through checks that hold. This invite gets you in."; | |
| 186 | 186 | } | |
| 187 | 187 | ||
| 188 | 188 | /** What accepting is called on its button. */ | |
| ⋯ | |||
| 424 | 424 | </div> | |
| 425 | 425 | )} | |
| 426 | 426 | <div className="mt-8 flex flex-wrap gap-3"> | |
| 427 | − | <ButtonLink to="/register#request">Request access</ButtonLink> | |
| 427 | + | <ButtonLink to="/register#request">Sign up</ButtonLink> | |
| 428 | 428 | <ButtonLink to={loaded.viewer ? "/" : "/login"} variant="quiet"> | |
| 429 | 429 | {loaded.viewer ? "Go to g1t" : "Sign in"} | |
| 430 | 430 | </ButtonLink> | |
| 2 | 2 | ||
| 3 | 3 | import type { Route } from "./+types/login"; | |
| 4 | 4 | import { page } from "../lib/meta"; | |
| 5 | − | import { useInviteOnly } from "../lib/registration"; | |
| 6 | 5 | import { AuthCard } from "../components/auth-card"; | |
| 7 | 6 | import { ContinueWithGithub, GithubMark, OrDivider } from "../components/github"; | |
| 8 | 7 | import { PENDING_COOKIE, TWO_FACTOR_COOKIE, TWO_FACTOR_SECONDS, cookie, readCookie } from "../lib/github"; | |
| ⋯ | |||
| 60 | 59 | } | |
| 61 | 60 | ||
| 62 | 61 | export default function Login({ loaderData, actionData }: Route.ComponentProps) { | |
| 63 | − | const inviteOnly = useInviteOnly(); | |
| 64 | 62 | const registerUrl = | |
| 65 | 63 | loaderData.next === "/" | |
| 66 | 64 | ? "/register" | |
| ⋯ | |||
| 73 | 71 | <> | |
| 74 | 72 | New to g1t?{" "} | |
| 75 | 73 | <Link to={registerUrl} className="text-fg underline underline-offset-4"> | |
| 76 | − | {inviteOnly ? "Request access or use an invite" : "Create an account"} | |
| 74 | + | Create an account | |
| 77 | 75 | </Link> | |
| 78 | 76 | </> | |
| 79 | 77 | } | |
| 38 | 38 | icon: <Ticket size={16} />, | |
| 39 | 39 | title: "Invites", | |
| 40 | 40 | address: CONTACT.support, | |
| 41 | − | about: "More invites for you or your workspace while g1t is invite-only. Say who you would like to bring.", | |
| 41 | + | about: "More invites for you or your workspace. Say who you would like to bring.", | |
| 42 | 42 | }, | |
| 43 | 43 | { | |
| 44 | 44 | icon: <CreditCard size={16} />, |
| 15 | 15 | ||
| 16 | 16 | /** The checks, in order. `body` is the page's text with tags removed. */ | |
| 17 | 17 | export const CHECKS = [ | |
| 18 | − | { name: "Landing page", path: "/", expect: ["Request access"] }, | |
| 18 | + | { name: "Landing page", path: "/", expect: ["Plan in issues"] }, | |
| 19 | 19 | { name: "Sign in", path: "/login", expect: ["Sign in"] }, | |
| 20 | 20 | { | |
| 21 | 21 | name: "Sign up: invite and waitlist", |