# File request upload

> The public page of a file request link, where anyone with the link sends a file into a folder, with the link's password when it has one.

- Blocks: [drive](https://bettersupabase.com/blocks/drive.md), [share-links](https://bettersupabase.com/blocks/share-links.md)
- Kind: screen
- Data: loaded on the server through the block's client
- Page: https://bettersupabase.com/recipes/file-request-upload

## Install the blocks

```bash
npx better-supabase add drive share-links
```

## Web (Next.js)

Add the shadcn components:

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

From `apps/examples/nextjs/src/features/drive/components/file-request-upload.tsx` (https://github.com/ScaleDockHQ/better-supabase/blob/main/apps/examples/nextjs/src/features/drive/components/file-request-upload.tsx):

```tsx
/**
 * The public page of a file request link: pick a file and send it into the
 * folder, with the link's password when it has one. The sender never sees what
 * the folder holds.
 */
export function FileRequestUpload({ token }: { token: string }) {
  const t = useExtracted("drive");
  const fieldId = useId();
  const errorMessage = useErrorMessage();
  const [password, setPassword] = useState<"none" | "asked" | "wrong">("none");
  const [sent, setSent] = useState<string | null>(null);

  const form = useActionForm(sendToFileRequest, {
    resetOnSuccess: false,
    onSuccess: (result, element) => {
      if (result.passwordRequired) {
        setPassword((current) => (current === "none" ? "asked" : "wrong"));

        return;
      }

      element.reset();
      setSent(result.name);
    },
  });

  const error = form.error === undefined ? null : errorMessage(form.error);
  const locked = password !== "none";
  const wrongPassword = password === "wrong";

  return (
    <Card data-recipe="file-request-upload">
      <CardHeader>
        <CardTitle>{t("Send files")}</CardTitle>
        <CardDescription>
          {t("Files you send go straight to the folder's owner.")}
        </CardDescription>
      </CardHeader>
      <CardContent>
        {sent === null ? (
          <form {...form.formProps} className="flex flex-col gap-4">
            <input type="hidden" name="token" value={token} />
            <FieldGroup>
              <Field data-invalid={form.fieldErrors["file"] ? true : undefined}>
                <FieldLabel htmlFor={`${fieldId}-file`}>{t("File")}</FieldLabel>
                <Input
                  id={`${fieldId}-file`}
                  name="file"
                  type="file"
                  required
                />
                <FieldDescription>{t("Up to 1 MB per file.")}</FieldDescription>
              </Field>
              {locked ? (
                <Field data-invalid={wrongPassword ? true : undefined}>
                  <FieldLabel htmlFor={`${fieldId}-password`}>
                    {t("Password")}
                  </FieldLabel>
                  <Input
                    id={`${fieldId}-password`}
                    name="password"
                    type="password"
                    required
                    maxLength={200}
                    autoComplete="off"
                  />
                  {wrongPassword ? (
                    <FieldError>
                      {t(
                        "This link needs its password. Check it and try again.",
                      )}
                    </FieldError>
                  ) : null}
                </Field>
              ) : null}
              {error ? <FieldError>{error}</FieldError> : null}
            </FieldGroup>
            <div className="flex justify-end">
              <Button type="submit" disabled={form.pending}>
                {t("Send")}
              </Button>
            </div>
          </form>
        ) : (
          <Empty className="border">
            <EmptyHeader>
              <EmptyMedia variant="icon">
                <CircleCheckIcon />
              </EmptyMedia>
              <EmptyTitle>{t("File sent")}</EmptyTitle>
              <EmptyDescription>{sent}</EmptyDescription>
            </EmptyHeader>
            <EmptyContent>
              <Badge variant="success-light">{t("Received")}</Badge>
              <Button
                variant="outline"
                onClick={() => {
                  form.reset();
                  setSent(null);
                }}
              >
                {t("Send another file")}
              </Button>
            </EmptyContent>
          </Empty>
        )}
      </CardContent>
    </Card>
  );
}
```

## Prompt

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

### Web

```text
Build "File request upload" in a React web app that uses shadcn/ui components. The public page of a file request link, where anyone with the link sends a file into a folder, with the link's password when it has one.

It uses the better-supabase `drive` block (Drive) with `share-links`. It loads its data on the server through the block's client.

Steps:

1. If the app doesn't have the blocks yet, add the SQL with `npx better-supabase add drive share-links` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add badge button card empty field input`.
3. The recipe needs no glue: it calls the block's client on the server.
4. Write the screen. Load the data on the server with the block's client, starting from the recipe's snippet (`apps/examples/nextjs/src/features/drive/components/file-request-upload.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/file-request-upload.md
- Block docs: https://bettersupabase.com/docs/blocks/drive
```
