mirror of
https://github.com/gotson/komga.git
synced 2026-05-08 04:22:28 +02:00
account details screen
This commit is contained in:
parent
baf547d5a7
commit
0336dbb4fc
9 changed files with 231 additions and 7 deletions
1
next-ui/src/components.d.ts
vendored
1
next-ui/src/components.d.ts
vendored
|
|
@ -37,6 +37,7 @@ declare module 'vue' {
|
|||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ServerSettings: typeof import('./components/server/Settings.vue')['default']
|
||||
UserDeletionWarning: typeof import('./components/user/DeletionWarning.vue')['default']
|
||||
UserDetails: typeof import('./components/user/Details.vue')['default']
|
||||
UserFormChangePassword: typeof import('./components/user/form/ChangePassword.vue')['default']
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
next-ui/src/components/user/Details.mdx
Normal file
11
next-ui/src/components/user/Details.mdx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
|
||||
|
||||
import * as Stories from './Details.stories';
|
||||
|
||||
<Meta of={Stories} />
|
||||
|
||||
# UserDetails
|
||||
|
||||
Displays user information.
|
||||
|
||||
<Canvas of={Stories.Default} />
|
||||
35
next-ui/src/components/user/Details.stories.ts
Normal file
35
next-ui/src/components/user/Details.stories.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import Details from './Details.vue'
|
||||
|
||||
const meta = {
|
||||
component: Details,
|
||||
render: (args: object) => ({
|
||||
components: { Details },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: '<Details :user="args.user" />',
|
||||
}),
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||
},
|
||||
args: {
|
||||
user: {
|
||||
id: '0JEDA00AV4Z7G',
|
||||
email: 'admin@example.org',
|
||||
roles: ['ADMIN', 'FILE_DOWNLOAD', 'KOBO_SYNC', 'KOREADER_SYNC', 'PAGE_STREAMING', 'USER'],
|
||||
sharedAllLibraries: true,
|
||||
sharedLibrariesIds: [],
|
||||
labelsAllow: [],
|
||||
labelsExclude: [],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Details>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
}
|
||||
38
next-ui/src/components/user/Details.vue
Normal file
38
next-ui/src/components/user/Details.vue
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<v-container class="pa-0">
|
||||
<v-row align="center">
|
||||
<v-col cols="auto">
|
||||
<v-avatar
|
||||
color="primary"
|
||||
size="x-large"
|
||||
><span class="text-h5 text-uppercase">{{ user.email.charAt(0) }}</span></v-avatar
|
||||
>
|
||||
</v-col>
|
||||
<v-col>
|
||||
{{ user.email }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="d-flex ga-1 flex-wrap">
|
||||
<v-chip
|
||||
v-for="role in user.roles"
|
||||
:key="role"
|
||||
:text="$formatMessage(userRolesMessages[role as UserRoles])"
|
||||
size="small"
|
||||
rounded
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from '@/generated/openapi/komga'
|
||||
import { UserRoles, userRolesMessages } from '@/types/UserRoles'
|
||||
|
||||
const { user } = defineProps<{
|
||||
user: components['schemas']['UserDto']
|
||||
}>()
|
||||
</script>
|
||||
11
next-ui/src/pages/account/details.mdx
Normal file
11
next-ui/src/pages/account/details.mdx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
|
||||
|
||||
import * as Stories from './details.stories';
|
||||
|
||||
<Meta of={Stories} />
|
||||
|
||||
# Account Details
|
||||
|
||||
Page showing the current user's details.
|
||||
|
||||
<Canvas of={Stories.Default} />
|
||||
25
next-ui/src/pages/account/details.stories.ts
Normal file
25
next-ui/src/pages/account/details.stories.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import AccountDetails from './details.vue'
|
||||
|
||||
const meta = {
|
||||
component: AccountDetails,
|
||||
render: (args: object) => ({
|
||||
components: { AccountDetails },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: '<AccountDetails />',
|
||||
}),
|
||||
parameters: {
|
||||
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
|
||||
},
|
||||
args: {},
|
||||
} satisfies Meta<typeof AccountDetails>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
}
|
||||
|
|
@ -1,7 +1,89 @@
|
|||
<template>
|
||||
<h1>Account Details</h1>
|
||||
<v-empty-state
|
||||
v-if="error"
|
||||
icon="i-mdi:connection"
|
||||
:title="$formatMessage(commonMessages.somethingWentWrongTitle)"
|
||||
:text="$formatMessage(commonMessages.somethingWentWrongSubTitle)"
|
||||
/>
|
||||
|
||||
<template v-else-if="currentUser">
|
||||
<UserDetails :user="currentUser" />
|
||||
<v-btn
|
||||
:text="
|
||||
$formatMessage({
|
||||
description: 'User details screen: change password button',
|
||||
defaultMessage: 'Change password',
|
||||
id: 'sGsWvI',
|
||||
})
|
||||
"
|
||||
class="mt-8"
|
||||
@click="changePassword()"
|
||||
@mouseenter="dialogConfirmEdit.activator = $event.currentTarget"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
import { useCurrentUser, useUpdateUserPassword } from '@/colada/users'
|
||||
import { commonMessages } from '@/utils/i18n/common-messages'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useDialogsStore } from '@/stores/dialogs'
|
||||
import UserFormChangePassword from '@/components/user/form/ChangePassword.vue'
|
||||
import type { ErrorCause } from '@/api/komga-client'
|
||||
import { useMessagesStore } from '@/stores/messages'
|
||||
import { useIntl } from 'vue-intl'
|
||||
|
||||
const intl = useIntl()
|
||||
|
||||
const { data: currentUser, error } = useCurrentUser()
|
||||
const { mutateAsync: mutateUserPassword } = useUpdateUserPassword()
|
||||
|
||||
const { confirmEdit: dialogConfirmEdit } = storeToRefs(useDialogsStore())
|
||||
const messagesStore = useMessagesStore()
|
||||
|
||||
function changePassword() {
|
||||
dialogConfirmEdit.value.dialogProps = {
|
||||
title: intl.formatMessage(commonMessages.changePasswordDialogTitle),
|
||||
subtitle: currentUser.value?.email,
|
||||
maxWidth: 400,
|
||||
closeOnSave: false,
|
||||
}
|
||||
dialogConfirmEdit.value.slot = {
|
||||
component: markRaw(UserFormChangePassword),
|
||||
props: {},
|
||||
}
|
||||
// password change initiated with an empty string
|
||||
dialogConfirmEdit.value.record = ''
|
||||
dialogConfirmEdit.value.callback = handleDialogConfirmation
|
||||
}
|
||||
|
||||
function handleDialogConfirmation(
|
||||
hideDialog: () => void,
|
||||
setLoading: (isLoading: boolean) => void,
|
||||
) {
|
||||
setLoading(true)
|
||||
|
||||
mutateUserPassword({
|
||||
userId: currentUser.value!.id,
|
||||
newPassword: dialogConfirmEdit.value.record as string,
|
||||
})
|
||||
?.then(() => {
|
||||
hideDialog()
|
||||
messagesStore.messages.push({
|
||||
text: intl.formatMessage({
|
||||
description:
|
||||
"Snackbar notification shown upon successful current user's password modification",
|
||||
defaultMessage: 'Password changed',
|
||||
id: '0FEy0X',
|
||||
}),
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
messagesStore.messages.push({
|
||||
text:
|
||||
(error?.cause as ErrorCause)?.message || intl.formatMessage(commonMessages.networkError),
|
||||
})
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,11 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
switch (action) {
|
||||
case ACTION.ADD:
|
||||
dialogConfirmEdit.value.dialogProps = {
|
||||
title: 'Add User',
|
||||
title: intl.formatMessage({
|
||||
description: 'Add user dialog title',
|
||||
defaultMessage: 'Add User',
|
||||
id: 'Bl30xt',
|
||||
}),
|
||||
maxWidth: 600,
|
||||
closeOnSave: false,
|
||||
fullscreen: display.xs.value,
|
||||
|
|
@ -107,7 +111,11 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
break
|
||||
case ACTION.EDIT:
|
||||
dialogConfirmEdit.value.dialogProps = {
|
||||
title: 'Edit User',
|
||||
title: intl.formatMessage({
|
||||
description: 'Edit user dialog title',
|
||||
defaultMessage: 'Edit User',
|
||||
id: 'Zh8AOV',
|
||||
}),
|
||||
subtitle: user?.email,
|
||||
maxWidth: 600,
|
||||
closeOnSave: false,
|
||||
|
|
@ -136,11 +144,19 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
break
|
||||
case ACTION.DELETE:
|
||||
dialogConfirm.value.dialogProps = {
|
||||
title: 'Delete User',
|
||||
title: intl.formatMessage({
|
||||
description: 'Delete user dialog title',
|
||||
defaultMessage: 'Delete User',
|
||||
id: '9XDmYO',
|
||||
}),
|
||||
subtitle: user?.email,
|
||||
maxWidth: 600,
|
||||
validateText: user?.email,
|
||||
okText: 'Delete',
|
||||
okText: intl.formatMessage({
|
||||
description: 'Delete user dialog: confirmation button text',
|
||||
defaultMessage: 'Delete',
|
||||
id: 'o8WeX3',
|
||||
}),
|
||||
closeOnSave: false,
|
||||
}
|
||||
dialogConfirm.value.slotWarning = {
|
||||
|
|
@ -151,7 +167,7 @@ function showDialog(action: ACTION, user?: components['schemas']['UserDto']) {
|
|||
break
|
||||
case ACTION.PASSWORD:
|
||||
dialogConfirmEdit.value.dialogProps = {
|
||||
title: 'Change Password',
|
||||
title: intl.formatMessage(commonMessages.changePasswordDialogTitle),
|
||||
subtitle: user?.email,
|
||||
maxWidth: 400,
|
||||
closeOnSave: false,
|
||||
|
|
|
|||
|
|
@ -26,4 +26,9 @@ export const commonMessages = {
|
|||
defaultMessage: 'Select an item or create one',
|
||||
id: 'HXms0S',
|
||||
}),
|
||||
changePasswordDialogTitle: defineMessage({
|
||||
description: 'Change Password dialog title',
|
||||
defaultMessage: 'Change Password',
|
||||
id: 'dHyAgE',
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue