@ticketboothapp/booking 0.1.18 → 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 (152) 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/PickupLocationSelector.tsx +1 -1
  8. package/src/index.ts +3 -1
  9. package/src/app/photo-sessions/photo-packages.ts +0 -75
  10. package/src/assets/icons/minus.svg +0 -7
  11. package/src/assets/icons/partner-logos/getyourguide.svg +0 -8
  12. package/src/assets/icons/plus.svg +0 -3
  13. package/src/colours.css +0 -23
  14. package/src/components/BookingDetails.module.css +0 -1591
  15. package/src/components/BookingDetails.tsx +0 -2264
  16. package/src/components/JobApplicationDialog.module.css +0 -440
  17. package/src/components/JobApplicationDialog.tsx +0 -620
  18. package/src/components/PhoneInputWithCountry.module.css +0 -131
  19. package/src/components/PhoneInputWithCountry.tsx +0 -44
  20. package/src/components/PickupLocationDialog.module.css +0 -360
  21. package/src/components/PickupLocationDialog.tsx +0 -357
  22. package/src/components/PickupLocationMap.tsx +0 -110
  23. package/src/components/PostBookingDependentAddOnUpsell.module.css +0 -174
  24. package/src/components/PostBookingDependentAddOnUpsell.tsx +0 -407
  25. package/src/components/accordion.css +0 -27
  26. package/src/components/accordion.tsx +0 -29
  27. package/src/components/analytics/AnalyticsConsentRestore.tsx +0 -19
  28. package/src/components/analytics/AnalyticsScripts.tsx +0 -106
  29. package/src/components/analytics/CookieConsentBanner.css +0 -86
  30. package/src/components/analytics/CookieConsentBanner.tsx +0 -102
  31. package/src/components/bottom-sheet.module.css +0 -78
  32. package/src/components/bottom-sheet.tsx +0 -60
  33. package/src/components/breadcrumb.module.css +0 -40
  34. package/src/components/breadcrumb.tsx +0 -36
  35. package/src/components/button.css +0 -245
  36. package/src/components/button.tsx +0 -152
  37. package/src/components/client-bottom-sheet.tsx +0 -14
  38. package/src/components/colorable-svg.tsx +0 -29
  39. package/src/components/conditional-footer.tsx +0 -27
  40. package/src/components/contact-us.module.css +0 -147
  41. package/src/components/contact-us.tsx +0 -49
  42. package/src/components/email-signup.css +0 -151
  43. package/src/components/email-signup.tsx +0 -63
  44. package/src/components/faq-wrapper.module.css +0 -47
  45. package/src/components/faq-wrapper.tsx +0 -15
  46. package/src/components/footer.css +0 -187
  47. package/src/components/footer.tsx +0 -143
  48. package/src/components/global-simple-modal.tsx +0 -33
  49. package/src/components/google-review-summary.module.css +0 -77
  50. package/src/components/google-review-summary.tsx +0 -50
  51. package/src/components/hero-image.css +0 -13
  52. package/src/components/hero-image.tsx +0 -44
  53. package/src/components/image.css +0 -29
  54. package/src/components/image.tsx +0 -113
  55. package/src/components/language-aware-link.tsx +0 -72
  56. package/src/components/language-switcher.module.css +0 -124
  57. package/src/components/language-switcher.tsx +0 -75
  58. package/src/components/map-section.css +0 -59
  59. package/src/components/map-section.tsx +0 -63
  60. package/src/components/navbar.module.css +0 -152
  61. package/src/components/navbar.tsx +0 -125
  62. package/src/components/parallax-provider.tsx +0 -11
  63. package/src/components/product-tag.module.css +0 -30
  64. package/src/components/product-tag.tsx +0 -34
  65. package/src/components/product-theme-pages/best-option.module.css +0 -70
  66. package/src/components/product-theme-pages/best-option.tsx +0 -35
  67. package/src/components/product-theme-pages/extended-tour-options.module.css +0 -22
  68. package/src/components/product-theme-pages/extended-tour-options.tsx +0 -11
  69. package/src/components/product-theme-pages/image-modal.tsx +0 -248
  70. package/src/components/product-theme-pages/photo-gallery.module.css +0 -200
  71. package/src/components/product-theme-pages/photo-gallery.tsx +0 -90
  72. package/src/components/product-theme-pages/product-theme-page-layout.module.css +0 -13
  73. package/src/components/product-theme-pages/product-theme-page-layout.tsx +0 -67
  74. package/src/components/product-theme-pages/top-of-fold.module.css +0 -179
  75. package/src/components/product-theme-pages/top-of-fold.tsx +0 -80
  76. package/src/components/product-tile/image-only-product-tile-desktop.module.css +0 -106
  77. package/src/components/product-tile/image-only-product-tile-desktop.tsx +0 -56
  78. package/src/components/product-tile/image-only-product-tile-mobile.module.css +0 -122
  79. package/src/components/product-tile/image-only-product-tile-mobile.tsx +0 -89
  80. package/src/components/product-tile/image-only-product-tile.tsx +0 -44
  81. package/src/components/product-tile/product-tile-card.module.css +0 -84
  82. package/src/components/product-tile/product-tile-card.tsx +0 -61
  83. package/src/components/review-highlights-section.css +0 -85
  84. package/src/components/review-highlights-section.tsx +0 -127
  85. package/src/components/season-closure-overlay.module.css +0 -99
  86. package/src/components/season-closure-overlay.tsx +0 -98
  87. package/src/components/simple-modal.tsx +0 -69
  88. package/src/components/simple-top-of-fold.module.css +0 -76
  89. package/src/components/simple-top-of-fold.tsx +0 -34
  90. package/src/components/spacer.css +0 -41
  91. package/src/components/spacer.tsx +0 -23
  92. package/src/components/star-rating.module.css +0 -74
  93. package/src/components/star-rating.tsx +0 -48
  94. package/src/components/terms/TermsContent.tsx +0 -178
  95. package/src/components/title-subtitle.module.css +0 -10
  96. package/src/components/title-subtitle.tsx +0 -30
  97. package/src/components/translatable-reviews.tsx +0 -75
  98. package/src/components/value-pill.module.css +0 -59
  99. package/src/components/value-pill.tsx +0 -46
  100. package/src/components/value-props.css +0 -185
  101. package/src/components/value-props.tsx +0 -88
  102. package/src/constants/booking-guide-quiz.ts +0 -64
  103. package/src/constants/contact-info.ts +0 -2
  104. package/src/constants/faq.ts +0 -44
  105. package/src/constants/images.ts +0 -556
  106. package/src/constants/json-ld/faq-json-ld.tsx +0 -170
  107. package/src/constants/json-ld/homepage-json-ld.tsx +0 -138
  108. package/src/constants/json-ld/job-posting-json-ld.tsx +0 -92
  109. package/src/constants/json-ld/organization-json-ld.tsx +0 -62
  110. package/src/constants/json-ld/page-json-ld.tsx +0 -6
  111. package/src/constants/json-ld/product-json-ld.tsx +0 -154
  112. package/src/constants/json-ld/review-json-ld.tsx +0 -377
  113. package/src/constants/navigation-links/footer-links.ts +0 -48
  114. package/src/constants/navigation-links/nav-bar-links.ts +0 -41
  115. package/src/constants/navigation-links/navigation-link.ts +0 -6
  116. package/src/constants/pill-values.ts +0 -210
  117. package/src/constants/products.ts +0 -155
  118. package/src/constants/quiz-recommendations.ts +0 -506
  119. package/src/constants/reviews.ts +0 -75
  120. package/src/constants/staff.ts +0 -197
  121. package/src/constants/value-props.ts +0 -58
  122. package/src/data/dap-descriptions/session-couples-families-friends.en.json +0 -61
  123. package/src/data/dap-descriptions/session-elopements.en.json +0 -60
  124. package/src/data/dap-descriptions/session-proposals.en.json +0 -60
  125. package/src/data/product-descriptions/afternoon-delight.en.json +0 -35
  126. package/src/data/product-descriptions/emerald-lake-escape.en.json +0 -68
  127. package/src/data/product-descriptions/lake-louise-adventure.en.json +0 -74
  128. package/src/data/product-descriptions/moraine-lake-adventure.en.json +0 -78
  129. package/src/data/product-descriptions/moraine-lake-sunrise-lake-louise-golden-hour.en.json +0 -65
  130. package/src/data/product-descriptions/moraine-lake-sunrise.en.json +0 -64
  131. package/src/data/product-descriptions/private-tour.en.json +0 -80
  132. package/src/data/product-descriptions/two-lakes-combo.en.json +0 -65
  133. package/src/data/products-config.json +0 -101
  134. package/src/hooks/use-bottom-sheet.tsx +0 -15
  135. package/src/hooks/use-simple-modal.tsx +0 -27
  136. package/src/hooks/useBookingSourceMetadataFromLocation.ts +0 -21
  137. package/src/hooks/useEmailSubscription.tsx +0 -103
  138. package/src/hooks/useEmbeddedInIframe.ts +0 -16
  139. package/src/hooks/useIsBookingLaunchLive.ts +0 -49
  140. package/src/hooks/useQuiz.tsx +0 -210
  141. package/src/providers/bottom-sheet-provider.tsx +0 -40
  142. package/src/providers/dependent-add-on-dialog-provider.tsx +0 -105
  143. package/src/radius.css +0 -5
  144. package/src/spacing.css +0 -7
  145. package/src/strings/en.json +0 -1774
  146. package/src/strings/es.json +0 -1573
  147. package/src/strings/fr.json +0 -1573
  148. package/src/strings/index.js +0 -23
  149. package/src/text-style.css +0 -97
  150. package/src/types/fareharbor.d.ts +0 -12
  151. package/src/types/quiz.ts +0 -59
  152. package/src/utils/currency-converter.ts +0 -101
