Skip to content

Commit

Permalink
[WIP] Vuetiful emails
Browse files Browse the repository at this point in the history
Signed-off-by: Christopher Ng <chrng8@gmail.com>
  • Loading branch information
Pytal committed Jun 9, 2021
1 parent 70483a1 commit 9df74ae
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 57 deletions.
15 changes: 11 additions & 4 deletions apps/provisioning_api/lib/Controller/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -740,12 +740,19 @@ public function editUser(string $userId, string $key, string $value): DataRespon
$this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value);
break;
case IAccountManager::PROPERTY_EMAIL:
if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
$targetUser->setEMailAddress($value);
} else {
throw new OCSException('', 102);
$userAccount = $this->accountManager->getAccount($targetUser);
$userProperty = $userAccount->getProperty($key);
if ($userProperty->getValue() !== $value) {
try {
$userProperty->setValue($value);
$this->accountManager->updateAccount($userAccount);
} catch (\InvalidArgumentException $e) {
throw new OCSException('Invalid ' . $e->getMessage(), 102);
}
}
break;
case IAccountManager::COLLECTION_EMAIL:
break;
case IAccountManager::PROPERTY_PHONE:
case IAccountManager::PROPERTY_ADDRESS:
case IAccountManager::PROPERTY_WEBSITE:
Expand Down
2 changes: 1 addition & 1 deletion apps/settings/js/federationsettingsview.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
this._inputFields = [
'displayname',
'phone',
'email',
// 'email',
'website',
'twitter',
'address',
Expand Down
35 changes: 34 additions & 1 deletion apps/settings/lib/Settings/Personal/PersonalInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Settings\ISettings;
use OCP\Accounts\IAccountProperty;
use OCP\AppFramework\Services\IInitialState;

class PersonalInfo implements ISettings {

Expand All @@ -65,6 +67,8 @@ class PersonalInfo implements ISettings {
private $l10nFactory;
/** @var IL10N */
private $l;
/** @var IInitialState */
private $initialStateService;

public function __construct(
IConfig $config,
Expand All @@ -73,7 +77,8 @@ public function __construct(
IAccountManager $accountManager,
IAppManager $appManager,
IFactory $l10nFactory,
IL10N $l
IL10N $l,
IInitialState $initialStateService
) {
$this->config = $config;
$this->userManager = $userManager;
Expand All @@ -82,6 +87,7 @@ public function __construct(
$this->appManager = $appManager;
$this->l10nFactory = $l10nFactory;
$this->l = $l;
$this->initialStateService = $initialStateService;
}

public function getForm(): TemplateResponse {
Expand Down Expand Up @@ -138,6 +144,33 @@ public function getForm(): TemplateResponse {
'groups' => $this->getGroups($user),
] + $messageParameters + $languageParameters + $localeParameters;

$primaryEmail = [
'value' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
'scope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
'verified' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getVerified(),
'message' => $messageParameters[IAccountManager::PROPERTY_EMAIL . 'Message'],
];

$additionalEmails = array_map(
fn(IAccountProperty $property) => [
'value' => $property->getValue(),
'scope' => $property->getScope(),
'verified' => $property->getVerified(),
],
$account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties()
);

$emails = [
'primaryEmail' => $primaryEmail,
'additionalEmails' => $additionalEmails,
];

$accountParameters = [
'displayNameChangeSupported' => $user->canChangeDisplayName(),
];

$this->initialStateService->provideInitialState('emails', $emails);
$this->initialStateService->provideInitialState('accountParameters', $accountParameters);

return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, '');
}
Expand Down
158 changes: 158 additions & 0 deletions apps/settings/src/components/PersonalInfo/EmailSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<!--
- @copyright 2021 Christopher Ng <chrng8@gmail.com>
-
- @author 2021 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>
<form id="emailform" class="section">
<h3>
<label for="email">{{ t('settings', 'Email') }}</label>
<a href="#" class="federation-menu" :aria-label="t('settings', 'Change privacy level of email')">
<span class="icon-federation-menu icon-password">
<span class="icon-triangle-s"></span>
</span>
</a>
<Actions class="actions">
<ActionButton icon="icon-add" @click.stop.prevent="addAdditionalEmail">
{{ t('settings', 'Add email address') }}
</ActionButton>
</Actions>
</h3>
<div
class="verify"
:class="{ hidden: hideVerification }">
<img
id="verify-email"
:src="verificationIcon"
:title="email.message"
:data-status="email.verified" />
</div>
<input
type="email"
name="email"
id="email"
v-model="email.value"
@keyup.enter.stop.prevent="updateEmails"
:class="{ hidden: hideEmailInput }"
:placeholder="t('settings', 'Your primary email address')"
autocomplete="on"
autocapitalize="none"
autocorrect="off" />
<span class="icon-checkmark hidden"></span>
<span class="icon-error hidden"></span>
<span v-if="hideEmailInput">{{ email.value ? email.value : t('settings', 'No email address set') }}</span>
<em v-if="!hideEmailInput">{{ t('settings', 'For password reset and notifications') }}</em>
<input type="hidden" id="emailscope" v-model="email.scope" />
<input v-for="(email, index) in additionalEmails"
type="email"
name="additionalEmail[]"
:id="`additionalEmail-${index}`"
v-model="email.value"
@keyup.enter.stop.prevent="updateEmails"
:class="{ hidden: hideEmailInput }"
:placeholder="t('settings', `Additional email address ${index+1}`)"
:key="index"
autocomplete="on"
autocapitalize="none"
autocorrect="off" />
</form>
</template>

<script>
import { Actions, ActionButton } from '@nextcloud/vue'
import { imagePath } from '@nextcloud/router'
// import { showError } from '@nextcloud/dialogs'
import { setPrimaryEmail, setAdditionalEmails } from '../../service/PersonalInfoService'

export default {
name: 'EmailSection',
components: {
Actions,
ActionButton,
},
props: {
initialEmails: {
type: Object,
required: true,
},
accountParams: {
type: Object,
required: true,
},
VerificationEnum: {
type: Object,
required: true,
},
},
computed: {
verificationIcon() {
const verificationIconMap = {
[this.VerificationEnum.VERIFIED]: imagePath('core', 'actions/verified.svg'),
[this.VerificationEnum.VERIFICATION_IN_PROGRESS]: imagePath('core', 'actions/verifying.svg'),
[this.VerificationEnum.NOT_VERIFIED]: imagePath('core', 'actions/verify.svg'),
default: imagePath('core', 'actions/verify.svg'),
}

return Object.prototype.hasOwnProperty.call(verificationIconMap, this.email.verified) ? verificationIconMap[this.email.verified] : verificationIconMap.default
}
},
data() {
/* eslint-disable */
console.log(this.initialEmails)
console.log(this.accountParams)
return {
email: this.initialEmails.primaryEmail,
hideVerification: this.initialEmails.primaryEmail.value === '' || this.initialEmails.primaryEmail.scope !== 'public',
hideEmailInput: !this.accountParams.displayNameChangeSupported,
additionalEmails: this.initialEmails.additionalEmails,
}
},
methods: {
async addAdditionalEmail() {
if (this.additionalEmails.every(({ value }) => value !== '')) this.additionalEmails.push({ value: '' })
},

async updateEmails() {
try {
const data = await setPrimaryEmail(this.email.value)
console.log(data.ocs.meta)
} catch (e) {
console.error('Unable to update primary email address', e)
// TODO fix error dialog styles
// showError(t('settings', 'An error happened while updating the primary email address'))
}

try {
const data = await setAdditionalEmails(this.additionalEmails.map(({ value }) => value))
console.log(data.ocs.meta)
} catch (e) {
console.error('Unable to update additional email addresses', e)
// showError(t('settings', 'An error happened while updating additional email addresses'))
}
},
}
}
</script>

<style scoped lang="scss">
.actions {
// TODO do this the right way
margin: -12px 0 0 2px !important;
}
</style>
40 changes: 40 additions & 0 deletions apps/settings/src/main-personal-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @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/>.
*
*/

import Vue from 'vue'
import { loadState } from '@nextcloud/initial-state'

import EmailSection from './components/PersonalInfo/EmailSection'

// eslint-disable-next-line camelcase
__webpack_nonce__ = btoa(OC.requestToken)

Vue.prototype.t = t

const View = Vue.extend(EmailSection)
new View({
propsData: {
initialEmails: loadState('settings', 'emails'),
accountParams: loadState('settings', 'accountParameters'),
VerificationEnum: Object.freeze({ NOT_VERIFIED: '0', VERIFICATION_IN_PROGRESS: '1', VERIFIED: '2' }),
},
}).$mount('#vue-emailform')
51 changes: 51 additions & 0 deletions apps/settings/src/service/PersonalInfoService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @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/>.
*
*/

import axios from '@nextcloud/axios'
import { getCurrentUser } from '@nextcloud/auth'
import { generateOcsUrl } from '@nextcloud/router'

export const setPrimaryEmail = async(email) => {
const userId = getCurrentUser().uid
// TODO upgrade @nextcloud/router to v2.0 so we can remove the .slice() trailing slash hack
const url = generateOcsUrl(`cloud/users/${userId}`, 2).slice(0, -1)

const res = await axios.put(url, {
key: 'email',
value: email,
})

return res.data
}

export const setAdditionalEmails = async(emails) => {
const userId = getCurrentUser().uid
// TODO upgrade @nextcloud/router to v2.0 so we can remove the .slice() trailing slash hack
const url = generateOcsUrl(`cloud/users/${userId}`, 2).slice(0, -1)

const res = await axios.put(url, {
key: 'additional_mail',
value: emails,
})

return res.data
}
Loading

0 comments on commit 9df74ae

Please sign in to comment.