* refactor: add HTTP client wrapper and upgrade axios to v1 Introduce a thin HTTP wrapper (resources/scripts/http) that centralizes axios configuration, interceptors, and auth header injection. All 43 files now import from the wrapper instead of axios directly, making future library swaps a single-file change. Upgrade axios from 0.30.0 to 1.14.0. * fix: restore window.Ls assignment removed during axios refactor company.js uses window.Ls.set() to persist selected company, which broke after the axios plugin (that set window.Ls) was deleted.
130 lines
3.2 KiB
Vue
130 lines
3.2 KiB
Vue
<template>
|
|
<form id="loginForm" class="mt-12 text-left" @submit.prevent="onSubmit">
|
|
<BaseInputGroup
|
|
:error="v$.email.$error && v$.email.$errors[0].$message"
|
|
:label="$t('login.email')"
|
|
class="mb-4"
|
|
required
|
|
>
|
|
<BaseInput
|
|
v-model="authStore.loginData.email"
|
|
:invalid="v$.email.$error"
|
|
focus
|
|
type="email"
|
|
name="email"
|
|
@input="v$.email.$touch()"
|
|
/>
|
|
</BaseInputGroup>
|
|
|
|
<BaseInputGroup
|
|
:error="v$.password.$error && v$.password.$errors[0].$message"
|
|
:label="$t('login.password')"
|
|
class="mb-4"
|
|
required
|
|
>
|
|
<BaseInput
|
|
v-model="authStore.loginData.password"
|
|
:invalid="v$.password.$error"
|
|
:type="getInputType"
|
|
name="password"
|
|
@input="v$.password.$touch()"
|
|
>
|
|
<template #right>
|
|
<BaseIcon
|
|
:name="isShowPassword ? 'EyeIcon' : 'EyeSlashIcon'"
|
|
class="mr-1 text-gray-500 cursor-pointer"
|
|
@click="isShowPassword = !isShowPassword"
|
|
/>
|
|
</template>
|
|
</BaseInput>
|
|
</BaseInputGroup>
|
|
|
|
<div class="mt-5 mb-8">
|
|
<div class="mb-4">
|
|
<router-link
|
|
to="forgot-password"
|
|
class="text-sm text-primary-400 hover:text-gray-700"
|
|
>
|
|
{{ $t('login.forgot_password') }}
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
<BaseButton :loading="isLoading" type="submit">
|
|
{{ $t('login.login') }}
|
|
</BaseButton>
|
|
</form>
|
|
</template>
|
|
|
|
<script setup>
|
|
import http from '@/scripts/http'
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useNotificationStore } from '@/scripts/stores/notification'
|
|
import { useRouter } from 'vue-router'
|
|
import { required, email, helpers } from '@vuelidate/validators'
|
|
import { useVuelidate } from '@vuelidate/core'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useAuthStore } from '@/scripts/admin/stores/auth'
|
|
import { handleError } from '@/scripts/helpers/error-handling'
|
|
|
|
const notificationStore = useNotificationStore()
|
|
const authStore = useAuthStore()
|
|
const { t } = useI18n()
|
|
const router = useRouter()
|
|
const isLoading = ref(false)
|
|
let isShowPassword = ref(false)
|
|
|
|
const rules = {
|
|
email: {
|
|
required: helpers.withMessage(t('validation.required'), required),
|
|
email: helpers.withMessage(t('validation.email_incorrect'), email),
|
|
},
|
|
password: {
|
|
required: helpers.withMessage(t('validation.required'), required),
|
|
},
|
|
}
|
|
|
|
const v$ = useVuelidate(
|
|
rules,
|
|
computed(() => authStore.loginData)
|
|
)
|
|
|
|
const getInputType = computed(() => {
|
|
if (isShowPassword.value) {
|
|
return 'text'
|
|
}
|
|
return 'password'
|
|
})
|
|
|
|
async function onSubmit() {
|
|
v$.value.$touch()
|
|
|
|
if (v$.value.$invalid) {
|
|
return true
|
|
}
|
|
|
|
isLoading.value = true
|
|
|
|
try {
|
|
isLoading.value = true
|
|
await authStore.login(authStore.loginData)
|
|
|
|
router.push('/admin/dashboard')
|
|
|
|
notificationStore.showNotification({
|
|
type: 'success',
|
|
message: 'Logged in successfully.',
|
|
})
|
|
} catch (error) {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
// Pre-fill demo credentials if in demo environment
|
|
onMounted(() => {
|
|
if (window.demo_mode) {
|
|
authStore.loginData.email = 'demo@invoiceshelf.com'
|
|
authStore.loginData.password = 'demo'
|
|
}
|
|
})
|
|
</script>
|