summaryrefslogtreecommitdiffstats
path: root/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue')
-rw-r--r--apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue265
1 files changed, 265 insertions, 0 deletions
diff --git a/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue b/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
new file mode 100644
index 00000000000..351af504682
--- /dev/null
+++ b/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
@@ -0,0 +1,265 @@
+<!--
+ - @copyright 2022 Christopher Ng <chrng8@gmail.com>
+ -
+ - @author Christopher Ng <chrng8@gmail.com>
+ -
+ - @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 :scope.sync="scope"
+ :readable.sync="readable"
+ :input-id="inputId"
+ :is-editable="isEditable" />
+
+ <div v-if="isEditable" class="property">
+ <textarea v-if="multiLine"
+ :id="inputId"
+ :placeholder="placeholder"
+ :value="value"
+ rows="8"
+ autocapitalize="none"
+ autocomplete="off"
+ autocorrect="off"
+ @input="onPropertyChange" />
+ <input v-else
+ :id="inputId"
+ :placeholder="placeholder"
+ :type="type"
+ :value="value"
+ autocapitalize="none"
+ autocomplete="on"
+ autocorrect="off"
+ @input="onPropertyChange">
+
+ <div class="property__actions-container">
+ <transition name="fade">
+ <Check v-if="showCheckmarkIcon" :size="20" />
+ <AlertOctagon v-else-if="showErrorIcon" :size="20" />
+ </transition>
+ </div>
+ </div>
+ <span v-else>
+ {{ value || t('settings', 'No {property} set', { property: readable.toLocaleLowerCase() }) }}
+ </span>
+ </section>
+</template>
+
+<script>
+import debounce from 'debounce'
+import { showError } from '@nextcloud/dialogs'
+
+import Check from 'vue-material-design-icons/Check'
+import AlertOctagon from 'vue-material-design-icons/AlertOctagon'
+
+import HeaderBar from '../shared/HeaderBar.vue'
+
+import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService.js'
+import logger from '../../../logger.js'
+
+export default {
+ name: 'AccountPropertySection',
+
+ components: {
+ AlertOctagon,
+ Check,
+ HeaderBar,
+ },
+
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
+ value: {
+ type: String,
+ required: true,
+ },
+ scope: {
+ type: String,
+ required: true,
+ },
+ readable: {
+ type: String,
+ required: true,
+ },
+ placeholder: {
+ type: String,
+ required: true,
+ },
+ type: {
+ type: String,
+ default: 'text',
+ },
+ isEditable: {
+ type: Boolean,
+ default: true,
+ },
+ multiLine: {
+ type: Boolean,
+ default: false,
+ },
+ onValidate: {
+ type: Function,
+ default: null,
+ },
+ onSave: {
+ type: Function,
+ default: null,
+ },
+ },
+
+ data() {
+ return {
+ initialValue: this.value,
+ showCheckmarkIcon: false,
+ showErrorIcon: false,
+ }
+ },
+
+ computed: {
+ inputId() {
+ return `account-property-${this.name}`
+ },
+ },
+
+ methods: {
+ onPropertyChange(e) {
+ this.$emit('update:value', e.target.value)
+ this.debouncePropertyChange(e.target.value.trim())
+ },
+
+ debouncePropertyChange: debounce(async function(value) {
+ 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)
+ showError(errorMessage)
+ logger.error(errorMessage, error)
+ this.showErrorIcon = true
+ setTimeout(() => { this.showErrorIcon = false }, 2000)
+ }
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+section {
+ padding: 10px 10px;
+
+ &::v-deep button:disabled {
+ cursor: default;
+ }
+
+ .property {
+ display: grid;
+ align-items: center;
+
+ textarea {
+ resize: vertical;
+ grid-area: 1 / 1;
+ width: 100%;
+ margin: 3px 3px 3px 0;
+ padding: 7px 6px;
+ color: var(--color-main-text);
+ border: 1px solid var(--color-border-dark);
+ border-radius: var(--border-radius);
+ background-color: var(--color-main-background);
+ font-family: var(--font-face);
+ cursor: text;
+
+ &:hover,
+ &:focus,
+ &:active {
+ border-color: var(--color-primary-element) !important;
+ outline: none !important;
+ }
+ }
+
+ input {
+ grid-area: 1 / 1;
+ width: 100%;
+ height: 34px;
+ margin: 3px 3px 3px 0;
+ padding: 7px 6px;
+ color: var(--color-main-text);
+ border: 1px solid var(--color-border-dark);
+ border-radius: var(--border-radius);
+ background-color: var(--color-main-background);
+ font-family: var(--font-face);
+ cursor: text;
+ }
+
+ .property__actions-container {
+ grid-area: 1 / 1;
+ justify-self: flex-end;
+ align-self: flex-end;
+ height: 30px;
+
+ display: flex;
+ gap: 0 2px;
+ margin-right: 5px;
+ margin-bottom: 5px;
+ }
+ }
+
+ .fade-enter,
+ .fade-leave-to {
+ opacity: 0;
+ }
+
+ .fade-enter-active {
+ transition: opacity 200ms ease-out;
+ }
+
+ .fade-leave-active {
+ transition: opacity 300ms ease-out;
+ }
+}
+</style>