Files
gochat/frontend/app/javascript/v3/views/login/Index.vue
T
rogee ca2a3c2dee
Build and publish Docker images / Build and publish images (push) Successful in 2m35s
fix: localize API error messages
2026-09-14 14:40:49 +08:00

205 lines
5.9 KiB
Vue

<script>
// utils and composables
import { login } from '../../api/auth';
import { useAlert } from 'dashboard/composables';
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
import { required, email } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage';
import SessionStorage from 'shared/helpers/sessionStorage';
// components
import FormInput from '../../components/Form/Input.vue';
import Spinner from 'shared/components/Spinner.vue';
import NextButton from 'dashboard/components-next/button/Button.vue';
const IMPERSONATION_URL_SEARCH_KEY = 'impersonation';
const USER_NOT_CONFIRMED_ERROR_CODE = 'user_not_confirmed';
export default {
components: {
FormInput,
Spinner,
NextButton,
},
props: {
ssoAuthToken: { type: String, default: '' },
ssoAccountId: { type: String, default: '' },
ssoConversationId: { type: String, default: '' },
email: { type: String, default: '' },
authError: { type: String, default: '' },
},
setup() {
return {
v$: useVuelidate(),
};
},
data() {
return {
credentials: {
email: '',
password: '',
},
loginApi: {
message: '',
showLoading: false,
hasErrored: false,
},
error: '',
};
},
validations() {
return {
credentials: {
password: {
required,
},
email: {
required,
email,
},
},
};
},
created() {
if (this.ssoAuthToken) {
this.submitLogin();
}
if (this.authError) {
this.showAlertMessage(this.$t('LOGIN.API.UNAUTH'));
this.requestIdleCallbackPolyfill(() => {
const { query } = this.$route;
this.$router.replace({ query: { ...query, error: undefined } });
});
}
},
methods: {
requestIdleCallbackPolyfill(callback) {
if (window.requestIdleCallback) {
window.requestIdleCallback(callback);
} else {
setTimeout(callback, 0);
}
},
showAlertMessage(message) {
this.loginApi.showLoading = false;
this.loginApi.message = parseAPIErrorResponse(message);
useAlert(this.loginApi.message);
},
handleImpersonation() {
const urlParams = new URLSearchParams(window.location.search);
const impersonation = urlParams.get(IMPERSONATION_URL_SEARCH_KEY);
if (impersonation) {
SessionStorage.set(SESSION_STORAGE_KEYS.IMPERSONATION_USER, true);
}
},
submitLogin() {
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.handleImpersonation();
this.showAlertMessage(this.$t('LOGIN.API.SUCCESS_MESSAGE'));
})
.catch(response => {
if (response?.errorCode === USER_NOT_CONFIRMED_ERROR_CODE) {
this.loginApi.showLoading = false;
this.$router.push({
name: 'auth_verify_email',
state: { email: credentials.email },
});
return;
}
if (this.email) {
window.location = '/app/login';
}
this.loginApi.hasErrored = true;
let errorMessage = this.$t('LOGIN.API.ERROR_MESSAGE');
if (response?.response?.status === 401) {
errorMessage = this.$t('LOGIN.API.UNAUTH');
} else if (response) {
errorMessage = parseAPIErrorResponse(response);
}
this.showAlertMessage(errorMessage);
});
},
submitFormLogin() {
if (this.v$.credentials.email.$invalid && !this.email) {
this.showAlertMessage(this.$t('LOGIN.EMAIL.ERROR'));
return;
}
this.submitLogin();
},
},
};
</script>
<template>
<main
class="flex flex-col items-center justify-center w-full min-h-screen bg-n-brand/5 dark:bg-n-background sm:px-6 lg:px-8"
>
<!-- Regular Login Section -->
<section
class="bg-white shadow sm:mx-auto mb-8 mt-15 sm:w-full sm:max-w-lg dark:bg-n-solid-2 p-11 sm:shadow-lg sm:rounded-lg"
:class="{
'animate-wiggle': loginApi.hasErrored,
}"
>
<div v-if="!email">
<form class="space-y-5" @submit.prevent="submitFormLogin">
<FormInput
v-model="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"
/>
<FormInput
v-model="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"
/>
<NextButton
lg
type="submit"
data-testid="submit_button"
class="w-full"
:tabindex="3"
:label="$t('LOGIN.SUBMIT')"
:disabled="loginApi.showLoading"
:is-loading="loginApi.showLoading"
/>
</form>
</div>
<div v-else class="flex items-center justify-center">
<Spinner color-scheme="primary" size="" />
</div>
</section>
</main>
</template>