@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.
Files changed (106) hide show
  1. package/package.json +2 -1
  2. package/src/components/BookingWidget.tsx +282 -26
  3. package/src/components/ManageBookingView.tsx +75 -23
  4. package/src/components/PostBookingDependentAddOnUpsell.tsx +1 -1
  5. package/src/components/booking/BookingProductGrid.tsx +1 -1
  6. package/src/components/booking/Calendar.module.css +3 -3
  7. package/src/components/booking/CheckoutForm.tsx +1 -1
  8. package/src/components/booking/InfoTooltip.tsx +2 -13
  9. package/src/components/booking/PickupLocationSelector.tsx +2 -2
  10. package/src/components/booking/PriceBreakdown.tsx +11 -34
  11. package/src/index.ts +3 -1
  12. package/tsconfig.json +1 -1
  13. package/src/components/JobApplicationDialog.module.css +0 -440
  14. package/src/components/JobApplicationDialog.tsx +0 -620
  15. package/src/components/PickupLocationMap.tsx +0 -110
  16. package/src/components/accordion.css +0 -27
  17. package/src/components/accordion.tsx +0 -29
  18. package/src/components/analytics/AnalyticsConsentRestore.tsx +0 -19
  19. package/src/components/analytics/AnalyticsScripts.tsx +0 -106
  20. package/src/components/analytics/CookieConsentBanner.css +0 -86
  21. package/src/components/analytics/CookieConsentBanner.tsx +0 -102
  22. package/src/components/bottom-sheet.module.css +0 -78
  23. package/src/components/bottom-sheet.tsx +0 -60
  24. package/src/components/breadcrumb.module.css +0 -40
  25. package/src/components/breadcrumb.tsx +0 -36
  26. package/src/components/client-bottom-sheet.tsx +0 -14
  27. package/src/components/conditional-footer.tsx +0 -27
  28. package/src/components/contact-us.module.css +0 -147
  29. package/src/components/contact-us.tsx +0 -49
  30. package/src/components/email-signup.css +0 -151
  31. package/src/components/email-signup.tsx +0 -63
  32. package/src/components/faq-wrapper.module.css +0 -47
  33. package/src/components/faq-wrapper.tsx +0 -15
  34. package/src/components/footer.css +0 -187
  35. package/src/components/footer.tsx +0 -143
  36. package/src/components/global-simple-modal.tsx +0 -33
  37. package/src/components/google-review-summary.module.css +0 -77
  38. package/src/components/google-review-summary.tsx +0 -50
  39. package/src/components/hero-image.css +0 -13
  40. package/src/components/hero-image.tsx +0 -44
  41. package/src/components/language-aware-link.tsx +0 -72
  42. package/src/components/language-switcher.module.css +0 -124
  43. package/src/components/language-switcher.tsx +0 -75
  44. package/src/components/map-section.css +0 -59
  45. package/src/components/map-section.tsx +0 -63
  46. package/src/components/navbar.module.css +0 -152
  47. package/src/components/navbar.tsx +0 -125
  48. package/src/components/parallax-provider.tsx +0 -11
  49. package/src/components/product-theme-pages/best-option.module.css +0 -70
  50. package/src/components/product-theme-pages/best-option.tsx +0 -35
  51. package/src/components/product-theme-pages/extended-tour-options.module.css +0 -22
  52. package/src/components/product-theme-pages/extended-tour-options.tsx +0 -11
  53. package/src/components/product-theme-pages/photo-gallery.tsx +0 -90
  54. package/src/components/product-theme-pages/product-theme-page-layout.module.css +0 -13
  55. package/src/components/product-theme-pages/product-theme-page-layout.tsx +0 -67
  56. package/src/components/product-theme-pages/top-of-fold.module.css +0 -179
  57. package/src/components/product-theme-pages/top-of-fold.tsx +0 -80
  58. package/src/components/product-tile/image-only-product-tile-desktop.module.css +0 -106
  59. package/src/components/product-tile/image-only-product-tile-desktop.tsx +0 -56
  60. package/src/components/product-tile/image-only-product-tile-mobile.module.css +0 -122
  61. package/src/components/product-tile/image-only-product-tile-mobile.tsx +0 -89
  62. package/src/components/product-tile/image-only-product-tile.tsx +0 -44
  63. package/src/components/product-tile/product-tile-card.module.css +0 -84
  64. package/src/components/product-tile/product-tile-card.tsx +0 -61
  65. package/src/components/review-highlights-section.css +0 -85
  66. package/src/components/review-highlights-section.tsx +0 -127
  67. package/src/components/season-closure-overlay.module.css +0 -99
  68. package/src/components/season-closure-overlay.tsx +0 -98
  69. package/src/components/simple-modal.tsx +0 -69
  70. package/src/components/simple-top-of-fold.module.css +0 -76
  71. package/src/components/simple-top-of-fold.tsx +0 -34
  72. package/src/components/spacer.css +0 -41
  73. package/src/components/spacer.tsx +0 -23
  74. package/src/components/star-rating.module.css +0 -74
  75. package/src/components/star-rating.tsx +0 -48
  76. package/src/components/title-subtitle.module.css +0 -10
  77. package/src/components/title-subtitle.tsx +0 -30
  78. package/src/components/translatable-reviews.tsx +0 -75
  79. package/src/components/value-props.css +0 -185
  80. package/src/components/value-props.tsx +0 -88
  81. package/src/constants/booking-guide-quiz.ts +0 -64
  82. package/src/constants/contact-info.ts +0 -2
  83. package/src/constants/faq.ts +0 -44
  84. package/src/constants/json-ld/faq-json-ld.tsx +0 -170
  85. package/src/constants/json-ld/homepage-json-ld.tsx +0 -138
  86. package/src/constants/json-ld/job-posting-json-ld.tsx +0 -92
  87. package/src/constants/json-ld/organization-json-ld.tsx +0 -62
  88. package/src/constants/json-ld/page-json-ld.tsx +0 -6
  89. package/src/constants/json-ld/product-json-ld.tsx +0 -154
  90. package/src/constants/json-ld/review-json-ld.tsx +0 -377
  91. package/src/constants/navigation-links/footer-links.ts +0 -48
  92. package/src/constants/navigation-links/nav-bar-links.ts +0 -41
  93. package/src/constants/navigation-links/navigation-link.ts +0 -6
  94. package/src/constants/quiz-recommendations.ts +0 -506
  95. package/src/constants/reviews.ts +0 -75
  96. package/src/constants/staff.ts +0 -197
  97. package/src/constants/value-props.ts +0 -58
  98. package/src/hooks/use-bottom-sheet.tsx +0 -15
  99. package/src/hooks/use-simple-modal.tsx +0 -27
  100. package/src/hooks/useEmailSubscription.tsx +0 -103
  101. package/src/hooks/useEmbeddedInIframe.ts +0 -16
  102. package/src/hooks/useQuiz.tsx +0 -210
  103. package/src/providers/bottom-sheet-provider.tsx +0 -40
  104. package/src/types/fareharbor.d.ts +0 -12
  105. package/src/types/quiz.ts +0 -59
  106. /package/src/{app/photo-sessions → lib}/photo-packages.ts +0 -0
