cambios programación semana 22

This commit is contained in:
Jaume Solís 2024-01-24 19:52:24 +01:00
parent 5197f896d1
commit 3ca9560313
33 changed files with 1089 additions and 739 deletions

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))
} }
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)
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,69 +48,75 @@ 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
}) })
} }
} }

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!");
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> -->
</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",
}; };
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);
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([
{ 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);