@bailierich/booking-components 2.0.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 (83) hide show
  1. package/README.md +319 -0
  2. package/TENANT_DATA_INTEGRATION.md +402 -0
  3. package/TENANT_SETUP.md +316 -0
  4. package/components/BookingFlow/BookingFlow.tsx +790 -0
  5. package/components/BookingFlow/index.ts +5 -0
  6. package/components/BookingFlow/steps/AddonsSelection.tsx +118 -0
  7. package/components/BookingFlow/steps/Confirmation.tsx +185 -0
  8. package/components/BookingFlow/steps/ContactForm.tsx +292 -0
  9. package/components/BookingFlow/steps/CycleAwareDateSelection.tsx +277 -0
  10. package/components/BookingFlow/steps/DateSelection.tsx +473 -0
  11. package/components/BookingFlow/steps/ServiceSelection.tsx +315 -0
  12. package/components/BookingFlow/steps/TimeSelection.tsx +230 -0
  13. package/components/BookingFlow/steps/index.ts +10 -0
  14. package/components/BottomSheet/index.tsx +120 -0
  15. package/components/Forms/FormBlock.tsx +283 -0
  16. package/components/Forms/FormField.tsx +385 -0
  17. package/components/Forms/FormRenderer.tsx +216 -0
  18. package/components/Forms/FormValidation.ts +122 -0
  19. package/components/Forms/index.ts +4 -0
  20. package/components/HoldTimer/HoldTimer.tsx +266 -0
  21. package/components/HoldTimer/index.ts +2 -0
  22. package/components/SectionRenderer.tsx +558 -0
  23. package/components/Sections/About.tsx +145 -0
  24. package/components/Sections/BeforeAfter.tsx +81 -0
  25. package/components/Sections/BookingSection.tsx +76 -0
  26. package/components/Sections/Contact.tsx +103 -0
  27. package/components/Sections/FAQSection.tsx +239 -0
  28. package/components/Sections/FeatureContent.tsx +113 -0
  29. package/components/Sections/FeaturedLink.tsx +103 -0
  30. package/components/Sections/FixedInfoCard.tsx +189 -0
  31. package/components/Sections/Gallery.tsx +83 -0
  32. package/components/Sections/Header.tsx +78 -0
  33. package/components/Sections/Hero.tsx +178 -0
  34. package/components/Sections/ImageSection.tsx +147 -0
  35. package/components/Sections/InstagramFeed.tsx +38 -0
  36. package/components/Sections/LinkList.tsx +76 -0
  37. package/components/Sections/LocationMap.tsx +202 -0
  38. package/components/Sections/Logo.tsx +61 -0
  39. package/components/Sections/MinimalFooter.tsx +78 -0
  40. package/components/Sections/MinimalHeader.tsx +81 -0
  41. package/components/Sections/MinimalNavigation.tsx +63 -0
  42. package/components/Sections/Navbar.tsx +258 -0
  43. package/components/Sections/PricingTable.tsx +106 -0
  44. package/components/Sections/ScrollingTextDivider.tsx +138 -0
  45. package/components/Sections/ScrollingTextDivider.tsx.bak +138 -0
  46. package/components/Sections/ServicesPreview.tsx +129 -0
  47. package/components/Sections/SocialBar.tsx +177 -0
  48. package/components/Sections/Team.tsx +80 -0
  49. package/components/Sections/Testimonials.tsx +92 -0
  50. package/components/Sections/TextSection.tsx +116 -0
  51. package/components/Sections/VideoSection.tsx +178 -0
  52. package/components/Sections/index.ts +57 -0
  53. package/components/index.ts +21 -0
  54. package/dist/index-DAai7Glf.d.mts +474 -0
  55. package/dist/index-DAai7Glf.d.ts +474 -0
  56. package/dist/index.d.mts +1075 -0
  57. package/dist/index.d.ts +1075 -0
  58. package/dist/index.js +22 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/index.mjs +22 -0
  61. package/dist/index.mjs.map +1 -0
  62. package/dist/styles/index.d.mts +1 -0
  63. package/dist/styles/index.d.ts +1 -0
  64. package/dist/styles/index.js +2 -0
  65. package/dist/styles/index.js.map +1 -0
  66. package/dist/styles/index.mjs +2 -0
  67. package/dist/styles/index.mjs.map +1 -0
  68. package/docs/API.md +849 -0
  69. package/docs/CALLBACKS.md +760 -0
  70. package/docs/COMPLETE_SESSION_SUMMARY.md +404 -0
  71. package/docs/DATA_SHAPES.md +684 -0
  72. package/docs/MIGRATION.md +662 -0
  73. package/docs/PAYMENT_INTEGRATION.md +766 -0
  74. package/docs/SESSION_SUMMARY.md +185 -0
  75. package/docs/STYLING.md +735 -0
  76. package/index.ts +4 -0
  77. package/lib/storage.ts +239 -0
  78. package/package.json +59 -0
  79. package/styles/animations.ts +210 -0
  80. package/styles/index.ts +1 -0
  81. package/tsconfig.json +32 -0
  82. package/tsup.config.ts +13 -0
  83. package/types/index.ts +369 -0
