mirror of https://github.com/nextcloud/server.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
442 lines
11 KiB
Vue
442 lines
11 KiB
Vue
<!--
|
|
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
|
-
|
|
- @author Christopher Ng <chrng8@gmail.com>
|
|
-
|
|
- @license GNU AGPL version 3 or any later version
|
|
-
|
|
- This program is free software: you can redistribute it and/or modify
|
|
- it under the terms of the GNU Affero General Public License as
|
|
- published by the Free Software Foundation, either version 3 of the
|
|
- License, or (at your option) any later version.
|
|
-
|
|
- This program is distributed in the hope that it will be useful,
|
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
- GNU Affero General Public License for more details.
|
|
-
|
|
- You should have received a copy of the GNU Affero General Public License
|
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
-
|
|
-->
|
|
|
|
<template>
|
|
<div>
|
|
<div class="email">
|
|
<input :id="inputId"
|
|
ref="email"
|
|
type="email"
|
|
:placeholder="inputPlaceholder"
|
|
:value="email"
|
|
:aria-describedby="helperText ? `${inputId}-helper-text` : ''"
|
|
autocapitalize="none"
|
|
autocomplete="on"
|
|
autocorrect="off"
|
|
@input="onEmailChange">
|
|
|
|
<div class="email__actions-container">
|
|
<transition name="fade">
|
|
<Check v-if="showCheckmarkIcon" :size="20" />
|
|
<AlertOctagon v-else-if="showErrorIcon" :size="20" />
|
|
</transition>
|
|
|
|
<template v-if="!primary">
|
|
<FederationControl :readable="propertyReadable"
|
|
:additional="true"
|
|
:additional-value="email"
|
|
:disabled="federationDisabled"
|
|
:handle-additional-scope-change="saveAdditionalEmailScope"
|
|
:scope.sync="localScope"
|
|
@update:scope="onScopeChange" />
|
|
</template>
|
|
|
|
<NcActions class="email__actions"
|
|
:aria-label="t('settings', 'Email options')"
|
|
:force-menu="true">
|
|
<NcActionButton :aria-label="deleteEmailLabel"
|
|
:close-after-click="true"
|
|
:disabled="deleteDisabled"
|
|
icon="icon-delete"
|
|
@click.stop.prevent="deleteEmail">
|
|
{{ deleteEmailLabel }}
|
|
</NcActionButton>
|
|
<NcActionButton v-if="!primary || !isNotificationEmail"
|
|
:aria-label="setNotificationMailLabel"
|
|
:close-after-click="true"
|
|
:disabled="setNotificationMailDisabled"
|
|
icon="icon-favorite"
|
|
@click.stop.prevent="setNotificationMail">
|
|
{{ setNotificationMailLabel }}
|
|
</NcActionButton>
|
|
</NcActions>
|
|
</div>
|
|
</div>
|
|
|
|
<p v-if="helperText"
|
|
:id="`${inputId}-helper-text`"
|
|
class="email__helper-text-message email__helper-text-message--error">
|
|
<AlertCircle class="email__helper-text-message__icon" :size="18" />
|
|
{{ helperText }}
|
|
</p>
|
|
|
|
<em v-if="isNotificationEmail">
|
|
{{ t('settings', 'Primary email for password reset and notifications') }}
|
|
</em>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { NcActions, NcActionButton } from '@nextcloud/vue'
|
|
import AlertCircle from 'vue-material-design-icons/AlertCircleOutline.vue'
|
|
import AlertOctagon from 'vue-material-design-icons/AlertOctagon.vue'
|
|
import Check from 'vue-material-design-icons/Check.vue'
|
|
import { showError } from '@nextcloud/dialogs'
|
|
import debounce from 'debounce'
|
|
|
|
import FederationControl from '../shared/FederationControl.vue'
|
|
import logger from '../../../logger.js'
|
|
|
|
import { ACCOUNT_PROPERTY_READABLE_ENUM, VERIFICATION_ENUM } from '../../../constants/AccountPropertyConstants.js'
|
|
import {
|
|
removeAdditionalEmail,
|
|
saveAdditionalEmail,
|
|
saveAdditionalEmailScope,
|
|
saveNotificationEmail,
|
|
savePrimaryEmail,
|
|
updateAdditionalEmail,
|
|
} from '../../../service/PersonalInfo/EmailService.js'
|
|
import { validateEmail } from '../../../utils/validate.js'
|
|
|
|
export default {
|
|
name: 'Email',
|
|
|
|
components: {
|
|
NcActions,
|
|
NcActionButton,
|
|
AlertCircle,
|
|
AlertOctagon,
|
|
Check,
|
|
FederationControl,
|
|
},
|
|
|
|
props: {
|
|
email: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
index: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
primary: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
scope: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
activeNotificationEmail: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
localVerificationState: {
|
|
type: Number,
|
|
default: VERIFICATION_ENUM.NOT_VERIFIED,
|
|
},
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
propertyReadable: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL,
|
|
initialEmail: this.email,
|
|
localScope: this.scope,
|
|
saveAdditionalEmailScope,
|
|
helperText: null,
|
|
showCheckmarkIcon: false,
|
|
showErrorIcon: false,
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
deleteDisabled() {
|
|
if (this.primary) {
|
|
// Disable for empty primary email as there is nothing to delete
|
|
// OR when initialEmail (reflects server state) and email (current input) are not the same
|
|
return this.email === '' || this.initialEmail !== this.email
|
|
} else if (this.initialEmail !== '') {
|
|
return this.initialEmail !== this.email
|
|
}
|
|
return false
|
|
},
|
|
|
|
deleteEmailLabel() {
|
|
if (this.primary) {
|
|
return t('settings', 'Remove primary email')
|
|
}
|
|
return t('settings', 'Delete email')
|
|
},
|
|
|
|
setNotificationMailDisabled() {
|
|
return !this.primary && this.localVerificationState !== VERIFICATION_ENUM.VERIFIED
|
|
},
|
|
|
|
setNotificationMailLabel() {
|
|
if (this.isNotificationEmail) {
|
|
return t('settings', 'Unset as primary email')
|
|
} else if (!this.primary && this.localVerificationState !== VERIFICATION_ENUM.VERIFIED) {
|
|
return t('settings', 'This address is not confirmed')
|
|
}
|
|
return t('settings', 'Set as primary email')
|
|
},
|
|
|
|
federationDisabled() {
|
|
return !this.initialEmail
|
|
},
|
|
|
|
inputId() {
|
|
if (this.primary) {
|
|
return 'email'
|
|
}
|
|
return `email-${this.index}`
|
|
},
|
|
|
|
inputPlaceholder() {
|
|
if (this.primary) {
|
|
return t('settings', 'Your email address')
|
|
}
|
|
return t('settings', 'Additional email address {index}', { index: this.index + 1 })
|
|
},
|
|
|
|
isNotificationEmail() {
|
|
return (this.email && this.email === this.activeNotificationEmail)
|
|
|| (this.primary && this.activeNotificationEmail === '')
|
|
},
|
|
},
|
|
|
|
mounted() {
|
|
if (!this.primary && this.initialEmail === '') {
|
|
// $nextTick is needed here, otherwise it may not always work https://stackoverflow.com/questions/51922767/autofocus-input-on-mount-vue-ios/63485725#63485725
|
|
this.$nextTick(() => this.$refs.email?.focus())
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
onEmailChange(e) {
|
|
this.$emit('update:email', e.target.value)
|
|
this.debounceEmailChange(e.target.value.trim())
|
|
},
|
|
|
|
debounceEmailChange: debounce(async function(email) {
|
|
this.helperText = null
|
|
if (this.$refs.email?.validationMessage) {
|
|
this.helperText = this.$refs.email.validationMessage
|
|
return
|
|
}
|
|
if (validateEmail(email) || email === '') {
|
|
if (this.primary) {
|
|
await this.updatePrimaryEmail(email)
|
|
} else {
|
|
if (email) {
|
|
if (this.initialEmail === '') {
|
|
await this.addAdditionalEmail(email)
|
|
} else {
|
|
await this.updateAdditionalEmail(email)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, 500),
|
|
|
|
async deleteEmail() {
|
|
if (this.primary) {
|
|
this.$emit('update:email', '')
|
|
await this.updatePrimaryEmail('')
|
|
} else {
|
|
await this.deleteAdditionalEmail()
|
|
}
|
|
},
|
|
|
|
async updatePrimaryEmail(email) {
|
|
try {
|
|
const responseData = await savePrimaryEmail(email)
|
|
this.handleResponse({
|
|
email,
|
|
status: responseData.ocs?.meta?.status,
|
|
})
|
|
} catch (e) {
|
|
if (email === '') {
|
|
this.handleResponse({
|
|
errorMessage: t('settings', 'Unable to delete primary email address'),
|
|
error: e,
|
|
})
|
|
} else {
|
|
this.handleResponse({
|
|
errorMessage: t('settings', 'Unable to update primary email address'),
|
|
error: e,
|
|
})
|
|
}
|
|
}
|
|
},
|
|
|
|
async addAdditionalEmail(email) {
|
|
try {
|
|
const responseData = await saveAdditionalEmail(email)
|
|
this.handleResponse({
|
|
email,
|
|
status: responseData.ocs?.meta?.status,
|
|
})
|
|
} catch (e) {
|
|
this.handleResponse({
|
|
errorMessage: t('settings', 'Unable to add additional email address'),
|
|
error: e,
|
|
})
|
|
}
|
|
},
|
|
|
|
async setNotificationMail() {
|
|
try {
|
|
const newNotificationMailValue = (this.primary || this.isNotificationEmail) ? '' : this.initialEmail
|
|
const responseData = await saveNotificationEmail(newNotificationMailValue)
|
|
this.handleResponse({
|
|
notificationEmail: newNotificationMailValue,
|
|
status: responseData.ocs?.meta?.status,
|
|
})
|
|
} catch (e) {
|
|
this.handleResponse({
|
|
errorMessage: 'Unable to choose this email for notifications',
|
|
error: e,
|
|
})
|
|
}
|
|
},
|
|
|
|
async updateAdditionalEmail(email) {
|
|
try {
|
|
const responseData = await updateAdditionalEmail(this.initialEmail, email)
|
|
this.handleResponse({
|
|
email,
|
|
status: responseData.ocs?.meta?.status,
|
|
})
|
|
} catch (e) {
|
|
this.handleResponse({
|
|
errorMessage: t('settings', 'Unable to update additional email address'),
|
|
error: e,
|
|
})
|
|
}
|
|
},
|
|
|
|
async deleteAdditionalEmail() {
|
|
try {
|
|
const responseData = await removeAdditionalEmail(this.initialEmail)
|
|
this.handleDeleteAdditionalEmail(responseData.ocs?.meta?.status)
|
|
} catch (e) {
|
|
this.handleResponse({
|
|
errorMessage: t('settings', 'Unable to delete additional email address'),
|
|
error: e,
|
|
})
|
|
}
|
|
},
|
|
|
|
handleDeleteAdditionalEmail(status) {
|
|
if (status === 'ok') {
|
|
this.$emit('delete-additional-email')
|
|
} else {
|
|
this.handleResponse({
|
|
errorMessage: t('settings', 'Unable to delete additional email address'),
|
|
})
|
|
}
|
|
},
|
|
|
|
handleResponse({ email, notificationEmail, status, errorMessage, error }) {
|
|
if (status === 'ok') {
|
|
// Ensure that local state reflects server state
|
|
if (email) {
|
|
this.initialEmail = email
|
|
} else if (notificationEmail !== undefined) {
|
|
this.$emit('update:notification-email', notificationEmail)
|
|
}
|
|
this.showCheckmarkIcon = true
|
|
setTimeout(() => { this.showCheckmarkIcon = false }, 2000)
|
|
} else {
|
|
showError(errorMessage)
|
|
logger.error(errorMessage, error)
|
|
this.showErrorIcon = true
|
|
setTimeout(() => { this.showErrorIcon = false }, 2000)
|
|
}
|
|
},
|
|
|
|
onScopeChange(scope) {
|
|
this.$emit('update:scope', scope)
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.email {
|
|
display: grid;
|
|
align-items: center;
|
|
|
|
input {
|
|
grid-area: 1 / 1;
|
|
width: 100%;
|
|
}
|
|
|
|
.email__actions-container {
|
|
grid-area: 1 / 1;
|
|
justify-self: flex-end;
|
|
height: 30px;
|
|
|
|
display: flex;
|
|
gap: 0 2px;
|
|
margin-right: 5px;
|
|
|
|
.email__actions {
|
|
opacity: 0.4 !important;
|
|
|
|
&:hover,
|
|
&:focus,
|
|
&:active {
|
|
opacity: 0.8 !important;
|
|
}
|
|
|
|
&::v-deep button {
|
|
height: 30px !important;
|
|
min-height: 30px !important;
|
|
width: 30px !important;
|
|
min-width: 30px !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
&__helper-text-message {
|
|
padding: 4px 0;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
&__icon {
|
|
margin-right: 8px;
|
|
align-self: start;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
&--error {
|
|
color: var(--color-error);
|
|
}
|
|
}
|
|
}
|
|
|
|
.fade-enter,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
.fade-enter-active {
|
|
transition: opacity 200ms ease-out;
|
|
}
|
|
|
|
.fade-leave-active {
|
|
transition: opacity 300ms ease-out;
|
|
}
|
|
</style>
|