refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s

This commit is contained in:
Kroonk
2026-05-22 16:19:55 +02:00
parent 38cc4c09ca
commit a213a1dba0
2215 changed files with 242567 additions and 6015 deletions

View File

@@ -0,0 +1,478 @@
<template>
<div>
<notifications group="activityTypes" position="bottom right" />
<h3 class="with-actions">
{{ $t('settings.personalization_activity_type_category_title') }}
<a v-if="!limited" v-cy-name="'add-activity-type-category-button'" class="btn nt2" :class="[ dirltr ? 'fr' : 'fl' ]" href=""
@click.prevent="showCreateCategoryModal"
>
{{ $t('settings.personalization_activity_type_category_add') }}
</a>
</h3>
<p>{{ $t('settings.personalization_activity_type_category_description') }}</p>
<div v-if="limited" v-cy-name="'activity-type-premium-message'" class="mt3 mb3 form-information-message br2">
<div class="pa3 flex">
<div class="mr3">
<svg viewBox="0 0 20 20">
<g fill-rule="evenodd">
<circle cx="10" cy="10" r="9" fill="currentColor" /><path d="M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.486 10-10S15.514 0 10 0m0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8m1-5v-3a1 1 0 0 0-1-1H9a1 1 0 1 0 0 2v3a1 1 0 0 0 1 1h1a1 1 0 1 0 0-2m-1-5.9a1.1 1.1 0 1 0 0-2.2 1.1 1.1 0 0 0 0 2.2" />
</g>
</svg>
</div>
<div v-html="$t('settings.personalisation_paid_upgrade_vue', {url: 'settings/subscriptions' })"></div>
</div>
</div>
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
<div class="dt-row">
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_activity_type_category_table_name') }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2 b">
{{ $t('settings.personalization_activity_type_category_table_actions') }}
</div>
</div>
</div>
</div>
<div v-cy-name="'activity-types'">
<ul v-cy-name="'activity-type-categories'" v-cy-items="activityTypeCategories.map(a => a.id)">
<li v-for="activityTypeCategory in activityTypeCategories" :key="activityTypeCategory.id" v-cy-name="'activity-types-'+activityTypeCategory.id"
v-cy-items="activityTypeCategory.activityTypes ? activityTypeCategory.activityTypes.map(a => a.id) : ''" class="dt dt--fixed w-100 collapse br--top br--bottom mt3"
>
<!-- ACTIVITY TYPE CATEGORY -->
<div class="dt-row hover bb b--light-gray">
<div class="dtc">
<div class="pa2 b">
<strong>{{ activityTypeCategory.name }}</strong>
</div>
</div>
<div class="dtc">
<div class="pa2" :class="[ dirltr ? 'tr' : 'tl' ]">
<em v-if="!limited" v-cy-name="'activity-type-category-edit-button-'+activityTypeCategory.id"
class="fa fa-pencil-square-o pointer pr2" @click="showEditCategory(activityTypeCategory)"
></em>
<em v-if="!limited" v-cy-name="'activity-type-category-delete-button-'+activityTypeCategory.id"
class="fa fa-trash-o pointer" @click="showDeleteCategory(activityTypeCategory)"
></em>
</div>
</div>
</div>
<div v-for="activityType in activityTypeCategory.activityTypes" :key="activityType.id" class="dt-row hover bb b--light-gray">
<div class="dtc">
<div class="pa2 pl4">
{{ activityType.name }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2">
<em v-if="!limited" v-cy-name="'activity-type-edit-button-'+activityType.id"
class="fa fa-pencil-square-o pointer pr2" @click="showEditType(activityType, activityTypeCategory.id)"
></em>
<em v-if="!limited" v-cy-name="'activity-type-delete-button-'+activityType.id"
class="fa fa-trash-o pointer" @click="showDeleteType(activityType)"
></em>
</div>
</div>
</div>
<div v-if="!limited" class="dt-row">
<div class="dtc">
<div class="pa2 pl4">
<a v-cy-name="'add-activity-type-button-for-category-'+activityTypeCategory.id" class="pointer" href=""
@click.prevent="showCreateTypeModal(activityTypeCategory)"
>
{{ $t('settings.personalization_activity_type_add_button') }}
</a>
</div>
</div>
</div>
</li>
</ul>
</div>
<!-- Create Activity Type Category -->
<sweet-modal ref="createCategoryModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_category_modal_add')">
<form @submit.prevent="storeCategory()">
<div class="mb4">
<p class="b mb2"></p>
<form-input
:id="'add-category-name'"
v-model="createCategoryForm.name"
:input-type="'text'"
:required="true"
:title="$t('settings.personalization_activity_type_category_modal_question')"
/>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeCategoryModal()">
{{ $t('app.cancel') }}
</a>
<a v-cy-name="'add-activity-type-category-save-button'" class="btn btn-primary" href="" @click.prevent="storeCategory()">
{{ $t('app.save') }}
</a>
</div>
</sweet-modal>
<!-- Update Activity Type Category -->
<sweet-modal ref="updateCategoryModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_category_modal_edit')">
<form @submit.prevent="updateCategory()">
<div class="mb4">
<p class="b mb2"></p>
<form-input
:id="'update-category-name'"
v-model="updateCategoryForm.name"
:input-type="'text'"
:required="true"
:title="$t('settings.personalization_activity_type_category_modal_question')"
/>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeUpdateCategoryModal()">
{{ $t('app.cancel') }}
</a>
<a v-cy-name="'update-activity-type-category-button'" class="btn btn-primary" href="" @click.prevent="updateCategory()">
{{ $t('app.update') }}
</a>
</div>
</sweet-modal>
<!-- Create Activity Type -->
<sweet-modal ref="createTypeModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_modal_add')">
<form @submit.prevent="storeType()">
<div class="mb4">
<p class="b mb2"></p>
<form-input
:id="'add-type-name'"
v-model="createTypeForm.name"
:input-type="'text'"
:required="true"
:title="$t('settings.personalization_activity_type_modal_question')"
/>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeCreateTypeModal()">
{{ $t('app.cancel') }}
</a>
<a v-cy-name="'add-type-button'" class="btn btn-primary" href="" @click.prevent="storeType()">
{{ $t('app.save') }}
</a>
</div>
</sweet-modal>
<!-- Update Activity Type -->
<sweet-modal ref="updateTypeModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_modal_edit')">
<form @submit.prevent="updateType()">
<div class="mb4">
<p class="b mb2"></p>
<form-input
:id="'update-type-name'"
v-model="updateTypeForm.name"
:input-type="'text'"
:required="true"
:title="$t('settings.personalization_activity_type_modal_question')"
/>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeUpdateTypeModal()">
{{ $t('app.cancel') }}
</a>
<a v-cy-name="'update-type-button'" class="btn btn-primary" href="" @click.prevent="updateType()">
{{ $t('app.update') }}
</a>
</div>
</sweet-modal>
<!-- Delete Activiy type category -->
<sweet-modal ref="deleteCategoryModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_category_modal_delete')">
<form>
<div v-if="errorMessage !== ''" class="form-error-message mb3">
<div class="pa2">
<p class="mb0">
{{ errorMessage }}
</p>
</div>
</div>
<div class="mb4">
<p class="mb2">
{{ $t('settings.personalization_activity_type_category_modal_delete_desc') }}
</p>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeDeleteCategoryModal()">
{{ $t('app.cancel') }}
</a>
<a v-cy-name="'delete-category-button'" class="btn btn-primary" href="" @click.prevent="destroyCategory()">
{{ $t('app.delete') }}
</a>
</div>
</sweet-modal>
<!-- Delete Activiy type -->
<sweet-modal ref="deleteTypeModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_modal_delete')">
<form>
<div v-if="errorMessage !== ''" class="form-error-message mb3">
<div class="pa2">
<p class="mb0">
{{ errorMessage }}
</p>
</div>
</div>
<div class="mb4">
<p class="mb2">
{{ $t('settings.personalization_activity_type_modal_delete_desc') }}
</p>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeDeleteTypeModal()">
{{ $t('app.cancel') }}
</a>
<a v-cy-name="'delete-type-button'" class="btn btn-primary" href="" @click.prevent="destroyType()">
{{ $t('app.delete') }}
</a>
</div>
</sweet-modal>
</div>
</template>
<script>
import { SweetModal } from 'sweet-modal-vue';
export default {
components: {
SweetModal
},
props: {
limited: {
type: Boolean,
default: false,
},
},
data() {
return {
activityTypes: [],
activityTypeCategories: [],
errorMessage: '',
updatedCategory: {
id: '',
name: ''
},
createCategoryForm: {
name: '',
errors: []
},
createTypeForm: {
name: '',
activity_type_category_id: '',
errors: []
},
updateCategoryForm: {
id: '',
name: '',
activity_type_category_id: '',
errors: []
},
updateTypeForm: {
id: '',
name: '',
errors: []
},
destroyCategoryForm: {
id: '',
errors: []
},
destroyTypeForm: {
id: '',
errors: []
},
};
},
computed: {
dirltr() {
return this.$root.htmldir === 'ltr';
}
},
mounted() {
this.prepareComponent();
},
methods: {
prepareComponent() {
this.getActivityTypeCategories();
},
getActivityTypeCategories() {
axios.get('settings/personalization/activitytypecategories')
.then(response => {
this.activityTypeCategories = response.data;
});
},
closeCategoryModal() {
this.$refs.createCategoryModal.close();
},
closeDeleteCategoryModal() {
this.$refs.deleteCategoryModal.close();
},
showCreateCategoryModal() {
this.$refs.createCategoryModal.open();
},
storeCategory() {
axios.post('settings/personalization/activitytypecategories', this.createCategoryForm)
.then(response => {
this.$refs.createCategoryModal.close();
this.activityTypeCategories.push(response.data.data);
this.createCategoryForm.name = '';
this.notify(this.$t('app.default_save_success'), true);
});
},
showEditCategory(category) {
this.updateCategoryForm.id = category.id;
this.updateCategoryForm.name = category.name;
this.updatedCategory = category;
this.$refs.updateCategoryModal.open();
},
showDeleteCategory(category) {
this.destroyCategoryForm.id = category.id;
this.$refs.deleteCategoryModal.open();
},
showDeleteType(type) {
this.destroyTypeForm.id = type.id;
this.$refs.deleteTypeModal.open();
},
showEditType(type, categoryId) {
this.updateTypeForm.id = type.id;
this.updateTypeForm.name = type.name;
this.updateTypeForm.activity_type_category_id = categoryId;
this.$refs.updateTypeModal.open();
},
closeUpdateCategoryModal() {
this.$refs.updateCategoryModal.close();
},
closeCreateTypeModal() {
this.$refs.createTypeModal.close();
},
closeUpdateTypeModal() {
this.$refs.updateTypeModal.close();
},
closeDeleteTypeModal() {
this.$refs.deleteTypeModal.close();
},
updateCategory() {
axios.put('settings/personalization/activitytypecategories/' + this.updateCategoryForm.id, this.updateCategoryForm)
.then(response => {
this.$refs.updateCategoryModal.close();
this.updatedCategory.name = this.updateCategoryForm.name;
this.updateCategoryForm.name = '';
this.notify(this.$t('app.default_save_success'), true);
});
},
showCreateTypeModal(category) {
this.$refs.createTypeModal.open();
this.createTypeForm.activity_type_category_id = category.id;
},
storeType() {
axios.post('settings/personalization/activitytypes', this.createTypeForm)
.then(response => {
this.$refs.createTypeModal.close();
this.activityTypes.push(response.data);
this.createTypeForm.name = '';
this.getActivityTypeCategories();
this.notify(this.$t('app.default_save_success'), true);
});
},
destroyCategory() {
axios.delete('settings/personalization/activitytypecategories/' + this.destroyCategoryForm.id)
.then(response => {
this.$refs.deleteCategoryModal.close();
this.destroyCategoryForm.id = '';
this.getActivityTypeCategories();
this.notify(this.$t('app.default_save_success'), true);
})
.catch(error => {
this.errorMessage = error.response.data.message;
});
},
updateType() {
axios.put('settings/personalization/activitytypes/' + this.updateTypeForm.id, this.updateTypeForm)
.then(response => {
this.$refs.updateTypeModal.close();
this.updatedCategory.name = this.updateTypeForm.name;
this.updateTypeForm.name = '';
this.getActivityTypeCategories();
this.notify(this.$t('app.default_save_success'), true);
});
},
destroyType() {
axios.delete('settings/personalization/activitytypes/' + this.destroyTypeForm.id)
.then(response => {
this.$refs.deleteTypeModal.close();
this.destroyTypeForm.id = '';
this.getActivityTypeCategories();
this.notify(this.$t('app.default_save_success'), true);
})
.catch(error => {
this.errorMessage = error.response.data.message;
});
},
notify(text, success) {
this.$notify({
group: 'activityTypes',
title: text,
text: '',
type: success ? 'success' : 'error'
});
}
}
};
</script>

View File

@@ -0,0 +1,392 @@
<template>
<div>
<notifications group="main" position="bottom right" />
<h3 class="with-actions">
{{ $t('settings.personalization_contact_field_type_title') }}
<a class="btn nt2" :class="[ dirltr ? 'fr' : 'fl' ]" href="" @click.prevent="add">
{{ $t('settings.personalization_contact_field_type_add') }}
</a>
</h3>
<p>{{ $t('settings.personalization_contact_field_type_description') }}</p>
<div v-if="submitted" class="pa2 ba b--yellow mb3 mt3 br2 bg-washed-yellow">
{{ $t('settings.personalization_contact_field_type_add_success') }}
</div>
<div v-if="edited" class="pa2 ba b--yellow mb3 mt3 br2 bg-washed-yellow">
{{ $t('settings.personalization_contact_field_type_edit_success') }}
</div>
<div v-if="deleted" class="pa2 ba b--yellow mb3 mt3 br2 bg-washed-yellow">
{{ $t('settings.personalization_contact_field_type_delete_success') }}
</div>
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
<div class="dt-row">
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_contact_field_type_table_name') }}
</div>
</div>
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_contact_field_type_table_protocol') }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2 b">
{{ $t('settings.personalization_contact_field_type_table_actions') }}
</div>
</div>
</div>
<div v-for="contactFieldType in contactFieldTypes" :key="contactFieldType.id" class="dt-row hover bb b--light-gray">
<div class="dtc">
<div class="pa2">
<em v-if="contactFieldType.fontawesome_icon" :class="contactFieldType.fontawesome_icon" class="pr2"></em>
<em v-else class="pr2 fa fa-address-card-o"></em>
{{ contactFieldType.name }}
</div>
</div>
<div class="dtc">
<code class="f7">
{{ contactFieldType.protocol }}
</code>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2">
<em class="fa fa-pencil-square-o pointer pr2" @click="edit(contactFieldType)"></em>
<em v-if="contactFieldType.delible" class="fa fa-trash-o pointer" @click="showDelete(contactFieldType)"></em>
</div>
</div>
</div>
</div>
<!-- Create Contact field type -->
<sweet-modal ref="modalCreateContactFieldType" overlay-theme="dark"
:title="$t('settings.personalization_contact_field_type_modal_title')"
@open="_focusCreateInput"
>
<!-- Form Errors -->
<errors :errors="createForm.errors" />
<form class="form-horizontal" role="form" @submit.prevent="store">
<div class="form-group">
<div class="col-md-auto">
<form-input
:id="'name'"
ref="createName"
v-model="createForm.name"
:required="true"
:title="$t('settings.personalization_contact_field_type_modal_name')"
@submit="store"
/>
</div>
</div>
<div class="form-group">
<div class="col-md-auto">
<form-input
:id="'protocol'"
v-model="createForm.protocol"
:placeholder="'mailto:'"
:required="true"
:title="$t('settings.personalization_contact_field_type_modal_protocol')"
@submit="store"
/>
<small class="form-text text-muted">
{{ $t('settings.personalization_contact_field_type_modal_protocol_help') }}
</small>
</div>
</div>
<div class="form-group">
<div class="col-md-auto">
<form-input
:id="'icon'"
v-model="createForm.icon"
:placeholder="'fa fa-address-book-o'"
:required="true"
:title="$t('settings.personalization_contact_field_type_modal_icon')"
@submit="store"
/>
<small class="form-text text-muted">
{{ $t('settings.personalization_contact_field_type_modal_icon_help') }}
</small>
</div>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeModal">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="store">
{{ $t('app.save') }}
</a>
</div>
</sweet-modal>
<!-- Edit Contact field type -->
<sweet-modal ref="modalEditContactFieldType" overlay-theme="dark"
:title="$t('settings.personalization_contact_field_type_modal_edit_title')"
@open="_focusEditInput"
>
<!-- Form Errors -->
<error :errors="editForm.errors" />
<form class="form-horizontal" role="form" @submit.prevent="update">
<div class="form-group">
<div class="col-md-auto">
<form-input
:id="'name'"
ref="editName"
v-model="editForm.name"
:required="true"
:title="$t('settings.personalization_contact_field_type_modal_name')"
@submit="update"
/>
</div>
</div>
<div class="form-group">
<div class="col-md-auto">
<form-input
:id="'protocol'"
v-model="editForm.protocol"
:placeholder="'mailto:'"
:required="true"
:title="$t('settings.personalization_contact_field_type_modal_protocol')"
@submit="update"
/>
<small class="form-text text-muted">
{{ $t('settings.personalization_contact_field_type_modal_protocol_help') }}
</small>
</div>
</div>
<div class="form-group">
<div class="col-md-auto">
<form-input
:id="'icon'"
v-model="editForm.icon"
:placeholder="'fa fa-address-book-o'"
:required="true"
:title="$t('settings.personalization_contact_field_type_modal_icon')"
@submit="update"
/>
<small class="form-text text-muted">
{{ $t('settings.personalization_contact_field_type_modal_icon_help') }}
</small>
</div>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeModal">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="update">
{{ $t('app.edit') }}
</a>
</div>
</sweet-modal>
<!-- Delete Contact field type -->
<sweet-modal ref="modalDeleteContactFieldType" overlay-theme="dark"
:title="$t('settings.personalization_contact_field_type_modal_delete_title')"
>
<p>
{{ $t('settings.personalization_contact_field_type_modal_delete_description') }}
</p>
<div slot="button">
<a class="btn" href="" @click.prevent="closeModal">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="trash">
{{ $t('app.delete') }}
</a>
</div>
</sweet-modal>
</div>
</template>
<script>
import { SweetModal } from 'sweet-modal-vue';
import Errors from '../partials/Error.vue';
export default {
components: {
SweetModal,
Errors,
},
data() {
return {
contactFieldTypes: [],
submitted: false,
edited: false,
deleted: false,
createForm: {
name: '',
protocol: '',
icon: '',
errors: []
},
editForm: {
id: '',
name: '',
protocol: '',
icon: '',
errors: []
},
};
},
computed: {
dirltr() {
return this.$root.htmldir === 'ltr';
}
},
mounted() {
this.prepareComponent();
},
methods: {
prepareComponent() {
this.getContactFieldTypes();
},
getContactFieldTypes() {
axios.get('settings/personalization/contactfieldtypes')
.then(response => {
this.contactFieldTypes = response.data;
});
},
add() {
this.$refs.modalCreateContactFieldType.open();
},
closeModal() {
this.$refs.modalCreateContactFieldType.close();
this.$refs.modalEditContactFieldType.close();
this.$refs.modalDeleteContactFieldType.close();
},
store() {
this.persistClient(
'post', 'settings/personalization/contactfieldtypes',
this.createForm, this.submitted
);
this.$notify({
group: 'main',
title: this.$t('settings.personalization_contact_field_type_add_success'),
text: '',
width: '500px',
type: 'success'
});
},
edit(contactFieldType) {
this.editForm.id = contactFieldType.id;
this.editForm.name = contactFieldType.name;
this.editForm.protocol = contactFieldType.protocol;
this.editForm.icon = contactFieldType.fontawesome_icon;
this.$refs.modalEditContactFieldType.open();
},
update() {
this.persistClient(
'put', 'settings/personalization/contactfieldtypes/' + this.editForm.id,
this.editForm, this.edited
);
this.$notify({
group: 'main',
title: this.$t('settings.personalization_contact_field_type_edit_success'),
text: '',
width: '500px',
type: 'success'
});
},
showDelete(contactFieldType) {
this.editForm.id = contactFieldType.id;
this.$refs.modalDeleteContactFieldType.open();
},
trash() {
this.persistClient(
'delete', 'settings/personalization/contactfieldtypes/' + this.editForm.id,
this.editForm, this.deleted
);
this.$notify({
group: 'main',
title: this.$t('settings.personalization_contact_field_type_delete_success'),
text: '',
width: '500px',
type: 'success'
});
},
persistClient(method, uri, form, success) {
form.errors = {};
axios[method](uri, form)
.then(response => {
this.getContactFieldTypes();
form.id = '';
form.name = '';
form.protocol = '';
form.icon = '';
form.errors = [];
this.closeModal();
success = true;
})
.catch(error => {
if (typeof error.response.data === 'object') {
form.errors = _.flatten(_.toArray(error.response.data));
} else {
form.errors = [this.$t('app.error_try_again')];
}
});
},
/**
* Focus on modal open.
*/
_focusCreateInput() {
const vm = this;
setTimeout(function() {
vm.$refs.createName.focus();
}, 10);
},
/**
* Focus on modal open.
*/
_focusEditInput() {
const vm = this;
setTimeout(function() {
vm.$refs.editName.focus();
}, 10);
},
}
};
</script>

View File

@@ -0,0 +1,128 @@
<style scoped>
.url {
width: 650px;
display: inline-block;
margin-bottom: 0;
vertical-align: middle;
}
</style>
<template>
<div>
<h2>{{ $t('settings.dav_title') }}</h2>
<p>{{ $t('settings.dav_description') }}</p>
<notifications group="dav" position="top middle" :duration="5000" width="400" />
<div class="form-group">
<label for="dav_url_base">
{{ $t('settings.dav_url_base') }}
</label>
<br />
<input id="dav_url_base" :value="davRoute" class="url form-control" type="text" readonly />
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(davRoute)">
{{ $t('app.copy') }}
</a>
<p>
{{ $t('settings.dav_connect_help') }}
</p>
<p>
{{ $t('settings.dav_connect_help2') }}
</p>
</div>
<div class="settings-group">
<h2>{{ $t('settings.dav_title_carddav') }}</h2>
<div class="form-group">
<label for="dav_url_carddav">
{{ $t('settings.dav_url_carddav') }}
</label>
<br />
<input id="dav_url_carddav" :value="cardDavRoute" class="url form-control" type="text" readonly />
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(cardDavRoute)">
{{ $t('app.copy') }}
</a>
<br />
<a :href="cardDavRoute+'?export'">
{{ $t('settings.dav_carddav_export') }}
</a>
</div>
</div>
<div class="settings-group">
<h2>{{ $t('settings.dav_title_caldav') }}</h2>
<div class="form-group">
<label for="dav_url_caldav_birthdays">
{{ $t('settings.dav_url_caldav_birthdays') }}
</label>
<br />
<input id="dav_url_caldav_birthdays" :value="calDavBirthdaysRoute" class="url form-control" type="text" readonly />
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(calDavBirthdaysRoute)">
{{ $t('app.copy') }}
</a>
<br />
<a :href="calDavBirthdaysRoute+'?export'">
{{ $t('settings.dav_caldav_birthdays_export') }}
</a>
</div>
<div class="form-group">
<label for="dav_url_caldav_tasks">
{{ $t('settings.dav_url_caldav_tasks') }}
</label>
<br />
<input id="dav_url_caldav_tasks" :value="calDavTasksRoute" class="url form-control" type="text" readonly />
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(calDavTasksRoute)">
{{ $t('app.copy') }}
</a>
<br />
<a :href="calDavTasksRoute+'?export'">
{{ $t('settings.dav_caldav_tasks_export') }}
</a>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
davRoute: {
type: String,
default: '',
},
cardDavRoute: {
type: String,
default: '',
},
calDavBirthdaysRoute: {
type: String,
default: '',
},
calDavTasksRoute: {
type: String,
default: '',
},
},
methods: {
copyIntoClipboard(text) {
this.$copyText(text)
.then(response => {
this.notify(this.$t('settings.dav_clipboard_copied'), true);
});
},
notify(text, success) {
this.$notify({
group: 'dav',
title: text,
text: '',
type: success ? 'success' : 'error'
});
}
}
};
</script>

