rework login screen

This commit is contained in:
Gauthier Roebroeck 2025-06-06 12:03:00 +08:00
parent 1e20aa15b8
commit c9df6a01e9
8 changed files with 91 additions and 27 deletions

View file

@ -1,6 +1,8 @@
<template>
<v-app>
<router-view />
<SnackQueue />
</v-app>
</template>

View file

@ -1,6 +1,7 @@
import type { Middleware } from 'openapi-fetch'
import createClient from 'openapi-fetch'
import type { paths } from '@/generated/openapi/komga'
import { globalProperties } from '@/main'
// Middleware that throws on error, so it works with Pinia Colada
const coladaMiddleware: Middleware = {
@ -23,7 +24,11 @@ const coladaMiddleware: Middleware = {
onError() {
throw new Error('error', {
cause: {
message: 'Server is unreachable',
message: globalProperties.$intl.formatMessage({
description: 'Network error: error message when the server cannot be reached',
defaultMessage: 'Server is unreachable',
id: '/6WuF/',
}),
},
})
},

View file

@ -29,6 +29,7 @@ declare module 'vue' {
LocaleSelector: typeof import('./components/LocaleSelector.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SnackQueue: typeof import('./components/SnackQueue.vue')['default']
ThemeSelector: typeof import('./components/ThemeSelector.vue')['default']
}
}

View file

@ -0,0 +1,16 @@
<template>
<v-snackbar-queue
v-model="messagesStore.messages"
timer
/>
</template>
<script setup lang="ts">
import { useMessagesStore } from '@/stores/messages'
const messagesStore = useMessagesStore()
</script>
<script lang="ts"></script>
<style scoped></style>

View file

@ -18,3 +18,5 @@ const app = createApp(App)
registerPlugins(app)
app.mount('#app')
export const globalProperties = app.config.globalProperties

View file

@ -4,9 +4,9 @@
:disabled="isLoading"
@submit.prevent="submitForm()"
>
<v-container max-width="550px">
<v-container max-width="450px">
<v-row justify="center">
<v-col>
<v-col cols="10">
<v-img src="@/assets/logo.svg" />
</v-col>
</v-row>
@ -41,12 +41,17 @@
"
type="password"
:rules="[rules.required()]"
:error-messages="loginError"
@update:modelValue="loginError = ''"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-row
align="center"
justify="space-between"
>
<v-col cols="auto">
<v-checkbox
v-model="rememberMe"
:label="
@ -59,6 +64,22 @@
hide-details
/>
</v-col>
<v-col cols="auto">
<a
href="https://komga.org/docs/faq#i-forgot-my-password"
target="_blank"
class="link-underline"
>
{{
$formatMessage({
description: 'Login screen: Forgot your password link',
defaultMessage: 'Forgot your password?',
id: 'r6JNfI',
})
}}
</a>
</v-col>
</v-row>
<v-row>
@ -73,28 +94,19 @@
"
:loading="isLoading"
type="submit"
block
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-snackbar
v-model="showError"
color="error"
:text="showErrorText"
timer="red"
>
<template v-slot:actions>
<v-btn
color="white"
variant="text"
@click="showError = false"
>
Dismiss
</v-btn>
</template></v-snackbar
>
<v-divider class="my-8" />
<v-row justify="center">
<v-col cols="auto">
<div class="d-flex ga-4">
<LocaleSelector />
<ThemeSelector />
</div>
</v-col>
</v-row>
</v-container>
@ -105,15 +117,19 @@
import { type ErrorCause, komgaClient } from '@/api/komga-client'
import { useMutation, useQueryCache } from '@pinia/colada'
import { useRules } from 'vuetify/labs/rules'
import { useMessagesStore } from '@/stores/messages'
import { useIntl } from 'vue-intl'
import { commonMessages } from '@/utils/i18n/common-messages'
const rules = useRules()
const messagesStore = useMessagesStore()
const intl = useIntl()
const formValid = ref<boolean>(false)
const username = ref('')
const password = ref('')
const rememberMe = ref(false)
const showError = ref<boolean>(false)
const showErrorText = ref<string>('')
const loginError = ref<string>('')
const router = useRouter()
const route = useRoute()
@ -139,8 +155,17 @@ const { mutate: performLogin, isLoading } = useMutation({
else void router.push('/')
},
onError: (error) => {
showErrorText.value = (error.cause as ErrorCause).message || 'Invalid authentication'
showError.value = true
if ((error.cause as ErrorCause).status === 401)
loginError.value = intl.formatMessage({
description: 'Login screen: error message displayed when login failed',
defaultMessage: 'Invalid login or password',
id: 'AjWlka',
})
else
messagesStore.messages.push({
text:
(error.cause as ErrorCause).message || intl.formatMessage(commonMessages.networkError),
})
},
})

View file

@ -0,0 +1,8 @@
// Utilities
import { defineStore } from 'pinia'
export const useMessagesStore = defineStore('messages', {
state: () => ({
messages: [] as object[],
}),
})

View file

@ -11,4 +11,9 @@ export const commonMessages = {
defaultMessage: 'There might be a problem with your connection or your server.',
id: 'hYO2n6',
}),
networkError: defineMessage({
description: 'Common message: a network error happened when communicating with the server',
defaultMessage: 'Network error',
id: 'Z/EY89',
}),
}