diff --git a/next-ui/src/colada/mutations/update-user.ts b/next-ui/src/colada/mutations/update-user.ts
index bdc74a24..a6d32489 100644
--- a/next-ui/src/colada/mutations/update-user.ts
+++ b/next-ui/src/colada/mutations/update-user.ts
@@ -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)
- },
})
})
diff --git a/next-ui/src/components/dialog/Confirm.vue b/next-ui/src/components/dialog/Confirm.vue
index b2dc2663..5c44c234 100644
--- a/next-ui/src/components/dialog/Confirm.vue
+++ b/next-ui/src/components/dialog/Confirm.vue
@@ -4,61 +4,66 @@
:activator="activator"
:max-width="maxWidth"
>
-
-
+
-
-
-
- {{
- $formatMessage(
- {
- description: 'Confirmation dialog: default hint to retype validation text',
- defaultMessage: 'Please type {validateText} to confirm.',
- id: 'eVoe+D',
- },
- {
- validateText: validateText,
- },
- )
- }}
-
+
+
+
+
+ {{
+ $formatMessage(
+ {
+ description: 'Confirmation dialog: default hint to retype validation text',
+ defaultMessage: 'Please type {validateText} to confirm.',
+ id: 'eVoe+D',
+ },
+ {
+ validateText: validateText,
+ },
+ )
+ }}
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -74,10 +79,10 @@ const formValid = ref(false)
const rules = useRules()
-function submitForm() {
+function submitForm(isActive: Ref) {
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()
-
-function close() {
- showDialog.value = false
-}
diff --git a/next-ui/src/components/dialog/ConfirmEdit.vue b/next-ui/src/components/dialog/ConfirmEdit.vue
index 1ed8232b..330efbb2 100644
--- a/next-ui/src/components/dialog/ConfirmEdit.vue
+++ b/next-ui/src/components/dialog/ConfirmEdit.vue
@@ -4,57 +4,62 @@
:activator="activator"
:max-width="maxWidth"
>
-
-
-
-
+
+
+
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -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()
-
-function close() {
- showDialog.value = false
-}
diff --git a/next-ui/src/components/dialog/instance/Confirm.vue b/next-ui/src/components/dialog/instance/Confirm.vue
index c94170f2..c86d6792 100644
--- a/next-ui/src/components/dialog/instance/Confirm.vue
+++ b/next-ui/src/components/dialog/instance/Confirm.vue
@@ -1,8 +1,10 @@
(false)
+const loading = ref(false)
+
const { confirm } = storeToRefs(useDialogsStore())
+
+function hideDialog() {
+ showDialog.value = false
+ loading.value = false
+}
+
+function setLoading(isLoading: boolean) {
+ loading.value = isLoading
+}
diff --git a/next-ui/src/components/dialog/instance/ConfirmEdit.vue b/next-ui/src/components/dialog/instance/ConfirmEdit.vue
index 257ec02c..01374a41 100644
--- a/next-ui/src/components/dialog/instance/ConfirmEdit.vue
+++ b/next-ui/src/components/dialog/instance/ConfirmEdit.vue
@@ -1,9 +1,11 @@
(false)
+const loading = ref(false)
+
const { confirmEdit } = storeToRefs(useDialogsStore())
+
+function hideDialog() {
+ showDialog.value = false
+ loading.value = false
+}
+
+function setLoading(isLoading: boolean) {
+ loading.value = isLoading
+}
diff --git a/next-ui/src/pages/server/users.vue b/next-ui/src/pages/server/users.vue
index a3564553..ec1477db 100644
--- a/next-ui/src/pages/server/users.vue
+++ b/next-ui/src/pages/server/users.vue
@@ -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()
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 | 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)
+ })
}
diff --git a/next-ui/src/stores/dialogs.ts b/next-ui/src/stores/dialogs.ts
index e40b8e30..1b47d27c 100644
--- a/next-ui/src/stores/dialogs.ts
+++ b/next-ui/src/stores/dialogs.ts
@@ -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 {
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 {
+ slot: ComponentWithProps
+ record?: unknown
+}
+
+interface DialogConfirmActivation extends DialogActivation {
slotWarning: ComponentWithProps
- confirmCallback: () => void
}
interface ComponentWithProps {