@connectstudio/gastro-ui 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,51 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 — Connect Tables, Stufe 1 (docs/15)
4
+
5
+ - `DEFAULT_BASE_URL` zeigt auf `https://gastro.connectstudio.io` (vorher eine alte Domain) — `baseUrl` muss nur noch bei eigenem Host gesetzt werden.
6
+ - Neu `dietary.ts`: `DIETS`, `DietaryInfo`, `parseDietary`, `dietarySummary` — Kostformen + LMIV-Allergene + Hinweis.
7
+ - `CreateReservationInput.dietary`, `PublicInfo.booking.dietaryEnabled`. Das Widget zeigt „Ernährung (optional)"
8
+ mit Chips und aufklappbaren Allergien, nur wenn der Betrieb das Modul „Connect Tables" an hat.
9
+ - Styles: `.cg-dietary`, `.cg-chips`, `.cg-chip` (Zustand per `:has(input:checked)`), `.cg-details`.
10
+ - `PublicReservation.links.menu` (Speisekarte mit Merkliste); die Erfolgsseite zeigt „Speisekarte ansehen“ als Hauptaktion, wenn der Link da ist.
11
+ - Fix: der Element-Reset (`color: inherit` auf Buttons/Inputs) gilt nur noch innerhalb von `.cg-widget`/`.cg-menu`, nicht mehr unter `.cg-root`.
12
+
13
+ ## 0.2.1 — Speisekarte v2 (Typen)
14
+
15
+ - `PublicMenuItem`: optionale Felder `imageUrl`, `tags`, `variants`, `extras`;
16
+ `PublicMenuCategory`: `parentId`, `description`, `icon`, `imageUrl`; neu `PublicMenuOffer`,
17
+ `PublicMenuPriceOption` — Spiegel der Public API v2 (docs/14). `<Menu />` rendert unverändert.
18
+
19
+ ## 0.2.0 — Redesign (docs/10, Referenz cal.com)
20
+
21
+ - Default-Theme monochrom: Auswahl ist ein Ink-Fill statt Braun, `--cg-accent` bleibt
22
+ überschreibbar und ist der Betriebs-Akzent (Default `#0A0A0A`)
23
+ - Enge Radien statt Pillen: `--cg-radius-control` 10px (vorher 999px), Karte 16px;
24
+ `--cg-radius-field` und `--cg-radius-slot` erben jetzt von `--cg-radius-control`
25
+ - Hairlines statt Schatten; bedienbare Flächen tragen nur noch eine 2px-Kante
26
+ - `BookingWidget`: Datums-Pillen → Kalenderzellen (`.cg-day`, Wochentag über Tageszahl,
27
+ Heute mit Punkt); Mittag/Abend → Segment-Control (`.cg-seg`); jede Gruppe mit
28
+ Label-Zeile (`.cg-label`); Zusammenfassung als Tint-Leiste
29
+ - Neu: Prop `theme` (`'light'` Default | `'dark'` | `'auto'`) setzt `data-cg-theme`.
30
+ Ohne Attribut bleibt das Widget hell — es kippt nie ungefragt mit der
31
+ Systemeinstellung des Besuchers
32
+ - Neu: Label `dateHeading` (Default „Datum")
33
+ - Neu: `formatDayCell()` (Wochentag-Kürzel + Tageszahl)
34
+ - **Breaking (nur Styling):** die Klassen `.cg-chip` / `.cg-chips` entfallen. Wer sie
35
+ überschrieben hat, stellt auf `.cg-day` (Datum) bzw. `.cg-seg__btn` (Tageszeit) um.
36
+
37
+ ## 0.1.0 — Phase 3
38
+
39
+ - Fix: Element-Resets in `styles.css` über `:where()` auf Spezifität 0 — `color: inherit`
40
+ auf Buttons hatte gewählte Chips/Slots und den Primär-CTA im Ruhezustand überstimmt
41
+ (Text schwarz statt weiß, weiß nur beim Hover)
42
+
43
+ - `BookingWidget`: Datum-Chips, Personen-Stepper, Slot-Grid (volle Slots sichtbar), Kontaktformular mit nicht vorangekreuztem Consent, SLOT_FULL-Reload, Bestätigungsscreen mit ICS-Link, Labels überschreibbar, `referrer: 'gbp'`
44
+ - `Menu`: SSR über `data`, sonst Fetch per Key; Preise de-DE, LMIV-Fußnoten + Legende, `hideUnavailable`, `renderItem`
45
+ - `createPublicClient()` + `PublicApiError`; Default-Theme `styles.css` (Tokens `--cg-*`); Build per `tsc` (ESM, `'use client'` erhalten)
46
+
47
+ ## 0.0.1 — Gerüst (Phase 1/2)
48
+
49
+ - Verträge: `PublicMenuCategory`, `PublicMenuItem`, `PublicAvailabilitySlot`, `PublicBookingError`
50
+ - LMIV-Allergene A–N + Zusatzstoffe, `allergenLabel()`, `additiveLabel()`
51
+ - Theme-Vertrag `--cg-*` (Namen + Defaults aus docs/10), `formatPrice()`
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # @connectstudio/gastro-ui
2
+
3
+ Buchungs-Widget und Online-Speisekarte für Connect-Gastro-Kunden-Websites (siehe `docs/09-website-integration.md`). React 18/19, SSR-safe (App Router + Pages Router), keine Abhängigkeiten außer React.
4
+
5
+ ```tsx
6
+ import { BookingWidget, Menu } from '@connectstudio/gastro-ui'
7
+ import '@connectstudio/gastro-ui/styles.css' // Default-Theme (einmal global)
8
+
9
+ // baseUrl ist standardmäßig https://gastro.connectstudio.io — nur bei eigenem Host setzen
10
+
11
+ <BookingWidget publishableKey="pk_live_…" />
12
+ <Menu publishableKey="pk_live_…" showPrices showAllergens />
13
+ ```
14
+
15
+ - **Key**: Publishable Key aus dem Dashboard des Betriebs (Einstellungen → Website-Integration); die Domain der Website muss dort in der Allowlist stehen.
16
+ - **Logik lebt in der Plattform** (Verfügbarkeit, Validierung, Consent, Kapazität). Das Package rendert und ruft die Public API.
17
+ - **Theming**: CSS-Variablen auf `.cg-root` überschreiben (`--cg-accent`, `--cg-ink`, `--cg-line`, `--cg-radius-control`, `--cg-font`, …). Klassen `cg-*` sind stabil.
18
+ - **Farbwelt**: Default ist hell und monochrom — die Auswahl ist ein Ink-Fill, `--cg-accent` ist der Akzent des Betriebs. Dunkel gibt es über `theme="dark"` bzw. `theme="auto"` (folgt `prefers-color-scheme`); ohne das Prop bleibt das Widget hell, damit es auf einer hellen Kunden-Website nicht ungefragt umkippt.
19
+ - **SSR**: `<Menu data={…} />` rendert ohne Fetch (z. B. serverseitig geladen); `BookingWidget` ist eine Client-Insel.
20
+ - Weitere Exporte: `createPublicClient`, `PublicApiError`, `LMIV_ALLERGENS`, `formatPrice`, Typen der Public API.
21
+
22
+ Beispiel-Integration: `examples/kunden-website` im Monorepo.
@@ -0,0 +1,129 @@
1
+ import type { DietaryInfo } from './dietary.js';
2
+ import type { PublicBookingError, PublicMenuCategory, PublicAvailabilitySlot } from './types.js';
3
+ /** Standard-Basis-URL der Plattform (überschreibbar per `baseUrl`). */
4
+ export declare const DEFAULT_BASE_URL = "https://gastro.connectstudio.io";
5
+ export interface PublicClientOptions {
6
+ publishableKey: string;
7
+ /** z. B. 'https://gastro.connectstudio.io' oder '' für same-origin */
8
+ baseUrl?: string;
9
+ fetch?: typeof fetch;
10
+ }
11
+ export interface PublicZone {
12
+ id: string;
13
+ name: string;
14
+ imageUrl: string | null;
15
+ }
16
+ export interface PublicInfo {
17
+ tenant: {
18
+ name: string;
19
+ slug: string;
20
+ timezone: string;
21
+ };
22
+ contact: {
23
+ phone: string | null;
24
+ email: string | null;
25
+ address: {
26
+ street?: string;
27
+ zip?: string;
28
+ city?: string;
29
+ } | null;
30
+ };
31
+ booking: {
32
+ welcomeText: string | null;
33
+ privacyUrl: string | null;
34
+ largePartyHintFrom: number | null;
35
+ maxPartySize: number;
36
+ accentColor: string | null;
37
+ logoUrl: string | null;
38
+ /** wählbare Bereiche (leer = kein Zonen-Schritt) */
39
+ zones?: PublicZone[];
40
+ /** Connect Tables (docs/15): Ernährungsangaben im Formular anbieten */
41
+ dietaryEnabled?: boolean;
42
+ };
43
+ today: {
44
+ date: string;
45
+ weekday: string;
46
+ isOpen: boolean;
47
+ closedReason: string | null;
48
+ windows: {
49
+ start: string;
50
+ end: string;
51
+ }[];
52
+ };
53
+ nextClosure: {
54
+ from: string;
55
+ to: string;
56
+ reason: string | null;
57
+ } | null;
58
+ }
59
+ export interface PublicMenu {
60
+ tenant: {
61
+ name: string;
62
+ slug: string;
63
+ };
64
+ categories: PublicMenuCategory[];
65
+ legend: {
66
+ allergens: Record<string, string>;
67
+ additives: Record<string, string>;
68
+ };
69
+ }
70
+ export interface PublicAvailability {
71
+ date: string;
72
+ partySize: number;
73
+ maxPartySize: number;
74
+ slots: PublicAvailabilitySlot[];
75
+ }
76
+ export interface CreateReservationInput {
77
+ slotStart: string;
78
+ partySize: number;
79
+ guest: {
80
+ name: string;
81
+ phone: string;
82
+ email: string;
83
+ };
84
+ notes?: string;
85
+ consentEmail?: boolean;
86
+ /** Wunsch-Bereich aus dem Zonen-Schritt */
87
+ zoneId?: string;
88
+ /** Ernährung/Allergien (docs/15) — nur wenn der Betrieb es anbietet */
89
+ dietary?: DietaryInfo;
90
+ referrer?: 'gbp';
91
+ }
92
+ export interface PublicReservation {
93
+ reservation: {
94
+ id: string;
95
+ slotStart: string;
96
+ partySize: number;
97
+ status: string;
98
+ publicToken: string;
99
+ };
100
+ /** `menu`: Speisekarte mit Merkliste (Connect Tables) — nur wenn der Betrieb es anbietet */
101
+ links: {
102
+ booking: string;
103
+ cancel: string;
104
+ ics: string;
105
+ menu?: string;
106
+ reservation?: string;
107
+ };
108
+ }
109
+ export declare class PublicApiError extends Error {
110
+ code: PublicBookingError;
111
+ status: number;
112
+ details?: unknown;
113
+ constructor(code: PublicBookingError, message: string, status: number, details?: unknown);
114
+ }
115
+ export declare function createPublicClient(opts: PublicClientOptions): {
116
+ getInfo: () => Promise<PublicInfo>;
117
+ getMenu: () => Promise<PublicMenu>;
118
+ getAvailability: (date: string, partySize: number, zoneId?: string) => Promise<PublicAvailability>;
119
+ createReservation: (input: CreateReservationInput) => Promise<PublicReservation>;
120
+ cancel: (token: string) => Promise<{
121
+ reservation: {
122
+ id: string;
123
+ slotStart: string;
124
+ partySize: number;
125
+ status: string;
126
+ };
127
+ }>;
128
+ };
129
+ export type PublicClient = ReturnType<typeof createPublicClient>;
package/dist/client.js ADDED
@@ -0,0 +1,47 @@
1
+ /** Standard-Basis-URL der Plattform (überschreibbar per `baseUrl`). */
2
+ export const DEFAULT_BASE_URL = 'https://gastro.connectstudio.io';
3
+ export class PublicApiError extends Error {
4
+ code;
5
+ status;
6
+ details;
7
+ constructor(code, message, status, details) {
8
+ super(message);
9
+ this.name = 'PublicApiError';
10
+ this.code = code;
11
+ this.status = status;
12
+ this.details = details;
13
+ }
14
+ }
15
+ const KNOWN = ['SLOT_FULL', 'OUTSIDE_HOURS', 'VALIDATION', 'NOT_FOUND', 'RATE_LIMITED', 'UNKNOWN'];
16
+ export function createPublicClient(opts) {
17
+ const base = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
18
+ const f = opts.fetch ?? ((...args) => fetch(...args));
19
+ async function call(path, init = {}) {
20
+ const res = await f(`${base}/api/public/v1${path}`, {
21
+ ...init,
22
+ headers: { 'Content-Type': 'application/json', 'X-Publishable-Key': opts.publishableKey, ...(init.headers ?? {}) },
23
+ });
24
+ const text = await res.text();
25
+ let json = null;
26
+ try {
27
+ json = text ? JSON.parse(text) : null;
28
+ }
29
+ catch {
30
+ json = null;
31
+ }
32
+ if (!res.ok) {
33
+ const err = json?.error;
34
+ const code = (err?.code && KNOWN.includes(err.code) ? err.code : res.status === 429 ? 'RATE_LIMITED' : res.status === 404 ? 'NOT_FOUND' : 'UNKNOWN');
35
+ throw new PublicApiError(code, err?.message ?? `Anfrage fehlgeschlagen (${res.status})`, res.status, err?.details);
36
+ }
37
+ return json;
38
+ }
39
+ return {
40
+ getInfo: () => call('/info'),
41
+ getMenu: () => call('/menu'),
42
+ getAvailability: (date, partySize, zoneId) => call(`/availability?date=${encodeURIComponent(date)}&party=${partySize}${zoneId ? `&zone=${encodeURIComponent(zoneId)}` : ''}`),
43
+ createReservation: (input) => call('/reservations', { method: 'POST', body: JSON.stringify(input) }),
44
+ cancel: (token) => call('/cancel', { method: 'POST', body: JSON.stringify({ token }) }),
45
+ };
46
+ }
47
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type PublicReservation } from '../client.js';
3
+ export interface BookingWidgetLabels {
4
+ title: string;
5
+ dateHeading: string;
6
+ persons: string;
7
+ slotsHeading: string;
8
+ noSlots: string;
9
+ closedToday: string;
10
+ name: string;
11
+ phone: string;
12
+ email: string;
13
+ notes: string;
14
+ dietary: string;
15
+ allergens: string;
16
+ dietaryNote: string;
17
+ consent: string;
18
+ transactionalHint: string;
19
+ submit: string;
20
+ submitting: string;
21
+ slotFull: string;
22
+ successTitle: string;
23
+ successText: string;
24
+ addToCalendar: string;
25
+ viewMenu: string;
26
+ viewMenuHint: string;
27
+ cancelHint: string;
28
+ largeParty: (n: number, phone: string | null) => string;
29
+ privacy: string;
30
+ error: string;
31
+ zoneHeading: string;
32
+ zoneAny: string;
33
+ }
34
+ export declare const DEFAULT_LABELS: BookingWidgetLabels;
35
+ export interface BookingWidgetProps {
36
+ publishableKey: string;
37
+ baseUrl?: string;
38
+ locale?: string;
39
+ defaultPartySize?: number;
40
+ /** Wie viele Tage ab heute wählbar sind (Default 14) */
41
+ daysAhead?: number;
42
+ /** 'gbp' wenn die Seite vom Google-Profil verlinkt ist (docs/06) */
43
+ referrer?: 'gbp';
44
+ labels?: Partial<BookingWidgetLabels>;
45
+ className?: string;
46
+ /** Slot über der Kopfzeile (Logo o. ä.) */
47
+ header?: ReactNode;
48
+ /** Kopfzeile komplett weglassen (Titel kommt von der Website) */
49
+ hideHeader?: boolean;
50
+ /** Farbwelt: 'light' (Default), 'dark' oder 'auto' (folgt prefers-color-scheme) */
51
+ theme?: 'light' | 'dark' | 'auto';
52
+ onSuccess?: (reservation: PublicReservation) => void;
53
+ }
54
+ /**
55
+ * Buchungs-Widget (docs/03): ein Screen, drei Schritte — Datum + Personen, Uhrzeit,
56
+ * Kontaktdaten + Consent. Logik (Verfügbarkeit, Validierung, Kapazität) liegt in der
57
+ * Plattform; hier nur Darstellung + Aufruf der Public API. Headless-first: `cg-*`-Klassen,
58
+ * `--cg-*`-Variablen (styles.css liefert Defaults).
59
+ */
60
+ export declare function BookingWidget(props: BookingWidgetProps): import("react").JSX.Element;
@@ -0,0 +1,176 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
4
+ import { createPublicClient, PublicApiError } from '../client.js';
5
+ import { addDays, formatDateLong, formatDayCell, formatDayLabel, formatTime, localDateIn } from '../format.js';
6
+ import { ALLERGEN_CODES, LMIV_ALLERGENS } from '../lmiv.js';
7
+ import { DIETS, parseDietary } from '../dietary.js';
8
+ export const DEFAULT_LABELS = {
9
+ title: 'Tisch reservieren',
10
+ dateHeading: 'Datum',
11
+ persons: 'Personen',
12
+ slotsHeading: 'Uhrzeit',
13
+ noSlots: 'An diesem Tag sind leider keine Zeiten mehr frei.',
14
+ closedToday: 'An diesem Tag haben wir geschlossen.',
15
+ name: 'Name',
16
+ phone: 'Telefon',
17
+ email: 'E-Mail',
18
+ notes: 'Anmerkung (optional)',
19
+ dietary: 'Ernährung (optional)',
20
+ allergens: 'Allergien angeben',
21
+ dietaryNote: 'Hinweis für die Küche',
22
+ consent: 'Ich möchte nach meinem Besuch eine kurze Nachricht mit der Möglichkeit zur Bewertung und gelegentliche Infos des Restaurants erhalten.',
23
+ transactionalHint: 'Bestätigung per E-Mail · jederzeit stornierbar',
24
+ submit: 'Verbindlich reservieren',
25
+ submitting: 'Wird reserviert …',
26
+ slotFull: 'Dieser Zeitpunkt wurde gerade vergeben. Bitte wählen Sie eine andere Uhrzeit.',
27
+ successTitle: 'Ihr Tisch ist reserviert',
28
+ successText: 'Wir haben Ihnen eine Bestätigung per E-Mail geschickt.',
29
+ addToCalendar: 'Zum Kalender hinzufügen',
30
+ viewMenu: 'Speisekarte ansehen',
31
+ viewMenuHint: 'Schon Lust bekommen? Merken Sie sich Gerichte — am Tisch geht die Bestellung dann schneller.',
32
+ cancelHint: 'Sie können die Reservierung über den Link in der E-Mail jederzeit stornieren.',
33
+ largeParty: (n, phone) => `Für Gruppen ab ${n} Personen rufen Sie uns bitte an${phone ? `: ${phone}` : '.'}`,
34
+ privacy: 'Datenschutz',
35
+ error: 'Das hat leider nicht geklappt. Bitte versuchen Sie es noch einmal.',
36
+ zoneHeading: 'Bereich',
37
+ zoneAny: 'Keine Präferenz',
38
+ };
39
+ /**
40
+ * Buchungs-Widget (docs/03): ein Screen, drei Schritte — Datum + Personen, Uhrzeit,
41
+ * Kontaktdaten + Consent. Logik (Verfügbarkeit, Validierung, Kapazität) liegt in der
42
+ * Plattform; hier nur Darstellung + Aufruf der Public API. Headless-first: `cg-*`-Klassen,
43
+ * `--cg-*`-Variablen (styles.css liefert Defaults).
44
+ */
45
+ export function BookingWidget(props) {
46
+ const { publishableKey, baseUrl, locale = 'de-DE', defaultPartySize = 2, daysAhead = 14, referrer, className, header, hideHeader, theme, onSuccess } = props;
47
+ const L = useMemo(() => ({ ...DEFAULT_LABELS, ...(props.labels ?? {}) }), [props.labels]);
48
+ const client = useMemo(() => createPublicClient({ publishableKey, baseUrl }), [publishableKey, baseUrl]);
49
+ const uid = useId();
50
+ const [info, setInfo] = useState(null);
51
+ const [infoError, setInfoError] = useState(false);
52
+ const [date, setDate] = useState(null);
53
+ const [party, setParty] = useState(defaultPartySize);
54
+ const [zoneId, setZoneId] = useState(null);
55
+ const [availability, setAvailability] = useState(null);
56
+ const [slotsLoading, setSlotsLoading] = useState(false);
57
+ const [slot, setSlot] = useState(null);
58
+ const [phase, setPhase] = useState('idle');
59
+ const [message, setMessage] = useState(null);
60
+ const [result, setResult] = useState(null);
61
+ const [daypart, setDaypart] = useState(null);
62
+ const [showAllSlots, setShowAllSlots] = useState(false);
63
+ const formRef = useRef(null);
64
+ const formId = `cg-form-${uid.replace(/[^a-zA-Z0-9_-]/g, '')}`;
65
+ const tz = info?.tenant.timezone ?? 'Europe/Berlin';
66
+ const today = useMemo(() => localDateIn(new Date(), tz), [tz]);
67
+ const days = useMemo(() => Array.from({ length: daysAhead }, (_, i) => addDays(today, i)), [today, daysAhead]);
68
+ const maxParty = availability?.maxPartySize || info?.booking.maxPartySize || 8;
69
+ const largeFrom = info?.booking.largePartyHintFrom ?? null;
70
+ // 1) Info (Zeitzone, Texte, max. Gruppe)
71
+ useEffect(() => {
72
+ let alive = true;
73
+ client
74
+ .getInfo()
75
+ .then((i) => {
76
+ if (!alive)
77
+ return;
78
+ setInfo(i);
79
+ setDate((d) => d ?? localDateIn(new Date(), i.tenant.timezone));
80
+ })
81
+ .catch(() => alive && setInfoError(true));
82
+ return () => {
83
+ alive = false;
84
+ };
85
+ }, [client]);
86
+ // 2) Verfügbarkeit bei Datum/Personen-Änderung (immer frisch, nie gecacht)
87
+ const loadSlots = useCallback(async () => {
88
+ if (!date)
89
+ return;
90
+ setSlotsLoading(true);
91
+ try {
92
+ const a = await client.getAvailability(date, party, zoneId ?? undefined);
93
+ setAvailability(a);
94
+ setSlot((s) => (s && a.slots.some((x) => x.slotStart === s && x.remainingCovers >= party) ? s : null));
95
+ }
96
+ catch {
97
+ setAvailability(null);
98
+ setMessage({ kind: 'error', text: L.error });
99
+ }
100
+ finally {
101
+ setSlotsLoading(false);
102
+ }
103
+ setShowAllSlots(false);
104
+ setDaypart(null);
105
+ }, [client, date, party, zoneId, L.error]);
106
+ useEffect(() => {
107
+ void loadSlots();
108
+ }, [loadSlots]);
109
+ const tooLarge = largeFrom !== null && party >= largeFrom;
110
+ const bookable = (s) => s.remainingCovers >= party && s.maxPartySize >= party && !tooLarge;
111
+ async function submit(e) {
112
+ e.preventDefault();
113
+ if (!slot || phase === 'submitting')
114
+ return;
115
+ const fd = new FormData(e.currentTarget);
116
+ setPhase('submitting');
117
+ setMessage(null);
118
+ try {
119
+ const r = await client.createReservation({
120
+ slotStart: slot,
121
+ partySize: party,
122
+ guest: { name: String(fd.get('name') ?? ''), phone: String(fd.get('phone') ?? ''), email: String(fd.get('email') ?? '') },
123
+ notes: String(fd.get('notes') ?? '') || undefined,
124
+ dietary: parseDietary({ diets: fd.getAll('diet'), allergens: fd.getAll('allergen'), note: String(fd.get('dietary_note') ?? '') }) ?? undefined,
125
+ consentEmail: fd.get('consent') === 'on',
126
+ zoneId: zoneId ?? undefined,
127
+ referrer,
128
+ });
129
+ setResult(r);
130
+ setPhase('done');
131
+ onSuccess?.(r);
132
+ }
133
+ catch (err) {
134
+ setPhase('idle');
135
+ if (err instanceof PublicApiError && err.code === 'SLOT_FULL') {
136
+ setSlot(null);
137
+ setMessage({ kind: 'info', text: L.slotFull });
138
+ void loadSlots();
139
+ }
140
+ else {
141
+ setMessage({ kind: 'error', text: err instanceof PublicApiError && err.code === 'VALIDATION' ? err.message : L.error });
142
+ }
143
+ }
144
+ }
145
+ // Bestätigungsscreen: zentrierter Haken (Original-Look, auf User-Wunsch wiederhergestellt)
146
+ if (phase === 'done' && result) {
147
+ const when = `${formatDateLong(localDateIn(new Date(result.reservation.slotStart), tz), locale)} · ${formatTime(result.reservation.slotStart, tz, locale)} Uhr`;
148
+ return (_jsxs("section", { className: cx('cg-root cg-widget', className), "data-cg-theme": theme, "aria-live": "polite", children: [!hideHeader && (_jsxs("header", { className: "cg-widget__header", children: [header, _jsx("span", { className: "cg-widget__title", children: L.title })] })), _jsxs("div", { className: "cg-widget__body cg-widget__success", children: [_jsx("div", { className: "cg-widget__success-icon", "aria-hidden": true, children: "\u2713" }), _jsx("h2", { className: "cg-widget__success-title", children: L.successTitle }), _jsxs("p", { className: "cg-widget__success-when", children: [when, " \u00B7 ", result.reservation.partySize, " ", result.reservation.partySize === 1 ? 'Person' : 'Personen'] }), _jsx("p", { className: "cg-widget__muted", children: L.successText }), result.links.menu ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "cg-widget__muted cg-widget__small", children: L.viewMenuHint }), _jsxs("div", { className: "cg-widget__actions", children: [_jsx("a", { className: "cg-btn cg-btn--primary", href: result.links.menu, children: L.viewMenu }), _jsx("a", { className: "cg-btn cg-btn--secondary cg-btn--icon", href: result.links.ics, "aria-label": L.addToCalendar, title: L.addToCalendar, children: _jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [_jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }), _jsx("path", { d: "M16 2v4M8 2v4M3 10h18M12 14v4M10 16h4" })] }) })] })] })) : (_jsx("a", { className: "cg-btn cg-btn--secondary", href: result.links.ics, children: L.addToCalendar })), _jsx("p", { className: "cg-widget__muted cg-widget__small", children: L.cancelHint })] })] }));
149
+ }
150
+ const summary = slot && date ? `${formatDayLabel(date, today, locale)} · ${formatTime(slot, tz, locale)} · ${party} ${party === 1 ? 'Person' : 'Personen'}` : null;
151
+ return (_jsxs("section", { className: cx('cg-root cg-widget', className), "data-cg-theme": theme, "aria-busy": !info && !infoError, children: [!hideHeader && (_jsxs("header", { className: "cg-widget__header", children: [header, _jsx("span", { className: "cg-widget__title", children: L.title })] })), _jsxs("div", { className: "cg-widget__body", children: [_jsxs("div", { className: "cg-section", children: [_jsxs("div", { className: "cg-label", children: [_jsx("span", { children: L.dateHeading }), date && _jsx("span", { className: "cg-label__value", children: formatDateLong(date, locale) })] }), _jsx("div", { className: "cg-days", role: "group", "aria-label": L.dateHeading, children: days.map((d) => {
152
+ const cell = formatDayCell(d, locale);
153
+ return (_jsxs("button", { type: "button", className: cx('cg-day', date === d && 'cg-day--selected'), "aria-pressed": date === d, "aria-label": formatDateLong(d, locale), onClick: () => setDate(d), disabled: !info, children: [_jsx("span", { className: "cg-day__wd", children: cell.weekday }), _jsx("span", { className: "cg-day__num", children: cell.day }), _jsx("span", { className: "cg-day__dot", style: { visibility: d === today ? 'visible' : 'hidden' }, "aria-hidden": true })] }, d));
154
+ }) })] }), _jsxs("div", { className: "cg-stepper", children: [_jsx("span", { className: "cg-stepper__label", id: `${uid}-persons`, children: L.persons }), _jsxs("div", { className: "cg-stepper__controls", role: "group", "aria-labelledby": `${uid}-persons`, children: [_jsx("button", { type: "button", className: "cg-stepper__btn", "aria-label": "Weniger Personen", onClick: () => setParty((p) => Math.max(1, p - 1)), disabled: party <= 1, children: "\u2212" }), _jsx("output", { className: "cg-stepper__value", "aria-live": "polite", children: party }), _jsx("button", { type: "button", className: "cg-stepper__btn", "aria-label": "Mehr Personen", onClick: () => setParty((p) => Math.min(Math.max(maxParty, largeFrom ?? 0), p + 1)), disabled: party >= Math.max(maxParty, largeFrom ?? 0), children: "+" })] })] }), tooLarge && _jsx("p", { className: "cg-widget__hint", children: L.largeParty(largeFrom, info?.contact.phone ?? null) }), (info?.booking.zones?.length ?? 0) >= 2 && (_jsxs("div", { className: "cg-section", children: [_jsx("div", { className: "cg-label", children: _jsx("span", { children: L.zoneHeading }) }), _jsxs("div", { className: "cg-zones", role: "radiogroup", "aria-label": L.zoneHeading, children: [_jsx("button", { type: "button", className: cx('cg-zone cg-zone--any', zoneId === null && 'cg-zone--selected'), role: "radio", "aria-checked": zoneId === null, onClick: () => setZoneId(null), children: _jsx("span", { className: "cg-zone__name", children: L.zoneAny }) }), info.booking.zones.map((z) => (_jsxs("button", { type: "button", className: cx('cg-zone', zoneId === z.id && 'cg-zone--selected'), role: "radio", "aria-checked": zoneId === z.id, onClick: () => setZoneId((cur) => (cur === z.id ? null : z.id)), children: [z.imageUrl ? (_jsx("img", { className: "cg-zone__img", src: z.imageUrl, alt: "", loading: "lazy" })) : (_jsx("span", { className: "cg-zone__img cg-zone__img--empty", "aria-hidden": true, children: z.name.slice(0, 1).toUpperCase() })), _jsx("span", { className: "cg-zone__name", children: z.name })] }, z.id)))] })] })), (() => {
155
+ const all = availability?.slots ?? [];
156
+ const hourOf = (iso) => Number(formatTime(iso, tz, 'de-DE').slice(0, 2));
157
+ const lunch = all.filter((s) => hourOf(s.slotStart) < 15);
158
+ const dinner = all.filter((s) => hourOf(s.slotStart) >= 15);
159
+ const hasBoth = lunch.length > 0 && dinner.length > 0;
160
+ const activePart = daypart ?? (dinner.length > 0 ? 'abend' : 'mittag');
161
+ const group = hasBoth ? (activePart === 'mittag' ? lunch : dinner) : all;
162
+ const CAP = 9;
163
+ const visible = showAllSlots || group.length <= CAP + 3 ? group : group.slice(0, CAP);
164
+ return (_jsxs("div", { className: "cg-slotarea", children: [_jsxs("div", { className: "cg-label", children: [_jsx("span", { children: L.slotsHeading }), hasBoth && (_jsx("span", { className: "cg-seg", role: "group", "aria-label": "Tageszeit", children: [
165
+ { key: 'mittag', label: 'Mittag' },
166
+ { key: 'abend', label: 'Abend' },
167
+ ].map((g) => (_jsx("button", { type: "button", className: cx('cg-seg__btn', activePart === g.key && 'cg-seg__btn--active'), "aria-pressed": activePart === g.key, onClick: () => { setDaypart(g.key); setShowAllSlots(false); }, children: g.label }, g.key))) }))] }), _jsxs("div", { className: "cg-slots", role: "radiogroup", "aria-label": L.slotsHeading, "aria-busy": slotsLoading, children: [!availability && !infoError && Array.from({ length: 6 }, (_, i) => _jsx("div", { className: "cg-skeleton cg-slot", "aria-hidden": true }, i)), availability && all.length === 0 && (_jsxs("p", { className: "cg-widget__empty", children: [date === today && info && !info.today.isOpen ? L.closedToday : L.noSlots, info?.contact.phone && (_jsxs(_Fragment, { children: [' ', _jsx("a", { href: `tel:${info.contact.phone.replace(/\s/g, '')}`, children: info.contact.phone })] }))] })), visible.map((s) => {
168
+ const ok = bookable(s);
169
+ return (_jsx("button", { type: "button", role: "radio", "aria-checked": slot === s.slotStart, "aria-disabled": !ok || undefined, className: cx('cg-slot', slot === s.slotStart && 'cg-slot--selected', !ok && 'cg-slot--full'), onClick: () => ok && setSlot(s.slotStart), disabled: !ok, children: formatTime(s.slotStart, tz, locale) }, s.slotStart));
170
+ })] }), !showAllSlots && visible.length < group.length && (_jsxs("button", { type: "button", className: "cg-more", onClick: () => setShowAllSlots(true), children: ["Alle ", group.length, " Zeiten anzeigen"] }))] }));
171
+ })(), message && (_jsx("p", { role: message.kind === 'error' ? 'alert' : 'status', className: cx('cg-widget__message', `cg-widget__message--${message.kind}`), children: message.text })), slot && !tooLarge && (_jsx("div", { ref: formRef, className: "cg-form-wrap", children: _jsxs("form", { id: formId, className: "cg-form", onSubmit: submit, noValidate: false, children: [_jsxs("div", { className: "cg-field", children: [_jsx("label", { htmlFor: `${uid}-name`, children: L.name }), _jsx("input", { id: `${uid}-name`, name: "name", type: "text", autoComplete: "name", required: true, maxLength: 200 })] }), _jsxs("div", { className: "cg-field", children: [_jsx("label", { htmlFor: `${uid}-phone`, children: L.phone }), _jsx("input", { id: `${uid}-phone`, name: "phone", type: "tel", inputMode: "tel", autoComplete: "tel", required: true, minLength: 5, maxLength: 40 })] }), _jsxs("div", { className: "cg-field", children: [_jsx("label", { htmlFor: `${uid}-email`, children: L.email }), _jsx("input", { id: `${uid}-email`, name: "email", type: "email", inputMode: "email", autoComplete: "email", required: true, maxLength: 254 })] }), _jsxs("div", { className: "cg-field", children: [_jsx("label", { htmlFor: `${uid}-notes`, children: L.notes }), _jsx("textarea", { id: `${uid}-notes`, name: "notes", rows: 2, maxLength: 1000 })] }), info?.booking.dietaryEnabled && (_jsxs("div", { className: "cg-dietary", role: "group", "aria-labelledby": `${uid}-dietary`, children: [_jsxs("div", { className: "cg-field", children: [_jsx("span", { id: `${uid}-dietary`, className: "cg-field__label", children: L.dietary }), _jsx("div", { className: "cg-chips", children: DIETS.map((d) => (_jsxs("label", { className: "cg-chip", children: [_jsx("input", { type: "checkbox", name: "diet", value: d.key }), _jsx("span", { children: d.label })] }, d.key))) })] }), _jsxs("details", { className: "cg-details", children: [_jsxs("summary", { children: [_jsx("span", { className: "cg-details__chevron", "aria-hidden": true }), L.allergens] }), _jsxs("div", { className: "cg-details__body", children: [_jsx("div", { className: "cg-field", children: _jsx("div", { className: "cg-chips", children: ALLERGEN_CODES.map((a) => (_jsxs("label", { className: "cg-chip", children: [_jsx("input", { type: "checkbox", name: "allergen", value: a }), _jsx("span", { children: LMIV_ALLERGENS[a] })] }, a))) }) }), _jsxs("div", { className: "cg-field", children: [_jsx("label", { htmlFor: `${uid}-dietary-note`, children: L.dietaryNote }), _jsx("input", { id: `${uid}-dietary-note`, name: "dietary_note", type: "text", maxLength: 300 })] })] })] })] })), _jsxs("label", { className: "cg-consent", children: [_jsx("input", { type: "checkbox", name: "consent" }), _jsx("span", { children: L.consent })] })] }) })), summary && _jsx("p", { className: "cg-summary", children: summary }), _jsx("button", { type: "submit", form: formId, className: "cg-btn cg-btn--primary", disabled: !slot || tooLarge || phase === 'submitting', "aria-describedby": `${uid}-hinttext`, children: phase === 'submitting' ? L.submitting : L.submit }), _jsxs("p", { id: `${uid}-hinttext`, className: "cg-widget__muted cg-widget__small cg-widget__center", children: [L.transactionalHint, info?.booking.privacyUrl && (_jsxs("span", { style: { whiteSpace: 'nowrap' }, children: [' · ', _jsx("a", { href: info.booking.privacyUrl, target: "_blank", rel: "noreferrer", children: L.privacy })] }))] }), infoError && _jsx("p", { role: "alert", className: "cg-widget__message cg-widget__message--error", children: L.error })] })] }));
172
+ }
173
+ function cx(...c) {
174
+ return c.filter(Boolean).join(' ');
175
+ }
176
+ //# sourceMappingURL=BookingWidget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BookingWidget.js","sourceRoot":"","sources":["../../src/components/BookingWidget.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type PublicMenu } from '../client.js';
3
+ import type { PublicMenuItem } from '../types.js';
4
+ export interface MenuProps {
5
+ /** Publishable Key des Betriebs — lädt die Karte clientseitig */
6
+ publishableKey?: string;
7
+ baseUrl?: string;
8
+ /** Bereits geladene Daten (SSR/SEO) — dann kein Fetch */
9
+ data?: Pick<PublicMenu, 'categories'> & Partial<Pick<PublicMenu, 'legend' | 'tenant'>>;
10
+ showPrices?: boolean;
11
+ showAllergens?: boolean;
12
+ /** „heute aus“-Gerichte ausblenden statt durchzustreichen */
13
+ hideUnavailable?: boolean;
14
+ locale?: string;
15
+ className?: string;
16
+ /** Eigenes Rendering pro Gericht (Slot/Render-Prop) */
17
+ renderItem?: (item: PublicMenuItem, defaults: {
18
+ price: string;
19
+ footnote: string;
20
+ }) => ReactNode;
21
+ /** Überschriften-Level der Kategorien (Default h2) */
22
+ headingLevel?: 2 | 3 | 4;
23
+ }
24
+ /**
25
+ * Speisekarte (docs/09): rendert Kategorien + Gerichte mit LMIV-Fußnoten. Headless-first:
26
+ * Klassen `cg-menu*`, Farben/Radius/Fonts über `--cg-*` (styles.css liefert Defaults).
27
+ */
28
+ export declare function Menu({ publishableKey, baseUrl, data, showPrices, showAllergens, hideUnavailable, locale, className, renderItem, headingLevel }: MenuProps): import("react").JSX.Element;
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ import { createPublicClient } from '../client.js';
5
+ import { additiveLabel, allergenLabel } from '../lmiv.js';
6
+ import { formatPrice } from '../theme.js';
7
+ /**
8
+ * Speisekarte (docs/09): rendert Kategorien + Gerichte mit LMIV-Fußnoten. Headless-first:
9
+ * Klassen `cg-menu*`, Farben/Radius/Fonts über `--cg-*` (styles.css liefert Defaults).
10
+ */
11
+ export function Menu({ publishableKey, baseUrl, data, showPrices = true, showAllergens = true, hideUnavailable = false, locale = 'de-DE', className, renderItem, headingLevel = 2 }) {
12
+ const [loaded, setLoaded] = useState(data ?? null);
13
+ const [error, setError] = useState(null);
14
+ useEffect(() => {
15
+ if (data || !publishableKey)
16
+ return;
17
+ let alive = true;
18
+ createPublicClient({ publishableKey, baseUrl })
19
+ .getMenu()
20
+ .then((m) => alive && setLoaded(m))
21
+ .catch(() => alive && setError('Die Speisekarte konnte nicht geladen werden.'));
22
+ return () => {
23
+ alive = false;
24
+ };
25
+ }, [publishableKey, baseUrl, data]);
26
+ if (error)
27
+ return _jsx("p", { className: cx('cg-root cg-menu cg-menu--error', className), children: error });
28
+ if (!loaded) {
29
+ return (_jsxs("div", { className: cx('cg-root cg-menu cg-menu--loading', className), "aria-busy": "true", children: [_jsx("div", { className: "cg-skeleton", style: { width: '40%' } }), _jsx("div", { className: "cg-skeleton" }), _jsx("div", { className: "cg-skeleton" })] }));
30
+ }
31
+ const H = `h${headingLevel}`;
32
+ const used = new Set();
33
+ const usedAdd = new Set();
34
+ const cats = loaded.categories.map((c) => ({ ...c, items: hideUnavailable ? c.items.filter((i) => i.isAvailable) : c.items })).filter((c) => c.items.length > 0);
35
+ for (const c of cats)
36
+ for (const i of c.items) {
37
+ i.allergens.forEach((a) => used.add(a));
38
+ i.additives.forEach((a) => usedAdd.add(a));
39
+ }
40
+ return (_jsxs("div", { className: cx('cg-root cg-menu', className), children: [cats.map((c) => (_jsxs("section", { className: "cg-menu__category", "aria-labelledby": `cg-cat-${c.id}`, children: [_jsx(H, { id: `cg-cat-${c.id}`, className: "cg-menu__category-title", children: c.name }), _jsx("ul", { className: "cg-menu__items", children: c.items.map((i) => {
41
+ const price = formatPrice(i.priceCents, locale);
42
+ const footnote = showAllergens ? [...i.allergens, ...i.additives].join(',') : '';
43
+ return (_jsx("li", { className: cx('cg-menu__item', !i.isAvailable && 'cg-menu__item--unavailable', i.isSpecial && 'cg-menu__item--special'), children: renderItem ? (renderItem(i, { price, footnote })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cg-menu__item-main", children: [_jsxs("span", { className: "cg-menu__item-name", children: [i.name, footnote && _jsx("sup", { className: "cg-menu__footnote", children: footnote }), i.isSpecial && _jsx("span", { className: "cg-menu__badge", children: "Empfehlung" }), !i.isAvailable && _jsx("span", { className: "cg-menu__badge cg-menu__badge--muted", children: "heute nicht" })] }), i.description && _jsx("span", { className: "cg-menu__item-desc", children: i.description })] }), showPrices && _jsx("span", { className: "cg-menu__item-price", children: price })] })) }, i.id));
44
+ }) })] }, c.id))), showAllergens && (used.size > 0 || usedAdd.size > 0) && (_jsxs("footer", { className: "cg-menu__legend", children: [_jsx("p", { className: "cg-menu__legend-title", children: "Allergene und Zusatzstoffe" }), _jsxs("dl", { className: "cg-menu__legend-list", children: [[...used].sort().map((code) => (_jsxs("div", { children: [_jsx("dt", { children: code }), _jsx("dd", { children: allergenLabel(code) })] }, code))), [...usedAdd]
45
+ .sort((a, b) => Number(a) - Number(b))
46
+ .map((code) => (_jsxs("div", { children: [_jsx("dt", { children: code }), _jsx("dd", { children: additiveLabel(code) })] }, code)))] })] }))] }));
47
+ }
48
+ function cx(...c) {
49
+ return c.filter(Boolean).join(' ');
50
+ }
51
+ //# sourceMappingURL=Menu.js.map