diff --git a/next-ui/src/mocks/api/handlers.ts b/next-ui/src/mocks/api/handlers.ts index 416e838f8..6a7c0b8ba 100644 --- a/next-ui/src/mocks/api/handlers.ts +++ b/next-ui/src/mocks/api/handlers.ts @@ -4,6 +4,7 @@ import { releasesHandlers } from '@/mocks/api/handlers/releases' import { HttpResponse } from 'msw' import { librariesHandlers } from '@/mocks/api/handlers/libraries' import { referentialHandlers } from '@/mocks/api/handlers/referential' +import { usersHandlers } from '@/mocks/api/handlers/users' export const handlers = [ ...librariesHandlers, @@ -11,6 +12,7 @@ export const handlers = [ ...actuatorHandlers, ...announcementHandlers, ...releasesHandlers, + ...usersHandlers, ] export const response401Unauthorized = () => diff --git a/next-ui/src/mocks/api/handlers/users.ts b/next-ui/src/mocks/api/handlers/users.ts new file mode 100644 index 000000000..1d7898d47 --- /dev/null +++ b/next-ui/src/mocks/api/handlers/users.ts @@ -0,0 +1,45 @@ +import { httpTyped } from '@/mocks/api/httpTyped' + +export const users = [ + { + id: '0JEDA00AV4Z7G', + email: 'admin@example.org', + roles: ['ADMIN', 'FILE_DOWNLOAD', 'KOBO_SYNC', 'KOREADER_SYNC', 'PAGE_STREAMING', 'USER'], + sharedAllLibraries: true, + sharedLibrariesIds: [], + labelsAllow: [], + labelsExclude: [], + ageRestriction: null, + }, + { + id: '0JEDA00AZ4QXH', + email: 'user@example.org', + roles: ['KOBO_SYNC', 'PAGE_STREAMING', 'USER'], + sharedAllLibraries: true, + sharedLibrariesIds: [], + labelsAllow: [], + labelsExclude: ['book'], + ageRestriction: null, + }, +] + +const latestActivity = { + userId: '0JEDA00AV4Z7G', + email: 'admin@example.org', + apiKeyId: null, + apiKeyComment: null, + ip: '127.0.0.1', + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0', + success: true, + error: null, + dateTime: new Date('2025-06-30T06:56:33Z'), + source: 'Password', +} + +export const usersHandlers = [ + httpTyped.get('/api/v2/users/me', ({ response }) => response(200).json(users[0])), + httpTyped.get('/api/v2/users', ({ response }) => response(200).json(users)), + httpTyped.get('/api/v2/users/{userId}/authentication-activity/latest', ({ response }) => + response(200).json(latestActivity), + ), +] diff --git a/next-ui/src/pages/server/users.stories.ts b/next-ui/src/pages/server/users.stories.ts new file mode 100644 index 000000000..901f4c9c3 --- /dev/null +++ b/next-ui/src/pages/server/users.stories.ts @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import users from './users.vue' +import { http, delay } from 'msw' + +import { response401Unauthorized } from '@/mocks/api/handlers' + +const meta = { + component: users, + render: (args: object) => ({ + components: { users }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {}, +} + +export const Loading: Story = { + parameters: { + msw: { + handlers: [http.all('*', async () => await delay(5_000))], + }, + }, +} + +export const Error: Story = { + parameters: { + msw: { + handlers: [http.all('*', response401Unauthorized)], + }, + }, +} diff --git a/next-ui/src/pages/server/users.vue b/next-ui/src/pages/server/users.vue index 15d5c05cc..5ad9dc1e9 100644 --- a/next-ui/src/pages/server/users.vue +++ b/next-ui/src/pages/server/users.vue @@ -51,27 +51,34 @@ + + @@ -96,7 +103,7 @@ 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' +import { defineMessage, useIntl } from 'vue-intl' import { useDisplay } from 'vuetify' import UserDeletionWarning from '@/components/user/DeletionWarning.vue' import UserFormCreateEdit from '@/fragments/fragment/user/form/CreateEdit.vue' @@ -348,6 +355,24 @@ function handleDialogConfirmation( setLoading(false) }) } + +const messages = { + deleteUser: defineMessage({ + description: 'Tooltip for the delete user button in the users table', + defaultMessage: 'Delete user', + id: 'r6CqyT', + }), + editUser: defineMessage({ + description: 'Tooltip for the edit user button in the users table', + defaultMessage: 'Edit user', + id: 'K40g4r', + }), + changePassword: defineMessage({ + description: 'Tooltip for the change password button in the users table', + defaultMessage: 'Change password', + id: 'r7xCeA', + }), +}