@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.
- package/README.md +319 -0
- package/TENANT_DATA_INTEGRATION.md +402 -0
- package/TENANT_SETUP.md +316 -0
- package/components/BookingFlow/BookingFlow.tsx +790 -0
- package/components/BookingFlow/index.ts +5 -0
- package/components/BookingFlow/steps/AddonsSelection.tsx +118 -0
- package/components/BookingFlow/steps/Confirmation.tsx +185 -0
- package/components/BookingFlow/steps/ContactForm.tsx +292 -0
- package/components/BookingFlow/steps/CycleAwareDateSelection.tsx +277 -0
- package/components/BookingFlow/steps/DateSelection.tsx +473 -0
- package/components/BookingFlow/steps/ServiceSelection.tsx +315 -0
- package/components/BookingFlow/steps/TimeSelection.tsx +230 -0
- package/components/BookingFlow/steps/index.ts +10 -0
- package/components/BottomSheet/index.tsx +120 -0
- package/components/Forms/FormBlock.tsx +283 -0
- package/components/Forms/FormField.tsx +385 -0
- package/components/Forms/FormRenderer.tsx +216 -0
- package/components/Forms/FormValidation.ts +122 -0
- package/components/Forms/index.ts +4 -0
- package/components/HoldTimer/HoldTimer.tsx +266 -0
- package/components/HoldTimer/index.ts +2 -0
- package/components/SectionRenderer.tsx +558 -0
- package/components/Sections/About.tsx +145 -0
- package/components/Sections/BeforeAfter.tsx +81 -0
- package/components/Sections/BookingSection.tsx +76 -0
- package/components/Sections/Contact.tsx +103 -0
- package/components/Sections/FAQSection.tsx +239 -0
- package/components/Sections/FeatureContent.tsx +113 -0
- package/components/Sections/FeaturedLink.tsx +103 -0
- package/components/Sections/FixedInfoCard.tsx +189 -0
- package/components/Sections/Gallery.tsx +83 -0
- package/components/Sections/Header.tsx +78 -0
- package/components/Sections/Hero.tsx +178 -0
- package/components/Sections/ImageSection.tsx +147 -0
- package/components/Sections/InstagramFeed.tsx +38 -0
- package/components/Sections/LinkList.tsx +76 -0
- package/components/Sections/LocationMap.tsx +202 -0
- package/components/Sections/Logo.tsx +61 -0
- package/components/Sections/MinimalFooter.tsx +78 -0
- package/components/Sections/MinimalHeader.tsx +81 -0
- package/components/Sections/MinimalNavigation.tsx +63 -0
- package/components/Sections/Navbar.tsx +258 -0
- package/components/Sections/PricingTable.tsx +106 -0
- package/components/Sections/ScrollingTextDivider.tsx +138 -0
- package/components/Sections/ScrollingTextDivider.tsx.bak +138 -0
- package/components/Sections/ServicesPreview.tsx +129 -0
- package/components/Sections/SocialBar.tsx +177 -0
- package/components/Sections/Team.tsx +80 -0
- package/components/Sections/Testimonials.tsx +92 -0
- package/components/Sections/TextSection.tsx +116 -0
- package/components/Sections/VideoSection.tsx +178 -0
- package/components/Sections/index.ts +57 -0
- package/components/index.ts +21 -0
- package/dist/index-DAai7Glf.d.mts +474 -0
- package/dist/index-DAai7Glf.d.ts +474 -0
- package/dist/index.d.mts +1075 -0
- package/dist/index.d.ts +1075 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +22 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles/index.d.mts +1 -0
- package/dist/styles/index.d.ts +1 -0
- package/dist/styles/index.js +2 -0
- package/dist/styles/index.js.map +1 -0
- package/dist/styles/index.mjs +2 -0
- package/dist/styles/index.mjs.map +1 -0
- package/docs/API.md +849 -0
- package/docs/CALLBACKS.md +760 -0
- package/docs/COMPLETE_SESSION_SUMMARY.md +404 -0
- package/docs/DATA_SHAPES.md +684 -0
- package/docs/MIGRATION.md +662 -0
- package/docs/PAYMENT_INTEGRATION.md +766 -0
- package/docs/SESSION_SUMMARY.md +185 -0
- package/docs/STYLING.md +735 -0
- package/index.ts +4 -0
- package/lib/storage.ts +239 -0
- package/package.json +59 -0
- package/styles/animations.ts +210 -0
- package/styles/index.ts +1 -0
- package/tsconfig.json +32 -0
- package/tsup.config.ts +13 -0
- 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;
|