View File

@@ -0,0 +1,443 @@
<template>
<div>
<notifications group="main" position="bottom right" />
<h3 class="mb3">
{{ $t('settings.personalization_genders_title') }}
<a class="btn nt2" :class="[ dirltr ? 'fr' : 'fl' ]" href="" @click.prevent="showCreateModal">
{{ $t('settings.personalization_genders_add') }}
</a>
</h3>
<p>{{ $t('settings.personalization_genders_desc') }}</p>
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
<div class="dt-row">
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_genders_table_name') }}
</div>
</div>
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_genders_table_sex') }}
</div>
</div>
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_genders_table_default') }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2 b">
{{ $t('settings.personalization_contact_field_type_table_actions') }}
</div>
</div>
</div>
<div v-for="gender in genders" :key="gender.id"
class="dt-row bb b--light-gray"
>
<div class="dtc">
<div class="pa2">
{{ gender.name }}
<span class="i">
{{ $tc('settings.personalization_genders_list_contact_number', gender.numberOfContacts, { count: gender.numberOfContacts }) }}
</span>
</div>
</div>
<div class="dtc">
<div class="pa2">
{{ $t('settings.personalization_genders_' + gender.type.toLowerCase()) }}
</div>
</div>
<div class="dtc">
<div class="pa2">
<template v-if="gender.isDefault">
{{ $t('settings.personalization_genders_default') }}
</template>
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2">
<em class="fa fa-pencil-square-o pointer pr2" @click="showEdit(gender)"></em>
<em v-if="genders.length > 1" class="fa fa-trash-o pointer" @click="showDelete(gender)"></em>
</div>
</div>
</div>
</div>
<div class="mt2" :class="[ dirltr ? 'tr' : 'tl' ]">
<a class="pointer" href="" @click.prevent="showDefaultGenderModal">{{ $t('settings.personalization_genders_make_default') }}</a>
</div>
<!-- Create Gender type -->
<sweet-modal ref="createModal" overlay-theme="dark" :title="$t('settings.personalization_genders_modal_add')">
<form @submit.prevent="store()">
<div class="form-group">
<div class="form-group">
<form-input
:id="''"
v-model="createForm.name"
:input-type="'text'"
:required="true"
:title="$t('settings.personalization_genders_modal_name')"
/>
<small class="form-text text-muted">
{{ $t('settings.personalization_genders_modal_name_help') }}
</small>
</div>
<div class="form-group">
<form-select
:id="''"
v-model="createForm.type"
:options="genderTypes"
:required="true"
:title="$t('settings.personalization_genders_modal_sex')"
/>
<small class="form-text text-muted">
{{ $t('settings.personalization_genders_modal_sex_help') }}
</small>
</div>
<div class="form-group">
<form-toggle
:id="''"
v-model="createForm.isDefault"
:labels="toggleOptions"
:required="true"
:title="$t('settings.personalization_genders_modal_default')"
/>
</div>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeModal()">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="store()">
{{ $t('app.save') }}
</a>
</div>
</sweet-modal>
<!-- Edit gender type -->
<sweet-modal ref="updateModal" overlay-theme="dark" :title="$t('settings.personalization_genders_modal_edit')">
<form @submit.prevent="update(updatedGender)">
<div class="form-group">
<div class="form-group">
<form-input
:id="''"
v-model="updateForm.name"
:input-type="'text'"
:required="true"
:title="$t('settings.personalization_genders_modal_name')"
/>
<small class="form-text text-muted">
{{ $t('settings.personalization_genders_modal_name_help') }}
</small>
</div>
<div class="form-group">
<form-select
:id="''"
v-model="updateForm.type"
:options="genderTypes"
:required="true"
:title="$t('settings.personalization_genders_modal_sex')"
/>
<small class="form-text text-muted">
{{ $t('settings.personalization_genders_modal_sex_help') }}
</small>
</div>
<div class="form-group">
<form-toggle
:id="''"
v-model="updateForm.isDefault"
:labels="toggleOptions"
:required="true"
:title="$t('settings.personalization_genders_modal_default')"
/>
</div>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeUpdateModal()">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="update(updatedGender)">
{{ $t('app.update') }}
</a>
</div>
</sweet-modal>
<!-- Delete Gender type -->
<sweet-modal ref="deleteModal" overlay-theme="dark" :title="$t('settings.personalization_genders_modal_delete')">
<form>
<div v-if="errorMessage !== ''" class="form-error-message mb3">
<div class="pa2">
<p class="mb0">
{{ errorMessage }}
</p>
</div>
</div>
<div class="mb4">
<p class="mb2">
{{ $t('settings.personalization_genders_modal_delete_desc', {name: deleteForm.name}) }}
</p>
<div v-if="deleteForm.numberOfContacts !== 0 || deleteForm.isDefault">
<p v-if="deleteForm.numberOfContacts !== 0">
{{ $tc('settings.personalization_genders_modal_delete_question', deleteForm.numberOfContacts, {count: deleteForm.numberOfContacts}) }}
</p>
<p v-else>
{{ $tc('settings.personalization_genders_modal_delete_question_default') }}
</p>
<form-select
:id="'deleteNewId'"
v-model="deleteForm.newId"
:options="genders"
:required="true"
:title="''"
:excluded-id="deleteForm.id"
/>
</div>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeDeleteModal()">
{{ $t('app.cancel') }}
</a>
<a v-if="deleteForm.numberOfContacts === 0 && ! deleteForm.isDefault"
class="btn btn-primary"
href=""
@click.prevent="trash()"
>
{{ $t('app.delete') }}
</a>
<a v-else class="btn btn-primary" href="" @click.prevent="trashAndReplace()">
{{ $t('app.delete') }}
</a>
</div>
</sweet-modal>
<!-- Change default Gender -->
<sweet-modal ref="defaultGenderModal" overlay-theme="dark" :title="$t('settings.personalization_genders_modal_default')">
<form>
<div class="form-group">
<div class="form-group">
<form-select
:id="''"
v-model="defaultGenderId"
:options="genders"
:required="true"
:title="$t('settings.personalization_genders_select_default')"
/>
</div>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeDefaultGenderModal()">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="updateDefaultGender()">
{{ $t('app.save') }}
</a>
</div>
</sweet-modal>
</div>
</template>
<script>
import { SweetModal } from 'sweet-modal-vue';
export default {
components: {
SweetModal
},
data() {
return {
genders: [],
genderTypes: [],
updatedGender: {
id: '',
name: '',
type: '',
isDefault: false,
},
errorMessage: '',
createForm: {
name: '',
type: '',
isDefault: false,
errors: []
},
updateForm: {
id: '',
name: '',
type: '',
isDefault: false,
errors: []
},
deleteForm: {
id: '',
name: '',
isDefault: false,
numberOfContacts: 0,
newId: 0
},
defaultGenderId: null,
};
},
computed: {
dirltr() {
return this.$root.htmldir === 'ltr';
},
toggleOptions() {
return {
checked: this.$t('app.yes'),
unchecked: this.$t('app.no')
};
},
defaultGenderType() {
const defaultGender = _.findIndex(this.genders, ['isDefault', true]);
return this.genders[defaultGender >= 0 ? defaultGender : 0];
}
},
mounted() {
this.prepareComponent();
},
methods: {
prepareComponent() {
axios.all([
this.getGenders(),
this.getGenderTypes()
]);
},
setDefaultGenderType() {
this.createForm.type = this.defaultGenderType.type;
},
getGenders() {
return axios.get('settings/personalization/genders')
.then(response => {
this.genders = _.toArray(response.data);
this.setDefaultGenderType();
});
},
getGenderTypes() {
return axios.get('settings/personalization/genderTypes')
.then(response => {
this.genderTypes = _.toArray(response.data);
});
},
closeModal() {
this.$refs.createModal.close();
},
closeUpdateModal() {
this.$refs.updateModal.close();
},
closeDeleteModal() {
this.$refs.deleteModal.close();
},
closeDefaultGenderModal() {
this.$refs.defaultGenderModal.close();
},
showCreateModal() {
this.$refs.createModal.open();
},
showDefaultGenderModal() {
this.defaultGenderId = this.defaultGenderType.id;
this.$refs.defaultGenderModal.open();
},
store() {
axios.post('settings/personalization/genders', this.createForm)
.then(response => {
this.closeModal();
this.getGenders();
this.createForm.name = '';
this.createForm.type = '';
});
},
showEdit(gender) {
this.updateForm.id = gender.id.toString();
this.updateForm.name = gender.name;
this.updateForm.type = gender.type;
this.updateForm.isDefault = gender.isDefault;
this.updatedGender = gender;
this.$refs.updateModal.open();
},
update() {
axios.put('settings/personalization/genders/' + this.updateForm.id, this.updateForm)
.then(response => {
this.closeUpdateModal();
this.getGenders();
this.updatedGender.name = this.updateForm.name;
this.updatedGender.type = this.updateForm.type;
this.updatedGender.isDefault = this.updateForm.isDefault;
this.updateForm.name = '';
});
},
showDelete(gender) {
this.errorMessage = '';
this.deleteForm.name = gender.name;
this.deleteForm.id = gender.id.toString();
this.deleteForm.isDefault = gender.isDefault;
this.deleteForm.numberOfContacts = gender.numberOfContacts;
this.$refs.deleteModal.open();
},
trash() {
axios.delete('settings/personalization/genders/' + this.deleteForm.id)
.then(response => {
this.closeDeleteModal();
this.getGenders();
});
},
trashAndReplace() {
axios.delete('settings/personalization/genders/' + this.deleteForm.id + '/replaceby/' + this.deleteForm.newId)
.then(response => {
this.closeDeleteModal();
this.getGenders();
})
.catch(error => {
if (typeof error.response.data === 'object') {
this.errorMessage = error.response.data.message;
} else {
this.errorMessage = this.$t('app.error_try_again');
}
});
},
updateDefaultGender() {
axios.put('settings/personalization/genders/default/' + this.defaultGenderId)
.then(response => {
this.closeDefaultGenderModal();
this.getGenders();
});
}
}
};
</script>

