{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "booking15",
  "title": "Booking Confirmation Receipt",
  "description": "Post-booking success screen with animated check icon, confirmation number, booking summary (service, date/time, location, total), scannable QR code ticket, action buttons (calendar, receipt, directions), and email confirmation note.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "lucide-react"
  ],
  "registryDependencies": [
    "avatar",
    "badge",
    "button"
  ],
  "files": [
    {
      "path": "registry/booking15/booking15.tsx",
      "content": "\"use client\";\n\nimport { Avatar, AvatarImage } from \"@/components/ui/avatar\";\nimport {\n  CalendarDays,\n  CalendarPlus,\n  Check,\n  Clock,\n  CreditCard,\n  Mail,\n  MapPin,\n  Navigation,\n  ShieldCheck,\n  Ticket,\n  User,\n} from \"lucide-react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport type { LucideIcon } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ntype DetailIconKey = \"service\" | \"date\" | \"time\" | \"location\" | \"guest\" | \"price\";\n\ninterface BookingDetail {\n  key: DetailIconKey;\n  label: string;\n  value: string;\n  subValue?: string;\n}\n\ninterface Booking15Action {\n  icon: LucideIcon;\n  label: string;\n  href?: string;\n}\n\ninterface Booking15Labels {\n  confirmationPrefix?: string;\n  scanLabel?: string;\n  emailSentTemplate?: string;\n  policyLabel?: string;\n}\n\ninterface Booking15Props {\n  badge?: {\n    label: string;\n    variant?: \"default\" | \"secondary\" | \"outline\";\n  };\n  heading?: string;\n  description?: string;\n  confirmationNumber?: string;\n  details?: BookingDetail[];\n  provider?: {\n    name: string;\n    title: string;\n    avatar: string;\n  };\n  actions?: Booking15Action[];\n  email?: string;\n  policyNote?: string;\n  labels?: Booking15Labels;\n  className?: string;\n}\n\nconst detailIcons: Record<DetailIconKey, LucideIcon> = {\n  service: Ticket,\n  date: CalendarDays,\n  time: Clock,\n  location: MapPin,\n  guest: User,\n  price: CreditCard,\n};\n\n// Deterministic 21×21 QR-like pattern seeded by the confirmation number.\n// Generates a visually convincing QR code (with the three finder squares)\n// without pulling in a real QR library. Perfect for static marketing previews.\nfunction generateQRPattern(seed: string, size = 21): boolean[][] {\n  const cells: boolean[][] = Array.from({ length: size }, () =>\n    Array.from({ length: size }, () => false)\n  );\n\n  const setCell = (r: number, c: number, value: boolean) => {\n    const row = cells[r];\n    if (row) row[c] = value;\n  };\n\n  let hash = 0x9e3779b9;\n  for (let i = 0; i < seed.length; i++) {\n    hash = ((hash << 5) - hash + seed.charCodeAt(i)) | 0;\n  }\n\n  for (let r = 0; r < size; r++) {\n    for (let c = 0; c < size; c++) {\n      hash = (Math.imul(hash, 1_103_515_245) + 12_345) & 0x7f_ff_ff_ff;\n      setCell(r, c, (hash & 1) === 1);\n    }\n  }\n\n  // Blank an 8×8 quiet zone around each finder square.\n  const clearZone = (startRow: number, startCol: number) => {\n    for (let r = 0; r < 8; r++) {\n      for (let c = 0; c < 8; c++) {\n        const rr = startRow + r;\n        const cc = startCol + c;\n        if (rr >= 0 && rr < size && cc >= 0 && cc < size) {\n          setCell(rr, cc, false);\n        }\n      }\n    }\n  };\n  clearZone(0, 0);\n  clearZone(0, size - 8);\n  clearZone(size - 8, 0);\n\n  // Draw the three finder squares (outer 7×7 ring + inner 3×3 block).\n  const drawFinder = (startRow: number, startCol: number) => {\n    for (let r = 0; r < 7; r++) {\n      for (let c = 0; c < 7; c++) {\n        const rr = startRow + r;\n        const cc = startCol + c;\n        if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue;\n        const isOuter = r === 0 || r === 6 || c === 0 || c === 6;\n        const isInner = r >= 2 && r <= 4 && c >= 2 && c <= 4;\n        setCell(rr, cc, isOuter || isInner);\n      }\n    }\n  };\n  drawFinder(0, 0);\n  drawFinder(0, size - 7);\n  drawFinder(size - 7, 0);\n\n  return cells;\n}\n\nfunction formatFutureDate(offsetDays: number) {\n  const d = new Date();\n  d.setDate(d.getDate() + offsetDays);\n  return d.toLocaleDateString(\"en-US\", {\n    weekday: \"long\",\n    month: \"long\",\n    day: \"numeric\",\n    year: \"numeric\",\n  });\n}\n\n// NOTE: In production, pass `details` and `confirmationNumber` as static\n// strings read from your booking API response. The demo factory derives a\n// future date from today so the preview stays relevant at any viewing time.\nfunction createBooking15Demo(): Booking15Props {\n  return {\n    badge: { label: \"Confirmed\", variant: \"secondary\" },\n    heading: \"You're all <strong>set</strong>\",\n    description: \"Your booking is confirmed. Show this receipt at check-in or save it for later.\",\n    confirmationNumber: \"BK-2026-0482\",\n    details: [\n      {\n        key: \"service\",\n        label: \"Service\",\n        value: \"Deep Tissue Massage\",\n        subValue: \"60 min session\",\n      },\n      {\n        key: \"date\",\n        label: \"Date\",\n        value: formatFutureDate(4),\n      },\n      {\n        key: \"time\",\n        label: \"Time\",\n        value: \"14:00 — 15:00\",\n      },\n      {\n        key: \"location\",\n        label: \"Location\",\n        value: \"Serenity Spa\",\n        subValue: \"42 Wellness Ave, San Francisco\",\n      },\n      {\n        key: \"guest\",\n        label: \"Guest\",\n        value: \"Alex Morgan\",\n        subValue: \"alex@beste.co\",\n      },\n      {\n        key: \"price\",\n        label: \"Total paid\",\n        value: \"$120.40\",\n        subValue: \"Visa •••• 4242\",\n      },\n    ],\n    provider: {\n      name: \"Dr. Emily Carter\",\n      title: \"Licensed Massage Therapist\",\n      avatar: \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop\",\n    },\n    actions: [\n      { icon: CalendarPlus, label: \"Add to calendar\", href: \"https://beste.co\" },\n      { icon: Navigation, label: \"Get directions\", href: \"https://beste.co\" },\n    ],\n    email: \"alex@beste.co\",\n    policyNote:\n      \"Free cancellation up to 24 hours before your appointment. Late cancellations may incur a 50% charge.\",\n    labels: {\n      confirmationPrefix: \"Confirmation\",\n      scanLabel: \"Scan at check-in\",\n      emailSentTemplate: \"Confirmation emailed to {email}\",\n      policyLabel: \"Cancellation policy\",\n    },\n  };\n}\n\nexport const booking15Demo: Booking15Props = createBooking15Demo();\n\nexport function Booking15({\n  badge,\n  heading,\n  description,\n  confirmationNumber,\n  details = [],\n  provider,\n  actions = [],\n  email,\n  policyNote,\n  labels = {},\n  className,\n}: Booking15Props) {\n  const { confirmationPrefix, scanLabel, emailSentTemplate, policyLabel } = labels;\n\n  const qrCells = generateQRPattern(confirmationNumber ?? \"BK-DEMO\");\n  const qrSize = qrCells.length;\n  const emailSentText = email ? emailSentTemplate?.replace(\"{email}\", email) : undefined;\n\n  return (\n    <section className={cn(\"py-16 md:py-24 w-full\", className)}>\n      <div className=\"mx-auto max-w-lg px-4 md:px-6\">\n        {(badge || heading || description) && (\n          <div className=\"mx-auto mb-12 max-w-3xl text-center\">\n            {badge && (\n              <div className=\"mb-4 flex justify-center\">\n                <Badge variant={badge.variant ?? \"default\"} className=\"gap-1\">\n                  <ShieldCheck className=\"size-4\" />\n                  {badge.label}\n                </Badge>\n              </div>\n            )}\n            {heading && (\n              <h2\n                className=\"text-2xl font-semibold md:text-4xl [&>strong]:font-bold [&>strong]:text-primary\"\n                dangerouslySetInnerHTML={{ __html: heading }}\n              />\n            )}\n            {description && (\n              <p className=\"mt-4 text-base text-muted-foreground md:text-lg\">{description}</p>\n            )}\n          </div>\n        )}\n\n        <div className=\"group/booking15 overflow-hidden rounded-md border bg-card shadow-sm\">\n          {/* Success header */}\n          <div className=\"flex flex-col items-center gap-3 border-b bg-emerald-500/5 px-5 py-8 text-center\">\n            <div className=\"relative flex size-16 items-center justify-center rounded-full bg-emerald-500/10\">\n              <span className=\"absolute inset-0 animate-ping rounded-full bg-emerald-500/20\" />\n              <Check className=\"relative size-8 text-emerald-500\" />\n            </div>\n            {confirmationNumber && (\n              <div>\n                <p className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground\">\n                  {confirmationPrefix}\n                </p>\n                <p className=\"mt-0.5 text-lg font-bold tracking-wider tabular-nums\">\n                  {confirmationNumber}\n                </p>\n              </div>\n            )}\n          </div>\n\n          {/* Details grid */}\n          {details.length > 0 && (\n            <div className=\"divide-y\">\n              {details.map((detail, i) => {\n                const Icon = detailIcons[detail.key];\n                return (\n                  <div key={i} className=\"flex items-start gap-3 px-5 py-4\">\n                    <div className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground\">\n                      <Icon className=\"size-5\" />\n                    </div>\n                    <div className=\"min-w-0 flex-1\">\n                      <p className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground\">\n                        {detail.label}\n                      </p>\n                      <p className=\"mt-0.5 text-base font-semibold\">{detail.value}</p>\n                      {detail.subValue && (\n                        <p className=\"mt-0.5 text-sm text-muted-foreground\">{detail.subValue}</p>\n                      )}\n                    </div>\n                  </div>\n                );\n              })}\n            </div>\n          )}\n\n          {/* Provider */}\n          {provider && (\n            <div className=\"flex items-center gap-3 border-t bg-muted/30 px-5 py-4\">\n              <Avatar className=\"size-11\">\n                <AvatarImage src={provider.avatar} alt={provider.name} className=\"object-cover\" />\n              </Avatar>\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"text-base font-semibold\">{provider.name}</p>\n                <p className=\"text-sm text-muted-foreground\">{provider.title}</p>\n              </div>\n            </div>\n          )}\n\n          {/* QR code */}\n          <div className=\"flex flex-col items-center gap-3 border-t bg-background px-5 py-6\">\n            <div className=\"relative rounded-md border-2 border-dashed bg-card p-4\">\n              <svg\n                viewBox={`0 0 ${qrSize} ${qrSize}`}\n                className=\"h-40 w-40 text-foreground\"\n                aria-label=\"Booking QR code\"\n                role=\"img\"\n              >\n                {qrCells.flatMap((row, r) =>\n                  row.map((filled, c) =>\n                    filled ? (\n                      <rect\n                        key={`${r}-${c}`}\n                        x={c}\n                        y={r}\n                        width={1}\n                        height={1}\n                        fill=\"currentColor\"\n                      />\n                    ) : null\n                  )\n                )}\n              </svg>\n            </div>\n            {scanLabel && <p className=\"text-sm font-medium text-muted-foreground\">{scanLabel}</p>}\n          </div>\n\n          {/* Actions */}\n          {actions.length > 0 && (\n            <div className=\"grid gap-2 border-t p-4 md:grid-cols-2\">\n              {actions.map((action, i) => {\n                const Icon = action.icon;\n                return (\n                  <Button\n                    key={i}\n                    variant=\"outline\"\n                    asChild={Boolean(action.href)}\n                    className=\"cursor-pointer\"\n                  >\n                    {action.href ? (\n                      <a href={action.href}>\n                        <Icon className=\"size-4\" />\n                        {action.label}\n                      </a>\n                    ) : (\n                      <>\n                        <Icon className=\"size-4\" />\n                        {action.label}\n                      </>\n                    )}\n                  </Button>\n                );\n              })}\n            </div>\n          )}\n\n          {/* Footer notes */}\n          {(emailSentText || policyNote) && (\n            <div className=\"space-y-3 border-t bg-muted/30 px-5 py-4\">\n              {emailSentText && (\n                <p className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n                  <Mail className=\"size-4 shrink-0\" />\n                  {emailSentText}\n                </p>\n              )}\n              {policyNote && (\n                <div className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n                  <ShieldCheck className=\"mt-0.5 size-4 shrink-0\" />\n                  <p>\n                    {policyLabel && (\n                      <span className=\"font-medium text-foreground\">{policyLabel}:</span>\n                    )}{\" \"}\n                    {policyNote}\n                  </p>\n                </div>\n              )}\n            </div>\n          )}\n        </div>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/beste/block/booking15.tsx"
    }
  ],
  "type": "registry:block"
}