# Team picker

> A select of the caller's teams in the active organization, to scope a page or assign work.

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

## Install the block

```bash
npx better-supabase add teams
```

## Web (Next.js)

Add the shadcn components:

```bash
npx shadcn add button empty field input select
```

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

```tsx
/**
 * Scopes the page to one of the organization's teams through `?team=`.
 * With no teams yet, it offers to create one when the caller holds
 * `teams.manage`.
 */
export function TeamPicker({
  teams,
  teamId,
  canManage,
}: {
  teams: readonly TeamOption[];
  teamId: string | null;
  canManage: boolean;
}) {
  const t = useExtracted("work");
  const fieldId = useId();
  const router = useRouter();
  const pathname = usePathname();
  const errorMessage = useErrorMessage();
  const [switching, startTransition] = useTransition();

  const form = useActionForm(createTeam, {
    onSuccess: () => {
      toast.success(t("Team created"));
    },
  });

  if (teams.length === 0) {
    const error = form.error === undefined ? null : errorMessage(form.error);

    return (
      <Empty className="border" data-recipe="team-picker">
        <EmptyHeader>
          <EmptyMedia variant="icon">
            <UsersIcon />
          </EmptyMedia>
          <EmptyTitle>{t("No teams yet")}</EmptyTitle>
          <EmptyDescription>
            {canManage
              ? t("Create a team to group work and filter the board by it.")
              : t("An admin can create teams to group work.")}
          </EmptyDescription>
        </EmptyHeader>
        {canManage ? (
          <EmptyContent>
            <form {...form.formProps} className="flex w-full items-end gap-2">
              <Field data-invalid={error ? true : undefined}>
                <FieldLabel htmlFor={`${fieldId}-team-name`}>
                  {t("Team name")}
                </FieldLabel>
                <Input
                  id={`${fieldId}-team-name`}
                  name="name"
                  required
                  maxLength={80}
                  autoComplete="off"
                />
                {error ? <FieldError>{error}</FieldError> : null}
              </Field>
              <Button type="submit" disabled={form.pending}>
                {t("Create team")}
              </Button>
            </form>
          </EmptyContent>
        ) : null}
      </Empty>
    );
  }

  const items = [
    { value: ALL_TEAMS, label: t("All teams") },
    ...teams.map((team) => ({ value: team.id, label: team.name })),
  ];

  return (
    <div className="flex items-center gap-2" data-recipe="team-picker">
      <span className="text-sm text-muted-foreground" id={`${fieldId}-label`}>
        {t("Team")}
      </span>
      <Select
        value={teamId ?? ALL_TEAMS}
        items={items}
        disabled={switching}
        onValueChange={(value) => {
          const next = value ?? ALL_TEAMS;

          startTransition(() => {
            router.replace(
              next === ALL_TEAMS
                ? pathname
                : `${pathname}?team=${encodeURIComponent(next)}`,
            );
          });
        }}
      >
        <SelectTrigger className="w-48" aria-labelledby={`${fieldId}-label`}>
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          {items.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}
```

## Prompt

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

### Web

```text
Build "Team picker" in a React web app that uses shadcn/ui components. A select of the caller's teams in the active organization, to scope a page or assign work.

It uses the better-supabase `teams` block (Teams). 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 teams` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add button empty field input select`.
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/work/components/team-picker.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/team-picker.md
- Block docs: https://bettersupabase.com/docs/blocks/teams
```
