develop #6

Merged
jsolis merged 4 commits from develop into master 2024-02-08 11:25:48 +00:00
33 changed files with 1089 additions and 739 deletions
Showing only changes of commit 0365a57b3a - Show all commits

12
.vscode/settings.json vendored
View File

@ -17,6 +17,13 @@
"terminal.integrated.cursorStyleInactive": "line", "terminal.integrated.cursorStyleInactive": "line",
"workbench.iconTheme": "material-icon-theme", "workbench.iconTheme": "material-icon-theme",
"workbench.colorTheme": "Default Dark+", "workbench.colorTheme": "Default Dark+",
"editor.codeActionsOnSave": [
"source.addMissingImports",
"source.organizeImports",
"source.fixAll.eslint",
"source.fixAll.stylelint"
],
"files.exclude": { "files.exclude": {
"**/.git": true, "**/.git": true,
"**/.svn": true, "**/.svn": true,
@ -46,7 +53,6 @@
"git.confirmSync": false, "git.confirmSync": false,
"javascript.updateImportsOnFileMove.enabled": "always", "javascript.updateImportsOnFileMove.enabled": "always",
"console-ninja.featureSet": "Community", "console-ninja.featureSet": "Community",
"liveServer.settings.donotShowInfoMsg": true,
"typescript.updateImportsOnFileMove.enabled": "always", "typescript.updateImportsOnFileMove.enabled": "always",
"editor.cursorSmoothCaretAnimation": "on", "editor.cursorSmoothCaretAnimation": "on",
"editor.fontLigatures": true, "editor.fontLigatures": true,
@ -60,9 +66,7 @@
"source.organizeImports", "source.organizeImports",
"source.fixAll.eslint" "source.fixAll.eslint"
], ],
"liveServer.settings.donotVerifyTags": true,
"gitlens.gitCommands.skipConfirmations": ["fetch:command", "switch:command"], "gitlens.gitCommands.skipConfirmations": ["fetch:command", "switch:command"],
"symbols.hidesExplorerArrows": false,
"diffEditor.ignoreTrimWhitespace": false, "diffEditor.ignoreTrimWhitespace": false,
"svg.preview.mode": "svg", "svg.preview.mode": "svg",
"[svg]": { "[svg]": {
@ -72,7 +76,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"workbench.tree.indent": 16, "workbench.tree.indent": 16,
"window.zoomLevel": 0, "window.zoomLevel": -1,
"git.ignoreRebaseWarning": true, "git.ignoreRebaseWarning": true,
"editor.largeFileOptimizations": false, "editor.largeFileOptimizations": false,
"[javascript]": { "[javascript]": {

View File

@ -5,45 +5,34 @@ const productsJson = require("./products.json")
class ProductController { class ProductController {
async findAll(req, res) { 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; const params = req.query;
let productsFilter = mapedProducts const _products = await db.getProducts(params.dateExpired, params.postalCode);
console.log(params); let productsFilter = _products[0];
if (Number(params.featured)) { if (Number(params.recommend)) {
productsFilter = productsFilter.filter(item => item.featured === Number(params.featured)) productsFilter = productsFilter.filter(item => item.recommend == Number(params.recommend))
jsolis marked this conversation as resolved
Review

ya compruebas que es un numero en el if

ya compruebas que es un numero en el if
} }
if (params.category) { if (params.type) {
productsFilter = productsFilter.filter(item => item.category === Number(params.category)) productsFilter = productsFilter.filter(item => item.type === params.type)
} }
if (params.postalCode) { /*if (params.postalCode) {
productsFilter = productsFilter.filter(item => item.postalCode === 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 => {
const price = Number(item.price.replace(/€/g, '')) const price = Number(item.price)
jsolis marked this conversation as resolved
Review

se utiliza de manera reiterada la función Number() podemos plantearlo de otra manera
para que no sea necesario

se utiliza de manera reiterada la función Number() podemos plantearlo de otra manera para que no sea necesario
if (price >= Number(params.minPrice)) { if (price >= Number(params.minPrice)) {
return item return item
} }
@ -51,7 +40,7 @@ class ProductController {
} }
if (params.maxPrice && !params.minPrice) { if (params.maxPrice && !params.minPrice) {
productsFilter = productsFilter.filter(item => { productsFilter = productsFilter.filter(item => {
const price = Number(item.price.replace(/€/g, '')) const price = Number(item.price)
if (price <= Number(params.maxPrice)) { if (price <= Number(params.maxPrice)) {
return item return item
} }
@ -59,71 +48,77 @@ class ProductController {
} }
if (params.maxPrice && params.minPrice) { if (params.maxPrice && params.minPrice) {
productsFilter = productsFilter.filter(item => { 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)) { 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 return item
} }
}) })
} }
if (Number(params.bigPrice)) { if (Number(params.bigPrice)) {
productsFilter.sort((a, b) => { productsFilter.sort((a, b) => {
const itemA = Number(a.price.replace(/€/g, '')) const itemA = Number(a.price)
const itemB = Number(b.price.replace(/€/g, '')) const itemB = Number(b.price)
return itemB - itemA; return itemB - itemA;
}) })
} }
if (Number(params.lowPrice)) { if (Number(params.lowPrice)) {
productsFilter.sort((a, b) => { productsFilter.sort((a, b) => {
const itemA = Number(a.price.replace(/€/g, '')) const itemA = Number(a.price)
const itemB = Number(b.price.replace(/€/g, '')) const itemB = Number(b.price)
return itemA - itemB; return itemA - itemB;
}) })
} }
if (params.isNew) { if (Number(params.order_descending)) {
productsFilter = productsFilter.filter(item => item.isNew === true) productsFilter.sort((a, b) => {
const itemA = a.order_position
const itemB = b.order_position
return itemB - itemA;
})
} }
let productsFilterPages = [] if (Number(params.order_crescent)) {
const totalItens = params?.itens ? Number(params.itens) : 200 productsFilter.sort((a, b) => {
const page = params.page ? Number(params.page) : 1 const itemA = a.order_position
const startIndex = (totalItens * page) - totalItens const itemB = b.order_position
const lastIndex = (totalItens * page) return itemA - itemB;
const products = productsFilter.slice(startIndex, lastIndex) })
productsFilterPages.push({ }
page: page,
productsPerPage: products.length, if (Number(params.isNew)) {
products: products 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({ return res.status(200).send({
data: productsFilterPages data: productsFilter
}) })
} }
findBySlug(req, res) { async findById(req, res) {
const slug = req.params.slug const id = Number(req.params.id)
const products = productsJson const _products = await db.getProducts();
const filterSlug = products.filter(item => item.slug === slug) const filterProduct = _products[0].filter(item => item.id === id)
return res.status(200).send({ return res.status(200).send({
data: filterSlug data: filterProduct
}) })
} }
} }
module.exports = new ProductController(); module.exports = new ProductController();

View File

@ -1,27 +1,27 @@
async function connect() { async function connect() {
if (global.connection && global.connection.state !== 'disconnected') if (global.connection && global.connection.state !== 'disconnected')
return global.connection; return global.connection;
const host = process.env.HOST; const host = process.env.HOST;
const port = process.env.PORT; const port = process.env.PORT;
const database = process.env.DATABASE; const database = process.env.DATABASE;
const user = process.env.DB_USER; const user = process.env.DB_USER;
const password = process.env.DB_PASSWORD; const password = process.env.DB_PASSWORD;
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!"); console.log("Connected to MySQL!");
global.connection = connection; global.connection = connection;
return connection; return connection;
} }
async function getProducts() { async function getProducts(dateExpired, postalCode) {
const conn = await connect(); console.log("Query in table MySQL!");
jsolis marked this conversation as resolved
Review

borrar console.log

borrar console.log
const [rows] = await conn.query('CALL catalogue_get("2024-01-30", "08001")'); const conn = await connect();
const [rows] = await conn.query(`CALL catalogue_get("${dateExpired}", "${postalCode}")`);
return rows; return rows;
} }

View File

@ -20,7 +20,7 @@ app.get('/', (req, res) => {
//Products //Products
app.get('/api/products', productController.findAll); app.get('/api/products', productController.findAll);
app.get('/api/products/slug/:slug', productController.findBySlug); app.get('/api/products/:id', productController.findById);
app.listen(port, () => { app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`); console.log(`Server listening at http://localhost:${port}`);

View File

@ -51,7 +51,7 @@
src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-element-bundle.min.js" src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-element-bundle.min.js"
defer defer
></script> ></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
Review

borrar comentario

borrar comentario
</head> </head>
<body> <body>
<!-- quasar:entry-point --> <!-- quasar:entry-point -->

View File

@ -28,7 +28,7 @@ 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" /* , "stripe" */], boot: ["i18n", "axios" /* , { path: "stripe", 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"],
@ -119,7 +119,7 @@ module.exports = configure(function (/* ctx */) {
// directives: [], // directives: [],
// Quasar plugins // Quasar plugins
plugins: ["Meta", "Loading", "Notify"], plugins: ["Meta", "Loading", "Notify", "LocalStorage", "SessionStorage"],
}, },
// animations: 'all', // --- includes all animations // animations: 'all', // --- includes all animations

View File

@ -3,15 +3,15 @@ import { boot } from "quasar/wrappers";
// "async" is optional; // "async" is optional;
// more info on params: https://v2.quasar.dev/quasar-cli/boot-files // more info on params: https://v2.quasar.dev/quasar-cli/boot-files
export default boot(async ({ app, router, store }) => { export default boot(({ app }) => {
if (typeof window === "undefined") return;
const options = { const options = {
pk: process.env.STRIPE_PUBLISHABLE_KEY, pk: "pk_test_51OZaJdIK1lTlG93d2y0B81n4XrjvjQwqfIUZ7ggb9wEBa1e4h34GlYFYPwjtGl3OUT7DJZlVNX9EMXaCdOBkIC3T007mLnfvCu",
stripeAccount: process.env.STRIPE_ACCOUNT, stripeAccount: "acct_1OXQt7GMODwoSxWA",
apiVersion: process.env.API_VERSION, apiVersion: "2023-10-16",
locale: process.env.LOCALE, locale: "pt-BR",
jsolis marked this conversation as resolved
Review

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); app.use(StripePlugin, options);
}); });

View File

@ -1,63 +1,45 @@
<script> <script>
import { toTypedSchema } from "@vee-validate/zod";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { quasarNotify } from "src/functions/quasarNotify"; import { fullCurrentDate } from "src/constants/date";
import { useFormStore } from "src/stores/forms"; import { useFormStore } from "src/stores/forms";
import { availabilitySchema } from "src/utils/zod/schemas/availabilitySchema"; import { defineComponent, ref } from "vue";
import { useForm } from "vee-validate";
import { defineComponent, ref, watch } 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 },
setup() { inheritAttrs: true,
const getDate = new Date(); props: {
const currentDay = getDate.getDate().toString().padStart(2, "0"); setValues: {
const currentMonth = getDate.getMonth() + 1; type: Function,
const currentYear = getDate.getFullYear(); default: () => {},
const fullCurrentDate = `${currentYear}/${currentMonth}/${currentDay}`; },
},
setup({ setValues }) {
const formStore = useFormStore(); const formStore = useFormStore();
const { availability } = storeToRefs(formStore); const { availability } = storeToRefs(formStore);
const proxyDate = ref(fullCurrentDate); const proxyDate = ref(fullCurrentDate);
const validationSchema = toTypedSchema( function updateProxy() {
availabilitySchema.pick({ date: true }) proxyDate.value = fullCurrentDate;
); }
const { errors, defineField, values } = useForm({
validationSchema,
});
const [calendar, calendarAttrs] = defineField("date");
const onBlur = () => { function optionsValidDates(date) {
availability.value.date = calendar.value; return date >= fullCurrentDate;
}; }
availability.value.date = values.date;
watch(errors, (newErrors) => { function save() {
if (newErrors.date) { availability.value.date = proxyDate.value;
quasarNotify({ message: newErrors.date, type: "erro" }); setValues({ date: proxyDate.value });
} }
});
return { return {
availability, availability,
proxyDate, proxyDate,
calendar, updateProxy,
calendarAttrs, optionsValidDates,
errors, save,
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;
},
}; };
}, },
}); });
@ -76,6 +58,7 @@ export default defineComponent({
> >
<q-date <q-date
v-model="proxyDate" v-model="proxyDate"
v-bind="calendarAttrs"
:options="optionsValidDates" :options="optionsValidDates"
mask="DD-MM-YYYY" mask="DD-MM-YYYY"
> >
@ -95,16 +78,7 @@ 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>
<!-- <q-input
class="custom-date-input"
placeholder="Elige una fecha"
v-model="calendar"
mask="##/##/####"
:error="!!errors.date"
@blur="onBlur"
borderless
dense
/> -->
<slot></slot> <slot></slot>
</div> </div>
</div> </div>

View File

@ -28,18 +28,12 @@ export default defineComponent({
<li class="footer-list-item"> <li class="footer-list-item">
<p class="footer-list-content"> <p class="footer-list-content">
Contáctanos <br /> 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. Horario: 9 a 14h. y de 15 a 18h.
</p> </p>
</li> </li>
<li class="footer-list-item"> <li class="footer-list-item">
<p class="footer-list-content"> <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> </p>
</li> </li>
</ul> </ul>

View File

@ -43,7 +43,9 @@ export default defineComponent({
<nav-links /> <nav-links />
<user-area /> <q-no-ssr>
<user-area />
</q-no-ssr>
</div> </div>
</q-header> </q-header>
</template> </template>

View File

@ -3,27 +3,23 @@ import { 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 IconUser from "components/icons/IconUser.vue";
import DropdownGroup from "components/quasar-components/dropdown/DropdownGroup.vue"; import { useLocalStorage } from "src/hooks/useLocalStorage";
import DropdownItem from "components/quasar-components/dropdown/DropdownItem.vue"; */
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({
name: "user-area", name: "user-area",
components: { components: {
IconCart, IconCart,
// IconUser,
// DropdownGroup,
// DropdownItem,
IconHamburger, IconHamburger,
}, },
setup() { setup() {
const { getItem } = useLocalStorage();
const mobileStore = useMobileStore(); const mobileStore = useMobileStore();
const { handleOpenMobileNav } = mobileStore; const { handleOpenMobileNav } = mobileStore;
const cartStore = useCartStore();
const { cartLength } = storeToRefs(cartStore); const cartLength = getItem("cart").length;
return { handleOpenMobileNav, cartLength }; return { handleOpenMobileNav, cartLength };
}, },

View File

@ -20,6 +20,7 @@ export default defineComponent({
setup() { setup() {
const { const {
onSubmit, onSubmit,
setValues,
fields: { calendar, calendarAttrs, postalCode, postalCodeAttrs }, fields: { calendar, calendarAttrs, postalCode, postalCodeAttrs },
errors, errors,
} = usePostalCalendar({ type: "home" }); } = usePostalCalendar({ type: "home" });
@ -43,6 +44,7 @@ export default defineComponent({
screenWidth, screenWidth,
errors, errors,
onSubmit, onSubmit,
setValues,
postalCode, postalCode,
postalCodeAttrs, postalCodeAttrs,
@ -86,15 +88,12 @@ export default defineComponent({
</h1> </h1>
<p class="carousel-header-paragraph"> <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> </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 /> --> <Calendar :setValues="setValues">
<Calendar>
<q-input <q-input
borderless borderless
class="custom-date-input" class="custom-date-input"
@ -109,7 +108,6 @@ export default defineComponent({
</div> </div>
<div class="carousel-content-item"> <div class="carousel-content-item">
<!-- <PostalCode /> -->
<PostalCode> <PostalCode>
<q-input <q-input
borderless borderless

View File

@ -26,8 +26,6 @@ export default defineComponent({
<LogoWhite /> <LogoWhite />
<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>

View File

@ -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>

View File

@ -46,22 +46,35 @@ export default defineComponent({
default: "", default: "",
}, },
id: { id: {
type: String, type: Number,
required: true, required: true,
}, },
}, },
setup({ price, discount }) { setup({ price, discount }) {
const isLoaded = ref(false); const isLoaded = ref(false);
const isError = ref(false);
const percent = +discount / 100; const percent = +discount / 100;
//const priceWithoutLetter = ~~price.replaceAll("", ""); //const priceWithoutLetter = ~~price?.replaceAll("", "");
const priceWithoutLetter = price; const priceWithoutLetter = price;
const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent); const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent);
console.log(price);
jsolis marked this conversation as resolved
Review

borrar console.log

borrar console.log
const onLoad = () => { const onLoad = () => {
isLoaded.value = true; isLoaded.value = true;
}; };
return { onLoad, isLoaded, finalValue, priceWithoutLetter }; const onError = () => {
isError.value = true;
};
return {
onLoad,
onError,
isLoaded,
isError,
finalValue,
priceWithoutLetter,
};
}, },
}); });
</script> </script>
@ -82,10 +95,11 @@ export default defineComponent({
<img <img
class="card-img" class="card-img"
:class="[imgClass]" :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" :alt="alt"
:key="imgSrc" :key="imgSrc"
@load="onLoad" @load="onLoad"
@error="onError"
/> />
<q-skeleton <q-skeleton
v-if="!isLoaded" v-if="!isLoaded"
@ -105,7 +119,7 @@ export default defineComponent({
<div class="card-values"> <div class="card-values">
<p class="price" v-if="finalValue">{{ finalValue }}</p> <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 }} {{ price }}
</p> </p>
</div> </div>

View File

@ -30,6 +30,7 @@ export default defineComponent({
setup({ modalItem, typeModal }) { setup({ modalItem, typeModal }) {
const { const {
onSubmit, onSubmit,
setValues,
fields: { fields: {
calendar, calendar,
calendarAttrs, calendarAttrs,
@ -61,6 +62,7 @@ export default defineComponent({
onSubmit, onSubmit,
modalStore, modalStore,
modalTextContent, modalTextContent,
setValues,
postalCode, postalCode,
postalCodeAttrs, postalCodeAttrs,
@ -121,7 +123,7 @@ export default defineComponent({
v-if="modalItem === 'isOpenAvailability'" v-if="modalItem === 'isOpenAvailability'"
class="modal-body-availability" class="modal-body-availability"
> >
<Calendar> <Calendar :setValues="setValues">
<q-input <q-input
borderless borderless
class="custom-date-input" class="custom-date-input"

7
src/constants/date.js Normal file
View File

@ -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}`;

View File

@ -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;
}

View File

@ -26,10 +26,20 @@ export function quasarNotify({ message = "", type, timeout = 1000 }) {
icon: "report_problem", icon: "report_problem",
timeout, timeout,
}), }),
info: () => Notify.create({
message,
color: "info",
position: "top",
icon: "info",
timeout,
}),
default: () => {
console.error(`Type is invalid! TYPE: ${type}`)
}
}; };
if (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"); console.error("Type is required, success, warning or erro");
} }

View File

@ -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
Review

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')

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,
},
};
}

View File

@ -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,
};
}

View File

@ -1,10 +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 { watch } from "vue"; import { ref, watch } from "vue";
import { useRouter } from "vue-router"; 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 { quasarNotify } from "src/functions/quasarNotify";
import { useCartStore } from "src/stores/cart"; import { useCartStore } from "src/stores/cart";
import { useFormStore } from "src/stores/forms"; import { useFormStore } from "src/stores/forms";
@ -13,38 +13,50 @@ 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";
/**
* 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" }) { export function usePostalCalendar({ modalItem = "", type = "home" }) {
const { push } = useRouter(); const route = useRoute();
const { push } = useRouter()
const rangePriceStore = useRangePriceStore(); const rangePriceStore = useRangePriceStore();
const { rangeValue } = storeToRefs(rangePriceStore); const { rangeValue } = storeToRefs(rangePriceStore);
const modalStore = useModalStore(); const modalStore = useModalStore();
const { isOpenAvailability } = storeToRefs(modalStore); const { openModal } = modalStore
const formStore = useFormStore(); const formStore = useFormStore();
const { availability, sortProductFilters } = storeToRefs(formStore); const { sortProductFilters } = storeToRefs(formStore);
const cartStore = useCartStore(); const cartStore = useCartStore();
const { addToCart, getProducts } = cartStore; const { addToCart, getProducts } = cartStore;
const { currentProduct, products } = storeToRefs(cartStore); const { products, homeSection } = storeToRefs(cartStore);
const min = 0; const min = 0;
const max = 200; const max = 200;
const category = ref(route.path.split("/")[2])
const { handleSubmit, handleReset, defineField, errors } = useForm({ const { handleSubmit, handleReset, defineField, errors, setValues } = useForm(
validationSchema: toTypedSchema( {
type !== "filter" ? availabilitySchema : rangePriceSchema validationSchema: toTypedSchema(
), type !== "filter" ? availabilitySchema : rangePriceSchema
initialValues: { ),
range: { initialValues: {
min, range: {
max, min,
max,
},
postalCode: "",
date: "",
}, },
postalCode: "", }
date: "", );
},
});
const [calendar, calendarAttrs] = defineField("date"); const [calendar, calendarAttrs] = defineField("date");
const [postalCode, postalCodeAttrs] = defineField("postalCode"); const [postalCode, postalCodeAttrs] = defineField("postalCode");
const [priceRange, priceRangeAttrs] = defineField("range"); const [priceRange, priceRangeAttrs] = defineField("range");
@ -56,6 +68,8 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
quasarNotify({ message: newErrors.postalCode, type: "erro" }), quasarNotify({ message: newErrors.postalCode, type: "erro" }),
date: () => quasarNotify({ message: newErrors.date, type: "erro" }), date: () => quasarNotify({ message: newErrors.date, type: "erro" }),
range: () => quasarNotify({ message: newErrors.range, type: "erro" }), range: () => quasarNotify({ message: newErrors.range, type: "erro" }),
dedication: () =>
quasarNotify({ message: newErrors.dedication, type: "erro" }),
}; };
const keys = Object.keys(newErrors); const keys = Object.keys(newErrors);
keys.forEach((key) => { 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 onSubmit = handleSubmit((values) => {
const postalAndDateParams = { const postalAndDateParams = {
postalCode: values.postalCode, postalCode: values.postalCode,
dateExpired: values.date, dateExpired: invertDate(values.date),
};
const categoryObj = {
plantas: "Floranet Plantas",
ramos: "Floranet Ramos",
}; };
const objVal = { const objVal = {
home: async () => { home: async () => {
console.log(type); console.log(type);
getProducts( await getProducts(
{ {
itens: 30,
category: 1,
postalCode: values.postalCode, postalCode: values.postalCode,
dateExpired: values.date, dateExpired: invertDate(values.date),
}, },
() => push("/categoria/ramos") () => homeSection.value.scrollIntoView()
); );
}, },
product: async () => { product: async () => {
console.log(type); console.log(type);
try { await getProducts(postalAndDateParams);
const {
data: { data }, const hasProduct = products.value.data.some((item) => {
} = await apiBack.get(`products/slug/${currentProduct.value.id}`, { const date = new Date(item.dateExpired);
params: postalAndDateParams, const day = date.getDate();
}); const month = (date.getMonth() + 1).toString().padStart(2, '0');
} catch (error) { const year = date.getFullYear();
console.error(error); const dateExpired = `${day}/${month}/${year}`;
push("/");
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 () => { availability: async () => {
console.log(type); console.log(type);
getProducts({ await getProducts({
itens: 20,
postalCode: values.postalCode, postalCode: values.postalCode,
dateExpired: values.date, dateExpired: invertDate(values.date),
}); });
}, },
filter: async () => { filter: async () => {
@ -112,26 +152,21 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
rangeValue.value.max = values.range.max; rangeValue.value.max = values.range.max;
rangeValue.value.min = values.range.min; rangeValue.value.min = values.range.min;
const category = sortProductFilters.value.category;
const categoryObj = {
plantas: 1,
ramos: 2,
};
const params = { const params = {
itens: 20, type: categoryObj[category.value],
category: categoryObj[category],
minPrice: values.range.min, minPrice: values.range.min,
maxPrice: values.range.max, 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]() || objVal[type]() || objVal["default"]();
console.error(
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
);
if (modalItem) { if (modalItem) {
modalStore[modalItem] = false; modalStore[modalItem] = false;
@ -141,6 +176,7 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
return { return {
onSubmit, onSubmit,
setValues,
modalStore, modalStore,
fields: { fields: {
calendar, calendar,

View File

@ -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 }
}

View File

@ -27,9 +27,11 @@ export default defineComponent({
</q-no-ssr> </q-no-ssr>
<mobile-nav /> <mobile-nav />
<q-page-container class="no-padding more product-layout"> <q-no-ssr>
<router-view /> <q-page-container class="no-padding more product-layout">
</q-page-container> <router-view />
</q-page-container>
</q-no-ssr>
<reasons-section /> <reasons-section />

View File

@ -26,7 +26,7 @@ export function mockGenerator({ length }) {
slug: fakerES.commerce.isbn({ separator: "-", variant: 13 }), slug: fakerES.commerce.isbn({ separator: "-", variant: 13 }),
category: fakerES.number.int({ min: 1, max: 2 }), category: fakerES.number.int({ min: 1, max: 2 }),
postalCode: "12345", postalCode: "12345",
dateExpired: "30/01/2024", dateExpired: "2024-01-30",
images: Array.from( images: Array.from(
{ length: fakerES.number.int({ min: 2, max: 6 }) }, { length: fakerES.number.int({ min: 2, max: 6 }) },
() => fakerES.image.urlPicsumPhotos() () => fakerES.image.urlPicsumPhotos()

View File

@ -12,8 +12,10 @@ import DudasSection from "src/components/sections/DudasSection.vue";
import Card from "src/components/ui/Card.vue"; 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 { 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 { useModalStore } from "src/stores/modalStore"; import { useModalStore } from "src/stores/modalStore";
export default defineComponent({ export default defineComponent({
@ -32,6 +34,9 @@ export default defineComponent({
setup() { setup() {
const route = useRoute(); const route = useRoute();
const mobileStore = useMobileStore();
const { screenWidth } = storeToRefs(mobileStore);
const modalStore = useModalStore(); const modalStore = useModalStore();
const { openModal } = modalStore; const { openModal } = modalStore;
@ -66,8 +71,8 @@ export default defineComponent({
latest: "más recientes", latest: "más recientes",
}; };
const categoryObj = { const categoryObj = {
plantas: 1, plantas: "Floranet Plantas",
ramos: 2, ramos: "Floranet Ramos",
}; };
watch(availability, (newDate) => { watch(availability, (newDate) => {
@ -83,14 +88,13 @@ export default defineComponent({
sortProductFilters.value.category = categoryPath; sortProductFilters.value.category = categoryPath;
const params = { const params = {
category: categoryObj[categoryPath], type: categoryObj[categoryPath],
itens: window.screen.width <= 445 ? 16 : 20,
}; };
const paramsObj = { const paramsObj = {
"lowest-price": () => (params.lowPrice = 1), "lowest-price": () => (params.lowPrice = 1),
"highest-price": () => (params.bigPrice = 1), "highest-price": () => (params.bigPrice = 1),
latest: () => (params.isNew = 1), latest: () => (params.isNew = 1),
// recommended: () => params.featured = 1, recommended: () => (params.recommend = 1),
}; };
if (newOrder) { if (newOrder) {
paramsObj[newOrder](); paramsObj[newOrder]();
@ -104,8 +108,7 @@ export default defineComponent({
const categoryPath = route.path.split("/")[2]; const categoryPath = route.path.split("/")[2];
await getProducts({ await getProducts({
category: categoryObj[categoryPath], type: categoryObj[categoryPath],
itens: window.screen.width <= 445 ? 16 : 20,
}); });
}); });
@ -122,11 +125,12 @@ export default defineComponent({
} }
return { return {
sortProductFilters,
openOrderFilter, openOrderFilter,
openModal, openModal,
sortProductFilters,
availability, availability,
isOpenOrder, isOpenOrder,
screenWidth,
modalStore, modalStore,
orderText, orderText,
products, products,
@ -145,9 +149,6 @@ export default defineComponent({
{{ sortProductFilters.category }} para obsequiar {{ sortProductFilters.category }} para obsequiar
</h3> </h3>
<p class="product-header-paragraph"> <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> </p>
</div> </div>
@ -218,25 +219,14 @@ export default defineComponent({
<div class="products-section-body"> <div class="products-section-body">
<Container cardContainer class="category-container"> <Container cardContainer class="category-container">
<template <template v-for="item in products.data" :key="item?.id">
v-for="{
images,
discount,
isNew,
name,
price,
slug,
id,
} in products.data.products"
:key="id"
>
<Card <Card
:price="price" v-if="item"
:title="name" :price="item.price"
:discount="discount" :title="item.name"
:imgSrc="images[0]" :imgSrc="item.image"
:isNew="isNew" :isNew="item.isNew"
:id="slug" :id="item.id"
/> />
</template> </template>
</Container> </Container>

View File

@ -1,14 +1,9 @@
<script> <script>
import { toTypedSchema } from "@vee-validate/zod"; import { defineComponent, onBeforeMount, ref } from "vue";
import { storeToRefs } from "pinia";
import { useForm } from "vee-validate";
import { computed, defineComponent, reactive, ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import Container from "src/components/ui/Container.vue"; import Container from "src/components/ui/Container.vue";
import { useCartStore } from "src/stores/cart"; import { useCheckoutForm } from "src/hooks/useCheckoutForm";
import { useFormStore } from "src/stores/forms";
import { checkoutSchema } from "src/utils/zod/schemas/checkoutSchema";
export default defineComponent({ export default defineComponent({
name: "CheckoutPage", name: "CheckoutPage",
@ -17,150 +12,75 @@ export default defineComponent({
}, },
setup() { setup() {
const { push } = useRouter(); const { push } = useRouter();
const {
const cartStore = useCartStore(); provinceOptions,
const { cart, cartList, totalPrice, cartLength } = storeToRefs(cartStore); handleClickStep,
stepsFormated,
if (cartLength.value === 0) return push("/"); stepList,
checkoutBlock,
const formStore = useFormStore(); cart,
const { handleCheckoutData } = formStore; totalPrice,
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({ formState: { errors, meta, onSubmit, submitLoading },
validationSchema: toTypedSchema(checkoutSchema), fields: {
initialValues: { name,
paymentMethod: "stripe", nameAttrs,
terms: false, 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,
}, },
}); } = useCheckoutForm();
const [name, nameAttrs] = defineField("name");
const [surname, surnameAttrs] = defineField("surname");
const [address, addressAttrs] = defineField("address");
const [postalCode, postalCodeAttrs] = defineField("postalCode");
const [phone, phoneAttrs] = defineField("phone");
const [city, cityAttrs] = defineField("city");
const [province, provinceAttrs] = defineField("province");
const [senderName, senderNameAttrs] = defineField("senderName");
const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif");
const [senderEmail, senderEmailAttrs] = defineField("senderEmail");
const [senderPhone, senderPhoneAttrs] = defineField("senderPhone");
const [senderNotes, senderNotesAttrs] = defineField("senderNotes");
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
const [terms, termsAttrs] = defineField("terms");
const stepActive = reactive({ data: 1 }); onBeforeMount(() => {
const stepList = reactive({ if (cart.length === 0) return push("/");
data: [
{
value: 1,
name: "Paso 1",
description: "Datos de facturación",
active: true,
},
{
value: 2,
name: "Paso 2",
description: "Confirmación",
active: false,
},
{
value: 3,
name: "Paso 3",
description: "Pago",
active: false,
},
],
});
const checkoutBlock = ref(true);
const onSubmit = handleSubmit((values) => {
handleCheckoutData(values);
stepList.data[2].active = true;
checkoutBlock.value = false;
resetForm();
}); });
const handleClickStep = (value) => { const isError = ref(false);
stepActive["data"] = value; 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 { return {
handleClickStep, handleClickStep,
stepsFormated,
onSubmit, onSubmit,
stepList, onError,
checkoutBlock,
stepsFormated,
provinceOptions, provinceOptions,
totalPrice, totalPrice,
cartList, stepList,
step: ref(1),
cart, cart,
checkoutBlock, step: ref(1),
submitLoading,
successURL: ref(""),
cancelURL: ref(""),
meta, meta,
errors, errors,
isError,
name, name,
nameAttrs, nameAttrs,
surname, surname,
@ -199,19 +119,18 @@ export default defineComponent({
<Container tag="section"> <Container tag="section">
<header class="header-title" :class="!checkoutBlock && 'success'"> <header class="header-title" :class="!checkoutBlock && 'success'">
<h1 class="pege-title" v-if="checkoutBlock"> <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> </h1>
<p class="page-subtitle checkout" v-if="checkoutBlock"> <p class="page-subtitle checkout" v-if="checkoutBlock">
{{ {{
checkoutBlock && checkoutBlock
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 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."
{{
!checkoutBlock &&
"¡Tu pedido se ha realizado con éxito! Gracias por confiar en nosotros, en breves recibirás un correo con la confirmación de tu pedido."
}} }}
</p> </p>
</header> </header>
@ -260,7 +179,6 @@ export default defineComponent({
<div class="checkout-content"> <div class="checkout-content">
<div class="checkout-form"> <div class="checkout-form">
<q-form <q-form
action=""
method="post" method="post"
id="checkout-form" id="checkout-form"
@submit.prevent="onSubmit" @submit.prevent="onSubmit"
@ -477,12 +395,12 @@ export default defineComponent({
<ul class="checkout-summary-list"> <ul class="checkout-summary-list">
<li <li
class="checkout-summary-item" class="checkout-summary-item"
v-for="({ title, price, quantity }, index) in cartList" v-for="({ name, price }, index) in cart"
:key="index" :key="index"
> >
<p> <p>
{{ title }} ({{ quantity }}) {{ name }}
<span>{{ price }}</span> <span>{{ price }}</span>
</p> </p>
</li> </li>
</ul> </ul>
@ -545,47 +463,49 @@ export default defineComponent({
</div> </div>
</template> </template>
<div v-if="!checkoutBlock" class="checkout-success" id="success-block"> <template v-else>
<h6 class="checkout-success-title green-text"> <div class="checkout-success" id="success-block">
Has efectuado la siguiente compra <h6 class="checkout-success-title green-text">
</h6> Has efectuado la siguiente compra
</h6>
<div class="checkout-success-body"> <div class="checkout-success-body">
<div class="checkout-success-content"> <div class="checkout-success-content">
<ul class="checkout-success-list"> <ul class="checkout-success-list">
<li <li
v-for="({ title, price, quantity }, index) in cartList" v-for="({ name, price, image }, index) in cart"
:key="index" :key="index"
class="checkout-success-item" class="checkout-success-item"
> >
<div class="checkout-item-content"> <div class="checkout-item-content">
<div class="checkout-product-details"> <div class="checkout-product-details">
<img <img
src="../assets/checkout-flower.png" :src="isError ? '../assets/empty-img.jpg' : image"
alt="product" :alt="name"
class="checkout-product-img" class="checkout-product-img"
/> @error="onError"
<p class="checkout-product-title"> />
{{ title }} ({{ quantity }})
</p> <p class="checkout-product-title">
{{ name }}
</p>
</div>
<p class="checkout-product-price">{{ price }}</p>
</div> </div>
</li>
</ul>
</div>
<p class="checkout-product-price"> <footer class="checkout-success-footer">
{{ price }} <p class="checkout-success-paragraph">Total</p>
</p> <p class="checkout-success-paragraph">
</div> {{ totalPrice?.toFixed(2) }}
</li> </p>
</ul> </footer>
</div> </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>
</div> </template>
</div> </div>
</Container> </Container>
</q-page> </q-page>

View File

@ -9,11 +9,8 @@ export default defineComponent({
const { push } = useRouter(); const { push } = useRouter();
function startCountdown() { function startCountdown() {
// Cria um intervalo que executa a cada segundo
const interval = setInterval(() => { const interval = setInterval(() => {
// Decrementa o valor de count
counter.value--; counter.value--;
// Se o valor de count for zero, para o intervalo
if (counter.value === 1) { if (counter.value === 1) {
clearInterval(interval); clearInterval(interval);
push("/"); push("/");
@ -21,7 +18,6 @@ export default defineComponent({
}, 1000); }, 1000);
} }
// Chama a função para iniciar o contador quando o componente for montado
onMounted(startCountdown); onMounted(startCountdown);
return { return {

View File

@ -21,9 +21,10 @@ export default defineComponent({
const mobileStore = useMobileStore(); const mobileStore = useMobileStore();
const { isCarouselVisible, isOpenNav, screenWidth } = const { isCarouselVisible, isOpenNav, screenWidth } =
storeToRefs(mobileStore); storeToRefs(mobileStore);
const cartStore = useCartStore(); const cartStore = useCartStore();
const { getProducts } = cartStore; const { getProducts } = cartStore;
const { products } = storeToRefs(cartStore); const { products, homeSection } = storeToRefs(cartStore);
onBeforeMount(async () => { onBeforeMount(async () => {
await getProducts(); await getProducts();
@ -41,6 +42,7 @@ export default defineComponent({
isCarouselVisible, isCarouselVisible,
slidesContent, slidesContent,
screenWidth, screenWidth,
homeSection,
isOpenNav, isOpenNav,
products, products,
}; };
@ -54,35 +56,29 @@ export default defineComponent({
<VerticalCarouselImgs :imgsArr="slidesContent" class="home-carousel" /> <VerticalCarouselImgs :imgsArr="slidesContent" class="home-carousel" />
</q-no-ssr> </q-no-ssr>
<section class="products-section"> <section class="products-section" ref="homeSection">
<header class="products-section-header section-header"> <header class="products-section-header section-header">
<h3 class="products-header-title subtitle"> <h3 class="products-header-title subtitle">
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">
Descripción SEO: Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua.
</p> </p>
</header> </header>
<div class="products-body"> <div class="products-body">
<Container cardContainer> <Container cardContainer>
<template <template
v-for="( v-for="({ id, name, price, image, isNew }, i) in products?.data"
{ id, slug, name, price, images, isNew, discount }, i :key="id"
) in products.data.products"
> >
<Card <Card
v-if="i < 8" v-if="i < 8"
:id="slug" :id="id"
:price="price" :price="price"
:title="name" :title="name"
:discount="discount" :imgSrc="image"
:imgSrc="images[0]"
:isNew="isNew" :isNew="isNew"
:key="id"
imgClass="list-products" imgClass="list-products"
size="md-card" size="md-card"
/> />
@ -102,9 +98,6 @@ export default defineComponent({
</h3> </h3>
<p class="products-selection-paragraph section-paragraph"> <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> </p>
</header> </header>
@ -112,19 +105,15 @@ export default defineComponent({
<q-no-ssr> <q-no-ssr>
<Swiper> <Swiper>
<template <template
v-for="( v-for="({ id, isNew, price, name, image }, i) in products.data"
{ slug, discount, isNew, price, name, images }, i :key="id"
) in products.data.products"
:key="slug"
> >
<swiper-slide class="swiper-slide" v-if="i < 10"> <swiper-slide class="swiper-slide" v-if="i < 10">
<Card <Card
:id="slug" :id="id"
:key="slug"
:price="price" :price="price"
:title="name" :title="name"
:discount="discount" :imgSrc="image"
:imgSrc="images[0]"
:isNew="isNew" :isNew="isNew"
imgClass="carousel" imgClass="carousel"
size="lg-card" size="lg-card"
@ -177,6 +166,7 @@ export default defineComponent({
.products-section { .products-section {
text-align: center; text-align: center;
margin-bottom: 87px; margin-bottom: 87px;
scroll-margin: 30px;
& .products-section-header { & .products-section-header {
& .products-header-title { & .products-header-title {

View File

@ -1,8 +1,7 @@
<script> <script>
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { useMeta } from "quasar"; import { useMeta } from "quasar";
import { useForm } from "vee-validate"; import { defineComponent, onBeforeMount, ref, watch } from "vue";
import { defineComponent, onBeforeMount, reactive, 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";
@ -19,7 +18,7 @@ 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 { dedicationSchema } from "src/utils/zod/schemas"; import { usePostalCalendar } from "src/hooks/usePostalCalendar";
import { useCartStore } from "stores/cart"; import { useCartStore } from "stores/cart";
import { useModalStore } from "stores/modalStore"; import { useModalStore } from "stores/modalStore";
@ -43,48 +42,30 @@ export default defineComponent({
setup() { setup() {
const route = useRoute(); const route = useRoute();
const {
fields: { dedication, dedicationAttrs },
} = usePostalCalendar({ modalItem: "isOpenAvailability" });
const modalStore = useModalStore(); const modalStore = useModalStore();
const { openModal } = modalStore; const { openModal } = modalStore;
const cartStore = useCartStore(); const cartStore = useCartStore();
const { getProduct, getProducts, products } = cartStore; const { getProduct, getProducts } = cartStore;
const { prevProduct, currentProduct, nextProduct, addCartLoadingBtn } = const { products, featuredProducts, addCartLoadingBtn } =
storeToRefs(cartStore); storeToRefs(cartStore);
onBeforeMount(() => { onBeforeMount(() => {
getProduct(route.params.id); getProduct(route.params.id);
getProducts();
}); });
watch(currentProduct.value, (newValue) => { watch(
useMeta(() => { () => products.value.current?.type,
return { (newCategory) => {
title: `${newValue.value?.title}`, getProducts({
titleTemplate: (title) => `${title} - FloraNet`, // type: newCategory,
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( watch(
() => route.params.id, () => route.params.id,
@ -93,45 +74,55 @@ export default defineComponent({
} }
); );
const currentData = reactive({}); useMeta(() => ({
watch(currentProduct.value, (newData) => { title: `${products.value.current?.title}`,
if (newData.value) { titleTemplate: (title) => `${title} - FloraNet`,
const { id, ...newDataWhithoutId } = newData.value; meta: {
currentData.value = { description: {
...newDataWhithoutId, name: "description",
productId: +route.params.id, 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({ return true;
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 { return {
openModal,
checkImageValidity,
slide: ref(1), slide: ref(1),
fullscreen: ref(false), fullscreen: ref(false),
dedication, dedication,
dedicationAttrs, dedicationAttrs,
products, products,
featuredProducts,
addCartLoadingBtn, addCartLoadingBtn,
prevProduct,
currentProduct,
nextProduct,
currentData,
category,
openModal,
}; };
}, },
}); });
@ -141,39 +132,33 @@ export default defineComponent({
<q-page> <q-page>
<Container class="product-container" tag="section"> <Container class="product-container" tag="section">
<ProductCarousel> <ProductCarousel>
<template v-for="(img, i) in currentProduct?.images" :key="i"> <q-carousel-slide
<q-carousel-slide :img-src="
v-if="img" checkImageValidity(products.current?.image)
:img-src="img" ? products.current?.image
class="product-gallery-item" : '../assets/empty-img.jpg'
:name="i + 1" "
/> class="product-gallery-item"
:name="1"
<q-carousel-slide />
v-else
:img-src="'../assets/empty-img.jpg'"
class="product-gallery-item"
:name="1"
/>
</template>
</ProductCarousel> </ProductCarousel>
<div class="product-content"> <div class="product-content">
<header class="product-content-header"> <header class="product-content-header">
<h3 class="product-content-title subtitle"> <h3 class="product-content-title subtitle">
{{ currentProduct?.name }} {{ products.current?.name }}
<q-skeleton type="rect" v-if="!currentProduct?.name" /> <q-skeleton type="rect" v-if="!products.current?.name" />
</h3> </h3>
<div class="product-header-block"> <div class="product-header-block">
<p class="product-content-paragraph"> <p class="product-content-paragraph">
SKU: SKU:
<span class="green-text" style="display: inline-flex"> <span class="green-text" style="display: inline-flex">
{{ currentProduct?.slug }} {{ products.current?.id }}
<q-skeleton <q-skeleton
width="100px" width="100px"
type="text" type="text"
v-if="!currentProduct?.slug" v-if="!products.current?.id"
/> />
</span> </span>
</p> </p>
@ -181,11 +166,11 @@ export default defineComponent({
<p class="product-content-paragraph"> <p class="product-content-paragraph">
Categoría: Categoría:
<span class="green-text"> <span class="green-text">
{{ category[currentProduct?.category] }} {{ products.current?.type }}
<q-skeleton <q-skeleton
type="text" type="text"
width="50px" width="50px"
v-if="!currentProduct?.category" v-if="!products.current?.type"
/> />
</span> </span>
</p> </p>
@ -195,19 +180,21 @@ export default defineComponent({
<div class="product-content-body"> <div class="product-content-body">
<div class="product-content-paragraphs"> <div class="product-content-paragraphs">
<p class="product-price green-text"> <p class="product-price green-text">
{{ currentProduct?.price }} {{ products.current?.price }}
<q-skeleton <q-skeleton
type="text" type="text"
height="90px" height="90px"
width="80px" width="80px"
v-if="!currentProduct?.price" 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>
<p class="product-description"> <p class="product-description">
{{ currentProduct?.description }} {{ products.current?.description }}
<q-skeleton type="text" v-if="!currentProduct?.description" /> <q-skeleton type="text" v-if="!products.current?.description" />
<q-skeleton type="text" v-if="!currentProduct?.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> </p>
</div> </div>
@ -263,39 +250,32 @@ export default defineComponent({
:style="+$route.params.id === 1 && 'justify-content: flex-end;'" :style="+$route.params.id === 1 && 'justify-content: flex-end;'"
> >
<q-btn <q-btn
v-if="+$route.params.id > 1"
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}`"
v-if="+$route.params.id > 1" @click="products.current.value = undefined"
@click="currentProduct.value = undefined"
> >
<IconArrowCircleFilledLeft /> <IconArrowCircleFilledLeft />
<div class="btn-pag-paragraphs"> <div class="btn-pag-paragraphs">
<p class="btn-paragraph-top green-text">Produto anterior</p> <p class="btn-paragraph-top green-text">Produto anterior</p>
<p <p class="product-paragraph-bottom" :title="products.prev?.name">
class="product-paragraph-bottom" {{ products.prev?.name }}
:title="prevProduct.value?.title"
>
{{ prevProduct.value?.title }}
</p> </p>
</div> </div>
</q-btn> </q-btn>
<q-btn <q-btn
v-if="products.next?.id"
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}`"
v-if="nextProduct.value?.id"
@click="currentProduct.value = undefined"
> >
<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>
<p <p class="product-paragraph-bottom" :title="products.next?.name">
class="product-paragraph-bottom" {{ products.next?.name }}
:title="nextProduct.value?.title"
>
{{ nextProduct.value?.title }}
</p> </p>
</div> </div>
@ -314,26 +294,24 @@ export default defineComponent({
</h3> </h3>
<p class="like-another-paragraph"> <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> </p>
</header> </header>
<Container cardContainer class="no-padding"> <Container cardContainer class="no-padding">
<template <template
v-for="({ images, discount, isNew, name, price, slug }, i) in products v-for="(
.data.products" { image, discount, isNew, name, price, id }, i
) in products.data"
:key="id"
> >
<Card <Card
v-if="i < 4" v-if="i < 4"
:price="price" :price="price"
:title="name" :title="name"
:discount="discount" :discount="discount"
:imgSrc="images[0]" :imgSrc="image"
:isNew="isNew" :isNew="isNew"
:key="slug" :id="id"
:id="slug"
/> />
</template> </template>
</Container> </Container>

View File

@ -37,7 +37,7 @@ const routes = [
path: "", path: "",
name: "Checkout", name: "Checkout",
component: () => import("pages/CheckoutPage.vue"), component: () => import("pages/CheckoutPage.vue"),
}, }
], ],
}, },
{ {

View File

@ -1,105 +1,98 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { computed, reactive, ref } from "vue"; import { ref } from "vue";
import { useRouter } from "vue-router"; 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 { quasarNotify } from "src/functions/quasarNotify";
import { useLocalStorage } from "src/hooks/useLocalStorage";
export const useCartStore = defineStore("cart", () => { 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 { 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 }) { function transformOptionsToParams(
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(
options = { options = {
itens: undefined,
featured: undefined,
page: undefined,
category: undefined,
postalCode: undefined, postalCode: undefined,
dateExpired: undefined, dateExpired: undefined,
type: undefined,
minPrice: undefined, minPrice: undefined,
maxPrice: undefined, maxPrice: undefined,
bigPrice: undefined, bigPrice: undefined,
lowPrice: undefined, lowPrice: undefined,
isNew: undefined, isNew: undefined,
}, order_crescent: undefined,
navigate order_descending: undefined,
recommend: undefined,
}
) { ) {
const optionsObj = { const optionsObj = {
itens: options.itens,
featured: options.featured,
page: options.page,
category: options.category,
postalCode: options.postalCode, postalCode: options.postalCode,
dateExpired: options.dateExpired, dateExpired: options.dateExpired,
type: options.type,
minPrice: options.minPrice, minPrice: options.minPrice,
maxPrice: options.maxPrice, maxPrice: options.maxPrice,
bigPrice: options.bigPrice, bigPrice: options.bigPrice,
lowPrice: options.lowPrice, lowPrice: options.lowPrice,
isNew: options.isNew, isNew: options.isNew,
order_crescent: options.order_crescent,
order_descending: options.order_descending,
recommend: options.recommend,
}; };
const validKeys = Object.keys(options).filter( const validKeys = Object.keys(options).filter(
(key) => options[key] !== undefined (key) => options[key] !== undefined
@ -109,14 +102,51 @@ export const useCartStore = defineStore("cart", () => {
return acc; return acc;
}, {}); }, {});
try { return params;
const { }
data: { data },
} = await apiBack.get("products", {
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({ 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",
@ -124,22 +154,12 @@ export const useCartStore = defineStore("cart", () => {
}); });
} }
products.value.data = data[0]; products.value.data = data;
if (navigate) { if (scrollIntoView) {
navigate(); 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 FETCHED!", "color: green;");
console.groupCollapsed("%c PRODUCTS DATA", "color: tomato;"); console.groupCollapsed("%c PRODUCTS DATA", "color: tomato;");
console.table(products.value.data); console.table(products.value.data);
@ -147,7 +167,10 @@ export const useCartStore = defineStore("cart", () => {
console.groupCollapsed("%c PREV PRODUCT", "color: tomato;"); console.groupCollapsed("%c PREV PRODUCT", "color: tomato;");
console.table(products.value.prev); console.table(products.value.prev);
console.groupEnd(); 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.table(products.value.current);
console.groupEnd(); console.groupEnd();
console.groupCollapsed("%c NEXT PRODUCT", "color: tomato;"); 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) { if (id) {
routeId.value = id; routeId.value = id;
try { try {
/* const promises = [ const params = transformOptionsToParams(options);
api.get(`flowers/${id - 1}`),
api.get(`flowers/${id}`), const promises = [
api.get(`flowers/${id + 1}`), apiBack.get(`products/${+id - 1}`),
apiBack.get(`products/${+id}`),
apiBack.get(`products/${+id + 1}`),
]; ];
const results = await Promise.allSettled(promises); const results = await Promise.allSettled(promises);
const [prev, current, next] = results.map((res) => { const [prev, current, next] = results.map((res) => {
@ -180,27 +219,29 @@ export const useCartStore = defineStore("cart", () => {
rejected: res.reason, 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 = {}; if (!current) {
currentProduct.value = data.data[0]; push({ name: "NotFound" })
nextProduct.value = {}; }
console.groupCollapsed( if (debug) {
`%c PRODUCT FETCHED! SLUG: ${routeId.value}`, console.groupCollapsed(
"color: green;" `%c PRODUCT FETCHED! SLUG: ${routeId.value}`,
); "color: green;"
console.time(); );
console.table(prevProduct.value); console.table(products.value.prev);
console.table(currentProduct.value); console.table(products.value.current);
console.table(nextProduct.value); console.table(products.value.next);
console.timeEnd(); console.groupEnd();
console.groupEnd(); }
if (currentProduct.value.response?.status === 404) { if (products.value.current.response?.status === 404) {
push({ name: "NotFound" }); push({ name: "NotFound" });
} }
} catch (err) { } catch (err) {
@ -209,46 +250,107 @@ export const useCartStore = defineStore("cart", () => {
} }
} }
async function addToCart(product, dedication) { /**
cart.value.push({ ...product.value }); * Retrieves featured products based on the provided options.
dedicationTxt.value = dedication; *
addCartLoadingBtn.value = true; * @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 { try {
await api.post("cart", product); const params = transformOptionsToParams(options);
addCartLoadingBtn.value = false;
// push("/checkout");
console.groupCollapsed("%c Adicionado com sucesso!", "color: green"); (async () => {
console.table(cart.value); const {
console.groupEnd(); 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) { } catch (err) {
addCartLoadingBtn.value = false;
new Error(`FATAL ERROR ::: ${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",
});
}
/**
* Remove an item from the cart by its ID.
* @param {number} id - The ID of the item to be removed.
*/
function removeFromCart(id) { function removeFromCart(id) {
cart.value = cart.value.filter((p) => id !== p.id); const newArrRemovedItem = cart.value.filter((p) => id !== p.id);
api.delete(`cart/${id}`); addItem("cart", JSON.stringify(newArrRemovedItem))
} }
return { return {
checkoutRef,
homeSection,
cart, cart,
cartList,
totalPrice,
dedicationTxt,
cartLength,
prevProduct,
currentProduct,
nextProduct,
addCartLoadingBtn, addCartLoadingBtn,
products, products,
featuredProducts,
getFeaturedProducts,
getProducts, getProducts,
addToCart, addToCart,
removeFromCart, removeFromCart,
getProduct, getProduct,
getCart,
}; };
}); });

View File

@ -1,18 +1,20 @@
import { fullCurrentDate } from "src/constants/date";
import { z } from "zod"; import { z } from "zod";
import { postalCode } from ".."; import { postalCode } from "..";
import * as M from "../messages"; import * as M from "../messages";
const availabilityObj = { const availabilityObj = {
date: z.string({ required_error: M.requiredMessage }).refine((val) => { 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 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!"), }, "La fecha no puede ser inferior al día de hoy!"),
postalCode, postalCode,
dedication: z.string().optional(),
}; };
export const availabilitySchema = z.object(availabilityObj); export const availabilitySchema = z.object(availabilityObj);