Merge pull request 'Creamos api en node para la conexión a base de datos y cambios que pidió pablo' (#4) from develop into master
Reviewed-on: #4 Reviewed-by: Pablo Natek <pablone@verdnatura.es>
This commit is contained in:
commit
b769c14e0f
|
@ -0,0 +1,4 @@
|
|||
STRIPE_PUBLISHABLE_KEY=''
|
||||
STRIPE_ACCOUNT=''
|
||||
API_VERSION=''
|
||||
LOCALE=''
|
|
@ -5,6 +5,7 @@ node_modules
|
|||
# Quasar core related directories
|
||||
.quasar
|
||||
/dist
|
||||
dist.rar
|
||||
/quasar.config.*.temporary.compiled*
|
||||
|
||||
# Cordova related directories and files
|
||||
|
@ -35,3 +36,4 @@ yarn-error.log*
|
|||
|
||||
# local .env files
|
||||
.env.local*
|
||||
api/.env
|
||||
|
|
|
@ -74,5 +74,8 @@
|
|||
"workbench.tree.indent": 16,
|
||||
"window.zoomLevel": 0,
|
||||
"git.ignoreRebaseWarning": true,
|
||||
"editor.largeFileOptimizations": false
|
||||
"editor.largeFileOptimizations": false,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
HOST="127.0.0.1"
|
||||
DB_USER="root"
|
||||
DB_PASSWORD="root"
|
||||
PORT ="3306"
|
||||
DATABASE="floranet"
|
|
@ -0,0 +1,129 @@
|
|||
const db = require("../db/db");
|
||||
|
||||
const productsJson = require("./products.json")
|
||||
|
||||
class ProductController {
|
||||
async findAll(req, res) {
|
||||
|
||||
|
||||
const _products = await db.getProducts();
|
||||
const mapedProducts = await _products[0].map(function (item) {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
type: item.type,
|
||||
price: item.size,
|
||||
specialPrice: item.specialPrice,
|
||||
isNew: item.isNew,
|
||||
slug: item.slug,
|
||||
category: item.category,
|
||||
postalCode: item.postalCode,
|
||||
dateExpired: item.dateExpired,
|
||||
images: [item.image],
|
||||
featured: item.featured,
|
||||
}
|
||||
})
|
||||
|
||||
//Gerar produtos fake
|
||||
/* const fakeProducts = generateFlowers(50)
|
||||
console.log(fakeProducts); */
|
||||
const params = req.query;
|
||||
let productsFilter = mapedProducts
|
||||
console.log(params);
|
||||
|
||||
if (Number(params.featured)) {
|
||||
productsFilter = productsFilter.filter(item => item.featured === Number(params.featured))
|
||||
}
|
||||
if (params.category) {
|
||||
productsFilter = productsFilter.filter(item => item.category === Number(params.category))
|
||||
}
|
||||
if (params.postalCode) {
|
||||
productsFilter = productsFilter.filter(item => item.postalCode === params.postalCode)
|
||||
}
|
||||
if (params.minPrice && !params.maxPrice) {
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const price = Number(item.price.replace(/€/g, ''))
|
||||
if (price >= Number(params.minPrice)) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
}
|
||||
if (params.maxPrice && !params.minPrice) {
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const price = Number(item.price.replace(/€/g, ''))
|
||||
if (price <= Number(params.maxPrice)) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
}
|
||||
if (params.maxPrice && params.minPrice) {
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const price = Number(item.price.replace(/€/g, ''))
|
||||
if (price >= Number(params.minPrice) && price <= Number(params.maxPrice)) {
|
||||
console.log(price);
|
||||
return item
|
||||
}
|
||||
})
|
||||
}
|
||||
if (params.dateExpired) {
|
||||
const [day, month, year] = params.dateExpired.split("/");
|
||||
const dateSearch = new Date(year, month - 1, day);
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const [day, month, year] = item.dateExpired.split("/");
|
||||
const dateProduct = new Date(year, month - 1, day);
|
||||
if (dateProduct >= dateSearch) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (Number(params.bigPrice)) {
|
||||
productsFilter.sort((a, b) => {
|
||||
const itemA = Number(a.price.replace(/€/g, ''))
|
||||
const itemB = Number(b.price.replace(/€/g, ''))
|
||||
return itemB - itemA;
|
||||
})
|
||||
}
|
||||
|
||||
if (Number(params.lowPrice)) {
|
||||
productsFilter.sort((a, b) => {
|
||||
const itemA = Number(a.price.replace(/€/g, ''))
|
||||
const itemB = Number(b.price.replace(/€/g, ''))
|
||||
return itemA - itemB;
|
||||
})
|
||||
}
|
||||
|
||||
if (params.isNew) {
|
||||
productsFilter = productsFilter.filter(item => item.isNew === true)
|
||||
}
|
||||
|
||||
let productsFilterPages = []
|
||||
const totalItens = params?.itens ? Number(params.itens) : 200
|
||||
const page = params.page ? Number(params.page) : 1
|
||||
const startIndex = (totalItens * page) - totalItens
|
||||
const lastIndex = (totalItens * page)
|
||||
const products = productsFilter.slice(startIndex, lastIndex)
|
||||
productsFilterPages.push({
|
||||
page: page,
|
||||
productsPerPage: products.length,
|
||||
products: products
|
||||
})
|
||||
|
||||
return res.status(200).send({
|
||||
data: productsFilterPages
|
||||
})
|
||||
}
|
||||
|
||||
findBySlug(req, res) {
|
||||
const slug = req.params.slug
|
||||
const products = productsJson
|
||||
const filterSlug = products.filter(item => item.slug === slug)
|
||||
|
||||
return res.status(200).send({
|
||||
data: filterSlug
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ProductController();
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,28 @@
|
|||
async function connect() {
|
||||
if (global.connection && global.connection.state !== 'disconnected')
|
||||
return global.connection;
|
||||
|
||||
const host = process.env.HOST;
|
||||
const port = process.env.PORT;
|
||||
const database = process.env.DATABASE;
|
||||
const user = process.env.DB_USER;
|
||||
const password = process.env.DB_PASSWORD;
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async function getProducts() {
|
||||
const conn = await connect();
|
||||
const [rows] = await conn.query('CALL catalogue_get("2024-01-30", "08001")');
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
module.exports = { getProducts }
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
const cors = require('cors');
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const productController = require('./controller/product.controller');
|
||||
|
||||
const app = express();
|
||||
const port = 9999;
|
||||
|
||||
const allowedOrigins = ['http://localhost:9100', 'https://floranet.onecommerce.dev/'];
|
||||
const corsOptions = {
|
||||
origin: allowedOrigins,
|
||||
optionsSuccessStatus: 200,
|
||||
};
|
||||
app.use(cors(corsOptions));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
const indexPath = path.join(__dirname, './', 'index.html');
|
||||
res.sendFile(indexPath);
|
||||
});
|
||||
|
||||
//Products
|
||||
app.get('/api/products', productController.findAll);
|
||||
app.get('/api/products/slug/:slug', productController.findBySlug);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server listening at http://localhost:${port}`);
|
||||
});
|
|
@ -0,0 +1,657 @@
|
|||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.1",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
|
||||
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"set-function-length": "^1.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
|
||||
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.1",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.5.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.11.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.18.0",
|
||||
"serve-static": "1.15.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "2.0.1",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
|
||||
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
|
||||
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"dependencies": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.1",
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
}
|
11
index.html
11
index.html
|
@ -16,27 +16,27 @@
|
|||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="128x128"
|
||||
href="icons/favicon-128x128.png"
|
||||
href="icons/floranet-favicon.jpg"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="96x96"
|
||||
href="icons/favicon-96x96.png"
|
||||
href="icons/floranet-favicon.jpg"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="icons/favicon-32x32.png"
|
||||
href="icons/floranet-favicon.jpg"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="icons/favicon-16x16.png"
|
||||
href="icons/floranet-favicon.jpg"
|
||||
/>
|
||||
<link rel="icon" type="image/ico" href="favicon.ico" />
|
||||
<link rel="icon" type="image/ico" href="icons/floranet-favicon.jpg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
|
@ -51,6 +51,7 @@
|
|||
src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-element-bundle.min.js"
|
||||
defer
|
||||
></script>
|
||||
<script src="https://js.stripe.com/v3" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- quasar:entry-point -->
|
||||
|
|
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
|
@ -9,35 +9,42 @@
|
|||
"lint": "eslint --ext .js,.vue ./",
|
||||
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||
"dev": "quasar dev -m ssr",
|
||||
"api": "cd api && node --env-file .env index.js ",
|
||||
"build": "quasar build -m ssr",
|
||||
"build:api": "cd api && tsc",
|
||||
"start:build": "npm run build && cd dist/ssr && npm i && npm run start",
|
||||
"backend": "json-server -p 5000 -d 600 -w src/services/json-server/db.json"
|
||||
"backend": "json-server -p 3000 -d 600 -w src/services/json-server/db.json --routes src/services/json-server/routes.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vee-validate/zod": "^4.12.2",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"vee-validate": "^4.12.2",
|
||||
"vue-image-zoomer": "^2.2.3",
|
||||
"zod": "^3.22.4",
|
||||
"axios": "^1.2.1",
|
||||
"vue-i18n": "^9.0.0",
|
||||
"pinia": "^2.0.11",
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"@vee-validate/zod": "^4.12.2",
|
||||
"@vue-stripe/vue-stripe": "^4.5.0",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"axios": "^1.2.1",
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^3.7.0",
|
||||
"pinia": "^2.0.11",
|
||||
"quasar": "^2.6.0",
|
||||
"vee-validate": "^4.12.2",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0"
|
||||
"vue-i18n": "^9.0.0",
|
||||
"vue-image-zoomer": "^2.2.3",
|
||||
"vue-router": "^4.0.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.3.1",
|
||||
"json-server": "^0.17.4",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"prettier": "^2.5.1",
|
||||
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
|
||||
"@quasar/app-vite": "^1.3.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"postcss": "^8.4.14"
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"json-server": "^0.17.4",
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "^2.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || ^16 || ^14.19",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -28,7 +28,7 @@ module.exports = configure(function (/* ctx */) {
|
|||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||
boot: ["i18n", "axios"],
|
||||
boot: ["i18n", "axios" /* , "stripe" */],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
css: ["app.scss"],
|
||||
|
@ -119,7 +119,7 @@ module.exports = configure(function (/* ctx */) {
|
|||
// directives: [],
|
||||
|
||||
// Quasar plugins
|
||||
plugins: ["Meta", "Loading"],
|
||||
plugins: ["Meta", "Loading", "Notify"],
|
||||
},
|
||||
|
||||
// animations: 'all', // --- includes all animations
|
||||
|
|
16
src/App.vue
16
src/App.vue
|
@ -1,11 +1,11 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "App",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App'
|
||||
});
|
||||
</script>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB |
|
@ -7,7 +7,8 @@ import { boot } from "quasar/wrappers";
|
|||
// good idea to move this instance creation inside of the
|
||||
// "export default () => {}" function below (which runs individually
|
||||
// for each client)
|
||||
const api = axios.create({ baseURL: "http://localhost:5000/" });
|
||||
const api = axios.create({ baseURL: "http://localhost:3000/jsonServer/" });
|
||||
const apiBack = axios.create({ baseURL: "http://localhost:9999/api/" });
|
||||
|
||||
export default boot(({ app }) => {
|
||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||
|
@ -17,8 +18,9 @@ export default boot(({ app }) => {
|
|||
// so you won't necessarily have to import axios in each vue file
|
||||
|
||||
app.config.globalProperties.$api = api;
|
||||
app.config.globalProperties.$apiBack = apiBack;
|
||||
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
|
||||
// so you can easily perform requests against your app's API
|
||||
});
|
||||
|
||||
export { api };
|
||||
export { api, apiBack };
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { boot } from "quasar/wrappers";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import messages from "src/i18n";
|
||||
import { createI18n } from "vue-i18n";
|
||||
|
||||
export default boot(({ app }) => {
|
||||
const i18n = createI18n({
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { StripePlugin } from "@vue-stripe/vue-stripe";
|
||||
import { boot } from "quasar/wrappers";
|
||||
|
||||
// "async" is optional;
|
||||
// more info on params: https://v2.quasar.dev/quasar-cli/boot-files
|
||||
export default boot(async ({ app, router, store }) => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const options = {
|
||||
pk: process.env.STRIPE_PUBLISHABLE_KEY,
|
||||
stripeAccount: process.env.STRIPE_ACCOUNT,
|
||||
apiVersion: process.env.API_VERSION,
|
||||
locale: process.env.LOCALE,
|
||||
};
|
||||
|
||||
app.use(StripePlugin, options);
|
||||
});
|
|
@ -1,3 +1,68 @@
|
|||
<script>
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { quasarNotify } from "src/functions/quasarNotify";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import IconCalendar from "../icons/IconCalendar.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "calendar-input",
|
||||
components: { IconCalendar },
|
||||
setup() {
|
||||
const getDate = new Date();
|
||||
const currentDay = getDate.getDate().toString().padStart(2, "0");
|
||||
const currentMonth = getDate.getMonth() + 1;
|
||||
const currentYear = getDate.getFullYear();
|
||||
const fullCurrentDate = `${currentYear}/${currentMonth}/${currentDay}`;
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { availability } = storeToRefs(formStore);
|
||||
const proxyDate = ref(fullCurrentDate);
|
||||
|
||||
const validationSchema = toTypedSchema(
|
||||
availabilitySchema.pick({ date: true })
|
||||
);
|
||||
const { errors, defineField, values } = useForm({
|
||||
validationSchema,
|
||||
});
|
||||
const [calendar, calendarAttrs] = defineField("date");
|
||||
|
||||
const onBlur = () => {
|
||||
availability.value.date = calendar.value;
|
||||
};
|
||||
availability.value.date = values.date;
|
||||
|
||||
watch(errors, (newErrors) => {
|
||||
if (newErrors.date) {
|
||||
quasarNotify({ message: newErrors.date, type: "erro" });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
availability,
|
||||
proxyDate,
|
||||
calendar,
|
||||
calendarAttrs,
|
||||
errors,
|
||||
onBlur,
|
||||
updateProxy() {
|
||||
proxyDate.value = availability.value.date;
|
||||
},
|
||||
optionsValidDates(date) {
|
||||
return date >= fullCurrentDate /* && date <= '2019/02/15' */;
|
||||
},
|
||||
save() {
|
||||
availability.value.date = proxyDate.value;
|
||||
calendar.value = proxyDate.value;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom-input-el calendar">
|
||||
<q-btn round size="sm" class="custom-date-btn">
|
||||
|
@ -30,74 +95,17 @@
|
|||
|
||||
<div class="custom-block-content">
|
||||
<p class="custom-head-paragraph">¿Cuándo?</p>
|
||||
<q-input
|
||||
<!-- <q-input
|
||||
class="custom-date-input"
|
||||
label="Elige una fecha"
|
||||
placeholder="DD/MM/YYYY"
|
||||
placeholder="Elige una fecha"
|
||||
v-model="calendar"
|
||||
mask="##/##/####"
|
||||
:error="!!errors.date"
|
||||
:error-message="errors.date"
|
||||
@blur="onBlur"
|
||||
borderless
|
||||
dense
|
||||
/>
|
||||
/> -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFormStore } from 'src/stores/forms';
|
||||
import { availabilitySchema } from 'src/utils/zod/schemas/availabilitySchema';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import IconCalendar from '../icons/IconCalendar.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'calendar-input',
|
||||
components: { IconCalendar },
|
||||
setup() {
|
||||
const getDate = new Date();
|
||||
const currentDay = getDate.getDate().toString().padStart(2, '0');
|
||||
const currentMonth = getDate.getMonth() + 1;
|
||||
const currentYear = getDate.getFullYear();
|
||||
const fullCurrentDate = `${currentYear}/${currentMonth}/${currentDay}`;
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { availability } = storeToRefs(formStore);
|
||||
const proxyDate = ref(fullCurrentDate);
|
||||
|
||||
const validationSchema = toTypedSchema(
|
||||
availabilitySchema.pick({ date: true })
|
||||
);
|
||||
const { errors, defineField } = useForm({
|
||||
validationSchema,
|
||||
});
|
||||
const [calendar, calendarAttrs] = defineField('date');
|
||||
const onBlur = () => {
|
||||
availability.value.date = calendar.value;
|
||||
};
|
||||
|
||||
return {
|
||||
availability,
|
||||
proxyDate,
|
||||
calendar,
|
||||
calendarAttrs,
|
||||
errors,
|
||||
onBlur,
|
||||
updateProxy() {
|
||||
proxyDate.value = availability.value.date;
|
||||
},
|
||||
optionsValidDates(date) {
|
||||
return date >= fullCurrentDate /* && date <= '2019/02/15' */;
|
||||
},
|
||||
save() {
|
||||
availability.value.date = proxyDate.value;
|
||||
calendar.value = proxyDate.value;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<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>
|
|
@ -0,0 +1,62 @@
|
|||
<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>
|
|
@ -1,38 +1,16 @@
|
|||
<template>
|
||||
<div class="custom-input-el postal-code">
|
||||
<IconPostalCode />
|
||||
<script>
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent, watch } from "vue";
|
||||
|
||||
<div class="custom-block-content">
|
||||
<p class="custom-head-paragraph">¿Dónde?</p>
|
||||
<!-- <p class="custom-main-paragraph">código postal</p> -->
|
||||
<q-input
|
||||
borderless
|
||||
class="custom-main-paragraph"
|
||||
v-model="postalCode"
|
||||
v-bind="postalCodeAttrs"
|
||||
:error="!!errors.postalCode"
|
||||
:error-message="errors.postalCode"
|
||||
label="código postal"
|
||||
placeholder="00000-000"
|
||||
mask="#####-###"
|
||||
@blur="onBlur"
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFormStore } from 'src/stores/forms';
|
||||
import { availabilitySchema } from 'src/utils/zod/schemas/availabilitySchema';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { defineComponent } from 'vue';
|
||||
import IconPostalCode from '../icons/IconPostalCode.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',
|
||||
name: "postal-code",
|
||||
components: { IconPostalCode },
|
||||
setup() {
|
||||
const formStore = useFormStore();
|
||||
|
@ -40,16 +18,23 @@ export default defineComponent({
|
|||
const validationSchema = toTypedSchema(
|
||||
availabilitySchema.pick({ postalCode: true }).partial()
|
||||
);
|
||||
const { errors, defineField } = useForm({
|
||||
const { errors, defineField, values } = useForm({
|
||||
validationSchema,
|
||||
initialValues: {
|
||||
postalCode: availability.value.date,
|
||||
postalCode: availability.value.postalCode,
|
||||
},
|
||||
});
|
||||
const [postalCode, postalCodeAttrs] = defineField('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,
|
||||
|
@ -60,3 +45,26 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom-input-el postal-code">
|
||||
<IconPostalCode />
|
||||
|
||||
<div class="custom-block-content">
|
||||
<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>
|
||||
|
|
|
@ -1,36 +1,22 @@
|
|||
<template>
|
||||
<div class="range-container">
|
||||
<p class="filter-item-paragraph">Precio</p>
|
||||
|
||||
<q-range
|
||||
v-model="rangePriceStore.rangeValue"
|
||||
@change="rangePriceStore.handlePriceRange"
|
||||
:min="0"
|
||||
:max="200"
|
||||
color="primary"
|
||||
/>
|
||||
|
||||
<div class="range-price-content">
|
||||
<p class="filter-item-paragraph min-price">
|
||||
Desde:
|
||||
<span class="green-text">{{ rangePriceStore.rangeValue.min }}€</span>
|
||||
</p>
|
||||
|
||||
<p class="filter-item-paragraph max-price">
|
||||
Hasta:
|
||||
<span class="green-text">{{ rangePriceStore.rangeValue.max }}€</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { useRangePriceStore } from 'src/stores/rangePrice';
|
||||
import { defineComponent } from 'vue';
|
||||
<script>
|
||||
import { useRangePriceStore } from "src/stores/rangePrice";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'range-component',
|
||||
name: "range-component",
|
||||
components: {},
|
||||
props: {
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
required: true,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const rangePriceStore = useRangePriceStore();
|
||||
|
||||
|
@ -39,6 +25,26 @@ export default defineComponent({
|
|||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="range-container">
|
||||
<p class="filter-item-paragraph">Precio</p>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<div class="range-price-content">
|
||||
<p class="filter-item-paragraph min-price">
|
||||
Desde:
|
||||
<span class="green-text">{{ min }}€</span>
|
||||
</p>
|
||||
|
||||
<p class="filter-item-paragraph max-price">
|
||||
Hasta:
|
||||
<span class="green-text">{{ max }}€</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.range-container {
|
||||
display: flex;
|
||||
|
|
|
@ -1,3 +1,25 @@
|
|||
<script>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SortSelect",
|
||||
components: {},
|
||||
setup() {
|
||||
const formStore = useFormStore();
|
||||
const { sortProductFilters } = storeToRefs(formStore);
|
||||
|
||||
async function handleOrder(order) {
|
||||
sortProductFilters.value.order = order;
|
||||
sortProductFilters.value.isOpenOrderFilter = false;
|
||||
}
|
||||
|
||||
return { sortProductFilters, handleOrder };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="order-values" role="select">
|
||||
<div
|
||||
|
@ -34,28 +56,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFormStore } from 'src/stores/forms';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SortSelect',
|
||||
components: {},
|
||||
setup() {
|
||||
const formStore = useFormStore();
|
||||
const { sortProductFilters } = storeToRefs(formStore);
|
||||
|
||||
function handleOrder(order) {
|
||||
sortProductFilters.value.order = order;
|
||||
sortProductFilters.value.isOpenOrderFilter = false;
|
||||
}
|
||||
|
||||
return { sortProductFilters, handleOrder };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-values {
|
||||
display: flex;
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { ref } from "vue";
|
||||
import IconArrowCircleRight from "../icons/IconArrowCircleRight.vue";
|
||||
import LogoWhite from "../logo/LogoWhite.vue";
|
||||
import Container from "../ui/Container.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "footer-component",
|
||||
components: { Container, LogoWhite, IconArrowCircleRight },
|
||||
setup() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const year = ref(currentYear);
|
||||
|
||||
return { year };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-footer class="custom-q-footer" elevated>
|
||||
<container class="footer-container">
|
||||
|
@ -26,10 +46,10 @@
|
|||
|
||||
<ul class="footer-list footer-secondary">
|
||||
<li class="footer-list-item list-links">
|
||||
<RouterLink to="/">Ramos</RouterLink><br />
|
||||
<RouterLink to="/">Plantas</RouterLink><br />
|
||||
<RouterLink to="/">Nosotros</RouterLink><br />
|
||||
<RouterLink to="/faq">FAQs</RouterLink><br />
|
||||
<RouterLink to="/categoria/ramos">Ramos</RouterLink><br />
|
||||
<RouterLink to="/categoria/plantas">Plantas</RouterLink><br />
|
||||
<!-- <RouterLink to="/">Nosotros</RouterLink><br />
|
||||
<RouterLink to="/faq">FAQs</RouterLink><br /> -->
|
||||
<RouterLink to="/contacta">Contacta</RouterLink>
|
||||
</li>
|
||||
|
||||
|
@ -39,11 +59,11 @@
|
|||
envío, calcular un nuevo envío o solicitar recogida, estamos para
|
||||
ayudarte. <br /><br />
|
||||
|
||||
<RouterLink to="/">
|
||||
<!-- <RouterLink to="/">
|
||||
<IconArrowCircleRight /> Preguntas frecuentes
|
||||
</RouterLink>
|
||||
</RouterLink> -->
|
||||
<br />
|
||||
<RouterLink to="/">
|
||||
<RouterLink to="/example">
|
||||
<IconArrowCircleRight /> Contacta con nosotros
|
||||
</RouterLink>
|
||||
</p>
|
||||
|
@ -51,11 +71,13 @@
|
|||
|
||||
<li class="footer-list-item">
|
||||
<p class="footer-list-content">
|
||||
Floranet ©2023 <br /><br />
|
||||
<RouterLink to="/">Aviso Legal</RouterLink> <br />
|
||||
<RouterLink to="/">Condiciones de uso</RouterLink><br />
|
||||
<RouterLink to="/">Política de cookies</RouterLink><br />
|
||||
<RouterLink to="/"> Política de Redes Sociales </RouterLink>
|
||||
Floranet ©{{ year }} <br /><br />
|
||||
<RouterLink to="/example">Aviso Legal</RouterLink> <br />
|
||||
<RouterLink to="/example">Condiciones de uso</RouterLink><br />
|
||||
<RouterLink to="/example">Política de cookies</RouterLink><br />
|
||||
<RouterLink to="/example">
|
||||
Política de Redes Sociales
|
||||
</RouterLink>
|
||||
<br /><br />
|
||||
|
||||
Desarrollado por diligent
|
||||
|
@ -67,19 +89,6 @@
|
|||
</q-footer>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import IconArrowCircleRight from '../icons/IconArrowCircleRight.vue';
|
||||
import LogoWhite from '../logo/LogoWhite.vue';
|
||||
import Container from '../ui/Container.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'footer-component',
|
||||
components: { Container, LogoWhite, IconArrowCircleRight },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-q-footer {
|
||||
position: static;
|
||||
|
@ -138,6 +147,16 @@ a:hover {
|
|||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
&.footer-primary {
|
||||
flex: 0 0 min(100%, 275px);
|
||||
}
|
||||
&.footer-secondary {
|
||||
gap: 50px;
|
||||
flex: 0 0 min(100%, 545px);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-md) {
|
||||
justify-content: space-evenly;
|
||||
&.footer-primary {
|
||||
|
@ -176,6 +195,9 @@ a:hover {
|
|||
}
|
||||
}
|
||||
}
|
||||
/* @media only screen and (max-width: $med-lg) and (max-width: 1200px) {
|
||||
gap: initial;
|
||||
} */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,28 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
import LogoGreenLight from "../logo/LogoGreenLight.vue";
|
||||
import LogoWhite from "../logo/LogoWhite.vue";
|
||||
import SendBanner from "../ui/SendBanner.vue";
|
||||
import NavLinks from "./NavLinks.vue";
|
||||
import UserArea from "./UserArea.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "header-primary",
|
||||
components: { SendBanner, UserArea, NavLinks, LogoWhite, LogoGreenLight },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
const { handleResize } = mobileStore;
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
return { isOpenNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-header
|
||||
class="header-container transparent"
|
||||
|
@ -23,31 +48,6 @@
|
|||
</q-header>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
import LogoGreenLight from '../logo/LogoGreenLight.vue';
|
||||
import LogoWhite from '../logo/LogoWhite.vue';
|
||||
import SendBanner from '../ui/SendBanner.vue';
|
||||
import NavLinks from './NavLinks.vue';
|
||||
import UserArea from './UserArea.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'header-primary',
|
||||
components: { SendBanner, UserArea, NavLinks, LogoWhite, LogoGreenLight },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
const { handleResize } = mobileStore;
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return { isOpenNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-container {
|
||||
display: flex;
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
import LogoWhite from "../logo/LogoWhite.vue";
|
||||
import SendBanner from "../ui/SendBanner.vue";
|
||||
import NavLinks from "./NavLinks.vue";
|
||||
import UserArea from "./UserArea.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "header-secondary",
|
||||
components: { SendBanner, UserArea, NavLinks, LogoWhite },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { handleResize } = mobileStore;
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-header class="header-container transparent">
|
||||
<send-banner
|
||||
|
@ -21,29 +44,6 @@
|
|||
</q-header>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
import LogoWhite from '../logo/LogoWhite.vue';
|
||||
import SendBanner from '../ui/SendBanner.vue';
|
||||
import NavLinks from './NavLinks.vue';
|
||||
import UserArea from './UserArea.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'header-secondary',
|
||||
components: { SendBanner, UserArea, NavLinks, LogoWhite },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { handleResize } = mobileStore;
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-container {
|
||||
display: flex;
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "NavLinks",
|
||||
components: {},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="menu-nav">
|
||||
<ul class="links-list">
|
||||
|
@ -24,18 +36,6 @@
|
|||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NavLinks',
|
||||
components: {},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-nav .links-list {
|
||||
display: flex;
|
||||
|
|
|
@ -1,13 +1,49 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import IconCart from "components/icons/IconCart.vue";
|
||||
import IconHamburger from "components/icons/IconHamburger.vue";
|
||||
/* import IconUser from "components/icons/IconUser.vue";
|
||||
import DropdownGroup from "components/quasar-components/dropdown/DropdownGroup.vue";
|
||||
import DropdownItem from "components/quasar-components/dropdown/DropdownItem.vue"; */
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useMobileStore } from "stores/mobileNav";
|
||||
|
||||
export default defineComponent({
|
||||
name: "user-area",
|
||||
components: {
|
||||
IconCart,
|
||||
// IconUser,
|
||||
// DropdownGroup,
|
||||
// DropdownItem,
|
||||
IconHamburger,
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { handleOpenMobileNav } = mobileStore;
|
||||
const cartStore = useCartStore();
|
||||
const { cartLength } = storeToRefs(cartStore);
|
||||
|
||||
return { handleOpenMobileNav, cartLength };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="user-area">
|
||||
<dropdown-group class="user-area-lang" label="Idioma">
|
||||
<!-- <dropdown-group class="user-area-lang" label="Idioma">
|
||||
<dropdown-item current-lang="en"> EN </dropdown-item>
|
||||
<dropdown-item current-lang="pt"> PT </dropdown-item>
|
||||
<dropdown-item current-lang="es"> ES </dropdown-item>
|
||||
</dropdown-group>
|
||||
<RouterLink class="user-area-link user" to="/"><icon-user /></RouterLink> -->
|
||||
|
||||
<RouterLink class="user-area-link user" to="/"><icon-user /></RouterLink>
|
||||
<RouterLink class="user-area-link cart" to="/checkout">
|
||||
<RouterLink
|
||||
class="user-area-link cart"
|
||||
to="/checkout"
|
||||
v-if="cartLength > 0"
|
||||
>
|
||||
<icon-cart />
|
||||
<span class="cart-count" :class="cartLength > 0 && 'active'">
|
||||
{{ cartLength }}
|
||||
|
@ -27,37 +63,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import IconCart from "components/icons/IconCart.vue";
|
||||
import IconHamburger from "components/icons/IconHamburger.vue";
|
||||
import IconUser from "components/icons/IconUser.vue";
|
||||
import DropdownGroup from "components/quasar-components/dropdown/DropdownGroup.vue";
|
||||
import DropdownItem from "components/quasar-components/dropdown/DropdownItem.vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useMobileStore } from "stores/mobileNav";
|
||||
|
||||
export default defineComponent({
|
||||
name: "user-area",
|
||||
components: {
|
||||
IconCart,
|
||||
IconUser,
|
||||
DropdownGroup,
|
||||
DropdownItem,
|
||||
IconHamburger,
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { handleOpenMobileNav } = mobileStore;
|
||||
const cartStore = useCartStore();
|
||||
const { cartLength } = storeToRefs(cartStore);
|
||||
return { handleOpenMobileNav, cartLength };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-area {
|
||||
display: flex;
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
<script>
|
||||
import { defineComponent, ref } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "product-carousel",
|
||||
components: {},
|
||||
setup() {
|
||||
return { slide: ref(1), fullscreen: ref(false) };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-carousel
|
||||
animated
|
||||
|
@ -24,16 +36,4 @@
|
|||
</q-carousel>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'product-carousel',
|
||||
components: {},
|
||||
setup() {
|
||||
return { slide: ref(1), fullscreen: ref(false) };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,3 +1,59 @@
|
|||
<script>
|
||||
import { useIntersectionObserver } from "@vueuse/core";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { defineComponent, ref } from "vue";
|
||||
|
||||
import Calendar from "src/components/@inputs/Calendar.vue";
|
||||
import PostalCode from "src/components/@inputs/PostalCode.vue";
|
||||
import IconSearch from "src/components/icons/IconSearch.vue";
|
||||
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
|
||||
export default defineComponent({
|
||||
name: "vertical-carousel-imgs",
|
||||
props: {
|
||||
imgsArr: {
|
||||
type: Array,
|
||||
default: () => [""],
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const {
|
||||
onSubmit,
|
||||
fields: { calendar, calendarAttrs, postalCode, postalCodeAttrs },
|
||||
errors,
|
||||
} = usePostalCalendar({ type: "home" });
|
||||
|
||||
const mobileStore = useMobileStore();
|
||||
const { screenWidth } = storeToRefs(mobileStore);
|
||||
const { handleResize } = mobileStore;
|
||||
|
||||
const target = ref(null);
|
||||
const navPos = ref("bottom");
|
||||
|
||||
useIntersectionObserver(target, ([{ isIntersecting }]) => {
|
||||
mobileStore.isCarouselVisible = isIntersecting;
|
||||
});
|
||||
document.addEventListener("resize", handleResize);
|
||||
|
||||
return {
|
||||
slide: ref("style"),
|
||||
navPos,
|
||||
target,
|
||||
screenWidth,
|
||||
errors,
|
||||
onSubmit,
|
||||
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
calendar,
|
||||
calendarAttrs,
|
||||
};
|
||||
},
|
||||
components: { IconSearch, Calendar, PostalCode },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="target"
|
||||
|
@ -8,7 +64,7 @@
|
|||
navigation-position="right"
|
||||
style="min-height: 100dvh"
|
||||
v-model="slide"
|
||||
:navigation="screenWidth >= 768"
|
||||
navigation
|
||||
autoplay
|
||||
infinite
|
||||
animated
|
||||
|
@ -35,65 +91,49 @@
|
|||
</p>
|
||||
</header>
|
||||
|
||||
<div class="carousel-content-body">
|
||||
<form @submit="onSubmit" class="carousel-content-body">
|
||||
<div class="carousel-content-item">
|
||||
<Calendar />
|
||||
<!-- <Calendar /> -->
|
||||
<Calendar>
|
||||
<q-input
|
||||
borderless
|
||||
class="custom-date-input"
|
||||
v-model="calendar"
|
||||
v-bind="calendarAttrs"
|
||||
:error="!!errors.date"
|
||||
placeholder="Elige una fecha"
|
||||
mask="##/##/####"
|
||||
dense
|
||||
/>
|
||||
</Calendar>
|
||||
</div>
|
||||
|
||||
<div class="carousel-content-item">
|
||||
<PostalCode />
|
||||
<!-- <PostalCode /> -->
|
||||
<PostalCode>
|
||||
<q-input
|
||||
borderless
|
||||
class="custom-main-paragraph"
|
||||
v-model="postalCode"
|
||||
v-bind="postalCodeAttrs"
|
||||
:error="!!errors.postalCode"
|
||||
placeholder="código postal"
|
||||
mask="#####"
|
||||
dense
|
||||
/>
|
||||
</PostalCode>
|
||||
</div>
|
||||
|
||||
<q-btn to="/categoria/ramos" class="btn carousel-content-item">
|
||||
<q-btn type="submit" class="btn carousel-content-item">
|
||||
<IconSearch /> ver disponibilidad
|
||||
</q-btn>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- <footer class="carousel-content-footer"></footer> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { useIntersectionObserver } from '@vueuse/core';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import Calendar from 'src/components/@inputs/Calendar.vue';
|
||||
import PostalCode from 'src/components/@inputs/PostalCode.vue';
|
||||
import IconSearch from 'src/components/icons/IconSearch.vue';
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'vertical-carousel-imgs',
|
||||
props: {
|
||||
imgsArr: {
|
||||
type: Array,
|
||||
default: () => [''],
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { screenWidth } = storeToRefs(mobileStore);
|
||||
const { handleResize } = mobileStore;
|
||||
|
||||
const target = ref(null);
|
||||
const navPos = ref('bottom');
|
||||
useIntersectionObserver(target, ([{ isIntersecting }]) => {
|
||||
mobileStore.isCarouselVisible = isIntersecting;
|
||||
});
|
||||
document.addEventListener('resize', handleResize);
|
||||
return {
|
||||
slide: ref('style'),
|
||||
navPos,
|
||||
target,
|
||||
screenWidth,
|
||||
};
|
||||
},
|
||||
components: { IconSearch, Calendar, PostalCode },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vertical-carousel-container {
|
||||
position: relative;
|
||||
|
@ -106,7 +146,7 @@ export default defineComponent({
|
|||
min-height: fit-content;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 70px;
|
||||
bottom: 110px;
|
||||
width: min(100%, 845px);
|
||||
margin: 0 auto;
|
||||
& .carousel-content-header {
|
||||
|
@ -135,12 +175,11 @@ export default defineComponent({
|
|||
flex-wrap: wrap;
|
||||
border-radius: 10px 0 0 10px;
|
||||
overflow: hidden;
|
||||
/* margin-bottom: 82px; */
|
||||
min-height: 150px;
|
||||
min-height: 96px;
|
||||
& .carousel-content-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 27px 53px 34px 48px;
|
||||
padding-inline: 53px 34px;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
&:first-child::after {
|
||||
|
@ -169,7 +208,7 @@ export default defineComponent({
|
|||
|
||||
&:last-child {
|
||||
border-radius: 0 10px 10px 0;
|
||||
padding: 27px 56px 34px;
|
||||
padding-inline: 56px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dropdown-group",
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-btn-dropdown :label="label">
|
||||
<q-list>
|
||||
|
@ -6,21 +21,6 @@
|
|||
</q-btn-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'dropdown-group',
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-btn {
|
||||
text-transform: initial;
|
||||
|
|
|
@ -1,24 +1,14 @@
|
|||
<template>
|
||||
<q-item clickable v-close-popup @click="onItemClick(currentLang)">
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
<slot></slot>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useLanguageStore } from 'src/stores/language';
|
||||
import { defineComponent } from 'vue';
|
||||
<script>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useLanguageStore } from "src/stores/language";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DropdownItem',
|
||||
name: "DropdownItem",
|
||||
props: {
|
||||
currentLang: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
|
@ -33,3 +23,13 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-item clickable v-close-popup @click="onItemClick(currentLang)">
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
<slot></slot>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import IconChatRoundedFill from "../icons/IconChatRoundedFill.vue";
|
||||
import Container from "../ui/Container.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dudas-section",
|
||||
components: { IconChatRoundedFill, Container },
|
||||
props: {
|
||||
isWhite: {
|
||||
type: Boolean,
|
||||
defaul: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="questions-section"
|
||||
|
@ -21,23 +38,6 @@
|
|||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
import IconChatRoundedFill from '../icons/IconChatRoundedFill.vue';
|
||||
import Container from '../ui/Container.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'dudas-section',
|
||||
components: { IconChatRoundedFill, Container },
|
||||
props: {
|
||||
isWhite: {
|
||||
type: Boolean,
|
||||
defaul: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.questions-section {
|
||||
background-color: $secondary-10;
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import IconCarr from "../icons/IconCar.vue";
|
||||
import IconChatRoundedFill from "../icons/IconChatRoundedFill.vue";
|
||||
import IconLocation from "../icons/IconLocation.vue";
|
||||
|
||||
// import Container from '../ui/Container.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: "info-section",
|
||||
components: {
|
||||
IconChatRoundedFill,
|
||||
IconLocation,
|
||||
IconCarr,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="info-container">
|
||||
<div class="info-content amplia">
|
||||
|
@ -40,24 +58,6 @@
|
|||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
import IconCarr from '../icons/IconCar.vue';
|
||||
import IconChatRoundedFill from '../icons/IconChatRoundedFill.vue';
|
||||
import IconLocation from '../icons/IconLocation.vue';
|
||||
|
||||
// import Container from '../ui/Container.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'info-section',
|
||||
components: {
|
||||
IconChatRoundedFill,
|
||||
IconLocation,
|
||||
IconCarr,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.info-container {
|
||||
display: flex;
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import IconQuestion from "../icons/IconQuestion.vue";
|
||||
import LogoWhite from "../logo/LogoWhite.vue";
|
||||
import Container from "../ui/Container.vue";
|
||||
import QuestionForm from "../ui/QuestionForm.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "question-component",
|
||||
components: { Container, LogoWhite, IconQuestion, QuestionForm },
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="question-container"
|
||||
|
@ -32,27 +49,6 @@
|
|||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
import IconQuestion from '../icons/IconQuestion.vue';
|
||||
import LogoWhite from '../logo/LogoWhite.vue';
|
||||
import Container from '../ui/Container.vue';
|
||||
import QuestionForm from '../ui/QuestionForm.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'question-component',
|
||||
components: { Container, LogoWhite, IconQuestion, QuestionForm },
|
||||
setup() {
|
||||
const handleSubmit = () => {
|
||||
console.log('Foi submit');
|
||||
};
|
||||
|
||||
return { handleSubmit, text: ref(''), checkbox: ref(false) };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
color: $white;
|
||||
|
|
|
@ -1,3 +1,29 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import IconChatRoundedFill from "../icons/IconChatRoundedFill.vue";
|
||||
import IconCheck from "../icons/IconCheck.vue";
|
||||
import IconFlower from "../icons/IconFlower.vue";
|
||||
import IconQuality from "../icons/IconQuality.vue";
|
||||
import IconService from "../icons/IconService.vue";
|
||||
import IconShop from "../icons/IconShop.vue";
|
||||
import LogoWhite from "../logo/LogoWhite.vue";
|
||||
import Container from "../ui/Container.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "reasons-section",
|
||||
components: {
|
||||
Container,
|
||||
LogoWhite,
|
||||
IconChatRoundedFill,
|
||||
IconQuality,
|
||||
IconFlower,
|
||||
IconShop,
|
||||
IconService,
|
||||
IconCheck,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="reasons-container"
|
||||
|
@ -84,32 +110,6 @@
|
|||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
import IconChatRoundedFill from '../icons/IconChatRoundedFill.vue';
|
||||
import IconCheck from '../icons/IconCheck.vue';
|
||||
import IconFlower from '../icons/IconFlower.vue';
|
||||
import IconQuality from '../icons/IconQuality.vue';
|
||||
import IconService from '../icons/IconService.vue';
|
||||
import IconShop from '../icons/IconShop.vue';
|
||||
import LogoWhite from '../logo/LogoWhite.vue';
|
||||
import Container from '../ui/Container.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'reasons-section',
|
||||
components: {
|
||||
Container,
|
||||
LogoWhite,
|
||||
IconChatRoundedFill,
|
||||
IconQuality,
|
||||
IconFlower,
|
||||
IconShop,
|
||||
IconService,
|
||||
IconCheck,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
p,
|
||||
h5 {
|
||||
|
|
|
@ -1,3 +1,57 @@
|
|||
<script>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { defineComponent, onMounted, ref } from "vue";
|
||||
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
import IconArrowCircleFilledLeft from "../icons/IconArrowCircleFilledLeft.vue";
|
||||
import IconArrowCircleFilledRight from "../icons/IconArrowCircleFilledRight.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SwiperComponent",
|
||||
components: { IconArrowCircleFilledLeft, IconArrowCircleFilledRight },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { screenWidth } = storeToRefs(mobileStore);
|
||||
|
||||
const prevBtn = ref(null);
|
||||
const nextBtn = ref(null);
|
||||
const swiperContainer = ref(null);
|
||||
const prevSwiperBtn = ref(null);
|
||||
const nextSwiperBtn = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('Montado!');
|
||||
|
||||
swiperContainer.value =
|
||||
document.querySelector("swiper-container").shadowRoot;
|
||||
prevSwiperBtn.value = swiperContainer.value.querySelector(
|
||||
".swiper-button-prev"
|
||||
);
|
||||
nextSwiperBtn.value = swiperContainer.value.querySelector(
|
||||
".swiper-button-next"
|
||||
);
|
||||
const swiperDisplay = "none";
|
||||
nextSwiperBtn.value.style.display = swiperDisplay;
|
||||
prevSwiperBtn.value.style.display = swiperDisplay;
|
||||
nextBtn.value = document.querySelector(".swiper-btn.next");
|
||||
prevBtn.value = document.querySelector(".swiper-btn.prev");
|
||||
});
|
||||
|
||||
return {
|
||||
screenWidth,
|
||||
handlePrev() {
|
||||
// console.log('Prev click');
|
||||
prevSwiperBtn.value.click();
|
||||
},
|
||||
handleNext() {
|
||||
// console.log('Next click');
|
||||
nextSwiperBtn.value.click();
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-btn
|
||||
title="previous button"
|
||||
|
@ -15,10 +69,8 @@
|
|||
class="swiper"
|
||||
:space-between="screenWidth > 1024 ? 56 : 25"
|
||||
:slides-per-view="'auto'"
|
||||
:centered-slides="true"
|
||||
:grabCursor="true"
|
||||
:navigation="true"
|
||||
:loop="true"
|
||||
:pagination="{
|
||||
dynamicBullets: true,
|
||||
clickable: true,
|
||||
|
@ -43,72 +95,6 @@
|
|||
</q-btn>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
import IconArrowCircleFilledLeft from '../icons/IconArrowCircleFilledLeft.vue';
|
||||
import IconArrowCircleFilledRight from '../icons/IconArrowCircleFilledRight.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SwiperComponent',
|
||||
components: { IconArrowCircleFilledLeft, IconArrowCircleFilledRight },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { screenWidth } = storeToRefs(mobileStore);
|
||||
|
||||
const prevBtn = ref(null);
|
||||
const nextBtn = ref(null);
|
||||
const swiperContainer = ref(null);
|
||||
const prevSwiperBtn = ref(null);
|
||||
const nextSwiperBtn = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('Montado!');
|
||||
|
||||
swiperContainer.value =
|
||||
document.querySelector('swiper-container').shadowRoot;
|
||||
prevSwiperBtn.value = swiperContainer.value.querySelector(
|
||||
'.swiper-button-prev'
|
||||
);
|
||||
nextSwiperBtn.value = swiperContainer.value.querySelector(
|
||||
'.swiper-button-next'
|
||||
);
|
||||
const swiperDisplay = 'none';
|
||||
nextSwiperBtn.value.style.display = swiperDisplay;
|
||||
prevSwiperBtn.value.style.display = swiperDisplay;
|
||||
nextBtn.value = document.querySelector('.swiper-btn.next');
|
||||
prevBtn.value = document.querySelector('.swiper-btn.prev');
|
||||
});
|
||||
|
||||
/* onUpdated(() => {
|
||||
console.log('Atualizado!');
|
||||
|
||||
console.groupCollapsed('%c Custom', 'color: tomato;');
|
||||
console.log({ prevBtn: prevBtn.value, nextBtn: nextBtn.value });
|
||||
console.groupEnd();
|
||||
|
||||
console.groupCollapsed('%c Swiper', 'color: hotpink;');
|
||||
console.log(prevSwiperBtn.value);
|
||||
console.groupEnd();
|
||||
}); */
|
||||
|
||||
return {
|
||||
screenWidth,
|
||||
handlePrev() {
|
||||
// console.log('Prev click');
|
||||
prevSwiperBtn.value.click();
|
||||
},
|
||||
handleNext() {
|
||||
// console.log('Next click');
|
||||
nextSwiperBtn.value.click();
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.swiper {
|
||||
height: 100%;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "button-component",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="btn" type="button">
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'button-component',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
|
|
|
@ -1,8 +1,76 @@
|
|||
<script>
|
||||
import { defineComponent, ref } from "vue";
|
||||
import IconEyes from "../icons/IconEyes.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "card-component",
|
||||
components: { IconEyes },
|
||||
props: {
|
||||
price: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: true,
|
||||
},
|
||||
discount: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
imgSrc: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: true,
|
||||
},
|
||||
imgClass: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
headClass: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
isNew: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "md-card",
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup({ price, discount }) {
|
||||
const isLoaded = ref(false);
|
||||
const percent = +discount / 100;
|
||||
const priceWithoutLetter = ~~price.replaceAll("€", "");
|
||||
const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent);
|
||||
|
||||
const onLoad = () => {
|
||||
isLoaded.value = true;
|
||||
};
|
||||
|
||||
return { onLoad, isLoaded, finalValue, priceWithoutLetter };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-container" :class="size">
|
||||
<RouterLink
|
||||
:to="`/product/${id}`"
|
||||
class="card-container-head"
|
||||
:class="[headClass]"
|
||||
role="heading"
|
||||
>
|
||||
<div v-if="isNew || discount" class="tags">
|
||||
|
@ -12,9 +80,17 @@
|
|||
|
||||
<img
|
||||
class="card-img"
|
||||
:src="imgSrc ? imgSrc : '../../assets/empty-img.png'"
|
||||
:class="[imgClass]"
|
||||
:src="imgSrc ? imgSrc : '../../assets/empty-img.jpg'"
|
||||
:alt="alt"
|
||||
@load="handleLoad"
|
||||
:key="imgSrc"
|
||||
@load="onLoad"
|
||||
/>
|
||||
<q-skeleton
|
||||
v-if="!isLoaded"
|
||||
class="skeleton"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
|
||||
<div class="head-hovered">
|
||||
|
@ -24,113 +100,50 @@
|
|||
</RouterLink>
|
||||
|
||||
<div class="card-container-body">
|
||||
<p class="card-name" v-if="productName">{{ productName }}</p>
|
||||
<p class="card-name" v-if="title">{{ title }}</p>
|
||||
|
||||
<div class="card-values">
|
||||
<p class="price" v-if="productValue">{{ productValue }}€</p>
|
||||
<p class="price offer tachado" v-if="+valueWithDiscount > 0">
|
||||
{{ valueWithDiscount() }}€
|
||||
<p class="price" v-if="finalValue">{{ finalValue }}€</p>
|
||||
<p class="price offer tachado" v-if="priceWithoutLetter !== finalValue">
|
||||
{{ priceWithoutLetter }}€
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import IconEyes from '../icons/IconEyes.vue';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'card-component',
|
||||
components: { IconEyes },
|
||||
props: {
|
||||
productValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
productName: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
discount: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
imgSrc: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
isNew: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup({ productValue, discount }) {
|
||||
const loadingImg = ref(true);
|
||||
const handleLoad = () => {
|
||||
loadingImg.value = false;
|
||||
};
|
||||
|
||||
const valueWithDiscount = () => {
|
||||
const productWithouCaracters = ~~productValue.replaceAll('€', '');
|
||||
|
||||
if (discount) {
|
||||
const finalValue = (+discount / 100) * productWithouCaracters;
|
||||
return finalValue.toFixed(2);
|
||||
}
|
||||
};
|
||||
|
||||
return { handleLoad, valueWithDiscount };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
user-select: none;
|
||||
height: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid $secondary;
|
||||
}
|
||||
|
||||
&.sm-card {
|
||||
width: min(100%, 166px);
|
||||
& .card-container-head {
|
||||
border-radius: 10px;
|
||||
min-height: 188px;
|
||||
}
|
||||
}
|
||||
|
||||
&.md-card {
|
||||
width: min(100%, 310px);
|
||||
& .card-container-head {
|
||||
min-height: 352px;
|
||||
height: 300px;
|
||||
@media only screen and (min-width: $med-hg) {
|
||||
height: 352px;
|
||||
}
|
||||
@media only screen and (max-width: $med-md) {
|
||||
border-radius: 10px;
|
||||
height: 188px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.lg-card {
|
||||
width: min(100%, 360px);
|
||||
& .card-container-head {
|
||||
min-height: 409px;
|
||||
height: 409px;
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
border-radius: 10px;
|
||||
height: 188px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +151,15 @@ export default defineComponent({
|
|||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
max-height: 409px;
|
||||
&.list-products-head {
|
||||
min-height: 365px;
|
||||
@media only screen and (max-width: $med-sm) {
|
||||
min-height: 190px;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $primary-light;
|
||||
}
|
||||
|
@ -193,8 +215,27 @@ export default defineComponent({
|
|||
& .card-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
&.list-products {
|
||||
@media only screen and (min-width: calc($med-md + 1px)) {
|
||||
min-height: 352px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-md) {
|
||||
min-height: 190px;
|
||||
}
|
||||
}
|
||||
|
||||
&.carousel {
|
||||
max-height: 410px;
|
||||
}
|
||||
}
|
||||
|
||||
& .skeleton {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: $primary;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
& .head-hovered {
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<template>
|
||||
<q-btn flat @click="handleClick"><IconChat /></q-btn>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
import IconChat from '../icons/IconChat.vue';
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import IconChat from "../icons/IconChat.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'chat-component',
|
||||
name: "chat-component",
|
||||
components: { IconChat },
|
||||
setup() {
|
||||
const handleClick = () => {
|
||||
console.log('click');
|
||||
console.log("click");
|
||||
};
|
||||
return { handleClick };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-btn flat @click="handleClick"><IconChat /></q-btn>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
<template>
|
||||
<component
|
||||
:is="tag"
|
||||
class="custom-container"
|
||||
:class="cardContainer && 'cards-container'"
|
||||
>
|
||||
<slot></slot>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
|
@ -29,3 +19,13 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="tag"
|
||||
class="custom-container"
|
||||
:class="cardContainer && 'cards-container'"
|
||||
>
|
||||
<slot></slot>
|
||||
</component>
|
||||
</template>
|
||||
|
|
|
@ -1,45 +1,12 @@
|
|||
<template>
|
||||
<div class="mobile-nav-container" :class="!isOpenNav && 'hide'">
|
||||
<header class="mobile-nav-links">
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/categoria/ramos"
|
||||
>Ramos</RouterLink
|
||||
>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/categoria/plantas">
|
||||
Plantas
|
||||
</RouterLink>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/"
|
||||
>Floranet</RouterLink
|
||||
>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/">FAQs</RouterLink>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/"
|
||||
>Contacta</RouterLink
|
||||
>
|
||||
</header>
|
||||
<script>
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
|
||||
<div class="mobile-nav-lang">
|
||||
<p class="mobile-lang-paragraph">Idioma</p>
|
||||
<select class="mobile-lang-select" name="lang">
|
||||
<option value="">Español</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<footer class="mobile-nav-footer">
|
||||
<RouterLink to="/" class="btn outlined rounded sm-btn">
|
||||
¿Tienes dudas? hablemos <IconChatRoundedFill />
|
||||
</RouterLink>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineComponent, watch } from 'vue';
|
||||
import IconChatRoundedFill from '../icons/IconChatRoundedFill.vue';
|
||||
import { storeToRefs } from "pinia";
|
||||
import { defineComponent, watch } from "vue";
|
||||
import IconChatRoundedFill from "../icons/IconChatRoundedFill.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'mobile-nav',
|
||||
name: "mobile-nav",
|
||||
components: { IconChatRoundedFill },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
|
@ -51,16 +18,16 @@ export default defineComponent({
|
|||
|
||||
function closeNav() {
|
||||
isOpenNav.value = false;
|
||||
console.log('foi click');
|
||||
console.log("foi click");
|
||||
}
|
||||
|
||||
watch(isOpenNav, (newValue) => {
|
||||
if (newValue) {
|
||||
setBodyStyle('hidden');
|
||||
setBodyStyle("hidden");
|
||||
window.scrollTo(0, 0);
|
||||
return;
|
||||
}
|
||||
setBodyStyle('visible');
|
||||
setBodyStyle("visible");
|
||||
});
|
||||
|
||||
return { isOpenNav, closeNav };
|
||||
|
@ -68,6 +35,39 @@ export default defineComponent({
|
|||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mobile-nav-container" :class="!isOpenNav && 'hide'">
|
||||
<header class="mobile-nav-links">
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/categoria/ramos">
|
||||
Ramos
|
||||
</RouterLink>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/categoria/plantas">
|
||||
Plantas
|
||||
</RouterLink>
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/example">
|
||||
Floranet
|
||||
</RouterLink>
|
||||
<!-- <RouterLink @click="closeNav" class="mobile-link" to="/">FAQs</RouterLink> -->
|
||||
<RouterLink @click="closeNav" class="mobile-link" to="/example">
|
||||
Contacta
|
||||
</RouterLink>
|
||||
</header>
|
||||
|
||||
<!-- <div class="mobile-nav-lang">
|
||||
<p class="mobile-lang-paragraph">Idioma</p>
|
||||
<select class="mobile-lang-select" name="lang">
|
||||
<option value="">Español</option>
|
||||
</select>
|
||||
</div> -->
|
||||
|
||||
<footer class="mobile-nav-footer">
|
||||
<RouterLink to="/" class="btn outlined rounded sm-btn">
|
||||
¿Tienes dudas? hablemos <IconChatRoundedFill />
|
||||
</RouterLink>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mobile-nav-container {
|
||||
position: absolute;
|
||||
|
|
|
@ -1,3 +1,78 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
||||
import Calendar from "../@inputs/Calendar.vue";
|
||||
import PostalCode from "../@inputs/PostalCode.vue";
|
||||
import PriceRange from "../@inputs/PriceRange.vue";
|
||||
import IconCloseModal from "../icons/IconCloseModal.vue";
|
||||
import IconSearch from "../icons/IconSearch.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "modal-component",
|
||||
components: {
|
||||
IconSearch,
|
||||
IconCloseModal,
|
||||
PriceRange,
|
||||
Calendar,
|
||||
PostalCode,
|
||||
},
|
||||
props: {
|
||||
modalItem: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
typeModal: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
setup({ modalItem, typeModal }) {
|
||||
const {
|
||||
onSubmit,
|
||||
fields: {
|
||||
calendar,
|
||||
calendarAttrs,
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
priceRange,
|
||||
priceRangeAttrs,
|
||||
},
|
||||
errors,
|
||||
modalStore,
|
||||
} = usePostalCalendar({
|
||||
modalItem,
|
||||
type: typeModal,
|
||||
});
|
||||
|
||||
const modalTextContent = {
|
||||
isOpenAvailability: {
|
||||
title: "Disponibilidad",
|
||||
subtitle: "Elige a dónde y cuando quieres enviar el ramo",
|
||||
},
|
||||
isOpenFilters: {
|
||||
title: "Filtros",
|
||||
subtitle: "Personaliza tu búsqueda",
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
errors,
|
||||
onSubmit,
|
||||
modalStore,
|
||||
modalTextContent,
|
||||
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
calendar,
|
||||
calendarAttrs,
|
||||
priceRange,
|
||||
priceRangeAttrs,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-dialog v-model="modalStore[modalItem]" class="modal-container">
|
||||
<q-card class="modal-content">
|
||||
|
@ -23,9 +98,22 @@
|
|||
</p>
|
||||
|
||||
<div class="modal-body-content">
|
||||
<div v-if="modalItem === 'isOpenFilters'" class="modal-body-filters">
|
||||
<form @submit="onSubmit" id="filters-form">
|
||||
<div
|
||||
v-if="modalItem === 'isOpenFilters'"
|
||||
class="modal-body-filters"
|
||||
>
|
||||
<div class="filter-field">
|
||||
<PriceRange />
|
||||
<PriceRange :min="priceRange.min" :max="priceRange.max">
|
||||
<q-range
|
||||
v-model="priceRange"
|
||||
v-bind="priceRangeAttrs"
|
||||
:min="0"
|
||||
:max="200"
|
||||
color="primary"
|
||||
/>
|
||||
<!-- @change="rangePriceStore.handlePriceRange" -->
|
||||
</PriceRange>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -33,75 +121,63 @@
|
|||
v-if="modalItem === 'isOpenAvailability'"
|
||||
class="modal-body-availability"
|
||||
>
|
||||
<calendar />
|
||||
<Calendar>
|
||||
<q-input
|
||||
borderless
|
||||
class="custom-date-input"
|
||||
v-model="calendar"
|
||||
v-bind="calendarAttrs"
|
||||
:error="!!errors.date"
|
||||
placeholder="Elige una fecha"
|
||||
mask="##/##/####"
|
||||
dense
|
||||
/>
|
||||
</Calendar>
|
||||
|
||||
<postal-code />
|
||||
<PostalCode>
|
||||
<q-input
|
||||
borderless
|
||||
class="custom-main-paragraph"
|
||||
v-model="postalCode"
|
||||
v-bind="postalCodeAttrs"
|
||||
:error="!!errors.postalCode"
|
||||
placeholder="código postal"
|
||||
mask="#####"
|
||||
dense
|
||||
/>
|
||||
</PostalCode>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-btn
|
||||
flat
|
||||
type="submit"
|
||||
align="center"
|
||||
class="modal-footer"
|
||||
form="filters-form"
|
||||
>
|
||||
<!-- v-close-popup -->
|
||||
<IconSearch />
|
||||
<p>ver disponibilidad</p>
|
||||
</q-btn>
|
||||
<!-- <q-btn
|
||||
flat
|
||||
type="button"
|
||||
align="center"
|
||||
class="modal-footer"
|
||||
@click="modalStore.handleSubmit({ isModalContent: modalItem })"
|
||||
form="filters-form"
|
||||
@click="handleSubmit({ content: modalItem })"
|
||||
v-close-popup
|
||||
>
|
||||
<IconSearch />
|
||||
<p>ver disponibilidad</p>
|
||||
</q-btn>
|
||||
</q-btn> -->
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { useModalStore } from 'src/stores/modalStore';
|
||||
import Calendar from '../@inputs/Calendar.vue';
|
||||
import PostalCode from '../@inputs/PostalCode.vue';
|
||||
import PriceRange from '../@inputs/PriceRange.vue';
|
||||
import IconCloseModal from '../icons/IconCloseModal.vue';
|
||||
import IconSearch from '../icons/IconSearch.vue';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'modal-component',
|
||||
components: {
|
||||
IconSearch,
|
||||
IconCloseModal,
|
||||
PriceRange,
|
||||
Calendar,
|
||||
PostalCode,
|
||||
},
|
||||
props: {
|
||||
modalItem: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const modalStore = useModalStore();
|
||||
const modalTextContent = {
|
||||
isOpenAvailability: {
|
||||
title: 'Disponibilidad',
|
||||
subtitle: 'Elige a dónde y cuando quieres enviar el ramo',
|
||||
},
|
||||
isOpenFilters: {
|
||||
title: 'Filtros',
|
||||
subtitle: 'Personaliza tu búsqueda',
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
modalStore,
|
||||
modalTextContent,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-container {
|
||||
& .q-dialog__backdrop {
|
||||
|
|
|
@ -1,3 +1,70 @@
|
|||
<script>
|
||||
import { useQuasar } from "quasar";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import IconArrowRightOne from "src/components/icons/IconArrowRightOne.vue";
|
||||
import { questionSchema } from "src/utils/zod/schemas/questionSchema";
|
||||
|
||||
export default defineComponent({
|
||||
name: "QuestionForm",
|
||||
components: { IconArrowRightOne },
|
||||
setup() {
|
||||
const $q = useQuasar();
|
||||
const formStore = useFormStore();
|
||||
const { handleQuestionData } = formStore;
|
||||
|
||||
const { errors, meta, defineField, handleSubmit, handleReset } = useForm({
|
||||
validationSchema: questionSchema,
|
||||
initialValues: {
|
||||
terms: false,
|
||||
},
|
||||
});
|
||||
const [firstName, firstNameAttrs] = defineField("name");
|
||||
const [secondName, secondNameAttrs] = defineField("surname");
|
||||
const [email, emailAttrs] = defineField("email");
|
||||
const [phone, phoneAttrs] = defineField("phone");
|
||||
const [query, queryAttrs] = defineField("query");
|
||||
const [message, messageAttrs] = defineField("message");
|
||||
const [terms, termsAttrs] = defineField("terms");
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
console.log(values);
|
||||
handleQuestionData(values);
|
||||
handleReset();
|
||||
if (!terms.value) {
|
||||
$q.notify({
|
||||
color: "negative",
|
||||
message: "Primero tienes que aceptar la licencia y las condiciones",
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
errors,
|
||||
firstName,
|
||||
firstNameAttrs,
|
||||
secondName,
|
||||
secondNameAttrs,
|
||||
email,
|
||||
emailAttrs,
|
||||
phone,
|
||||
phoneAttrs,
|
||||
query,
|
||||
queryAttrs,
|
||||
message,
|
||||
messageAttrs,
|
||||
terms,
|
||||
termsAttrs,
|
||||
onSubmit,
|
||||
meta,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="question-form-body" @submit.prevent="onSubmit">
|
||||
<div class="fields">
|
||||
|
@ -90,73 +157,6 @@
|
|||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useQuasar } from "quasar";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import IconArrowRightOne from "src/components/icons/IconArrowRightOne.vue";
|
||||
import { questionSchema } from "src/utils/zod/schemas/questionSchema";
|
||||
|
||||
export default defineComponent({
|
||||
name: "QuestionForm",
|
||||
components: { IconArrowRightOne },
|
||||
setup() {
|
||||
const $q = useQuasar();
|
||||
const formStore = useFormStore();
|
||||
const { handleQuestionData } = formStore;
|
||||
|
||||
const { errors, meta, defineField, handleSubmit, handleReset } = useForm({
|
||||
validationSchema: questionSchema,
|
||||
initialValues: {
|
||||
terms: false,
|
||||
},
|
||||
});
|
||||
const [firstName, firstNameAttrs] = defineField("name");
|
||||
const [secondName, secondNameAttrs] = defineField("surname");
|
||||
const [email, emailAttrs] = defineField("email");
|
||||
const [phone, phoneAttrs] = defineField("phone");
|
||||
const [query, queryAttrs] = defineField("query");
|
||||
const [message, messageAttrs] = defineField("message");
|
||||
const [terms, termsAttrs] = defineField("terms");
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
console.log(values);
|
||||
handleQuestionData(values);
|
||||
handleReset();
|
||||
if (!terms.value) {
|
||||
$q.notify({
|
||||
color: "negative",
|
||||
message: "Primero tienes que aceptar la licencia y las condiciones",
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
errors,
|
||||
firstName,
|
||||
firstNameAttrs,
|
||||
secondName,
|
||||
secondNameAttrs,
|
||||
email,
|
||||
emailAttrs,
|
||||
phone,
|
||||
phoneAttrs,
|
||||
query,
|
||||
queryAttrs,
|
||||
message,
|
||||
messageAttrs,
|
||||
terms,
|
||||
termsAttrs,
|
||||
onSubmit,
|
||||
meta,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.question-form-body {
|
||||
display: flex;
|
||||
|
|
|
@ -1,3 +1,28 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "send-banner",
|
||||
props: {
|
||||
leftText: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: true,
|
||||
},
|
||||
rightText: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: true,
|
||||
},
|
||||
mobileText: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="send-banner-container">
|
||||
<div class="send-banner-wrapper custom-container">
|
||||
|
@ -8,31 +33,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'send-banner',
|
||||
props: {
|
||||
leftText: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
rightText: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
mobileText: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.send-banner-container {
|
||||
display: flex;
|
||||
|
|
|
@ -151,6 +151,9 @@ html {
|
|||
grid-auto-rows: 1fr;
|
||||
gap: 47px;
|
||||
}
|
||||
&.category-container {
|
||||
width: min(100%, 1920px);
|
||||
}
|
||||
|
||||
//! 1440px
|
||||
@media only screen and (max-width: calc($med-xlg + 100px)) {
|
||||
|
@ -164,7 +167,7 @@ html {
|
|||
padding-inline: 16px;
|
||||
&.cards-container {
|
||||
gap: 25px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 166px), 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 235px), 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,7 +294,7 @@ p,
|
|||
color: $title-default;
|
||||
}
|
||||
|
||||
& .pege-subtitle {
|
||||
& .page-subtitle {
|
||||
font-family: $font-questrial;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
|
@ -313,3 +316,42 @@ body {
|
|||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.carousel-content-item .custom-block-content .custom-head-paragraph {
|
||||
margin-bottom: -14px;
|
||||
}
|
||||
|
||||
//! QUASAR
|
||||
|
||||
.q-virtual-scroll__content .q-item .q-item__label {
|
||||
font-family: $font-questrial;
|
||||
font-size: $font-14;
|
||||
color: $text-default;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.28px;
|
||||
}
|
||||
|
||||
.checkout-fields {
|
||||
& .q-placeholder::placeholder,
|
||||
& .q-field__label {
|
||||
font-family: $font-questrial;
|
||||
font-size: $font-14;
|
||||
color: $text-default;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.28px;
|
||||
}
|
||||
}
|
||||
|
||||
.q-carousel__navigation {
|
||||
@media only screen and (max-width: $med-md) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-input-el {
|
||||
& .q-placeholder::placeholder {
|
||||
font-family: $font-questrial;
|
||||
color: $text-normal-100 !important;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { Notify } from "quasar";
|
||||
|
||||
export function quasarNotify({ message = "", type, timeout = 1000 }) {
|
||||
const obj = {
|
||||
success: () =>
|
||||
Notify.create({
|
||||
message,
|
||||
color: "positive",
|
||||
position: "top",
|
||||
icon: "check_circle",
|
||||
timeout,
|
||||
}),
|
||||
warning: () =>
|
||||
Notify.create({
|
||||
message,
|
||||
color: "warning",
|
||||
position: "top",
|
||||
icon: "report_problem",
|
||||
timeout,
|
||||
}),
|
||||
erro: () =>
|
||||
Notify.create({
|
||||
message,
|
||||
color: "negative",
|
||||
position: "top",
|
||||
icon: "report_problem",
|
||||
timeout,
|
||||
}),
|
||||
};
|
||||
|
||||
if (type) {
|
||||
return obj[type]() || console.error(`Type is invalid! TYPE: ${type}`);
|
||||
}
|
||||
console.error("Type is required, success, warning or erro");
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useForm } from "vee-validate";
|
||||
import { watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { apiBack } from "src/boot/axios";
|
||||
import { quasarNotify } from "src/functions/quasarNotify";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
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";
|
||||
|
||||
export function usePostalCalendar({ modalItem = "", type = "home" }) {
|
||||
const { push } = useRouter();
|
||||
|
||||
const rangePriceStore = useRangePriceStore();
|
||||
const { rangeValue } = storeToRefs(rangePriceStore);
|
||||
|
||||
const modalStore = useModalStore();
|
||||
const { isOpenAvailability } = storeToRefs(modalStore);
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { availability, sortProductFilters } = storeToRefs(formStore);
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { addToCart, getProducts } = cartStore;
|
||||
const { currentProduct, products } = storeToRefs(cartStore);
|
||||
|
||||
const min = 0;
|
||||
const max = 200;
|
||||
|
||||
const { handleSubmit, handleReset, defineField, errors } = useForm({
|
||||
validationSchema: toTypedSchema(
|
||||
type !== "filter" ? availabilitySchema : rangePriceSchema
|
||||
),
|
||||
initialValues: {
|
||||
range: {
|
||||
min,
|
||||
max,
|
||||
},
|
||||
postalCode: "",
|
||||
date: "",
|
||||
},
|
||||
});
|
||||
const [calendar, calendarAttrs] = defineField("date");
|
||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
||||
const [priceRange, priceRangeAttrs] = defineField("range");
|
||||
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" }),
|
||||
};
|
||||
const keys = Object.keys(newErrors);
|
||||
keys.forEach((key) => {
|
||||
errorsObj[key]();
|
||||
});
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
const postalAndDateParams = {
|
||||
postalCode: values.postalCode,
|
||||
dateExpired: values.date,
|
||||
};
|
||||
|
||||
const objVal = {
|
||||
home: async () => {
|
||||
console.log(type);
|
||||
|
||||
getProducts(
|
||||
{
|
||||
itens: 30,
|
||||
category: 1,
|
||||
postalCode: values.postalCode,
|
||||
dateExpired: values.date,
|
||||
},
|
||||
() => push("/categoria/ramos")
|
||||
);
|
||||
},
|
||||
product: async () => {
|
||||
console.log(type);
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { data },
|
||||
} = await apiBack.get(`products/slug/${currentProduct.value.id}`, {
|
||||
params: postalAndDateParams,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
push("/");
|
||||
}
|
||||
},
|
||||
availability: async () => {
|
||||
console.log(type);
|
||||
|
||||
getProducts({
|
||||
itens: 20,
|
||||
postalCode: values.postalCode,
|
||||
dateExpired: values.date,
|
||||
});
|
||||
},
|
||||
filter: async () => {
|
||||
console.log(type);
|
||||
|
||||
rangeValue.value.max = values.range.max;
|
||||
rangeValue.value.min = values.range.min;
|
||||
|
||||
const category = sortProductFilters.value.category;
|
||||
const categoryObj = {
|
||||
plantas: 1,
|
||||
ramos: 2,
|
||||
};
|
||||
|
||||
const params = {
|
||||
itens: 20,
|
||||
category: categoryObj[category],
|
||||
minPrice: values.range.min,
|
||||
maxPrice: values.range.max,
|
||||
};
|
||||
|
||||
getProducts(params);
|
||||
},
|
||||
};
|
||||
objVal[type]() ||
|
||||
console.error(
|
||||
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
|
||||
);
|
||||
|
||||
if (modalItem) {
|
||||
modalStore[modalItem] = false;
|
||||
}
|
||||
handleReset();
|
||||
});
|
||||
|
||||
return {
|
||||
onSubmit,
|
||||
modalStore,
|
||||
fields: {
|
||||
calendar,
|
||||
calendarAttrs,
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
priceRange,
|
||||
priceRangeAttrs,
|
||||
dedication,
|
||||
dedicationAttrs,
|
||||
},
|
||||
errors,
|
||||
};
|
||||
}
|
|
@ -1,6 +1,3 @@
|
|||
// This is just an example,
|
||||
// so you can safely delete all default props below
|
||||
|
||||
export default {
|
||||
failed: "Action failed",
|
||||
success: "Action was successful",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
failed: "Acción fallida",
|
||||
success: "La acción se ha realizado correctamente",
|
||||
};
|
|
@ -1,5 +1,9 @@
|
|||
import enUS from "./en-US";
|
||||
import es from "./es";
|
||||
import pt from "./pt";
|
||||
|
||||
export default {
|
||||
"en-US": enUS,
|
||||
es,
|
||||
pt,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
failed: "Ação falhou",
|
||||
success: "Ação foi um sucesso",
|
||||
};
|
|
@ -1,22 +1,4 @@
|
|||
<template>
|
||||
<q-layout>
|
||||
<q-no-ssr>
|
||||
<header-secondary />
|
||||
</q-no-ssr>
|
||||
<mobile-nav />
|
||||
|
||||
<q-page-container class="no-padding padding-top more">
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
|
||||
<reasons-section />
|
||||
<question-section />
|
||||
|
||||
<footer-component />
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import FooterComponent from "src/components/footer/FooterComponent.vue";
|
||||
|
@ -46,4 +28,22 @@ export default defineComponent({
|
|||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-layout>
|
||||
<q-no-ssr>
|
||||
<header-secondary />
|
||||
</q-no-ssr>
|
||||
<mobile-nav />
|
||||
|
||||
<q-page-container class="no-padding padding-top more">
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
|
||||
<reasons-section />
|
||||
<question-section />
|
||||
|
||||
<footer-component />
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,3 +1,32 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { storeToRefs } from "pinia";
|
||||
import FooterComponent from "src/components/footer/FooterComponent.vue";
|
||||
import HeaderSecondary from "src/components/header/HeaderSecondary.vue";
|
||||
import DudasSection from "src/components/sections/DudasSection.vue";
|
||||
import QuestionSection from "src/components/sections/QuestionSection.vue";
|
||||
import MobileNav from "src/components/ui/MobileNav.vue";
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ProductLayout",
|
||||
components: {
|
||||
HeaderSecondary,
|
||||
FooterComponent,
|
||||
QuestionSection,
|
||||
MobileNav,
|
||||
DudasSection,
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
|
||||
return { isOpenNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-layout>
|
||||
<q-no-ssr>
|
||||
|
@ -17,33 +46,4 @@
|
|||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import FooterComponent from 'src/components/footer/FooterComponent.vue';
|
||||
import HeaderSecondary from 'src/components/header/HeaderSecondary.vue';
|
||||
import DudasSection from 'src/components/sections/DudasSection.vue';
|
||||
import QuestionSection from 'src/components/sections/QuestionSection.vue';
|
||||
import MobileNav from 'src/components/ui/MobileNav.vue';
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProductLayout',
|
||||
components: {
|
||||
HeaderSecondary,
|
||||
FooterComponent,
|
||||
QuestionSection,
|
||||
MobileNav,
|
||||
DudasSection,
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
|
||||
return { isOpenNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,3 +1,25 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { storeToRefs } from "pinia";
|
||||
import FooterComponent from "src/components/footer/FooterComponent.vue";
|
||||
import HeaderSecondary from "src/components/header/HeaderSecondary.vue";
|
||||
import ReasonsSection from "src/components/sections/ReasonsSection.vue";
|
||||
import MobileNav from "src/components/ui/MobileNav.vue";
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
|
||||
export default defineComponent({
|
||||
name: "DefaultLayout",
|
||||
components: { FooterComponent, ReasonsSection, HeaderSecondary, MobileNav },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
|
||||
return { isOpenNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-layout>
|
||||
<q-no-ssr>
|
||||
|
@ -15,26 +37,4 @@
|
|||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import FooterComponent from 'src/components/footer/FooterComponent.vue';
|
||||
import HeaderSecondary from 'src/components/header/HeaderSecondary.vue';
|
||||
import ReasonsSection from 'src/components/sections/ReasonsSection.vue';
|
||||
import MobileNav from 'src/components/ui/MobileNav.vue';
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DefaultLayout',
|
||||
components: { FooterComponent, ReasonsSection, HeaderSecondary, MobileNav },
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
|
||||
return { isOpenNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,3 +1,34 @@
|
|||
<script>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import FooterComponent from "src/components/footer/FooterComponent.vue";
|
||||
import HeaderPrimary from "src/components/header/HeaderPrimary.vue";
|
||||
import InfoSection from "src/components/sections/InfoSection.vue";
|
||||
import QuestionSection from "src/components/sections/QuestionSection.vue";
|
||||
import ReasonsSection from "src/components/sections/ReasonsSection.vue";
|
||||
import MobileNav from "src/components/ui/MobileNav.vue";
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
|
||||
export default defineComponent({
|
||||
name: "HomeLayout",
|
||||
components: {
|
||||
HeaderPrimary,
|
||||
QuestionSection,
|
||||
InfoSection,
|
||||
ReasonsSection,
|
||||
FooterComponent,
|
||||
MobileNav,
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
|
||||
return { isOpenNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-layout>
|
||||
<q-no-ssr>
|
||||
|
@ -17,35 +48,4 @@
|
|||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import FooterComponent from 'src/components/footer/FooterComponent.vue';
|
||||
import HeaderPrimary from 'src/components/header/HeaderPrimary.vue';
|
||||
import InfoSection from 'src/components/sections/InfoSection.vue';
|
||||
import QuestionSection from 'src/components/sections/QuestionSection.vue';
|
||||
import ReasonsSection from 'src/components/sections/ReasonsSection.vue';
|
||||
import MobileNav from 'src/components/ui/MobileNav.vue';
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HomeLayout',
|
||||
components: {
|
||||
HeaderPrimary,
|
||||
QuestionSection,
|
||||
InfoSection,
|
||||
ReasonsSection,
|
||||
FooterComponent,
|
||||
MobileNav,
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isOpenNav } = storeToRefs(mobileStore);
|
||||
|
||||
return { isOpenNav };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
import { fakerES } from "@faker-js/faker";
|
||||
export function mockGenerator({ length }) {
|
||||
const flowersMock = Array.from({ length }, (_, i) => {
|
||||
const position = fakerES.datatype.boolean() && i + 1;
|
||||
const discount =
|
||||
fakerES.datatype.boolean() &&
|
||||
fakerES.commerce.price({ min: 5, max: 15, dec: 0 });
|
||||
|
||||
export const cardMock = Array.from({ length: 8 }, (_, i) => ({
|
||||
const flower = {
|
||||
id: i + 1,
|
||||
imgSrc: `assets/flowers/flower-${i + 1}.png`,
|
||||
title: fakerES.commerce.productName(),
|
||||
discount: fakerES.commerce.price({ min: 5, max: 15, dec: 0 }),
|
||||
isNew: fakerES.datatype.boolean(),
|
||||
value: fakerES.commerce.price({ min: 20, max: 150 }),
|
||||
// title: 'Nombre del producto',
|
||||
// discount: i % 2 === 0 ? '10' : '',
|
||||
// isNew: i % 2 === 0,
|
||||
// value: '25,90',
|
||||
}));
|
||||
|
||||
export function generateFlowers({ length }) {
|
||||
const flowersMock = Array.from({ length }, (_, i) => ({
|
||||
id: i + 1,
|
||||
title: fakerES.commerce.productName(),
|
||||
name: fakerES.commerce.productName(),
|
||||
description: fakerES.commerce.productDescription(),
|
||||
price: fakerES.commerce.price({
|
||||
symbol: "€",
|
||||
|
@ -24,12 +16,29 @@ export function generateFlowers({ length }) {
|
|||
max: 200,
|
||||
dec: 0,
|
||||
}),
|
||||
sku: fakerES.commerce.isbn({ separator: "", variant: 13 }),
|
||||
category: fakerES.commerce.productMaterial(),
|
||||
images: Array.from({ length: fakerES.number.int({ min: 2, max: 6 }) }, () =>
|
||||
fakerES.image.urlPicsumPhotos()
|
||||
specialPrice: fakerES.commerce.price({
|
||||
symbol: "€",
|
||||
min: 20,
|
||||
max: 60,
|
||||
dec: 0,
|
||||
}),
|
||||
isNew: fakerES.datatype.boolean(),
|
||||
slug: fakerES.commerce.isbn({ separator: "-", variant: 13 }),
|
||||
category: fakerES.number.int({ min: 1, max: 2 }),
|
||||
postalCode: "12345",
|
||||
dateExpired: "30/01/2024",
|
||||
images: Array.from(
|
||||
{ length: fakerES.number.int({ min: 2, max: 6 }) },
|
||||
() => fakerES.image.urlPicsumPhotos()
|
||||
),
|
||||
}));
|
||||
featured: fakerES.number.int({ min: 0, max: 1 }),
|
||||
};
|
||||
|
||||
if (position) flower.position = position;
|
||||
if (discount) flower.discount = discount;
|
||||
|
||||
return flower;
|
||||
});
|
||||
|
||||
return flowersMock;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,140 @@
|
|||
<script>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { defineComponent, onBeforeMount, onUpdated, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import SortSelect from "src/components/@inputs/SortSelect.vue";
|
||||
import IconArrowCircleFilledRight from "src/components/icons/IconArrowCircleFilledRight.vue";
|
||||
import IconArrowDownWhite from "src/components/icons/IconArrowDownWhite.vue";
|
||||
import IconFilter from "src/components/icons/IconFilter.vue";
|
||||
import IconPencil from "src/components/icons/IconPencil.vue";
|
||||
import DudasSection from "src/components/sections/DudasSection.vue";
|
||||
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 { useCartStore } from "src/stores/cart";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { useModalStore } from "src/stores/modalStore";
|
||||
|
||||
export default defineComponent({
|
||||
name: "CategoryPage",
|
||||
components: {
|
||||
IconArrowCircleFilledRight,
|
||||
IconArrowDownWhite,
|
||||
IconPencil,
|
||||
IconFilter,
|
||||
Container,
|
||||
DudasSection,
|
||||
Modal,
|
||||
Card,
|
||||
SortSelect,
|
||||
},
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
|
||||
const modalStore = useModalStore();
|
||||
const { openModal } = modalStore;
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { availability, sortProductFilters } = storeToRefs(formStore);
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { products } = storeToRefs(cartStore);
|
||||
const { getProducts } = cartStore;
|
||||
|
||||
const monthTest = ref("");
|
||||
const isOpenOrder = ref(false);
|
||||
|
||||
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",
|
||||
recommended: "recomendados",
|
||||
latest: "más recientes",
|
||||
};
|
||||
const categoryObj = {
|
||||
plantas: 1,
|
||||
ramos: 2,
|
||||
};
|
||||
|
||||
watch(availability, (newDate) => {
|
||||
const [_day, month, _year] = newDate.date.split("/");
|
||||
monthTest.value = monthES[+month - 1];
|
||||
console.log(monthTest.value);
|
||||
});
|
||||
|
||||
watch(
|
||||
[() => route.path, () => sortProductFilters.value.order],
|
||||
([newPath, newOrder]) => {
|
||||
const categoryPath = newPath.split("/")[2];
|
||||
sortProductFilters.value.category = categoryPath;
|
||||
|
||||
const params = {
|
||||
category: categoryObj[categoryPath],
|
||||
itens: window.screen.width <= 445 ? 16 : 20,
|
||||
};
|
||||
const paramsObj = {
|
||||
"lowest-price": () => (params.lowPrice = 1),
|
||||
"highest-price": () => (params.bigPrice = 1),
|
||||
latest: () => (params.isNew = 1),
|
||||
// recommended: () => params.featured = 1,
|
||||
};
|
||||
if (newOrder) {
|
||||
paramsObj[newOrder]();
|
||||
}
|
||||
|
||||
getProducts(params);
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const categoryPath = route.path.split("/")[2];
|
||||
|
||||
await getProducts({
|
||||
category: categoryObj[categoryPath],
|
||||
itens: window.screen.width <= 445 ? 16 : 20,
|
||||
});
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
console.groupCollapsed("%c Updated!", "color: green;");
|
||||
console.log(sortProductFilters.value);
|
||||
console.log(availability.value);
|
||||
console.groupEnd();
|
||||
});
|
||||
|
||||
function openOrderFilter() {
|
||||
sortProductFilters.value.isOpenOrderFilter =
|
||||
!sortProductFilters.value.isOpenOrderFilter;
|
||||
}
|
||||
|
||||
return {
|
||||
sortProductFilters,
|
||||
openOrderFilter,
|
||||
openModal,
|
||||
availability,
|
||||
isOpenOrder,
|
||||
modalStore,
|
||||
orderText,
|
||||
products,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page class="category-container">
|
||||
<section class="products-section">
|
||||
|
@ -32,7 +169,7 @@
|
|||
flat
|
||||
class="btn filter-btn availability"
|
||||
type="button"
|
||||
@click="modalStore.openModal({ modal: 'availability' })"
|
||||
@click="openModal({ modal: 'availability' })"
|
||||
>
|
||||
<IconPencil />
|
||||
</q-btn>
|
||||
|
@ -44,7 +181,7 @@
|
|||
flat
|
||||
class="btn filter-item filters filter-btn"
|
||||
type="button"
|
||||
@click="modalStore.openModal({ modal: 'filters' })"
|
||||
@click="openModal({ modal: 'filters' })"
|
||||
>
|
||||
<p class="filter-paragraph remove-mob">Filtros</p>
|
||||
<IconFilter />
|
||||
|
@ -80,19 +217,28 @@
|
|||
</header>
|
||||
|
||||
<div class="products-section-body">
|
||||
<Container cardContainer>
|
||||
<Container cardContainer class="category-container">
|
||||
<template
|
||||
v-for="{
|
||||
images,
|
||||
discount,
|
||||
isNew,
|
||||
name,
|
||||
price,
|
||||
slug,
|
||||
id,
|
||||
} in products.data.products"
|
||||
:key="id"
|
||||
>
|
||||
<Card
|
||||
v-for="(
|
||||
{ imgSrc, discount, isNew, title, value, id }, i
|
||||
) in cardsMock"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
:discount="discount.toString()"
|
||||
:imgSrc="imgSrc"
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="images[0]"
|
||||
:isNew="isNew"
|
||||
:key="i"
|
||||
:id="id"
|
||||
:id="slug"
|
||||
/>
|
||||
</template>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
|
@ -104,114 +250,11 @@
|
|||
</section>
|
||||
|
||||
<dudas-section />
|
||||
<modal modalItem="isOpenAvailability" />
|
||||
<modal modalItem="isOpenFilters" />
|
||||
<Modal modalItem="isOpenAvailability" typeModal="availability" />
|
||||
<Modal modalItem="isOpenFilters" typeModal="filter" />
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fakerES } from "@faker-js/faker";
|
||||
import { storeToRefs } from "pinia";
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
defineComponent,
|
||||
onUpdated,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import SortSelect from "src/components/@inputs/SortSelect.vue";
|
||||
import IconArrowCircleFilledRight from "src/components/icons/IconArrowCircleFilledRight.vue";
|
||||
import IconArrowDownWhite from "src/components/icons/IconArrowDownWhite.vue";
|
||||
import IconFilter from "src/components/icons/IconFilter.vue";
|
||||
import IconPencil from "src/components/icons/IconPencil.vue";
|
||||
import Container from "src/components/ui/Container.vue";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { useModalStore } from "src/stores/modalStore";
|
||||
|
||||
export default defineComponent({
|
||||
name: "CategoryPage",
|
||||
components: {
|
||||
IconArrowCircleFilledRight,
|
||||
IconArrowDownWhite,
|
||||
IconPencil,
|
||||
IconFilter,
|
||||
Container,
|
||||
DudasSection: defineAsyncComponent(() =>
|
||||
import("src/components/sections/DudasSection.vue")
|
||||
),
|
||||
Modal: defineAsyncComponent(() => import("src/components/ui/Modal.vue")),
|
||||
Card: defineAsyncComponent(() => import("src/components/ui/Card.vue")),
|
||||
SortSelect,
|
||||
},
|
||||
setup() {
|
||||
const modalStore = useModalStore();
|
||||
const formStore = useFormStore();
|
||||
const { availability, sortProductFilters } = storeToRefs(formStore);
|
||||
const monthES = reactive({
|
||||
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 isOpenOrder = ref(false);
|
||||
const route = useRoute();
|
||||
const cardsMock = Array.from({ length: 8 }, (_, i) => ({
|
||||
id: i + 1,
|
||||
imgSrc: `../assets/flowers/flower-${i + 1}.png`,
|
||||
discount: fakerES.number.int({ min: 5, max: 15 }),
|
||||
isNew: fakerES.datatype.boolean(),
|
||||
title: fakerES.commerce.product(),
|
||||
value: fakerES.commerce.price({ min: 30, max: 100 }),
|
||||
}));
|
||||
const orderText = {
|
||||
"lowest-price": "menor precio",
|
||||
"highest-price": "mayor precio",
|
||||
recommended: "recomendados",
|
||||
latest: "más recientes",
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(newPatch) => {
|
||||
sortProductFilters.value.category = newPatch.split("/")[2];
|
||||
}
|
||||
);
|
||||
|
||||
onUpdated(() => {
|
||||
console.groupCollapsed("%c Updated!", "color: green;");
|
||||
console.log(sortProductFilters.value);
|
||||
console.groupEnd();
|
||||
});
|
||||
|
||||
function openOrderFilter() {
|
||||
sortProductFilters.value.isOpenOrderFilter =
|
||||
!sortProductFilters.value.isOpenOrderFilter;
|
||||
}
|
||||
|
||||
return {
|
||||
sortProductFilters,
|
||||
openOrderFilter,
|
||||
availability,
|
||||
isOpenOrder,
|
||||
modalStore,
|
||||
orderText,
|
||||
cardsMock,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.products-section {
|
||||
display: flex;
|
||||
|
|
|
@ -1,26 +1,225 @@
|
|||
<script>
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useForm } from "vee-validate";
|
||||
import { computed, defineComponent, reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import Container from "src/components/ui/Container.vue";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { checkoutSchema } from "src/utils/zod/schemas/checkoutSchema";
|
||||
|
||||
export default defineComponent({
|
||||
name: "CheckoutPage",
|
||||
components: {
|
||||
Container,
|
||||
},
|
||||
setup() {
|
||||
const { push } = useRouter();
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { cart, cartList, totalPrice, cartLength } = storeToRefs(cartStore);
|
||||
|
||||
if (cartLength.value === 0) return push("/");
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { handleCheckoutData } = formStore;
|
||||
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
|
||||
validationSchema: toTypedSchema(checkoutSchema),
|
||||
initialValues: {
|
||||
paymentMethod: "stripe",
|
||||
terms: false,
|
||||
},
|
||||
});
|
||||
const [name, nameAttrs] = defineField("name");
|
||||
const [surname, surnameAttrs] = defineField("surname");
|
||||
const [address, addressAttrs] = defineField("address");
|
||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
||||
const [phone, phoneAttrs] = defineField("phone");
|
||||
const [city, cityAttrs] = defineField("city");
|
||||
const [province, provinceAttrs] = defineField("province");
|
||||
const [senderName, senderNameAttrs] = defineField("senderName");
|
||||
const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif");
|
||||
const [senderEmail, senderEmailAttrs] = defineField("senderEmail");
|
||||
const [senderPhone, senderPhoneAttrs] = defineField("senderPhone");
|
||||
const [senderNotes, senderNotesAttrs] = defineField("senderNotes");
|
||||
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
|
||||
const [terms, termsAttrs] = defineField("terms");
|
||||
|
||||
const stepActive = reactive({ data: 1 });
|
||||
const stepList = reactive({
|
||||
data: [
|
||||
{
|
||||
value: 1,
|
||||
name: "Paso 1",
|
||||
description: "Datos de facturación",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
name: "Paso 2",
|
||||
description: "Confirmación",
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
name: "Paso 3",
|
||||
description: "Pago",
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
const checkoutBlock = ref(true);
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
handleCheckoutData(values);
|
||||
stepList.data[2].active = true;
|
||||
checkoutBlock.value = false;
|
||||
resetForm();
|
||||
});
|
||||
|
||||
const handleClickStep = (value) => {
|
||||
stepActive["data"] = value;
|
||||
};
|
||||
|
||||
const stepsFormated = computed(() => {
|
||||
return stepList["data"].map((step) => {
|
||||
if (step.value === stepActive["data"]) {
|
||||
return { ...step, active: true };
|
||||
}
|
||||
return step;
|
||||
});
|
||||
});
|
||||
|
||||
const provinceOptions = ref([
|
||||
{ code: "01", name: "Araba/Álava" },
|
||||
{ code: "02", name: "Albacete" },
|
||||
{ code: "03", name: "Alicante/Alacant" },
|
||||
{ code: "04", name: "Almería" },
|
||||
{ code: "05", name: "Ávila" },
|
||||
{ code: "06", name: "Badajoz" },
|
||||
{ code: "07", name: "Balears, Illes" },
|
||||
{ code: "08", name: "Barcelona" },
|
||||
{ code: "09", name: "Burgos" },
|
||||
{ code: "10", name: "Cáceres" },
|
||||
{ code: "11", name: "Cádiz" },
|
||||
{ code: "12", name: "Castellón/Castelló" },
|
||||
{ code: "13", name: "Ciudad Real" },
|
||||
{ code: "14", name: "Córdoba" },
|
||||
{ code: "15", name: "Coruña, A" },
|
||||
{ code: "16", name: "Cuenca" },
|
||||
{ code: "17", name: "Girona" },
|
||||
{ code: "18", name: "Granada" },
|
||||
{ code: "19", name: "Guadalajara" },
|
||||
{ code: "20", name: "Gipuzkoa" },
|
||||
{ code: "21", name: "Huelva" },
|
||||
{ code: "22", name: "Huesca" },
|
||||
{ code: "23", name: "Jaén" },
|
||||
{ code: "24", name: "León" },
|
||||
{ code: "25", name: "Lleida" },
|
||||
{ code: "26", name: "Rioja, La" },
|
||||
{ code: "27", name: "Lugo" },
|
||||
{ code: "28", name: "Madrid" },
|
||||
{ code: "29", name: "Málaga" },
|
||||
{ code: "30", name: "Murcia" },
|
||||
{ code: "31", name: "Navarra" },
|
||||
{ code: "32", name: "Ourense" },
|
||||
{ code: "33", name: "Asturias" },
|
||||
{ code: "34", name: "Palencia" },
|
||||
{ code: "35", name: "Palmas, Las" },
|
||||
{ code: "36", name: "Pontevedra" },
|
||||
{ code: "37", name: "Salamanca" },
|
||||
{ code: "38", name: "Santa Cruz de Tenerife" },
|
||||
{ code: "39", name: "Cantabria" },
|
||||
{ code: "40", name: "Segovia" },
|
||||
{ code: "41", name: "Sevilla" },
|
||||
{ code: "42", name: "Soria" },
|
||||
{ code: "43", name: "Tarragona" },
|
||||
{ code: "44", name: "Teruel" },
|
||||
{ code: "45", name: "Toledo" },
|
||||
{ code: "46", name: "Valencia/València" },
|
||||
{ code: "47", name: "Valladolid" },
|
||||
{ code: "48", name: "Bizkaia" },
|
||||
{ code: "49", name: "Zamora" },
|
||||
{ code: "50", name: "Zaragoza" },
|
||||
{ code: "51", name: "Ceuta" },
|
||||
{ code: "52", name: "Melilla" },
|
||||
]);
|
||||
|
||||
return {
|
||||
handleClickStep,
|
||||
stepsFormated,
|
||||
onSubmit,
|
||||
stepList,
|
||||
provinceOptions,
|
||||
totalPrice,
|
||||
cartList,
|
||||
|
||||
step: ref(1),
|
||||
cart,
|
||||
checkoutBlock,
|
||||
meta,
|
||||
errors,
|
||||
name,
|
||||
nameAttrs,
|
||||
surname,
|
||||
surnameAttrs,
|
||||
address,
|
||||
addressAttrs,
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
phone,
|
||||
phoneAttrs,
|
||||
city,
|
||||
cityAttrs,
|
||||
province,
|
||||
provinceAttrs,
|
||||
senderName,
|
||||
senderNameAttrs,
|
||||
senderCifNif,
|
||||
senderCifNifAttrs,
|
||||
senderEmail,
|
||||
senderEmailAttrs,
|
||||
senderPhone,
|
||||
senderPhoneAttrs,
|
||||
senderNotes,
|
||||
senderNotesAttrs,
|
||||
terms,
|
||||
termsAttrs,
|
||||
paymentMethod,
|
||||
paymentMethodAttrs,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page class="checkout-page">
|
||||
<Container tag="section">
|
||||
<header class="header-title" :class="!checkoutBlock && 'success'">
|
||||
<h1 class="pege-title" v-if="checkoutBlock">
|
||||
¿A quién y dónde lo entregamos?
|
||||
{{ checkoutBlock && "¿A quién y dónde lo entregamos?" }}
|
||||
{{ !checkoutBlock && "¡Muchas gracias Jerom!" }}
|
||||
</h1>
|
||||
<h1 class="pege-title" v-if="!checkoutBlock">¡Muchas gracias Jerom!</h1>
|
||||
|
||||
<p class="pege-subtitle checkout" v-if="checkoutBlock">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</p>
|
||||
<p class="pege-subtitle checkout" v-if="!checkoutBlock">
|
||||
¡Tu pedido se ha realizado con éxito! Gracias por confiar en nosotros,
|
||||
en breves recibirás un correo con la confirmación de tu pedido.
|
||||
<p class="page-subtitle checkout" v-if="checkoutBlock">
|
||||
{{
|
||||
checkoutBlock &&
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
}}
|
||||
|
||||
{{
|
||||
!checkoutBlock &&
|
||||
"¡Tu pedido se ha realizado con éxito! Gracias por confiar en nosotros, en breves recibirás un correo con la confirmación de tu pedido."
|
||||
}}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="checkout-container">
|
||||
<div class="checkout-steps">
|
||||
<div
|
||||
v-for="({ active, description, name, value }, i) in stepsFormated()"
|
||||
v-for="({ active, description, name, value }, i) in stepsFormated"
|
||||
class="step-item-container"
|
||||
:key="i"
|
||||
>
|
||||
|
@ -116,7 +315,7 @@
|
|||
placeholder="Código postal*"
|
||||
name="postalCode"
|
||||
type="text"
|
||||
mask="#####-###"
|
||||
mask="#####"
|
||||
v-model="postalCode"
|
||||
v-bind:="postalCodeAttrs"
|
||||
:error="!!errors.postalCode"
|
||||
|
@ -135,11 +334,7 @@
|
|||
:options="provinceOptions"
|
||||
option-value="code"
|
||||
option-label="name"
|
||||
:label="
|
||||
!province
|
||||
? 'Complete la dirección y el código postal'
|
||||
: 'Provincia*'
|
||||
"
|
||||
label="Provincia*"
|
||||
stack-label
|
||||
map-options
|
||||
emit-value
|
||||
|
@ -149,7 +344,7 @@
|
|||
|
||||
<div class="field-control field-select">
|
||||
<q-input
|
||||
placeholder="Ciudade*"
|
||||
placeholder="Ciudad*"
|
||||
name="city"
|
||||
type="text"
|
||||
v-model="city"
|
||||
|
@ -282,11 +477,11 @@
|
|||
<ul class="checkout-summary-list">
|
||||
<li
|
||||
class="checkout-summary-item"
|
||||
v-for="({ title, price }, index) in cart"
|
||||
v-for="({ title, price, quantity }, index) in cartList"
|
||||
:key="index"
|
||||
>
|
||||
<p>
|
||||
{{ title }}
|
||||
{{ title }} ({{ quantity }})
|
||||
<span>{{ price }}</span>
|
||||
</p>
|
||||
</li>
|
||||
|
@ -309,7 +504,7 @@
|
|||
</header>
|
||||
|
||||
<div class="checkout-payment-body">
|
||||
<q-radio
|
||||
<!-- <q-radio
|
||||
v-model="paymentMethod"
|
||||
v-bind="paymentMethodAttrs"
|
||||
val="credit"
|
||||
|
@ -321,7 +516,7 @@
|
|||
<IconMaster /><IconVisa /> <IconAny /> <IconExpress />
|
||||
</span>
|
||||
</p>
|
||||
</q-radio>
|
||||
</q-radio> -->
|
||||
|
||||
<q-radio
|
||||
v-model="paymentMethod"
|
||||
|
@ -359,7 +554,7 @@
|
|||
<div class="checkout-success-content">
|
||||
<ul class="checkout-success-list">
|
||||
<li
|
||||
v-for="({ title, price }, index) in cart"
|
||||
v-for="({ title, price, quantity }, index) in cartList"
|
||||
:key="index"
|
||||
class="checkout-success-item"
|
||||
>
|
||||
|
@ -370,7 +565,9 @@
|
|||
alt="product"
|
||||
class="checkout-product-img"
|
||||
/>
|
||||
<p class="checkout-product-title">{{ title }}</p>
|
||||
<p class="checkout-product-title">
|
||||
{{ title }} ({{ quantity }})
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="checkout-product-price">
|
||||
|
@ -383,7 +580,9 @@
|
|||
|
||||
<footer class="checkout-success-footer">
|
||||
<p class="checkout-success-paragraph">Total</p>
|
||||
<p class="checkout-success-paragraph">{{ totalPrice }}.00€</p>
|
||||
<p class="checkout-success-paragraph">
|
||||
{{ totalPrice?.toFixed(2) }}€
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -392,230 +591,32 @@
|
|||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent, reactive, ref } from "vue";
|
||||
|
||||
import IconAny from "src/components/icons/credit-flags/IconAny.vue";
|
||||
import IconExpress from "src/components/icons/credit-flags/IconExpress.vue";
|
||||
import IconMaster from "src/components/icons/credit-flags/IconMaster.vue";
|
||||
import IconVisa from "src/components/icons/credit-flags/IconVisa.vue";
|
||||
import Container from "src/components/ui/Container.vue";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { checkoutSchema } from "src/utils/zod/schemas/checkoutSchema";
|
||||
|
||||
export default defineComponent({
|
||||
name: "CheckoutPage",
|
||||
components: {
|
||||
Container,
|
||||
IconAny,
|
||||
IconVisa,
|
||||
IconExpress,
|
||||
IconMaster,
|
||||
},
|
||||
setup() {
|
||||
const cartStore = useCartStore();
|
||||
const { cart, totalPrice } = storeToRefs(cartStore);
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { handleCheckoutData } = formStore;
|
||||
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
|
||||
validationSchema: toTypedSchema(checkoutSchema),
|
||||
initialValues: {
|
||||
paymentMethod: "credit",
|
||||
terms: false,
|
||||
},
|
||||
});
|
||||
const [name, nameAttrs] = defineField("name");
|
||||
const [surname, surnameAttrs] = defineField("surname");
|
||||
const [address, addressAttrs] = defineField("address");
|
||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
||||
const [phone, phoneAttrs] = defineField("phone");
|
||||
const [city, cityAttrs] = defineField("city");
|
||||
const [province, provinceAttrs] = defineField("province");
|
||||
const [senderName, senderNameAttrs] = defineField("senderName");
|
||||
const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif");
|
||||
const [senderEmail, senderEmailAttrs] = defineField("senderEmail");
|
||||
const [senderPhone, senderPhoneAttrs] = defineField("senderPhone");
|
||||
const [senderNotes, senderNotesAttrs] = defineField("senderNotes");
|
||||
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
|
||||
const [terms, termsAttrs] = defineField("terms");
|
||||
|
||||
const stepActive = reactive({ data: 1 });
|
||||
const stepList = reactive({
|
||||
data: [
|
||||
{
|
||||
value: 1,
|
||||
name: "Paso 1",
|
||||
description: "Datos de facturación",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
name: "Paso 2",
|
||||
description: "Confirmación",
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
name: "Paso 3",
|
||||
description: "Pago",
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
const checkoutBlock = ref(true);
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
handleCheckoutData(values);
|
||||
stepList.data[2].active = true;
|
||||
checkoutBlock.value = false;
|
||||
resetForm();
|
||||
});
|
||||
|
||||
const handleClickStep = (value) => {
|
||||
stepActive["data"] = value;
|
||||
};
|
||||
|
||||
const stepsFormated = () => {
|
||||
return stepList["data"].map((step) => {
|
||||
if (step.value === stepActive["data"]) {
|
||||
return { ...step, active: true };
|
||||
}
|
||||
return step;
|
||||
});
|
||||
};
|
||||
|
||||
const provinceOptions = ref([
|
||||
{ code: "01", name: "Araba/Álava" },
|
||||
{ code: "02", name: "Albacete" },
|
||||
{ code: "03", name: "Alicante/Alacant" },
|
||||
{ code: "04", name: "Almería" },
|
||||
{ code: "05", name: "Ávila" },
|
||||
{ code: "06", name: "Badajoz" },
|
||||
{ code: "07", name: "Balears, Illes" },
|
||||
{ code: "08", name: "Barcelona" },
|
||||
{ code: "09", name: "Burgos" },
|
||||
{ code: "10", name: "Cáceres" },
|
||||
{ code: "11", name: "Cádiz" },
|
||||
{ code: "12", name: "Castellón/Castelló" },
|
||||
{ code: "13", name: "Ciudad Real" },
|
||||
{ code: "14", name: "Córdoba" },
|
||||
{ code: "15", name: "Coruña, A" },
|
||||
{ code: "16", name: "Cuenca" },
|
||||
{ code: "17", name: "Girona" },
|
||||
{ code: "18", name: "Granada" },
|
||||
{ code: "19", name: "Guadalajara" },
|
||||
{ code: "20", name: "Gipuzkoa" },
|
||||
{ code: "21", name: "Huelva" },
|
||||
{ code: "22", name: "Huesca" },
|
||||
{ code: "23", name: "Jaén" },
|
||||
{ code: "24", name: "León" },
|
||||
{ code: "25", name: "Lleida" },
|
||||
{ code: "26", name: "Rioja, La" },
|
||||
{ code: "27", name: "Lugo" },
|
||||
{ code: "28", name: "Madrid" },
|
||||
{ code: "29", name: "Málaga" },
|
||||
{ code: "30", name: "Murcia" },
|
||||
{ code: "31", name: "Navarra" },
|
||||
{ code: "32", name: "Ourense" },
|
||||
{ code: "33", name: "Asturias" },
|
||||
{ code: "34", name: "Palencia" },
|
||||
{ code: "35", name: "Palmas, Las" },
|
||||
{ code: "36", name: "Pontevedra" },
|
||||
{ code: "37", name: "Salamanca" },
|
||||
{ code: "38", name: "Santa Cruz de Tenerife" },
|
||||
{ code: "39", name: "Cantabria" },
|
||||
{ code: "40", name: "Segovia" },
|
||||
{ code: "41", name: "Sevilla" },
|
||||
{ code: "42", name: "Soria" },
|
||||
{ code: "43", name: "Tarragona" },
|
||||
{ code: "44", name: "Teruel" },
|
||||
{ code: "45", name: "Toledo" },
|
||||
{ code: "46", name: "Valencia/València" },
|
||||
{ code: "47", name: "Valladolid" },
|
||||
{ code: "48", name: "Bizkaia" },
|
||||
{ code: "49", name: "Zamora" },
|
||||
{ code: "50", name: "Zaragoza" },
|
||||
{ code: "51", name: "Ceuta" },
|
||||
{ code: "52", name: "Melilla" },
|
||||
]);
|
||||
|
||||
return {
|
||||
handleClickStep,
|
||||
stepsFormated,
|
||||
onSubmit,
|
||||
stepList,
|
||||
provinceOptions,
|
||||
totalPrice,
|
||||
|
||||
step: ref(1),
|
||||
cart,
|
||||
checkoutBlock,
|
||||
meta,
|
||||
errors,
|
||||
name,
|
||||
nameAttrs,
|
||||
surname,
|
||||
surnameAttrs,
|
||||
address,
|
||||
addressAttrs,
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
phone,
|
||||
phoneAttrs,
|
||||
city,
|
||||
cityAttrs,
|
||||
province,
|
||||
provinceAttrs,
|
||||
senderName,
|
||||
senderNameAttrs,
|
||||
senderCifNif,
|
||||
senderCifNifAttrs,
|
||||
senderEmail,
|
||||
senderEmailAttrs,
|
||||
senderPhone,
|
||||
senderPhoneAttrs,
|
||||
senderNotes,
|
||||
senderNotesAttrs,
|
||||
terms,
|
||||
termsAttrs,
|
||||
paymentMethod,
|
||||
paymentMethodAttrs,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.checkout-page {
|
||||
.checkout-steps {
|
||||
& .checkout-steps {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.step-item-container {
|
||||
& .step-item-container {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.border-step {
|
||||
& .border-step {
|
||||
width: 90px;
|
||||
height: 1px;
|
||||
background-color: $primary-dark;
|
||||
}
|
||||
|
||||
.circle-step-container {
|
||||
& .circle-step-container {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
}
|
||||
|
||||
.circle-step {
|
||||
& .circle-step {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border: 1px solid $primary-dark;
|
||||
|
@ -637,7 +638,7 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
.step-content {
|
||||
& .step-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
@ -659,7 +660,7 @@ export default defineComponent({
|
|||
|
||||
& .checkout-content {
|
||||
width: min(100%, 1144px);
|
||||
margin: 50px auto 0;
|
||||
margin: 50px auto calc(146px - 72px);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
|
@ -670,10 +671,12 @@ export default defineComponent({
|
|||
width: 100%;
|
||||
margin-bottom: 21px;
|
||||
border-radius: 5px;
|
||||
h3 {
|
||||
& h3 {
|
||||
color: $text-default;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.28px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $med-lg) {
|
||||
|
@ -909,7 +912,7 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
.form-fields-container {
|
||||
& .form-fields-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
&.delivery {
|
||||
|
@ -942,15 +945,21 @@ export default defineComponent({
|
|||
label {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.q-field__control {
|
||||
& .q-field__control {
|
||||
background-color: #fff;
|
||||
height: 40px;
|
||||
border: 1px solid $primary-light;
|
||||
input {
|
||||
padding: 0px 30px;
|
||||
& input {
|
||||
padding: 0px 0px 0px 20px;
|
||||
font-family: $font-questrial;
|
||||
color: $text-default !important;
|
||||
}
|
||||
|
||||
& select {
|
||||
font-family: $font-questrial;
|
||||
color: $text-default !important;
|
||||
}
|
||||
|
||||
&.text-negative {
|
||||
border-color: $negative;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ContactaPage",
|
||||
components: {},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page class="">
|
||||
<p>Contacta</p>
|
||||
|
@ -6,16 +18,4 @@
|
|||
</q-page>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ContactaPage',
|
||||
components: {},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,11 +1,46 @@
|
|||
<script>
|
||||
import { defineComponent, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ErrorNotFound",
|
||||
setup() {
|
||||
const counter = ref(10);
|
||||
const { push } = useRouter();
|
||||
|
||||
function startCountdown() {
|
||||
// Cria um intervalo que executa a cada segundo
|
||||
const interval = setInterval(() => {
|
||||
// Decrementa o valor de count
|
||||
counter.value--;
|
||||
// Se o valor de count for zero, para o intervalo
|
||||
if (counter.value === 1) {
|
||||
clearInterval(interval);
|
||||
push("/");
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Chama a função para iniciar o contador quando o componente for montado
|
||||
onMounted(startCountdown);
|
||||
|
||||
return {
|
||||
counter,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center"
|
||||
class="not-found fullscreen text-white text-center q-pa-md flex flex-center"
|
||||
>
|
||||
<div>
|
||||
<div style="font-size: 30vh">404</div>
|
||||
|
||||
<div class="text-h2" style="opacity: 0.4">Oops. Nothing here...</div>
|
||||
<div class="text-h2" style="opacity: 0.4" :key="counter">
|
||||
Redirigiendo a la home en... {{ counter }}
|
||||
</div>
|
||||
|
||||
<q-btn
|
||||
class="q-mt-xl"
|
||||
|
@ -20,10 +55,8 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ErrorNotFound',
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.not-found {
|
||||
background-color: $primary;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<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>
|
|
@ -1,3 +1,15 @@
|
|||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "FaqPage",
|
||||
components: {},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page class="">
|
||||
<p>Faq</p>
|
||||
|
@ -6,16 +18,4 @@
|
|||
</q-page>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FaqPage',
|
||||
components: {},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,3 +1,53 @@
|
|||
<script>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { defineComponent, onBeforeMount } from "vue";
|
||||
|
||||
import VerticalCarouselImgs from "src/components/quasar-components/carousel/VerticalCarouselImgs.vue";
|
||||
import Swiper from "src/components/swiper/Swiper.vue";
|
||||
import Card from "src/components/ui/Card.vue";
|
||||
import Container from "src/components/ui/Container.vue";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
|
||||
export default defineComponent({
|
||||
name: "HomePage",
|
||||
components: {
|
||||
VerticalCarouselImgs,
|
||||
Container,
|
||||
Swiper,
|
||||
Card,
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isCarouselVisible, isOpenNav, screenWidth } =
|
||||
storeToRefs(mobileStore);
|
||||
const cartStore = useCartStore();
|
||||
const { getProducts } = cartStore;
|
||||
const { products } = storeToRefs(cartStore);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await getProducts();
|
||||
});
|
||||
|
||||
const slidesContent = [
|
||||
"assets/1.jpg",
|
||||
"assets/2.jpg",
|
||||
"assets/3.jpg",
|
||||
"assets/4.jpg",
|
||||
"assets/5.jpg",
|
||||
];
|
||||
|
||||
return {
|
||||
isCarouselVisible,
|
||||
slidesContent,
|
||||
screenWidth,
|
||||
isOpenNav,
|
||||
products,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page>
|
||||
<q-no-ssr>
|
||||
|
@ -19,18 +69,24 @@
|
|||
|
||||
<div class="products-body">
|
||||
<Container cardContainer>
|
||||
<Card
|
||||
<template
|
||||
v-for="(
|
||||
{ imgSrc, discount, isNew, title, value, id }, i
|
||||
) in cardMock"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
{ id, slug, name, price, images, isNew, discount }, i
|
||||
) in products.data.products"
|
||||
>
|
||||
<Card
|
||||
v-if="i < 8"
|
||||
:id="slug"
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="imgSrc"
|
||||
:imgSrc="images[0]"
|
||||
:isNew="isNew"
|
||||
:key="i"
|
||||
:id="id"
|
||||
:key="id"
|
||||
imgClass="list-products"
|
||||
size="md-card"
|
||||
/>
|
||||
</template>
|
||||
</Container>
|
||||
|
||||
<RouterLink class="btn rounded outlined" to="/">
|
||||
|
@ -53,40 +109,28 @@
|
|||
</header>
|
||||
|
||||
<div class="products-selection-body">
|
||||
<!-- <HorizontalCarousel>
|
||||
<SwiperSlideOne
|
||||
v-for="{ id, discount, isNew, value, title, imgSrc } in cardMock"
|
||||
:key="id"
|
||||
>
|
||||
<Card
|
||||
:id="id"
|
||||
:key="id"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
:discount="discount"
|
||||
:imgSrc="imgSrc"
|
||||
:isNew="isNew"
|
||||
/>
|
||||
</SwiperSlideOne>
|
||||
</HorizontalCarousel> -->
|
||||
|
||||
<q-no-ssr>
|
||||
<Swiper>
|
||||
<swiper-slide
|
||||
v-for="{ id, discount, isNew, value, title, imgSrc } in cardMock"
|
||||
:key="id"
|
||||
class="swiper-slide"
|
||||
<template
|
||||
v-for="(
|
||||
{ slug, discount, isNew, price, name, images }, i
|
||||
) in products.data.products"
|
||||
:key="slug"
|
||||
>
|
||||
<swiper-slide class="swiper-slide" v-if="i < 10">
|
||||
<Card
|
||||
:id="id"
|
||||
:key="id"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
:id="slug"
|
||||
:key="slug"
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="imgSrc"
|
||||
:imgSrc="images[0]"
|
||||
:isNew="isNew"
|
||||
imgClass="carousel"
|
||||
size="lg-card"
|
||||
/>
|
||||
</swiper-slide>
|
||||
</template>
|
||||
</Swiper>
|
||||
</q-no-ssr>
|
||||
</div>
|
||||
|
@ -100,55 +144,6 @@
|
|||
</q-page>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { defineAsyncComponent, defineComponent, ref } from 'vue';
|
||||
|
||||
import Container from 'src/components/ui/Container.vue';
|
||||
import { cardMock } from 'src/mock/cards';
|
||||
import { useMobileStore } from 'src/stores/mobileNav';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HomePage',
|
||||
components: {
|
||||
VerticalCarouselImgs: defineAsyncComponent(
|
||||
() =>
|
||||
import(
|
||||
'src/components/quasar-components/carousel/VerticalCarouselImgs.vue'
|
||||
)
|
||||
),
|
||||
Swiper: defineAsyncComponent(
|
||||
() => import('src/components/swiper/Swiper.vue')
|
||||
),
|
||||
Card: defineAsyncComponent(() => import('src/components/ui/Card.vue')),
|
||||
Container,
|
||||
},
|
||||
setup() {
|
||||
const mobileStore = useMobileStore();
|
||||
const { isCarouselVisible, isOpenNav, screenWidth } =
|
||||
storeToRefs(mobileStore);
|
||||
|
||||
const slidesContent = [
|
||||
'assets/1.jpg',
|
||||
'assets/2.jpg',
|
||||
'assets/3.jpg',
|
||||
'assets/4.jpg',
|
||||
'assets/5.jpg',
|
||||
];
|
||||
const data = ref(null);
|
||||
|
||||
return {
|
||||
isCarouselVisible,
|
||||
slidesContent,
|
||||
screenWidth,
|
||||
isOpenNav,
|
||||
cardMock,
|
||||
data,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home-carousel {
|
||||
margin-bottom: 132px;
|
||||
|
|
|
@ -1,49 +1,179 @@
|
|||
<script>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useMeta } from "quasar";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent, onBeforeMount, reactive, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue";
|
||||
import IconArrowCircleFilledRight from "components/icons/IconArrowCircleFilledRight.vue";
|
||||
import IconPencilGreen from "components/icons/IconPencilGreen.vue";
|
||||
import IconEmail from "components/icons/social/IconEmail.vue";
|
||||
import IconLinkedin from "components/icons/social/IconLinkedin.vue";
|
||||
import IconShare from "components/icons/social/IconShare.vue";
|
||||
import IconTwitter from "components/icons/social/IconTwitter.vue";
|
||||
import IconWhatsapp from "components/icons/social/IconWhatsapp.vue";
|
||||
import ProductCarousel from "components/quasar-components/carousel/ProductCarousel.vue";
|
||||
import DudasSection from "components/sections/DudasSection.vue";
|
||||
import Card from "components/ui/Card.vue";
|
||||
import Container from "components/ui/Container.vue";
|
||||
import Modal from "components/ui/Modal.vue";
|
||||
|
||||
import { dedicationSchema } from "src/utils/zod/schemas";
|
||||
import { useCartStore } from "stores/cart";
|
||||
import { useModalStore } from "stores/modalStore";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ProductPage",
|
||||
components: {
|
||||
IconPencilGreen,
|
||||
IconWhatsapp,
|
||||
IconLinkedin,
|
||||
IconTwitter,
|
||||
IconShare,
|
||||
IconEmail,
|
||||
DudasSection,
|
||||
Container,
|
||||
Card,
|
||||
IconArrowCircleFilledRight,
|
||||
IconArrowCircleFilledLeft,
|
||||
ProductCarousel,
|
||||
Modal,
|
||||
},
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
|
||||
const modalStore = useModalStore();
|
||||
const { openModal } = modalStore;
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { getProduct, getProducts, products } = cartStore;
|
||||
const { prevProduct, currentProduct, nextProduct, addCartLoadingBtn } =
|
||||
storeToRefs(cartStore);
|
||||
|
||||
onBeforeMount(() => {
|
||||
getProduct(route.params.id);
|
||||
getProducts();
|
||||
});
|
||||
|
||||
watch(currentProduct.value, (newValue) => {
|
||||
useMeta(() => {
|
||||
return {
|
||||
title: `${newValue.value?.title}`,
|
||||
titleTemplate: (title) => `${title} - FloraNet`,
|
||||
meta: {
|
||||
description: {
|
||||
name: "description",
|
||||
content: `${newValue.value?.description}`,
|
||||
},
|
||||
keywords: { name: "keywords", content: `${newValue.value?.title}` },
|
||||
equiv: {
|
||||
"http-equiv": "Content-Type",
|
||||
content: "text/html; charset=UTF-8",
|
||||
},
|
||||
ogTitle: {
|
||||
property: "og:title",
|
||||
template(ogTitle) {
|
||||
return `${ogTitle} - FloraNet`;
|
||||
},
|
||||
},
|
||||
noscript: {
|
||||
default:
|
||||
"This is content for browsers with no JS (or disabled JS)",
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(newId) => {
|
||||
getProduct(newId);
|
||||
}
|
||||
);
|
||||
|
||||
const currentData = reactive({});
|
||||
watch(currentProduct.value, (newData) => {
|
||||
if (newData.value) {
|
||||
const { id, ...newDataWhithoutId } = newData.value;
|
||||
currentData.value = {
|
||||
...newDataWhithoutId,
|
||||
productId: +route.params.id,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const category = reactive({
|
||||
1: "Planta",
|
||||
2: "Ramos",
|
||||
});
|
||||
|
||||
const { handleSubmit, defineField, handleReset } = useForm({
|
||||
validationSchema: dedicationSchema,
|
||||
});
|
||||
const [dedication, dedicationAttrs] = defineField("dedication");
|
||||
/* const onSubmit = handleSubmit(() => {
|
||||
openModal({ modal: "availability" });
|
||||
// addToCart(currentData.value, dedication);
|
||||
// handleReset();
|
||||
}); */
|
||||
|
||||
return {
|
||||
slide: ref(1),
|
||||
fullscreen: ref(false),
|
||||
dedication,
|
||||
dedicationAttrs,
|
||||
products,
|
||||
addCartLoadingBtn,
|
||||
prevProduct,
|
||||
currentProduct,
|
||||
nextProduct,
|
||||
currentData,
|
||||
category,
|
||||
openModal,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page>
|
||||
<!-- <p>{{ $route.params.id }}</p> -->
|
||||
|
||||
<Container class="product-container" tag="section">
|
||||
<ProductCarousel>
|
||||
<template v-for="(img, i) in currentProduct?.images" :key="i">
|
||||
<q-carousel-slide
|
||||
v-if="img"
|
||||
:img-src="img"
|
||||
class="product-gallery-item"
|
||||
:name="i + 1"
|
||||
/>
|
||||
|
||||
<q-carousel-slide
|
||||
v-else
|
||||
:img-src="'../assets/empty-img.jpg'"
|
||||
class="product-gallery-item"
|
||||
:name="1"
|
||||
img-src="https://cdn.quasar.dev/img/mountains.jpg"
|
||||
class="product-gallery-item"
|
||||
/>
|
||||
<q-carousel-slide
|
||||
:name="2"
|
||||
img-src="https://cdn.quasar.dev/img/parallax1.jpg"
|
||||
class="product-gallery-item"
|
||||
/>
|
||||
<q-carousel-slide
|
||||
:name="3"
|
||||
img-src="https://cdn.quasar.dev/img/parallax2.jpg"
|
||||
class="product-gallery-item"
|
||||
/>
|
||||
<q-carousel-slide
|
||||
:name="4"
|
||||
img-src="https://cdn.quasar.dev/img/quasar.jpg"
|
||||
class="product-gallery-item"
|
||||
/>
|
||||
</template>
|
||||
</ProductCarousel>
|
||||
|
||||
<div class="product-content">
|
||||
<header class="product-content-header">
|
||||
<h3 class="product-content-title subtitle">
|
||||
{{ currentProduct.value?.title }}
|
||||
<q-skeleton type="rect" v-if="!currentProduct.value?.title" />
|
||||
{{ currentProduct?.name }}
|
||||
<q-skeleton type="rect" v-if="!currentProduct?.name" />
|
||||
</h3>
|
||||
|
||||
<!-- <div>{{ currentData.value }}</div> -->
|
||||
|
||||
<div class="product-header-block">
|
||||
<p class="product-content-paragraph">
|
||||
SKU:
|
||||
<span class="green-text" style="display: inline-flex">
|
||||
{{ currentProduct.value?.sku }}
|
||||
{{ currentProduct?.slug }}
|
||||
<q-skeleton
|
||||
width="100px"
|
||||
type="text"
|
||||
v-if="!currentProduct.value?.sku"
|
||||
v-if="!currentProduct?.slug"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
|
@ -51,11 +181,11 @@
|
|||
<p class="product-content-paragraph">
|
||||
Categoría:
|
||||
<span class="green-text">
|
||||
{{ currentProduct.value?.category }}
|
||||
{{ category[currentProduct?.category] }}
|
||||
<q-skeleton
|
||||
type="text"
|
||||
width="50px"
|
||||
v-if="!currentProduct.value?.category"
|
||||
v-if="!currentProduct?.category"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
|
@ -65,29 +195,23 @@
|
|||
<div class="product-content-body">
|
||||
<div class="product-content-paragraphs">
|
||||
<p class="product-price green-text">
|
||||
{{ currentProduct.value?.price }}
|
||||
{{ currentProduct?.price }}
|
||||
<q-skeleton
|
||||
type="text"
|
||||
height="90px"
|
||||
width="80px"
|
||||
v-if="!currentProduct.value?.price"
|
||||
v-if="!currentProduct?.price"
|
||||
/>
|
||||
</p>
|
||||
<p class="product-delivery green-text">Envío Gratuito</p>
|
||||
<p class="product-description">
|
||||
{{ currentProduct.value?.description }}
|
||||
<q-skeleton
|
||||
type="text"
|
||||
v-if="!currentProduct.value?.description"
|
||||
/>
|
||||
<q-skeleton
|
||||
type="text"
|
||||
v-if="!currentProduct.value?.description"
|
||||
/>
|
||||
{{ currentProduct?.description }}
|
||||
<q-skeleton type="text" v-if="!currentProduct?.description" />
|
||||
<q-skeleton type="text" v-if="!currentProduct?.description" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form class="product-form" @submit="onSubmit">
|
||||
<div class="product-form">
|
||||
<div class="product-dedication">
|
||||
<header class="product-dedication-header">
|
||||
<IconPencilGreen />
|
||||
|
@ -113,12 +237,12 @@
|
|||
|
||||
<q-btn
|
||||
:loading="addCartLoadingBtn"
|
||||
type="submit"
|
||||
color="primary"
|
||||
class="btn sm-btn"
|
||||
label="AÑADIR AL CARRITO"
|
||||
@click="openModal({ modal: 'availability' })"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="product-content-footer">
|
||||
|
@ -143,6 +267,7 @@
|
|||
class="btn outlined rounded sm-btn product-pag-item product-prev-btn"
|
||||
:to="`${+$route.params.id - 1}`"
|
||||
v-if="+$route.params.id > 1"
|
||||
@click="currentProduct.value = undefined"
|
||||
>
|
||||
<IconArrowCircleFilledLeft />
|
||||
|
||||
|
@ -161,6 +286,8 @@
|
|||
color="white"
|
||||
class="btn outlined rounded sm-btn product-pag-item product-next-btn"
|
||||
:to="`${+$route.params.id + 1}`"
|
||||
v-if="nextProduct.value?.id"
|
||||
@click="currentProduct.value = undefined"
|
||||
>
|
||||
<div class="btn-pag-paragraphs">
|
||||
<p class="btn-paragraph-top green-text">Siguiente producto</p>
|
||||
|
@ -178,6 +305,7 @@
|
|||
</Container>
|
||||
|
||||
<DudasSection isWhite />
|
||||
<Modal modalItem="isOpenAvailability" typeModal="product" />
|
||||
|
||||
<Container class="like-another-container gray-bg" tag="section">
|
||||
<header class="like-another-header">
|
||||
|
@ -192,19 +320,20 @@
|
|||
</p>
|
||||
</header>
|
||||
|
||||
<Container cardContainer>
|
||||
<Container cardContainer class="no-padding">
|
||||
<template
|
||||
v-for="({ imgSrc, discount, isNew, title, value, id }, i) in cardMock"
|
||||
v-for="({ images, discount, isNew, name, price, slug }, i) in products
|
||||
.data.products"
|
||||
>
|
||||
<card
|
||||
<Card
|
||||
v-if="i < 4"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
:discount="discount.toString()"
|
||||
:imgSrc="imgSrc"
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="images[0]"
|
||||
:isNew="isNew"
|
||||
:key="i"
|
||||
:id="id"
|
||||
:key="slug"
|
||||
:id="slug"
|
||||
/>
|
||||
</template>
|
||||
</Container>
|
||||
|
@ -212,128 +341,6 @@
|
|||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fakerES } from "@faker-js/faker";
|
||||
import { useMeta } from "quasar";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent, onBeforeMount, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue";
|
||||
import IconArrowCircleFilledRight from "components/icons/IconArrowCircleFilledRight.vue";
|
||||
import IconPencilGreen from "components/icons/IconPencilGreen.vue";
|
||||
import IconEmail from "components/icons/social/IconEmail.vue";
|
||||
import IconLinkedin from "components/icons/social/IconLinkedin.vue";
|
||||
import IconShare from "components/icons/social/IconShare.vue";
|
||||
import IconTwitter from "components/icons/social/IconTwitter.vue";
|
||||
import IconWhatsapp from "components/icons/social/IconWhatsapp.vue";
|
||||
import ProductCarousel from "components/quasar-components/carousel/ProductCarousel.vue";
|
||||
import DudasSection from "components/sections/DudasSection.vue";
|
||||
import Card from "components/ui/Card.vue";
|
||||
import Container from "components/ui/Container.vue";
|
||||
|
||||
import { storeToRefs } from "pinia";
|
||||
import { dedicationSchema } from "src/utils/zod/schemas";
|
||||
import { useCartStore } from "stores/cart";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ProductPage",
|
||||
components: {
|
||||
IconPencilGreen,
|
||||
IconWhatsapp,
|
||||
IconLinkedin,
|
||||
IconTwitter,
|
||||
IconShare,
|
||||
IconEmail,
|
||||
DudasSection,
|
||||
Container,
|
||||
Card,
|
||||
IconArrowCircleFilledRight,
|
||||
IconArrowCircleFilledLeft,
|
||||
ProductCarousel,
|
||||
},
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const cardMock = Array.from({ length: 4 }, (_, i) => ({
|
||||
id: i + 1,
|
||||
title: fakerES.lorem.word(),
|
||||
attributes: [""],
|
||||
imgSrc: `../assets/flowers/flower-${i + 1}.png`,
|
||||
value: fakerES.commerce.price({ symbol: "€", min: 15, max: 200 }),
|
||||
isNew: fakerES.datatype.boolean(),
|
||||
discount: fakerES.number.int({ min: 5, max: 25 }),
|
||||
}));
|
||||
|
||||
useMeta({
|
||||
title: "Product",
|
||||
titleTemplate: (title) => `${title} ${route.params.id} - FloraNet`,
|
||||
meta: {
|
||||
description: { name: "description", content: "Page 1" },
|
||||
keywords: { name: "keywords", content: "Quasar website" },
|
||||
equiv: {
|
||||
"http-equiv": "Content-Type",
|
||||
content: "text/html; charset=UTF-8",
|
||||
},
|
||||
ogTitle: {
|
||||
property: "og:title",
|
||||
template(ogTitle) {
|
||||
return `${ogTitle} - FloraNet`;
|
||||
},
|
||||
},
|
||||
noscript: {
|
||||
default: "This is content for browsers with no JS (or disabled JS)",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { addToCart, getProduct } = cartStore;
|
||||
const { prevProduct, currentProduct, nextProduct, addCartLoadingBtn } =
|
||||
storeToRefs(cartStore);
|
||||
|
||||
onBeforeMount(() => {
|
||||
getProduct(+route.params.id);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(newId) => {
|
||||
getProduct(+newId);
|
||||
}
|
||||
);
|
||||
|
||||
const currentData = ref({});
|
||||
watch(currentProduct.value, (newData) => {
|
||||
const { id, ...newDataWhithoutId } = newData.value;
|
||||
currentData.value = { ...newDataWhithoutId, productId: +route.params.id };
|
||||
});
|
||||
|
||||
const { handleSubmit, defineField, handleReset } = useForm({
|
||||
validationSchema: dedicationSchema,
|
||||
});
|
||||
const [dedication, dedicationAttrs] = defineField("dedication");
|
||||
const onSubmit = handleSubmit(() => {
|
||||
addToCart(currentData.value, dedication);
|
||||
handleReset();
|
||||
});
|
||||
|
||||
return {
|
||||
cardMock,
|
||||
slide: ref(1),
|
||||
fullscreen: ref(false),
|
||||
dedication,
|
||||
dedicationAttrs,
|
||||
onSubmit,
|
||||
addCartLoadingBtn,
|
||||
prevProduct,
|
||||
currentProduct,
|
||||
nextProduct,
|
||||
currentData,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product-container {
|
||||
display: flex;
|
||||
|
|
|
@ -62,8 +62,14 @@ const routes = [
|
|||
name: "Contacta",
|
||||
component: () => import("pages/ContactaPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/example",
|
||||
name: "Example",
|
||||
component: () => import("pages/ExamplePage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/:catchAll(.*)*",
|
||||
name: "NotFound",
|
||||
component: () => import("pages/ErrorNotFound.vue"),
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1397,54 +1397,42 @@
|
|||
],
|
||||
"cart": [
|
||||
{
|
||||
"title": "Increible Algodón Silla",
|
||||
"description": "Ergonomic executive chair upholstered in bonded black leather and PVC padded seat and back for all-day comfort and support",
|
||||
"price": "€198",
|
||||
"sku": "9781774532096",
|
||||
"category": "Acero",
|
||||
"title": "Guapa Algodón Ordenador",
|
||||
"description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
|
||||
"price": "€150",
|
||||
"sku": "9781616149024",
|
||||
"category": "Plástico",
|
||||
"images": [
|
||||
"https://picsum.photos/seed/jnIFY2/640/480",
|
||||
"https://picsum.photos/seed/sMMlbf6zc8/640/480",
|
||||
"https://picsum.photos/seed/NpRZteKf/640/480",
|
||||
"https://picsum.photos/seed/cQ7Oei/640/480",
|
||||
"https://picsum.photos/seed/wUVXjS1zi/640/480",
|
||||
"https://picsum.photos/seed/ym8fuWY8/640/480"
|
||||
"https://picsum.photos/seed/dyj4Y/640/480",
|
||||
"https://picsum.photos/seed/vQWtap1t/640/480"
|
||||
],
|
||||
"productId": 10,
|
||||
"productId": 3,
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"title": "Increible Algodón Silla",
|
||||
"description": "Ergonomic executive chair upholstered in bonded black leather and PVC padded seat and back for all-day comfort and support",
|
||||
"price": "€198",
|
||||
"sku": "9781774532096",
|
||||
"category": "Acero",
|
||||
"title": "Inteligente Acero Pantalones",
|
||||
"description": "Carbonite web goalkeeper gloves are ergonomically designed to give easy fit",
|
||||
"price": "€24",
|
||||
"sku": "9780350182908",
|
||||
"category": "Hormigon",
|
||||
"images": [
|
||||
"https://picsum.photos/seed/jnIFY2/640/480",
|
||||
"https://picsum.photos/seed/sMMlbf6zc8/640/480",
|
||||
"https://picsum.photos/seed/NpRZteKf/640/480",
|
||||
"https://picsum.photos/seed/cQ7Oei/640/480",
|
||||
"https://picsum.photos/seed/wUVXjS1zi/640/480",
|
||||
"https://picsum.photos/seed/ym8fuWY8/640/480"
|
||||
"https://picsum.photos/seed/c3QvIh5QR/640/480",
|
||||
"https://picsum.photos/seed/NZWI0TRX3E/640/480"
|
||||
],
|
||||
"productId": 10,
|
||||
"productId": 1,
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"title": "Increible Algodón Silla",
|
||||
"description": "Ergonomic executive chair upholstered in bonded black leather and PVC padded seat and back for all-day comfort and support",
|
||||
"price": "€198",
|
||||
"sku": "9781774532096",
|
||||
"category": "Acero",
|
||||
"title": "Inteligente Acero Pantalones",
|
||||
"description": "Carbonite web goalkeeper gloves are ergonomically designed to give easy fit",
|
||||
"price": "€24",
|
||||
"sku": "9780350182908",
|
||||
"category": "Hormigon",
|
||||
"images": [
|
||||
"https://picsum.photos/seed/jnIFY2/640/480",
|
||||
"https://picsum.photos/seed/sMMlbf6zc8/640/480",
|
||||
"https://picsum.photos/seed/NpRZteKf/640/480",
|
||||
"https://picsum.photos/seed/cQ7Oei/640/480",
|
||||
"https://picsum.photos/seed/wUVXjS1zi/640/480",
|
||||
"https://picsum.photos/seed/ym8fuWY8/640/480"
|
||||
"https://picsum.photos/seed/c3QvIh5QR/640/480",
|
||||
"https://picsum.photos/seed/NZWI0TRX3E/640/480"
|
||||
],
|
||||
"productId": 10,
|
||||
"productId": 1,
|
||||
"id": 3
|
||||
},
|
||||
{
|
||||
|
@ -1461,43 +1449,57 @@
|
|||
"id": 4
|
||||
},
|
||||
{
|
||||
"title": "Inteligente Acero Pantalones",
|
||||
"description": "Carbonite web goalkeeper gloves are ergonomically designed to give easy fit",
|
||||
"price": "€24",
|
||||
"sku": "9780350182908",
|
||||
"category": "Hormigon",
|
||||
"title": "Pequeño Ladrillo Pollo",
|
||||
"description": "The beautiful range of Apple Naturalé that has an exciting mix of natural ingredients. With the Goodness of 100% Natural Ingredients",
|
||||
"price": "€195",
|
||||
"sku": "9781027438533",
|
||||
"category": "Algodón",
|
||||
"images": [
|
||||
"https://picsum.photos/seed/c3QvIh5QR/640/480",
|
||||
"https://picsum.photos/seed/NZWI0TRX3E/640/480"
|
||||
"https://picsum.photos/seed/akHdlbK3/640/480",
|
||||
"https://picsum.photos/seed/KSxDr8aQqe/640/480",
|
||||
"https://picsum.photos/seed/e3x6PdCNg/640/480"
|
||||
],
|
||||
"productId": 1,
|
||||
"productId": 2,
|
||||
"id": 5
|
||||
},
|
||||
{
|
||||
"title": "Inteligente Acero Pantalones",
|
||||
"description": "Carbonite web goalkeeper gloves are ergonomically designed to give easy fit",
|
||||
"price": "€24",
|
||||
"sku": "9780350182908",
|
||||
"category": "Hormigon",
|
||||
"title": "Guapa Algodón Ordenador",
|
||||
"description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
|
||||
"price": "€150",
|
||||
"sku": "9781616149024",
|
||||
"category": "Plástico",
|
||||
"images": [
|
||||
"https://picsum.photos/seed/c3QvIh5QR/640/480",
|
||||
"https://picsum.photos/seed/NZWI0TRX3E/640/480"
|
||||
"https://picsum.photos/seed/dyj4Y/640/480",
|
||||
"https://picsum.photos/seed/vQWtap1t/640/480"
|
||||
],
|
||||
"productId": 1,
|
||||
"productId": 3,
|
||||
"id": 6
|
||||
},
|
||||
{
|
||||
"title": "Inteligente Acero Pantalones",
|
||||
"description": "Carbonite web goalkeeper gloves are ergonomically designed to give easy fit",
|
||||
"price": "€24",
|
||||
"sku": "9780350182908",
|
||||
"category": "Hormigon",
|
||||
"title": "Guapa Algodón Ordenador",
|
||||
"description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
|
||||
"price": "€150",
|
||||
"sku": "9781616149024",
|
||||
"category": "Plástico",
|
||||
"images": [
|
||||
"https://picsum.photos/seed/c3QvIh5QR/640/480",
|
||||
"https://picsum.photos/seed/NZWI0TRX3E/640/480"
|
||||
"https://picsum.photos/seed/dyj4Y/640/480",
|
||||
"https://picsum.photos/seed/vQWtap1t/640/480"
|
||||
],
|
||||
"productId": 1,
|
||||
"productId": 3,
|
||||
"id": 7
|
||||
},
|
||||
{
|
||||
"title": "Guapa Algodón Ordenador",
|
||||
"description": "Boston's most advanced compression wear technology increases muscle oxygenation, stabilizes active muscles",
|
||||
"price": "€150",
|
||||
"sku": "9781616149024",
|
||||
"category": "Plástico",
|
||||
"images": [
|
||||
"https://picsum.photos/seed/dyj4Y/640/480",
|
||||
"https://picsum.photos/seed/vQWtap1t/640/480"
|
||||
],
|
||||
"productId": 3,
|
||||
"id": 8
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"/jsonServer/*": "/$1"
|
||||
}
|
|
@ -1,15 +1,30 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { api } from "src/boot/axios";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { api, apiBack } from "src/boot/axios";
|
||||
import { quasarNotify } from "src/functions/quasarNotify";
|
||||
|
||||
export const useCartStore = defineStore("cart", () => {
|
||||
const cart = ref([]);
|
||||
const cartList = ref([]);
|
||||
const products = ref({
|
||||
data: {
|
||||
page: undefined,
|
||||
productsPerPage: undefined,
|
||||
products: [],
|
||||
},
|
||||
prev: {},
|
||||
current: {},
|
||||
next: {},
|
||||
});
|
||||
const dedicationTxt = ref("");
|
||||
const prevProduct = reactive({});
|
||||
const currentProduct = reactive({});
|
||||
const currentProduct = ref();
|
||||
const nextProduct = reactive({});
|
||||
const addCartLoadingBtn = ref(false);
|
||||
const cartLength = computed(() => cart.value.length);
|
||||
const routeId = ref(null);
|
||||
const totalPrice = computed(() => {
|
||||
return cart.value.reduce((acc, { price }) => {
|
||||
if (price) {
|
||||
|
@ -18,6 +33,7 @@ export const useCartStore = defineStore("cart", () => {
|
|||
}
|
||||
}, 0);
|
||||
});
|
||||
const { push } = useRouter();
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -27,6 +43,22 @@ export const useCartStore = defineStore("cart", () => {
|
|||
async function getCart({ debug }) {
|
||||
try {
|
||||
const { data } = await api.get("cart");
|
||||
const cartItems = data.reduce((obj, { title, price, ...rest }) => {
|
||||
const priceWithoutLetter = +price.replace("€", "");
|
||||
|
||||
if (obj[title]) {
|
||||
obj[title].quantity++;
|
||||
} else {
|
||||
obj[title] = {
|
||||
title,
|
||||
price: `${priceWithoutLetter}€`,
|
||||
quantity: 1,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
cartList.value = Object.values(cartItems);
|
||||
cart.value = data;
|
||||
|
||||
if (debug) {
|
||||
|
@ -35,38 +67,144 @@ export const useCartStore = defineStore("cart", () => {
|
|||
console.groupEnd();
|
||||
}
|
||||
} catch (err) {
|
||||
/* throw */ new Error(`FATAL ERROR ::: ${err}`);
|
||||
new Error(`FATAL ERROR ::: ${err}`);
|
||||
}
|
||||
}
|
||||
getCart({ debug: true });
|
||||
|
||||
async function getProducts(
|
||||
options = {
|
||||
itens: undefined,
|
||||
featured: undefined,
|
||||
page: undefined,
|
||||
category: undefined,
|
||||
postalCode: undefined,
|
||||
dateExpired: undefined,
|
||||
minPrice: undefined,
|
||||
maxPrice: undefined,
|
||||
bigPrice: undefined,
|
||||
lowPrice: undefined,
|
||||
isNew: undefined,
|
||||
},
|
||||
navigate
|
||||
) {
|
||||
const optionsObj = {
|
||||
itens: options.itens,
|
||||
featured: options.featured,
|
||||
page: options.page,
|
||||
category: options.category,
|
||||
postalCode: options.postalCode,
|
||||
dateExpired: options.dateExpired,
|
||||
minPrice: options.minPrice,
|
||||
maxPrice: options.maxPrice,
|
||||
bigPrice: options.bigPrice,
|
||||
lowPrice: options.lowPrice,
|
||||
isNew: options.isNew,
|
||||
};
|
||||
const validKeys = Object.keys(options).filter(
|
||||
(key) => options[key] !== undefined
|
||||
);
|
||||
const params = validKeys.reduce((acc, key) => {
|
||||
acc[key] = optionsObj[key];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { data },
|
||||
} = await apiBack.get("products", {
|
||||
params,
|
||||
});
|
||||
|
||||
if (data[0].products.length === 0) {
|
||||
return quasarNotify({
|
||||
message:
|
||||
"No hay productos disponibles para la fecha y el código postal seleccionados",
|
||||
type: "erro",
|
||||
});
|
||||
}
|
||||
|
||||
products.value.data = data[0];
|
||||
|
||||
if (navigate) {
|
||||
navigate();
|
||||
}
|
||||
|
||||
const currentProductIndex = data.findIndex(
|
||||
({ slug }) => slug === routeId.value
|
||||
);
|
||||
const prevProductIndex = currentProductIndex - 1;
|
||||
const nextProductIndex = currentProductIndex + 1;
|
||||
|
||||
products.value.prev = data.data[prevProductIndex];
|
||||
products.value.current = data.data[currentProductIndex];
|
||||
products.value.next = data.data[nextProductIndex];
|
||||
|
||||
console.groupCollapsed("%c PRODUCTS FETCHED!", "color: green;");
|
||||
console.groupCollapsed("%c PRODUCTS DATA", "color: tomato;");
|
||||
console.table(products.value.data);
|
||||
console.groupEnd();
|
||||
console.groupCollapsed("%c PREV PRODUCT", "color: tomato;");
|
||||
console.table(products.value.prev);
|
||||
console.groupEnd();
|
||||
console.groupCollapsed("%c CURRENT PRODUCT", "color: tomato;");
|
||||
console.table(products.value.current);
|
||||
console.groupEnd();
|
||||
console.groupCollapsed("%c NEXT PRODUCT", "color: tomato;");
|
||||
console.table(products.value.next);
|
||||
console.groupEnd();
|
||||
console.groupEnd();
|
||||
} catch (err) {
|
||||
new Error(`FATAL ERROR ::: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id Id to get product
|
||||
* @returns 'id: number; title: string; description: string; price: string; sku: string; category: string; images: string[]'
|
||||
*
|
||||
*/
|
||||
async function getProduct(id) {
|
||||
if (id) {
|
||||
routeId.value = id;
|
||||
try {
|
||||
const { data } = await api.get(`flowers/${id}`);
|
||||
currentProduct.value = data;
|
||||
const { data: dataNext } = await api.get(`flowers/${id + 1}`);
|
||||
if (dataNext) {
|
||||
nextProduct.value = dataNext;
|
||||
}
|
||||
/* const promises = [
|
||||
api.get(`flowers/${id - 1}`),
|
||||
api.get(`flowers/${id}`),
|
||||
api.get(`flowers/${id + 1}`),
|
||||
];
|
||||
const results = await Promise.allSettled(promises);
|
||||
const [prev, current, next] = results.map((res) => {
|
||||
const result = {
|
||||
fulfilled: res.value?.data,
|
||||
rejected: res.reason,
|
||||
};
|
||||
|
||||
console.groupCollapsed("%c Produtos recebido!", "color: green;");
|
||||
if (id - 1 > 0) {
|
||||
const { data: dataPrev } = await api.get(`flowers/${id - 1}`);
|
||||
prevProduct.value = dataPrev;
|
||||
return result[res.status];
|
||||
}); */
|
||||
|
||||
const { data } = await apiBack.get(`products/slug/${id}`);
|
||||
|
||||
prevProduct.value = {};
|
||||
currentProduct.value = data.data[0];
|
||||
nextProduct.value = {};
|
||||
|
||||
console.groupCollapsed(
|
||||
`%c PRODUCT FETCHED! SLUG: ${routeId.value}`,
|
||||
"color: green;"
|
||||
);
|
||||
console.time();
|
||||
console.table(prevProduct.value);
|
||||
}
|
||||
console.table(currentProduct.value);
|
||||
console.table(nextProduct.value);
|
||||
console.timeEnd();
|
||||
console.groupEnd();
|
||||
|
||||
if (currentProduct.value.response?.status === 404) {
|
||||
push({ name: "NotFound" });
|
||||
}
|
||||
} catch (err) {
|
||||
new Error(`FATAL ERROR ::: ${err}`);
|
||||
console.error(`FATAL ERROR ::: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,23 +217,25 @@ export const useCartStore = defineStore("cart", () => {
|
|||
try {
|
||||
await api.post("cart", product);
|
||||
addCartLoadingBtn.value = false;
|
||||
} catch (err) {
|
||||
addCartLoadingBtn.value = false;
|
||||
new Error(`FATAL ERROR ::: ${err}`);
|
||||
}
|
||||
// push("/checkout");
|
||||
|
||||
console.groupCollapsed("%c Adicionado com sucesso!", "color: green");
|
||||
console.table(cart.value);
|
||||
console.groupEnd();
|
||||
} catch (err) {
|
||||
addCartLoadingBtn.value = false;
|
||||
new Error(`FATAL ERROR ::: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromCart(id) {
|
||||
cart.value = cart.value.filter((p) => p.id !== id);
|
||||
cart.value = cart.value.filter((p) => id !== p.id);
|
||||
api.delete(`cart/${id}`);
|
||||
}
|
||||
|
||||
return {
|
||||
cart,
|
||||
cartList,
|
||||
totalPrice,
|
||||
dedicationTxt,
|
||||
cartLength,
|
||||
|
@ -103,7 +243,9 @@ export const useCartStore = defineStore("cart", () => {
|
|||
currentProduct,
|
||||
nextProduct,
|
||||
addCartLoadingBtn,
|
||||
products,
|
||||
|
||||
getProducts,
|
||||
addToCart,
|
||||
removeFromCart,
|
||||
getProduct,
|
||||
|
|
|
@ -50,6 +50,10 @@ export const useFormStore = defineStore("forms", {
|
|||
this.availability = values;
|
||||
},
|
||||
|
||||
registerAvailability() {
|
||||
console.log(this.availability);
|
||||
},
|
||||
|
||||
handleCheckoutData(values) {
|
||||
// console.log(values);
|
||||
this.checkout = values;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useLanguageStore = defineStore("language", {
|
||||
state: () => ({
|
||||
lang: "es",
|
||||
}),
|
||||
export const useLanguageStore = defineStore("language", () => {
|
||||
const lang = ref("es");
|
||||
|
||||
return { lang };
|
||||
});
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export const useMobileStore = defineStore("mobile", {
|
||||
state: () => ({
|
||||
isOpenNav: false,
|
||||
isCarouselVisible: false,
|
||||
screenWidth: 0,
|
||||
}),
|
||||
export const useMobileStore = defineStore("mobile", () => {
|
||||
const isOpenNav = ref(false);
|
||||
const isCarouselVisible = ref(false);
|
||||
const screenWidth = ref(0);
|
||||
|
||||
actions: {
|
||||
handleOpenMobileNav() {
|
||||
this.isOpenNav = !this.isOpenNav;
|
||||
},
|
||||
handleResize() {
|
||||
this.screenWidth = screen.width;
|
||||
if (this.screenWidth > 768) {
|
||||
this.isOpenNav = false;
|
||||
function handleOpenMobileNav() {
|
||||
isOpenNav.value = !isOpenNav.value;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
function handleResize() {
|
||||
screenWidth.value = screen.width;
|
||||
if (screenWidth.value > 768) {
|
||||
isOpenNav.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isOpenNav,
|
||||
isCarouselVisible,
|
||||
screenWidth,
|
||||
handleOpenMobileNav,
|
||||
handleResize,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
import { useFormStore } from "./forms";
|
||||
|
||||
export const useModalStore = defineStore("modal", {
|
||||
state: () => ({
|
||||
isOpenAvailability: false,
|
||||
isOpenFilters: false,
|
||||
}),
|
||||
export const useModalStore = defineStore("modal", () => {
|
||||
const formStore = useFormStore();
|
||||
const availability = computed(() => formStore.availability);
|
||||
|
||||
actions: {
|
||||
openModal({ modal }) {
|
||||
const isOpenAvailability = ref(false);
|
||||
const isOpenFilters = ref(false);
|
||||
|
||||
function openModal({ modal }) {
|
||||
const open = {
|
||||
availability: () =>
|
||||
(this.isOpenAvailability = !this.isOpenAvailability),
|
||||
filters: () => (this.isOpenFilters = !this.isOpenFilters),
|
||||
(isOpenAvailability.value = !isOpenAvailability.value),
|
||||
filters: () => (isOpenFilters.value = !isOpenFilters.value),
|
||||
};
|
||||
open[modal]();
|
||||
},
|
||||
handleSubmit({ isModalContent }) {
|
||||
}
|
||||
|
||||
function handleSubmit({ content }) {
|
||||
const isModal = {
|
||||
isOpenAvailability: () => "Contenido modal availability",
|
||||
isOpenFilters: () => "Contenido modal filters",
|
||||
};
|
||||
console.log(isModal[isModalContent]());
|
||||
},
|
||||
},
|
||||
console.log(availability.value);
|
||||
console.log(isModal[content]());
|
||||
}
|
||||
|
||||
return { openModal, handleSubmit, isOpenAvailability, isOpenFilters };
|
||||
});
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { reactive } from "vue";
|
||||
|
||||
export const useRangePriceStore = defineStore("range-price", {
|
||||
state: () => ({
|
||||
rangeValue: {
|
||||
export const useRangePriceStore = defineStore("range-price", () => {
|
||||
const rangeValue = reactive({
|
||||
min: 0,
|
||||
max: 200,
|
||||
},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
handlePriceRange({ min, max }) {
|
||||
console.log(min, max);
|
||||
this.rangeValue = {
|
||||
max,
|
||||
min,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function handlePriceRange({ min, max }) {
|
||||
console.log({ min, max });
|
||||
rangeValue.min = min;
|
||||
rangeValue.max = max;
|
||||
}
|
||||
|
||||
return { rangeValue, handlePriceRange };
|
||||
});
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useTextInputStore = defineStore("text-input", {
|
||||
state: () => ({
|
||||
dedication: "",
|
||||
}),
|
||||
export const useTextInputStore = defineStore("text-input", () => {
|
||||
const dedication = ref("");
|
||||
|
||||
actions: {
|
||||
handleDedicationSubmit() {
|
||||
console.log(this.dedication);
|
||||
},
|
||||
},
|
||||
function handleDedicationSubmit() {
|
||||
console.log(dedication);
|
||||
}
|
||||
|
||||
return {
|
||||
dedication,
|
||||
handleDedicationSubmit,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -8,5 +8,5 @@ export const postalCode = z
|
|||
const valWithoutHifen = val.replaceAll("-", "");
|
||||
const valLength = valWithoutHifen.length;
|
||||
|
||||
return valLength === 8 && valLength >= 1;
|
||||
}, "El código postal debe tener 8 caracteres numéricos válidos");
|
||||
return valLength === 5 && valLength >= 1;
|
||||
}, "El código postal debe tener 5 caracteres numéricos válidos");
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { z } from "zod";
|
||||
import { postalCode } from "..";
|
||||
import * as M from "../messages";
|
||||
|
||||
const availabilityObj = {
|
||||
date: z.string().refine((val) => {
|
||||
date: z.string({ required_error: M.requiredMessage }).refine((val) => {
|
||||
const [day, month, year] = val.split("/");
|
||||
const regex = /\//g;
|
||||
const valWithoutSlash = val.replace(regex, "");
|
||||
|
|
|
@ -29,7 +29,7 @@ const checkoutObjVal = {
|
|||
senderPhone: z.string().refine(handlePhoneVal, M.phoneMessage),
|
||||
senderNotes: z.string(),
|
||||
paymentMethod: z.enum(["credit", "stripe"], {
|
||||
required_error: "Seleccione uno de los métodos de pago",
|
||||
required_error: "Seleccione uno de los métodos de pago!",
|
||||
}),
|
||||
terms: z.boolean().refine((val) => {
|
||||
return val === true;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
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"),
|
||||
max: z.number(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const rangePriceSchema = z.object(rangePriceObj);
|
Loading…
Reference in New Issue