View File

@@ -0,0 +1,300 @@
<template>
<div>
<notifications group="lifeEventTypes" position="bottom right" />
<h3 class="with-actions">
{{ $t('settings.personalization_life_event_category_title') }}
</h3>
<p>{{ $t('settings.personalization_life_event_category_description') }}</p>
<div v-if="limited" class="mt3 mb3 form-information-message br2">
<div class="pa3 flex">
<div class="mr3">
<svg viewBox="0 0 20 20">
<g fill-rule="evenodd">
<circle cx="10" cy="10" r="9" fill="currentColor" /><path d="M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.486 10-10S15.514 0 10 0m0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8m1-5v-3a1 1 0 0 0-1-1H9a1 1 0 1 0 0 2v3a1 1 0 0 0 1 1h1a1 1 0 1 0 0-2m-1-5.9a1.1 1.1 0 1 0 0-2.2 1.1 1.1 0 0 0 0 2.2" />
</g>
</svg>
</div>
<div v-html="$t('settings.personalisation_paid_upgrade_vue', {url: 'settings/subscriptions' })"></div>
</div>
</div>
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
<div class="dt-row">
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_live_event_category_table_name') }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2 b">
{{ $t('settings.personalization_live_event_category_table_actions') }}
</div>
</div>
</div>
</div>
<div>
<ul>
<li v-for="lifeEventCategory in lifeEventCategories" :key="lifeEventCategory.id" class="dt dt--fixed w-100 collapse br--top br--bottom mt3">
<!-- LIFE EVENT CATEGORY -->
<div class="dt-row hover bb b--light-gray">
<div class="dtc">
<div class="pa2 b">
<strong>{{ $t('people.life_event_category_' + lifeEventCategory.default_life_event_category_key) }}</strong>
</div>
</div>
<div class="dtc">
</div>
</div>
<div v-for="lifeEventType in lifeEventCategory.lifeEventTypes" :key="lifeEventType.id" class="dt-row hover bb b--light-gray">
<div class="dtc">
<div class="pa2 pl4">
<template v-if="lifeEventType.name">
{{ lifeEventType.name }}
</template>
<template v-else>
{{ $t('people.life_event_sentence_' + lifeEventType.default_life_event_type_key) }}
</template>
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2">
<em v-if="!limited" class="fa fa-pencil-square-o pointer pr2" @click="showEditType(lifeEventType, lifeEventCategory.id)"></em>
<em v-if="!limited" class="fa fa-trash-o pointer" @click="showDeleteType(lifeEventType)"></em>
</div>
</div>
</div>
<div v-if="!limited" class="dt-row">
<div class="dtc">
<div class="pa2 pl4">
<a class="pointer" href=""
@click.prevent="showCreateType(lifeEventCategory)"
>
{{ $t('settings.personalization_life_event_type_add_button') }}
</a>
</div>
</div>
</div>
</li>
</ul>
</div>
<!-- Create Life Event Type -->
<sweet-modal ref="createTypeModal" overlay-theme="dark" :title="$t('settings.personalization_life_event_type_modal_add')">
<form @submit.prevent="storeType()">
<div class="mb4">
<p class="b mb2"></p>
<form-input
:id="'add-type-name'"
v-model="createTypeForm.name"
:input-type="'text'"
:required="true"
:title="$t('settings.personalization_life_event_type_modal_question')"
/>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeCreateTypeModal()">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="storeType()">
{{ $t('app.save') }}
</a>
</div>
</sweet-modal>
<!-- Update Life Event Type -->
<sweet-modal ref="updateTypeModal" overlay-theme="dark" :title="$t('settings.personalization_life_event_type_modal_edit')">
<form @submit.prevent="updateType()">
<div class="mb4">
<p class="b mb2"></p>
<form-input
:id="'update-type-name'"
v-model="updateTypeForm.name"
:input-type="'text'"
:required="true"
:title="$t('settings.personalization_life_event_type_modal_question')"
/>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeUpdateTypeModal()">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="updateType()">
{{ $t('app.update') }}
</a>
</div>
</sweet-modal>
<!-- Delete Life Event type -->
<sweet-modal ref="deleteTypeModal" overlay-theme="dark" :title="$t('settings.personalization_life_event_type_modal_delete')">
<form>
<div v-if="errorMessage !== ''" class="form-error-message mb3">
<div class="pa2">
<p class="mb0">
{{ errorMessage }}
</p>
</div>
</div>
<div class="mb4">
<p class="mb2">
{{ $t('settings.personalization_life_event_type_modal_delete_desc') }}
</p>
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeDeleteTypeModal()">
{{ $t('app.cancel') }}
</a>
<a class="btn btn-primary" href="" @click.prevent="destroyType()">
{{ $t('app.delete') }}
</a>
</div>
</sweet-modal>
</div>
</template>
<script>
import { SweetModal } from 'sweet-modal-vue';
export default {
components: {
SweetModal
},
props: {
limited: {
type: Boolean,
default: false,
},
},
data() {
return {
lifeEventCategories: [],
errorMessage: '',
createTypeForm: {
name: '',
life_event_category_id: '',
errors: []
},
updateTypeForm: {
id: '',
name: '',
life_event_category_id: '',
errors: []
},
destroyTypeForm: {
id: '',
errors: []
},
};
},
computed: {
dirltr() {
return this.$root.htmldir === 'ltr';
}
},
mounted() {
this.prepareComponent();
},
methods: {
prepareComponent() {
this.getLifeEventCategories();
},
getLifeEventCategories() {
axios.get('settings/personalization/lifeeventcategories')
.then(response => {
this.lifeEventCategories = response.data;
});
},
showCreateType(category) {
this.$refs.createTypeModal.open();
this.createTypeForm.life_event_category_id = category.id;
},
showDeleteType(type) {
this.destroyTypeForm.id = type.id;
this.$refs.deleteTypeModal.open();
},
showEditType(type, categoryId) {
this.updateTypeForm.id = type.id;
this.updateTypeForm.name = type.name ? type.name : this.$t('people.life_event_sentence_' + type.default_life_event_type_key);
this.updateTypeForm.life_event_category_id = categoryId;
this.$refs.updateTypeModal.open();
},
closeCreateTypeModal() {
this.$refs.createTypeModal.close();
},
closeUpdateTypeModal() {
this.$refs.updateTypeModal.close();
},
closeDeleteTypeModal() {
this.$refs.deleteTypeModal.close();
},
storeType() {
axios.post('settings/personalization/lifeeventtypes', this.createTypeForm)
.then(response => {
this.$refs.createTypeModal.close();
this.createTypeForm.name = '';
this.getLifeEventCategories();
this.notify(this.$t('app.default_save_success'), true);
});
},
updateType() {
axios.put('settings/personalization/lifeeventtypes/' + this.updateTypeForm.id, this.updateTypeForm)
.then(response => {
this.$refs.updateTypeModal.close();
this.updateTypeForm.name = '';
this.getLifeEventCategories();
this.notify(this.$t('app.default_save_success'), true);
});
},
destroyType() {
axios.delete('settings/personalization/lifeeventtypes/' + this.destroyTypeForm.id)
.then(response => {
this.$refs.deleteTypeModal.close();
this.destroyTypeForm.id = '';
this.getLifeEventCategories();
this.notify(this.$t('app.default_save_success'), true);
})
.catch(error => {
this.errorMessage = error.response.data.message;
});
},
notify(text, success) {
this.$notify({
group: 'lifeEventTypes',
title: text,
text: '',
type: success ? 'success' : 'error'
});
}
}
};
</script>

