PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.8
Booking for Appointments and Events Calendar – Amelia v2.4.8
2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / views / public / StepForm / BookingStepForm.vue
ameliabooking / v3 / src / views / public / StepForm Last commit date
BringingAnyone 1 month ago CartStep 1 month ago Congratulations 1 month ago DateTimeStep 1 month ago EmployeeStep 1 month ago Extras 1 month ago InfoStep 1 month ago InitStep 1 day ago LocationStep 1 month ago PakagesStep 1 month ago PaymentStep 1 day ago RecurringStep 1 month ago ServiceStep 1 month ago _fields 1 day ago common 1 day ago BookingStepForm.vue 1 day ago
BookingStepForm.vue
1965 lines
1 <template>
2 <template v-if="!amFonts.customFontSelected">
3 <link rel="preconnect" href="https://fonts.googleapis.com" />
4 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
5 <link
6 rel="stylesheet"
7 type="text/css"
8 :href="`${baseUrls.wpAmeliaPluginURL}v3/src/assets/scss/common/fonts/font.css`"
9 media="all"
10 />
11 </template>
12 <div
13 v-if="!empty && formDialogVisibility"
14 id="amelia-container"
15 ref="ameliaContainer"
16 class="am-fs__wrapper"
17 :class="{ 'am-collapsed': sidebarCollapsed }"
18 :style="cssVars"
19 >
20 <div class="am-sr-only" role="status" aria-live="polite" aria-atomic="true">
21 {{ statusMessage }}
22 </div>
23 <div class="am-sr-only" role="alert" aria-live="assertive" aria-atomic="true">
24 {{ alertMessage }}
25 </div>
26 <SideBar
27 v-if="containerWidth > 560 && sidebarVisibility"
28 class="am-fs-sb"
29 :class="[{ 'am-collapsed': sidebarCollapsed }, { 'am-rtl': isRtl }]"
30 :style="{
31 width: !sidebarCollapsed ? '240px' : '72px',
32 paddingBottom: `${sidebarFooterHeight + 16}px`,
33 }"
34 >
35 <template #step-list>
36 <div class="am-fs-sb__step-wrapper" tabindex="0">
37 <template
38 v-if="
39 ready &&
40 (stepsArray[stepIndex] !== congratulationsStep ||
41 !amSettings.general.addToCalendar ||
42 (booked && booked.data.length === 0))
43 "
44 >
45 <div v-for="step in sidebarSteps" :key="step.key" class="am-fs-sb__step">
46 <div class="am-fs-sb__step-inner">
47 <div class="am-fs-sb__step-icon">
48 <span :class="`am-icon-${step.icon}`"></span>
49 <span
50 v-if="step.key === 'cartStep' && useCartHasItems(store) !== 0"
51 class="am-fs-sb__step-icon__number"
52 :class="[{ 'am-rtl': isRtl }, sidebarCollapseItemsClass]"
53 >
54 {{ useCartHasItems(store) }}
55 </span>
56 </div>
57 <transition name="fade">
58 <p
59 v-if="!sidebarCollapsed"
60 class="am-fs-sb__step-heading"
61 :class="sidebarCollapseItemsClass"
62 >
63 {{ step.label }}
64 </p>
65 </transition>
66 <div
67 class="am-fs-sb__step-checker"
68 :class="[
69 { 'am-fs-sb__step-checker-selected': step.selected },
70 { 'am-rtl': isRtl },
71 sidebarCollapseItemsClass,
72 ]"
73 >
74 <transition name="fade">
75 <span v-if="step.finished" class="am-icon-check"></span>
76 </transition>
77 </div>
78 </div>
79 <TransitionGroup
80 tag="span"
81 name="fade"
82 appear
83 class="am-fs-sb__step-selection__wrapper"
84 :class="{
85 'am-fs-sb__step-selection-packages':
86 step.key === packageAppointmentsStep.key && step.stepSelectedData.length > 3,
87 }"
88 >
89 <template
90 v-if="
91 !sidebarCollapsed && (useCart(store).length === 1 || step.key === 'cartStep')
92 "
93 >
94 <p
95 v-for="(itemSelected, index) in step.stepSelectedData"
96 :key="itemSelected.position"
97 class="am-fs-sb__step-selection"
98 >
99 <span v-if="step.key !== packageAppointmentsStep.key || index < 3">
100 {{ itemSelected.value }}
101 </span>
102 <span v-else>
103 {{ amLabels.plus_more }}
104 </span>
105 </p>
106 </template>
107 </TransitionGroup>
108 </div>
109 </template>
110 <template v-else-if="ready">
111 <div>
112 <transition name="fade">
113 <AddToCalendar v-if="!sidebarCollapsed"></AddToCalendar>
114 </transition>
115 <div v-if="sidebarCollapsed" class="am-fs-sb__step">
116 <div class="am-fs-sb__step-inner">
117 <div class="am-fs-sb__step-icon">
118 <span :class="`am-icon-${stepsArray[stepIndex].sidebarData.icon}`"></span>
119 </div>
120 <div
121 class="am-fs-sb__step-checker"
122 :class="[
123 {
124 'am-fs-sb__step-checker-selected':
125 stepsArray[stepIndex].sidebarData.selected,
126 },
127 sidebarCollapseItemsClass,
128 ]"
129 >
130 <transition name="fade">
131 <span
132 v-if="stepsArray[stepIndex].sidebarData.finished"
133 class="am-icon-check"
134 ></span>
135 </transition>
136 </div>
137 </div>
138 </div>
139 </div>
140 </template>
141 <template v-else>
142 <!-- Skeleton -->
143 <el-skeleton animated>
144 <template #template>
145 <div v-for="item in new Array(3)" :key="item">
146 <div class="el-skeleton-item-wrapper">
147 <el-skeleton-item variant="text" />
148 </div>
149 </div>
150 </template>
151 </el-skeleton>
152 <!-- /Skeleton -->
153 </template>
154 </div>
155 </template>
156 <template #support-info>
157 <div ref="sidebarFooterRef" class="am-fs-sb__footer">
158 <div
159 v-if="
160 (amSettings.company.email || amSettings.company.phone) &&
161 (footerCustomizeOptions.heading ||
162 footerCustomizeOptions.phone ||
163 footerCustomizeOptions.email)
164 "
165 class="am-fs-sb__support"
166 >
167 <transition name="fade">
168 <div
169 v-if="!sidebarCollapsed && footerCustomizeOptions.heading"
170 class="am-fs-sb__support-heading"
171 :class="sidebarCollapseItemsClass"
172 >
173 {{ amLabels.get_in_touch }}
174 </div>
175 </transition>
176 <a
177 v-if="amSettings.company.phone && footerCustomizeOptions.phone"
178 class="am-fs-sb__support-email"
179 :aria-label="`Company phone: ${amSettings.company.phone}`"
180 :href="`tel:${amSettings.company.phone}`"
181 >
182 <template v-if="!sidebarCollapsed">
183 {{ amSettings.company.phone }}
184 </template>
185 <template v-else>
186 <span class="am-icon-phone"></span>
187 </template>
188 </a>
189 <a
190 v-if="amSettings.company.email && footerCustomizeOptions.email"
191 class="am-fs-sb__support-email"
192 :aria-label="`Company email: ${amSettings.company.email}`"
193 :href="`mailto:${amSettings.company.email}`"
194 >
195 <template v-if="!sidebarCollapsed">
196 {{ amSettings.company.email }}
197 </template>
198 <template v-else>
199 <span class="am-icon-email"></span>
200 </template>
201 </a>
202 </div>
203 <div
204 class="am-fs-sb__menu"
205 tabindex="0"
206 :aria-label="amLabels.collapse_menu"
207 :class="sidebarCollapseItemsClass"
208 @click="sidebarCollapsed = !sidebarCollapsed"
209 @keydown.enter="sidebarCollapsed = !sidebarCollapsed"
210 @keydown.space.prevent="sidebarCollapsed = !sidebarCollapsed"
211 >
212 <Transition name="fade">
213 <span v-if="!sidebarCollapsed" class="am-fs-sb__menu-text">
214 {{ amLabels.collapse_menu }}
215 </span>
216 </Transition>
217 <span :class="`am-icon-arrow-circle-${sidebarCollapsed ? 'left' : 'right'}`"></span>
218 </div>
219 </div>
220 </template>
221 </SideBar>
222 <MainContent>
223 <template v-if="stepsArray[stepIndex] !== congratulationsStep" #header>
224 <MainContentHeader :sidebar-visible="sidebarVisibility" :ready="ready"></MainContentHeader>
225 </template>
226 <template #step>
227 <component :is="stepsArray[stepIndex]" global-class="am-fs__main-content"></component>
228 </template>
229 <template #footer>
230 <MainContentFooter
231 :second-button-show="
232 stepsArray[stepIndex] === congratulationsStep &&
233 !!amSettings.roles.customerCabinet.pageUrl
234 "
235 :add-to-cart-button-show="useCartStep(store) && stepsArray[stepIndex] === cartStep"
236 :back-to-cart-button-show="
237 useCartStep(store) &&
238 cart.length > 1 &&
239 stepsArray[stepIndex] !== cartStep &&
240 stepsArray[stepIndex] !== infoStep &&
241 stepsArray[stepIndex] !== paymentStep &&
242 stepsArray[stepIndex] !== congratulationsStep
243 "
244 :booked="booked"
245 :loading="loading"
246 :payment-gateway="paymentGateway"
247 :pay-pal-button-show="stepsArray[stepIndex] === paymentStep"
248 :customized-labels="footerLabels"
249 :primary-footer-button-type="primFooterBtnType"
250 :secondary-footer-button-type="secFooterBtnType"
251 :add-to-cart-button-type="addToCartBtnType"
252 :back-to-cart-button-type="backToCartBtnType"
253 :back-to-cart-label="dedicatedStepLabel('cancel', 'cartStep')"
254 :ready="ready"
255 @add-to-cart="addToCart"
256 @back-to-cart="backToCart"
257 ></MainContentFooter>
258 </template>
259 </MainContent>
260 </div>
261 <template v-else>
262 <div
263 v-if="shortcodeData.show !== 'packages' && !shortcodeData.package"
264 ref="ameliaContainer"
265 class="am-no-services"
266 :style="cssVars"
267 >
268 <img
269 :src="baseUrls.wpAmeliaPluginURL + '/v3/src/assets/img/am-empty-booking.svg'"
270 style="margin-top: 10px"
271 :alt="amLabels.no_services_employees"
272 />
273 <div class="am-no-services-oops">{{ amLabels.oops }}</div>
274 <div class="am-no-services-text">
275 {{ amLabels.no_services_employees }}
276 </div>
277 <div class="am-no-services-text-2">
278 <p>{{ amLabels.add_services_employees }}</p>
279 <a href="https://wpamelia.com/services-and-categories/" rel="nofollow">
280 {{ amLabels.add_services_url }}&nbsp;
281 </a>
282 <span style="font-size: 14px">{{ amLabels.and }}&nbsp;</span>
283 <a href="https://wpamelia.com/employees/" rel="nofollow">
284 {{ amLabels.add_employees_url }}
285 </a>
286 </div>
287 </div>
288 <div
289 v-else
290 ref="ameliaContainer"
291 class="am-no-services"
292 :style="{ height: '100%', ...cssVars }"
293 >
294 <img
295 :src="baseUrls.wpAmeliaPluginURL + '/v3/src/assets/img/am-empty-booking.svg'"
296 style="margin-top: 10px"
297 :alt="amLabels.no_package_services"
298 />
299 <div>{{ amLabels.oops }}</div>
300 <div>{{ amLabels.no_package_services }}</div>
301 <a href="https://wpamelia.com/services-and-categories/" rel="nofollow">
302 {{ amLabels.add_services_url }}&nbsp;
303 </a>
304 </div>
305 </template>
306 <BackLink />
307 </template>
308
309 <script setup>
310 // * Form construction
311 import MainContent from '../../common/SbsFormConstruction/MainContent/MainContent.vue'
312 import MainContentHeader from '../../common/SbsFormConstruction/MainContent/parts/MainContentHeader.vue'
313 import MainContentFooter from '../../common/SbsFormConstruction/MainContent/parts/MainContentFooter'
314 import SideBar from '../../common/SbsFormConstruction/SideBar/SideBar.vue'
315 import BackLink from '../Parts/BackLink'
316
317 // * Step components
318 import InitStep from './InitStep/InitStep.vue'
319 import BringingAnyone from './BringingAnyone/BringingAnyone'
320 import Extras from './Extras/Extras.vue'
321 import PackageInfo from './PakagesStep/PackageInfoStep'
322 import DateTimeStep from './DateTimeStep/DateTimeStep.vue'
323 import PackageAppointmentsStep from './PakagesStep/PackageAppointmentsStep.vue'
324 import PackageAppointmentsListStep from './PakagesStep/PackageAppointmentsListStep.vue'
325 import CartStep from './CartStep/CartStep.vue'
326 import InfoStep from './InfoStep/InfoStep.vue'
327 import Congratulations from './Congratulations/Congratulations.vue'
328 import AddToCalendar from './Congratulations/AddToCalendar.vue'
329 import PaymentStep from './PaymentStep/PaymentStep.vue'
330 import PackagesStep from './PakagesStep/PackageStep'
331 import RecurringStep from './RecurringStep/RecurringStep'
332 import RecurringSummary from './RecurringStep/RecurringSummary'
333 import ServiceStep from './ServiceStep/ServiceStep.vue'
334 import EmployeeStep from './EmployeeStep/EmployeeStep.vue'
335 import LocationStep from './LocationStep/LocationStep.vue'
336
337 // * import from Vue
338 import {
339 ref,
340 reactive,
341 watch,
342 provide,
343 inject,
344 markRaw,
345 computed,
346 onMounted,
347 watchEffect,
348 nextTick,
349 } from 'vue'
350 // * import from Vuex
351 import { useStore } from 'vuex'
352 // * import composable
353 import useRestore, { resolveRestoreStepIndex } from '../../../assets/js/public/restore'
354 import { useBuildPackage } from '../../../assets/js/public/package.js'
355 import { defaultCustomizeSettings } from '../../../assets/js/common/defaultCustomize.js'
356 import useAction from '../../../assets/js/public/actions'
357 import { useRenderAction } from '../../../assets/js/public/renderActions.js'
358 import { useColorTransparency } from '../../../assets/js/common/colorManipulation'
359 import {
360 usePrepaidPrice,
361 useCapacity,
362 useCheckingIfAllNotFree,
363 } from '../../../assets/js/common/appointments'
364 import {
365 useCart,
366 useCartStep,
367 useInitCartItem,
368 useCartHasItems,
369 useInitSelection,
370 useAddToCart,
371 useGoToCartStep,
372 } from '../../../assets/js/public/cart'
373 import { provideLiveAnnouncer } from '../../../assets/js/common/liveAnnouncer'
374
375 let formDialogVisibility = inject('formDialogVisibility', ref(true))
376
377 const { statusMessage, alertMessage, announceStatus } = provideLiveAnnouncer()
378
379 // * Shortcode data
380 const shortcodeData = inject('shortcodeData')
381
382 // * Plugin Licence
383 let licence = inject('licence')
384
385 // * Component reference
386 let ameliaContainer = ref(null)
387 // * Plugin wrapper width
388 let containerWidth = ref()
389 provide('containerWidth', containerWidth)
390
391 let empty = ref(false)
392
393 // * window resize listener
394 window.addEventListener('resize', resize)
395 // * resize function
396 function resize() {
397 if (ameliaContainer.value) {
398 containerWidth.value = ameliaContainer.value.offsetWidth
399 }
400 }
401
402 onMounted(() => {
403 store.commit('shortcodeParams/setForm', 'stepForm')
404
405 document
406 .getElementById('amelia-v2-booking-' + shortcodeData.value.counter)
407 .classList.add('amelia-v2-booking-' + shortcodeData.value.counter + '-loaded')
408
409 useAction(store, {}, 'ViewContent', 'appointment', null, null)
410
411 nextTick(() => {
412 containerWidth.value = ameliaContainer.value.offsetWidth
413 })
414
415 useAction(store, { containerWidth }, 'ContainerWidth', 'appointment', null, null)
416 })
417
418 // * Root Settings
419 const amSettings = inject('settings')
420
421 // * Customize
422 const amCustomize =
423 amSettings.customizedData && amSettings.customizedData.sbsNew
424 ? amSettings.customizedData.sbsNew
425 : defaultCustomizeSettings.sbsNew
426 if (amCustomize) {
427 provide('amCustomize', amCustomize)
428 }
429
430 let footerCustomizeOptions = computed(() => {
431 let obj = {
432 heading: true,
433 phone: true,
434 email: true,
435 }
436
437 if ('supportHeading' in amCustomize.sidebar.options) {
438 obj.heading = amCustomize.sidebar.options.supportHeading.visibility
439 }
440
441 if ('companyPhone' in amCustomize.sidebar.options) {
442 obj.phone = amCustomize.sidebar.options.companyPhone.visibility
443 }
444
445 if ('companyEmail' in amCustomize.sidebar.options) {
446 obj.email = amCustomize.sidebar.options.companyEmail.visibility
447 }
448
449 return obj
450 })
451
452 // * Fonts
453 const amFonts = ref(
454 amSettings.customizedData ? amSettings.customizedData.fonts : defaultCustomizeSettings.fonts,
455 )
456 provide('amFonts', amFonts)
457
458 // * Dialog width
459 let dialogWrapperWidth = inject('dialogWrapperWidth', ref(''))
460
461 // * Form Sidebar Collapse
462 let sidebarCollapsed = ref(false)
463 provide('sidebarCollapsed', sidebarCollapsed)
464
465 let sidebarCollapseItemsClass = ref('')
466
467 watch(sidebarCollapsed, (current) => {
468 if (current) {
469 setTimeout(() => {
470 sidebarCollapseItemsClass.value = 'am-collapsed'
471 dialogWrapperWidth.value = '592px'
472 }, 1000)
473 } else {
474 sidebarCollapseItemsClass.value = ''
475 dialogWrapperWidth.value = '760px'
476 }
477 })
478
479 let sidebarFooterRef = ref(null)
480 let sidebarFooterHeight = ref(0)
481
482 // * Form Sidebar Visibility
483 let sidebarVisibility = ref(
484 amCustomize.sidebar ? amCustomize.sidebar.options.self.visibility : true,
485 )
486
487 // * Root Urls
488 const baseUrls = inject('baseUrls')
489
490 // * Define store
491 const store = useStore()
492
493 let isRtl = computed(() => store.getters['getIsRtl'])
494
495 let ready = computed(() => store.getters['entities/getReady'])
496
497 let isRestored = ref(false)
498
499 watch(ready, (current) => {
500 if (current) {
501 nextTick(() => {
502 if (sidebarFooterRef.value) {
503 setTimeout(() => {
504 sidebarFooterHeight.value = sidebarFooterRef.value.offsetHeight
505 }, 200)
506 }
507 })
508
509 setShortcodeParams()
510
511 let preselected = store.getters['entities/getPreselected']
512
513 let selectionPackages = store.getters['entities/filteredPackages'](
514 store.getters['booking/getSelection'],
515 )
516
517 empty.value =
518 store.getters['entities/getServices'].length === 0 ||
519 store.getters['entities/getEmployees'].length === 0 ||
520 (store.getters['entities/getPackages'].length === 0 &&
521 (shortcodeData.value.show === 'packages' || preselected.package.length > 0)) ||
522 ((shortcodeData.value.show === 'packages' ||
523 preselected.show === 'packages' ||
524 preselected.package.length > 1) &&
525 selectionPackages.length === 0)
526
527 let restore = useRestore(store, shortcodeData.value)
528
529 if (restore) {
530 stepsArray.value.splice(0, stepsArray.value.length)
531
532 sidebarSteps.value.splice(0, sidebarSteps.value.length)
533
534 stepIndex.value = 0
535
536 restore.steps.forEach((key) => {
537 switch (key) {
538 case 'packageStep':
539 stepsArray.value.push(packagesStep)
540 break
541
542 case 'bringingAnyone':
543 stepsArray.value.push(bringingAnyone)
544 break
545
546 case 'initStep':
547 stepsArray.value.push(initStep)
548 break
549
550 case 'serviceStep':
551 stepsArray.value.push(serviceStep)
552 break
553
554 case 'employeeStep':
555 stepsArray.value.push(employeeStep)
556 break
557
558 case 'locationStep':
559 stepsArray.value.push(locationStep)
560 break
561
562 case 'packageInfoStep':
563 stepsArray.value.push(packageInfoStep)
564 break
565
566 case 'packageAppointmentsStep':
567 stepsArray.value.push(packageAppointmentsStep)
568 break
569
570 case 'packageAppointmentsListStep':
571 stepsArray.value.push(packageAppointmentsListStep)
572 break
573
574 case 'extrasStep':
575 stepsArray.value.push(extras)
576 break
577
578 case 'dateTimeStep':
579 stepsArray.value.push(dateTimeStep)
580 break
581
582 case 'recurringStep':
583 stepsArray.value.push(recurringStep)
584 break
585
586 case 'recurringSummary':
587 stepsArray.value.push(recurringSummary)
588 break
589
590 case 'cartStep':
591 stepsArray.value.push(cartStep)
592 break
593
594 case 'infoStep':
595 stepsArray.value.push(infoStep)
596 break
597
598 case 'paymentStep':
599 stepsArray.value.push(paymentStep)
600 break
601
602 case 'congratulations':
603 stepsArray.value.push(congratulationsStep)
604 break
605 }
606
607 let sideBarItem = restore.sidebar.find((i) => i.key === key)
608
609 sidebarDataUpdate()
610
611 if (typeof sideBarItem !== 'undefined') {
612 if (sideBarItem.data) {
613 sideBarItem.data.forEach((i) => {
614 sidebarDataCollector({
615 reference: i.reference,
616 position: i.position,
617 value: i.value,
618 })
619 })
620 }
621 }
622
623 stepIndex.value++
624 })
625
626 store.commit('booking/setLoading', false)
627
628 let index = -1
629
630 if (restore.result === 'success') {
631 index = stepsArray.value.length - 1
632 } else if (restore.result === 'error' || restore.result === 'canceled') {
633 index = resolveRestoreStepIndex(stepsArray.value, restore.targetStep, 'paymentStep')
634 }
635
636 for (let i = 0; i <= index; i++) {
637 stepsArray.value[i].finished = true
638 }
639
640 for (let i = 0; i <= index - 1; i++) {
641 sidebarSteps.value[i].finished = true
642 sidebarSteps.value[i].selected = false
643 }
644 if (restore.result === 'error' || restore.result === 'canceled') {
645 sidebarSteps.value[index].finished = false
646 sidebarSteps.value[index].selected = true
647 }
648
649 isRestored.value = true
650 stepIndex.value = index
651 }
652 }
653 })
654
655 store.commit('entities/setPreselected', shortcodeData.value)
656
657 // * Get Entities from server
658 store.dispatch('entities/getEntities', {
659 types: [
660 'employees',
661 'categories',
662 'locations',
663 'packages',
664 'entitiesRelations',
665 'customFields',
666 'taxes',
667 ],
668 licence: licence,
669 loadEntities: shortcodeData.value.hasApiCall || shortcodeData.value.trigger,
670 showHidden: false,
671 isPanel: false,
672 })
673
674 // * Form Component Collection
675 const initStep = markRaw(InitStep)
676 const extras = markRaw(Extras)
677 const packageInfoStep = markRaw(PackageInfo)
678 const recurringStep = markRaw(RecurringStep)
679 const recurringSummary = markRaw(RecurringSummary)
680 const dateTimeStep = markRaw(DateTimeStep)
681 const packageAppointmentsStep = markRaw(PackageAppointmentsStep)
682 const packageAppointmentsListStep = markRaw(PackageAppointmentsListStep)
683 const congratulationsStep = markRaw(Congratulations)
684 const cartStep = markRaw(CartStep)
685 const infoStep = markRaw(InfoStep)
686 const paymentStep = markRaw(PaymentStep)
687 const packagesStep = markRaw(PackagesStep)
688 const bringingAnyone = markRaw(BringingAnyone)
689
690 // * List Layout Components
691 const serviceStep = markRaw(ServiceStep)
692 const employeeStep = markRaw(EmployeeStep)
693 const locationStep = markRaw(LocationStep)
694
695 // * Array of step components
696 const stepsArray = ref(calculateInitStepsArray())
697
698 function calculateInitStepsArray() {
699 let steps
700 if (useCartStep(store)) {
701 steps = [initStep, dateTimeStep, cartStep, infoStep, congratulationsStep]
702 } else {
703 steps = [initStep, dateTimeStep, infoStep, congratulationsStep]
704 }
705
706 if (shortcodeData.value.layout === '2') {
707 steps.splice(0, 1)
708 let stepsOrder =
709 'order' in amCustomize
710 ? amCustomize.order
711 : [{ id: 'ServiceStep' }, { id: 'EmployeeStep' }, { id: 'LocationStep' }]
712 stepsOrder.forEach((stepKey, index) => {
713 if (stepKey.id === 'ServiceStep') {
714 steps.splice(index, 0, serviceStep)
715 }
716
717 if (stepKey.id === 'EmployeeStep') {
718 steps.splice(index, 0, employeeStep)
719 }
720
721 if (stepKey.id === 'LocationStep') {
722 steps.splice(index, 0, locationStep)
723 }
724 })
725 }
726
727 return steps
728 }
729
730 provide('stepsArray', stepsArray)
731
732 // * labels
733 const labels = inject('labels')
734
735 // * local language short code
736 const localLanguage = inject('localLanguage')
737
738 // * if local lang is in settings lang
739 let langDetection = computed(() => amSettings.general.usedLanguages.includes(localLanguage.value))
740
741 let cart = useCart(store)
742
743 // * Steps flags and boolean values - we use these flags and booleans to achieve certain functionalities on pages
744 // * preventRebuildRecurringBookingData flag prevents the recurring booking data from being rebuilt when navigating back to the recurring summary step
745 const preventRebuildRecurringBookingData = ref(false)
746 provide('stepsFlagsAndBooleans', {
747 preventRebuildRecurringBookingData,
748 })
749
750 // * Computed labels
751 let amLabels = computed(() => {
752 let computedLabels = reactive({ ...labels })
753
754 if (amSettings.customizedData && amSettings.customizedData.sbsNew) {
755 Object.keys(amSettings.customizedData.sbsNew).forEach((stepKey) => {
756 if (stepKey !== 'colors' && amSettings.customizedData.sbsNew[stepKey].translations) {
757 let customizedLabels = amSettings.customizedData.sbsNew[stepKey].translations
758 Object.keys(customizedLabels).forEach((labelKey) => {
759 if (customizedLabels[labelKey][localLanguage.value] && langDetection.value) {
760 computedLabels[labelKey] = customizedLabels[labelKey][localLanguage.value]
761 } else if (customizedLabels[labelKey].default) {
762 computedLabels[labelKey] = customizedLabels[labelKey].default
763 }
764 })
765 }
766 })
767 }
768 return computedLabels
769 })
770
771 provide('amLabels', amLabels)
772
773 let footerLabels = computed(() => {
774 let customLabels = {}
775 if (amSettings.customizedData && amSettings.customizedData.sbsNew) {
776 let customizedLabels = amSettings.customizedData.sbsNew[stepsArray.value[stepIndex.value].key]
777 ? amSettings.customizedData.sbsNew[stepsArray.value[stepIndex.value].key].translations
778 : null
779 if (amSettings.customizedData && customizedLabels) {
780 Object.keys(customizedLabels).forEach((labelKey) => {
781 if (customizedLabels[labelKey][localLanguage.value] && langDetection.value) {
782 customLabels[labelKey] = customizedLabels[labelKey][localLanguage.value]
783 } else if (customizedLabels[labelKey].default) {
784 customLabels[labelKey] = customizedLabels[labelKey].default
785 }
786 })
787 }
788 }
789
790 return Object.keys(customLabels).length ? customLabels : labels
791 })
792
793 let primFooterBtnType = computed(() => {
794 let btnType = 'filled'
795 if (
796 amSettings.customizedData &&
797 amSettings.customizedData.sbsNew &&
798 amSettings.customizedData.sbsNew[stepsArray.value[stepIndex.value].key]
799 ) {
800 btnType =
801 amSettings.customizedData.sbsNew[stepsArray.value[stepIndex.value].key].options
802 .primaryFooterButton.buttonType
803 }
804
805 return btnType
806 })
807
808 provide('primFooterBtnType', primFooterBtnType)
809
810 provide('primDescBtnType', primFooterBtnType)
811
812 let secFooterBtnType = computed(() => {
813 let btnType = 'text'
814 if (
815 amSettings.customizedData &&
816 amSettings.customizedData.sbsNew &&
817 amSettings.customizedData.sbsNew[stepsArray.value[stepIndex.value].key] &&
818 amSettings.customizedData.sbsNew[stepsArray.value[stepIndex.value].key].options
819 .secondaryFooterButton
820 ) {
821 btnType =
822 amSettings.customizedData.sbsNew[stepsArray.value[stepIndex.value].key].options
823 .secondaryFooterButton.buttonType
824 }
825
826 return btnType
827 })
828
829 let addToCartBtnType = computed(() => {
830 if (
831 amSettings.customizedData &&
832 amSettings.customizedData.sbsNew &&
833 amSettings.customizedData.sbsNew['cartStep']
834 ) {
835 return amSettings.customizedData.sbsNew['cartStep'].options['addToCart'].buttonType
836 }
837
838 return 'text'
839 })
840
841 let backToCartBtnType = computed(() => {
842 if (
843 amSettings.customizedData &&
844 amSettings.customizedData.sbsNew &&
845 amSettings.customizedData.sbsNew['cartStep']
846 ) {
847 return amSettings.customizedData.sbsNew['cartStep'].options['backToCart'].buttonType
848 }
849
850 return 'text'
851 })
852
853 function dedicatedStepLabel(labelKey, stepKey) {
854 let customLabel = ''
855 if (amSettings.customizedData && amSettings.customizedData.sbsNew) {
856 let customizedLabels = amSettings.customizedData.sbsNew[stepKey]
857 ? amSettings.customizedData.sbsNew[stepKey].translations
858 : null
859 if (amSettings.customizedData && customizedLabels && customizedLabels[labelKey]) {
860 if (
861 customLabel === '' &&
862 customizedLabels[labelKey][localLanguage.value] &&
863 langDetection.value
864 ) {
865 customLabel = customizedLabels[labelKey][localLanguage.value]
866 } else if (customLabel === '' && customizedLabels[labelKey].default) {
867 customLabel = customizedLabels[labelKey].default
868 }
869 }
870 }
871
872 return customLabel ? customLabel : labels[labelKey]
873 }
874
875 let bringingAnyoneOptions = computed(() => {
876 return useCapacity(
877 store.getters['entities/getEmployeeServices'](store.getters['booking/getSelection']),
878 )
879 })
880
881 function setShortcodeParams() {
882 let preselected = store.getters['entities/getPreselected']
883
884 // * When single category selected
885 if (preselected.category.length === 1) {
886 store.commit('booking/setCategoryId', parseInt(preselected.category[0]))
887 }
888
889 // * When single service selected
890 if (preselected.service.length === 1) {
891 let service = store.getters['entities/getService'](parseInt(preselected.service[0]))
892
893 if (service) {
894 store.commit('booking/setServiceId', parseInt(preselected.service[0]))
895 }
896 // * Set category id from service if it exists
897 store.commit('booking/setCategoryId', service ? parseInt(service.categoryId) : null)
898 }
899
900 // * When single employee selected
901 if (preselected.employee.length === 1) {
902 store.commit('booking/setEmployeeId', parseInt(preselected.employee[0]))
903 }
904
905 // * When single location selected
906 if (preselected.location.length === 1) {
907 store.commit('booking/setLocationId', parseInt(preselected.location[0]))
908 }
909
910 // * When single package selected
911 if (preselected.package.length === 1) {
912 store.commit('booking/setPackageId', parseInt(preselected.package[0]))
913 }
914
915 if (preselected.package.length === 1) {
916 if (shortcodeData.value.layout === '2') {
917 stepsArray.value.splice(0, 3, packageInfoStep)
918 sidebarSteps.value.splice(0, 3)
919 } else {
920 stepsArray.value.splice(0, 1, packageInfoStep)
921 sidebarSteps.value.splice(0, 1)
922 }
923
924 sidebarDataUpdate()
925 let selectedPackage = store.getters['entities/filteredPackages'](
926 store.getters['booking/getSelection'],
927 )[0]
928 if (selectedPackage) {
929 goToPackageStep(selectedPackage, false)
930 }
931 } else if (preselected.show === 'packages' || preselected.package.length > 1) {
932 if (shortcodeData.value.layout === '2') {
933 stepsArray.value.splice(0, 3, packagesStep)
934 sidebarSteps.value.splice(0, 3)
935 } else {
936 stepsArray.value.splice(0, 1, packagesStep)
937 sidebarSteps.value.splice(0, 1)
938 }
939
940 // * Set bookable type to package
941 store.commit('booking/setBookableType', 'package')
942 sidebarDataUpdate()
943 } else {
944 changeInitStepDataService()
945 store.commit('booking/setBookableType', 'appointment')
946 let employeeOptions =
947 store.getters['entities/filteredEmployees'](store.getters['booking/getSelection']).length <= 1
948
949 let employeeStepVisibility = amCustomize?.employeeStep
950 ? !amCustomize.employeeStep.options.employee.visibility
951 : !defaultCustomizeSettings.sbsNew.employeeStep.options.employee.visibility
952 let employeeVisibility =
953 shortcodeData.value.layout === '2'
954 ? employeeStepVisibility
955 : !amCustomize.initStep.options.employee.visibility
956 let locationOptions =
957 store.getters['entities/filteredLocations'](store.getters['booking/getSelection']).length <= 1
958 let locationStepVisibility = amCustomize?.locationStep
959 ? !amCustomize.locationStep.options.location.visibility
960 : !defaultCustomizeSettings.sbsNew.locationStep.options.location.visibility
961 let locationVisibility =
962 shortcodeData.value.layout === '2'
963 ? locationStepVisibility
964 : !amCustomize.initStep.options.location.visibility
965
966 if (shortcodeData.value.layout === '2') {
967 // * SeviceStep index in stepsArray
968 let serviceStepIndex = stepsArray.value.findIndex((item) => item.name === 'ServiceStep')
969
970 // * If sevice is preslected
971 if (preselected.service.length === 1 && serviceStepIndex !== -1) {
972 stepsArray.value.splice(serviceStepIndex, 1)
973 sidebarSteps.value.splice(serviceStepIndex, 1)
974 }
975
976 // * EmployeeStep index in stepsArray
977 let employeeStepIndex = stepsArray.value.findIndex((item) => item.name === 'EmployeeStep')
978
979 // * If employee is preslected
980 if (
981 (preselected.employee.length === 1 && employeeStepIndex !== -1 && employeeOptions) ||
982 employeeVisibility
983 ) {
984 stepsArray.value.splice(employeeStepIndex, 1)
985 sidebarSteps.value.splice(employeeStepIndex, 1)
986 }
987
988 // * LocationStep index in stepsArray
989 let locationStepIndex = stepsArray.value.findIndex((item) => item.name === 'LocationStep')
990
991 // * If location is preslected
992 if (
993 ((preselected.location.length === 1 ||
994 store.getters['entities/getLocations'].length === 0) &&
995 locationStepIndex !== -1 &&
996 locationOptions) ||
997 locationVisibility
998 ) {
999 stepsArray.value.splice(locationStepIndex, 1)
1000 sidebarSteps.value.splice(locationStepIndex, 1)
1001 }
1002
1003 if (
1004 preselected.service.length === 1 &&
1005 (preselected.employee.length === 1 || employeeOptions || employeeVisibility) &&
1006 (preselected.location.length === 1 || locationOptions || locationVisibility)
1007 ) {
1008 if (bringingAnyoneOptions.value.availability) {
1009 stepsArray.value.splice(0, 0, bringingAnyone)
1010 }
1011 sidebarDataUpdate()
1012 }
1013 } else {
1014 if (
1015 preselected.service.length === 1 &&
1016 (preselected.employee.length === 1 || employeeOptions || employeeVisibility) &&
1017 (preselected.location.length === 1 || locationOptions || locationVisibility)
1018 ) {
1019 if (bringingAnyoneOptions.value.availability) {
1020 stepsArray.value.splice(0, 1, bringingAnyone)
1021 sidebarSteps.value.splice(0, 1)
1022 } else {
1023 stepsArray.value.splice(0, 1)
1024 sidebarSteps.value.splice(0, 1)
1025 }
1026 sidebarDataUpdate()
1027 }
1028 }
1029 }
1030 }
1031
1032 function stepChanger(steps, stepsNamesToRemove, stepsToAdd, startIndex) {
1033 let removeIndexes = steps.value
1034 .map((item, index) => (stepsNamesToRemove.includes(item.name) ? index : null))
1035 .filter((item) => item !== null)
1036 .reverse()
1037
1038 removeIndexes.forEach((item) => {
1039 steps.value.splice(item, 1)
1040 })
1041
1042 stepsToAdd.forEach((item) => {
1043 if (steps.value.indexOf(item) === -1) {
1044 steps.value.splice(startIndex + 1, 0, item)
1045 }
1046 })
1047 }
1048
1049 provide('goToPackageStep', { goToPackageStep })
1050
1051 // * Those variables are used to store initial steps array if layout is 2
1052 let initStepsArray = ref([])
1053 let lowestInitStepsIndex = ref(0)
1054
1055 function goToPackageStep(pack, goToNextStep = true) {
1056 store.commit('booking/setPackageId', pack.id)
1057 store.commit('booking/setBookableType', 'package')
1058 store.commit('booking/setMultipleAppointments', useBuildPackage(0, pack))
1059 store.commit('booking/setMultipleAppointmentsIndex', 0)
1060
1061 let bookingCount = pack.bookable.reduce(
1062 (partialSum, book) => partialSum + book.maximumScheduled,
1063 0,
1064 )
1065
1066 let removeSteps = []
1067 let addSteps = []
1068
1069 if (shortcodeData.value.layout === '2') {
1070 stepsArray.value.forEach((step, index) => {
1071 if (
1072 step.name === 'ServiceStep' ||
1073 step.name === 'EmployeeStep' ||
1074 step.name === 'LocationStep'
1075 ) {
1076 initStepsArray.value.push({
1077 index: index,
1078 component: step,
1079 })
1080 }
1081 })
1082
1083 lowestInitStepsIndex.value = Math.min(...initStepsArray.value.map((step) => step.index))
1084
1085 // * If ServiceStep is present in initial steps
1086 if (initStepsArray.value.find((step) => step.component.name === 'ServiceStep')) {
1087 initStepsArray.value.forEach((step) => {
1088 if (step.component.name !== 'ServiceStep') {
1089 removeSteps.push(step.component.name)
1090 }
1091 })
1092 } else {
1093 // * If ServiceStep is not present in initial steps
1094 initStepsArray.value.forEach((step) => {
1095 if (step.index !== lowestInitStepsIndex.value) {
1096 removeSteps.push(step.component.name)
1097 }
1098 })
1099 }
1100 }
1101
1102 if (stepsArray.value[0] !== extras) {
1103 removeSteps.push('ExtrasStep')
1104 }
1105
1106 if (stepsArray.value[0] !== dateTimeStep) {
1107 removeSteps.push('DateTimeStep')
1108 }
1109
1110 if (stepsArray.value[0] !== cartStep) {
1111 removeSteps.push('CartStep')
1112 }
1113
1114 if (bookingCount > 0) {
1115 addSteps = addSteps.concat([
1116 packageAppointmentsListStep,
1117 packageAppointmentsStep,
1118 packageInfoStep,
1119 ])
1120 } else {
1121 addSteps.push(packageInfoStep)
1122 }
1123
1124 if (stepIndex.value > 0) {
1125 stepIndex.value = 0
1126 }
1127 stepChanger(stepsArray, removeSteps, addSteps, stepIndex.value)
1128
1129 stepChanger(sidebarSteps, removeSteps, [], stepIndex.value)
1130
1131 sidebarDataUpdate()
1132
1133 if (goToNextStep) {
1134 nextStep()
1135 }
1136 }
1137
1138 function removePackageStep() {
1139 store.commit('booking/setBookableType', 'appointment')
1140
1141 store.commit('booking/setMultipleAppointments', [
1142 {
1143 packageId: null,
1144 serviceId: null,
1145 index: 0,
1146 services: {},
1147 },
1148 ])
1149
1150 store.commit('booking/setPackageId', null)
1151
1152 store.commit('booking/setMultipleAppointmentsIndex', 0)
1153
1154 useInitCartItem(store)
1155
1156 let addSteps = []
1157
1158 if (stepsArray.value[0] !== dateTimeStep) {
1159 if (useCartStep(store)) {
1160 addSteps.push(cartStep)
1161 }
1162
1163 addSteps.push(dateTimeStep)
1164 }
1165
1166 if (
1167 stepsArray.value[0] !== extras &&
1168 selectedServiceExtras.value &&
1169 selectedServiceExtras.value.length
1170 ) {
1171 addSteps.push(extras)
1172 }
1173
1174 let addingStartIndex = stepIndex.value - 1
1175
1176 if (shortcodeData.value.layout === '2' && initStepsArray.value.length) {
1177 initStepsArray.value.forEach((step) => {
1178 if (!stepsArray.value.some((s) => s.name === step.component.name)) {
1179 stepsArray.value.splice(step.index, 0, step.component)
1180 }
1181 })
1182
1183 addingStartIndex = initStepsArray.value.length - 1
1184 }
1185
1186 stepChanger(
1187 stepsArray,
1188 ['PackageInfoStep', 'PackageAppointmentsStep', 'PackageAppointmentsListStep'],
1189 addSteps,
1190 addingStartIndex,
1191 )
1192
1193 stepChanger(
1194 sidebarSteps,
1195 ['PackageInfoStep', 'PackageAppointmentsStep', 'PackageAppointmentsListStep'],
1196 [],
1197 stepIndex.value,
1198 )
1199
1200 sidebarDataUpdate()
1201 }
1202
1203 provide('goToRecurringStep', { goToRecurringStep })
1204
1205 provide('removeRecurringStep', { removeRecurringStep })
1206
1207 function goToRecurringStep() {
1208 if (useCartStep(store)) {
1209 removeCartStep()
1210 }
1211
1212 let startIndex = 0
1213
1214 for (let i = 0; i < stepsArray.value.length; i++) {
1215 if (stepsArray.value[i].name === 'DateTimeStep') {
1216 startIndex = i
1217
1218 break
1219 }
1220 }
1221
1222 stepsArray.value.splice(startIndex + 1, 0, recurringStep)
1223 stepsArray.value.splice(startIndex + 2, 0, recurringSummary)
1224
1225 sidebarDataUpdate()
1226
1227 nextStep()
1228 }
1229
1230 function removeRecurringStep() {
1231 for (let i = stepsArray.value.length - 1; i >= 0; i--) {
1232 if (
1233 stepsArray.value[i].name === 'RecurringStep' ||
1234 stepsArray.value[i].name === 'RecurringSummary'
1235 ) {
1236 stepsArray.value.splice(i + 1, 1)
1237 }
1238 }
1239
1240 if (useCartStep(store)) {
1241 addCartStep(stepIndex.value)
1242 }
1243
1244 sidebarDataUpdate()
1245 }
1246
1247 provide('addPaymentsStep', { addPaymentsStep })
1248
1249 provide('removePaymentsStep', { removePaymentsStep })
1250
1251 // Provide cart step controls for child components (e.g., Calendar) so they can hide cart during waiting list selection
1252 provide('addCartStep', { addCartStep: () => addCartStep(stepIndex.value) })
1253
1254 provide('removeCartStep', { removeCartStep })
1255
1256 function addPaymentsStep() {
1257 stepsArray.value.splice(stepsArray.value.length - 1, 0, paymentStep)
1258
1259 sidebarDataUpdate()
1260 }
1261
1262 function removePaymentsStep() {
1263 let removeSteps = ['PaymentStep']
1264
1265 stepChanger(stepsArray, removeSteps, [], stepIndex.value)
1266
1267 stepChanger(sidebarSteps, removeSteps, [], stepIndex.value)
1268 }
1269
1270 function addCartStep(index) {
1271 stepChanger(stepsArray, [], [cartStep], index)
1272
1273 stepChanger(sidebarSteps, [], [], index)
1274
1275 sidebarDataUpdate()
1276 }
1277
1278 function removeCartStep() {
1279 stepChanger(stepsArray, ['CartStep'], [], stepIndex.value)
1280
1281 stepChanger(sidebarSteps, ['CartStep'], [], stepIndex.value)
1282
1283 sidebarDataUpdate()
1284 }
1285
1286 /**
1287 * Detect if Selected Service has Extras
1288 * @type {any}
1289 */
1290 let selectedServiceExtras = computed(() => {
1291 let service = store.getters['entities/getService'](store.getters['booking/getServiceId'])
1292
1293 return service ? service.extras : []
1294 })
1295
1296 // * Appointment Waiting List: populate module on service selection
1297 watch(
1298 () => store.getters['booking/getServiceId'],
1299 (newServiceId) => {
1300 // Reset first
1301 store.dispatch('appointmentWaitingListOptions/resetWaitingOptions')
1302
1303 if (!newServiceId) return
1304
1305 let globalEnabled = !!amSettings.featuresIntegrations?.waitingListAppointments?.enabled
1306 if (!globalEnabled) return
1307
1308 let service = store.getters['entities/getService'](newServiceId)
1309 if (!service || !service.settings) return
1310
1311 let serviceSettings = null
1312 try {
1313 serviceSettings = JSON.parse(service.settings)
1314 } catch (e) {
1315 serviceSettings = null
1316 }
1317 if (!serviceSettings || !serviceSettings.waitingList || !serviceSettings.waitingList.enabled)
1318 return
1319
1320 const wl = serviceSettings.waitingList
1321 // Backend may not yet supply peopleWaiting for appointments; default 0
1322 let waitingPayload = {
1323 enabled: !!wl.enabled,
1324 maxCapacity: wl.maxCapacity || 0,
1325 maxExtraPeople: wl.maxExtraPeople || 0,
1326 maxExtraPeopleEnabled: !!wl.maxExtraPeopleEnabled,
1327 peopleWaiting: wl.peopleWaiting || 0,
1328 isWaitingListSlot: false,
1329 }
1330 store.commit('appointmentWaitingListOptions/setAllData', waitingPayload)
1331 },
1332 { immediate: true },
1333 )
1334
1335 /**
1336 * Add or Remove steps from Steps Array
1337 */
1338 function changeInitStepDataService() {
1339 // * Adding Extras Step
1340 if (
1341 selectedServiceExtras.value.length &&
1342 !stepsArray.value.find((step) => step.name === 'ExtrasStep')
1343 ) {
1344 if (shortcodeData.value.layout === '2') {
1345 let initStepsIndex = Math.max(
1346 stepsArray.value.findIndex((step) => step.name === 'ServiceStep'),
1347 stepsArray.value.findIndex((step) => step.name === 'EmployeeStep'),
1348 stepsArray.value.findIndex((step) => step.name === 'LocationStep'),
1349 )
1350 stepsArray.value.splice(initStepsIndex + 1, 0, extras)
1351 } else {
1352 stepsArray.value.splice(stepIndex.value + 1, 0, extras)
1353 }
1354
1355 sidebarDataUpdate()
1356 }
1357
1358 // * Removing Extras Step
1359 if (
1360 sidebarSteps.value.find((step) => step.name === 'ExtrasStep') &&
1361 !Object.keys(selectedServiceExtras.value).length
1362 ) {
1363 let extrasIndex = stepsArray.value.findIndex((step) => step.name === 'ExtrasStep')
1364 stepsArray.value.splice(extrasIndex, 1)
1365 sidebarSteps.value.splice(extrasIndex, 1)
1366 }
1367
1368 useInitCartItem(store)
1369 }
1370
1371 provide('initDataChanges', {
1372 changeInitStepDataService,
1373 })
1374
1375 // * Step index
1376 const stepIndex = ref(0)
1377 // * StepIndex provide
1378 provide('stepIndex', stepIndex)
1379
1380 // * Monitoring step index for step selection
1381 watch(stepIndex, (currStepIndex, prevStepIndex) => {
1382 if (currStepIndex < sidebarSteps.value.length) {
1383 sidebarSteps.value[prevStepIndex].selected = false
1384 sidebarSteps.value[currStepIndex].selected = true
1385 }
1386
1387 if (currStepIndex === sidebarSteps.value.length) {
1388 sidebarSteps.value[prevStepIndex].selected = false
1389 }
1390
1391 const step = sidebarSteps.value[currStepIndex]
1392 if (step?.label) {
1393 announceStatus(`${step.label}, step ${currStepIndex + 1} of ${sidebarSteps.value.length}`)
1394 }
1395 })
1396
1397 let goBackToPackageBooking = ref(false)
1398 provide('goBackToPackageBooking', goBackToPackageBooking)
1399
1400 /**
1401 * Move to previous Form Step
1402 */
1403 function previousStep() {
1404 footerBtnDisabledUpdater(false)
1405
1406 if (!navigateInsideStep.value) {
1407 if (stepsArray.value[stepIndex.value].name === packageInfoStep.name) {
1408 removePackageStep()
1409 }
1410
1411 if (stepsArray.value[stepIndex.value].name === recurringStep.name) {
1412 stepsArray.value.splice(stepIndex.value, 2)
1413 sidebarSteps.value.splice(stepIndex.value, 2)
1414
1415 if (useCartStep(store)) {
1416 stepsArray.value.splice(stepIndex.value, 0, cartStep)
1417
1418 stepChanger(sidebarSteps, [], [], stepIndex.value)
1419
1420 sidebarDataUpdate()
1421 }
1422 }
1423
1424 if (
1425 stepIndex.value > 0 &&
1426 stepsArray.value[stepIndex.value - 1]?.name === recurringSummary.name
1427 ) {
1428 preventRebuildRecurringBookingData.value = true
1429 }
1430
1431 if (stepsArray.value[stepIndex.value].name === packageAppointmentsListStep.name) {
1432 goBackToPackageBooking.value = true
1433 }
1434
1435 stepIndex.value = stepIndex.value - 1
1436 }
1437
1438 headerButtonPreviousClicked.value = !headerButtonPreviousClicked.value
1439 }
1440
1441 /**
1442 * Move to next Form Step
1443 */
1444 function nextStep() {
1445 if (!navigateInsideStep.value) {
1446 sidebarSteps.value[stepIndex.value].finished = true
1447 stepIndex.value = stepIndex.value + 1
1448 }
1449 }
1450
1451 // * Footer states
1452 let loading = computed(() => store.getters['booking/getLoading'])
1453 let booked = computed(() => store.getters['booking/getBooked'])
1454 let paymentGateway = computed(() => store.getters['booking/getPaymentGateway'])
1455
1456 // * Footer btn flag
1457 let footerButtonClicked = ref(false)
1458 let footerBtnDisabled = ref(false)
1459
1460 let headerButtonPreviousClicked = ref(false)
1461
1462 /**
1463 * Footer btn clicked
1464 */
1465 function footerButtonClick() {
1466 footerButtonClicked.value = true
1467 }
1468
1469 /**
1470 * Footer btn reset
1471 */
1472 function footerButtonReset() {
1473 footerButtonClicked.value = false
1474 }
1475
1476 /**
1477 * Header btn previous clicked
1478 */
1479 function headerButtonPreviousClick() {
1480 headerButtonPreviousClicked.value = true
1481 }
1482
1483 /**
1484 * Header btn previous reset
1485 */
1486 function headerButtonPreviousReset() {
1487 headerButtonPreviousClicked.value = false
1488 }
1489
1490 function footerBtnDisabledUpdater(data) {
1491 footerBtnDisabled.value = data
1492 }
1493
1494 function secondButtonClick() {
1495 if (booked.value) {
1496 if (booked.value.customerCabinetUrl) {
1497 window.location.href = booked.value.customerCabinetUrl
1498 } else {
1499 window.location.href = amSettings.roles.customerCabinet.pageUrl
1500 }
1501 }
1502 }
1503
1504 provide('secondButton', {
1505 secondButtonClick,
1506 })
1507
1508 let navigateInsideStep = ref(false)
1509
1510 provide('changingStepsFunctions', {
1511 nextStep,
1512 previousStep,
1513 footerButtonClick,
1514 footerButtonReset,
1515 footerBtnDisabledUpdater,
1516 headerButtonPreviousClick,
1517 headerButtonPreviousReset,
1518 footerBtnDisabled,
1519 footerButtonClicked,
1520 headerButtonPreviousClicked,
1521 navigateInsideStep,
1522 })
1523
1524 // * Array of Sidebar steps
1525 const sidebarSteps = ref([])
1526 provide('sidebarSteps', sidebarSteps)
1527
1528 let keepPaymentStep = computed(() =>
1529 !empty.value
1530 ? useCart(store).length
1531 ? usePrepaidPrice(store) !== 0
1532 : useCheckingIfAllNotFree(store)
1533 : true,
1534 )
1535
1536 // Appointment waiting list flag
1537 const isWaitingListBooking = computed(
1538 () => store.getters['appointmentWaitingListOptions/getIsWaitingListSlot'],
1539 )
1540
1541 watchEffect(() => {
1542 // If waiting list, remove payment step
1543 if (isWaitingListBooking.value) {
1544 removePaymentsStep()
1545 return
1546 }
1547 if (!isRestored.value && !keepPaymentStep.value) {
1548 removePaymentsStep()
1549 } else if (!isRestored.value) {
1550 if (stepsArray.value.indexOf(paymentStep) === -1) {
1551 addPaymentsStep()
1552 }
1553 }
1554 })
1555
1556 /**
1557 * Collecting and rearranging selected data in form sidebar based on item position in form
1558 * @param data
1559 */
1560 function sidebarDataCollector(data) {
1561 // Determines are selected data already exists
1562 if (
1563 sidebarSteps.value[stepIndex.value].stepSelectedData.filter(
1564 (item) => item.reference === data.reference,
1565 ).length
1566 ) {
1567 sidebarSteps.value[stepIndex.value].stepSelectedData.forEach((item, index, array) => {
1568 // Handles changing value of existing data
1569 if (item.reference === data.reference && data.value && data.value !== item.value) {
1570 item.value = data.value
1571 }
1572
1573 // Removes un-selected form data from sidebar array
1574 if (item.reference === data.reference && !data.value) {
1575 array.splice(index, 1)
1576 }
1577 })
1578 } else {
1579 if (data.value) {
1580 sidebarSteps.value[stepIndex.value].stepSelectedData.push(data)
1581 }
1582 }
1583
1584 // Rearranges data in sidebar based on item position in the form
1585 sidebarSteps.value[stepIndex.value].stepSelectedData.sort((a, b) => {
1586 return a.position - b.position
1587 })
1588
1589 const currentStep = sidebarSteps.value[stepIndex.value]
1590 const selections = currentStep.stepSelectedData
1591 .map((item) => item.value)
1592 .filter(Boolean)
1593 .join(', ')
1594
1595 if (selections) {
1596 announceStatus(`${currentStep.label}: ${selections}`)
1597 }
1598 }
1599
1600 provide('sidebarStepsFunctions', {
1601 sidebarDataCollector,
1602 })
1603
1604 /**
1605 * Function that update SideBar Data Array when some component are dynamically added
1606 */
1607 function sidebarDataUpdate() {
1608 stepsArray.value.forEach((item, index) => {
1609 if (item.name === 'CongratulationsStep') {
1610 return
1611 }
1612 if (!sidebarSteps.value.find((block) => block.labelKey === item.sidebarData.label)) {
1613 let labelKey = item.sidebarData.label
1614 let step = {
1615 name: item.name,
1616 key: item.key,
1617 label: amLabels.value[labelKey],
1618 labelKey: labelKey,
1619 icon: item.sidebarData.icon,
1620 stepSelectedData: [],
1621 finished: false,
1622 selected: index === stepIndex.value,
1623 }
1624 sidebarSteps.value.splice(index, 0, step)
1625 }
1626 })
1627 }
1628
1629 // * Cart
1630 function addToCart() {
1631 sidebarSteps.value.forEach((item) => {
1632 item.finished = false
1633 item.selected = false
1634 })
1635
1636 stepsArray.value.forEach((item) => {
1637 item.finished = false
1638 item.selected = false
1639 })
1640
1641 stepIndex.value = 0
1642
1643 useInitSelection(store, true)
1644
1645 useAddToCart(store)
1646
1647 useInitCartItem(store)
1648
1649 const cartCount = useCartHasItems(store)
1650 announceStatus(
1651 `${amLabels.value.added_to_cart || 'Added to cart'}. ${cartCount} ${cartCount === 1 ? amLabels.value.appointment : amLabels.value.appointments} ${amLabels.value.in_cart}.`,
1652 )
1653 }
1654
1655 function backToCart() {
1656 let currentCartItem = store.getters['booking/getCurrentCartItem']
1657
1658 let items = store.getters['booking/getAllMultipleAppointments']
1659
1660 if (currentCartItem) {
1661 store.commit('booking/setCartItem', currentCartItem)
1662
1663 store.commit('booking/setCartItemIndex', items.length - 1)
1664
1665 store.commit('booking/setCurrentCartItem', null)
1666 } else {
1667 items.pop()
1668
1669 store.commit('booking/setCartItemIndex', items.length - 1)
1670
1671 store.commit('booking/setServiceId', items[items.length - 1].serviceId)
1672
1673 store.commit(
1674 'booking/setEmployeeId',
1675 items[items.length - 1].services[items[items.length - 1].serviceId].providerId,
1676 )
1677
1678 store.commit(
1679 'booking/setLocationId',
1680 items[items.length - 1].services[items[items.length - 1].serviceId].locationId,
1681 )
1682 }
1683
1684 useGoToCartStep(stepsArray, stepIndex)
1685 }
1686
1687 // * Colors block
1688 let amColors = computed(() => {
1689 return amSettings.customizedData && amSettings.customizedData.sbsNew
1690 ? amSettings.customizedData.sbsNew.colors
1691 : defaultCustomizeSettings.sbsNew.colors
1692 })
1693 provide('amColors', amColors)
1694
1695 let borderCalculation = computed(() =>
1696 containerWidth.value !== 0 && containerWidth.value > 560
1697 ? sidebarVisibility.value || sidebarCollapsed.value
1698 : false,
1699 )
1700
1701 let cssVars = computed(() => {
1702 return {
1703 '--am-c-primary': amColors.value.colorPrimary,
1704 '--am-c-success': amColors.value.colorSuccess,
1705 '--am-c-error': amColors.value.colorError,
1706 '--am-c-warning': amColors.value.colorWarning,
1707 '--am-c-main-bgr': amColors.value.colorMainBgr,
1708 '--am-c-main-heading-text': amColors.value.colorMainHeadingText,
1709 '--am-c-main-text': amColors.value.colorMainText,
1710 '--am-c-sb-bgr': amColors.value.colorSbBgr,
1711 '--am-c-sb-text': amColors.value.colorSbText,
1712 '--am-c-inp-bgr': amColors.value.colorInpBgr,
1713 '--am-c-inp-border': amColors.value.colorInpBorder,
1714 '--am-c-inp-text': amColors.value.colorInpText,
1715 '--am-c-inp-placeholder': amColors.value.colorInpPlaceHolder,
1716 '--am-c-drop-bgr': amColors.value.colorDropBgr,
1717 '--am-c-drop-text': amColors.value.colorDropText,
1718 '--am-c-btn-prim': amColors.value.colorBtnPrim,
1719 '--am-c-btn-prim-text': amColors.value.colorBtnPrimText,
1720 '--am-c-btn-sec': amColors.value.colorBtnSec,
1721 '--am-c-btn-sec-text': amColors.value.colorBtnSecText,
1722 '--am-c-skeleton-op20': useColorTransparency(amColors.value.colorMainText, 0.2),
1723 '--am-c-skeleton-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
1724 '--am-c-skeleton-sb-op20': useColorTransparency(amColors.value.colorSbText, 0.2),
1725 '--am-c-skeleton-sb-op60': useColorTransparency(amColors.value.colorSbText, 0.6),
1726 '--am-font-family': amFonts.value.fontFamily,
1727
1728 // css properties
1729 // -mw- max width
1730 // -brad- border-radius
1731 '--am-mw-main': sidebarVisibility.value
1732 ? sidebarCollapsed.value
1733 ? '592px'
1734 : '760px'
1735 : '520px',
1736 '--am-brad-main': borderCalculation.value
1737 ? isRtl.value
1738 ? '0.5rem 0 0 0.5rem'
1739 : '0 0.5rem 0.5rem 0'
1740 : '0.5rem',
1741 }
1742 })
1743
1744 function activateCustomFontStyles() {
1745 let head = document.head || document.getElementsByTagName('head')[0]
1746 if (head.querySelector('#amCustomFont')) {
1747 head.querySelector('#amCustomFont').remove()
1748 }
1749
1750 let css = `@font-face {font-family: '${amFonts.value.fontFamily}'; src: url(${amFonts.value.fontUrl});}`
1751 let style = document.createElement('style')
1752 head.appendChild(style)
1753 style.setAttribute('type', 'text/css')
1754 style.setAttribute('id', 'amCustomFont')
1755 style.appendChild(document.createTextNode(css))
1756 }
1757
1758 if (amFonts.value.customFontSelected) activateCustomFontStyles()
1759
1760 // * Design Properties
1761 let amDesignProperties = computed(() => {
1762 return {
1763 colorInputBorderRadius: '6px',
1764 }
1765 })
1766 provide('amDesignProperties', amDesignProperties)
1767
1768 onMounted(() => {
1769 useRenderAction('renderForm', {
1770 ameliaContainer,
1771 containerWidth,
1772 empty,
1773 amCustomize,
1774 amFonts,
1775 sidebarCollapsed,
1776 sidebarCollapseItemsClass,
1777 sidebarFooterRef,
1778 sidebarFooterHeight,
1779 sidebarVisibility,
1780 })
1781 })
1782 </script>
1783
1784 <style lang="scss">
1785 @import '../../../assets/scss/public/overides/overides';
1786 @import '../../../assets/scss/common/reset/reset';
1787 @import '../../../assets/scss/common/icon-fonts/style';
1788 @import '../../../assets/scss/common/skeleton/skeleton.scss';
1789
1790 :root {
1791 // Colors
1792 // shortcuts
1793 // -c- color
1794 // -bgr- background
1795 // -prim- primary
1796 // -sec- secondary
1797 // primitive colors
1798 --am-c-primary: #{$blue-1000};
1799 --am-c-success: #{$green-1000};
1800 --am-c-error: #{$red-900};
1801 --am-c-warning: #{$yellow-1000};
1802 // main container colors - right part of the form
1803 --am-c-main-bgr: #{$am-white};
1804 --am-c-main-heading-text: #{$shade-800};
1805 --am-c-main-text: #{$shade-900};
1806 // sidebar container colors - left part of the form
1807 --am-c-sb-bgr: #17295a;
1808 --am-c-sb-text: #{$am-white};
1809 // input global colors - usage input, textarea, checkbox, radio button, select input, adv select input
1810 --am-c-inp-bgr: #{$am-white};
1811 --am-c-inp-border: #{$shade-250};
1812 --am-c-inp-text: #{$shade-900};
1813 --am-c-inp-placeholder: #{$shade-500};
1814 // dropdown global colors - usage select dropdown, adv select dropdown
1815 --am-c-drop-bgr: #{$am-white};
1816 --am-c-drop-text: #{$shade-1000};
1817 // button global colors
1818 --am-c-btn-prim: #{$blue-900};
1819 --am-c-btn-prim-text: #{$am-white};
1820 --am-c-btn-sec: #{$am-white};
1821 --am-c-btn-sec-text: #{$shade-900};
1822
1823 // Properties
1824 // shortcuts
1825 // -h- height
1826 // -fs- font size
1827 // -rad- border radius
1828 --am-h-inp: 40px;
1829 --am-fs-inp: 15px;
1830 --am-rad-inp: 6px;
1831 --am-fs-label: 15px;
1832 --am-fs-btn: 15px;
1833
1834 // Font
1835 --am-font-family: 'Amelia Roboto', sans-serif;
1836 }
1837
1838 // am -- amelia
1839 // fs -- form steps
1840 // sb -- sidebar
1841 .amelia-v2-booking {
1842 background-color: transparent;
1843
1844 #amelia-container {
1845 background-color: transparent;
1846 * {
1847 font-family: var(--am-font-family);
1848 font-style: initial;
1849 box-sizing: border-box;
1850 word-break: break-word;
1851 letter-spacing: normal;
1852 }
1853
1854 &.am-fs {
1855 // Container Wrapper
1856 &__wrapper {
1857 display: flex;
1858 justify-content: center;
1859 max-width: var(--am-mw-main);
1860 width: 100%;
1861 height: 560px;
1862 margin: 100px auto;
1863 border-radius: 8px;
1864 box-shadow: 0 30px 40px rgba(0, 0, 0, 0.12);
1865 transition: max-width 0.3s ease-in-out;
1866
1867 &.am-collapsed {
1868 transition-delay: 1s;
1869 }
1870
1871 .el-form {
1872 &-item {
1873 display: block;
1874 font-family: var(--am-font-family);
1875 font-size: var(--am-fs-label);
1876 margin-bottom: 24px;
1877
1878 &__label {
1879 flex: 0 0 auto;
1880 text-align: left;
1881 font-size: var(--am-fs-label);
1882 line-height: 1.3;
1883 color: var(--am-c-main-text);
1884 box-sizing: border-box;
1885 margin: 0;
1886
1887 &:before {
1888 color: var(--am-c-error);
1889 }
1890 }
1891
1892 &__content {
1893 display: flex;
1894 flex-wrap: wrap;
1895 align-items: center;
1896 flex: 1;
1897 position: relative;
1898 font-size: var(--am-fs-inp);
1899 min-width: 0;
1900 color: var(--am-c-main-text);
1901 }
1902
1903 &__error {
1904 font-size: 12px;
1905 color: var(--am-c-error);
1906 padding-top: 4px;
1907 }
1908 }
1909 }
1910
1911 * {
1912 font-family: var(--am-font-family);
1913 box-sizing: border-box;
1914 }
1915 }
1916 }
1917 }
1918
1919 .am-no-services {
1920 box-shadow: 0 30px 40px rgba(0, 0, 0, 0.12);
1921 margin: 100px auto;
1922 text-align: center;
1923 padding: 56px;
1924 max-width: 760px;
1925 height: 460px;
1926 width: 100%;
1927 background: var(--am-c-main-bgr);
1928 border-radius: 8px;
1929
1930 p {
1931 margin-bottom: 8px;
1932 padding: 0;
1933 }
1934
1935 &-oops {
1936 font-size: 24px;
1937 line-height: 1.5;
1938 font-weight: 400;
1939 color: #354052;
1940 margin: 0;
1941 }
1942
1943 &-text {
1944 font-size: 16px;
1945 line-height: 1.5;
1946 font-weight: 400;
1947 color: #354052;
1948 &-2 {
1949 color: #354052;
1950 font-size: 14px;
1951 }
1952 }
1953
1954 a {
1955 font-size: 14px;
1956 }
1957
1958 * {
1959 font-family: 'Amelia Roboto', sans-serif;
1960 box-sizing: border-box;
1961 }
1962 }
1963 }
1964 </style>
1965