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