View File

@@ -0,0 +1,188 @@
<template>
<div>
<notifications group="mfa" position="bottom right" :duration="5000" width="400" />
<h3>{{ $t('settings.2fa_otp_title') }}</h3>
<div class="form-group">
<a v-if="selectActivated" class="btn btn-warning" href="" @click.prevent="showDisableModal">
{{ $t('settings.2fa_disable_title') }}
</a>
<a v-else class="btn btn-primary" href="" @click.prevent="showEnableModal">
{{ $t('settings.2fa_enable_title') }}
</a>
</div>
<sweet-modal id="enableModal" ref="enableModal" overlay-theme="dark" :title="$t('settings.2fa_otp_title')">
<form @submit.prevent="register()">
<p>{{ $t('settings.2fa_enable_description') }}</p>
<div class="panel-body">
{{ $t('settings.2fa_enable_otp') }}
<div v-html="image"></div>
<p>
{{ $t('settings.2fa_enable_otp_help') }}
<code id="secretkey">
{{ secret }}
</code>
</p>
</div>
<div class="form-group">
<p>
{{ $t('settings.2fa_enable_otp_validate') }}
</p>
<form-input
:id="'one_time_password1'"
v-model="one_time_password"
:title="$t('auth.2fa_one_time_password')"
:input-type="'number'"
:width="100"
:required="true"
/>
</div>
</form>
<div slot="button">
<a id="verify1" class="btn btn-primary" href="" @click.prevent="register()">
{{ $t('app.verify') }}
</a>
<a class="btn" href="" @click.prevent="closeEnableModal()">
{{ $t('app.cancel') }}
</a>
</div>
</sweet-modal>
<sweet-modal id="disableModal" ref="disableModal" overlay-theme="dark" :title="$t('settings.2fa_otp_title')">
<form @submit.prevent="register()">
<p>{{ $t('settings.2fa_disable_description') }}</p>
<div class="form-group">
<form-input
:id="'one_time_password2'"
v-model="one_time_password"
:title="$t('auth.2fa_one_time_or_recuperation')"
:input-type="'text'"
:width="100"
:required="true"
/>
</div>
</form>
<div slot="button">
<a id="verify2" class="btn btn-primary" href="" @click.prevent="unregister()">
{{ $t('app.verify') }}
</a>
<a class="btn" href="" @click.prevent="closeDisableModal()">
{{ $t('app.cancel') }}
</a>
</div>
</sweet-modal>
</div>
</template>
<script>
import { SweetModal } from 'sweet-modal-vue';
export default {
components: {
SweetModal
},
props: {
activated: {
type: Boolean,
default: false
},
},
data() {
return {
selectActivated: false,
errorMessage: '',
infoMessage: '',
success: false,
one_time_password: '',
image: '',
secret: '',
};
},
watch: {
activated: function (val) {
this.selectActivated = val;
}
},
mounted() {
this.selectActivated = this.activated;
},
methods: {
register() {
axios.post('settings/security/2fa-enable', { one_time_password: this.one_time_password })
.then(response => {
this.closeEnableModal();
this.selectActivated = response.data.success;
if (response.data.success) {
this.notify(this.$t('settings.2fa_enable_success'), true);
} else {
this.notify(this.$t('settings.2fa_enable_error'), false);
}
}).catch(error => {
this.closeEnableModal();
this.notify(error.response.data.message, false);
});
},
unregister() {
axios.post('settings/security/2fa-disable', { one_time_password: this.one_time_password })
.then(response => {
this.closeDisableModal();
this.selectActivated = ! response.data.success;
if (response.data.success) {
this.notify(this.$t('settings.2fa_disable_success'), true);
} else {
this.notify(this.$t('settings.2fa_disable_error'), false);
}
}).catch(error => {
this.closeDisableModal();
this.notify(error.response.data.message, false);
});
},
showEnableModal() {
this.one_time_password = '';
axios.get('settings/security/2fa-enable')
.then(response => {
this.image = response.data.image;
this.secret = response.data.secret;
this.$refs.enableModal.open();
}).catch(error => {
this.notify(error.response.data.message, false);
});
},
showDisableModal() {
this.one_time_password = '';
this.$refs.disableModal.open();
},
closeEnableModal() {
this.$refs.enableModal.close();
},
closeDisableModal() {
this.$refs.disableModal.close();
},
notify(text, success) {
this.$notify({
group: 'mfa',
title: text,
text: '',
type: success ? 'success' : 'error'
});
}
}
};
</script>

