Untitled
unknown
plain_text
8 months ago
65 kB
4
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