@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,473 @@
1
+ 'use client';
2
+
3
+ import { useState, useMemo } from 'react';
4
+ import { motion } from 'framer-motion';
5
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
6
+ import type { DateSelectionSettings } from '../../../types';
7
+ import { createEntranceAnimation } from '../../../styles';
8
+
9
+ interface DateSelectionProps {
10
+ availableDates: string[];
11
+ selectedDate: string | null;
12
+ onDateSelect: (date: string) => void;
13
+ settings: DateSelectionSettings;
14
+ colors: {
15
+ primary: string;
16
+ secondary: string;
17
+ };
18
+ cyclePhases?: Record<string, string>; // Map of date -> phase ('avoid', 'caution', 'optimal', 'neutral')
19
+ selectedService?: any; // Selected service object with name, price, description, duration
20
+ addons?: any[]; // Available addons
21
+ selectedAddons?: string[]; // Selected addon IDs
22
+ onAddonsChange?: (addonIds: string[]) => void; // Callback when addons change
23
+ addonPlacement?: string; // NEW: 'date_selection' | 'separate_step' - controls addon visibility
24
+ }
25
+
26
+ // Generate mock dates for preview mode
27
+ function generateMockDates(): string[] {
28
+ const dates: string[] = [];
29
+ const today = new Date();
30
+
31
+ // Set to start of today to avoid timezone issues
32
+ today.setHours(0, 0, 0, 0);
33
+
34
+ // Generate dates for current + next 2 months, skip some to show unavailable dates
35
+ for (let monthOffset = 0; monthOffset < 3; monthOffset++) {
36
+ const month = new Date(today.getFullYear(), today.getMonth() + monthOffset, 1);
37
+ const daysInMonth = new Date(month.getFullYear(), month.getMonth() + 1, 0).getDate();
38
+
39
+ for (let day = 1; day <= daysInMonth; day++) {
40
+ const dateObj = new Date(month.getFullYear(), month.getMonth(), day);
41
+
42
+ // Skip past dates
43
+ if (dateObj < today) continue;
44
+
45
+ // Skip some dates randomly to show unavailable styling (skip ~40% of dates)
46
+ if (Math.random() > 0.6) continue;
47
+
48
+ const dateStr = `${month.getFullYear()}-${String(month.getMonth() + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
49
+ dates.push(dateStr);
50
+ }
51
+ }
52
+
53
+ return dates;
54
+ }
55
+
56
+ // Cycle phase color mapping
57
+ const CYCLE_PHASE_COLORS: Record<string, { bg: string; text: string; label: string }> = {
58
+ avoid: { bg: '#FEE2E2', text: '#991B1B', label: 'Avoid' },
59
+ caution: { bg: '#FEF3C7', text: '#92400E', label: 'Caution' },
60
+ optimal: { bg: '#D1FAE5', text: '#065F46', label: 'Optimal' },
61
+ neutral: { bg: '#F3F4F6', text: '#4B5563', label: 'Neutral' }
62
+ };
63
+
64
+ export function DateSelection({
65
+ availableDates,
66
+ selectedDate,
67
+ onDateSelect,
68
+ settings,
69
+ colors,
70
+ cyclePhases,
71
+ selectedService,
72
+ addons = [],
73
+ selectedAddons = [],
74
+ onAddonsChange,
75
+ addonPlacement = 'date_selection' // Default to showing on date selection
76
+ }: DateSelectionProps) {
77
+ // DEBUG: Log addon data to troubleshoot visibility
78
+ console.log('[DateSelection] Addon Debug:', {
79
+ addonsLength: addons.length,
80
+ hasOnAddonsChange: !!onAddonsChange,
81
+ addonPlacement: addonPlacement,
82
+ showAddons: addonPlacement !== 'separate_step' && addons.length > 0 && !!onAddonsChange,
83
+ addons: addons,
84
+ selectedService: selectedService
85
+ });
86
+
87
+ const calendarView = settings.calendarView || 'month';
88
+ const headerText = settings.headerContent?.value || 'Select a Date';
89
+
90
+ // Detect preview mode vs live mode
91
+ const isPreviewMode = !availableDates || availableDates.length === 0;
92
+
93
+ // Use mock dates in preview mode, real dates in live mode
94
+ const effectiveDates = useMemo(() => {
95
+ return isPreviewMode ? generateMockDates() : availableDates;
96
+ }, [isPreviewMode, availableDates]);
97
+
98
+ // Determine initial month from first available date (today or later)
99
+ const initialMonth = useMemo(() => {
100
+ const today = new Date();
101
+ today.setHours(0, 0, 0, 0);
102
+
103
+ if (effectiveDates.length > 0) {
104
+ // Find first date that is today or later
105
+ const futureDate = effectiveDates.find(dateStr => {
106
+ const date = new Date(dateStr);
107
+ return date >= today;
108
+ });
109
+
110
+ if (futureDate) {
111
+ const firstDate = new Date(futureDate);
112
+ return `${firstDate.getFullYear()}-${String(firstDate.getMonth() + 1).padStart(2, '0')}`;
113
+ }
114
+ }
115
+
116
+ // Fallback to current month
117
+ return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}`;
118
+ }, [effectiveDates]);
119
+
120
+ const [currentMonth, setCurrentMonth] = useState(initialMonth);
121
+
122
+ // Create a Set for O(1) lookup of available dates
123
+ const availableDatesSet = useMemo(() => new Set(effectiveDates), [effectiveDates]);
124
+
125
+ // Parse current month
126
+ const [year, month] = currentMonth.split('-').map(Number);
127
+
128
+ // Get month name
129
+ const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
130
+ 'July', 'August', 'September', 'October', 'November', 'December'];
131
+ const monthName = monthNames[month - 1];
132
+
133
+ // Calculate calendar grid
134
+ const calendarDays = useMemo(() => {
135
+ const firstDay = new Date(year, month - 1, 1);
136
+ const lastDay = new Date(year, month, 0);
137
+ const startingDayOfWeek = firstDay.getDay();
138
+ const daysInMonth = lastDay.getDate();
139
+
140
+ const days = [];
141
+
142
+ // Add empty cells for days before the first of the month
143
+ for (let i = 0; i < startingDayOfWeek; i++) {
144
+ days.push(null);
145
+ }
146
+
147
+ // Add actual days
148
+ for (let day = 1; day <= daysInMonth; day++) {
149
+ const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
150
+ const dateObj = new Date(dateStr);
151
+ const today = new Date();
152
+ today.setHours(0, 0, 0, 0);
153
+
154
+ days.push({
155
+ day,
156
+ dateStr,
157
+ isAvailable: availableDatesSet.has(dateStr) && dateObj >= today
158
+ });
159
+ }
160
+
161
+ return days;
162
+ }, [year, month, availableDatesSet]);
163
+
164
+ // Check if we can navigate to previous/next month
165
+ const canGoPrevious = useMemo(() => {
166
+ const prevMonth = new Date(year, month - 2, 1);
167
+ const prevMonthStr = `${prevMonth.getFullYear()}-${String(prevMonth.getMonth() + 1).padStart(2, '0')}`;
168
+ return effectiveDates.some(date => date.startsWith(prevMonthStr));
169
+ }, [effectiveDates, year, month]);
170
+
171
+ const canGoNext = useMemo(() => {
172
+ const nextMonth = new Date(year, month, 1);
173
+ const nextMonthStr = `${nextMonth.getFullYear()}-${String(nextMonth.getMonth() + 1).padStart(2, '0')}`;
174
+ return effectiveDates.some(date => date.startsWith(nextMonthStr));
175
+ }, [effectiveDates, year, month]);
176
+
177
+ const handlePreviousMonth = () => {
178
+ if (!canGoPrevious) return;
179
+ const prevMonth = new Date(year, month - 2, 1);
180
+ setCurrentMonth(`${prevMonth.getFullYear()}-${String(prevMonth.getMonth() + 1).padStart(2, '0')}`);
181
+ };
182
+
183
+ const handleNextMonth = () => {
184
+ if (!canGoNext) return;
185
+ const nextMonth = new Date(year, month, 1);
186
+ setCurrentMonth(`${nextMonth.getFullYear()}-${String(nextMonth.getMonth() + 1).padStart(2, '0')}`);
187
+ };
188
+
189
+ const hasCycleData = cyclePhases && Object.keys(cyclePhases).length > 0;
190
+
191
+ // Calculate total price including addons
192
+ const calculateTotal = () => {
193
+ let total = selectedService?.price || 0;
194
+
195
+ if (selectedAddons.length > 0 && addons.length > 0) {
196
+ selectedAddons.forEach(addonId => {
197
+ const addon = addons.find(a => a.id === addonId);
198
+ if (addon) {
199
+ total += addon.addonPrice || addon.price || 0;
200
+ }
201
+ });
202
+ }
203
+
204
+ return total;
205
+ };
206
+
207
+ // Handle addon toggle
208
+ const handleAddonToggle = (addonId: string) => {
209
+ if (!onAddonsChange) return;
210
+
211
+ const isSelected = selectedAddons.includes(addonId);
212
+ if (isSelected) {
213
+ onAddonsChange(selectedAddons.filter(id => id !== addonId));
214
+ } else {
215
+ onAddonsChange([...selectedAddons, addonId]);
216
+ }
217
+ };
218
+
219
+ return (
220
+ <div className="space-y-6">
221
+ {/* Service Summary Card */}
222
+ {selectedService && (
223
+ <motion.div
224
+ {...createEntranceAnimation(0.05)}
225
+ className="bg-gradient-to-br from-white to-gray-50/30 rounded-xl border border-gray-200/80 p-6 shadow-sm hover:shadow-md transition-shadow"
226
+ >
227
+ <div className="flex items-start justify-between gap-4">
228
+ <div className="flex-1">
229
+ <div className="flex items-center gap-2 mb-3">
230
+ <h3 className="text-sm font-semibold tracking-wide uppercase" style={{ color: colors.primary }}>
231
+ Your Selection
232
+ </h3>
233
+ </div>
234
+ <div className="space-y-2">
235
+ <p className="font-bold text-lg text-gray-900">
236
+ {selectedService.name}
237
+ </p>
238
+ {selectedService.description && (
239
+ <p className="text-sm text-gray-600 leading-relaxed">{selectedService.description}</p>
240
+ )}
241
+ {selectedService.duration && (
242
+ <div className="flex items-center gap-1.5 text-xs text-gray-500">
243
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
244
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
245
+ </svg>
246
+ <span>{selectedService.duration} minutes</span>
247
+ </div>
248
+ )}
249
+ </div>
250
+ </div>
251
+ <div className="text-right">
252
+ <div className="text-xs text-gray-500 mb-1 font-medium">Price</div>
253
+ <div className="text-2xl font-bold" style={{ color: colors.primary }}>
254
+ ${selectedService.price}
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </motion.div>
259
+ )}
260
+
261
+ {/* Addon Selection - Only show if placement is on date_selection (not separate_step) */}
262
+ {addonPlacement !== 'separate_step' && addons.length > 0 && onAddonsChange && (
263
+ <motion.div
264
+ {...createEntranceAnimation(0.1)}
265
+ className="bg-white rounded-xl border border-gray-200/80 p-6 shadow-sm hover:shadow-md transition-shadow"
266
+ >
267
+ <div className="flex items-center gap-2 mb-4">
268
+ <h3 className="text-sm font-semibold tracking-wide uppercase" style={{ color: colors.primary }}>
269
+ Enhance Your Experience
270
+ </h3>
271
+ </div>
272
+
273
+ <div className="space-y-3">
274
+ {addons.map((addon) => {
275
+ const isSelected = selectedAddons.includes(addon.id);
276
+ const addonPrice = addon.addonPrice || addon.price || 0;
277
+
278
+ return (
279
+ <label
280
+ key={addon.id}
281
+ className="flex items-start gap-4 p-4 rounded-xl border-2 cursor-pointer transition-all group"
282
+ style={{
283
+ borderColor: isSelected ? colors.primary : '#E5E7EB',
284
+ backgroundColor: isSelected ? `${colors.primary}08` : 'transparent',
285
+ boxShadow: isSelected ? `0 0 0 3px ${colors.primary}15` : 'none'
286
+ }}
287
+ >
288
+ <div className="flex items-center pt-0.5">
289
+ <input
290
+ type="checkbox"
291
+ checked={isSelected}
292
+ onChange={() => handleAddonToggle(addon.id)}
293
+ className="w-5 h-5 rounded border-2 border-gray-300 cursor-pointer"
294
+ style={{ accentColor: colors.primary }}
295
+ />
296
+ </div>
297
+ <div className="flex-1 min-w-0">
298
+ <div className="flex items-start justify-between gap-3 mb-1">
299
+ <span className="font-semibold text-base text-gray-900 group-hover:text-gray-700 transition-colors">
300
+ {addon.name}
301
+ </span>
302
+ <span
303
+ className="text-base font-bold whitespace-nowrap flex-shrink-0"
304
+ style={{ color: colors.primary }}
305
+ >
306
+ +${addonPrice}
307
+ </span>
308
+ </div>
309
+ {addon.description && (
310
+ <p className="text-sm text-gray-600 leading-relaxed">{addon.description}</p>
311
+ )}
312
+ </div>
313
+ </label>
314
+ );
315
+ })}
316
+ </div>
317
+
318
+ {/* Updated Total */}
319
+ {selectedAddons.length > 0 && (
320
+ <div className="mt-6 pt-5 border-t-2 border-gray-100">
321
+ <div className="flex items-center justify-between px-2">
322
+ <div>
323
+ <div className="text-xs text-gray-500 mb-1 font-medium uppercase tracking-wide">New Total</div>
324
+ <div className="text-sm text-gray-600">
325
+ Service + {selectedAddons.length} add-on{selectedAddons.length > 1 ? 's' : ''}
326
+ </div>
327
+ </div>
328
+ <div
329
+ className="text-3xl font-bold"
330
+ style={{ color: colors.primary }}
331
+ >
332
+ ${calculateTotal()}
333
+ </div>
334
+ </div>
335
+ </div>
336
+ )}
337
+ </motion.div>
338
+ )}
339
+
340
+ <motion.h2
341
+ {...createEntranceAnimation(0.15)}
342
+ className="text-xl sm:text-2xl font-bold text-center sm:text-left"
343
+ style={{ color: colors.primary }}
344
+ >
345
+ {headerText}
346
+ </motion.h2>
347
+
348
+ {/* Cycle Legend */}
349
+ {hasCycleData && (
350
+ <motion.div
351
+ {...createEntranceAnimation(0.1)}
352
+ className="bg-white rounded-lg border border-gray-200 px-4 py-3"
353
+ >
354
+ <div className="flex items-center justify-between flex-wrap gap-2 text-xs">
355
+ <span className="font-medium flex items-center" style={{ color: colors.primary }}>
356
+ Cycle Calendar:
357
+ </span>
358
+ <div className="flex items-center gap-3">
359
+ {Object.entries(CYCLE_PHASE_COLORS).map(([phase, phaseInfo]) => (
360
+ <div key={phase} className="flex items-center space-x-1.5">
361
+ <div
362
+ className="w-3 h-3 rounded border border-gray-300"
363
+ style={{ backgroundColor: phaseInfo.bg }}
364
+ />
365
+ <span>{phaseInfo.label}</span>
366
+ </div>
367
+ ))}
368
+ </div>
369
+ </div>
370
+ </motion.div>
371
+ )}
372
+
373
+ {calendarView === 'month' ? (
374
+ <div className="border border-gray-200 rounded-lg p-4">
375
+ <div className="flex items-center justify-between mb-4">
376
+ <button
377
+ className={`p-2 rounded transition-colors ${
378
+ canGoPrevious ? 'hover:bg-gray-100' : 'opacity-30 cursor-not-allowed'
379
+ }`}
380
+ onClick={handlePreviousMonth}
381
+ disabled={!canGoPrevious}
382
+ >
383
+ <ChevronLeft className="w-4 h-4" />
384
+ </button>
385
+ <p className="font-semibold">{monthName} {year}</p>
386
+ <button
387
+ className={`p-2 rounded transition-colors ${
388
+ canGoNext ? 'hover:bg-gray-100' : 'opacity-30 cursor-not-allowed'
389
+ }`}
390
+ onClick={handleNextMonth}
391
+ disabled={!canGoNext}
392
+ >
393
+ <ChevronRight className="w-4 h-4" />
394
+ </button>
395
+ </div>
396
+ <div className="grid grid-cols-7 gap-2">
397
+ {['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(day => (
398
+ <div key={day} className="text-center text-xs font-medium py-2 opacity-60">
399
+ {day}
400
+ </div>
401
+ ))}
402
+ {calendarDays.map((dayInfo, index) => {
403
+ if (!dayInfo) {
404
+ return <div key={`empty-${index}`} />;
405
+ }
406
+
407
+ const { day, dateStr, isAvailable } = dayInfo;
408
+ const isSelected = selectedDate === dateStr;
409
+ const cyclePhase = cyclePhases?.[dateStr];
410
+ const phaseColors = cyclePhase ? CYCLE_PHASE_COLORS[cyclePhase] : null;
411
+
412
+ return (
413
+ <button
414
+ key={dateStr}
415
+ onClick={() => isAvailable && onDateSelect(dateStr)}
416
+ disabled={!isAvailable}
417
+ className={`aspect-square rounded-lg text-sm transition-all ${
418
+ isSelected
419
+ ? 'text-white shadow-lg font-semibold'
420
+ : isAvailable
421
+ ? 'hover:scale-105 cursor-pointer'
422
+ : 'bg-gray-50 text-gray-300 cursor-not-allowed opacity-40'
423
+ }`}
424
+ style={{
425
+ backgroundColor: isSelected
426
+ ? colors.primary
427
+ : phaseColors?.bg || undefined,
428
+ color: isSelected
429
+ ? 'white'
430
+ : phaseColors?.text || undefined
431
+ }}
432
+ >
433
+ {day}
434
+ </button>
435
+ );
436
+ })}
437
+ </div>
438
+ </div>
439
+ ) : (
440
+ <div className="grid grid-cols-5 gap-3">
441
+ {effectiveDates.slice(0, 5).map(date => {
442
+ const dateObj = new Date(date);
443
+ const day = dateObj.getDate();
444
+ const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
445
+ const dayName = dayNames[dateObj.getDay()];
446
+ const isSelected = selectedDate === date;
447
+
448
+ return (
449
+ <button
450
+ key={date}
451
+ onClick={() => onDateSelect(date)}
452
+ className={`p-4 rounded-lg border-2 transition-all text-center ${
453
+ isSelected
454
+ ? 'border-current text-white'
455
+ : 'border-gray-200 hover:border-gray-300'
456
+ }`}
457
+ style={{
458
+ borderColor: isSelected ? colors.primary : undefined,
459
+ backgroundColor: isSelected ? colors.primary : undefined
460
+ }}
461
+ >
462
+ <p className="text-xs mb-1 opacity-75">{dayName}</p>
463
+ <p className="text-2xl font-semibold">{day}</p>
464
+ </button>
465
+ );
466
+ })}
467
+ </div>
468
+ )}
469
+ </div>
470
+ );
471
+ }
472
+
473
+ export default DateSelection;