diff --git a/REUSE.toml b/REUSE.toml index 6925a14daf..e9e447c556 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -144,7 +144,7 @@ SPDX-FileCopyrightText = "2018-2024 Google LLC, 2016-2024 Nextcloud GmbH and Nex SPDX-License-Identifier = "Apache-2.0" [[annotations]] -path = ["img/mail.png", "img/mail.svg", "img/mail-dark.svg", "img/important.svg", "img/star.png", "img/star.svg", "img/mail-notification.png", "img/mail-notification.svg", "img/text_snippet.svg", "img/format-pilcrow-arrow-right.svg", "img/format-pilcrow-arrow-left.svg"] +path = ["img/mail.png", "img/mail.svg", "img/mail-dark.svg", "img/important.svg", "img/star.png", "img/star.svg", "img/mail-notification.png", "img/mail-notification.svg", "img/text_snippet.svg", "img/format-pilcrow-arrow-right.svg", "img/format-pilcrow-arrow-left.svg", "img/delegation.svg"] precedence = "aggregate" SPDX-FileCopyrightText = "2018-2026 Google LLC" SPDX-License-Identifier = "Apache-2.0" diff --git a/img/delegation.svg b/img/delegation.svg new file mode 100644 index 0000000000..dac6d654f5 --- /dev/null +++ b/img/delegation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/AppSettingsMenu.vue b/src/components/AppSettingsMenu.vue index 62f64aea4b..c10fa694ff 100755 --- a/src/components/AppSettingsMenu.vue +++ b/src/components/AppSettingsMenu.vue @@ -32,6 +32,16 @@ {{ account.emailAddress }} + + + + + {{ t('mail', '{email} (delegated)', {email: account.emailAddress}) }} + account && account.emailAddress) + return this.getAccounts.filter((account) => account && account.emailAddress && !account.isDelegated) + }, + + delegatedAccounts() { + return this.getAccounts.filter((account) => account && account.emailAddress && account.isDelegated) }, sortFavorites: { diff --git a/src/components/DelegationModal.vue b/src/components/DelegationModal.vue new file mode 100644 index 0000000000..cc27f09e26 --- /dev/null +++ b/src/components/DelegationModal.vue @@ -0,0 +1,315 @@ + + + + + + {{ t('mail', 'Delegation') }} + + + + {{ t('mail', 'Allow users to send, receive, and delete mail on your behalf') }} + + + + + + + + + + + + + + + + + + + + {{ t('mail', 'Add delegate') }} + + + + + + + + + {{ t('mail', 'They will be able to send, receive, and delete mail on your behalf') }} + + + + + + {{ revokeText }} + + + + + + + diff --git a/src/components/NavigationAccount.vue b/src/components/NavigationAccount.vue index 797ec33081..3cc520a4ee 100644 --- a/src/components/NavigationAccount.vue +++ b/src/components/NavigationAccount.vue @@ -36,6 +36,18 @@ {{ t('mail', 'Account settings') }} + + + + + {{ t('mail', 'Delegate account') }} + + @@ -92,7 +105,7 @@ import { DialogBuilder, showError } from '@nextcloud/dialogs' import { formatFileSize } from '@nextcloud/files' import { generateUrl } from '@nextcloud/router' -import { NcActionButton as ActionButton, NcActionCheckbox as ActionCheckbox, NcActionInput as ActionInput, NcActionText as ActionText, NcLoadingIcon as IconLoading, NcAppNavigationCaption } from '@nextcloud/vue' +import { NcActionButton as ActionButton, NcActionCheckbox as ActionCheckbox, NcActionInput as ActionInput, NcActionText as ActionText, NcLoadingIcon as IconLoading, NcAppNavigationCaption, NcIconSvgWrapper } from '@nextcloud/vue' import { mapStores } from 'pinia' import { Fragment } from 'vue-frag' import MenuDown from 'vue-material-design-icons/ChevronDown.vue' @@ -104,6 +117,7 @@ import IconDelete from 'vue-material-design-icons/TrashCanOutline.vue' import logger from '../logger.js' import { fetchQuota } from '../service/AccountService.js' import useMainStore from '../store/mainStore.js' +import IconDelegation from './../../img/delegation.svg' export default { name: 'NavigationAccount', @@ -115,8 +129,10 @@ export default { ActionInput, ActionText, AccountSettings: () => import(/* webpackChunkName: "account-settings" */ './AccountSettings.vue'), + DelegationModal: () => import(/* webpackChunkName: "delegation-modal" */ './DelegationModal.vue'), IconInfo, IconSettings, + NcIconSvgWrapper, IconFolderAdd, MenuDown, MenuUp, @@ -162,6 +178,8 @@ export default { quota: undefined, editing: false, showSaving: false, + showDelegationModal: false, + IconDelegation, createMailboxName: '', showMailboxes: false, nameInput: false, @@ -179,6 +197,10 @@ export default { return this.account.isUnified !== true && this.account.visible !== false }, + canDelegate() { + return !this.account.isDelegated + }, + id() { return 'account-' + this.account.id }, diff --git a/src/service/DelegationService.js b/src/service/DelegationService.js new file mode 100644 index 0000000000..379ab86c79 --- /dev/null +++ b/src/service/DelegationService.js @@ -0,0 +1,58 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' + +/** + * @typedef Delegation + * @property {number} id the delegation id + * @property {number} accountId the account id + * @property {string} userId the delegated user id + */ + +/** + * Fetch all users that have delegation for a given account + * + * @param {number} accountId id of the account + * @return {Promise} + */ +export async function fetchDelegatedUsers(accountId) { + const url = generateUrl('/apps/mail/api/delegations/{accountId}', { + accountId, + }) + + return axios.get(url).then((resp) => resp.data) +} + +/** + * Delegate an account to a user + * + * @param {number} accountId id of the account + * @param {string} userId id of the user to delegate to + * @return {Promise} + */ +export async function delegate(accountId, userId) { + const url = generateUrl('/apps/mail/api/delegations/{accountId}', { + accountId, + }) + + return axios.post(url, { userId }).then((resp) => resp.data) +} + +/** + * Revoke delegation of an account for a user + * + * @param {number} accountId id of the account + * @param {string} userId id of the user to revoke delegation for + * @return {Promise} + */ +export async function unDelegate(accountId, userId) { + const url = generateUrl('/apps/mail/api/delegations/{accountId}/{userId}', { + accountId, + userId, + }) + + return axios.delete(url).then((resp) => resp.data) +}
+ {{ t('mail', 'Allow users to send, receive, and delete mail on your behalf') }} +
+ {{ t('mail', 'They will be able to send, receive, and delete mail on your behalf') }} +
+ {{ revokeText }} +