@@ -0,0 +1,277 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback, useMemo } from 'react';
4
+ import { DateSelection } from './DateSelection';
5
+ import type {
6
+ DateSelectionSettings,
7
+ CyclePhase,
8
+ CyclePhaseMap,
9
+ CycleClient,
10
+ FetchCycleDataFn,
11
+ CycleWarningCallback
12
+ } from '../../../types';
13
+
14
+ /**
15
+ * Cycle phase definitions matching tenant backend
16
+ */
17
+ export const CYCLE_PHASES: Record<string, Omit<CyclePhase, 'daysSinceStart' | 'confidence'>> = {
18
+ avoid: {
19
+ phase: 'avoid',
20
+ color: '#EF4444',
21
+ bgColor: '#FEF2F2',
22
+ label: 'Avoid',
23
+ icon: '🔴',
24
+ description: 'Period - avoid waxing'
25
+ },
26
+ caution: {
27
+ phase: 'caution',
28
+ color: '#F59E0B',
29
+ bgColor: '#FFFBEB',
30
+ label: 'Caution',
31
+ icon: '🟡',
32
+ description: 'End of period - proceed with caution'
33
+ },
34
+ optimal: {
35
+ phase: 'optimal',
36
+ color: '#10B981',
37
+ bgColor: '#F0FDF4',
38
+ label: 'Optimal',
39
+ icon: '🟢',
40
+ description: 'Best time for waxing'
41
+ },
42
+ neutral: {
43
+ phase: 'neutral',
44
+ color: '#6B7280',
45
+ bgColor: '#F9FAFB',
46
+ label: 'Neutral',
47
+ icon: '⚪',
48
+ description: 'Okay but not optimal'
49
+ },
50
+ unknown: {
51
+ phase: 'unknown',
52
+ color: '#9CA3AF',
53
+ bgColor: '#F3F4F6',
54
+ label: 'Unknown',
55
+ icon: '❓',
56
+ description: 'No cycle data available'
57
+ }
58
+ };
59
+
60
+ export interface CycleAwareDateSelectionProps {
61
+ // Standard DateSelection props
62
+ availableDates: string[];
63
+ selectedDate: string | null;
64
+ onDateSelect: (date: string, cyclePhase?: CyclePhase) => void;
65
+ settings: DateSelectionSettings;
66
+ colors: {
67
+ primary: string;
68
+ secondary: string;
69
+ };
70
+
71
+ // Cycle-aware specific props
72
+ /** Tenant-provided function to fetch cycle data */
73
+ fetchCycleData?: FetchCycleDataFn;
74
+
75
+ /** Selected client (must have cycle_opt_in = true to show cycle features) */
76
+ selectedClient?: CycleClient;
77
+
78
+ /** Callback when a warning should be shown (e.g., avoid phase selected) */
79
+ onCycleWarning?: CycleWarningCallback;
80
+
81
+ /** Override to force enable/disable cycle features */
82
+ cycleAwareEnabled?: boolean;
83
+ }
84
+
85
+ /**
86
+ * CycleAwareDateSelection - Enhanced date picker with cycle phase indicators
87
+ *
88
+ * Wraps the base DateSelection component with cycle-aware features:
89
+ * - Visual indicators for each cycle phase
90
+ * - Color-coded calendar days
91
+ * - Legend explaining phases
92
+ * - Warnings for non-optimal dates
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * const fetchCycleData = async (client, dates) => {
97
+ * const res = await fetch(`/api/cycle?clientId=${client.id}`);
98
+ * const data = await res.json();
99
+ * return transformToCyclePhases(data, dates);
100
+ * };
101
+ *
102
+ * <CycleAwareDateSelection
103
+ * availableDates={dates}
104
+ * selectedDate={selectedDate}
105
+ * onDateSelect={(date, phase) => {
106
+ * setSelectedDate(date);
107
+ * if (phase?.phase === 'avoid') {
108
+ * showWarning('This date may be uncomfortable');
109
+ * }
110
+ * }}
111
+ * settings={settings}
112
+ * colors={colors}
113
+ * fetchCycleData={fetchCycleData}
114
+ * selectedClient={client}
115
+ * onCycleWarning={(phase, date) => console.log('Warning:', phase, date)}
116
+ * />
117
+ * ```
118
+ */
119
+ export function CycleAwareDateSelection({
120
+ availableDates,
121
+ selectedDate,
122
+ onDateSelect,
123
+ settings,
124
+ colors,
125
+ fetchCycleData,
126
+ selectedClient,
127
+ onCycleWarning,
128
+ cycleAwareEnabled
129
+ }: CycleAwareDateSelectionProps) {
130
+ const [cyclePhases, setCyclePhases] = useState<CyclePhaseMap>({});
131
+ const [isLoading, setIsLoading] = useState(false);
132
+ const [error, setError] = useState<string | null>(null);
133
+
134
+ // Determine if cycle features should be shown
135
+ const showCycleFeatures = useMemo(() => {
136
+ if (cycleAwareEnabled !== undefined) return cycleAwareEnabled;
137
+ return !!(selectedClient?.cycle_opt_in && fetchCycleData);
138
+ }, [cycleAwareEnabled, selectedClient?.cycle_opt_in, fetchCycleData]);
139
+
140
+ // Fetch cycle data when client or dates change
141
+ useEffect(() => {
142
+ if (!showCycleFeatures || !fetchCycleData || !selectedClient) {
143
+ setCyclePhases({});
144
+ return;
145
+ }
146
+
147
+ if (availableDates.length === 0) {
148
+ return;
149
+ }
150
+
151
+ setIsLoading(true);
152
+ setError(null);
153
+
154
+ fetchCycleData(selectedClient, availableDates)
155
+ .then((phases) => {
156
+ setCyclePhases(phases);
157
+ })
158
+ .catch((err) => {
159
+ console.error('[CycleAwareDateSelection] Error fetching cycle data:', err);
160
+ setError(err.message || 'Failed to load cycle data');
161
+ setCyclePhases({});
162
+ })
163
+ .finally(() => {
164
+ setIsLoading(false);
165
+ });
166
+ }, [showCycleFeatures, fetchCycleData, selectedClient, availableDates]);
167
+
168
+ // Enhanced date selection handler with cycle awareness
169
+ const handleDateSelect = useCallback((date: string) => {
170
+ const phaseData = cyclePhases[date];
171
+
172
+ // Trigger warning callback if date is in avoid phase
173
+ if (phaseData?.phase === 'avoid' && onCycleWarning) {
174
+ onCycleWarning(phaseData, date);
175
+ }
176
+
177
+ // Call original handler with cycle data
178
+ onDateSelect(date, phaseData);
179
+ }, [cyclePhases, onDateSelect, onCycleWarning]);
180
+
181
+ return (
182
+ <div className="cycle-aware-date-selection space-y-4">
183
+ {/* Cycle Legend */}
184
+ {showCycleFeatures && Object.keys(cyclePhases).length > 0 && (
185
+ <div className="bg-white rounded-lg border border-gray-200 p-4">
186
+ <h4 className="font-medium mb-3 flex items-center" style={{ color: colors.primary }}>
187
+ <span className="mr-2">📅</span>
188
+ Your Cycle Calendar
189
+ </h4>
190
+ <div className="grid grid-cols-2 gap-3 text-sm">
191
+ {Object.entries(CYCLE_PHASES).map(([key, phase]) => (
192
+ key !== 'unknown' && (
193
+ <div key={key} className="flex items-center space-x-2">
194
+ <div
195
+ className="w-3 h-3 rounded-full border border-gray-300"
196
+ style={{ backgroundColor: phase.color }}
197
+ />
198
+ <span>{phase.label}</span>
199
+ </div>
200
+ )
201
+ ))}
202
+ </div>
203
+ <p className="text-xs opacity-70 mt-2">
204
+ Green dates are optimal for comfortable waxing appointments
205
+ </p>
206
+ </div>
207
+ )}
208
+
209
+ {/* Loading State */}
210
+ {showCycleFeatures && isLoading && (
211
+ <div className="text-center py-2 text-sm opacity-70">
212
+ Loading cycle predictions...
213
+ </div>
214
+ )}
215
+
216
+ {/* Error State */}
217
+ {showCycleFeatures && error && (
218
+ <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
219
+ <p className="text-yellow-800 text-sm">
220
+ Unable to load cycle predictions: {error}
221
+ </p>
222
+ </div>
223
+ )}
224
+
225
+ {/* Enhanced Calendar with Cycle Indicators */}
226
+ <DateSelection
227
+ availableDates={availableDates}
228
+ selectedDate={selectedDate}
229
+ onDateSelect={handleDateSelect}
230
+ settings={settings}
231
+ colors={colors}
232
+ // TODO: Pass cycle phases to DateSelection for rendering
233
+ // This requires updating DateSelection to accept and render cycle indicators
234
+ // For now, cycle indicators are shown separately via the legend
235
+ />
236
+
237
+ {/* Cycle Warning for Selected Date */}
238
+ {showCycleFeatures && selectedDate && cyclePhases[selectedDate]?.phase === 'avoid' && (
239
+ <div className="bg-red-50 border border-red-200 rounded-lg p-4">
240
+ <div className="flex items-start space-x-3">
241
+ <span className="text-red-500 text-xl">⚠️</span>
242
+ <div>
243
+ <h4 className="font-medium text-red-800 mb-1">
244
+ This date falls during your predicted period
245
+ </h4>
246
+ <p className="text-red-700 text-sm mb-3">
247
+ Waxing during this time may be more uncomfortable than usual.
248
+ Would you like to see better alternatives?
249
+ </p>
250
+ <div className="flex space-x-2">
251
+ <button
252
+ className="px-3 py-1 bg-red-100 text-red-700 rounded text-sm hover:bg-red-200 transition-colors"
253
+ onClick={() => {
254
+ // Find next optimal date
255
+ const optimalDates = availableDates.filter(d =>
256
+ cyclePhases[d]?.phase === 'optimal'
257
+ );
258
+ if (optimalDates.length > 0) {
259
+ handleDateSelect(optimalDates[0]);
260
+ }
261
+ }}
262
+ >
263
+ Show optimal dates
264
+ </button>
265
+ <button className="px-3 py-1 text-red-600 text-sm hover:underline">
266
+ Continue anyway
267
+ </button>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ )}
273
+ </div>
274
+ );
275
+ }
276
+
277
+ export default CycleAwareDateSelection;