# Case detail

> One case with its severity, root cause, what still blocks closing it and its actions, and a Close button once nothing blocks it.

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

## Install the block

```bash
npx better-supabase add cases
```

## Web (Next.js)

Add the shadcn components:

```bash
npx shadcn add alert badge button empty @reui/frame
```

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

```tsx
/**
 * One case with what still stops it from closing and its actions. Close is
 * enabled once nothing blocks it; the database checks again when it runs.
 */
export function CaseDetail({
  detail,
  canClose,
}: {
  detail: CaseDetailView;
  canClose: boolean;
}) {
  const t = useExtracted("cases");
  const format = useFormatter();
  const errorMessage = useErrorMessage();
  const blocker = useBlockerLabel();
  const actionType = useActionTypeLabel();
  const severity = useSeverityLabel();

  const close = useAction(closeCase, {
    onSuccess: ({ number }) => {
      toast.success(t("Closed {number}", { number }));
    },
    onError: (error) => {
      toast.error(errorMessage(error));
    },
  });

  const open = detail.status === "open";

  return (
    <Frame spacing="sm" data-recipe="case-detail">
      <FrameHeader className="gap-0.5">
        <FrameTitle>
          {t("{number}: {title}", {
            number: detail.number,
            title: detail.title,
          })}
        </FrameTitle>
        <FrameDescription>
          {detail.stage === null
            ? detail.kind
            : t("{kind} in {stage}", {
                kind: detail.kind,
                stage: detail.stage,
              })}
        </FrameDescription>
      </FrameHeader>
      <FramePanel className="flex flex-col gap-4">
        <div className="flex flex-wrap items-center gap-2">
          {detail.severity === null ? null : (
            <Badge variant={severityVariant(detail.severity)}>
              {severity(detail.severity)}
            </Badge>
          )}
          {detail.effectivenessDueOn === null ? null : (
            <Badge variant="info-light">
              {t("Effectiveness check due {date}", {
                date: format.dateTime(new Date(detail.effectivenessDueOn), {
                  dateStyle: "medium",
                  timeZone: "UTC",
                }),
              })}
            </Badge>
          )}
        </div>
        <p className="text-sm">
          {detail.rootCause ?? (
            <span className="text-muted-foreground">
              {t("No root cause recorded yet.")}
            </span>
          )}
        </p>
        {open && detail.blockers.length > 0 ? (
          <Alert variant="warning">
            <CircleDotIcon />
            <AlertTitle>{t("Before this case can close")}</AlertTitle>
            <AlertDescription>
              <ul className="flex list-disc flex-col gap-1 pl-4">
                {detail.blockers.map((code) => (
                  <li key={code}>{blocker(code)}</li>
                ))}
              </ul>
            </AlertDescription>
          </Alert>
        ) : open ? (
          <Alert variant="success">
            <CircleCheckIcon />
            <AlertTitle>{t("Nothing blocks closing this case")}</AlertTitle>
          </Alert>
        ) : null}
        {detail.actions.length === 0 ? (
          <Empty>
            <EmptyHeader>
              <EmptyMedia variant="icon">
                <ListChecksIcon />
              </EmptyMedia>
              <EmptyTitle>{t("No actions yet")}</EmptyTitle>
              <EmptyDescription>
                {t(
                  "Containment and corrective actions are tasks; add them from the case's work items.",
                )}
              </EmptyDescription>
            </EmptyHeader>
          </Empty>
        ) : (
          <ul className="flex flex-col gap-2">
            {detail.actions.map((action) => (
              <li
                key={action.id}
                className="flex items-center justify-between gap-2 text-sm"
              >
                <span className="flex items-center gap-2">
                  <Badge variant="outline">{actionType(action.type)}</Badge>
                  {action.title}
                </span>
                {action.done ? (
                  <Badge variant="success-light">{t("Done")}</Badge>
                ) : (
                  <Badge variant="secondary">
                    {action.required ? t("Open, required") : t("Open")}
                  </Badge>
                )}
              </li>
            ))}
          </ul>
        )}
      </FramePanel>
      {open && canClose ? (
        <FrameFooter>
          <Button
            type="button"
            size="sm"
            disabled={detail.blockers.length > 0 || close.pending}
            onClick={() => {
              void close.run({ caseId: detail.id });
            }}
          >
            {t("Close case")}
          </Button>
        </FrameFooter>
      ) : null}
    </Frame>
  );
}
```

## Prompt

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

### Web

```text
Build "Case detail" in a React web app that uses shadcn/ui components. One case with its severity, root cause, what still blocks closing it and its actions, and a Close button once nothing blocks it.

It uses the better-supabase `cases` block (Cases). 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 cases` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add alert badge button empty @reui/frame`.
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/cases/components/case-detail.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/case-detail.md
- Block docs: https://bettersupabase.com/docs/blocks/cases
```
