#7750 - first-beta-review #95
19
README.md
19
README.md
|
@ -6,31 +6,30 @@ Hedera is the main web page for Verdnatura.
|
|||
|
||||
Required dependencies.
|
||||
|
||||
* PHP >= 7.0
|
||||
* Node.js >= 18.0
|
||||
- PHP >= 7.0
|
||||
- Node.js >= 18.0
|
||||
|
||||
Launch application for development.
|
||||
|
||||
```
|
||||
$ quasar dev
|
||||
```
|
||||
|
||||
Launch Salix backend.
|
||||
```
|
||||
npm run salix
|
||||
|
||||
```
|
||||
|
||||
Launch legacy PHP backend.
|
||||
```
|
||||
npm run back
|
||||
pnpm run back
|
||||
```
|
||||
|
||||
Run server side method from command line.
|
||||
|
||||
```
|
||||
php hedera-web.php -m method_path
|
||||
```
|
||||
|
||||
## Built with
|
||||
|
||||
* [Webpack](https://webpack.js.org/)
|
||||
* [MooTools](https://mootools.net/)
|
||||
* [TinyMCE](https://www.tinymce.com/)
|
||||
- [Webpack](https://webpack.js.org/)
|
||||
- [MooTools](https://mootools.net/)
|
||||
- [TinyMCE](https://www.tinymce.com/)
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Connection } from '../js/db/connection';
|
|||
import { useUserStore } from 'stores/user';
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { useAppStore } from 'src/stores/app';
|
||||
|
||||
const { notify } = useNotify();
|
||||
// Be careful when using SSR for cross-request state pollution
|
||||
|
@ -37,9 +38,11 @@ const onResponseError = error => {
|
|||
|
||||
export default boot(({ app }) => {
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
function addToken(config) {
|
||||
if (userStore.token) {
|
||||
config.headers.Authorization = userStore.token;
|
||||
config.headers['Accept-Language'] = appStore.siteLang;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
|
|
@ -129,17 +129,20 @@ async function filterHandler(val, update) {
|
|||
|
||||
if (!$props.defaultFilter) return update();
|
||||
const newOptions = filter(val, myOptionsOriginal.value);
|
||||
update(
|
||||
() => {
|
||||
myOptions.value = newOptions;
|
||||
},
|
||||
ref => {
|
||||
if (val !== '' && ref.options.length > 0) {
|
||||
ref.setOptionIndex(-1);
|
||||
ref.moveOptionSelection(1, true);
|
||||
|
||||
setTimeout(() => {
|
||||
update(
|
||||
() => {
|
||||
myOptions.value = newOptions;
|
||||
},
|
||||
ref => {
|
||||
if (val !== '' && ref.options.length > 0) {
|
||||
ref.setOptionIndex(-1);
|
||||
ref.moveOptionSelection(1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}, 300);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -178,6 +181,13 @@ async function filterHandler(val, update) {
|
|||
>
|
||||
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
|
||||
</template>
|
||||
<template #no-option>
|
||||
<QItem>
|
||||
<QItemSection class="text-grey">
|
||||
{{ t('emptyList') }}
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</QSelect>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -100,26 +100,21 @@ const onSubmit = async () => {
|
|||
en-US:
|
||||
name: Name
|
||||
file: File
|
||||
send: Send
|
||||
imageAdded: Image added successfully
|
||||
es-ES:
|
||||
name: Nombre
|
||||
file: Archivo
|
||||
send: Enviar
|
||||
imageAdded: Imagen añadida correctamente
|
||||
ca-ES:
|
||||
name: Nom
|
||||
file: Arxiu
|
||||
send: Enviar
|
||||
imageAdded: Imatge afegida correctament
|
||||
fr-FR:
|
||||
name: Nom
|
||||
file: Fichier
|
||||
send: Envoyer
|
||||
imageAdded: Image ajoutée correctement
|
||||
pt-PT:
|
||||
name: Nome
|
||||
file: Arquivo
|
||||
send: Enviar
|
||||
imageAdded: Imagen adicionada corretamente
|
||||
</i18n>
|
||||
|
|
|
@ -67,6 +67,10 @@ const url = computed(() => {
|
|||
return `${props.baseURL ?? app.imageUrl}/${props.storage}/${props.size}/${props.id}`;
|
||||
});
|
||||
|
||||
const zoomUrl = computed(() => {
|
||||
return `${props.baseURL ?? app.imageUrl}/${props.storage}/${props.zoomSize}/${props.id}`;
|
||||
});
|
||||
|
||||
const rounded = computed(() => {
|
||||
const roundedMap = {
|
||||
none: '',
|
||||
|
@ -114,7 +118,7 @@ const rounded = computed(() => {
|
|||
|
||||
<QDialog v-if="props.zoomSize" v-model="showZoom">
|
||||
<QImg
|
||||
:src="url"
|
||||
:src="zoomUrl"
|
||||
size="full"
|
||||
class="img_zoom"
|
||||
v-bind="$attrs"
|
||||
|
|
|
@ -6,10 +6,6 @@ defineProps({
|
|||
type: String,
|
||||
default: 'emptyList'
|
||||
},
|
||||
emptyIcon: {
|
||||
type: String,
|
||||
default: 'block'
|
||||
},
|
||||
rows: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
|
@ -29,7 +25,6 @@ const { t } = useI18n();
|
|||
v-if="!rows?.length"
|
||||
class="flex items-center q-pa-md justify-center items-center"
|
||||
>
|
||||
<QIcon :name="emptyIcon" size="sm" class="q-mr-sm" />
|
||||
{{ t(emptyMessage) }}
|
||||
</span>
|
||||
<QSpinner v-if="loading" color="primary" size="3em" :thickness="2" />
|
||||
|
|
|
@ -40,6 +40,11 @@ const search = async () => {
|
|||
query: searchTerm.value ? { search: searchTerm.value } : {}
|
||||
});
|
||||
|
||||
if (!searchTerm.value) {
|
||||
emit('onSearchError');
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.sqlQuery) {
|
||||
data = await jApi.query(props.sqlQuery, {
|
||||
[props.searchField]: searchTerm.value
|
||||
|
|
|
@ -134,6 +134,7 @@ export default {
|
|||
minQuantity: 'Quantitat mínima',
|
||||
introduceSearchTerm: 'Introdueix un terme de cerca',
|
||||
noOrdersFound: `No s'han trobat comandes`,
|
||||
send: 'Enviar',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'No es pot bloquejar la memòria cau',
|
||||
'Bad file format': 'Format de fitxer no reconegut',
|
||||
|
|
|
@ -168,6 +168,7 @@ export default {
|
|||
minQuantity: 'Minimum quantity',
|
||||
introduceSearchTerm: 'Enter a search term',
|
||||
noOrdersFound: 'No orders found',
|
||||
send: 'Send',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'The cache could not be blocked',
|
||||
'Bad file format': 'Unrecognized file format',
|
||||
|
|
|
@ -167,6 +167,7 @@ export default {
|
|||
minQuantity: 'Cantidad mínima',
|
||||
introduceSearchTerm: 'Introduce un término de búsqueda',
|
||||
noOrdersFound: 'No se encontrado pedidos',
|
||||
send: 'Enviar',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'La caché no pudo ser bloqueada',
|
||||
'Bad file format': 'Formato de archivo no reconocido',
|
||||
|
|
|
@ -135,6 +135,7 @@ export default {
|
|||
minQuantity: 'Quantité minimum',
|
||||
introduceSearchTerm: 'Entrez un terme de recherche',
|
||||
noOrdersFound: 'Aucune commande trouvée',
|
||||
send: 'Envoyer',
|
||||
// Image related translations
|
||||
'Cant lock cache': "Le cache n'a pas pu être verrouillé",
|
||||
'Bad file format': 'Format de fichier non reconnu',
|
||||
|
|
|
@ -133,6 +133,7 @@ export default {
|
|||
minQuantity: 'Quantidade mínima',
|
||||
introduceSearchTerm: 'Digite um termo de pesquisa',
|
||||
noOrdersFound: 'Nenhum pedido encontrado',
|
||||
send: 'Enviar',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'O cache não pôde ser bloqueado',
|
||||
'Bad file format': 'Formato de arquivo inválido',
|
||||
|
|
|
@ -3,13 +3,11 @@
|
|||
id="bg"
|
||||
class="fullscreen row justify-center items-center layout-view scroll"
|
||||
>
|
||||
<div class="column q-pa-md row items-center justify-center">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition>
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
<QPageContainer class="column q-pa-md row items-center justify-center">
|
||||
<transition>
|
||||
<router-view />
|
||||
</transition>
|
||||
</QPageContainer>
|
||||
</QLayout>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ const logoutSupplantedUser = async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<QLayout view="hhh Lpr fFf">
|
||||
<QLayout view="hhh LpR fFf">
|
||||
<QHeader>
|
||||
<QToolbar>
|
||||
<QBtn
|
||||
|
@ -216,6 +216,10 @@ const logoutSupplantedUser = async () => {
|
|||
padding: 16px;
|
||||
}
|
||||
|
||||
div .q-drawer-container {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
#actions {
|
||||
.q-btn {
|
||||
|
|
|
@ -109,12 +109,12 @@ const fetchData = async () => {
|
|||
</QBtn>
|
||||
<QBtn
|
||||
icon="shopping_bag"
|
||||
:label="t('catalog')"
|
||||
:label="t('titles.Catalog')"
|
||||
:to="{ name: 'catalog' }"
|
||||
rounded
|
||||
no-caps
|
||||
>
|
||||
<QTooltip>{{ t('catalog') }}</QTooltip>
|
||||
<QTooltip>{{ t('titles.Catalog') }}</QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
icon="shopping_cart_checkout"
|
||||
|
|
|
@ -25,8 +25,9 @@ const { t } = useI18n();
|
|||
storage="catalog"
|
||||
size="200x200"
|
||||
:id="item.image"
|
||||
height="210px"
|
||||
height="190px"
|
||||
rounded="bottom"
|
||||
zoom-size="1600x900"
|
||||
/>
|
||||
<div
|
||||
class="column"
|
||||
|
@ -45,7 +46,7 @@ const { t } = useI18n();
|
|||
</span>
|
||||
<span> #{{ item.id }}</span>
|
||||
</div>
|
||||
<div class="tags q-pt-xs text-caption">
|
||||
<div class="tags text-caption">
|
||||
<div
|
||||
v-for="(tag, index) in item.previewTags"
|
||||
:key="index"
|
||||
|
@ -75,7 +76,7 @@ const { t } = useI18n();
|
|||
<div class="row justify-between items-cente q-gutter-x-xs">
|
||||
<QBadge
|
||||
:label="`x${item.grouping}`"
|
||||
color="grey"
|
||||
color="grey-4"
|
||||
class="col-2 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
|
@ -85,8 +86,7 @@ const { t } = useI18n();
|
|||
<QBadge
|
||||
outline
|
||||
:label="item.available"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
color="grey-6"
|
||||
class="col justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
|
@ -97,7 +97,6 @@ const { t } = useI18n();
|
|||
outline
|
||||
:label="currency(item.price)"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
|
@ -117,6 +116,7 @@ const { t } = useI18n();
|
|||
height="105px"
|
||||
rounded-borders="full"
|
||||
class="q-mr-md"
|
||||
zoom-size="1600x900"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -153,7 +153,8 @@ const { t } = useI18n();
|
|||
<div class="row justify-end items-center q-gutter-x-xs q-mt-sm">
|
||||
<QBadge
|
||||
:label="`x${item.grouping}`"
|
||||
color="grey"
|
||||
color="grey-4"
|
||||
text-color="black"
|
||||
class="col-2 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
|
@ -163,8 +164,7 @@ const { t } = useI18n();
|
|||
<QBadge
|
||||
outline
|
||||
:label="item.available"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
color="grey-6"
|
||||
class="col-3 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
|
@ -175,7 +175,6 @@ const { t } = useI18n();
|
|||
outline
|
||||
:label="currency(item.price)"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col-3 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
<template>
|
||||
<Teleport v-if="isHeaderMounted" to="#actions">
|
||||
<div class="row">
|
||||
<VnSearchBar :search-term="search" @on-search-error="items = []" />
|
||||
<VnSearchBar
|
||||
@on-search-error="
|
||||
() => {
|
||||
items = [];
|
||||
search = '';
|
||||
}
|
||||
"
|
||||
/>
|
||||
<QBtn
|
||||
:icon="viewTypeButtonContent.icon"
|
||||
:label="viewTypeButtonContent.label"
|
||||
|
@ -36,7 +43,7 @@
|
|||
/>
|
||||
</div>
|
||||
</Teleport>
|
||||
<div style="padding-bottom: 5em">
|
||||
<div>
|
||||
<QDrawer v-model="rightDrawerOpen" side="right" :width="250" persistent>
|
||||
<div class="q-pa-md">
|
||||
<div class="basket-info q-gutter-y-sm">
|
||||
|
@ -55,6 +62,9 @@
|
|||
no-caps
|
||||
@click="redirectToCheckout()"
|
||||
data-testid="orderModifyButton"
|
||||
color="light-green-7"
|
||||
unelevated
|
||||
text-color="white"
|
||||
>
|
||||
{{ t('modify') }}
|
||||
</QBtn>
|
||||
|
@ -132,6 +142,12 @@
|
|||
:disable="!category"
|
||||
:label="t('category')"
|
||||
/>
|
||||
<div
|
||||
v-if="isSomeFilterSelected"
|
||||
class="q-mt-md text-grey-7"
|
||||
>
|
||||
{{ t('orderBy') }}
|
||||
</div>
|
||||
<VnSelect
|
||||
v-if="isSomeFilterSelected"
|
||||
v-model="selectedOrderBy"
|
||||
|
@ -139,7 +155,7 @@
|
|||
option-value="value"
|
||||
option-label="label"
|
||||
:is-clearable="false"
|
||||
:label="t('orderBy')"
|
||||
:label="t('sort')"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
|
@ -153,7 +169,7 @@
|
|||
<div
|
||||
:class="
|
||||
viewMode === 'grid'
|
||||
? 'q-pa-md row justify-center q-gutter-md'
|
||||
? ' row justify-center q-gutter-md'
|
||||
: 'column items-center'
|
||||
"
|
||||
>
|
||||
|
@ -167,7 +183,6 @@
|
|||
v-else-if="!items || !items.length || !isSomeFilterSelected"
|
||||
class="text-subtitle1 text-grey-7 q-pa-md"
|
||||
>
|
||||
<QIcon name="refresh" size="sm" class="q-mr-sm"></QIcon>
|
||||
<span>{{ t('pleaseSetFilter') }}</span>
|
||||
</div>
|
||||
<CatalogCard
|
||||
|
@ -180,13 +195,13 @@
|
|||
/>
|
||||
</div>
|
||||
<QDialog v-model="showItemDialog" @hide="resetAmounts()">
|
||||
<QCard style="width: 25em" class="column">
|
||||
<QCard v-if="selectedItem" style="width: 25em" class="column">
|
||||
<div class="q-pa-md relative-position">
|
||||
<div class="q-mb-md" style="display: flex">
|
||||
<VnImg
|
||||
storage="catalog"
|
||||
size="200x200"
|
||||
:id="'asd'"
|
||||
:id="selectedItem.image"
|
||||
width="112px"
|
||||
height="112px"
|
||||
rounded="bottom"
|
||||
|
@ -282,6 +297,7 @@
|
|||
</QBtn>
|
||||
</div>
|
||||
</QCard>
|
||||
<QSpinner v-else color="primary" size="3em" :thickness="5" />
|
||||
</QDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -744,7 +760,8 @@ const getSubcategories = async () => {
|
|||
DROP TEMPORARY TABLE tmp.itemAvailable;`,
|
||||
{ orderId: basketOrderId.value }
|
||||
);
|
||||
itemSubcategories.value = res.results[1].data;
|
||||
const filtered = res.results[1].data.filter(item => item.category);
|
||||
itemSubcategories.value = filtered.map(i => i.category);
|
||||
} catch (error) {
|
||||
console.error('Error getting subcategories:', error);
|
||||
|
||||
}
|
||||
|
@ -753,11 +770,13 @@ const getSubcategories = async () => {
|
|||
const showItem = async item => {
|
||||
if (checkGuest()) return;
|
||||
|
||||
const itemLots = await calcItem(item.id);
|
||||
const tags = await getItemTags(item.id);
|
||||
showItemDialog.value = true;
|
||||
const [itemLots, tags] = await Promise.all([
|
||||
calcItem(item.id),
|
||||
getItemTags(item.id)
|
||||
]);
|
||||
item.lots = itemLots;
|
||||
item.tags = tags;
|
||||
showItemDialog.value = true;
|
||||
selectedItem.value = item;
|
||||
};
|
||||
|
||||
|
@ -848,6 +867,7 @@ const onAddLotClick = async lot => {
|
|||
};
|
||||
|
||||
const resetAmounts = () => {
|
||||
selectedItem.value = null;
|
||||
addedItemsAmountAcc.value = {};
|
||||
amount.value = 0;
|
||||
};
|
||||
|
@ -1031,6 +1051,8 @@ en-US:
|
|||
filterBy: Filter by
|
||||
chooseCategory: Choose a category
|
||||
youMustBeLoggedIn: You must be a registered user
|
||||
sort: Order
|
||||
amountNotAvailable: Amount not available
|
||||
es-ES:
|
||||
category: Categoría
|
||||
deleteFilter: Quitar filtro
|
||||
|
@ -1054,6 +1076,8 @@ es-ES:
|
|||
filterBy: Filtrar por
|
||||
chooseCategory: Elige una categoría
|
||||
youMustBeLoggedIn: Debes estar registrado como usuario
|
||||
sort: Ordenar
|
||||
amountNotAvailable: Cantidad no disponible
|
||||
ca-ES:
|
||||
category: Categoría
|
||||
deleteFilter: Eliminar filtro
|
||||
|
@ -1075,6 +1099,8 @@ ca-ES:
|
|||
filterBy: Filtrar per
|
||||
chooseCategory: Tria una categoria
|
||||
youMustBeLoggedIn: Has d'estar registrat com a usuari
|
||||
sort: Ordenar
|
||||
amountNotAvailable: Quantitat no disponible
|
||||
fr-FR:
|
||||
category: Catégorie
|
||||
deleteFilter: Supprimer le filtre
|
||||
|
@ -1096,6 +1122,8 @@ fr-FR:
|
|||
filterBy: Filtrer par
|
||||
chooseCategory: Choisissez une catégorie
|
||||
youMustBeLoggedIn: Vous devez être un utilisateur enregistré
|
||||
sort: Trier
|
||||
amountNotAvailable: Quantité non disponible
|
||||
pt-PT:
|
||||
category: Categoria
|
||||
deleteFilter: Apagar filtro
|
||||
|
@ -1117,4 +1145,6 @@ pt-PT:
|
|||
filterBy: Filtrar por
|
||||
chooseCategory: Escolha uma categoria
|
||||
youMustBeLoggedIn: Deves estar registrado como usuario
|
||||
sort: Ordenar
|
||||
amountNotAvailable: Quantidade não disponível
|
||||
</i18n>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, onMounted, inject, computed } from 'vue';
|
||||
import { ref, onMounted, inject, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
|
@ -28,6 +28,7 @@ const agencies = ref([]);
|
|||
const warehouses = ref([]);
|
||||
const currentStep = ref('method');
|
||||
const id = route.params.id;
|
||||
const defaultValues = ref(null);
|
||||
const orderForm = ref({
|
||||
method: 'AGENCY',
|
||||
date: formatDate(Date.vnNew(), 'YYYY/MM/DD'),
|
||||
|
@ -179,6 +180,15 @@ const getAgencies = async () => {
|
|||
}
|
||||
);
|
||||
agencies.value = results[1].data;
|
||||
|
||||
if (agencies.value && agencies.value.length && defaultValues.value) {
|
||||
const found = agencies.value.find(
|
||||
agency => agency.id === defaultValues.value.defaultAgencyFk
|
||||
);
|
||||
|
||||
jsegarra
commented
Duda, si no se encuentra no debería mostrar mensaje de error al usuario? Duda, si no se encuentra no debería mostrar mensaje de error al usuario?
Cambiar found por agency
wbuezas
commented
No creo que se deba mostrar un error por esto, supongo que por algo el hedera viejo no lo hacía No creo que se deba mostrar un error por esto, supongo que por algo el hedera viejo no lo hacía
|
||||
if (found)
|
||||
orderForm.value.agency = defaultValues.value.defaultAgencyFk;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting agencies:', error);
|
||||
}
|
||||
|
@ -284,6 +294,13 @@ const submit = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const getDefaultValues = async () => {
|
||||
return await jApi.query(
|
||||
`SELECT deliveryMethod, agencyModeFk, addressFk, defaultAgencyFk
|
||||
FROM myBasketDefaults`
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
today.value = Date.vnNew();
|
||||
today.value.setHours(0, 0, 0, 0);
|
||||
|
@ -305,10 +322,21 @@ onMounted(async () => {
|
|||
orderForm.value.agency = order.agencyModeFk;
|
||||
orderForm.value.address = order.addressFk;
|
||||
}
|
||||
} else {
|
||||
const [_defaultValues] = await getDefaultValues();
|
||||
if (_defaultValues) defaultValues.value = _defaultValues;
|
||||
}
|
||||
|
||||
getAddresses();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => orderForm.value.method,
|
||||
() => {
|
||||
orderForm.value.address = '';
|
||||
orderForm.value.agency = '';
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -468,7 +496,7 @@ onMounted(async () => {
|
|||
</QTooltip>
|
||||
</QBtn>
|
||||
<QBtn
|
||||
v-if="showNavigationButtons"
|
||||
v-if="showNavigationButtons || currentStep === 'confirm'"
|
||||
@click="onNextStep(stepIndex)"
|
||||
:color="currentStep === 'confirm' ? 'accent ' : 'primary'"
|
||||
:icon="
|
||||
|
@ -477,6 +505,7 @@ onMounted(async () => {
|
|||
dense
|
||||
class="right-navigation-button"
|
||||
data-testid="checkoutStepperRightButton"
|
||||
:loading="loading"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t(`${step.nextButtonLabel || 'next'}`) }}
|
||||
|
@ -491,7 +520,7 @@ onMounted(async () => {
|
|||
@import 'src/css/responsive';
|
||||
|
||||
.step-title {
|
||||
min-width: 100%;
|
||||
max-width: 90%;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
|
@ -523,7 +552,7 @@ onMounted(async () => {
|
|||
.left-navigation-button {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 50%;
|
||||
top: 25px;
|
||||
@include mobile {
|
||||
top: 35%;
|
||||
}
|
||||
|
@ -532,7 +561,7 @@ onMounted(async () => {
|
|||
.right-navigation-button {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 50%;
|
||||
top: 25px;
|
||||
@include mobile {
|
||||
top: 35%;
|
||||
}
|
||||
|
|
|
@ -288,9 +288,8 @@ onMounted(async () => {
|
|||
>
|
||||
<div
|
||||
v-if="!transferAccounts.length"
|
||||
class="row items-center justify-center q-pa-md bg-red"
|
||||
class="row items-center justify-center q-pa-md"
|
||||
>
|
||||
<QIcon class="q-mr-md" name="block" size="sm" />
|
||||
<span>{{ t('emptyList') }}</span>
|
||||
</div>
|
||||
<QList>
|
||||
|
|
|
@ -172,7 +172,6 @@ const deleteRow = id => {
|
|||
class="row items-center justify-center q-pa-md"
|
||||
style="margin-top: 32px"
|
||||
>
|
||||
<QIcon class="q-mr-md" name="block" size="sm" />
|
||||
<span>{{ t('emptyList') }}</span>
|
||||
</div>
|
||||
</QCard>
|
||||
|
|
|
@ -136,8 +136,11 @@ const loginAsGuest = async () => {
|
|||
outline
|
||||
/>
|
||||
</div>
|
||||
<p class="password-forgotten text-center q-mt-lg">
|
||||
<router-link to="/remember-password" class="link">
|
||||
<p
|
||||
class="password-forgotten text-center q-mt-lg"
|
||||
data-testid="recoverPasswordViewLink"
|
||||
>
|
||||
<router-link :to="{ name: 'recoverPassword' }" class="link">
|
||||
{{ $t('haveForgottenPassword') }}
|
||||
</router-link>
|
||||
</p>
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
const user = ref('');
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const onSend = async () => {
|
||||
const params = {
|
||||
user: user.value,
|
||||
app: 'hedera'
|
||||
};
|
||||
await api.post('VnUsers/recoverPassword', params);
|
||||
notify(t('weHaveSentEmailToRecover'), 'positive');
|
||||
router.push('/login');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QPage class="text-center">
|
||||
<div>
|
||||
<QIcon
|
||||
name="contact_support"
|
||||
class="block q-mx-auto text-accent"
|
||||
style="font-size: 120px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<QForm @submit="onSend" class="q-gutter-y-md text-grey-8">
|
||||
<VnInput
|
||||
v-model="user"
|
||||
:label="t('user')"
|
||||
autofocus
|
||||
data-testid="recoverPasswordUserInput"
|
||||
/>
|
||||
<div class="q-mt-lg">
|
||||
{{ t('weSendEmail') }}
|
||||
</div>
|
||||
<div>
|
||||
<QBtn
|
||||
type="submit"
|
||||
:label="t('send')"
|
||||
class="full-width q-mt-md"
|
||||
color="primary"
|
||||
rounded
|
||||
no-caps
|
||||
unelevated
|
||||
data-testid="recoverPasswordSubmitButton"
|
||||
/>
|
||||
<div class="text-center q-mt-md">
|
||||
<router-link to="/login" class="link">
|
||||
{{ t('back') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</QForm>
|
||||
</div>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-btn {
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en-US:
|
||||
inputEmail: Input email
|
||||
rememberPassword: Rememeber password
|
||||
weSendEmail: We will sent you an email to recover your password
|
||||
weHaveSentEmailToRecover: We've sent you an email where you can recover your password
|
||||
es-ES:
|
||||
inputEmail: Introduce el correo electrónico
|
||||
rememberPassword: Recordar contraseña
|
||||
weSendEmail: Te enviaremos un correo para restablecer tu contraseña
|
||||
weHaveSentEmailToRecover: Te hemos enviado un correo donde podrás recuperar tu contraseña
|
||||
ca-ES:
|
||||
inputEmail: Introdueix el correu electrònic
|
||||
rememberPassword: Recordar contrasenya
|
||||
weSendEmail: T'enviarem un correu per restablir la teva contrasenya
|
||||
weHaveSentEmailToRecover: T'hem enviat un correu on podràs recuperar la teva contrasenya
|
||||
fr-FR:
|
||||
inputEmail: Entrez l'email
|
||||
rememberPassword: Se souvenir du mot de passe
|
||||
weSendEmail: Nous vous enverrons un e-mail pour récupérer votre mot de passe
|
||||
weHaveSentEmailToRecover: Nous vous avons envoyé un e-mail où vous pouvez récupérer votre mot de passe
|
||||
pr-BR:
|
||||
inputEmail: Digite o e-mail
|
||||
rememberPassword: Lembrar senha
|
||||
weSendEmail: Enviaremos um e-mail para recuperar sua senha
|
||||
weHaveSentEmailToRecover: Enviamos um e-mail onde você pode recuperar sua senha
|
||||
</i18n>
|
|
@ -1,114 +0,0 @@
|
|||
<template>
|
||||
<div class="text-center">
|
||||
<div>
|
||||
<QIcon
|
||||
name="contact_support"
|
||||
class="block q-mx-auto text-accent"
|
||||
style="font-size: 120px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<QForm
|
||||
@submit="onSend"
|
||||
class="q-gutter-y-md text-grey-8"
|
||||
>
|
||||
<div class="text-h5">
|
||||
<div>
|
||||
{{ $t('dontWorry') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('fillData') }}
|
||||
</div>
|
||||
</div>
|
||||
<QInput
|
||||
v-model="email"
|
||||
:label="$t('user')"
|
||||
:rules="[val => !!val || $t('inputEmail')]"
|
||||
autofocus
|
||||
/>
|
||||
<div class="q-mt-lg">
|
||||
{{ $t('weSendEmail') }}
|
||||
</div>
|
||||
<div>
|
||||
<QBtn
|
||||
type="submit"
|
||||
:label="$t('send')"
|
||||
class="full-width q-mt-md"
|
||||
color="primary"
|
||||
rounded
|
||||
no-caps
|
||||
unelevated
|
||||
/>
|
||||
<div class="text-center q-mt-md">
|
||||
<router-link
|
||||
to="/login"
|
||||
class="link"
|
||||
>
|
||||
{{ $t('return') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</QForm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#image {
|
||||
height: 190px;
|
||||
}
|
||||
.q-btn {
|
||||
height: 50px;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VnRememberPasword',
|
||||
data() {
|
||||
return {
|
||||
email: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async onSend() {
|
||||
const params = {
|
||||
email: this.email
|
||||
};
|
||||
await this.$axios.post('Users/reset', params);
|
||||
this.$q.notify({
|
||||
message: this.$t('weHaveSentEmailToRecover'),
|
||||
type: 'positive'
|
||||
});
|
||||
this.$router.push('/login');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en-US:
|
||||
user: User
|
||||
inputEmail: Input email
|
||||
rememberPassword: Rememeber password
|
||||
dontWorry: Don't worry!
|
||||
fillData: Fill the data
|
||||
weSendEmail: We will sent you an email to recover your password
|
||||
weHaveSentEmailToRecover: We've sent you an email where you can recover your password
|
||||
send: Send
|
||||
return: Return
|
||||
es-ES:
|
||||
user: Usuario
|
||||
inputEmail: Introduce el correo electrónico
|
||||
rememberPassword: Recordar contraseña
|
||||
dontWorry: ¡No te preocupes!
|
||||
fillData: Rellena los datos
|
||||
weSendEmail: Te enviaremos un correo para restablecer tu contraseña
|
||||
weHaveSentEmailToRecover: Te hemos enviado un correo donde podrás recuperar tu contraseña
|
||||
send: Enviar
|
||||
return: Volver
|
||||
</i18n>
|
|
@ -39,10 +39,11 @@ export default route(function (/* { store, ssrContext } */) {
|
|||
|
||||
Router.beforeEach((to, from, next) => {
|
||||
const userStore = useUserStore();
|
||||
const allowedRoutes = ['login', 'recoverPassword'];
|
||||
|
||||
if (
|
||||
!userStore.storage.getItem('token') &&
|
||||
to.name !== 'login' &&
|
||||
!allowedRoutes.includes(to.name) &&
|
||||
!userStore.isGuest
|
||||
) {
|
||||
return next({ name: 'login' });
|
||||
|
|
|
@ -5,17 +5,17 @@ const routes = [
|
|||
children: [
|
||||
{
|
||||
name: 'login',
|
||||
path: '/login/:email?',
|
||||
path: '',
|
||||
component: () => import('pages/Login/LoginView.vue')
|
||||
},
|
||||
{
|
||||
name: 'rememberPassword',
|
||||
path: '/remember-password',
|
||||
component: () => import('pages/Login/RememberPassword.vue')
|
||||
name: 'recoverPassword',
|
||||
path: 'recover',
|
||||
component: () => import('pages/Login/RecoverPassword.vue')
|
||||
},
|
||||
{
|
||||
name: 'resetPassword',
|
||||
path: '/reset-password',
|
||||
path: 'reset',
|
||||
component: () => import('pages/Login/ResetPassword.vue')
|
||||
}
|
||||
]
|
||||
|
|
|
@ -20,12 +20,7 @@ export const useAppStore = defineStore('hedera', {
|
|||
menuEssentialLinks: [],
|
||||
hiddenMenuLinks: new Set(['Reports']),
|
||||
basketOrderId: null,
|
||||
localeDates: {
|
||||
days: [],
|
||||
months: [],
|
||||
daysShort: [],
|
||||
monthsShort: []
|
||||
},
|
||||
|
||||
siteLang: null,
|
||||
localeOptions: [
|
||||
{ label: t('langs.en'), lang: 'en-US', value: 'en' },
|
||||
|
@ -66,20 +61,9 @@ export const useAppStore = defineStore('hedera', {
|
|||
this.$patch({ imageUrl });
|
||||
},
|
||||
|
||||
getLocaleDates() {
|
||||
const { messages, locale } = i18n.global;
|
||||
this.localeDates = {
|
||||
days: messages.value[locale.value].date.days,
|
||||
months: messages.value[locale.value].date.months,
|
||||
daysShort: messages.value[locale.value].date.daysShort,
|
||||
monthsShort: messages.value[locale.value].date.monthsShort
|
||||
};
|
||||
},
|
||||
|
||||
async init() {
|
||||
this.updateSiteLocale(localStorage.getItem('siteLang') || 'es-ES');
|
||||
this.getBasketOrderId();
|
||||
this.getLocaleDates();
|
||||
},
|
||||
|
||||
getBasketOrderId() {
|
||||
|
@ -187,6 +171,12 @@ export const useAppStore = defineStore('hedera', {
|
|||
isDesktop() {
|
||||
const $q = useQuasar();
|
||||
return $q?.screen?.width > 1024;
|
||||
},
|
||||
localeDates() {
|
||||
const { messages, locale } = i18n.global;
|
||||
const { days, months, daysShort, monthsShort } =
|
||||
jsegarra marked this conversation as resolved
Outdated
jsegarra
commented
dale una vuelta porque esto se puede simplificar mucho dale una vuelta porque esto se puede simplificar mucho
|
||||
messages.value[locale.value].date;
|
||||
return { days, months, daysShort, monthsShort };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
describe('Login Tests', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/#/login');
|
||||
});
|
||||
|
||||
it('should ssend recover email', () => {
|
||||
cy.dataCy('recoverPasswordViewLink').should('exist');
|
||||
cy.dataCy('recoverPasswordViewLink').click();
|
||||
cy.dataCy('recoverPasswordUserInput').find('input').should('exist');
|
||||
cy.dataCy('recoverPasswordUserInput').find('input').type('developer');
|
||||
cy.dataCy('recoverPasswordSubmitButton').should('exist');
|
||||
cy.dataCy('recoverPasswordSubmitButton').click();
|
||||
cy.checkNotify(
|
||||
'positive',
|
||||
'Te hemos enviado un correo donde podrás recuperar tu contraseña'
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
En lilium hemos quitado los trycatch porque si la peticion falla, el usuario no se entera.
Ya tenemos el controlador de axios, no?
Si, tenemos el controlador de axios