PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.20
Booking for Appointments and Events Calendar – Amelia v1.2.20
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 / admin / customize / pages / CustomizeCustomerPanel.vue
ameliabooking / v3 / src / views / admin / customize / pages Last commit date
CustomizeCatalog.vue 3 years ago CustomizeCustomerPanel.vue 2 years ago CustomizeEmployeePanel.vue 1 year ago CustomizeEventCalendar.vue 1 year ago CustomizeEventList.vue 1 year ago CustomizeSearch.vue 4 years ago CustomizeStepNew.vue 2 years ago CustomizeStepOld.vue 4 years ago
CustomizeCustomerPanel.vue
641 lines
1 <template>
2 <template v-if="!amCustomize.fonts.customFontSelected">
3 <link rel="preconnect" href="https://fonts.googleapis.com">
4 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
5 <link rel="stylesheet" :href="`${baseUrls.wpAmeliaPluginURL}v3/src/assets/scss/common/fonts/font.css`" type="text/css" media="all">
6 </template>
7 <div
8 id="amelia-container"
9 ref="ameliaContainer"
10 class="am-cap__wrapper"
11 :class="[{'am-collapsed': sidebarCollapsed}, {'am-auth': pagesType === 'auth'}]"
12 :style="cssVars"
13 >
14 <template v-if="pagesType === 'auth'">
15 <component :is="stepsArray[stepIndex]"></component>
16 </template>
17 <template v-if="pagesType === 'panel'">
18 <SideBar
19 v-if="sidebarVisibility"
20 class="am-fs-sb"
21 :class="{'am-collapsed': sidebarCollapsed}"
22 :style="{width: !sidebarCollapsed ? '240px' : '72px', paddingBottom: `${sidebarFooterHeight + 16}px` }"
23 >
24 <template #step-list>
25 <div class="am-fs-sb__page-wrapper">
26 <template
27 v-for="(step, index) in sidebarSteps"
28 :key="step.key"
29 >
30 <div
31 class="am-fs-sb__page"
32 :class="[{'selected': stepName === step.key}, {'selected': (stepName === 'packagesList' || stepName === 'packageAppointmentsList') && step.key === 'packages'}]"
33 >
34 <div
35 class="am-fs-sb__page-inner"
36 :class="{'am-collapsed': sidebarCollapsed}"
37 >
38 <div class="am-fs-sb__page-icon">
39 <span :class="`am-icon-${step.icon}`"></span>
40 </div>
41 <transition name="fade">
42 <p
43 v-if="!sidebarCollapsed"
44 class="am-fs-sb__page-heading"
45 :class="sidebarCollapseItemsClass"
46 >
47 {{ step.label }}
48 </p>
49 </transition>
50 <transition name="fade">
51 <div
52 v-if="!sidebarCollapsed"
53 class="am-fs-sb__page-indicator"
54 :class="sidebarCollapseItemsClass"
55 >
56 <span class="am-icon-arrow-big-right"></span>
57 </div>
58 </transition>
59 </div>
60 </div>
61 <div
62 v-if="index === 0"
63 class="am-fs-sb__page-divider"
64 ></div>
65 </template>
66 </div>
67 </template>
68 <template #support-info>
69 <div
70 ref="sidebarFooterRef"
71 class="am-fs-sb__footer"
72 >
73 <div
74 class="am-fs-sb__page"
75 @click="sidebarCollapsed = !sidebarCollapsed"
76 >
77 <div class="am-fs-sb__page-inner">
78 <div class="am-fs-sb__page-icon">
79 <span class="am-icon-dashboard"></span>
80 </div>
81 <transition name="fade">
82 <p
83 v-if="!sidebarCollapsed"
84 class="am-fs-sb__page-heading"
85 :class="sidebarCollapseItemsClass"
86 >
87 {{ labelsDisplay('toggle_sidebar', 'sidebar') }}
88 </p>
89 </transition>
90 </div>
91 </div>
92 <div class="am-fs-sb__page-divider"></div>
93 <div class="am-fs-sb__page">
94 <div class="am-fs-sb__page-inner">
95 <div class="am-fs-sb__page-icon">
96 <span class="am-icon-logout"></span>
97 </div>
98 <transition name="fade">
99 <p
100 v-if="!sidebarCollapsed"
101 class="am-fs-sb__page-heading"
102 :class="sidebarCollapseItemsClass"
103 >
104 {{ labelsDisplay('log_out', 'sidebar') }}
105 </p>
106 </transition>
107 </div>
108 </div>
109 </div>
110 </template>
111 </SideBar>
112 <MainContent
113 :max-width="786"
114 :old-responsive="false"
115 >
116 <template #header>
117 <MainPanelHeader>
118 <template #default>
119 <div class="am-caph">
120 <div class="am-caph__text">
121 <template v-if="stepIndex === 4">
122 {{ sidebarSteps[3].label }}
123 </template>
124 <template v-else-if="sidebarSteps[stepIndex].key === 'profile'">
125 {{ sidebarSteps[stepIndex].pageLabel }}
126 </template>
127 <template v-else>
128 {{ sidebarSteps[stepIndex].label }}
129 </template>
130 </div>
131 <div
132 v-if="!sidebarVisibility"
133 class="am-cap__menu"
134 @click="() => menuVisibility = !menuVisibility"
135 >
136 <span class="am-icon-menu"></span>
137 </div>
138 <TimeZoneSelect
139 v-if="sidebarVisibility && stepName !== 'profile' && customizedOptions.timeZone.visibility"
140 />
141 <MenuSlideDialog
142 :menu-items="sidebarSteps"
143 :monitor="stepName"
144 :visibility="menuVisibility"
145 :custom-css="cssSideMenu"
146 :width="240"
147 :customized-labels="globalStepLabels('sidebar')"
148 position="right"
149 @update:visibility="(e) => menuVisibility = e"
150 @logout="menuLogout"
151 />
152 </div>
153 </template>
154 </MainPanelHeader>
155 </template>
156 <template #step>
157 <component
158 :is="stepsArray[stepIndex]"
159 class="am-fs__main-content"
160 ></component>
161 </template>
162 </MainContent>
163 </template>
164 </div>
165 </template>
166
167 <script setup>
168 // * Form construction
169 import MainContent from "../../../common/SbsFormConstruction/MainContent/MainContent.vue";
170 import SideBar from "../../../common/SbsFormConstruction/SideBar/SideBar.vue";
171 import MainPanelHeader from "../../../common/SbsFormConstruction/MainContent/parts/MainPanelHeader.vue";
172
173 // * Form Component Collection
174 // authentication
175 import SignIn from '../steps/Cabinet/common/Authentication/SignIn.vue'
176 import SendAccessLink from '../steps/Cabinet/common/Authentication/SendAccessLink.vue'
177 import SendAccessLinkSuccess from '../steps/Cabinet/common/Authentication/SendAccessLinkSuccess.vue'
178 import SetPass from '../steps/Cabinet/common/Authentication/SetPass.vue'
179 // panel
180 import Profile from "../steps/Cabinet/common/Profile/Profile.vue"
181 import Appointments from "../steps/Cabinet/common/Appointments/Appointments.vue";
182 import Events from "../steps/Cabinet/common/Events/Events.vue";
183 import PackageAppointmentsList from "../steps/Cabinet/common/Packages/PackageAppointmentsList.vue";
184 import PackagesList from "../steps/Cabinet/common/Packages/PackagesList.vue";
185
186 // * Dedicated Components
187 import TimeZoneSelect from "../steps/Cabinet/common/parts/TimeZoneSelect.vue";
188 import MenuSlideDialog from "../../../common/SbsFormConstruction/MenuSlideDialog/MenuSlideDialog.vue";
189
190 // * import from Vue
191 import {
192 ref,
193 provide,
194 inject,
195 markRaw,
196 computed,
197 onBeforeMount,
198 onMounted,
199 readonly,
200 watchEffect
201 } from 'vue'
202
203 // * + this has to be tested for responsive usage
204 // import { useElementSize } from "@vueuse/core";
205
206 // * import composable
207 import {
208 defaultCustomizeSettings
209 } from '../../../../assets/js/common/defaultCustomize.js'
210 import {
211 useColorTransparency
212 } from '../../../../assets/js/common/colorManipulation.js'
213 import {usePopulateMultiDimensionalObject} from "../../../../assets/js/common/objectAndArrayManipulation";
214
215 let { pageNameHandler } = inject('headerFunctionality', {
216 pageNameHandler: () => 'Step-by-Step Booking Form'
217 })
218
219 pageNameHandler('Customer Panel')
220
221 // * Plugin Licence
222 // let licence = inject('licence')
223
224 // * Step index
225 let stepIndex = inject('stepIndex')
226
227 let pagesType = inject('pagesType')
228
229 const amLabels = inject('labels')
230 let amTranslations = inject('translations')
231 let langKey = inject('langKey')
232 let stepName = inject('stepName')
233 let pageRenderKey = inject('pageRenderKey')
234 let amCustomize = inject('customize')
235
236 let amFonts = computed(() => {
237 return amCustomize.value.fonts
238 })
239 provide('amFonts', amFonts)
240
241 // * Origin key
242 let originKey = ref('capc')
243 provide('originKey', originKey)
244
245 // * Cabinet Type
246 let cabinetType = ref('customer')
247 provide('cabinetType', cabinetType)
248
249 // * Autehtication steps
250 let signIn = markRaw(SignIn)
251 let sendAccessLink = markRaw(SendAccessLink)
252 let sendAccessLinkSuccess = markRaw(SendAccessLinkSuccess)
253 let setPass = markRaw(SetPass)
254
255 let authFlow = ref([
256 signIn,
257 sendAccessLink,
258 sendAccessLinkSuccess,
259 setPass
260 ])
261
262 // * Panel steps
263 let profile = markRaw(Profile)
264 let appointments = markRaw(Appointments)
265 let events = markRaw(Events)
266 let packagesList = markRaw(PackagesList)
267 let packageAppointmentsList = markRaw(PackageAppointmentsList)
268
269 let panelFlow = ref([
270 profile,
271 appointments,
272 events,
273 packagesList,
274 packageAppointmentsList
275 ])
276
277 let stepsArray = computed(() => {
278 if (pagesType.value === 'auth') return authFlow.value
279
280 return panelFlow.value
281 })
282
283 watchEffect(() => {
284 stepName.value = stepsArray.value[stepIndex.value].key
285 })
286
287 // * Component reference
288 let ameliaContainer = ref(null)
289
290 // * Plugin wrapper width
291 let containerWidth = ref()
292 provide('containerWidth', containerWidth)
293
294 // * Empty state
295 // let empty = ref(false)
296
297 // * Mobile menu
298 let menuVisibility = ref(false)
299
300 function menuLogout () {
301 menuVisibility.value = false
302 }
303
304 // * Form Sidebar Collapse
305 let sidebarCollapsed = ref(false)
306 provide('sidebarCollapsed', readonly(sidebarCollapsed))
307
308 let sidebarCollapseItemsClass = ref('')
309
310 watchEffect(() => {
311 if (sidebarCollapsed.value) {
312 setTimeout(() => {
313 sidebarCollapseItemsClass.value = 'am-collapsed'
314 }, 1000)
315 } else {
316 sidebarCollapseItemsClass.value = ''
317 }
318 })
319
320 let toggleSidebar = computed(() => {
321 return amCustomize.value.capc.sidebar.options.toggle.visibility
322 })
323
324 watchEffect( () => {
325 sidebarCollapsed.value = toggleSidebar.value
326 })
327
328 let sidebarFooterRef = ref(null)
329 let sidebarFooterHeight = ref(0)
330
331 // * Form Sidebar Visibility
332 let sidebarVisibility = ref(true)
333
334 onMounted(() => {
335 if(sidebarFooterRef.value) {
336 setTimeout(() => {
337 sidebarFooterHeight.value = sidebarFooterRef.value.offsetHeight
338 }, 200)
339 }
340
341 if (ameliaContainer.value) {
342 containerWidth.value = ameliaContainer.value.offsetWidth
343 sidebarCollapsed.value = !toggleSidebar.value ? ameliaContainer.value.offsetWidth <= 600 : toggleSidebar.value
344 sidebarVisibility.value = ameliaContainer.value.offsetWidth > 480
345 }
346 })
347
348 // * window resize listener
349 window.addEventListener('resize', resize);
350 // * resize function
351 function resize() {
352 if (ameliaContainer.value) {
353 containerWidth.value = ameliaContainer.value.offsetWidth
354 sidebarCollapsed.value = !toggleSidebar.value ? ameliaContainer.value.offsetWidth <= 600 : toggleSidebar.value
355 sidebarVisibility.value = ameliaContainer.value.offsetWidth > 480
356 menuVisibility.value = ameliaContainer.value.offsetWidth > 481 ? false : menuVisibility.value
357 }
358 }
359
360 // * Root Urls
361 const baseUrls = inject('baseUrls')
362
363 // * Array of Sidebar steps
364 const sidebarSteps = computed(() =>
365 [{
366 key: 'profile',
367 icon: 'user',
368 pageLabel: labelsDisplay('my_profile', 'profile'),
369 label: amCustomize.value.capc.profile.options.lastName.visibility ? 'John Doe' : 'John',
370 },
371 {
372 key: 'appointments',
373 icon: 'service',
374 label: labelsDisplay('appointments', 'appointments'),
375 },
376 {
377 key: 'events',
378 icon: 'star-outline',
379 label: labelsDisplay('events', 'events'),
380 },
381 {
382 key: 'packages',
383 icon: 'shipment',
384 label: labelsDisplay('packages', 'packagesList'),
385 }]
386 )
387 provide('sidebarSteps', sidebarSteps)
388
389 let sidebarIndex = ref(0)
390
391 onMounted(() => {
392 sidebarIndex.value = sidebarSteps.value.findIndex(a => a.key === stepName.value)
393
394 if (amCustomize.value.fonts.customFontSelected) {
395 activateCustomFontStyles()
396 }
397 })
398
399 // * implementation of saved labels into amTranslation object
400 let stepKey = ref('')
401
402 function savedLabelsImplementation (labelObj) {
403 Object.keys(labelObj).forEach((labelKey) => {
404 if (labelKey in amCustomize.value[pageRenderKey.value][stepKey.value].translations) {
405 labelObj[labelKey] = {...labelObj[labelKey], ...amCustomize.value[pageRenderKey.value][stepKey.value].translations[labelKey]}
406 }
407 })
408 }
409
410 function activateCustomFontStyles () {
411 let head = document.head || document.getElementsByTagName('head')[0]
412 if (head.querySelector('#amCustomFont')) {
413 head.querySelector('#amCustomFont').remove()
414 }
415
416 let css = `@font-face {font-family: '${amCustomize.value.fonts.fontFamily}'; src: url(${amCustomize.value.fonts.fontUrl});}`
417 let style = document.createElement('style')
418 head.appendChild(style)
419 style.setAttribute('type', 'text/css')
420 style.setAttribute('id', 'amCustomFont')
421 style.appendChild(document.createTextNode(css))
422 }
423
424 /**
425 * Lifecycle Hooks
426 */
427 onBeforeMount(() => {
428 window.scrollTo({
429 top: 0,
430 left: 0,
431 behavior: 'smooth'
432 })
433 Object.keys(amCustomize.value[pageRenderKey.value]).forEach(step => {
434 if (step !== 'colors' && amCustomize.value[pageRenderKey.value][step].translations) {
435 stepKey.value = step
436 usePopulateMultiDimensionalObject('labels', amTranslations[pageRenderKey.value][step], savedLabelsImplementation)
437 }
438 })
439 })
440
441 // * Label computed function
442 function labelsDisplay (label, stepKey) {
443 let computedLabel = computed(() => {
444 return amCustomize.value[pageRenderKey.value][stepKey].translations
445 && amCustomize.value[pageRenderKey.value][stepKey].translations[label]
446 && amCustomize.value[pageRenderKey.value][stepKey].translations[label][langKey.value]
447 ? amCustomize.value[pageRenderKey.value][stepKey].translations[label][langKey.value]
448 : amLabels[label]
449 })
450
451 return computedLabel.value
452 }
453
454 // * Step labels passed to inner components
455 function globalStepLabels (step) {
456 let stepLabels = {}
457 let customizedLabels = step ? amCustomize.value[pageRenderKey.value][step].translations : null
458 if (customizedLabels && Object.keys(customizedLabels)) {
459 Object.keys(amCustomize.value[pageRenderKey.value][step].translations).forEach(label => {
460 stepLabels[label] = amCustomize.value[pageRenderKey.value][step].translations[label][langKey.value]
461 })
462 } else {
463 stepLabels = {}
464 }
465
466 return stepLabels
467 }
468
469 // * Custmozes Options
470 let customizedOptions = computed(() => {
471 if (stepName.value === 'packageAppointmentsList') return amCustomize.value[pageRenderKey.value].packagesList.options
472 return amCustomize.value[pageRenderKey.value][stepName.value].options
473 })
474
475 onBeforeMount(() => {})
476
477 // * Colors block
478 let amColors = computed(() => {
479 return amCustomize.value[pageRenderKey.value] ? amCustomize.value[pageRenderKey.value].colors : defaultCustomizeSettings[pageRenderKey.value].colors
480 })
481 provide('amColors', amColors);
482
483 let cssVars = computed(() => {
484 return {
485 '--am-c-primary': amColors.value.colorPrimary,
486 '--am-c-success': amColors.value.colorSuccess,
487 '--am-c-error': amColors.value.colorError,
488 '--am-c-warning': amColors.value.colorWarning,
489 '--am-c-main-bgr': amColors.value.colorMainBgr,
490 '--am-c-main-heading-text': amColors.value.colorMainHeadingText,
491 '--am-c-main-text': amColors.value.colorMainText,
492 '--am-c-sb-bgr': amColors.value.colorSbBgr,
493 '--am-c-sb-text': amColors.value.colorSbText,
494 '--am-c-inp-bgr': amColors.value.colorInpBgr,
495 '--am-c-inp-border': amColors.value.colorInpBorder,
496 '--am-c-inp-text': amColors.value.colorInpText,
497 '--am-c-inp-placeholder': amColors.value.colorInpPlaceHolder,
498 '--am-c-drop-bgr': amColors.value.colorDropBgr,
499 '--am-c-drop-text': amColors.value.colorDropText,
500 '--am-c-btn-prim': amColors.value.colorBtnPrim,
501 '--am-c-btn-prim-text': amColors.value.colorBtnPrimText,
502 '--am-c-btn-sec': amColors.value.colorBtnSec,
503 '--am-c-btn-sec-text': amColors.value.colorBtnSecText,
504 '--am-c-skeleton-op20': useColorTransparency(amColors.value.colorMainText, 0.2),
505 '--am-c-skeleton-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
506 '--am-c-skeleton-sb-op20': useColorTransparency(amColors.value.colorSbText, 0.2),
507 '--am-c-skeleton-sb-op60': useColorTransparency(amColors.value.colorSbText, 0.6),
508 '--am-font-family': amFonts.value.fontFamily,
509
510 // css properties
511 '--am-rad-input': '6px',
512 '--am-fs-input': '15px',
513 // -mw- max width
514 // -brad- border-radius
515 '--am-mw-main': sidebarVisibility.value ? sidebarCollapsed.value ? '858px' : '1024px' : '520px',
516 '--am-brad-main': sidebarVisibility.value ? '0 0.5rem 0.5rem 0' : '0.5rem'
517 }
518 })
519
520 let cssSideMenu = computed(() => {
521 return {
522 '--am-c-msd-bgr': amColors.value.colorSbBgr,
523 '--am-c-msd-text': amColors.value.colorSbText,
524 '--am-c-msd-text-op05': useColorTransparency(amColors.value.colorSbText, 0.05),
525 '--am-c-msd-text-op10': useColorTransparency(amColors.value.colorSbText, 0.1),
526 '--am-c-msd-text-op60': useColorTransparency(amColors.value.colorSbText, 0.6),
527 }
528 })
529 </script>
530
531 <script>
532
533 export default {
534 name: "CustomerPanel",
535 }
536 </script>
537
538 <style lang="scss">
539 @import '../../../../assets/scss/public/overides/overides';
540 @import '../../../../assets/scss/common/icon-fonts/style';
541 @import '../../../../assets/scss/common/empty-state/_empty-state-mixin.scss';
542 @import '../../../../assets/scss/common/transitions/_transitions-mixin.scss';
543
544 #amelia-app-backend-new {
545 background-color: transparent;
546
547 #amelia-container {
548 background-color: transparent;
549
550 * {
551 font-family: var(--am-font-family);
552 font-style: initial;
553 box-sizing: border-box;
554 }
555
556 // cap - cabinet panel
557 &.am-cap {
558 // Container Wrapper
559 &__wrapper {
560 display: flex;
561 justify-content: center;
562 max-width: var(--am-mw-main);
563 width: 100%;
564 height: 700px;
565 margin: 16px auto 100px;
566 border-radius: 8px;
567 box-shadow: 0 30px 40px rgba(0, 0, 0, 0.12);
568 transition: max-width 0.3s ease-in-out;
569
570 &.am-auth {
571 box-shadow: unset;
572 height: auto;
573 }
574
575 &.am-collapsed {
576 transition-delay: 1s;
577 }
578
579 * {
580 font-family: var(--am-font-family);
581 box-sizing: border-box;
582 }
583 }
584 }
585
586 .am-cap {
587 &__header {
588 display: flex;
589 align-items: center;
590 justify-content: space-between;
591
592 .am-select-wrapper {
593 max-width: 250px;
594 }
595 }
596 }
597
598 .am-asi {
599 .el-form {
600 &-item {
601 display: block;
602 font-family: var(--am-font-family);
603 font-size: var(--am-fs-label);
604 margin-bottom: 24px;
605
606 &__label {
607 flex: 0 0 auto;
608 text-align: left;
609 font-size: var(--am-fs-label);
610 line-height: 1.3;
611 color: var(--am-c-main-text);
612 box-sizing: border-box;
613 margin: 0;
614
615 &:before {
616 color: var(--am-c-error);
617 }
618 }
619
620 &__content {
621 display: flex;
622 flex-wrap: wrap;
623 align-items: center;
624 flex: 1;
625 position: relative;
626 font-size: var(--am-fs-input);
627 min-width: 0;
628 }
629
630 &__error {
631 font-size: 12px;
632 color: var(--am-c-error);
633 padding-top: 4px;
634 }
635 }
636 }
637 }
638 }
639 }
640 </style>
641