mirror of
https://github.com/gotson/komga.git
synced 2026-05-08 04:22:28 +02:00
reusable dialogs loading state
This commit is contained in:
parent
c78240c832
commit
ddc849a57d
7 changed files with 248 additions and 150 deletions
|
|
@ -12,9 +12,6 @@ export const useCreateUser = defineMutation(() => {
|
|||
onSuccess: () => {
|
||||
void queryCache.invalidateQueries({ key: ['users'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('create user error', error)
|
||||
},
|
||||
})
|
||||
})
|
||||
export const useUpdateUser = defineMutation(() => {
|
||||
|
|
@ -28,9 +25,6 @@ export const useUpdateUser = defineMutation(() => {
|
|||
onSuccess: () => {
|
||||
void queryCache.invalidateQueries({ key: ['users'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('update user error', error)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -43,9 +37,6 @@ export const useUpdateUserPassword = defineMutation(() => {
|
|||
password: newPassword,
|
||||
},
|
||||
}),
|
||||
onError: (error) => {
|
||||
console.log('update user password error', error)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -59,8 +50,5 @@ export const useDeleteUser = defineMutation(() => {
|
|||
onSuccess: () => {
|
||||
void queryCache.invalidateQueries({ key: ['users'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log('delete user error', error)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,61 +4,66 @@
|
|||
:activator="activator"
|
||||
:max-width="maxWidth"
|
||||
>
|
||||
<v-form
|
||||
v-model="formValid"
|
||||
@submit.prevent="submitForm()"
|
||||
>
|
||||
<v-card
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
<template #default="{ isActive }">
|
||||
<v-form
|
||||
v-model="formValid"
|
||||
@submit.prevent="submitForm(isActive)"
|
||||
:disabled="loading"
|
||||
>
|
||||
<template #text>
|
||||
<slot name="warning" />
|
||||
<slot name="text">
|
||||
{{
|
||||
$formatMessage(
|
||||
{
|
||||
description: 'Confirmation dialog: default hint to retype validation text',
|
||||
defaultMessage: 'Please type {validateText} to confirm.',
|
||||
id: 'eVoe+D',
|
||||
},
|
||||
{
|
||||
validateText: validateText,
|
||||
},
|
||||
)
|
||||
}}
|
||||
</slot>
|
||||
<v-card
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #text>
|
||||
<slot name="warning" />
|
||||
<slot name="text">
|
||||
{{
|
||||
$formatMessage(
|
||||
{
|
||||
description: 'Confirmation dialog: default hint to retype validation text',
|
||||
defaultMessage: 'Please type {validateText} to confirm.',
|
||||
id: 'eVoe+D',
|
||||
},
|
||||
{
|
||||
validateText: validateText,
|
||||
},
|
||||
)
|
||||
}}
|
||||
</slot>
|
||||
|
||||
<v-text-field
|
||||
:rules="[rules.sameAs(validateText)]"
|
||||
hide-details
|
||||
class="mt-2"
|
||||
/>
|
||||
</template>
|
||||
<v-text-field
|
||||
:rules="[rules.sameAs(validateText)]"
|
||||
hide-details
|
||||
class="mt-2"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Confirmation dialog: Cancel button',
|
||||
defaultMessage: 'Cancel',
|
||||
id: 'pENCUD',
|
||||
})
|
||||
"
|
||||
@click="close()"
|
||||
/>
|
||||
<v-btn
|
||||
:disabled="!formValid"
|
||||
:text="okText"
|
||||
type="submit"
|
||||
variant="elevated"
|
||||
rounded="xs"
|
||||
color="error"
|
||||
/>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-form>
|
||||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'Confirmation dialog: Cancel button',
|
||||
defaultMessage: 'Cancel',
|
||||
id: 'pENCUD',
|
||||
})
|
||||
"
|
||||
@click="isActive.value = false"
|
||||
/>
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
:disabled="!formValid"
|
||||
:text="okText"
|
||||
type="submit"
|
||||
variant="elevated"
|
||||
rounded="xs"
|
||||
color="error"
|
||||
/>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</template>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
|
|
@ -74,10 +79,10 @@ const formValid = ref<boolean>(false)
|
|||
|
||||
const rules = useRules()
|
||||
|
||||
function submitForm() {
|
||||
function submitForm(isActive: Ref<boolean, boolean>) {
|
||||
if (formValid.value) {
|
||||
emit('confirm')
|
||||
close()
|
||||
if (closeOnSave) isActive.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +93,8 @@ export interface DialogConfirmProps {
|
|||
validateText?: string
|
||||
maxWidth?: string | number
|
||||
activator?: Element | string
|
||||
loading?: boolean
|
||||
closeOnSave?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -97,9 +104,7 @@ const {
|
|||
validateText = 'confirm',
|
||||
maxWidth = undefined,
|
||||
activator = undefined,
|
||||
loading = false,
|
||||
closeOnSave = true,
|
||||
} = defineProps<DialogConfirmProps>()
|
||||
|
||||
function close() {
|
||||
showDialog.value = false
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -4,57 +4,62 @@
|
|||
:activator="activator"
|
||||
:max-width="maxWidth"
|
||||
>
|
||||
<v-confirm-edit
|
||||
v-model="record"
|
||||
hide-actions
|
||||
@save="close()"
|
||||
>
|
||||
<template #default="{ model: proxyModel, cancel, save, isPristine }">
|
||||
<v-form
|
||||
v-model="formValid"
|
||||
@submit.prevent="submitForm(save)"
|
||||
>
|
||||
<v-card
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
<template #default="{ isActive }">
|
||||
<v-confirm-edit
|
||||
v-model="record"
|
||||
hide-actions
|
||||
@save="closeOnSave ? (isActive.value = false) : undefined"
|
||||
>
|
||||
<template #default="{ model: proxyModel, cancel, save, isPristine }">
|
||||
<v-form
|
||||
v-model="formValid"
|
||||
@submit.prevent="submitForm(save)"
|
||||
:disabled="loading"
|
||||
>
|
||||
<template #text>
|
||||
<slot
|
||||
name="text"
|
||||
:proxy-model="proxyModel"
|
||||
:cancel="cancel"
|
||||
:save="save"
|
||||
:is-pristine="isPristine"
|
||||
/>
|
||||
</template>
|
||||
<v-card
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #text>
|
||||
<slot
|
||||
name="text"
|
||||
:proxy-model="proxyModel"
|
||||
:cancel="cancel"
|
||||
:save="save"
|
||||
:is-pristine="isPristine"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'ConfirmEdit dialog: Cancel button',
|
||||
defaultMessage: 'Cancel',
|
||||
id: 'G/T8/2',
|
||||
})
|
||||
"
|
||||
@click="close()"
|
||||
/>
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'ConfirmEdit dialog: Save button',
|
||||
defaultMessage: 'Save',
|
||||
id: 'N9WFH4',
|
||||
})
|
||||
"
|
||||
type="submit"
|
||||
/>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</template>
|
||||
</v-confirm-edit>
|
||||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'ConfirmEdit dialog: Cancel button',
|
||||
defaultMessage: 'Cancel',
|
||||
id: 'G/T8/2',
|
||||
})
|
||||
"
|
||||
@click="isActive.value = false"
|
||||
/>
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'ConfirmEdit dialog: Save button',
|
||||
defaultMessage: 'Save',
|
||||
id: 'N9WFH4',
|
||||
})
|
||||
"
|
||||
type="submit"
|
||||
:loading="loading"
|
||||
/>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</template>
|
||||
</v-confirm-edit>
|
||||
</template>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
|
|
@ -77,6 +82,8 @@ export interface DialogConfirmEditProps {
|
|||
subtitle?: string
|
||||
maxWidth?: string | number
|
||||
activator?: Element | string
|
||||
loading?: boolean
|
||||
closeOnSave?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -84,9 +91,7 @@ const {
|
|||
subtitle = undefined,
|
||||
maxWidth = undefined,
|
||||
activator = undefined,
|
||||
loading = false,
|
||||
closeOnSave = true,
|
||||
} = defineProps<DialogConfirmEditProps>()
|
||||
|
||||
function close() {
|
||||
showDialog.value = false
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<DialogConfirm
|
||||
v-model="showDialog"
|
||||
:loading="loading"
|
||||
v-bind="confirm.dialogProps"
|
||||
:activator="confirm.activator"
|
||||
@confirm="confirm.confirmCallback()"
|
||||
@confirm="confirm.callback(hideDialog, setLoading)"
|
||||
>
|
||||
<template #warning>
|
||||
<component
|
||||
|
|
@ -21,7 +23,19 @@
|
|||
import { useDialogsStore } from '@/stores/dialogs'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const showDialog = ref<boolean>(false)
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const { confirm } = storeToRefs(useDialogsStore())
|
||||
|
||||
function hideDialog() {
|
||||
showDialog.value = false
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function setLoading(isLoading: boolean) {
|
||||
loading.value = isLoading
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<template>
|
||||
<DialogConfirmEdit
|
||||
v-model="showDialog"
|
||||
:loading="loading"
|
||||
v-bind="confirmEdit.dialogProps"
|
||||
:activator="confirmEdit.activator"
|
||||
v-model:record="confirmEdit.record"
|
||||
@update:record="confirmEdit.recordUpdatedCallback()"
|
||||
@update:record="confirmEdit.callback(hideDialog, setLoading)"
|
||||
>
|
||||
<template #text="{ proxyModel }">
|
||||
<component
|
||||
|
|
@ -23,7 +25,19 @@
|
|||
import { useDialogsStore } from '@/stores/dialogs'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const showDialog = ref<boolean>(false)
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const { confirmEdit } = storeToRefs(useDialogsStore())
|
||||
|
||||
function hideDialog() {
|
||||
showDialog.value = false
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function setLoading(isLoading: boolean) {
|
||||
loading.value = isLoading
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ import mdiLockReset from '~icons/mdi/lock-reset'
|
|||
import mdiPencil from '~icons/mdi/pencil'
|
||||
import mdiDelete from '~icons/mdi/delete'
|
||||
import { useUsers } from '@/colada/queries/users'
|
||||
import { komgaClient } from '@/api/komga-client'
|
||||
import { type ErrorCause, komgaClient } from '@/api/komga-client'
|
||||
import type { components } from '@/generated/openapi/komga'
|
||||
import { useCurrentUser } from '@/colada/queries/current-user'
|
||||
import { UserRoles } from '@/types/UserRoles'
|
||||
|
|
@ -102,6 +102,10 @@ import { useLibraries } from '@/colada/queries/libraries'
|
|||
import { commonMessages } from '@/utils/i18n/common-messages'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useDialogsStore } from '@/stores/dialogs'
|
||||
import { useMessagesStore } from '@/stores/messages'
|
||||
import { useIntl } from 'vue-intl'
|
||||
|
||||
const intl = useIntl()
|
||||
|
||||
// API data
|
||||
const { data: users, error, isLoading, refetch: refetchUsers } = useUsers()
|
||||
|
|
@ -157,11 +161,12 @@ const currentAction = ref<ACTION>()
|
|||
|
||||
const { confirmEdit: dialogConfirmEdit, confirm: dialogConfirm } = storeToRefs(useDialogsStore())
|
||||
|
||||
const { mutate: mutateCreateUser } = useCreateUser()
|
||||
const { mutate: mutateUser } = useUpdateUser()
|
||||
const { mutate: mutateUserPassword } = useUpdateUserPassword()
|
||||
const { mutate: mutateDeleteUser } = useDeleteUser()
|
||||
const { mutateAsync: mutateCreateUser } = useCreateUser()
|
||||
const { mutateAsync: mutateUser } = useUpdateUser()
|
||||
const { mutateAsync: mutateUserPassword } = useUpdateUserPassword()
|
||||
const { mutateAsync: mutateDeleteUser } = useDeleteUser()
|
||||
const { data: libraries } = useLibraries()
|
||||
const messagesStore = useMessagesStore()
|
||||
|
||||
enum ACTION {
|
||||
ADD,
|
||||
|
|
@ -177,6 +182,7 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
dialogConfirmEdit.value.dialogProps = {
|
||||
title: 'Add User',
|
||||
maxWidth: 600,
|
||||
closeOnSave: false,
|
||||
}
|
||||
dialogConfirmEdit.value.slot = {
|
||||
component: markRaw(FormUserEdit),
|
||||
|
|
@ -196,13 +202,14 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
restriction: 'NONE',
|
||||
},
|
||||
} as components['schemas']['UserCreationDto']
|
||||
dialogConfirmEdit.value.recordUpdatedCallback = handleDialogConfirmation
|
||||
dialogConfirmEdit.value.callback = handleDialogConfirmation
|
||||
break
|
||||
case ACTION.EDIT:
|
||||
dialogConfirmEdit.value.dialogProps = {
|
||||
title: 'Edit User',
|
||||
subtitle: user?.email,
|
||||
maxWidth: 600,
|
||||
closeOnSave: false,
|
||||
}
|
||||
dialogConfirmEdit.value.slot = {
|
||||
component: markRaw(FormUserEdit),
|
||||
|
|
@ -223,7 +230,7 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
restriction: 'NONE',
|
||||
},
|
||||
} as components['schemas']['UserUpdateDto']
|
||||
dialogConfirmEdit.value.recordUpdatedCallback = handleDialogConfirmation
|
||||
dialogConfirmEdit.value.callback = handleDialogConfirmation
|
||||
break
|
||||
case ACTION.DELETE:
|
||||
dialogConfirm.value.dialogProps = {
|
||||
|
|
@ -232,18 +239,20 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
maxWidth: 600,
|
||||
validateText: user?.email,
|
||||
okText: 'Delete',
|
||||
closeOnSave: false,
|
||||
}
|
||||
dialogConfirm.value.slotWarning = {
|
||||
component: markRaw(NoticeUserDeletion),
|
||||
props: {},
|
||||
}
|
||||
dialogConfirm.value.confirmCallback = handleDialogConfirmation
|
||||
dialogConfirm.value.callback = handleDialogConfirmation
|
||||
break
|
||||
case ACTION.PASSWORD:
|
||||
dialogConfirmEdit.value.dialogProps = {
|
||||
title: 'Change Password',
|
||||
subtitle: user?.email,
|
||||
maxWidth: 400,
|
||||
closeOnSave: false,
|
||||
}
|
||||
dialogConfirmEdit.value.slot = {
|
||||
component: markRaw(FormUserChangePassword),
|
||||
|
|
@ -251,29 +260,92 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
}
|
||||
// password change initiated with an empty string
|
||||
dialogConfirmEdit.value.record = ''
|
||||
dialogConfirmEdit.value.recordUpdatedCallback = handleDialogConfirmation
|
||||
dialogConfirmEdit.value.callback = handleDialogConfirmation
|
||||
}
|
||||
userRecord.value = user
|
||||
}
|
||||
|
||||
function handleDialogConfirmation() {
|
||||
function handleDialogConfirmation(
|
||||
hideDialog: () => void,
|
||||
setLoading: (isLoading: boolean) => void,
|
||||
) {
|
||||
let mutation: Promise<unknown> | undefined
|
||||
let successMessage: string | undefined
|
||||
|
||||
setLoading(true)
|
||||
|
||||
switch (currentAction.value) {
|
||||
case ACTION.ADD:
|
||||
mutateCreateUser(dialogConfirmEdit.value.record as components['schemas']['UserCreationDto'])
|
||||
const newUser = dialogConfirmEdit.value.record as components['schemas']['UserCreationDto']
|
||||
mutation = mutateCreateUser(newUser)
|
||||
successMessage = intl.formatMessage(
|
||||
{
|
||||
description: 'Snackbar notification shown upon successful user creation',
|
||||
defaultMessage: 'User created: {email}',
|
||||
id: 'egrxd6',
|
||||
},
|
||||
{
|
||||
email: newUser.email,
|
||||
},
|
||||
)
|
||||
break
|
||||
case ACTION.EDIT:
|
||||
mutateUser(dialogConfirmEdit.value.record as components['schemas']['UserDto'])
|
||||
const editUser = dialogConfirmEdit.value.record as components['schemas']['UserDto']
|
||||
mutation = mutateUser(editUser)
|
||||
successMessage = intl.formatMessage(
|
||||
{
|
||||
description: 'Snackbar notification shown upon successful user update',
|
||||
defaultMessage: 'User updated: {email}',
|
||||
id: 'kvbi4j',
|
||||
},
|
||||
{
|
||||
email: editUser.email,
|
||||
},
|
||||
)
|
||||
break
|
||||
case ACTION.DELETE:
|
||||
mutateDeleteUser(userRecord.value!.id)
|
||||
mutation = mutateDeleteUser(userRecord.value!.id)
|
||||
successMessage = intl.formatMessage(
|
||||
{
|
||||
description: 'Snackbar notification shown upon successful user deletion',
|
||||
defaultMessage: 'User deleted: {email}',
|
||||
id: 'V/OYJE',
|
||||
},
|
||||
{
|
||||
email: userRecord.value!.email,
|
||||
},
|
||||
)
|
||||
break
|
||||
case ACTION.PASSWORD:
|
||||
mutateUserPassword({
|
||||
mutation = mutateUserPassword({
|
||||
userId: userRecord.value!.id,
|
||||
newPassword: dialogConfirmEdit.value.record as string,
|
||||
})
|
||||
successMessage = intl.formatMessage(
|
||||
{
|
||||
description: "Snackbar notification shown upon successful user's password modification",
|
||||
defaultMessage: 'Password changed for user: {email}',
|
||||
id: 'JbF1nK',
|
||||
},
|
||||
{
|
||||
email: userRecord.value!.email,
|
||||
},
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
mutation
|
||||
?.then(() => {
|
||||
hideDialog()
|
||||
if (successMessage) messagesStore.messages.push({ text: successMessage })
|
||||
})
|
||||
.catch((error) => {
|
||||
messagesStore.messages.push({
|
||||
text:
|
||||
(error.cause as ErrorCause).message || intl.formatMessage(commonMessages.networkError),
|
||||
})
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const useDialogsStore = defineStore('dialogs', {
|
|||
props: {},
|
||||
},
|
||||
record: undefined,
|
||||
recordUpdatedCallback: () => {},
|
||||
callback: () => {},
|
||||
} as DialogConfirmEditActivation,
|
||||
confirm: {
|
||||
dialogProps: {},
|
||||
|
|
@ -24,24 +24,24 @@ export const useDialogsStore = defineStore('dialogs', {
|
|||
component: undefined,
|
||||
props: {},
|
||||
},
|
||||
confirmCallback: () => {},
|
||||
callback: () => {},
|
||||
} as DialogConfirmActivation,
|
||||
}),
|
||||
})
|
||||
|
||||
interface DialogConfirmEditActivation {
|
||||
interface DialogActivation<T> {
|
||||
activator?: Element | string
|
||||
dialogProps: DialogConfirmEditProps
|
||||
slot: ComponentWithProps
|
||||
record?: unknown
|
||||
recordUpdatedCallback: () => void
|
||||
dialogProps: T
|
||||
callback: (hideDialog: () => void, setLoading: (isLoading: boolean) => void) => void
|
||||
}
|
||||
|
||||
interface DialogConfirmActivation {
|
||||
activator?: Element | string
|
||||
dialogProps: DialogConfirmProps
|
||||
interface DialogConfirmEditActivation extends DialogActivation<DialogConfirmEditProps> {
|
||||
slot: ComponentWithProps
|
||||
record?: unknown
|
||||
}
|
||||
|
||||
interface DialogConfirmActivation extends DialogActivation<DialogConfirmProps> {
|
||||
slotWarning: ComponentWithProps
|
||||
confirmCallback: () => void
|
||||
}
|
||||
|
||||
interface ComponentWithProps {
|
||||
|
|
|
|||
Loading…
Reference in a new issue