develop #6
|
@ -17,6 +17,13 @@
|
|||
"terminal.integrated.cursorStyleInactive": "line",
|
||||
"workbench.iconTheme": "material-icon-theme",
|
||||
"workbench.colorTheme": "Default Dark+",
|
||||
"editor.codeActionsOnSave": [
|
||||
"source.addMissingImports",
|
||||
"source.organizeImports",
|
||||
"source.fixAll.eslint",
|
||||
"source.fixAll.stylelint"
|
||||
],
|
||||
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
|
@ -46,7 +53,6 @@
|
|||
"git.confirmSync": false,
|
||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||
"console-ninja.featureSet": "Community",
|
||||
"liveServer.settings.donotShowInfoMsg": true,
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"editor.cursorSmoothCaretAnimation": "on",
|
||||
"editor.fontLigatures": true,
|
||||
|
@ -60,9 +66,7 @@
|
|||
"source.organizeImports",
|
||||
"source.fixAll.eslint"
|
||||
],
|
||||
"liveServer.settings.donotVerifyTags": true,
|
||||
"gitlens.gitCommands.skipConfirmations": ["fetch:command", "switch:command"],
|
||||
"symbols.hidesExplorerArrows": false,
|
||||
"diffEditor.ignoreTrimWhitespace": false,
|
||||
"svg.preview.mode": "svg",
|
||||
"[svg]": {
|
||||
|
@ -72,7 +76,7 @@
|
|||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"workbench.tree.indent": 16,
|
||||
"window.zoomLevel": 0,
|
||||
"window.zoomLevel": -1,
|
||||
"git.ignoreRebaseWarning": true,
|
||||
"editor.largeFileOptimizations": false,
|
||||
"[javascript]": {
|
||||
|
|
|
@ -5,45 +5,34 @@ const productsJson = require("./products.json")
|
|||
class ProductController {
|
||||
async findAll(req, res) {
|
||||
|
||||
|
||||
const _products = await db.getProducts();
|
||||
const mapedProducts = await _products[0].map(function (item) {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
type: item.type,
|
||||
price: item.price,
|
||||
specialPrice: item.specialPrice,
|
||||
isNew: item.isNew,
|
||||
slug: item.slug,
|
||||
category: item.category,
|
||||
postalCode: item.postalCode,
|
||||
dateExpired: item.dateExpired,
|
||||
images: [item.image],
|
||||
featured: item.featured,
|
||||
}
|
||||
})
|
||||
|
||||
//Gerar produtos fake
|
||||
/* const fakeProducts = generateFlowers(50)
|
||||
console.log(fakeProducts); */
|
||||
const params = req.query;
|
||||
let productsFilter = mapedProducts
|
||||
console.log(params);
|
||||
const _products = await db.getProducts(params.dateExpired, params.postalCode);
|
||||
let productsFilter = _products[0];
|
||||
|
||||
if (Number(params.featured)) {
|
||||
productsFilter = productsFilter.filter(item => item.featured === Number(params.featured))
|
||||
if (Number(params.recommend)) {
|
||||
productsFilter = productsFilter.filter(item => item.recommend == Number(params.recommend))
|
||||
jsolis marked this conversation as resolved
|
||||
}
|
||||
if (params.category) {
|
||||
productsFilter = productsFilter.filter(item => item.category === Number(params.category))
|
||||
if (params.type) {
|
||||
productsFilter = productsFilter.filter(item => item.type === params.type)
|
||||
}
|
||||
if (params.postalCode) {
|
||||
/*if (params.postalCode) {
|
||||
productsFilter = productsFilter.filter(item => item.postalCode === params.postalCode)
|
||||
}
|
||||
if (params.dateExpired) {
|
||||
const dateSearch = new Date(params.dateExpired);
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const dateProduct = new Date(item.dateExpired);
|
||||
if (dateProduct >= dateSearch) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
}*/
|
||||
console.log(productsFilter.length);
|
||||
|
||||
|
||||
if (params.minPrice && !params.maxPrice) {
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const price = Number(item.price.replace(/€/g, ''))
|
||||
const price = Number(item.price)
|
||||
jsolis marked this conversation as resolved
pablone
commented
se utiliza de manera reiterada la función Number() podemos plantearlo de otra manera se utiliza de manera reiterada la función Number() podemos plantearlo de otra manera
para que no sea necesario
|
||||
if (price >= Number(params.minPrice)) {
|
||||
return item
|
||||
}
|
||||
|
@ -51,7 +40,7 @@ class ProductController {
|
|||
}
|
||||
if (params.maxPrice && !params.minPrice) {
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const price = Number(item.price.replace(/€/g, ''))
|
||||
const price = Number(item.price)
|
||||
if (price <= Number(params.maxPrice)) {
|
||||
return item
|
||||
}
|
||||
|
@ -59,71 +48,77 @@ class ProductController {
|
|||
}
|
||||
if (params.maxPrice && params.minPrice) {
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const price = Number(item.price.replace(/€/g, ''))
|
||||
const price = Number(item.price)
|
||||
if (price >= Number(params.minPrice) && price <= Number(params.maxPrice)) {
|
||||
console.log(price);
|
||||
return item
|
||||
}
|
||||
})
|
||||
}
|
||||
if (params.dateExpired) {
|
||||
const [day, month, year] = params.dateExpired.split("/");
|
||||
const dateSearch = new Date(year, month - 1, day);
|
||||
productsFilter = productsFilter.filter(item => {
|
||||
const [day, month, year] = item.dateExpired.split("/");
|
||||
const dateProduct = new Date(year, month - 1, day);
|
||||
if (dateProduct >= dateSearch) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if (Number(params.bigPrice)) {
|
||||
productsFilter.sort((a, b) => {
|
||||
const itemA = Number(a.price.replace(/€/g, ''))
|
||||
const itemB = Number(b.price.replace(/€/g, ''))
|
||||
const itemA = Number(a.price)
|
||||
const itemB = Number(b.price)
|
||||
return itemB - itemA;
|
||||
})
|
||||
}
|
||||
|
||||
if (Number(params.lowPrice)) {
|
||||
productsFilter.sort((a, b) => {
|
||||
const itemA = Number(a.price.replace(/€/g, ''))
|
||||
const itemB = Number(b.price.replace(/€/g, ''))
|
||||
const itemA = Number(a.price)
|
||||
const itemB = Number(b.price)
|
||||
return itemA - itemB;
|
||||
})
|
||||
}
|
||||
|
||||
if (params.isNew) {
|
||||
productsFilter = productsFilter.filter(item => item.isNew === true)
|
||||
if (Number(params.order_descending)) {
|
||||
productsFilter.sort((a, b) => {
|
||||
const itemA = a.order_position
|
||||
const itemB = b.order_position
|
||||
return itemB - itemA;
|
||||
})
|
||||
}
|
||||
|
||||
let productsFilterPages = []
|
||||
const totalItens = params?.itens ? Number(params.itens) : 200
|
||||
const page = params.page ? Number(params.page) : 1
|
||||
const startIndex = (totalItens * page) - totalItens
|
||||
const lastIndex = (totalItens * page)
|
||||
const products = productsFilter.slice(startIndex, lastIndex)
|
||||
productsFilterPages.push({
|
||||
page: page,
|
||||
productsPerPage: products.length,
|
||||
products: products
|
||||
})
|
||||
if (Number(params.order_crescent)) {
|
||||
productsFilter.sort((a, b) => {
|
||||
const itemA = a.order_position
|
||||
const itemB = b.order_position
|
||||
return itemA - itemB;
|
||||
})
|
||||
}
|
||||
|
||||
if (Number(params.isNew)) {
|
||||
console.log(params.isNew);
|
||||
productsFilter = productsFilter.filter(item => item.isNew == Number(params.isNew))
|
||||
}
|
||||
|
||||
/* let productsFilterPages = []
|
||||
const totalItens = params?.itens ? Number(params.itens) : 200
|
||||
const page = params.page ? Number(params.page) : 1
|
||||
const startIndex = (totalItens * page) - totalItens
|
||||
const lastIndex = (totalItens * page)
|
||||
const products = productsFilter.slice(startIndex, lastIndex)
|
||||
productsFilterPages.push({
|
||||
page: page,
|
||||
productsPerPage: products.length,
|
||||
products: products
|
||||
}) */
|
||||
|
||||
return res.status(200).send({
|
||||
data: productsFilterPages
|
||||
data: productsFilter
|
||||
})
|
||||
}
|
||||
|
||||
findBySlug(req, res) {
|
||||
const slug = req.params.slug
|
||||
const products = productsJson
|
||||
const filterSlug = products.filter(item => item.slug === slug)
|
||||
async findById(req, res) {
|
||||
const id = Number(req.params.id)
|
||||
const _products = await db.getProducts();
|
||||
const filterProduct = _products[0].filter(item => item.id === id)
|
||||
|
||||
return res.status(200).send({
|
||||
data: filterSlug
|
||||
data: filterProduct
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ProductController();
|
||||
module.exports = new ProductController();
|
||||
|
|
34
api/db/db.js
|
@ -1,27 +1,27 @@
|
|||
async function connect() {
|
||||
if (global.connection && global.connection.state !== 'disconnected')
|
||||
return global.connection;
|
||||
if (global.connection && global.connection.state !== 'disconnected')
|
||||
return global.connection;
|
||||
|
||||
const host = process.env.HOST;
|
||||
const port = process.env.PORT;
|
||||
const database = process.env.DATABASE;
|
||||
const user = process.env.DB_USER;
|
||||
const password = process.env.DB_PASSWORD;
|
||||
const host = process.env.HOST;
|
||||
const port = process.env.PORT;
|
||||
const database = process.env.DATABASE;
|
||||
const user = process.env.DB_USER;
|
||||
const password = process.env.DB_PASSWORD;
|
||||
|
||||
|
||||
|
||||
const mysql = require("mysql2/promise");
|
||||
const connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + "");
|
||||
console.log("Connected to MySQL!");
|
||||
global.connection = connection;
|
||||
return connection;
|
||||
const mysql = require("mysql2/promise");
|
||||
const connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + "");
|
||||
console.log("Connected to MySQL!");
|
||||
global.connection = connection;
|
||||
return connection;
|
||||
}
|
||||
|
||||
async function getProducts() {
|
||||
const conn = await connect();
|
||||
const [rows] = await conn.query('CALL catalogue_get("2024-01-30", "08001")');
|
||||
|
||||
return rows;
|
||||
async function getProducts(dateExpired, postalCode) {
|
||||
console.log("Query in table MySQL!");
|
||||
jsolis marked this conversation as resolved
pablone
commented
borrar console.log borrar console.log
|
||||
const conn = await connect();
|
||||
const [rows] = await conn.query(`CALL catalogue_get("${dateExpired}", "${postalCode}")`);
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ app.get('/', (req, res) => {
|
|||
|
||||
//Products
|
||||
app.get('/api/products', productController.findAll);
|
||||
app.get('/api/products/slug/:slug', productController.findBySlug);
|
||||
app.get('/api/products/:id', productController.findById);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server listening at http://localhost:${port}`);
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-element-bundle.min.js"
|
||||
defer
|
||||
></script>
|
||||
<script src="https://js.stripe.com/v3" defer></script>
|
||||
<!-- <script src="https://js.stripe.com/v3" defer></script> -->
|
||||
jsolis marked this conversation as resolved
pablone
commented
borrar comentario borrar comentario
|
||||
</head>
|
||||
<body>
|
||||
<!-- quasar:entry-point -->
|
||||
|
|
|
@ -28,7 +28,7 @@ module.exports = configure(function (/* ctx */) {
|
|||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||
boot: ["i18n", "axios" /* , "stripe" */],
|
||||
boot: ["i18n", "axios" /* , { path: "stripe", server: false } */],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
css: ["app.scss"],
|
||||
|
@ -119,7 +119,7 @@ module.exports = configure(function (/* ctx */) {
|
|||
// directives: [],
|
||||
|
||||
// Quasar plugins
|
||||
plugins: ["Meta", "Loading", "Notify"],
|
||||
plugins: ["Meta", "Loading", "Notify", "LocalStorage", "SessionStorage"],
|
||||
},
|
||||
|
||||
// animations: 'all', // --- includes all animations
|
||||
|
|
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 111 KiB |
|
@ -3,15 +3,15 @@ import { boot } from "quasar/wrappers";
|
|||
|
||||
// "async" is optional;
|
||||
// more info on params: https://v2.quasar.dev/quasar-cli/boot-files
|
||||
export default boot(async ({ app, router, store }) => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
export default boot(({ app }) => {
|
||||
const options = {
|
||||
pk: process.env.STRIPE_PUBLISHABLE_KEY,
|
||||
stripeAccount: process.env.STRIPE_ACCOUNT,
|
||||
apiVersion: process.env.API_VERSION,
|
||||
locale: process.env.LOCALE,
|
||||
pk: "pk_test_51OZaJdIK1lTlG93d2y0B81n4XrjvjQwqfIUZ7ggb9wEBa1e4h34GlYFYPwjtGl3OUT7DJZlVNX9EMXaCdOBkIC3T007mLnfvCu",
|
||||
stripeAccount: "acct_1OXQt7GMODwoSxWA",
|
||||
apiVersion: "2023-10-16",
|
||||
locale: "pt-BR",
|
||||
jsolis marked this conversation as resolved
pablone
commented
las credenciales es mejor tenerlas en variables de entorno aunque tenia entendido que no vamos a trabajar con stripe las credenciales es mejor tenerlas en variables de entorno aunque tenia entendido que no vamos a trabajar con stripe
|
||||
};
|
||||
|
||||
app.config.globalProperties.$stripe = StripePlugin.install = options;
|
||||
|
||||
app.use(StripePlugin, options);
|
||||
});
|
||||
|
|
|
@ -1,63 +1,45 @@
|
|||
<script>
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { quasarNotify } from "src/functions/quasarNotify";
|
||||
import { fullCurrentDate } from "src/constants/date";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import { defineComponent, ref } from "vue";
|
||||
import IconCalendar from "../icons/IconCalendar.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "calendar-input",
|
||||
components: { IconCalendar },
|
||||
setup() {
|
||||
const getDate = new Date();
|
||||
const currentDay = getDate.getDate().toString().padStart(2, "0");
|
||||
const currentMonth = getDate.getMonth() + 1;
|
||||
const currentYear = getDate.getFullYear();
|
||||
const fullCurrentDate = `${currentYear}/${currentMonth}/${currentDay}`;
|
||||
|
||||
inheritAttrs: true,
|
||||
props: {
|
||||
setValues: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
setup({ setValues }) {
|
||||
const formStore = useFormStore();
|
||||
const { availability } = storeToRefs(formStore);
|
||||
|
||||
const proxyDate = ref(fullCurrentDate);
|
||||
|
||||
const validationSchema = toTypedSchema(
|
||||
availabilitySchema.pick({ date: true })
|
||||
);
|
||||
const { errors, defineField, values } = useForm({
|
||||
validationSchema,
|
||||
});
|
||||
const [calendar, calendarAttrs] = defineField("date");
|
||||
function updateProxy() {
|
||||
proxyDate.value = fullCurrentDate;
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
availability.value.date = calendar.value;
|
||||
};
|
||||
availability.value.date = values.date;
|
||||
function optionsValidDates(date) {
|
||||
return date >= fullCurrentDate;
|
||||
}
|
||||
|
||||
watch(errors, (newErrors) => {
|
||||
if (newErrors.date) {
|
||||
quasarNotify({ message: newErrors.date, type: "erro" });
|
||||
}
|
||||
});
|
||||
function save() {
|
||||
availability.value.date = proxyDate.value;
|
||||
setValues({ date: proxyDate.value });
|
||||
}
|
||||
|
||||
return {
|
||||
availability,
|
||||
proxyDate,
|
||||
calendar,
|
||||
calendarAttrs,
|
||||
errors,
|
||||
onBlur,
|
||||
updateProxy() {
|
||||
proxyDate.value = availability.value.date;
|
||||
},
|
||||
optionsValidDates(date) {
|
||||
return date >= fullCurrentDate /* && date <= '2019/02/15' */;
|
||||
},
|
||||
save() {
|
||||
availability.value.date = proxyDate.value;
|
||||
calendar.value = proxyDate.value;
|
||||
},
|
||||
updateProxy,
|
||||
optionsValidDates,
|
||||
save,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -76,6 +58,7 @@ export default defineComponent({
|
|||
>
|
||||
<q-date
|
||||
v-model="proxyDate"
|
||||
v-bind="calendarAttrs"
|
||||
:options="optionsValidDates"
|
||||
mask="DD-MM-YYYY"
|
||||
>
|
||||
|
@ -95,16 +78,7 @@ export default defineComponent({
|
|||
|
||||
<div class="custom-block-content">
|
||||
<p class="custom-head-paragraph">¿Cuándo?</p>
|
||||
<!-- <q-input
|
||||
class="custom-date-input"
|
||||
placeholder="Elige una fecha"
|
||||
v-model="calendar"
|
||||
mask="##/##/####"
|
||||
:error="!!errors.date"
|
||||
@blur="onBlur"
|
||||
borderless
|
||||
dense
|
||||
/> -->
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,18 +28,12 @@ export default defineComponent({
|
|||
<li class="footer-list-item">
|
||||
<p class="footer-list-content">
|
||||
Contáctanos <br />
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua
|
||||
<br /><br />
|
||||
Horario: 9 a 14h. y de 15 a 18h.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="footer-list-item">
|
||||
<p class="footer-list-content">
|
||||
Lorem ipsum dolor sit amet,<br />
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -43,7 +43,9 @@ export default defineComponent({
|
|||
|
||||
<nav-links />
|
||||
|
||||
<user-area />
|
||||
<q-no-ssr>
|
||||
<user-area />
|
||||
</q-no-ssr>
|
||||
</div>
|
||||
</q-header>
|
||||
</template>
|
||||
|
|
|
@ -3,27 +3,23 @@ import { defineComponent } from "vue";
|
|||
|
||||
import IconCart from "components/icons/IconCart.vue";
|
||||
import IconHamburger from "components/icons/IconHamburger.vue";
|
||||
/* import IconUser from "components/icons/IconUser.vue";
|
||||
import DropdownGroup from "components/quasar-components/dropdown/DropdownGroup.vue";
|
||||
import DropdownItem from "components/quasar-components/dropdown/DropdownItem.vue"; */
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
|
||||
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
||||
import { useMobileStore } from "stores/mobileNav";
|
||||
|
||||
export default defineComponent({
|
||||
name: "user-area",
|
||||
components: {
|
||||
IconCart,
|
||||
// IconUser,
|
||||
// DropdownGroup,
|
||||
// DropdownItem,
|
||||
IconHamburger,
|
||||
},
|
||||
setup() {
|
||||
const { getItem } = useLocalStorage();
|
||||
|
||||
const mobileStore = useMobileStore();
|
||||
const { handleOpenMobileNav } = mobileStore;
|
||||
const cartStore = useCartStore();
|
||||
const { cartLength } = storeToRefs(cartStore);
|
||||
|
||||
const cartLength = getItem("cart").length;
|
||||
|
||||
return { handleOpenMobileNav, cartLength };
|
||||
},
|
||||
|
|
|
@ -20,6 +20,7 @@ export default defineComponent({
|
|||
setup() {
|
||||
const {
|
||||
onSubmit,
|
||||
setValues,
|
||||
fields: { calendar, calendarAttrs, postalCode, postalCodeAttrs },
|
||||
errors,
|
||||
} = usePostalCalendar({ type: "home" });
|
||||
|
@ -43,6 +44,7 @@ export default defineComponent({
|
|||
screenWidth,
|
||||
errors,
|
||||
onSubmit,
|
||||
setValues,
|
||||
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
|
@ -86,15 +88,12 @@ export default defineComponent({
|
|||
</h1>
|
||||
|
||||
<p class="carousel-header-paragraph">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form @submit="onSubmit" class="carousel-content-body">
|
||||
<div class="carousel-content-item">
|
||||
<!-- <Calendar /> -->
|
||||
<Calendar>
|
||||
<Calendar :setValues="setValues">
|
||||
<q-input
|
||||
borderless
|
||||
class="custom-date-input"
|
||||
|
@ -109,7 +108,6 @@ export default defineComponent({
|
|||
</div>
|
||||
|
||||
<div class="carousel-content-item">
|
||||
<!-- <PostalCode /> -->
|
||||
<PostalCode>
|
||||
<q-input
|
||||
borderless
|
||||
|
|
|
@ -18,7 +18,7 @@ export default defineComponent({
|
|||
<template>
|
||||
<section
|
||||
class="question-container"
|
||||
style="background-image: url('../../assets/question-bg.png')"
|
||||
style="background-image: url('../../assets/question-bg.jpg')"
|
||||
id="question-section"
|
||||
>
|
||||
<Container class="question-container-form">
|
||||
|
@ -26,8 +26,6 @@ export default defineComponent({
|
|||
<LogoWhite />
|
||||
|
||||
<p class="question-paragraph">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore...
|
||||
</p>
|
||||
</header>
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ export default defineComponent({
|
|||
<template>
|
||||
<section
|
||||
class="reasons-container"
|
||||
style="background-image: url('../../assets/reasons-bg.png')"
|
||||
style="background-image: url('../../assets/reasons-bg.jpg')"
|
||||
>
|
||||
<Container class="reasons-container-body container-modded">
|
||||
<header class="reasons-container-header">
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<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>
|
|
@ -46,22 +46,35 @@ export default defineComponent({
|
|||
default: "",
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup({ price, discount }) {
|
||||
const isLoaded = ref(false);
|
||||
const isError = ref(false);
|
||||
const percent = +discount / 100;
|
||||
//const priceWithoutLetter = ~~price.replaceAll("€", "");
|
||||
//const priceWithoutLetter = ~~price?.replaceAll("€", "");
|
||||
const priceWithoutLetter = price;
|
||||
const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent);
|
||||
console.log(price);
|
||||
jsolis marked this conversation as resolved
pablone
commented
borrar console.log borrar console.log
|
||||
|
||||
const onLoad = () => {
|
||||
isLoaded.value = true;
|
||||
};
|
||||
|
||||
return { onLoad, isLoaded, finalValue, priceWithoutLetter };
|
||||
const onError = () => {
|
||||
isError.value = true;
|
||||
};
|
||||
|
||||
return {
|
||||
onLoad,
|
||||
onError,
|
||||
isLoaded,
|
||||
isError,
|
||||
finalValue,
|
||||
priceWithoutLetter,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -82,10 +95,11 @@ export default defineComponent({
|
|||
<img
|
||||
class="card-img"
|
||||
:class="[imgClass]"
|
||||
:src="imgSrc ? imgSrc.replace('https://drive.google.com/file/d/', 'https://drive.google.com/thumbnail?id=').replace('/view?usp=drive_link', '') : '../../assets/empty-img.jpg'"
|
||||
:src="imgSrc && !isError ? imgSrc : '../../assets/empty-img.jpg'"
|
||||
:alt="alt"
|
||||
:key="imgSrc"
|
||||
@load="onLoad"
|
||||
@error="onError"
|
||||
/>
|
||||
<q-skeleton
|
||||
v-if="!isLoaded"
|
||||
|
@ -105,7 +119,7 @@ export default defineComponent({
|
|||
|
||||
<div class="card-values">
|
||||
<p class="price" v-if="finalValue">{{ finalValue }}€</p>
|
||||
<p class="price offer tachado" v-if="price !== finalValue">
|
||||
<p class="price offer tachado" v-if="+price !== finalValue">
|
||||
{{ price }}€
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -30,6 +30,7 @@ export default defineComponent({
|
|||
setup({ modalItem, typeModal }) {
|
||||
const {
|
||||
onSubmit,
|
||||
setValues,
|
||||
fields: {
|
||||
calendar,
|
||||
calendarAttrs,
|
||||
|
@ -61,6 +62,7 @@ export default defineComponent({
|
|||
onSubmit,
|
||||
modalStore,
|
||||
modalTextContent,
|
||||
setValues,
|
||||
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
|
@ -121,7 +123,7 @@ export default defineComponent({
|
|||
v-if="modalItem === 'isOpenAvailability'"
|
||||
class="modal-body-availability"
|
||||
>
|
||||
<Calendar>
|
||||
<Calendar :setValues="setValues">
|
||||
<q-input
|
||||
borderless
|
||||
class="custom-date-input"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const getDate = new Date();
|
||||
export const currentDay = getDate.getDate().toString().padStart(2, "0");
|
||||
export const currentMonth = (getDate.getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, "0");
|
||||
export const currentYear = getDate.getFullYear();
|
||||
export const fullCurrentDate = `${currentYear}/${currentMonth}/${currentDay}`;
|
|
@ -0,0 +1,7 @@
|
|||
export function invertDate(date = "") {
|
||||
const regex = /-|\//g;
|
||||
const [day = "", month = " ", year = ""] = date.split(regex);
|
||||
const invertedDate = `${year}-${month}-${day}`;
|
||||
|
||||
return invertedDate;
|
||||
}
|
|
@ -26,10 +26,20 @@ export function quasarNotify({ message = "", type, timeout = 1000 }) {
|
|||
icon: "report_problem",
|
||||
timeout,
|
||||
}),
|
||||
info: () => Notify.create({
|
||||
message,
|
||||
color: "info",
|
||||
position: "top",
|
||||
icon: "info",
|
||||
timeout,
|
||||
}),
|
||||
default: () => {
|
||||
console.error(`Type is invalid! TYPE: ${type}`)
|
||||
}
|
||||
};
|
||||
|
||||
if (type) {
|
||||
return obj[type]() || console.error(`Type is invalid! TYPE: ${type}`);
|
||||
return obj[type]() || obj['default']();
|
||||
}
|
||||
console.error("Type is required, success, warning or erro");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { useForm } from "vee-validate";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { checkoutSchema } from "src/utils/zod/schemas";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useLocalStorage } from "./useLocalStorage";
|
||||
|
||||
export function useCheckoutForm() {
|
||||
const { getItem } = useLocalStorage();
|
||||
const { push } = useRouter()
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { handleCheckoutData } = formStore;
|
||||
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
|
||||
validationSchema: toTypedSchema(checkoutSchema),
|
||||
initialValues: {
|
||||
paymentMethod: "stripe",
|
||||
terms: false,
|
||||
},
|
||||
});
|
||||
|
||||
const [name, nameAttrs] = defineField("name");
|
||||
const [surname, surnameAttrs] = defineField("surname");
|
||||
const [address, addressAttrs] = defineField("address");
|
||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
||||
const [phone, phoneAttrs] = defineField("phone");
|
||||
const [city, cityAttrs] = defineField("city");
|
||||
const [province, provinceAttrs] = defineField("province");
|
||||
const [senderName, senderNameAttrs] = defineField("senderName");
|
||||
const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif");
|
||||
const [senderEmail, senderEmailAttrs] = defineField("senderEmail");
|
||||
const [senderPhone, senderPhoneAttrs] = defineField("senderPhone");
|
||||
const [senderNotes, senderNotesAttrs] = defineField("senderNotes");
|
||||
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
|
||||
const [terms, termsAttrs] = defineField("terms");
|
||||
|
||||
const provinceOptions = ref([
|
||||
jsolis marked this conversation as resolved
pablone
commented
para las provincias puedes hacer esta consulta: SELECT p.id, p.name, c.code, c.country para las provincias puedes hacer esta consulta:
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')
|
||||
{ code: "01", name: "Araba/Álava" },
|
||||
{ code: "02", name: "Albacete" },
|
||||
{ code: "03", name: "Alicante/Alacant" },
|
||||
{ code: "04", name: "Almería" },
|
||||
{ code: "05", name: "Ávila" },
|
||||
{ code: "06", name: "Badajoz" },
|
||||
{ code: "07", name: "Balears, Illes" },
|
||||
{ code: "08", name: "Barcelona" },
|
||||
{ code: "09", name: "Burgos" },
|
||||
{ code: "10", name: "Cáceres" },
|
||||
{ code: "11", name: "Cádiz" },
|
||||
{ code: "12", name: "Castellón/Castelló" },
|
||||
{ code: "13", name: "Ciudad Real" },
|
||||
{ code: "14", name: "Córdoba" },
|
||||
{ code: "15", name: "Coruña, A" },
|
||||
{ code: "16", name: "Cuenca" },
|
||||
{ code: "17", name: "Girona" },
|
||||
{ code: "18", name: "Granada" },
|
||||
{ code: "19", name: "Guadalajara" },
|
||||
{ code: "20", name: "Gipuzkoa" },
|
||||
{ code: "21", name: "Huelva" },
|
||||
{ code: "22", name: "Huesca" },
|
||||
{ code: "23", name: "Jaén" },
|
||||
{ code: "24", name: "León" },
|
||||
{ code: "25", name: "Lleida" },
|
||||
{ code: "26", name: "Rioja, La" },
|
||||
{ code: "27", name: "Lugo" },
|
||||
{ code: "28", name: "Madrid" },
|
||||
{ code: "29", name: "Málaga" },
|
||||
{ code: "30", name: "Murcia" },
|
||||
{ code: "31", name: "Navarra" },
|
||||
{ code: "32", name: "Ourense" },
|
||||
{ code: "33", name: "Asturias" },
|
||||
{ code: "34", name: "Palencia" },
|
||||
{ code: "35", name: "Palmas, Las" },
|
||||
{ code: "36", name: "Pontevedra" },
|
||||
{ code: "37", name: "Salamanca" },
|
||||
{ code: "38", name: "Santa Cruz de Tenerife" },
|
||||
{ code: "39", name: "Cantabria" },
|
||||
{ code: "40", name: "Segovia" },
|
||||
{ code: "41", name: "Sevilla" },
|
||||
{ code: "42", name: "Soria" },
|
||||
{ code: "43", name: "Tarragona" },
|
||||
{ code: "44", name: "Teruel" },
|
||||
{ code: "45", name: "Toledo" },
|
||||
{ code: "46", name: "Valencia/València" },
|
||||
{ code: "47", name: "Valladolid" },
|
||||
{ code: "48", name: "Bizkaia" },
|
||||
{ code: "49", name: "Zamora" },
|
||||
{ code: "50", name: "Zaragoza" },
|
||||
{ code: "51", name: "Ceuta" },
|
||||
{ code: "52", name: "Melilla" },
|
||||
]);
|
||||
|
||||
const stepActive = reactive({ data: 1 });
|
||||
const stepList = reactive({
|
||||
data: [
|
||||
{
|
||||
value: 1,
|
||||
name: "Paso 1",
|
||||
description: "Datos de facturación",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
name: "Paso 2",
|
||||
description: "Confirmación",
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
name: "Paso 3",
|
||||
description: "Pago",
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
const stepsFormated = computed(() => {
|
||||
return stepList["data"].map((step) => {
|
||||
if (step.value === stepActive["data"]) {
|
||||
return { ...step, active: true };
|
||||
}
|
||||
return step;
|
||||
});
|
||||
});
|
||||
|
||||
const handleClickStep = (value) => {
|
||||
stepActive["data"] = value;
|
||||
};
|
||||
|
||||
const checkoutBlock = ref(true);
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
handleCheckoutData(values);
|
||||
stepList.data[2].active = true;
|
||||
checkoutBlock.value = false;
|
||||
resetForm();
|
||||
});
|
||||
|
||||
const cart = getItem("cart");
|
||||
const totalPrice = ref(0)
|
||||
totalPrice.value = cart?.reduce((acc, { price }) => {
|
||||
if (price) {
|
||||
const priceWithoutLetter = price?.replace("€", "");
|
||||
return +priceWithoutLetter + acc;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
handleClickStep,
|
||||
provinceOptions,
|
||||
stepsFormated,
|
||||
stepList,
|
||||
checkoutBlock,
|
||||
cart,
|
||||
totalPrice,
|
||||
formState: {
|
||||
meta,
|
||||
errors,
|
||||
onSubmit,
|
||||
submitLoading: ref(false),
|
||||
},
|
||||
fields: {
|
||||
name,
|
||||
nameAttrs,
|
||||
surname,
|
||||
surnameAttrs,
|
||||
address,
|
||||
addressAttrs,
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
phone,
|
||||
phoneAttrs,
|
||||
city,
|
||||
cityAttrs,
|
||||
province,
|
||||
provinceAttrs,
|
||||
senderName,
|
||||
senderNameAttrs,
|
||||
senderCifNif,
|
||||
senderCifNifAttrs,
|
||||
senderEmail,
|
||||
senderEmailAttrs,
|
||||
senderPhone,
|
||||
senderPhoneAttrs,
|
||||
senderNotes,
|
||||
senderNotesAttrs,
|
||||
paymentMethod,
|
||||
paymentMethodAttrs,
|
||||
terms,
|
||||
termsAttrs,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { LocalStorage } from "quasar";
|
||||
|
||||
export function useLocalStorage() {
|
||||
const addItem = (key, value) => {
|
||||
LocalStorage.set(`@${key}`, value);
|
||||
};
|
||||
|
||||
const getItem = (key) => {
|
||||
const data = JSON.parse(LocalStorage.getItem(`@${key}`));
|
||||
|
||||
|
||||
return (data || []);
|
||||
};
|
||||
|
||||
const removeItem = (key) => {
|
||||
LocalStorage.remove(`@${key}`);
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
addItem,
|
||||
getItem,
|
||||
removeItem,
|
||||
};
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useForm } from "vee-validate";
|
||||
import { watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
import { apiBack } from "src/boot/axios";
|
||||
import { invertDate } from "src/functions/invertDate";
|
||||
import { quasarNotify } from "src/functions/quasarNotify";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
|
@ -13,38 +13,50 @@ import { useRangePriceStore } from "src/stores/rangePrice";
|
|||
import { availabilitySchema } from "src/utils/zod/schemas";
|
||||
import { rangePriceSchema } from "src/utils/zod/schemas/rangePriceSchema";
|
||||
|
||||
/**
|
||||
* Custom hook for managing the postal and calendar functionality.
|
||||
*
|
||||
* @param {Object} options - The options for the hook.
|
||||
* @param {string} options.modalItem - The modal item isOpenAvailability || isOpenFilters.
|
||||
* @param {string} options.type - The type of the hook. home || product || availability || filter
|
||||
* @returns {Object} - The hook functions and data.
|
||||
*/
|
||||
export function usePostalCalendar({ modalItem = "", type = "home" }) {
|
||||
const { push } = useRouter();
|
||||
const route = useRoute();
|
||||
const { push } = useRouter()
|
||||
|
||||
const rangePriceStore = useRangePriceStore();
|
||||
const { rangeValue } = storeToRefs(rangePriceStore);
|
||||
|
||||
const modalStore = useModalStore();
|
||||
const { isOpenAvailability } = storeToRefs(modalStore);
|
||||
const { openModal } = modalStore
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { availability, sortProductFilters } = storeToRefs(formStore);
|
||||
const { sortProductFilters } = storeToRefs(formStore);
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { addToCart, getProducts } = cartStore;
|
||||
const { currentProduct, products } = storeToRefs(cartStore);
|
||||
const { products, homeSection } = storeToRefs(cartStore);
|
||||
|
||||
const min = 0;
|
||||
const max = 200;
|
||||
const category = ref(route.path.split("/")[2])
|
||||
|
||||
const { handleSubmit, handleReset, defineField, errors } = useForm({
|
||||
validationSchema: toTypedSchema(
|
||||
type !== "filter" ? availabilitySchema : rangePriceSchema
|
||||
),
|
||||
initialValues: {
|
||||
range: {
|
||||
min,
|
||||
max,
|
||||
const { handleSubmit, handleReset, defineField, errors, setValues } = useForm(
|
||||
{
|
||||
validationSchema: toTypedSchema(
|
||||
type !== "filter" ? availabilitySchema : rangePriceSchema
|
||||
),
|
||||
initialValues: {
|
||||
range: {
|
||||
min,
|
||||
max,
|
||||
},
|
||||
postalCode: "",
|
||||
date: "",
|
||||
},
|
||||
postalCode: "",
|
||||
date: "",
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
const [calendar, calendarAttrs] = defineField("date");
|
||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
||||
const [priceRange, priceRangeAttrs] = defineField("range");
|
||||
|
@ -56,6 +68,8 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
|
|||
quasarNotify({ message: newErrors.postalCode, type: "erro" }),
|
||||
date: () => quasarNotify({ message: newErrors.date, type: "erro" }),
|
||||
range: () => quasarNotify({ message: newErrors.range, type: "erro" }),
|
||||
dedication: () =>
|
||||
quasarNotify({ message: newErrors.dedication, type: "erro" }),
|
||||
};
|
||||
const keys = Object.keys(newErrors);
|
||||
keys.forEach((key) => {
|
||||
|
@ -63,47 +77,73 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
|
|||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
[() => route.path, () => sortProductFilters.value],
|
||||
([newPath]) => {
|
||||
const categoryPath = newPath.split("/")[2];
|
||||
category.value = categoryPath;
|
||||
}
|
||||
);
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
const postalAndDateParams = {
|
||||
postalCode: values.postalCode,
|
||||
dateExpired: values.date,
|
||||
dateExpired: invertDate(values.date),
|
||||
};
|
||||
|
||||
const categoryObj = {
|
||||
plantas: "Floranet Plantas",
|
||||
ramos: "Floranet Ramos",
|
||||
};
|
||||
|
||||
const objVal = {
|
||||
home: async () => {
|
||||
console.log(type);
|
||||
|
||||
getProducts(
|
||||
await getProducts(
|
||||
{
|
||||
itens: 30,
|
||||
category: 1,
|
||||
postalCode: values.postalCode,
|
||||
dateExpired: values.date,
|
||||
dateExpired: invertDate(values.date),
|
||||
},
|
||||
() => push("/categoria/ramos")
|
||||
() => homeSection.value.scrollIntoView()
|
||||
);
|
||||
},
|
||||
product: async () => {
|
||||
console.log(type);
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { data },
|
||||
} = await apiBack.get(`products/slug/${currentProduct.value.id}`, {
|
||||
params: postalAndDateParams,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
push("/");
|
||||
await getProducts(postalAndDateParams);
|
||||
|
||||
const hasProduct = products.value.data.some((item) => {
|
||||
const date = new Date(item.dateExpired);
|
||||
const day = date.getDate();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const year = date.getFullYear();
|
||||
const dateExpired = `${day}/${month}/${year}`;
|
||||
|
||||
const id = +route.path.split('/')[2];
|
||||
|
||||
return item.postalCode === values.postalCode && item.id === id && values.date <= dateExpired
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
getProducts({
|
||||
itens: 20,
|
||||
await getProducts({
|
||||
postalCode: values.postalCode,
|
||||
dateExpired: values.date,
|
||||
dateExpired: invertDate(values.date),
|
||||
});
|
||||
},
|
||||
filter: async () => {
|
||||
|
@ -112,26 +152,21 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
|
|||
rangeValue.value.max = values.range.max;
|
||||
rangeValue.value.min = values.range.min;
|
||||
|
||||
const category = sortProductFilters.value.category;
|
||||
const categoryObj = {
|
||||
plantas: 1,
|
||||
ramos: 2,
|
||||
};
|
||||
|
||||
const params = {
|
||||
itens: 20,
|
||||
category: categoryObj[category],
|
||||
type: categoryObj[category.value],
|
||||
minPrice: values.range.min,
|
||||
maxPrice: values.range.max,
|
||||
};
|
||||
|
||||
getProducts(params);
|
||||
await getProducts(params);
|
||||
},
|
||||
default: () => {
|
||||
console.error(
|
||||
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
|
||||
);
|
||||
},
|
||||
};
|
||||
objVal[type]() ||
|
||||
console.error(
|
||||
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
|
||||
);
|
||||
objVal[type]() || objVal["default"]();
|
||||
|
||||
if (modalItem) {
|
||||
modalStore[modalItem] = false;
|
||||
|
@ -141,6 +176,7 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
|
|||
|
||||
return {
|
||||
onSubmit,
|
||||
setValues,
|
||||
modalStore,
|
||||
fields: {
|
||||
calendar,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
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 }
|
||||
}
|
|
@ -27,9 +27,11 @@ export default defineComponent({
|
|||
</q-no-ssr>
|
||||
<mobile-nav />
|
||||
|
||||
<q-page-container class="no-padding more product-layout">
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
<q-no-ssr>
|
||||
<q-page-container class="no-padding more product-layout">
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
</q-no-ssr>
|
||||
|
||||
<reasons-section />
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ export function mockGenerator({ length }) {
|
|||
slug: fakerES.commerce.isbn({ separator: "-", variant: 13 }),
|
||||
category: fakerES.number.int({ min: 1, max: 2 }),
|
||||
postalCode: "12345",
|
||||
dateExpired: "30/01/2024",
|
||||
dateExpired: "2024-01-30",
|
||||
images: Array.from(
|
||||
{ length: fakerES.number.int({ min: 2, max: 6 }) },
|
||||
() => fakerES.image.urlPicsumPhotos()
|
||||
|
|
|
@ -12,8 +12,10 @@ import DudasSection from "src/components/sections/DudasSection.vue";
|
|||
import Card from "src/components/ui/Card.vue";
|
||||
import Container from "src/components/ui/Container.vue";
|
||||
import Modal from "src/components/ui/Modal.vue";
|
||||
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { useMobileStore } from "src/stores/mobileNav";
|
||||
import { useModalStore } from "src/stores/modalStore";
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -32,6 +34,9 @@ export default defineComponent({
|
|||
setup() {
|
||||
const route = useRoute();
|
||||
|
||||
const mobileStore = useMobileStore();
|
||||
const { screenWidth } = storeToRefs(mobileStore);
|
||||
|
||||
const modalStore = useModalStore();
|
||||
const { openModal } = modalStore;
|
||||
|
||||
|
@ -66,8 +71,8 @@ export default defineComponent({
|
|||
latest: "más recientes",
|
||||
};
|
||||
const categoryObj = {
|
||||
plantas: 1,
|
||||
ramos: 2,
|
||||
plantas: "Floranet Plantas",
|
||||
ramos: "Floranet Ramos",
|
||||
};
|
||||
|
||||
watch(availability, (newDate) => {
|
||||
|
@ -83,14 +88,13 @@ export default defineComponent({
|
|||
sortProductFilters.value.category = categoryPath;
|
||||
|
||||
const params = {
|
||||
category: categoryObj[categoryPath],
|
||||
itens: window.screen.width <= 445 ? 16 : 20,
|
||||
type: categoryObj[categoryPath],
|
||||
};
|
||||
const paramsObj = {
|
||||
"lowest-price": () => (params.lowPrice = 1),
|
||||
"highest-price": () => (params.bigPrice = 1),
|
||||
latest: () => (params.isNew = 1),
|
||||
// recommended: () => params.featured = 1,
|
||||
recommended: () => (params.recommend = 1),
|
||||
};
|
||||
if (newOrder) {
|
||||
paramsObj[newOrder]();
|
||||
|
@ -104,8 +108,7 @@ export default defineComponent({
|
|||
const categoryPath = route.path.split("/")[2];
|
||||
|
||||
await getProducts({
|
||||
category: categoryObj[categoryPath],
|
||||
itens: window.screen.width <= 445 ? 16 : 20,
|
||||
type: categoryObj[categoryPath],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -122,11 +125,12 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
return {
|
||||
sortProductFilters,
|
||||
openOrderFilter,
|
||||
openModal,
|
||||
sortProductFilters,
|
||||
availability,
|
||||
isOpenOrder,
|
||||
screenWidth,
|
||||
modalStore,
|
||||
orderText,
|
||||
products,
|
||||
|
@ -145,9 +149,6 @@ export default defineComponent({
|
|||
{{ sortProductFilters.category }} para obsequiar
|
||||
</h3>
|
||||
<p class="product-header-paragraph">
|
||||
Descripción SEO: Lorem ipsum dolor sit amet, consectetur
|
||||
adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -218,25 +219,14 @@ export default defineComponent({
|
|||
|
||||
<div class="products-section-body">
|
||||
<Container cardContainer class="category-container">
|
||||
<template
|
||||
v-for="{
|
||||
images,
|
||||
discount,
|
||||
isNew,
|
||||
name,
|
||||
price,
|
||||
slug,
|
||||
id,
|
||||
} in products.data.products"
|
||||
:key="id"
|
||||
>
|
||||
<template v-for="item in products.data" :key="item?.id">
|
||||
<Card
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="images[0]"
|
||||
:isNew="isNew"
|
||||
:id="slug"
|
||||
v-if="item"
|
||||
:price="item.price"
|
||||
:title="item.name"
|
||||
:imgSrc="item.image"
|
||||
:isNew="item.isNew"
|
||||
:id="item.id"
|
||||
/>
|
||||
</template>
|
||||
</Container>
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
<script>
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useForm } from "vee-validate";
|
||||
import { computed, defineComponent, reactive, ref } from "vue";
|
||||
import { defineComponent, onBeforeMount, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import Container from "src/components/ui/Container.vue";
|
||||
import { useCartStore } from "src/stores/cart";
|
||||
import { useFormStore } from "src/stores/forms";
|
||||
import { checkoutSchema } from "src/utils/zod/schemas/checkoutSchema";
|
||||
import { useCheckoutForm } from "src/hooks/useCheckoutForm";
|
||||
|
||||
export default defineComponent({
|
||||
name: "CheckoutPage",
|
||||
|
@ -17,150 +12,75 @@ export default defineComponent({
|
|||
},
|
||||
setup() {
|
||||
const { push } = useRouter();
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { cart, cartList, totalPrice, cartLength } = storeToRefs(cartStore);
|
||||
|
||||
if (cartLength.value === 0) return push("/");
|
||||
|
||||
const formStore = useFormStore();
|
||||
const { handleCheckoutData } = formStore;
|
||||
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
|
||||
validationSchema: toTypedSchema(checkoutSchema),
|
||||
initialValues: {
|
||||
paymentMethod: "stripe",
|
||||
terms: false,
|
||||
const {
|
||||
provinceOptions,
|
||||
handleClickStep,
|
||||
stepsFormated,
|
||||
stepList,
|
||||
checkoutBlock,
|
||||
cart,
|
||||
totalPrice,
|
||||
formState: { errors, meta, onSubmit, submitLoading },
|
||||
fields: {
|
||||
name,
|
||||
nameAttrs,
|
||||
surname,
|
||||
surnameAttrs,
|
||||
address,
|
||||
addressAttrs,
|
||||
postalCode,
|
||||
postalCodeAttrs,
|
||||
phone,
|
||||
phoneAttrs,
|
||||
city,
|
||||
cityAttrs,
|
||||
province,
|
||||
provinceAttrs,
|
||||
senderName,
|
||||
senderNameAttrs,
|
||||
senderCifNif,
|
||||
senderCifNifAttrs,
|
||||
senderEmail,
|
||||
senderEmailAttrs,
|
||||
senderPhone,
|
||||
senderPhoneAttrs,
|
||||
senderNotes,
|
||||
senderNotesAttrs,
|
||||
paymentMethod,
|
||||
paymentMethodAttrs,
|
||||
terms,
|
||||
termsAttrs,
|
||||
},
|
||||
});
|
||||
const [name, nameAttrs] = defineField("name");
|
||||
const [surname, surnameAttrs] = defineField("surname");
|
||||
const [address, addressAttrs] = defineField("address");
|
||||
const [postalCode, postalCodeAttrs] = defineField("postalCode");
|
||||
const [phone, phoneAttrs] = defineField("phone");
|
||||
const [city, cityAttrs] = defineField("city");
|
||||
const [province, provinceAttrs] = defineField("province");
|
||||
const [senderName, senderNameAttrs] = defineField("senderName");
|
||||
const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif");
|
||||
const [senderEmail, senderEmailAttrs] = defineField("senderEmail");
|
||||
const [senderPhone, senderPhoneAttrs] = defineField("senderPhone");
|
||||
const [senderNotes, senderNotesAttrs] = defineField("senderNotes");
|
||||
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
|
||||
const [terms, termsAttrs] = defineField("terms");
|
||||
} = useCheckoutForm();
|
||||
|
||||
const stepActive = reactive({ data: 1 });
|
||||
const stepList = reactive({
|
||||
data: [
|
||||
{
|
||||
value: 1,
|
||||
name: "Paso 1",
|
||||
description: "Datos de facturación",
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
name: "Paso 2",
|
||||
description: "Confirmación",
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
name: "Paso 3",
|
||||
description: "Pago",
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
const checkoutBlock = ref(true);
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
handleCheckoutData(values);
|
||||
stepList.data[2].active = true;
|
||||
checkoutBlock.value = false;
|
||||
resetForm();
|
||||
onBeforeMount(() => {
|
||||
if (cart.length === 0) return push("/");
|
||||
});
|
||||
|
||||
const handleClickStep = (value) => {
|
||||
stepActive["data"] = value;
|
||||
const isError = ref(false);
|
||||
const onError = () => {
|
||||
isError.value = true;
|
||||
};
|
||||
|
||||
const stepsFormated = computed(() => {
|
||||
return stepList["data"].map((step) => {
|
||||
if (step.value === stepActive["data"]) {
|
||||
return { ...step, active: true };
|
||||
}
|
||||
return step;
|
||||
});
|
||||
});
|
||||
|
||||
const provinceOptions = ref([
|
||||
{ code: "01", name: "Araba/Álava" },
|
||||
{ code: "02", name: "Albacete" },
|
||||
{ code: "03", name: "Alicante/Alacant" },
|
||||
{ code: "04", name: "Almería" },
|
||||
{ code: "05", name: "Ávila" },
|
||||
{ code: "06", name: "Badajoz" },
|
||||
{ code: "07", name: "Balears, Illes" },
|
||||
{ code: "08", name: "Barcelona" },
|
||||
{ code: "09", name: "Burgos" },
|
||||
{ code: "10", name: "Cáceres" },
|
||||
{ code: "11", name: "Cádiz" },
|
||||
{ code: "12", name: "Castellón/Castelló" },
|
||||
{ code: "13", name: "Ciudad Real" },
|
||||
{ code: "14", name: "Córdoba" },
|
||||
{ code: "15", name: "Coruña, A" },
|
||||
{ code: "16", name: "Cuenca" },
|
||||
{ code: "17", name: "Girona" },
|
||||
{ code: "18", name: "Granada" },
|
||||
{ code: "19", name: "Guadalajara" },
|
||||
{ code: "20", name: "Gipuzkoa" },
|
||||
{ code: "21", name: "Huelva" },
|
||||
{ code: "22", name: "Huesca" },
|
||||
{ code: "23", name: "Jaén" },
|
||||
{ code: "24", name: "León" },
|
||||
{ code: "25", name: "Lleida" },
|
||||
{ code: "26", name: "Rioja, La" },
|
||||
{ code: "27", name: "Lugo" },
|
||||
{ code: "28", name: "Madrid" },
|
||||
{ code: "29", name: "Málaga" },
|
||||
{ code: "30", name: "Murcia" },
|
||||
{ code: "31", name: "Navarra" },
|
||||
{ code: "32", name: "Ourense" },
|
||||
{ code: "33", name: "Asturias" },
|
||||
{ code: "34", name: "Palencia" },
|
||||
{ code: "35", name: "Palmas, Las" },
|
||||
{ code: "36", name: "Pontevedra" },
|
||||
{ code: "37", name: "Salamanca" },
|
||||
{ code: "38", name: "Santa Cruz de Tenerife" },
|
||||
{ code: "39", name: "Cantabria" },
|
||||
{ code: "40", name: "Segovia" },
|
||||
{ code: "41", name: "Sevilla" },
|
||||
{ code: "42", name: "Soria" },
|
||||
{ code: "43", name: "Tarragona" },
|
||||
{ code: "44", name: "Teruel" },
|
||||
{ code: "45", name: "Toledo" },
|
||||
{ code: "46", name: "Valencia/València" },
|
||||
{ code: "47", name: "Valladolid" },
|
||||
{ code: "48", name: "Bizkaia" },
|
||||
{ code: "49", name: "Zamora" },
|
||||
{ code: "50", name: "Zaragoza" },
|
||||
{ code: "51", name: "Ceuta" },
|
||||
{ code: "52", name: "Melilla" },
|
||||
]);
|
||||
|
||||
return {
|
||||
handleClickStep,
|
||||
stepsFormated,
|
||||
onSubmit,
|
||||
stepList,
|
||||
onError,
|
||||
|
||||
checkoutBlock,
|
||||
stepsFormated,
|
||||
provinceOptions,
|
||||
totalPrice,
|
||||
cartList,
|
||||
|
||||
step: ref(1),
|
||||
stepList,
|
||||
cart,
|
||||
checkoutBlock,
|
||||
step: ref(1),
|
||||
submitLoading,
|
||||
successURL: ref(""),
|
||||
cancelURL: ref(""),
|
||||
meta,
|
||||
errors,
|
||||
isError,
|
||||
|
||||
name,
|
||||
nameAttrs,
|
||||
surname,
|
||||
|
@ -199,19 +119,18 @@ export default defineComponent({
|
|||
<Container tag="section">
|
||||
<header class="header-title" :class="!checkoutBlock && 'success'">
|
||||
<h1 class="pege-title" v-if="checkoutBlock">
|
||||
{{ checkoutBlock && "¿A quién y dónde lo entregamos?" }}
|
||||
{{ !checkoutBlock && "¡Muchas gracias Jerom!" }}
|
||||
{{
|
||||
checkoutBlock
|
||||
? "¿A quién y dónde lo entregamos?"
|
||||
: '"¡Muchas gracias Jerom!"'
|
||||
}}
|
||||
</h1>
|
||||
|
||||
<p class="page-subtitle checkout" v-if="checkoutBlock">
|
||||
{{
|
||||
checkoutBlock &&
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
}}
|
||||
|
||||
{{
|
||||
!checkoutBlock &&
|
||||
"¡Tu pedido se ha realizado con éxito! Gracias por confiar en nosotros, en breves recibirás un correo con la confirmación de tu pedido."
|
||||
checkoutBlock
|
||||
? "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>
|
||||
</header>
|
||||
|
@ -260,7 +179,6 @@ export default defineComponent({
|
|||
<div class="checkout-content">
|
||||
<div class="checkout-form">
|
||||
<q-form
|
||||
action=""
|
||||
method="post"
|
||||
id="checkout-form"
|
||||
@submit.prevent="onSubmit"
|
||||
|
@ -477,12 +395,12 @@ export default defineComponent({
|
|||
<ul class="checkout-summary-list">
|
||||
<li
|
||||
class="checkout-summary-item"
|
||||
v-for="({ title, price, quantity }, index) in cartList"
|
||||
v-for="({ name, price }, index) in cart"
|
||||
:key="index"
|
||||
>
|
||||
<p>
|
||||
{{ title }} ({{ quantity }})
|
||||
<span>{{ price }}</span>
|
||||
{{ name }}
|
||||
<span>{{ price }}€</span>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -545,47 +463,49 @@ export default defineComponent({
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="!checkoutBlock" class="checkout-success" id="success-block">
|
||||
<h6 class="checkout-success-title green-text">
|
||||
Has efectuado la siguiente compra
|
||||
</h6>
|
||||
<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="({ title, price, quantity }, index) in cartList"
|
||||
:key="index"
|
||||
class="checkout-success-item"
|
||||
>
|
||||
<div class="checkout-item-content">
|
||||
<div class="checkout-product-details">
|
||||
<img
|
||||
src="../assets/checkout-flower.png"
|
||||
alt="product"
|
||||
class="checkout-product-img"
|
||||
/>
|
||||
<p class="checkout-product-title">
|
||||
{{ title }} ({{ quantity }})
|
||||
</p>
|
||||
<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>
|
||||
|
||||
<p class="checkout-product-price">
|
||||
{{ price }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<footer class="checkout-success-footer">
|
||||
<p class="checkout-success-paragraph">Total</p>
|
||||
<p class="checkout-success-paragraph">
|
||||
{{ totalPrice?.toFixed(2) }}€
|
||||
</p>
|
||||
</footer>
|
||||
</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>
|
||||
</template>
|
||||
</div>
|
||||
</Container>
|
||||
</q-page>
|
||||
|
|
|
@ -9,11 +9,8 @@ export default defineComponent({
|
|||
const { push } = useRouter();
|
||||
|
||||
function startCountdown() {
|
||||
// Cria um intervalo que executa a cada segundo
|
||||
const interval = setInterval(() => {
|
||||
// Decrementa o valor de count
|
||||
counter.value--;
|
||||
// Se o valor de count for zero, para o intervalo
|
||||
if (counter.value === 1) {
|
||||
clearInterval(interval);
|
||||
push("/");
|
||||
|
@ -21,7 +18,6 @@ export default defineComponent({
|
|||
}, 1000);
|
||||
}
|
||||
|
||||
// Chama a função para iniciar o contador quando o componente for montado
|
||||
onMounted(startCountdown);
|
||||
|
||||
return {
|
||||
|
|
|
@ -21,9 +21,10 @@ export default defineComponent({
|
|||
const mobileStore = useMobileStore();
|
||||
const { isCarouselVisible, isOpenNav, screenWidth } =
|
||||
storeToRefs(mobileStore);
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { getProducts } = cartStore;
|
||||
const { products } = storeToRefs(cartStore);
|
||||
const { products, homeSection } = storeToRefs(cartStore);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await getProducts();
|
||||
|
@ -41,6 +42,7 @@ export default defineComponent({
|
|||
isCarouselVisible,
|
||||
slidesContent,
|
||||
screenWidth,
|
||||
homeSection,
|
||||
isOpenNav,
|
||||
products,
|
||||
};
|
||||
|
@ -54,35 +56,29 @@ export default defineComponent({
|
|||
<VerticalCarouselImgs :imgsArr="slidesContent" class="home-carousel" />
|
||||
</q-no-ssr>
|
||||
|
||||
<section class="products-section">
|
||||
<section class="products-section" ref="homeSection">
|
||||
<header class="products-section-header section-header">
|
||||
<h3 class="products-header-title subtitle">
|
||||
Diseños de ramos más vendidos
|
||||
</h3>
|
||||
|
||||
<p class="products-header-paragraph section-paragraph">
|
||||
Descripción SEO: Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="products-body">
|
||||
<Container cardContainer>
|
||||
<template
|
||||
v-for="(
|
||||
{ id, slug, name, price, images, isNew, discount }, i
|
||||
) in products.data.products"
|
||||
v-for="({ id, name, price, image, isNew }, i) in products?.data"
|
||||
:key="id"
|
||||
>
|
||||
<Card
|
||||
v-if="i < 8"
|
||||
:id="slug"
|
||||
:id="id"
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="images[0]"
|
||||
:imgSrc="image"
|
||||
:isNew="isNew"
|
||||
:key="id"
|
||||
imgClass="list-products"
|
||||
size="md-card"
|
||||
/>
|
||||
|
@ -102,9 +98,6 @@ export default defineComponent({
|
|||
</h3>
|
||||
|
||||
<p class="products-selection-paragraph section-paragraph">
|
||||
Descripción SEO: Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
|
@ -112,19 +105,15 @@ export default defineComponent({
|
|||
<q-no-ssr>
|
||||
<Swiper>
|
||||
<template
|
||||
v-for="(
|
||||
{ slug, discount, isNew, price, name, images }, i
|
||||
) in products.data.products"
|
||||
:key="slug"
|
||||
v-for="({ id, isNew, price, name, image }, i) in products.data"
|
||||
:key="id"
|
||||
>
|
||||
<swiper-slide class="swiper-slide" v-if="i < 10">
|
||||
<Card
|
||||
:id="slug"
|
||||
:key="slug"
|
||||
:id="id"
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="images[0]"
|
||||
:imgSrc="image"
|
||||
:isNew="isNew"
|
||||
imgClass="carousel"
|
||||
size="lg-card"
|
||||
|
@ -177,6 +166,7 @@ export default defineComponent({
|
|||
.products-section {
|
||||
text-align: center;
|
||||
margin-bottom: 87px;
|
||||
scroll-margin: 30px;
|
||||
|
||||
& .products-section-header {
|
||||
& .products-header-title {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<script>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useMeta } from "quasar";
|
||||
import { useForm } from "vee-validate";
|
||||
import { defineComponent, onBeforeMount, reactive, ref, watch } from "vue";
|
||||
import { defineComponent, onBeforeMount, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue";
|
||||
|
@ -19,7 +18,7 @@ import Card from "components/ui/Card.vue";
|
|||
import Container from "components/ui/Container.vue";
|
||||
import Modal from "components/ui/Modal.vue";
|
||||
|
||||
import { dedicationSchema } from "src/utils/zod/schemas";
|
||||
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
||||
import { useCartStore } from "stores/cart";
|
||||
import { useModalStore } from "stores/modalStore";
|
||||
|
||||
|
@ -43,48 +42,30 @@ export default defineComponent({
|
|||
setup() {
|
||||
const route = useRoute();
|
||||
|
||||
const {
|
||||
fields: { dedication, dedicationAttrs },
|
||||
} = usePostalCalendar({ modalItem: "isOpenAvailability" });
|
||||
|
||||
const modalStore = useModalStore();
|
||||
const { openModal } = modalStore;
|
||||
|
||||
const cartStore = useCartStore();
|
||||
const { getProduct, getProducts, products } = cartStore;
|
||||
const { prevProduct, currentProduct, nextProduct, addCartLoadingBtn } =
|
||||
const { getProduct, getProducts } = cartStore;
|
||||
const { products, featuredProducts, addCartLoadingBtn } =
|
||||
storeToRefs(cartStore);
|
||||
|
||||
onBeforeMount(() => {
|
||||
getProduct(route.params.id);
|
||||
getProducts();
|
||||
});
|
||||
|
||||
watch(currentProduct.value, (newValue) => {
|
||||
useMeta(() => {
|
||||
return {
|
||||
title: `${newValue.value?.title}`,
|
||||
titleTemplate: (title) => `${title} - FloraNet`,
|
||||
meta: {
|
||||
description: {
|
||||
name: "description",
|
||||
content: `${newValue.value?.description}`,
|
||||
},
|
||||
keywords: { name: "keywords", content: `${newValue.value?.title}` },
|
||||
equiv: {
|
||||
"http-equiv": "Content-Type",
|
||||
content: "text/html; charset=UTF-8",
|
||||
},
|
||||
ogTitle: {
|
||||
property: "og:title",
|
||||
template(ogTitle) {
|
||||
return `${ogTitle} - FloraNet`;
|
||||
},
|
||||
},
|
||||
noscript: {
|
||||
default:
|
||||
"This is content for browsers with no JS (or disabled JS)",
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
watch(
|
||||
() => products.value.current?.type,
|
||||
(newCategory) => {
|
||||
getProducts({
|
||||
// type: newCategory,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
|
@ -93,45 +74,55 @@ export default defineComponent({
|
|||
}
|
||||
);
|
||||
|
||||
const currentData = reactive({});
|
||||
watch(currentProduct.value, (newData) => {
|
||||
if (newData.value) {
|
||||
const { id, ...newDataWhithoutId } = newData.value;
|
||||
currentData.value = {
|
||||
...newDataWhithoutId,
|
||||
productId: +route.params.id,
|
||||
};
|
||||
useMeta(() => ({
|
||||
title: `${products.value.current?.title}`,
|
||||
titleTemplate: (title) => `${title} - FloraNet`,
|
||||
meta: {
|
||||
description: {
|
||||
name: "description",
|
||||
content: `${products.value.current?.description}`,
|
||||
},
|
||||
keywords: {
|
||||
name: "keywords",
|
||||
content: `${products.value.current?.title}`,
|
||||
},
|
||||
equiv: {
|
||||
"http-equiv": "Content-Type",
|
||||
content: "text/html; charset=UTF-8",
|
||||
},
|
||||
ogTitle: {
|
||||
property: "og:title",
|
||||
template(ogTitle) {
|
||||
return `${ogTitle} - FloraNet`;
|
||||
},
|
||||
},
|
||||
noscript: {
|
||||
default: "This is content for browsers with no JS (or disabled JS)",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const checkImageValidity = (imageLink) => {
|
||||
const validExtensions = [".jpg", ".jpeg", ".png"];
|
||||
|
||||
if (imageLink) {
|
||||
const extension = imageLink.substring(imageLink.lastIndexOf("."));
|
||||
return validExtensions.includes(extension.toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
const category = reactive({
|
||||
1: "Planta",
|
||||
2: "Ramos",
|
||||
});
|
||||
|
||||
const { handleSubmit, defineField, handleReset } = useForm({
|
||||
validationSchema: dedicationSchema,
|
||||
});
|
||||
const [dedication, dedicationAttrs] = defineField("dedication");
|
||||
/* const onSubmit = handleSubmit(() => {
|
||||
openModal({ modal: "availability" });
|
||||
// addToCart(currentData.value, dedication);
|
||||
// handleReset();
|
||||
}); */
|
||||
return true;
|
||||
};
|
||||
|
||||
return {
|
||||
openModal,
|
||||
checkImageValidity,
|
||||
slide: ref(1),
|
||||
fullscreen: ref(false),
|
||||
dedication,
|
||||
dedicationAttrs,
|
||||
products,
|
||||
featuredProducts,
|
||||
addCartLoadingBtn,
|
||||
prevProduct,
|
||||
currentProduct,
|
||||
nextProduct,
|
||||
currentData,
|
||||
category,
|
||||
openModal,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -141,39 +132,33 @@ export default defineComponent({
|
|||
<q-page>
|
||||
<Container class="product-container" tag="section">
|
||||
<ProductCarousel>
|
||||
<template v-for="(img, i) in currentProduct?.images" :key="i">
|
||||
<q-carousel-slide
|
||||
v-if="img"
|
||||
:img-src="img"
|
||||
class="product-gallery-item"
|
||||
:name="i + 1"
|
||||
/>
|
||||
|
||||
<q-carousel-slide
|
||||
v-else
|
||||
:img-src="'../assets/empty-img.jpg'"
|
||||
class="product-gallery-item"
|
||||
:name="1"
|
||||
/>
|
||||
</template>
|
||||
<q-carousel-slide
|
||||
:img-src="
|
||||
checkImageValidity(products.current?.image)
|
||||
? products.current?.image
|
||||
: '../assets/empty-img.jpg'
|
||||
"
|
||||
class="product-gallery-item"
|
||||
:name="1"
|
||||
/>
|
||||
</ProductCarousel>
|
||||
|
||||
<div class="product-content">
|
||||
<header class="product-content-header">
|
||||
<h3 class="product-content-title subtitle">
|
||||
{{ currentProduct?.name }}
|
||||
<q-skeleton type="rect" v-if="!currentProduct?.name" />
|
||||
{{ products.current?.name }}
|
||||
<q-skeleton type="rect" v-if="!products.current?.name" />
|
||||
</h3>
|
||||
|
||||
<div class="product-header-block">
|
||||
<p class="product-content-paragraph">
|
||||
SKU:
|
||||
<span class="green-text" style="display: inline-flex">
|
||||
{{ currentProduct?.slug }}
|
||||
{{ products.current?.id }}
|
||||
<q-skeleton
|
||||
width="100px"
|
||||
type="text"
|
||||
v-if="!currentProduct?.slug"
|
||||
v-if="!products.current?.id"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
|
@ -181,11 +166,11 @@ export default defineComponent({
|
|||
<p class="product-content-paragraph">
|
||||
Categoría:
|
||||
<span class="green-text">
|
||||
{{ category[currentProduct?.category] }}
|
||||
{{ products.current?.type }}
|
||||
<q-skeleton
|
||||
type="text"
|
||||
width="50px"
|
||||
v-if="!currentProduct?.category"
|
||||
v-if="!products.current?.type"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
|
@ -195,19 +180,21 @@ export default defineComponent({
|
|||
<div class="product-content-body">
|
||||
<div class="product-content-paragraphs">
|
||||
<p class="product-price green-text">
|
||||
{{ currentProduct?.price }}
|
||||
{{ products.current?.price }}€
|
||||
<q-skeleton
|
||||
type="text"
|
||||
height="90px"
|
||||
width="80px"
|
||||
v-if="!currentProduct?.price"
|
||||
v-if="!products.current?.price"
|
||||
/>
|
||||
</p>
|
||||
<p class="product-delivery green-text">Envío Gratuito</p>
|
||||
<p class="product-description">
|
||||
{{ currentProduct?.description }}
|
||||
<q-skeleton type="text" v-if="!currentProduct?.description" />
|
||||
<q-skeleton type="text" v-if="!currentProduct?.description" />
|
||||
{{ products.current?.description }}
|
||||
<q-skeleton type="text" v-if="!products.current?.description" />
|
||||
<q-skeleton type="text" v-if="!products.current?.description" />
|
||||
<q-skeleton type="text" v-if="!products.current?.description" />
|
||||
<q-skeleton type="text" v-if="!products.current?.description" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -263,39 +250,32 @@ export default defineComponent({
|
|||
:style="+$route.params.id === 1 && 'justify-content: flex-end;'"
|
||||
>
|
||||
<q-btn
|
||||
v-if="+$route.params.id > 1"
|
||||
color="white"
|
||||
class="btn outlined rounded sm-btn product-pag-item product-prev-btn"
|
||||
:to="`${+$route.params.id - 1}`"
|
||||
v-if="+$route.params.id > 1"
|
||||
@click="currentProduct.value = undefined"
|
||||
@click="products.current.value = undefined"
|
||||
>
|
||||
<IconArrowCircleFilledLeft />
|
||||
|
||||
<div class="btn-pag-paragraphs">
|
||||
<p class="btn-paragraph-top green-text">Produto anterior</p>
|
||||
<p
|
||||
class="product-paragraph-bottom"
|
||||
:title="prevProduct.value?.title"
|
||||
>
|
||||
{{ prevProduct.value?.title }}
|
||||
<p class="product-paragraph-bottom" :title="products.prev?.name">
|
||||
{{ products.prev?.name }}
|
||||
</p>
|
||||
</div>
|
||||
</q-btn>
|
||||
|
||||
<q-btn
|
||||
v-if="products.next?.id"
|
||||
color="white"
|
||||
class="btn outlined rounded sm-btn product-pag-item product-next-btn"
|
||||
:to="`${+$route.params.id + 1}`"
|
||||
v-if="nextProduct.value?.id"
|
||||
@click="currentProduct.value = undefined"
|
||||
>
|
||||
<div class="btn-pag-paragraphs">
|
||||
<p class="btn-paragraph-top green-text">Siguiente producto</p>
|
||||
<p
|
||||
class="product-paragraph-bottom"
|
||||
:title="nextProduct.value?.title"
|
||||
>
|
||||
{{ nextProduct.value?.title }}
|
||||
<p class="product-paragraph-bottom" :title="products.next?.name">
|
||||
{{ products.next?.name }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -314,26 +294,24 @@ export default defineComponent({
|
|||
</h3>
|
||||
|
||||
<p class="like-another-paragraph">
|
||||
Descripción SEO: Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
elit, sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<Container cardContainer class="no-padding">
|
||||
<template
|
||||
v-for="({ images, discount, isNew, name, price, slug }, i) in products
|
||||
.data.products"
|
||||
v-for="(
|
||||
{ image, discount, isNew, name, price, id }, i
|
||||
) in products.data"
|
||||
:key="id"
|
||||
>
|
||||
<Card
|
||||
v-if="i < 4"
|
||||
:price="price"
|
||||
:title="name"
|
||||
:discount="discount"
|
||||
:imgSrc="images[0]"
|
||||
:imgSrc="image"
|
||||
:isNew="isNew"
|
||||
:key="slug"
|
||||
:id="slug"
|
||||
:id="id"
|
||||
/>
|
||||
</template>
|
||||
</Container>
|
||||
|
|
|
@ -37,7 +37,7 @@ const routes = [
|
|||
path: "",
|
||||
name: "Checkout",
|
||||
component: () => import("pages/CheckoutPage.vue"),
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,105 +1,98 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { api, apiBack } from "src/boot/axios";
|
||||
import { apiBack } from "src/boot/axios";
|
||||
import { quasarNotify } from "src/functions/quasarNotify";
|
||||
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
||||
|
||||
export const useCartStore = defineStore("cart", () => {
|
||||
const cart = ref([]);
|
||||
const cartList = ref([]);
|
||||
const products = ref({
|
||||
data: {
|
||||
page: undefined,
|
||||
productsPerPage: undefined,
|
||||
products: [],
|
||||
},
|
||||
prev: {},
|
||||
current: {},
|
||||
next: {},
|
||||
});
|
||||
const dedicationTxt = ref("");
|
||||
const prevProduct = reactive({});
|
||||
const currentProduct = ref();
|
||||
const nextProduct = reactive({});
|
||||
const addCartLoadingBtn = ref(false);
|
||||
const cartLength = computed(() => cart.value.length);
|
||||
const routeId = ref(null);
|
||||
const totalPrice = computed(() => {
|
||||
return cart.value.reduce((acc, { price }) => {
|
||||
if (price) {
|
||||
const priceWithoutLetter = price?.replace("€", "");
|
||||
return +priceWithoutLetter + acc;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
const { push } = useRouter();
|
||||
const { addItem, getItem, removeItem } = useLocalStorage()
|
||||
|
||||
//! Elements
|
||||
const checkoutRef = ref(null);
|
||||
const homeSection = ref(null);
|
||||
|
||||
const initialValues = [{
|
||||
id: null,
|
||||
name: "",
|
||||
price: null,
|
||||
image: "",
|
||||
description: "",
|
||||
dateExpired: "",
|
||||
isNew: null,
|
||||
type: "",
|
||||
postalCode: "",
|
||||
order_position: null,
|
||||
recommend: null
|
||||
}]
|
||||
|
||||
//! Variables
|
||||
const cart = ref([]);
|
||||
|
||||
(() => {
|
||||
cart.value = getItem('cart');
|
||||
})()
|
||||
|
||||
const addCartLoadingBtn = ref(false);
|
||||
const routeId = ref(null);
|
||||
const products = ref({
|
||||
data: initialValues,
|
||||
prev: initialValues,
|
||||
current: initialValues,
|
||||
next: initialValues,
|
||||
});
|
||||
const featuredProducts = ref({
|
||||
page: undefined,
|
||||
productsPerPage: undefined,
|
||||
products: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* Transforms options object into params object.
|
||||
*
|
||||
* @param debug Allow the data console - boolean
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
async function getCart({ debug }) {
|
||||
try {
|
||||
const { data } = await api.get("cart");
|
||||
const cartItems = data.reduce((obj, { title, price, ...rest }) => {
|
||||
const priceWithoutLetter = +price.replace("€", "");
|
||||
|
||||
if (obj[title]) {
|
||||
obj[title].quantity++;
|
||||
} else {
|
||||
obj[title] = {
|
||||
title,
|
||||
price: `${priceWithoutLetter}€`,
|
||||
quantity: 1,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
cartList.value = Object.values(cartItems);
|
||||
cart.value = data;
|
||||
|
||||
if (debug) {
|
||||
console.groupCollapsed("%c Cart is fetched!", "color: green;");
|
||||
console.table(cart.value);
|
||||
console.groupEnd();
|
||||
}
|
||||
} catch (err) {
|
||||
new Error(`FATAL ERROR ::: ${err}`);
|
||||
}
|
||||
}
|
||||
getCart({ debug: true });
|
||||
|
||||
async function getProducts(
|
||||
function transformOptionsToParams(
|
||||
options = {
|
||||
itens: undefined,
|
||||
featured: undefined,
|
||||
page: undefined,
|
||||
category: undefined,
|
||||
postalCode: undefined,
|
||||
dateExpired: undefined,
|
||||
type: undefined,
|
||||
minPrice: undefined,
|
||||
maxPrice: undefined,
|
||||
bigPrice: undefined,
|
||||
lowPrice: undefined,
|
||||
isNew: undefined,
|
||||
},
|
||||
navigate
|
||||
order_crescent: undefined,
|
||||
order_descending: undefined,
|
||||
recommend: undefined,
|
||||
}
|
||||
) {
|
||||
const optionsObj = {
|
||||
itens: options.itens,
|
||||
featured: options.featured,
|
||||
page: options.page,
|
||||
category: options.category,
|
||||
postalCode: options.postalCode,
|
||||
dateExpired: options.dateExpired,
|
||||
type: options.type,
|
||||
minPrice: options.minPrice,
|
||||
maxPrice: options.maxPrice,
|
||||
bigPrice: options.bigPrice,
|
||||
lowPrice: options.lowPrice,
|
||||
isNew: options.isNew,
|
||||
order_crescent: options.order_crescent,
|
||||
order_descending: options.order_descending,
|
||||
recommend: options.recommend,
|
||||
};
|
||||
const validKeys = Object.keys(options).filter(
|
||||
(key) => options[key] !== undefined
|
||||
|
@ -109,14 +102,51 @@ export const useCartStore = defineStore("cart", () => {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { data },
|
||||
} = await apiBack.get("products", {
|
||||
params,
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
if (data[0].products.length === 0) {
|
||||
/**
|
||||
* 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(
|
||||
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,
|
||||
},
|
||||
scrollIntoView = () => { }
|
||||
) {
|
||||
const params = transformOptionsToParams(options);
|
||||
console.log(params);
|
||||
|
||||
try {
|
||||
const { data: { data } } = await apiBack.get("products", { params });
|
||||
|
||||
|
||||
if (data.length === 0) {
|
||||
return quasarNotify({
|
||||
message:
|
||||
"No hay productos disponibles para la fecha y el código postal seleccionados",
|
||||
|
@ -124,22 +154,12 @@ export const useCartStore = defineStore("cart", () => {
|
|||
});
|
||||
}
|
||||
|
||||
products.value.data = data[0];
|
||||
products.value.data = data;
|
||||
|
||||
if (navigate) {
|
||||
navigate();
|
||||
if (scrollIntoView) {
|
||||
scrollIntoView();
|
||||
}
|
||||
|
||||
const currentProductIndex = data.findIndex(
|
||||
({ slug }) => slug === routeId.value
|
||||
);
|
||||
const prevProductIndex = currentProductIndex - 1;
|
||||
const nextProductIndex = currentProductIndex + 1;
|
||||
|
||||
products.value.prev = data.data[prevProductIndex];
|
||||
products.value.current = data.data[currentProductIndex];
|
||||
products.value.next = data.data[nextProductIndex];
|
||||
|
||||
console.groupCollapsed("%c PRODUCTS FETCHED!", "color: green;");
|
||||
console.groupCollapsed("%c PRODUCTS DATA", "color: tomato;");
|
||||
console.table(products.value.data);
|
||||
|
@ -147,7 +167,10 @@ export const useCartStore = defineStore("cart", () => {
|
|||
console.groupCollapsed("%c PREV PRODUCT", "color: tomato;");
|
||||
console.table(products.value.prev);
|
||||
console.groupEnd();
|
||||
console.groupCollapsed("%c CURRENT PRODUCT", "color: tomato;");
|
||||
console.groupCollapsed(
|
||||
`%c CURRENT PRODUCT: ${products.value.current.slug}`,
|
||||
"color: tomato;"
|
||||
);
|
||||
console.table(products.value.current);
|
||||
console.groupEnd();
|
||||
console.groupCollapsed("%c NEXT PRODUCT", "color: tomato;");
|
||||
|
@ -160,18 +183,34 @@ export const useCartStore = defineStore("cart", () => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetches a product by its ID and updates the cart state.
|
||||
*
|
||||
* @param id Id to get product
|
||||
*
|
||||
* @param {string} id - The ID of the product to fetch.
|
||||
* @param {object} options - Additional options for the product fetch.
|
||||
* @param {string} options.type - The type of the product.
|
||||
* @param {string} options.postalCode - The postal code for location-based filtering.
|
||||
* @param {string} options.dateExpired - The expiration date for time-based filtering.
|
||||
* @param {boolean} debug - Flag indicating whether to enable debug mode.
|
||||
* @returns {Promise<void>} - A promise that resolves when the product is fetched and the cart state is updated.
|
||||
*/
|
||||
async function getProduct(id) {
|
||||
async function getProduct(
|
||||
id,
|
||||
options = {
|
||||
type: undefined,
|
||||
postalCode: undefined,
|
||||
dateExpired: undefined,
|
||||
},
|
||||
debug = false
|
||||
) {
|
||||
if (id) {
|
||||
routeId.value = id;
|
||||
try {
|
||||
/* const promises = [
|
||||
api.get(`flowers/${id - 1}`),
|
||||
api.get(`flowers/${id}`),
|
||||
api.get(`flowers/${id + 1}`),
|
||||
const params = transformOptionsToParams(options);
|
||||
|
||||
const promises = [
|
||||
apiBack.get(`products/${+id - 1}`),
|
||||
apiBack.get(`products/${+id}`),
|
||||
apiBack.get(`products/${+id + 1}`),
|
||||
];
|
||||
const results = await Promise.allSettled(promises);
|
||||
const [prev, current, next] = results.map((res) => {
|
||||
|
@ -180,27 +219,29 @@ export const useCartStore = defineStore("cart", () => {
|
|||
rejected: res.reason,
|
||||
};
|
||||
|
||||
return result[res.status];
|
||||
}); */
|
||||
return result[res.status].data[0];
|
||||
})
|
||||
|
||||
const { data } = await apiBack.get(`products/slug/${id}`);
|
||||
products.value.prev = prev;
|
||||
products.value.current = current;
|
||||
products.value.next = next;
|
||||
|
||||
prevProduct.value = {};
|
||||
currentProduct.value = data.data[0];
|
||||
nextProduct.value = {};
|
||||
if (!current) {
|
||||
push({ name: "NotFound" })
|
||||
}
|
||||
|
||||
console.groupCollapsed(
|
||||
`%c PRODUCT FETCHED! SLUG: ${routeId.value}`,
|
||||
"color: green;"
|
||||
);
|
||||
console.time();
|
||||
console.table(prevProduct.value);
|
||||
console.table(currentProduct.value);
|
||||
console.table(nextProduct.value);
|
||||
console.timeEnd();
|
||||
console.groupEnd();
|
||||
if (debug) {
|
||||
console.groupCollapsed(
|
||||
`%c PRODUCT FETCHED! SLUG: ${routeId.value}`,
|
||||
"color: green;"
|
||||
);
|
||||
console.table(products.value.prev);
|
||||
console.table(products.value.current);
|
||||
console.table(products.value.next);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
if (currentProduct.value.response?.status === 404) {
|
||||
if (products.value.current.response?.status === 404) {
|
||||
push({ name: "NotFound" });
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -209,46 +250,107 @@ export const useCartStore = defineStore("cart", () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function addToCart(product, dedication) {
|
||||
cart.value.push({ ...product.value });
|
||||
dedicationTxt.value = dedication;
|
||||
addCartLoadingBtn.value = true;
|
||||
|
||||
/**
|
||||
* Retrieves featured products based on the provided options.
|
||||
*
|
||||
* @param {Object} options - The options for retrieving featured products.
|
||||
* @param {number} options.itens - The number of items to retrieve.
|
||||
* @param {number} options.featured - The flag indicating if the products should be featured.
|
||||
* @param {number} [options.page] - The page number for pagination.
|
||||
* @param {string} [options.type] - The type of the products.
|
||||
* @param {string} [options.postalCode] - The postal code for location-based filtering.
|
||||
* @param {string} [options.dateExpired] - The expiration date for filtering.
|
||||
* @param {number} [options.minPrice] - The minimum price for filtering.
|
||||
* @param {number} [options.maxPrice] - The maximum price for filtering.
|
||||
* @param {number} [options.bigPrice] - The big price for filtering.
|
||||
* @param {number} [options.lowPrice] - The low price for filtering.
|
||||
* @param {boolean} [options.isNew] - The flag indicating if the products are new.
|
||||
* @returns {Promise<void>} - A promise that resolves when the featured products are retrieved.
|
||||
*/
|
||||
async function getFeaturedProducts(
|
||||
options = {
|
||||
postalCode: undefined,
|
||||
dateExpired: undefined,
|
||||
type: undefined,
|
||||
minPrice: undefined,
|
||||
maxPrice: undefined,
|
||||
bigPrice: undefined,
|
||||
lowPrice: undefined,
|
||||
isNew: undefined,
|
||||
order_crescent: undefined,
|
||||
order_descending: undefined,
|
||||
recommend: 1,
|
||||
},
|
||||
debug = false
|
||||
) {
|
||||
try {
|
||||
await api.post("cart", product);
|
||||
addCartLoadingBtn.value = false;
|
||||
// push("/checkout");
|
||||
const params = transformOptionsToParams(options);
|
||||
|
||||
console.groupCollapsed("%c Adicionado com sucesso!", "color: green");
|
||||
console.table(cart.value);
|
||||
console.groupEnd();
|
||||
(async () => {
|
||||
const {
|
||||
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) {
|
||||
addCartLoadingBtn.value = false;
|
||||
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",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cart by its ID.
|
||||
* @param {number} id - The ID of the item to be removed.
|
||||
*/
|
||||
function removeFromCart(id) {
|
||||
cart.value = cart.value.filter((p) => id !== p.id);
|
||||
api.delete(`cart/${id}`);
|
||||
const newArrRemovedItem = cart.value.filter((p) => id !== p.id);
|
||||
addItem("cart", JSON.stringify(newArrRemovedItem))
|
||||
}
|
||||
|
||||
return {
|
||||
checkoutRef,
|
||||
homeSection,
|
||||
|
||||
cart,
|
||||
cartList,
|
||||
totalPrice,
|
||||
dedicationTxt,
|
||||
cartLength,
|
||||
prevProduct,
|
||||
currentProduct,
|
||||
nextProduct,
|
||||
addCartLoadingBtn,
|
||||
products,
|
||||
featuredProducts,
|
||||
|
||||
getFeaturedProducts,
|
||||
getProducts,
|
||||
addToCart,
|
||||
removeFromCart,
|
||||
getProduct,
|
||||
getCart,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import { fullCurrentDate } from "src/constants/date";
|
||||
import { z } from "zod";
|
||||
import { postalCode } from "..";
|
||||
import * as M from "../messages";
|
||||
|
||||
const availabilityObj = {
|
||||
date: z.string({ required_error: M.requiredMessage }).refine((val) => {
|
||||
const [day, month, year] = val.split("/");
|
||||
const regex = /\//g;
|
||||
const regex = /-|\//g;
|
||||
const [day, month = "", year = ""] = val.split(regex);
|
||||
const valWithoutSlash = val.replace(regex, "");
|
||||
const data = new Date(`${year}-${month}-${day}`);
|
||||
const today = new Date();
|
||||
|
||||
return valWithoutSlash.length === 8 && data >= today;
|
||||
const inputDate = `${year}/${month}/${day}`;
|
||||
|
||||
return valWithoutSlash.length === 8 && inputDate >= fullCurrentDate;
|
||||
}, "La fecha no puede ser inferior al día de hoy!"),
|
||||
postalCode,
|
||||
dedication: z.string().optional(),
|
||||
};
|
||||
|
||||
export const availabilitySchema = z.object(availabilityObj);
|
||||
|
|
ya compruebas que es un numero en el if