# Shipment tracking

> Shipments with their status and the tracking timeline of the selected one, newest event first.

Source: https://bettersupabase.com/docs/registry/ui-recipes/shipment-tracking

{/* Generated by scripts/build-recipes.ts from the example apps. Edit the examples or the script, then run `pnpm recipes`. */}

It loads its data on the server through the block's client; the [shipments block](https://bettersupabase.com/blocks/shipments) must be installed (`npx better-supabase add shipments`). Its page on bettersupabase.com is [/recipes/shipment-tracking](https://bettersupabase.com/recipes/shipment-tracking).

Web, with shadcn components:

```bash
npx shadcn add alert badge button card dialog empty field input skeleton table
```

```tsx title="apps/examples/nextjs/src/features/field/components/shipment-tracking.tsx"
/**
 * Render inside `<Suspense>`: the latest shipments with their status, and the
 * tracking timeline of the selected one, newest event first. `?shipment=<id>`
 * picks the shipment.
 */
export async function ShipmentTracking({
  searchParams,
}: {
  searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
  const [session, t, format, query] = await Promise.all([
    getSession(),
    getExtracted("field"),
    getFormatter(),
    searchParams,
    connection(),
  ]);

  if (!can(session, "shipments.read")) {
    forbidden();
  }

  const picked = v.safeParse(ShipmentParam, query["shipment"]);

  const tracking = await settle(() =>
    loadShipmentTracking(picked.success ? picked.output : undefined),
  );

  if (tracking.kind === "no-organization") {
    return null;
  }

  const newShipment = can(session, "shipments.manage") ? (
    <NewShipmentDialog />
  ) : null;

  const selected = tracking.kind === "ok" ? tracking.data.selected : null;

  return (
    <div data-recipe="shipment-tracking" className="flex flex-col gap-6">
      <Card>
        <CardHeader>
          <CardTitle>{t("Shipments")}</CardTitle>
          <CardDescription>
            {t("The status follows the latest tracking event.")}
          </CardDescription>
          {newShipment ? <CardAction>{newShipment}</CardAction> : null}
        </CardHeader>
        <CardContent>
          {tracking.kind === "error" ? (
            <FieldLoadError title={t("The shipments didn't load")} />
          ) : tracking.data.shipments.length === 0 ? (
            <Empty className="border">
              <EmptyHeader>
                <EmptyMedia variant="icon">
                  <PackageIcon />
                </EmptyMedia>
                <EmptyTitle>{t("No shipments yet")}</EmptyTitle>
                <EmptyDescription>
                  {t("Create a shipment, then buy its label from a carrier.")}
                </EmptyDescription>
              </EmptyHeader>
              {newShipment ? <EmptyContent>{newShipment}</EmptyContent> : null}
            </Empty>
          ) : (
            <Table>
              <TableHeader>
                <TableRow>
                  <TableHead>{t("Reference")}</TableHead>
                  <TableHead>{t("Recipient")}</TableHead>
                  <TableHead>{t("Destination")}</TableHead>
                  <TableHead>{t("Tracking number")}</TableHead>
                  <TableHead>{t("Status")}</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {tracking.data.shipments.map((shipment) => (
                  <TableRow
                    key={shipment.id}
                    data-state={
                      shipment.id === selected?.id ? "selected" : undefined
                    }
                  >
                    <TableCell>
                      <Link
                        href={`/field/shipments?shipment=${shipment.id}`}
                        className="text-sm font-medium underline-offset-4 transition-colors hover:underline"
                        aria-current={
                          shipment.id === selected?.id ? "true" : undefined
                        }
                      >
                        {shipment.reference}
                      </Link>
                    </TableCell>
                    <TableCell className="text-sm">
                      {shipment.recipient}
                    </TableCell>
                    <TableCell className="text-sm text-muted-foreground">
                      {shipment.destination}
                    </TableCell>
                    <TableCell className="text-sm tabular-nums">
                      {shipment.trackingNumber ?? ""}
                    </TableCell>
                    <TableCell>
                      <ShipmentStatusBadge status={shipment.status} />
                    </TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          )}
        </CardContent>
      </Card>
      {tracking.kind === "ok" && selected ? (
        <Card>
          <CardHeader>
            <CardTitle>
              {t("Tracking for {reference}", { reference: selected.reference })}
            </CardTitle>
            <CardDescription>
              {selected.carrier
                ? t("Events from {carrier}, newest first.", {
                    carrier: selected.carrier,
                  })
                : t("Events show once the shipment has a label.")}
            </CardDescription>
          </CardHeader>
          <CardContent>
            {tracking.data.timeline.length === 0 ? (
              <Empty className="border">
                <EmptyHeader>
                  <EmptyMedia variant="icon">
                    <TruckIcon />
                  </EmptyMedia>
                  <EmptyTitle>{t("No tracking events yet")}</EmptyTitle>
                  <EmptyDescription>
                    {t(
                      "The carrier's webhook adds events as the package moves.",
                    )}
                  </EmptyDescription>
                </EmptyHeader>
                <EmptyContent>
                  <Link
                    href={`/field/shipments?shipment=${selected.id}`}
                    className="text-sm font-medium underline-offset-4 transition-colors hover:underline"
                  >
                    {t("Check again")}
                  </Link>
                </EmptyContent>
              </Empty>
            ) : (
              <ol className="flex flex-col divide-y">
                {tracking.data.timeline.map((step) => (
                  <li
                    key={step.id}
                    className="flex flex-wrap items-center gap-3 py-3"
                  >
                    <ShipmentStatusBadge status={step.status} />
                    <div className="flex min-w-0 flex-1 flex-col gap-0.5">
                      <span className="text-sm">
                        {step.description ?? step.location ?? ""}
                      </span>
                      {step.description && step.location ? (
                        <span className="text-xs text-muted-foreground">
                          {step.location}
                        </span>
                      ) : null}
                    </div>
                    <span className="text-xs text-muted-foreground tabular-nums">
                      {format.dateTime(new Date(step.occurredAt), {
                        dateStyle: "medium",
                        timeStyle: "short",
                      })}
                    </span>
                  </li>
                ))}
              </ol>
            )}
          </CardContent>
        </Card>
      ) : null}
    </div>
  );
}
```