@fayz-ai/plugin-agenda 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/chunk-DRZF6CV2.js +421 -0
  2. package/dist/chunk-DRZF6CV2.js.map +1 -0
  3. package/dist/chunk-LFUELUIT.cjs +423 -0
  4. package/dist/chunk-LFUELUIT.cjs.map +1 -0
  5. package/dist/data/mock.d.ts.map +1 -1
  6. package/dist/index.cjs +300 -421
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +297 -420
  10. package/dist/index.js.map +1 -1
  11. package/dist/migrations/index.d.ts +2 -0
  12. package/dist/migrations/index.d.ts.map +1 -0
  13. package/dist/public/BookingWidget.d.ts +11 -0
  14. package/dist/public/BookingWidget.d.ts.map +1 -0
  15. package/dist/public/components/BrandHeader.d.ts +6 -0
  16. package/dist/public/components/BrandHeader.d.ts.map +1 -0
  17. package/dist/public/components/ServiceCard.d.ts +5 -0
  18. package/dist/public/components/ServiceCard.d.ts.map +1 -0
  19. package/dist/public/components/SidebarExtras.d.ts +4 -0
  20. package/dist/public/components/SidebarExtras.d.ts.map +1 -0
  21. package/dist/public/components/StepRailItem.d.ts +4 -0
  22. package/dist/public/components/StepRailItem.d.ts.map +1 -0
  23. package/dist/public/components/SuccessPanel.d.ts +4 -0
  24. package/dist/public/components/SuccessPanel.d.ts.map +1 -0
  25. package/dist/public/components/SummaryRail.d.ts +27 -0
  26. package/dist/public/components/SummaryRail.d.ts.map +1 -0
  27. package/dist/public/components/defaults.d.ts +3 -0
  28. package/dist/public/components/defaults.d.ts.map +1 -0
  29. package/dist/public/context.d.ts +51 -0
  30. package/dist/public/context.d.ts.map +1 -0
  31. package/dist/public/createPublicBookingPlugin.d.ts +62 -0
  32. package/dist/public/createPublicBookingPlugin.d.ts.map +1 -0
  33. package/dist/public/data.d.ts +37 -0
  34. package/dist/public/data.d.ts.map +1 -0
  35. package/dist/public/data.supabase.d.ts +7 -0
  36. package/dist/public/data.supabase.d.ts.map +1 -0
  37. package/dist/public/format.d.ts +18 -0
  38. package/dist/public/format.d.ts.map +1 -0
  39. package/dist/public/hooks.d.ts +34 -0
  40. package/dist/public/hooks.d.ts.map +1 -0
  41. package/dist/public/index.cjs +1423 -0
  42. package/dist/public/index.cjs.map +1 -0
  43. package/dist/public/index.d.ts +20 -0
  44. package/dist/public/index.d.ts.map +1 -0
  45. package/dist/public/index.js +1402 -0
  46. package/dist/public/index.js.map +1 -0
  47. package/dist/public/steps/ContactStep.d.ts +30 -0
  48. package/dist/public/steps/ContactStep.d.ts.map +1 -0
  49. package/dist/public/steps/DateTimeStep.d.ts +23 -0
  50. package/dist/public/steps/DateTimeStep.d.ts.map +1 -0
  51. package/dist/public/steps/OverviewStep.d.ts +16 -0
  52. package/dist/public/steps/OverviewStep.d.ts.map +1 -0
  53. package/dist/public/steps/PaymentStep.d.ts +20 -0
  54. package/dist/public/steps/PaymentStep.d.ts.map +1 -0
  55. package/dist/public/types.d.ts +185 -0
  56. package/dist/public/types.d.ts.map +1 -0
  57. package/package.json +20 -14
  58. package/src/data/mock.ts +4 -1
  59. package/src/index.ts +14 -0
  60. package/src/integrations/google-calendar/integration.test.ts +1 -1
  61. package/src/migrations/001_public_booking.sql +278 -0
  62. package/src/migrations/index.ts +282 -0
  63. package/src/public/BookingWidget.tsx +315 -0
  64. package/src/public/components/BrandHeader.tsx +27 -0
  65. package/src/public/components/ServiceCard.tsx +39 -0
  66. package/src/public/components/SidebarExtras.tsx +30 -0
  67. package/src/public/components/StepRailItem.tsx +27 -0
  68. package/src/public/components/SuccessPanel.tsx +27 -0
  69. package/src/public/components/SummaryRail.tsx +90 -0
  70. package/src/public/components/defaults.ts +14 -0
  71. package/src/public/context.tsx +100 -0
  72. package/src/public/createPublicBookingPlugin.ts +265 -0
  73. package/src/public/data.supabase.ts +94 -0
  74. package/src/public/data.ts +99 -0
  75. package/src/public/format.ts +61 -0
  76. package/src/public/hooks.ts +105 -0
  77. package/src/public/index.ts +68 -0
  78. package/src/public/steps/ContactStep.tsx +179 -0
  79. package/src/public/steps/DateTimeStep.tsx +138 -0
  80. package/src/public/steps/OverviewStep.tsx +81 -0
  81. package/src/public/steps/PaymentStep.tsx +180 -0
  82. package/src/public/types.ts +229 -0
