This commit is contained in:
parent
b13706651b
commit
308c649325
|
@ -23,7 +23,6 @@
|
||||||
"source.fixAll.eslint",
|
"source.fixAll.eslint",
|
||||||
"source.fixAll.stylelint"
|
"source.fixAll.stylelint"
|
||||||
],
|
],
|
||||||
|
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"**/.git": true,
|
"**/.git": true,
|
||||||
"**/.svn": true,
|
"**/.svn": true,
|
||||||
|
@ -61,11 +60,6 @@
|
||||||
"terminal.integrated.enableImages": true,
|
"terminal.integrated.enableImages": true,
|
||||||
"figma.autocompleteBlocks": true,
|
"figma.autocompleteBlocks": true,
|
||||||
"figma.assetExportDirectory": "src/assets",
|
"figma.assetExportDirectory": "src/assets",
|
||||||
"editor.codeActionsOnSave": [
|
|
||||||
"source.addMissingImports",
|
|
||||||
"source.organizeImports",
|
|
||||||
"source.fixAll.eslint"
|
|
||||||
],
|
|
||||||
"gitlens.gitCommands.skipConfirmations": ["fetch:command", "switch:command"],
|
"gitlens.gitCommands.skipConfirmations": ["fetch:command", "switch:command"],
|
||||||
"diffEditor.ignoreTrimWhitespace": false,
|
"diffEditor.ignoreTrimWhitespace": false,
|
||||||
"svg.preview.mode": "svg",
|
"svg.preview.mode": "svg",
|
||||||
|
@ -78,8 +72,5 @@
|
||||||
"workbench.tree.indent": 16,
|
"workbench.tree.indent": 16,
|
||||||
"window.zoomLevel": -1,
|
"window.zoomLevel": -1,
|
||||||
"git.ignoreRebaseWarning": true,
|
"git.ignoreRebaseWarning": true,
|
||||||
"editor.largeFileOptimizations": false,
|
"editor.largeFileOptimizations": false
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,3 +3,7 @@ DB_USER="root"
|
||||||
DB_PASSWORD="root"
|
DB_PASSWORD="root"
|
||||||
PORT ="3306"
|
PORT ="3306"
|
||||||
DATABASE="floranet"
|
DATABASE="floranet"
|
||||||
|
BASE_URL =http://localhost:9100
|
||||||
|
|
||||||
|
CLIENT_ID="Ab5vEddhdvdJhLUkXtTiS2pe43W6PD1JNKns7XMnlw8FvC31H2VYakyVEHvuFBi2b543QIHiPh8j4FLF"
|
||||||
|
SECRET_KEY="EAxLf05kp08cvbLgZrqjwdx-NXnhQtnP4Y0B4LHAM_7T9-HOh4RaNTirinWfTV8GR6DJWg9djry5yHfO"
|
|
@ -0,0 +1,22 @@
|
||||||
|
const db = require("../../db/db");
|
||||||
|
|
||||||
|
class ContactController {
|
||||||
|
async Create(req, res) {
|
||||||
|
try {
|
||||||
|
const { name, phone, email, message } = req.body;
|
||||||
|
console.log(name, phone, email, message);
|
||||||
|
const contact = await db.contact_Request(name, phone, email, message);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
data: contact[0]
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return res.status(422).send({
|
||||||
|
message: "error al guardar contacto"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new ContactController();
|
|
@ -0,0 +1,13 @@
|
||||||
|
const db = require("../../db/db");
|
||||||
|
|
||||||
|
class DeliveryController {
|
||||||
|
async findByPostalCode(req, res) {
|
||||||
|
const { postalCode } = req.query;
|
||||||
|
const dates = await db.deliveryDate_get(postalCode);
|
||||||
|
return res.status(200).send({
|
||||||
|
data: dates[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new DeliveryController();
|
|
@ -0,0 +1,23 @@
|
||||||
|
const db = require("../../db/db");
|
||||||
|
const paypal = require('paypal-rest-sdk');
|
||||||
|
const paymentServices = require('./payment.services')
|
||||||
|
|
||||||
|
class PaymentController {
|
||||||
|
async Create(req, res) {
|
||||||
|
return await paymentServices.Create(req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
async Success(req, res) {
|
||||||
|
return await paymentServices.Success(req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
Cancel(req, res) {
|
||||||
|
return res.status(200).send({
|
||||||
|
data: {
|
||||||
|
menssage: "cancelado"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new PaymentController();
|
|
@ -0,0 +1,113 @@
|
||||||
|
const db = require("../../db/db");
|
||||||
|
const payPalProviders = require('./paypal.providers')
|
||||||
|
const redsysProviders = require('./redsys.providers')
|
||||||
|
|
||||||
|
class PaymentServices {
|
||||||
|
async Create(req, res) {
|
||||||
|
try {
|
||||||
|
//parâmetros para retornar os produtos que serão comprados
|
||||||
|
const { products, dateExpired, postalCode, customer, type } = req.body
|
||||||
|
const _products = await db.getProducts(dateExpired, postalCode)
|
||||||
|
|
||||||
|
const productsFilter = _products[0].filter((item) => {
|
||||||
|
if (products.includes(item.id)) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (productsFilter.length !== products.length) {
|
||||||
|
return res.status(422).send({
|
||||||
|
data: {
|
||||||
|
message: "Uno de los productos no existe."
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let priceIntial = 0
|
||||||
|
let price = productsFilter.reduce((accumulator, curValue) => accumulator + Number(curValue.price), priceIntial)
|
||||||
|
|
||||||
|
let productsIds = ''
|
||||||
|
for (let i = 0; i < products.length; i++) {
|
||||||
|
productsIds += `${products[i]}${i === products.length - 1 ? '' : '-'}`
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create new order
|
||||||
|
const jsonOrderData = JSON.stringify({
|
||||||
|
"customer": {
|
||||||
|
customerData: {
|
||||||
|
...customer.customerData,
|
||||||
|
type: type,
|
||||||
|
products: productsFilter
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const order = await db.orderData_put(jsonOrderData);
|
||||||
|
const orderFk = order[0][0].orderFk
|
||||||
|
|
||||||
|
if (type === "paypal") {
|
||||||
|
const data = await payPalProviders.New(orderFk, price)
|
||||||
|
return res.status(200).send({
|
||||||
|
data: { ...data, orderId: orderFk }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (type === "redsys") {
|
||||||
|
const data = await redsysProviders.New(orderFk, price)
|
||||||
|
return res.status(200).send({
|
||||||
|
data: { ...data, orderId: orderFk }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/* if (newOrder) {
|
||||||
|
return res.status(200).send({
|
||||||
|
data: { link: newOrder.links, orderId: orderFk }
|
||||||
|
})
|
||||||
|
} */
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return res.status(422).send({
|
||||||
|
data: {
|
||||||
|
message: "Error al iniciar el pago"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Success(req, res) {
|
||||||
|
try {
|
||||||
|
//Parameters payment
|
||||||
|
const { paymentId, PayerID, orderId } = req.body
|
||||||
|
const payerId = { 'payer_id': PayerID };
|
||||||
|
|
||||||
|
//API validation payent and confirnm order
|
||||||
|
paypal.payment.execute(paymentId, payerId, async function (error, payment) {
|
||||||
|
if (error) {
|
||||||
|
return res.status(422).send({
|
||||||
|
data: {
|
||||||
|
message: "payment not successful"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (payment.state == 'approved') {
|
||||||
|
await db.order_confirm(orderId)
|
||||||
|
return res.status(200).send({
|
||||||
|
data: {
|
||||||
|
id: payment.id,
|
||||||
|
message: "payment completed successfully",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return res.status(422).send({
|
||||||
|
data: {
|
||||||
|
message: "payment not successful"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new PaymentServices();
|
|
@ -0,0 +1,59 @@
|
||||||
|
const paypal = require('paypal-rest-sdk');
|
||||||
|
|
||||||
|
class PayPalProviders {
|
||||||
|
async New(orderFk, price) {
|
||||||
|
try {
|
||||||
|
const payReq = JSON.stringify({
|
||||||
|
'intent': 'sale',
|
||||||
|
'redirect_urls': {
|
||||||
|
'return_url': `${process.env.BASE_URL}/checkout/success?orderId=${orderFk}`,
|
||||||
|
'cancel_url': `${process.env.BASE_URL}/checkout/error`
|
||||||
|
},
|
||||||
|
'payer': {
|
||||||
|
'payment_method': 'paypal'
|
||||||
|
},
|
||||||
|
'transactions': [{
|
||||||
|
'amount': {
|
||||||
|
'total': 0.0000000001,
|
||||||
|
'currency': 'EUR'
|
||||||
|
},
|
||||||
|
'description': 'This is the payment transaction description.'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
//Starting checkout process and returning sandbox url
|
||||||
|
const newOrder = await new Promise(async (resolve, reject) => {
|
||||||
|
paypal.payment.create(payReq, function (error, payment) {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
//capture HATEOAS links
|
||||||
|
var links = {};
|
||||||
|
payment.links.forEach(function (linkObj) {
|
||||||
|
links[linkObj.rel] = {
|
||||||
|
'href': linkObj.href,
|
||||||
|
'method': linkObj.method
|
||||||
|
};
|
||||||
|
})
|
||||||
|
//if redirect url present, redirect user
|
||||||
|
if (links.hasOwnProperty('approval_url')) {
|
||||||
|
resolve(
|
||||||
|
{
|
||||||
|
id: payment.id,
|
||||||
|
link: links['approval_url'].href,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
console.error('no redirect URI present');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).then(res => res)
|
||||||
|
return newOrder
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new PayPalProviders();
|
|
@ -0,0 +1,39 @@
|
||||||
|
const Redsys = require('redsys-easy');
|
||||||
|
const {
|
||||||
|
SANDBOX_URLS,
|
||||||
|
PRODUCTION_URLS,
|
||||||
|
TRANSACTION_TYPES
|
||||||
|
} = Redsys
|
||||||
|
|
||||||
|
class RedsysProviders {
|
||||||
|
async New(orderFk, price) {
|
||||||
|
console.log("Chama");
|
||||||
|
try {
|
||||||
|
const redsys = new Redsys({
|
||||||
|
secretKey: 'sq7HjrUOBfKmC576ILgskD5srU870gJ7',
|
||||||
|
urls: SANDBOX_URLS, // Also PRODUCTION_URLS
|
||||||
|
});
|
||||||
|
const obj = {
|
||||||
|
amount: price,
|
||||||
|
currency: 'EUR',
|
||||||
|
order: orderFk,
|
||||||
|
merchantName: 'Floraner',
|
||||||
|
merchantCode: '999008881',
|
||||||
|
transactionType: TRANSACTION_TYPES.AUTHORIZATION, // '0'
|
||||||
|
terminal: '001',
|
||||||
|
merchantURL: `${process.env.BASE_URL}/payments/redsys/notification`,
|
||||||
|
successURL: `${process.env.BASE_URL}/checkout/success?orderId=${orderFk}`,
|
||||||
|
errorURL: `${process.env.BASE_URL}/checkout/error`
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = redsys.redirectPetition(obj)
|
||||||
|
|
||||||
|
console.log(form);
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new RedsysProviders();
|
|
@ -0,0 +1,34 @@
|
||||||
|
const RedsysPos = require('redsys-pos');
|
||||||
|
const {
|
||||||
|
CURRENCIES, TRANSACTION_TYPES
|
||||||
|
} = RedsysPos;
|
||||||
|
|
||||||
|
class RedsysProviders {
|
||||||
|
async New(orderFk, price) {
|
||||||
|
console.log("Chama");
|
||||||
|
try {
|
||||||
|
const MERCHANT_KEY = "sq7HjrUOBfKmC576ILgskD5srU870gJ7";
|
||||||
|
const redsys = new RedsysPos(MERCHANT_KEY);
|
||||||
|
const obj = JSON.stringify({
|
||||||
|
amount: 100, // 100 euros
|
||||||
|
orderReference: orderFk,
|
||||||
|
merchantName: "Floranet",
|
||||||
|
merchantCode: "999008881",
|
||||||
|
currency: CURRENCIES.EUR,
|
||||||
|
transactionType: TRANSACTION_TYPES.AUTHORIZATION, // '0'
|
||||||
|
terminal: "001",
|
||||||
|
merchantURL: `${process.env.BASE_URL}/payments/redsys/notification`,
|
||||||
|
successURL: `${process.env.BASE_URL}/checkout/success?orderId=${orderFk}`,
|
||||||
|
errorURL: `${process.env.BASE_URL}/checkout/error`
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(obj);
|
||||||
|
const result = redsys.makePaymentParameters(obj);
|
||||||
|
return ""
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new RedsysProviders();
|
|
@ -0,0 +1,33 @@
|
||||||
|
const RedsysPos = require('redsys-pos');
|
||||||
|
const {
|
||||||
|
CURRENCIES, TRANSACTION_TYPES
|
||||||
|
} = RedsysPos;
|
||||||
|
|
||||||
|
class RedsysProviders {
|
||||||
|
async New(orderFk, price) {
|
||||||
|
try {
|
||||||
|
const MERCHANT_KEY = "sq7HjrUOBfKmC576ILgskD5srU870gJ7";
|
||||||
|
const redsys = new RedsysPos(MERCHANT_KEY);
|
||||||
|
const obj = {
|
||||||
|
amount: String(price),
|
||||||
|
orderReference: String(orderFk),
|
||||||
|
merchantName: "Floranet",
|
||||||
|
merchantCode: "999008881",
|
||||||
|
currency: CURRENCIES.EUR,
|
||||||
|
transactionType: TRANSACTION_TYPES.AUTHORIZATION, // '0'
|
||||||
|
terminal: "001",
|
||||||
|
merchantURL: `${process.env.BASE_URL}/payments/redsys/notification`,
|
||||||
|
successURL: `${process.env.BASE_URL}/checkout/success?orderId=${orderFk}`,
|
||||||
|
errorURL: `${process.env.BASE_URL}/checkout/error`
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = redsys.makePaymentParameters(obj);
|
||||||
|
console.log(result);
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new RedsysProviders();
|
|
@ -1,34 +1,18 @@
|
||||||
const db = require("../db/db");
|
const db = require("../../db/db");
|
||||||
|
|
||||||
const productsJson = require("./products.json")
|
|
||||||
|
|
||||||
class ProductController {
|
class ProductController {
|
||||||
async findAll(req, res) {
|
async findAll(req, res) {
|
||||||
|
|
||||||
const params = req.query;
|
const params = req.query;
|
||||||
const _products = await db.getProducts(params.dateExpired, params.postalCode);
|
const _products = await db.getProducts(params.dateExpired, params.postalCode);
|
||||||
let productsFilter = _products[0];
|
let productsFilter = _products[0]
|
||||||
|
|
||||||
if (Number(params.recommend)) {
|
if (Number(params.recommend)) {
|
||||||
productsFilter = productsFilter.filter(item => item.recommend == Number(params.recommend))
|
productsFilter = productsFilter.filter(item => item.recommend == params.recommend)
|
||||||
}
|
}
|
||||||
if (params.type) {
|
if (params.type) {
|
||||||
productsFilter = productsFilter.filter(item => item.type === params.type)
|
productsFilter = productsFilter.filter(item => item.type === params.type)
|
||||||
}
|
}
|
||||||
/*if (params.postalCode) {
|
|
||||||
productsFilter = productsFilter.filter(item => item.postalCode === params.postalCode)
|
|
||||||
}
|
|
||||||
if (params.dateExpired) {
|
|
||||||
const dateSearch = new Date(params.dateExpired);
|
|
||||||
productsFilter = productsFilter.filter(item => {
|
|
||||||
const dateProduct = new Date(item.dateExpired);
|
|
||||||
if (dateProduct >= dateSearch) {
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}*/
|
|
||||||
console.log(productsFilter.length);
|
|
||||||
|
|
||||||
|
|
||||||
if (params.minPrice && !params.maxPrice) {
|
if (params.minPrice && !params.maxPrice) {
|
||||||
productsFilter = productsFilter.filter(item => {
|
productsFilter = productsFilter.filter(item => {
|
||||||
|
@ -105,6 +89,7 @@ class ProductController {
|
||||||
products: products
|
products: products
|
||||||
}) */
|
}) */
|
||||||
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
data: productsFilter
|
data: productsFilter
|
||||||
})
|
})
|
|
@ -0,0 +1,23 @@
|
||||||
|
const db = require("../../db/db");
|
||||||
|
|
||||||
|
class ProvincesController {
|
||||||
|
async findAll(req, res) {
|
||||||
|
const params = req.query;
|
||||||
|
const tmpProvinces = await db.getProvinces();
|
||||||
|
|
||||||
|
let provinces = [];
|
||||||
|
|
||||||
|
tmpProvinces.forEach(element => {
|
||||||
|
provinces = [...provinces,{
|
||||||
|
code: element.id,
|
||||||
|
name: element.name
|
||||||
|
}];
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
data: provinces
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new ProvincesController();
|
File diff suppressed because it is too large
Load Diff
54
api/db/db.js
54
api/db/db.js
|
@ -12,17 +12,65 @@ async function connect() {
|
||||||
|
|
||||||
const mysql = require("mysql2/promise");
|
const mysql = require("mysql2/promise");
|
||||||
const connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + "");
|
const connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + "");
|
||||||
console.log("Connected to MySQL!");
|
|
||||||
global.connection = connection;
|
global.connection = connection;
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Procedure for get products
|
||||||
async function getProducts(dateExpired, postalCode) {
|
async function getProducts(dateExpired, postalCode) {
|
||||||
console.log("Query in table MySQL!");
|
|
||||||
const conn = await connect();
|
const conn = await connect();
|
||||||
const [rows] = await conn.query(`CALL catalogue_get("${dateExpired}", "${postalCode}")`);
|
const [rows] = await conn.query(`CALL catalogue_get("${dateExpired}", "${postalCode}")`);
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Procedure for create transactions, do not carry out any manipulation at the bank
|
||||||
|
async function orderData_put(jsonOrderData) {
|
||||||
|
const conn = await connect();
|
||||||
|
const [rows] = await conn.query(`CALL orderData_put(?)`, [jsonOrderData], (err, results) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
console.log('Result:', results);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
async function order_confirm(orderFk) {
|
||||||
|
const conn = await connect();
|
||||||
|
const [rows] = await conn.query(`CALL order_confirm(${orderFk})`);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = { getProducts }
|
|
||||||
|
//Procedure for get transactions, do not carry out any manipulation at the bank
|
||||||
|
async function orderData_get() {
|
||||||
|
const conn = await connect();
|
||||||
|
const [rows] = await conn.query(`CALL orderData_get()`);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getProvinces() {
|
||||||
|
const conn = await connect();
|
||||||
|
const [rows] = await conn.query(`SELECT p.id, p.name, c.code, c.country
|
||||||
|
FROM vn.province p
|
||||||
|
JOIN vn.country c ON c.id = p.countryFk
|
||||||
|
WHERE c.country IN('España', 'Francia', 'Portugal')`);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function deliveryDate_get(postalCode) {
|
||||||
|
const conn = await connect();
|
||||||
|
const [rows] = await conn.query(`CALL deliveryDate_get("${postalCode}")`);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function contact_Request(name, phone, email, message) {
|
||||||
|
const conn = await connect();
|
||||||
|
const [rows] = await conn.query(`CALL contact_Request("${name}", "${phone}", "${email}", "${message}")`);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { getProducts, orderData_get, orderData_put, getProvinces, deliveryDate_get, contact_Request, order_confirm }
|
31
api/index.js
31
api/index.js
|
@ -1,18 +1,37 @@
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const productController = require('./controller/product.controller');
|
const paypal = require('paypal-rest-sdk');
|
||||||
|
const productController = require('./controller/Product/product.controller');
|
||||||
|
const paymengtController = require('./controller/Payment/payment.controller');
|
||||||
|
const provincesController = require('./controller/Provinces/provinces.controller');
|
||||||
|
const deliveryController = require('./controller/Delivery/delivery.controller');
|
||||||
|
const contactController = require('./controller/Contact/contact.controller');
|
||||||
|
|
||||||
|
paypal.configure({
|
||||||
|
'mode': 'sandbox',
|
||||||
|
'client_id': process.env.CLIENT_ID,
|
||||||
|
'client_secret': process.env.SECRET_KEY
|
||||||
|
});
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 9999;
|
const port = 9999;
|
||||||
|
|
||||||
const allowedOrigins = ['http://localhost:9100', 'https://floranet.onecommerce.dev/'];
|
const allowedOrigins = ['http://localhost:9100', 'https://floranet.onecommerce.dev','http://49.13.85.117','http://floranet.onecommerce.dev'];
|
||||||
const corsOptions = {
|
const corsOptions = {
|
||||||
origin: allowedOrigins,
|
origin: allowedOrigins,
|
||||||
optionsSuccessStatus: 200,
|
optionsSuccessStatus: 200,
|
||||||
};
|
};
|
||||||
|
|
||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(
|
||||||
|
express.urlencoded({
|
||||||
|
extended: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
const indexPath = path.join(__dirname, './', 'index.html');
|
const indexPath = path.join(__dirname, './', 'index.html');
|
||||||
res.sendFile(indexPath);
|
res.sendFile(indexPath);
|
||||||
|
@ -21,6 +40,14 @@ app.get('/', (req, res) => {
|
||||||
//Products
|
//Products
|
||||||
app.get('/api/products', productController.findAll);
|
app.get('/api/products', productController.findAll);
|
||||||
app.get('/api/products/:id', productController.findById);
|
app.get('/api/products/:id', productController.findById);
|
||||||
|
app.post('/api/payment/', paymengtController.Create)
|
||||||
|
app.post('/api/payment/success', paymengtController.Success)
|
||||||
|
app.get('/api/payment/cancel', paymengtController.Cancel)
|
||||||
|
app.get('/api/provinces', provincesController.findAll)
|
||||||
|
app.get('/api/delivery/dates', deliveryController.findByPostalCode)
|
||||||
|
app.post('/api/contact/save', contactController.Create)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server listening at http://localhost:${port}`);
|
console.log(`Server listening at http://localhost:${port}`);
|
||||||
|
|
|
@ -8,8 +8,5 @@
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC"
|
||||||
"dependencies": {
|
|
||||||
"express": "^4.18.2"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,10 @@
|
||||||
<link rel="icon" type="image/ico" href="icons/floranet-favicon.jpg" />
|
<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.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://unpkg.com/vue-tel-input/dist/vue-tel-input.css"
|
||||||
|
/>
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Mulish:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;0,1000;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900;1,1000&family=Questrial&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Mulish:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;0,1000;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900;1,1000&family=Questrial&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
@ -2,7 +2,7 @@
|
||||||
"name": "floranet",
|
"name": "floranet",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "A floranet app",
|
"description": "A floranet app",
|
||||||
"productName": "Floranet App",
|
"productName": "Floranet",
|
||||||
"author": "user",
|
"author": "user",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -16,20 +16,29 @@
|
||||||
"backend": "json-server -p 3000 -d 600 -w src/services/json-server/db.json --routes src/services/json-server/routes.json"
|
"backend": "json-server -p 3000 -d 600 -w src/services/json-server/db.json --routes src/services/json-server/routes.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@floating-ui/vue": "^1.0.6",
|
||||||
"@quasar/extras": "^1.16.4",
|
"@quasar/extras": "^1.16.4",
|
||||||
"@vee-validate/zod": "^4.12.2",
|
"@vee-validate/zod": "^4.12.2",
|
||||||
"@vue-stripe/vue-stripe": "^4.5.0",
|
"@vue-stripe/vue-stripe": "^4.5.0",
|
||||||
"@vueuse/core": "^10.7.0",
|
"@vueuse/core": "^10.7.0",
|
||||||
"axios": "^1.2.1",
|
"axios": "^1.2.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"fs": "^0.0.1-security",
|
||||||
"mysql2": "^3.7.0",
|
"mysql2": "^3.7.0",
|
||||||
|
"node-redsys-api": "^0.0.5",
|
||||||
|
"paypal-rest-sdk": "^1.8.1",
|
||||||
"pinia": "^2.0.11",
|
"pinia": "^2.0.11",
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
|
"redsys-easy": "^5.2.3",
|
||||||
|
"redsys-pay": "^1.2.0",
|
||||||
|
"redsys-pos": "^1.0.2",
|
||||||
"vee-validate": "^4.12.2",
|
"vee-validate": "^4.12.2",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
|
"vue-country-flag-next": "^2.3.2",
|
||||||
"vue-i18n": "^9.0.0",
|
"vue-i18n": "^9.0.0",
|
||||||
"vue-image-zoomer": "^2.2.3",
|
"vue-image-zoomer": "^2.2.3",
|
||||||
"vue-router": "^4.0.0",
|
"vue-router": "^4.0.0",
|
||||||
|
"vue-tel-input": "^8.3.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
const { configure } = require("quasar/wrappers");
|
const { configure } = require("quasar/wrappers");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = configure(function (/* ctx */) {
|
module.exports = configure(function (ctx) {
|
||||||
return {
|
return {
|
||||||
eslint: {
|
eslint: {
|
||||||
// fix: true,
|
// fix: true,
|
||||||
|
@ -28,7 +28,15 @@ module.exports = configure(function (/* ctx */) {
|
||||||
// app boot file (/src/boot)
|
// app boot file (/src/boot)
|
||||||
// --> boot files are part of "main.js"
|
// --> boot files are part of "main.js"
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||||
boot: ["i18n", "axios" /* , { path: "stripe", server: false } */],
|
boot: [
|
||||||
|
"i18n",
|
||||||
|
"axios",
|
||||||
|
{
|
||||||
|
path: "VueCountryFlag",
|
||||||
|
server: false,
|
||||||
|
},
|
||||||
|
{ path: "VueTelInput", server: false },
|
||||||
|
],
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||||
css: ["app.scss"],
|
css: ["app.scss"],
|
||||||
|
@ -49,6 +57,12 @@ module.exports = configure(function (/* ctx */) {
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
||||||
build: {
|
build: {
|
||||||
|
env: {
|
||||||
|
API: ctx.dev
|
||||||
|
? "http://localhost:9999/api/"
|
||||||
|
: "https://floranet-back.onecommerce.dev/api/",
|
||||||
|
},
|
||||||
|
|
||||||
target: {
|
target: {
|
||||||
browser: ["es2019", "edge88", "firefox78", "chrome87", "safari13.1"],
|
browser: ["es2019", "edge88", "firefox78", "chrome87", "safari13.1"],
|
||||||
node: "node16",
|
node: "node16",
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { boot } from "quasar/wrappers";
|
||||||
|
import CountryFlag from "vue-country-flag-next";
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
app.use(CountryFlag);
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { boot } from "quasar/wrappers";
|
||||||
|
import VueTelInput from "vue-tel-input";
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
const options = {
|
||||||
|
mode: "auto",
|
||||||
|
dropdownOptions: {
|
||||||
|
showDialCodeInSelection: true,
|
||||||
|
showFlags: true,
|
||||||
|
showDialCodeInList: true,
|
||||||
|
},
|
||||||
|
autoFormat: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use(VueTelInput, options);
|
||||||
|
});
|
|
@ -7,8 +7,8 @@ import { boot } from "quasar/wrappers";
|
||||||
// good idea to move this instance creation inside of the
|
// good idea to move this instance creation inside of the
|
||||||
// "export default () => {}" function below (which runs individually
|
// "export default () => {}" function below (which runs individually
|
||||||
// for each client)
|
// for each client)
|
||||||
const api = axios.create({ baseURL: "http://localhost:3000/jsonServer/" });
|
|
||||||
const apiBack = axios.create({ baseURL: "http://localhost:9999/api/" });
|
const apiBack = axios.create({ baseURL: process.env.API });
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||||
|
@ -17,10 +17,9 @@ export default boot(({ app }) => {
|
||||||
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
||||||
// so you won't necessarily have to import axios in each vue file
|
// so you won't necessarily have to import axios in each vue file
|
||||||
|
|
||||||
app.config.globalProperties.$api = api;
|
|
||||||
app.config.globalProperties.$apiBack = apiBack;
|
app.config.globalProperties.$apiBack = apiBack;
|
||||||
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
|
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
|
||||||
// so you can easily perform requests against your app's API
|
// so you can easily perform requests against your app's API
|
||||||
});
|
});
|
||||||
|
|
||||||
export { api, apiBack };
|
export { apiBack };
|
||||||
|
|
|
@ -1,45 +1,65 @@
|
||||||
<script>
|
<script>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
|
import { defineComponent, onBeforeMount, ref, watch } from "vue";
|
||||||
|
|
||||||
import { fullCurrentDate } from "src/constants/date";
|
import { fullCurrentDate } from "src/constants/date";
|
||||||
|
import { invertDate } from "src/functions/invertDate";
|
||||||
import { useFormStore } from "src/stores/forms";
|
import { useFormStore } from "src/stores/forms";
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
import IconCalendar from "../icons/IconCalendar.vue";
|
import IconCalendar from "../icons/IconCalendar.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "calendar-input",
|
name: "calendar-input",
|
||||||
components: { IconCalendar },
|
components: { IconCalendar },
|
||||||
inheritAttrs: true,
|
props: ["modelValue", "bindValue", "setValues"],
|
||||||
props: {
|
setup({ setValues }, { emit }) {
|
||||||
setValues: {
|
|
||||||
type: Function,
|
|
||||||
default: () => {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup({ setValues }) {
|
|
||||||
const formStore = useFormStore();
|
const formStore = useFormStore();
|
||||||
const { availability } = storeToRefs(formStore);
|
const { availability, postalCodeValid } = storeToRefs(formStore);
|
||||||
|
|
||||||
const proxyDate = ref(fullCurrentDate);
|
const [year, month, day] = fullCurrentDate.replaceAll("/", "-").split("-");
|
||||||
|
const currentDate = `${day}-${month}-${year}`;
|
||||||
|
const proxyDate = ref(invertDate(currentDate));
|
||||||
|
|
||||||
function updateProxy() {
|
function updateProxy() {
|
||||||
proxyDate.value = fullCurrentDate;
|
proxyDate.value = invertDate(currentDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function optionsValidDates(date) {
|
function optionsValidDates(date) {
|
||||||
return date >= fullCurrentDate;
|
return date >= fullCurrentDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function updateModel(value) {
|
||||||
availability.value.date = proxyDate.value;
|
emit("update:modelValue", value);
|
||||||
setValues({ date: proxyDate.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
setValues({ date: invertDate(proxyDate.value) });
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(proxyDate, (newProxy) => {
|
||||||
|
setValues({ date: invertDate(newProxy) });
|
||||||
|
});
|
||||||
|
|
||||||
|
const LOCALE = {
|
||||||
|
days: "Domingo_Lunes_Martes_Miércoles_Jueves_Viernes_Sábado".split("_"),
|
||||||
|
daysShort: "Dom_Lun_Mar_Mié_Jue_Vie_Sáb".split("_"),
|
||||||
|
months:
|
||||||
|
"Enero_Febrero_Marzo_Abril_Mayo_Junio_Julio_Agosto_Septiembre_Octubre_Noviembre_Diciembre".split(
|
||||||
|
"_"
|
||||||
|
),
|
||||||
|
monthsShort: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Sep_Oct_Nov_Dic".split("_"),
|
||||||
|
firstDayOfWeek: 1,
|
||||||
|
format24h: false,
|
||||||
|
pluralDay: "dias",
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
availability,
|
availability,
|
||||||
|
postalCodeValid,
|
||||||
proxyDate,
|
proxyDate,
|
||||||
|
LOCALE,
|
||||||
updateProxy,
|
updateProxy,
|
||||||
optionsValidDates,
|
optionsValidDates,
|
||||||
save,
|
updateModel,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -58,19 +78,14 @@ export default defineComponent({
|
||||||
>
|
>
|
||||||
<q-date
|
<q-date
|
||||||
v-model="proxyDate"
|
v-model="proxyDate"
|
||||||
v-bind="calendarAttrs"
|
:options="postalCodeValid.dataOptions"
|
||||||
:options="optionsValidDates"
|
:locale="LOCALE"
|
||||||
mask="DD-MM-YYYY"
|
:readonly="!postalCodeValid.isValid"
|
||||||
|
mask="YYYY-MM-DD"
|
||||||
>
|
>
|
||||||
<div class="row items-center justify-end q-gutter-sm">
|
<div class="row items-center justify-end q-gutter-sm">
|
||||||
<q-btn label="Cancel" color="primary" flat v-close-popup />
|
<q-btn label="Cancel" color="primary" flat v-close-popup />
|
||||||
<q-btn
|
<q-btn label="OK" color="primary" flat v-close-popup />
|
||||||
label="OK"
|
|
||||||
color="primary"
|
|
||||||
flat
|
|
||||||
@click="save"
|
|
||||||
v-close-popup
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</q-date>
|
</q-date>
|
||||||
</q-popup-proxy>
|
</q-popup-proxy>
|
||||||
|
@ -79,7 +94,18 @@ export default defineComponent({
|
||||||
<div class="custom-block-content">
|
<div class="custom-block-content">
|
||||||
<p class="custom-head-paragraph">¿Cuándo?</p>
|
<p class="custom-head-paragraph">¿Cuándo?</p>
|
||||||
|
|
||||||
<slot></slot>
|
<q-input
|
||||||
|
:model-value="modelValue"
|
||||||
|
@update:model-value="updateModel"
|
||||||
|
v-bind="bindValue"
|
||||||
|
class="custom-date-input"
|
||||||
|
:error="false"
|
||||||
|
placeholder="Elige una fecha"
|
||||||
|
mask="##/##/####"
|
||||||
|
borderless
|
||||||
|
dense
|
||||||
|
:disable="!postalCodeValid.isValid"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
<script>
|
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
|
||||||
import { Field, useForm } from "vee-validate";
|
|
||||||
import { defineComponent, watch } from "vue";
|
|
||||||
|
|
||||||
import { quasarNotify } from "src/functions/quasarNotify";
|
|
||||||
import { useFormStore } from "src/stores/forms";
|
|
||||||
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema";
|
|
||||||
import IconPostalCode from "../icons/IconPostalCode.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "PostalCodeEx",
|
|
||||||
components: { IconPostalCode, Field },
|
|
||||||
setup() {
|
|
||||||
const formStore = useFormStore();
|
|
||||||
const validationSchema = toTypedSchema(
|
|
||||||
availabilitySchema.pick({ date: true })
|
|
||||||
);
|
|
||||||
const { errors, values, handleSubmit } = useForm({
|
|
||||||
validationSchema,
|
|
||||||
initialValues: {
|
|
||||||
date: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(values, (newValues) => {
|
|
||||||
formStore.$patch({
|
|
||||||
availability: {
|
|
||||||
postalCode: newValues.date,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(errors, (newErrors) => {
|
|
||||||
if (newErrors.date) {
|
|
||||||
quasarNotify({ message: newErrors.date, type: "erro" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = handleSubmit(formStore.registerAvailability);
|
|
||||||
|
|
||||||
return {
|
|
||||||
postalCode,
|
|
||||||
postalCodeAttrs,
|
|
||||||
errors,
|
|
||||||
onBlur,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="custom-input-el postal-code">
|
|
||||||
<IconPostalCode />
|
|
||||||
|
|
||||||
<div class="custom-block-content">
|
|
||||||
<p class="custom-head-paragraph">¿Dónde?</p>
|
|
||||||
<!-- <p class="custom-main-paragraph">código postal</p> -->
|
|
||||||
<Field />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,62 +0,0 @@
|
||||||
<script>
|
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
|
||||||
import { Field, useForm } from "vee-validate";
|
|
||||||
import { defineComponent, watch } from "vue";
|
|
||||||
|
|
||||||
import { quasarNotify } from "src/functions/quasarNotify";
|
|
||||||
import { useFormStore } from "src/stores/forms";
|
|
||||||
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema";
|
|
||||||
import IconPostalCode from "../icons/IconPostalCode.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "PostalCodeEx",
|
|
||||||
components: { IconPostalCode, Field },
|
|
||||||
setup() {
|
|
||||||
const formStore = useFormStore();
|
|
||||||
const validationSchema = toTypedSchema(
|
|
||||||
availabilitySchema.pick({ postalCode: true })
|
|
||||||
);
|
|
||||||
const { errors, values, handleSubmit } = useForm({
|
|
||||||
validationSchema,
|
|
||||||
initialValues: {
|
|
||||||
postalCode: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(values, (newValues) => {
|
|
||||||
formStore.$patch({
|
|
||||||
availability: {
|
|
||||||
postalCode: newValues.postalCode,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(errors, (newErrors) => {
|
|
||||||
if (newErrors.postalCode) {
|
|
||||||
quasarNotify({ message: newErrors.postalCode, type: "erro" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = handleSubmit(formStore.registerAvailability);
|
|
||||||
|
|
||||||
return {
|
|
||||||
postalCode,
|
|
||||||
postalCodeAttrs,
|
|
||||||
errors,
|
|
||||||
onBlur,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="custom-input-el postal-code">
|
|
||||||
<IconPostalCode />
|
|
||||||
|
|
||||||
<div class="custom-block-content">
|
|
||||||
<p class="custom-head-paragraph">¿Dónde?</p>
|
|
||||||
<!-- <p class="custom-main-paragraph">código postal</p> -->
|
|
||||||
<Field />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,47 +1,75 @@
|
||||||
<script>
|
<script>
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { useForm } from "vee-validate";
|
import { defineComponent, ref } from "vue";
|
||||||
import { defineComponent, watch } from "vue";
|
|
||||||
|
|
||||||
|
import { apiBack } from "src/boot/axios";
|
||||||
import { quasarNotify } from "src/functions/quasarNotify";
|
import { quasarNotify } from "src/functions/quasarNotify";
|
||||||
import { useFormStore } from "src/stores/forms";
|
import { useFormStore } from "src/stores/forms";
|
||||||
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema";
|
import * as M from "src/utils/zod/messages";
|
||||||
import IconPostalCode from "../icons/IconPostalCode.vue";
|
import IconPostalCode from "../icons/IconPostalCode.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "postal-code",
|
name: "postal-code",
|
||||||
components: { IconPostalCode },
|
components: { IconPostalCode /* IconInfo, */ /* IconSearch */ },
|
||||||
setup() {
|
props: ["modelValue", "bindValue", "setFieldError"],
|
||||||
|
setup({ setFieldError, modelValue }, { emit }) {
|
||||||
const formStore = useFormStore();
|
const formStore = useFormStore();
|
||||||
const { availability } = storeToRefs(formStore);
|
const { postalCodeValid } = storeToRefs(formStore);
|
||||||
const validationSchema = toTypedSchema(
|
|
||||||
availabilitySchema.pick({ postalCode: true }).partial()
|
|
||||||
);
|
|
||||||
const { errors, defineField, values } = useForm({
|
|
||||||
validationSchema,
|
|
||||||
initialValues: {
|
|
||||||
postalCode: availability.value.postalCode,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
|
||||||
const onBlur = () => {
|
|
||||||
availability.value.postalCode = postalCode.value;
|
|
||||||
};
|
|
||||||
availability.value.postalCode = values.postalCode;
|
|
||||||
|
|
||||||
watch(errors, (newErrors) => {
|
const postalCodeInput = ref(modelValue);
|
||||||
if (newErrors.postalCode) {
|
|
||||||
quasarNotify({ message: newErrors.postalCode, type: "erro" });
|
function updateModel(value) {
|
||||||
|
emit("update:modelValue", value);
|
||||||
|
postalCodeInput.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPostalCodeLoading = ref(false);
|
||||||
|
async function onBlur() {
|
||||||
|
try {
|
||||||
|
if (postalCodeInput.value.length < 5) {
|
||||||
|
quasarNotify({
|
||||||
|
type: "info",
|
||||||
|
message: `${M.fiveLength}, Cantidad de caracteres: ${postalCodeInput.value.length}`,
|
||||||
|
});
|
||||||
|
setFieldError("postalCode", M.fiveLength);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPostalCodeLoading.value = true;
|
||||||
|
|
||||||
|
//TODO - Promesa consultando la api para ver las fechas existentes
|
||||||
|
const {
|
||||||
|
data: { data },
|
||||||
|
} = await apiBack.get(`/delivery/dates`, {
|
||||||
|
params: { postalCode: postalCodeInput.value },
|
||||||
|
});
|
||||||
|
const dates = data.map(({ dated }) => {
|
||||||
|
const getDate = new Date(dated);
|
||||||
|
const day = getDate.getDate().toString().padStart(2, "0");
|
||||||
|
const month = (getDate.getMonth() + 1).toString().padStart(2, "0");
|
||||||
|
const year = getDate.getFullYear();
|
||||||
|
const formattedDate = `${year}/${month}/${day}`;
|
||||||
|
|
||||||
|
return formattedDate;
|
||||||
|
});
|
||||||
|
|
||||||
|
postalCodeValid.value.dataOptions = dates;
|
||||||
|
postalCodeValid.value.isValid = true;
|
||||||
|
isPostalCodeLoading.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
quasarNotify({
|
||||||
|
type: "erro",
|
||||||
|
message:
|
||||||
|
"Se ha producido un error en el proceso de identificación del código postal",
|
||||||
|
});
|
||||||
|
isPostalCodeLoading.value = false;
|
||||||
|
console.error(`FATAL ERROR ::: ${error}`);
|
||||||
|
} finally {
|
||||||
|
// console.log("click");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return {
|
return { updateModel, onBlur, isPostalCodeLoading };
|
||||||
postalCode,
|
|
||||||
postalCodeAttrs,
|
|
||||||
errors,
|
|
||||||
onBlur,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -51,20 +79,35 @@ export default defineComponent({
|
||||||
<IconPostalCode />
|
<IconPostalCode />
|
||||||
|
|
||||||
<div class="custom-block-content">
|
<div class="custom-block-content">
|
||||||
<p class="custom-head-paragraph">¿Dónde?</p>
|
<p class="custom-head-paragraph">
|
||||||
|
¿Dónde?
|
||||||
|
<!-- <IconInfo /> -->
|
||||||
|
</p>
|
||||||
|
|
||||||
<slot></slot>
|
<q-input
|
||||||
<!-- <q-input
|
:model-value="modelValue"
|
||||||
borderless
|
@update:model-value="updateModel"
|
||||||
|
v-bind="bindValue"
|
||||||
class="custom-main-paragraph"
|
class="custom-main-paragraph"
|
||||||
v-model="postalCode"
|
:error="false"
|
||||||
v-bind="postalCodeAttrs"
|
|
||||||
:error="!!errors.postalCode"
|
|
||||||
placeholder="código postal"
|
placeholder="código postal"
|
||||||
mask="#####"
|
mask="#####"
|
||||||
@blur="onBlur"
|
borderless
|
||||||
dense
|
dense
|
||||||
/> -->
|
@blur="onBlur"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.custom-input-el .custom-block-content .search-btn {
|
||||||
|
padding: 8px;
|
||||||
|
& .q-btn__content {
|
||||||
|
& svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -11,16 +11,36 @@ export default defineComponent({
|
||||||
default: 0,
|
default: 0,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
minDefault: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
max: {
|
max: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 200,
|
default: 200,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
maxDefault: {
|
||||||
|
type: Number,
|
||||||
|
default: 200,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
bindValue: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props, { emit }) {
|
||||||
const rangePriceStore = useRangePriceStore();
|
const rangePriceStore = useRangePriceStore();
|
||||||
|
|
||||||
return { rangePriceStore };
|
const updateModel = (value) => {
|
||||||
|
emit("update:modelValue", value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { rangePriceStore, updateModel };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -29,7 +49,14 @@ export default defineComponent({
|
||||||
<div class="range-container">
|
<div class="range-container">
|
||||||
<p class="filter-item-paragraph">Precio</p>
|
<p class="filter-item-paragraph">Precio</p>
|
||||||
|
|
||||||
<slot></slot>
|
<q-range
|
||||||
|
:model-value="modelValue"
|
||||||
|
@update:model-value="updateModel"
|
||||||
|
v-bind="bindValue"
|
||||||
|
:min="minDefault"
|
||||||
|
:max="maxDefault"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="range-price-content">
|
<div class="range-price-content">
|
||||||
<p class="filter-item-paragraph min-price">
|
<p class="filter-item-paragraph min-price">
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default defineComponent({
|
||||||
const formStore = useFormStore();
|
const formStore = useFormStore();
|
||||||
const { sortProductFilters } = storeToRefs(formStore);
|
const { sortProductFilters } = storeToRefs(formStore);
|
||||||
|
|
||||||
async function handleOrder(order) {
|
function handleOrder(order) {
|
||||||
sortProductFilters.value.order = order;
|
sortProductFilters.value.order = order;
|
||||||
sortProductFilters.value.isOpenOrderFilter = false;
|
sortProductFilters.value.isOpenOrderFilter = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,7 @@ export default defineComponent({
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="footer-list-item">
|
<li class="footer-list-item">
|
||||||
<p class="footer-list-content">
|
<p class="footer-list-content"></p>
|
||||||
</p>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -44,7 +43,7 @@ export default defineComponent({
|
||||||
<RouterLink to="/categoria/plantas">Plantas</RouterLink><br />
|
<RouterLink to="/categoria/plantas">Plantas</RouterLink><br />
|
||||||
<!-- <RouterLink to="/">Nosotros</RouterLink><br />
|
<!-- <RouterLink to="/">Nosotros</RouterLink><br />
|
||||||
<RouterLink to="/faq">FAQs</RouterLink><br /> -->
|
<RouterLink to="/faq">FAQs</RouterLink><br /> -->
|
||||||
<RouterLink to="/contacta">Contacta</RouterLink>
|
<RouterLink to="/#question-section">Contacta</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="footer-list-item">
|
<li class="footer-list-item">
|
||||||
|
@ -57,7 +56,7 @@ export default defineComponent({
|
||||||
<IconArrowCircleRight /> Preguntas frecuentes
|
<IconArrowCircleRight /> Preguntas frecuentes
|
||||||
</RouterLink> -->
|
</RouterLink> -->
|
||||||
<br />
|
<br />
|
||||||
<RouterLink to="/example">
|
<RouterLink to="/#question-section">
|
||||||
<IconArrowCircleRight /> Contacta con nosotros
|
<IconArrowCircleRight /> Contacta con nosotros
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
|
@ -66,12 +65,10 @@ export default defineComponent({
|
||||||
<li class="footer-list-item">
|
<li class="footer-list-item">
|
||||||
<p class="footer-list-content">
|
<p class="footer-list-content">
|
||||||
Floranet ©{{ year }} <br /><br />
|
Floranet ©{{ year }} <br /><br />
|
||||||
<RouterLink to="/example">Aviso Legal</RouterLink> <br />
|
<RouterLink to="/">Aviso Legal</RouterLink> <br />
|
||||||
<RouterLink to="/example">Condiciones de uso</RouterLink><br />
|
<RouterLink to="/">Condiciones de uso</RouterLink><br />
|
||||||
<RouterLink to="/example">Política de cookies</RouterLink><br />
|
<RouterLink to="/">Política de cookies</RouterLink><br />
|
||||||
<RouterLink to="/example">
|
<RouterLink to="/"> Política de Redes Sociales </RouterLink>
|
||||||
Política de Redes Sociales
|
|
||||||
</RouterLink>
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
Desarrollado por diligent
|
Desarrollado por diligent
|
||||||
|
@ -141,6 +138,12 @@ a:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: calc($med-xlg + 100px)) {
|
||||||
|
&.footer-primary {
|
||||||
|
flex: 0 0 min(100%, 480px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $med-lg) {
|
@media only screen and (max-width: $med-lg) {
|
||||||
&.footer-primary {
|
&.footer-primary {
|
||||||
flex: 0 0 min(100%, 275px);
|
flex: 0 0 min(100%, 275px);
|
||||||
|
@ -150,6 +153,11 @@ a:hover {
|
||||||
flex: 0 0 min(100%, 545px);
|
flex: 0 0 min(100%, 545px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media only screen and (max-width: $med-md + 600px) {
|
||||||
|
&.footer-primary {
|
||||||
|
flex: 0 0 min(100%, 300px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $med-md) {
|
@media only screen and (max-width: $med-md) {
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
|
|
|
@ -29,9 +29,9 @@ export default defineComponent({
|
||||||
:class="isOpenNav && 'mobile-nav'"
|
:class="isOpenNav && 'mobile-nav'"
|
||||||
>
|
>
|
||||||
<send-banner
|
<send-banner
|
||||||
left-text="ENVÍO GRATIS a partir de 60€ | Compra el sábado hasta 14h y entrega el domingo"
|
left-text="ENVÍO GRATIS"
|
||||||
right-text="Envíos 24-48 h a toda España, Portugal y sur de Francia"
|
right-text="Envíos 24-48 h a toda España, Portugal y sur de Francia"
|
||||||
mobile-text="ENVÍO GRATIS a partir de 60€"
|
mobile-text="ENVÍO GRATIS"
|
||||||
v-if="!isOpenNav"
|
v-if="!isOpenNav"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,9 @@ export default defineComponent({
|
||||||
<template>
|
<template>
|
||||||
<q-header class="header-container transparent">
|
<q-header class="header-container transparent">
|
||||||
<send-banner
|
<send-banner
|
||||||
left-text="ENVÍO GRATIS a partir de 60€ | Compra el sábado hasta 14h y entrega el domingo"
|
left-text="ENVÍO GRATIS"
|
||||||
right-text="Envíos 24-48 h a toda España, Portugal y sur de Francia"
|
right-text="Envíos 24-48 h a toda España, Portugal y sur de Francia"
|
||||||
mobile-text="ENVÍO GRATIS a partir de 60€"
|
mobile-text="ENVÍO GRATIS"
|
||||||
class="remove-mobile"
|
class="remove-mobile"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from "vue";
|
import { computed, defineComponent } from "vue";
|
||||||
|
|
||||||
import IconCart from "components/icons/IconCart.vue";
|
import IconCart from "components/icons/IconCart.vue";
|
||||||
import IconHamburger from "components/icons/IconHamburger.vue";
|
import IconHamburger from "components/icons/IconHamburger.vue";
|
||||||
|
|
||||||
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useCartStore } from "src/stores/cart";
|
||||||
import { useMobileStore } from "stores/mobileNav";
|
import { useMobileStore } from "stores/mobileNav";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -14,14 +15,15 @@ export default defineComponent({
|
||||||
IconHamburger,
|
IconHamburger,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { getItem } = useLocalStorage();
|
const cartStore = useCartStore();
|
||||||
|
const { cart } = storeToRefs(cartStore);
|
||||||
|
|
||||||
const mobileStore = useMobileStore();
|
const mobileStore = useMobileStore();
|
||||||
const { handleOpenMobileNav } = mobileStore;
|
const { handleOpenMobileNav } = mobileStore;
|
||||||
|
|
||||||
const cartLength = getItem("cart").length;
|
const currentLength = computed(() => cart.value.length);
|
||||||
|
|
||||||
return { handleOpenMobileNav, cartLength };
|
return { handleOpenMobileNav, currentLength };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -38,11 +40,11 @@ export default defineComponent({
|
||||||
<RouterLink
|
<RouterLink
|
||||||
class="user-area-link cart"
|
class="user-area-link cart"
|
||||||
to="/checkout"
|
to="/checkout"
|
||||||
v-if="cartLength > 0"
|
v-if="currentLength > 0"
|
||||||
>
|
>
|
||||||
<icon-cart />
|
<icon-cart />
|
||||||
<span class="cart-count" :class="cartLength > 0 && 'active'">
|
<span class="cart-count" :class="currentLength > 0 && 'active'">
|
||||||
{{ cartLength }}
|
{{ currentLength }}
|
||||||
</span>
|
</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<script>
|
||||||
|
import { autoUpdate, useFloating } from "@floating-ui/vue";
|
||||||
|
import { defineComponent, ref } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "icon-info",
|
||||||
|
setup() {
|
||||||
|
const iconRef = ref(null);
|
||||||
|
const tooltipRef = ref(null);
|
||||||
|
|
||||||
|
const isHidden = ref(true);
|
||||||
|
const hideTooltip = () => {
|
||||||
|
isHidden.value = true;
|
||||||
|
};
|
||||||
|
const showTooltip = () => {
|
||||||
|
isHidden.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { floatingStyles } = useFloating(iconRef, tooltipRef, {
|
||||||
|
placement: "top-start",
|
||||||
|
whileElementsMounted: autoUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { floatingStyles, isHidden, hideTooltip, showTooltip };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="info-container">
|
||||||
|
<svg
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
width="48"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@mouseenter="showTooltip"
|
||||||
|
@mouseleave="hideTooltip"
|
||||||
|
>
|
||||||
|
<path d="M0 0h48v48h-48z" fill="transparent" />
|
||||||
|
<path
|
||||||
|
d="M22 34h4v-12h-4v12zm2-30c-11.05 0-20 8.95-20 20s8.95 20 20 20 20-8.95 20-20-8.95-20-20-20zm0 36c-8.82 0-16-7.18-16-16s7.18-16 16-16 16 7.18 16 16-7.18 16-16 16zm-2-22h4v-4h-4v4z"
|
||||||
|
fill="#117564"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://www.google.com/maps"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
:style="[floatingStyles, '--clr: #117564']"
|
||||||
|
:class="[isHidden && 'hidden', 'tooltip info', 'paragraph-sm', 'link']"
|
||||||
|
@mouseenter="showTooltip"
|
||||||
|
@mouseleave="hideTooltip"
|
||||||
|
>
|
||||||
|
Si no sabes tu código postal pincha aquí
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.info-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,13 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { useIntersectionObserver } from "@vueuse/core";
|
import { defineComponent } from "vue";
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { defineComponent, ref } from "vue";
|
|
||||||
|
|
||||||
import Calendar from "src/components/@inputs/Calendar.vue";
|
import Calendar from "src/components/@inputs/Calendar.vue";
|
||||||
import PostalCode from "src/components/@inputs/PostalCode.vue";
|
import PostalCode from "src/components/@inputs/PostalCode.vue";
|
||||||
import IconSearch from "src/components/icons/IconSearch.vue";
|
import IconSearch from "src/components/icons/IconSearch.vue";
|
||||||
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
||||||
import { useMobileStore } from "src/stores/mobileNav";
|
import { useVerticalCarouselImgs } from "src/hooks/useVerticalCarouselImgs";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "vertical-carousel-imgs",
|
name: "vertical-carousel-imgs",
|
||||||
|
@ -17,42 +15,32 @@ export default defineComponent({
|
||||||
default: () => [""],
|
default: () => [""],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: { IconSearch, Calendar, PostalCode },
|
||||||
setup() {
|
setup() {
|
||||||
const {
|
const {
|
||||||
onSubmit,
|
onSubmit,
|
||||||
setValues,
|
setValues,
|
||||||
|
setFieldError,
|
||||||
fields: { calendar, calendarAttrs, postalCode, postalCodeAttrs },
|
fields: { calendar, calendarAttrs, postalCode, postalCodeAttrs },
|
||||||
errors,
|
errors,
|
||||||
} = usePostalCalendar({ type: "home" });
|
} = usePostalCalendar({ type: "home" });
|
||||||
|
const { navPos, screenWidth, slide, target } = useVerticalCarouselImgs();
|
||||||
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 {
|
return {
|
||||||
slide: ref("style"),
|
slide,
|
||||||
navPos,
|
navPos,
|
||||||
target,
|
target,
|
||||||
screenWidth,
|
screenWidth,
|
||||||
errors,
|
errors,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
setValues,
|
setValues,
|
||||||
|
setFieldError,
|
||||||
postalCode,
|
postalCode,
|
||||||
postalCodeAttrs,
|
postalCodeAttrs,
|
||||||
calendar,
|
calendar,
|
||||||
calendarAttrs,
|
calendarAttrs,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: { IconSearch, Calendar, PostalCode },
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -86,48 +74,29 @@ export default defineComponent({
|
||||||
<h1 class="carousel-header-title">
|
<h1 class="carousel-header-title">
|
||||||
Regala un verano lleno de flores y plantas
|
Regala un verano lleno de flores y plantas
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="carousel-header-paragraph">
|
|
||||||
</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<form @submit="onSubmit" class="carousel-content-body">
|
<form @submit="onSubmit" class="carousel-content-body">
|
||||||
<div class="carousel-content-item">
|
<div class="carousel-content-item">
|
||||||
<Calendar :setValues="setValues">
|
<PostalCode
|
||||||
<q-input
|
v-model="postalCode"
|
||||||
borderless
|
v-bind:bindValue="postalCodeAttrs"
|
||||||
class="custom-date-input"
|
:setFieldError="setFieldError"
|
||||||
v-model="calendar"
|
/>
|
||||||
v-bind="calendarAttrs"
|
|
||||||
:error="!!errors.date"
|
|
||||||
placeholder="Elige una fecha"
|
|
||||||
mask="##/##/####"
|
|
||||||
dense
|
|
||||||
/>
|
|
||||||
</Calendar>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="carousel-content-item">
|
<div class="carousel-content-item">
|
||||||
<PostalCode>
|
<Calendar
|
||||||
<q-input
|
v-model="calendar"
|
||||||
borderless
|
v-bind:bindValue="calendarAttrs"
|
||||||
class="custom-main-paragraph"
|
:setValues="setValues"
|
||||||
v-model="postalCode"
|
/>
|
||||||
v-bind="postalCodeAttrs"
|
|
||||||
:error="!!errors.postalCode"
|
|
||||||
placeholder="código postal"
|
|
||||||
mask="#####"
|
|
||||||
dense
|
|
||||||
/>
|
|
||||||
</PostalCode>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-btn type="submit" class="btn carousel-content-item">
|
<q-btn type="submit" class="btn carousel-content-item">
|
||||||
<IconSearch /> ver disponibilidad
|
<IconSearch /> ver disponibilidad
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- <footer class="carousel-content-footer"></footer> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -174,6 +143,18 @@ export default defineComponent({
|
||||||
border-radius: 10px 0 0 10px;
|
border-radius: 10px 0 0 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 96px;
|
min-height: 96px;
|
||||||
|
|
||||||
|
/* & .carousel-content {
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex: 2;
|
||||||
|
background-color: $white;
|
||||||
|
} */
|
||||||
|
|
||||||
& .carousel-content-item {
|
& .carousel-content-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -23,9 +23,11 @@ export default defineComponent({
|
||||||
>
|
>
|
||||||
<Container class="question-container-form">
|
<Container class="question-container-form">
|
||||||
<header class="question-content">
|
<header class="question-content">
|
||||||
<LogoWhite />
|
<LogoWhite class="white-logo" />
|
||||||
|
|
||||||
<p class="question-paragraph">
|
<p class="question-paragraph">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||||
|
eiusmod tempor incididunt ut labore et dolore...
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -70,6 +72,16 @@ p {
|
||||||
|
|
||||||
& .question-content {
|
& .question-content {
|
||||||
flex: 1 0 min(100%, 540px);
|
flex: 1 0 min(100%, 540px);
|
||||||
|
|
||||||
|
& .white-logo {
|
||||||
|
width: min(100%, 335px);
|
||||||
|
height: 75px;
|
||||||
|
@media only screen and (max-width: $med-xmd) {
|
||||||
|
width: min(100%, 175px);
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& .question-paragraph {
|
& .question-paragraph {
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
font-size: $font-25;
|
font-size: $font-25;
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<StripeCheckout
|
|
||||||
ref="checkoutRef"
|
|
||||||
mode="payment"
|
|
||||||
:pk="pK"
|
|
||||||
:line-items="cartItems"
|
|
||||||
:success-url="successURL"
|
|
||||||
:cancel-url="cancelURL"
|
|
||||||
@loading="(v) => (loading = v)"
|
|
||||||
style="display: none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<slot></slot>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { defineComponent, ref, toRefs } from "vue";
|
|
||||||
|
|
||||||
import { useCartStore } from "src/stores/cart";
|
|
||||||
import { onUpdated } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "StripeCheckoutComponent",
|
|
||||||
components: {},
|
|
||||||
props: {
|
|
||||||
submitLoading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
onSubmit: {
|
|
||||||
type: Function,
|
|
||||||
default: () => {},
|
|
||||||
},
|
|
||||||
cartItems: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup({ submitLoading, cartItems }) {
|
|
||||||
const cartStore = useCartStore();
|
|
||||||
const { checkoutRef } = storeToRefs(cartStore);
|
|
||||||
|
|
||||||
const loading = toRefs(submitLoading);
|
|
||||||
const pK = ref(
|
|
||||||
"pk_test_51OZaJdIK1lTlG93d2y0B81n4XrjvjQwqfIUZ7ggb9wEBa1e4h34GlYFYPwjtGl3OUT7DJZlVNX9EMXaCdOBkIC3T007mLnfvCu"
|
|
||||||
);
|
|
||||||
|
|
||||||
onUpdated(() => {
|
|
||||||
console.log(checkoutRef.value);
|
|
||||||
console.log(cartItems);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
pK,
|
|
||||||
loading,
|
|
||||||
checkoutRef,
|
|
||||||
successURL: ref("/checkout/success"),
|
|
||||||
cancelURL: ref("/checkout/cancel"),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss"></style>
|
|
|
@ -20,8 +20,6 @@ export default defineComponent({
|
||||||
const nextSwiperBtn = ref(null);
|
const nextSwiperBtn = ref(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// console.log('Montado!');
|
|
||||||
|
|
||||||
swiperContainer.value =
|
swiperContainer.value =
|
||||||
document.querySelector("swiper-container").shadowRoot;
|
document.querySelector("swiper-container").shadowRoot;
|
||||||
prevSwiperBtn.value = swiperContainer.value.querySelector(
|
prevSwiperBtn.value = swiperContainer.value.querySelector(
|
||||||
|
@ -40,11 +38,9 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
screenWidth,
|
screenWidth,
|
||||||
handlePrev() {
|
handlePrev() {
|
||||||
// console.log('Prev click');
|
|
||||||
prevSwiperBtn.value.click();
|
prevSwiperBtn.value.click();
|
||||||
},
|
},
|
||||||
handleNext() {
|
handleNext() {
|
||||||
// console.log('Next click');
|
|
||||||
nextSwiperBtn.value.click();
|
nextSwiperBtn.value.click();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -33,10 +33,7 @@ export default defineComponent({
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
isNew: {
|
isNew: String,
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "md-card",
|
default: "md-card",
|
||||||
|
@ -54,10 +51,8 @@ export default defineComponent({
|
||||||
const isLoaded = ref(false);
|
const isLoaded = ref(false);
|
||||||
const isError = ref(false);
|
const isError = ref(false);
|
||||||
const percent = +discount / 100;
|
const percent = +discount / 100;
|
||||||
//const priceWithoutLetter = ~~price?.replaceAll("€", "");
|
|
||||||
const priceWithoutLetter = price;
|
const priceWithoutLetter = price;
|
||||||
const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent);
|
const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent);
|
||||||
console.log(price);
|
|
||||||
|
|
||||||
const onLoad = () => {
|
const onLoad = () => {
|
||||||
isLoaded.value = true;
|
isLoaded.value = true;
|
||||||
|
|
|
@ -6,9 +6,7 @@ export default defineComponent({
|
||||||
name: "chat-component",
|
name: "chat-component",
|
||||||
components: { IconChat },
|
components: { IconChat },
|
||||||
setup() {
|
setup() {
|
||||||
const handleClick = () => {
|
const handleClick = () => {};
|
||||||
console.log("click");
|
|
||||||
};
|
|
||||||
return { handleClick };
|
return { handleClick };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,6 @@ export default defineComponent({
|
||||||
|
|
||||||
function closeNav() {
|
function closeNav() {
|
||||||
isOpenNav.value = false;
|
isOpenNav.value = false;
|
||||||
console.log("foi click");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(isOpenNav, (newValue) => {
|
watch(isOpenNav, (newValue) => {
|
||||||
|
|
|
@ -31,6 +31,7 @@ export default defineComponent({
|
||||||
const {
|
const {
|
||||||
onSubmit,
|
onSubmit,
|
||||||
setValues,
|
setValues,
|
||||||
|
setFieldError,
|
||||||
fields: {
|
fields: {
|
||||||
calendar,
|
calendar,
|
||||||
calendarAttrs,
|
calendarAttrs,
|
||||||
|
@ -39,6 +40,8 @@ export default defineComponent({
|
||||||
priceRange,
|
priceRange,
|
||||||
priceRangeAttrs,
|
priceRangeAttrs,
|
||||||
},
|
},
|
||||||
|
isPostalCalendarEmpty,
|
||||||
|
isAvailabilityEmpty,
|
||||||
errors,
|
errors,
|
||||||
modalStore,
|
modalStore,
|
||||||
} = usePostalCalendar({
|
} = usePostalCalendar({
|
||||||
|
@ -63,6 +66,10 @@ export default defineComponent({
|
||||||
modalStore,
|
modalStore,
|
||||||
modalTextContent,
|
modalTextContent,
|
||||||
setValues,
|
setValues,
|
||||||
|
setFieldError,
|
||||||
|
|
||||||
|
isAvailabilityEmpty,
|
||||||
|
isPostalCalendarEmpty,
|
||||||
|
|
||||||
postalCode,
|
postalCode,
|
||||||
postalCodeAttrs,
|
postalCodeAttrs,
|
||||||
|
@ -106,16 +113,14 @@ export default defineComponent({
|
||||||
class="modal-body-filters"
|
class="modal-body-filters"
|
||||||
>
|
>
|
||||||
<div class="filter-field">
|
<div class="filter-field">
|
||||||
<PriceRange :min="priceRange.min" :max="priceRange.max">
|
<PriceRange
|
||||||
<q-range
|
:minDefault="0"
|
||||||
v-model="priceRange"
|
:maxDefault="200"
|
||||||
v-bind="priceRangeAttrs"
|
v-model="priceRange"
|
||||||
:min="0"
|
v-bind:bindValue="priceRangeAttrs"
|
||||||
:max="200"
|
:min="priceRange.min"
|
||||||
color="primary"
|
:max="priceRange.max"
|
||||||
/>
|
/>
|
||||||
<!-- @change="rangePriceStore.handlePriceRange" -->
|
|
||||||
</PriceRange>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -123,31 +128,17 @@ export default defineComponent({
|
||||||
v-if="modalItem === 'isOpenAvailability'"
|
v-if="modalItem === 'isOpenAvailability'"
|
||||||
class="modal-body-availability"
|
class="modal-body-availability"
|
||||||
>
|
>
|
||||||
<Calendar :setValues="setValues">
|
<PostalCode
|
||||||
<q-input
|
v-model="postalCode"
|
||||||
borderless
|
v-bind:bindValue="postalCodeAttrs"
|
||||||
class="custom-date-input"
|
:setFieldError="setFieldError"
|
||||||
v-model="calendar"
|
/>
|
||||||
v-bind="calendarAttrs"
|
|
||||||
:error="!!errors.date"
|
|
||||||
placeholder="Elige una fecha"
|
|
||||||
mask="##/##/####"
|
|
||||||
dense
|
|
||||||
/>
|
|
||||||
</Calendar>
|
|
||||||
|
|
||||||
<PostalCode>
|
<Calendar
|
||||||
<q-input
|
v-model="calendar"
|
||||||
borderless
|
v-bind:bindValue="calendarAttrs"
|
||||||
class="custom-main-paragraph"
|
:setValues="setValues"
|
||||||
v-model="postalCode"
|
/>
|
||||||
v-bind="postalCodeAttrs"
|
|
||||||
:error="!!errors.postalCode"
|
|
||||||
placeholder="código postal"
|
|
||||||
mask="#####"
|
|
||||||
dense
|
|
||||||
/>
|
|
||||||
</PostalCode>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -159,23 +150,11 @@ export default defineComponent({
|
||||||
align="center"
|
align="center"
|
||||||
class="modal-footer"
|
class="modal-footer"
|
||||||
form="filters-form"
|
form="filters-form"
|
||||||
|
:disabled="modalItem === 'isOpenFilters' && isPostalCalendarEmpty"
|
||||||
>
|
>
|
||||||
<!-- v-close-popup -->
|
|
||||||
<IconSearch />
|
<IconSearch />
|
||||||
<p>ver disponibilidad</p>
|
<p>ver disponibilidad</p>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<!-- <q-btn
|
|
||||||
flat
|
|
||||||
type="button"
|
|
||||||
align="center"
|
|
||||||
class="modal-footer"
|
|
||||||
form="filters-form"
|
|
||||||
@click="handleSubmit({ content: modalItem })"
|
|
||||||
v-close-popup
|
|
||||||
>
|
|
||||||
<IconSearch />
|
|
||||||
<p>ver disponibilidad</p>
|
|
||||||
</q-btn> -->
|
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
@ -238,6 +217,10 @@ export default defineComponent({
|
||||||
margin-bottom: 29px;
|
margin-bottom: 29px;
|
||||||
&.availability {
|
&.availability {
|
||||||
gap: 65px;
|
gap: 65px;
|
||||||
|
|
||||||
|
@media only screen and (max-width: $med-md) {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .modal-body-paragraph {
|
& .modal-body-paragraph {
|
||||||
|
|
|
@ -1,49 +1,37 @@
|
||||||
<script>
|
<script>
|
||||||
import { useQuasar } from "quasar";
|
|
||||||
import { useFormStore } from "src/stores/forms";
|
|
||||||
import { useForm } from "vee-validate";
|
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import IconArrowRightOne from "src/components/icons/IconArrowRightOne.vue";
|
import IconArrowRightOne from "src/components/icons/IconArrowRightOne.vue";
|
||||||
import { questionSchema } from "src/utils/zod/schemas/questionSchema";
|
import { useQuestionForm } from "src/hooks/useQuestionForm";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "QuestionForm",
|
name: "QuestionForm",
|
||||||
components: { IconArrowRightOne },
|
components: { IconArrowRightOne },
|
||||||
setup() {
|
setup() {
|
||||||
const $q = useQuasar();
|
const {
|
||||||
const formStore = useFormStore();
|
questionPhoneRef,
|
||||||
const { handleQuestionData } = formStore;
|
formState: { isQuestionSubmitLoading, onSubmit, errors, meta },
|
||||||
|
fields: {
|
||||||
const { errors, meta, defineField, handleSubmit, handleReset } = useForm({
|
email,
|
||||||
validationSchema: questionSchema,
|
emailAttrs,
|
||||||
initialValues: {
|
firstName,
|
||||||
terms: false,
|
firstNameAttrs,
|
||||||
|
message,
|
||||||
|
messageAttrs,
|
||||||
|
phone,
|
||||||
|
phoneAttrs,
|
||||||
|
query,
|
||||||
|
queryAttrs,
|
||||||
|
secondName,
|
||||||
|
secondNameAttrs,
|
||||||
|
terms,
|
||||||
|
termsAttrs,
|
||||||
},
|
},
|
||||||
});
|
} = useQuestionForm();
|
||||||
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 {
|
return {
|
||||||
errors,
|
isQuestionSubmitLoading,
|
||||||
|
questionPhoneRef,
|
||||||
firstName,
|
firstName,
|
||||||
firstNameAttrs,
|
firstNameAttrs,
|
||||||
secondName,
|
secondName,
|
||||||
|
@ -59,6 +47,7 @@ export default defineComponent({
|
||||||
terms,
|
terms,
|
||||||
termsAttrs,
|
termsAttrs,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
errors,
|
||||||
meta,
|
meta,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -78,6 +67,7 @@ export default defineComponent({
|
||||||
class="name"
|
class="name"
|
||||||
outlined
|
outlined
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model="secondName"
|
v-model="secondName"
|
||||||
v-bind="secondNameAttrs"
|
v-bind="secondNameAttrs"
|
||||||
|
@ -88,6 +78,7 @@ export default defineComponent({
|
||||||
class="nickname"
|
class="nickname"
|
||||||
outlined
|
outlined
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model="email"
|
v-model="email"
|
||||||
v-bind="emailAttrs"
|
v-bind="emailAttrs"
|
||||||
|
@ -97,20 +88,25 @@ export default defineComponent({
|
||||||
type="email"
|
type="email"
|
||||||
label="Email"
|
label="Email"
|
||||||
class="email"
|
class="email"
|
||||||
|
autocomplete="email"
|
||||||
outlined
|
outlined
|
||||||
/>
|
/>
|
||||||
<q-input
|
|
||||||
v-model="phone"
|
<div class="field-control field-input telephone">
|
||||||
v-bind="phoneAttrs"
|
<vue-tel-input
|
||||||
:error-message="errors.phone"
|
v-model="phone"
|
||||||
:error="!!errors.phone"
|
v-bind="phoneAttrs"
|
||||||
bg-color="white"
|
:styleClasses="['custom-input', !!errors.phone && 'error']"
|
||||||
type="tel"
|
ref="questionPhoneRef"
|
||||||
label="Teléfono"
|
:inputOptions="{
|
||||||
class="telephone"
|
placeholder: 'Teléfono*',
|
||||||
mask="(##) ##### ####"
|
}"
|
||||||
outlined
|
/>
|
||||||
/>
|
<p v-if="!!errors.phone" class="error">
|
||||||
|
{{ errors.phone }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model="query"
|
v-model="query"
|
||||||
v-bind="queryAttrs"
|
v-bind="queryAttrs"
|
||||||
|
@ -151,6 +147,7 @@ export default defineComponent({
|
||||||
class="question-submit-btn btn rounded"
|
class="question-submit-btn btn rounded"
|
||||||
flat
|
flat
|
||||||
:disable="!meta.valid"
|
:disable="!meta.valid"
|
||||||
|
:loading="isQuestionSubmitLoading"
|
||||||
>
|
>
|
||||||
Enviar solicitud <IconArrowRightOne />
|
Enviar solicitud <IconArrowRightOne />
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
@ -202,6 +199,49 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .field-control.field-input {
|
||||||
|
flex: 1 0 min(100%, 218px);
|
||||||
|
|
||||||
|
&.telephone {
|
||||||
|
flex: 0 0 calc(50% - 5px);
|
||||||
|
@media only screen and (max-width: $med-lg) {
|
||||||
|
flex: 1 0 min(100%, 390px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .custom-input {
|
||||||
|
padding: 10.5px 1px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: 200ms ease-in-out;
|
||||||
|
background-color: $white;
|
||||||
|
&:hover {
|
||||||
|
border-color: $black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-color: $primary;
|
||||||
|
box-shadow: inset 0 0 0 1px $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
border-color: $negative;
|
||||||
|
box-shadow: inset 0 0 0 1px $negative;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .vti__input::placeholder {
|
||||||
|
font-family: $font-questrial;
|
||||||
|
font-size: $font-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& p.error {
|
||||||
|
font-family: $font-questrial;
|
||||||
|
color: $negative;
|
||||||
|
font-size: $font-12;
|
||||||
|
padding: 8px 12px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& .question-submit-btn {
|
& .question-submit-btn {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
@media only screen and (max-width: $med-lg) {
|
@media only screen and (max-width: $med-lg) {
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./date";
|
||||||
|
export * from "./objValitation";
|
|
@ -0,0 +1,15 @@
|
||||||
|
export let countryFlagObj = {};
|
||||||
|
|
||||||
|
const countryFlagObjMessage = (code = "") => {
|
||||||
|
const { default: defaultValue, ...rest } = countryFlagObj;
|
||||||
|
const objectKeys = Object.keys(rest).join(", ");
|
||||||
|
|
||||||
|
return `El código de país ${code} no es válido! Apoyamos los siguientes códigos de país: ${objectKeys}.`;
|
||||||
|
};
|
||||||
|
|
||||||
|
countryFlagObj = {
|
||||||
|
34: "es",
|
||||||
|
351: "pt",
|
||||||
|
33: "fr",
|
||||||
|
default: countryFlagObjMessage,
|
||||||
|
};
|
|
@ -321,8 +321,70 @@ body {
|
||||||
margin-bottom: -14px;
|
margin-bottom: -14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! QUASAR
|
.error-message {
|
||||||
|
min-height: 525px !important;
|
||||||
|
color: $primary;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
& h1 {
|
||||||
|
font-size: $font-40;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $med-md) {
|
||||||
|
min-height: 400px !important;
|
||||||
|
|
||||||
|
& h1 {
|
||||||
|
line-height: 1.2;
|
||||||
|
font-size: $font-28;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: $white;
|
||||||
|
border: 1px solid $primary-dark;
|
||||||
|
|
||||||
|
&.info {
|
||||||
|
left: 16px !important;
|
||||||
|
top: -20px !important;
|
||||||
|
padding: 2px 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.paragraph-sm {
|
||||||
|
color: $primary;
|
||||||
|
font-size: $font-14;
|
||||||
|
font-family: $font-questrial;
|
||||||
|
color: var(--clr);
|
||||||
|
&.link {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Vue-tel-input
|
||||||
|
.vti__input {
|
||||||
|
font-family: $font-questrial;
|
||||||
|
font-size: $font-14;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
font-size: $font-14;
|
||||||
|
font-family: $font-questrial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! QUASAR
|
||||||
.q-virtual-scroll__content .q-item .q-item__label {
|
.q-virtual-scroll__content .q-item .q-item__label {
|
||||||
font-family: $font-questrial;
|
font-family: $font-questrial;
|
||||||
font-size: $font-14;
|
font-size: $font-14;
|
||||||
|
|
|
@ -22,11 +22,13 @@
|
||||||
& .custom-head-paragraph,
|
& .custom-head-paragraph,
|
||||||
& .custom-main-paragraph {
|
& .custom-main-paragraph {
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .custom-head-paragraph {
|
& .custom-head-paragraph {
|
||||||
font-size: $font-12;
|
font-size: $font-12;
|
||||||
opacity: 0.4;
|
// opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .custom-main-paragraph {
|
& .custom-main-paragraph {
|
||||||
|
|
|
@ -26,20 +26,25 @@ export function quasarNotify({ message = "", type, timeout = 1000 }) {
|
||||||
icon: "report_problem",
|
icon: "report_problem",
|
||||||
timeout,
|
timeout,
|
||||||
}),
|
}),
|
||||||
info: () => Notify.create({
|
info: () =>
|
||||||
message,
|
Notify.create({
|
||||||
color: "info",
|
message,
|
||||||
position: "top",
|
color: "info",
|
||||||
icon: "info",
|
position: "top",
|
||||||
timeout,
|
icon: "info",
|
||||||
}),
|
timeout,
|
||||||
|
}),
|
||||||
default: () => {
|
default: () => {
|
||||||
console.error(`Type is invalid! TYPE: ${type}`)
|
console.error(`Type is invalid! TYPE: ${type}`);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type) {
|
if (type) {
|
||||||
return obj[type]() || obj['default']();
|
return obj[type]() || obj["default"]();
|
||||||
}
|
}
|
||||||
console.error("Type is required, success, warning or erro");
|
|
||||||
|
const { default: defaultValue, ...rest } = obj;
|
||||||
|
const objTypes = Object.keys(rest).join(", ");
|
||||||
|
|
||||||
|
console.error(`Type is required, valid types: ${objTypes}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,78 @@
|
||||||
|
import { autoUpdate, useFloating } from "@floating-ui/vue";
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
import { toTypedSchema } from "@vee-validate/zod";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
import { useForm } from "vee-validate";
|
import { useForm } from "vee-validate";
|
||||||
import { computed, reactive, ref } from "vue";
|
import { computed, reactive, ref, watch } from "vue";
|
||||||
|
|
||||||
|
import { apiBack } from "src/boot/axios";
|
||||||
|
import { quasarNotify } from "src/functions/quasarNotify";
|
||||||
import { useFormStore } from "src/stores/forms";
|
import { useFormStore } from "src/stores/forms";
|
||||||
import { checkoutSchema } from "src/utils/zod/schemas";
|
import { checkoutSchema } from "src/utils/zod/schemas";
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { useLocalStorage } from "./useLocalStorage";
|
import { useLocalStorage } from "./useLocalStorage";
|
||||||
|
|
||||||
export function useCheckoutForm() {
|
export function useCheckoutForm() {
|
||||||
const { getItem } = useLocalStorage();
|
const { addItem, getItem, removeItem } = useLocalStorage();
|
||||||
const { push } = useRouter()
|
|
||||||
|
|
||||||
|
//! Elements ref
|
||||||
|
const postalCodeRef = ref(null);
|
||||||
|
const postalCodeTooltip = ref(null);
|
||||||
|
const phoneInputRef = ref(null);
|
||||||
|
const phoneSenderInputRef = ref(null);
|
||||||
|
const redsysFormRef = ref(null);
|
||||||
|
|
||||||
|
//! Form
|
||||||
const formStore = useFormStore();
|
const formStore = useFormStore();
|
||||||
|
const { availability: availabilityForm } = storeToRefs(formStore);
|
||||||
const { handleCheckoutData } = formStore;
|
const { handleCheckoutData } = formStore;
|
||||||
|
|
||||||
|
const availability =
|
||||||
|
availabilityForm.value.dateExpired || getItem("availability");
|
||||||
|
|
||||||
|
const phoneData = ref({
|
||||||
|
country: {
|
||||||
|
name: "",
|
||||||
|
iso2: "",
|
||||||
|
dialCode: "",
|
||||||
|
priority: 0,
|
||||||
|
areaCodes: null,
|
||||||
|
},
|
||||||
|
countryCallingCode: "",
|
||||||
|
nationalNumber: "",
|
||||||
|
number: "",
|
||||||
|
countryCode: "",
|
||||||
|
valid: false,
|
||||||
|
formatted: "",
|
||||||
|
});
|
||||||
|
const phoneSenderData = ref({
|
||||||
|
country: {
|
||||||
|
name: "",
|
||||||
|
iso2: "",
|
||||||
|
dialCode: "",
|
||||||
|
priority: 0,
|
||||||
|
areaCodes: null,
|
||||||
|
},
|
||||||
|
countryCallingCode: "",
|
||||||
|
nationalNumber: "",
|
||||||
|
number: "",
|
||||||
|
countryCode: "",
|
||||||
|
valid: false,
|
||||||
|
formatted: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const provinceOptions = ref([
|
||||||
|
{ code: "es", name: "España" },
|
||||||
|
// { code: "fr", name: "Francia" },
|
||||||
|
// { code: "pt", name: "Portugal" },
|
||||||
|
]);
|
||||||
|
|
||||||
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
|
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
|
||||||
validationSchema: toTypedSchema(checkoutSchema),
|
validationSchema: toTypedSchema(checkoutSchema),
|
||||||
initialValues: {
|
initialValues: {
|
||||||
paymentMethod: "stripe",
|
paymentMethod: "",
|
||||||
terms: false,
|
terms: false,
|
||||||
|
postalCode: availabilityForm.value.postalCode || availability.postalCode,
|
||||||
|
phone: "",
|
||||||
|
senderPhone: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,71 +80,51 @@ export function useCheckoutForm() {
|
||||||
const [surname, surnameAttrs] = defineField("surname");
|
const [surname, surnameAttrs] = defineField("surname");
|
||||||
const [address, addressAttrs] = defineField("address");
|
const [address, addressAttrs] = defineField("address");
|
||||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
||||||
const [phone, phoneAttrs] = defineField("phone");
|
const [phone, phoneAttrs] = defineField("phone", {
|
||||||
|
validateOnModelUpdate: false,
|
||||||
|
});
|
||||||
const [city, cityAttrs] = defineField("city");
|
const [city, cityAttrs] = defineField("city");
|
||||||
const [province, provinceAttrs] = defineField("province");
|
const [province, provinceAttrs] = defineField("province");
|
||||||
const [senderName, senderNameAttrs] = defineField("senderName");
|
const [senderName, senderNameAttrs] = defineField("senderName");
|
||||||
const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif");
|
const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif");
|
||||||
const [senderEmail, senderEmailAttrs] = defineField("senderEmail");
|
const [senderEmail, senderEmailAttrs] = defineField("senderEmail");
|
||||||
const [senderPhone, senderPhoneAttrs] = defineField("senderPhone");
|
const [senderPhone, senderPhoneAttrs] = defineField("senderPhone", {
|
||||||
|
validateOnModelUpdate: false,
|
||||||
|
});
|
||||||
const [senderNotes, senderNotesAttrs] = defineField("senderNotes");
|
const [senderNotes, senderNotesAttrs] = defineField("senderNotes");
|
||||||
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
|
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
|
||||||
const [terms, termsAttrs] = defineField("terms");
|
const [terms, termsAttrs] = defineField("terms");
|
||||||
|
|
||||||
const provinceOptions = ref([
|
//! Tooltip hook
|
||||||
{ code: "01", name: "Araba/Álava" },
|
const { floatingStyles } = useFloating(postalCodeRef, postalCodeTooltip, {
|
||||||
{ code: "02", name: "Albacete" },
|
placement: "top-start",
|
||||||
{ code: "03", name: "Alicante/Alacant" },
|
whileElementsMounted: autoUpdate,
|
||||||
{ code: "04", name: "Almería" },
|
});
|
||||||
{ code: "05", name: "Ávila" },
|
|
||||||
{ code: "06", name: "Badajoz" },
|
const isHidden = ref(true);
|
||||||
{ code: "07", name: "Balears, Illes" },
|
const hideTooltip = () => {
|
||||||
{ code: "08", name: "Barcelona" },
|
isHidden.value = true;
|
||||||
{ code: "09", name: "Burgos" },
|
};
|
||||||
{ code: "10", name: "Cáceres" },
|
const showTooltip = () => {
|
||||||
{ code: "11", name: "Cádiz" },
|
isHidden.value = false;
|
||||||
{ code: "12", name: "Castellón/Castelló" },
|
};
|
||||||
{ code: "13", name: "Ciudad Real" },
|
|
||||||
{ code: "14", name: "Córdoba" },
|
// TODO hacer el await de las provincias
|
||||||
{ code: "15", name: "Coruña, A" },
|
/**
|
||||||
{ code: "16", name: "Cuenca" },
|
* const provinceOptions = getProvinces();
|
||||||
{ code: "17", name: "Girona" },
|
* onBeforeMount(async () => {});
|
||||||
{ code: "18", name: "Granada" },
|
* */
|
||||||
{ code: "19", name: "Guadalajara" },
|
|
||||||
{ code: "20", name: "Gipuzkoa" },
|
watch(
|
||||||
{ code: "21", name: "Huelva" },
|
[
|
||||||
{ code: "22", name: "Huesca" },
|
() => phoneInputRef.value?.modelValue,
|
||||||
{ code: "23", name: "Jaén" },
|
() => phoneSenderInputRef.value?.modelValue,
|
||||||
{ code: "24", name: "León" },
|
],
|
||||||
{ code: "25", name: "Lleida" },
|
([a, b]) => {
|
||||||
{ code: "26", name: "Rioja, La" },
|
phoneData.value = phoneInputRef.value.phoneObject;
|
||||||
{ code: "27", name: "Lugo" },
|
phoneSenderData.value = phoneSenderInputRef.value.phoneObject;
|
||||||
{ 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" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
const stepActive = reactive({ data: 1 });
|
const stepActive = reactive({ data: 1 });
|
||||||
const stepList = reactive({
|
const stepList = reactive({
|
||||||
|
@ -122,27 +157,164 @@ export function useCheckoutForm() {
|
||||||
return step;
|
return step;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
const isError = ref(false);
|
||||||
|
const onError = () => {
|
||||||
|
isError.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
const handleClickStep = (value) => {
|
const handleClickStep = (value) => {
|
||||||
stepActive["data"] = value;
|
stepActive["data"] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkoutBlock = ref(true);
|
const checkoutBlock = ref(true);
|
||||||
const onSubmit = handleSubmit((values) => {
|
const cart = getItem("cart");
|
||||||
handleCheckoutData(values);
|
const totalPrice = computed(() => {
|
||||||
stepList.data[2].active = true;
|
return cart?.reduce((acc, { price }) => {
|
||||||
checkoutBlock.value = false;
|
if (price) {
|
||||||
resetForm();
|
//const priceWithoutLetter = price?.replace("€", "");
|
||||||
|
return +price + acc;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
const cart = getItem("cart");
|
const redsysData = ref({
|
||||||
const totalPrice = ref(0)
|
Ds_MerchantParameters: "",
|
||||||
totalPrice.value = cart?.reduce((acc, { price }) => {
|
Ds_Signature: "",
|
||||||
if (price) {
|
Ds_SignatureVersion: "",
|
||||||
const priceWithoutLetter = price?.replace("€", "");
|
orderId: null,
|
||||||
return +priceWithoutLetter + acc;
|
});
|
||||||
|
|
||||||
|
const isLoadingSubmit = ref(false);
|
||||||
|
const isErrorSubmit = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the fetching of the payment method.
|
||||||
|
*
|
||||||
|
* @param {string} type - The type of payment method paypal or redsys are valid!.
|
||||||
|
* @param {Object} values - The values needed for the payment method.
|
||||||
|
* @returns {Promise<void>} - A promise that resolves when the payment method is fetched.
|
||||||
|
*/
|
||||||
|
const handleFetchPaymentMethod = async ({ type, values }) => {
|
||||||
|
console.log({ type, values });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const productsId = cart.map((item) => item.id);
|
||||||
|
const cartItensData = cart.map(({ id, message, ...rest }) => ({
|
||||||
|
id,
|
||||||
|
message: message || "",
|
||||||
|
}));
|
||||||
|
const deliveryData = {
|
||||||
|
customerData: {
|
||||||
|
custumerName: `${values.name} ${values.surname}`,
|
||||||
|
email: values.senderEmail,
|
||||||
|
custumerPhone: phoneData.value.number,
|
||||||
|
},
|
||||||
|
itemData: cartItensData,
|
||||||
|
deliveryData: {
|
||||||
|
dated: availability.dateExpired,
|
||||||
|
deliveryName: values.senderName,
|
||||||
|
address: values.address,
|
||||||
|
postalCode: availability.postalCode,
|
||||||
|
deliveryPhone: phoneSenderData.value.number,
|
||||||
|
deliveryMessage: values.senderNotes,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const customerName = `${values.name} ${values.surname}`;
|
||||||
|
|
||||||
|
addItem("costumer", deliveryData);
|
||||||
|
const productData = {
|
||||||
|
products: productsId,
|
||||||
|
dateExpired: availability.dateExpired,
|
||||||
|
postalCode: postalCode.value,
|
||||||
|
customer: {
|
||||||
|
customerData: {
|
||||||
|
customerName,
|
||||||
|
email: values.senderEmail,
|
||||||
|
customerPhone: phoneData.value.number,
|
||||||
|
message: values.senderNotes,
|
||||||
|
deliveryName: values.senderName || customerName,
|
||||||
|
address: values.address,
|
||||||
|
deliveryPhone:
|
||||||
|
phoneSenderData.value.number || phoneData.value.number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: values.paymentMethod,
|
||||||
|
};
|
||||||
|
addItem("payment", values.paymentMethod);
|
||||||
|
|
||||||
|
const typeObj = {
|
||||||
|
paypal: async () => {
|
||||||
|
const {
|
||||||
|
data: { data },
|
||||||
|
} = await apiBack.post("payment", productData);
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
location.href = data.link;
|
||||||
|
},
|
||||||
|
redsys: async () => {
|
||||||
|
const {
|
||||||
|
data: { data },
|
||||||
|
} = await apiBack.post("payment", productData);
|
||||||
|
redsysData.value = data;
|
||||||
|
|
||||||
|
redsysFormRef.value.click();
|
||||||
|
},
|
||||||
|
default: () => {
|
||||||
|
console.error(
|
||||||
|
`FATAL ERROR ::: Payment method not found, TYPE: ${type}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentMethod = typeObj[type] || typeObj["default"];
|
||||||
|
await paymentMethod();
|
||||||
|
|
||||||
|
// removeItem("cart");
|
||||||
|
// removeItem("availability");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`FATAL ERROR ::: ${error}`);
|
||||||
|
|
||||||
|
quasarNotify({
|
||||||
|
type: "erro",
|
||||||
|
message:
|
||||||
|
"Se produjo un error al procesar tu compra, inténtalo de nuevo.",
|
||||||
|
});
|
||||||
|
|
||||||
|
isErrorSubmit.value = true;
|
||||||
|
} finally {
|
||||||
|
isLoadingSubmit.value = false;
|
||||||
|
handleCheckoutData(values);
|
||||||
|
resetForm();
|
||||||
}
|
}
|
||||||
}, 0);
|
};
|
||||||
|
|
||||||
|
const onSuccess = async (values, actions) => {
|
||||||
|
const INVALID_NUMBER =
|
||||||
|
"Número no válido introducido, por favor, compruébelo e inténtelo de nuevo";
|
||||||
|
|
||||||
|
if (!phoneData.value.valid) {
|
||||||
|
actions.setFieldError("phone", INVALID_NUMBER);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.senderPhone.length > 0 && !phoneSenderData.value.valid) {
|
||||||
|
actions.setFieldError("senderPhone", INVALID_NUMBER);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingSubmit.value = true;
|
||||||
|
stepsFormated.value[1].active = true;
|
||||||
|
|
||||||
|
await handleFetchPaymentMethod({
|
||||||
|
type: values.paymentMethod,
|
||||||
|
values,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit(onSuccess);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleClickStep,
|
handleClickStep,
|
||||||
|
@ -152,11 +324,31 @@ export function useCheckoutForm() {
|
||||||
checkoutBlock,
|
checkoutBlock,
|
||||||
cart,
|
cart,
|
||||||
totalPrice,
|
totalPrice,
|
||||||
|
isError,
|
||||||
|
redsysData,
|
||||||
|
|
||||||
|
phoneInputRef,
|
||||||
|
phoneSenderInputRef,
|
||||||
|
redsysFormRef,
|
||||||
|
|
||||||
|
phone: { phoneData, phoneSenderData },
|
||||||
|
|
||||||
|
onError,
|
||||||
|
tooltip: {
|
||||||
|
postalCode: {
|
||||||
|
postalCodeRef,
|
||||||
|
postalCodeTooltip,
|
||||||
|
floatingStyles,
|
||||||
|
isHidden,
|
||||||
|
hideTooltip,
|
||||||
|
showTooltip,
|
||||||
|
},
|
||||||
|
},
|
||||||
formState: {
|
formState: {
|
||||||
meta,
|
meta,
|
||||||
errors,
|
errors,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
submitLoading: ref(false),
|
isLoadingSubmit,
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -1,22 +1,39 @@
|
||||||
import { LocalStorage } from "quasar";
|
import { LocalStorage } from "quasar";
|
||||||
|
|
||||||
export function useLocalStorage() {
|
export function useLocalStorage() {
|
||||||
|
/**
|
||||||
|
* Adds an item to localStorage.
|
||||||
|
* @param {string} key - The key of the item to be added.
|
||||||
|
* @param {*} value - The value of the item to be added.
|
||||||
|
*/
|
||||||
const addItem = (key, value) => {
|
const addItem = (key, value) => {
|
||||||
LocalStorage.set(`@${key}`, value);
|
const stringifyValue = JSON.stringify(value);
|
||||||
|
|
||||||
|
LocalStorage.set(`@${key}`, stringifyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an item from the local storage based on the provided key.
|
||||||
|
*
|
||||||
|
* @param {string} key - The key of the item to retrieve.
|
||||||
|
* @returns {Object|Array} - The retrieved item from the local storage. If the key is "availability", it returns an object, otherwise it returns an array.
|
||||||
|
*/
|
||||||
const getItem = (key) => {
|
const getItem = (key) => {
|
||||||
const data = JSON.parse(LocalStorage.getItem(`@${key}`));
|
const data = JSON.parse(LocalStorage.getItem(`@${key}`));
|
||||||
|
|
||||||
|
if (key === "availability") return data || {};
|
||||||
return (data || []);
|
return data || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an item from local storage.
|
||||||
|
*
|
||||||
|
* @param {string} key - The key of the item to remove.
|
||||||
|
*/
|
||||||
const removeItem = (key) => {
|
const removeItem = (key) => {
|
||||||
LocalStorage.remove(`@${key}`);
|
LocalStorage.remove(`@${key}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addItem,
|
addItem,
|
||||||
getItem,
|
getItem,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
import { toTypedSchema } from "@vee-validate/zod";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { useForm } from "vee-validate";
|
import { useForm } from "vee-validate";
|
||||||
import { ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
|
import { fullCurrentDate } from "src/constants";
|
||||||
import { invertDate } from "src/functions/invertDate";
|
import { invertDate } from "src/functions/invertDate";
|
||||||
import { quasarNotify } from "src/functions/quasarNotify";
|
import { quasarNotify } from "src/functions/quasarNotify";
|
||||||
import { useCartStore } from "src/stores/cart";
|
import { useCartStore } from "src/stores/cart";
|
||||||
|
@ -12,172 +13,300 @@ import { useModalStore } from "src/stores/modalStore";
|
||||||
import { useRangePriceStore } from "src/stores/rangePrice";
|
import { useRangePriceStore } from "src/stores/rangePrice";
|
||||||
import { availabilitySchema } from "src/utils/zod/schemas";
|
import { availabilitySchema } from "src/utils/zod/schemas";
|
||||||
import { rangePriceSchema } from "src/utils/zod/schemas/rangePriceSchema";
|
import { rangePriceSchema } from "src/utils/zod/schemas/rangePriceSchema";
|
||||||
|
import { useLocalStorage } from "./useLocalStorage";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook for managing the postal and calendar functionality.
|
* Custom hook for managing postal calendar functionality.
|
||||||
*
|
*
|
||||||
* @param {Object} options - The options for the hook.
|
* @param {Object} options - The options for the hook.
|
||||||
* @param {string} options.modalItem - The modal item isOpenAvailability || isOpenFilters.
|
* @param {string} options.modalItem - The modal item.
|
||||||
* @param {string} options.type - The type of the hook. home || product || availability || filter
|
* @param {string} options.type - The type of the calendar.
|
||||||
* @returns {Object} - The hook functions and data.
|
* @returns {Object} - The hook functions and properties.
|
||||||
*/
|
*/
|
||||||
export function usePostalCalendar({ modalItem = "", type = "home" }) {
|
export function usePostalCalendar({ modalItem = "", type = "home" }) {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { push } = useRouter()
|
const { push } = useRouter();
|
||||||
|
const { addItem, getItem, removeItem } = useLocalStorage();
|
||||||
|
|
||||||
|
const modalStore = useModalStore();
|
||||||
|
|
||||||
const rangePriceStore = useRangePriceStore();
|
const rangePriceStore = useRangePriceStore();
|
||||||
const { rangeValue } = storeToRefs(rangePriceStore);
|
const { rangeValue } = storeToRefs(rangePriceStore);
|
||||||
|
|
||||||
const modalStore = useModalStore();
|
|
||||||
const { openModal } = modalStore
|
|
||||||
|
|
||||||
const formStore = useFormStore();
|
const formStore = useFormStore();
|
||||||
const { sortProductFilters } = storeToRefs(formStore);
|
const { sortProductFilters, availability: availabilityForm } =
|
||||||
|
storeToRefs(formStore);
|
||||||
|
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
const { addToCart, getProducts } = cartStore;
|
const { addToCart, getProducts } = cartStore;
|
||||||
const { products, homeSection } = storeToRefs(cartStore);
|
const { products, cart } = storeToRefs(cartStore);
|
||||||
|
|
||||||
const min = 0;
|
const min = 0;
|
||||||
const max = 200;
|
const max = 200;
|
||||||
const category = ref(route.path.split("/")[2])
|
const category = ref(route.path.split("/")[2]);
|
||||||
|
const categoryObj = {
|
||||||
|
plantas: "Floranet Plantas",
|
||||||
|
ramos: "Floranet Ramos",
|
||||||
|
};
|
||||||
|
const availability = ref(getItem("availability"));
|
||||||
|
|
||||||
const { handleSubmit, handleReset, defineField, errors, setValues } = useForm(
|
const availabilityFormKeys = computed(() => {
|
||||||
{
|
return Object.fromEntries(
|
||||||
validationSchema: toTypedSchema(
|
Object.entries(availabilityForm.value).filter(
|
||||||
type !== "filter" ? availabilitySchema : rangePriceSchema
|
([key, value]) => value !== ""
|
||||||
),
|
)
|
||||||
initialValues: {
|
);
|
||||||
range: {
|
});
|
||||||
min,
|
|
||||||
max,
|
const isAvailabilityEmpty = computed(() => {
|
||||||
},
|
return (
|
||||||
postalCode: "",
|
Object.keys(availability.value || availabilityFormKeys.value).length === 0
|
||||||
date: "",
|
);
|
||||||
},
|
});
|
||||||
|
|
||||||
|
const isPostalCalendarEmpty = computed(() => {
|
||||||
|
if (category.value === "ramos" || category.value === "plantas") {
|
||||||
|
const isAvailabilityEmptyForm =
|
||||||
|
Object.keys(availabilityFormKeys.value).length === 0;
|
||||||
|
|
||||||
|
return isAvailabilityEmptyForm;
|
||||||
}
|
}
|
||||||
);
|
|
||||||
const [calendar, calendarAttrs] = defineField("date");
|
return isAvailabilityEmpty.value;
|
||||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
});
|
||||||
const [priceRange, priceRangeAttrs] = defineField("range");
|
|
||||||
|
const [YEAR, MONTH, DAY] = fullCurrentDate.replaceAll("/", "-").split("-");
|
||||||
|
const CURRENT_DATE = `${DAY}-${MONTH}-${YEAR}`;
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
handleReset,
|
||||||
|
defineField,
|
||||||
|
errors,
|
||||||
|
setValues,
|
||||||
|
setFieldError,
|
||||||
|
} = useForm({
|
||||||
|
validateOnMount: false,
|
||||||
|
validationSchema: toTypedSchema(
|
||||||
|
type !== "filter" ? availabilitySchema : rangePriceSchema
|
||||||
|
),
|
||||||
|
initialValues: {
|
||||||
|
range: {
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
},
|
||||||
|
postalCode: "",
|
||||||
|
date: CURRENT_DATE,
|
||||||
|
},
|
||||||
|
initialTouched: {
|
||||||
|
date: false,
|
||||||
|
postalCode: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
validateOnBlur: false,
|
||||||
|
validateOnChange: false,
|
||||||
|
validateOnInput: false,
|
||||||
|
validateOnModelUpdate: false,
|
||||||
|
};
|
||||||
|
const [calendar, calendarAttrs] = defineField("date", options);
|
||||||
|
const [postalCode, postalCodeAttrs] = defineField("postalCode", options);
|
||||||
|
const [priceRange, priceRangeAttrs] = defineField("range", options);
|
||||||
const [dedication, dedicationAttrs] = defineField("dedication");
|
const [dedication, dedicationAttrs] = defineField("dedication");
|
||||||
|
|
||||||
watch(errors, (newErrors) => {
|
watch(errors, (newErrors) => {
|
||||||
const errorsObj = {
|
const hasErrors = {
|
||||||
postalCode: () =>
|
range: newErrors.range,
|
||||||
quasarNotify({ message: newErrors.postalCode, type: "erro" }),
|
dedication: newErrors.dedication,
|
||||||
date: () => quasarNotify({ message: newErrors.date, type: "erro" }),
|
|
||||||
range: () => quasarNotify({ message: newErrors.range, type: "erro" }),
|
|
||||||
dedication: () =>
|
|
||||||
quasarNotify({ message: newErrors.dedication, type: "erro" }),
|
|
||||||
};
|
};
|
||||||
const keys = Object.keys(newErrors);
|
for (const [field, hasError] of Object.entries(hasErrors)) {
|
||||||
keys.forEach((key) => {
|
if (hasError) {
|
||||||
errorsObj[key]();
|
quasarNotify({ message: newErrors[field], type: "erro" });
|
||||||
});
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch([() => route.path, () => sortProductFilters.value], ([newPath]) => {
|
||||||
[() => route.path, () => sortProductFilters.value],
|
const categoryPath = newPath.split("/")[2];
|
||||||
([newPath]) => {
|
category.value = categoryPath;
|
||||||
const categoryPath = newPath.split("/")[2];
|
availabilityForm.value.dateExpired = "";
|
||||||
category.value = categoryPath;
|
availability.value.postalCode = "";
|
||||||
}
|
sortProductFilters.value.isOpenOrderFilter = false;
|
||||||
);
|
sortProductFilters.value.order = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const removeCart = () => {
|
||||||
const postalAndDateParams = {
|
removeItem("cart");
|
||||||
postalCode: values.postalCode,
|
cart.value = [];
|
||||||
dateExpired: invertDate(values.date),
|
};
|
||||||
|
|
||||||
|
const onSuccess = async (values) => {
|
||||||
|
const handleAvailability = async () => {
|
||||||
|
console.log(type);
|
||||||
|
|
||||||
|
addItem("availability", {
|
||||||
|
postalCode: values.postalCode,
|
||||||
|
dateExpired: invertDate(values.date),
|
||||||
|
});
|
||||||
|
removeCart();
|
||||||
|
availabilityForm.value.dateExpired = invertDate(values.date);
|
||||||
|
availabilityForm.value.postalCode = values.postalCode;
|
||||||
|
|
||||||
|
await getProducts({
|
||||||
|
type: categoryObj[category.value],
|
||||||
|
postalCode: values.postalCode,
|
||||||
|
dateExpired: invertDate(values.date),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const categoryObj = {
|
const handleHome = async () => {
|
||||||
plantas: "Floranet Plantas",
|
console.log(type);
|
||||||
ramos: "Floranet Ramos",
|
|
||||||
};
|
|
||||||
|
|
||||||
const objVal = {
|
addItem("availability", {
|
||||||
home: async () => {
|
postalCode: values.postalCode,
|
||||||
console.log(type);
|
dateExpired: invertDate(values.date),
|
||||||
|
});
|
||||||
|
availabilityForm.value.dateExpired = invertDate(values.date);
|
||||||
|
availabilityForm.value.postalCode = values.postalCode;
|
||||||
|
removeCart();
|
||||||
|
|
||||||
await getProducts(
|
const callback = async () => {
|
||||||
{
|
await push("/categoria/all");
|
||||||
postalCode: values.postalCode,
|
};
|
||||||
dateExpired: invertDate(values.date),
|
|
||||||
},
|
|
||||||
() => homeSection.value.scrollIntoView()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
product: async () => {
|
|
||||||
console.log(type);
|
|
||||||
|
|
||||||
await getProducts(postalAndDateParams);
|
await getProducts(
|
||||||
|
{
|
||||||
const hasProduct = products.value.data.some((item) => {
|
|
||||||
const date = new Date(item.dateExpired);
|
|
||||||
const day = date.getDate();
|
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const dateExpired = `${day}/${month}/${year}`;
|
|
||||||
|
|
||||||
const id = +route.path.split('/')[2];
|
|
||||||
|
|
||||||
return item.postalCode === values.postalCode && item.id === id && values.date <= dateExpired
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasProduct) {
|
|
||||||
push('/categoria/ramos')
|
|
||||||
quasarNotify({ message: 'Seleccione una nueva fecha y un nuevo código postal.', type: 'warning' })
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
openModal({ modal: 'availability' })
|
|
||||||
}, 2000)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addToCart(products.value.current, dedication)
|
|
||||||
},
|
|
||||||
availability: async () => {
|
|
||||||
console.log(type);
|
|
||||||
|
|
||||||
await getProducts({
|
|
||||||
postalCode: values.postalCode,
|
postalCode: values.postalCode,
|
||||||
dateExpired: invertDate(values.date),
|
dateExpired: invertDate(values.date),
|
||||||
});
|
},
|
||||||
},
|
callback
|
||||||
filter: async () => {
|
);
|
||||||
console.log(type);
|
|
||||||
|
|
||||||
rangeValue.value.max = values.range.max;
|
|
||||||
rangeValue.value.min = values.range.min;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
type: categoryObj[category.value],
|
|
||||||
minPrice: values.range.min,
|
|
||||||
maxPrice: values.range.max,
|
|
||||||
};
|
|
||||||
|
|
||||||
await getProducts(params);
|
|
||||||
},
|
|
||||||
default: () => {
|
|
||||||
console.error(
|
|
||||||
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
objVal[type]() || objVal["default"]();
|
|
||||||
|
const handleProduct = async () => {
|
||||||
|
console.log(type);
|
||||||
|
|
||||||
|
addItem("availability", {
|
||||||
|
postalCode: values.postalCode,
|
||||||
|
dateExpired: invertDate(values.date),
|
||||||
|
});
|
||||||
|
removeCart();
|
||||||
|
availabilityForm.value.dateExpired = invertDate(values.date);
|
||||||
|
availabilityForm.value.postalCode = values.postalCode;
|
||||||
|
|
||||||
|
await getProducts({
|
||||||
|
postalCode: values.postalCode,
|
||||||
|
dateExpired: invertDate(values.date),
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasProduct = computed(() => {
|
||||||
|
return products.value.data.some((item) => {
|
||||||
|
const date = new Date(item.dateExpired);
|
||||||
|
const day = date.getDate();
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const dateExpired = `${day}/${month}/${year}`;
|
||||||
|
const dateSelected = values.date.replaceAll("-", "/");
|
||||||
|
|
||||||
|
const id = +route.path.split("/")[2];
|
||||||
|
|
||||||
|
return (
|
||||||
|
item.postalCode === values.postalCode &&
|
||||||
|
item.id === id &&
|
||||||
|
dateSelected <= dateExpired
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasProduct.value) {
|
||||||
|
quasarNotify({
|
||||||
|
message: "Código postal y fecha de caducidad añadidos con éxito",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go();
|
||||||
|
addToCart(products.value.current, dedication);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilter = async () => {
|
||||||
|
console.log(type);
|
||||||
|
|
||||||
|
rangeValue.value.max = values.range.max;
|
||||||
|
rangeValue.value.min = values.range.min;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
type: categoryObj[category.value],
|
||||||
|
minPrice: values.range.min,
|
||||||
|
maxPrice: values.range.max,
|
||||||
|
postalCode: availabilityForm.value.postalCode,
|
||||||
|
dateExpired: availabilityForm.value.dateExpired,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (category.value === "all") {
|
||||||
|
params.postalCode =
|
||||||
|
availability.value.postalCode || availabilityForm.value.postalCode;
|
||||||
|
params.dateExpired =
|
||||||
|
availability.value.dateExpired || availabilityForm.value.dateExpired;
|
||||||
|
|
||||||
|
const { type, ...rest } = params;
|
||||||
|
console.log(rest);
|
||||||
|
await getProducts({ ...rest });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProducts(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDefault = () => {
|
||||||
|
console.error(
|
||||||
|
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
availability: handleAvailability,
|
||||||
|
home: handleHome,
|
||||||
|
product: handleProduct,
|
||||||
|
filter: handleFilter,
|
||||||
|
default: handleDefault,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = handlers[type] || handlers.default;
|
||||||
|
await handler();
|
||||||
|
|
||||||
if (modalItem) {
|
if (modalItem) {
|
||||||
modalStore[modalItem] = false;
|
modalStore[modalItem] = false;
|
||||||
}
|
}
|
||||||
handleReset();
|
handleReset();
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const onError = ({ values, errors, results }) => {
|
||||||
|
const hasErrors = {
|
||||||
|
postalCode: !!errors.postalCode,
|
||||||
|
date: !!errors.date,
|
||||||
|
};
|
||||||
|
for (const [field, hasError] of Object.entries(hasErrors)) {
|
||||||
|
if (hasError) {
|
||||||
|
quasarNotify({ message: errors[field], type: "erro" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit(onSuccess, onError);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onSubmit,
|
onSubmit,
|
||||||
setValues,
|
setValues,
|
||||||
|
handleReset,
|
||||||
modalStore,
|
modalStore,
|
||||||
|
setFieldError,
|
||||||
|
isAvailabilityEmpty,
|
||||||
|
isPostalCalendarEmpty,
|
||||||
|
availabilityFormKeys,
|
||||||
|
category,
|
||||||
fields: {
|
fields: {
|
||||||
calendar,
|
calendar,
|
||||||
calendarAttrs,
|
calendarAttrs,
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { useCartStore } from "src/stores/cart";
|
|
||||||
import { useModalStore } from "src/stores/modalStore";
|
|
||||||
import { watch } from "vue";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import { usePostalCalendar } from "./usePostalCalendar";
|
|
||||||
|
|
||||||
export function useProductPage() {
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const {
|
|
||||||
fields: { dedication, dedicationAttrs },
|
|
||||||
} = usePostalCalendar({ modalItem: "isOpenAvailability" });
|
|
||||||
|
|
||||||
const modalStore = useModalStore();
|
|
||||||
const { openModal } = modalStore;
|
|
||||||
|
|
||||||
const cartStore = useCartStore();
|
|
||||||
const { getProduct, getProducts } = cartStore;
|
|
||||||
const { products, featuredProducts, addCartLoadingBtn } =
|
|
||||||
storeToRefs(cartStore);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => products.value.current?.type,
|
|
||||||
(newCategory) => {
|
|
||||||
getProducts({
|
|
||||||
// type: newCategory,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => route.params.id,
|
|
||||||
(newId) => {
|
|
||||||
getProduct(newId);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const checkImageValidity = (imageLink) => {
|
|
||||||
const validExtensions = [".jpg", ".jpeg", ".png"];
|
|
||||||
|
|
||||||
if (imageLink) {
|
|
||||||
const extension = imageLink.substring(imageLink.lastIndexOf("."));
|
|
||||||
return validExtensions.includes(extension.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return { checkImageValidity, openModal }
|
|
||||||
}
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { useForm } from "vee-validate";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
import { apiBack } from "src/boot/axios";
|
||||||
|
import { quasarNotify } from "src/functions/quasarNotify";
|
||||||
|
import { useFormStore } from "src/stores/forms";
|
||||||
|
import { questionSchema } from "src/utils/zod/schemas";
|
||||||
|
import { watch } from "vue";
|
||||||
|
|
||||||
|
export function useQuestionForm() {
|
||||||
|
const formStore = useFormStore();
|
||||||
|
const { handleQuestionData } = formStore;
|
||||||
|
|
||||||
|
//! Elements
|
||||||
|
const questionPhoneRef = ref(null);
|
||||||
|
const questionPhoneData = ref({
|
||||||
|
country: {
|
||||||
|
name: "",
|
||||||
|
iso2: "",
|
||||||
|
dialCode: "",
|
||||||
|
priority: 0,
|
||||||
|
areaCodes: null,
|
||||||
|
},
|
||||||
|
countryCallingCode: "",
|
||||||
|
nationalNumber: "",
|
||||||
|
number: "",
|
||||||
|
countryCode: "",
|
||||||
|
valid: false,
|
||||||
|
formatted: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
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", {
|
||||||
|
validateOnModelUpdate: false,
|
||||||
|
});
|
||||||
|
const [query, queryAttrs] = defineField("query");
|
||||||
|
const [message, messageAttrs] = defineField("message");
|
||||||
|
const [terms, termsAttrs] = defineField("terms");
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => questionPhoneRef.value?.modelValue,
|
||||||
|
() => {
|
||||||
|
questionPhoneData.value = questionPhoneRef.value.phoneObject;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const isQuestionSubmitLoading = ref(false);
|
||||||
|
const isQuestionSubmitError = ref(false);
|
||||||
|
const onSubmit = handleSubmit(async (values, actions) => {
|
||||||
|
isQuestionSubmitLoading.value = true;
|
||||||
|
|
||||||
|
if (!questionPhoneData.value.valid) {
|
||||||
|
actions.setFieldError("phone", "El teléfono no es válido");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const contactData = {
|
||||||
|
name: `${values.name} ${values.surname}`,
|
||||||
|
phone: questionPhoneData.value.number,
|
||||||
|
email: values.email,
|
||||||
|
message: values.message,
|
||||||
|
};
|
||||||
|
await apiBack.post("contact/save", contactData);
|
||||||
|
|
||||||
|
isQuestionSubmitLoading.value = false;
|
||||||
|
quasarNotify({ type: "success", message: "Mensaje enviado" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`FATAL ERROR ::: ${error}`);
|
||||||
|
|
||||||
|
isQuestionSubmitLoading.value = false;
|
||||||
|
isQuestionSubmitError.value = true;
|
||||||
|
} finally {
|
||||||
|
handleQuestionData(values);
|
||||||
|
handleReset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
questionPhoneData,
|
||||||
|
questionPhoneRef,
|
||||||
|
formState: {
|
||||||
|
isQuestionSubmitLoading,
|
||||||
|
onSubmit,
|
||||||
|
errors,
|
||||||
|
meta,
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
firstName,
|
||||||
|
firstNameAttrs,
|
||||||
|
secondName,
|
||||||
|
secondNameAttrs,
|
||||||
|
email,
|
||||||
|
emailAttrs,
|
||||||
|
phone,
|
||||||
|
phoneAttrs,
|
||||||
|
query,
|
||||||
|
queryAttrs,
|
||||||
|
message,
|
||||||
|
messageAttrs,
|
||||||
|
terms,
|
||||||
|
termsAttrs,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { useIntersectionObserver } from "@vueuse/core";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useMobileStore } from "src/stores/mobileNav";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
|
export function useVerticalCarouselImgs() {
|
||||||
|
const mobileStore = useMobileStore();
|
||||||
|
const { screenWidth } = storeToRefs(mobileStore);
|
||||||
|
const { handleResize } = mobileStore;
|
||||||
|
|
||||||
|
const target = ref(null);
|
||||||
|
const navPos = ref("bottom");
|
||||||
|
const slide = ref("style");
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
screenWidth.value = window.innerWidth;
|
||||||
|
});
|
||||||
|
|
||||||
|
useIntersectionObserver(target, ([{ isIntersecting }]) => {
|
||||||
|
mobileStore.isCarouselVisible = isIntersecting;
|
||||||
|
});
|
||||||
|
document.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
|
return { screenWidth, navPos, slide, target };
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { defineComponent, onBeforeMount, onUpdated, ref, watch } from "vue";
|
import { computed, defineComponent, onBeforeMount, ref, watch } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
import SortSelect from "src/components/@inputs/SortSelect.vue";
|
import SortSelect from "src/components/@inputs/SortSelect.vue";
|
||||||
import IconArrowCircleFilledRight from "src/components/icons/IconArrowCircleFilledRight.vue";
|
import IconArrowCircleFilledRight from "src/components/icons/IconArrowCircleFilledRight.vue";
|
||||||
|
@ -13,6 +13,9 @@ import Card from "src/components/ui/Card.vue";
|
||||||
import Container from "src/components/ui/Container.vue";
|
import Container from "src/components/ui/Container.vue";
|
||||||
import Modal from "src/components/ui/Modal.vue";
|
import Modal from "src/components/ui/Modal.vue";
|
||||||
|
|
||||||
|
import { quasarNotify } from "src/functions/quasarNotify";
|
||||||
|
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
||||||
|
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
||||||
import { useCartStore } from "src/stores/cart";
|
import { useCartStore } from "src/stores/cart";
|
||||||
import { useFormStore } from "src/stores/forms";
|
import { useFormStore } from "src/stores/forms";
|
||||||
import { useMobileStore } from "src/stores/mobileNav";
|
import { useMobileStore } from "src/stores/mobileNav";
|
||||||
|
@ -33,6 +36,9 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const { push } = useRouter();
|
||||||
|
const { getItem } = useLocalStorage();
|
||||||
|
const { isAvailabilityEmpty } = usePostalCalendar({});
|
||||||
|
|
||||||
const mobileStore = useMobileStore();
|
const mobileStore = useMobileStore();
|
||||||
const { screenWidth } = storeToRefs(mobileStore);
|
const { screenWidth } = storeToRefs(mobileStore);
|
||||||
|
@ -41,29 +47,19 @@ export default defineComponent({
|
||||||
const { openModal } = modalStore;
|
const { openModal } = modalStore;
|
||||||
|
|
||||||
const formStore = useFormStore();
|
const formStore = useFormStore();
|
||||||
const { availability, sortProductFilters } = storeToRefs(formStore);
|
const { sortProductFilters, availability } = storeToRefs(formStore);
|
||||||
|
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
const { products } = storeToRefs(cartStore);
|
const { products } = storeToRefs(cartStore);
|
||||||
const { getProducts } = cartStore;
|
const { getProducts } = cartStore;
|
||||||
|
|
||||||
const monthTest = ref("");
|
|
||||||
const isOpenOrder = ref(false);
|
const isOpenOrder = ref(false);
|
||||||
|
const availabilityStoraged = ref(getItem("availability"));
|
||||||
|
const isNotAllCategory = computed(() => {
|
||||||
|
return route.path.split("/")[2] !== "all";
|
||||||
|
});
|
||||||
|
const datePostalCode = ref({});
|
||||||
|
|
||||||
const monthES = {
|
|
||||||
0: "Enero",
|
|
||||||
1: "Febrero",
|
|
||||||
2: "Marzo",
|
|
||||||
3: "Abril",
|
|
||||||
4: "Mayo",
|
|
||||||
5: "Junio",
|
|
||||||
6: "Julio",
|
|
||||||
7: "Agosto",
|
|
||||||
8: "Septiembre",
|
|
||||||
9: "Octubre",
|
|
||||||
10: "Noviembre",
|
|
||||||
11: "Diciembre",
|
|
||||||
};
|
|
||||||
const orderText = {
|
const orderText = {
|
||||||
"lowest-price": "menor precio",
|
"lowest-price": "menor precio",
|
||||||
"highest-price": "mayor precio",
|
"highest-price": "mayor precio",
|
||||||
|
@ -74,12 +70,32 @@ export default defineComponent({
|
||||||
plantas: "Floranet Plantas",
|
plantas: "Floranet Plantas",
|
||||||
ramos: "Floranet Ramos",
|
ramos: "Floranet Ramos",
|
||||||
};
|
};
|
||||||
|
const dateExpiredMonth = computed(
|
||||||
|
() =>
|
||||||
|
availability.value.dateExpired?.split("-")[1] ||
|
||||||
|
availabilityStoraged.value.dateExpired?.split("-")[1]
|
||||||
|
);
|
||||||
|
const dateExpiredDay = computed(
|
||||||
|
() =>
|
||||||
|
availability.value.dateExpired?.split("-")[2] ||
|
||||||
|
availabilityStoraged.value.dateExpired?.split("-")[2]
|
||||||
|
);
|
||||||
|
|
||||||
watch(availability, (newDate) => {
|
const months = {
|
||||||
const [_day, month, _year] = newDate.date.split("/");
|
"01": "Enero",
|
||||||
monthTest.value = monthES[+month - 1];
|
"02": "Febrero",
|
||||||
console.log(monthTest.value);
|
"03": "Marzo",
|
||||||
});
|
"04": "Abril",
|
||||||
|
"05": "Mayo",
|
||||||
|
"06": "Junio",
|
||||||
|
"07": "Julio",
|
||||||
|
"08": "Agosto",
|
||||||
|
"09": "Septiembre",
|
||||||
|
10: "Octubre",
|
||||||
|
11: "Noviembre",
|
||||||
|
12: "Diciembre",
|
||||||
|
};
|
||||||
|
const currentMonth = months[dateExpiredMonth.value];
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => route.path, () => sortProductFilters.value.order],
|
[() => route.path, () => sortProductFilters.value.order],
|
||||||
|
@ -87,14 +103,29 @@ export default defineComponent({
|
||||||
const categoryPath = newPath.split("/")[2];
|
const categoryPath = newPath.split("/")[2];
|
||||||
sortProductFilters.value.category = categoryPath;
|
sortProductFilters.value.category = categoryPath;
|
||||||
|
|
||||||
const params = {
|
const params = {};
|
||||||
type: categoryObj[categoryPath],
|
|
||||||
};
|
if (categoryPath !== "all") {
|
||||||
|
params.type = categoryObj[categoryPath];
|
||||||
|
}
|
||||||
|
if (categoryPath === "all") {
|
||||||
|
params.dateExpired = availabilityStoraged.value.dateExpired;
|
||||||
|
params.postalCode = availabilityStoraged.value.postalCode;
|
||||||
|
}
|
||||||
|
|
||||||
const paramsObj = {
|
const paramsObj = {
|
||||||
"lowest-price": () => (params.lowPrice = 1),
|
"lowest-price": () => {
|
||||||
"highest-price": () => (params.bigPrice = 1),
|
params.lowPrice = 1;
|
||||||
latest: () => (params.isNew = 1),
|
},
|
||||||
recommended: () => (params.recommend = 1),
|
"highest-price": () => {
|
||||||
|
params.bigPrice = 1;
|
||||||
|
},
|
||||||
|
latest: () => {
|
||||||
|
params.isNew = 1;
|
||||||
|
},
|
||||||
|
recommended: () => {
|
||||||
|
params.recommend = 1;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if (newOrder) {
|
if (newOrder) {
|
||||||
paramsObj[newOrder]();
|
paramsObj[newOrder]();
|
||||||
|
@ -104,19 +135,37 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
() => {
|
||||||
|
datePostalCode.value = isNotAllCategory.value
|
||||||
|
? availability.value
|
||||||
|
: availabilityStoraged.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
const categoryPath = route.path.split("/")[2];
|
const categoryPath = route.path.split("/")[2];
|
||||||
|
|
||||||
await getProducts({
|
if (categoryPath !== "all") {
|
||||||
type: categoryObj[categoryPath],
|
await getProducts({
|
||||||
});
|
type: categoryObj[categoryPath],
|
||||||
});
|
});
|
||||||
|
datePostalCode.value = availability.value;
|
||||||
|
|
||||||
onUpdated(() => {
|
return;
|
||||||
console.groupCollapsed("%c Updated!", "color: green;");
|
}
|
||||||
console.log(sortProductFilters.value);
|
|
||||||
console.log(availability.value);
|
await getProducts(availabilityStoraged.value);
|
||||||
console.groupEnd();
|
datePostalCode.value = availabilityStoraged.value;
|
||||||
|
if (isAvailabilityEmpty.value) {
|
||||||
|
await push("/");
|
||||||
|
|
||||||
|
quasarNotify({
|
||||||
|
message: "Debes seleccionar una fecha y código postal",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function openOrderFilter() {
|
function openOrderFilter() {
|
||||||
|
@ -128,12 +177,17 @@ export default defineComponent({
|
||||||
openOrderFilter,
|
openOrderFilter,
|
||||||
openModal,
|
openModal,
|
||||||
sortProductFilters,
|
sortProductFilters,
|
||||||
availability,
|
|
||||||
isOpenOrder,
|
isOpenOrder,
|
||||||
screenWidth,
|
screenWidth,
|
||||||
modalStore,
|
modalStore,
|
||||||
orderText,
|
orderText,
|
||||||
products,
|
products,
|
||||||
|
isNotAllCategory,
|
||||||
|
availabilityStoraged,
|
||||||
|
currentMonth,
|
||||||
|
dateExpiredDay,
|
||||||
|
datePostalCode,
|
||||||
|
availability,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -144,12 +198,10 @@ export default defineComponent({
|
||||||
<section class="products-section">
|
<section class="products-section">
|
||||||
<header class="products-section-header">
|
<header class="products-section-header">
|
||||||
<Container>
|
<Container>
|
||||||
<div class="product-header-content">
|
<div class="product-header-content" v-if="isNotAllCategory">
|
||||||
<h3 class="product-header-title subtitle">
|
<h3 class="product-header-title subtitle">
|
||||||
{{ sortProductFilters.category }} para obsequiar
|
{{ sortProductFilters.category }} para obsequiar
|
||||||
</h3>
|
</h3>
|
||||||
<p class="product-header-paragraph">
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="product-header-filters">
|
<div class="product-header-filters">
|
||||||
|
@ -158,11 +210,15 @@ export default defineComponent({
|
||||||
<p class="filter-paragraph availability">
|
<p class="filter-paragraph availability">
|
||||||
Disponibilidad para:
|
Disponibilidad para:
|
||||||
<span
|
<span
|
||||||
v-if="availability.date && availability.postalCode"
|
v-if="
|
||||||
|
datePostalCode.dateExpired && datePostalCode.postalCode
|
||||||
|
"
|
||||||
class="green-text"
|
class="green-text"
|
||||||
>
|
>
|
||||||
25 Julio en
|
{{ dateExpiredDay }} {{ currentMonth }} en
|
||||||
{{ availability.postalCode.replace("-", "") }}</span
|
{{
|
||||||
|
availability.postalCode || datePostalCode.postalCode
|
||||||
|
}}</span
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -232,8 +288,8 @@ export default defineComponent({
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="products-section-footer">
|
<footer class="products-section-footer" v-if="isNotAllCategory">
|
||||||
<RouterLink class="btn rounded outlined" to="/">
|
<RouterLink class="btn rounded outlined" to="/categoria/all">
|
||||||
Ver todos los diseños <IconArrowCircleFilledRight />
|
Ver todos los diseños <IconArrowCircleFilledRight />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script>
|
||||||
|
import Container from "src/components/ui/Container.vue";
|
||||||
|
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "CheckoutErrorPage",
|
||||||
|
components: { Container },
|
||||||
|
setup() {
|
||||||
|
const { removeItem } = useLocalStorage();
|
||||||
|
|
||||||
|
removeItem("cart");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-page class="checkout-error-page error-message">
|
||||||
|
<container>
|
||||||
|
<h1>¡Uy! Algo ha ido mal durante el proceso de compra.</h1>
|
||||||
|
</container>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.checkout-error-page {
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -20,7 +20,20 @@ export default defineComponent({
|
||||||
checkoutBlock,
|
checkoutBlock,
|
||||||
cart,
|
cart,
|
||||||
totalPrice,
|
totalPrice,
|
||||||
formState: { errors, meta, onSubmit, submitLoading },
|
isError,
|
||||||
|
onError,
|
||||||
|
redsysData,
|
||||||
|
tooltip: {
|
||||||
|
postalCode: {
|
||||||
|
postalCodeRef,
|
||||||
|
postalCodeTooltip,
|
||||||
|
floatingStyles,
|
||||||
|
isHidden,
|
||||||
|
hideTooltip,
|
||||||
|
showTooltip,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formState: { errors, meta, onSubmit, isLoadingSubmit },
|
||||||
fields: {
|
fields: {
|
||||||
name,
|
name,
|
||||||
nameAttrs,
|
nameAttrs,
|
||||||
|
@ -51,21 +64,31 @@ export default defineComponent({
|
||||||
terms,
|
terms,
|
||||||
termsAttrs,
|
termsAttrs,
|
||||||
},
|
},
|
||||||
|
phoneInputRef,
|
||||||
|
phoneSenderInputRef,
|
||||||
|
redsysFormRef,
|
||||||
} = useCheckoutForm();
|
} = useCheckoutForm();
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
if (cart.length === 0) return push("/");
|
if (cart.length === 0) return push("/");
|
||||||
});
|
});
|
||||||
|
|
||||||
const isError = ref(false);
|
|
||||||
const onError = () => {
|
|
||||||
isError.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleClickStep,
|
handleClickStep,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onError,
|
onError,
|
||||||
|
redsysData,
|
||||||
|
|
||||||
|
phoneInputRef,
|
||||||
|
phoneSenderInputRef,
|
||||||
|
redsysFormRef,
|
||||||
|
|
||||||
|
postalCodeRef,
|
||||||
|
postalCodeTooltip,
|
||||||
|
floatingStyles,
|
||||||
|
isHidden,
|
||||||
|
hideTooltip,
|
||||||
|
showTooltip,
|
||||||
|
|
||||||
checkoutBlock,
|
checkoutBlock,
|
||||||
stepsFormated,
|
stepsFormated,
|
||||||
|
@ -74,7 +97,7 @@ export default defineComponent({
|
||||||
stepList,
|
stepList,
|
||||||
cart,
|
cart,
|
||||||
step: ref(1),
|
step: ref(1),
|
||||||
submitLoading,
|
isLoadingSubmit,
|
||||||
successURL: ref(""),
|
successURL: ref(""),
|
||||||
cancelURL: ref(""),
|
cancelURL: ref(""),
|
||||||
meta,
|
meta,
|
||||||
|
@ -117,21 +140,12 @@ export default defineComponent({
|
||||||
<template>
|
<template>
|
||||||
<q-page class="checkout-page">
|
<q-page class="checkout-page">
|
||||||
<Container tag="section">
|
<Container tag="section">
|
||||||
<header class="header-title" :class="!checkoutBlock && 'success'">
|
<header class="header-title">
|
||||||
<h1 class="pege-title" v-if="checkoutBlock">
|
<h1 class="pege-title">¿A quién y dónde lo entregamos"</h1>
|
||||||
{{
|
|
||||||
checkoutBlock
|
|
||||||
? "¿A quién y dónde lo entregamos?"
|
|
||||||
: '"¡Muchas gracias Jerom!"'
|
|
||||||
}}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p class="page-subtitle checkout" v-if="checkoutBlock">
|
<p class="page-subtitle checkout">
|
||||||
{{
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||||
checkoutBlock
|
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||||
? "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
|
||||||
: "¡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>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -175,337 +189,354 @@ export default defineComponent({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="checkoutBlock">
|
<div class="checkout-content">
|
||||||
<div class="checkout-content">
|
<div class="checkout-form">
|
||||||
<div class="checkout-form">
|
<q-form method="post" id="checkout-form" @submit.prevent="onSubmit">
|
||||||
<q-form
|
<div class="form-fields-container delivery">
|
||||||
method="post"
|
<header class="checkout-header-form">
|
||||||
id="checkout-form"
|
<h3>Instrucciones para la entrega</h3>
|
||||||
@submit.prevent="onSubmit"
|
</header>
|
||||||
>
|
|
||||||
<div class="form-fields-container delivery">
|
|
||||||
<header class="checkout-header-form">
|
|
||||||
<h3>Instrucciones para la entrega</h3>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="checkout-fields">
|
<div class="checkout-fields">
|
||||||
<div class="field-control field-input">
|
<div class="field-control field-input">
|
||||||
<q-input
|
<q-input
|
||||||
placeholder="Nombre*"
|
placeholder="Nombre*"
|
||||||
name="name"
|
name="name"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="name"
|
v-model="name"
|
||||||
v-bind:="nameAttrs"
|
v-bind:="nameAttrs"
|
||||||
:error="!!errors.name"
|
:error="!!errors.name"
|
||||||
:error-message="errors.name"
|
:error-message="errors.name"
|
||||||
outlined
|
outlined
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-control field-input">
|
<div class="field-control field-input">
|
||||||
<q-input
|
<q-input
|
||||||
placeholder="Apellidos*"
|
placeholder="Apellidos*"
|
||||||
name="surname"
|
name="surname"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="surname"
|
v-model="surname"
|
||||||
v-bind:="surnameAttrs"
|
v-bind:="surnameAttrs"
|
||||||
:error="!!errors.surname"
|
:error="!!errors.surname"
|
||||||
:error-message="errors.surname"
|
:error-message="errors.surname"
|
||||||
outlined
|
outlined
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-control field-input">
|
<div class="field-control field-input">
|
||||||
<q-input
|
<q-input
|
||||||
placeholder="Dirección*"
|
placeholder="Dirección*"
|
||||||
name="address"
|
name="address"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="address"
|
v-model="address"
|
||||||
v-bind:="addressAttrs"
|
v-bind:="addressAttrs"
|
||||||
:error="!!errors.address"
|
:error="!!errors.address"
|
||||||
:error-message="errors.address"
|
:error-message="errors.address"
|
||||||
outlined
|
outlined
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-control field-input">
|
<div class="field-control field-input">
|
||||||
<q-input
|
<q-input
|
||||||
placeholder="Código postal*"
|
placeholder="Código postal*"
|
||||||
name="postalCode"
|
name="postalCode"
|
||||||
type="text"
|
type="text"
|
||||||
mask="#####"
|
mask="#####"
|
||||||
v-model="postalCode"
|
v-model="postalCode"
|
||||||
v-bind:="postalCodeAttrs"
|
v-bind:="postalCodeAttrs"
|
||||||
:error="!!errors.postalCode"
|
:error="!!errors.postalCode"
|
||||||
:error-message="errors.postalCode"
|
:error-message="errors.postalCode"
|
||||||
outlined
|
ref="postalCodeRef"
|
||||||
/>
|
readonly
|
||||||
</div>
|
outlined
|
||||||
|
@mouseenter="showTooltip"
|
||||||
|
@mouseleave="hideTooltip"
|
||||||
|
>
|
||||||
|
<template #after>
|
||||||
|
<q-btn
|
||||||
|
to="/"
|
||||||
|
:style="'--clr: #ffffff'"
|
||||||
|
class="btn custom-btn-input paragraph-sm"
|
||||||
|
label="EDITAR"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
<div class="field-control field-select">
|
<div
|
||||||
<q-select
|
ref="postalCodeTooltip"
|
||||||
name="province"
|
:style="[floatingStyles, '--clr: #117564']"
|
||||||
v-model="province"
|
:class="['tooltip ', isHidden && 'hidden']"
|
||||||
v-bind:="provinceAttrs"
|
@mouseenter="showTooltip"
|
||||||
:error="!!errors.province"
|
@mouseleave="hideTooltip"
|
||||||
:error-message="errors.province"
|
>
|
||||||
:options="provinceOptions"
|
<p class="paragraph-sm">
|
||||||
option-value="code"
|
No se puede editar este campo
|
||||||
option-label="name"
|
<a
|
||||||
label="Provincia*"
|
href="https://www.google.com/maps"
|
||||||
stack-label
|
rel="noreferrer noopener"
|
||||||
map-options
|
target="_blank"
|
||||||
emit-value
|
class="paragraph-sm link"
|
||||||
outlined
|
>
|
||||||
/>
|
¿No conoce su código postal?
|
||||||
</div>
|
</a>
|
||||||
|
</p>
|
||||||
<div class="field-control field-select">
|
|
||||||
<q-input
|
|
||||||
placeholder="Ciudad*"
|
|
||||||
name="city"
|
|
||||||
type="text"
|
|
||||||
v-model="city"
|
|
||||||
v-bind:="cityAttrs"
|
|
||||||
:error="!!errors.city"
|
|
||||||
:error-message="errors.city"
|
|
||||||
outlined
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-control field-input telephone">
|
|
||||||
<q-input
|
|
||||||
placeholder="Teléfono*"
|
|
||||||
name="phone"
|
|
||||||
type="text"
|
|
||||||
mask="(##) ##### ####"
|
|
||||||
v-model="phone"
|
|
||||||
v-bind:="phoneAttrs"
|
|
||||||
:error="!!errors.phone"
|
|
||||||
:error-message="errors.phone"
|
|
||||||
outlined
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-fields-container sender">
|
<div class="field-control field-select">
|
||||||
<header class="checkout-header-form">
|
<q-select
|
||||||
<h3>Remitente</h3>
|
name="province"
|
||||||
</header>
|
v-model="province"
|
||||||
|
v-bind:="provinceAttrs"
|
||||||
|
:error="!!errors.province"
|
||||||
|
:error-message="errors.province"
|
||||||
|
:options="provinceOptions"
|
||||||
|
option-value="code"
|
||||||
|
option-label="name"
|
||||||
|
label="País*"
|
||||||
|
stack-label
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
outlined
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkout-fields">
|
<div class="field-control field-select">
|
||||||
<div class="field-control field-input">
|
<q-input
|
||||||
<q-input
|
placeholder="Ciudad*"
|
||||||
placeholder="Nombre y apellidos o nombre de empresa"
|
name="city"
|
||||||
name="senderName"
|
type="text"
|
||||||
type="text"
|
v-model="city"
|
||||||
v-model="senderName"
|
v-bind:="cityAttrs"
|
||||||
v-bind:="senderNameAttrs"
|
:error="!!errors.city"
|
||||||
:error="!!errors.senderName"
|
:error-message="errors.city"
|
||||||
:error-message="errors.senderName"
|
outlined
|
||||||
outlined
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-control field-input">
|
<div class="field-control field-input telephone">
|
||||||
<q-input
|
<vue-tel-input
|
||||||
placeholder="CIF / NIF"
|
v-model="phone"
|
||||||
name="senderCifNif"
|
v-bind="phoneAttrs"
|
||||||
type="text"
|
:styleClasses="[
|
||||||
mask="#########"
|
'custom-input',
|
||||||
v-model="senderCifNif"
|
!!errors.phone && 'error',
|
||||||
v-bind:="senderCifNifAttrs"
|
]"
|
||||||
:error="!!errors.senderCifNif"
|
ref="phoneInputRef"
|
||||||
:error-message="errors.senderCifNif"
|
:inputOptions="{
|
||||||
outlined
|
placeholder: 'Teléfono*',
|
||||||
/>
|
}"
|
||||||
</div>
|
/>
|
||||||
|
<p v-if="!!errors.phone" class="error">
|
||||||
<div class="field-control field-input">
|
{{ errors.phone }}
|
||||||
<q-input
|
</p>
|
||||||
placeholder="Email"
|
|
||||||
name="senderEmail"
|
|
||||||
type="email"
|
|
||||||
v-model="senderEmail"
|
|
||||||
v-bind:="senderEmailAttrs"
|
|
||||||
:error="!!errors.senderEmail"
|
|
||||||
:error-message="errors.senderEmail"
|
|
||||||
outlined
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-control field-input">
|
|
||||||
<q-input
|
|
||||||
placeholder="Teléfono"
|
|
||||||
name="senderPhone"
|
|
||||||
type="text"
|
|
||||||
mask="(##) ##### ####"
|
|
||||||
v-model="senderPhone"
|
|
||||||
v-bind:="senderPhoneAttrs"
|
|
||||||
:error="!!errors.senderPhone"
|
|
||||||
:error-message="errors.senderPhone"
|
|
||||||
outlined
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-control field-input">
|
|
||||||
<q-input
|
|
||||||
placeholder="Notas sobre tu pedido (Opcional), por ejemplo, notas especiales para la entrega"
|
|
||||||
name="senderNotes"
|
|
||||||
type="textarea"
|
|
||||||
v-model="senderNotes"
|
|
||||||
v-bind:="senderNotesAttrs"
|
|
||||||
:error="!!errors.senderNotes"
|
|
||||||
:error-message="errors.senderNotes"
|
|
||||||
class="message"
|
|
||||||
autogrow
|
|
||||||
outlined
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-form>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-fields-container sender">
|
||||||
|
<header class="checkout-header-form">
|
||||||
|
<h3>Remitente</h3>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="checkout-fields">
|
||||||
|
<div class="field-control field-input">
|
||||||
|
<q-input
|
||||||
|
placeholder="Nombre y apellidos o nombre de empresa"
|
||||||
|
name="senderName"
|
||||||
|
type="text"
|
||||||
|
v-model="senderName"
|
||||||
|
v-bind:="senderNameAttrs"
|
||||||
|
:error="!!errors.senderName"
|
||||||
|
:error-message="errors.senderName"
|
||||||
|
outlined
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-control field-input">
|
||||||
|
<q-input
|
||||||
|
placeholder="CIF / NIF"
|
||||||
|
name="senderCifNif"
|
||||||
|
type="text"
|
||||||
|
v-model="senderCifNif"
|
||||||
|
v-bind:="senderCifNifAttrs"
|
||||||
|
:error="!!errors.senderCifNif"
|
||||||
|
:error-message="errors.senderCifNif"
|
||||||
|
outlined
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-control field-input">
|
||||||
|
<q-input
|
||||||
|
placeholder="Email"
|
||||||
|
name="senderEmail"
|
||||||
|
type="email"
|
||||||
|
v-model="senderEmail"
|
||||||
|
v-bind:="senderEmailAttrs"
|
||||||
|
:error="!!errors.senderEmail"
|
||||||
|
:error-message="errors.senderEmail"
|
||||||
|
outlined
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-control field-input telephone">
|
||||||
|
<vue-tel-input
|
||||||
|
v-model="senderPhone"
|
||||||
|
v-bind="senderPhoneAttrs"
|
||||||
|
:styleClasses="[
|
||||||
|
'custom-input',
|
||||||
|
!!errors.senderPhone && 'error',
|
||||||
|
]"
|
||||||
|
ref="phoneSenderInputRef"
|
||||||
|
:inputOptions="{
|
||||||
|
placeholder: 'Teléfono*',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<p v-if="!!errors.senderPhone" class="error">
|
||||||
|
{{ errors.senderPhone }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-control field-input">
|
||||||
|
<q-input
|
||||||
|
placeholder="Notas sobre tu pedido (Opcional), por ejemplo, notas especiales para la entrega"
|
||||||
|
name="senderNotes"
|
||||||
|
type="textarea"
|
||||||
|
v-model="senderNotes"
|
||||||
|
v-bind:="senderNotesAttrs"
|
||||||
|
:error="!!errors.senderNotes"
|
||||||
|
:error-message="errors.senderNotes"
|
||||||
|
class="message"
|
||||||
|
autogrow
|
||||||
|
outlined
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
|
||||||
|
<form
|
||||||
|
v-if="paymentMethod === 'redsys' && meta.valid"
|
||||||
|
name="from"
|
||||||
|
action="https://sis-t.redsys.es:25443/sis/realizarPago"
|
||||||
|
method="POST"
|
||||||
|
class="hide"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="Ds_SignatureVersion"
|
||||||
|
:value="redsysData.Ds_SignatureVersion"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="Ds_MerchantParameters"
|
||||||
|
:value="redsysData.Ds_MerchantParameters"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="Ds_Signature"
|
||||||
|
:value="redsysData.Ds_Signature"
|
||||||
|
/>
|
||||||
|
<input ref="redsysFormRef" type="submit" value="Go to pay" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside class="checkout-aside">
|
||||||
|
<div class="checkout-delivery-date" :class="meta.valid && 'active'">
|
||||||
|
<header class="checkout-aside-header green-text">
|
||||||
|
<strong class="checkout-aside-title"> Fecha de entrega </strong>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="checkout-delivery-body">
|
||||||
|
<p class="green-text">13 de julio - De 11h - 12 h</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="checkout-aside">
|
<div class="checkout-summary">
|
||||||
<div
|
<header class="checkout-aside-header gray-bg">
|
||||||
class="checkout-delivery-date"
|
<strong class="checkout-aside-title">
|
||||||
:class="(meta.valid || !checkoutBlock) && 'active'"
|
Resumen del pedido
|
||||||
>
|
</strong>
|
||||||
<header class="checkout-aside-header green-text">
|
</header>
|
||||||
<strong class="checkout-aside-title">
|
|
||||||
Fecha de entrega
|
|
||||||
</strong>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="checkout-delivery-body">
|
<div class="checkout-summary-body gray-bg">
|
||||||
<p class="green-text">13 de julio - De 11h - 12 h</p>
|
<ul class="checkout-summary-list">
|
||||||
</div>
|
<li
|
||||||
</div>
|
class="checkout-summary-item"
|
||||||
|
v-for="({ name, price }, index) in cart"
|
||||||
<div class="checkout-summary">
|
:key="index"
|
||||||
<header class="checkout-aside-header gray-bg">
|
|
||||||
<strong class="checkout-aside-title">
|
|
||||||
Resumen del pedido
|
|
||||||
</strong>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="checkout-summary-body gray-bg">
|
|
||||||
<ul class="checkout-summary-list">
|
|
||||||
<li
|
|
||||||
class="checkout-summary-item"
|
|
||||||
v-for="({ name, price }, index) in cart"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
{{ name }}
|
|
||||||
<span>{{ price }}€</span>
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p class="green-text">Envio Gratuíto</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="checkout-summary-footer">
|
|
||||||
<p class="checkout-summary-paragraph">Total</p>
|
|
||||||
<p class="checkout-summary-paragraph summary-price">
|
|
||||||
{{ totalPrice }}€
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkout-payment-methods gray-bg">
|
|
||||||
<header class="checkout-aside-header">
|
|
||||||
<strong class="checkout-aside-title">Método de pago</strong>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="checkout-payment-body">
|
|
||||||
<!-- <q-radio
|
|
||||||
v-model="paymentMethod"
|
|
||||||
v-bind="paymentMethodAttrs"
|
|
||||||
val="credit"
|
|
||||||
color="primary"
|
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Tarjeta
|
{{ name }}
|
||||||
<span class="card-flags">
|
<span>{{ price }}€</span>
|
||||||
<IconMaster /><IconVisa /> <IconAny /> <IconExpress />
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
</q-radio> -->
|
|
||||||
|
|
||||||
<q-radio
|
|
||||||
v-model="paymentMethod"
|
|
||||||
v-bind="paymentMethodAttrs"
|
|
||||||
val="stripe"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<p>Stripe <a href="#">¿Qué es Stripe?</a></p>
|
|
||||||
</q-radio>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkout-terms">
|
|
||||||
<q-checkbox v-model="terms" v-bind="termsAttrs" class="terms">
|
|
||||||
<p :style="!!errors.terms && 'color: red;'">
|
|
||||||
He leído y estoy de acuerdo con los términosy condiciones de
|
|
||||||
la tienda Floranet
|
|
||||||
</p>
|
|
||||||
</q-checkbox>
|
|
||||||
|
|
||||||
<q-btn flat class="btn" type="submit" form="checkout-form">
|
|
||||||
PROCEDER AL PAGO
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<div class="checkout-success" id="success-block">
|
|
||||||
<h6 class="checkout-success-title green-text">
|
|
||||||
Has efectuado la siguiente compra
|
|
||||||
</h6>
|
|
||||||
|
|
||||||
<div class="checkout-success-body">
|
|
||||||
<div class="checkout-success-content">
|
|
||||||
<ul class="checkout-success-list">
|
|
||||||
<li
|
|
||||||
v-for="({ name, price, image }, index) in cart"
|
|
||||||
:key="index"
|
|
||||||
class="checkout-success-item"
|
|
||||||
>
|
|
||||||
<div class="checkout-item-content">
|
|
||||||
<div class="checkout-product-details">
|
|
||||||
<img
|
|
||||||
:src="isError ? '../assets/empty-img.jpg' : image"
|
|
||||||
:alt="name"
|
|
||||||
class="checkout-product-img"
|
|
||||||
@error="onError"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<p class="checkout-product-title">
|
|
||||||
{{ name }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="checkout-product-price">{{ price }}€</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<p class="green-text">Envio Gratuíto</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="checkout-success-footer">
|
<footer class="checkout-summary-footer">
|
||||||
<p class="checkout-success-paragraph">Total</p>
|
<p class="checkout-summary-paragraph">Total</p>
|
||||||
<p class="checkout-success-paragraph">
|
<p class="checkout-summary-paragraph summary-price">
|
||||||
{{ totalPrice?.toFixed(2) }}€
|
{{ totalPrice }}€
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
<div class="checkout-payment-methods gray-bg">
|
||||||
|
<header class="checkout-aside-header">
|
||||||
|
<strong
|
||||||
|
class="checkout-aside-title"
|
||||||
|
:style="!!errors.paymentMethod && 'color: red'"
|
||||||
|
>
|
||||||
|
Método de pago
|
||||||
|
</strong>
|
||||||
|
<p v-if="!!errors.paymentMethod"></p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="checkout-payment-body">
|
||||||
|
<q-radio
|
||||||
|
v-model="paymentMethod"
|
||||||
|
v-bind="paymentMethodAttrs"
|
||||||
|
val="paypal"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<p>Paypal</p>
|
||||||
|
</q-radio>
|
||||||
|
|
||||||
|
<q-radio
|
||||||
|
v-model="paymentMethod"
|
||||||
|
v-bind="paymentMethodAttrs"
|
||||||
|
val="redsys"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<p>Redsys</p>
|
||||||
|
</q-radio>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkout-terms">
|
||||||
|
<q-checkbox v-model="terms" v-bind="termsAttrs" class="terms">
|
||||||
|
<p :style="!!errors.terms && 'color: red;'">
|
||||||
|
He leído y estoy de acuerdo con los términosy condiciones de
|
||||||
|
la tienda Floranet
|
||||||
|
</p>
|
||||||
|
</q-checkbox>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
class="btn"
|
||||||
|
type="submit"
|
||||||
|
form="checkout-form"
|
||||||
|
:loading="isLoadingSubmit"
|
||||||
|
>
|
||||||
|
PROCEDER AL PAGO
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
</q-page>
|
</q-page>
|
||||||
|
@ -517,10 +548,19 @@ export default defineComponent({
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@media only screen and (max-width: $med-sm) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
& .border-step {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .step-item-container {
|
& .step-item-container {
|
||||||
min-width: 200px;
|
width: min(100%, 200px);
|
||||||
}
|
}
|
||||||
|
|
||||||
& .border-step {
|
& .border-step {
|
||||||
|
@ -927,5 +967,44 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .custom-input {
|
||||||
|
padding: 10.5px 1px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: 200ms ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
border-color: $black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-color: $primary;
|
||||||
|
box-shadow: inset 0 0 0 1px $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
border-color: $negative;
|
||||||
|
box-shadow: inset 0 0 0 1px $negative;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .vti__input::placeholder {
|
||||||
|
font-family: $font-questrial;
|
||||||
|
font-size: $font-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& p.error {
|
||||||
|
font-family: $font-questrial;
|
||||||
|
color: $negative;
|
||||||
|
font-size: $font-12;
|
||||||
|
padding: 8px 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .custom-btn-input {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .q-field__native {
|
||||||
|
font-family: "Roboto" !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,321 @@
|
||||||
|
<script>
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { apiBack } from "src/boot/axios";
|
||||||
|
import { useCheckoutForm } from "src/hooks/useCheckoutForm";
|
||||||
|
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
||||||
|
import { useCartStore } from "src/stores/cart";
|
||||||
|
import { defineComponent, onBeforeMount, ref } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "CheckoutSuccessPage",
|
||||||
|
setup() {
|
||||||
|
const { query } = useRoute();
|
||||||
|
const { push } = useRouter();
|
||||||
|
const { getItem, removeItem } = useLocalStorage();
|
||||||
|
const cartStore = useCartStore();
|
||||||
|
const { cart: cartStoreArr } = storeToRefs(cartStore);
|
||||||
|
const cart = getItem("cart");
|
||||||
|
|
||||||
|
const totalPrice = ref(0);
|
||||||
|
if (cart) {
|
||||||
|
totalPrice.value = cart.reduce((acc, { price }) => acc + +price, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSuccessData() {
|
||||||
|
if (!query.orderId) return push("/checkout/error");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { data },
|
||||||
|
} = await apiBack.post("payment/success", {
|
||||||
|
...query,
|
||||||
|
});
|
||||||
|
resolve(data.products);
|
||||||
|
removeItem("costumer");
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}).then((res) => res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`FATAL ERROR ::: ${error}`);
|
||||||
|
push("/checkout/error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
/* const queryObj = {
|
||||||
|
orderId: query.orderId,
|
||||||
|
productsIds: query.productsIds,
|
||||||
|
PayerID: query.PayerID,
|
||||||
|
};
|
||||||
|
for (const [_, value] of Object.entries(queryObj)) {
|
||||||
|
if (!value) return push("/");
|
||||||
|
} */
|
||||||
|
|
||||||
|
if (cart.length === 0) return push("/");
|
||||||
|
|
||||||
|
await getSuccessData();
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isError, onError } = useCheckoutForm();
|
||||||
|
const steppers = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
name: "Paso 1",
|
||||||
|
description: "Datos de facturación",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
name: "Paso 2",
|
||||||
|
description: "Confirmación",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 3,
|
||||||
|
name: "Paso 3",
|
||||||
|
description: "Pago",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
cartStoreArr.value = [];
|
||||||
|
setTimeout(() => {
|
||||||
|
removeItem("cart");
|
||||||
|
removeItem("payment");
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return { isError, onError, steppers, totalPrice, cart };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-page class="success-container">
|
||||||
|
<div class="checkout-steps">
|
||||||
|
<div
|
||||||
|
v-for="({ active, description, name, value }, i) in steppers"
|
||||||
|
class="step-item-container"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<div class="step-item">
|
||||||
|
<div class="circle-step-container">
|
||||||
|
<span class="border-step" :class="[i == 0 && 'transparent']" />
|
||||||
|
|
||||||
|
<div class="circle-step" :class="active && 'active'">
|
||||||
|
<span class="step-value">{{ value }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="border-step"
|
||||||
|
:class="[i == steppers.length - 1 && 'transparent']"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="title">
|
||||||
|
<h4>{{ name }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="description">
|
||||||
|
<p>{{ description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkout-success" id="success-block">
|
||||||
|
<h6 class="checkout-success-title green-text">
|
||||||
|
Has efectuado la siguiente compra
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<div class="checkout-success-body">
|
||||||
|
<div class="checkout-success-content">
|
||||||
|
<ul class="checkout-success-list">
|
||||||
|
<li
|
||||||
|
v-for="({ name, price, image }, index) in cart"
|
||||||
|
:key="index"
|
||||||
|
class="checkout-success-item"
|
||||||
|
>
|
||||||
|
<div class="checkout-item-content">
|
||||||
|
<div class="checkout-product-details">
|
||||||
|
<img
|
||||||
|
:src="isError ? '../assets/empty-img.jpg' : image"
|
||||||
|
:alt="name"
|
||||||
|
class="checkout-product-img"
|
||||||
|
@error="onError"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p class="checkout-product-title">
|
||||||
|
{{ name }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="checkout-product-price">{{ price }}€</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="checkout-success-footer">
|
||||||
|
<p class="checkout-success-paragraph">Total</p>
|
||||||
|
<p class="checkout-success-paragraph">{{ totalPrice.toFixed(2) }}€</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.success-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-steps {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item-container {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-step {
|
||||||
|
width: 90px;
|
||||||
|
height: 1px;
|
||||||
|
background-color: $primary-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-step-container {
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-step {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border: 1px solid $primary-dark;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
user-select: none;
|
||||||
|
.step-value {
|
||||||
|
font-family: $font-questrial;
|
||||||
|
color: $primary-dark;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background-color: $primary-dark;
|
||||||
|
.step-value {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
font-family: $font-questrial;
|
||||||
|
h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $text-default;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: $text-default;
|
||||||
|
font-family: $font-lora;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-success {
|
||||||
|
width: min(100%, 499px);
|
||||||
|
margin: 122px auto 0;
|
||||||
|
text-align: center;
|
||||||
|
& .checkout-success-title {
|
||||||
|
margin-bottom: 26px;
|
||||||
|
}
|
||||||
|
& .checkout-success-body {
|
||||||
|
& .checkout-success-content {
|
||||||
|
background-color: $secondary-5;
|
||||||
|
padding: 30px 46px 42px 38px;
|
||||||
|
border-radius: 5px 5px 0px 0px;
|
||||||
|
& .checkout-success-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 28px;
|
||||||
|
& .checkout-success-item {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
& .checkout-item-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 61px;
|
||||||
|
& .checkout-product-details {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
& .checkout-product-img {
|
||||||
|
object-fit: cover;
|
||||||
|
width: 54px;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .checkout-product-title {
|
||||||
|
font-size: $font-12;
|
||||||
|
line-height: 21px;
|
||||||
|
letter-spacing: 0.24px;
|
||||||
|
font-family: $font-questrial;
|
||||||
|
color: $text-default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .checkout-product-price {
|
||||||
|
color: $text-muted-one;
|
||||||
|
font-family: $font-roboto;
|
||||||
|
font-size: $font-12;
|
||||||
|
line-height: 21px;
|
||||||
|
letter-spacing: 0.24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $med-lg) {
|
||||||
|
padding-right: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .checkout-success-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: $secondary-40;
|
||||||
|
border-radius: 0px 0px 5px 5px;
|
||||||
|
padding: 14px 46px 7px 36px;
|
||||||
|
|
||||||
|
& .checkout-success-paragraph {
|
||||||
|
font-family: $font-lora;
|
||||||
|
letter-spacing: 0.32px;
|
||||||
|
line-height: 21px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-muted-one;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,21 +0,0 @@
|
||||||
<script>
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "ExamplePage",
|
|
||||||
components: {},
|
|
||||||
setup() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quam rerum omnis
|
|
||||||
repellat. Harum ducimus nulla repellendus neque officia eveniet corporis
|
|
||||||
odio sequi animi ut, non incidunt est error esse aperiam?
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
|
@ -62,8 +62,7 @@ export default defineComponent({
|
||||||
Diseños de ramos más vendidos
|
Diseños de ramos más vendidos
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p class="products-header-paragraph section-paragraph">
|
<p class="products-header-paragraph section-paragraph"></p>
|
||||||
</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="products-body">
|
<div class="products-body">
|
||||||
|
@ -85,7 +84,7 @@ export default defineComponent({
|
||||||
</template>
|
</template>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<RouterLink class="btn rounded outlined" to="/">
|
<RouterLink class="btn rounded outlined" to="/categoria/all">
|
||||||
Ver todos los diseños <IconArrowCircleFilledRight />
|
Ver todos los diseños <IconArrowCircleFilledRight />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,8 +96,7 @@ export default defineComponent({
|
||||||
Nuestra selección de plantas para el verano
|
Nuestra selección de plantas para el verano
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p class="products-selection-paragraph section-paragraph">
|
<p class="products-selection-paragraph section-paragraph"></p>
|
||||||
</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="products-selection-body">
|
<div class="products-selection-body">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { useMeta } from "quasar";
|
import { useMeta } from "quasar";
|
||||||
import { defineComponent, onBeforeMount, ref, watch } from "vue";
|
import { computed, defineComponent, onBeforeMount, ref, watch } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue";
|
import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue";
|
||||||
|
@ -18,8 +18,10 @@ import Card from "components/ui/Card.vue";
|
||||||
import Container from "components/ui/Container.vue";
|
import Container from "components/ui/Container.vue";
|
||||||
import Modal from "components/ui/Modal.vue";
|
import Modal from "components/ui/Modal.vue";
|
||||||
|
|
||||||
|
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
||||||
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
||||||
import { useCartStore } from "stores/cart";
|
import { useCartStore } from "stores/cart";
|
||||||
|
import { useFormStore } from "stores/forms";
|
||||||
import { useModalStore } from "stores/modalStore";
|
import { useModalStore } from "stores/modalStore";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -41,28 +43,48 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const { getItem } = useLocalStorage();
|
||||||
|
|
||||||
|
const formStore = useFormStore();
|
||||||
|
const { availability: availabilityForm } = storeToRefs(formStore);
|
||||||
|
|
||||||
|
const availability = ref(getItem("availability"));
|
||||||
|
const availabilityFormKeys = computed(() => {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(availabilityForm.value).filter(
|
||||||
|
([key, value]) => value !== ""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isAvailabilityEmpty = computed(() => {
|
||||||
|
return (
|
||||||
|
Object.keys(availabilityFormKeys.value || availability.value).length ===
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
handleReset,
|
||||||
fields: { dedication, dedicationAttrs },
|
fields: { dedication, dedicationAttrs },
|
||||||
} = usePostalCalendar({ modalItem: "isOpenAvailability" });
|
} = usePostalCalendar({ modalItem: "isOpenAvailability", type: "product" });
|
||||||
|
|
||||||
const modalStore = useModalStore();
|
const modalStore = useModalStore();
|
||||||
const { openModal } = modalStore;
|
const { openModal } = modalStore;
|
||||||
|
|
||||||
const cartStore = useCartStore();
|
const cartStore = useCartStore();
|
||||||
const { getProduct, getProducts } = cartStore;
|
const { getProduct, getProducts, addToCart } = cartStore;
|
||||||
const { products, featuredProducts, addCartLoadingBtn } =
|
const { products, addCartLoadingBtn } = storeToRefs(cartStore);
|
||||||
storeToRefs(cartStore);
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(async () => {
|
||||||
getProduct(route.params.id);
|
await getProduct(route.params.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => products.value.current?.type,
|
() => products.value.current?.type,
|
||||||
(newCategory) => {
|
(newCategory) => {
|
||||||
getProducts({
|
getProducts({
|
||||||
// type: newCategory,
|
type: newCategory,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -74,33 +96,34 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
useMeta(() => ({
|
useMeta(() => {
|
||||||
title: `${products.value.current?.title}`,
|
return {
|
||||||
titleTemplate: (title) => `${title} - FloraNet`,
|
title: "FloraNet",
|
||||||
meta: {
|
meta: {
|
||||||
description: {
|
description: {
|
||||||
name: "description",
|
name: "description",
|
||||||
content: `${products.value.current?.description}`,
|
content: `${products.value.current?.description}`,
|
||||||
},
|
},
|
||||||
keywords: {
|
keywords: {
|
||||||
name: "keywords",
|
name: "keywords",
|
||||||
content: `${products.value.current?.title}`,
|
content: `${products.value.current?.title}`,
|
||||||
},
|
},
|
||||||
equiv: {
|
equiv: {
|
||||||
"http-equiv": "Content-Type",
|
"http-equiv": "Content-Type",
|
||||||
content: "text/html; charset=UTF-8",
|
content: "text/html; charset=UTF-8",
|
||||||
},
|
},
|
||||||
ogTitle: {
|
ogTitle: {
|
||||||
property: "og:title",
|
property: "og:title",
|
||||||
template(ogTitle) {
|
template(ogTitle) {
|
||||||
return `${ogTitle} - FloraNet`;
|
return `${ogTitle} - FloraNet`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
noscript: {
|
||||||
|
default: "This is content for browsers with no JS (or disabled JS)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
noscript: {
|
};
|
||||||
default: "This is content for browsers with no JS (or disabled JS)",
|
});
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const checkImageValidity = (imageLink) => {
|
const checkImageValidity = (imageLink) => {
|
||||||
const validExtensions = [".jpg", ".jpeg", ".png"];
|
const validExtensions = [".jpg", ".jpeg", ".png"];
|
||||||
|
@ -113,15 +136,28 @@ export default defineComponent({
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addModal = () => {
|
||||||
|
if (!isAvailabilityEmpty.value) {
|
||||||
|
addToCart(products.value.current, dedication);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openModal({ modal: "availability" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePagClick = () => {
|
||||||
|
handleReset();
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
addModal,
|
||||||
openModal,
|
openModal,
|
||||||
|
handlePagClick,
|
||||||
checkImageValidity,
|
checkImageValidity,
|
||||||
slide: ref(1),
|
slide: ref(1),
|
||||||
fullscreen: ref(false),
|
fullscreen: ref(false),
|
||||||
dedication,
|
dedication,
|
||||||
dedicationAttrs,
|
dedicationAttrs,
|
||||||
products,
|
products,
|
||||||
featuredProducts,
|
|
||||||
addCartLoadingBtn,
|
addCartLoadingBtn,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -156,9 +192,9 @@ export default defineComponent({
|
||||||
<span class="green-text" style="display: inline-flex">
|
<span class="green-text" style="display: inline-flex">
|
||||||
{{ products.current?.id }}
|
{{ products.current?.id }}
|
||||||
<q-skeleton
|
<q-skeleton
|
||||||
|
v-if="!products.current?.id"
|
||||||
width="100px"
|
width="100px"
|
||||||
type="text"
|
type="text"
|
||||||
v-if="!products.current?.id"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
@ -168,9 +204,9 @@ export default defineComponent({
|
||||||
<span class="green-text">
|
<span class="green-text">
|
||||||
{{ products.current?.type }}
|
{{ products.current?.type }}
|
||||||
<q-skeleton
|
<q-skeleton
|
||||||
|
v-if="!products.current?.type"
|
||||||
type="text"
|
type="text"
|
||||||
width="50px"
|
width="50px"
|
||||||
v-if="!products.current?.type"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
@ -182,10 +218,10 @@ export default defineComponent({
|
||||||
<p class="product-price green-text">
|
<p class="product-price green-text">
|
||||||
{{ products.current?.price }}€
|
{{ products.current?.price }}€
|
||||||
<q-skeleton
|
<q-skeleton
|
||||||
|
v-if="!products.current?.price"
|
||||||
type="text"
|
type="text"
|
||||||
height="90px"
|
height="90px"
|
||||||
width="80px"
|
width="80px"
|
||||||
v-if="!products.current?.price"
|
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p class="product-delivery green-text">Envío Gratuito</p>
|
<p class="product-delivery green-text">Envío Gratuito</p>
|
||||||
|
@ -226,8 +262,8 @@ export default defineComponent({
|
||||||
:loading="addCartLoadingBtn"
|
:loading="addCartLoadingBtn"
|
||||||
color="primary"
|
color="primary"
|
||||||
class="btn sm-btn"
|
class="btn sm-btn"
|
||||||
label="AÑADIR AL CARRITO"
|
label="COMPRAR"
|
||||||
@click="openModal({ modal: 'availability' })"
|
@click="addModal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -254,7 +290,7 @@ export default defineComponent({
|
||||||
color="white"
|
color="white"
|
||||||
class="btn outlined rounded sm-btn product-pag-item product-prev-btn"
|
class="btn outlined rounded sm-btn product-pag-item product-prev-btn"
|
||||||
:to="`${+$route.params.id - 1}`"
|
:to="`${+$route.params.id - 1}`"
|
||||||
@click="products.current.value = undefined"
|
@click="handlePagClick"
|
||||||
>
|
>
|
||||||
<IconArrowCircleFilledLeft />
|
<IconArrowCircleFilledLeft />
|
||||||
|
|
||||||
|
@ -271,6 +307,7 @@ export default defineComponent({
|
||||||
color="white"
|
color="white"
|
||||||
class="btn outlined rounded sm-btn product-pag-item product-next-btn"
|
class="btn outlined rounded sm-btn product-pag-item product-next-btn"
|
||||||
:to="`${+$route.params.id + 1}`"
|
:to="`${+$route.params.id + 1}`"
|
||||||
|
@click="handlePagClick"
|
||||||
>
|
>
|
||||||
<div class="btn-pag-paragraphs">
|
<div class="btn-pag-paragraphs">
|
||||||
<p class="btn-paragraph-top green-text">Siguiente producto</p>
|
<p class="btn-paragraph-top green-text">Siguiente producto</p>
|
||||||
|
@ -293,8 +330,7 @@ export default defineComponent({
|
||||||
Quizás también te gusten estos ramos
|
Quizás también te gusten estos ramos
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p class="like-another-paragraph">
|
<p class="like-another-paragraph"></p>
|
||||||
</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Container cardContainer class="no-padding">
|
<Container cardContainer class="no-padding">
|
||||||
|
@ -361,6 +397,7 @@ export default defineComponent({
|
||||||
height: 396px;
|
height: 396px;
|
||||||
& .q-carousel__navigation {
|
& .q-carousel__navigation {
|
||||||
bottom: -83px;
|
bottom: -83px;
|
||||||
|
display: block;
|
||||||
& .q-carousel__navigation-inner {
|
& .q-carousel__navigation-inner {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
& .q-carousel__thumbnail {
|
& .q-carousel__thumbnail {
|
||||||
|
|
|
@ -26,6 +26,11 @@ const routes = [
|
||||||
name: "Plantas",
|
name: "Plantas",
|
||||||
component: () => import("pages/CategoryPage.vue"),
|
component: () => import("pages/CategoryPage.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "all",
|
||||||
|
name: "All",
|
||||||
|
component: () => import("pages/CategoryPage.vue"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -37,7 +42,17 @@ const routes = [
|
||||||
path: "",
|
path: "",
|
||||||
name: "Checkout",
|
name: "Checkout",
|
||||||
component: () => import("pages/CheckoutPage.vue"),
|
component: () => import("pages/CheckoutPage.vue"),
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: "success",
|
||||||
|
name: "CheckoutSuccess",
|
||||||
|
component: () => import("pages/CheckoutSuccessPage.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "error",
|
||||||
|
name: "CheckoutError",
|
||||||
|
component: () => import("pages/CheckoutErrorPage.vue"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -62,11 +77,7 @@ const routes = [
|
||||||
name: "Contacta",
|
name: "Contacta",
|
||||||
component: () => import("pages/ContactaPage.vue"),
|
component: () => import("pages/ContactaPage.vue"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/example",
|
|
||||||
name: "Example",
|
|
||||||
component: () => import("pages/ExamplePage.vue"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/:catchAll(.*)*",
|
path: "/:catchAll(.*)*",
|
||||||
name: "NotFound",
|
name: "NotFound",
|
||||||
|
|
|
@ -1,39 +1,42 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore, storeToRefs } from "pinia";
|
||||||
import { ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
import { apiBack } from "src/boot/axios";
|
import { apiBack } from "src/boot/axios";
|
||||||
import { quasarNotify } from "src/functions/quasarNotify";
|
import { quasarNotify } from "src/functions/quasarNotify";
|
||||||
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
||||||
|
import { useFormStore } from "./forms";
|
||||||
|
|
||||||
export const useCartStore = defineStore("cart", () => {
|
export const useCartStore = defineStore("cart", () => {
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const { addItem, getItem, removeItem } = useLocalStorage()
|
const { addItem, getItem } = useLocalStorage();
|
||||||
|
|
||||||
|
const formStore = useFormStore();
|
||||||
|
const { availability: availabilityForm } = storeToRefs(formStore);
|
||||||
|
|
||||||
//! Elements
|
//! Elements
|
||||||
const checkoutRef = ref(null);
|
const checkoutRef = ref(null);
|
||||||
const homeSection = ref(null);
|
const homeSection = ref(null);
|
||||||
|
|
||||||
const initialValues = [{
|
const initialValues = [
|
||||||
id: null,
|
{
|
||||||
name: "",
|
id: null,
|
||||||
price: null,
|
name: "",
|
||||||
image: "",
|
price: null,
|
||||||
description: "",
|
image: "",
|
||||||
dateExpired: "",
|
description: "",
|
||||||
isNew: null,
|
dateExpired: "",
|
||||||
type: "",
|
isNew: null,
|
||||||
postalCode: "",
|
type: "",
|
||||||
order_position: null,
|
postalCode: "",
|
||||||
recommend: null
|
order_position: null,
|
||||||
}]
|
recommend: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
//! Variables
|
//! Variables
|
||||||
const cart = ref([]);
|
const cart = ref(getItem("cart"));
|
||||||
|
const availability = ref(getItem("availability"));
|
||||||
(() => {
|
|
||||||
cart.value = getItem('cart');
|
|
||||||
})()
|
|
||||||
|
|
||||||
const addCartLoadingBtn = ref(false);
|
const addCartLoadingBtn = ref(false);
|
||||||
const routeId = ref(null);
|
const routeId = ref(null);
|
||||||
|
@ -43,44 +46,8 @@ export const useCartStore = defineStore("cart", () => {
|
||||||
current: initialValues,
|
current: initialValues,
|
||||||
next: initialValues,
|
next: initialValues,
|
||||||
});
|
});
|
||||||
const featuredProducts = ref({
|
|
||||||
page: undefined,
|
|
||||||
productsPerPage: undefined,
|
|
||||||
products: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
function transformOptionsToParams(options = {}) {
|
||||||
* Transforms options object into params object.
|
|
||||||
*
|
|
||||||
* @param {Object} options - The options object.
|
|
||||||
* @param {number} options.itens - The items array.
|
|
||||||
* @param {boolean} options.featured - The featured flag.
|
|
||||||
* @param {number} options.page - The page number.
|
|
||||||
* @param {string} options.type - The type name.
|
|
||||||
* @param {string} options.postalCode - The postal code.
|
|
||||||
* @param {string} options.dateExpired - The expiration date.
|
|
||||||
* @param {number} options.minPrice - The minimum price.
|
|
||||||
* @param {number} options.maxPrice - The maximum price.
|
|
||||||
* @param {number} options.bigPrice - The big price.
|
|
||||||
* @param {number} options.lowPrice - The low price.
|
|
||||||
* @param {boolean} options.isNew - The new flag.
|
|
||||||
* @returns {Object} - The params object.
|
|
||||||
*/
|
|
||||||
function transformOptionsToParams(
|
|
||||||
options = {
|
|
||||||
postalCode: undefined,
|
|
||||||
dateExpired: undefined,
|
|
||||||
type: undefined,
|
|
||||||
minPrice: undefined,
|
|
||||||
maxPrice: undefined,
|
|
||||||
bigPrice: undefined,
|
|
||||||
lowPrice: undefined,
|
|
||||||
isNew: undefined,
|
|
||||||
order_crescent: undefined,
|
|
||||||
order_descending: undefined,
|
|
||||||
recommend: undefined,
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const optionsObj = {
|
const optionsObj = {
|
||||||
postalCode: options.postalCode,
|
postalCode: options.postalCode,
|
||||||
dateExpired: options.dateExpired,
|
dateExpired: options.dateExpired,
|
||||||
|
@ -105,24 +72,7 @@ export const useCartStore = defineStore("cart", () => {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const isEmpty = ref(false);
|
||||||
* Fetches products based on the provided options.
|
|
||||||
*
|
|
||||||
* @param {Object} options - The options for fetching products.
|
|
||||||
* @param {number} options.itens - The items to fetch.
|
|
||||||
* @param {boolean} options.featured - Whether to fetch only featured products.
|
|
||||||
* @param {number} options.page - The page number to fetch.
|
|
||||||
* @param {string} options.type - The type of products to fetch.
|
|
||||||
* @param {string} options.postalCode - The postal code for filtering products.
|
|
||||||
* @param {string} options.dateExpired - The expiration date for filtering products.
|
|
||||||
* @param {number} options.minPrice - The minimum price for filtering products.
|
|
||||||
* @param {number} options.maxPrice - The maximum price for filtering products.
|
|
||||||
* @param {number} options.bigPrice - The big price for filtering products.
|
|
||||||
* @param {number} options.lowPrice - The low price for filtering products.
|
|
||||||
* @param {boolean} options.isNew - Whether to fetch only new products.
|
|
||||||
* @param {Function} navigate - The navigation function to call after fetching products.
|
|
||||||
* @returns {Promise<void>} - A promise that resolves when the products are fetched.
|
|
||||||
*/
|
|
||||||
async function getProducts(
|
async function getProducts(
|
||||||
options = {
|
options = {
|
||||||
postalCode: undefined,
|
postalCode: undefined,
|
||||||
|
@ -137,16 +87,17 @@ export const useCartStore = defineStore("cart", () => {
|
||||||
order_descending: undefined,
|
order_descending: undefined,
|
||||||
recommend: undefined,
|
recommend: undefined,
|
||||||
},
|
},
|
||||||
scrollIntoView = () => { }
|
callback
|
||||||
) {
|
) {
|
||||||
const params = transformOptionsToParams(options);
|
const params = transformOptionsToParams(options);
|
||||||
console.log(params);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: { data } } = await apiBack.get("products", { params });
|
const {
|
||||||
|
data: { data },
|
||||||
|
} = await apiBack.get("products", { params });
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
|
isEmpty.value = true;
|
||||||
return quasarNotify({
|
return quasarNotify({
|
||||||
message:
|
message:
|
||||||
"No hay productos disponibles para la fecha y el código postal seleccionados",
|
"No hay productos disponibles para la fecha y el código postal seleccionados",
|
||||||
|
@ -154,10 +105,11 @@ export const useCartStore = defineStore("cart", () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEmpty.value = false;
|
||||||
products.value.data = data;
|
products.value.data = data;
|
||||||
|
|
||||||
if (scrollIntoView) {
|
if (callback) {
|
||||||
scrollIntoView();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.groupCollapsed("%c PRODUCTS FETCHED!", "color: green;");
|
console.groupCollapsed("%c PRODUCTS FETCHED!", "color: green;");
|
||||||
|
@ -182,17 +134,6 @@ export const useCartStore = defineStore("cart", () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a product by its ID and updates the cart state.
|
|
||||||
*
|
|
||||||
* @param {string} id - The ID of the product to fetch.
|
|
||||||
* @param {object} options - Additional options for the product fetch.
|
|
||||||
* @param {string} options.type - The type of the product.
|
|
||||||
* @param {string} options.postalCode - The postal code for location-based filtering.
|
|
||||||
* @param {string} options.dateExpired - The expiration date for time-based filtering.
|
|
||||||
* @param {boolean} debug - Flag indicating whether to enable debug mode.
|
|
||||||
* @returns {Promise<void>} - A promise that resolves when the product is fetched and the cart state is updated.
|
|
||||||
*/
|
|
||||||
async function getProduct(
|
async function getProduct(
|
||||||
id,
|
id,
|
||||||
options = {
|
options = {
|
||||||
|
@ -220,14 +161,14 @@ export const useCartStore = defineStore("cart", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return result[res.status].data[0];
|
return result[res.status].data[0];
|
||||||
})
|
});
|
||||||
|
|
||||||
products.value.prev = prev;
|
products.value.prev = prev;
|
||||||
products.value.current = current;
|
products.value.current = current;
|
||||||
products.value.next = next;
|
products.value.next = next;
|
||||||
|
|
||||||
if (!current) {
|
if (!current) {
|
||||||
push({ name: "NotFound" })
|
push({ name: "NotFound" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
|
@ -250,92 +191,51 @@ export const useCartStore = defineStore("cart", () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function addToCart(product, message) {
|
||||||
* Retrieves featured products based on the provided options.
|
const params = transformOptionsToParams(
|
||||||
*
|
availabilityForm.value || availability.value
|
||||||
* @param {Object} options - The options for retrieving featured products.
|
);
|
||||||
* @param {number} options.itens - The number of items to retrieve.
|
await getProducts(params);
|
||||||
* @param {number} options.featured - The flag indicating if the products should be featured.
|
|
||||||
* @param {number} [options.page] - The page number for pagination.
|
|
||||||
* @param {string} [options.type] - The type of the products.
|
|
||||||
* @param {string} [options.postalCode] - The postal code for location-based filtering.
|
|
||||||
* @param {string} [options.dateExpired] - The expiration date for filtering.
|
|
||||||
* @param {number} [options.minPrice] - The minimum price for filtering.
|
|
||||||
* @param {number} [options.maxPrice] - The maximum price for filtering.
|
|
||||||
* @param {number} [options.bigPrice] - The big price for filtering.
|
|
||||||
* @param {number} [options.lowPrice] - The low price for filtering.
|
|
||||||
* @param {boolean} [options.isNew] - The flag indicating if the products are new.
|
|
||||||
* @returns {Promise<void>} - A promise that resolves when the featured products are retrieved.
|
|
||||||
*/
|
|
||||||
async function getFeaturedProducts(
|
|
||||||
options = {
|
|
||||||
postalCode: undefined,
|
|
||||||
dateExpired: undefined,
|
|
||||||
type: undefined,
|
|
||||||
minPrice: undefined,
|
|
||||||
maxPrice: undefined,
|
|
||||||
bigPrice: undefined,
|
|
||||||
lowPrice: undefined,
|
|
||||||
isNew: undefined,
|
|
||||||
order_crescent: undefined,
|
|
||||||
order_descending: undefined,
|
|
||||||
recommend: 1,
|
|
||||||
},
|
|
||||||
debug = false
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const params = transformOptionsToParams(options);
|
|
||||||
|
|
||||||
(async () => {
|
const hasCurrentProduct = computed(() => {
|
||||||
const {
|
return cart.value.find((p) => p.id === product.id);
|
||||||
data: { data },
|
|
||||||
} = await apiBack.get("products", { params });
|
|
||||||
featuredProducts.value = data[0];
|
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
console.groupCollapsed(
|
|
||||||
"%c FEATURED PRODUCTS FETCHED!",
|
|
||||||
"color: green;"
|
|
||||||
);
|
|
||||||
console.table(data.products);
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
} catch (err) {
|
|
||||||
new Error(`FATAL ERROR ::: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adiciona um produto ao carrinho.
|
|
||||||
* @param {Object} product - O produto a ser adicionado.
|
|
||||||
* @param {string} dedication - A dedicação associada ao produto.
|
|
||||||
*/
|
|
||||||
function addToCart(product, dedication) {
|
|
||||||
const existingProduct = cart.value.find(p => p.id === product.id);
|
|
||||||
console.log(existingProduct)
|
|
||||||
|
|
||||||
if (!existingProduct) {
|
|
||||||
const arr = [...cart.value];
|
|
||||||
arr.push(product);
|
|
||||||
console.log(arr)
|
|
||||||
addItem("cart", JSON.stringify(arr));
|
|
||||||
quasarNotify({ message: 'Producto añadido al carrito.', type: 'success' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
quasarNotify({
|
|
||||||
message: "Este producto ya está en el carrito",
|
|
||||||
type: "info",
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (isEmpty.value) {
|
||||||
* Remove an item from the cart by its ID.
|
push("/");
|
||||||
* @param {number} id - The ID of the item to be removed.
|
return quasarNotify({
|
||||||
*/
|
message:
|
||||||
function removeFromCart(id) {
|
"No hay productos disponibles para la fecha y el código postal seleccionados",
|
||||||
const newArrRemovedItem = cart.value.filter((p) => id !== p.id);
|
type: "erro",
|
||||||
addItem("cart", JSON.stringify(newArrRemovedItem))
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!products.value.data.some((item) => item.id === product.id)) {
|
||||||
|
push("/");
|
||||||
|
return quasarNotify({
|
||||||
|
message:
|
||||||
|
"Este producto no está disponible en su zona, intente añadir un nuevo código postal",
|
||||||
|
type: "erro",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCurrentProduct.value) {
|
||||||
|
return quasarNotify({
|
||||||
|
message: "Este producto ya está en el carrito",
|
||||||
|
type: "info",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const arr = [...cart.value];
|
||||||
|
arr.push({ ...product, message: message.value });
|
||||||
|
cart.value = arr;
|
||||||
|
addItem("cart", arr);
|
||||||
|
|
||||||
|
await push("/checkout");
|
||||||
|
quasarNotify({
|
||||||
|
message: "Producto añadido al carrito.",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -345,12 +245,9 @@ export const useCartStore = defineStore("cart", () => {
|
||||||
cart,
|
cart,
|
||||||
addCartLoadingBtn,
|
addCartLoadingBtn,
|
||||||
products,
|
products,
|
||||||
featuredProducts,
|
|
||||||
|
|
||||||
getFeaturedProducts,
|
|
||||||
getProducts,
|
getProducts,
|
||||||
addToCart,
|
addToCart,
|
||||||
removeFromCart,
|
|
||||||
getProduct,
|
getProduct,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,10 @@ import { defineStore } from "pinia";
|
||||||
|
|
||||||
export const useFormStore = defineStore("forms", {
|
export const useFormStore = defineStore("forms", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
postalCodeValid: {
|
||||||
|
isValid: false,
|
||||||
|
dataOptions: [],
|
||||||
|
},
|
||||||
sortProductFilters: {
|
sortProductFilters: {
|
||||||
isOpenOrderFilter: false,
|
isOpenOrderFilter: false,
|
||||||
order: undefined,
|
order: undefined,
|
||||||
|
@ -18,7 +22,7 @@ export const useFormStore = defineStore("forms", {
|
||||||
terms: false,
|
terms: false,
|
||||||
},
|
},
|
||||||
availability: {
|
availability: {
|
||||||
date: "",
|
dateExpired: "",
|
||||||
postalCode: "",
|
postalCode: "",
|
||||||
},
|
},
|
||||||
checkout: {
|
checkout: {
|
||||||
|
@ -34,28 +38,23 @@ export const useFormStore = defineStore("forms", {
|
||||||
senderEmail: "",
|
senderEmail: "",
|
||||||
senderPhone: "",
|
senderPhone: "",
|
||||||
senderNotes: "",
|
senderNotes: "",
|
||||||
paymentMethod: "credit",
|
paymentMethod: "paypal",
|
||||||
terms: false,
|
terms: false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
handleQuestionData(values) {
|
handleQuestionData(values) {
|
||||||
console.log(values);
|
|
||||||
this.question = values;
|
this.question = values;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAvailabilityData(values) {
|
handleAvailabilityData(values) {
|
||||||
console.log(values);
|
|
||||||
this.availability = values;
|
this.availability = values;
|
||||||
},
|
},
|
||||||
|
|
||||||
registerAvailability() {
|
registerAvailability() {},
|
||||||
console.log(this.availability);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCheckoutData(values) {
|
handleCheckoutData(values) {
|
||||||
// console.log(values);
|
|
||||||
this.checkout = values;
|
this.checkout = values;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,8 +23,7 @@ export const useModalStore = defineStore("modal", () => {
|
||||||
isOpenAvailability: () => "Contenido modal availability",
|
isOpenAvailability: () => "Contenido modal availability",
|
||||||
isOpenFilters: () => "Contenido modal filters",
|
isOpenFilters: () => "Contenido modal filters",
|
||||||
};
|
};
|
||||||
console.log(availability.value);
|
isModal[content]()
|
||||||
console.log(isModal[content]());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { openModal, handleSubmit, isOpenAvailability, isOpenFilters };
|
return { openModal, handleSubmit, isOpenAvailability, isOpenFilters };
|
||||||
|
|
|
@ -8,7 +8,6 @@ export const useRangePriceStore = defineStore("range-price", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function handlePriceRange({ min, max }) {
|
function handlePriceRange({ min, max }) {
|
||||||
console.log({ min, max });
|
|
||||||
rangeValue.min = min;
|
rangeValue.min = min;
|
||||||
rangeValue.max = max;
|
rangeValue.max = max;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,31 @@
|
||||||
export function handlePhoneVal(val) {
|
import { countryFlagObj } from "src/constants";
|
||||||
const regex = /[\(\) ]/g;
|
|
||||||
const valWithoutSpaceAndParenteses = val.replace(regex, "");
|
|
||||||
const valLength = valWithoutSpaceAndParenteses.length;
|
|
||||||
|
|
||||||
return valLength > 0 && valLength === 11;
|
import * as R from "./regex";
|
||||||
|
|
||||||
|
export function getCountryCode(countryNumber) {
|
||||||
|
return (
|
||||||
|
countryFlagObj[countryNumber] || countryFlagObj["default"](countryNumber)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleValidCountryCode(countryNumber) {
|
||||||
|
const validCountryCodes = ["34", "351", "33"];
|
||||||
|
const isCountryCodeValid = validCountryCodes.includes(countryNumber);
|
||||||
|
|
||||||
|
return isCountryCodeValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformPhoneVal(val) {
|
||||||
|
const obj = {
|
||||||
|
length: val.replace(R.selectSpaceAndSum, "").length,
|
||||||
|
val: val.replace(R.selectParenthesesSpacesDashes, ""),
|
||||||
|
};
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handlePhoneVal(input) {
|
||||||
|
const { length, val } = input;
|
||||||
|
|
||||||
|
return length === 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
export const nameMessage =
|
export const nameMessage =
|
||||||
"Sólo se aceptan una palabra y caracteres no numéricos";
|
"Sólo se aceptan una palabra y caracteres no numéricos";
|
||||||
export const phoneMessage =
|
export const phoneMessage =
|
||||||
"El número de teléfono debe contener 11 caracteres numéricos válidos";
|
"El teléfono no es válido, por favor, introduzca un número de teléfono válido.";
|
||||||
export const onlyMinimumTwoCharacters = "Añade al menos dos caracteres";
|
export const onlyMinimumTwoCharacters = "Añade al menos dos caracteres";
|
||||||
export const onlyTextMessage = "Sólo son válidas las letras";
|
export const onlyTextMessage = "Sólo son válidas las letras";
|
||||||
export const requiredMessage = "Campo obligatorio";
|
export const requiredMessage = "Campo obligatorio";
|
||||||
export const emailMessage =
|
export const emailMessage =
|
||||||
"Introduzca una dirección de correo electrónico válida.";
|
"Introduzca una dirección de correo electrónico válida.";
|
||||||
|
export const onlyTextAndNumbersMessage =
|
||||||
|
"Sólo son válidas las letras y números";
|
||||||
|
export const onlyNumbers = "¡Sólo se aceptan números!";
|
||||||
|
export const fiveLength = "El código postal debe tener 5 dígitos";
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
export const justOneWord = /^[A-Za-z]+$/;
|
export const justOneWord = /^[A-Za-z\u00C0-\u00FF]+$/;
|
||||||
export const justLetters = /^[A-Za-z ]+$/;
|
export const justLetters = /^[A-Za-z\u00C0-\u00FF ]+$/;
|
||||||
|
export const justLettersAndNumbers = /^[A-Za-z0-9]+$/;
|
||||||
|
export const justNumbers = /^[0-9 ]+$/;
|
||||||
|
export const selectSpaceAndSum = /\s|\+/g;
|
||||||
|
export const selectValuesWithSum = /\+\d+/g;
|
||||||
|
export const selectParenthesesSpacesDashes = /[\s()-]/g;
|
||||||
|
|
|
@ -1,34 +1,36 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { handlePhoneVal, justLetters, justOneWord, postalCode } from "..";
|
import * as GP from "../globalProperties";
|
||||||
|
|
||||||
import * as M from "../messages";
|
import * as M from "../messages";
|
||||||
|
import * as R from "../regex";
|
||||||
|
|
||||||
const checkoutObjVal = {
|
const checkoutObjVal = {
|
||||||
name: z
|
name: z
|
||||||
.string({ required_error: M.requiredMessage })
|
.string({ required_error: M.requiredMessage })
|
||||||
.regex(justOneWord, M.nameMessage),
|
.regex(R.justOneWord, M.nameMessage),
|
||||||
surname: z
|
surname: z
|
||||||
.string({ required_error: M.requiredMessage })
|
.string({ required_error: M.requiredMessage })
|
||||||
.regex(justOneWord, M.nameMessage),
|
.regex(R.justOneWord, M.nameMessage),
|
||||||
address: z.string({ required_error: M.requiredMessage }),
|
address: z.string({ required_error: M.requiredMessage }),
|
||||||
postalCode,
|
postalCode: GP.postalCode,
|
||||||
city: z
|
city: z
|
||||||
.string({ required_error: M.requiredMessage })
|
.string({ required_error: M.requiredMessage })
|
||||||
.min(2, M.onlyMinimumTwoCharacters)
|
.min(2, M.onlyMinimumTwoCharacters)
|
||||||
.regex(justLetters, M.onlyTextMessage),
|
.regex(R.justLetters, M.onlyTextMessage),
|
||||||
province: z.string({ required_error: M.requiredMessage }),
|
province: z.string({ required_error: M.requiredMessage }),
|
||||||
phone: z
|
phone: z.string({ required_error: M.requiredMessage }).refine((val) => {
|
||||||
.string({ required_error: M.requiredMessage })
|
return val.length > 0;
|
||||||
.refine(handlePhoneVal, M.phoneMessage),
|
}, M.requiredMessage),
|
||||||
senderName: z.string().regex(justOneWord, M.nameMessage),
|
senderName: z.string().regex(R.justLetters),
|
||||||
senderCifNif: z
|
senderCifNif: z
|
||||||
.string()
|
.string()
|
||||||
.length(9, "El código postal debe tener 9 caracteres numéricos válidos"),
|
.regex(R.justLettersAndNumbers, M.onlyTextAndNumbersMessage),
|
||||||
senderEmail: z.string().email(M.emailMessage),
|
senderEmail: z.string().email(M.emailMessage),
|
||||||
senderPhone: z.string().refine(handlePhoneVal, M.phoneMessage),
|
senderPhone: z.string().refine((val) => {
|
||||||
|
return val.length >= 0;
|
||||||
|
}, M.phoneMessage),
|
||||||
senderNotes: z.string(),
|
senderNotes: z.string(),
|
||||||
paymentMethod: z.enum(["credit", "stripe"], {
|
paymentMethod: z.enum(["redsys", "paypal"], {
|
||||||
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) => {
|
terms: z.boolean().refine((val) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
import { toTypedSchema } from "@vee-validate/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { handlePhoneVal, justOneWord } from "..";
|
import { justOneWord } from "..";
|
||||||
import * as M from "../messages";
|
import * as M from "../messages";
|
||||||
|
|
||||||
const questionObjVal = {
|
const questionObjVal = {
|
||||||
|
@ -14,9 +14,13 @@ const questionObjVal = {
|
||||||
email: z.string({ required_error: M.requiredMessage }).email(M.emailMessage),
|
email: z.string({ required_error: M.requiredMessage }).email(M.emailMessage),
|
||||||
phone: z
|
phone: z
|
||||||
.string({ required_error: M.requiredMessage })
|
.string({ required_error: M.requiredMessage })
|
||||||
.refine(handlePhoneVal, M.phoneMessage),
|
.min(1, M.requiredMessage),
|
||||||
query: z.string({ required_error: M.requiredMessage }).min(1),
|
query: z
|
||||||
message: z.string({ required_error: M.requiredMessage }).min(1),
|
.string({ required_error: M.requiredMessage })
|
||||||
|
.min(1, M.requiredMessage),
|
||||||
|
message: z
|
||||||
|
.string({ required_error: M.requiredMessage })
|
||||||
|
.min(1, M.requiredMessage),
|
||||||
terms: z.boolean({ required_error: M.requiredMessage }).refine((val) => {
|
terms: z.boolean({ required_error: M.requiredMessage }).refine((val) => {
|
||||||
return val === true;
|
return val === true;
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -2,9 +2,7 @@ import { z } from "zod";
|
||||||
|
|
||||||
const rangePriceObj = {
|
const rangePriceObj = {
|
||||||
range: z.object({
|
range: z.object({
|
||||||
min: z
|
min: z.number(),
|
||||||
.number()
|
|
||||||
.refine((n) => n > 0, "El valor mínimo debe ser superior a cero"),
|
|
||||||
max: z.number(),
|
max: z.number(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue