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

View File

@ -3,3 +3,7 @@ DB_USER="root"
DB_PASSWORD="root"
PORT ="3306"
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")
@ -7,28 +7,14 @@ class ProductController {
const params = req.query;
const _products = await db.getProducts(params.dateExpired, params.postalCode);
let productsFilter = _products[0];
let productsFilter = _products[0]
if (Number(params.recommend)) {
productsFilter = productsFilter.filter(item => item.recommend == Number(params.recommend))
productsFilter = productsFilter.filter(item => item.recommend == params.recommend)
}
if (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) {
productsFilter = productsFilter.filter(item => {
@ -105,6 +91,7 @@ class ProductController {
products: products
}) */
return res.status(200).send({
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 connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + "");
console.log("Connected to MySQL!");
global.connection = connection;
return connection;
}
//Procedure for get products
async function getProducts(dateExpired, postalCode) {
console.log("Query in table MySQL!");
const conn = await connect();
const [rows] = await conn.query(`CALL catalogue_get("${dateExpired}", "${postalCode}")`);
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 express = require('express');
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 port = 9999;
@ -11,8 +20,16 @@ const corsOptions = {
origin: allowedOrigins,
optionsSuccessStatus: 200,
};
app.use(cors(corsOptions));
app.use(express.json());
app.use(
express.urlencoded({
extended: true,
}),
);
app.get('/', (req, res) => {
const indexPath = path.join(__dirname, './', 'index.html');
res.sendFile(indexPath);
@ -21,6 +38,10 @@ app.get('/', (req, res) => {
//Products
app.get('/api/products', productController.findAll);
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, () => {
console.log(`Server listening at http://localhost:${port}`);

View File

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

407
package-lock.json generated
View File

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

View File

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

View File

@ -1,8 +1,10 @@
<script>
import { storeToRefs } from "pinia";
import { defineComponent, onBeforeMount, ref, watch } from "vue";
import { fullCurrentDate } from "src/constants/date";
import { invertDate } from "src/functions/invertDate";
import { useFormStore } from "src/stores/forms";
import { defineComponent, ref } from "vue";
import IconCalendar from "../icons/IconCalendar.vue";
export default defineComponent({
@ -19,27 +21,45 @@ export default defineComponent({
const formStore = useFormStore();
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() {
proxyDate.value = fullCurrentDate;
proxyDate.value = invertDate(currentDate);
}
function optionsValidDates(date) {
return date >= fullCurrentDate;
}
function save() {
availability.value.date = proxyDate.value;
setValues({ date: proxyDate.value });
}
onBeforeMount(() => {
setValues({ date: invertDate(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 {
availability,
proxyDate,
locale,
updateProxy,
optionsValidDates,
save,
};
},
});
@ -58,19 +78,14 @@ export default defineComponent({
>
<q-date
v-model="proxyDate"
v-bind="calendarAttrs"
:options="optionsValidDates"
mask="DD-MM-YYYY"
:locale="locale"
today-btn
mask="YYYY-MM-DD"
>
<div class="row items-center justify-end q-gutter-sm">
<q-btn label="Cancel" color="primary" flat v-close-popup />
<q-btn
label="OK"
color="primary"
flat
@click="save"
v-close-popup
/>
<q-btn label="OK" color="primary" flat v-close-popup />
</div>
</q-date>
</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>
import { toTypedSchema } from "@vee-validate/zod";
import { storeToRefs } from "pinia";
import { useForm } from "vee-validate";
import { defineComponent, watch } from "vue";
import { defineComponent } 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: "postal-code",
components: { IconPostalCode },
setup() {
const formStore = useFormStore();
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,
};
return {};
},
});
</script>
@ -54,17 +20,6 @@ export default defineComponent({
<p class="custom-head-paragraph">¿Dónde?</p>
<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>
</template>

View File

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

View File

@ -29,9 +29,9 @@ export default defineComponent({
:class="isOpenNav && 'mobile-nav'"
>
<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"
mobile-text="ENVÍO GRATIS a partir de 60€"
mobile-text="ENVÍO GRATIS"
v-if="!isOpenNav"
/>

View File

@ -24,9 +24,9 @@ export default defineComponent({
<template>
<q-header class="header-container transparent">
<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"
mobile-text="ENVÍO GRATIS a partir de 60€"
mobile-text="ENVÍO GRATIS"
class="remove-mobile"
/>

View File

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

View File

@ -87,8 +87,7 @@ export default defineComponent({
Regala un verano lleno de flores y plantas
</h1>
<p class="carousel-header-paragraph">
</p>
<!-- <p class="carousel-header-paragraph"></p> -->
</header>
<form @submit="onSubmit" class="carousel-content-body">
@ -99,7 +98,7 @@ export default defineComponent({
class="custom-date-input"
v-model="calendar"
v-bind="calendarAttrs"
:error="!!errors.date"
:error="false"
placeholder="Elige una fecha"
mask="##/##/####"
dense
@ -114,7 +113,7 @@ export default defineComponent({
class="custom-main-paragraph"
v-model="postalCode"
v-bind="postalCodeAttrs"
:error="!!errors.postalCode"
:error="false"
placeholder="código postal"
mask="#####"
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);
onMounted(() => {
// console.log('Montado!');
swiperContainer.value =
document.querySelector("swiper-container").shadowRoot;
prevSwiperBtn.value = swiperContainer.value.querySelector(
@ -40,11 +38,9 @@ export default defineComponent({
return {
screenWidth,
handlePrev() {
// console.log('Prev click');
prevSwiperBtn.value.click();
},
handleNext() {
// console.log('Next click');
nextSwiperBtn.value.click();
},
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -321,8 +321,29 @@ body {
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 {
font-family: $font-questrial;
font-size: $font-14;

View File

@ -2,21 +2,20 @@ import { toTypedSchema } from "@vee-validate/zod";
import { useForm } from "vee-validate";
import { computed, reactive, ref } from "vue";
import { apiBack } from "src/boot/axios";
import { useFormStore } from "src/stores/forms";
import { checkoutSchema } from "src/utils/zod/schemas";
import { useRouter } from "vue-router";
import { useLocalStorage } from "./useLocalStorage";
export function useCheckoutForm() {
const { getItem } = useLocalStorage();
const { push } = useRouter()
const { addItem, getItem, removeItem } = useLocalStorage();
const formStore = useFormStore();
const { handleCheckoutData } = formStore;
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
validationSchema: toTypedSchema(checkoutSchema),
initialValues: {
paymentMethod: "stripe",
paymentMethod: "paypal",
terms: false,
},
});
@ -36,6 +35,11 @@ export function useCheckoutForm() {
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
const [terms, termsAttrs] = defineField("terms");
//TODO hacer el await de las provincias
//const provinceOptions = getProvinces();
const provinceOptions = ref([
{ code: "01", name: "Araba/Álava" },
{ code: "02", name: "Albacete" },
@ -90,7 +94,6 @@ export function useCheckoutForm() {
{ code: "51", name: "Ceuta" },
{ code: "52", name: "Melilla" },
]);
const stepActive = reactive({ data: 1 });
const stepList = reactive({
data: [
@ -122,27 +125,90 @@ export function useCheckoutForm() {
return step;
});
});
const isError = ref(false);
const onError = () => {
isError.value = true;
};
const handleClickStep = (value) => {
stepActive["data"] = value;
};
const checkoutBlock = ref(true);
const onSubmit = handleSubmit((values) => {
handleCheckoutData(values);
stepList.data[2].active = true;
checkoutBlock.value = false;
resetForm();
});
const cart = getItem("cart");
const totalPrice = ref(0)
totalPrice.value = cart?.reduce((acc, { price }) => {
const availability = getItem("availability");
const totalPrice = computed(() => {
return cart?.reduce((acc, { price }) => {
if (price) {
const priceWithoutLetter = price?.replace("€", "");
return +priceWithoutLetter + acc;
//const priceWithoutLetter = price?.replace("€", "");
return +price + acc;
}
}, 0);
});
const isLoadingSubmit = ref(false);
const isErrorSubmit = ref(false);
const onSuccess = async (values) => {
isLoadingSubmit.value = true;
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();
}
};
const onSubmit = handleSubmit(onSuccess);
return {
handleClickStep,
@ -152,11 +218,13 @@ export function useCheckoutForm() {
checkoutBlock,
cart,
totalPrice,
isError,
onError,
formState: {
meta,
errors,
onSubmit,
submitLoading: ref(false),
isLoadingSubmit,
},
fields: {
name,

View File

@ -1,22 +1,39 @@
import { LocalStorage } from "quasar";
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) => {
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 data = JSON.parse(LocalStorage.getItem(`@${key}`));
return (data || []);
if (key === "availability") return data || {};
return data || [];
};
/**
* Remove an item from local storage.
*
* @param {string} key - The key of the item to remove.
*/
const removeItem = (key) => {
LocalStorage.remove(`@${key}`);
};
return {
addItem,
getItem,

View File

@ -1,7 +1,7 @@
import { toTypedSchema } from "@vee-validate/zod";
import { storeToRefs } from "pinia";
import { useForm } from "vee-validate";
import { ref, watch } from "vue";
import { computed, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { invertDate } from "src/functions/invertDate";
@ -12,38 +12,45 @@ import { useModalStore } from "src/stores/modalStore";
import { useRangePriceStore } from "src/stores/rangePrice";
import { availabilitySchema } from "src/utils/zod/schemas";
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 {string} options.modalItem - The modal item isOpenAvailability || isOpenFilters.
* @param {string} options.type - The type of the hook. home || product || availability || filter
* @returns {Object} - The hook functions and data.
* @param {string} options.modalItem - The modal item.
* @param {string} options.type - The type of the calendar.
* @returns {Object} - The hook functions and properties.
*/
export function usePostalCalendar({ modalItem = "", type = "home" }) {
const route = useRoute();
const { push } = useRouter()
const { push, go } = useRouter();
const { addItem, getItem, removeItem } = useLocalStorage();
const rangePriceStore = useRangePriceStore();
const { rangeValue } = storeToRefs(rangePriceStore);
const modalStore = useModalStore();
const { openModal } = modalStore
const formStore = useFormStore();
const { sortProductFilters } = storeToRefs(formStore);
const { sortProductFilters, availability: availabilityForm } =
storeToRefs(formStore);
const cartStore = useCartStore();
const { addToCart, getProducts } = cartStore;
const { products, homeSection } = storeToRefs(cartStore);
const { products, cart } = storeToRefs(cartStore);
const min = 0;
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(
{
validateOnMount: false,
validationSchema: toTypedSchema(
type !== "filter" ? availabilitySchema : rangePriceSchema
),
@ -55,98 +62,139 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
postalCode: "",
date: "",
},
initialTouched: {
date: false,
postalCode: true,
},
}
);
const [calendar, calendarAttrs] = defineField("date");
const [postalCode, postalCodeAttrs] = defineField("postalCode");
const [priceRange, priceRangeAttrs] = defineField("range");
const options = {
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");
watch(errors, (newErrors) => {
const errorsObj = {
postalCode: () =>
quasarNotify({ message: newErrors.postalCode, type: "erro" }),
date: () => quasarNotify({ message: newErrors.date, type: "erro" }),
range: () => quasarNotify({ message: newErrors.range, type: "erro" }),
dedication: () =>
quasarNotify({ message: newErrors.dedication, type: "erro" }),
const hasErrors = {
range: newErrors.range,
dedication: newErrors.dedication,
};
const keys = Object.keys(newErrors);
keys.forEach((key) => {
errorsObj[key]();
});
for (const [field, hasError] of Object.entries(hasErrors)) {
if (hasError) {
quasarNotify({ message: newErrors[field], type: "erro" });
}
}
});
watch(
[() => route.path, () => sortProductFilters.value],
([newPath]) => {
watch([() => route.path, () => sortProductFilters.value], ([newPath]) => {
const categoryPath = newPath.split("/")[2];
category.value = categoryPath;
}
);
});
const onSubmit = handleSubmit((values) => {
const postalAndDateParams = {
const removeCart = () => {
removeItem("cart");
cart.value = [];
};
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 = {
plantas: "Floranet Plantas",
ramos: "Floranet Ramos",
};
const objVal = {
home: async () => {
const handleHome = async () => {
console.log(type);
addItem("availability", {
postalCode: values.postalCode,
dateExpired: invertDate(values.date),
});
availabilityForm.value.dateExpired = invertDate(values.date);
availabilityForm.value.postalCode = values.postalCode;
removeCart();
const callback = async () => {
await push("/categoria/all");
};
await getProducts(
{
postalCode: values.postalCode,
dateExpired: invertDate(values.date),
},
() => homeSection.value.scrollIntoView()
callback
);
},
product: async () => {
};
const handleProduct = async () => {
console.log(type);
await getProducts(postalAndDateParams);
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
addItem("availability", {
postalCode: values.postalCode,
dateExpired: invertDate(values.date),
});
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);
removeCart();
availabilityForm.value.dateExpired = invertDate(values.date);
availabilityForm.value.postalCode = values.postalCode;
await getProducts({
postalCode: values.postalCode,
dateExpired: invertDate(values.date),
});
},
filter: async () => {
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;
@ -157,27 +205,63 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
minPrice: values.range.min,
maxPrice: values.range.max,
};
console.log(params);
await getProducts(params);
},
default: () => {
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!`
);
},
};
objVal[type]() || objVal["default"]();
const handlers = {
availability: handleAvailability,
home: handleHome,
product: handleProduct,
filter: handleFilter,
default: handleDefault,
};
const handler = handlers[type] || handlers.default;
await handler();
if (modalItem) {
modalStore[modalItem] = false;
}
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 {
onSubmit,
setValues,
handleReset,
modalStore,
isAvailabilityEmpty,
fields: {
calendar,
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>
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 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 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 { useFormStore } from "src/stores/forms";
import { useMobileStore } from "src/stores/mobileNav";
@ -34,6 +37,10 @@ export default defineComponent({
setup() {
const route = useRoute();
const { getItem } = useLocalStorage();
const { isAvailabilityEmpty } = usePostalCalendar({});
const mobileStore = useMobileStore();
const { screenWidth } = storeToRefs(mobileStore);
@ -41,29 +48,19 @@ export default defineComponent({
const { openModal } = modalStore;
const formStore = useFormStore();
const { availability, sortProductFilters } = storeToRefs(formStore);
const { sortProductFilters, availability } = storeToRefs(formStore);
const cartStore = useCartStore();
const { products } = storeToRefs(cartStore);
const { getProducts } = cartStore;
const monthTest = ref("");
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 = {
"lowest-price": "menor precio",
"highest-price": "mayor precio",
@ -74,12 +71,32 @@ export default defineComponent({
plantas: "Floranet Plantas",
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 [_day, month, _year] = newDate.date.split("/");
monthTest.value = monthES[+month - 1];
console.log(monthTest.value);
});
const months = {
"01": "Enero",
"02": "Febrero",
"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(
[() => route.path, () => sortProductFilters.value.order],
@ -87,14 +104,29 @@ export default defineComponent({
const categoryPath = newPath.split("/")[2];
sortProductFilters.value.category = categoryPath;
const params = {
type: categoryObj[categoryPath],
};
const params = {};
if (categoryPath !== "all") {
params.type = categoryObj[categoryPath];
}
if (categoryPath === "all") {
params.dateExpired = availabilityStoraged.value.dateExpired;
params.postalCode = availabilityStoraged.value.postalCode;
}
const paramsObj = {
"lowest-price": () => (params.lowPrice = 1),
"highest-price": () => (params.bigPrice = 1),
latest: () => (params.isNew = 1),
recommended: () => (params.recommend = 1),
"lowest-price": () => {
params.lowPrice = 1;
},
"highest-price": () => {
params.bigPrice = 1;
},
latest: () => {
params.isNew = 1;
},
recommended: () => {
params.recommend = 1;
},
};
if (newOrder) {
paramsObj[newOrder]();
@ -104,19 +136,35 @@ export default defineComponent({
}
);
watch(
() => route.path,
() => {
datePostalCode.value = isNotAllCategory.value
? availability.value
: availabilityStoraged.value;
}
);
onBeforeMount(async () => {
const categoryPath = route.path.split("/")[2];
if (categoryPath !== "all") {
await getProducts({
type: categoryObj[categoryPath],
});
});
datePostalCode.value = availability.value;
onUpdated(() => {
console.groupCollapsed("%c Updated!", "color: green;");
console.log(sortProductFilters.value);
console.log(availability.value);
console.groupEnd();
return;
}
await getProducts(availabilityStoraged.value);
datePostalCode.value = availabilityStoraged.value;
if (isAvailabilityEmpty.value) {
quasarNotify({
message: "Debes seleccionar una fecha y código postal",
type: "warning",
});
}
});
function openOrderFilter() {
@ -128,12 +176,17 @@ export default defineComponent({
openOrderFilter,
openModal,
sortProductFilters,
availability,
isOpenOrder,
screenWidth,
modalStore,
orderText,
products,
isNotAllCategory,
availabilityStoraged,
currentMonth,
dateExpiredDay,
datePostalCode,
availability,
};
},
});
@ -144,12 +197,10 @@ export default defineComponent({
<section class="products-section">
<header class="products-section-header">
<Container>
<div class="product-header-content">
<div class="product-header-content" v-if="isNotAllCategory">
<h3 class="product-header-title subtitle">
{{ sortProductFilters.category }} para obsequiar
</h3>
<p class="product-header-paragraph">
</p>
</div>
<div class="product-header-filters">
@ -158,11 +209,15 @@ export default defineComponent({
<p class="filter-paragraph availability">
Disponibilidad para:
<span
v-if="availability.date && availability.postalCode"
v-if="
datePostalCode.dateExpired && datePostalCode.postalCode
"
class="green-text"
>
25 Julio en
{{ availability.postalCode.replace("-", "") }}</span
{{ dateExpiredDay }} {{ currentMonth }} en
{{
availability.postalCode || datePostalCode.postalCode
}}</span
>
</p>
@ -232,8 +287,8 @@ export default defineComponent({
</Container>
</div>
<footer class="products-section-footer">
<RouterLink class="btn rounded outlined" to="/">
<footer class="products-section-footer" v-if="isNotAllCategory">
<RouterLink class="btn rounded outlined" to="/categoria/all">
Ver todos los diseños <IconArrowCircleFilledRight />
</RouterLink>
</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,
cart,
totalPrice,
formState: { errors, meta, onSubmit, submitLoading },
isError,
onError,
formState: { errors, meta, onSubmit, isLoadingSubmit },
fields: {
name,
nameAttrs,
@ -57,11 +59,6 @@ export default defineComponent({
if (cart.length === 0) return push("/");
});
const isError = ref(false);
const onError = () => {
isError.value = true;
};
return {
handleClickStep,
onSubmit,
@ -74,7 +71,7 @@ export default defineComponent({
stepList,
cart,
step: ref(1),
submitLoading,
isLoadingSubmit,
successURL: ref(""),
cancelURL: ref(""),
meta,
@ -439,10 +436,18 @@ export default defineComponent({
<q-radio
v-model="paymentMethod"
v-bind="paymentMethodAttrs"
val="stripe"
val="paypal"
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>
</div>
</div>
@ -455,7 +460,13 @@ export default defineComponent({
</p>
</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
</q-btn>
</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
</h3>
<p class="products-header-paragraph section-paragraph">
</p>
<p class="products-header-paragraph section-paragraph"></p>
</header>
<div class="products-body">
@ -85,7 +84,7 @@ export default defineComponent({
</template>
</Container>
<RouterLink class="btn rounded outlined" to="/">
<RouterLink class="btn rounded outlined" to="/categoria/all">
Ver todos los diseños <IconArrowCircleFilledRight />
</RouterLink>
</div>
@ -97,8 +96,7 @@ export default defineComponent({
Nuestra selección de plantas para el verano
</h3>
<p class="products-selection-paragraph section-paragraph">
</p>
<p class="products-selection-paragraph section-paragraph"></p>
</header>
<div class="products-selection-body">

View File

@ -1,7 +1,7 @@
<script>
import { storeToRefs } from "pinia";
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 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 Modal from "components/ui/Modal.vue";
import { useLocalStorage } from "src/hooks/useLocalStorage";
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
import { useCartStore } from "stores/cart";
import { useFormStore } from "stores/forms";
import { useModalStore } from "stores/modalStore";
export default defineComponent({
@ -41,28 +43,48 @@ export default defineComponent({
},
setup() {
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 {
handleReset,
fields: { dedication, dedicationAttrs },
} = usePostalCalendar({ modalItem: "isOpenAvailability" });
} = usePostalCalendar({ modalItem: "isOpenAvailability", type: "product" });
const modalStore = useModalStore();
const { openModal } = modalStore;
const cartStore = useCartStore();
const { getProduct, getProducts } = cartStore;
const { products, featuredProducts, addCartLoadingBtn } =
storeToRefs(cartStore);
const { getProduct, getProducts, addToCart } = cartStore;
const { products, addCartLoadingBtn } = storeToRefs(cartStore);
onBeforeMount(() => {
getProduct(route.params.id);
onBeforeMount(async () => {
await getProduct(route.params.id);
});
watch(
() => products.value.current?.type,
(newCategory) => {
getProducts({
// type: newCategory,
type: newCategory,
});
}
);
@ -74,9 +96,9 @@ export default defineComponent({
}
);
useMeta(() => ({
title: `${products.value.current?.title}`,
titleTemplate: (title) => `${title} - FloraNet`,
useMeta(() => {
return {
title: "FloraNet",
meta: {
description: {
name: "description",
@ -100,7 +122,8 @@ export default defineComponent({
default: "This is content for browsers with no JS (or disabled JS)",
},
},
}));
};
});
const checkImageValidity = (imageLink) => {
const validExtensions = [".jpg", ".jpeg", ".png"];
@ -113,15 +136,28 @@ export default defineComponent({
return true;
};
const addModal = () => {
if (!isAvailabilityEmpty.value) {
addToCart(products.value.current, dedication);
return;
}
openModal({ modal: "availability" });
};
const handlePagClick = () => {
handleReset();
};
return {
addModal,
openModal,
handlePagClick,
checkImageValidity,
slide: ref(1),
fullscreen: ref(false),
dedication,
dedicationAttrs,
products,
featuredProducts,
addCartLoadingBtn,
};
},
@ -156,9 +192,9 @@ export default defineComponent({
<span class="green-text" style="display: inline-flex">
{{ products.current?.id }}
<q-skeleton
v-if="!products.current?.id"
width="100px"
type="text"
v-if="!products.current?.id"
/>
</span>
</p>
@ -168,9 +204,9 @@ export default defineComponent({
<span class="green-text">
{{ products.current?.type }}
<q-skeleton
v-if="!products.current?.type"
type="text"
width="50px"
v-if="!products.current?.type"
/>
</span>
</p>
@ -182,10 +218,10 @@ export default defineComponent({
<p class="product-price green-text">
{{ products.current?.price }}
<q-skeleton
v-if="!products.current?.price"
type="text"
height="90px"
width="80px"
v-if="!products.current?.price"
/>
</p>
<p class="product-delivery green-text">Envío Gratuito</p>
@ -227,7 +263,7 @@ export default defineComponent({
color="primary"
class="btn sm-btn"
label="AÑADIR AL CARRITO"
@click="openModal({ modal: 'availability' })"
@click="addModal"
/>
</div>
</div>
@ -254,7 +290,7 @@ export default defineComponent({
color="white"
class="btn outlined rounded sm-btn product-pag-item product-prev-btn"
:to="`${+$route.params.id - 1}`"
@click="products.current.value = undefined"
@click="handlePagClick"
>
<IconArrowCircleFilledLeft />
@ -271,6 +307,7 @@ export default defineComponent({
color="white"
class="btn outlined rounded sm-btn product-pag-item product-next-btn"
:to="`${+$route.params.id + 1}`"
@click="handlePagClick"
>
<div class="btn-pag-paragraphs">
<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
</h3>
<p class="like-another-paragraph">
</p>
<p class="like-another-paragraph"></p>
</header>
<Container cardContainer class="no-padding">
@ -361,6 +397,7 @@ export default defineComponent({
height: 396px;
& .q-carousel__navigation {
bottom: -83px;
display: block;
& .q-carousel__navigation-inner {
gap: 12px;
& .q-carousel__thumbnail {

View File

@ -26,6 +26,11 @@ const routes = [
name: "Plantas",
component: () => import("pages/CategoryPage.vue"),
},
{
path: "all",
name: "All",
component: () => import("pages/CategoryPage.vue"),
},
],
},
{
@ -37,7 +42,17 @@ const routes = [
path: "",
name: "Checkout",
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,20 +1,25 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { defineStore, storeToRefs } from "pinia";
import { computed, ref } from "vue";
import { useRouter } from "vue-router";
import { apiBack } from "src/boot/axios";
import { quasarNotify } from "src/functions/quasarNotify";
import { useLocalStorage } from "src/hooks/useLocalStorage";
import { useFormStore } from "./forms";
export const useCartStore = defineStore("cart", () => {
const { push } = useRouter();
const { addItem, getItem, removeItem } = useLocalStorage()
const { addItem, getItem } = useLocalStorage();
const formStore = useFormStore();
const { availability: availabilityForm } = storeToRefs(formStore);
//! Elements
const checkoutRef = ref(null);
const homeSection = ref(null);
const initialValues = [{
const initialValues = [
{
id: null,
name: "",
price: null,
@ -25,16 +30,26 @@ export const useCartStore = defineStore("cart", () => {
type: "",
postalCode: "",
order_position: null,
recommend: null
}]
recommend: null,
},
];
//! Variables
const cart = ref([]);
(() => {
cart.value = getItem('cart');
})()
const cart = ref(getItem("cart"));
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 addCartLoadingBtn = ref(false);
const routeId = ref(null);
const products = ref({
@ -43,44 +58,8 @@ export const useCartStore = defineStore("cart", () => {
current: initialValues,
next: initialValues,
});
const featuredProducts = ref({
page: undefined,
productsPerPage: undefined,
products: [],
});
/**
* 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,
}
) {
function transformOptionsToParams(options = {}) {
const optionsObj = {
postalCode: options.postalCode,
dateExpired: options.dateExpired,
@ -105,24 +84,7 @@ export const useCartStore = defineStore("cart", () => {
return params;
}
/**
* 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.
*/
const isEmpty = ref(false);
async function getProducts(
options = {
postalCode: undefined,
@ -137,16 +99,17 @@ export const useCartStore = defineStore("cart", () => {
order_descending: undefined,
recommend: undefined,
},
scrollIntoView = () => { }
callback
) {
const params = transformOptionsToParams(options);
console.log(params);
try {
const { data: { data } } = await apiBack.get("products", { params });
const {
data: { data },
} = await apiBack.get("products", { params });
if (data.length === 0) {
isEmpty.value = true;
return quasarNotify({
message:
"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;
if (scrollIntoView) {
scrollIntoView();
if (callback) {
callback();
}
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(
id,
options = {
@ -220,14 +173,14 @@ export const useCartStore = defineStore("cart", () => {
};
return result[res.status].data[0];
})
});
products.value.prev = prev;
products.value.current = current;
products.value.next = next;
if (!current) {
push({ name: "NotFound" })
push({ name: "NotFound" });
}
if (debug) {
@ -250,92 +203,48 @@ export const useCartStore = defineStore("cart", () => {
}
}
/**
* Retrieves featured products based on the provided options.
*
* @param {Object} options - The options for retrieving featured products.
* @param {number} options.itens - The number of items to retrieve.
* @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 {
data: { data },
} = await apiBack.get("products", { params });
featuredProducts.value = data[0];
if (debug) {
console.groupCollapsed(
"%c FEATURED PRODUCTS FETCHED!",
"color: green;"
async function addToCart(product, message) {
const params = transformOptionsToParams(
availabilityForm.value || availability.value
);
console.table(data.products);
console.groupEnd();
}
})();
} catch (err) {
new Error(`FATAL ERROR ::: ${err}`);
await getProducts(params);
const hasCurrentProduct = computed(() => {
return cart.value.find((p) => p.id === product.id);
});
if (isEmpty.value) {
push("/");
return quasarNotify({
message:
"No hay productos disponibles para la fecha y el código postal seleccionados",
type: "erro",
});
}
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",
});
}
/**
* 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({
if (hasCurrentProduct.value) {
return quasarNotify({
message: "Este producto ya está en el carrito",
type: "info",
});
}
/**
* Remove an item from the cart by its ID.
* @param {number} id - The ID of the item to be removed.
*/
function removeFromCart(id) {
const newArrRemovedItem = cart.value.filter((p) => id !== p.id);
addItem("cart", JSON.stringify(newArrRemovedItem))
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 {
@ -345,12 +254,9 @@ export const useCartStore = defineStore("cart", () => {
cart,
addCartLoadingBtn,
products,
featuredProducts,
getFeaturedProducts,
getProducts,
addToCart,
removeFromCart,
getProduct,
};
});

View File

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

View File

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

View File

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

View File

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

View File

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