develop #8

Merged
jsolis merged 7 commits from develop into master 2024-03-14 11:54:32 +00:00
47 changed files with 3482 additions and 2994 deletions
Showing only changes of commit 5e6cddc076 - Show all commits

11
.vscode/settings.json vendored
View File

@ -23,7 +23,6 @@
"source.fixAll.eslint", "source.fixAll.eslint",
"source.fixAll.stylelint" "source.fixAll.stylelint"
], ],
"files.exclude": { "files.exclude": {
"**/.git": true, "**/.git": true,
"**/.svn": true, "**/.svn": true,
@ -61,11 +60,6 @@
"terminal.integrated.enableImages": true, "terminal.integrated.enableImages": true,
"figma.autocompleteBlocks": true, "figma.autocompleteBlocks": true,
"figma.assetExportDirectory": "src/assets", "figma.assetExportDirectory": "src/assets",
"editor.codeActionsOnSave": [
"source.addMissingImports",
"source.organizeImports",
"source.fixAll.eslint"
],
"gitlens.gitCommands.skipConfirmations": ["fetch:command", "switch:command"], "gitlens.gitCommands.skipConfirmations": ["fetch:command", "switch:command"],
"diffEditor.ignoreTrimWhitespace": false, "diffEditor.ignoreTrimWhitespace": false,
"svg.preview.mode": "svg", "svg.preview.mode": "svg",
@ -78,8 +72,5 @@
"workbench.tree.indent": 16, "workbench.tree.indent": 16,
"window.zoomLevel": -1, "window.zoomLevel": -1,
"git.ignoreRebaseWarning": true, "git.ignoreRebaseWarning": true,
"editor.largeFileOptimizations": false, "editor.largeFileOptimizations": false
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
} }

View File

@ -2,4 +2,8 @@ HOST="127.0.0.1"
DB_USER="root" DB_USER="root"
DB_PASSWORD="root" DB_PASSWORD="root"
PORT ="3306" PORT ="3306"
DATABASE="floranet" DATABASE="floranet"
BASE_URL =http://localhost:9100
CLIENT_ID="Ab5vEddhdvdJhLUkXtTiS2pe43W6PD1JNKns7XMnlw8FvC31H2VYakyVEHvuFBi2b543QIHiPh8j4FLF"
SECRET_KEY="EAxLf05kp08cvbLgZrqjwdx-NXnhQtnP4Y0B4LHAM_7T9-HOh4RaNTirinWfTV8GR6DJWg9djry5yHfO"

View File

@ -0,0 +1,146 @@
const db = require("../../db/db");
const paypal = require('paypal-rest-sdk');
const fs = require('fs');
const path = require('path');
class PaymentController {
async Create(req, res) {
//parâmetros para retornar os produtos que serão comprados
const products = req.body.products
//parameters to return price
const price = req.body.price
let productsIds = ''
for (let i = 0; i < products.length; i++) {
productsIds += `${products[i]}${i === products.length - 1 ? '' : '-'}`
}
//json for checkout
var payReq = JSON.stringify({
'intent': 'sale',
'redirect_urls': {
'return_url': `${process.env.BASE_URL}/checkout/success?productsIds=${productsIds}`,
'cancel_url': `${process.env.BASE_URL}/checkout/error`
},
'payer': {
'payment_method': 'paypal'
},
'transactions': [{
'amount': {
'total': price,
'currency': 'EUR'
},
'description': 'This is the payment transaction description.'
}]
});
//Starting checkout process and returning sandbox url
try {
let urlRedirect
urlRedirect = await new Promise(async (resolve, reject) => {
paypal.payment.create(payReq, function (error, payment) {
if (error) {
reject(error)
} else {
//capture HATEOAS links
var links = {};
payment.links.forEach(function (linkObj) {
links[linkObj.rel] = {
'href': linkObj.href,
'method': linkObj.method
};
})
//if redirect url present, redirect user
if (links.hasOwnProperty('approval_url')) {
resolve(links['approval_url'].href)
} else {
console.error('no redirect URI present');
}
}
});
}).then(res => res)
if (urlRedirect) {
return res.status(200).send({
data: urlRedirect
})
}
} catch (error) {
return res.status(422).send({
data: {
message: "Error when starting payment"
}
})
}
}
async Success(req, res) {
//Parameters for validating payment and purchased products
const paramns = JSON.parse(req.body.data)
const custumer = paramns.custumer
const productsIds = paramns.productsIds
const productsArray = productsIds.split('-')
const products = productsArray.map(Number)
const _products = await db.getProducts();
const productsFilter = _products[0].filter((item) => {
if (products.includes(item.id)) {
return item
}
});
const paymentId = paramns.paymentId;
const payerId = { 'payer_id': paramns.PayerID };
const jsonOrderData = JSON.stringify({
"paymentId": paymentId,
"custumer": custumer,
"products": productsFilter
})
fs.writeFileSync('order.json', jsonOrderData, 'utf-8')
const contentOrder = fs.readFileSync('order.json', 'utf-8');
//API validation and data
paypal.payment.execute(paymentId, payerId, async function (error, payment) {
if (error) {
console.log(error);
return res.status(422).send({
data: {
message: "payment not successful"
}
})
} else {
if (payment.state == 'approved') {
await db.orderData_put(contentOrder);
return res.status(200).send({
data: {
id: payment.id,
email: payment.payer.payer_info.email,
message: "payment completed successfully",
products: productsFilter
}
})
} else {
return res.status(422).send({
data: {
message: "payment not successful"
}
})
}
}
});
/* return res.status(200).send({
data: {
menssage: "sucesso"
}
}) */
}
Cancel(req, res) {
return res.status(200).send({
data: {
menssage: "cancelado"
}
})
}
}
module.exports = new PaymentController();

View File