View File

@@ -0,0 +1,109 @@
<template>
<div class="reminder-rules">
<notifications group="main" position="bottom right" />
<h3 class="mb3">
{{ $t('settings.personalization_module_title') }}
</h3>
<p>
{{ $t('settings.personalization_module_desc') }}
</p>
<div v-if="limited" class="mt3 mb3 form-information-message br2">
<div class="pa3 flex">
<div class="mr3">
<svg viewBox="0 0 20 20">
<g fill-rule="evenodd">
<circle cx="10" cy="10" r="9" fill="currentColor" /><path d="M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.486 10-10S15.514 0 10 0m0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8m1-5v-3a1 1 0 0 0-1-1H9a1 1 0 1 0 0 2v3a1 1 0 0 0 1 1h1a1 1 0 1 0 0-2m-1-5.9a1.1 1.1 0 1 0 0-2.2 1.1 1.1 0 0 0 0 2.2" />
</g>
</svg>
</div>
<div v-html="$t('settings.personalisation_paid_upgrade_vue', {url: 'settings/subscriptions' })"></div>
</div>
</div>
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
<div class="dt-row">
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_contact_field_type_table_name') }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2 b">
{{ $t('settings.personalization_contact_field_type_table_actions') }}
</div>
</div>
</div>
<div v-for="module in modules" :key="module.id" class="dt-row hover bb b--light-gray">
<div class="dtc">
<div class="pa2">
{{ module.name }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2">
<form-toggle
v-model="module.active"
:iclass="'module-'"
:disabled="limited"
:labels="true"
@change="toggle(module, $event)"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
limited: {
type: Boolean,
default: false,
},
},
data() {
return {
modules: [],
};
},
computed: {
dirltr() {
return this.$root.htmldir === 'ltr';
}
},
mounted() {
this.getModules();
},
methods: {
getModules() {
axios.get('settings/personalization/modules')
.then(response => {
this.modules = response.data;
});
},
toggle(module) {
axios.post('settings/personalization/modules/' + module.id)
.then(response => {
this.$notify({
group: 'main',
title: this.$t('settings.personalization_module_save'),
text: '',
type: 'success'
});
this.$set(module, 'active', response.data.data.active);
});
}
}
};
</script>

