diff --git a/client/src/assets/images/ellipsis-gradient.svg b/client/src/assets/images/ellipsis-gradient.svg new file mode 100644 index 00000000000..64ffe1c5b06 --- /dev/null +++ b/client/src/assets/images/ellipsis-gradient.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/src/assets/images/keypad-gradient.svg b/client/src/assets/images/keypad-gradient.svg new file mode 100644 index 00000000000..a6e8213336e --- /dev/null +++ b/client/src/assets/images/keypad-gradient.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/components/devices/DeviceCard.vue b/client/src/components/devices/DeviceCard.vue index 8ee44ff0889..07693c59357 100644 --- a/client/src/components/devices/DeviceCard.vue +++ b/client/src/components/devices/DeviceCard.vue @@ -5,33 +5,36 @@ class="card" :class="isCurrent ? 'device-active' : ''" > - -
-
- - {{ device.deviceLabel }} - - - {{ $msTranslate('DevicesPage.joinedOn') }} - {{ $msTranslate(formatTimeSince(device.createdOn, '--', 'short', true)) }} - -
- + +
+
+ + {{ device.deviceLabel }} + + + {{ $msTranslate('DevicesPage.joinedOn') }} + {{ $msTranslate(formatTimeSince(device.createdOn, '--', 'short', true)) }} + +
+
+ + + {{ $msTranslate('DevicesPage.activeDeviceBadge') }} +
- - {{ $msTranslate('DevicesPage.activeDeviceBadge') }} - + @@ -57,15 +60,18 @@ defineProps<{ width: 100%; border-radius: var(--parsec-radius-8); display: flex; - align-items: center; - gap: 1.5rem; + flex-direction: column; + gap: 1rem; - &.device-active { - background-color: var(--parsec-color-light-secondary-background); + &-content { + display: flex; + align-items: center; + gap: 1.5rem; } .icon-device { font-size: 1.5rem; + flex-shrink: 0; padding: 0.5rem; border-radius: var(--parsec-radius-circle); color: var(--parsec-color-light-secondary-grey); @@ -76,6 +82,7 @@ defineProps<{ display: flex; flex-direction: column; gap: 0.5rem; + overflow: hidden; &-info { display: flex; @@ -84,7 +91,10 @@ defineProps<{ } .device-name { - color: var(--parsec-color-light-secondary-text); + color: var(--parsec-color-light-primary-700); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .join-date { @@ -108,9 +118,19 @@ defineProps<{ } } } + + &.device-active { + background-color: var(--parsec-color-light-secondary-background); + + .icon-device { + color: var(--parsec-color-light-secondary-hard-grey); + background-color: var(--parsec-color-light-secondary-medium); + } + } } .badge { + font-size: 0.8125rem; margin: auto 0 auto auto; border-radius: var(--parsec-radius-32); padding: 0.25em 0.5em; diff --git a/client/src/components/profile/AuthentificationCard.vue b/client/src/components/profile/AuthentificationCard.vue new file mode 100644 index 00000000000..6f376d45463 --- /dev/null +++ b/client/src/components/profile/AuthentificationCard.vue @@ -0,0 +1,60 @@ + + + + + + diff --git a/client/src/locales/en-US.json b/client/src/locales/en-US.json index 4521117f32b..5b39ec3333a 100644 --- a/client/src/locales/en-US.json +++ b/client/src/locales/en-US.json @@ -361,6 +361,7 @@ }, "SettingsModal": { "pageTitle": "Settings", + "description": "Access all language, theme and privacy settings for the application.", "general": "General", "advanced": "Advanced", "enableTelemetry": { @@ -737,30 +738,13 @@ } }, "DevicesPage": { - "addDevice": "Add", + "title": "My devices", + "description": "You can preview and add new devices to your profile to access this organization.", + "addDevice": "Add a new device", "noDevices": "No devices.", "activeDeviceBadge": "Current", "joinedOn": "Joined:", "now": "Now", - "restorePassword": { - "title": "Create a recovery file", - "done": { - "label": "Recovery file already created", - "subtitle": "A recovery file allows you to get back access to your data in case your forgot your password or lose your devices.", - "subtitle2": "Without a recovery file, your account cannot be recovered and you will need to be re-invited to join the organization.", - "button": "Create a recovery file", - "recreateQuestionTitle": "Create a new recovery file?", - "recreateQuestionMessage": "You have already created a recovery file in the past. Do you want to create a new one?", - "recreateYes": "Create a new recovery file", - "recreateNo": "Cancel" - }, - "notDone": { - "label": "Action required", - "subtitle": "A recovery file allows you to get back access to your data in case your forgot your password or lose your devices.", - "subtitle2": "Without a recovery file, your account cannot be recovered and you will need to be re-invited to join the organization.", - "button": "Create a recovery file" - } - }, "greet": { "steps": { "hostCode": "Host code", @@ -833,10 +817,17 @@ "noMatch": "Do not match" }, "Authentication": { + "title": "Authentication", + "description": "You can change your authentication method at any time.", "keyringInfo": "Authentication will be handled through your system, so you do not have to remember an additional password. Just do not forget to lock your session when you are not in front of your computer!", + "method": { + "system": "Authentication system", + "password": "Password" + }, + "changeAuthenticationButton": "Change method", "useKeyring": "Use System Authentication", "usePassword": "Use Password", - "description": "Authentication method", + "label": "Authentication method", "keyringUnavailableOnSystem": "Unavailable on your system", "keyringUnavailableOnWeb": "Unavailable on web" }, @@ -891,6 +882,7 @@ "fileDownloaded": "File downloaded" }, "actions": { + "createRecoveryButton": "Create a recovery file", "download": "Download", "downloadAgain": "Download again", "backToDevices": "Back to my devices" @@ -1181,6 +1173,8 @@ } }, "AboutPage": { + "title": "About", + "description": "Get application information and latest changes.", "appInfo": { "version": "Version", "developer": "Developer", @@ -1257,15 +1251,48 @@ "changeAuthenticationButton": "Update", "authenticationUpdated": "Authentication has been updated.", "tabs": { - "devices": "My devices", - "authentication": "Authentication" + "account": { + "title": "My account", + "settings": "Settings", + "devices": "My devices", + "authentication": "Authentication", + "recovery": "Organization recovery" + }, + "support": { + "title": "Support", + "documentation": "Documentation", + "help": "Feedback", + "about": "About" + } }, + "logout": "Log out", "errors": { "wrongPassword": "Wrong password. Please try again.", "cannotChangeAuthentication": "Failed to update authentication", "failedToRetrieveInformation": "Failed to retrieve information. Please make sure you are online." } }, + "OrganizationRecovery": { + "title": "Organization recovery files", + "recoveryFile": "Recovery File", + "recoveryKey": "Secret Key", + "done": { + "label": "Recovery file already created", + "subtitle": "A recovery file allows you to get back access to your data in case your forgot your password or lose your devices.", + "subtitle2": "Without a recovery file, your account cannot be recovered and you will need to be re-invited to join the organization.", + "button": "Download file", + "recreateQuestionTitle": "Create a new recovery file?", + "recreateQuestionMessage": "You have already created a recovery file in the past. Do you want to create a new one?", + "recreateYes": "Create a new recovery file", + "recreateNo": "Cancel" + }, + "notDone": { + "label": "Action required", + "subtitle": "A recovery file allows you to get back access to your data in case your forgot your password or lose your devices.", + "subtitle2": "Without a recovery file, your account cannot be recovered and you will need to be re-invited to join the organization.", + "button": "Create a recovery file" + } + }, "validators": { "invalidProtocol": "Link should start with 'parsec3://'.", "organizationName": { @@ -1410,7 +1437,7 @@ "customOrderProcessing": "Order tracking", "orders": "Orders" }, - "preferences": "Preferences" + "settings": "Settings" }, "personalDataPage": { "personalInfo": { diff --git a/client/src/locales/fr-FR.json b/client/src/locales/fr-FR.json index 54892be21d3..1a4d8009b7b 100644 --- a/client/src/locales/fr-FR.json +++ b/client/src/locales/fr-FR.json @@ -361,6 +361,7 @@ }, "SettingsModal": { "pageTitle": "Paramètres", + "description": "Accéder à l'ensemble des paramètres de langue, de thème et de confidentialité de l'application.", "general": "Général", "advanced": "Avancé", "enableTelemetry": { @@ -737,30 +738,13 @@ } }, "DevicesPage": { - "addDevice": "Ajouter", + "title": "Mes appareils", + "description": "Vous pouvez consulter et ajouter de nouveaux appareils à votre profil pour accéder à cette organisation.", + "addDevice": "Ajouter un nouvel appareil", "noDevices": "Aucun appareil.", "activeDeviceBadge": "Actif", "joinedOn": "Rejoint :", "now": "À l'instant", - "restorePassword": { - "title": "Créer un fichier de récupération", - "done": { - "label": "Fichier de récupération déjà créé", - "subtitle": "Un fichier de récupération vous permet de retrouver l'accès à vos données en cas d'oubli de votre mot de passe ou de perte de vos appareils.", - "subtitle2": "Sans fichier de récupération, votre compte ne pourra pas être récupéré et vous devrez être réinvité à rejoindre l'organisation.", - "button": "Créer un fichier de récupération", - "recreateQuestionTitle": "Créer un nouveau fichier de récupération ?", - "recreateQuestionMessage": "Vous avez déjà créé un fichier de récupération dans le passé. Souhaitez-vous en créer un nouveau ?", - "recreateYes": "Créer un nouveau fichier", - "recreateNo": "Annuler" - }, - "notDone": { - "label": "Action requise", - "subtitle": "Un fichier de récupération vous permet de retrouver l'accès à vos données en cas d'oubli de votre mot de passe ou de perte de vos appareils.", - "subtitle2": "Sans fichier de récupération, votre compte ne pourra pas être récupéré et vous devrez être réinvité à rejoindre l'organisation.", - "button": "Créer un fichier de récupération" - } - }, "greet": { "steps": { "hostCode": "Code hôte", @@ -833,10 +817,17 @@ "noMatch": "Ne correspondent pas" }, "Authentication": { + "title": "Authentification", + "description": "Vous pouvez à tout moment changer de méthode pour vous authentifier à cette organisation.", "keyringInfo": "L'authentification sera gérée par votre système. Vous n'avez pas à retenir un mot de passe supplémentaire. N'oubliez pas de verrouiller votre session quand vous n'utilisez pas votre ordinateur !", + "method": { + "system": "Authentification du système", + "password": "Mot de passe" + }, + "changeAuthenticationButton": "Changer de méthode", "useKeyring": "Utiliser l'authentification du système", "usePassword": "Utiliser un mot de passe", - "description": "Méthode d'authentification", + "label": "Méthode d'authentification", "keyringUnavailableOnSystem": "Indisponible sur votre système", "keyringUnavailableOnWeb": "Indisponible sur le web" }, @@ -891,6 +882,7 @@ "fileDownloaded": "Fichier téléchargé" }, "actions": { + "createRecoveryButton": "Créer un fichier de récupération", "download": "Télécharger", "downloadAgain": "Télécharger à nouveau", "backToDevices": "Retour à mes appareils" @@ -1181,6 +1173,8 @@ } }, "AboutPage": { + "title": "À propos", + "description": "Consulter les informations de l'application et les derniers changements apportés.", "appInfo": { "version": "Version", "developer": "Développeur", @@ -1257,8 +1251,19 @@ "changeAuthenticationButton": "Modifier", "authenticationUpdated": "L'authentification a été mise à jour.", "tabs": { - "devices": "Mes appareils", - "authentication": "Authentification" + "account": { + "title": "Mon compte", + "settings": "Paramètres", + "devices": "Mes appareils", + "authentication": "Authentication", + "recovery": "Récupération d'organisation" + }, + "support": { + "title": "Support", + "documentation": "Documentation", + "help": "Commentaires", + "about": "A propos" + } }, "errors": { "wrongPassword": "Mot de passe incorrect. Veuillez réessayer.", @@ -1266,6 +1271,27 @@ "failedToRetrieveInformation": "Impossible de récupérer les informations. Veuillez vérifier que vous êtes bien connecté(e) à Internet." } }, + "OrganizationRecovery": { + "title": "Fichiers de récupération d'organisation", + "recoveryFile": "Fichier de récupération", + "recoveryKey": "Clé secrète", + "done": { + "label": "Fichier déjà créé", + "subtitle": "Un fichier de récupération vous permet de retrouver l'accès à vos données en cas d'oubli de votre mot de passe ou de perte de vos appareils.", + "subtitle2": "Sans fichier de récupération, votre compte ne pourra pas être récupéré et vous devrez être réinvité à rejoindre l'organisation.", + "button": "Créer un fichier de récupération", + "recreateQuestionTitle": "Créer un nouveau fichier de récupération ?", + "recreateQuestionMessage": "Vous avez déjà créé un fichier de récupération dans le passé. Souhaitez-vous en créer un nouveau ?", + "recreateYes": "Créer un nouveau fichier", + "recreateNo": "Annuler" + }, + "notDone": { + "label": "Action requise", + "subtitle": "Un fichier de récupération vous permet de retrouver l'accès à vos données en cas d'oubli de votre mot de passe ou de perte de vos appareils.", + "subtitle2": "Sans fichier de récupération, votre compte ne pourra pas être récupéré et vous devrez être réinvité à rejoindre l'organisation.", + "button": "Créer un fichier de récupération" + } + }, "validators": { "invalidProtocol": "Le lien doit commencer par 'parsec3://'.", "organizationName": { @@ -1410,7 +1436,7 @@ "customOrderProcessing": "Suivi de commande", "orders": "Commandes" }, - "preferences": "Préférences" + "settings": "Settings" }, "personalDataPage": { "personalInfo": { diff --git a/client/src/router/types.ts b/client/src/router/types.ts index 74a8ec576d1..912a69bfd66 100644 --- a/client/src/router/types.ts +++ b/client/src/router/types.ts @@ -117,7 +117,7 @@ const routes: Array = [ { path: `/:handle(\\d+)/${Routes.RecoveryExport}`, name: Routes.RecoveryExport, - component: () => import('@/views/devices/ExportRecoveryDevicePage.vue'), + component: () => import('@/views/profile/OrganizationRecoveryPage.vue'), }, { path: `/:handle(\\d+)/${Routes.Viewer}`, diff --git a/client/src/views/client-area/ClientAreaHeader.vue b/client/src/views/client-area/ClientAreaHeader.vue index 9a9a84998d0..22d9e889ba5 100644 --- a/client/src/views/client-area/ClientAreaHeader.vue +++ b/client/src/views/client-area/ClientAreaHeader.vue @@ -5,14 +5,14 @@ {{ $msTranslate(title) }}
- + - {{ $msTranslate('clientArea.header.preferences') }} + {{ $msTranslate('clientArea.header.settings') }} @@ -53,10 +53,6 @@ onMounted(async () => { } }); -async function openPreferencesModal(): Promise { - await openSettingsModal(); -} - const emits = defineEmits<{ (e: 'pageSelected', page: ClientAreaPages): void; }>(); diff --git a/client/src/views/devices/DevicesPage.vue b/client/src/views/devices/DevicesPage.vue index c9733ad8791..9f9fa81a0e6 100644 --- a/client/src/views/devices/DevicesPage.vue +++ b/client/src/views/devices/DevicesPage.vue @@ -1,149 +1,57 @@ diff --git a/client/src/views/devices/ExportRecoveryDevicePage.vue b/client/src/views/devices/ExportRecoveryDevicePage.vue deleted file mode 100644 index ed1fae50add..00000000000 --- a/client/src/views/devices/ExportRecoveryDevicePage.vue +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - diff --git a/client/src/views/profile/AuthentificationPage.vue b/client/src/views/profile/AuthentificationPage.vue new file mode 100644 index 00000000000..25acf9e9c92 --- /dev/null +++ b/client/src/views/profile/AuthentificationPage.vue @@ -0,0 +1,144 @@ + + + + + + + diff --git a/client/src/views/profile/OrganizationRecoveryPage.vue b/client/src/views/profile/OrganizationRecoveryPage.vue new file mode 100644 index 00000000000..b6d9e04803f --- /dev/null +++ b/client/src/views/profile/OrganizationRecoveryPage.vue @@ -0,0 +1,326 @@ + + + + + + + diff --git a/client/src/views/settings/SettingsPage.vue b/client/src/views/settings/SettingsPage.vue new file mode 100644 index 00000000000..19386ba41a2 --- /dev/null +++ b/client/src/views/settings/SettingsPage.vue @@ -0,0 +1,186 @@ + + + + + + + diff --git a/client/src/views/users/MyProfilePage.vue b/client/src/views/users/MyProfilePage.vue index ead4396cf9c..ad9cd7c57a9 100644 --- a/client/src/views/users/MyProfilePage.vue +++ b/client/src/views/users/MyProfilePage.vue @@ -3,13 +3,30 @@ diff --git a/client/tests/e2e/helpers/fixtures.ts b/client/tests/e2e/helpers/fixtures.ts index f24cff18bb3..231e77cabe8 100644 --- a/client/tests/e2e/helpers/fixtures.ts +++ b/client/tests/e2e/helpers/fixtures.ts @@ -51,7 +51,6 @@ export const msTest = base.extend<{ await home.locator('#password-input').locator('input').fill('P@ssw0rd.'); await expect(home.locator('.login-button')).toBeEnabled(); await home.locator('.login-button').click(); - await expect(home.locator('.loading-container')).toBeVisible(); await expect(home).toHaveURL(/\/loading\??.*$/); await expect(home.locator('#connected-header')).toContainText('My workspaces'); await expect(home).toBeWorkspacePage(); @@ -145,7 +144,9 @@ export const msTest = base.extend<{ await myProfileButton.click(); await expect(connected).toHavePageTitle('My profile'); await expect(connected).toBeMyProfilePage(); - await connected.locator('.devices-header-button').click(); + await expect(connected.locator('.menu-list__item').nth(1)).toHaveText('My devices'); + await connected.locator('.menu-list__item').nth(1).click(); + await connected.locator('#add-device-button').click(); const modal = connected.locator('.greet-organization-modal'); await expect(modal).toBeVisible(); await use(modal); diff --git a/client/tests/e2e/specs/home_page.spec.ts b/client/tests/e2e/specs/home_page.spec.ts index d4ca480ca7a..a8532d351ba 100644 --- a/client/tests/e2e/specs/home_page.spec.ts +++ b/client/tests/e2e/specs/home_page.spec.ts @@ -105,6 +105,7 @@ msTest('Check join link', async ({ home }) => { }); msTest('Login', async ({ home }) => { + await expect(home.locator('.loading-container')).toBeVisible(); await home.locator('.organization-list').locator('.organization-card').nth(0).click(); await expect(home.locator('.login-header__title')).toHaveText('Log in'); const loginButton = home.locator('.login-card-footer').locator('.login-button'); diff --git a/client/tests/e2e/specs/my_profile_page.spec.ts b/client/tests/e2e/specs/my_profile_page.spec.ts index b67724a6ada..07220649ae4 100644 --- a/client/tests/e2e/specs/my_profile_page.spec.ts +++ b/client/tests/e2e/specs/my_profile_page.spec.ts @@ -3,7 +3,9 @@ import { expect, fillIonInput, msTest } from '@tests/e2e/helpers'; msTest('Check devices list', async ({ myProfilePage }) => { - await expect(myProfilePage.locator('#add-device-button')).toHaveText('Add'); + await expect(myProfilePage.locator('.menu-list__item').nth(1)).toHaveText('My devices'); + await myProfilePage.locator('.menu-list__item').nth(1).click(); + await expect(myProfilePage.locator('#add-device-button')).toHaveText('Add a new device'); const devices = myProfilePage.locator('#devices-list').getByRole('listitem'); await expect(devices.locator('.device-name')).toHaveText([/^device\d$/, /^device\d$/]); await expect(devices.locator('.join-date')).toHaveText(['Joined: Today', 'Joined: Today']); @@ -12,29 +14,31 @@ msTest('Check devices list', async ({ myProfilePage }) => { await expect(devices.nth(1).locator('.badge')).toBeHidden(); }); +msTest('Open authentication section', async ({ myProfilePage }) => { + await expect(myProfilePage.locator('.menu-list__item').nth(2)).toHaveText('Authentication'); + await myProfilePage.locator('.menu-list__item').nth(2).click(); + await expect(myProfilePage.locator('.profile-content-item').locator('.item-header__title')).toHaveText('Authentication'); + await expect(myProfilePage.locator('#change-authentication-button')).toBeVisible(); +}); + msTest('Check if restore-password section is displayed', async ({ myProfilePage }) => { - const restorePassword = myProfilePage.locator('.restore-password'); + await expect(myProfilePage.locator('.menu-list__item').nth(3)).toHaveText('Organization recovery'); + await myProfilePage.locator('.menu-list__item').nth(3).click(); + const restorePassword = myProfilePage.locator('.recovery'); await expect(restorePassword).toBeVisible(); - await expect(restorePassword.locator('.restore-password-header__title')).toHaveText('Create a recovery file'); - await expect(restorePassword.locator('.restore-password-subtitles')).toHaveText( + await expect(restorePassword.locator('.item-header__title')).toHaveText('Organization recovery files'); + await expect(restorePassword.locator('.item-header__description')).toHaveText( `A recovery file allows you to get back access to your data in case your forgot your password or lose your devices.Without a recovery file, your account cannot be recovered and you will need to be re-invited to join the organization.`, ); await expect(restorePassword.locator('.restore-password-button')).toHaveText('Create a recovery file'); }); -msTest('Open authentication section', async ({ myProfilePage }) => { - await myProfilePage.locator('ion-radio').nth(1).click(); - await expect(myProfilePage.locator('.user-info').locator('.title')).toHaveText('Password'); - await expect(myProfilePage.locator('.user-info').locator('.input-container').locator('.user-info__input')).toHaveTheClass( - 'input-disabled', - ); - await expect(myProfilePage.locator('#change-authentication-button')).toBeVisible(); -}); - msTest('Change password', async ({ home, myProfilePage }) => { - await myProfilePage.locator('ion-radio').nth(1).click(); - await myProfilePage.locator('.user-info').locator('#change-authentication-button').click(); + await expect(myProfilePage.locator('.menu-list__item').nth(2)).toHaveText('Authentication'); + await myProfilePage.locator('.menu-list__item').nth(2).click(); + await expect(myProfilePage.locator('.profile-content-item').locator('.item-header__title')).toHaveText('Authentication'); + await myProfilePage.locator('#change-authentication-button').click(); const changePasswordModal = home.locator('.change-authentication-modal'); await expect(changePasswordModal).toBeVisible(); await expect(changePasswordModal.locator('.modal-header')).toHaveText('Enter your current password'); diff --git a/newsfragments/9470.bugfix.rst b/newsfragments/9470.bugfix.rst new file mode 100644 index 00000000000..0d1a546e978 --- /dev/null +++ b/newsfragments/9470.bugfix.rst @@ -0,0 +1 @@ +Gathering all account settings and support content in Profile with tabs