@@ -0,0 +1,81 @@
1
+ import { Clock } from 'lucide-react'
2
+ import { DAY_NAMES, WEEK_ORDER } from '../format'
3
+ import type { BookingComponents, BookingFormatters, PublicBookingLabels, PublicService, ResolvedBookingBrand, WorkingHours } from '../types'
4
+
5
+ interface OverviewStepProps {
6
+ services: PublicService[]
7
+ servicesLoading: boolean
8
+ businessHours: WorkingHours
9
+ professionalName: string
10
+ labels: PublicBookingLabels
11
+ brand: ResolvedBookingBrand
12
+ fmt: BookingFormatters
13
+ components: BookingComponents
14
+ onStartBooking: (serviceId?: string) => void
15
+ }
16
+
17
+ /** Default view: services + opening hours + sidebar CTA (Quaddro-style). */
18
+ export function OverviewStep({
19
+ services, servicesLoading, businessHours, professionalName, labels, brand, fmt, components: C, onStartBooking,
20
+ }: OverviewStepProps) {
21
+ const openDays = new Set(businessHours.daysOfWeek)
22
+ return (
23
+ <div className="grid gap-6 lg:grid-cols-[1fr_18rem]">
24
+ <div className="space-y-6">
25
+ {/* Serviços */}
26
+ <section className="rounded-2xl border border-border bg-card p-6 shadow-sm">
27
+ <h3 className="mb-4 font-heading text-lg font-bold text-foreground">{labels.servicesHeading}</h3>
28
+ <div className="space-y-3">
29
+ {servicesLoading ? (
30
+ Array.from({ length: 2 }).map((_, i) => (
31
+ <div key={i} className="h-24 animate-pulse rounded-2xl bg-muted" />
32
+ ))
33
+ ) : (
34
+ services.map((s, i) => (
35
+ <C.ServiceCard
36
+ key={s.id}
37
+ service={s}
38
+ featured={i === 0}
39
+ onSelect={() => onStartBooking(s.id)}
40
+ fmt={fmt}
41
+ brand={brand}
42
+ />
43
+ ))
44
+ )}
45
+ </div>
46
+ </section>
47
+
48
+ {/* Sobre — horário de funcionamento */}
49
+ <section className="rounded-2xl border border-border bg-card p-6 shadow-sm">
50
+ <h3 className="font-heading text-lg font-bold text-foreground">{labels.aboutHeading}</h3>
51
+ <p className="mt-3 mb-2 flex items-center gap-1.5 text-sm font-medium text-muted-foreground">
52
+ <Clock className="h-4 w-4" /> {labels.openingHoursHeading}
53
+ </p>
54
+ <ul className="divide-y divide-border border-l-2 border-primary/20 pl-4">
55
+ {WEEK_ORDER.map((day) => {
56
+ const open = openDays.has(day)
57
+ return (
58
+ <li key={day} className="flex items-center justify-between py-2 text-sm">
59
+ <span className="text-foreground">{DAY_NAMES[day]}</span>
60
+ <span className={open ? 'tabular-nums text-foreground' : 'text-muted-foreground'}>
61
+ {open ? `${businessHours.start} – ${businessHours.end}` : labels.closedLabel}
62
+ </span>
63
+ </li>
64
+ )
65
+ })}
66
+ </ul>
67
+ </section>
68
+ </div>
69
+
70
+ {/* Sidebar CTA + highlights */}
71
+ <aside className="lg:sticky lg:top-24 lg:self-start">
72
+ <C.SidebarExtras
73
+ professionalName={professionalName}
74
+ brand={brand}
75
+ labels={labels}
76
+ onStartBooking={() => onStartBooking()}
77
+ />
78
+ </aside>
79
+ </div>
80
+ )
81
+ }
@@ -0,0 +1,180 @@
1
+ import { CalendarCheck, Check, Copy, CreditCard, Loader2, Plus, QrCode, ShieldCheck } from 'lucide-react'
2
+ import type { PixCharge } from '@fayz-ai/core'
3
+ import type { BookingFormatters, PublicService, ResolvedPayment } from '../types'
4
+
5
+ // Static, realistic-looking Pix payload for the POC copia-e-cola fallback
6
+ // (used only when no payment provider is injected).
7
+ const PIX_PAYLOAD =
8
+ '00020126840014br.gov.bcb.pix2562qrcode.fayz.ai/pix/v2/9d8f7a6b-agendamento5204000053039865802BR5904FAYZ6009SAO PAULO62070503***6304AB12'
9
+
10
+ /** Deterministic QR-looking placeholder rendered from a payload (POC visual). */
11
+ function QrPlaceholder({ data }: { data: string }) {
12
+ const size = 25
13
+ // Seed the data-cell pattern from the payload so different charges look different.
14
+ let seed = 0
15
+ for (let i = 0; i < data.length; i++) seed = (seed * 31 + data.charCodeAt(i)) % 100000
16
+ const cells: { x: number; y: number }[] = []
17
+ const isFinder = (x: number, y: number) => {
18
+ const inBox = (bx: number, by: number) => x >= bx && x < bx + 7 && y >= by && y < by + 7
19
+ return inBox(0, 0) || inBox(size - 7, 0) || inBox(0, size - 7)
20
+ }
21
+ for (let y = 0; y < size; y++) {
22
+ for (let x = 0; x < size; x++) {
23
+ if (isFinder(x, y)) {
24
+ const corner = x < 7 && y < 7 ? { bx: 0, by: 0 } : x >= size - 7 && y < 7 ? { bx: size - 7, by: 0 } : { bx: 0, by: size - 7 }
25
+ const lx = x - corner.bx
26
+ const ly = y - corner.by
27
+ const outer = lx === 0 || lx === 6 || ly === 0 || ly === 6
28
+ const inner = lx >= 2 && lx <= 4 && ly >= 2 && ly <= 4
29
+ if (outer || inner) cells.push({ x, y })
30
+ } else if ((x * 31 + y * 17 + x * y + seed) % 3 === 0) {
31
+ cells.push({ x, y })
32
+ }
33
+ }
34
+ }
35
+ return (
36
+ <svg viewBox={`0 0 ${size} ${size}`} className="h-40 w-40" role="img" aria-label="QR Code Pix (demonstração)">
37
+ <rect width={size} height={size} fill="white" />
38
+ {cells.map((c, i) => (
39
+ <rect key={i} x={c.x} y={c.y} width={1} height={1} fill="currentColor" className="text-foreground" />
40
+ ))}
41
+ </svg>
42
+ )
43
+ }
44
+
45
+ interface PaymentStepProps {
46
+ service: PublicService
47
+ payment: ResolvedPayment
48
+ paymentStep: 'choose' | 'pix'
49
+ charge: PixCharge | null
50
+ busy: boolean
51
+ error: Error | null
52
+ copied: boolean
53
+ fmt: BookingFormatters
54
+ onPayWithPix: (fee: number) => void
55
+ onCopy: () => void
56
+ onSimulatePaid: () => void
57
+ }
58
+
59
+ /** Payment step: order summary + Pix method → QR/copia-e-cola while polling. */
60
+ export function PaymentStep({
61
+ service, payment, paymentStep, charge, busy, error, copied, fmt, onPayWithPix, onCopy, onSimulatePaid,
62
+ }: PaymentStepProps) {
63
+ const feePct = payment.reservationFeePercent
64
+ const fee = Math.round(service.price * feePct) / 100
65
+ const remaining = Math.round((service.price - fee) * 100) / 100
66
+
67
+ if (paymentStep === 'choose') {
68
+ return (
69
+ <div>
70
+ {/* Order summary */}
71
+ <div className="rounded-2xl border border-border p-4">
72
+ <div className="flex items-center justify-between font-semibold text-foreground">
73
+ <span>{service.name}</span>
74
+ <span className="tabular-nums">{fmt.price.format(service.price)}</span>
75
+ </div>
76
+ <div className="mt-3 space-y-2 text-sm">
77
+ <div className="flex items-center justify-between text-foreground">
78
+ <span className="flex items-center gap-2">
79
+ <span className="flex h-4 w-4 items-center justify-center rounded bg-primary text-primary-foreground"><Check className="h-3 w-3" /></span>
80
+ Taxa de reserva <span className="rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">{feePct}%</span>
81
+ </span>
82
+ <span className="tabular-nums">{fmt.price.format(fee)}</span>
83
+ </div>
84
+ <div className="flex items-center justify-between text-muted-foreground">
85
+ <span className="flex items-center gap-2">
86
+ <span className="h-4 w-4 rounded border border-border" /> Valor restante
87
+ </span>
88
+ <span className="tabular-nums">{remaining <= 0 ? 'Grátis' : fmt.price.format(remaining)}</span>
89
+ </div>
90
+ <button type="button" className="flex items-center gap-1 text-sm font-medium text-primary hover:underline">
91
+ <Plus className="h-3.5 w-3.5" /> Adicionar cupom
92
+ </button>
93
+ </div>
94
+ <div className="mt-3 flex items-center justify-between border-t border-dashed border-border pt-3 font-semibold text-foreground">
95
+ <span>Valor total</span>
96
+ <span className="tabular-nums">{fmt.price.format(service.price)}</span>
97
+ </div>
98
+ </div>
99
+
100
+ {/* Method */}
101
+ <div className="mt-6 flex items-center justify-between">
102
+ <h3 className="font-heading text-lg font-bold text-foreground">Escolha como pagar</h3>
103
+ <span className="flex items-center gap-1 text-xs text-muted-foreground"><ShieldCheck className="h-3.5 w-3.5 text-primary" /> Pagamento seguro</span>
104
+ </div>
105
+ <div className="mt-3 rounded-2xl border-2 border-primary bg-accent/20 p-4">
106
+ <p className="flex items-center gap-2 font-semibold text-foreground">
107
+ <span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary text-primary-foreground"><Check className="h-3.5 w-3.5" /></span>
108
+ Pix
109
+ </p>
110
+ <div className="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3">
111
+ {[
112
+ { icon: Check, t: 'Finalize o agendamento clicando em "Pagar com Pix".' },
113
+ { icon: QrCode, t: 'Mostraremos um QR Code e o Pix copia-e-cola.' },
114
+ { icon: CalendarCheck, t: 'Após o pagamento, seu agendamento é reservado.' },
115
+ ].map((s, i) => (
116
+ <div key={i} className="text-sm text-muted-foreground">
117
+ <span className="mb-2 flex h-8 w-8 items-center justify-center rounded-lg bg-accent text-primary"><s.icon className="h-4 w-4" /></span>
118
+ <span className="font-semibold text-foreground">{i + 1}.</span> {s.t}
119
+ </div>
120
+ ))}
121
+ </div>
122
+ </div>
123
+
124
+ {error ? <p role="alert" className="mt-3 text-sm text-destructive">Não foi possível iniciar o pagamento. Tente novamente.</p> : null}
125
+ <button
126
+ type="button"
127
+ disabled={busy}
128
+ onClick={() => onPayWithPix(fee)}
129
+ className="mt-6 flex w-full items-center justify-center gap-2 rounded-xl bg-primary py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
130
+ >
131
+ {busy ? <Loader2 className="h-4 w-4 animate-spin" /> : <CreditCard className="h-4 w-4" />}
132
+ Pagar {fmt.price.format(fee)} com Pix
133
+ </button>
134
+ </div>
135
+ )
136
+ }
137
+
138
+ // Pix QR — aguardando pagamento
139
+ return (
140
+ <div>
141
+ <div className="rounded-xl border border-amber-300/60 bg-amber-50 p-4 dark:border-amber-400/30 dark:bg-amber-950/30">
142
+ <p className="flex items-center gap-2 text-sm font-semibold text-foreground">
143
+ <Loader2 className="h-4 w-4 animate-spin text-primary" /> Realize o pagamento para finalizar
144
+ </p>
145
+ <p className="mt-1 text-xs text-muted-foreground">
146
+ Este código Pix é válido por somente 5 minutos. Não feche esta página até receber a confirmação do pagamento.
147
+ </p>
148
+ </div>
149
+ <h3 className="mt-6 font-heading text-lg font-bold text-foreground">Escaneie ou copie este código para pagar</h3>
150
+ <ol className="mt-2 space-y-1 text-sm text-muted-foreground">
151
+ <li>1. Acesse o app do seu banco, pagamentos ou Internet Banking.</li>
152
+ <li>2. Escolha pagar via Pix.</li>
153
+ <li>3. Escaneie o QR Code ou use o Pix copia-e-cola:</li>
154
+ </ol>
155
+ <div className="mt-4 w-fit rounded-xl border border-border bg-white p-3">
156
+ <QrPlaceholder data={charge?.pixQrCode ?? PIX_PAYLOAD} />
157
+ </div>
158
+ <div className="mt-4 flex max-w-md items-center gap-2">
159
+ <code className="flex-1 truncate rounded-lg border border-border bg-muted px-3 py-2 text-xs text-muted-foreground">{charge?.pixCopyPaste ?? PIX_PAYLOAD}</code>
160
+ <button
161
+ type="button"
162
+ onClick={onCopy}
163
+ className="inline-flex shrink-0 items-center gap-1.5 rounded-lg bg-foreground px-3 py-2 text-xs font-medium text-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
164
+ >
165
+ {copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />} {copied ? 'Copiado' : 'Copiar'}
166
+ </button>
167
+ </div>
168
+ {/* POC shortcut — the mock provider also auto-confirms via polling after a few seconds. */}
169
+ <button
170
+ type="button"
171
+ onClick={onSimulatePaid}
172
+ className="mt-6 w-full rounded-xl border border-primary py-3 font-semibold text-primary transition-colors hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
173
+ >
174
+ Já paguei — confirmar (simulação)
175
+ </button>
176
+ </div>
177
+ )
178
+ }
179
+
180
+ export { PIX_PAYLOAD }
@@ -0,0 +1,229 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Public (customer-facing) booking types. This surface is deliberately decoupled
3
+ // from the admin calendar: it depends only on the narrow PublicBookingDataProvider
4
+ // seam (see ./data), so mock slots today and real availability (Supabase RPC /
5
+ // ERP / Google Calendar) later are a provider swap with no widget change.
6
+ //
7
+ // Customization model: hosts personalize via three levers, cheapest first —
8
+ // 1. `labels` — every user-visible string (all defaulted, pt-BR)
9
+ // 2. `brand` — name/logo/tagline/icons/badges/highlights (text + assets)
10
+ // 3. `components` — swap whole subcomponents (BookingComponents) when the
11
+ // structure itself must change. Defaults are exported for composition.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import type { ComponentType, ReactNode } from 'react'
15
+
16
+ export interface PublicService {
17
+ id: string
18
+ name: string
19
+ durationMinutes: number
20
+ price: number
21
+ description?: string
22
+ }
23
+
24
+ export interface ContactInfo {
25
+ name: string
26
+ email?: string
27
+ phone?: string
28
+ notes?: string
29
+ }
30
+
31
+ export interface WorkingHours {
32
+ /** 0=Sun … 6=Sat. */
33
+ daysOfWeek: number[]
34
+ /** 'HH:MM' */
35
+ start: string
36
+ /** 'HH:MM' */
37
+ end: string
38
+ }
39
+
40
+ export interface BookingWindow {
41
+ /** Earliest lead time before a slot can be booked. */
42
+ minAdvanceHours: number
43
+ /** How many days ahead the date picker offers. */
44
+ maxAdvanceDays: number
45
+ /** Slot granularity in minutes. */
46
+ slotInterval: number
47
+ }
48
+
49
+ export interface PaymentConfig {
50
+ /** Show a Pagamento step as the final stage of the flow. */
51
+ enabled?: boolean
52
+ /** % of the service price charged up-front as a reservation fee (default 100). */
53
+ reservationFeePercent?: number
54
+ /** Payment method (POC ships 'pix'). */
55
+ method?: 'pix'
56
+ }
57
+
58
+ export interface ResolvedPayment {
59
+ reservationFeePercent: number
60
+ method: 'pix'
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Brand
65
+ // ---------------------------------------------------------------------------
66
+
67
+ /** One icon+text bullet on the overview sidebar. */
68
+ export interface BookingHighlight {
69
+ icon?: ComponentType<{ className?: string }>
70
+ text: string
71
+ }
72
+
73
+ /** Brand/text-level personalization — no components needed for these. */
74
+ export interface BookingBrand {
75
+ /** e.g. "HempDent · Consulta Canábica". */
76
+ name?: string
77
+ /** Avatar letter; defaults to name.charAt(0). Ignored when logoUrl is set. */
78
+ initials?: string
79
+ logoUrl?: string
80
+ /** Subheader line under the name. `null` hides the row. */
81
+ tagline?: string | null
82
+ /** Icon on service rows. Default: lucide Video. */
83
+ serviceIcon?: ComponentType<{ className?: string }>
84
+ /** Chip after the duration ("Online", "Presencial"…). `null` hides. */
85
+ serviceMeta?: string | null
86
+ /** Badge on the first service. `null` disables. Default 'Mais procurado'. */
87
+ featuredBadge?: string | null
88
+ /** Overview sidebar bullets (replaces the default generic ones). */
89
+ highlights?: BookingHighlight[]
90
+ }
91
+
92
+ export interface ResolvedBookingBrand {
93
+ name: string
94
+ initials: string
95
+ logoUrl?: string
96
+ tagline: string | null
97
+ serviceIcon: ComponentType<{ className?: string }>
98
+ serviceMeta: string | null
99
+ featuredBadge: string | null
100
+ highlights: BookingHighlight[]
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Formatters (shared Intl instances, see ./format)
105
+ // ---------------------------------------------------------------------------
106
+
107
+ export interface BookingFormatters {
108
+ price: Intl.NumberFormat
109
+ time: Intl.DateTimeFormat
110
+ weekday: Intl.DateTimeFormat
111
+ dayNum: Intl.DateTimeFormat
112
+ longDay: Intl.DateTimeFormat
113
+ dateShort: Intl.DateTimeFormat
114
+ month: Intl.DateTimeFormat
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Component override seam
119
+ // ---------------------------------------------------------------------------
120
+
121
+ export type StepState = 'done' | 'active' | 'todo'
122
+
123
+ export interface BrandHeaderProps {
124
+ brand: ResolvedBookingBrand
125
+ }
126
+
127
+ export interface ServiceCardProps {
128
+ service: PublicService
129
+ featured: boolean
130
+ onSelect: () => void
131
+ fmt: BookingFormatters
132
+ brand: ResolvedBookingBrand
133
+ }
134
+
135
+ export interface SidebarExtrasProps {
136
+ professionalName: string
137
+ brand: ResolvedBookingBrand
138
+ /** Renders the primary CTA — replacements can call it from their own button. */
139
+ onStartBooking: () => void
140
+ labels: PublicBookingLabels
141
+ }
142
+
143
+ export interface SuccessPanelProps {
144
+ service: PublicService | null
145
+ /** ISO datetime of the booked slot. */
146
+ slotStart: string | null
147
+ professionalName: string
148
+ labels: PublicBookingLabels
149
+ fmt: BookingFormatters
150
+ }
151
+
152
+ export interface StepRailItemProps {
153
+ n: number
154
+ label: string
155
+ state: StepState
156
+ children?: ReactNode
157
+ }
158
+
159
+ /**
160
+ * Host-overridable subcomponents. Flow-critical parts (week strip, slot grid,
161
+ * contact forms, payment panel) stay internal — these are the brand-visible
162
+ * seams. Defaults are exported so hosts can wrap instead of rewriting.
163
+ */
164
+ export interface BookingComponents {
165
+ BrandHeader: ComponentType<BrandHeaderProps>
166
+ ServiceCard: ComponentType<ServiceCardProps>
167
+ SidebarExtras: ComponentType<SidebarExtrasProps>
168
+ SuccessPanel: ComponentType<SuccessPanelProps>
169
+ StepRailItem: ComponentType<StepRailItemProps>
170
+ }
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Labels — every user-visible string in the flow (all defaulted, pt-BR)
174
+ // ---------------------------------------------------------------------------
175
+
176
+ export interface PublicBookingLabels {
177
+ title: string
178
+ serviceStep: string
179
+ dateStep: string
180
+ contactStep: string
181
+ confirmCta: string
182
+ successTitle: string
183
+ successBody: string
184
+ // overview
185
+ servicesHeading: string
186
+ aboutHeading: string
187
+ openingHoursHeading: string
188
+ closedLabel: string
189
+ bookNowCta: string
190
+ // datetime
191
+ timezoneNote: string
192
+ slotsHeading: string
193
+ noSlots: string
194
+ continueCta: string
195
+ backToServices: string
196
+ back: string
197
+ prevWeek: string
198
+ nextWeek: string
199
+ // contact
200
+ contactIntro: string
201
+ phoneLabel: string
202
+ phoneHint: string
203
+ sendCodeCta: string
204
+ codeTitle: string
205
+ codeBody: string
206
+ codeInvalid: string
207
+ codeConfirmCta: string
208
+ codeResendHint: string
209
+ phoneVerified: string
210
+ changePhone: string
211
+ nameLabel: string
212
+ namePlaceholder: string
213
+ emailLabel: string
214
+ emailPlaceholder: string
215
+ emailHint: string
216
+ notesLabel: string
217
+ notesPlaceholder: string
218
+ saveAndContinueCta: string
219
+ submitError: string
220
+ noChargeNote: string
221
+ // right rail
222
+ railTitle: string
223
+ railWaitingPayment: string
224
+ railService: string
225
+ railProfessional: string
226
+ railDatetime: string
227
+ railContact: string
228
+ railPayment: string
229
+ }