@@ -1,63 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import ViaViaImage from "@/components/image";
5
- import { IMAGES } from "@/constants/images";
6
- import "./map-section.css";
7
- import { motion } from "motion/react";
8
- import { useState } from "react";
9
-
10
- interface MapSectionProps {
11
- title: string;
12
- }
13
-
14
- const variants = {
15
- hidden: { x: 0, y: 200 },
16
- visible: { x: 0, y: 0 }
17
- };
18
-
19
- export default function MapSection(
20
- { title }: MapSectionProps
21
- ) {
22
- const [hasAnimated, setHasAnimated] = useState(false);
23
-
24
- const handleViewportEnter = (entry: IntersectionObserverEntry | null) => {
25
- if (!hasAnimated) {
26
- setHasAnimated(true);
27
- }
28
- };
29
-
30
- const handleViewportLeave = (entry: IntersectionObserverEntry | null) => {
31
- if (entry?.boundingClientRect.top! > 0) {
32
- setHasAnimated(false);
33
- }
34
- };
35
-
36
- return (
37
- <div className="map-section-wrapper">
38
- <motion.div className="map-section-container"
39
- variants={variants}
40
- initial="hidden"
41
- animate={hasAnimated ? "visible" : "hidden"}
42
- transition={{
43
- type: "spring",
44
- bounce: 0.1,
45
- duration: 1,
46
- }}
47
- viewport={{ once: false }}
48
- onViewportEnter={handleViewportEnter}
49
- onViewportLeave={handleViewportLeave}
50
- >
51
- <h2>{title}</h2>
52
- <div className="map-section-image">
53
- <ViaViaImage
54
- imageId={IMAGES.VIA_VIA_OPERATIONS_MAP.id}
55
- alt={IMAGES.VIA_VIA_OPERATIONS_MAP.alt}
56
- context="GALLERY"
57
- maintainAspectRatio={true}
58
- />
59
- </div>
60
- </motion.div>
61
- </div>
62
- );
63
- }
@@ -1,152 +0,0 @@
1
- .hideAndSlideNavbar {
2
- position: fixed;
3
- top: -150px; /* Start hidden above the viewport */
4
- transition: transform 0.5s ease;
5
- }
6
-
7
- .showAndSlideNavbar {
8
- transform: translateY(150px); /* Slide down to become visible */
9
- }
10
-
11
- .navbarContainer {
12
- left: 0;
13
- right: 0;
14
- z-index: 100;
15
- }
16
-
17
- .navbarContent {
18
- width: 100%;
19
- height: 100%;
20
- display: flex;
21
- flex-direction: column;
22
- }
23
-
24
- .navbarRow {
25
- display: flex;
26
- flex-direction: row;
27
- justify-content: space-between;
28
- align-items: center;
29
- }
30
-
31
- /* Desktop Navbar */
32
- @media (min-width: 1024px) {
33
- .navbarContainer {
34
- background-color: rgba(255, 238, 232, 0.7);
35
- backdrop-filter: blur(8px);
36
- padding: 1rem;
37
- }
38
-
39
- .navigationBarIcon {
40
- width: 60px;
41
- height: 60px;
42
- }
43
-
44
- .navbarContent {
45
- max-width: 95%;
46
- margin: 0 auto;
47
- }
48
-
49
- .mobileNavbarContent {
50
- display: none;
51
- }
52
-
53
- .mobileNavbarLinks {
54
- display: none;
55
- }
56
-
57
- .desktopNavbarContent {
58
- display: flex;
59
- flex-direction: row;
60
- justify-content: space-between;
61
- align-items: center;
62
- width: 100%;
63
- }
64
-
65
- .navbarLinks {
66
- display: flex;
67
- flex-direction: row;
68
- justify-content: space-between;
69
- align-items: center;
70
- padding: 0 2rem;
71
- width: 100%;
72
- text-align: center;
73
- }
74
-
75
- .bookNowButton {
76
- min-width: 160px;
77
- }
78
-
79
- .showAndStickyOrMobileOverlapNavbar {
80
- position: sticky;
81
- top: 0;
82
- }
83
-
84
- .showAndStickyNavbar {
85
- position: sticky;
86
- top: 0;
87
- }
88
- }
89
-
90
- /* Mobile Navbar */
91
- @media (max-width: 1023px) {
92
- .navbarContainer {
93
- border-color: var(--accent-orange);
94
- border-width: 2px;
95
- border-style: solid;
96
- border-radius: 40px;
97
- margin: 0.5rem;
98
- overflow: hidden;
99
- padding: 1rem;
100
- background-color: var(--light-orange-background);
101
- }
102
-
103
- .navigationBarIcon {
104
- width: 32px;
105
- height: 32px;
106
- }
107
-
108
- .desktopNavbarContent {
109
- display: none;
110
- }
111
-
112
- .navbarLinks {
113
- display: flex;
114
- flex-direction: column;
115
- align-items: center;
116
- height: 100%;
117
- justify-content: space-between;
118
- }
119
-
120
- .showAndStickyOrMobileOverlapNavbar {
121
- position: fixed;
122
- top: 0.5rem;
123
- }
124
-
125
- .showAndStickyNavbar {
126
- position: sticky;
127
- top: 0.5rem;
128
- }
129
- }
130
-
131
- .navbarLinks a {
132
- text-decoration: none;
133
- color: var(--accent-orange);
134
- font-size: 1.5rem;
135
- font-weight: 800;
136
- font-family: 'Poppins', sans-serif;
137
- text-transform: lowercase;
138
- padding: 0 var(--spacing-small);
139
- }
140
- .nonCurrentLink:hover {
141
- text-decoration: underline;
142
- }
143
-
144
- .mobileNavbarLinks {
145
- height: 0;
146
- overflow: hidden;
147
- transition: height 0.5s ease;
148
- }
149
-
150
- .mobileNavbarLinks.open {
151
- height: 300px;
152
- }
@@ -1,125 +0,0 @@
1
- 'use client';
2
-
3
- import styles from './navbar.module.css';
4
- import { usePathname } from 'next/navigation';
5
- import { useEffect, useState } from 'react';
6
- import Link from 'next/link';
7
- import LanguageAwareLink from './language-aware-link';
8
- import Button, { MenuButton, PresetButtonActions } from '@/components/button';
9
- import Image from 'next/image';
10
- import defaultStrings from '@/strings';
11
- import { getProducts } from '@/constants/products';
12
-
13
- export default function Navbar(
14
- {
15
- stickyMode,
16
- strings = defaultStrings,
17
- }: {
18
- stickyMode: string | "hideAndSlide" | "showAndStickyOrMobileOverlap" | "showAndSticky",
19
- strings?: any,
20
- }
21
- ) {
22
- const pathname = usePathname() || '/';
23
- const [isMenuOpen, setIsMenuOpen] = useState(false);
24
- const [isVisible, setIsVisible] = useState(false);
25
-
26
- if (stickyMode == "hideAndSlide") {
27
- useEffect(() => {
28
- const handleScroll = () => {
29
- const scrollPosition = window.scrollY;
30
-
31
- // Show navbar when scrolled past yHeightForSlide
32
- setIsVisible(scrollPosition > 50);
33
- };
34
-
35
- window.addEventListener('scroll', handleScroll);
36
- return () => window.removeEventListener('scroll', handleScroll);
37
- }, []);
38
- }
39
-
40
- return (
41
- <nav className={`${styles.navbarContainer} ${stickyMode == "hideAndSlide" ? styles.hideAndSlideNavbar : ''} ${isVisible && stickyMode == "hideAndSlide" ? styles.showAndSlideNavbar : ''} ${stickyMode == "showAndStickyOrMobileOverlap" ? styles.showAndStickyOrMobileOverlapNavbar : ''} ${stickyMode == "showAndSticky" ? styles.showAndStickyNavbar : ''}`}>
42
- <div className={styles.navbarContent}>
43
- <div className={styles.navbarRow}>
44
- <LanguageAwareLink href="/">
45
- <Image
46
- src="logo192.png"
47
- alt="Via Via Moraine Lake Shuttle Logo"
48
- className={styles.navigationBarIcon}
49
- width={60}
50
- height={60}
51
- />
52
- </LanguageAwareLink>
53
- <div className={styles.desktopNavbarContent}>
54
- <NavbarLinks setIsMenuOpen={setIsMenuOpen} currentPath={pathname} strings={strings} />
55
- <Button isLarge={true} className={styles.bookNowButton} action={PresetButtonActions.BOOK_ALL}>{strings.common.bookNow}</Button>
56
- </div>
57
- <div className={styles.mobileNavbarContent}>
58
- <MenuButton onClick={() => setIsMenuOpen(!isMenuOpen)} isOpen={isMenuOpen} strings={strings} />
59
- </div>
60
- </div>
61
- <div className={`${styles.mobileNavbarLinks} ${isMenuOpen ? styles.open : ''}`}>
62
- <NavbarLinks setIsMenuOpen={setIsMenuOpen} currentPath={pathname} strings={strings} />
63
- </div>
64
- </div>
65
- </nav>
66
- );
67
- }
68
-
69
- function NavbarLinks({ setIsMenuOpen, currentPath, strings }: {
70
- setIsMenuOpen: (open: boolean) => void,
71
- currentPath: string,
72
- strings: any
73
- }) {
74
-
75
- const products = getProducts(strings);
76
- const navbarLinks = [
77
- {
78
- name: strings.navigationLinks.home,
79
- path: '/',
80
- },
81
- {
82
- name: strings.navigationLinks.sunriseTours,
83
- path: products.MORAINE_LAKE_SUNRISE.path,
84
- },
85
- {
86
- name: strings.navigationLinks.moraineLake,
87
- path: products.MORAINE_LAKE_ADVENTURE.path,
88
- },
89
- {
90
- name: strings.navigationLinks.lakeLouise,
91
- path: products.LAKE_LOUISE_ADVENTURE.path,
92
- },
93
- {
94
- name: strings.navigationLinks.emeraldLake,
95
- path: products.EMERALD_LAKE_ESCAPE.path,
96
- },
97
- {
98
- name: strings.navigationLinks.privateTours,
99
- path: products.PRIVATE_TOUR.path,
100
- },
101
- {
102
- name: strings.navigationLinks.photoSessions,
103
- path: '/photo-sessions',
104
- },
105
- {
106
- name: strings.navigationLinks.faq,
107
- path: '/faq',
108
- },
109
- ];
110
-
111
- return (
112
- <div className={styles.navbarLinks}>
113
- {navbarLinks.map((link) => (
114
- <LanguageAwareLink
115
- onClick={() => setIsMenuOpen(false)}
116
- className={link.path == '/' && currentPath == '/' || link.path == '/' && currentPath == '/es' || link.path === currentPath.replace(/\/$/, '') ? styles.currentLink : styles.nonCurrentLink}
117
- key={link.name}
118
- href={link.path}
119
- >
120
- {link.name}
121
- </LanguageAwareLink>
122
- ))}
123
- </div>
124
- );
125
- }
@@ -1,11 +0,0 @@
1
- 'use client';
2
-
3
- import { ParallaxProvider } from 'react-scroll-parallax';
4
-
5
- export default function ParallaxWrapper({ children }: { children: React.ReactNode }) {
6
- return (
7
- <ParallaxProvider>
8
- {children}
9
- </ParallaxProvider>
10
- );
11
- }
@@ -1,70 +0,0 @@
1
- .bestOptionContainer {
2
- display: flex;
3
- flex-direction: column;
4
- align-items: center;
5
- padding-top: var(--spacing-large);
6
- padding-bottom: var(--spacing-large);
7
- padding-left: var(--spacing-small);
8
- padding-right: var(--spacing-small);
9
- }
10
-
11
- .otherOptionsRow {
12
- display: flex;
13
- flex-direction: row;
14
- gap: 1rem;
15
- justify-content: center;
16
- }
17
-
18
- .bestOptionProductsRowWrapper {
19
- width: 100%;
20
- max-width: 1200px;
21
- margin: 0 auto;
22
- display: flex;
23
- flex-direction: column;
24
- gap: 1rem;
25
- }
26
-
27
- .bestOptionProductsRow {
28
- display: grid;
29
- grid-template-columns: 1fr;
30
- gap: 1rem;
31
- width: 100%;
32
- }
33
-
34
- /* Tablet breakpoint - 2 tiles per row */
35
- @media (min-width: 768px) {
36
- .bestOptionProductsRow {
37
- grid-template-columns: repeat(2, 1fr);
38
- }
39
- }
40
-
41
- /* Desktop breakpoint - 3 tiles per row */
42
- @media (min-width: 1024px) {
43
- .bestOptionProductsRow {
44
- display: flex;
45
- flex-wrap: wrap;
46
- justify-content: flex-start;
47
- gap: 1rem;
48
- max-width: 1200px;
49
- margin: 0 auto;
50
- }
51
-
52
- .bestOptionProductsRow > * {
53
- flex: 0 0 calc(33.333% - 0.67rem);
54
- max-width: calc(33.333% - 0.67rem);
55
- }
56
- }
57
-
58
- .bestOptionProductsRow > a {
59
- text-decoration: none;
60
- color: inherit;
61
- }
62
-
63
- .bestOptionBottomContainer {
64
- padding-top: var(--spacing-large);
65
- }
66
-
67
- .bestOptionBottomTitle {
68
- color: var(--primary-text);
69
- font-size: 1.2rem;
70
- }
@@ -1,35 +0,0 @@
1
- "use client";
2
-
3
- import defaultStrings from "@/strings";
4
- import styles from "./best-option.module.css";
5
- import { Product } from "@/constants/products";
6
- import Link from "next/link";
7
- import ProductTileCard from "../product-tile/product-tile-card";
8
-
9
- export default function BestOption({ bestOptionProducts, otherProductThemePages, strings = defaultStrings }: { bestOptionProducts: Product[], otherProductThemePages: { title: string, path: string }[], strings?: any }) {
10
- return (
11
- <div className={styles.bestOptionContainer}>
12
- {bestOptionProducts.length > 0 ? (
13
- <>
14
- <h2>{strings.productThemePages.bestOption.title}</h2>
15
- <div className={styles.bestOptionProductsRowWrapper}>
16
- <div className={styles.bestOptionProductsRow}>
17
- {bestOptionProducts.map((product) => (
18
- <ProductTileCard key={product.id} product={product} strings={strings} />
19
- ))}
20
- </div>
21
- </div>
22
- </>
23
- ) : null}
24
-
25
- <div className={styles.bestOptionBottomContainer}>
26
- <h3 className={styles.bestOptionBottomTitle}>{strings.productThemePages.bestOption.bottomTitle}</h3>
27
- <div className={styles.otherOptionsRow}>
28
- {otherProductThemePages.map((page) => (
29
- <Link href={page.path} key={page.title}>{page.title}</Link>
30
- ))}
31
- </div>
32
- </div>
33
- </div>
34
- );
35
- }
@@ -1,22 +0,0 @@
1
- .container {
2
- border: 4px dashed var(--accent-turquoise-60);
3
- padding: 1rem;
4
- }
5
-
6
- .description {
7
- text-align: center;
8
- }
9
-
10
- @media (max-width: 1023px) {
11
- .container {
12
- margin: 0 auto;
13
- width: 70%;
14
- }
15
- }
16
-
17
- @media (min-width: 1024px) {
18
- .container {
19
- margin: 2rem auto;
20
- width: 50%;
21
- }
22
- }
@@ -1,11 +0,0 @@
1
- import styles from "./extended-tour-options.module.css";
2
- import strings from "@/strings";
3
-
4
- export default function ExtendedTourOptions() {
5
- return (
6
- <div className={styles.container}>
7
- <h4>{strings.productThemePages.extendedTourOptions.title}</h4>
8
- <p dangerouslySetInnerHTML={{ __html: strings.productThemePages.extendedTourOptions.description }} className={styles.description} />
9
- </div>
10
- )
11
- }
@@ -1,90 +0,0 @@
1
- "use client";
2
-
3
- import { ImageData } from "@/constants/images";
4
- import ViaViaImage from "@/components/image";
5
- import styles from "./photo-gallery.module.css";
6
- import { useState, lazy, Suspense, useEffect } from "react";
7
-
8
- // Lazy load the modal component
9
- const ImageModal = lazy(() => import('@/components/product-theme-pages/image-modal'));
10
-
11
- interface PhotoGalleryProps {
12
- photos: ImageData[];
13
- }
14
-
15
- export default function PhotoGallery({ photos }: PhotoGalleryProps) {
16
- const [selectedImage, setSelectedImage] = useState<ImageData | null>(null);
17
- const [currentIndex, setCurrentIndex] = useState<number>(0);
18
- const [isMobile, setIsMobile] = useState(false);
19
- const [isClient, setIsClient] = useState(false);
20
-
21
- useEffect(() => {
22
- setIsClient(true);
23
- setIsMobile(window.innerWidth <= 1023);
24
-
25
- const handleResize = () => {
26
- setIsMobile(window.innerWidth <= 1023);
27
- };
28
-
29
- window.addEventListener('resize', handleResize);
30
- return () => window.removeEventListener('resize', handleResize);
31
- }, []);
32
-
33
- // Only show first 9 photos on mobile
34
- const displayPhotos = isClient && isMobile ? photos.slice(0, 9) : photos;
35
-
36
- const handleImageClick = (photo: ImageData) => {
37
- const index = photos.findIndex(p => p.id === photo.id);
38
- setCurrentIndex(index);
39
- setSelectedImage(photo);
40
- };
41
-
42
- return (
43
- <>
44
- <div className={styles.photoGallery}>
45
- <div className={styles.grid}>
46
- {displayPhotos.map((photo) => (
47
- <div
48
- key={photo.id}
49
- className={styles.photoItem}
50
- onClick={() => handleImageClick(photo)}
51
- >
52
- <ViaViaImage
53
- imageId={photo.id}
54
- alt={photo.alt}
55
- context="GALLERY"
56
- className={styles.galleryImage}
57
- />
58
- </div>
59
- ))}
60
- </div>
61
- </div>
62
-
63
- {selectedImage && (
64
- <Suspense fallback={<div className={styles.modalOverlay}>Loading...</div>}>
65
- <ImageModal
66
- selectedImage={selectedImage}
67
- currentIndex={currentIndex}
68
- totalImages={photos.length}
69
- images={photos}
70
- onClose={() => setSelectedImage(null)}
71
- onNext={() => {
72
- if (currentIndex < photos.length - 1) {
73
- const newIndex = currentIndex + 1;
74
- setCurrentIndex(newIndex);
75
- setSelectedImage(photos[newIndex]);
76
- }
77
- }}
78
- onPrevious={() => {
79
- if (currentIndex > 0) {
80
- const newIndex = currentIndex - 1;
81
- setCurrentIndex(newIndex);
82
- setSelectedImage(photos[newIndex]);
83
- }
84
- }}
85
- />
86
- </Suspense>
87
- )}
88
- </>
89
- );
90
- }
@@ -1,13 +0,0 @@
1
- .productThemePageLayout {
2
- background-color: var(--light-orange-background);
3
- }
4
-
5
- .titleSubtitle {
6
- max-width: 1200px;
7
- padding: var(--spacing-medium);
8
- margin: 0 auto;
9
- }
10
-
11
- .reviewHighlightsSection {
12
- margin-top: var(--spacing-large);
13
- }
@@ -1,67 +0,0 @@
1
- "use client";
2
-
3
- import TopOfFold from "@/components/product-theme-pages/top-of-fold";
4
- import { TopOfFoldProps } from "@/components/product-theme-pages/top-of-fold";
5
- import TitleSubtitle from "../title-subtitle";
6
- import defaultStrings from "@/strings";
7
- import styles from "./product-theme-page-layout.module.css";
8
- import { VALUE_PROPS_KEYS } from "@/constants/value-props";
9
- import ValueProps from "@/components/value-props";
10
- import ReviewHighlightsSection from "@/components/review-highlights-section";
11
- import MapSection from "../map-section";
12
- import FAQWrapper from "@/components/faq-wrapper";
13
- import { faqSection } from "@/constants/faq";
14
- import Navbar from "@/components/navbar";
15
- import BestOption from "./best-option";
16
- import { Product } from "@/constants/products";
17
- import { pageJsonLd } from "@/constants/json-ld/page-json-ld";
18
- import { organizationJsonLd } from "@/constants/json-ld/organization-json-ld";
19
- import { aggregateRatingJsonLd } from "@/constants/json-ld/review-json-ld";
20
- import PhotoGallery from "./photo-gallery";
21
- import { ImageData } from "@/constants/images";
22
- import ExtendedTourOptions from "./extended-tour-options";
23
-
24
- interface ProductThemePageLayoutProps {
25
- topOfFoldProps: TopOfFoldProps;
26
- description: string;
27
- photoGalleryPhotos: ImageData[];
28
- includeExtendedTourOptions: boolean;
29
- valuePropsKeys: VALUE_PROPS_KEYS[];
30
- valuePropsTitle?: string;
31
- faqSection: faqSection;
32
- bestOptionProducts: Product[];
33
- otherProductThemePages: { title: string, path: string }[];
34
- productJsonLd: any[];
35
- }
36
-
37
- export default function ProductThemePageLayout(props: ProductThemePageLayoutProps & { strings?: any }) {
38
- const { topOfFoldProps, description, photoGalleryPhotos, includeExtendedTourOptions, valuePropsKeys, valuePropsTitle, faqSection, bestOptionProducts, otherProductThemePages, strings = defaultStrings } = props;
39
- return (
40
- <div>
41
- <Navbar stickyMode="showAndStickyOrMobileOverlap"/>
42
- <TopOfFold {...topOfFoldProps} />
43
- <div className={styles.productThemePageLayout}>
44
- <TitleSubtitle
45
- className={styles.titleSubtitle}
46
- title={strings.productThemePages.title}
47
- subtitle={strings.productThemePages.subtitle}
48
- description={description}
49
- />
50
- {includeExtendedTourOptions && <ExtendedTourOptions />}
51
- <ValueProps filterKeys={valuePropsKeys} title={valuePropsTitle} strings={strings} />
52
- {photoGalleryPhotos && photoGalleryPhotos.length > 0 && (
53
- <PhotoGallery photos={photoGalleryPhotos} />
54
- )}
55
- <BestOption bestOptionProducts={bestOptionProducts} otherProductThemePages={otherProductThemePages} strings={strings} />
56
- <FAQWrapper faqSection={faqSection} strings={strings} />
57
- <ReviewHighlightsSection className={styles.reviewHighlightsSection} strings={strings} />
58
- <MapSection title={strings.productThemePages.mapSection.title} />
59
- </div>
60
-
61
- <script
62
- type="application/ld+json"
63
- dangerouslySetInnerHTML={{ __html: JSON.stringify(pageJsonLd([organizationJsonLd, aggregateRatingJsonLd, ...props.productJsonLd])) }}
64
- />
65
- </div>
66
- )
67
- }