@ -1,4 +1,4 @@
const db = require("../db/db"); const db = require("../../db/db");
const productsJson = require("./products.json") const productsJson = require("./products.json")
@ -7,28 +7,14 @@ class ProductController {
const params = req.query; const params = req.query;
const _products = await db.getProducts(params.dateExpired, params.postalCode); const _products = await db.getProducts(params.dateExpired, params.postalCode);
let productsFilter = _products[0]; let productsFilter = _products[0]
if (Number(params.recommend)) { if (Number(params.recommend)) {
productsFilter = productsFilter.filter(item => item.recommend == Number(params.recommend)) productsFilter = productsFilter.filter(item => item.recommend == params.recommend)
} }
if (params.type) { if (params.type) {
productsFilter = productsFilter.filter(item => item.type === params.type) productsFilter = productsFilter.filter(item => item.type === params.type)
} }
/*if (params.postalCode) {
productsFilter = productsFilter.filter(item => item.postalCode === params.postalCode)
}
if (params.dateExpired) {
const dateSearch = new Date(params.dateExpired);
productsFilter = productsFilter.filter(item => {
const dateProduct = new Date(item.dateExpired);
if (dateProduct >= dateSearch) {
return item
}
})
}*/
console.log(productsFilter.length);
if (params.minPrice && !params.maxPrice) { if (params.minPrice && !params.maxPrice) {
productsFilter = productsFilter.filter(item => { productsFilter = productsFilter.filter(item => {
@ -105,6 +91,7 @@ class ProductController {
products: products products: products
}) */ }) */
return res.status(200).send({ return res.status(200).send({
data: productsFilter data: productsFilter
}) })

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
const db = require("../../db/db");
class ProvincesController {
async findAll(req, res) {
const params = req.query;
const tmpProvinces = await db.getProvinces();
let provinces = [];
tmpProvinces.forEach(element => {
provinces = [...provinces,{
code: element.id,
name: element.name
}];
})
return res.status(200).send({
data: provinces
})
}
}
module.exports = new ProvincesController();

View File

@ -12,17 +12,47 @@ async function connect() {
const mysql = require("mysql2/promise"); const mysql = require("mysql2/promise");
const connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + ""); const connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + "");
console.log("Connected to MySQL!");
global.connection = connection; global.connection = connection;
return connection; return connection;
} }
//Procedure for get products
async function getProducts(dateExpired, postalCode) { async function getProducts(dateExpired, postalCode) {
console.log("Query in table MySQL!");
const conn = await connect(); const conn = await connect();
const [rows] = await conn.query(`CALL catalogue_get("${dateExpired}", "${postalCode}")`); const [rows] = await conn.query(`CALL catalogue_get("${dateExpired}", "${postalCode}")`);
return rows; return rows;
} }
//Procedure for create transactions, do not carry out any manipulation at the bank
async function orderData_put(jsonOrderData) {
const conn = await connect();
console.log(jsonOrderData);
const [rows] = await conn.query(`CALL orderData_put(?)`, [jsonOrderData], (err, results) => {
if (err) {
console.error(err);
} else {
console.log('Result:', results);
}
});
return rows;
}
module.exports = { getProducts } //Procedure for get transactions, do not carry out any manipulation at the bank
async function orderData_get() {
const conn = await connect();
const [rows] = await conn.query(`CALL orderData_get()`);
return rows;
}
async function getProvinces() {
const conn = await connect();
const [rows] = await conn.query(`SELECT p.id, p.name, c.code, c.country
FROM vn.province p
JOIN vn.country c ON c.id = p.countryFk
WHERE c.country IN('España', 'Francia', 'Portugal')`);
return rows;
}
module.exports = { getProducts, orderData_get, orderData_put, getProvinces }

View File

@ -1,7 +1,16 @@
const cors = require('cors'); const cors = require('cors');
const express = require('express'); const express = require('express');
const path = require('path'); const path = require('path');
const productController = require('./controller/product.controller'); const paypal = require('paypal-rest-sdk');
const productController = require('./controller/product/product.controller');
const paymengtController = require('./controller/payment/payment.controller');
const provincesController = require('./controller/provinces/provinces.controller');
paypal.configure({
'mode': 'sandbox',
'client_id': process.env.CLIENT_ID,
'client_secret': process.env.SECRET_KEY
});
const app = express(); const app = express();
const port = 9999; const port = 9999;
@ -11,8 +20,16 @@ const corsOptions = {
origin: allowedOrigins, origin: allowedOrigins,
optionsSuccessStatus: 200, optionsSuccessStatus: 200,
}; };
app.use(cors(corsOptions)); app.use(cors(corsOptions));
app.use(express.json());
app.use(
express.urlencoded({
extended: true,
}),
);
app.get('/', (req, res) => { app.get('/', (req, res) => {
const indexPath = path.join(__dirname, './', 'index.html'); const indexPath = path.join(__dirname, './', 'index.html');
res.sendFile(indexPath); res.sendFile(indexPath);
@ -21,6 +38,10 @@ app.get('/', (req, res) => {
//Products //Products
app.get('/api/products', productController.findAll); app.get('/api/products', productController.findAll);
app.get('/api/products/:id', productController.findById); app.get('/api/products/:id', productController.findById);
app.post('/api/payment/', paymengtController.Create)
app.post('/api/payment/success', paymengtController.Success)
app.get('/api/payment/cancel', paymengtController.Cancel)
app.get('/api/provinces', provincesController.findAll)
app.listen(port, () => { app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`); console.log(`Server listening at http://localhost:${port}`);

View File

@ -8,8 +8,5 @@
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC"
"dependencies": {
"express": "^4.18.2"
}
} }

407
package-lock.json generated
View File

@ -14,7 +14,9 @@
"@vueuse/core": "^10.7.0", "@vueuse/core": "^10.7.0",
"axios": "^1.2.1", "axios": "^1.2.1",
"express": "^4.18.2", "express": "^4.18.2",
"fs": "^0.0.1-security",
"mysql2": "^3.7.0", "mysql2": "^3.7.0",
"paypal-rest-sdk": "^1.8.1",
"pinia": "^2.0.11", "pinia": "^2.0.11",
"quasar": "^2.6.0", "quasar": "^2.6.0",
"vee-validate": "^4.12.2", "vee-validate": "^4.12.2",
@ -54,9 +56,9 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.23.6", "version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@ -133,9 +135,9 @@
} }
}, },
"node_modules/@faker-js/faker": { "node_modules/@faker-js/faker": {
"version": "8.3.1", "version": "8.4.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.3.1.tgz", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.0.tgz",
"integrity": "sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw==", "integrity": "sha512-htW87352wzUCdX1jyUQocUcmAaFqcR/w082EC8iP/gtkF0K+aKcBp0hR5Arb7dzR8tQ1TrhE9DNa5EbJELm84w==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -149,13 +151,13 @@
} }
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.13", "version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@humanwhocodes/object-schema": "^2.0.1", "@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.1.1", "debug": "^4.3.1",
"minimatch": "^3.0.5" "minimatch": "^3.0.5"
}, },
"engines": { "engines": {
@ -176,9 +178,9 @@
} }
}, },
"node_modules/@humanwhocodes/object-schema": { "node_modules/@humanwhocodes/object-schema": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true "dev": true
}, },
"node_modules/@intlify/bundle-utils": { "node_modules/@intlify/bundle-utils": {
@ -206,12 +208,12 @@
} }
}, },
"node_modules/@intlify/core-base": { "node_modules/@intlify/core-base": {
"version": "9.9.0", "version": "9.9.1",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.9.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.9.1.tgz",
"integrity": "sha512-C7UXPymDIOlMGSNjAhNLtKgzITc/8BjINK5gNKXg8GiWCTwL6n3MWr55czksxn8RM5wTMz0qcLOFT+adtaVQaA==", "integrity": "sha512-qsV15dg7jNX2faBRyKMgZS8UcFJViWEUPLdzZ9UR0kQZpFVeIpc0AG7ZOfeP7pX2T9SQ5jSiorq/tii9nkkafA==",
"dependencies": { "dependencies": {
"@intlify/message-compiler": "9.9.0", "@intlify/message-compiler": "9.9.1",
"@intlify/shared": "9.9.0" "@intlify/shared": "9.9.1"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
@ -221,11 +223,11 @@
} }
}, },
"node_modules/@intlify/message-compiler": { "node_modules/@intlify/message-compiler": {
"version": "9.9.0", "version": "9.9.1",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.9.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.9.1.tgz",
"integrity": "sha512-yDU/jdUm9KuhEzYfS+wuyja209yXgdl1XFhMlKtXEgSFTxz4COZQCRXXbbH8JrAjMsaJ7bdoPSLsKlY6mXG2iA==", "integrity": "sha512-zTvP6X6HeumHOXuAE1CMMsV6tTX+opKMOxO1OHTCg5N5Sm/F7d8o2jdT6W6L5oHUsJ/vvkGefHIs7Q3hfowmsA==",
"dependencies": { "dependencies": {
"@intlify/shared": "9.9.0", "@intlify/shared": "9.9.1",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
"engines": { "engines": {
@ -236,9 +238,9 @@
} }
}, },
"node_modules/@intlify/shared": { "node_modules/@intlify/shared": {
"version": "9.9.0", "version": "9.9.1",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.9.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.9.1.tgz",
"integrity": "sha512-1ECUyAHRrzOJbOizyGufYP2yukqGrWXtkmTu4PcswVnWbkcjzk3YQGmJ0bLkM7JZ0ZYAaohLGdYvBYnTOGYJ9g==", "integrity": "sha512-b3Pta1nwkz5rGq434v0psHwEwHGy1pYCttfcM22IE//K9owbpkEvFptx9VcuRAxjQdrO2If249cmDDjBu5wMDA==",
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
}, },
@ -336,9 +338,9 @@
} }
}, },
"node_modules/@quasar/app-vite": { "node_modules/@quasar/app-vite": {
"version": "1.7.1", "version": "1.7.3",
"resolved": "https://registry.npmjs.org/@quasar/app-vite/-/app-vite-1.7.1.tgz", "resolved": "https://registry.npmjs.org/@quasar/app-vite/-/app-vite-1.7.3.tgz",
"integrity": "sha512-cs3ix7w8f7884JiTp3EW6auZ9R+Fg4qoPxEZ7VRGOrSsUg5oQtR/i91jeQk4Z96J/JUOqtcKqdqbzN4fzaFyIg==", "integrity": "sha512-pnDInCFP9M1d7lJzS8UkiFq8bGWdekLz8Gu+NLI9UAxruIM9QVlSD4hUmWptTQXaVEvYlDnqfW3LOr57B8eVtw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@quasar/render-ssr-error": "^1.0.3", "@quasar/render-ssr-error": "^1.0.3",
@ -579,9 +581,9 @@
} }
}, },
"node_modules/@types/express-serve-static-core": { "node_modules/@types/express-serve-static-core": {
"version": "4.17.41", "version": "4.17.42",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.42.tgz",
"integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", "integrity": "sha512-ckM3jm2bf/MfB3+spLPWYPUH573plBFwpOhqQ2WottxYV85j1HQFlxmnTq57X1yHY9awZPig06hL/cLMgNWHIQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
@ -600,9 +602,9 @@
} }
}, },
"node_modules/@types/filewriter": { "node_modules/@types/filewriter": {
"version": "0.0.32", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.32.tgz", "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz",
"integrity": "sha512-Kpi2GXQyYJdjL8mFclL1eDgihn1SIzorMZjD94kdPZh9E4VxGOeyjPxi5LpsM4Zku7P0reqegZTt2GxhmA9VBg==", "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==",
"dev": true "dev": true
}, },
"node_modules/@types/har-format": { "node_modules/@types/har-format": {
@ -624,9 +626,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.10.8", "version": "20.11.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.13.tgz",
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==", "integrity": "sha512-5G4zQwdiQBSWYTDAH1ctw2eidqdhMJaNsiIDKHFr55ihz5Trl2qqR8fdrT732yPBho5gkNxXm67OxWFBqX9aPg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
@ -677,12 +679,12 @@
"dev": true "dev": true
}, },
"node_modules/@vee-validate/zod": { "node_modules/@vee-validate/zod": {
"version": "4.12.4", "version": "4.12.5",
"resolved": "https://registry.npmjs.org/@vee-validate/zod/-/zod-4.12.4.tgz", "resolved": "https://registry.npmjs.org/@vee-validate/zod/-/zod-4.12.5.tgz",
"integrity": "sha512-iNFhkBfGkre2b+eBXgBpNlNVStxDrI59sJUbzBr01EjyTkFOUgc/0wPJrhY/kBp+0pnGzNi04jklJaKfNK2ibg==", "integrity": "sha512-hUjvXaa4HHvlZeosucViIDOUikQmyKaXXuL6P8LR1ETOUrBV6ntTsafJGvRYtwhXosoLYuolUD6Km737okK4Gg==",
"dependencies": { "dependencies": {
"type-fest": "^4.8.3", "type-fest": "^4.8.3",
"vee-validate": "4.12.4", "vee-validate": "4.12.5",
"zod": "^3.22.4" "zod": "^3.22.4"
} }
}, },
@ -709,49 +711,49 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz",
"integrity": "sha512-hhCaE3pTMrlIJK7M/o3Xf7HV8+JoNTGOQ/coWS+V+pH6QFFyqtoXqQzpqsNp7UK17xYKua/MBiKj4e1vgZOBYw==", "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.6",
"@vue/shared": "3.4.7", "@vue/shared": "3.4.15",
"entities": "^4.5.0", "entities": "^4.5.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz",
"integrity": "sha512-qDKBAIurCTub4n/6jDYkXwgsFuriqqmmLrIq1N2QDfYJA/mwiwvxi09OGn28g+uDdERX9NaKDLji0oTjE3sScg==", "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.4.7", "@vue/compiler-core": "3.4.15",
"@vue/shared": "3.4.7" "@vue/shared": "3.4.15"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz",
"integrity": "sha512-Gec6CLkReVswDYjQFq79O5rktri4R7TsD/VPCiUoJw40JhNNxaNJJa8mrQrWoJluW4ETy6QN0NUyC/JO77OCOw==", "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.6",
"@vue/compiler-core": "3.4.7", "@vue/compiler-core": "3.4.15",
"@vue/compiler-dom": "3.4.7", "@vue/compiler-dom": "3.4.15",
"@vue/compiler-ssr": "3.4.7", "@vue/compiler-ssr": "3.4.15",
"@vue/shared": "3.4.7", "@vue/shared": "3.4.15",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.5", "magic-string": "^0.30.5",
"postcss": "^8.4.32", "postcss": "^8.4.33",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz",
"integrity": "sha512-PvYeSOvnCkST5mGS0TLwEn5w+4GavtEn6adcq8AspbHaIr+mId5hp7cG3ASy3iy8b+LuXEG2/QaV/nj5BQ/Aww==", "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.7", "@vue/compiler-dom": "3.4.15",
"@vue/shared": "3.4.7" "@vue/shared": "3.4.15"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
@ -760,57 +762,57 @@
"integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz",
"integrity": "sha512-F539DO0ogH0+L8F9Pnw7cjqibcmSOh5UTk16u5f4MKQ8fraqepI9zdh+sozPX6VmEHOcjo8qw3Or9ZcFFw4SZA==", "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==",
"dependencies": { "dependencies": {
"@vue/shared": "3.4.7" "@vue/shared": "3.4.15"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz",
"integrity": "sha512-QMMsWRQaD3BpGyjjChthpl4Mji4Fjx1qfdufsXlDkKU3HV+hWNor2z+29F+E1MmVcP0ZfRZUfqYgtsQoL7IGwQ==", "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.4.7", "@vue/reactivity": "3.4.15",
"@vue/shared": "3.4.7" "@vue/shared": "3.4.15"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz",
"integrity": "sha512-XwegyUY1rw8zxsX1Z36vwYcqo+uOgih5ti7y9vx+pPFhNdSQmN4LqK2RmSeAJG1oKV8NqSUmjpv92f/x6h0SeQ==", "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==",
"dependencies": { "dependencies": {
"@vue/runtime-core": "3.4.7", "@vue/runtime-core": "3.4.15",
"@vue/shared": "3.4.7", "@vue/shared": "3.4.15",
"csstype": "^3.1.3" "csstype": "^3.1.3"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz",
"integrity": "sha512-3bWnYLEkLLhkDWqvNk7IvbQD4UcxvFKxELBiOO2iG3m6AniFIsBWfHOO5tLVQnjdWkODu4rq0GipmfEenVAK5Q==", "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.4.7", "@vue/compiler-ssr": "3.4.15",
"@vue/shared": "3.4.7" "@vue/shared": "3.4.15"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.4.7" "vue": "3.4.15"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.7.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-G+i4glX1dMJk88sbJEcQEGWRQnVm9eIY7CcQbO5dpdsD9SF8jka3Mr5OqZYGjczGN1+D6EUwdu6phcmcx9iuPA==" "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g=="
}, },
"node_modules/@vueuse/core": { "node_modules/@vueuse/core": {
"version": "10.7.1", "version": "10.7.2",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.1.tgz", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz",
"integrity": "sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==", "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==",
"dependencies": { "dependencies": {
"@types/web-bluetooth": "^0.0.20", "@types/web-bluetooth": "^0.0.20",
"@vueuse/metadata": "10.7.1", "@vueuse/metadata": "10.7.2",
"@vueuse/shared": "10.7.1", "@vueuse/shared": "10.7.2",
"vue-demi": ">=0.14.6" "vue-demi": ">=0.14.6"
}, },
"funding": { "funding": {
@ -843,17 +845,17 @@
} }
}, },
"node_modules/@vueuse/metadata": { "node_modules/@vueuse/metadata": {
"version": "10.7.1", "version": "10.7.2",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.1.tgz", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz",
"integrity": "sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==", "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==",
"funding": { "funding": {
"url": "https://github.com/sponsors/antfu" "url": "https://github.com/sponsors/antfu"
} }
}, },
"node_modules/@vueuse/shared": { "node_modules/@vueuse/shared": {
"version": "10.7.1", "version": "10.7.2",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.1.tgz", "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz",
"integrity": "sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==", "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==",
"dependencies": { "dependencies": {
"vue-demi": ">=0.14.6" "vue-demi": ">=0.14.6"
}, },
@ -920,9 +922,9 @@
} }
}, },
"node_modules/acorn-walk": { "node_modules/acorn-walk": {
"version": "8.3.1", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
"integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=0.4.0" "node": ">=0.4.0"
@ -1109,9 +1111,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
}, },
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.16", "version": "10.4.17",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz",
"integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -1128,9 +1130,9 @@
} }
], ],
"dependencies": { "dependencies": {
"browserslist": "^4.21.10", "browserslist": "^4.22.2",
"caniuse-lite": "^1.0.30001538", "caniuse-lite": "^1.0.30001578",
"fraction.js": "^4.3.6", "fraction.js": "^4.3.7",
"normalize-range": "^0.1.2", "normalize-range": "^0.1.2",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"postcss-value-parser": "^4.2.0" "postcss-value-parser": "^4.2.0"
@ -1146,9 +1148,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.5", "version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.4", "follow-redirects": "^1.15.4",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -1286,9 +1288,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.22.2", "version": "4.22.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz",
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -1305,8 +1307,8 @@
} }
], ],
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001565", "caniuse-lite": "^1.0.30001580",
"electron-to-chromium": "^1.4.601", "electron-to-chromium": "^1.4.648",
"node-releases": "^2.0.14", "node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13" "update-browserslist-db": "^1.0.13"
}, },
@ -1345,7 +1347,6 @@
"version": "0.2.13", "version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"dev": true,
"engines": { "engines": {
"node": "*" "node": "*"
} }
@ -1392,9 +1393,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001576", "version": "1.0.30001581",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz",
"integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -1948,9 +1949,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.625", "version": "1.4.651",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.625.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.651.tgz",
"integrity": "sha512-DENMhh3MFgaPDoXWrVIqSPInQoLImywfCwrSmVl3cf9QHzoZSiutHwGaB/Ql3VkqcQV30rzgdM+BjKqBAJxo5Q==", "integrity": "sha512-jjks7Xx+4I7dslwsbaFocSwqBbGHQmuXBJUK9QBZTIrzPq3pzn6Uf2szFSP728FtLYE3ldiccmlkOM/zhGKCpA==",
"dev": true "dev": true
}, },
"node_modules/elementtree": { "node_modules/elementtree": {
@ -2461,9 +2462,9 @@
} }
}, },
"node_modules/eslint-plugin-vue": { "node_modules/eslint-plugin-vue": {
"version": "9.19.2", "version": "9.21.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.19.2.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.21.0.tgz",
"integrity": "sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA==", "integrity": "sha512-B3NgZRtbi9kSl7M0x/PqhSMk7ULJUwWxQpTvM8b2Z6gNTORK0YSt5v1vzwY84oMs/2+3BWH5XmTepaQebcJwfA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
@ -2471,7 +2472,7 @@
"nth-check": "^2.1.1", "nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.13", "postcss-selector-parser": "^6.0.13",
"semver": "^7.5.4", "semver": "^7.5.4",
"vue-eslint-parser": "^9.3.1", "vue-eslint-parser": "^9.4.2",
"xml-name-validator": "^4.0.0" "xml-name-validator": "^4.0.0"
}, },
"engines": { "engines": {
@ -2764,9 +2765,9 @@
"dev": true "dev": true
}, },
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.16.0", "version": "1.17.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz",
"integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
@ -2896,9 +2897,9 @@
"dev": true "dev": true
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.4", "version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -2956,6 +2957,11 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/fs": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
"integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
},
"node_modules/fs-constants": { "node_modules/fs-constants": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@ -3256,9 +3262,9 @@
} }
}, },
"node_modules/immutable": { "node_modules/immutable": {
"version": "4.3.4", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
"integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
"dev": true "dev": true
}, },
"node_modules/import-fresh": { "node_modules/import-fresh": {
@ -3859,15 +3865,11 @@
"dev": true "dev": true
}, },
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "6.0.0", "version": "8.0.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": { "engines": {
"node": ">=10" "node": ">=16.14"
} }
}, },
"node_modules/magic-string": { "node_modules/magic-string": {
@ -4076,9 +4078,9 @@
"dev": true "dev": true
}, },
"node_modules/mysql2": { "node_modules/mysql2": {
"version": "3.7.0", "version": "3.9.1",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.7.0.tgz", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.1.tgz",
"integrity": "sha512-c45jA3Jc1X8yJKzrWu1GpplBKGwv/wIV6ITZTlCSY7npF2YfJR+6nMP5e+NTQhUeJPSyOQAbGDCGEHbAl8HN9w==", "integrity": "sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==",
"dependencies": { "dependencies": {
"denque": "^2.1.0", "denque": "^2.1.0",
"generate-function": "^2.3.1", "generate-function": "^2.3.1",
@ -4104,14 +4106,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/mysql2/node_modules/lru-cache": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
"integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
"engines": {
"node": ">=16.14"
}
},
"node_modules/named-placeholders": { "node_modules/named-placeholders": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
@ -4431,6 +4425,26 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
}, },
"node_modules/paypal-rest-sdk": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/paypal-rest-sdk/-/paypal-rest-sdk-1.8.1.tgz",
"integrity": "sha512-Trj2GuPn10GqpICAxQh5wjxuDT7rq7DMOkvyatz05wI5xPGmqXN7UC0WfDSF9WSBs4YdcWZP0g+nY+sOdaFggw==",
"dependencies": {
"buffer-crc32": "^0.2.3",
"semver": "^5.0.3"
},
"engines": {
"node": ">= v0.6.0"
}
},
"node_modules/paypal-rest-sdk/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -4642,9 +4656,9 @@
} }
}, },
"node_modules/quasar": { "node_modules/quasar": {
"version": "2.14.2", "version": "2.14.3",
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.14.2.tgz", "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.14.3.tgz",
"integrity": "sha512-f5KliWtM5BEuFsDU4yvuP+dlVIWZNrGu5VpWFsxzjpoykcP4B2HIOUiCl3mx2NCqERHd4Ts0aeioRkt9TTeExA==", "integrity": "sha512-7WzbtZxykLn2ic5oNpepZ2ZjDVpmxyVD4KC9rWe+Ia+4Er61svGr4jOuttN+Ok7IerLSCmKIRyjQMgasF60rFA==",
"engines": { "engines": {
"node": ">= 10.18.1", "node": ">= 10.18.1",
"npm": ">= 6.13.4", "npm": ">= 6.13.4",
@ -4970,9 +4984,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.69.7", "version": "1.70.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz",
"integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
@ -5013,6 +5027,18 @@
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"dev": true "dev": true
}, },
"node_modules/semver/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/send": { "node_modules/send": {
"version": "0.18.0", "version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
@ -5089,14 +5115,15 @@
"dev": true "dev": true
}, },
"node_modules/set-function-length": { "node_modules/set-function-length": {
"version": "1.1.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz",
"integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==",
"dependencies": { "dependencies": {
"define-data-property": "^1.1.1", "define-data-property": "^1.1.1",
"get-intrinsic": "^1.2.1", "function-bind": "^1.1.2",
"get-intrinsic": "^1.2.2",
"gopd": "^1.0.1", "gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0" "has-property-descriptors": "^1.0.1"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -5478,9 +5505,9 @@
} }
}, },
"node_modules/type-fest": { "node_modules/type-fest": {
"version": "4.9.0", "version": "4.10.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.9.0.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz",
"integrity": "sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==", "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==",
"engines": { "engines": {
"node": ">=16" "node": ">=16"
}, },
@ -5622,9 +5649,9 @@
} }
}, },
"node_modules/vee-validate": { "node_modules/vee-validate": {
"version": "4.12.4", "version": "4.12.5",
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.12.4.tgz", "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.12.5.tgz",
"integrity": "sha512-rqSjMdl0l/RiGKywKhkXttUKwDlQOoxTxe31uMQiMlwK4Hbtlvr3OcQvpREp/qPTARxNKudKWCUVW/mfzuxUVQ==", "integrity": "sha512-rvaDfLPSLwTk+mf016XWE4drB8yXzOsKXiKHTb9gNXNLTtQSZ0Ww26O0/xbIFQe+n3+u8Wv1Y8uO/aLDX4fxOg==",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.5.1", "@vue/devtools-api": "^6.5.1",
"type-fest": "^4.8.3" "type-fest": "^4.8.3"
@ -5634,9 +5661,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "2.9.16", "version": "2.9.17",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.17.tgz",
"integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==", "integrity": "sha512-XxcRzra6d7xrKXH66jZUgb+srThoPu+TLJc06GifUyKq9JmjHkc1Numc8ra0h56rju2jfVWw3B3fs5l3OFMvUw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.14.27", "esbuild": "^0.14.27",
@ -5671,15 +5698,15 @@
} }
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.4.7", "version": "3.4.15",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.7.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz",
"integrity": "sha512-4urmkWpudekq0CPNMO7p6mBGa9qmTXwJMO2r6CT4EzIJVG7WoSReiysiNb7OSi/WI113oX0Srn9Rz1k/DCXKFQ==", "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.7", "@vue/compiler-dom": "3.4.15",
"@vue/compiler-sfc": "3.4.7", "@vue/compiler-sfc": "3.4.15",
"@vue/runtime-dom": "3.4.7", "@vue/runtime-dom": "3.4.15",
"@vue/server-renderer": "3.4.7", "@vue/server-renderer": "3.4.15",
"@vue/shared": "3.4.7" "@vue/shared": "3.4.15"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@ -5696,9 +5723,9 @@
"integrity": "sha512-4fdRMXO6FHzmE7H4soAph6QmPg3sL/RiGdd+axuxuU07f02LNMns0jMM88fmt1bvSbN+2Wyd8raho6p6nXUzag==" "integrity": "sha512-4fdRMXO6FHzmE7H4soAph6QmPg3sL/RiGdd+axuxuU07f02LNMns0jMM88fmt1bvSbN+2Wyd8raho6p6nXUzag=="
}, },
"node_modules/vue-eslint-parser": { "node_modules/vue-eslint-parser": {
"version": "9.4.0", "version": "9.4.2",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.0.tgz", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
"integrity": "sha512-7KsNBb6gHFA75BtneJsoK/dbZ281whUIwFYdQxA68QrCrGMXYzUMbPDHGcOQ0OocIVKrWSKWXZ4mL7tonCXoUw==", "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"debug": "^4.3.4", "debug": "^4.3.4",
@ -5720,12 +5747,12 @@
} }
}, },
"node_modules/vue-i18n": { "node_modules/vue-i18n": {
"version": "9.9.0", "version": "9.9.1",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.0.tgz", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.1.tgz",
"integrity": "sha512-xQ5SxszUAqK5n84N+uUyHH/PiQl9xZ24FOxyAaNonmOQgXeN+rD9z/6DStOpOxNFQn4Cgcquot05gZc+CdOujA==", "integrity": "sha512-xyQ4VspLdNSPTKBFBPWa1tvtj+9HuockZwgFeD2OhxxXuC2CWeNvV4seu2o9+vbQOyQbhAM5Ez56oxUrrnTWdw==",
"dependencies": { "dependencies": {
"@intlify/core-base": "9.9.0", "@intlify/core-base": "9.9.1",
"@intlify/shared": "9.9.0", "@intlify/shared": "9.9.1",
"@vue/devtools-api": "^6.5.0" "@vue/devtools-api": "^6.5.0"
}, },
"engines": { "engines": {

View File

@ -2,7 +2,7 @@
"name": "floranet", "name": "floranet",
"version": "0.0.1", "version": "0.0.1",
"description": "A floranet app", "description": "A floranet app",
"productName": "Floranet App", "productName": "Floranet",
"author": "user", "author": "user",
"private": true, "private": true,
"scripts": { "scripts": {
@ -22,7 +22,9 @@
"@vueuse/core": "^10.7.0", "@vueuse/core": "^10.7.0",
"axios": "^1.2.1", "axios": "^1.2.1",
"express": "^4.18.2", "express": "^4.18.2",
"fs": "^0.0.1-security",
"mysql2": "^3.7.0", "mysql2": "^3.7.0",
"paypal-rest-sdk": "^1.8.1",
"pinia": "^2.0.11", "pinia": "^2.0.11",
"quasar": "^2.6.0", "quasar": "^2.6.0",
"vee-validate": "^4.12.2", "vee-validate": "^4.12.2",

View File

@ -1,8 +1,10 @@
<script> <script>
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { defineComponent, onBeforeMount, ref, watch } from "vue";
import { fullCurrentDate } from "src/constants/date"; import { fullCurrentDate } from "src/constants/date";
import { invertDate } from "src/functions/invertDate";
import { useFormStore } from "src/stores/forms"; import { useFormStore } from "src/stores/forms";
import { defineComponent, ref } from "vue";
import IconCalendar from "../icons/IconCalendar.vue"; import IconCalendar from "../icons/IconCalendar.vue";
export default defineComponent({ export default defineComponent({
@ -19,27 +21,45 @@ export default defineComponent({
const formStore = useFormStore(); const formStore = useFormStore();
const { availability } = storeToRefs(formStore); const { availability } = storeToRefs(formStore);
const proxyDate = ref(fullCurrentDate); const [year, month, day] = fullCurrentDate.replaceAll("/", "-").split("-");
const currentDate = `${day}-${month}-${year}`;
const proxyDate = ref(invertDate(currentDate));
function updateProxy() { function updateProxy() {
proxyDate.value = fullCurrentDate; proxyDate.value = invertDate(currentDate);
} }
function optionsValidDates(date) { function optionsValidDates(date) {
return date >= fullCurrentDate; return date >= fullCurrentDate;
} }
function save() { onBeforeMount(() => {
availability.value.date = proxyDate.value; setValues({ date: invertDate(proxyDate.value) });
setValues({ date: proxyDate.value }); });
}
watch(proxyDate, (newProxy) => {
setValues({ date: invertDate(newProxy) });
});
const locale = {
days: "Domingo_Lunes_Martes_Miércoles_Jueves_Viernes_Sábado".split("_"),
daysShort: "Dom_Lun_Mar_Mié_Jue_Vie_Sáb".split("_"),
months:
"Enero_Febrero_Marzo_Abril_Mayo_Junio_Julio_Agosto_Septiembre_Octubre_Noviembre_Diciembre".split(
"_"
),
monthsShort: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Sep_Oct_Nov_Dic".split("_"),
firstDayOfWeek: 1,
format24h: false,
pluralDay: "dias",
};
return { return {
availability, availability,
proxyDate, proxyDate,
locale,
updateProxy, updateProxy,
optionsValidDates, optionsValidDates,
save,
}; };
}, },
}); });
@ -58,19 +78,14 @@ export default defineComponent({
> >
<q-date <q-date
v-model="proxyDate" v-model="proxyDate"
v-bind="calendarAttrs"
:options="optionsValidDates" :options="optionsValidDates"
mask="DD-MM-YYYY" :locale="locale"
today-btn
mask="YYYY-MM-DD"
> >
<div class="row items-center justify-end q-gutter-sm"> <div class="row items-center justify-end q-gutter-sm">
<q-btn label="Cancel" color="primary" flat v-close-popup /> <q-btn label="Cancel" color="primary" flat v-close-popup />
<q-btn <q-btn label="OK" color="primary" flat v-close-popup />
label="OK"
color="primary"
flat
@click="save"
v-close-popup
/>
</div> </div>
</q-date> </q-date>
</q-popup-proxy> </q-popup-proxy>

View File

@ -1,62 +0,0 @@
<script>
import { toTypedSchema } from "@vee-validate/zod";
import { Field, useForm } from "vee-validate";
import { defineComponent, watch } from "vue";
import { quasarNotify } from "src/functions/quasarNotify";
import { useFormStore } from "src/stores/forms";
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema";
import IconPostalCode from "../icons/IconPostalCode.vue";
export default defineComponent({
name: "PostalCodeEx",
components: { IconPostalCode, Field },
setup() {
const formStore = useFormStore();
const validationSchema = toTypedSchema(
availabilitySchema.pick({ date: true })
);
const { errors, values, handleSubmit } = useForm({
validationSchema,
initialValues: {
date: "",
},
});
watch(values, (newValues) => {
formStore.$patch({
availability: {
postalCode: newValues.date,
},
});
});
watch(errors, (newErrors) => {
if (newErrors.date) {
quasarNotify({ message: newErrors.date, type: "erro" });
}
});
const onSubmit = handleSubmit(formStore.registerAvailability);
return {
postalCode,
postalCodeAttrs,
errors,
onBlur,
};
},
});
</script>
<template>
<div class="custom-input-el postal-code">
<IconPostalCode />
<div class="custom-block-content">
<p class="custom-head-paragraph">¿Dónde?</p>
<!-- <p class="custom-main-paragraph">código postal</p> -->
<Field />
</div>
</div>
</template>

View File

@ -1,62 +0,0 @@
<script>
import { toTypedSchema } from "@vee-validate/zod";
import { Field, useForm } from "vee-validate";
import { defineComponent, watch } from "vue";
import { quasarNotify } from "src/functions/quasarNotify";
import { useFormStore } from "src/stores/forms";
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema";
import IconPostalCode from "../icons/IconPostalCode.vue";
export default defineComponent({
name: "PostalCodeEx",
components: { IconPostalCode, Field },
setup() {
const formStore = useFormStore();
const validationSchema = toTypedSchema(
availabilitySchema.pick({ postalCode: true })
);
const { errors, values, handleSubmit } = useForm({
validationSchema,
initialValues: {
postalCode: "",
},
});
watch(values, (newValues) => {
formStore.$patch({
availability: {
postalCode: newValues.postalCode,
},
});
});
watch(errors, (newErrors) => {
if (newErrors.postalCode) {
quasarNotify({ message: newErrors.postalCode, type: "erro" });
}
});
const onSubmit = handleSubmit(formStore.registerAvailability);
return {
postalCode,
postalCodeAttrs,
errors,
onBlur,
};
},
});
</script>
<template>
<div class="custom-input-el postal-code">
<IconPostalCode />
<div class="custom-block-content">
<p class="custom-head-paragraph">¿Dónde?</p>
<!-- <p class="custom-main-paragraph">código postal</p> -->
<Field />
</div>
</div>
</template>

View File

@ -1,47 +1,13 @@
<script> <script>
import { toTypedSchema } from "@vee-validate/zod"; import { defineComponent } from "vue";
import { storeToRefs } from "pinia";
import { useForm } from "vee-validate";
import { defineComponent, watch } from "vue";
import { quasarNotify } from "src/functions/quasarNotify";
import { useFormStore } from "src/stores/forms";
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema";
import IconPostalCode from "../icons/IconPostalCode.vue"; import IconPostalCode from "../icons/IconPostalCode.vue";
export default defineComponent({ export default defineComponent({
name: "postal-code", name: "postal-code",
components: { IconPostalCode }, components: { IconPostalCode },
setup() { setup() {
const formStore = useFormStore(); return {};
const { availability } = storeToRefs(formStore);
const validationSchema = toTypedSchema(
availabilitySchema.pick({ postalCode: true }).partial()
);
const { errors, defineField, values } = useForm({
validationSchema,
initialValues: {
postalCode: availability.value.postalCode,
},
});
const [postalCode, postalCodeAttrs] = defineField("postalCode");
const onBlur = () => {
availability.value.postalCode = postalCode.value;
};
availability.value.postalCode = values.postalCode;
watch(errors, (newErrors) => {
if (newErrors.postalCode) {
quasarNotify({ message: newErrors.postalCode, type: "erro" });
}
});
return {
postalCode,
postalCodeAttrs,
errors,
onBlur,
};
}, },
}); });
</script> </script>
@ -54,17 +20,6 @@ export default defineComponent({
<p class="custom-head-paragraph">¿Dónde?</p> <p class="custom-head-paragraph">¿Dónde?</p>
<slot></slot> <slot></slot>
<!-- <q-input
borderless
class="custom-main-paragraph"
v-model="postalCode"
v-bind="postalCodeAttrs"
:error="!!errors.postalCode"
placeholder="código postal"
mask="#####"
@blur="onBlur"
dense
/> -->
</div> </div>
</div> </div>
</template> </template>

View File

@ -10,7 +10,7 @@ export default defineComponent({
const formStore = useFormStore(); const formStore = useFormStore();
const { sortProductFilters } = storeToRefs(formStore); const { sortProductFilters } = storeToRefs(formStore);
async function handleOrder(order) { function handleOrder(order) {
sortProductFilters.value.order = order; sortProductFilters.value.order = order;
sortProductFilters.value.isOpenOrderFilter = false; sortProductFilters.value.isOpenOrderFilter = false;
} }

View File

@ -29,9 +29,9 @@ export default defineComponent({
:class="isOpenNav && 'mobile-nav'" :class="isOpenNav && 'mobile-nav'"
> >
<send-banner <send-banner
left-text="ENVÍO GRATIS a partir de 60€ | Compra el sábado hasta 14h y entrega el domingo" left-text="ENVÍO GRATIS"
right-text="Envíos 24-48 h a toda España, Portugal y sur de Francia" right-text="Envíos 24-48 h a toda España, Portugal y sur de Francia"
mobile-text="ENVÍO GRATIS a partir de 60€" mobile-text="ENVÍO GRATIS"
v-if="!isOpenNav" v-if="!isOpenNav"
/> />

View File

@ -24,9 +24,9 @@ export default defineComponent({
<template> <template>
<q-header class="header-container transparent"> <q-header class="header-container transparent">
<send-banner <send-banner
left-text="ENVÍO GRATIS a partir de 60€ | Compra el sábado hasta 14h y entrega el domingo" left-text="ENVÍO GRATIS"
right-text="Envíos 24-48 h a toda España, Portugal y sur de Francia" right-text="Envíos 24-48 h a toda España, Portugal y sur de Francia"
mobile-text="ENVÍO GRATIS a partir de 60€" mobile-text="ENVÍO GRATIS"
class="remove-mobile" class="remove-mobile"
/> />

View File

@ -1,10 +1,11 @@
<script> <script>
import { defineComponent } from "vue"; import { computed, defineComponent } from "vue";
import IconCart from "components/icons/IconCart.vue"; import IconCart from "components/icons/IconCart.vue";
import IconHamburger from "components/icons/IconHamburger.vue"; import IconHamburger from "components/icons/IconHamburger.vue";
import { useLocalStorage } from "src/hooks/useLocalStorage"; import { storeToRefs } from "pinia";
import { useCartStore } from "src/stores/cart";
import { useMobileStore } from "stores/mobileNav"; import { useMobileStore } from "stores/mobileNav";
export default defineComponent({ export default defineComponent({
@ -14,14 +15,15 @@ export default defineComponent({
IconHamburger, IconHamburger,
}, },
setup() { setup() {
const { getItem } = useLocalStorage(); const cartStore = useCartStore();
const { cart } = storeToRefs(cartStore);
const mobileStore = useMobileStore(); const mobileStore = useMobileStore();
const { handleOpenMobileNav } = mobileStore; const { handleOpenMobileNav } = mobileStore;
const cartLength = getItem("cart").length; const currentLength = computed(() => cart.value.length);
return { handleOpenMobileNav, cartLength }; return { handleOpenMobileNav, currentLength };
}, },
}); });
</script> </script>
@ -38,11 +40,11 @@ export default defineComponent({
<RouterLink <RouterLink
class="user-area-link cart" class="user-area-link cart"
to="/checkout" to="/checkout"
v-if="cartLength > 0" v-if="currentLength > 0"
> >
<icon-cart /> <icon-cart />
<span class="cart-count" :class="cartLength > 0 && 'active'"> <span class="cart-count" :class="currentLength > 0 && 'active'">
{{ cartLength }} {{ currentLength }}
</span> </span>
</RouterLink> </RouterLink>

View File

@ -87,8 +87,7 @@ export default defineComponent({
Regala un verano lleno de flores y plantas Regala un verano lleno de flores y plantas
</h1> </h1>
<p class="carousel-header-paragraph"> <!-- <p class="carousel-header-paragraph"></p> -->
</p>
</header> </header>
<form @submit="onSubmit" class="carousel-content-body"> <form @submit="onSubmit" class="carousel-content-body">
@ -99,7 +98,7 @@ export default defineComponent({
class="custom-date-input" class="custom-date-input"
v-model="calendar" v-model="calendar"
v-bind="calendarAttrs" v-bind="calendarAttrs"
:error="!!errors.date" :error="false"
placeholder="Elige una fecha" placeholder="Elige una fecha"
mask="##/##/####" mask="##/##/####"
dense dense
@ -114,7 +113,7 @@ export default defineComponent({
class="custom-main-paragraph" class="custom-main-paragraph"
v-model="postalCode" v-model="postalCode"
v-bind="postalCodeAttrs" v-bind="postalCodeAttrs"
:error="!!errors.postalCode" :error="false"
placeholder="código postal" placeholder="código postal"
mask="#####" mask="#####"
dense dense

View File

@ -1,65 +0,0 @@
<template>
<StripeCheckout
ref="checkoutRef"
mode="payment"
:pk="pK"
:line-items="cartItems"
:success-url="successURL"
:cancel-url="cancelURL"
@loading="(v) => (loading = v)"
style="display: none"
/>
<slot></slot>
</template>
<script>
import { storeToRefs } from "pinia";
import { defineComponent, ref, toRefs } from "vue";
import { useCartStore } from "src/stores/cart";
import { onUpdated } from "vue";
export default defineComponent({
name: "StripeCheckoutComponent",
components: {},
props: {
submitLoading: {
type: Boolean,
default: false,
},
onSubmit: {
type: Function,
default: () => {},
},
cartItems: {
type: Array,
default: () => [],
},
},
setup({ submitLoading, cartItems }) {
const cartStore = useCartStore();
const { checkoutRef } = storeToRefs(cartStore);
const loading = toRefs(submitLoading);
const pK = ref(
"pk_test_51OZaJdIK1lTlG93d2y0B81n4XrjvjQwqfIUZ7ggb9wEBa1e4h34GlYFYPwjtGl3OUT7DJZlVNX9EMXaCdOBkIC3T007mLnfvCu"
);
onUpdated(() => {
console.log(checkoutRef.value);
console.log(cartItems);
});
return {
pK,
loading,
checkoutRef,
successURL: ref("/checkout/success"),
cancelURL: ref("/checkout/cancel"),
};
},
});
</script>
<style lang="scss"></style>

View File

@ -20,8 +20,6 @@ export default defineComponent({
const nextSwiperBtn = ref(null); const nextSwiperBtn = ref(null);
onMounted(() => { onMounted(() => {
// console.log('Montado!');
swiperContainer.value = swiperContainer.value =
document.querySelector("swiper-container").shadowRoot; document.querySelector("swiper-container").shadowRoot;
prevSwiperBtn.value = swiperContainer.value.querySelector( prevSwiperBtn.value = swiperContainer.value.querySelector(
@ -40,11 +38,9 @@ export default defineComponent({
return { return {
screenWidth, screenWidth,
handlePrev() { handlePrev() {
// console.log('Prev click');
prevSwiperBtn.value.click(); prevSwiperBtn.value.click();
}, },
handleNext() { handleNext() {
// console.log('Next click');
nextSwiperBtn.value.click(); nextSwiperBtn.value.click();
}, },
}; };

View File

@ -33,10 +33,7 @@ export default defineComponent({
type: String, type: String,
default: "", default: "",
}, },
isNew: { isNew: String,
type: Boolean,
default: false,
},
size: { size: {
type: String, type: String,
default: "md-card", default: "md-card",
@ -54,10 +51,8 @@ export default defineComponent({
const isLoaded = ref(false); const isLoaded = ref(false);
const isError = ref(false); const isError = ref(false);
const percent = +discount / 100; const percent = +discount / 100;
//const priceWithoutLetter = ~~price?.replaceAll("", "");
const priceWithoutLetter = price; const priceWithoutLetter = price;
const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent); const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent);
console.log(price);
const onLoad = () => { const onLoad = () => {
isLoaded.value = true; isLoaded.value = true;

View File

@ -6,9 +6,7 @@ export default defineComponent({
name: "chat-component", name: "chat-component",
components: { IconChat }, components: { IconChat },
setup() { setup() {
const handleClick = () => { const handleClick = () => {};
console.log("click");
};
return { handleClick }; return { handleClick };
}, },
}); });

View File

@ -18,7 +18,6 @@ export default defineComponent({
function closeNav() { function closeNav() {
isOpenNav.value = false; isOpenNav.value = false;
console.log("foi click");
} }
watch(isOpenNav, (newValue) => { watch(isOpenNav, (newValue) => {

View File

@ -129,7 +129,7 @@ export default defineComponent({
class="custom-date-input" class="custom-date-input"
v-model="calendar" v-model="calendar"
v-bind="calendarAttrs" v-bind="calendarAttrs"
:error="!!errors.date" :error="false"
placeholder="Elige una fecha" placeholder="Elige una fecha"
mask="##/##/####" mask="##/##/####"
dense dense
@ -142,7 +142,7 @@ export default defineComponent({
class="custom-main-paragraph" class="custom-main-paragraph"
v-model="postalCode" v-model="postalCode"
v-bind="postalCodeAttrs" v-bind="postalCodeAttrs"
:error="!!errors.postalCode" :error="false"
placeholder="código postal" placeholder="código postal"
mask="#####" mask="#####"
dense dense

View File

@ -30,7 +30,6 @@ export default defineComponent({
const [terms, termsAttrs] = defineField("terms"); const [terms, termsAttrs] = defineField("terms");
const onSubmit = handleSubmit((values) => { const onSubmit = handleSubmit((values) => {
console.log(values);
handleQuestionData(values); handleQuestionData(values);
handleReset(); handleReset();
if (!terms.value) { if (!terms.value) {

View File

@ -321,8 +321,29 @@ body {
margin-bottom: -14px; margin-bottom: -14px;
} }
//! QUASAR .error-message {
min-height: 525px !important;
color: $primary;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
& h1 {
font-size: $font-40;
}
@media only screen and (max-width: $med-md) {
min-height: 400px !important;
& h1 {
line-height: 1.2;
font-size: $font-28;
}
}
}
//! QUASAR
.q-virtual-scroll__content .q-item .q-item__label { .q-virtual-scroll__content .q-item .q-item__label {
font-family: $font-questrial; font-family: $font-questrial;
font-size: $font-14; font-size: $font-14;

View File

@ -2,21 +2,20 @@ import { toTypedSchema } from "@vee-validate/zod";
import { useForm } from "vee-validate"; import { useForm } from "vee-validate";
import { computed, reactive, ref } from "vue"; import { computed, reactive, ref } from "vue";
import { apiBack } from "src/boot/axios";
import { useFormStore } from "src/stores/forms"; import { useFormStore } from "src/stores/forms";
import { checkoutSchema } from "src/utils/zod/schemas"; import { checkoutSchema } from "src/utils/zod/schemas";
import { useRouter } from "vue-router";
import { useLocalStorage } from "./useLocalStorage"; import { useLocalStorage } from "./useLocalStorage";
export function useCheckoutForm() { export function useCheckoutForm() {
const { getItem } = useLocalStorage(); const { addItem, getItem, removeItem } = useLocalStorage();
const { push } = useRouter()
const formStore = useFormStore(); const formStore = useFormStore();
const { handleCheckoutData } = formStore; const { handleCheckoutData } = formStore;
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({ const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
validationSchema: toTypedSchema(checkoutSchema), validationSchema: toTypedSchema(checkoutSchema),
initialValues: { initialValues: {
paymentMethod: "stripe", paymentMethod: "paypal",
terms: false, terms: false,
}, },
}); });
@ -36,6 +35,11 @@ export function useCheckoutForm() {
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod"); const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
const [terms, termsAttrs] = defineField("terms"); const [terms, termsAttrs] = defineField("terms");
//TODO hacer el await de las provincias
//const provinceOptions = getProvinces();
const provinceOptions = ref([ const provinceOptions = ref([
{ code: "01", name: "Araba/Álava" }, { code: "01", name: "Araba/Álava" },
{ code: "02", name: "Albacete" }, { code: "02", name: "Albacete" },
@ -90,7 +94,6 @@ export function useCheckoutForm() {
{ code: "51", name: "Ceuta" }, { code: "51", name: "Ceuta" },
{ code: "52", name: "Melilla" }, { code: "52", name: "Melilla" },
]); ]);
const stepActive = reactive({ data: 1 }); const stepActive = reactive({ data: 1 });
const stepList = reactive({ const stepList = reactive({
data: [ data: [
@ -122,27 +125,90 @@ export function useCheckoutForm() {
return step; return step;
}); });
}); });
const isError = ref(false);
const onError = () => {
isError.value = true;
};
const handleClickStep = (value) => { const handleClickStep = (value) => {
stepActive["data"] = value; stepActive["data"] = value;
}; };
const checkoutBlock = ref(true); const checkoutBlock = ref(true);
const onSubmit = handleSubmit((values) => { const cart = getItem("cart");
handleCheckoutData(values); const availability = getItem("availability");
stepList.data[2].active = true; const totalPrice = computed(() => {
checkoutBlock.value = false; return cart?.reduce((acc, { price }) => {
resetForm(); if (price) {
//const priceWithoutLetter = price?.replace("€", "");
return +price + acc;
}
}, 0);
}); });
const cart = getItem("cart"); const isLoadingSubmit = ref(false);
const totalPrice = ref(0) const isErrorSubmit = ref(false);
totalPrice.value = cart?.reduce((acc, { price }) => {
if (price) { const onSuccess = async (values) => {
const priceWithoutLetter = price?.replace("€", ""); isLoadingSubmit.value = true;
return +priceWithoutLetter + acc; stepsFormated.value[1].active = true;
try {
const productsId = cart.map((item) => item.id);
const cartItensData = cart.map((item) => {
const { id, message, ...rest } = item;
if (message) {
return { id, message };
}
return { id, message: "" };
});
const deliveryData = {
customerData: {
custumerName: `${values.name} ${values.surname}`,
email: values.senderEmail,
custumerPhone: values.phone,
},
itemData: cartItensData,
deliveryData: {
dated: availability.dateExpired,
deliveryName: values.senderName,
address: values.address,
postalCode: availability.postalCode,
deliveryPhone: values.senderPhone,
deliveryMessage: values.senderNotes,
},
};
addItem("costumer", deliveryData);
const productData = {
products: productsId,
price: totalPrice.value.toFixed(2).toString(),
};
const {
data: { data },
} = await apiBack.post("payment", productData);
isLoadingSubmit.value = false;
location.href = data;
removeItem("cart");
removeItem("availability");
} catch (error) {
console.error(`FATAL ERROR ::: ${error}`);
isErrorSubmit.value = true;
isLoadingSubmit.value = false;
} finally {
handleCheckoutData(values);
resetForm();
} }
}, 0); };
const onSubmit = handleSubmit(onSuccess);
return { return {
handleClickStep, handleClickStep,
@ -152,11 +218,13 @@ export function useCheckoutForm() {
checkoutBlock, checkoutBlock,
cart, cart,
totalPrice, totalPrice,
isError,
onError,
formState: { formState: {
meta, meta,
errors, errors,
onSubmit, onSubmit,
submitLoading: ref(false), isLoadingSubmit,
}, },
fields: { fields: {
name, name,
@ -189,4 +257,4 @@ export function useCheckoutForm() {
termsAttrs, termsAttrs,
}, },
}; };
} }

View File

@ -1,22 +1,39 @@
import { LocalStorage } from "quasar"; import { LocalStorage } from "quasar";
export function useLocalStorage() { export function useLocalStorage() {
/**
* Adds an item to localStorage.
* @param {string} key - The key of the item to be added.
* @param {*} value - The value of the item to be added.
*/
const addItem = (key, value) => { const addItem = (key, value) => {
LocalStorage.set(`@${key}`, value); const stringifyValue = JSON.stringify(value);
LocalStorage.set(`@${key}`, stringifyValue);
}; };
/**
* Retrieves an item from the local storage based on the provided key.
*
* @param {string} key - The key of the item to retrieve.
* @returns {Object|Array} - The retrieved item from the local storage. If the key is "availability", it returns an object, otherwise it returns an array.
*/
const getItem = (key) => { const getItem = (key) => {
const data = JSON.parse(LocalStorage.getItem(`@${key}`)); const data = JSON.parse(LocalStorage.getItem(`@${key}`));
if (key === "availability") return data || {};
return (data || []); return data || [];
}; };
/**
* Remove an item from local storage.
*
* @param {string} key - The key of the item to remove.
*/
const removeItem = (key) => { const removeItem = (key) => {
LocalStorage.remove(`@${key}`); LocalStorage.remove(`@${key}`);
}; };
return { return {
addItem, addItem,
getItem, getItem,

View File

@ -1,7 +1,7 @@
import { toTypedSchema } from "@vee-validate/zod"; import { toTypedSchema } from "@vee-validate/zod";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { useForm } from "vee-validate"; import { useForm } from "vee-validate";
import { ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { invertDate } from "src/functions/invertDate"; import { invertDate } from "src/functions/invertDate";
@ -12,38 +12,45 @@ import { useModalStore } from "src/stores/modalStore";
import { useRangePriceStore } from "src/stores/rangePrice"; import { useRangePriceStore } from "src/stores/rangePrice";
import { availabilitySchema } from "src/utils/zod/schemas"; import { availabilitySchema } from "src/utils/zod/schemas";
import { rangePriceSchema } from "src/utils/zod/schemas/rangePriceSchema"; import { rangePriceSchema } from "src/utils/zod/schemas/rangePriceSchema";
import { useLocalStorage } from "./useLocalStorage";
/** /**
* Custom hook for managing the postal and calendar functionality. * Custom hook for managing postal calendar functionality.
* *
* @param {Object} options - The options for the hook. * @param {Object} options - The options for the hook.
* @param {string} options.modalItem - The modal item isOpenAvailability || isOpenFilters. * @param {string} options.modalItem - The modal item.
* @param {string} options.type - The type of the hook. home || product || availability || filter * @param {string} options.type - The type of the calendar.
* @returns {Object} - The hook functions and data. * @returns {Object} - The hook functions and properties.
*/ */
export function usePostalCalendar({ modalItem = "", type = "home" }) { export function usePostalCalendar({ modalItem = "", type = "home" }) {
const route = useRoute(); const route = useRoute();
const { push } = useRouter() const { push, go } = useRouter();
const { addItem, getItem, removeItem } = useLocalStorage();
const rangePriceStore = useRangePriceStore(); const rangePriceStore = useRangePriceStore();
const { rangeValue } = storeToRefs(rangePriceStore); const { rangeValue } = storeToRefs(rangePriceStore);
const modalStore = useModalStore(); const modalStore = useModalStore();
const { openModal } = modalStore
const formStore = useFormStore(); const formStore = useFormStore();
const { sortProductFilters } = storeToRefs(formStore); const { sortProductFilters, availability: availabilityForm } =
storeToRefs(formStore);
const cartStore = useCartStore(); const cartStore = useCartStore();
const { addToCart, getProducts } = cartStore; const { addToCart, getProducts } = cartStore;
const { products, homeSection } = storeToRefs(cartStore); const { products, cart } = storeToRefs(cartStore);
const min = 0; const min = 0;
const max = 200; const max = 200;
const category = ref(route.path.split("/")[2]) const category = ref(route.path.split("/")[2]);
const availability = ref(getItem("availability"));
const isAvailabilityEmpty = computed(() => {
return Object.keys(availability.value).length === 0;
});
const { handleSubmit, handleReset, defineField, errors, setValues } = useForm( const { handleSubmit, handleReset, defineField, errors, setValues } = useForm(
{ {
validateOnMount: false,
validationSchema: toTypedSchema( validationSchema: toTypedSchema(
type !== "filter" ? availabilitySchema : rangePriceSchema type !== "filter" ? availabilitySchema : rangePriceSchema
), ),
@ -55,129 +62,206 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
postalCode: "", postalCode: "",
date: "", date: "",
}, },
initialTouched: {
date: false,
postalCode: true,
},
} }
); );
const [calendar, calendarAttrs] = defineField("date");
const [postalCode, postalCodeAttrs] = defineField("postalCode"); const options = {
const [priceRange, priceRangeAttrs] = defineField("range"); validateOnBlur: false,
validateOnChange: false,
validateOnInput: false,
validateOnModelUpdate: false,
};
const [calendar, calendarAttrs] = defineField("date", options);
const [postalCode, postalCodeAttrs] = defineField("postalCode", options);
const [priceRange, priceRangeAttrs] = defineField("range", options);
const [dedication, dedicationAttrs] = defineField("dedication"); const [dedication, dedicationAttrs] = defineField("dedication");
watch(errors, (newErrors) => { watch(errors, (newErrors) => {
const errorsObj = { const hasErrors = {
postalCode: () => range: newErrors.range,
quasarNotify({ message: newErrors.postalCode, type: "erro" }), dedication: newErrors.dedication,
date: () => quasarNotify({ message: newErrors.date, type: "erro" }),
range: () => quasarNotify({ message: newErrors.range, type: "erro" }),
dedication: () =>
quasarNotify({ message: newErrors.dedication, type: "erro" }),
}; };
const keys = Object.keys(newErrors); for (const [field, hasError] of Object.entries(hasErrors)) {
keys.forEach((key) => { if (hasError) {
errorsObj[key](); quasarNotify({ message: newErrors[field], type: "erro" });
}); }
}
}); });
watch( watch([() => route.path, () => sortProductFilters.value], ([newPath]) => {
[() => route.path, () => sortProductFilters.value], const categoryPath = newPath.split("/")[2];
([newPath]) => { category.value = categoryPath;
const categoryPath = newPath.split("/")[2]; });
category.value = categoryPath;
}
);
const onSubmit = handleSubmit((values) => { const removeCart = () => {
const postalAndDateParams = { removeItem("cart");
postalCode: values.postalCode, cart.value = [];
dateExpired: invertDate(values.date), };
const onSuccess = async (values) => {
const handleAvailability = async () => {
addItem("availability", {
postalCode: values.postalCode,
dateExpired: invertDate(values.date),
});
removeCart();
availabilityForm.value.dateExpired = invertDate(values.date);
availabilityForm.value.postalCode = values.postalCode;
await getProducts({
postalCode: values.postalCode,
dateExpired: invertDate(values.date),
});
}; };
const categoryObj = { const handleHome = async () => {
plantas: "Floranet Plantas", console.log(type);
ramos: "Floranet Ramos",
};
const objVal = { addItem("availability", {
home: async () => { postalCode: values.postalCode,
console.log(type); dateExpired: invertDate(values.date),
});
availabilityForm.value.dateExpired = invertDate(values.date);
availabilityForm.value.postalCode = values.postalCode;
removeCart();
await getProducts( const callback = async () => {
{ await push("/categoria/all");
postalCode: values.postalCode, };
dateExpired: invertDate(values.date),
},
() => homeSection.value.scrollIntoView()
);
},
product: async () => {
console.log(type);
await getProducts(postalAndDateParams); await getProducts(
{
const hasProduct = products.value.data.some((item) => {
const date = new Date(item.dateExpired);
const day = date.getDate();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear();
const dateExpired = `${day}/${month}/${year}`;
const id = +route.path.split('/')[2];
return item.postalCode === values.postalCode && item.id === id && values.date <= dateExpired
});
if (!hasProduct) {
push('/categoria/ramos')
quasarNotify({ message: 'Seleccione una nueva fecha y un nuevo código postal.', type: 'warning' })
setTimeout(() => {
openModal({ modal: 'availability' })
}, 2000)
return
}
addToCart(products.value.current, dedication)
},
availability: async () => {
console.log(type);
await getProducts({
postalCode: values.postalCode, postalCode: values.postalCode,
dateExpired: invertDate(values.date), dateExpired: invertDate(values.date),
}); },
}, callback
filter: async () => { );
console.log(type);
rangeValue.value.max = values.range.max;
rangeValue.value.min = values.range.min;
const params = {
type: categoryObj[category.value],
minPrice: values.range.min,
maxPrice: values.range.max,
};
await getProducts(params);
},
default: () => {
console.error(
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
);
},
}; };
objVal[type]() || objVal["default"]();
const handleProduct = async () => {
console.log(type);
addItem("availability", {
postalCode: values.postalCode,
dateExpired: invertDate(values.date),
});
removeCart();
availabilityForm.value.dateExpired = invertDate(values.date);
availabilityForm.value.postalCode = values.postalCode;
await getProducts({
postalCode: values.postalCode,
dateExpired: invertDate(values.date),
});
const hasProduct = computed(() => {
return products.value.data.some((item) => {
const date = new Date(item.dateExpired);
const day = date.getDate();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const year = date.getFullYear();
const dateExpired = `${day}/${month}/${year}`;
const dateSelected = values.date.replaceAll("-", "/");
const id = +route.path.split("/")[2];
console.log(item.postalCode === values.postalCode);
console.log(item.id === id);
console.log(dateSelected <= dateExpired);
return (
item.postalCode === values.postalCode &&
item.id === id &&
dateSelected <= dateExpired
);
});
});
if (!hasProduct.value) {
quasarNotify({
message: "Código postal y fecha de caducidad añadidos con éxito",
type: "success",
});
return;
}
// go();
addToCart(products.value.current, dedication);
};
const handleFilter = async () => {
console.log(type);
rangeValue.value.max = values.range.max;
rangeValue.value.min = values.range.min;
const params = {
type: categoryObj[category.value],
minPrice: values.range.min,
maxPrice: values.range.max,
};
console.log(params);
if (category.value === "all") {
params.postalCode = availability.value.postalCode;
params.dateExpired = availability.value.dateExpired;
const { type, ...rest } = params;
await getProducts({ ...rest });
return;
}
getProducts(params);
};
const handleDefault = () => {
console.error(
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
);
};
const handlers = {
availability: handleAvailability,
home: handleHome,
product: handleProduct,
filter: handleFilter,
default: handleDefault,
};
const handler = handlers[type] || handlers.default;
await handler();
if (modalItem) { if (modalItem) {
modalStore[modalItem] = false; modalStore[modalItem] = false;
} }
handleReset(); handleReset();
}); };
const onError = ({ values, errors, results }) => {
const hasErrors = {
postalCode: !!errors.postalCode,
date: !!errors.date,
};
for (const [field, hasError] of Object.entries(hasErrors)) {
if (hasError) {
quasarNotify({ message: errors[field], type: "erro" });
}
}
};
const onSubmit = handleSubmit(onSuccess, onError);
return { return {
onSubmit, onSubmit,
setValues, setValues,
handleReset,
modalStore, modalStore,
isAvailabilityEmpty,
fields: { fields: {
calendar, calendar,
calendarAttrs, calendarAttrs,

View File

@ -1,51 +0,0 @@
import { storeToRefs } from "pinia";
import { useCartStore } from "src/stores/cart";
import { useModalStore } from "src/stores/modalStore";
import { watch } from "vue";
import { useRoute } from "vue-router";
import { usePostalCalendar } from "./usePostalCalendar";
export function useProductPage() {
const route = useRoute();
const {
fields: { dedication, dedicationAttrs },
} = usePostalCalendar({ modalItem: "isOpenAvailability" });
const modalStore = useModalStore();
const { openModal } = modalStore;
const cartStore = useCartStore();
const { getProduct, getProducts } = cartStore;
const { products, featuredProducts, addCartLoadingBtn } =
storeToRefs(cartStore);
watch(
() => products.value.current?.type,
(newCategory) => {
getProducts({
// type: newCategory,
});
}
);
watch(
() => route.params.id,
(newId) => {
getProduct(newId);
}
);
const checkImageValidity = (imageLink) => {
const validExtensions = [".jpg", ".jpeg", ".png"];
if (imageLink) {
const extension = imageLink.substring(imageLink.lastIndexOf("."));
return validExtensions.includes(extension.toLowerCase());
}
return true;
};
return { checkImageValidity, openModal }
}

View File

@ -1,6 +1,6 @@
<script> <script>
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { defineComponent, onBeforeMount, onUpdated, ref, watch } from "vue"; import { computed, defineComponent, onBeforeMount, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import SortSelect from "src/components/@inputs/SortSelect.vue"; import SortSelect from "src/components/@inputs/SortSelect.vue";
@ -13,6 +13,9 @@ import Card from "src/components/ui/Card.vue";
import Container from "src/components/ui/Container.vue"; import Container from "src/components/ui/Container.vue";
import Modal from "src/components/ui/Modal.vue"; import Modal from "src/components/ui/Modal.vue";
import { quasarNotify } from "src/functions/quasarNotify";
import { useLocalStorage } from "src/hooks/useLocalStorage";
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
import { useCartStore } from "src/stores/cart"; import { useCartStore } from "src/stores/cart";
import { useFormStore } from "src/stores/forms"; import { useFormStore } from "src/stores/forms";
import { useMobileStore } from "src/stores/mobileNav"; import { useMobileStore } from "src/stores/mobileNav";
@ -34,6 +37,10 @@ export default defineComponent({
setup() { setup() {
const route = useRoute(); const route = useRoute();
const { getItem } = useLocalStorage();
const { isAvailabilityEmpty } = usePostalCalendar({});
const mobileStore = useMobileStore(); const mobileStore = useMobileStore();
const { screenWidth } = storeToRefs(mobileStore); const { screenWidth } = storeToRefs(mobileStore);
@ -41,29 +48,19 @@ export default defineComponent({
const { openModal } = modalStore; const { openModal } = modalStore;
const formStore = useFormStore(); const formStore = useFormStore();
const { availability, sortProductFilters } = storeToRefs(formStore); const { sortProductFilters, availability } = storeToRefs(formStore);
const cartStore = useCartStore(); const cartStore = useCartStore();
const { products } = storeToRefs(cartStore); const { products } = storeToRefs(cartStore);
const { getProducts } = cartStore; const { getProducts } = cartStore;
const monthTest = ref("");
const isOpenOrder = ref(false); const isOpenOrder = ref(false);
const availabilityStoraged = ref(getItem("availability"));
const isNotAllCategory = computed(() => {
return route.path.split("/")[2] !== "all";
});
const datePostalCode = ref({});
const monthES = {
0: "Enero",
1: "Febrero",
2: "Marzo",
3: "Abril",
4: "Mayo",
5: "Junio",
6: "Julio",
7: "Agosto",
8: "Septiembre",
9: "Octubre",
10: "Noviembre",
11: "Diciembre",
};
const orderText = { const orderText = {
"lowest-price": "menor precio", "lowest-price": "menor precio",
"highest-price": "mayor precio", "highest-price": "mayor precio",
@ -74,12 +71,32 @@ export default defineComponent({
plantas: "Floranet Plantas", plantas: "Floranet Plantas",
ramos: "Floranet Ramos", ramos: "Floranet Ramos",
}; };
const dateExpiredMonth = computed(
() =>
availability.value.dateExpired?.split("-")[1] ||
availabilityStoraged.value.dateExpired?.split("-")[1]
);
const dateExpiredDay = computed(
() =>
availability.value.dateExpired?.split("-")[2] ||
availabilityStoraged.value.dateExpired?.split("-")[2]
);
watch(availability, (newDate) => { const months = {
const [_day, month, _year] = newDate.date.split("/"); "01": "Enero",
monthTest.value = monthES[+month - 1]; "02": "Febrero",
console.log(monthTest.value); "03": "Marzo",
}); "04": "Abril",
"05": "Mayo",
"06": "Junio",
"07": "Julio",
"08": "Agosto",
"09": "Septiembre",
10: "Octubre",
11: "Noviembre",
12: "Diciembre",
};
const currentMonth = months[dateExpiredMonth.value];
watch( watch(
[() => route.path, () => sortProductFilters.value.order], [() => route.path, () => sortProductFilters.value.order],
@ -87,14 +104,29 @@ export default defineComponent({
const categoryPath = newPath.split("/")[2]; const categoryPath = newPath.split("/")[2];
sortProductFilters.value.category = categoryPath; sortProductFilters.value.category = categoryPath;
const params = { const params = {};
type: categoryObj[categoryPath],
}; if (categoryPath !== "all") {
params.type = categoryObj[categoryPath];
}
if (categoryPath === "all") {
params.dateExpired = availabilityStoraged.value.dateExpired;
params.postalCode = availabilityStoraged.value.postalCode;
}
const paramsObj = { const paramsObj = {
"lowest-price": () => (params.lowPrice = 1), "lowest-price": () => {
"highest-price": () => (params.bigPrice = 1), params.lowPrice = 1;
latest: () => (params.isNew = 1), },
recommended: () => (params.recommend = 1), "highest-price": () => {
params.bigPrice = 1;
},
latest: () => {
params.isNew = 1;
},
recommended: () => {
params.recommend = 1;
},
}; };
if (newOrder) { if (newOrder) {
paramsObj[newOrder](); paramsObj[newOrder]();
@ -104,19 +136,35 @@ export default defineComponent({
} }
); );
watch(
() => route.path,
() => {
datePostalCode.value = isNotAllCategory.value
? availability.value
: availabilityStoraged.value;
}
);
onBeforeMount(async () => { onBeforeMount(async () => {
const categoryPath = route.path.split("/")[2]; const categoryPath = route.path.split("/")[2];
await getProducts({ if (categoryPath !== "all") {
type: categoryObj[categoryPath], await getProducts({
}); type: categoryObj[categoryPath],
}); });
datePostalCode.value = availability.value;
onUpdated(() => { return;
console.groupCollapsed("%c Updated!", "color: green;"); }
console.log(sortProductFilters.value);
console.log(availability.value); await getProducts(availabilityStoraged.value);
console.groupEnd(); datePostalCode.value = availabilityStoraged.value;
if (isAvailabilityEmpty.value) {
quasarNotify({
message: "Debes seleccionar una fecha y código postal",
type: "warning",
});
}
}); });
function openOrderFilter() { function openOrderFilter() {
@ -128,12 +176,17 @@ export default defineComponent({
openOrderFilter, openOrderFilter,
openModal, openModal,
sortProductFilters, sortProductFilters,
availability,
isOpenOrder, isOpenOrder,
screenWidth, screenWidth,
modalStore, modalStore,
orderText, orderText,
products, products,
isNotAllCategory,
availabilityStoraged,
currentMonth,
dateExpiredDay,
datePostalCode,
availability,
}; };
}, },
}); });
@ -144,12 +197,10 @@ export default defineComponent({
<section class="products-section"> <section class="products-section">
<header class="products-section-header"> <header class="products-section-header">
<Container> <Container>
<div class="product-header-content"> <div class="product-header-content" v-if="isNotAllCategory">
<h3 class="product-header-title subtitle"> <h3 class="product-header-title subtitle">
{{ sortProductFilters.category }} para obsequiar {{ sortProductFilters.category }} para obsequiar
</h3> </h3>
<p class="product-header-paragraph">
</p>
</div> </div>
<div class="product-header-filters"> <div class="product-header-filters">
@ -158,11 +209,15 @@ export default defineComponent({
<p class="filter-paragraph availability"> <p class="filter-paragraph availability">
Disponibilidad para: Disponibilidad para:
<span <span
v-if="availability.date && availability.postalCode" v-if="
datePostalCode.dateExpired && datePostalCode.postalCode
"
class="green-text" class="green-text"
> >
25 Julio en {{ dateExpiredDay }} {{ currentMonth }} en
{{ availability.postalCode.replace("-", "") }}</span {{
availability.postalCode || datePostalCode.postalCode
}}</span
> >
</p> </p>
@ -232,8 +287,8 @@ export default defineComponent({
</Container> </Container>
</div> </div>
<footer class="products-section-footer"> <footer class="products-section-footer" v-if="isNotAllCategory">
<RouterLink class="btn rounded outlined" to="/"> <RouterLink class="btn rounded outlined" to="/categoria/all">
Ver todos los diseños <IconArrowCircleFilledRight /> Ver todos los diseños <IconArrowCircleFilledRight />
</RouterLink> </RouterLink>
</footer> </footer>

View File

@ -0,0 +1,22 @@
<template>
<q-page class="checkout-error-page error-message">
<container>
<h1>¡Uy! Algo ha ido mal durante el proceso de compra.</h1>
</container>
</q-page>
</template>
<script>
import Container from "src/components/ui/Container.vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "CheckoutErrorPage",
components: { Container },
});
</script>
<style lang="scss" scoped>
.checkout-error-page {
}
</style>

View File

@ -20,7 +20,9 @@ export default defineComponent({
checkoutBlock, checkoutBlock,
cart, cart,
totalPrice, totalPrice,
formState: { errors, meta, onSubmit, submitLoading }, isError,
onError,
formState: { errors, meta, onSubmit, isLoadingSubmit },
fields: { fields: {
name, name,
nameAttrs, nameAttrs,
@ -57,11 +59,6 @@ export default defineComponent({
if (cart.length === 0) return push("/"); if (cart.length === 0) return push("/");
}); });
const isError = ref(false);
const onError = () => {
isError.value = true;
};
return { return {
handleClickStep, handleClickStep,
onSubmit, onSubmit,
@ -74,7 +71,7 @@ export default defineComponent({
stepList, stepList,
cart, cart,
step: ref(1), step: ref(1),
submitLoading, isLoadingSubmit,
successURL: ref(""), successURL: ref(""),
cancelURL: ref(""), cancelURL: ref(""),
meta, meta,
@ -439,10 +436,18 @@ export default defineComponent({
<q-radio <q-radio
v-model="paymentMethod" v-model="paymentMethod"
v-bind="paymentMethodAttrs" v-bind="paymentMethodAttrs"
val="stripe" val="paypal"
color="primary" color="primary"
> >
<p>Stripe <a href="#">¿Qué es Stripe?</a></p> <p>
Paypal
<!-- <a
href="https://www.paypal.com/br/digital-wallet/how-paypal-works"
target="_blank"
rel="noopener noreferrer"
>¿Qué es Paypal?</a
> -->
</p>
</q-radio> </q-radio>
</div> </div>
</div> </div>
@ -455,7 +460,13 @@ export default defineComponent({
</p> </p>
</q-checkbox> </q-checkbox>
<q-btn flat class="btn" type="submit" form="checkout-form"> <q-btn
flat
class="btn"
type="submit"
form="checkout-form"
:loading="isLoadingSubmit"
>
PROCEDER AL PAGO PROCEDER AL PAGO
</q-btn> </q-btn>
</div> </div>

View File

@ -0,0 +1,313 @@
<script>
import { apiBack } from "src/boot/axios";
import { useCheckoutForm } from "src/hooks/useCheckoutForm";
import { useLocalStorage } from "src/hooks/useLocalStorage";
import { defineComponent, onBeforeMount, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
export default defineComponent({
name: "CheckoutSuccessPage",
setup() {
const { query } = useRoute();
const { push } = useRouter();
const { getItem, removeItem } = useLocalStorage();
console.log(query);
const costumerData = getItem("costumer");
const productsPurchased = reactive({ data: [] });
const totalPrice = ref();
async function getSuccessData() {
try {
productsPurchased.data = await new Promise(async (resolve, reject) => {
try {
const {
data: { data },
} = await apiBack.post("payment/success", {
// params: query,
data: JSON.stringify({ customer: costumerData, ...query }),
});
resolve(data.products);
removeItem("costumer");
} catch (error) {
reject(error);
}
}).then((res) => res);
totalPrice.value = await productsPurchased.data.reduce(
(acc, { price }) => acc + Number(price),
0
);
} catch (error) {
console.error(`FATAL ERROR ::: ${error}`);
push("/checkout/error");
}
}
onBeforeMount(async () => {
const queryObj = {
paymentId: query.paymentId,
productsIds: query.productsIds,
PayerID: query.PayerID,
};
for (const [_, value] of Object.entries(queryObj)) {
if (!value) return push("/");
}
await getSuccessData();
console.log(productsPurchased.data);
});
const { isError, onError } = useCheckoutForm();
const steppers = [
{
value: 1,
name: "Paso 1",
description: "Datos de facturación",
active: true,
},
{
value: 2,
name: "Paso 2",
description: "Confirmación",
active: true,
},
{
value: 3,
name: "Paso 3",
description: "Pago",
active: true,
},
];
return { isError, onError, steppers, productsPurchased, totalPrice };
},
});
</script>
<template>
<q-page class="success-container">
<div class="checkout-steps">
<div
v-for="({ active, description, name, value }, i) in steppers"
class="step-item-container"
:key="i"
>
<div class="step-item">
<div class="circle-step-container">
<span class="border-step" :class="[i == 0 && 'transparent']" />
<div class="circle-step" :class="active && 'active'">
<span class="step-value">{{ value }}</span>
</div>
<span
class="border-step"
:class="[i == steppers.length - 1 && 'transparent']"
/>
</div>
<div class="step-content">
<div class="title">
<h4>{{ name }}</h4>
</div>
<div class="description">
<p>{{ description }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="checkout-success" id="success-block">
<h6 class="checkout-success-title green-text">
Has efectuado la siguiente compra
</h6>
<div class="checkout-success-body">
<div class="checkout-success-content">
<ul class="checkout-success-list">
<li
v-for="({ name, price, image }, index) in productsPurchased.data"
:key="index"
class="checkout-success-item"
>
<div class="checkout-item-content">
<div class="checkout-product-details">
<img
:src="isError ? '../assets/empty-img.jpg' : image"
:alt="name"
class="checkout-product-img"
@error="onError"
/>
<p class="checkout-product-title">
{{ name }}
</p>
</div>
<p class="checkout-product-price">{{ price }}</p>
</div>
</li>
</ul>
</div>
<footer class="checkout-success-footer">
<p class="checkout-success-paragraph">Total</p>
<p class="checkout-success-paragraph">{{ totalPrice }}</p>
</footer>
</div>
</div>
</q-page>
</template>
<style lang="scss" scoped>
.success-container {
display: flex;
flex-direction: column;
margin-top: 50px;
}
.checkout-steps {
display: flex;
justify-content: center;
align-items: center;
}
.step-item-container {
min-width: 200px;
}
.border-step {
width: 90px;
height: 1px;
background-color: $primary-dark;
}
.circle-step-container {
display: grid;
justify-content: center;
align-items: center;
grid-template-columns: 1fr auto 1fr;
}
.circle-step {
width: 56px;
height: 56px;
border: 1px solid $primary-dark;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
user-select: none;
.step-value {
font-family: $font-questrial;
color: $primary-dark;
font-size: 1.25rem;
}
&.active {
background-color: $primary-dark;
.step-value {
color: $white;
}
}
}
.step-content {
display: flex;
flex-direction: column;
align-items: center;
font-family: $font-questrial;
h4 {
font-size: 1rem;
font-weight: 700;
color: $text-default;
margin-top: 5px;
margin-bottom: 4px;
line-height: 1.3;
}
p {
font-size: 0.875rem;
color: $text-default;
font-family: $font-lora;
}
}
.checkout-success {
width: min(100%, 499px);
margin: 122px auto 0;
text-align: center;
& .checkout-success-title {
margin-bottom: 26px;
}
& .checkout-success-body {
& .checkout-success-content {
background-color: $secondary-5;
padding: 30px 46px 42px 38px;
border-radius: 5px 5px 0px 0px;
& .checkout-success-list {
display: flex;
flex-direction: column;
gap: 28px;
& .checkout-success-item {
display: flex;
flex: 1;
& .checkout-item-content {
display: flex;
justify-content: space-between;
flex: 1;
min-height: 61px;
& .checkout-product-details {
display: flex;
gap: 14px;
& .checkout-product-img {
object-fit: cover;
width: 54px;
height: 100%;
border-radius: 5px;
}
& .checkout-product-title {
font-size: $font-12;
line-height: 21px;
letter-spacing: 0.24px;
font-family: $font-questrial;
color: $text-default;
}
}
& .checkout-product-price {
color: $text-muted-one;
font-family: $font-roboto;
font-size: $font-12;
line-height: 21px;
letter-spacing: 0.24px;
}
}
}
}
@media only screen and (max-width: $med-lg) {
padding-right: 9px;
}
}
& .checkout-success-footer {
display: flex;
justify-content: space-between;
background-color: $secondary-40;
border-radius: 0px 0px 5px 5px;
padding: 14px 46px 7px 36px;
& .checkout-success-paragraph {
font-family: $font-lora;
letter-spacing: 0.32px;
line-height: 21px;
font-weight: 600;
color: $text-muted-one;
}
}
}
}
</style>

View File

@ -1,21 +0,0 @@
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "ExamplePage",
components: {},
setup() {
return {};
},
});
</script>
<template>
<div>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quam rerum omnis
repellat. Harum ducimus nulla repellendus neque officia eveniet corporis
odio sequi animi ut, non incidunt est error esse aperiam?
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -62,8 +62,7 @@ export default defineComponent({
Diseños de ramos más vendidos Diseños de ramos más vendidos
</h3> </h3>
<p class="products-header-paragraph section-paragraph"> <p class="products-header-paragraph section-paragraph"></p>
</p>
</header> </header>
<div class="products-body"> <div class="products-body">
@ -85,7 +84,7 @@ export default defineComponent({
</template> </template>
</Container> </Container>
<RouterLink class="btn rounded outlined" to="/"> <RouterLink class="btn rounded outlined" to="/categoria/all">
Ver todos los diseños <IconArrowCircleFilledRight /> Ver todos los diseños <IconArrowCircleFilledRight />
</RouterLink> </RouterLink>
</div> </div>
@ -97,8 +96,7 @@ export default defineComponent({
Nuestra selección de plantas para el verano Nuestra selección de plantas para el verano
</h3> </h3>
<p class="products-selection-paragraph section-paragraph"> <p class="products-selection-paragraph section-paragraph"></p>
</p>
</header> </header>
<div class="products-selection-body"> <div class="products-selection-body">

View File

@ -1,7 +1,7 @@
<script> <script>
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { useMeta } from "quasar"; import { useMeta } from "quasar";
import { defineComponent, onBeforeMount, ref, watch } from "vue"; import { computed, defineComponent, onBeforeMount, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue"; import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue";
@ -18,8 +18,10 @@ import Card from "components/ui/Card.vue";
import Container from "components/ui/Container.vue"; import Container from "components/ui/Container.vue";
import Modal from "components/ui/Modal.vue"; import Modal from "components/ui/Modal.vue";
import { useLocalStorage } from "src/hooks/useLocalStorage";
import { usePostalCalendar } from "src/hooks/usePostalCalendar"; import { usePostalCalendar } from "src/hooks/usePostalCalendar";
import { useCartStore } from "stores/cart"; import { useCartStore } from "stores/cart";
import { useFormStore } from "stores/forms";
import { useModalStore } from "stores/modalStore"; import { useModalStore } from "stores/modalStore";
export default defineComponent({ export default defineComponent({
@ -41,28 +43,48 @@ export default defineComponent({
}, },
setup() { setup() {
const route = useRoute(); const route = useRoute();
const { getItem } = useLocalStorage();
const formStore = useFormStore();
const { availability: availabilityForm } = storeToRefs(formStore);
const availability = ref(getItem("availability"));
const filteredAvailabilityForm = computed(() => {
return Object.fromEntries(
Object.entries(availabilityForm.value).filter(
([key, value]) => value !== ""
)
);
});
const isAvailabilityEmpty = computed(() => {
return (
Object.keys(filteredAvailabilityForm.value || availability.value)
.length === 0
);
});
const { const {
handleReset,
fields: { dedication, dedicationAttrs }, fields: { dedication, dedicationAttrs },
} = usePostalCalendar({ modalItem: "isOpenAvailability" }); } = usePostalCalendar({ modalItem: "isOpenAvailability", type: "product" });
const modalStore = useModalStore(); const modalStore = useModalStore();
const { openModal } = modalStore; const { openModal } = modalStore;
const cartStore = useCartStore(); const cartStore = useCartStore();
const { getProduct, getProducts } = cartStore; const { getProduct, getProducts, addToCart } = cartStore;
const { products, featuredProducts, addCartLoadingBtn } = const { products, addCartLoadingBtn } = storeToRefs(cartStore);
storeToRefs(cartStore);
onBeforeMount(() => { onBeforeMount(async () => {
getProduct(route.params.id); await getProduct(route.params.id);
}); });
watch( watch(
() => products.value.current?.type, () => products.value.current?.type,
(newCategory) => { (newCategory) => {
getProducts({ getProducts({
// type: newCategory, type: newCategory,
}); });
} }
); );
@ -74,33 +96,34 @@ export default defineComponent({
} }
); );
useMeta(() => ({ useMeta(() => {
title: `${products.value.current?.title}`, return {
titleTemplate: (title) => `${title} - FloraNet`, title: "FloraNet",
meta: { meta: {
description: { description: {
name: "description", name: "description",
content: `${products.value.current?.description}`, content: `${products.value.current?.description}`,
}, },
keywords: { keywords: {
name: "keywords", name: "keywords",
content: `${products.value.current?.title}`, content: `${products.value.current?.title}`,
}, },
equiv: { equiv: {
"http-equiv": "Content-Type", "http-equiv": "Content-Type",
content: "text/html; charset=UTF-8", content: "text/html; charset=UTF-8",
}, },
ogTitle: { ogTitle: {
property: "og:title", property: "og:title",
template(ogTitle) { template(ogTitle) {
return `${ogTitle} - FloraNet`; return `${ogTitle} - FloraNet`;
},
},
noscript: {
default: "This is content for browsers with no JS (or disabled JS)",
}, },
}, },
noscript: { };
default: "This is content for browsers with no JS (or disabled JS)", });
},
},
}));
const checkImageValidity = (imageLink) => { const checkImageValidity = (imageLink) => {
const validExtensions = [".jpg", ".jpeg", ".png"]; const validExtensions = [".jpg", ".jpeg", ".png"];
@ -113,15 +136,28 @@ export default defineComponent({
return true; return true;
}; };
const addModal = () => {
if (!isAvailabilityEmpty.value) {
addToCart(products.value.current, dedication);
return;
}
openModal({ modal: "availability" });
};
const handlePagClick = () => {
handleReset();
};
return { return {
addModal,
openModal, openModal,
handlePagClick,
checkImageValidity, checkImageValidity,
slide: ref(1), slide: ref(1),
fullscreen: ref(false), fullscreen: ref(false),
dedication, dedication,
dedicationAttrs, dedicationAttrs,
products, products,
featuredProducts,
addCartLoadingBtn, addCartLoadingBtn,
}; };
}, },
@ -156,9 +192,9 @@ export default defineComponent({
<span class="green-text" style="display: inline-flex"> <span class="green-text" style="display: inline-flex">
{{ products.current?.id }} {{ products.current?.id }}
<q-skeleton <q-skeleton
v-if="!products.current?.id"
width="100px" width="100px"
type="text" type="text"
v-if="!products.current?.id"
/> />
</span> </span>
</p> </p>
@ -168,9 +204,9 @@ export default defineComponent({
<span class="green-text"> <span class="green-text">
{{ products.current?.type }} {{ products.current?.type }}
<q-skeleton <q-skeleton
v-if="!products.current?.type"
type="text" type="text"
width="50px" width="50px"
v-if="!products.current?.type"
/> />
</span> </span>
</p> </p>
@ -182,10 +218,10 @@ export default defineComponent({
<p class="product-price green-text"> <p class="product-price green-text">
{{ products.current?.price }} {{ products.current?.price }}
<q-skeleton <q-skeleton
v-if="!products.current?.price"
type="text" type="text"
height="90px" height="90px"
width="80px" width="80px"
v-if="!products.current?.price"
/> />
</p> </p>
<p class="product-delivery green-text">Envío Gratuito</p> <p class="product-delivery green-text">Envío Gratuito</p>
@ -227,7 +263,7 @@ export default defineComponent({
color="primary" color="primary"
class="btn sm-btn" class="btn sm-btn"
label="AÑADIR AL CARRITO" label="AÑADIR AL CARRITO"
@click="openModal({ modal: 'availability' })" @click="addModal"
/> />
</div> </div>
</div> </div>
@ -254,7 +290,7 @@ export default defineComponent({
color="white" color="white"
class="btn outlined rounded sm-btn product-pag-item product-prev-btn" class="btn outlined rounded sm-btn product-pag-item product-prev-btn"
:to="`${+$route.params.id - 1}`" :to="`${+$route.params.id - 1}`"
@click="products.current.value = undefined" @click="handlePagClick"
> >
<IconArrowCircleFilledLeft /> <IconArrowCircleFilledLeft />
@ -271,6 +307,7 @@ export default defineComponent({
color="white" color="white"
class="btn outlined rounded sm-btn product-pag-item product-next-btn" class="btn outlined rounded sm-btn product-pag-item product-next-btn"
:to="`${+$route.params.id + 1}`" :to="`${+$route.params.id + 1}`"
@click="handlePagClick"
> >
<div class="btn-pag-paragraphs"> <div class="btn-pag-paragraphs">
<p class="btn-paragraph-top green-text">Siguiente producto</p> <p class="btn-paragraph-top green-text">Siguiente producto</p>
@ -293,8 +330,7 @@ export default defineComponent({
Quizás también te gusten estos ramos Quizás también te gusten estos ramos
</h3> </h3>
<p class="like-another-paragraph"> <p class="like-another-paragraph"></p>
</p>
</header> </header>
<Container cardContainer class="no-padding"> <Container cardContainer class="no-padding">
@ -361,6 +397,7 @@ export default defineComponent({
height: 396px; height: 396px;
& .q-carousel__navigation { & .q-carousel__navigation {
bottom: -83px; bottom: -83px;
display: block;
& .q-carousel__navigation-inner { & .q-carousel__navigation-inner {
gap: 12px; gap: 12px;
& .q-carousel__thumbnail { & .q-carousel__thumbnail {

View File

@ -26,6 +26,11 @@ const routes = [
name: "Plantas", name: "Plantas",
component: () => import("pages/CategoryPage.vue"), component: () => import("pages/CategoryPage.vue"),
}, },
{
path: "all",
name: "All",
component: () => import("pages/CategoryPage.vue"),
},
], ],
}, },
{ {
@ -37,7 +42,17 @@ const routes = [
path: "", path: "",
name: "Checkout", name: "Checkout",
component: () => import("pages/CheckoutPage.vue"), component: () => import("pages/CheckoutPage.vue"),
} },
{
path: "success",
name: "CheckoutSuccess",
component: () => import("pages/CheckoutSuccessPage.vue"),
},
{
path: "error",
name: "CheckoutError",
component: () => import("pages/CheckoutErrorPage.vue"),
},
], ],
}, },
{ {

View File

@ -1,40 +1,55 @@
import { defineStore } from "pinia"; import { defineStore, storeToRefs } from "pinia";
import { ref } from "vue"; import { computed, ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { apiBack } from "src/boot/axios"; import { apiBack } from "src/boot/axios";
import { quasarNotify } from "src/functions/quasarNotify"; import { quasarNotify } from "src/functions/quasarNotify";
import { useLocalStorage } from "src/hooks/useLocalStorage"; import { useLocalStorage } from "src/hooks/useLocalStorage";
import { useFormStore } from "./forms";
export const useCartStore = defineStore("cart", () => { export const useCartStore = defineStore("cart", () => {
const { push } = useRouter(); const { push } = useRouter();
const { addItem, getItem, removeItem } = useLocalStorage() const { addItem, getItem } = useLocalStorage();
const formStore = useFormStore();
const { availability: availabilityForm } = storeToRefs(formStore);
//! Elements //! Elements
const checkoutRef = ref(null); const checkoutRef = ref(null);
const homeSection = ref(null); const homeSection = ref(null);
const initialValues = [{ const initialValues = [
id: null, {
name: "", id: null,
price: null, name: "",
image: "", price: null,
description: "", image: "",
dateExpired: "", description: "",
isNew: null, dateExpired: "",
type: "", isNew: null,
postalCode: "", type: "",
order_position: null, postalCode: "",
recommend: null order_position: null,
}] recommend: null,
},
];
//! Variables //! Variables
const cart = ref([]); const cart = ref(getItem("cart"));
const availability = ref(getItem("availability"));
(() => { const filteredAvailabilityForm = computed(() => {
cart.value = getItem('cart'); return Object.fromEntries(
})() Object.entries(availabilityForm.value).filter(
([key, value]) => value !== ""
)
);
});
const isAvailabilityEmpty = computed(() => {
return (
Object.keys(filteredAvailabilityForm.value || availability.value)
.length === 0
);
});
const addCartLoadingBtn = ref(false); const addCartLoadingBtn = ref(false);
const routeId = ref(null); const routeId = ref(null);
const products = ref({ const products = ref({
@ -43,44 +58,8 @@ export const useCartStore = defineStore("cart", () => {
current: initialValues, current: initialValues,
next: initialValues, next: initialValues,
}); });
const featuredProducts = ref({
page: undefined,
productsPerPage: undefined,
products: [],
});
/** function transformOptionsToParams(options = {}) {
* Transforms options object into params object.
*
* @param {Object} options - The options object.
* @param {number} options.itens - The items array.
* @param {boolean} options.featured - The featured flag.
* @param {number} options.page - The page number.
* @param {string} options.type - The type name.
* @param {string} options.postalCode - The postal code.
* @param {string} options.dateExpired - The expiration date.
* @param {number} options.minPrice - The minimum price.
* @param {number} options.maxPrice - The maximum price.
* @param {number} options.bigPrice - The big price.
* @param {number} options.lowPrice - The low price.
* @param {boolean} options.isNew - The new flag.
* @returns {Object} - The params object.
*/
function transformOptionsToParams(
options = {
postalCode: undefined,
dateExpired: undefined,
type: undefined,
minPrice: undefined,
maxPrice: undefined,
bigPrice: undefined,
lowPrice: undefined,
isNew: undefined,
order_crescent: undefined,
order_descending: undefined,
recommend: undefined,
}
) {
const optionsObj = { const optionsObj = {
postalCode: options.postalCode, postalCode: options.postalCode,
dateExpired: options.dateExpired, dateExpired: options.dateExpired,
@ -105,24 +84,7 @@ export const useCartStore = defineStore("cart", () => {
return params; return params;
} }
/** const isEmpty = ref(false);
* Fetches products based on the provided options.
*
* @param {Object} options - The options for fetching products.
* @param {number} options.itens - The items to fetch.
* @param {boolean} options.featured - Whether to fetch only featured products.
* @param {number} options.page - The page number to fetch.
* @param {string} options.type - The type of products to fetch.
* @param {string} options.postalCode - The postal code for filtering products.
* @param {string} options.dateExpired - The expiration date for filtering products.
* @param {number} options.minPrice - The minimum price for filtering products.
* @param {number} options.maxPrice - The maximum price for filtering products.
* @param {number} options.bigPrice - The big price for filtering products.
* @param {number} options.lowPrice - The low price for filtering products.
* @param {boolean} options.isNew - Whether to fetch only new products.
* @param {Function} navigate - The navigation function to call after fetching products.
* @returns {Promise<void>} - A promise that resolves when the products are fetched.
*/
async function getProducts( async function getProducts(
options = { options = {
postalCode: undefined, postalCode: undefined,
@ -137,16 +99,17 @@ export const useCartStore = defineStore("cart", () => {
order_descending: undefined, order_descending: undefined,
recommend: undefined, recommend: undefined,
}, },
scrollIntoView = () => { } callback
) { ) {
const params = transformOptionsToParams(options); const params = transformOptionsToParams(options);
console.log(params);
try { try {
const { data: { data } } = await apiBack.get("products", { params }); const {
data: { data },
} = await apiBack.get("products", { params });
if (data.length === 0) { if (data.length === 0) {
isEmpty.value = true;
return quasarNotify({ return quasarNotify({
message: message:
"No hay productos disponibles para la fecha y el código postal seleccionados", "No hay productos disponibles para la fecha y el código postal seleccionados",
@ -154,10 +117,11 @@ export const useCartStore = defineStore("cart", () => {
}); });
} }
isEmpty.value = false;
products.value.data = data; products.value.data = data;
if (scrollIntoView) { if (callback) {
scrollIntoView(); callback();
} }
console.groupCollapsed("%c PRODUCTS FETCHED!", "color: green;"); console.groupCollapsed("%c PRODUCTS FETCHED!", "color: green;");
@ -182,17 +146,6 @@ export const useCartStore = defineStore("cart", () => {
} }
} }
/**
* Fetches a product by its ID and updates the cart state.
*
* @param {string} id - The ID of the product to fetch.
* @param {object} options - Additional options for the product fetch.
* @param {string} options.type - The type of the product.
* @param {string} options.postalCode - The postal code for location-based filtering.
* @param {string} options.dateExpired - The expiration date for time-based filtering.
* @param {boolean} debug - Flag indicating whether to enable debug mode.
* @returns {Promise<void>} - A promise that resolves when the product is fetched and the cart state is updated.
*/
async function getProduct( async function getProduct(
id, id,
options = { options = {
@ -220,14 +173,14 @@ export const useCartStore = defineStore("cart", () => {
}; };
return result[res.status].data[0]; return result[res.status].data[0];
}) });
products.value.prev = prev; products.value.prev = prev;
products.value.current = current; products.value.current = current;
products.value.next = next; products.value.next = next;
if (!current) { if (!current) {
push({ name: "NotFound" }) push({ name: "NotFound" });
} }
if (debug) { if (debug) {
@ -250,92 +203,48 @@ export const useCartStore = defineStore("cart", () => {
} }
} }
/** async function addToCart(product, message) {
* Retrieves featured products based on the provided options. const params = transformOptionsToParams(
* availabilityForm.value || availability.value
* @param {Object} options - The options for retrieving featured products. );
* @param {number} options.itens - The number of items to retrieve. await getProducts(params);
* @param {number} options.featured - The flag indicating if the products should be featured.
* @param {number} [options.page] - The page number for pagination.
* @param {string} [options.type] - The type of the products.
* @param {string} [options.postalCode] - The postal code for location-based filtering.
* @param {string} [options.dateExpired] - The expiration date for filtering.
* @param {number} [options.minPrice] - The minimum price for filtering.
* @param {number} [options.maxPrice] - The maximum price for filtering.
* @param {number} [options.bigPrice] - The big price for filtering.
* @param {number} [options.lowPrice] - The low price for filtering.
* @param {boolean} [options.isNew] - The flag indicating if the products are new.
* @returns {Promise<void>} - A promise that resolves when the featured products are retrieved.
*/
async function getFeaturedProducts(
options = {
postalCode: undefined,
dateExpired: undefined,
type: undefined,
minPrice: undefined,
maxPrice: undefined,
bigPrice: undefined,
lowPrice: undefined,
isNew: undefined,
order_crescent: undefined,
order_descending: undefined,
recommend: 1,
},
debug = false
) {
try {
const params = transformOptionsToParams(options);
(async () => { const hasCurrentProduct = computed(() => {
const { return cart.value.find((p) => p.id === product.id);
data: { data },
} = await apiBack.get("products", { params });
featuredProducts.value = data[0];
if (debug) {
console.groupCollapsed(
"%c FEATURED PRODUCTS FETCHED!",
"color: green;"
);
console.table(data.products);
console.groupEnd();
}
})();
} catch (err) {
new Error(`FATAL ERROR ::: ${err}`);
}
}
/**
* Adiciona um produto ao carrinho.
* @param {Object} product - O produto a ser adicionado.
* @param {string} dedication - A dedicação associada ao produto.
*/
function addToCart(product, dedication) {
const existingProduct = cart.value.find(p => p.id === product.id);
console.log(existingProduct)
if (!existingProduct) {
const arr = [...cart.value];
arr.push(product);
console.log(arr)
addItem("cart", JSON.stringify(arr));
quasarNotify({ message: 'Producto añadido al carrito.', type: 'success' })
return
}
quasarNotify({
message: "Este producto ya está en el carrito",
type: "info",
}); });
}
/** if (isEmpty.value) {
* Remove an item from the cart by its ID. push("/");
* @param {number} id - The ID of the item to be removed. return quasarNotify({
*/ message:
function removeFromCart(id) { "No hay productos disponibles para la fecha y el código postal seleccionados",
const newArrRemovedItem = cart.value.filter((p) => id !== p.id); type: "erro",
addItem("cart", JSON.stringify(newArrRemovedItem)) });
}
if (!products.value.data.some((item) => item.id === product.id)) {
push("/");
return quasarNotify({
message:
"Este producto no está disponible en su zona, intente añadir un nuevo código postal",
type: "erro",
});
}
if (hasCurrentProduct.value) {
return quasarNotify({
message: "Este producto ya está en el carrito",
type: "info",
});
}
const arr = [...cart.value];
arr.push({ ...product, message: message.value });
cart.value = arr;
addItem("cart", arr);
quasarNotify({
message: "Producto añadido al carrito.",
type: "success",
});
} }
return { return {
@ -345,12 +254,9 @@ export const useCartStore = defineStore("cart", () => {
cart, cart,
addCartLoadingBtn, addCartLoadingBtn,
products, products,
featuredProducts,
getFeaturedProducts,
getProducts, getProducts,
addToCart, addToCart,
removeFromCart,
getProduct, getProduct,
}; };
}); });

View File

@ -18,7 +18,7 @@ export const useFormStore = defineStore("forms", {
terms: false, terms: false,
}, },
availability: { availability: {
date: "", dateExpired: "",
postalCode: "", postalCode: "",
}, },
checkout: { checkout: {
@ -34,28 +34,23 @@ export const useFormStore = defineStore("forms", {
senderEmail: "", senderEmail: "",
senderPhone: "", senderPhone: "",
senderNotes: "", senderNotes: "",
paymentMethod: "credit", paymentMethod: "paypal",
terms: false, terms: false,
}, },
}), }),
actions: { actions: {
handleQuestionData(values) { handleQuestionData(values) {
console.log(values);
this.question = values; this.question = values;
}, },
handleAvailabilityData(values) { handleAvailabilityData(values) {
console.log(values);
this.availability = values; this.availability = values;
}, },
registerAvailability() { registerAvailability() {},
console.log(this.availability);
},
handleCheckoutData(values) { handleCheckoutData(values) {
// console.log(values);
this.checkout = values; this.checkout = values;
}, },
}, },

View File

@ -23,8 +23,7 @@ export const useModalStore = defineStore("modal", () => {
isOpenAvailability: () => "Contenido modal availability", isOpenAvailability: () => "Contenido modal availability",
isOpenFilters: () => "Contenido modal filters", isOpenFilters: () => "Contenido modal filters",
}; };
console.log(availability.value); isModal[content]()
console.log(isModal[content]());
} }
return { openModal, handleSubmit, isOpenAvailability, isOpenFilters }; return { openModal, handleSubmit, isOpenAvailability, isOpenFilters };

View File

@ -8,7 +8,6 @@ export const useRangePriceStore = defineStore("range-price", () => {
}); });
function handlePriceRange({ min, max }) { function handlePriceRange({ min, max }) {
console.log({ min, max });
rangeValue.min = min; rangeValue.min = min;
rangeValue.max = max; rangeValue.max = max;
} }

View File

@ -21,16 +21,18 @@ const checkoutObjVal = {
phone: z phone: z
.string({ required_error: M.requiredMessage }) .string({ required_error: M.requiredMessage })
.refine(handlePhoneVal, M.phoneMessage), .refine(handlePhoneVal, M.phoneMessage),
senderName: z.string().regex(justOneWord, M.nameMessage), senderName: z.string().regex(justLetters, M.nameMessage),
senderCifNif: z senderCifNif: z
.string() .string()
.length(9, "El código postal debe tener 9 caracteres numéricos válidos"), .length(9, "El código postal debe tener 9 caracteres numéricos válidos"),
senderEmail: z.string().email(M.emailMessage), senderEmail: z.string().email(M.emailMessage),
senderPhone: z.string().refine(handlePhoneVal, M.phoneMessage), senderPhone: z.string().refine(handlePhoneVal, M.phoneMessage),
senderNotes: z.string(), senderNotes: z.string(),
paymentMethod: z.enum(["credit", "stripe"], { paymentMethod: z
required_error: "Seleccione uno de los métodos de pago!", .enum(["credit", "paypal"], {
}), required_error: "Seleccione uno de los métodos de pago!",
})
.default("paypal"),
terms: z.boolean().refine((val) => { terms: z.boolean().refine((val) => {
return val === true; return val === true;
}, "Acepte las condiciones antes de continuar con la compra"), }, "Acepte las condiciones antes de continuar con la compra"),

View File

@ -2,9 +2,7 @@ import { z } from "zod";
const rangePriceObj = { const rangePriceObj = {
range: z.object({ range: z.object({
min: z min: z.number(),
.number()
.refine((n) => n > 0, "El valor mínimo debe ser superior a cero"),
max: z.number(), max: z.number(),
}), }),
}; };