@@ -1,122 +0,0 @@
1
- .productTile {
2
- height: 100%;
3
- width: 100%;
4
- text-decoration: none;
5
- position: relative;
6
- margin-top: var(--spacing-large);
7
- }
8
-
9
- .card {
10
- width: 100%;
11
- height: 380px;
12
- perspective: 1000px;
13
- cursor: pointer;
14
- }
15
-
16
- .cardInner {
17
- position: relative;
18
- width: 100%;
19
- height: 100%;
20
- transition: transform 0.6s;
21
- transform-style: preserve-3d;
22
- }
23
-
24
- .card.isFlipped .cardInner {
25
- transform: rotateY(180deg);
26
- }
27
-
28
- .cardFaceFront,
29
- .cardFaceBack {
30
- position: absolute;
31
- width: 100%;
32
- height: 100%;
33
- -webkit-backface-visibility: hidden;
34
- backface-visibility: hidden;
35
- }
36
-
37
- .productTileImageContainer {
38
- width: 100%;
39
- height: 100%;
40
- position: relative;
41
- border-radius: var(--border-radius-small);
42
- overflow: hidden;
43
- border: 10px solid var(--accent-orange);
44
- box-sizing: border-box;
45
- }
46
-
47
- .cardFaceFront {
48
- transform: rotateY(0deg);
49
- }
50
-
51
- .cardFaceBack {
52
- transform: rotateY(180deg);
53
- height: 380px;
54
- background: var(--grey-text-dark-70);
55
- border-radius: var(--border-radius-small);
56
- }
57
-
58
- .productTilePillValuesView {
59
- height: 100%;
60
- display: flex;
61
- flex-direction: column;
62
- align-items: center;
63
- justify-content: center;
64
- gap: 8px;
65
- }
66
-
67
- .productTileImage {
68
- width: 100%;
69
- height: 100%;
70
- position: relative;
71
- }
72
-
73
- .productTileImage::after {
74
- content: '';
75
- position: absolute;
76
- bottom: 0;
77
- left: 0;
78
- right: 0;
79
- height: 30%;
80
- background: linear-gradient(to top, rgba(0,0,0,0.5), transparent);
81
- z-index: 1;
82
- }
83
-
84
- .productTileContentOverlay {
85
- position: absolute;
86
- top: 0;
87
- left: 0;
88
- right: 0;
89
- padding: 8px;
90
- z-index: 2;
91
- display: flex;
92
- flex-direction: column;
93
- }
94
-
95
- .productTileTags {
96
- display: flex;
97
- flex-direction: row;
98
- gap: var(--spacing-small);
99
- }
100
-
101
- .productTileTitle {
102
- margin: 0;
103
- color: white;
104
- text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
105
- }
106
-
107
- .productStartTime {
108
- position: absolute;
109
- bottom: 0;
110
- right: 0;
111
- color: #FFFFFF;
112
- font-family: 'Northlake', sans-serif;
113
- font-weight: 400;
114
- font-size: 2.5rem;
115
- text-align: right;
116
- z-index: 2;
117
- line-height: 0.8;
118
- }
119
-
120
- .backCardProductTileButton {
121
- margin-top: var(--spacing-small);
122
- }
@@ -1,89 +0,0 @@
1
- 'use client';
2
- import { Product } from "@/constants/products"
3
- import ViaViaImage from "../image"
4
- import styles from "./image-only-product-tile-mobile.module.css"
5
- import { PillVariant } from "../value-pill"
6
- import ValuePill from "../value-pill"
7
- import Button from "../button";
8
- import defaultStrings from "@/strings";
9
- import ProductTag from "../product-tag";
10
- import { OPEN_BOOKING_FOR_PRODUCT } from "@/providers/booking-dialog-provider";
11
-
12
- export default function ProductTileMobile({
13
- product,
14
- isFlipped,
15
- onFlip,
16
- strings = defaultStrings
17
- }: {
18
- product: Product;
19
- isFlipped: boolean;
20
- onFlip: () => void;
21
- strings?: any;
22
- }) {
23
- const handleClick = (e: React.MouseEvent) => {
24
- e.preventDefault();
25
- onFlip();
26
- };
27
-
28
- const handleMoreInfo = (e: React.MouseEvent) => {
29
- e.preventDefault();
30
- e.stopPropagation();
31
- window.dispatchEvent(
32
- new CustomEvent(OPEN_BOOKING_FOR_PRODUCT, { detail: { productId: product.id } })
33
- );
34
- };
35
-
36
- return (
37
- <div className={styles.productTile} onClick={handleClick}>
38
- <div className={`${styles.card} ${isFlipped ? styles.isFlipped : ''}`}>
39
- <div className={styles.cardInner}>
40
- <div className={styles.cardFaceFront}>
41
- <div className={styles.productTileImageContainer}>
42
- <ViaViaImage
43
- className={styles.productTileImage}
44
- imageId={product.images[0].id}
45
- alt={product.images[0].alt}
46
- context="GALLERY"
47
- />
48
- <div className={styles.productTileContentOverlay}>
49
- {/* Render tags if they exist */}
50
- {product.tags && (
51
- <div className={styles.productTileTags}>
52
- {product.tags.map((tag, index) => (
53
- <ProductTag
54
- key={`${tag.text}-${index}`}
55
- text={tag.text}
56
- style={tag.style}
57
- />
58
- ))}
59
- </div>
60
- )}
61
- <h3 className={styles.productTileTitle}>{product.shortName}</h3>
62
- </div>
63
- <span className={styles.productStartTime} dangerouslySetInnerHTML={{ __html: product.currentStartTime }} />
64
- </div>
65
- </div>
66
- <div className={styles.cardFaceBack}>
67
- <div className={styles.productTilePillValuesView}>
68
- {product.pillValues.map((pillValue, index) => (
69
- <ValuePill
70
- key={`${pillValue.label}-${index}`}
71
- variant={PillVariant.solid}
72
- pillValue={pillValue}
73
- />
74
- ))}
75
- <Button
76
- variant="outline"
77
- className={styles.backCardProductTileButton}
78
- action=""
79
- onClick={handleMoreInfo}
80
- >
81
- {strings.common.moreInfo}
82
- </Button>
83
- </div>
84
- </div>
85
- </div>
86
- </div>
87
- </div>
88
- );
89
- }
@@ -1,44 +0,0 @@
1
- 'use client';
2
- import { Product } from "@/constants/products"
3
- import { useState, useEffect } from "react";
4
- import ProductTileDesktop from "./image-only-product-tile-desktop";
5
- import ProductTileMobile from "./image-only-product-tile-mobile";
6
-
7
- export default function ProductTile({
8
- product,
9
- flippedProductId,
10
- onFlip,
11
- strings
12
- }: {
13
- product: Product;
14
- flippedProductId?: string;
15
- onFlip?: (productId: string) => void;
16
- strings?: any;
17
- }) {
18
- const [isMobile, setIsMobile] = useState(false); // Default to desktop view
19
- const [hasMounted, setHasMounted] = useState(false);
20
-
21
- useEffect(() => {
22
- setHasMounted(true);
23
- const checkMobile = () => setIsMobile(window.innerWidth < 1025);
24
- checkMobile();
25
- window.addEventListener('resize', checkMobile);
26
- return () => window.removeEventListener('resize', checkMobile);
27
- }, []);
28
-
29
- // Always render desktop version for SEO/initial render
30
- if (!hasMounted) {
31
- return <ProductTileDesktop product={product} strings={strings} />;
32
- }
33
-
34
- return isMobile ? (
35
- <ProductTileMobile
36
- product={product}
37
- isFlipped={flippedProductId === product.id}
38
- onFlip={() => onFlip?.(product.id)}
39
- strings={strings}
40
- />
41
- ) : (
42
- <ProductTileDesktop product={product} strings={strings} />
43
- );
44
- }
@@ -1,84 +0,0 @@
1
- .bestOptionCard {
2
- background-color: var(--accent-white);
3
- border-radius: 1rem;
4
- box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
5
- overflow: hidden;
6
- width: 100%;
7
- display: flex;
8
- flex-direction: column;
9
- height: 100%;
10
- color: inherit;
11
- }
12
-
13
- .bestOptionCardImage img { /* Target the actual image element */
14
- transition: transform 0.3s ease-in-out;
15
- will-change: transform; /* Optimize performance */
16
- }
17
-
18
- .bestOptionCardImageContainer {
19
- position: relative;
20
- display: flex;
21
- min-height: 250px; /* Minimum height for short content */
22
- }
23
-
24
- .bestOptionCardImage {
25
- width: 100%;
26
- height: 100%; /* Fill container height */
27
- object-fit: cover;
28
- position: absolute; /* Position behind pills */
29
- top: 0;
30
- left: 0;
31
- overflow: hidden;
32
- }
33
-
34
- .bestOptionCardTitle {
35
- font-weight: 700;
36
- font-size: 1.2rem;
37
- text-align: left;
38
- width: 100%;
39
- margin-bottom: 1rem;
40
- word-wrap: break-word;
41
- overflow-wrap: break-word;
42
- max-width: 100%;
43
- text-decoration: none;
44
- }
45
-
46
- .bestOptionCardContent {
47
- display: flex;
48
- flex-direction: column;
49
- align-items: center;
50
- width: 100%;
51
- padding: 1rem;
52
- flex: 1;
53
- justify-content: space-between;
54
- box-sizing: border-box;
55
- }
56
-
57
- .bestOptionCardButton {
58
- width: fit-content;
59
- margin-top: auto;
60
- }
61
-
62
- .productTilePillValuesView {
63
- position: relative;
64
- width: 100%;
65
- display: flex;
66
- flex-direction: row;
67
- flex-wrap: wrap;
68
- gap: 4px;
69
- align-content: flex-end;
70
- justify-content: flex-start;
71
- padding: 16px;
72
- z-index: 1;
73
- }
74
-
75
- /* Add hover effect for desktop */
76
- @media (min-width: 1024px) {
77
- .bestOptionCard:hover .productTilePillValuesView {
78
- opacity: 1;
79
- }
80
-
81
- .bestOptionCard:hover .bestOptionCardImage img {
82
- transform: scale(1.1); /* 10% zoom on hover */
83
- }
84
- }
@@ -1,61 +0,0 @@
1
- "use client";
2
-
3
- import { Product } from "@/constants/products";
4
- import ViaViaImage from "../image";
5
- import styles from "./product-tile-card.module.css";
6
- import Button, { ButtonHoverColor } from "../button";
7
- import defaultStrings from "@/strings";
8
- import ValuePill from "../value-pill";
9
- import { PillVariant } from "../value-pill";
10
- import { OPEN_BOOKING_FOR_PRODUCT } from "@/providers/booking-dialog-provider";
11
-
12
- export default function ProductTileCard({ product, strings = defaultStrings }: { product: Product; strings?: any }) {
13
- const handleClick = (e: React.MouseEvent) => {
14
- e.preventDefault();
15
- window.dispatchEvent(
16
- new CustomEvent(OPEN_BOOKING_FOR_PRODUCT, { detail: { productId: product.id } })
17
- );
18
- };
19
-
20
- return (
21
- <div onClick={handleClick} className={styles.bestOptionCard} style={{ cursor: 'pointer' }}>
22
- <div className={styles.bestOptionCardImageContainer}>
23
- <ViaViaImage
24
- imageId={product.images[0].id}
25
- alt={product.images[0].alt}
26
- context="GALLERY"
27
- className={styles.bestOptionCardImage}
28
- />
29
- <ProductTilePillValuesView product={product} />
30
- </div>
31
- <div className={styles.bestOptionCardContent}>
32
- <p className={styles.bestOptionCardTitle}>{product.name}</p>
33
- <Button
34
- variant="primary"
35
- className={styles.bestOptionCardButton}
36
- hoverColor={ButtonHoverColor.Turquoise}
37
- >
38
- {strings.common.moreInfo}
39
- </Button>
40
- </div>
41
- </div>
42
- );
43
- }
44
-
45
- function ProductTilePillValuesView({
46
- product
47
- }: {
48
- product: Product
49
- }) {
50
- return (
51
- <div className={styles.productTilePillValuesView}>
52
- {product.pillValues.map((pillValue, index) => (
53
- <ValuePill
54
- key={index}
55
- variant={PillVariant.overlay}
56
- pillValue={pillValue}
57
- />
58
- ))}
59
- </div>
60
- )
61
- }
@@ -1,85 +0,0 @@
1
- .review-highlights-section {
2
- background-color: var(--accent-turquoise-60);
3
- padding: var(--spacing-large) 0;
4
- }
5
-
6
- .review-highlights-container {
7
- overflow-x: auto;
8
- -ms-overflow-style: none;
9
- scrollbar-width: none;
10
- }
11
-
12
- .review-highlights-container::-webkit-scrollbar {
13
- display: none;
14
- }
15
-
16
- .review-highlights-row {
17
- display: flex;
18
- gap: 24px;
19
- min-width: min-content;
20
- }
21
-
22
- .review-highlights-title {
23
- color: var(--accent-white);
24
- padding: 0 var(--spacing-small);
25
- }
26
-
27
- .review-card {
28
- min-width: 300px;
29
- background: white;
30
- padding: 32px;
31
- border-radius: 40px;
32
- position: relative;
33
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
34
- margin-top: 50px;
35
- margin-right: 40px;
36
- display: flex;
37
- flex-direction: column;
38
- justify-content: space-between;
39
- }
40
-
41
- .author-avatar {
42
- width: 80px;
43
- height: 80px;
44
- border-radius: 50%;
45
- background: transparent;
46
- position: absolute;
47
- top: -40px;
48
- right: -40px;
49
- border: 4px solid white;
50
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
51
- }
52
-
53
- .review-author-name {
54
- text-align: right;
55
- padding-top: var(--spacing-small);
56
- }
57
-
58
- /* Desktop */
59
- @media (min-width: 1024px) {
60
- .review-quote {
61
- font-size: 14px;
62
- }
63
- }
64
-
65
- .expand-button {
66
- background: none;
67
- border: none;
68
- color: var(--accent-orange);
69
- cursor: pointer;
70
- font-weight: 600;
71
- font-family: 'Figtree', sans-serif;
72
- padding: 0;
73
- margin-left: 4px;
74
- text-decoration: underline;
75
- transition: color 0.2s ease;
76
- }
77
-
78
- .expand-button:hover {
79
- color: var(--accent-orange-dark);
80
- }
81
-
82
- .expand-button:focus {
83
- outline: none;
84
- color: var(--accent-orange-dark);
85
- }
@@ -1,127 +0,0 @@
1
- "use client";
2
-
3
- import { Parallax } from 'react-scroll-parallax';
4
- import './review-highlights-section.css';
5
- import ParallaxWrapper from '@/components/parallax-provider';
6
- import { REVIEWS, Review } from '@/constants/reviews';
7
- import Image from 'next/image';
8
- import StarRating from '@/components/star-rating';
9
- import { useState } from 'react';
10
- import TranslatableReviews from './translatable-reviews';
11
- import { usePathname } from 'next/navigation';
12
-
13
- function ReviewCard({
14
- review,
15
- originalReview,
16
- showOriginal,
17
- onToggleOriginal,
18
- language
19
- }: {
20
- review: typeof REVIEWS[0];
21
- originalReview?: typeof REVIEWS[0];
22
- showOriginal?: boolean;
23
- onToggleOriginal?: () => void;
24
- language: 'en' | 'es' | 'fr';
25
- }) {
26
- const [isExpanded, setIsExpanded] = useState(false);
27
-
28
- // Use original review if showing original, otherwise use the current review
29
- const displayReview = showOriginal && originalReview ? originalReview : review;
30
-
31
- // Truncate text to first 100 characters
32
- const truncatedText = displayReview.review.length > 500 ? displayReview.review.substring(0, 500) + '...' : displayReview.review;
33
- const shouldShowExpand = displayReview.review.length > 500;
34
-
35
- return (
36
- <div className="review-card">
37
- <p className="review-quote">
38
- <span dangerouslySetInnerHTML={{
39
- __html: isExpanded ? displayReview.review : truncatedText
40
- }} />
41
- {shouldShowExpand && (
42
- <button
43
- className="expand-button"
44
- onClick={() => setIsExpanded(!isExpanded)}
45
- >
46
- {isExpanded ? ' Show less' : ' Read more'}
47
- </button>
48
- )}
49
- </p>
50
- {(language === 'es' || language === 'fr') && originalReview && onToggleOriginal && (
51
- <div style={{ marginBottom: '1rem', textAlign: 'center' }}>
52
- <button
53
- onClick={onToggleOriginal}
54
- style={{
55
- background: 'none',
56
- border: 'none',
57
- color: 'var(--accent-orange)',
58
- cursor: 'pointer',
59
- fontWeight: '600',
60
- fontFamily: "'Figtree', sans-serif",
61
- padding: '0',
62
- textDecoration: 'underline',
63
- transition: 'color 0.2s ease'
64
- }}
65
- >
66
- {showOriginal ? (language === 'es' ? 'Ver traducido' : 'Voir traduit') : (language === 'es' ? 'Ver original' : 'Voir original')}
67
- </button>
68
- </div>
69
- )}
70
- <div className="review-author">
71
- <div className="author-avatar">
72
- <Image src={displayReview.profilePicture} alt={displayReview.author} width={80} height={80} />
73
- </div>
74
- </div>
75
- <div>
76
- <p className="review-author-name">{displayReview.author}</p>
77
- <StarRating rating={5} align="right" />
78
- </div>
79
- </div>
80
- );
81
- }
82
-
83
- export default function ReviewHighlightsSection({ className, strings }: { className: string; strings: any }) {
84
- const pathname = usePathname();
85
- const language = pathname?.startsWith('/es') ? 'es' : pathname?.startsWith('/fr') ? 'fr' : 'en';
86
- const [individualToggles, setIndividualToggles] = useState<Record<string, boolean>>({});
87
-
88
- const toggleIndividualOriginal = (reviewLink: string) => {
89
- setIndividualToggles(prev => ({
90
- ...prev,
91
- [reviewLink]: !prev[reviewLink]
92
- }));
93
- };
94
-
95
- return (
96
- <div id="reviews-section" className={`review-highlights-section ${className}`}>
97
- <ParallaxWrapper>
98
- <h2 className="review-highlights-title">{strings.home.reviewHighlights.title}</h2>
99
- <section className="review-highlights-container">
100
- <Parallax translateX={['100%', '-100%']}>
101
- <div className="review-highlights-row">
102
- <TranslatableReviews language={language}>
103
- {(reviews) =>
104
- reviews.map((review, index) => {
105
- const originalReview = REVIEWS[index];
106
- const showOriginal = individualToggles[review.link] || false;
107
-
108
- return (
109
- <ReviewCard
110
- key={review.link}
111
- review={review}
112
- originalReview={originalReview}
113
- showOriginal={showOriginal}
114
- onToggleOriginal={() => toggleIndividualOriginal(review.link)}
115
- language={language}
116
- />
117
- );
118
- })
119
- }
120
- </TranslatableReviews>
121
- </div>
122
- </Parallax>
123
- </section>
124
- </ParallaxWrapper>
125
- </div>
126
- );
127
- }