View File

@@ -0,0 +1,139 @@
<style scoped>
.code {
margin-bottom: 0.1em;
}
.used {
text-decoration: line-through;
}
</style>
<template>
<div>
<h3>{{ $t('settings.recovery_title') }}</h3>
<div class="form-group">
<a class="btn btn-primary" href="" @click.prevent="showRecoveryModal">
{{ $t('settings.recovery_show') }}
</a>
</div>
<sweet-modal id="recoveryModal" ref="recoveryModal" overlay-theme="dark" :title="$t('settings.recovery_title')">
<notifications group="recovery" position="top middle" :duration="5000" width="400" />
<p>{{ $t('settings.recovery_help_intro') }}</p>
<p :class="[ dirltr ? 'ml3' : 'mr3' ]">
<span v-for="code in codes" :key="code.id" v-cy-name="'recovery-' + code.id">
<pre class="code" :class="[ code.used ? 'used' : '' ]" :title="[ code.used ? usedHelp : '']">{{ code.recovery }}</pre>
</span>
</p>
<p>{{ $t('settings.recovery_help_information') }}</p>
<div slot="button">
<span :class="[ dirltr ? 'fl' : 'fr' ]">
<a class="btn" href="" @click.prevent="generateNewCodes">
{{ $t('settings.recovery_generate') }}
</a>
<br />
<small class="form-text text-muted">
{{ $t('settings.recovery_generate_help') }}
</small>
</span>
<span :class="[ dirltr ? 'fr' : 'fl' ]">
<a class="btn btn-primary" :title="copyHelp" href="" @click.prevent="copyIntoClipboard">
{{ $t('app.copy') }}
</a>
<!--
<a @click.prevent="download" class="btn" href="">{{ $t('app.download') }}</a>
-->
<a class="btn" href="" @click.prevent="closeRecoveryModal">
{{ $t('app.close') }}
</a>
</span>
</div>
</sweet-modal>
</div>
</template>
<script>
import { SweetModal } from 'sweet-modal-vue';
export default {
components: {
SweetModal
},
data() {
return {
codes: [],
};
},
computed: {
dirltr() {
return this.$root.htmldir === 'ltr';
},
usedHelp() {
return this.$t('settings.recovery_already_used_help');
},
copyHelp() {
return this.$t('settings.recovery_copy_help');
},
},
methods: {
showRecoveryModal() {
this.codes = [];
axios.post('settings/security/recovery-codes')
.then(response => {
this.codes = response.data;
this.$refs.recoveryModal.open();
}).catch(error => {
this.notify(error.response.data.message, false);
});
},
generateNewCodes() {
this.codes = [];
axios.post('settings/security/generate-recovery-codes')
.then(response => {
this.codes = response.data;
}).catch(error => {
this.notify(error.response.data.message, false);
});
},
closeRecoveryModal() {
this.$refs.recoveryModal.close();
},
copyIntoClipboard() {
this.$copyText(this.getDataStream())
.then(response => {
this.notify(this.$t('settings.recovery_clipboard'), true);
});
},
getDataStream() {
var text = this.$t('settings.recovery_help_intro')+'\n';
var i = 1;
this.codes.forEach(code => {
if (code.used) {
text += i + '. ---------\n';
} else {
text += i + '. ' + code.recovery + '\n';
}
i++;
});
return text;
},
notify(text, success) {
this.$notify({
group: 'recovery',
title: text,
text: '',
type: success ? 'success' : 'error'
});
}
}
};
</script>

View File

@@ -0,0 +1,92 @@
<template>
<div class="reminder-rules">
<notifications group="main" position="bottom right" />
<h3 class="mb3">
{{ $t('settings.personalization_reminder_rule_title') }}
</h3>
<p>
{{ $t('settings.personalization_reminder_rule_desc') }}
</p>
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
<div class="dt-row">
<div class="dtc">
<div class="pa2 b">
{{ $t('settings.personalization_contact_field_type_table_name') }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2 b">
{{ $t('settings.personalization_contact_field_type_table_actions') }}
</div>
</div>
</div>
<div v-for="reminderRule in reminderRules" :key="reminderRule.id" class="dt-row bb b--light-gray">
<div class="dtc">
<div class="pa2">
{{ $tc('settings.personalization_reminder_rule_line', reminderRule.number_of_days_before, {count: reminderRule.number_of_days_before}) }}
</div>
</div>
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
<div class="pa2">
<form-toggle
v-model="reminderRule.active"
:iclass="'reminder-rule-' + reminderRule.number_of_days_before"
:labels="true"
@change="toggle(reminderRule, $event)"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
reminderRules: [],
};
},
computed: {
dirltr() {
return this.$root.htmldir === 'ltr';
}
},
mounted() {
this.prepareComponent();
},
methods: {
prepareComponent() {
this.getReminderRules();
},
getReminderRules() {
axios.get('settings/personalization/reminderrules')
.then(response => {
this.reminderRules = response.data;
});
},
toggle(reminderRule, event) {
axios.post('settings/personalization/reminderrules/' + reminderRule.id)
.then(response => {
this.$notify({
group: 'main',
title: this.$t('settings.personalization_reminder_rule_save'),
text: '',
type: 'success'
});
this.$set(reminderRule, 'active', response.data.data.active);
});
}
}
};
</script>

