diff --git a/client/src/locales/en-US.json b/client/src/locales/en-US.json index 4e49621a949..7e07f2e9ced 100644 --- a/client/src/locales/en-US.json +++ b/client/src/locales/en-US.json @@ -776,6 +776,18 @@ "cancel": "Cancel", "create": "Invite" }, + "UserDetailsModal": { + "title": "User details", + "subtitles": { + "name": "Name", + "joined": "Joined", + "commonWorkspaces": "Shared workspaces", + "revoked": "Revoked" + }, + "actions": { + "close": "Close" + } + }, "revocation": { "revokeTitle": "Revoke this user? | Revoke these users?", "revokeQuestion": "This will revoke {user}, preventing them from accessing this organization. Are you sure you want to proceed? | This will revoke these {count} users, preventing them from accessing this organization. Are you sure you want to proceed?", diff --git a/client/src/locales/fr-FR.json b/client/src/locales/fr-FR.json index d2a51fc18e0..5123306e5dd 100644 --- a/client/src/locales/fr-FR.json +++ b/client/src/locales/fr-FR.json @@ -776,6 +776,18 @@ "cancel": "Annuler", "create": "Inviter" }, + "UserDetailsModal": { + "title": "Détails de l'utilisateur", + "subtitles": { + "name": "Nom", + "joined": "Rejoint", + "commonWorkspaces": "Espaces de travail communs", + "revoked": "Révoqué" + }, + "actions": { + "close": "Fermer" + } + }, "revocation": { "revokeTitle": "Révoquer cet utilisateur ? | Révoquer ces utilisateurs ?", "revokeQuestion": "L'utilisateur {user} sera révoqué. Il ne pourra plus accéder à l'organisation. Voulez-vous continuer ? | Ces {count} utilisateurs seront révoqués. Ils ne pourront plus accéder à l'organisation. Voulez-vous continuer ?", diff --git a/client/src/theme/components/modals.scss b/client/src/theme/components/modals.scss index 58eed0baf9b..fd02452b1f3 100644 --- a/client/src/theme/components/modals.scss +++ b/client/src/theme/components/modals.scss @@ -182,6 +182,10 @@ ion-modal.join-by-link-modal::part(content) { } } +.user-details-modal { + --width: 642px; +} + .file-upload-modal { --width: 842px; } diff --git a/client/src/views/users/ActiveUsersPage.vue b/client/src/views/users/ActiveUsersPage.vue index f055e384849..96d5225ecf6 100644 --- a/client/src/views/users/ActiveUsersPage.vue +++ b/client/src/views/users/ActiveUsersPage.vue @@ -159,7 +159,19 @@ import { import { routerNavigateTo } from '@/router'; import { Notification, NotificationKey, NotificationLevel, NotificationManager } from '@/services/notificationManager'; import UserContextMenu, { UserAction } from '@/views/users/UserContextMenu.vue'; -import { IonCheckbox, IonContent, IonItem, IonLabel, IonList, IonListHeader, IonPage, IonText, popoverController } from '@ionic/vue'; +import UserDetailsModal from '@/views/users/UserDetailsModal.vue'; +import { + IonCheckbox, + IonContent, + IonItem, + IonLabel, + IonList, + IonListHeader, + IonPage, + IonText, + modalController, + popoverController, +} from '@ionic/vue'; import { eye, personAdd, personRemove } from 'ionicons/icons'; import { Ref, computed, inject, onMounted, onUnmounted, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; @@ -327,7 +339,16 @@ async function revokeSelectedUsers(): Promise { } async function details(user: UserInfo): Promise { - console.log(`Show details on user ${user.humanHandle.label}`); + const modal = await modalController.create({ + component: UserDetailsModal, + cssClass: 'user-details-modal', + componentProps: { + user: user, + }, + }); + await modal.present(); + await modal.onWillDismiss(); + await modal.dismiss(); } function isCurrentUser(userId: UserID): boolean { diff --git a/client/src/views/users/RevokedUsersPage.vue b/client/src/views/users/RevokedUsersPage.vue index 85e85e19c18..772d8df8a69 100644 --- a/client/src/views/users/RevokedUsersPage.vue +++ b/client/src/views/users/RevokedUsersPage.vue @@ -117,7 +117,19 @@ import UserListItem from '@/components/users/UserListItem.vue'; import { UserInfo, listRevokedUsers as parsecListRevokedUsers } from '@/parsec'; import { Notification, NotificationKey, NotificationLevel, NotificationManager } from '@/services/notificationManager'; import UserContextMenu, { UserAction } from '@/views/users/UserContextMenu.vue'; -import { IonCheckbox, IonContent, IonItem, IonLabel, IonList, IonListHeader, IonPage, IonText, popoverController } from '@ionic/vue'; +import UserDetailsModal from '@/views/users/UserDetailsModal.vue'; +import { + IonCheckbox, + IonContent, + IonItem, + IonLabel, + IonList, + IonListHeader, + IonPage, + IonText, + modalController, + popoverController, +} from '@ionic/vue'; import { eye } from 'ionicons/icons'; import { Ref, computed, inject, onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; @@ -181,7 +193,16 @@ function selectAllUsers(checked: boolean): void { } async function details(user: UserInfo): Promise { - console.log(`Show details on user ${user.humanHandle.label}`); + const modal = await modalController.create({ + component: UserDetailsModal, + cssClass: 'user-details-modal', + componentProps: { + user: user, + }, + }); + await modal.present(); + await modal.onWillDismiss(); + await modal.dismiss(); } async function openUserContextMenu(event: Event, user: UserInfo, onFinished?: () => void): Promise { diff --git a/client/src/views/users/UserDetailsModal.vue b/client/src/views/users/UserDetailsModal.vue new file mode 100644 index 00000000000..81d9b52be98 --- /dev/null +++ b/client/src/views/users/UserDetailsModal.vue @@ -0,0 +1,190 @@ + + + + + + + diff --git a/client/tests/e2e/specs/test_user_details_modal.ts b/client/tests/e2e/specs/test_user_details_modal.ts new file mode 100644 index 00000000000..c14156331c7 --- /dev/null +++ b/client/tests/e2e/specs/test_user_details_modal.ts @@ -0,0 +1,35 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +describe('Check user details modal', () => { + beforeEach(() => { + cy.visitApp(); + cy.login('Boby', 'P@ssw0rd.'); + cy.get('.organization-card__manageBtn').click(); + cy.get('.users-container').find('.user-list-item').eq(2).find('.options-button').invoke('show').click(); + cy.get('.user-context-menu').find('.menu-list').find('ion-item').as('menuItems'); + cy.get('@menuItems').eq(3).contains('View details').click(); + }); + + afterEach(() => { + cy.dropTestbed(); + }); + + it('Tests user details modal', () => { + cy.get('.user-details-modal').as('modal').find('ion-header').contains('User details'); + cy.get('@modal').find('.ms-modal-content').as('modal-content').find('ion-text').eq(0).contains('Name'); + // cspell:disable-next-line + cy.get('@modal-content').find('ion-text').eq(1).contains('Jaheira'); + cy.get('@modal-content').find('ion-text').eq(2).contains('Joined'); + cy.get('@modal-content').find('ion-text').eq(3).contains('one second ago'); + cy.get('@modal-content').find('ion-text').eq(4).contains('Shared workspaces'); + cy.get('@modal-content').find('ion-list').find('ion-card').as('workspace-cards').should('have.length', 2); + cy.get('@workspace-cards').eq(0).find('ion-text').contains('Workspace1'); + cy.get('@workspace-cards').eq(0).find('ion-icon').should('exist'); + cy.get('@workspace-cards').eq(0).find('ion-label').contains('Owner'); + cy.get('@workspace-cards').eq(1).find('ion-text').contains('Workspace2'); + cy.get('@workspace-cards').eq(1).find('ion-icon').should('exist'); + cy.get('@workspace-cards').eq(1).find('ion-label').contains('Contributor'); + cy.get('@modal-content').find('ion-button').contains('Close').click(); + cy.get('@modal').should('not.exist'); + }); +});