Merge pull request 'Renew token' (!89) from wbuezas/hedera-web-mindshore:feature/RenewToken into 4922-vueMigration

Reviewed-on: #89
This commit is contained in:
Javier Segarra 2024-10-14 11:15:56 +00:00
commit 8021a171f8
16 changed files with 438 additions and 202 deletions

View File

@ -2,11 +2,14 @@
import { useAppStore } from 'stores/app'; import { useAppStore } from 'stores/app';
import { useUserStore } from 'stores/user'; import { useUserStore } from 'stores/user';
import { onBeforeMount } from 'vue'; import { onBeforeMount } from 'vue';
import { useRouter } from 'vue-router';
const appStore = useAppStore(); const appStore = useAppStore();
const userStore = useUserStore(); const userStore = useUserStore();
const router = useRouter();
onBeforeMount(async () => { onBeforeMount(async () => {
await userStore.init(); await userStore.init(router);
await appStore.init(); await appStore.init();
}); });
</script> </script>

View File

@ -5,8 +5,7 @@ import { useQuasar } from 'quasar';
export function usePrintService() { export function usePrintService() {
const quasar = useQuasar(); const quasar = useQuasar();
const userStore = useUserStore(); const { getTokenMultimedia } = useUserStore();
const token = userStore.token;
function sendEmail(path, params) { function sendEmail(path, params) {
return axios.post(path, params).then(() => return axios.post(path, params).then(() =>
@ -18,17 +17,17 @@ export function usePrintService() {
); );
} }
function openReport(path, params) { function openReport(path, params, isNewTab = '_self') {
if (typeof params === 'string') params = JSON.parse(params);
params = Object.assign( params = Object.assign(
{ {
access_token: token access_token: getTokenMultimedia()
}, },
params params
); );
const query = new URLSearchParams(params).toString(); const query = new URLSearchParams(params).toString();
window.open(`api/${path}?${query}`, isNewTab);
window.open(`api/${path}?${query}`);
} }
return { return {

View File

@ -73,7 +73,7 @@ export default {
Reports: 'Informes', Reports: 'Informes',
Configuration: 'Configuració', Configuration: 'Configuració',
Shelves: 'Prestatgeries', Shelves: 'Prestatgeries',
Account: 'Compte', Account: 'Configuració',
Addresses: 'Adreces', Addresses: 'Adreces',
OrderSummary: 'Resum de la comanda', OrderSummary: 'Resum de la comanda',
Checkout: `Configurar l'encarrec`, Checkout: `Configurar l'encarrec`,
@ -162,5 +162,10 @@ export default {
ErrCantWrite: "No s'ha pogut escriure el fitxer al disc", ErrCantWrite: "No s'ha pogut escriure el fitxer al disc",
ErrExtension: "La pujada del fitxer s'ha aturat per una extensió", ErrExtension: "La pujada del fitxer s'ha aturat per una extensió",
ErrDefault: 'Error de pujada desconegut', ErrDefault: 'Error de pujada desconegut',
'Sync complete': 'Sincronització completa' 'Sync complete': 'Sincronització completa',
// Errors
errors: {
statusUnauthorized: 'Accés denegat',
tokenConfig: 'Error al obtenir la configuració del token'
}
}; };

View File

@ -85,7 +85,7 @@ export default {
Reports: 'Reports', Reports: 'Reports',
Configuration: 'Configuration', Configuration: 'Configuration',
Shelves: 'Shelves', Shelves: 'Shelves',
Account: 'Account', Account: 'Configuration',
Addresses: 'Addresses', Addresses: 'Addresses',
OrderSummary: 'Order summary', OrderSummary: 'Order summary',
Checkout: 'Configure order', Checkout: 'Configure order',
@ -195,5 +195,10 @@ export default {
ErrCantWrite: 'Failed to write file to disk', ErrCantWrite: 'Failed to write file to disk',
ErrExtension: 'File upload stopped by extension', ErrExtension: 'File upload stopped by extension',
ErrDefault: 'Unknown upload error', ErrDefault: 'Unknown upload error',
'Sync complete': 'Synchronization complete' 'Sync complete': 'Synchronization complete',
// Errors
errors: {
statusUnauthorized: 'Access denied',
tokenConfig: 'Error fetching token config'
}
}; };

View File

@ -67,11 +67,11 @@ export default {
'Pending orders': 'Pedidos pendientes', 'Pending orders': 'Pedidos pendientes',
'Last orders': 'Pedidos confirmados', 'Last orders': 'Pedidos confirmados',
Invoices: 'Facturas', Invoices: 'Facturas',
Basket: 'Cesta', Basket: 'Cesta de la compra',
Catalog: 'Catálogo', Catalog: 'Catálogo',
Administration: 'Administración', Administration: 'Administración',
'Control panel': 'Panel de control', 'Control panel': 'Panel de control',
Users: 'Usuarios', Users: 'Gestión de usuarios',
Connections: 'Conexiones', Connections: 'Conexiones',
Visits: 'Visitas', Visits: 'Visitas',
News: 'Gestión de noticias', News: 'Gestión de noticias',
@ -82,7 +82,7 @@ export default {
Reports: 'Informes', Reports: 'Informes',
Configuration: 'Configuración', Configuration: 'Configuración',
Shelves: 'Estanterías', Shelves: 'Estanterías',
Account: 'Cuenta', Account: 'Configuración',
Addresses: 'Direcciones', Addresses: 'Direcciones',
OrderSummary: 'Resumen del pedido', OrderSummary: 'Resumen del pedido',
Checkout: 'Configurar pedido', Checkout: 'Configurar pedido',
@ -108,7 +108,7 @@ export default {
Connections: 'Conexiones', Connections: 'Conexiones',
Visits: 'Visitas', Visits: 'Visitas',
News: 'Noticias', News: 'Noticias',
Photos: 'Imágenes', Photos: 'Fotos',
Items: 'Artículos', Items: 'Artículos',
Account: 'Cuenta', Account: 'Cuenta',
Addresses: 'Direcciones' Addresses: 'Direcciones'
@ -194,5 +194,10 @@ export default {
ErrCantWrite: 'Failed to write file to disk', ErrCantWrite: 'Failed to write file to disk',
ErrExtension: 'File upload stopped by extension', ErrExtension: 'File upload stopped by extension',
ErrDefault: 'Unknown upload error', ErrDefault: 'Unknown upload error',
'Sync complete': 'Sincronización completada' 'Sync complete': 'Sincronización completada',
// Errors
errors: {
statusUnauthorized: 'Acceso denegado',
tokenConfig: 'Error al obtener configuración de token'
}
}; };

View File

@ -73,7 +73,7 @@ export default {
Reports: 'Rapports', Reports: 'Rapports',
Configuration: 'Configuration', Configuration: 'Configuration',
Shelves: 'Étagères', Shelves: 'Étagères',
Account: 'Compte', Account: 'Configuration',
Addresses: 'Adresses', Addresses: 'Adresses',
OrderSummary: 'Résumé de la commande', OrderSummary: 'Résumé de la commande',
Checkout: 'Configurer la commande', Checkout: 'Configurer la commande',
@ -166,5 +166,11 @@ export default {
ErrCantWrite: "Échec de l'écriture du fichier sur le disque", ErrCantWrite: "Échec de l'écriture du fichier sur le disque",
ErrExtension: 'Téléchargement du fichier arrêté par extension', ErrExtension: 'Téléchargement du fichier arrêté par extension',
ErrDefault: 'Erreur de téléchargement inconnue', ErrDefault: 'Erreur de téléchargement inconnue',
'Sync complete': 'Synchronisation terminée' 'Sync complete': 'Synchronisation terminée',
// Errors
errors: {
statusUnauthorized: 'Accès refusé',
tokenConfig:
'Erreur lors de la récupération de la configuration du jeton'
}
}; };

View File

@ -72,7 +72,7 @@ export default {
Reports: 'Informes', Reports: 'Informes',
Configuration: 'Configuração', Configuration: 'Configuração',
Shelves: 'Estantes', Shelves: 'Estantes',
Account: 'Conta', Account: 'Configuração',
Addresses: 'Moradas', Addresses: 'Moradas',
OrderSummary: 'Resumo da encomenda', OrderSummary: 'Resumo da encomenda',
Checkout: 'Configurar encomenda', Checkout: 'Configurar encomenda',
@ -159,5 +159,10 @@ export default {
ErrCantWrite: 'Erro ao gravar arquivo no disco', ErrCantWrite: 'Erro ao gravar arquivo no disco',
ErrExtension: 'Erro de extensão do arquivo', ErrExtension: 'Erro de extensão do arquivo',
ErrDefault: 'Erro desconhecido ao subir arquivo', ErrDefault: 'Erro desconhecido ao subir arquivo',
'Sync complete': 'Sincronização completa' 'Sync complete': 'Sincronização completa',
// Errors
errors: {
statusUnauthorized: 'Acesso negado',
tokenConfig: 'Erro ao obter configuração do token'
}
}; };

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -10,19 +10,19 @@ import { useAppStore } from 'stores/app';
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const appStore = useAppStore(); const appStore = useAppStore();
const hiddenMenuItems = new Set(['Reports']);
const refreshContentKey = ref(0); const refreshContentKey = ref(0);
const { user, supplantedUser } = storeToRefs(userStore); const { mainUser, supplantedUser } = storeToRefs(userStore);
const { menuEssentialLinks, title, subtitle, useRightDrawer, rightDrawerOpen } = const {
storeToRefs(appStore); menuTitle,
subtitle,
useRightDrawer,
rightDrawerOpen,
filteredMenuItems
} = storeToRefs(appStore);
const actions = ref(null); const actions = ref(null);
const leftDrawerOpen = ref(false); const leftDrawerOpen = ref(false);
const filteredMenuItems = computed(() =>
menuEssentialLinks.value.filter(
item => !hiddenMenuItems.has(item.description)
)
);
const toggleLeftDrawer = () => { const toggleLeftDrawer = () => {
leftDrawerOpen.value = !leftDrawerOpen.value; leftDrawerOpen.value = !leftDrawerOpen.value;
}; };
@ -58,7 +58,7 @@ const logoutSupplantedUser = async () => {
@click="toggleLeftDrawer" @click="toggleLeftDrawer"
/> />
<QToolbarTitle> <QToolbarTitle>
{{ title }} {{ menuTitle }}
<div v-if="subtitle" class="subtitle text-caption"> <div v-if="subtitle" class="subtitle text-caption">
{{ subtitle }} {{ subtitle }}
</div> </div>
@ -82,7 +82,7 @@ const logoutSupplantedUser = async () => {
</QToolbar> </QToolbar>
<div class="user-info"> <div class="user-info">
<div> <div>
<span id="user-name">{{ user?.nickname }}</span> <span id="user-name">{{ mainUser?.nickname }}</span>
<QBtn flat icon="logout" alt="_Exit" @click="logout()" /> <QBtn flat icon="logout" alt="_Exit" @click="logout()" />
</div> </div>
<div v-if="supplantedUser" id="supplant" class="supplant"> <div v-if="supplantedUser" id="supplant" class="supplant">

View File

@ -57,6 +57,15 @@ const formatMailData = data => {
data.isToBeMailed = Boolean(data.isToBeMailed); data.isToBeMailed = Boolean(data.isToBeMailed);
}; };
const updateConfigLang = async lang => {
try {
await vnFormRef.value.submit();
userStore.updateUserLang(lang);
} catch (error) {
console.error(error);
}
};
onMounted(() => fetchLanguagesSql()); onMounted(() => fetchLanguagesSql());
</script> </script>
@ -113,9 +122,7 @@ onMounted(() => fetchLanguagesSql());
option-label="name" option-label="name"
option-value="code" option-value="code"
:options="langOptions" :options="langOptions"
@update:model-value=" @update:model-value="updateConfigLang(data.lang)"
userStore.updateUserLang(data.lang)
"
/> />
</template> </template>
<template #extraForm> <template #extraForm>

View File

@ -104,21 +104,27 @@ const fetchData = async () => {
:to="{ name: 'checkout', params: { id: orderId } }" :to="{ name: 'checkout', params: { id: orderId } }"
rounded rounded
no-caps no-caps
/> >
<QTooltip>{{ t('configureOrder') }}</QTooltip>
</QBtn>
<QBtn <QBtn
icon="shopping_bag" icon="shopping_bag"
:label="t('catalog')" :label="t('catalog')"
:to="{ name: 'catalog' }" :to="{ name: 'catalog' }"
rounded rounded
no-caps no-caps
/> >
<QTooltip>{{ t('catalog') }}</QTooltip>
</QBtn>
<QBtn <QBtn
icon="shopping_cart_checkout" icon="shopping_cart_checkout"
:label="t('checkout')" :label="t('checkout')"
:to="{ name: 'confirm', params: { id: orderId } }" :to="{ name: 'confirm', params: { id: orderId } }"
rounded rounded
no-caps no-caps
/> >
<QTooltip>{{ t('checkout') }}</QTooltip>
</QBtn>
</Teleport> </Teleport>
<QPage> <QPage>
<TicketDetails <TicketDetails

View File

@ -626,7 +626,7 @@ const getItemFamilies = async () => {
JOIN vn.itemTypeL10n l ON l.id = t.id JOIN vn.itemTypeL10n l ON l.id = t.id
WHERE t.order >= 0 WHERE t.order >= 0
AND t.categoryFk = #category AND t.categoryFk = #category
ORDER BY t.order, l.name; ORDER BY t.order, l.name ASC;
DROP TEMPORARY TABLE tmp.itemAvailable`, DROP TEMPORARY TABLE tmp.itemAvailable`,
{ {
category: selectedCategory.value, category: selectedCategory.value,
@ -650,7 +650,7 @@ const getItemColors = async () => {
JOIN tmp.itemAvailable a ON a.id = i.id JOIN tmp.itemAvailable a ON a.id = i.id
JOIN vn.inkL10n l ON l.id = i.inkFk JOIN vn.inkL10n l ON l.id = i.inkFk
WHERE (${queryFilter.value}) WHERE (${queryFilter.value})
ORDER BY name; ORDER BY name ASC;
DROP TEMPORARY TABLE tmp.itemAvailable;`, DROP TEMPORARY TABLE tmp.itemAvailable;`,
{ {
orderId: basketOrderId.value orderId: basketOrderId.value
@ -683,7 +683,7 @@ const getProducers = async () => {
JOIN tmp.itemAvailable a ON a.id = i.id JOIN tmp.itemAvailable a ON a.id = i.id
JOIN vn.producer p ON p.id = i.producerFk JOIN vn.producer p ON p.id = i.producerFk
WHERE (${queryFilter.value}) WHERE (${queryFilter.value})
ORDER BY name; ORDER BY name ASC;
DROP TEMPORARY TABLE tmp.itemAvailable;`, DROP TEMPORARY TABLE tmp.itemAvailable;`,
{ orderId: basketOrderId.value } { orderId: basketOrderId.value }
); );
@ -704,7 +704,7 @@ const getOrigins = async () => {
JOIN vn.origin o ON o.id = i.originFk JOIN vn.origin o ON o.id = i.originFk
JOIN vn.originL10n l ON l.id = o.id JOIN vn.originL10n l ON l.id = o.id
WHERE (${queryFilter.value}) WHERE (${queryFilter.value})
ORDER BY name; ORDER BY name ASC;
DROP TEMPORARY TABLE tmp.itemAvailable;`, DROP TEMPORARY TABLE tmp.itemAvailable;`,
{ orderId: basketOrderId.value } { orderId: basketOrderId.value }
); );
@ -723,7 +723,7 @@ const getSubcategories = async () => {
JOIN vn.itemType t ON t.id = i.typeFk JOIN vn.itemType t ON t.id = i.typeFk
JOIN tmp.itemAvailable a ON a.id = i.id JOIN tmp.itemAvailable a ON a.id = i.id
WHERE (${queryFilter.value}) WHERE (${queryFilter.value})
ORDER BY category; ORDER BY category ASC;
DROP TEMPORARY TABLE tmp.itemAvailable;`, DROP TEMPORARY TABLE tmp.itemAvailable;`,
{ orderId: basketOrderId.value } { orderId: basketOrderId.value }
); );

View File

@ -5,17 +5,16 @@ import { useI18n } from 'vue-i18n';
import TicketDetails from 'src/pages/Ecomerce/TicketDetails.vue'; import TicketDetails from 'src/pages/Ecomerce/TicketDetails.vue';
import { useUserStore } from 'stores/user';
import { useAppStore } from 'stores/app'; import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { usePrintService } from 'src/composables/usePrintService';
const { t } = useI18n(); const { t } = useI18n();
const jApi = inject('jApi'); const jApi = inject('jApi');
const route = useRoute(); const route = useRoute();
const userStore = useUserStore();
const appStore = useAppStore(); const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore); const { isHeaderMounted } = storeToRefs(appStore);
const { user, token } = storeToRefs(userStore); const { openReport } = usePrintService();
const ticket = ref({}); const ticket = ref({});
const rows = ref([]); const rows = ref([]);
@ -38,16 +37,8 @@ onMounted(async () => {
); );
}); });
const onPrintClick = () => { const onPrintClick = () =>
const params = new URLSearchParams({ openReport(`Tickets/${ticket.value.id}/delivery-note-pdf`, {}, '_blank');
access_token: token.value,
recipientId: user.value.id,
type: 'deliveryNote'
});
window.open(
`/api/Tickets/${ticket.value.id}/delivery-note-pdf?${params.toString()}`
);
};
</script> </script>
<template> <template>

View File

@ -5,14 +5,18 @@ import { useI18n } from 'vue-i18n';
import { useUserStore } from 'stores/user'; import { useUserStore } from 'stores/user';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useAppStore } from 'src/stores/app';
const { notify } = useNotify(); const { notify } = useNotify();
const { t } = useI18n(); const { t } = useI18n();
const { locale } = useI18n({ useScope: 'global' }); const { locale } = useI18n({ useScope: 'global' });
const userStore = useUserStore(); const userStore = useUserStore();
const appStore = useAppStore();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { siteLang, localeOptions } = storeToRefs(appStore);
const email = ref(null); const email = ref(null);
const password = ref(null); const password = ref(null);
const remember = ref(false); const remember = ref(false);
@ -20,12 +24,11 @@ const showPwd = ref(false);
const selectedLocaleValue = computed({ const selectedLocaleValue = computed({
get() { get() {
return userStore.localeOptions.find( return siteLang.value;
option => option.lang === locale.value
).value;
}, },
set(value) { set(value) {
locale.value = value; locale.value = value;
appStore.updateSiteLocale(value);
} }
}); });
@ -44,7 +47,6 @@ onMounted(() => {
const onLogin = async () => { const onLogin = async () => {
await userStore.fetchUser(); await userStore.fetchUser();
await userStore.updateUserLang(selectedLocaleValue.value);
await router.push('/'); await router.push('/');
}; };
@ -90,7 +92,7 @@ const loginAsGuest = async () => {
/> />
<QSelect <QSelect
v-model="selectedLocaleValue" v-model="selectedLocaleValue"
:options="userStore.localeOptions" :options="localeOptions"
:label="t('language')" :label="t('language')"
option-value="lang" option-value="lang"
dense dense

View File

@ -7,7 +7,6 @@ import {
createWebHashHistory createWebHashHistory
} from 'vue-router'; } from 'vue-router';
import routes from './routes'; import routes from './routes';
import { i18n } from 'src/boot/i18n';
/* /*
* If not building with SSR mode, you can * If not building with SSR mode, you can
@ -40,10 +39,9 @@ export default route(function (/* { store, ssrContext } */) {
Router.afterEach((to, from) => { Router.afterEach((to, from) => {
if (from.name === to.name) return; if (from.name === to.name) return;
const app = useAppStore(); const app = useAppStore();
app.$patch({ app.$patch({
title: i18n.global.t( title: to.meta.title || 'Home',
to.meta.title ? `titles.${to.meta.title}` : 'titles.Home'
),
subtitle: null, subtitle: null,
useRightDrawer: false, useRightDrawer: false,
rightDrawerOpen: true rightDrawerOpen: true

View File

@ -6,6 +6,7 @@ import { useQuasar } from 'quasar';
const { notify } = useNotify(); const { notify } = useNotify();
const storageOrderName = 'hederaBasket'; const storageOrderName = 'hederaBasket';
const { t } = i18n.global;
export const useAppStore = defineStore('hedera', { export const useAppStore = defineStore('hedera', {
state: () => ({ state: () => ({
@ -16,15 +17,23 @@ export const useAppStore = defineStore('hedera', {
rightDrawerOpen: false, rightDrawerOpen: false,
isHeaderMounted: false, isHeaderMounted: false,
menuEssentialLinks: [], menuEssentialLinks: [],
hiddenMenuLinks: new Set(['Reports']),
basketOrderId: null, basketOrderId: null,
localeDates: { localeDates: {
days: [], days: [],
months: [], months: [],
daysShort: [], daysShort: [],
monthsShort: [] monthsShort: []
} },
siteLang: null,
localeOptions: [
{ label: t('langs.en'), lang: 'en-US', value: 'en' },
{ label: t('langs.es'), lang: 'es-ES', value: 'es' },
{ label: t('langs.ca'), lang: 'ca-ES', value: 'ca' },
{ label: t('langs.fr'), lang: 'fr-FR', value: 'fr' },
{ label: t('langs.pt'), lang: 'pt-PT', value: 'pt' }
]
}), }),
actions: { actions: {
async getMenuLinks() { async getMenuLinks() {
const sections = await jApi.query('SELECT * FROM myMenu'); const sections = await jApi.query('SELECT * FROM myMenu');
@ -51,7 +60,6 @@ export const useAppStore = defineStore('hedera', {
this.menuEssentialLinks = sectionTree; this.menuEssentialLinks = sectionTree;
}, },
async loadConfig() { async loadConfig() {
const imageUrl = await jApi.getValue('SELECT url FROM imageConfig'); const imageUrl = await jApi.getValue('SELECT url FROM imageConfig');
this.$patch({ imageUrl }); this.$patch({ imageUrl });
@ -68,7 +76,7 @@ export const useAppStore = defineStore('hedera', {
}, },
async init() { async init() {
// this.router.push({ name: 'login' }); this.updateSiteLocale(localStorage.getItem('siteLang') || 'es-ES');
this.getBasketOrderId(); this.getBasketOrderId();
this.getLocaleDates(); this.getLocaleDates();
}, },
@ -145,9 +153,24 @@ export const useAppStore = defineStore('hedera', {
onLogout() { onLogout() {
this.unloadOrder(); this.unloadOrder();
this.menuEssentialLinks = []; this.menuEssentialLinks = [];
},
updateSiteLocale(locale = null) {
const _locale = locale || 'es-ES';
i18n.global.locale.value = _locale;
this.siteLang = _locale;
localStorage.setItem('siteLang', _locale);
} }
}, },
getters: { getters: {
filteredMenuItems: state => {
return (state.menuEssentialLinks || []).filter(
item => !state.hiddenMenuLinks.has(item.description)
);
},
siteLocaleOption: state =>
state.localeOptions.find(l => l.value === state.siteLang),
menuTitle: state => t(`titles.${state.title}`),
isMobile() { isMobile() {
const $q = useQuasar(); const $q = useQuasar();
return $q?.screen?.width <= 768; return $q?.screen?.width <= 768;

View File

@ -1,146 +1,327 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { ref, computed, watch } from 'vue';
import { api, jApi } from 'boot/axios'; import { api, jApi } from 'boot/axios';
import { i18n } from 'src/boot/i18n';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useAppStore } from 'src/stores/app.js'; import { useAppStore } from 'src/stores/app.js';
const { t } = i18n.global;
const { notify } = useNotify(); const { notify } = useNotify();
const TOKEN_MULTIMEDIA = 'tokenMultimedia';
const TOKEN = 'token';
export const useUserStore = defineStore('user', { export const useUserStore = defineStore('user', () => {
state: () => { const token = ref('');
const token = const tokenMultimedia = ref('');
sessionStorage.getItem('vnToken') || const isGuest = ref(false);
localStorage.getItem('vnToken'); const user = ref(null); // Usuario en uso => supplantedUser || mainUser
const supplantedUser = ref(null); // Usuario suplantado
const mainUser = ref(null); // Usuario principal logueado
return { const keepLogin = ref(false);
token, const intervalId = ref(null);
isGuest: false, const isCheckingToken = ref(false);
user: null, const tokenConfig = ref(null);
supplantedUser: null, let router;
localeOptions: [
{ label: t('langs.en'), lang: 'en-US', value: 'en' },
{ label: t('langs.es'), lang: 'es-ES', value: 'es' },
{ label: t('langs.ca'), lang: 'ca-ES', value: 'ca' },
{ label: t('langs.fr'), lang: 'fr-FR', value: 'fr' },
{ label: t('langs.pt'), lang: 'pt-PT', value: 'pt' }
]
};
},
getters: { const loggedIn = computed(() => !!token.value);
loggedIn: state => state.token != null, const storage = computed(() =>
userLocaleOption: state => keepLogin.value ? localStorage : sessionStorage
state.localeOptions?.find(l => l.value === state?.user?.lang) );
},
actions: { const init = async _router => {
async init() { router = _router;
this.isGuest = localStorage.getItem('hederaGuest') || false; isGuest.value = localStorage.getItem('hederaGuest') || false;
const autoLoginStatus = await this.tryAutoLogin(); await getToken();
if (!loggedIn.value) {
const autoLoginStatus = await tryAutoLogin();
if (!autoLoginStatus) { if (!autoLoginStatus) {
this.router.push({ name: 'login' }); router.push({ name: 'login' });
} }
await this.fetchUser(); }
await this.supplantInit(); await fetchTokenConfig();
this.updateSiteLocale(); await fetchUser();
}, await supplantInit();
startInterval();
};
async getToken() { const getToken = () => {
this.token = const tokenValue =
sessionStorage.getItem('vnToken') || sessionStorage.getItem(TOKEN) || localStorage.getItem(TOKEN);
localStorage.getItem('vnToken'); token.value = tokenValue;
}, return tokenValue;
};
async tryAutoLogin() { const getTokenMultimedia = () => {
const guest = localStorage.getItem('hederaGuest'); const tokenMultimediaValue =
sessionStorage.getItem(TOKEN_MULTIMEDIA) ||
localStorage.getItem(TOKEN_MULTIMEDIA);
tokenMultimedia.value = tokenMultimediaValue;
return tokenMultimediaValue;
};
if (this.isGuest || guest) { const setToken = ({ _token, _tokenMultimedia }) => {
storage.value.setItem(TOKEN, _token);
storage.value.setItem(TOKEN_MULTIMEDIA, _tokenMultimedia);
token.value = _token;
tokenMultimedia.value = _tokenMultimedia;
};
const destroyToken = async (url, storageType, key) => {
if (storageType.getItem(key)) {
try {
await api.post(url, null, {
headers: { Authorization: storageType.getItem(key) }
});
} catch (error) {
notify('errors.statusUnauthorized', 'negative');
} finally {
storageType.removeItem(key);
}
}
};
const destroy = async (destroyTokens = true) => {
const tokens = {
tokenMultimedia: 'Accounts/logout',
token: 'VnUsers/logout'
};
let destroyTokenPromises = [];
try {
if (destroyTokens) {
const { data: isValidToken } = await api.get(
'VnUsers/validateToken'
);
if (isValidToken) {
destroyTokenPromises = Object.entries(tokens).map(
([key, url]) => destroyToken(url, storage.value, key)
);
}
}
} finally {
localStorage.clear();
sessionStorage.clear();
await Promise.allSettled(destroyTokenPromises);
user.value = null;
stopRenewer();
}
};
const setSession = data => {
setToken({
_token: data.token,
_tokenMultimedia: data.tokenMultimedia
});
storage.value.setItem('hederaLastUser', data.username);
storage.value.setItem('created', data.created);
storage.value.setItem('ttl', data.ttl);
localStorage.setItem('keepLogin', keepLogin.value);
};
const fetchMultimediaToken = async data => {
const {
data: { multimediaToken }
} = await api.get('VnUsers/ShareToken', {
headers: { Authorization: data.token }
});
return multimediaToken;
};
const login = async (username, password, remember) => {
const params = { user: username, password };
const { data } = await api.post('Accounts/login', params);
const multimediaToken = await fetchMultimediaToken(data);
if (!multimediaToken) return;
keepLogin.value = remember;
setSession({
created: Date.now(),
tokenMultimedia: multimediaToken.id,
username,
...data
});
await fetchTokenConfig();
startInterval();
};
const tryAutoLogin = async () => {
if (isGuest.value) {
localStorage.setItem('hederaGuest', true); localStorage.setItem('hederaGuest', true);
return true; return true;
} }
if (!this.token) this.getToken(); if (!token.value) getToken();
if (this.token) return true; if (token.value) return true;
return false; return false;
}, };
async login(user, password, remember) { const logout = async () => {
const params = { user, password };
const res = await api.post('Accounts/login', params);
if (remember) {
localStorage.setItem('vnToken', res.data.token);
} else {
sessionStorage.setItem('vnToken', res.data.token);
}
this.$patch({
token: res.data.token,
name: user
});
},
async logout() {
if (this.token != null) {
try { try {
await api.post('Accounts/logout'); await api.post('Accounts/logout');
} catch (e) {} } catch (e) {}
localStorage.removeItem('vnToken'); destroy();
sessionStorage.removeItem('vnToken'); $reset();
}
this.$reset();
localStorage.removeItem('hederaGuest');
useAppStore().onLogout(); useAppStore().onLogout();
}, };
async fetchUser(userType = 'user') { const fetchTokenConfig = async () => {
try {
const { data } = await api.get('AccessTokenConfigs/findOne', {
filter: { fields: ['renewInterval', 'renewPeriod'] }
});
if (!data) return;
tokenConfig.value = data;
storage.value.setItem('renewPeriod', data.renewPeriod);
return data;
} catch (error) {
notify('errors.tokenConfig', 'negative');
console.error('Error fetching token config:', error);
}
};
const renewToken = async () => {
const _token = getToken();
const tokenData = await api.post('VnUsers/renewToken', {
headers: { Authorization: _token }
});
const _tokenMultimedia = getTokenMultimedia();
const tokenMultimedia = await api.post('VnUsers/renewToken', {
headers: { Authorization: _tokenMultimedia }
});
setToken({
_token: tokenData.data.id,
_tokenMultimedia: tokenMultimedia.data.id
});
};
const checkValidity = async () => {
const created = +storage.value.getItem('created');
const ttl = +storage.value.getItem('ttl');
if (isCheckingToken.value || !created) return;
isCheckingToken.value = true;
const renewPeriodInSeconds =
Math.min(ttl, tokenConfig.value?.renewPeriod) * 1000;
const maxDate = created + renewPeriodInSeconds;
const now = new Date().getTime();
if (isNaN(renewPeriodInSeconds) || now <= maxDate) {
isCheckingToken.value = false;
return;
}
await renewToken();
isCheckingToken.value = false;
};
const stopRenewer = () => {
clearInterval(intervalId.value);
};
const startInterval = () => {
stopRenewer();
const renewPeriod = +storage.value.getItem('renewPeriod');
if (!renewPeriod) return;
intervalId.value = setInterval(
() => checkValidity(),
renewPeriod * 1000
);
};
const fetchUser = async (userType = 'user') => {
try { try {
const userData = await jApi.getObject( const userData = await jApi.getObject(
'SELECT id, nickname, name, lang FROM account.myUser' 'SELECT id, nickname, name, lang FROM account.myUser'
); );
this.$patch({ [userType]: userData }); if (userType === 'user') mainUser.value = userData;
else supplantedUser.value = userData;
} catch (error) { } catch (error) {
console.error('Error fetching user: ', error); console.error('Error fetching user: ', error);
} }
}, };
async supplantUser(supplantUser) { const supplantUser = async supplantUser => {
const json = await jApi.send('client/supplant', { const json = await jApi.send('client/supplant', { supplantUser });
supplantUser token.value = json;
});
this.token = json;
sessionStorage.setItem('supplantUser', supplantUser); sessionStorage.setItem('supplantUser', supplantUser);
await this.fetchUser('supplantedUser'); await fetchUser('supplantedUser');
}, };
async supplantInit() { const supplantInit = async () => {
const user = sessionStorage.getItem('supplantUser'); const user = sessionStorage.getItem('supplantUser');
if (user == null) return; if (!user) return;
await this.supplantUser(user); await supplantUser(user);
}, };
async logoutSupplantedUser() { const logoutSupplantedUser = async () => {
sessionStorage.removeItem('supplantUser'); sessionStorage.removeItem('supplantUser');
this.supplantedUser = null; supplantedUser.value = null;
await api.post('Accounts/logout'); await api.post('Accounts/logout');
this.getToken(); getToken();
await this.fetchUser(); await fetchUser();
}, };
updateSiteLocale(locale = null) { const updateUserLang = async lang => {
i18n.global.locale.value = if (!user.value || user.value.lang === lang) return;
locale || this.userLocaleOption.lang || 'en-US'; user.value.lang = lang;
}, };
async updateUserLang(lang) { const $reset = () => {
if (!this.user || this.user.lang === lang) return; token.value = '';
isGuest.value = false;
user.value = null;
supplantedUser.value = null;
mainUser.value = null;
keepLogin.value = false;
intervalId.value = null;
isCheckingToken.value = false;
tokenConfig.value = null;
};
this.user.lang = lang; watch(
this.updateSiteLocale(); [mainUser, supplantedUser],
notify(t('dataSaved'), 'positive'); () => (user.value = supplantedUser.value || mainUser.value),
}
} { immediate: true }
);
return {
token,
tokenMultimedia,
isGuest,
user,
supplantedUser,
mainUser,
keepLogin,
intervalId,
isCheckingToken,
tokenConfig,
loggedIn,
storage,
getToken,
getTokenMultimedia,
setToken,
destroyToken,
destroy,
setSession,
login,
tryAutoLogin,
logout,
fetchTokenConfig,
renewToken,
checkValidity,
stopRenewer,
startInterval,
fetchUser,
supplantUser,
supplantInit,
logoutSupplantedUser,
updateUserLang,
init,
$reset
};
}); });