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 |