View File

@@ -0,0 +1,108 @@
<template>
<div>
<!-- Timezone -->
<div class="form-group">
<form-select
:id="'timezone'"
:value="updatedTimezone"
:options="timezones"
:title="$t('settings.timezone')"
:required="true"
:iclass="'form-control'"
@input="timezoneUpdate"
/>
</div>
<!-- Reminders -->
<div class="form-group">
<form-select
:id="'reminder_time'"
:value="updatedReminder"
:options="hours"
:title="$t('settings.reminder_time_to_send')"
:required="true"
:iclass="'form-control'"
@input="reminderUpdate"
/>
<small class="form-text text-muted" v-html="message"></small>
</div>
</div>
</template>
<script>
export default {
props: {
timezone: {
type: String,
default: 'UTC',
},
timezones: {
type: Array,
default: function () {
return [];
}
},
reminder: {
type: String,
default: '',
},
hours: {
type: Array,
default: function () {
return [];
}
}
},
data() {
return {
message: '',
updatedTimezone: '',
updatedReminder: ''
};
},
mounted() {
this.prepareComponent();
},
methods: {
prepareComponent() {
this.updatedReminder = this.reminder;
this.updatedTimezone = this.timezone;
this.computeMessage();
},
timezoneUpdate: function(event) {
this.updatedTimezone = event;
this.computeMessage();
},
reminderUpdate: function(event) {
this.updatedReminder = event;
this.computeMessage();
},
computeMessage() {
var moment = require('moment-timezone');
moment.locale(this._i18n.locale);
moment.tz.setDefault('UTC');
var now = moment();
var t = now.format('YYYY-MM-DD ' + this.updatedReminder + ':00');
var date = moment.tz(t, this.updatedTimezone);
if (date.isBefore(now)) {
date = date.add(1, 'days');
}
this.message = this.$t('settings.reminder_time_to_send_help', {
dateTime: date.format('LLL'),
dateTimeUtc: date.utc().format('YYYY-MM-DD HH:mm z')
});
}
}
};
</script>

View File

