@ticketboothapp/booking 0.1.19 → 0.1.20

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 (154) hide show
  1. package/package.json +1 -1
  2. package/src/components/BookingWidget.tsx +282 -26
  3. package/src/components/ManageBookingView.tsx +75 -23
  4. package/src/components/booking/BookingProductGrid.tsx +1 -1
  5. package/src/components/booking/Calendar.module.css +3 -3
  6. package/src/components/booking/CheckoutForm.tsx +1 -1
  7. package/src/components/booking/InfoTooltip.tsx +2 -13
  8. package/src/components/booking/PickupLocationSelector.tsx +2 -2
  9. package/src/components/booking/PriceBreakdown.tsx +11 -34
  10. package/src/index.ts +3 -1
  11. package/src/app/photo-sessions/photo-packages.ts +0 -75
  12. package/src/assets/icons/minus.svg +0 -7
  13. package/src/assets/icons/partner-logos/getyourguide.svg +0 -8
  14. package/src/assets/icons/plus.svg +0 -3
  15. package/src/colours.css +0 -23
  16. package/src/components/BookingDetails.module.css +0 -1591
  17. package/src/components/BookingDetails.tsx +0 -2264
  18. package/src/components/JobApplicationDialog.module.css +0 -440
  19. package/src/components/JobApplicationDialog.tsx +0 -620
  20. package/src/components/PhoneInputWithCountry.module.css +0 -131
  21. package/src/components/PhoneInputWithCountry.tsx +0 -44
  22. package/src/components/PickupLocationDialog.module.css +0 -360
  23. package/src/components/PickupLocationDialog.tsx +0 -357
  24. package/src/components/PickupLocationMap.tsx +0 -110
  25. package/src/components/PostBookingDependentAddOnUpsell.module.css +0 -174
  26. package/src/components/PostBookingDependentAddOnUpsell.tsx +0 -407
  27. package/src/components/accordion.css +0 -27
  28. package/src/components/accordion.tsx +0 -29
  29. package/src/components/analytics/AnalyticsConsentRestore.tsx +0 -19
  30. package/src/components/analytics/AnalyticsScripts.tsx +0 -106
  31. package/src/components/analytics/CookieConsentBanner.css +0 -86
  32. package/src/components/analytics/CookieConsentBanner.tsx +0 -102
  33. package/src/components/bottom-sheet.module.css +0 -78
  34. package/src/components/bottom-sheet.tsx +0 -60
  35. package/src/components/breadcrumb.module.css +0 -40
  36. package/src/components/breadcrumb.tsx +0 -36
  37. package/src/components/button.css +0 -245
  38. package/src/components/button.tsx +0 -152
  39. package/src/components/client-bottom-sheet.tsx +0 -14
  40. package/src/components/colorable-svg.tsx +0 -29
  41. package/src/components/conditional-footer.tsx +0 -27
  42. package/src/components/contact-us.module.css +0 -147
  43. package/src/components/contact-us.tsx +0 -49
  44. package/src/components/email-signup.css +0 -151
  45. package/src/components/email-signup.tsx +0 -63
  46. package/src/components/faq-wrapper.module.css +0 -47
  47. package/src/components/faq-wrapper.tsx +0 -15
  48. package/src/components/footer.css +0 -187
  49. package/src/components/footer.tsx +0 -143
  50. package/src/components/global-simple-modal.tsx +0 -33
  51. package/src/components/google-review-summary.module.css +0 -77
  52. package/src/components/google-review-summary.tsx +0 -50
  53. package/src/components/hero-image.css +0 -13
  54. package/src/components/hero-image.tsx +0 -44
  55. package/src/components/image.css +0 -29
  56. package/src/components/image.tsx +0 -113
  57. package/src/components/language-aware-link.tsx +0 -72
  58. package/src/components/language-switcher.module.css +0 -124
  59. package/src/components/language-switcher.tsx +0 -75
  60. package/src/components/map-section.css +0 -59
  61. package/src/components/map-section.tsx +0 -63
  62. package/src/components/navbar.module.css +0 -152
  63. package/src/components/navbar.tsx +0 -125
  64. package/src/components/parallax-provider.tsx +0 -11
  65. package/src/components/product-tag.module.css +0 -30
  66. package/src/components/product-tag.tsx +0 -34
  67. package/src/components/product-theme-pages/best-option.module.css +0 -70
  68. package/src/components/product-theme-pages/best-option.tsx +0 -35
  69. package/src/components/product-theme-pages/extended-tour-options.module.css +0 -22
  70. package/src/components/product-theme-pages/extended-tour-options.tsx +0 -11
  71. package/src/components/product-theme-pages/image-modal.tsx +0 -248
  72. package/src/components/product-theme-pages/photo-gallery.module.css +0 -200
  73. package/src/components/product-theme-pages/photo-gallery.tsx +0 -90
  74. package/src/components/product-theme-pages/product-theme-page-layout.module.css +0 -13
  75. package/src/components/product-theme-pages/product-theme-page-layout.tsx +0 -67
  76. package/src/components/product-theme-pages/top-of-fold.module.css +0 -179
  77. package/src/components/product-theme-pages/top-of-fold.tsx +0 -80
  78. package/src/components/product-tile/image-only-product-tile-desktop.module.css +0 -106
  79. package/src/components/product-tile/image-only-product-tile-desktop.tsx +0 -56
  80. package/src/components/product-tile/image-only-product-tile-mobile.module.css +0 -122
  81. package/src/components/product-tile/image-only-product-tile-mobile.tsx +0 -89
  82. package/src/components/product-tile/image-only-product-tile.tsx +0 -44
  83. package/src/components/product-tile/product-tile-card.module.css +0 -84
  84. package/src/components/product-tile/product-tile-card.tsx +0 -61
  85. package/src/components/review-highlights-section.css +0 -85
  86. package/src/components/review-highlights-section.tsx +0 -127
  87. package/src/components/season-closure-overlay.module.css +0 -99
  88. package/src/components/season-closure-overlay.tsx +0 -98
  89. package/src/components/simple-modal.tsx +0 -69
  90. package/src/components/simple-top-of-fold.module.css +0 -76
  91. package/src/components/simple-top-of-fold.tsx +0 -34
  92. package/src/components/spacer.css +0 -41
  93. package/src/components/spacer.tsx +0 -23
  94. package/src/components/star-rating.module.css +0 -74
  95. package/src/components/star-rating.tsx +0 -48
  96. package/src/components/terms/TermsContent.tsx +0 -178
  97. package/src/components/title-subtitle.module.css +0 -10
  98. package/src/components/title-subtitle.tsx +0 -30
  99. package/src/components/translatable-reviews.tsx +0 -75
  100. package/src/components/value-pill.module.css +0 -59
  101. package/src/components/value-pill.tsx +0 -46
  102. package/src/components/value-props.css +0 -185
  103. package/src/components/value-props.tsx +0 -88
  104. package/src/constants/booking-guide-quiz.ts +0 -64
  105. package/src/constants/contact-info.ts +0 -2
  106. package/src/constants/faq.ts +0 -44
  107. package/src/constants/images.ts +0 -556
  108. package/src/constants/json-ld/faq-json-ld.tsx +0 -170
  109. package/src/constants/json-ld/homepage-json-ld.tsx +0 -138
  110. package/src/constants/json-ld/job-posting-json-ld.tsx +0 -92
  111. package/src/constants/json-ld/organization-json-ld.tsx +0 -62
  112. package/src/constants/json-ld/page-json-ld.tsx +0 -6
  113. package/src/constants/json-ld/product-json-ld.tsx +0 -154
  114. package/src/constants/json-ld/review-json-ld.tsx +0 -377
  115. package/src/constants/navigation-links/footer-links.ts +0 -48
  116. package/src/constants/navigation-links/nav-bar-links.ts +0 -41
  117. package/src/constants/navigation-links/navigation-link.ts +0 -6
  118. package/src/constants/pill-values.ts +0 -210
  119. package/src/constants/products.ts +0 -155
  120. package/src/constants/quiz-recommendations.ts +0 -506
  121. package/src/constants/reviews.ts +0 -75
  122. package/src/constants/staff.ts +0 -197
  123. package/src/constants/value-props.ts +0 -58
  124. package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -61
  125. package/src/data/dap-descriptions/session-elopements.en.json +0 -60
  126. package/src/data/dap-descriptions/session-proposals.en.json +0 -60
  127. package/src/data/product-descriptions/afternoon-delight.en.json +0 -35
  128. package/src/data/product-descriptions/emerald-lake-escape.en.json +0 -68
  129. package/src/data/product-descriptions/lake-louise-adventure.en.json +0 -74
  130. package/src/data/product-descriptions/moraine-lake-adventure.en.json +0 -78
  131. package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +0 -65
  132. package/src/data/product-descriptions/moraine-lake-sunrise.en.json +0 -64
  133. package/src/data/product-descriptions/private-tour.en.json +0 -80
  134. package/src/data/product-descriptions/two-lakes-combo.en.json +0 -65
  135. package/src/data/products-config.json +0 -101
  136. package/src/hooks/use-bottom-sheet.tsx +0 -15
  137. package/src/hooks/use-simple-modal.tsx +0 -27
  138. package/src/hooks/useBookingSourceMetadataFromLocation.ts +0 -21
  139. package/src/hooks/useEmailSubscription.tsx +0 -103
  140. package/src/hooks/useEmbeddedInIframe.ts +0 -16
  141. package/src/hooks/useIsBookingLaunchLive.ts +0 -49
  142. package/src/hooks/useQuiz.tsx +0 -210
  143. package/src/providers/bottom-sheet-provider.tsx +0 -40
  144. package/src/providers/dependent-add-on-dialog-provider.tsx +0 -105
  145. package/src/radius.css +0 -5
  146. package/src/spacing.css +0 -7
  147. package/src/strings/en.json +0 -1774
  148. package/src/strings/es.json +0 -1573
  149. package/src/strings/fr.json +0 -1573
  150. package/src/strings/index.js +0 -23
  151. package/src/text-style.css +0 -97
  152. package/src/types/fareharbor.d.ts +0 -12
  153. package/src/types/quiz.ts +0 -59
  154. package/src/utils/currency-converter.ts +0 -101
