@ticketboothapp/booking 0.1.19 → 0.1.22
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/package.json +2 -1
- package/src/components/BookingWidget.tsx +282 -26
- package/src/components/ManageBookingView.tsx +75 -23
- package/src/components/PostBookingDependentAddOnUpsell.tsx +1 -1
- package/src/components/booking/BookingProductGrid.tsx +1 -1
- package/src/components/booking/Calendar.module.css +3 -3
- package/src/components/booking/CheckoutForm.tsx +1 -1
- package/src/components/booking/InfoTooltip.tsx +2 -13
- package/src/components/booking/PickupLocationSelector.tsx +2 -2
- package/src/components/booking/PriceBreakdown.tsx +11 -34
- package/src/index.ts +3 -1
- package/tsconfig.json +1 -1
- package/src/components/JobApplicationDialog.module.css +0 -440
- package/src/components/JobApplicationDialog.tsx +0 -620
- package/src/components/PickupLocationMap.tsx +0 -110
- package/src/components/accordion.css +0 -27
- package/src/components/accordion.tsx +0 -29
- package/src/components/analytics/AnalyticsConsentRestore.tsx +0 -19
- package/src/components/analytics/AnalyticsScripts.tsx +0 -106
- package/src/components/analytics/CookieConsentBanner.css +0 -86
- package/src/components/analytics/CookieConsentBanner.tsx +0 -102
- package/src/components/bottom-sheet.module.css +0 -78
- package/src/components/bottom-sheet.tsx +0 -60
- package/src/components/breadcrumb.module.css +0 -40
- package/src/components/breadcrumb.tsx +0 -36
- package/src/components/client-bottom-sheet.tsx +0 -14
- package/src/components/conditional-footer.tsx +0 -27
- package/src/components/contact-us.module.css +0 -147
- package/src/components/contact-us.tsx +0 -49
- package/src/components/email-signup.css +0 -151
- package/src/components/email-signup.tsx +0 -63
- package/src/components/faq-wrapper.module.css +0 -47
- package/src/components/faq-wrapper.tsx +0 -15
- package/src/components/footer.css +0 -187
- package/src/components/footer.tsx +0 -143
- package/src/components/global-simple-modal.tsx +0 -33
- package/src/components/google-review-summary.module.css +0 -77
- package/src/components/google-review-summary.tsx +0 -50
- package/src/components/hero-image.css +0 -13
- package/src/components/hero-image.tsx +0 -44
- package/src/components/language-aware-link.tsx +0 -72
- package/src/components/language-switcher.module.css +0 -124
- package/src/components/language-switcher.tsx +0 -75
- package/src/components/map-section.css +0 -59
- package/src/components/map-section.tsx +0 -63
- package/src/components/navbar.module.css +0 -152
- package/src/components/navbar.tsx +0 -125
- package/src/components/parallax-provider.tsx +0 -11
- package/src/components/product-theme-pages/best-option.module.css +0 -70
- package/src/components/product-theme-pages/best-option.tsx +0 -35
- package/src/components/product-theme-pages/extended-tour-options.module.css +0 -22
- package/src/components/product-theme-pages/extended-tour-options.tsx +0 -11
- package/src/components/product-theme-pages/photo-gallery.tsx +0 -90
- package/src/components/product-theme-pages/product-theme-page-layout.module.css +0 -13
- package/src/components/product-theme-pages/product-theme-page-layout.tsx +0 -67
- package/src/components/product-theme-pages/top-of-fold.module.css +0 -179
- package/src/components/product-theme-pages/top-of-fold.tsx +0 -80
- package/src/components/product-tile/image-only-product-tile-desktop.module.css +0 -106
- package/src/components/product-tile/image-only-product-tile-desktop.tsx +0 -56
- package/src/components/product-tile/image-only-product-tile-mobile.module.css +0 -122
- package/src/components/product-tile/image-only-product-tile-mobile.tsx +0 -89
- package/src/components/product-tile/image-only-product-tile.tsx +0 -44
- package/src/components/product-tile/product-tile-card.module.css +0 -84
- package/src/components/product-tile/product-tile-card.tsx +0 -61
- package/src/components/review-highlights-section.css +0 -85
- package/src/components/review-highlights-section.tsx +0 -127
- package/src/components/season-closure-overlay.module.css +0 -99
- package/src/components/season-closure-overlay.tsx +0 -98
- package/src/components/simple-modal.tsx +0 -69
- package/src/components/simple-top-of-fold.module.css +0 -76
- package/src/components/simple-top-of-fold.tsx +0 -34
- package/src/components/spacer.css +0 -41
- package/src/components/spacer.tsx +0 -23
- package/src/components/star-rating.module.css +0 -74
- package/src/components/star-rating.tsx +0 -48
- package/src/components/title-subtitle.module.css +0 -10
- package/src/components/title-subtitle.tsx +0 -30
- package/src/components/translatable-reviews.tsx +0 -75
- package/src/components/value-props.css +0 -185
- package/src/components/value-props.tsx +0 -88
- package/src/constants/booking-guide-quiz.ts +0 -64
- package/src/constants/contact-info.ts +0 -2
- package/src/constants/faq.ts +0 -44
- package/src/constants/json-ld/faq-json-ld.tsx +0 -170
- package/src/constants/json-ld/homepage-json-ld.tsx +0 -138
- package/src/constants/json-ld/job-posting-json-ld.tsx +0 -92
- package/src/constants/json-ld/organization-json-ld.tsx +0 -62
- package/src/constants/json-ld/page-json-ld.tsx +0 -6
- package/src/constants/json-ld/product-json-ld.tsx +0 -154
- package/src/constants/json-ld/review-json-ld.tsx +0 -377
- package/src/constants/navigation-links/footer-links.ts +0 -48
- package/src/constants/navigation-links/nav-bar-links.ts +0 -41
- package/src/constants/navigation-links/navigation-link.ts +0 -6
- package/src/constants/quiz-recommendations.ts +0 -506
- package/src/constants/reviews.ts +0 -75
- package/src/constants/staff.ts +0 -197
- package/src/constants/value-props.ts +0 -58
- package/src/hooks/use-bottom-sheet.tsx +0 -15
- package/src/hooks/use-simple-modal.tsx +0 -27
- package/src/hooks/useEmailSubscription.tsx +0 -103
- package/src/hooks/useEmbeddedInIframe.ts +0 -16
- package/src/hooks/useQuiz.tsx +0 -210
- package/src/providers/bottom-sheet-provider.tsx +0 -40
- package/src/types/fareharbor.d.ts +0 -12
- package/src/types/quiz.ts +0 -59
- /package/src/{app/photo-sessions → lib}/photo-packages.ts +0 -0
package/src/constants/staff.ts
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { IMAGES } from './images';
|
|
2
|
-
|
|
3
|
-
export interface StaffMember {
|
|
4
|
-
name: string;
|
|
5
|
-
origin: string;
|
|
6
|
-
languages: string;
|
|
7
|
-
funFact: string;
|
|
8
|
-
tipLink: string;
|
|
9
|
-
profilePhoto: typeof IMAGES[keyof typeof IMAGES];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const STAFF_MEMBERS: StaffMember[] = [
|
|
13
|
-
{
|
|
14
|
-
name: 'Léo',
|
|
15
|
-
origin: 'Normandie, France',
|
|
16
|
-
languages: 'EN, FR',
|
|
17
|
-
funFact: 'You’ll most often find Léo running further and faster than anyone you’ve ever met! He’s never faced a challenge he couldn’t overcome, and he’s got more energy than 3 average people combined🏃♂️',
|
|
18
|
-
tipLink: 'https://buy.stripe.com/14A3cvaSscRy5kY9DicjS05',
|
|
19
|
-
profilePhoto: IMAGES.STAFF_LEO
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
name: 'Jess',
|
|
23
|
-
origin: 'St. Catharines, Ontario',
|
|
24
|
-
languages: 'EN, FR',
|
|
25
|
-
funFact: 'Looking for adventure around every corner, Jess loves meeting new people and making memories all over the world! She’s worked as a tour guide most of her life, loves hiking every chance she gets, and finds some interesting tidbit of history wherever she finds herself 🌍',
|
|
26
|
-
tipLink: 'https://buy.stripe.com/cNicN5aSs04M00E3eUcjS06',
|
|
27
|
-
profilePhoto: IMAGES.STAFF_JESS
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: 'Marie',
|
|
31
|
-
origin: 'France',
|
|
32
|
-
languages: 'FR, EN, ES',
|
|
33
|
-
funFact: 'When’s she’s not cruising through the Rockies, Marie is out running, hiking, cycling, skiing… or trying to decide which one to do first! She’s always happy to share her love for the Rockies, with a very French touch🇫🇷',
|
|
34
|
-
tipLink: 'https://buy.stripe.com/eVqdR98Kk9FmaFig1GcjS07',
|
|
35
|
-
profilePhoto: IMAGES.STAFF_MARIE
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'Mike',
|
|
39
|
-
origin: 'Canmore',
|
|
40
|
-
languages: 'EN, ES',
|
|
41
|
-
funFact: 'Our photographer-extraordinaire, Mike is usually found hiking, running, and exploring every corner of the Bow Valley every chance he gets📸',
|
|
42
|
-
tipLink: 'https://buy.stripe.com/00w5kD3q07xe28Mg1GcjS08',
|
|
43
|
-
profilePhoto: IMAGES.STAFF_MIKE
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'Kai',
|
|
47
|
-
origin: 'Canmore',
|
|
48
|
-
languages: 'EN, ES',
|
|
49
|
-
funFact: 'Born in Canada, raised between cultures. The “CheXiCan” has a wild soul who once ran 4,654 km barefoot from Montreal to Southern Mexico. He lives for epic experiences and sharing them with good people — and he’s always down to swap stories on the road💪',
|
|
50
|
-
tipLink: 'https://buy.stripe.com/eVq9ATd0A4l27t66r6cjS09',
|
|
51
|
-
profilePhoto: IMAGES.STAFF_KAI
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'Gonzalo',
|
|
55
|
-
origin: 'Chile',
|
|
56
|
-
languages: 'EN, ES',
|
|
57
|
-
funFact: 'Born in Chile, seasoned by travels across multiple countries, and now a local in Canmore, Gonzalo brings a world of adventure to every shuttle ride. From towering peaks of the Andes to the turquoise lakes of Banff, his journey has taken him through remote mountain trails, lively food markets, and hidden villages around the world🏔️',
|
|
58
|
-
tipLink: 'https://buy.stripe.com/28EcN53q0dVCcNq02IcjS0a',
|
|
59
|
-
profilePhoto: IMAGES.STAFF_GONZALO
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: 'Mathieu',
|
|
63
|
-
origin: 'Calgary',
|
|
64
|
-
languages: 'EN, FR',
|
|
65
|
-
funFact: 'When he’s not cycling dozens of kilometres for fun, Matt can usually be found hiking, reading, and enjoying every challenge the Rockies present to him! In the winter, he works as a ski and snowboard instructor🚴♂️',
|
|
66
|
-
tipLink: 'https://buy.stripe.com/7sYbJ1d0A3gYbJm02IcjS02',
|
|
67
|
-
profilePhoto: IMAGES.STAFF_MATHIEU
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: 'Grace',
|
|
71
|
-
origin: 'United Kingdom',
|
|
72
|
-
languages: 'EN',
|
|
73
|
-
funFact: 'Originally from England, Grace fell in love with the Rockies and has called the Bow Valley home for the past 6 years. She loves to be outdoors, going to music concerts, and competing in CrossFit competitions💪',
|
|
74
|
-
tipLink: 'https://buy.stripe.com/9B6aEXd0A18QcNq3eUcjS01',
|
|
75
|
-
profilePhoto: IMAGES.STAFF_GRACE
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: 'Kieran',
|
|
79
|
-
origin: 'Sydney, Australia',
|
|
80
|
-
languages: 'EN',
|
|
81
|
-
funFact: 'Just another fun-loving Aussie making memories in the beautiful Canadian Rockies. After spending the winter non-stop on the slopes, Kieran has been keen on summer hiking, climbing, camping, and kayaking. He also completed the Banff Four Peaks Challenge this summer (legendary!!) 🧗',
|
|
82
|
-
tipLink: 'https://buy.stripe.com/bJe28r2lW5p66p202IcjS03',
|
|
83
|
-
profilePhoto: IMAGES.STAFF_KIERAN
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'Victor',
|
|
87
|
-
origin: 'Barcelona, Spain',
|
|
88
|
-
languages: 'EN, ES, CAT',
|
|
89
|
-
funFact: 'Originally from Spain, Victor has been travelling the world for the last 6 years, and fell in love with the ocean and mountains while living in Australia and New Zealand. He now lives in the Bow Valley, where he hikes, climbs, and rocks out at music festivals✌️',
|
|
90
|
-
tipLink: 'https://buy.stripe.com/eVqaEX5y8eZG9Be5n2cjS04',
|
|
91
|
-
profilePhoto: IMAGES.STAFF_VICTOR
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'Jake',
|
|
95
|
-
origin: 'Canada',
|
|
96
|
-
languages: 'EN, FR',
|
|
97
|
-
funFact: 'Originally from Oakville, Ontario, Jake moved to the Bow Valley to pursue a career in ski instructing. Nine years later, he is now teaching at all three world-class Banff ski resorts, and training new instructors each winter. In summer, you can find him hiking mountains, climbing rocky cliffs, and camping in the beautiful backcountry of the Rocky Mountains🎿',
|
|
98
|
-
tipLink: 'https://buy.stripe.com/6oU4gz5y85p67t6dTycjS0b',
|
|
99
|
-
profilePhoto: IMAGES.STAFF_JAKE
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
name: 'Carrie',
|
|
103
|
-
origin: 'Taiwan',
|
|
104
|
-
languages: 'EN, ZH, JA, MIN',
|
|
105
|
-
funFact: "5ft0 Taiwanese-snowboarding-addict turned mountain explorer! After five years chasing legendary powder in Japan, she’s landed in the Canadian Rockies. She’s traded in deep snow and sushi for alpine trails, campfires, and climbing routes (until snowboard season returns, of course!)🏂",
|
|
106
|
-
tipLink: 'https://buy.stripe.com/eVqdR9gcM3gYbJmeXCcjS0e',
|
|
107
|
-
profilePhoto: IMAGES.STAFF_CARRIE
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: 'Clém',
|
|
111
|
-
origin: 'France',
|
|
112
|
-
languages: 'FR, EN',
|
|
113
|
-
funFact: 'A French gal tackling the outdoors like no one else! Clém is a wealth of knowledge when it comes to the wildlife, trails, and history of the Rockies, and she can usually be found running, climbing, and hiking🧗♀️',
|
|
114
|
-
tipLink: 'https://buy.stripe.com/6oU5kDe4E3gY6p2g1GcjS0f',
|
|
115
|
-
profilePhoto: IMAGES.STAFF_CLEMENCE
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: 'Pieter',
|
|
119
|
-
origin: 'Netherlands',
|
|
120
|
-
languages: 'EN, NL',
|
|
121
|
-
funFact: 'Originally from the Netherlands, but has been chasing snow and mountains since 2013. Pieter loves snowboarding (and teaching it too!), painting beautiful landscapes and playing guitar in punk & metal bands🤙',
|
|
122
|
-
tipLink: 'https://buy.stripe.com/fZu3cv8KkeZG00EbLqcjS0g',
|
|
123
|
-
profilePhoto: IMAGES.STAFF_PIETER
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
name: 'Bec',
|
|
127
|
-
origin: 'Australia',
|
|
128
|
-
languages: 'EN',
|
|
129
|
-
funFact: 'Another Aussie with quite the backstory! Bec grew up on a farm and started driving at the age of 8! She’s been travelling since she can remember, caravaning around Australia with her family, cultural exchanges to Italy and Austria, and now the Canadian Rockies! What she loves most about living in the mountains? The same place never looks the same twice🏔️',
|
|
130
|
-
tipLink: 'https://buy.stripe.com/cNi14n1hS5p6fZC4iYcjS0k',
|
|
131
|
-
profilePhoto: IMAGES.STAFF_REBECCA
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
name: 'Haleigh',
|
|
135
|
-
origin: 'San Clemente, California',
|
|
136
|
-
languages: 'EN, ES',
|
|
137
|
-
funFact: 'A full-time traveller for the past 3 years, Haleigh is originally from San Diego, California. Having moved to the Rockies for a year of skiing, hiking, and camping, she’s planning the next leg of her nomadic life as we speak🗺️',
|
|
138
|
-
tipLink: 'https://buy.stripe.com/9B63cv6Cc6ta3cQaHmcjS0l',
|
|
139
|
-
profilePhoto: IMAGES.STAFF_HALEIGH
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
name: 'Vishal',
|
|
143
|
-
origin: 'India',
|
|
144
|
-
languages: 'EN, HI, PA',
|
|
145
|
-
funFact: 'Bringing a happiness and a warm smile to each shuttle he drives, Vishal can also be found working as a professional baker (lucky us!) on his days off from Via Via. Having lived in the Bow Valley many years, we’re so lucky to have his experience on our team🥐',
|
|
146
|
-
tipLink: 'https://buy.stripe.com/dRm3cv2lW04M14IdTycjS0j',
|
|
147
|
-
profilePhoto: IMAGES.STAFF_VISHAL
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
name: 'Charlie',
|
|
151
|
-
origin: 'United Kingdom',
|
|
152
|
-
languages: 'EN',
|
|
153
|
-
funFact: 'A world-travelling Brit with a background in TV! Charlie has lived in the UK, Australia, New Zealand, Whistler (BC), and Banff (AB). A lover of hiking, camping and all things outdoor, he’s putting off moving back home for as long as possible. Charlie has set the record for most bears seen on tour with a whopping number 7🐻',
|
|
154
|
-
tipLink: 'https://buy.stripe.com/8x27sLd0A8Bi28McPucjS0m',
|
|
155
|
-
profilePhoto: IMAGES.STAFF_CHARLIE
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: 'Ron',
|
|
159
|
-
origin: 'Hong Kong',
|
|
160
|
-
languages: 'EN, CN, YUE',
|
|
161
|
-
funFact: 'Loves doing things that he is passionate about. Ron has completed over 100 hikes in the Canadian Rockies. As for the winter time, he can always be found chasing powder and exploring new terrains 🏆',
|
|
162
|
-
tipLink: 'https://buy.stripe.com/5kQaEXf8I2cU7t6cPucjS0c',
|
|
163
|
-
profilePhoto: IMAGES.STAFF_RON
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: 'Kathy',
|
|
167
|
-
origin: 'Taiwan',
|
|
168
|
-
languages: 'EN, ZH',
|
|
169
|
-
funFact: 'A Taiwanese girl who loves exploring the world! Kathy has travelled to more than 25 countries and has been living in the Bow Valley for 5 years✈️',
|
|
170
|
-
tipLink: 'https://buy.stripe.com/28E8wPf8IcRy14Ig1GcjS0d',
|
|
171
|
-
profilePhoto: IMAGES.STAFF_KATHY
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
name: 'Kent',
|
|
175
|
-
origin: 'Windsor, Ontario',
|
|
176
|
-
languages: 'EN',
|
|
177
|
-
funFact: "Originally from Windsor, Ontario, Kent moved to the Rockies in 2019 and hasn’t turned back since! On his days off, he can be found exploring the mountains by hiking, camping, and taking stunning photos of the incredible landscapes⛺️",
|
|
178
|
-
tipLink: 'https://buy.stripe.com/4gMeVdgcMg3KaFicPucjS0h',
|
|
179
|
-
profilePhoto: IMAGES.STAFF_KENT
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
name: 'Nathan',
|
|
183
|
-
origin: 'Antwerp, Belgium',
|
|
184
|
-
languages: 'EN, FR, NL',
|
|
185
|
-
funFact: 'Meet Nathan — founder, adventurer, and the heart behind Via Via. Originally from Belgium, he followed his dream and the snow to the Canadian Rockies five years ago, fell in love, and never looked back. After years of exploring the mountains and seeing how hard it had become for visitors to access the area’s most iconic spots, he started Via Via to make the magic of the Rockies more accessible, more sustainable, and more unforgettable for everyone.',
|
|
186
|
-
tipLink: 'https://buy.stripe.com/cNi4gz5y89Fm7t62aQcjS00',
|
|
187
|
-
profilePhoto: IMAGES.STAFF_NATHAN
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: 'Jenna',
|
|
191
|
-
origin: 'Ontario, Canada',
|
|
192
|
-
languages: 'EN',
|
|
193
|
-
funFact: 'Originally from Kitchener-Waterloo, Jenna came to Banff for a ski season and discovered not only a love for the mountains but a shared vision with Nathan. As co-founder of Via Via, she draws on her background in software engineering to lead all things tech and operations — from developing internal systems to streamlining guest communications — ensuring the entire experience runs smoothly from behind the scenes.',
|
|
194
|
-
tipLink: '',
|
|
195
|
-
profilePhoto: IMAGES.STAFF_JENNA
|
|
196
|
-
},
|
|
197
|
-
];
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import blanketAnimation from '@/assets/icons/blanket.json';
|
|
2
|
-
import coffeeAnimation from '@/assets/icons/coffee-break.json';
|
|
3
|
-
import customerAnimation from '@/assets/icons/customer.json';
|
|
4
|
-
import driverAnimation from '@/assets/icons/driver.json';
|
|
5
|
-
import morningAnimation from '@/assets/icons/morning.json';
|
|
6
|
-
import soloAnimation from '@/assets/icons/solo-traveller.json';
|
|
7
|
-
import directAccessAnimation from '@/assets/icons/road-marking.json';
|
|
8
|
-
import avoidPaidParkingAnimation from '@/assets/icons/parking.json';
|
|
9
|
-
import flexibleCancellationAnimation from '@/assets/icons/calendar.json';
|
|
10
|
-
import multipleLocationsAnimation from '@/assets/icons/route.json';
|
|
11
|
-
import noParkingStrugglesAnimation from '@/assets/icons/no-parking.json';
|
|
12
|
-
import threeFriendsAnimation from '@/assets/icons/three-friends.json';
|
|
13
|
-
import mapAnimation from '@/assets/icons/map.json';
|
|
14
|
-
|
|
15
|
-
export enum VALUE_PROPS_KEYS {
|
|
16
|
-
SMALL_GROUPS = 'smallGroups',
|
|
17
|
-
COZY_BLANKETS = 'cozyBlankets',
|
|
18
|
-
HOT_DRINKS = 'hotDrinks',
|
|
19
|
-
PHOTO_TOURS = 'photoTours',
|
|
20
|
-
LOCAL_GUIDES = 'localGuides',
|
|
21
|
-
CONVENIENT_PICKUP = 'convenientPickup',
|
|
22
|
-
DIRECT_ACCESS = 'directAccess',
|
|
23
|
-
AVOID_PAID_PARKING = 'avoidPaidParking',
|
|
24
|
-
FLEXIBLE_CANCELLATION = 'flexibleCancellation',
|
|
25
|
-
MULTIPLE_LOCATIONS = 'multipleLocations',
|
|
26
|
-
NO_PARKING_STRUGGLES = 'noParkingStruggles',
|
|
27
|
-
YOUR_PRIVATE_GROUP = 'yourPrivateGroup',
|
|
28
|
-
CUSTOMIZED_ITINERARY = 'customizedItinerary',
|
|
29
|
-
DOORSTEP_PICKUP = 'doorstepPickup',
|
|
30
|
-
CAPTURE_THE_MOMENT = 'captureTheMoment',
|
|
31
|
-
COZY_COMFORT = 'cozyComfort',
|
|
32
|
-
PERSONAL_GUIDES = 'personalGuides'
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const animations = {
|
|
36
|
-
smallGroups: customerAnimation,
|
|
37
|
-
cozyBlankets: blanketAnimation,
|
|
38
|
-
hotDrinks: coffeeAnimation,
|
|
39
|
-
photoTours: soloAnimation,
|
|
40
|
-
localGuides: driverAnimation,
|
|
41
|
-
convenientPickup: morningAnimation,
|
|
42
|
-
directAccess: directAccessAnimation,
|
|
43
|
-
avoidPaidParking: avoidPaidParkingAnimation,
|
|
44
|
-
flexibleCancellation: flexibleCancellationAnimation,
|
|
45
|
-
multipleLocations: multipleLocationsAnimation,
|
|
46
|
-
noParkingStruggles: noParkingStrugglesAnimation,
|
|
47
|
-
threeFriends: threeFriendsAnimation,
|
|
48
|
-
map: mapAnimation
|
|
49
|
-
} as const;
|
|
50
|
-
|
|
51
|
-
type AnimationKey = keyof typeof animations;
|
|
52
|
-
|
|
53
|
-
export type ValueProp = {
|
|
54
|
-
key: VALUE_PROPS_KEYS;
|
|
55
|
-
animationKey: AnimationKey;
|
|
56
|
-
title: string;
|
|
57
|
-
description: string;
|
|
58
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { create } from 'zustand';
|
|
2
|
-
|
|
3
|
-
type BottomSheetStore = {
|
|
4
|
-
isOpen: boolean;
|
|
5
|
-
content: React.ReactNode | null;
|
|
6
|
-
openSheet: (content: React.ReactNode) => void;
|
|
7
|
-
closeSheet: () => void;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const useBottomSheet = create<BottomSheetStore>((set) => ({
|
|
11
|
-
isOpen: false,
|
|
12
|
-
content: null,
|
|
13
|
-
openSheet: (content) => set({ isOpen: true, content }),
|
|
14
|
-
closeSheet: () => set({ isOpen: false }),
|
|
15
|
-
}));
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
|
-
|
|
5
|
-
export function useSimpleModal() {
|
|
6
|
-
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
7
|
-
|
|
8
|
-
const openModal = () => {
|
|
9
|
-
setIsModalOpen(true);
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const closeModal = () => {
|
|
13
|
-
setIsModalOpen(false);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const handleOpen = () => openModal();
|
|
18
|
-
|
|
19
|
-
window.addEventListener('openSimpleModal', handleOpen);
|
|
20
|
-
|
|
21
|
-
return () => {
|
|
22
|
-
window.removeEventListener('openSimpleModal', handleOpen);
|
|
23
|
-
};
|
|
24
|
-
}, []);
|
|
25
|
-
|
|
26
|
-
return { isModalOpen, openModal, closeModal };
|
|
27
|
-
}
|
|
@@ -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
|
-
}
|
package/src/hooks/useQuiz.tsx
DELETED
|
@@ -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
|
-
}
|