@@ -0,0 +1,297 @@
<template>
<form ref="form" class="mb4" :action="callback" method="post" @submit.prevent="subscribe()">
<notifications group="subscription" position="top middle" :duration="5000" width="400" />
<div class="form-group">
<div v-if="errors" role="alert" class="alert alert-danger w-100">
{{ errors }}
</div>
<div v-if="paymentSucceeded">
<h1 class="text-xl mt-2 mb-4 text-gray-700">
{{ $t('settings.subscriptions_payment_succeeded_title') }}
</h1>
<p v-if="successMessage" class="mb-6">
{{ successMessage }}
</p>
<p v-else class="mb-6">
{{ $t('settings.subscriptions_payment_succeeded') }}
</p>
</div>
<div v-else-if="paymentCancelled">
<h1 class="text-xl mt-2 mb-4 text-gray-700">
{{ $t('settings.subscriptions_payment_cancelled_title') }}
</h1>
<p class="mb-6">
{{ $t('settings.subscriptions_payment_cancelled') }}
</p>
</div>
<div v-else-if="! paymentProcessed" id="payment-elements" class="b--gray-monica ba pa4 br2 mb3 bg-black-05">
<div class="form-row">
<div class="mb3">
<form-input
:id="'cardholder-name'"
v-model="selectedName"
:input-type="'text'"
:iclass="'br3 b--black-30 ba pa3 w-100 f4'"
:required="true"
:title="$t('settings.subscriptions_upgrade_name')"
/>
</div>
<div class="mb3">
<form-input
:id="'address-zip'"
v-model="zip"
:input-type="'text'"
:iclass="'br3 b--black-30 ba pa3 w-100 f4'"
:title="$t('settings.subscriptions_upgrade_zip')"
/>
</div>
<label for="card-element">
{{ $t('settings.subscriptions_upgrade_credit') }}
</label>
<div id="card-element">
<!-- a Stripe Element will be inserted here. -->
</div>
</div>
<button
id="card-button"
class="btn btn-primary w-100 mt3"
:disabled="paymentProcessing"
@click.prevent="confirm ? confirmPayment() : subscribe()"
v-html="$t('settings.subscriptions_upgrade_submit', { amount: amount })"
>
</button>
</div>
<a v-if="paymentProcessed" :href="callback"
class="btn btn-secondary w-100 tc"
>
{{ $t('app.go_back') }}
</a>
</div>
<input type="hidden" name="_token" :value="token" />
<input type="hidden" name="plan" :value="plan" />
<input type="hidden" name="payment_method" :value="paymentMethod" />
</form>
</template>
<script>
export default {
props: {
name: {
type: String,
default: '',
},
stripeKey: {
type: String,
default: '',
},
clientSecret: {
type: String,
default: '',
},
plan: {
type: String,
default: '',
},
amount: {
type: String,
default: '',
},
callback: {
type: String,
default: '',
},
token: {
type: String,
default: '',
},
confirm: {
type: Boolean,
default: false,
},
paymentSucceeded: {
type: Boolean,
default: false,
},
paymentCancelled: {
type: Boolean,
default: false,
},
},
data() {
return {
selectedName: '',
stripe: null,
zip: '',
errors: '',
successMessage: '',
cardElement: null,
paymentMethod: '',
paymentProcessing: false,
paymentProcessed: false,
};
},
watch: {
name() {
this.selectedName = this.name;
}
},
mounted() {
this.selectedName = this.name;
if (this.paymentSucceeded || this.paymentCancelled) {
this.paymentProcessed = true;
}
if (! this.paymentProcessed) {
this.start();
}
},
methods: {
start() {
this.stripe = Stripe(this.stripeKey);
const elements = this.stripe.elements();
// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
const style = {
base: {
color: '#32325d',
lineHeight: '18px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
// Create an instance of the card Element
this.cardElement = elements.create('card', {
hidePostalCode: true,
style: style
});
// Add an instance of the card Element into the `card-element` <div>
this.cardElement.mount('#card-element');
// Handle real-time validation errors from the card Element.
var self = this;
this.cardElement.addEventListener('change', function(event) {
if (event.error) {
self.errors = event.error.message;
} else {
self.errors = '';
}
});
},
handleError(error) {
if (error.code === 'parameter_invalid_empty' &&
error.param === 'payment_method_data[billing_details][name]') {
this.errors = this.$t('settings.subscriptions_payment_error_name');
} else {
this.errors = error.message;
}
},
subscribe() {
var self = this;
this.errors = '';
this.paymentProcessing = true;
this.paymentProcessed = false;
this.stripe.handleCardSetup(
self.clientSecret,
self.cardElement,
{
payment_method_data: {
billing_details: {
name: self.selectedName,
address: {
postal_code: self.zip,
}
}
}
}
).then(function (result) {
self.paymentProcessing = false;
if (result.error) {
self.handleError(result.error);
} else {
// The card has been verified successfully...
self.paymentProcessed = true;
self.paymentSucceeded = true;
self.successMessage = self.$t('settings.subscriptions_payment_success');
self.notify(self.successMessage, true);
self.processPayment(result.setupIntent);
}
});
},
processPayment(setupIntent) {
var self = this;
this.paymentMethod = setupIntent.payment_method;
setTimeout(function () {
self.$refs.form.submit();
}, 10);
},
confirmPayment() {
var self = this;
this.paymentProcessing = true;
this.paymentProcessed = false;
this.errorMessage = '';
this.stripe.handleCardPayment(
self.clientSecret, self.cardElement, {
payment_method_data: {
billing_details: { name: this.selectedName }
}
}
).then(function (result) {
self.paymentProcessing = false;
if (result.error) {
self.handleError(result.error);
} else {
self.paymentProcessed = true;
self.paymentSucceeded = true;
self.successMessage = self.$t('settings.subscriptions_payment_success');
self.notify(self.successMessage, true);
setTimeout(function () {
window.location = self.callback;
}, 3000);
}
});
},
notify(text, success) {
this.$notify({
group: 'subscription',
title: text,
text: '',
type: success ? 'success' : 'error'
});
}
}
};
</script>

View File

@@ -0,0 +1,402 @@
<style scoped>
.time {
color: gray;
}
</style>
<template>
<div class="form-group">
<notifications group="webauthn" position="top middle" :duration="5000" width="400" />
<div v-if="method === 'register-modal'">
<h3>{{ $t('settings.webauthn_title') }}</h3>
<div v-if="currentkeys !== null">
<ul class="table">
<li v-for="key in currentkeys"
:key="key.id"
class="table-row"
>
<div class="table-cell w-30">
<strong>{{ key.name }}</strong>
</div>
<div class="table-cell time w-50">
<template v-if="key.counter > 0">
{{ $t('settings.webauthn_last_use', {timestamp: formatTime(key.updated_at)}) }}
</template>
</div>
<div class="table-cell actions">
<a class="pointer" href="" @click.prevent="showDeleteModal(key.id)">
{{ $t('app.delete') }}
</a>
</div>
</li>
</ul>
</div>
<slot></slot>
<a v-if="isSupported" class="btn btn-primary" href="" @click.prevent="showRegisterModal">
{{ $t('settings.webauthn_enable_description') }}
</a>
<small v-else>
{{ notSupportedMessage() }}
</small>
<sweet-modal
id="registerModal"
ref="registerModal"
overlay-theme="dark"
:title="$t('settings.webauthn_title')"
>
<div v-if="registerTab === '1'">
<p>
{{ $t('settings.webauthn_key_name_help') }}
</p>
<form-input
:id="'keyName'"
v-model="keyName"
:title="$t('settings.webauthn_key_name')"
:value="keyName"
:input-type="'text'"
:width="150"
:required="true"
@keyup.enter="showRegisterModalTab('2');startRegister();"
/>
</div>
<div v-if="registerTab === '2'">
<div v-if="errorMessage !== ''" class="form-error-message mb3">
<div class="pa2">
<p class="mb0">
{{ errorMessage }}
</p>
<p>
<a href="" @click.prevent="startRegister()">
{{ $t('app.retry') }}
</a>
</p>
</div>
</div>
<div v-if="infoMessage !== ''" class="form-information-message mb3">
<div class="pa2">
<p class="mb0">
{{ infoMessage }}
</p>
</div>
</div>
<div v-if="errorMessage === ''" class="tc">
<img src="https://ssl.gstatic.com/accounts/strongauth/Challenge_2SV-Gnubby_graphic.png"
:alt="$t('settings.webauthn_insertKey')"
/>
</div>
<div v-if="errorMessage === ''" class="pa2">
<p>
{{ $t('settings.webauthn_insertKey') }}
</p>
<p>
{{ $t('settings.webauthn_buttonAdvise') }}
<br />
{{ $t('settings.webauthn_noButtonAdvise') }}
</p>
</div>
</div>
<div slot="button">
<a v-if="registerTab === '1'" class="btn" href="" @click.prevent="showRegisterModalTab('2');startRegister();">
{{ $t('pagination.next') }}
</a>
<a v-else class="btn" href="" @click.prevent="showRegisterModalTab('1')">
{{ $t('pagination.previous') }}
</a>
<a class="btn" href="" @click.prevent="closeRegisterModal()">
{{ $t('app.cancel') }}
</a>
</div>
</sweet-modal>
</div>
<div v-else>
<div v-if="errorMessage !== ''" class="form-error-message mb3">
<div class="pa2">
<p class="mb0">
{{ errorMessage }}
</p>
<p>
<a href="" @click.prevent="start()">
{{ $t('app.retry') }}
</a>
</p>
</div>
</div>
<div v-if="infoMessage !== ''" class="form-information-message mb3">
<div class="pa2">
<p class="mb0">
{{ infoMessage }}
</p>
</div>
</div>
<div class="tc">
<img src="https://ssl.gstatic.com/accounts/strongauth/Challenge_2SV-Gnubby_graphic.png"
:alt="$t('settings.webauthn_insertKey')"
/>
</div>
<div class="pa2">
<p>
{{ $t('settings.webauthn_insertKey') }}
</p>
<p>
{{ $t('settings.webauthn_buttonAdvise') }}
<br />
{{ $t('settings.webauthn_noButtonAdvise') }}
</p>
</div>
</div>
<sweet-modal ref="delete" overlay-theme="dark" title="Remove a key">
<form>
<div class="mb4">
{{ $t('settings.webauthn_delete_confirmation') }}
</div>
</form>
<div slot="button">
<a class="btn" href="" @click.prevent="closeDeleteModal()">
{{ $t('app.cancel') }}
</a>
<a class="btn" href="" @click.prevent="webauthnRemove(keyToTrash)">
{{ $t('app.delete') }}
</a>
</div>
</sweet-modal>
</div>
</template>
<script>
import { SweetModal } from 'sweet-modal-vue';
import * as WebAuthn from '../../../../vendor/asbiin/laravel-webauthn/resources/js/webauthn.js';
export default {
components: {
SweetModal
},
props: {
keys: {
type: Array,
default: function () {
return [];
}
},
publicKey: {
type: Object,
default: null,
},
method: {
type: String,
default: '',
},
timezone: {
type: String,
default: '',
},
script: {
type: String,
default: '',
},
},
data() {
return {
isSupported: true,
errorMessage: '',
infoMessage: '',
success: false,
currentkeys: [],
keyToTrash: '',
keyName: '',
registerTab: '',
data: null,
webauthn: null,
};
},
mounted() {
this.prepareComponent();
this.start();
},
methods: {
prepareComponent() {
this.currentkeys = this.keys;
this.data = this.registerdata;
this.webauthn = new WebAuthn((name, message) => {
this.errorMessage = this._errorMessage(name, message);
});
},
_errorMessage(name, message) {
switch (name) {
case 'InvalidStateError':
return this.$t('settings.webauthn_error_already_used');
case 'NotAllowedError':
return this.$t('settings.webauthn_error_not_allowed');
default:
return message;
}
},
notSupportedMessage() {
return this.$t('settings.webauthn_'+this.webauthn.notSupportedMessage());
},
start() {
var self = this;
this.errorMessage = '';
if (! this.webauthn.webAuthnSupport()) {
this.isSupported = false;
this.errorMessage = this.notSupportedMessage();
}
switch(this.method) {
case 'register':
setTimeout(function () {
self.webauthn.register(
self.publicKey,
function (datas) { self.webauthnRegisterCallback(datas, true); }
);
}, 10);
break;
case 'login':
this.webauthn.sign(
this.publicKey,
function (data) { self.webauthnLoginCallback(data); }
);
break;
}
},
showRegisterModal() {
this.errorMessage = '';
this.infoMessage = '';
this.keyName = '';
this.success = false;
this.showRegisterModalTab('1');
this.$refs.registerModal.open();
},
showRegisterModalTab(tab) {
this.registerTab = tab;
},
startRegister() {
var self = this;
this.errorMessage = '';
axios.post('webauthn/keys/options')
.then(response => {
if (self.registerTab === '2') {
var data = response.data.publicKey;
setTimeout(function () {
self.webauthn.register(
data,
function (datas) { self.webauthnRegisterCallback(datas, false); }
);
}, 10);
}
}).catch(error => {
self.notify(error.response.data.message, false);
});
},
closeRegisterModal() {
this.$refs.registerModal.close();
this.showRegisterModalTab('');
},
webauthnRegisterCallback(data, redirect) {
var self = this;
axios.post('webauthn/keys', {
...data,
name: self.keyName,
}).then(response => {
self.success = true;
self.notify(self.$t('settings.webauthn_success'), true);
self.currentkeys.push({
id: response.result.id,
name: response.result.name,
});
}).then(response => {
if (redirect) {
setTimeout(function () {
window.location = response.data.callback;
}, 100);
} else {
self.closeRegisterModal();
}
}).catch(error => {
self.errorMessage = error.message ? error.message : error.response.data.message;
});
},
webauthnLoginCallback(data) {
var self = this;
axios.post('webauthn/auth', {
...data
}).then(response => {
self.success = true;
self.notify(self.$t('settings.webauthn_success'), true);
window.location = response.data.callback;
}).catch(error => {
self.errorMessage = error.message ? error.message : error.response.data.message;
});
},
webauthnRemove(id) {
var self = this;
axios.delete('webauthn/keys/'+id)
.then(response => {
self.currentkeys.splice(self.currentkeys.indexOf(self.currentkeys.find(item => item.id === response.data.id)), 1);
self.success = true;
self.notify(self.$t('settings.webauthn_delete_success'), true);
self.closeDeleteModal();
}).catch(error => {
self.errorMessage = error.response.data.message;
});
},
showDeleteModal(id) {
this.keyToTrash = id;
this.$refs.delete.open();
},
closeDeleteModal() {
this.$refs.delete.close();
},
formatTime(value) {
var moment = require('moment-timezone');
moment.locale(this._i18n.locale);
moment.tz.setDefault('UTC');
var t = moment(value);
var date = moment.tz(t, this.timezone);
return date.format('LLLL');
},
notify(text, success) {
this.$notify({
group: 'webauthn',
title: text,
text: '',
type: success ? 'success' : 'error'
});
}
}
};
</script>