Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(accounts): implement birthday prop
  • Loading branch information
st3iny committed Jul 27, 2023
commit 840ceb2060dbb2e7689bfe31358253c9a9802553
4 changes: 4 additions & 0 deletions apps/provisioning_api/lib/Controller/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ public function getEditableFieldsForUser(string $userId): DataResponse {
}

$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
$permittedFields[] = IAccountManager::PROPERTY_BIRTHDAY;
$permittedFields[] = IAccountManager::PROPERTY_PHONE;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
Expand Down Expand Up @@ -786,6 +787,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon
$permittedFields[] = self::USER_FIELD_LOCALE;
}

$permittedFields[] = IAccountManager::PROPERTY_BIRTHDAY;
$permittedFields[] = IAccountManager::PROPERTY_PHONE;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
Expand Down Expand Up @@ -835,6 +837,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon
$permittedFields[] = self::USER_FIELD_PASSWORD;
$permittedFields[] = self::USER_FIELD_LANGUAGE;
$permittedFields[] = self::USER_FIELD_LOCALE;
$permittedFields[] = IAccountManager::PROPERTY_BIRTHDAY;
$permittedFields[] = IAccountManager::PROPERTY_PHONE;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
Expand Down Expand Up @@ -968,6 +971,7 @@ public function editUser(string $userId, string $key, string $value): DataRespon
throw new OCSException('', 102);
}
break;
case IAccountManager::PROPERTY_BIRTHDAY:
case IAccountManager::PROPERTY_PHONE:
case IAccountManager::PROPERTY_ADDRESS:
case IAccountManager::PROPERTY_WEBSITE:
Expand Down
9 changes: 8 additions & 1 deletion apps/settings/lib/Controller/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ protected function canAdminChangeUserPasswords(): bool {
* @param string|null $twitterScope
* @param string|null $fediverse
* @param string|null $fediverseScope
* @param string|null $birthday
* @param string|null $birthdayScope
*
* @return DataResponse
*/
Expand All @@ -380,7 +382,9 @@ public function setUserSettings(?string $avatarScope = null,
?string $twitter = null,
?string $twitterScope = null,
?string $fediverse = null,
?string $fediverseScope = null
?string $fediverseScope = null,
?string $birthday = null,
?string $birthdayScope = null
) {
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
Expand Down Expand Up @@ -420,6 +424,7 @@ public function setUserSettings(?string $avatarScope = null,
IAccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
IAccountManager::PROPERTY_BIRTHDAY => ['value' => $birthday, 'scope' => $birthdayScope],
];
$allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
foreach ($updatable as $property => $data) {
Expand Down Expand Up @@ -461,6 +466,8 @@ public function setUserSettings(?string $avatarScope = null,
'twitterScope' => $userAccount->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(),
'fediverse' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue(),
'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
'birthday' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDAY)->getValue(),
'birthdayScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDAY)->getScope(),
'message' => $this->l10n->t('Settings saved'),
],
],
Expand Down
1 change: 1 addition & 0 deletions apps/settings/lib/Settings/Personal/PersonalInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ public function getForm(): TemplateResponse {
'role' => $this->getProperty($account, IAccountManager::PROPERTY_ROLE),
'headline' => $this->getProperty($account, IAccountManager::PROPERTY_HEADLINE),
'biography' => $this->getProperty($account, IAccountManager::PROPERTY_BIOGRAPHY),
'birthday' => $this->getProperty($account, IAccountManager::PROPERTY_BIRTHDAY),
];

$accountParameters = [
Expand Down
150 changes: 150 additions & 0 deletions apps/settings/src/components/PersonalInfo/BirthdaySection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<!--
- @copyright 2022 Christopher Ng <[email protected]>
-
- @author Christopher Ng <[email protected]>
-
- @license AGPL-3.0-or-later
-
- 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>
<section>
<HeaderBar :input-id="inputId"
:readable="propertyReadable" />

<template>
<NcDateTimePickerNative :id="inputId"
type="date"
:label="t('settings', 'Your birthday')"
:value="birthdayValue"
@input="onInput" />
</template>
</section>
</template>

<script>
import { loadState } from '@nextcloud/initial-state'

import HeaderBar from './shared/HeaderBar.vue'
import AccountPropertySection from './shared/AccountPropertySection.vue'

import {
NAME_READABLE_ENUM,
ACCOUNT_SETTING_PROPERTY_READABLE_ENUM,
ACCOUNT_SETTING_PROPERTY_ENUM,
} from '../../constants/AccountPropertyConstants.js'

import { NcDateTimePickerNative } from '@nextcloud/vue'
import debounce from 'debounce'
import { savePrimaryAccountProperty } from '../../service/PersonalInfo/PersonalInfoService'
import { handleError } from '../../utils/handlers'

const { birthday } = loadState('settings', 'personalInfoParameters', {})

export default {
name: 'BirthdaySection',

components: {
AccountPropertySection,
NcDateTimePickerNative,
HeaderBar,
},

data() {
return {
propertyReadable: 'Birthday', // ACCOUNT_SETTING_PROPERTY_READABLE_ENUM.BIRTHDAY,
readable: 'Birthday',
birthday: { ...birthday, readable: NAME_READABLE_ENUM[birthday.name] },
birthdayValue: new Date(),
name: 'birthday',
}
},

computed: {
inputId() {
return `account-setting-${ACCOUNT_SETTING_PROPERTY_ENUM.BIRTHDAY}`
},
},

mounted() {
console.log('birthday', this, ACCOUNT_SETTING_PROPERTY_READABLE_ENUM)
},

methods: {
onInput(e) {
console.log('onInput', e)
this.birtdayValue = e
this.debouncePropertyChange(this.birthdayValue.toString())
},

debouncePropertyChange: debounce(async function(value) {
Copy link
Contributor

@szaimen szaimen Jul 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: should not duplicate the debounce logic

this.helperText = null
if (this.$refs.input && this.$refs.input.validationMessage) {
this.helperText = this.$refs.input.validationMessage
return
}
if (this.onValidate && !this.onValidate(value)) {
return
}
await this.updateProperty(value)
}, 500),

async updateProperty(value) {
try {
const responseData = await savePrimaryAccountProperty(
this.name,
value,
)
this.handleResponse({
value,
status: responseData.ocs?.meta?.status,
})
} catch (e) {
this.handleResponse({
errorMessage: t('settings', 'Unable to update {property}', { property: this.readable.toLocaleLowerCase() }),
error: e,
})
}
},

handleResponse({ value, status, errorMessage, error }) {
if (status === 'ok') {
this.initialValue = value
if (this.onSave) {
this.onSave(value)
}
this.showCheckmarkIcon = true
setTimeout(() => { this.showCheckmarkIcon = false }, 2000)
} else {
this.$emit('update:value', this.initialValue)
handleError(error, errorMessage)
this.showErrorIcon = true
setTimeout(() => { this.showErrorIcon = false }, 2000)
}
},
},
}
</script>

<style lang="scss" scoped>
section {
padding: 10px 10px;

&::v-deep button:disabled {
cursor: default;
}
}
</style>
5 changes: 5 additions & 0 deletions apps/settings/src/constants/AccountPropertyConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const ACCOUNT_PROPERTY_ENUM = Object.freeze({
ROLE: 'role',
TWITTER: 'twitter',
WEBSITE: 'website',
BIRTHDAY: 'birthday',
})

/** Enum of account properties to human readable account property names */
Expand All @@ -61,6 +62,7 @@ export const ACCOUNT_PROPERTY_READABLE_ENUM = Object.freeze({
TWITTER: t('settings', 'Twitter'),
FEDIVERSE: t('settings', 'Fediverse (e.g. Mastodon)'),
WEBSITE: t('settings', 'Website'),
BIRTHDAY: t('settings', 'Birthday'),
})

export const NAME_READABLE_ENUM = Object.freeze({
Expand All @@ -78,6 +80,7 @@ export const NAME_READABLE_ENUM = Object.freeze({
[ACCOUNT_PROPERTY_ENUM.TWITTER]: ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER,
[ACCOUNT_PROPERTY_ENUM.FEDIVERSE]: ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE,
[ACCOUNT_PROPERTY_ENUM.WEBSITE]: ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE,
[ACCOUNT_PROPERTY_ENUM.BIRTHDAY]: ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDAY,
})

/** Enum of profile specific sections to human readable names */
Expand All @@ -101,6 +104,7 @@ export const PROPERTY_READABLE_KEYS_ENUM = Object.freeze({
[ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER]: ACCOUNT_PROPERTY_ENUM.TWITTER,
[ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE]: ACCOUNT_PROPERTY_ENUM.FEDIVERSE,
[ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: ACCOUNT_PROPERTY_ENUM.WEBSITE,
[ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDAY]: ACCOUNT_PROPERTY_ENUM.BIRTHDAY,
})

/**
Expand Down Expand Up @@ -143,6 +147,7 @@ export const PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM = Object.freeze({
[ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
[ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
[ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
[ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDAY]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
})

/** List of readable account properties which aren't published to the lookup server */
Expand Down
3 changes: 3 additions & 0 deletions apps/settings/src/main-personal-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import RoleSection from './components/PersonalInfo/RoleSection.vue'
import HeadlineSection from './components/PersonalInfo/HeadlineSection.vue'
import BiographySection from './components/PersonalInfo/BiographySection.vue'
import ProfileVisibilitySection from './components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue'
import BirthdaySection from './components/PersonalInfo/BirthdaySection.vue'

__webpack_nonce__ = btoa(getRequestToken())

Expand All @@ -65,6 +66,7 @@ const TwitterView = Vue.extend(TwitterSection)
const FediverseView = Vue.extend(FediverseSection)
const LanguageView = Vue.extend(LanguageSection)
const LocaleView = Vue.extend(LocaleSection)
const BirthdayView = Vue.extend(BirthdaySection)

new AvatarView().$mount('#vue-avatar-section')
new DetailsView().$mount('#vue-details-section')
Expand All @@ -77,6 +79,7 @@ new TwitterView().$mount('#vue-twitter-section')
new FediverseView().$mount('#vue-fediverse-section')
new LanguageView().$mount('#vue-language-section')
new LocaleView().$mount('#vue-locale-section')
new BirthdayView().$mount('#vue-birthday-section')

if (profileEnabledGlobally) {
const ProfileView = Vue.extend(ProfileSection)
Expand Down
3 changes: 3 additions & 0 deletions apps/settings/templates/settings/personal/personal.info.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
<div class="personal-settings-setting-box">
<div id="vue-location-section"></div>
</div>
<div class="personal-settings-setting-box">
<div id="vue-birthday-section"></div>
</div>
<div class="personal-settings-setting-box personal-settings-language-box">
<div id="vue-language-section"></div>
</div>
Expand Down
8 changes: 8 additions & 0 deletions lib/private/Accounts/AccountManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class AccountManager implements IAccountManager {
self::PROPERTY_ROLE => self::SCOPE_LOCAL,
self::PROPERTY_HEADLINE => self::SCOPE_LOCAL,
self::PROPERTY_BIOGRAPHY => self::SCOPE_LOCAL,
self::PROPERTY_BIRTHDAY => self::SCOPE_LOCAL,
];

public function __construct(
Expand Down Expand Up @@ -668,6 +669,13 @@ protected function buildDefaultUserRecord(IUser $user): array {
'verified' => self::NOT_VERIFIED,
],

[
'name' => self::PROPERTY_BIRTHDAY,
'value' => '',
'scope' => $scopes[self::PROPERTY_BIRTHDAY],
'verified' => self::NOT_VERIFIED,
],

[
'name' => self::PROPERTY_FEDIVERSE,
'value' => '',
Expand Down
6 changes: 6 additions & 0 deletions lib/public/Accounts/IAccountManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ interface IAccountManager {
*/
public const PROPERTY_PROFILE_ENABLED = 'profile_enabled';

/**
* @since 28.0.0
*/
public const PROPERTY_BIRTHDAY = 'birthday';

/**
* The list of allowed properties
*
Expand All @@ -159,6 +164,7 @@ interface IAccountManager {
self::PROPERTY_HEADLINE,
self::PROPERTY_BIOGRAPHY,
self::PROPERTY_PROFILE_ENABLED,
self::PROPERTY_BIRTHDAY,
];

public const COLLECTION_EMAIL = 'additional_mail';
Expand Down