Files
chatwoot/app/javascript/v3/views/login/Index.vue
Fayaz Ahmed b474929f5e chore: Replace eventBus with mitt.js [CW-3275] (#9539)
# Replace the deprecated `eventBus` with mitt.js

## Description

Since eventBus and it's respective methods are deprecated and removed
from all future releases of vue, this was blocking us from migrating.
This PR replaces eventBus with
[mitt](https://github.com/developit/mitt). I have created a wrapper
mitt.js to simulate the same old event names so it's backwards
compatible, without making a lot of changes.


Fixes # (issue)

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

1. Made sure all the places we're listening to bus events are working as
expected.
2. Respective specsf or the events from mitt.


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
2024-05-31 15:50:36 +05:30

231 lines
7.0 KiB
Vue

<template>
<main
class="flex flex-col w-full min-h-screen py-20 bg-woot-25 sm:px-6 lg:px-8 dark:bg-slate-900"
>
<section class="max-w-5xl mx-auto">
<img
:src="globalConfig.logo"
:alt="globalConfig.installationName"
class="block w-auto h-8 mx-auto dark:hidden"
/>
<img
v-if="globalConfig.logoDark"
:src="globalConfig.logoDark"
:alt="globalConfig.installationName"
class="hidden w-auto h-8 mx-auto dark:block"
/>
<h2
class="mt-6 text-3xl font-medium text-center text-slate-900 dark:text-woot-50"
>
{{
useInstallationName($t('LOGIN.TITLE'), globalConfig.installationName)
}}
</h2>
<p
v-if="showSignupLink"
class="mt-3 text-sm text-center text-slate-600 dark:text-slate-400"
>
{{ $t('COMMON.OR') }}
<router-link to="auth/signup" class="lowercase text-link">
{{ $t('LOGIN.CREATE_NEW_ACCOUNT') }}
</router-link>
</p>
</section>
<section
class="bg-white shadow sm:mx-auto mt-11 sm:w-full sm:max-w-lg dark:bg-slate-800 p-11 sm:shadow-lg sm:rounded-lg"
:class="{
'mb-8 mt-15': !showGoogleOAuth,
'animate-wiggle': loginApi.hasErrored,
}"
>
<div v-if="!email">
<GoogleOAuthButton v-if="showGoogleOAuth" />
<form class="space-y-5" @submit.prevent="submitLogin">
<form-input
v-model.trim="credentials.email"
name="email_address"
type="text"
data-testid="email_input"
:tabindex="1"
required
:label="$t('LOGIN.EMAIL.LABEL')"
:placeholder="$t('LOGIN.EMAIL.PLACEHOLDER')"
:has-error="$v.credentials.email.$error"
@input="$v.credentials.email.$touch"
/>
<form-input
v-model.trim="credentials.password"
type="password"
name="password"
data-testid="password_input"
required
:tabindex="2"
:label="$t('LOGIN.PASSWORD.LABEL')"
:placeholder="$t('LOGIN.PASSWORD.PLACEHOLDER')"
:has-error="$v.credentials.password.$error"
@input="$v.credentials.password.$touch"
>
<p v-if="!globalConfig.disableUserProfileUpdate">
<router-link
to="auth/reset/password"
class="text-sm text-link"
tabindex="4"
>
{{ $t('LOGIN.FORGOT_PASSWORD') }}
</router-link>
</p>
</form-input>
<submit-button
:disabled="loginApi.showLoading"
:tabindex="3"
:button-text="$t('LOGIN.SUBMIT')"
:loading="loginApi.showLoading"
/>
</form>
</div>
<div v-else class="flex items-center justify-center">
<spinner color-scheme="primary" size="" />
</div>
</section>
</main>
</template>
<script>
import { required, email } from 'vuelidate/lib/validators';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import SubmitButton from '../../components/Button/SubmitButton.vue';
import { mapGetters } from 'vuex';
import { parseBoolean } from '@chatwoot/utils';
import GoogleOAuthButton from '../../components/GoogleOauth/Button.vue';
import FormInput from '../../components/Form/Input.vue';
import { login } from '../../api/auth';
import Spinner from 'shared/components/Spinner.vue';
const ERROR_MESSAGES = {
'no-account-found': 'LOGIN.OAUTH.NO_ACCOUNT_FOUND',
'business-account-only': 'LOGIN.OAUTH.BUSINESS_ACCOUNTS_ONLY',
};
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
FormInput,
GoogleOAuthButton,
Spinner,
SubmitButton,
},
mixins: [globalConfigMixin, alertMixin],
props: {
ssoAuthToken: { type: String, default: '' },
ssoAccountId: { type: String, default: '' },
ssoConversationId: { type: String, default: '' },
config: { type: String, default: '' },
email: { type: String, default: '' },
authError: { type: String, default: '' },
},
data() {
return {
// We need to initialize the component with any
// properties that will be used in it
credentials: {
email: '',
password: '',
},
loginApi: {
message: '',
showLoading: false,
hasErrored: false,
},
error: '',
};
},
validations: {
credentials: {
password: {
required,
},
email: {
required,
email,
},
},
},
computed: {
...mapGetters({ globalConfig: 'globalConfig/get' }),
showGoogleOAuth() {
return Boolean(window.chatwootConfig.googleOAuthClientId);
},
showSignupLink() {
return parseBoolean(window.chatwootConfig.signupEnabled);
},
},
created() {
if (this.ssoAuthToken) {
this.submitLogin();
}
if (this.authError) {
const message = ERROR_MESSAGES[this.authError] ?? 'LOGIN.API.UNAUTH';
this.showAlert(this.$t(message));
// wait for idle state
this.requestIdleCallbackPolyfill(() => {
// Remove the error query param from the url
const { query } = this.$route;
this.$router.replace({ query: { ...query, error: undefined } });
});
}
},
methods: {
// TODO: Remove this when Safari gets wider support
// Ref: https://caniuse.com/requestidlecallback
//
requestIdleCallbackPolyfill(callback) {
if (window.requestIdleCallback) {
window.requestIdleCallback(callback);
} else {
// Fallback for safari
// Using a delay of 0 allows the callback to be executed asynchronously
// in the next available event loop iteration, similar to requestIdleCallback
setTimeout(callback, 0);
}
},
showAlertMessage(message) {
// Reset loading, current selected agent
this.loginApi.showLoading = false;
this.loginApi.message = message;
this.showAlert(this.loginApi.message);
},
submitLogin() {
if (this.$v.credentials.email.$invalid && !this.email) {
this.showAlertMessage(this.$t('LOGIN.EMAIL.ERROR'));
return;
}
this.loginApi.hasErrored = false;
this.loginApi.showLoading = true;
const credentials = {
email: this.email
? decodeURIComponent(this.email)
: this.credentials.email,
password: this.credentials.password,
sso_auth_token: this.ssoAuthToken,
ssoAccountId: this.ssoAccountId,
ssoConversationId: this.ssoConversationId,
};
login(credentials)
.then(() => {
this.showAlert(this.$t('LOGIN.API.SUCCESS_MESSAGE'));
})
.catch(response => {
// Reset URL Params if the authentication is invalid
if (this.email) {
window.location = '/app/login';
}
this.loginApi.hasErrored = true;
this.showAlert(response?.message || this.$t('LOGIN.API.UNAUTH'));
});
},
},
};
</script>