diff --git a/app/[locale]/[slug]/page.tsx b/app/[locale]/[slug]/page.tsx index 9e50527da..215014a35 100644 --- a/app/[locale]/[slug]/page.tsx +++ b/app/[locale]/[slug]/page.tsx @@ -2,6 +2,7 @@ import StoryblokPage, { StoryblokPageProps } from '@/components/storyblok/Storyb import { getStoryblokApi, ISbStoriesParams } from '@storyblok/react/rsc'; import { routing } from '@/i18n/routing'; +import { STORYBLOK_ENVIRONMENT } from '@/lib/constants/common'; import { getStoryblokStory } from '@/lib/storyblok'; import { generateMetadataBasic } from '@/lib/utils/generateMetadataBase'; import { notFound } from 'next/navigation'; @@ -34,7 +35,7 @@ export async function generateStaticParams() { const storyblokApi = getStoryblokApi(); let sbParams: ISbStoriesParams = { - version: 'published', + version: STORYBLOK_ENVIRONMENT, }; const { data } = await storyblokApi.get('cdn/links/', sbParams); diff --git a/app/[locale]/conversations/[slug]/page.tsx b/app/[locale]/conversations/[slug]/page.tsx index b6832441b..0ae3e229a 100644 --- a/app/[locale]/conversations/[slug]/page.tsx +++ b/app/[locale]/conversations/[slug]/page.tsx @@ -2,6 +2,7 @@ import StoryblokResourceConversationPage, { StoryblokResourceConversationPageProps, } from '@/components/storyblok/StoryblokResourceConversationPage'; import { routing } from '@/i18n/routing'; +import { STORYBLOK_ENVIRONMENT } from '@/lib/constants/common'; import { getStoryblokStory } from '@/lib/storyblok'; import { generateMetadataBasic } from '@/lib/utils/generateMetadataBase'; import { getStoryblokApi, ISbStoriesParams } from '@storyblok/react/rsc'; @@ -40,7 +41,7 @@ export async function generateStaticParams() { const storyblokApi = getStoryblokApi(); let sbParams: ISbStoriesParams = { - version: 'published', + version: STORYBLOK_ENVIRONMENT, starts_with: 'conversations/', filter_query: { component: { diff --git a/app/[locale]/courses/[slug]/[sessionSlug]/page.tsx b/app/[locale]/courses/[slug]/[sessionSlug]/page.tsx index dd2f6d607..502995a1e 100644 --- a/app/[locale]/courses/[slug]/[sessionSlug]/page.tsx +++ b/app/[locale]/courses/[slug]/[sessionSlug]/page.tsx @@ -2,6 +2,7 @@ import StoryblokSessionPage, { StoryblokSessionPageProps, } from '@/components/storyblok/StoryblokSessionPage'; import { routing } from '@/i18n/routing'; +import { STORYBLOK_ENVIRONMENT } from '@/lib/constants/common'; import { getStoryblokStory } from '@/lib/storyblok'; import { generateMetadataBasic } from '@/lib/utils/generateMetadataBase'; import { getStoryblokApi, ISbStoriesParams } from '@storyblok/react/rsc'; @@ -43,7 +44,7 @@ export async function generateStaticParams() { const storyblokApi = getStoryblokApi(); let sbParams: ISbStoriesParams = { - version: 'published', + version: STORYBLOK_ENVIRONMENT, starts_with: 'courses/', }; diff --git a/app/[locale]/courses/[slug]/page.tsx b/app/[locale]/courses/[slug]/page.tsx index af0bf0940..2d1b76831 100644 --- a/app/[locale]/courses/[slug]/page.tsx +++ b/app/[locale]/courses/[slug]/page.tsx @@ -2,6 +2,7 @@ import StoryblokCoursePage, { StoryblokCoursePageProps, } from '@/components/storyblok/StoryblokCoursePage'; import { routing } from '@/i18n/routing'; +import { STORYBLOK_ENVIRONMENT } from '@/lib/constants/common'; import { getStoryblokStory } from '@/lib/storyblok'; import { generateMetadataBasic } from '@/lib/utils/generateMetadataBase'; import { getStoryblokApi, ISbStoriesParams } from '@storyblok/react/rsc'; @@ -40,7 +41,7 @@ export async function generateStaticParams() { const storyblokApi = getStoryblokApi(); let sbParams: ISbStoriesParams = { - version: 'published', + version: STORYBLOK_ENVIRONMENT, starts_with: 'courses/', filter_query: { component: { diff --git a/app/[locale]/courses/page.tsx b/app/[locale]/courses/page.tsx index 604d85391..588767c2e 100644 --- a/app/[locale]/courses/page.tsx +++ b/app/[locale]/courses/page.tsx @@ -1,3 +1,4 @@ +import { STORYBLOK_ENVIRONMENT } from '@/lib/constants/common'; import { FeatureFlag } from '@/lib/featureFlag'; import { getStoryblokStories } from '@/lib/storyblok'; import { generateMetadataBasic } from '@/lib/utils/generateMetadataBase'; @@ -21,7 +22,7 @@ export default async function Page({ params }: { params: Params }) { const baseProps: Partial = { language: locale, - version: 'published', + version: STORYBLOK_ENVIRONMENT, sort_by: 'position:description', }; diff --git a/app/[locale]/shorts/[slug]/page.tsx b/app/[locale]/shorts/[slug]/page.tsx index ed1c7c6b4..ae0cf9ff5 100644 --- a/app/[locale]/shorts/[slug]/page.tsx +++ b/app/[locale]/shorts/[slug]/page.tsx @@ -2,6 +2,7 @@ import StoryblokResourceShortPage, { StoryblokResourceShortPageProps, } from '@/components/storyblok/StoryblokResourceShortPage'; import { routing } from '@/i18n/routing'; +import { STORYBLOK_ENVIRONMENT } from '@/lib/constants/common'; import { COURSE_CATEGORIES } from '@/lib/constants/enums'; import { getStoryblokStories, getStoryblokStory } from '@/lib/storyblok'; import { generateMetadataBasic } from '@/lib/utils/generateMetadataBase'; @@ -45,7 +46,7 @@ export async function generateStaticParams() { const storyblokApi = getStoryblokApi(); let sbParams: ISbStoriesParams = { - version: 'published', + version: STORYBLOK_ENVIRONMENT, starts_with: 'shorts/', filter_query: { component: { diff --git a/app/[locale]/therapy/book-session/BookTherapyPage.tsx b/app/[locale]/therapy/book-session/BookTherapyPage.tsx index 3c8138095..cc7cb80bf 100644 --- a/app/[locale]/therapy/book-session/BookTherapyPage.tsx +++ b/app/[locale]/therapy/book-session/BookTherapyPage.tsx @@ -1,10 +1,11 @@ 'use client'; -import Faqs from '@/components/common/Faqs'; -import ImageTextGrid, { ImageTextItem } from '@/components/common/ImageTextGrid'; -import Header from '@/components/layout/Header'; +import ImageTextRow, { ImageTextItem } from '@/components/common/ImageTextRow'; +import NoDataAvailable from '@/components/common/NoDataAvailable'; +import Header, { HeaderProps } from '@/components/layout/Header'; +import StoryblokPageSection from '@/components/storyblok/StoryblokPageSection'; +import TherapyBookings from '@/components/therapy/TherapyBookings'; import { THERAPY_BOOKING_OPENED, THERAPY_BOOKING_VIEWED } from '@/lib/constants/events'; -import { therapyFaqs } from '@/lib/constants/faqs'; import { useTypedSelector } from '@/lib/hooks/store'; import { getSimplybookWidgetConfig } from '@/lib/simplybook'; import { PartnerAccess } from '@/lib/store/partnerAccessSlice'; @@ -13,35 +14,86 @@ import illustrationChange from '@/public/illustration_change.svg'; import illustrationChooseTherapist from '@/public/illustration_choose_therapist.svg'; import illustrationConfidential from '@/public/illustration_confidential.svg'; import illustrationDateSelector from '@/public/illustration_date_selector.svg'; -import illustrationLeafMix from '@/public/illustration_leaf_mix.svg'; -import illustrationPerson4Peach from '@/public/illustration_person4_peach.svg'; -import { rowStyle } from '@/styles/common'; -import { Box, Button, Container, Typography } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { Box, Button, Container, IconButton, Modal, Typography } from '@mui/material'; +import { ISbStoryData } from '@storyblok/react/rsc'; import { useTranslations } from 'next-intl'; -import Image from 'next/image'; import Script from 'next/script'; import { useEffect, useState } from 'react'; const containerStyle = { + pt: '0 !important', backgroundColor: 'secondary.light', +} as const; + +const bookingButtonStyle = { + minWidth: 200, + marginY: 4, +} as const; + +const bookingSectionStyle = { + backgroundColor: 'background.default', textAlign: 'center', - ...rowStyle, + paddingTop: 4, + paddingBottom: 4, } as const; -const ctaContent = { - flex: 1, - textAlign: 'left', - marginTop: 4, +const modalStyle = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', } as const; -const faqsContainerStyle = { - maxWidth: '680px !important', - margin: 'auto', +const modalContentBoxStyle = { + position: 'relative', + width: '95%', + height: '90vh', + maxWidth: '1000px', + maxHeight: '800px', + bgcolor: 'background.default', + boxShadow: 24, + borderRadius: 2, + p: 0, + overflow: 'hidden', + border: 'none', + outline: 'none', + display: 'flex', + flexDirection: 'column', } as const; -const bookingButtonStyle = { - minWidth: 200, - marginY: 4, +const widgetContainerStyle = { + flexGrow: 1, + width: '100%', + height: '100%', + overflowY: 'auto', + overflowX: 'hidden', + mt: 8, + '& iframe': { + width: '100%', + border: 'none', + display: 'block', + }, +} as const; + +const modalTitleStyle = { + position: 'absolute', + top: 10, + right: 0, + left: 0, + marginInline: 'auto', + width: 'fit-content', +} as const; + +const closeButtonStyle = { + position: 'absolute', + top: 20, + right: 20, + zIndex: 1301, + color: 'grey.700', + backgroundColor: 'rgba(255, 255, 255, 0.7)', + '&:hover': { + backgroundColor: 'rgba(255, 255, 255, 0.9)', + }, } as const; const steps: Array = [ @@ -67,34 +119,64 @@ const steps: Array = [ }, ]; -export default function BookTherapyPage() { +interface Props { + story: ISbStoryData | undefined; +} + +export default function BookTherapyPage({ story }: Props) { const t = useTranslations('Therapy'); - const tS = useTranslations('Shared'); const [partnerAccess, setPartnerAccess] = useState(null); const [hasTherapyRemaining, setHasTherapyRemaining] = useState(false); - const [widgetOpen, setWidgetOpen] = useState(false); + const [isWidgetModalOpen, setIsWidgetModalOpen] = useState(false); + const [isScriptLoaded, setIsScriptLoaded] = useState(false); + const [widgetError, setWidgetError] = useState(null); const user = useTypedSelector((state) => state.user); const partnerAccesses = useTypedSelector((state) => state.partnerAccesses); + function replacePartnerName(partnerName: string) { + const accordionDetailsElements = document.querySelectorAll('.MuiAccordionDetails-root'); + const introductionElement: HTMLParagraphElement | null = document.querySelector('h1 + p'); + + accordionDetailsElements.forEach((detailsElement) => { + const paragraphElements = detailsElement.querySelectorAll('p'); + + const allElementsToProcess = [...paragraphElements]; + if (introductionElement) { + allElementsToProcess.push(introductionElement); + } + + allElementsToProcess.forEach((element) => { + if (!element) return; + + // Use innerHTML to preserve embedded HTML elements + const currentHtml = element.innerHTML; + + // Replace all instances of '{partnerName}' with the provided partnerName + const escapedPartnerName = partnerName.replace(//g, '>'); + const newHtml = currentHtml.replace(/\{partnerName\}/g, escapedPartnerName); + + element.innerHTML = newHtml; + }); + }); + } + useEffect(() => { - let partnerAccess = partnerAccesses.find( - (partnerAccess) => - partnerAccess.featureTherapy === true && partnerAccess.therapySessionsRemaining > 0, + let currentPartnerAccess = partnerAccesses.find( + (pa) => pa.featureTherapy === true && pa.therapySessionsRemaining > 0, ); - if (partnerAccess) { + if (currentPartnerAccess) { setHasTherapyRemaining(true); } else { - // existing therapy access has no remaining sessions, return access that was last redeemed const redeemedAccesses = partnerAccesses.filter( - (partnerAccess) => - !!partnerAccess.featureTherapy && partnerAccess.therapySessionsRedeemed > 0, + (pa) => !!pa.featureTherapy && pa.therapySessionsRedeemed > 0, ); - partnerAccess = redeemedAccesses[redeemedAccesses.length - 1]; + currentPartnerAccess = redeemedAccesses[redeemedAccesses.length - 1]; } - if (partnerAccess?.partner.name) { - setPartnerAccess(partnerAccess); + if (currentPartnerAccess?.partner.name) { + setPartnerAccess(currentPartnerAccess); + replacePartnerName(currentPartnerAccess?.partner.name); } }, [setPartnerAccess, partnerAccesses]); @@ -102,93 +184,173 @@ export default function BookTherapyPage() { logEvent(THERAPY_BOOKING_VIEWED); }, []); - const headerProps = { - title: t('title'), - introduction: `${t.rich('introduction', { partnerName: partnerAccess?.partner?.name as string })}`, - imageSrc: illustrationPerson4Peach, - imageAlt: 'alt.personTea', + const handleOpenWidgetModal = () => { + logEvent(THERAPY_BOOKING_OPENED); + setWidgetError(null); + setIsWidgetModalOpen(true); }; - const openWidget = () => { - logEvent(THERAPY_BOOKING_OPENED); + const handleCloseWidgetModal = () => { + setIsWidgetModalOpen(false); + setWidgetError(null); + }; + + useEffect(() => { + let timeoutId: NodeJS.Timeout | null = null; + + const initializeWidget = () => { + if (typeof (window as any).SimplybookWidget === 'function') { + const container = document.getElementById('simplybook-widget-container'); + if (container) { + container.innerHTML = ''; + setWidgetError(null); + try { + new (window as any).SimplybookWidget(getSimplybookWidgetConfig(user)); + } catch (error) { + setWidgetError(t('error.initializingWidget')); + } + } else { + timeoutId = setTimeout(() => { + const containerRetry = document.getElementById('simplybook-widget-container'); + if (containerRetry) { + containerRetry.innerHTML = ''; + setWidgetError(null); + try { + new (window as any).SimplybookWidget(getSimplybookWidgetConfig(user)); + } catch (error) { + setWidgetError(t('error.initializingWidgetRetry')); + } + } else { + console.error('Simplybook widget container not found after retry.'); + setWidgetError(t('error.containerNotFoundRetry')); + } + }, 1000); + } + } else { + setWidgetError(t('error.scriptNotLoaded')); + } + }; + + if (isWidgetModalOpen && isScriptLoaded) { + initializeWidget(); + } else if (!isWidgetModalOpen) { + setWidgetError(null); + } + + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [isWidgetModalOpen, isScriptLoaded, user, t]); // Added t to dependencies + + if (!story) { + return ; + } - setWidgetOpen(true); + const headerProps: HeaderProps = { + title: story.content.title, + introduction: story.content.description, + imageSrc: story.content.header_image.filename, + translatedImageAlt: story.content.header_image.alt, }; return (
+ {t('bookingButton')} + + } /> - - - {hasTherapyRemaining - ? t.rich('therapySessionsRemaining', { - strongText: () => ( - - {partnerAccess?.therapySessionsRemaining} - - ), - }) - : t('noTherapySessionsRemaining')} - - {hasTherapyRemaining && ( - - )} - - + {partnerAccess && } - - - {t('faqHeader')} + + + {t('bookingSectionTitle')} - - {tS('alt.leafMix')} + + + {hasTherapyRemaining && ( + + )}{' '} + + + {story.content.page_sections?.length > 0 && + story.content.page_sections.map((section: any, index: number) => ( + + ))} + + + + + + + + {t('modalTitle')} + + + {t('modalDescription')} + - - - {hasTherapyRemaining && ( - + {widgetError && ( + + + {widgetError} + + )} + + - + - {widgetOpen && ( -