Creamos api en node para la conexión a base de datos y cambios que pidió pablo #4
|
@ -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();
|
|
@ -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
|
@ -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 -->
|
||||
|
|
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",
|
||||
|
|
After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 2.8 KiB |
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
|
@ -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>
|
||||
|
|
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 = 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>
|
||||
jsolis marked this conversation as resolved
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="reasons-container"
|
||||
|
@ -68,7 +94,7 @@
|
|||
<li class="reason-footer-item">
|
||||
<IconCheck />
|
||||
<p class="reason-item-paragraph">
|
||||
Simplicidad de compray pago seguro
|
||||
Simplicidad de compra y pago seguro
|
||||
</p>
|
||||
</li>
|
||||
|
||||
|
@ -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>
|
||||
jsolis marked this conversation as resolved
pablone
commented
para que utilizas este componente? para que utilizas este componente?
jsolis
commented
Lo íbamos a utilizar para el modal de las categorías Lo íbamos a utilizar para el modal de las categorías
|
||||
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: {
|
||||
jsolis marked this conversation as resolved
pablone
commented
De momento descartamos el descuento De momento descartamos el descuento
jsolis
commented
Correcto, pero en próximas fases entendemos que se utilizara no? Correcto, pero en próximas fases entendemos que se utilizara no?
|
||||
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,85 +98,86 @@
|
|||
</p>
|
||||
|
||||
<div class="modal-body-content">
|
||||
<div v-if="modalItem === 'isOpenFilters'" class="modal-body-filters">
|
||||
<div class="filter-field">
|
||||
<PriceRange />
|
||||
<form @submit="onSubmit" id="filters-form">
|
||||
<div
|
||||
v-if="modalItem === 'isOpenFilters'"
|
||||
class="modal-body-filters"
|
||||
>
|
||||
<div class="filter-field">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="modalItem === 'isOpenAvailability'"
|
||||
class="modal-body-availability"
|
||||
>
|
||||
<calendar />
|
||||
<div
|
||||
v-if="modalItem === 'isOpenAvailability'"
|
||||
class="modal-body-availability"
|
||||
>
|
||||
<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 />
|
||||
</div>
|
||||
<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 {
|
||||
jsolis marked this conversation as resolved
pablone
commented
Descartamos las traducciones Descartamos las traducciones
jsolis
commented
Como comentamos en la reunion, lo dejamos para proximas fases. Como comentamos en la reunion, lo dejamos para proximas fases.
|
||||
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,35 +1,44 @@
|
|||
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) => ({
|
||||
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',
|
||||
}));
|
||||
const flower = {
|
||||
id: i + 1,
|
||||
name: fakerES.commerce.productName(),
|
||||
description: fakerES.commerce.productDescription(),
|
||||
price: fakerES.commerce.price({
|
||||
symbol: "€",
|
||||
min: 20,
|
||||
max: 200,
|
||||
dec: 0,
|
||||
}),
|
||||
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 }),
|
||||
};
|
||||
|
||||
export function generateFlowers({ length }) {
|
||||
const flowersMock = Array.from({ length }, (_, i) => ({
|
||||
id: i + 1,
|
||||
title: fakerES.commerce.productName(),
|
||||
description: fakerES.commerce.productDescription(),
|
||||
price: fakerES.commerce.price({
|
||||
symbol: "€",
|
||||
min: 20,
|
||||
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()
|
||||
),
|
||||
}));
|
||||
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);
|
||||
jsolis marked this conversation as resolved
Outdated
pablone
commented
No veo donde se utiliza No veo donde se utiliza
jsolis
commented
En el proximo PR lo tenemos En el proximo PR lo tenemos
|
||||
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>
|
||||
<Card
|
||||
v-for="(
|
||||
{ imgSrc, discount, isNew, title, value, id }, i
|
||||
) in cardsMock"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
:discount="discount.toString()"
|
||||
:imgSrc="imgSrc"
|
||||
:isNew="isNew"
|
||||
:key="i"
|
||||
:id="id"
|
||||
/>
|
||||
<Container cardContainer class="category-container">
|
||||
<template
|
||||
v-for="{
|
||||
images,
|
||||
discount,
|
||||
isNew,
|
||||
name,
|
||||
price,
|
||||
slug,
|
||||
id,
|
||||
} in products.data.products"
|
||||
:key="id"
|
||||
>
|
||||
<Card
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="images[0]"
|
||||
:isNew="isNew"
|
||||
: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();
|
||||
jsolis marked this conversation as resolved
pablone
commented
Antes que comentar código bórralo git se acuerda de el Antes que comentar código bórralo git se acuerda de el
jsolis
commented
Ok Ok
|
||||
|
||||
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
|
||||
jsolis marked this conversation as resolved
pablone
commented
El idioma de los comentarios debe ser ingles El idioma de los comentarios debe ser ingles
jsolis
commented
Ok Ok
|
||||
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"
|
||||
:discount="discount"
|
||||
:imgSrc="imgSrc"
|
||||
:isNew="isNew"
|
||||
:key="i"
|
||||
:id="id"
|
||||
/>
|
||||
{ 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="images[0]"
|
||||
:isNew="isNew"
|
||||
: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"
|
||||
>
|
||||
<Card
|
||||
:id="id"
|
||||
:key="id"
|
||||
:productValue="value"
|
||||
:productName="title"
|
||||
:discount="discount"
|
||||
:imgSrc="imgSrc"
|
||||
:isNew="isNew"
|
||||
/>
|
||||
</swiper-slide>
|
||||
<swiper-slide class="swiper-slide" v-if="i < 10">
|
||||
<Card
|
||||
:id="slug"
|
||||
:key="slug"
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
: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>
|
||||
<q-carousel-slide
|
||||
: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 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"
|
||||
/>
|
||||
</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;
|
||||
console.table(prevProduct.value);
|
||||
}
|
||||
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;
|
||||
// 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}`);
|
||||
}
|
||||
|
||||
console.groupCollapsed("%c Adicionado com sucesso!", "color: green");
|
||||
console.table(cart.value);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
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 open = {
|
||||
availability: () =>
|
||||
(this.isOpenAvailability = !this.isOpenAvailability),
|
||||
filters: () => (this.isOpenFilters = !this.isOpenFilters),
|
||||
};
|
||||
open[modal]();
|
||||
},
|
||||
handleSubmit({ isModalContent }) {
|
||||
const isModal = {
|
||||
isOpenAvailability: () => "Contenido modal availability",
|
||||
isOpenFilters: () => "Contenido modal filters",
|
||||
};
|
||||
console.log(isModal[isModalContent]());
|
||||
},
|
||||
},
|
||||
const isOpenAvailability = ref(false);
|
||||
const isOpenFilters = ref(false);
|
||||
|
||||
function openModal({ modal }) {
|
||||
const open = {
|
||||
availability: () =>
|
||||
(isOpenAvailability.value = !isOpenAvailability.value),
|
||||
filters: () => (isOpenFilters.value = !isOpenFilters.value),
|
||||
};
|
||||
open[modal]();
|
||||
}
|
||||
|
||||
function handleSubmit({ content }) {
|
||||
const isModal = {
|
||||
isOpenAvailability: () => "Contenido modal availability",
|
||||
isOpenFilters: () => "Contenido modal filters",
|
||||
};
|
||||
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: {
|
||||
min: 0,
|
||||
max: 200,
|
||||
},
|
||||
}),
|
||||
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);
|
Te falta un espacio entre compra y la y "compray" → "compra y"
Ok