PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.6
Booking for Appointments and Events Calendar – Amelia v2.4.6
2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / views / public / Cabinet / common / Authentication / parts / SignIn.vue
ameliabooking / v3 / src / views / public / Cabinet / common / Authentication / parts Last commit date
SendAccessLink.vue 1 month ago SendAccessLinkSuccess.vue 1 month ago SetPass.vue 1 month ago SetPassSuccess.vue 1 month ago SignIn.vue 1 month ago Skeleton.vue 1 month ago
SignIn.vue
1014 lines
1 <template>
2 <div
3 v-if="!loading"
4 ref="signInContainerRef"
5 class="am-asi"
6 :style="cssVars"
7 role="region"
8 aria-labelledby="am-asi-signin-title"
9 aria-describedby="am-asi-signin-description"
10 >
11 <div class="am-asi__top">
12 <AmAlert
13 v-if="profileDeleted"
14 ref="profileDeletedAlertRef"
15 class="am-asi__top-message am-asi__top-message-success"
16 type="success"
17 :title="amLabels.profile_deleted"
18 :description="''"
19 :show-icon="true"
20 :closable="true"
21 @close="store.commit('auth/setProfileDeleted', false)"
22 ></AmAlert>
23
24 <AmAlert
25 v-if="authError"
26 ref="authErrorAlertRef"
27 class="am-asi__top-message am-asi__top-message-error"
28 type="error"
29 :title="authErrorMessage"
30 :description="''"
31 :show-icon="true"
32 :closable="true"
33 @close="store.commit('auth/setProfileDeleted', false)"
34 ></AmAlert>
35 <div id="am-asi-signin-title" class="am-asi__header">
36 {{ amLabels.welcome_back }}
37 </div>
38 <div id="am-asi-signin-description" class="am-asi__text">
39 {{ amLabels.enter_credentials }}
40 </div>
41 </div>
42
43 <!-- Social Buttons -->
44 <div
45 v-if="
46 (settings.socialLogin.googleLoginEnabled && settings.general.googleClientId) ||
47 (settings.socialLogin.facebookLoginEnabled &&
48 settings.socialLogin.facebookCredentialsEnabled)
49 "
50 >
51 <div class="am-asi__social-wrapper">
52 <am-social-button :provider="socialProvider" @social-action="onSignupSocial" />
53 </div>
54
55 <!-- Social Divider -->
56 <div class="am-asi__social-divider">
57 <span class="par-sm">{{ amLabels.or_enter_details_below }}</span>
58 </div>
59 <!-- /Social Divider -->
60 </div>
61 <!-- /Social Buttons -->
62
63 <el-form
64 ref="authFormRef"
65 :model="infoFormData"
66 :rules="infoFormRules"
67 label-position="top"
68 class="am-asi__form"
69 :class="responsiveClass"
70 @submit.prevent="submitForm"
71 >
72 <template v-for="(item, index) in signInFormConstruction" :key="index">
73 <component
74 :is="item.template"
75 ref="customerCollectorRef"
76 v-model="infoFormData[index]"
77 v-bind="item.props"
78 @enter="submitForm"
79 ></component>
80 </template>
81 </el-form>
82
83 <AmButton
84 class="am-asi__btn"
85 :type="amCustomize.signIn.options.signInBtn.buttonType"
86 @click="submitForm"
87 >
88 {{ amLabels.sign_in }}
89 </AmButton>
90
91 <div class="am-asi__footer">
92 <span class="am-asi__footer-text">
93 {{ amLabels.forgot_your_password }}
94 </span>
95 <AmButton
96 class="am-asi__footer-reset__btn"
97 type="text"
98 size="small"
99 @click="goToResetPassword"
100 >
101 {{ amLabels.reset_password }}
102 </AmButton>
103 </div>
104
105 <div
106 v-if="
107 amSettings.general.googleRecaptcha.enabled &&
108 amSettings.roles[cabinetType + 'Cabinet'].googleRecaptcha
109 "
110 id="am-recaptcha"
111 class="am-recaptcha-holder"
112 >
113 <vue-recaptcha
114 ref="recaptchaRef"
115 :size="amSettings.general.googleRecaptcha.invisible ? 'invisible' : null"
116 :load-recaptcha-script="true"
117 :sitekey="amSettings.general.googleRecaptcha.siteKey"
118 @verify="onRecaptchaVerify"
119 @expired="onRecaptchaExpired"
120 >
121 </vue-recaptcha>
122 </div>
123 </div>
124 <Skeleton v-else :count="4" :center-first="true" :main-class="'am-asi-sign-in am-asi'"></Skeleton>
125 </template>
126
127 <script setup>
128 // * import from Vue
129 import {
130 ref,
131 reactive,
132 computed,
133 inject,
134 onMounted,
135 markRaw,
136 onBeforeMount,
137 watch,
138 nextTick,
139 } from 'vue'
140 import VueAuthenticate from 'vue-authenticate'
141 import { VueRecaptcha } from 'vue-recaptcha'
142
143 // * Import from Vuex
144 import { useStore } from 'vuex'
145
146 // * Import from Libraries
147 import httpClient from '../../../../../../plugins/axios'
148
149 // * Composables
150 import { useResponsiveClass } from '../../../../../../assets/js/common/responsive.js'
151 import { useColorTransparency } from '../../../../../../assets/js/common/colorManipulation'
152 import {
153 useRemoveUrlParameter,
154 useUrlQueryParams,
155 useUrlQueryParam,
156 } from '../../../../../../assets/js/common/helper'
157 import { useDisableAuthorizationHeader } from '../../../../../../assets/js/public/panel'
158 import { SocialAuthOptions } from '../../../../../../assets/js/admin/socialAuthOptions'
159 import {
160 useParsedCustomPricing,
161 useFrontendEmployee,
162 useFrontendEmployeeServiceList,
163 useFeaturesAndIntegrations,
164 } from '../../../../../../assets/js/common/employee'
165
166 // * Components
167 import { formFieldsTemplates } from '../../../../../../assets/js/common/formFieldsTemplates'
168 import AmButton from '../../../../../_components/button/AmButton.vue'
169 import Skeleton from './Skeleton'
170 import AmAlert from '../../../../../_components/alert/AmAlert.vue'
171 import IconComponent from '../../../../../_components/icons/IconComponent.vue'
172 import { useGoogleSync } from '../../../../../../assets/js/common/integrationGoogle'
173 import { useOutlookSync } from '../../../../../../assets/js/common/integrationOutlook'
174 import { useZoomUsers } from '../../../../../../assets/js/common/integrationZoom'
175 import { useStripeSync } from '../../../../../../assets/js/common/integrationStripe'
176 import { useAppleSync } from '../../../../../../assets/js/common/integrationApple'
177 import AmSocialButton from '../../../../../common/FormFields/AmSocialButton.vue'
178 import { settings } from '../../../../../../plugins/settings'
179
180 // * Plugin Licence
181 let licence = inject('licence')
182
183 // * Vars
184 let store = useStore()
185
186 // * Root Settings
187 const amSettings = inject('settings')
188
189 // * Customized form data
190 let amCustomize = inject('amCustomize')
191
192 // * labels
193 const labels = inject('labels')
194
195 // * local language short code
196 const localLanguage = inject('localLanguage')
197
198 // * if local lang is in settings lang
199 let langDetection = computed(() => amSettings.general.usedLanguages.includes(localLanguage.value))
200
201 // * Computed labels
202 let amLabels = computed(() => {
203 let computedLabels = reactive({ ...labels })
204
205 let customizedLabels = amCustomize.value.signIn.translations
206 if (customizedLabels) {
207 Object.keys(customizedLabels).forEach((labelKey) => {
208 if (customizedLabels[labelKey][localLanguage.value] && langDetection.value) {
209 computedLabels[labelKey] = customizedLabels[labelKey][localLanguage.value]
210 } else if (customizedLabels[labelKey].default) {
211 computedLabels[labelKey] = customizedLabels[labelKey].default
212 }
213 })
214 }
215 return computedLabels
216 })
217
218 // * Icon components
219 let emailIcon = {
220 components: { IconComponent },
221 template: `<IconComponent icon="email"></IconComponent>`,
222 }
223
224 let passwordIcon = {
225 components: { IconComponent },
226 template: `<IconComponent icon="password"></IconComponent>`,
227 }
228
229 let socialProvider = ref('')
230 const VueAuthenticateInstance = VueAuthenticate.factory(httpClient, SocialAuthOptions)
231
232 /********
233 * Google Sign in *
234 ********/
235 function onSignupSocial({ provider, credentials }) {
236 store.commit('setLoading', true)
237 const socialCheckUrl = `/users/authentication/${provider}`
238 const data = {}
239 socialProvider = provider
240 data.cabinetType = cabinetType.value
241
242 if (provider === 'facebook') {
243 data.redirectUri = VueAuthenticateInstance.options.providers[provider].redirectUri
244 VueAuthenticateInstance.options.providers[provider].url = `${socialCheckUrl}`
245 store.commit('setLoading', false)
246 VueAuthenticateInstance.authenticate(provider, data)
247 .then((response) => {
248 infoFormData.value.email = response.data.data.user.email
249 setResponseData(response, true)
250 })
251 .catch((error) => {
252 if (!VueAuthenticateInstance.isAuthenticated()) {
253 authError.value = true
254 authErrorMessage.value = 'User is not authenticated.'
255 store.commit('setLoading', false)
256 }
257 if (error.response?.data) {
258 authError.value = true
259 authErrorMessage.value = error.response.data.message
260 }
261 store.commit('setLoading', false)
262 })
263 }
264
265 if (provider === 'google') {
266 data.code = credentials
267 httpClient
268 .post(`${socialCheckUrl}`, data)
269 .then((response) => {
270 infoFormData.value.email = response.data.data.user.email
271 setResponseData(response, true)
272 })
273 .catch((error) => {
274 if (!('data' in error.response.data) && 'message' in error.response.data) {
275 authError.value = true
276 authErrorMessage.value = error.response.data.message
277 return
278 }
279
280 if ('invalid_credentials' in error.response.data.data) {
281 authError.value = true
282 authErrorMessage.value = amLabels.value.invalid_credentials
283 }
284 })
285 .finally(() => {
286 store.commit('setLoading', false)
287 })
288 }
289 }
290
291 function setResponseData(response, authenticate) {
292 if ('user' in response.data.data && response.data.data.user.type === 'provider') {
293 setEmployee(response)
294 }
295
296 store.commit('auth/setProfile', response.data.data.user)
297
298 if (response.data.data.user.timeZone) {
299 store.commit('cabinet/setTimeZone', response.data.data.user.timeZone)
300 }
301
302 if (
303 !response.data.data.user.countryPhoneIso &&
304 amSettings.general.phoneDefaultCountryCode &&
305 amSettings.general.phoneDefaultCountryCode !== 'auto'
306 ) {
307 store.commit('auth/setProfileCountryPhoneIso', amSettings.general.phoneDefaultCountryCode)
308 }
309
310 if (authenticate) {
311 store.commit('auth/setAuthenticated', authenticate)
312 }
313
314 let tokenValidTime =
315 cabinetType.value === 'customer'
316 ? amSettings.roles.customerCabinet.tokenValidTime * 1000
317 : amSettings.roles.providerCabinet.tokenValidTime * 1000
318
319 if (tokenValidTime > 0 && tokenValidTime < 1814400000) {
320 setTimeout(() => {
321 store.dispatch('auth/logout')
322 }, tokenValidTime)
323 }
324
325 if (window?.ameliaBooking?.cabinet?.userLoggedIn) {
326 window.ameliaBooking.cabinet.userLoggedIn(response.data.data.user)
327 }
328 }
329
330 /*************
331 * Recaptcha *
332 ************/
333
334 let recaptchaRef = ref(null)
335
336 let recaptchaValid = ref(false)
337
338 let recaptchaResponse = ref(null)
339
340 function onRecaptchaExpired() {
341 recaptchaValid.value = false
342
343 authError.value = true
344 authErrorMessage.value = amLabels.value.recaptcha_invalid_error
345 }
346
347 function onRecaptchaVerify(response) {
348 recaptchaValid.value = true
349
350 recaptchaResponse.value = response
351
352 if (amSettings.general.googleRecaptcha.invisible) {
353 continueWithAuthenticate()
354 }
355 }
356
357 /********
358 * Form *
359 ********/
360 // * Form reference
361 let authFormRef = ref(null)
362
363 // * Form data
364 let infoFormData = ref({
365 email: computed({
366 get: () => store.getters['auth/getEmail'],
367 set: (val) => {
368 store.commit('auth/setEmail', val ? val : '')
369 },
370 }),
371 password: computed({
372 get: () => store.getters['auth/getPassword'],
373 set: (val) => {
374 store.commit('auth/setPassword', val ? val : '')
375 },
376 }),
377 })
378
379 // * Form validation rules
380 let infoFormRules = ref({
381 email: [
382 {
383 required: true,
384 message: amLabels.value.enter_email_or_username_warning,
385 trigger: 'submit',
386 },
387 ],
388 password: [
389 {
390 required: true,
391 message: amLabels.value.enter_password_warning,
392 trigger: 'submit',
393 },
394 ],
395 })
396
397 // * Form construction
398 let signInFormConstruction = ref({
399 email: {
400 template: formFieldsTemplates.text,
401 props: {
402 itemName: 'email',
403 label: amLabels.value.email_or_username,
404 prefixIcon: markRaw(emailIcon),
405 placeholder: '',
406 class: 'am-asi__item',
407 },
408 },
409 password: {
410 template: formFieldsTemplates.text,
411 props: {
412 itemName: 'password',
413 itemType: 'password',
414 showPassword: true,
415 label: amLabels.value.password,
416 prefixIcon: markRaw(passwordIcon),
417 placeholder: '',
418 class: 'am-asi__item',
419 },
420 },
421 })
422
423 // * Page key
424 let pageKey = inject('pageKey')
425
426 // * Cabinet type
427 let cabinetType = inject('cabinetType')
428
429 // * Loading
430 let loading = computed(() => {
431 return store.getters['getLoading']
432 })
433
434 let profileDeleted = computed(() => store.getters['auth/getProfileDeleted'])
435
436 // * Sign in error alert
437 let authError = ref(false)
438 let authErrorMessage = ref('')
439 let signInContainerRef = ref(null)
440 let authErrorAlertRef = ref(null)
441 let profileDeletedAlertRef = ref(null)
442 let hasInitialFocus = ref(false)
443
444 function focusFirstInput() {
445 if (!signInContainerRef.value) {
446 return
447 }
448
449 // Focus the first interactive field when form is initially rendered.
450 const firstInput = signInContainerRef.value.querySelector(
451 '.am-asi__form input, .am-asi__form textarea, .am-asi__form select',
452 )
453 if (firstInput) {
454 firstInput.focus()
455 }
456 }
457
458 function goToResetPassword() {
459 pageKey.value = 'sendAccessLink'
460 }
461
462 function useAuthenticateUser(changePass) {
463 let urlToken = useUrlQueryParam('token')
464
465 if (urlToken) {
466 useAuthenticate(urlToken, true, false, changePass)
467 } else {
468 useAuthenticate(null, false, true, false)
469 }
470 }
471
472 function setEmployee(response) {
473 let employee = {
474 id: response.data.data.user.id,
475 firstName: response.data.data.user.firstName,
476 lastName: response.data.data.user.lastName,
477 email: response.data.data.user.email,
478 phone: response.data.data.user.phone,
479 countryPhoneIso: response.data.data.user.countryPhoneIso,
480 googleCalendar: {
481 id:
482 'id' in response.data.data.user.googleCalendar
483 ? response.data.data.user.googleCalendar.id
484 : null,
485 calendarId: response.data.data.user.googleCalendar.calendarId
486 ? response.data.data.user.googleCalendar.calendarId
487 : '',
488 token:
489 'token' in response.data.data.user.googleCalendar
490 ? response.data.data.user.googleCalendar.token
491 : null,
492 accounts: response.data.data.user.googleCalendar.accounts || [],
493 blockedCalendars: response.data.data.user.googleCalendar.blockedCalendars || [],
494 calendarList: response.data.data.user.googleCalendar.calendarList || [],
495 },
496 outlookCalendar: {
497 id:
498 'id' in response.data.data.user.outlookCalendar
499 ? response.data.data.user.outlookCalendar.id
500 : null,
501 calendarId: response.data.data.user.outlookCalendar.calendarId
502 ? response.data.data.user.outlookCalendar.calendarId
503 : '',
504 token:
505 'token' in response.data.data.user.outlookCalendar
506 ? response.data.data.user.outlookCalendar.token
507 : null,
508 accounts: response.data.data.user.outlookCalendar.accounts || [],
509 blockedCalendars: response.data.data.user.outlookCalendar.blockedCalendars || [],
510 calendarList: response.data.data.user.outlookCalendar.calendarList || [],
511 },
512 appleCalendarId: response.data.data.user.appleCalendarId
513 ? response.data.data.user.appleCalendarId
514 : '',
515 googleCalendarId: response.data.data.user.googleCalendarId
516 ? response.data.data.user.googleCalendarId
517 : '',
518 outlookCalendarId: response.data.data.user.outlookCalendarId
519 ? response.data.data.user.outlookCalendarId
520 : '',
521 stripeConnect: response.data.data.user.stripeConnect,
522 zoomUserId: response.data.data.user.zoomUserId ? response.data.data.user.zoomUserId : '',
523 locationId: response.data.data.user.locationId,
524 pictureFullPath: response.data.data.user.pictureFullPath,
525 pictureThumbPath: response.data.data.user.pictureThumbPath,
526 description: response.data.data.user.description,
527 weekDayList: response.data.data.user.weekDayList,
528 specialDayList: response.data.data.user.specialDayList,
529 dayOffList: response.data.data.user.dayOffList,
530 serviceList: response.data.data.user.serviceList,
531 badgeId: response.data.data.user.badgeId ?? null,
532 }
533
534 employee.serviceList.forEach((employeeService) => {
535 useFeaturesAndIntegrations(employeeService, true)
536 })
537
538 store.commit(
539 'employee/setSavedSpecialDayList',
540 JSON.parse(JSON.stringify(response.data.data.user.specialDayList)),
541 )
542
543 store.commit(
544 'employee/setSavedDayOffList',
545 JSON.parse(JSON.stringify(response.data.data.user.dayOffList)),
546 )
547
548 employee.serviceList.forEach((employeeService) => {
549 useParsedCustomPricing(employeeService)
550 })
551
552 if (store.getters['entities/getReady']) {
553 store.commit('entities/setEmployees', [JSON.parse(JSON.stringify(employee))])
554
555 employee.serviceList = useFrontendEmployeeServiceList(store, employee.serviceList)
556 }
557
558 store.commit('employee/setEmployee', useFrontendEmployee(store, employee))
559
560 store.commit(
561 'auth/setOutlookCalendars',
562 response.data.data.user.outlookCalendar?.calendarList
563 ? response.data.data.user.outlookCalendar.calendarList
564 : [],
565 )
566
567 store.commit(
568 'auth/setGoogleCalendars',
569 response.data.data.user.googleCalendar?.calendarList
570 ? response.data.data.user.googleCalendar.calendarList
571 : [],
572 )
573
574 if (amSettings.appleCalendar.enabled && !licence.isLite && !licence.isStarter) {
575 useAppleSync(store)
576 }
577
578 if (
579 amSettings.payments.stripe.enabled &&
580 amSettings.payments.stripe.connect.enabled &&
581 !licence.isLite &&
582 !licence.isStarter &&
583 !licence.isBasic
584 ) {
585 useStripeSync(store)
586 }
587
588 if (amSettings.zoom.enabled && !licence.isLite && !licence.isStarter) {
589 useZoomUsers(store)
590 }
591 }
592
593 function useAuthenticate(tokenValue, isUrlToken, checkIfWpUser, changePass) {
594 let params = { cabinetType: cabinetType.value, changePass: changePass }
595
596 if (recaptchaResponse.value !== null) {
597 params.recaptcha = recaptchaResponse.value
598 }
599
600 if (checkIfWpUser) {
601 params.checkIfWpUser = true
602 }
603
604 let password = store.getters['auth/getPassword']
605
606 let email = store.getters['auth/getEmail']
607
608 if (!tokenValue && password && email) {
609 params.password = password
610 params.email = email
611 }
612
613 if (isUrlToken) {
614 params.token = tokenValue
615 }
616
617 httpClient
618 .post(
619 '/users/authenticate',
620 params,
621 tokenValue !== null && !useDisableAuthorizationHeader()
622 ? { headers: { Authorization: 'Bearer ' + tokenValue } }
623 : {},
624 )
625 .then((response) => {
626 if ('authentication_required' in response.data.data) {
627 if (!amSettings.roles[cabinetType.value + 'Cabinet']['loginEnabled']) {
628 pageKey.value = 'sendAccessLink'
629 }
630
631 return
632 }
633
634 setResponseData(response, false)
635
636 if (useUrlQueryParam('token')) {
637 window.history.replaceState(
638 null,
639 null,
640 useRemoveUrlParameter(window.location.href, 'token'),
641 )
642 }
643
644 if ('set_password' in response.data.data && response.data.data.set_password) {
645 pageKey.value = 'setPassword'
646 } else if ('change_password' in response.data.data && response.data.data.change_password) {
647 pageKey.value = 'setPassword'
648 } else {
649 store.commit('auth/setAuthenticated', true)
650 }
651 })
652 .catch((error) => {
653 if (!('data' in error.response.data) && 'message' in error.response.data) {
654 authError.value = true
655 authErrorMessage.value = error.response.data.message
656 return
657 }
658
659 if ('invalid_credentials' in error.response.data.data) {
660 authError.value = true
661 authErrorMessage.value = amLabels.value.invalid_credentials
662 }
663
664 if ('recaptcha_error' in error.response.data.data) {
665 authError.value = true
666 authErrorMessage.value = amLabels.value.recaptcha_error
667 }
668 })
669 .finally(() => {
670 store.commit('setLoading', false)
671 })
672 }
673
674 // * Submit Form
675 function submitForm() {
676 authFormRef.value.validate((valid) => {
677 if (valid) {
678 if (
679 amSettings.general.googleRecaptcha.enabled &&
680 amSettings.roles[cabinetType.value + 'Cabinet'].googleRecaptcha
681 ) {
682 if (amSettings.general.googleRecaptcha.invisible) {
683 recaptchaRef.value.execute()
684 } else if (!recaptchaValid.value) {
685 authError.value = true
686 authErrorMessage.value = amLabels.value.recaptcha_error
687
688 return false
689 } else {
690 continueWithAuthenticate()
691 }
692 } else {
693 continueWithAuthenticate()
694 }
695 } else {
696 return false
697 }
698 })
699 }
700
701 function continueWithAuthenticate() {
702 store.commit('setLoading', true)
703
704 useAuthenticate(null, false, false, false)
705 }
706
707 function authenticate() {
708 useAuthenticateUser('changePass' in useUrlQueryParams(window.location.href))
709 }
710
711 onBeforeMount(() => {
712 store.commit('setLoading', true)
713 })
714
715 onMounted(() => {
716 if (!store.getters['auth/getLoggedOut']) {
717 let queryParams = useUrlQueryParams(window.location.href)
718
719 if (queryParams && queryParams['iss'] && !queryParams['code']) {
720 window.history.replaceState(null, null, useRemoveUrlParameter(window.location.href, 'iss'))
721 }
722
723 if (
724 amSettings.googleCalendar.enabled &&
725 cabinetType.value === 'provider' &&
726 queryParams &&
727 queryParams['code'] &&
728 queryParams['scope']
729 ) {
730 useGoogleSync(queryParams['code'], authenticate)
731 } else if (
732 amSettings.outlookCalendar.enabled &&
733 cabinetType.value === 'provider' &&
734 queryParams &&
735 queryParams['code'] &&
736 queryParams['state']
737 ) {
738 useOutlookSync(queryParams['code'], authenticate)
739 } else {
740 authenticate()
741 }
742 } else {
743 if (!amSettings.roles[cabinetType.value + 'Cabinet']['loginEnabled']) {
744 pageKey.value = 'sendAccessLink'
745 }
746 store.commit('setLoading', false)
747 }
748 })
749
750 watch(loading, (isLoading) => {
751 if (!isLoading && !hasInitialFocus.value) {
752 hasInitialFocus.value = true
753 nextTick(() => {
754 focusFirstInput()
755 })
756 }
757 })
758
759 watch(authError, (hasError) => {
760 if (hasError) {
761 nextTick(() => {
762 authErrorAlertRef.value?.focus()
763 })
764 }
765 })
766
767 watch(profileDeleted, (isDeleted) => {
768 if (isDeleted) {
769 nextTick(() => {
770 profileDeletedAlertRef.value?.focus()
771 })
772 }
773 })
774 /*************
775 * Customize *
776 *************/
777
778 // * Responsive - Container Width
779 let cWidth = inject('containerWidth')
780
781 let responsiveClass = computed(() => useResponsiveClass(cWidth.value))
782
783 // * Fonts
784 let amFonts = inject('amFonts')
785
786 // * Colors block
787 let amColors = inject('amColors')
788
789 let cssVars = computed(() => {
790 return {
791 '--am-c-primary': amColors.value.colorPrimary,
792 '--am-c-success': amColors.value.colorSuccess,
793 '--am-c-error': amColors.value.colorError,
794 '--am-c-warning': amColors.value.colorWarning,
795 '--am-c-main-bgr': amColors.value.colorMainBgr,
796 '--am-c-main-heading-text': amColors.value.colorMainHeadingText,
797 '--am-c-main-text': amColors.value.colorMainText,
798 '--am-c-main-text-op70': useColorTransparency(amColors.value.colorMainText, 0.7),
799 '--am-c-main-text-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
800 '--am-c-main-text-op40': useColorTransparency(amColors.value.colorMainText, 0.4),
801 '--am-c-main-text-op25': useColorTransparency(amColors.value.colorMainText, 0.25),
802 '--am-c-inp-bgr': amColors.value.colorInpBgr,
803 '--am-c-inp-border': amColors.value.colorInpBorder,
804 '--am-c-inp-text': amColors.value.colorInpText,
805 '--am-c-inp-placeholder': amColors.value.colorInpPlaceHolder,
806 '--am-c-btn-prim': amColors.value.colorBtnPrim,
807 '--am-c-btn-prim-text': amColors.value.colorBtnPrimText,
808 '--am-c-skeleton-op20': useColorTransparency(amColors.value.colorMainText, 0.2),
809 '--am-c-skeleton-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
810 '--am-font-family': amFonts.value.fontFamily,
811
812 '--am-c-scroll-op30': useColorTransparency(amColors.value.colorPrimary, 0.3),
813 '--am-c-scroll-op10': useColorTransparency(amColors.value.colorPrimary, 0.1),
814 }
815 })
816 </script>
817
818 <script>
819 export default {
820 name: 'AuthSignIn',
821 }
822 </script>
823
824 <style lang="scss">
825 // am - amelia
826 // asi - authentication sign in
827 @mixin auth {
828 .am-asi {
829 max-width: 400px;
830 width: 100%;
831 background-color: var(--am-c-main-bgr);
832 box-shadow:
833 0 0 9px -4px var(--am-c-main-text-op40),
834 0px 17px 35px -12px var(--am-c-main-text-op25);
835 border-radius: 12px;
836 padding: 32px 24px 24px;
837 margin: 0 auto;
838 font-family: var(--am-font-family), sans-serif;
839
840 .am-recaptcha-holder {
841 justify-items: center;
842 }
843
844 * {
845 font-family: var(--am-font-family), sans-serif;
846 box-sizing: border-box;
847 }
848
849 &__social-wrapper {
850 display: flex;
851 align-items: center;
852 flex-direction: column;
853 width: 100%;
854 margin: 8px 0 24px;
855 gap: 8px;
856
857 .am-social-signin {
858 &__google {
859 #g_id_onload {
860 display: none;
861 }
862 .g_id_signin {
863 width: 64px;
864 }
865 }
866 }
867 }
868
869 &__social-divider {
870 align-items: center;
871 display: flex;
872 margin-bottom: 24px;
873
874 // Before & After
875 &:before,
876 &:after {
877 background: var(--shade-250, #d1d5d7);
878 content: '';
879 height: 1px;
880 width: 100%;
881 }
882
883 span {
884 flex: none;
885 font-size: 15px;
886 font-style: normal;
887 font-weight: 400;
888 line-height: 24px;
889 color: var(--shade-500, #808a90);
890 margin-left: 8px;
891 margin-right: 8px;
892 }
893 }
894
895 &__top {
896 display: flex;
897 flex-direction: column;
898 align-items: center;
899 margin: 0 0 32px;
900
901 &-message {
902 width: 100%;
903 margin-bottom: 22px;
904 font-weight: 500;
905 font-size: 16px;
906 line-height: 1.5;
907 border-width: 1px;
908 border-bottom-width: 4px;
909 border-style: solid;
910 box-shadow: 0 2px 3px rgba(26, 44, 55, 0.1);
911 border-radius: 5px;
912
913 &-success {
914 border-color: var(--am-c-success);
915 }
916
917 &-error {
918 border-color: var(--am-c-error);
919 }
920
921 .el-alert {
922 &--success,
923 &--error {
924 border: none;
925 }
926
927 &--success {
928 .el-icon {
929 color: var(--am-c-success);
930 }
931 }
932
933 &__closebtn {
934 font-size: 16px;
935 color: var(--am-c-main-text);
936 }
937 }
938 }
939 }
940
941 &__header {
942 font-size: 24px;
943 font-weight: 500;
944 line-height: 1.33333;
945 color: var(--am-c-main-heading-text);
946 margin: 0 0 4px;
947 }
948
949 &__text {
950 font-size: 15px;
951 font-weight: 400;
952 line-height: 1.6;
953 text-align: center;
954 color: var(--am-c-main-text-op70);
955 }
956
957 &__form {
958 .am-ff {
959 &__item {
960 &-label {
961 font-size: 15px;
962 font-weight: 500;
963 color: var(--am-c-main-text);
964 }
965 }
966 }
967
968 .el-form {
969 &-item {
970 margin-bottom: 30px;
971
972 &__label {
973 margin: 0 0 4px;
974 }
975 }
976 }
977 }
978
979 &__btn {
980 width: 100%;
981 margin: 16px 0;
982 }
983
984 &__footer {
985 display: flex;
986 align-items: center;
987 justify-content: center;
988 flex-wrap: wrap;
989
990 &-text {
991 font-size: 15px;
992 font-weight: 400;
993 line-height: 1.6;
994 color: var(--am-c-main-text-op70);
995 }
996
997 &-reset__btn {
998 padding: 0;
999 font-weight: 400;
1000
1001 &:hover {
1002 background-color: transparent;
1003 }
1004 }
1005 }
1006 }
1007 }
1008
1009 // Public
1010 .amelia-v2-booking #amelia-container {
1011 @include auth;
1012 }
1013 </style>
1014