forked from verdnatura/hedera-web
Merge pull request 'Account config and change password form' (!73) from wbuezas/hedera-web-mindshore:feature/AccountConfig into 4922-vueMigration
Reviewed-on: verdnatura/hedera-web#73 Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
This commit is contained in:
commit
24687e57e6
|
@ -2,7 +2,9 @@ import { boot } from 'quasar/wrappers';
|
||||||
import { Connection } from '../js/db/connection';
|
import { Connection } from '../js/db/connection';
|
||||||
import { userStore } from 'stores/user';
|
import { userStore } from 'stores/user';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
|
const { notify } = useNotify();
|
||||||
// Be careful when using SSR for cross-request state pollution
|
// Be careful when using SSR for cross-request state pollution
|
||||||
// due to creating a Singleton instance here;
|
// due to creating a Singleton instance here;
|
||||||
// If any client changes this (global) instance, it might be a
|
// If any client changes this (global) instance, it might be a
|
||||||
|
@ -12,9 +14,27 @@ import axios from 'axios';
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: `//${location.hostname}:${location.port}/api/`
|
baseURL: `//${location.hostname}:${location.port}/api/`
|
||||||
});
|
});
|
||||||
|
|
||||||
const jApi = new Connection();
|
const jApi = new Connection();
|
||||||
|
|
||||||
|
const onRequestError = error => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResponseError = error => {
|
||||||
|
let message = '';
|
||||||
|
|
||||||
|
const response = error.response;
|
||||||
|
const responseData = response && response.data;
|
||||||
|
const responseError = responseData && response.data.error;
|
||||||
|
if (responseError) {
|
||||||
|
message = responseError.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(message, 'negative');
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
const user = userStore();
|
const user = userStore();
|
||||||
function addToken(config) {
|
function addToken(config) {
|
||||||
|
@ -23,7 +43,9 @@ export default boot(({ app }) => {
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
api.interceptors.request.use(addToken);
|
api.interceptors.request.use(addToken, onRequestError);
|
||||||
|
api.interceptors.response.use(response => response, onResponseError);
|
||||||
|
|
||||||
jApi.use(addToken);
|
jApi.use(addToken);
|
||||||
|
|
||||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, inject, onMounted, computed } from 'vue';
|
import { ref, inject, onMounted, computed, Teleport } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
@ -65,6 +65,14 @@ const props = defineProps({
|
||||||
defaultActions: {
|
defaultActions: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
showBottomActions: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
saveFn: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,6 +99,8 @@ const updatedColumns = computed(() => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasChanges = computed(() => !!updatedColumns.value.length);
|
||||||
|
|
||||||
const fetchFormData = async () => {
|
const fetchFormData = async () => {
|
||||||
if (!props.fetchFormDataSql.query) return;
|
if (!props.fetchFormDataSql.query) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
@ -113,15 +123,26 @@ const fetchFormData = async () => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSubmitSuccess = () => {
|
||||||
|
emit('onDataSaved');
|
||||||
|
notify(t('dataSaved'), 'positive');
|
||||||
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
try {
|
try {
|
||||||
const sqlQuery = generateSqlQuery();
|
if (props.saveFn) {
|
||||||
await jApi.execQuery(sqlQuery, props.pks);
|
await props.saveFn(formData.value);
|
||||||
emit('onDataSaved');
|
} else {
|
||||||
notify(t('dataSaved'), 'positive');
|
if (!hasChanges.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sqlQuery = generateSqlQuery();
|
||||||
|
await jApi.execQuery(sqlQuery, props.pks);
|
||||||
|
modelInfo.value.data[0] = { ...formData.value };
|
||||||
|
}
|
||||||
|
onSubmitSuccess();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating address:', error);
|
console.error('Error:', error);
|
||||||
notify(t('addressNotUpdated'), 'negative');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -158,19 +179,7 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Teleport :to="$actions">
|
<QCard class="form-container" v-bind="$attrs">
|
||||||
<QBtn
|
|
||||||
v-if="defaultActions"
|
|
||||||
:label="t('save')"
|
|
||||||
type="submit"
|
|
||||||
icon="check"
|
|
||||||
rounded
|
|
||||||
no-caps
|
|
||||||
:disabled="!updatedColumns.length"
|
|
||||||
@click="addressFormRef.submit()"
|
|
||||||
/>
|
|
||||||
</Teleport>
|
|
||||||
<QCard class="form-container">
|
|
||||||
<QForm
|
<QForm
|
||||||
v-if="!loading"
|
v-if="!loading"
|
||||||
ref="addressFormRef"
|
ref="addressFormRef"
|
||||||
|
@ -181,6 +190,33 @@ defineExpose({
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</span>
|
</span>
|
||||||
<slot name="form" :data="formData" />
|
<slot name="form" :data="formData" />
|
||||||
|
<component
|
||||||
|
:is="showBottomActions ? 'div' : Teleport"
|
||||||
|
:to="$actions"
|
||||||
|
class="flex row justify-end q-gutter-x-sm"
|
||||||
|
:class="{ 'q-mt-md': showBottomActions }"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
v-if="defaultActions"
|
||||||
|
:label="t('cancel')"
|
||||||
|
:icon="showBottomActions ? undefined : 'check'"
|
||||||
|
rounded
|
||||||
|
no-caps
|
||||||
|
flat
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
v-if="defaultActions"
|
||||||
|
:label="t('save')"
|
||||||
|
type="submit"
|
||||||
|
:icon="showBottomActions ? undefined : 'check'"
|
||||||
|
rounded
|
||||||
|
no-caps
|
||||||
|
flat
|
||||||
|
:disabled="!showBottomActions && !updatedColumns.length"
|
||||||
|
/>
|
||||||
|
<slot name="actions" />
|
||||||
|
</component>
|
||||||
</QForm>
|
</QForm>
|
||||||
<QSpinner v-else color="primary" size="3em" :thickness="2" />
|
<QSpinner v-else color="primary" size="3em" :thickness="2" />
|
||||||
</QCard>
|
</QCard>
|
||||||
|
|
|
@ -0,0 +1,273 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, inject, onMounted, nextTick } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnForm from 'src/components/common/VnForm.vue';
|
||||||
|
|
||||||
|
import { userStore as useUserStore } from 'stores/user';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
verificationToken: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['onPasswordChanged']);
|
||||||
|
const showOldPwd = ref(false);
|
||||||
|
const showNewPwd = ref(false);
|
||||||
|
const showCopyPwd = ref(false);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const api = inject('api');
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const oldPasswordRef = ref(null);
|
||||||
|
const newPasswordRef = ref(null);
|
||||||
|
const passwordRequirementsDialogRef = ref(null);
|
||||||
|
const vnFormRef = ref(null);
|
||||||
|
const repeatPassword = ref('');
|
||||||
|
const passwordRequirements = ref(null);
|
||||||
|
|
||||||
|
const formData = ref({
|
||||||
|
userId: userStore.id,
|
||||||
|
oldPassword: '',
|
||||||
|
newPassword: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const changePassword = async () => {
|
||||||
|
if (!formData.value.newPassword || !repeatPassword.value) {
|
||||||
|
notify(t('passwordEmpty'), 'negative');
|
||||||
|
throw new Error('Password empty');
|
||||||
|
}
|
||||||
|
if (formData.value.newPassword !== repeatPassword.value) {
|
||||||
|
notify(t('passwordsDoNotMatch'), 'negative');
|
||||||
|
throw new Error('Passwords do not match');
|
||||||
|
}
|
||||||
|
if (props.verificationToken) {
|
||||||
|
await changePasswordWithToken();
|
||||||
|
} else {
|
||||||
|
await changePasswordWithoutToken();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changePasswordWithToken = async () => {
|
||||||
|
const headers = {
|
||||||
|
Authorization: props.verificationToken
|
||||||
|
};
|
||||||
|
await api.post('VnUsers/reset-password', formData.value, { headers });
|
||||||
|
};
|
||||||
|
|
||||||
|
const changePasswordWithoutToken = async () => {
|
||||||
|
await api.patch('Accounts/change-password', formData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPasswordRequirements = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('UserPasswords/findOne');
|
||||||
|
passwordRequirements.value = data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
await userStore.login(userStore.name, formData.value.newPassword);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPasswordChanged = async () => {
|
||||||
|
await login();
|
||||||
|
emit('onPasswordChanged');
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
getPasswordRequirements();
|
||||||
|
await nextTick();
|
||||||
|
if (props.verificationToken) {
|
||||||
|
newPasswordRef.value.focus();
|
||||||
|
} else {
|
||||||
|
oldPasswordRef.value.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VnForm
|
||||||
|
ref="vnFormRef"
|
||||||
|
:title="t('changePassword')"
|
||||||
|
:formInitialData="formData"
|
||||||
|
:saveFn="changePassword"
|
||||||
|
showBottomActions
|
||||||
|
:defaultActions="false"
|
||||||
|
style="max-width: 300px"
|
||||||
|
@onDataSaved="onPasswordChanged()"
|
||||||
|
>
|
||||||
|
<template #form>
|
||||||
|
<VnInput
|
||||||
|
v-if="!verificationToken"
|
||||||
|
ref="oldPasswordRef"
|
||||||
|
v-model="formData.oldPassword"
|
||||||
|
:type="!showOldPwd ? 'password' : 'text'"
|
||||||
|
:label="t('oldPassword')"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon
|
||||||
|
:name="showOldPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="showOldPwd = !showOldPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VnInput>
|
||||||
|
<VnInput
|
||||||
|
ref="newPasswordRef"
|
||||||
|
v-model="formData.newPassword"
|
||||||
|
:type="!showNewPwd ? 'password' : 'text'"
|
||||||
|
:label="t('newPassword')"
|
||||||
|
><template #append>
|
||||||
|
<QIcon
|
||||||
|
:name="showNewPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="showNewPwd = !showNewPwd"
|
||||||
|
/>
|
||||||
|
</template></VnInput>
|
||||||
|
<VnInput
|
||||||
|
v-model="repeatPassword"
|
||||||
|
:type="!showCopyPwd ? 'password' : 'text'"
|
||||||
|
:label="t('repeatPassword')"
|
||||||
|
><template #append>
|
||||||
|
<QIcon
|
||||||
|
:name="showCopyPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="showCopyPwd = !showCopyPwd"
|
||||||
|
/>
|
||||||
|
</template></VnInput>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<QBtn
|
||||||
|
:label="t('requirements')"
|
||||||
|
rounded
|
||||||
|
no-caps
|
||||||
|
flat
|
||||||
|
@click="passwordRequirementsDialogRef.show()"
|
||||||
|
/>
|
||||||
|
<QBtn :label="t('modify')" type="submit" rounded no-caps flat />
|
||||||
|
</template>
|
||||||
|
</VnForm>
|
||||||
|
<QDialog ref="passwordRequirementsDialogRef">
|
||||||
|
<QCard class="q-px-md q-py-lg column items-center">
|
||||||
|
<span class="text-h6 text-bold q-mb-md">
|
||||||
|
{{ t('passwordRequirements') }}
|
||||||
|
</span>
|
||||||
|
<div class="column" style="max-width: max-content">
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
t('charactersLong', {
|
||||||
|
length: passwordRequirements.length
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
t('alphabeticCharacters', {
|
||||||
|
nAlpha: passwordRequirements.nAlpha
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
t('capitalLetters', {
|
||||||
|
nUpper: passwordRequirements.nUpper
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ t('digits', { nDigits: passwordRequirements.nDigits }) }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ t('symbols', { nPunct: passwordRequirements.nPunct }) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</QCard>
|
||||||
|
</QDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n lang="yaml">
|
||||||
|
en-US:
|
||||||
|
changePassword: Change password
|
||||||
|
newPassword: New password
|
||||||
|
oldPassword: Old password
|
||||||
|
repeatPassword: Repeat password
|
||||||
|
modify: Modify
|
||||||
|
requirements: Requirements
|
||||||
|
passwordRequirements: Password requirements
|
||||||
|
charactersLong: '{length} characters long'
|
||||||
|
alphabeticCharacters: '{nAlpha} alphabetic characters'
|
||||||
|
capitalLetters: '{nUpper} capital letters'
|
||||||
|
digits: '{nDigits} digits'
|
||||||
|
symbols: '{nPunct} symbols. Ej: $%&.'
|
||||||
|
passwordsDoNotMatch: Passwords do not match
|
||||||
|
passwordEmpty: Password empty
|
||||||
|
es-ES:
|
||||||
|
changePassword: Cambiar contraseña
|
||||||
|
newPassword: Nueva contraseña
|
||||||
|
oldPassword: Contraseña antigua
|
||||||
|
repeatPassword: Repetir contraseña
|
||||||
|
modify: Modificar
|
||||||
|
requirements: Requisitos
|
||||||
|
passwordRequirements: Requisitos de contraseña
|
||||||
|
charactersLong: '{length} caracteres de longitud'
|
||||||
|
alphabeticCharacters: '{nAlpha} caracteres alfabéticos'
|
||||||
|
capitalLetters: '{nUpper} letras mayúsculas'
|
||||||
|
digits: '{nDigits} dígitos'
|
||||||
|
symbols: '{nPunct} símbolos. Ej: $%&.'
|
||||||
|
passwordsDoNotMatch: ¡Las contraseñas no coinciden!
|
||||||
|
passwordEmpty: Contraseña vacía
|
||||||
|
ca-ES:
|
||||||
|
changePassword: Canviar contrasenya
|
||||||
|
newPassword: Nova contrasenya
|
||||||
|
oldPassword: Contrasenya antiga
|
||||||
|
repeatPassword: Repetir contrasenya
|
||||||
|
modify: Modificar
|
||||||
|
requirements: Requisits
|
||||||
|
passwordRequirements: Requisits de contrasenya
|
||||||
|
charactersLong: '{length} caràcters de longitud'
|
||||||
|
alphabeticCharacters: '{nAlpha} caràcters alfabètics'
|
||||||
|
capitalLetters: '{nUpper} lletres majúscules'
|
||||||
|
digits: '{nDigits} dígits'
|
||||||
|
symbols: '{nPunct} símbols. Ej: $%&.'
|
||||||
|
passwordsDoNotMatch: Les contrasenyes no coincideixen!
|
||||||
|
passwordEmpty: Contrasenya buida
|
||||||
|
fr-FR:
|
||||||
|
changePassword: Changer le mot de passe
|
||||||
|
newPassword: Nouveau mot de passe
|
||||||
|
oldPassword: Ancien mot de passe
|
||||||
|
repeatPassword: Répéter le mot de passe
|
||||||
|
modify: Modifier
|
||||||
|
requirements: Exigences
|
||||||
|
passwordRequirements: Mot de passe exigences
|
||||||
|
charactersLong: '{length} caractères de longueur'
|
||||||
|
alphabeticCharacters: '{nAlpha} caractères alphabétiques'
|
||||||
|
capitalLetters: '{nUpper} lettres majuscules'
|
||||||
|
digits: '{nDigits} chiffres'
|
||||||
|
symbols: '{nPunct} symboles. Ej: $%&.'
|
||||||
|
passwordsDoNotMatch: Les mots de passe ne correspondent pas!
|
||||||
|
passwordEmpty: Mots de passe vides
|
||||||
|
pt-PT:
|
||||||
|
changePassword: Alterar palavra-passe
|
||||||
|
newPassword: Nova palavra-passe
|
||||||
|
oldPassword: Palavra-passe antiga
|
||||||
|
repeatPassword: Repetir palavra-passe
|
||||||
|
modify: Modificar
|
||||||
|
requirements: Requisitos
|
||||||
|
passwordRequirements: Requisitos de palavra-passe
|
||||||
|
charactersLong: '{length} caracteres de comprimento'
|
||||||
|
alphabeticCharacters: '{nAlpha} caracteres alfabéticos'
|
||||||
|
capitalLetters: '{nUpper} letras maiúsculas'
|
||||||
|
digits: '{nDigits} dígitos'
|
||||||
|
symbols: '{nPunct} símbolos. Ej: $%&.'
|
||||||
|
passwordsDoNotMatch: As palavras-passe não coincidem!
|
||||||
|
passwordEmpty: Palavra-passe vazia
|
||||||
|
</i18n>
|
|
@ -1,8 +1,8 @@
|
||||||
// app global css in SCSS form
|
// app global css in SCSS form
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Poppins;
|
font-family: Poppins;
|
||||||
src: url(./poppins.ttf) format('truetype');
|
src: url(./poppins.ttf) format('truetype');
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
|
|
|
@ -75,5 +75,6 @@ export default {
|
||||||
addresses: 'Addresses',
|
addresses: 'Addresses',
|
||||||
addressEdit: 'Edit address',
|
addressEdit: 'Edit address',
|
||||||
dataSaved: 'Data saved',
|
dataSaved: 'Data saved',
|
||||||
save: 'Save'
|
save: 'Save',
|
||||||
|
cancel: 'Cancel'
|
||||||
};
|
};
|
||||||
|
|
|
@ -93,5 +93,6 @@ export default {
|
||||||
addresses: 'Direcciones',
|
addresses: 'Direcciones',
|
||||||
addressEdit: 'Editar dirección',
|
addressEdit: 'Editar dirección',
|
||||||
dataSaved: 'Datos guardados',
|
dataSaved: 'Datos guardados',
|
||||||
save: 'Guardar'
|
save: 'Guardar',
|
||||||
|
cancel: 'Cancelar'
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
const sanitizeValue = value => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return `'${value}'`;
|
||||||
|
} else if (value === null) {
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
export const generateUpdateSqlQuery = (
|
export const generateUpdateSqlQuery = (
|
||||||
schema,
|
schema,
|
||||||
table,
|
table,
|
||||||
|
@ -6,7 +16,7 @@ export const generateUpdateSqlQuery = (
|
||||||
formData
|
formData
|
||||||
) => {
|
) => {
|
||||||
const setClauses = columnsUpdated
|
const setClauses = columnsUpdated
|
||||||
.map(colName => `${colName} = '${formData[colName]}'`)
|
.map(colName => `${colName} = ${sanitizeValue(formData[colName])}`)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
const whereClause = Object.keys(pks)
|
const whereClause = Object.keys(pks)
|
||||||
.map(pk => `${pk} = ${pks[pk]}`)
|
.map(pk => `${pk} = ${pks[pk]}`)
|
||||||
|
@ -30,7 +40,7 @@ export const generateInsertSqlQuery = (
|
||||||
const columns = [createModelDefault.field, ...columnsUpdated].join(', ');
|
const columns = [createModelDefault.field, ...columnsUpdated].join(', ');
|
||||||
const values = [
|
const values = [
|
||||||
createModelDefault.value,
|
createModelDefault.value,
|
||||||
...columnsUpdated.map(colName => `'${formData[colName]}'`)
|
...columnsUpdated.map(colName => sanitizeValue(formData[colName]))
|
||||||
].join(', ');
|
].join(', ');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
<script setup></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<QPage> // TODO: VISTA A DESARROLLAR! </QPage>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|
||||||
<i18n lang="yaml"></i18n>
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, inject, onMounted, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
import VnForm from 'src/components/common/VnForm.vue';
|
||||||
|
import ChangePasswordForm from 'src/components/ui/ChangePasswordForm.vue';
|
||||||
|
|
||||||
|
import { userStore as useUserStore } from 'stores/user';
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const jApi = inject('jApi');
|
||||||
|
|
||||||
|
const vnFormRef = ref(null);
|
||||||
|
const changePasswordFormDialog = ref(null);
|
||||||
|
const showChangePasswordForm = ref(false);
|
||||||
|
const langOptions = ref([]);
|
||||||
|
const pks = computed(() => ({ id: userStore.id }));
|
||||||
|
const fetchConfigDataSql = {
|
||||||
|
query: `
|
||||||
|
SELECT u.id, u.name, u.email, u.nickname,
|
||||||
|
u.lang, c.isToBeMailed, c.id clientFk
|
||||||
|
FROM account.myUser u
|
||||||
|
LEFT JOIN myClient c
|
||||||
|
ON u.id = c.id`,
|
||||||
|
params: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchLanguagesSql = async () => {
|
||||||
|
try {
|
||||||
|
const data = await jApi.query(
|
||||||
|
'SELECT code, name FROM language WHERE isActive'
|
||||||
|
);
|
||||||
|
langOptions.value = data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => fetchLanguagesSql());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QPage>
|
||||||
|
<QPage class="q-pa-md flex justify-center">
|
||||||
|
<Teleport :to="$actions">
|
||||||
|
<QBtn
|
||||||
|
:label="t('addresses')"
|
||||||
|
icon="location_on"
|
||||||
|
rounded
|
||||||
|
no-caps
|
||||||
|
:to="{ name: 'AddressesList' }"
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
:label="t('changePassword')"
|
||||||
|
icon="lock_reset"
|
||||||
|
rounded
|
||||||
|
no-caps
|
||||||
|
@click="showChangePasswordForm = true"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<VnForm
|
||||||
|
ref="vnFormRef"
|
||||||
|
:title="t('personalInformation')"
|
||||||
|
:fetchFormDataSql="fetchConfigDataSql"
|
||||||
|
:pks="pks"
|
||||||
|
table="myUser"
|
||||||
|
schema="account"
|
||||||
|
:defaultActions="false"
|
||||||
|
>
|
||||||
|
<template #form="{ data }">
|
||||||
|
<VnInput
|
||||||
|
v-model="data.name"
|
||||||
|
:label="t('name')"
|
||||||
|
disable
|
||||||
|
:clearable="false"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
v-model="data.email"
|
||||||
|
:label="t('email')"
|
||||||
|
@keyup.enter="vnFormRef.submit()"
|
||||||
|
@blur="vnFormRef.submit()"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
v-model="data.nickname"
|
||||||
|
:label="t('nickname')"
|
||||||
|
@keyup.enter="vnFormRef.submit()"
|
||||||
|
@blur="vnFormRef.submit()"
|
||||||
|
/>
|
||||||
|
<VnSelect
|
||||||
|
v-model="data.lang"
|
||||||
|
:label="t('lang')"
|
||||||
|
option-label="name"
|
||||||
|
option-value="code"
|
||||||
|
:options="langOptions"
|
||||||
|
@update:modelValue="vnFormRef.submit()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VnForm>
|
||||||
|
</QPage>
|
||||||
|
<QDialog
|
||||||
|
ref="changePasswordFormDialog"
|
||||||
|
v-model="showChangePasswordForm"
|
||||||
|
>
|
||||||
|
<ChangePasswordForm
|
||||||
|
@on-password-changed="showChangePasswordForm = false"
|
||||||
|
/>
|
||||||
|
</QDialog>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n lang="yaml">
|
||||||
|
en-US:
|
||||||
|
personalInformation: Personal Information
|
||||||
|
name: Name
|
||||||
|
email: Email
|
||||||
|
nickname: Display name
|
||||||
|
lang: Language
|
||||||
|
receiveInvoicesByMail: Receive invoices by mail
|
||||||
|
addresses: Addresses
|
||||||
|
changePassword: Change password
|
||||||
|
es-ES:
|
||||||
|
personalInformation: Datos personales
|
||||||
|
name: Nombre
|
||||||
|
email: Correo electrónico
|
||||||
|
nickname: Nombre a mostrar
|
||||||
|
lang: Idioma
|
||||||
|
receiveInvoicesByMail: Recibir facturas por correo
|
||||||
|
addresses: Direcciones
|
||||||
|
changePassword: Cambiar contraseña
|
||||||
|
ca-ES:
|
||||||
|
personalInformation: Dades personals
|
||||||
|
name: Nom
|
||||||
|
email: Correu electrònic
|
||||||
|
nickname: Nom a mostrar
|
||||||
|
lang: Idioma
|
||||||
|
receiveInvoicesByMail: Rebre factures per correu
|
||||||
|
addresses: Adreces
|
||||||
|
changePassword: Canviar contrasenya
|
||||||
|
fr-FR:
|
||||||
|
personalInformation: Informations personnelles
|
||||||
|
name: Nom
|
||||||
|
email: E-mail
|
||||||
|
nickname: Nom à afficher
|
||||||
|
lang: Langue
|
||||||
|
receiveInvoicesByMail: Recevoir des factures par courrier
|
||||||
|
addresses: Adresses
|
||||||
|
changePassword: Changer le mot de passe
|
||||||
|
pt-PT:
|
||||||
|
personalInformation: Dados pessoais
|
||||||
|
name: Nome
|
||||||
|
email: E-mail
|
||||||
|
nickname: Nom à afficher
|
||||||
|
lang: Língua
|
||||||
|
receiveInvoicesByMail: Receber faturas por correio
|
||||||
|
addresses: Endereços
|
||||||
|
changePassword: Alterar palavra-passe
|
||||||
|
</i18n>
|
|
@ -4,7 +4,7 @@ const routes = [
|
||||||
component: () => import('layouts/LoginLayout.vue'),
|
component: () => import('layouts/LoginLayout.vue'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'login',
|
name: 'Login',
|
||||||
path: '/login/:email?',
|
path: '/login/:email?',
|
||||||
component: () => import('pages/Login/LoginView.vue')
|
component: () => import('pages/Login/LoginView.vue')
|
||||||
},
|
},
|
||||||
|
@ -62,7 +62,7 @@ const routes = [
|
||||||
{
|
{
|
||||||
name: 'Account',
|
name: 'Account',
|
||||||
path: '/account/conf',
|
path: '/account/conf',
|
||||||
component: () => import('pages/Account/AccountConf.vue')
|
component: () => import('pages/Account/AccountConfig.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'AddressesList',
|
name: 'AddressesList',
|
||||||
|
|
|
@ -50,12 +50,13 @@ export const userStore = defineStore('user', {
|
||||||
|
|
||||||
async loadData() {
|
async loadData() {
|
||||||
const userData = await jApi.getObject(
|
const userData = await jApi.getObject(
|
||||||
'SELECT id, nickname FROM account.myUser'
|
'SELECT id, nickname, name FROM account.myUser'
|
||||||
);
|
);
|
||||||
|
|
||||||
this.$patch({
|
this.$patch({
|
||||||
id: userData.id,
|
id: userData.id,
|
||||||
nickname: userData.nickname
|
nickname: userData.nickname,
|
||||||
|
name: userData.name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue