From bd1e9b7037ca6d61ad86dd2a0a1f3a660416dd7f Mon Sep 17 00:00:00 2001 From: wbuezas Date: Tue, 4 Mar 2025 10:32:12 -0300 Subject: [PATCH 1/5] Address views refactor --- quasar.config.js | 2 +- src/boot/error-handler.js | 66 ----- src/components/common/FormModel.vue | 232 ++++++++++++++++++ src/pages/Account/AddressDetails.vue | 92 ++++--- src/pages/Account/AddressList.vue | 20 +- .../integration/config/AddresList.spec.js | 26 +- .../integration/{ => flows}/UserFlows.spec.js | 0 src/test/cypress/support/commands.js | 3 +- 8 files changed, 322 insertions(+), 119 deletions(-) delete mode 100644 src/boot/error-handler.js create mode 100644 src/components/common/FormModel.vue rename src/test/cypress/integration/{ => flows}/UserFlows.spec.js (100%) diff --git a/quasar.config.js b/quasar.config.js index 84e934d1..a9d50c62 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -23,7 +23,7 @@ module.exports = configure(function (ctx) { // app boot file (/src/boot) // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli-webpack/boot-files - boot: ['i18n', 'axios', 'vnDate', 'error-handler', 'app'], + boot: ['i18n', 'axios', 'vnDate', 'app'], // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css css: ['app.scss', 'width.scss', 'responsive.scss'], diff --git a/src/boot/error-handler.js b/src/boot/error-handler.js deleted file mode 100644 index 1e11c952..00000000 --- a/src/boot/error-handler.js +++ /dev/null @@ -1,66 +0,0 @@ -export default async ({ app }) => { - /* - window.addEventListener('error', - e => onWindowError(e)); - window.addEventListener('unhandledrejection', - e => onWindowRejection(e)); - - ,onWindowError(event) { - errorHandler(event.error); - } - ,onWindowRejection(event) { - errorHandler(event.reason); - } -*/ - app.config.errorHandler = (err, vm, info) => { - errorHandler(err, vm) - } - - function errorHandler (err, vm) { - let message - let tMessage - let res = err.response - - // XXX: Compatibility with old JSON service - if (err.name === 'JsonException') { - res = { - status: err.statusCode, - data: { error: { message: err.message } } - } - } - - if (res) { - const status = res.status - - if (status >= 400 && status < 500) { - switch (status) { - case 401: - tMessage = 'loginFailed' - break - case 403: - tMessage = 'authenticationRequired' - vm.$router.push('/login') - break - case 404: - tMessage = 'notFound' - break - default: - message = res.data.error.message - } - } else if (status >= 500) { - tMessage = 'internalServerError' - } - } else { - tMessage = 'somethingWentWrong' - console.error(err) - } - - if (tMessage) { - message = vm.$t(tMessage) - } - vm.$q.notify({ - message, - type: 'negative' - }) - } -} diff --git a/src/components/common/FormModel.vue b/src/components/common/FormModel.vue new file mode 100644 index 00000000..7f0bfa2e --- /dev/null +++ b/src/components/common/FormModel.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/pages/Account/AddressDetails.vue b/src/pages/Account/AddressDetails.vue index 9e4210d7..31a372d7 100644 --- a/src/pages/Account/AddressDetails.vue +++ b/src/pages/Account/AddressDetails.vue @@ -5,32 +5,26 @@ import { useRouter, useRoute } from 'vue-router'; 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 FormModel from 'src/components/common/FormModel.vue'; +import { useUserStore } from 'stores/user'; import { useAppStore } from 'stores/app'; import { storeToRefs } from 'pinia'; const router = useRouter(); const route = useRoute(); const { t } = useI18n(); -const jApi = inject('jApi'); +const api = inject('api'); const appStore = useAppStore(); +const userStore = useUserStore(); const { isHeaderMounted } = storeToRefs(appStore); const vnFormRef = ref(null); const countriesOptions = ref([]); const provincesOptions = ref([]); -const pks = { id: route.params.id }; const isEditMode = route.params.id !== '0'; -const fetchAddressDataSql = { - query: ` - SELECT a.id, a.street, a.nickname, a.city, a.postalCode, a.provinceFk, p.countryFk - FROM myAddress a - LEFT JOIN vn.province p ON p.id = a.provinceFk - WHERE a.id = #address - `, - params: { address: route.params.id } -}; +const editAddressData = ref(null); +const showForm = ref(false); watch( () => vnFormRef?.value?.formData?.countryFk, @@ -40,23 +34,49 @@ watch( const goBack = () => router.push({ name: 'addressesList' }); const getCountries = async () => { - countriesOptions.value = await jApi.query( - `SELECT id, name FROM vn.country - ORDER BY name` - ); + const filter = { fields: ['id', 'name'], order: 'name' }; + const { data } = await api.get('Countries', { + params: { filter: JSON.stringify(filter) } + }); + countriesOptions.value = data; }; const getProvinces = async countryFk => { if (!countryFk) return; - provincesOptions.value = await jApi.query( - `SELECT id, name FROM vn.province - WHERE countryFk = #id - ORDER BY name`, - { id: countryFk } - ); + + const filter = { + where: { countryFk }, + fields: ['id', 'name'], + order: 'name' + }; + const { data } = await api.get('Provinces', { + params: { filter: JSON.stringify(filter) } + }); + provincesOptions.value = data; }; -onMounted(() => getCountries()); +const getAddressDetails = async () => { + const { data } = await api.get(`Addresses/${route.params.id}`); + if (!data) return; + + const { nickname, street, city, postalCode, province, provinceFk } = data; + editAddressData.value = { + nickname, + street, + city, + postalCode, + countryFk: province?.countryFk, + provinceFk + }; +}; + +onMounted(async () => { + if (isEditMode) { + await getAddressDetails(); + } + getCountries(); + showForm.value = true; +}); diff --git a/src/pages/Account/AddressList.vue b/src/pages/Account/AddressList.vue index a603715c..43fe2b0c 100644 --- a/src/pages/Account/AddressList.vue +++ b/src/pages/Account/AddressList.vue @@ -10,13 +10,16 @@ import useNotify from 'src/composables/useNotify.js'; import { useVnConfirm } from 'src/composables/useVnConfirm.js'; import { useAppStore } from 'stores/app'; import { storeToRefs } from 'pinia'; +import { useUserStore } from 'stores/user'; const router = useRouter(); const jApi = inject('jApi'); +const api = inject('api'); const { notify } = useNotify(); const { t } = useI18n(); const { openConfirmationModal } = useVnConfirm(); const appStore = useAppStore(); +const userStore = useUserStore(); const { isHeaderMounted } = storeToRefs(appStore); const addresses = ref([]); @@ -65,16 +68,13 @@ const changeDefaultAddress = async () => { notify(t('defaultAddressModified'), 'positive'); }; -const removeAddress = async id => { +async function removeAddress(address) { try { - await jApi.execQuery( - `START TRANSACTION; - UPDATE hedera.myAddress SET isActive = FALSE - WHERE ((id = #id)); - SELECT isActive FROM hedera.myAddress WHERE ((id = #id)); - COMMIT`, + await api.patch( + `/Clients/${userStore?.user?.id}/updateAddress/${address.id}`, { - id + ...address, + isActive: false } ); getActiveAddresses(); @@ -82,7 +82,7 @@ const removeAddress = async id => { } catch (error) { console.error('Error removing address:', error); } -}; +} onMounted(async () => { getDefaultAddress(); @@ -145,7 +145,7 @@ onMounted(async () => { openConfirmationModal( null, t('confirmDeleteAddress'), - () => removeAddress(address.id) + () => removeAddress(address) ) " > diff --git a/src/test/cypress/integration/config/AddresList.spec.js b/src/test/cypress/integration/config/AddresList.spec.js index d8a39271..6b5ad682 100644 --- a/src/test/cypress/integration/config/AddresList.spec.js +++ b/src/test/cypress/integration/config/AddresList.spec.js @@ -46,15 +46,28 @@ describe('PendingOrders', () => { .should('contain', data.postcode); }; + it('should fail if we enter a wrong postcode', () => { + cy.dataCy('newAddressBtn').should('exist'); + cy.dataCy('newAddressBtn').click(); + cy.dataCy('formModelDefaultSaveButton').should('exist'); + cy.dataCy('formModelDefaultSaveButton').should('be.disabled'); + const addressFormData = getRandomAddressFormData(); + fillFormWithData(addressFormData); + cy.dataCy('formModelDefaultSaveButton').should('not.be.disabled'); + cy.dataCy('formModelDefaultSaveButton').click(); + cy.checkNotify('negative'); + }); + it('should create a new address', () => { cy.dataCy('newAddressBtn').should('exist'); cy.dataCy('newAddressBtn').click(); - cy.dataCy('formDefaultSaveButton').should('exist'); - cy.dataCy('formDefaultSaveButton').should('be.disabled'); + cy.dataCy('formModelDefaultSaveButton').should('exist'); + cy.dataCy('formModelDefaultSaveButton').should('be.disabled'); const addressFormData = getRandomAddressFormData(); + addressFormData.postcode = '46460'; // Usamos un postcode válido fillFormWithData(addressFormData); - cy.dataCy('formDefaultSaveButton').should('not.be.disabled'); - cy.dataCy('formDefaultSaveButton').click(); + cy.dataCy('formModelDefaultSaveButton').should('not.be.disabled'); + cy.dataCy('formModelDefaultSaveButton').click(); cy.checkNotify('positive', 'Datos guardados'); verifyAddressCardData(addressFormData); }); @@ -71,9 +84,10 @@ describe('PendingOrders', () => { }); // Fill form with new data const addressFormData = getRandomAddressFormData(); + addressFormData.postcode = '46460'; // Usamos un postcode válido fillFormWithData(addressFormData); - cy.dataCy('formDefaultSaveButton').should('not.be.disabled'); - cy.dataCy('formDefaultSaveButton').click(); + cy.dataCy('formModelDefaultSaveButton').should('not.be.disabled'); + cy.dataCy('formModelDefaultSaveButton').click(); cy.checkNotify('positive', 'Datos guardados'); verifyAddressCardData(addressFormData); }); diff --git a/src/test/cypress/integration/UserFlows.spec.js b/src/test/cypress/integration/flows/UserFlows.spec.js similarity index 100% rename from src/test/cypress/integration/UserFlows.spec.js rename to src/test/cypress/integration/flows/UserFlows.spec.js diff --git a/src/test/cypress/support/commands.js b/src/test/cypress/support/commands.js index 2d4de848..cb22f86a 100644 --- a/src/test/cypress/support/commands.js +++ b/src/test/cypress/support/commands.js @@ -90,5 +90,6 @@ Cypress.Commands.add('setConfirmDialog', () => { }); Cypress.Commands.add('checkNotify', (status, content) => { - cy.dataCy(`${status}Notify`).should('contain', content); + if (content) cy.dataCy(`${status}Notify`).should('contain', content); + else cy.dataCy(`${status}Notify`).should('exist'); }); -- 2.40.1 From 97ec2fabc550e53acf71a3f3eafbde7d3d1bc085 Mon Sep 17 00:00:00 2001 From: wbuezas Date: Wed, 5 Mar 2025 15:37:35 -0300 Subject: [PATCH 2/5] Improvements --- package.json | 2 +- src/components/common/FormModel.vue | 12 +- src/components/common/VnInput.vue | 171 +++++++++++++++++++++------ src/components/common/VnSelect.vue | 2 +- src/composables/useRequired.js | 17 +++ src/i18n/ca-ES/index.js | 2 + src/i18n/en-US/index.js | 2 + src/i18n/es-ES/index.js | 2 + src/i18n/fr-FR/index.js | 2 + src/i18n/pt-PT/index.js | 2 + src/pages/Account/AddressDetails.vue | 6 + 11 files changed, 179 insertions(+), 41 deletions(-) create mode 100644 src/composables/useRequired.js diff --git a/package.json b/package.json index 64004f37..da3b8435 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "salix": "cd ../salix && gulp back", "db": "cd ../salix && gulp docker", "cy:open": "npm run db && cypress open", - "test:e2e": "npm run db && cypress run", + "test:e2e": "npm run db && cypress run --headed --config video=false", "test:unit": "vitest", "build": "rm -rf dist/ ; quasar build", "clean": "rm -rf dist/", diff --git a/src/components/common/FormModel.vue b/src/components/common/FormModel.vue index 7f0bfa2e..be7ae1c4 100644 --- a/src/components/common/FormModel.vue +++ b/src/components/common/FormModel.vue @@ -81,7 +81,7 @@ const { isHeaderMounted } = storeToRefs(appStore); const isLoading = ref(false); const formData = ref({}); -const addressFormRef = ref(null); +const formModelRef = ref(null); const hasChanges = ref(!props.observeFormChanges); const isResetting = ref(false); const originalData = ref(null); @@ -119,6 +119,7 @@ onMounted(async () => { ); } }); + async function fetch() { try { let { data } = await api.get(props.url, { @@ -133,9 +134,12 @@ async function fetch() { } async function submit() { - console.log('submit: '); if (props.observeFormChanges && !hasChanges.value) - return notify('globals.noChanges', 'negative'); + return notify('noChanges', 'negative'); + + const isValid = await formModelRef.value.validate(); + console.log('isValid', isValid); + if (!isValid) return; isLoading.value = true; try { @@ -167,7 +171,7 @@ defineExpose({ diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index 508ce132..7dcf1164 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -1,15 +1,20 @@