Untitled
unknown
plain_text
10 days ago
65 kB
3
Indexable
/* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable @typescript-eslint/member-delimiter-style */ /* eslint-disable @typescript-eslint/array-type */ /* eslint-disable @nx/workspace/no-useeffect */ /* eslint-disable @typescript-eslint/consistent-indexed-object-style */ /* eslint-disable @typescript-eslint/promise-function-async */ /* eslint-disable @typescript-eslint/no-confusing-void-expression */ /* eslint-disable @typescript-eslint/no-misused-promises */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @nx/workspace/no-number-coercion */ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable complexity */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ import { useEffect, useMemo, useRef, useState, type ChangeEvent } from 'react' import { Header } from '../../../components/Header' import { Footer } from '../../../components/Footer' import LocationSelector from '../../../components/LocationStep/LocationSelector' import BookingDetails from '../../../components/BookingDetailsStep/BookingDetails' import BookingConfirmation from '../../../components/BookingConfirmationStep/BookingConfirmation' import EmployeeSelector from '../../../components/EmployeeStep/EmployeeSelector' import DateTimeSelector from '../../../components/DateTimeStep/DateTimeSelector' import styles from '../index.module.less' import { useTranslationI18 } from '../../../hooks/useTranslationI18' import { useFindServiceMessageTemplateQuery, useServicesByIdsQuery, useSelectedStaffUserQuery, useOnlineBookableLocationsQuery, useInsertNotificationBookingMutation, useCreateNotificationStateMutation, useNotificationsTypesByTypeQuery, Notification_Types_Enum_Enum, useAllCompanyStaffQuery, useMostVisitedBookingLocationLazyQuery, useContactsByEmailLazyQuery, useSendSmsToUnauthenticatedContactMutation, useResendCodeToEmailMutation, useAccountRegistrationMutation, useVerifySmsLazyQuery, usePatientMedicalFormsQuery, useCreateOnlineBookingMutation, useSendEmailToUnauthenticatedContactMutation, useConnectDepositPolicyQuery, useGeocodeAddressLazyQuery, type OnlineBookableLocationsQuery, useSendEmailWithTagsMutation, useOnlineBookableLocationsCountQuery, useUserWithAlertPermissionQuery, useNotificationSettingTemplateQuery, useCreateOneUserActivityLogMutation, useCreateContactMetaMutation, usePackageDataQuery, useBookitProGeneralEmailQuery, useBookingEmailTemplatesMutation, useCancellationPolicyQuery, Company_Services_Deposit_Type, PaymentType, useListSavedCardsQuery, useContactPaymentMethodIdQuery, useSendAppointmentConfirmationMailMutation, useNotificationSettingLazyQuery, useCompanyActiveNotificationSettingsQuery, useFindFavoriteMedicalFormsCountQuery, useMasterCategoryQuery, } from '@pabau/graphql' import { companyDetailsSelector, useCompanyDetailsStore, onlineBookingSettingsSelector, useOnlineBookingSettingsStore, connectUserSelector, useConnectUserStore, } from '@pabau/heap/connect' import { useSelectedDataStore } from '../../../store/selectedData' import { useStep, onlineBookingSteps } from '../../../hooks/useStep' import { PaymentWrapper } from '../../../components/payment/PaymentWrapper' import dayjs from 'dayjs' import GoogleAnalytics from '../../../components/Analytics/GoogleAnalytics' import { shallow } from 'zustand/shallow' import { useRouter } from 'next/router' import useLocationPermissions from '../../../hooks/useLocationPermissions' import { CustomHead } from '../custom-head-details' import { useBoolean, useDebounce } from 'usehooks-ts' import PatientInfo from '../../../components/patientinformatioon/PatientInfo' import { BackButton } from '../../../components/BackButton' import { useResponsive } from '@pabau/atlas' import { NotificationType, Notification, } from '@pabau/ui/notification/Notification' import { SuccessfulLottie } from '@pabau/ui/SuccessfulLottie/SuccessfulLottie' import { SmsVerification } from '@pabau/ui/sms-verification/SmsVerification' import { concatenateTruthyValues } from '@pabau/ui/helpers/stringUtils' import { createCdnLink } from '@pabau/quartz' import classNames from 'classnames' import { formatDateFromISO } from '../../../helpers/splitHours' import { useUserLanguage } from '../../../components/hooks/UserLanguage' import { isValidDateStringInFormat } from '@pabau/yup/lib/dayjsValidation' import type { BookingData, BookingInfo } from '../../../types/booking' import { StepNavigator } from '../../../components/StepNavigator' import { useSearchParams } from 'next/navigation' import SelectedItemsFooter, { handeLastStepFlag, } from '../../../components/ServicesStep/SelectedItemsFooter/SelectedItemsFooter' import { useCompanyTimeDetailsParse, useConnectScrollButton, useSendNotification, } from '@pabau/captain' import { prepareTemplateTypeWithLanguage } from '@pabau/quartz' import { StepNavigatorHeader } from '../../../components/StepNavigator/Header' import { areAllServicesFree, calculateDeposit, servicesHasPolicies, } from '@pabau/quartz/juno' import { getDepositPolicyFromType } from '@pabau/quartz/globus' import { serviceImage } from '../../../helpers/serviceImage' import { tagBookingFormStep, sendGoogleTagEvent, } from '../../../helpers/sendGoogleTagEvent' import { stepUrlData } from '../../../components/ServicesStep/SelectedItemsFooter/helpers/stepUrlData' import { calculateStaffCostTiers } from '../../../helpers/calculateStaffCostTiers' import { calculateTotalCostWithTiers } from '../../../helpers/calculateTotalCostWithTiers' import { shouldSkipPaymentScreen } from '../../../helpers/shouldSkipPaymentScreen' import { buzzFeedVariables } from '../../../helpers/createOneBuzzfeedVariables' import { useLaunchDarklyFlags } from '@pabau/captain/useLaunchDarklyFlags' import { sendWebNotifications } from '../../../helpers/bookingNotifications' import { areServicesEligibleForCancellationPolicy } from '../../../helpers/cancellationPolicy' import { redirectPageAfterBooking } from '../../../helpers/redirectPageAfterBooking' import pMap from '@cjs-exporter/p-map' import { totalDuration } from '../../../hooks/useServicesTotalDuration' import { createEcommerceData } from '../../../helpers/populateEcommerce' import { redirectToDateTimeStep, redirectToEmployeeStep, redirectToLocationStep, redirectToShiftStep, } from '../../../helpers/redirectToDesiredStep' import { DownUpArrowIcon } from '@pabau/idol/DownUpArrowIcon' import { getPrepaidTypeConfig } from '../../../helpers/getPrepaidAnalyticsConfig' import RevampDateTimeSelector from '../../../components/DateTimeStep/RevampDateTimeSelector' import { getCookie } from 'react-use-cookie' import { equals } from 'remeda' import { GoogleHeader } from '../../../components/StepNavigator/GoogleHeader' import { LazyLottie } from '@pabau/atlas' const { masterCategoryStep, categoryStep, servicesStep, locationStep, employeeStep, dateTimeStep, bookingStep, paymentStep, confirmStep, patientInformation, packageStep, accountStep, voucherStep, } = onlineBookingSteps export const Index = () => { const { t } = useTranslationI18() const router = useRouter() const searchParam = useSearchParams() const prepaidType = searchParam.get('prepaidType') const { location, discover, services, category, staff, date: urlDate = '', bookingId: bookingIdUrl, rescheduleBookingId, packageId, voucherDetails, shiftId, packageClickedId, withGoogle, } = router.query const { companyDetails, setDepositPolicy, depositPolicy } = useCompanyDetailsStore(companyDetailsSelector, shallow) const isFlagActive = useLaunchDarklyFlags() const isVoucherActive = isFlagActive( 'Online Bookings - Gift voucher purchase for someone else' ) const isGTMEventsActive = isFlagActive('GTM events') const stripeKey = companyDetails?.BookitProGeneral?.stripe_public_key const { onlineBookingSettings } = useOnlineBookingSettingsStore( onlineBookingSettingsSelector, shallow ) const [CreateOneContactMeta] = useCreateContactMetaMutation() const { mobile, tablet, laptop, desktop } = useResponsive() const { selectedData, setSelectedData, actionTypes, resetSelectedData } = useSelectedDataStore() const { connectUser, connectLogin } = useConnectUserStore( connectUserSelector, shallow ) const { currentStep, goToNextStep, goToPrevStep, setStep, changeTotalSteps, totalSteps, setCurrentStep, } = useStep(confirmStep, onlineBookingSettings?.show_service_groups) const { value: skipLocationStep, setTrue: setSkipLocationStep } = useBoolean(false) const { value: skipPaymentStep, setTrue: skipPaymentStepTrue } = useBoolean(false) const [smsPasscodeInput, setSmsPasscodeInput] = useState('') const [locationSearchString, setLocationSearchString] = useState('') const debounceTypedKey = useDebounce(locationSearchString, 1000) const { language } = useUserLanguage() const { value: categorySelect, setFalse: setCategorySelectFalse } = useBoolean(false) const { data: masterCategories } = useMasterCategoryQuery() const { data: { findManyServiceMessageTemplate: precareTemplates } = {}, } = useFindServiceMessageTemplateQuery({ variables: { serviceId: selectedData?.services.map((service) => service.id), }, skip: !selectedData?.services?.[0]?.id, }) const [sendTemplate] = useBookingEmailTemplatesMutation() const { value: smsVerificationModal, setTrue: openSmsVerificationModal } = useBoolean(false) const { setFalse: smsSendErrorDisable, setTrue: smsSendErrorEnable, value: smsSend, } = useBoolean(false) const { setTrue: setOnBackClickTrue } = useBoolean(false) const serviceIdMedicalForm = () => { const serviceIds = selectedData.services.map((service) => service.id) return serviceIds.map((id) => ({ service_id: { contains: String(id) } })) } const { data: { findManyMedicalFormCount = 0 } = {}, } = useFindFavoriteMedicalFormsCountQuery() const { data: { findManyMedicalForm: medicalForms = [] } = {}, loading: isFetchingMedicalForms, } = usePatientMedicalFormsQuery({ skip: !onlineBookingSettings?.require_form || (serviceIdMedicalForm().length === 0 && findManyMedicalFormCount === 0) || selectedData.prepaidItems.length > 0, variables: { where: { AND: [ { deleted_at: { equals: null } }, { OR: [ { AND: [ { form_type: { equals: 'questionnaire' } }, { is_fav: { equals: 2 } }, ], }, { AND: [ { OR: serviceIdMedicalForm() }, { bookingRequired: { equals: '1' } }, ], }, ], }, ], }, }, onCompleted({ findManyMedicalForm }) { if (findManyMedicalForm.length === 0) { params.delete('medicalForms') } else { params.set('medicalForms', 'true') } router.replace({ pathname: router.pathname, query: params.toString(), }) }, }) const [createOneBuzzFeed] = useCreateOneUserActivityLogMutation() const [ fetchMatchedContact, { data: cmContactData, loading: cmContactLoading }, ] = useContactsByEmailLazyQuery() const existingClientMatched = !cmContactLoading && Boolean(cmContactData?.findFirstCmContact) const [sendSms] = useSendSmsToUnauthenticatedContactMutation({ onCompleted({ sendSmsToUnauthenticatedContact }) { // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. if (sendSmsToUnauthenticatedContact.success) { Notification( NotificationType.success, t('notifications.connect.sms.send.successMessage') ) smsSendErrorDisable() } else { handleResendCodeToEmail() smsSendErrorEnable() } }, onError() { handleResendCodeToEmail() smsSendErrorEnable() }, }) const [resendCodeToEmail] = useResendCodeToEmailMutation() const handleResendCodeToEmail = () => { if (!cmContactData?.findFirstCmContact?.Email) return void resendCodeToEmail({ variables: { companyId: companyDetails?.id, contactEmail: cmContactData.findFirstCmContact.Email, contactId: cmContactData.findFirstCmContact.ID, }, onCompleted() { Notification( NotificationType.success, t('notifications.connect.email.send.successMessage') ) }, }) } usePackageDataQuery({ variables: { packageId: Number.parseInt(packageId?.toString() ?? '0.00'), }, skip: !packageId, onCompleted({ findFirstPackage }) { if (findFirstPackage?.id == null || findFirstPackage?.product_id == null) return setSelectedData(actionTypes.setPrepaidItems, [ { id: findFirstPackage.product_id, name: findFirstPackage.name, amount: findFirstPackage.price, type: 'package', package_id: findFirstPackage?.id, tax: findFirstPackage?.Product?.Tax, packageData: { count: Number.parseInt(findFirstPackage.session_count), categoryId: // @ts-expect-error unchecked index migration findFirstPackage.ServiceProduct?.CompanyService[0].group_id, masterCategoryId: findFirstPackage?.ServiceProduct?.InvCategory?.MasterCategory?.id, serviceId: findFirstPackage?.ServiceProduct?.CompanyService[0]?.id, }, }, ]) setSelectedData(actionTypes.SET_CATEGORY_ID, undefined) setSelectedData(actionTypes.SET_COST_TIERS, []) setSelectedData(actionTypes.SET_SELECTED_SERVICES, []) setSelectedData(actionTypes.SET_TOTAL_COST, 0) setSelectedData( actionTypes.setTotalPrepaidItemsCost, findFirstPackage.price ) nextStep() }, }) const { data: { findFirstBookitProGeneral } = {}, } = useBookitProGeneralEmailQuery({ onError: (error) => { console.error(error) }, }) const [sendEmail] = useSendEmailToUnauthenticatedContactMutation({ onCompleted({ sendEmailToUnauthenticatedContact }) { if (sendEmailToUnauthenticatedContact?.success) { Notification( NotificationType.success, t('notifications.connect.email.send.successMessage') ) } else { Notification( NotificationType.error, t('notifications.connect.email.send.failedMessage') ) } }, onError() { Notification( NotificationType.error, t('notifications.connect.email.send.failedMessage') ) }, }) const [ runSmsVerification, { error: verificationStatus, loading: verificationLoading }, ] = useVerifySmsLazyQuery() const verifySmsCode = () => { void runSmsVerification({ variables: { // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. contactId: cmContactData?.findFirstCmContact.ID, passcode: smsPasscodeInput, companyId: companyDetails?.id, }, onCompleted(data) { if (data.VerifySms?.success) { void createAccountAndSendNotification(selectedData.members) } }, }) } const onConfirmBookingDetails = async (members: BookingData['members']) => { if (!members?.[0]) { return } const { last_name, email, mobile } = members[0] if (!last_name) { return } const { data } = await fetchMatchedContact({ variables: { companyId: companyDetails?.id, lastName: last_name, email, mobile, }, }) if (data?.findFirstCmContact) { return } await createAccountAndSendNotification(members) } const createAccountAndSendNotification = async ( members: BookingData['members'] ) => { if (!members?.[0]) { return } const createdAccount = await createAccount({ variables: { input: { accountData: { ...members[0], location_id: typeof location === 'string' ? Number.parseInt(location) : 0, }, }, }, }) if ( createdAccount.data?.AccountRegistration?.CreatedAccount.status === 409 ) { return Notification( NotificationType.error, createdAccount.data?.AccountRegistration?.CreatedAccount .responseMessage ?? '' ) } if (!createdAccount.data?.AccountRegistration?.CreatedAccount) { return } const { status, jwtToken, id } = createdAccount.data.AccountRegistration.CreatedAccount setSelectedData(actionTypes.SET_MEMBERS, [ { ...members[0], contact_id: id }, ]) if (!jwtToken) { return } if (status === 409) { console.log('error 409') return } if (status === 200) { await connectLogin(jwtToken) } const { data: notificationMessageData } = await getNotificationMessage() if (connectRegistrationNotification) { await insertNotificationsOneMutation({ variables: { destination: companyDetails?.id.toString(), template: 'connect_registration', variables: { client_name: members[0].first_name, client_lastname: members[0].last_name, }, sentBy: id, }, }) } await CreateOneContactMeta({ variables: { data: { Contact: { connect: { ID: id, }, }, meta_name: 'connect_account_confirmed', meta_value: '0', }, }, }) await sendAppointmentConfirmation({ variables: { to: createdAccount?.data?.AccountRegistration?.CreatedAccount?.email, subject: t('connect.account.emailVerification'), html: notificationMessageData?.default.at(0)?.email_template ?? '', text: notificationMessageData?.default.at(0)?.email_template ?? '', contactId: createdAccount?.data?.AccountRegistration?.CreatedAccount?.id, replyTo: { email: '', name: '', }, fromConnect: true, }, }) } const queryVariables = { where: { payment_protection: { equals: 1 }, is_active: { equals: 1 }, }, } const { data: { findFirstCancellationPolicy } = {}, } = useCancellationPolicyQuery({ variables: queryVariables, }) const handleSmsPasscodeInput = ( event: ChangeEvent<HTMLInputElement> ): void => { setSmsPasscodeInput(event.target.value) } const sendSmsToUnauthenticatedContact = () => { if (!cmContactData?.findFirstCmContact) return return sendSms({ variables: { to: cmContactData?.findFirstCmContact.Mobile, from: 'Pabau', companyId: companyDetails?.id, contactId: cmContactData?.findFirstCmContact.ID, }, fetchPolicy: 'no-cache', }) } const sendEmailToUnauthenticatedContact = async () => { if (!cmContactData?.findFirstCmContact) return await sendEmail({ variables: { contactId: cmContactData.findFirstCmContact.ID, }, fetchPolicy: 'no-cache', }) } const { canLocationPerformService } = useLocationPermissions() const [connectRegistrationNotification, setConnectRegistrationNotification] = useState(false) const [allCompanyStaff, setAllCompanyStaff] = useState([]) if (!selectedData?.categoryID && category) { setSelectedData(actionTypes.SET_CATEGORY_ID, Number(category)) } if ( urlDate && !selectedData?.dateTime && isValidDateStringInFormat(String(urlDate), 'YYYY-MM-DDTHH:mm:ssZ') ) { const urlFullDate = dayjs(String(urlDate), 'YYYY-MM-DDTHH:mm:ss') if (!urlFullDate?.isBefore(dayjs())) { setSelectedData(actionTypes.SET_DATETIME, urlFullDate) } } const handleSmsVerificationModal = () => { openSmsVerificationModal() } const [insertNotificationsOneMutation] = useInsertNotificationBookingMutation( { onCompleted({ insert_notifications }) { // @ts-expect-error TS(2339) FIXME: Property 'template' does not exist on type const { id, template } = insert_notifications?.returning[0] if (template === 'connect_registration') { for (const staff of allCompanyStaff) { insertNotificationState({ variables: { companyId: connectUser?.company, // @ts-expect-error TS(2339) FIXME: Property 'pabau_id' does not exist on type 'never'. userId: staff.pabau_id, notificationId: id, createdAt: dayjs().utc(), }, }) } } else { insertNotificationState({ variables: { companyId: connectUser?.company, userId: selectedData?.employee?.User?.id, notificationId: id, createdAt: dayjs().utc(), }, }) } }, } ) const [insertNotificationState] = useCreateNotificationStateMutation() const handleStepUrl = (route: { [key: string]: string }) => { for (const [key, value] of Object.entries(route)) { router.query[key] = value } router.push(router) } useNotificationsTypesByTypeQuery({ variables: { type: 'connect_registration' as Notification_Types_Enum_Enum, }, onCompleted({ notification_types }) { setConnectRegistrationNotification( // @ts-expect-error unchecked index migration notification_types[0]?.notification_toggles[0]?.enabled ) }, }) const mainBodyRef = useRef<HTMLDivElement>(null) const { iconUp } = useConnectScrollButton(mainBodyRef, desktop, laptop) const [isEmployeePicked, setIsEmployeePicked] = useState(false) const handleEmployeePicked = (value: boolean) => { setIsEmployeePicked(value) } const handleArrowClick = (): void => { mainBodyRef.current?.scroll({ top: iconUp ? 0 : mainBodyRef.current?.scrollHeight, behavior: 'smooth', }) } useEffect(() => { if (connectUser?.contact_id && bookingIdUrl) { setStep(paymentStep) return } if (prepaidType) { // @ts-expect-error TODO CONNECT BIG FIX setSelectedData(actionTypes.setPrepaidType, prepaidType) onSelected(paymentStep) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { if (currentStep === confirmStep) { mainBodyRef.current?.scrollTo(0, 0) const { items, type, stage, pathway, appointmentId } = getPrepaidTypeConfig(prepaidType, selectedData) const ecommerceData = createEcommerceData( items, companyDetails.details.currency, type ) isGTMEventsActive ? sendGoogleTagEvent( { event: 'purchase', stage: stage, pathway: pathway, clientId: connectUser?.contact_id?.toString() ?? '', ...(appointmentId && { appointmentId: bookingIdUrl?.toString() }), }, undefined, undefined, ecommerceData ) : tagBookingFormStep( currentStep, connectUser?.contact_id?.toString() ?? '', selectedData.services, selectedData.location?.name, bookingIdUrl?.toString() ) } else { !isGTMEventsActive && tagBookingFormStep( currentStep, connectUser?.contact_id?.toString() ?? '', selectedData.services, selectedData.location?.name ) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentStep]) // to be investigated, causing refresh issue // useEffect(() => { // const totalQueryParams = Object.keys(router.query).length // if (totalQueryParams === 1 && 'company_slug' in router.query) { // setCurrentStep(masterCategoryStep) // } // }, [currentStep, router.query, setCurrentStep]) const nextStep = () => { !isGTMEventsActive && sendGoogleTagEvent( { event: 'stepInteraction', formStep: 'service', stepAction: `next button`, }, selectedData.services ) handleStepUrl( stepUrlData({ services: '', categoryId: 0, canLocationPerformService, isPrepaidItem: Boolean(prepaidType), bookableLocations: selectedData.bookableLocations, location: String(selectedData?.bookableLocations?.[0]?.id.toString()), }) ) if (prepaidType && connectUser?.id) { onSelected(paymentStep) return } if (!connectUser?.id) { handeLastStepFlag() onSelected(bookingStep) return } onSelected() } const googleTagId = companyDetails?.CompanyMeta.find( ({ meta_name }) => meta_name === 'google_tag_manager_id' )?.meta_value ?? '' const googleAnalyticsId = companyDetails?.CompanyMeta.find( ({ meta_name }) => meta_name === 'google_analytics_id' )?.meta_value ?? '' const metaPixelId = companyDetails?.CompanyMeta.find( ({ meta_name }) => meta_name === 'meta_pixel_id' )?.meta_value ?? '' const metaPixelEvent = companyDetails?.CompanyMeta.find( ({ meta_name }) => meta_name === 'meta_pixel_event' )?.meta_value ?? '' const [getFavoriteLocation, { data: favoriteLocation }] = useMostVisitedBookingLocationLazyQuery() const [getNotificationMessage] = useNotificationSettingLazyQuery({ variables: { type: 'verified-email', companyID: 0, }, }) const { data: requestFeedbackNotifications } = useCompanyActiveNotificationSettingsQuery({ skip: !companyDetails?.id || urlDate === '' || currentStep < bookingStep, variables: { type: 'request-feedback', }, }) const [createAccount] = useAccountRegistrationMutation() const { data: { findFirstPaymentProtectionStripe } = {}, } = useContactPaymentMethodIdQuery({ skip: !connectUser?.contact_id, ...(connectUser?.contact_id !== undefined && { variables: { contactId: connectUser.contact_id, }, }), }) const [bookingData, setBookingData] = useState<BookingInfo[]>( bookingIdUrl === null || bookingIdUrl === undefined || bookingIdUrl === '' ? [] : [{ id: Number.parseInt(String(bookingIdUrl)) }] ) const [contactId, setContactId] = useState<number>() const { data: { listSavedCards = [] } = {}, } = useListSavedCardsQuery({ skip: !connectUser?.contact_id, variables: { type: PaymentType.Stripe, contactId: connectUser?.contact_id ?? 0, }, }) const isActiveCancellationPolicy = Boolean( findFirstCancellationPolicy?.is_active && selectedData.services.length > 0 && isFlagActive('Cancellation Policy') && areServicesEligibleForCancellationPolicy( selectedData.services, depositPolicy ) && (listSavedCards.length === 0 || (!listSavedCards.some((card) => card?.is_expired === false) && (!findFirstPaymentProtectionStripe?.paymentMethodId || findFirstPaymentProtectionStripe?.paymentMethodId === ''))) ) const handleError = (error: Error) => { const errorHandlers = { 'User is suspended': () => { Notification( NotificationType.error, t('connect.onlinebooking.user.suspended.message') ) }, 'Package not found': () => { Notification( NotificationType.error, t('connect.onlinebooking.user.suspended.message') ) }, 'Package Service conflict': () => { Notification( NotificationType.error, t('connect.onlinebooking.user.suspended.message') ) }, 'Package is expired': () => { Notification( NotificationType.error, t('connect.onlinebooking.user.suspended.message') ) }, Location: redirectToLocationStep, Employee: redirectToEmployeeStep, Datetime: redirectToDateTimeStep, Shift: redirectToShiftStep, } for (const [key, handler] of Object.entries(errorHandlers)) { if (error.message.includes(key)) { handler() break } } } const { parseDate, parseTime } = useCompanyTimeDetailsParse() const sendNotification = useSendNotification() const [createBooking] = useCreateOnlineBookingMutation({ async onCompleted(data) { setContactId(contactId) setBookingData(data.public_createOnlineBooking) if ( shouldSkipPaymentScreen({ calculatedDeposit: calculateDeposit( selectedData, isFlagActive('Business detail tax setting'), companyDetails?.details?.is_tax_included, depositPolicy ), areAllServicesFree: areAllServicesFree(selectedData.services), depositAmount: depositPolicy?.amount, isActiveCancellationPolicy: Boolean(isActiveCancellationPolicy), hasStripeKey: Boolean(stripeKey), servicesPolicies: servicesHasPolicies(selectedData.services), flatDepositZero: selectedData.services.every( (service) => service.deposit_type === Company_Services_Deposit_Type.Amount && service.deposit_amount === 0 ), hasPackageClicked: Boolean(packageClickedId), }) && data.public_createOnlineBooking?.length > 0 ) { await skippedPaymentScreen(data.public_createOnlineBooking) if (medicalForms.length > 0) { setStep(patientInformation) } } else { handleStepUrl({ bookingId: String(data.public_createOnlineBooking?.[0]?.id), }) onSelected() } }, onError(error) { handleError(error) }, }) const createBookingAndNavigate = async () => { const loggedInContactId = selectedData.members?.at(0)?.contact_id ?? connectUser?.contact_id const skipPaymentStep = shouldSkipPaymentScreen({ calculatedDeposit: calculateDeposit( selectedData, isFlagActive('Business detail tax setting'), companyDetails?.details?.is_tax_included, depositPolicy ), areAllServicesFree: areAllServicesFree(selectedData.services), depositAmount: depositPolicy?.amount, isActiveCancellationPolicy: Boolean(isActiveCancellationPolicy), hasStripeKey: Boolean(stripeKey), servicesPolicies: servicesHasPolicies(selectedData.services), flatDepositZero: selectedData.services.every( (service) => service.deposit_type === Company_Services_Deposit_Type.Amount && service.deposit_amount === 0 ), hasPackageClicked: Boolean(packageClickedId), }) if ( !loggedInContactId || !selectedData?.employee?.User?.id || !selectedData.location?.id ) return await createBooking({ variables: { bookingId: bookingData[0]?.id ?? 0, companyId: companyDetails?.id, userId: selectedData?.employee?.User?.id, serviceIds: selectedData.services.map((service) => service.id), startDate: selectedData.dateTime?.utc(true).format(), endDate: selectedData.dateTime ?.utc(true) .add( totalDuration( selectedData?.services, selectedData?.employee?.User?.id ), 'minutes' ) .format(), locationId: selectedData.location?.id, comment: selectedData.comment, contactId: loggedInContactId, ...(withGoogle === 'true' && getCookie('_rwg_token') ? { rwgToken: getCookie('_rwg_token') } : {}), sendSurvey: requestFeedbackNotifications?.notification_settings.at(-1) ?.email_on || requestFeedbackNotifications?.notification_settings.at(-1)?.sms_on ? 1 : 0, skipPaymentStep, shiftId: Number.parseInt(shiftId?.toString() ?? '0'), createdBy: selectedData?.employee?.User?.id, ...(packageClickedId && { packageClickedId: Number.parseInt(packageClickedId.toString()), }), where: discover === 'true' ? 'discover' : withGoogle === 'true' ? 'google-reserve' : '', }, }) } useAllCompanyStaffQuery({ variables: { companyId: companyDetails?.id, }, onCompleted({ findFirstCompany }) { // @ts-expect-error TS(2345) FIXME: Type 'undefined' is not assignable to type 'SetStateAction<never[]>'. setAllCompanyStaff(findFirstCompany?.CmStaffGeneral) }, }) const [geoCodeAddress] = useGeocodeAddressLazyQuery() const handleLocations = async ( companyBranches: OnlineBookableLocationsQuery['findManyCompanyBranch'] ) => pMap( companyBranches, async (item) => { const { address, city, region, county } = item const fullAddress = `${address ?? ''} ${city ?? ''} ${ region ?? '' } ${county}` const { data } = await geoCodeAddress({ variables: { address: fullAddress, }, }) return { ...item, lat: data?.getAddressGeoData?.latitude, lng: data?.getAddressGeoData?.longitude, } }, { concurrency: 2 } ) const { data: { findManyCompanyBranchCount: locationsCount = 0 } = {}, } = useOnlineBookableLocationsCountQuery() const { value: showButton, setFalse: setFalseShowButton } = useBoolean(true) const { value: isLoadingLocations, setTrue: startLoadingLocations, setFalse: stopLoadingLocations, } = useBoolean(true) const [sendAppointmentConfirmation] = useSendEmailWithTagsMutation() const [sendEmailSmsConfirmation] = useSendAppointmentConfirmationMailMutation() const [getAppointmentNotification] = useNotificationSettingLazyQuery({ variables: { type: prepareTemplateTypeWithLanguage( 'new-appointment', companyDetails?.details?.language ?? 'english' ), companyID: companyDetails?.id, }, }) const [locationsLimit, setLocationsLimit] = useState<number>(50) const { value: hideFindNearestButton, setValue: setHideFindNearestButton } = useBoolean(false) const getQueryVariables = useMemo( () => ({ limit: locationsLimit, nameFilter: { contains: debounceTypedKey, }, }), [locationsLimit, debounceTypedKey] ) const handleShowMore = () => { setFalseShowButton() if (locationsLimit < locationsCount && !showButton) { setLocationsLimit((prevState) => prevState + 50) } } useOnlineBookableLocationsQuery({ variables: { ...getQueryVariables, }, async onCompleted({ findManyCompanyBranch }) { const updatedLocations = await handleLocations(findManyCompanyBranch) if (!equals(updatedLocations, selectedData.bookableLocations)) { const geoAddress = updatedLocations.every( (item) => item.lat === 0 && item.lng === 0 ) setHideFindNearestButton(geoAddress) // @ts-expect-error TODO CONNECT BIG FIX setSelectedData(actionTypes.SET_BOOKABLE_LOCATIONS, updatedLocations) } stopLoadingLocations() if (!selectedData?.location) { const urlLocation = findManyCompanyBranch?.find( ({ id }) => id === Number(location) ) if ( !urlLocation && locationsLimit !== 0 && currentStep > locationStep ) { setLocationsLimit(0) startLoadingLocations() } // @ts-expect-error TODO CONNECT BIG FIX if (urlLocation) setSelectedData(actionTypes.SET_LOCATION, urlLocation) } if ( findManyCompanyBranch.filter((location) => canLocationPerformService(location.id) ).length === 1 ) { changeTotalSteps() setSkipLocationStep() } if (connectUser && onlineBookingSettings?.most_visited_location) { getFavoriteLocation({ variables: { contactId: connectUser.contact_id ?? 0, }, }) } }, }) useSelectedStaffUserQuery({ variables: { companyId: companyDetails?.id, employeeId: Number(staff), }, skip: !!selectedData?.employee || !staff || currentStep < employeeStep, onCompleted({ findFirstCmStaffGeneral }) { if (!findFirstCmStaffGeneral || selectedData?.employee) return // @ts-expect-error TODO CONNECT BIG FIX setSelectedData(actionTypes.SET_EMPLOYEE, findFirstCmStaffGeneral) if ( findFirstCmStaffGeneral.User?.id && selectedData?.services?.length > 0 ) { const staffCostTier = calculateStaffCostTiers( findFirstCmStaffGeneral.User.id, selectedData?.services, selectedData?.costTiers ) setSelectedData(actionTypes.SET_COST_TIERS, staffCostTier) setSelectedData( actionTypes.SET_TOTAL_COST, (selectedData.totalPrepaidItemsCost ?? 0) + calculateTotalCostWithTiers(staffCostTier) ) } }, }) useServicesByIdsQuery({ variables: { companyId: companyDetails?.id, serviceIds: String(services)?.split(',')?.map(Number), }, skip: !services || !!selectedData?.services?.length || currentStep < locationStep, onCompleted({ findManyCompanyService }) { setSelectedData( actionTypes.SET_SELECTED_SERVICES, // @ts-expect-error TODO CONNECT BIG FIX findManyCompanyService.map((item) => ({ ...item, deposit_amount: packageClickedId ? 0 : item.deposit_amount, Product: { ...item.Product, image: serviceImage( item.Product?.image, item.category?.image, item.category?.masterCategory?.image ), }, })) ) setSelectedData(actionTypes.SET_COST_TIERS, [ ...(selectedData.costTiers ?? []), ...(findManyCompanyService?.map( ({ id, price: servicePrice, locationPrices: locations, staffPrices: staffs, }) => { const locationPrice = locations?.find( ({ location_id }) => Number.parseInt(String(location)) === location_id )?.price ?? 0 const staffPrice = staffs?.find( ({ user_id }) => selectedData?.employee?.User?.id === user_id )?.price ?? 0 return { service_id: id, regular_price: servicePrice, location_price: locationPrice, staff_price: staffPrice, } } ) ?? []), ]) setSelectedData( actionTypes.SET_TOTAL_COST, (selectedData?.totalCost ?? 0) + findManyCompanyService?.reduce( (final, { price = 0, staffPrices, locationPrices }) => { let nextPrice = price if (locationPrices.length > 0 && location) { const locationPrice = locationPrices.find( ({ location_id }) => location_id === Number.parseFloat(String(location)) )?.price ?? 0 if (locationPrice && locationPrice > 0) nextPrice = locationPrice } if (staffPrices.length > 0 && staff) { const staffPrice = staffPrices.find( ({ user_id }) => user_id === selectedData.employee?.User.id )?.price ?? 0 if (staffPrice && staffPrice > 0) nextPrice = staffPrice } return final + nextPrice }, 0 ) ) }, }) useConnectDepositPolicyQuery({ onCompleted(data) { const depositPolicyData = getDepositPolicyFromType( data?.deposit_policy[0]?.policy_type, data?.deposit_policy[0]?.custom_rate ) if (!equals(depositPolicyData, depositPolicy)) { setDepositPolicy(data) } }, }) const params = new URLSearchParams(router.query as unknown as URLSearchParams) let currentStepUrl = '' const removeQueryParamOnBackClick = async (queryParam: string) => { const { pathname, query = {} } = router const params = new URLSearchParams(query as unknown as URLSearchParams) const lastStep = params.get('lastStep') const category = params.get('category') params.delete('hidePrepaid') params.delete('viewAll') params.delete('subCategory') params.delete('cursorCategory') params.delete('prepaidFlow') params.delete('waitList') if (queryParam === 'prepaidFlow' && !query?.voucherDetails?.toString()) { params.delete('prepaidType') } if (query?.voucherDetails?.toString()) { params.delete('voucherDetails') } if (queryParam === 'category') { params.delete('category') params.delete('groupCategory') params.delete('hideButton') params.delete('cursorService') params.delete('cursorMasterCategory') params.delete('hideCategoryButton') params.delete('hideMasterCategoryButton') } if (queryParam === 'staff' && selectedData?.staff?.length === 1) { params.delete('date') params.delete('location') } if (queryParam === 'staff') params.delete('date') if ( queryParam === 'staff' && onlineBookingSettings?.skip_employee_step === true ) params.delete('location') if (queryParam === 'service') { params.delete('location') if (onlineBookingSettings?.enable_quick_service) { params.delete('services') } } params.delete(queryParam) if (lastStep === '6') { params.delete('lastStep') if (category) { params.delete('prepaidType') params.delete('courseUpsell') } await router.replace({ pathname, query: params.toString() }) } if (Object.entries(query).length > 1) { await router.replace({ pathname, query: params.toString() }) } } const getServiceAvailableLocations = useMemo(() => { const availableLocations = selectedData.bookableLocations.map( (location) => location.id ) const servicesDisabledLocation = selectedData.services.reduce<number[]>( (accumulator, service) => { const locations = service.disabled_locations?.split(',').map(Number) if (!locations) { return accumulator } return [...new Set([...accumulator, ...locations])] }, [] ) return availableLocations.filter( (locationId) => !servicesDisabledLocation.includes(locationId) ) }, [selectedData.bookableLocations, selectedData.services]) const arrowBack = async () => { const waitList = params.get('waitList') if (waitList) { setCurrentStep(dateTimeStep) await removeQueryParamOnBackClick(currentStepUrl) return } await removeQueryParamOnBackClick(currentStepUrl) const serviceEnabledLocation = getServiceAvailableLocations const courseUpsell = params.get('courseUpsell') if (courseUpsell) { setCurrentStep(servicesStep) return } if ( selectedData.services.length === 0 && selectedData.prepaidItems.length > 0 && (currentStep === bookingStep || currentStep === paymentStep) ) { setSelectedData(actionTypes.setPrepaidItems, []) setSelectedData(actionTypes.setTotalPrepaidItemsCost, 0) // @ts-expect-error TODO CONNECT BIG FIX setSelectedData(actionTypes.setPrepaidType, undefined) setCurrentStep(categoryStep) return } if (currentStep === dateTimeStep) { setSelectedData(actionTypes.SET_DATETIME, dayjs()) } const skipMedicalForm = medicalForms.length === 0 || !onlineBookingSettings?.require_form goToPrevStep( currentStep, skipLocationStep || serviceEnabledLocation.length === 1, onlineBookingSettings?.skip_employee_step || selectedData.staff?.length === 1, skipMedicalForm, isEmployeePicked ) if (categorySelect) { setOnBackClickTrue() setCategorySelectFalse() } if (selectedData.prepaidType) { // @ts-expect-error TODO CONNECT BIG FIX setSelectedData(actionTypes.setPrepaidType, null) } if ( (currentStep === locationStep || (currentStep === employeeStep && (skipLocationStep || serviceEnabledLocation.length === 1)) || (currentStep === dateTimeStep && (onlineBookingSettings?.skip_employee_step || selectedData.staff?.length === 1))) && onlineBookingSettings?.enable_quick_service ) { setSelectedData(actionTypes.SET_SELECTED_SERVICES, []) setSelectedData(actionTypes.SET_COST_TIERS, []) setSelectedData(actionTypes.SET_TOTAL_COST, 0) } if (currentStep === confirmStep && prepaidType === 'voucher') { router.reload() } } const goBackButton = () => { switch (currentStep) { case categoryStep: { currentStepUrl = 'groupCategory' break } case servicesStep: { currentStepUrl = 'category' break } case locationStep: { currentStepUrl = 'service' break } case employeeStep: { const serviceEnabledLocation = getServiceAvailableLocations currentStepUrl = skipLocationStep || serviceEnabledLocation.length === 1 ? 'service' : 'location' break } case dateTimeStep: { sessionStorage.removeItem('timeLeft') //this is used to clear the session for the countdown timer at PaymentAppointment.tsx currentStepUrl = 'staff' break } case bookingStep: { currentStepUrl = 'shiftId' break } case patientInformation: { break } case paymentStep: { currentStepUrl = 'bookingId' break } case confirmStep: { currentStepUrl = 'bookingId' break } case packageStep: case accountStep: case voucherStep: { currentStepUrl = 'prepaidFlow' break } // No default } if (prepaidType && currentStep !== confirmStep) { const hasUrlVoucherDetails = Boolean(voucherDetails?.toString()) return ( <BackButton onClick={() => { if (!hasUrlVoucherDetails) { resetSelectedData() } arrowBack() }} text={ hasUrlVoucherDetails ? t('gift.voucher.purchase.details.back.to.choose.gift') : '' } /> ) } return ( (currentStep > masterCategoryStep || categorySelect) && currentStep < totalSteps && !existingClientMatched && currentStep !== confirmStep && !( currentStep === categoryStep && Object.keys(masterCategories ?? {}).length < 2 ) && !tablet && !rescheduleBookingId && <BackButton onClick={arrowBack} /> ) } const { data: { findUniqueUser: staffUser } = {}, } = useUserWithAlertPermissionQuery({ variables: { userId: selectedData?.employee?.User?.id ?? 0, alertTitle: 'Appointment Booked', }, skip: !selectedData?.employee?.User?.id, }) const { data: appointmentUserNotification } = useNotificationSettingTemplateQuery({ variables: { type: prepareTemplateTypeWithLanguage( 'new-appointment-user', staffUser?.locale ?? 'english' ), }, }) const { data: onlineBookingNotifications } = useNotificationSettingTemplateQuery({ variables: { type: prepareTemplateTypeWithLanguage( 'new-online-booking', language ?? 'english' ), }, }) const sendUserWebNotification = ( bookingData: { id?: number; start_date?: number }[] = [] ) => { const contactId = connectUser?.contact_id ?? selectedData?.members?.[0]?.contact_id ?? 0 const userId = selectedData?.employee?.User?.id ?? 0 const who = connectUser?.contact_id ? concatenateTruthyValues([connectUser?.fname, connectUser?.lname]) : concatenateTruthyValues([ // @ts-expect-error unchecked index migration selectedData?.members?.[0].first_name, // @ts-expect-error unchecked index migration selectedData?.members?.[0].last_name, ]) sendWebNotifications( bookingData, selectedData?.services, Notification_Types_Enum_Enum.NewAppointmentViaPabau, Notification_Types_Enum_Enum.NewAppointmentViaCalendar, who, companyDetails?.id, userId, `/clients/${contactId}/dashboard`, sendNotification, parseDate, parseTime ) } const sendBookingNotification = (bookingId: { id: number }[]) => { const email = findFirstBookitProGeneral?.booking_emails if (!email) return const emailTemplate = onlineBookingNotifications?.notification_settings[0]?.email_template ?? '' void sendAppointmentConfirmation({ variables: { to: email, subject: t('connect.subject.email'), html: emailTemplate, text: emailTemplate, contactId: connectUser?.contact_id, bookingId: bookingId[0]?.id, staffId: selectedData.employee?.User?.id, fromConnect: true, isForStaff: true, }, }) } const skippedPaymentScreen = async ( bookingIds: { id: number; start_date?: number }[] ) => { const { data: appointmentNotification } = await getAppointmentNotification() skipPaymentStepTrue() void createOneBuzzFeed({ variables: { data: buzzFeedVariables( selectedData, companyDetails, connectUser, onlineBookingSettings ), }, }) handleStepUrl({ bookingIdAfterPayment: String( bookingIds.map((booking) => booking.id ?? 0)[0] ), }) if (precareTemplates !== undefined) { void sendTemplate({ variables: { bookingId: bookingIds.at(0)?.id ?? 0, templateIds: precareTemplates?.map((template) => template.template_id ?? 0) ?? [], }, }) } if ( appointmentNotification?.notification_settings[0]?.email_on ?? appointmentNotification?.default[0]?.email_on ) { const isVirtualService = selectedData.services?.[0]?.online_only_service === 1 void sendEmailSmsConfirmation({ variables: { bookingId: bookingIds.at(0)?.id ?? 0, isVirtualService: isVirtualService, saveCommunication: true, sendSms: appointmentNotification?.notification_settings[0]?.sms_on ?? false, }, }) } if (staffUser?.UserAlertPermission?.at(0)?.email_notification === 1) { const notificationUserEmailMessage = appointmentUserNotification?.notification_settings?.at(0) ?.email_template || appointmentUserNotification?.default?.at(0)?.email_template || '' void sendAppointmentConfirmation({ variables: { to: staffUser?.email, subject: t('connect.booking.created.success.email'), html: notificationUserEmailMessage, text: notificationUserEmailMessage, contactId: connectUser?.contact_id, bookingId: bookingIds.at(0)?.id, staffId: selectedData.employee?.User?.id, relatedType: 'APPOINTMENT', fromConnect: true, isForStaff: true, }, }) } sendUserWebNotification(bookingIds) if (bookingIds[0]?.id) sendBookingNotification([{ id: bookingIds[0].id }]) if (medicalForms.length === 0) void redirectPageAfterBooking( router, onlineBookingSettings?.skip_timely_booking, onlineBookingSettings?.page_url ) } const onSelected = (step?: number) => { if (step) { return setStep(step, true) //overriding maxStep control if we want to go to specific page } if (currentStep === patientInformation) { setStep(skipPaymentStep ? paymentStep + 1 : paymentStep) return } if (currentStep === bookingStep) { if (medicalForms.length > 0) { setStep(patientInformation) return } return setStep(skipPaymentStep ? paymentStep + 1 : paymentStep) } if ( currentStep === servicesStep && getServiceAvailableLocations.length === 1 ) { const getAvailableLocation = selectedData.bookableLocations.find( (item) => item.id === getServiceAvailableLocations[0] ) setSelectedData(actionTypes.SET_LOCATION, getAvailableLocation) setStep(locationStep) } if ( (currentStep === servicesStep && onlineBookingSettings?.skip_employee_step && skipLocationStep) || currentStep === employeeStep ) { setStep(dateTimeStep) return } goToNextStep() } if ( currentStep === paymentStep && (!stripeKey || stripeKey.trim() === '') && selectedData.totalCost >= 0 ) { setStep(confirmStep) return } const isBlocked = false return ( <> <CustomHead businessName={companyDetails?.slug} link={router.basePath} /> <GoogleAnalytics googleAnalyticsId={googleAnalyticsId} googleTagId={googleTagId} metaPixelId={metaPixelId} metaPixelEvent={metaPixelEvent} /> <div className={styles.onlineBooking}> <Header currentStep={ currentStep > masterCategoryStep ? currentStep : servicesStep } back={arrowBack} visible={ (currentStep > masterCategoryStep || !!categorySelect) && currentStep < totalSteps && !existingClientMatched && currentStep !== confirmStep && !( currentStep === categoryStep && Object.keys( masterCategories?.findManyServicesMasterCategory ?? {} ).length < 2 ) && !tablet && !rescheduleBookingId } confirmStep={confirmStep} /> <div ref={mainBodyRef} className={classNames(styles.mainBody, { // @ts-expect-error unchecked index migration [styles.noHeader]: currentStep === confirmStep, })} data-cy="mainbody" aria-label="Main Body" style={{ backgroundColor: onlineBookingSettings?.brand_color === 'white' || onlineBookingSettings?.brand_color === '#ffffff' ? '#f7f7f9' : (onlineBookingSettings?.brand_color ?? '#f7f7f9'), }} > {/* {isBlocked ? ( <div> */} {goBackButton()} {[ masterCategoryStep, categoryStep, servicesStep, employeeStep, dateTimeStep, ].includes(currentStep) && withGoogle !== undefined && String(withGoogle) === 'true' && <GoogleHeader />} <div className={classNames({ // @ts-expect-error unchecked index migration [styles.slide]: !mobile, // @ts-expect-error unchecked index migration [styles.slideMobile]: mobile, })} aria-label="Steps Navigator" > {currentStep < 6 && mainBodyRef.current?.clientHeight !== mainBodyRef.current?.scrollHeight ? ( <DownUpArrowIcon onClick={handleArrowClick} className={classNames(styles.fixedDynamicArrow, { [styles.iconUp ?? '']: iconUp || ((selectedData.services.length > 0 || selectedData.vouchers.length > 0 || selectedData.prepaidItems.length > 0) && currentStep < locationStep), })} rotate={iconUp ? 0 : 180} aria-label="Scroll Arrow" /> ) : null} <StepNavigator currentStep={currentStep} onSelected={onSelected} onHandleStepUrl={handleStepUrl} /> {currentStep === locationStep && ( <div className={styles.slide2} data-cy="Location Step"> <StepNavigatorHeader /> <LocationSelector onSelected={onSelected} favoriteLocation={favoriteLocation} handleStepUrl={handleStepUrl} loadingLocations={isLoadingLocations} handleShowMore={handleShowMore} showButton={showButton} hideFindNearestButton={hideFindNearestButton} searchString={locationSearchString} setSearchString={setLocationSearchString} /> </div> )} {currentStep === employeeStep && ( <div className={styles.slide3} data-cy="employee step"> <StepNavigatorHeader /> <EmployeeSelector onSelected={onSelected} handleStepUrl={handleStepUrl} loading={isLoadingLocations} isEmployeePicked={handleEmployeePicked} /> </div> )} {currentStep === dateTimeStep && ( <div className={styles.slide4} data-cy="date time step"> {isFlagActive('Connect - Date and time step revamp') ? ( <RevampDateTimeSelector onSelected={onSelected} handleStepUrl={handleStepUrl} setStep={setStep} minimumAdvance={onlineBookingSettings?.minimum_advance ?? 0} /> ) : ( <DateTimeSelector onSelected={onSelected} handleStepUrl={handleStepUrl} setStep={setStep} minimumAdvance={onlineBookingSettings?.minimum_advance ?? 0} /> )} </div> )} {currentStep === bookingStep && !smsVerificationModal && ( <div className={styles.slide5} data-cy="booking step"> {existingClientMatched ? ( <SuccessfulLottie name={`${cmContactData?.findFirstCmContact?.Fname} ${cmContactData?.findFirstCmContact?.Lname}`} email={cmContactData?.findFirstCmContact?.Email} mobilePhone={cmContactData?.findFirstCmContact?.Mobile} // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion dob={formatDateFromISO( dayjs(cmContactData?.findFirstCmContact?.DOB).unix(), companyDetails?.details?.date_format, // eslint-disable-next-line @typescript-eslint/no-explicit-any undefined as any, language, 'sms-code' )!.toString()} onSendSmsToUnauthenticatedContact={ sendSmsToUnauthenticatedContact } onSendEmailToUnauthenticatedContact={ sendEmailToUnauthenticatedContact } onOpenSmsVerificationModal={handleSmsVerificationModal} /> ) : ( <BookingDetails data-cy="booking details" onConfirmed={onConfirmBookingDetails} onSelected={onSelected} backToStep={setStep} stripeKey={stripeKey ?? ''} createBookingAndNavigate={createBookingAndNavigate} isActiveCancellationPolicy={isActiveCancellationPolicy} isFetchingMedicalForms={isFetchingMedicalForms} /> )} </div> )} {currentStep === patientInformation && ( <PatientInfo data-cy="patient info" onSelected={onSelected} onlineBookingSettings={onlineBookingSettings} skipPaymentStep={skipPaymentStep} patientId={connectUser?.contact_id} medicalForms={medicalForms} /> )} {currentStep === bookingStep && smsVerificationModal ? ( <SmsVerification handleSmsPasscodeInput={handleSmsPasscodeInput} smsPasscodeInput={smsPasscodeInput} verificationStatus={verificationStatus?.message} verifySmsCode={verifySmsCode} verificationLoading={verificationLoading} onSendSmsToUnauthenticatedContact={ sendSmsToUnauthenticatedContact } onSendEmailToUnauthenticatedContact={ sendEmailToUnauthenticatedContact } clinicLogo={createCdnLink(companyDetails?.details?.logo, false)} phoneNumber={cmContactData?.findFirstCmContact?.Mobile} clinicName={companyDetails?.details?.company_name} email={cmContactData?.findFirstCmContact?.Email} handleResendCodeToEmail={handleResendCodeToEmail} smsSend={smsSend} /> ) : null} {currentStep === paymentStep && ( <div className={classNames(styles.paymentWrapper, { // @ts-expect-error unchecked index migration [styles.prepaidPayment]: prepaidType && (laptop || desktop), })} data-cy="payment step" > <PaymentWrapper onSelected={onSelected} bookingId={bookingData} // eslint-disable-next-line @typescript-eslint/no-non-null-assertion contactId={contactId!} brandColor={onlineBookingSettings?.brand_color} isActiveCancellationPolicy={isActiveCancellationPolicy} hoursAhead={ ['8', '24', '48', '72'][ findFirstCancellationPolicy?.policy_notice ?? 1 ] } noShowFee={findFirstCancellationPolicy?.no_show_fee} advancedCancelFee={ findFirstCancellationPolicy?.advanced_cancellation_fee } /> </div> )} {Boolean(currentStep === confirmStep) && ( <BookingConfirmation online onSelected={onSelected} stripeKey={stripeKey} showPrices={onlineBookingSettings?.show_prices} currentStep={currentStep} bookingIds={bookingData} sendUserWebNotification={sendUserWebNotification} sendBookingNotification={sendBookingNotification} /> )} </div> {[servicesStep, packageStep, voucherStep, accountStep] .filter((step) => (isVoucherActive ? step !== voucherStep : step)) .includes(currentStep) && ( <SelectedItemsFooter onSelected={onSelected} onHandleStepUrl={handleStepUrl} /> )} <Footer /> </div> </div> </> ) } export default Index
Editor is loading...
Leave a Comment