# Booking 01

> Book a blood test: pick a date, a morning slot, and a clinic, then confirm.

A shadcn/ui block from the [@predict](https://predict.designali.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/predict/booking-01).

- Page, with a live preview: https://shadcn.exchange/r/predict/booking-01
- Screenshot: https://shadcn.exchange/shot/predict/booking-01/light.png
- Registry item: https://predict.designali.com/r/booking-01.json
- Categories: booking
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/predict/booking-01.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Files

### `registry/predict/blocks/booking-01/page.tsx`

```tsx
"use client"

import * as React from "react"
import { CheckIcon, MapPinIcon } from "lucide-react"

import { Button } from "@workspace/ui/components/button"
import { Calendar } from "@workspace/ui/components/calendar"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@workspace/ui/components/card"
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@workspace/ui/components/dialog"
import { RadioGroup, RadioGroupItem } from "@workspace/ui/components/radio-group"
import { Separator } from "@workspace/ui/components/separator"

// Blood draws need a fast, so only morning slots.
const slots = ["07:00", "07:30", "08:00", "08:30", "09:00", "09:30", "10:00", "10:30"]

const clinics = [
  { value: "andheri", name: "Andheri West", address: "Link Road, Mumbai" },
  { value: "bandra", name: "Bandra", address: "Hill Road, Mumbai" },
  { value: "home", name: "Home visit", address: "A phlebotomist comes to you" },
]

function tomorrow() {
  const date = new Date()
  date.setDate(date.getDate() + 1)
  return date
}

export default function Page() {
  const [date, setDate] = React.useState<Date | undefined>(tomorrow)
  const [slot, setSlot] = React.useState("08:00")
  const [clinic, setClinic] = React.useState("andheri")

  const clinicName = clinics.find((c) => c.value === clinic)?.name
  const dateLabel = date?.toLocaleDateString(undefined, {
    weekday: "long",
    day: "numeric",
    month: "long",
  })

  return (
    <div className="flex min-h-svh items-center justify-center p-4">
      <Card className="w-full max-w-3xl">
        <CardHeader>
          <CardTitle className="text-base">Book your blood test</CardTitle>
          <CardDescription>Fast for 10 to 12 hours before your slot. Water is fine.</CardDescription>
        </CardHeader>

        <CardContent className="grid gap-6 md:grid-cols-[auto_1fr]">
          <Calendar
            mode="single"
            selected={date}
            onSelect={setDate}
            disabled={{ before: tomorrow() }}
            className="mx-auto rounded-lg border"
          />

          <div className="flex flex-col gap-6">
            <div className="flex flex-col gap-2">
              <h2 id="slot-label" className="text-xs font-medium">Time</h2>
              <RadioGroup
                value={slot}
                onValueChange={(value) => setSlot(value as string)}
                aria-labelledby="slot-label"
                className="grid grid-cols-4 gap-2"
              >
                {slots.map((time) => (
                  <label
                    key={time}
                    className="flex cursor-pointer items-center justify-center rounded-full border py-2 text-xs tabular-nums has-data-checked:border-primary has-data-checked:bg-primary has-data-checked:text-primary-foreground"
                  >
                    <RadioGroupItem value={time} className="sr-only" />
                    {time}
                  </label>
                ))}
              </RadioGroup>
            </div>

            <div className="flex flex-col gap-2">
              <h2 id="clinic-label" className="text-xs font-medium">Where</h2>
              <RadioGroup
                value={clinic}
                onValueChange={(value) => setClinic(value as string)}
                aria-labelledby="clinic-label"
              >
                {clinics.map((option) => (
                  <label
                    key={option.value}
                    className="flex items-start gap-3 rounded-lg border p-3 has-data-checked:border-primary"
                  >
                    <RadioGroupItem value={option.value} className="mt-0.5" />
                    <span className="grid gap-0.5">
                      <span className="text-sm font-medium">{option.name}</span>
                      <span className="text-xs text-muted-foreground">{option.address}</span>
                    </span>
                  </label>
                ))}
              </RadioGroup>
            </div>
          </div>
        </CardContent>

        <Separator />

        <CardFooter className="flex-col items-stretch gap-3 sm:flex-row sm:items-center sm:justify-between">
          <p className="flex items-center gap-2 text-xs text-muted-foreground">
            <MapPinIcon className="size-3.5 shrink-0" />
            {date ? `${dateLabel} at ${slot}, ${clinicName}` : "Pick a date"}
          </p>
          <Dialog>
            <DialogTrigger render={<Button disabled={!date} />}>Confirm booking</DialogTrigger>
            <DialogContent showCloseButton={false}>
              <DialogHeader>
                <div className="mb-2 flex size-10 items-center justify-center rounded-full bg-status-normal/15 text-status-normal">
                  <CheckIcon className="size-5" />
                </div>
                <DialogTitle>You're booked</DialogTitle>
                <DialogDescription>
                  {dateLabel} at {slot}, {clinicName}. We'll send a reminder the evening before.
                </DialogDescription>
              </DialogHeader>
              <DialogFooter>
                <DialogClose render={<Button />}>Done</DialogClose>
              </DialogFooter>
            </DialogContent>
          </Dialog>
        </CardFooter>
      </Card>
    </div>
  )
}
```