@@ -1,103 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect, useCallback } from 'react';
4
- import { db } from '@/lib/firebase';
5
- import { collection, addDoc, query, where, getDocs } from 'firebase/firestore';
6
-
7
- interface UseEmailSubscriptionProps {
8
- initialEmail?: string;
9
- autoSubmit?: boolean;
10
- source?: string;
11
- }
12
-
13
- export function useEmailSubscription({ initialEmail = '', autoSubmit = false, source }: UseEmailSubscriptionProps) {
14
- const [email, setEmail] = useState(initialEmail);
15
- const [isSubmitting, setIsSubmitting] = useState(false);
16
- const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error' | 'duplicate'>('idle');
17
-
18
- const subscribeEmail = useCallback(async (emailToSubscribe: string) => {
19
- if (!emailToSubscribe || isSubmitting) return false;
20
-
21
- setIsSubmitting(true);
22
- setSubmitStatus('idle');
23
-
24
- try {
25
- // Check if email already exists
26
- const emailQuery = query(
27
- collection(db, 'mail'),
28
- where('to', '==', emailToSubscribe)
29
- );
30
- const existingEmails = await getDocs(emailQuery);
31
-
32
- if (!existingEmails.empty) {
33
- setSubmitStatus('duplicate');
34
- setIsSubmitting(false);
35
- return false;
36
- }
37
-
38
- // Add email to Firestore (this will trigger your email function)
39
- await addDoc(collection(db, 'mail'), {
40
- to: emailToSubscribe,
41
- message: {
42
- subject: 'Via Via Moraine Lake Shuttle - 2026 Season Updates',
43
- text: `Hi there! Thank you for subscribing to our updates! We're excited to keep you informed about the 2026 season at Moraine Lake, Lake Louise, and Emerald Lake. Moraine Lake Road is currently closed as Parks Canada closes the access road every year for the winter season. We'll get back to you with updates about 2026 ticket release dates and special offers as soon as we have more information. Best regards, The Via Via Team`,
44
- html: `
45
- <div style="font-family: Figtree, sans-serif; max-width: 700px; margin: 0 auto; padding: 20px;">
46
- <h2 style="color: #ff4d00; text-align: center; margin-bottom: 20px;">Thanks for signing up!</h2>
47
-
48
- <p style="color: inherit;">Hi there,</p>
49
- <p style="color: inherit;">Thank you for subscribing to our updates! We're excited to keep you informed about the 2026 season at Moraine Lake, Lake Louise, and Emerald Lake.</p>
50
-
51
- <p style="color: inherit;">Moraine Lake Road is currently closed as Parks Canada closes the access road every year for the winter season.</p>
52
-
53
- <p style="color: inherit;">We'll get back to you with updates about 2026 ticket release dates and special offers as soon as we have more information.</p>
54
-
55
- <p style="color: inherit;">In the meantime, feel free to follow us on <a href="https://www.instagram.com/viaviamorainelake/" style="color: #ff4d00; -webkit-text-size-adjust: 100%;">Instagram</a> for the latest news and beautiful photos from Banff National Park.</p>
56
-
57
- <p style="color: inherit;">Best regards,<br>
58
- The Via Via Team 🌄🚐</p>
59
-
60
- <hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
61
-
62
- <div style="text-align: center; margin-bottom: 30px;">
63
- <img src="https://viaviamorainelake.b-cdn.net/logo192.png?format=png&width=200" alt="Via Via Moraine Lake Shuttle" style="max-width: 200px; height: auto;">
64
- </div>
65
- <p style="font-size: 12px; color: #666; text-align: center;">
66
- Via Via Moraine Lake Shuttle • Canmore, Alberta<br>
67
- <a href="https://viaviamorainelake.com/?utm_source=email&utm_medium=sign-up-email&utm_campaign=2026-updates-signup-confirmation" style="color: #ff4d00;">viaviamorainelake.com</a><br>
68
- <a href="https://viaviamorainelake.com/unsubscribe?email=${emailToSubscribe}" style="color: #ff4d00; font-size: 11px;">Unsubscribe</a>
69
- </p>
70
- </div>
71
- `
72
- },
73
- createdAt: new Date(),
74
- source: source || 'unknown'
75
- });
76
-
77
- setSubmitStatus('success');
78
- setEmail('');
79
- return true;
80
- } catch (error) {
81
- console.error('Email signup error:', error);
82
- setSubmitStatus('error');
83
- return false;
84
- } finally {
85
- setIsSubmitting(false);
86
- }
87
- }, [isSubmitting, source]);
88
-
89
- // Auto-submit if requested and email is provided
90
- useEffect(() => {
91
- if (autoSubmit && initialEmail && submitStatus === 'idle') {
92
- subscribeEmail(initialEmail);
93
- }
94
- }, [autoSubmit, initialEmail, submitStatus, subscribeEmail]);
95
-
96
- return {
97
- email,
98
- setEmail,
99
- isSubmitting,
100
- submitStatus,
101
- subscribeEmail
102
- };
103
- }
@@ -1,16 +0,0 @@
1
- 'use client';
2
-
3
- import { useLayoutEffect, useState } from 'react';
4
-
5
- /** True when this document is loaded inside an iframe (e.g. partner portal manage-booking embed). */
6
- export function useEmbeddedInIframe(): boolean {
7
- const [embedded, setEmbedded] = useState(() => {
8
- if (typeof window === 'undefined') return false;
9
- return window.parent !== window.self;
10
- });
11
- useLayoutEffect(() => {
12
- if (typeof window === 'undefined') return;
13
- setEmbedded(window.parent !== window.self);
14
- }, []);
15
- return embedded;
16
- }
@@ -1,49 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect } from 'react';
4
- import { BOOKING_LAUNCH_AT } from '@/lib/booking-constants';
5
-
6
- /**
7
- * Returns whether the full booking flow is live (past launch time).
8
- * Before launch: shows partial flow (collage + tour description).
9
- * After launch: shows full flow (calendar, checkout, etc.).
10
- *
11
- * Schedules a state update at launch time so users on the site at 8am
12
- * see the full flow without refreshing.
13
- *
14
- * Dev override: ?booking_preview=full or ?booking_preview=partial in the URL
15
- * forces the flow for local testing (e.g. localhost:3000/?booking_preview=partial).
16
- */
17
- export function useIsBookingLaunchLive(): boolean {
18
- const [isLive, setIsLive] = useState(false);
19
-
20
- useEffect(() => {
21
- // Dev override for local testing
22
- if (typeof window !== 'undefined') {
23
- const params = new URLSearchParams(window.location.search);
24
- const preview = params.get('booking_preview');
25
- if (preview === 'full') {
26
- setIsLive(true);
27
- return;
28
- }
29
- if (preview === 'partial') {
30
- setIsLive(false);
31
- return;
32
- }
33
- }
34
-
35
- const launchMs = BOOKING_LAUNCH_AT.getTime();
36
- const now = Date.now();
37
-
38
- if (now >= launchMs) {
39
- setIsLive(true);
40
- return;
41
- }
42
-
43
- const msUntil = launchMs - now;
44
- const id = setTimeout(() => setIsLive(true), msUntil);
45
- return () => clearTimeout(id);
46
- }, []);
47
-
48
- return isLive;
49
- }
@@ -1,210 +0,0 @@
1
- 'use client';
2
-
3
- import { createContext, useContext, useCallback, useState, ReactNode, Dispatch, SetStateAction } from 'react';
4
- import { QuizState, UserAnswers } from '@/types/quiz';
5
- import { QUIZ_QUESTIONS } from '@/constants/booking-guide-quiz';
6
- import { getQuizRecommendations } from '@/constants/quiz-recommendations';
7
-
8
- interface QuizContextType {
9
- state: QuizState;
10
- setState: Dispatch<SetStateAction<QuizState>>;
11
- submitAnswer: (questionId: string, answerId: string) => boolean;
12
- toggleAnswer: (questionId: string, answerId: string) => void;
13
- getSelectedAnswers: (questionId: string) => string[];
14
- goToNextQuestion: () => void;
15
- goToPreviousQuestion: () => void;
16
- restartQuiz: () => void;
17
- getRecommendedProducts: () => ReturnType<typeof getQuizRecommendations>;
18
- getCurrentQuestion: () => typeof QUIZ_QUESTIONS[0] | undefined;
19
- deselectAnswer: (questionId: string) => boolean;
20
- getProgress: () => number;
21
- getVisibleQuestions: () => typeof QUIZ_QUESTIONS[0][];
22
- }
23
-
24
- const QuizContext = createContext<QuizContextType | undefined>(undefined);
25
-
26
- const initialState: QuizState = {
27
- currentQuestionIndex: 0,
28
- answers: {},
29
- isComplete: false,
30
- };
31
-
32
- export function QuizProvider({ children }: { children: ReactNode }) {
33
- const [state, setState] = useState<QuizState>(initialState);
34
-
35
- const getVisibleQuestions = useCallback((answers = state.answers) => {
36
- return QUIZ_QUESTIONS.filter(question => {
37
- if (!question.showIf) return true;
38
- return question.showIf(answers);
39
- });
40
- }, [state.answers]);
41
-
42
- const getCurrentQuestion = useCallback(() => {
43
- const visibleQuestions = getVisibleQuestions();
44
- return visibleQuestions[state.currentQuestionIndex];
45
- }, [state.currentQuestionIndex, getVisibleQuestions]);
46
-
47
- const getSelectedAnswers = useCallback((questionId: string): string[] => {
48
- const answer = state.answers[questionId];
49
- if (!answer) return [];
50
-
51
- const question = QUIZ_QUESTIONS.find(q => q.id === questionId);
52
- if (question?.isMultiSelect) {
53
- return Array.isArray(answer) ? answer : [answer];
54
- }
55
- return [answer as string];
56
- }, [state.answers]);
57
-
58
- const submitAnswer = useCallback((questionId: string, answerId: string) => {
59
- const question = QUIZ_QUESTIONS.find(q => q.id === questionId);
60
-
61
- setState(prev => {
62
- if (question?.isMultiSelect) {
63
- const currentAnswers = Array.isArray(prev.answers[questionId])
64
- ? prev.answers[questionId] as string[]
65
- : [];
66
-
67
- const updatedAnswers = currentAnswers.includes(answerId)
68
- ? currentAnswers.filter(id => id !== answerId)
69
- : [...currentAnswers, answerId];
70
-
71
- // If this is the activities question, handle hiking check
72
- if (questionId === 'what_activities') {
73
- const shouldContinue = updatedAnswers.includes('hiking');
74
- return {
75
- ...prev,
76
- answers: {
77
- ...prev.answers,
78
- [questionId]: updatedAnswers
79
- },
80
- isComplete: !shouldContinue
81
- };
82
- }
83
-
84
- return {
85
- ...prev,
86
- answers: {
87
- ...prev.answers,
88
- [questionId]: updatedAnswers
89
- }
90
- };
91
- }
92
-
93
- // For single select, just store the value directly
94
- return {
95
- ...prev,
96
- answers: {
97
- ...prev.answers,
98
- [questionId]: answerId
99
- }
100
- };
101
- });
102
-
103
- // Return true unless it's activities without hiking
104
- if (questionId === 'what_activities') {
105
- const answers = getSelectedAnswers(questionId);
106
- return answers.includes('hiking');
107
- }
108
- return true;
109
- }, [getSelectedAnswers]);
110
-
111
- const toggleAnswer = useCallback((questionId: string, answerId: string) => {
112
- setState(prev => {
113
- const currentAnswers = Array.isArray(prev.answers[questionId])
114
- ? prev.answers[questionId] as string[]
115
- : [];
116
-
117
- const updatedAnswers = currentAnswers.includes(answerId)
118
- ? currentAnswers.filter(id => id !== answerId)
119
- : [...currentAnswers, answerId];
120
-
121
- return {
122
- ...prev,
123
- answers: {
124
- ...prev.answers,
125
- [questionId]: updatedAnswers
126
- }
127
- };
128
- });
129
- }, []);
130
-
131
- const goToNextQuestion = useCallback(() => {
132
- setState(prev => ({
133
- ...prev,
134
- currentQuestionIndex: prev.currentQuestionIndex + 1
135
- }));
136
- }, []);
137
-
138
- const goToPreviousQuestion = useCallback(() => {
139
- setState(prev => ({
140
- ...prev,
141
- currentQuestionIndex: Math.max(0, prev.currentQuestionIndex - 1),
142
- isComplete: false
143
- }));
144
- }, []);
145
-
146
- const restartQuiz = useCallback(() => {
147
- setState(initialState);
148
- }, []);
149
-
150
- const getRecommendedProducts = useCallback(() => {
151
- return getQuizRecommendations(state.answers);
152
- }, [state.answers]);
153
-
154
- const deselectAnswer = useCallback((questionId: string) => {
155
- const newAnswers = { ...state.answers };
156
- delete newAnswers[questionId];
157
-
158
- // Check if removing this answer makes any questions disappear
159
- const nextVisible = QUIZ_QUESTIONS.filter(question => {
160
- if (!question.showIf) return true;
161
- return question.showIf(newAnswers);
162
- });
163
-
164
- // If we're currently on a question that will disappear, mark as complete
165
- const willCurrentQuestionDisappear = state.currentQuestionIndex >= nextVisible.length;
166
-
167
- setState(prev => ({
168
- ...prev,
169
- answers: newAnswers,
170
- isComplete: willCurrentQuestionDisappear
171
- }));
172
-
173
- return !willCurrentQuestionDisappear;
174
- }, [state.currentQuestionIndex, state.answers]);
175
-
176
- const getProgress = useCallback(() => {
177
- const visibleQuestions = getVisibleQuestions();
178
- return ((state.currentQuestionIndex + 1) / visibleQuestions.length) * 100;
179
- }, [state.currentQuestionIndex, getVisibleQuestions]);
180
-
181
- const value = {
182
- state,
183
- setState,
184
- submitAnswer,
185
- toggleAnswer,
186
- getSelectedAnswers,
187
- goToNextQuestion,
188
- goToPreviousQuestion,
189
- restartQuiz,
190
- getRecommendedProducts,
191
- getCurrentQuestion,
192
- deselectAnswer,
193
- getProgress,
194
- getVisibleQuestions
195
- };
196
-
197
- return (
198
- <QuizContext.Provider value={value}>
199
- {children}
200
- </QuizContext.Provider>
201
- );
202
- }
203
-
204
- export function useQuiz() {
205
- const context = useContext(QuizContext);
206
- if (context === undefined) {
207
- throw new Error('useQuiz must be used within a QuizProvider');
208
- }
209
- return context;
210
- }
@@ -1,40 +0,0 @@
1
- 'use client';
2
-
3
- import { createContext, useContext, useState } from 'react';
4
- import BottomSheet from '@/components/bottom-sheet';
5
-
6
- type BottomSheetContextType = {
7
- openSheet: (content: React.ReactNode) => void;
8
- closeSheet: () => void;
9
- };
10
-
11
- const BottomSheetContext = createContext<BottomSheetContextType | null>(null);
12
-
13
- export function BottomSheetProvider({ children }: { children: React.ReactNode }) {
14
- const [isOpen, setIsOpen] = useState(false);
15
- const [content, setContent] = useState<React.ReactNode>(null);
16
-
17
- const openSheet = (content: React.ReactNode) => {
18
- setContent(content);
19
- setIsOpen(true);
20
- };
21
-
22
- const closeSheet = () => {
23
- setIsOpen(false);
24
- };
25
-
26
- return (
27
- <BottomSheetContext.Provider value={{ openSheet, closeSheet }}>
28
- {children}
29
- <BottomSheet isOpen={isOpen} onClose={closeSheet}>
30
- {content}
31
- </BottomSheet>
32
- </BottomSheetContext.Provider>
33
- );
34
- }
35
-
36
- export function useBottomSheet() {
37
- const context = useContext(BottomSheetContext);
38
- if (!context) throw new Error('useBottomSheet must be used within BottomSheetProvider');
39
- return context;
40
- }
@@ -1,105 +0,0 @@
1
- 'use client';
2
-
3
- import {
4
- createContext,
5
- useCallback,
6
- useContext,
7
- useMemo,
8
- useRef,
9
- useState,
10
- type ReactNode,
11
- } from 'react';
12
- import type { PhotoDapSlug } from '@/lib/photo-dap-config';
13
-
14
- export type DependentAddOnProductOptionChoice = {
15
- dependentAddOnProductOptionId: string;
16
- label: string;
17
- photosLabel?: string;
18
- startingAtLabel?: string;
19
- };
20
-
21
- export type DependentAddOnDialogOpenPayload = {
22
- /** Card title shown in dialog header */
23
- productDisplayTitle: string;
24
- dependentAddOnProductId: string;
25
- /** Fixed catalog option (no picker) */
26
- dependentAddOnProductOptionId?: string;
27
- /** When provided without a fixed option id, user picks one (e.g. 30 / 60 / 90 min) */
28
- productOptions?: DependentAddOnProductOptionChoice[];
29
- /**
30
- * Default session-length id when multiple `productOptions` exist (e.g. manage-booking upsell probed 30 min).
31
- * Unlike `dependentAddOnProductOptionId`, this does not hide the picker — the user can switch length.
32
- */
33
- initialSelectedProductOptionId?: string;
34
- /** Hero + grid images (DapFlowCollage); Bunny CDN IDs */
35
- collageImageIds?: string[];
36
- /** Loads expandable copy from dap-descriptions */
37
- dapDescriptionSlug?: PhotoDapSlug;
38
- /**
39
- * From DAP catalog / TicketBooth product — days before the photo session for full-refund cancellation.
40
- * Availability API may override when it returns the same field.
41
- */
42
- cancellationDaysBeforeSession: number;
43
- /**
44
- * Pre-fill primary booking reference (e.g. manage-booking upsell after shuttle checkout).
45
- * Accepts short or bookRef_ form; dialog normalizes for display.
46
- */
47
- initialPrimaryBookingReference?: string;
48
- /** Optional pre-fill for booking-owner verification on DAP availability checks. */
49
- initialPrimaryBookingLastName?: string;
50
- };
51
-
52
- interface DependentAddOnDialogContextValue {
53
- isOpen: boolean;
54
- payload: DependentAddOnDialogOpenPayload | null;
55
- open: (p: DependentAddOnDialogOpenPayload) => void;
56
- close: () => void;
57
- }
58
-
59
- const DependentAddOnDialogContext =
60
- createContext<DependentAddOnDialogContextValue | null>(null);
61
-
62
- export function useDependentAddOnDialog() {
63
- const ctx = useContext(DependentAddOnDialogContext);
64
- if (!ctx) {
65
- throw new Error(
66
- 'useDependentAddOnDialog must be used within DependentAddOnDialogProvider'
67
- );
68
- }
69
- return ctx;
70
- }
71
-
72
- export function DependentAddOnDialogProvider({ children }: { children: ReactNode }) {
73
- const [isOpen, setIsOpen] = useState(false);
74
- const [payload, setPayload] = useState<DependentAddOnDialogOpenPayload | null>(null);
75
- const previouslyFocusedRef = useRef<HTMLElement | null>(null);
76
-
77
- const open = useCallback((p: DependentAddOnDialogOpenPayload) => {
78
- previouslyFocusedRef.current =
79
- document.activeElement instanceof HTMLElement ? document.activeElement : null;
80
- setPayload(p);
81
- setIsOpen(true);
82
- }, []);
83
-
84
- const close = useCallback(() => {
85
- setIsOpen(false);
86
- setPayload(null);
87
- const prev = previouslyFocusedRef.current;
88
- requestAnimationFrame(() => {
89
- if (prev && typeof prev.focus === 'function') {
90
- prev.focus();
91
- }
92
- });
93
- }, []);
94
-
95
- const value = useMemo(
96
- () => ({ isOpen, payload, open, close }),
97
- [isOpen, payload, open, close]
98
- );
99
-
100
- return (
101
- <DependentAddOnDialogContext.Provider value={value}>
102
- {children}
103
- </DependentAddOnDialogContext.Provider>
104
- );
105
- }
package/src/radius.css DELETED
@@ -1,5 +0,0 @@
1
- :root {
2
- --border-radius-small: 10px;
3
- --border-radius-medium: 24px;
4
- --border-radius-pill: 50px;
5
- }
package/src/spacing.css DELETED
@@ -1,7 +0,0 @@
1
- :root {
2
- --spacing-small: 8px;
3
- --spacing-medium: 16px;
4
- --spacing-large: 32px;
5
- --spacing-xlarge: 64px;
6
- --spacing-xxlarge: 92px;
7
- }