# Waitlist signup

> An email form that joins the waitlist and shows the position in line.

- Block: [waitlist](https://bettersupabase.com/blocks/waitlist.md)
- Kind: component
- Data: loaded on the server through the block's client
- Page: https://bettersupabase.com/recipes/waitlist-signup

## Install the block

```bash
npx better-supabase add waitlist
```

## Web (Next.js)

Add the shadcn components:

```bash
npx shadcn add badge button card field input
```

From `apps/examples/nextjs/src/features/waitlist/components/waitlist-signup.tsx` (https://github.com/ScaleDockHQ/better-supabase/blob/main/apps/examples/nextjs/src/features/waitlist/components/waitlist-signup.tsx):

```tsx
/**
 * Joins the waitlist with an email address and shows the place in line.
 * Joining again with the same address shows the same place.
 */
export function WaitlistSignup() {
  const t = useExtracted("waitlist");
  const fieldId = useId();
  const errorMessage = useErrorMessage();
  const form = useActionForm(joinWaitlist);

  const error =
    form.error === undefined
      ? null
      : form.error.kind === "validation"
        ? t("Enter a valid email address.")
        : errorMessage(form.error);

  if (form.data) {
    const { position, status } = form.data;

    return (
      <Card data-recipe="waitlist-signup">
        <CardHeader className="text-center">
          <ListOrderedIcon
            aria-hidden="true"
            className="mx-auto size-8 text-primary"
          />
          <CardTitle>
            {status === "waiting" && position !== null
              ? t("You're number {position} in line", {
                  position: String(position),
                })
              : t("You're on the list")}
          </CardTitle>
          <CardDescription>
            {status === "waiting"
              ? t("We'll email you when your spot opens up.")
              : t("Your spot is open. Create your account to get started.")}
          </CardDescription>
        </CardHeader>
        <CardContent className="flex flex-col items-center gap-4">
          <Badge
            variant={status === "waiting" ? "info-light" : "success-light"}
          >
            {status === "waiting" ? t("Waiting") : t("Approved")}
          </Badge>
          {status === "waiting" ? (
            <Button
              type="button"
              variant="outline"
              onClick={() => {
                form.reset();
              }}
            >
              {t("Add another address")}
            </Button>
          ) : (
            <Link href="/signup" className={buttonVariants()}>
              {t("Create your account")}
            </Link>
          )}
        </CardContent>
      </Card>
    );
  }

  return (
    <Card data-recipe="waitlist-signup">
      <CardHeader className="text-center">
        <CardTitle>{t("Join the waitlist")}</CardTitle>
        <CardDescription>
          {t("Acme Cloud opens to new teams in batches. Save your spot.")}
        </CardDescription>
      </CardHeader>
      <CardContent>
        <form {...form.formProps}>
          <FieldGroup>
            <Field data-invalid={error ? true : undefined}>
              <FieldLabel htmlFor={`${fieldId}-email`}>{t("Email")}</FieldLabel>
              <Input
                id={`${fieldId}-email`}
                name="email"
                type="email"
                autoComplete="email"
                required
                maxLength={320}
                aria-invalid={error ? true : undefined}
              />
              {error ? <FieldError>{error}</FieldError> : null}
            </Field>
            <Button type="submit" className="w-full" disabled={form.pending}>
              {t("Join the waitlist")}
            </Button>
            <p className="text-center text-sm text-muted-foreground">
              {t("Already have an account?")}{" "}
              <Link
                href="/login"
                className="text-foreground underline underline-offset-4"
              >
                {t("Sign in")}
              </Link>
            </p>
          </FieldGroup>
        </form>
      </CardContent>
    </Card>
  );
}
```

## Prompt

Paste this into a coding agent to build the recipe in your app.

### Web

```text
Build "Waitlist signup" in a React web app that uses shadcn/ui components. An email form that joins the waitlist and shows the position in line.

It uses the better-supabase `waitlist` block (Waitlist and invite codes). It loads its data on the server through the block's client.

Steps:

1. If the app doesn't have the block yet, add the SQL with `npx better-supabase add waitlist` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add badge button card field input`.
3. The recipe needs no glue: it calls the block's client on the server.
4. Write the component. Load the data on the server with the block's client, starting from the recipe's snippet (`apps/examples/nextjs/src/features/waitlist/components/waitlist-signup.tsx`). Adapt it to the app's existing components, styling and data loading instead of copying it as is.
5. Check the result against the recipe and the block's docs: it typechecks, it reads the same block data, and it handles the loading and empty states the snippet handles.

Read the full code first:

- Recipe: https://bettersupabase.com/recipes/waitlist-signup.md
- Block docs: https://bettersupabase.com/docs/blocks/waitlist
```
