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