develop #6

Merged
jsolis merged 4 commits from develop into master 2024-02-08 11:25:48 +00:00
37 changed files with 1091 additions and 741 deletions

12
.vscode/settings.json vendored
View File

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

View File

@ -5,45 +5,34 @@ const productsJson = require("./products.json")
class ProductController {
async findAll(req, res) {
const _products = await db.getProducts();
const mapedProducts = await _products[0].map(function (item) {
return {
id: item.id,
name: item.name,
description: item.description,
type: item.type,
price: item.price,
specialPrice: item.specialPrice,
isNew: item.isNew,
slug: item.slug,
category: item.category,
postalCode: item.postalCode,
dateExpired: item.dateExpired,
images: [item.image],
featured: item.featured,
}
})
//Gerar produtos fake
/* const fakeProducts = generateFlowers(50)
console.log(fakeProducts); */
const params = req.query;
let productsFilter = mapedProducts
console.log(params);
const _products = await db.getProducts(params.dateExpired, params.postalCode);
let productsFilter = _products[0];
if (Number(params.featured)) {
productsFilter = productsFilter.filter(item => item.featured === Number(params.featured))
if (Number(params.recommend)) {
productsFilter = productsFilter.filter(item => item.recommend == Number(params.recommend))
jsolis marked this conversation as resolved
Review

ya compruebas que es un numero en el if

ya compruebas que es un numero en el if
}
if (params.category) {
productsFilter = productsFilter.filter(item => item.category === Number(params.category))
if (params.type) {
productsFilter = productsFilter.filter(item => item.type === params.type)
}
if (params.postalCode) {
/*if (params.postalCode) {
productsFilter = productsFilter.filter(item => item.postalCode === params.postalCode)
}
if (params.dateExpired) {
const dateSearch = new Date(params.dateExpired);
productsFilter = productsFilter.filter(item => {
const dateProduct = new Date(item.dateExpired);
if (dateProduct >= dateSearch) {
return item
}
})
}*/
console.log(productsFilter.length);
if (params.minPrice && !params.maxPrice) {
productsFilter = productsFilter.filter(item => {
const price = Number(item.price.replace(/€/g, ''))
const price = Number(item.price)
jsolis marked this conversation as resolved
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)) {
return item
}
@ -51,7 +40,7 @@ class ProductController {
}
if (params.maxPrice && !params.minPrice) {
productsFilter = productsFilter.filter(item => {
const price = Number(item.price.replace(/€/g, ''))
const price = Number(item.price)
if (price <= Number(params.maxPrice)) {
return item
}
@ -59,71 +48,77 @@ class ProductController {
}
if (params.maxPrice && params.minPrice) {
productsFilter = productsFilter.filter(item => {
const price = Number(item.price.replace(/€/g, ''))
const price = Number(item.price)
if (price >= Number(params.minPrice) && price <= Number(params.maxPrice)) {
console.log(price);
return item
}
})
}
if (params.dateExpired) {
const [day, month, year] = params.dateExpired.split("/");
const dateSearch = new Date(year, month - 1, day);
productsFilter = productsFilter.filter(item => {
const [day, month, year] = item.dateExpired.split("/");
const dateProduct = new Date(year, month - 1, day);
if (dateProduct >= dateSearch) {
return item
}
})
}
if (Number(params.bigPrice)) {
productsFilter.sort((a, b) => {
const itemA = Number(a.price.replace(/€/g, ''))
const itemB = Number(b.price.replace(/€/g, ''))
const itemA = Number(a.price)
const itemB = Number(b.price)
return itemB - itemA;
})
}
if (Number(params.lowPrice)) {
productsFilter.sort((a, b) => {
const itemA = Number(a.price.replace(/€/g, ''))
const itemB = Number(b.price.replace(/€/g, ''))
const itemA = Number(a.price)
const itemB = Number(b.price)
return itemA - itemB;
})
}
if (params.isNew) {
productsFilter = productsFilter.filter(item => item.isNew === true)
if (Number(params.order_descending)) {
productsFilter.sort((a, b) => {
const itemA = a.order_position
const itemB = b.order_position
return itemB - itemA;
})
}
let productsFilterPages = []
const totalItens = params?.itens ? Number(params.itens) : 200
const page = params.page ? Number(params.page) : 1
const startIndex = (totalItens * page) - totalItens
const lastIndex = (totalItens * page)
const products = productsFilter.slice(startIndex, lastIndex)
productsFilterPages.push({
page: page,
productsPerPage: products.length,
products: products
})
if (Number(params.order_crescent)) {
productsFilter.sort((a, b) => {
const itemA = a.order_position
const itemB = b.order_position
return itemA - itemB;
})
}
if (Number(params.isNew)) {
console.log(params.isNew);
productsFilter = productsFilter.filter(item => item.isNew == Number(params.isNew))
}
/* let productsFilterPages = []
const totalItens = params?.itens ? Number(params.itens) : 200
const page = params.page ? Number(params.page) : 1
const startIndex = (totalItens * page) - totalItens
const lastIndex = (totalItens * page)
const products = productsFilter.slice(startIndex, lastIndex)
productsFilterPages.push({
page: page,
productsPerPage: products.length,
products: products
}) */
return res.status(200).send({
data: productsFilterPages
data: productsFilter
})
}
findBySlug(req, res) {
const slug = req.params.slug
const products = productsJson
const filterSlug = products.filter(item => item.slug === slug)
async findById(req, res) {
const id = Number(req.params.id)
const _products = await db.getProducts();
const filterProduct = _products[0].filter(item => item.id === id)
return res.status(200).send({
data: filterSlug
data: filterProduct
})
}
}
module.exports = new ProductController();
module.exports = new ProductController();

View File

@ -1,27 +1,27 @@
async function connect() {
if (global.connection && global.connection.state !== 'disconnected')
return global.connection;
if (global.connection && global.connection.state !== 'disconnected')
return global.connection;
const host = process.env.HOST;
const port = process.env.PORT;
const database = process.env.DATABASE;
const user = process.env.DB_USER;
const password = process.env.DB_PASSWORD;
const host = process.env.HOST;
const port = process.env.PORT;
const database = process.env.DATABASE;
const user = process.env.DB_USER;
const password = process.env.DB_PASSWORD;
const mysql = require("mysql2/promise");
const connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + "");
console.log("Connected to MySQL!");
global.connection = connection;
return connection;
const mysql = require("mysql2/promise");
const connection = await mysql.createConnection("mysql://" + user + ":" + password + "@" + host + ":" + port + "/" + database + "");
console.log("Connected to MySQL!");
global.connection = connection;
return connection;
}
async function getProducts() {
const conn = await connect();
const [rows] = await conn.query('CALL catalogue_get("2024-01-30", "08001")');
return rows;
async function getProducts(dateExpired, postalCode) {
console.log("Query in table MySQL!");
jsolis marked this conversation as resolved
Review

borrar console.log

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

View File

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

View File

@ -51,7 +51,7 @@
src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-element-bundle.min.js"
defer
></script>
<script src="https://js.stripe.com/v3" defer></script>
<!-- <script src="https://js.stripe.com/v3" defer></script> -->
jsolis marked this conversation as resolved
Review

borrar comentario

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

View File

@ -28,7 +28,7 @@ module.exports = configure(function (/* ctx */) {
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files
boot: ["i18n", "axios" /* , "stripe" */],
boot: ["i18n", "axios" /* , { path: "stripe", server: false } */],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ["app.scss"],
@ -119,7 +119,7 @@ module.exports = configure(function (/* ctx */) {
// directives: [],
// Quasar plugins
plugins: ["Meta", "Loading", "Notify"],
plugins: ["Meta", "Loading", "Notify", "LocalStorage", "SessionStorage"],
},
// animations: 'all', // --- includes all animations

BIN
src/assets/empty-img.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
src/assets/question-bg.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
src/assets/reasons-bg.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

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

View File

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

View File

@ -28,18 +28,12 @@ export default defineComponent({
<li class="footer-list-item">
<p class="footer-list-content">
Contáctanos <br />
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua
<br /><br />
Horario: 9 a 14h. y de 15 a 18h.
</p>
</li>
<li class="footer-list-item">
<p class="footer-list-content">
Lorem ipsum dolor sit amet,<br />
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua
</p>
</li>
</ul>

View File

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

View File

@ -3,27 +3,23 @@ import { defineComponent } from "vue";
import IconCart from "components/icons/IconCart.vue";
import IconHamburger from "components/icons/IconHamburger.vue";
/* import IconUser from "components/icons/IconUser.vue";
import DropdownGroup from "components/quasar-components/dropdown/DropdownGroup.vue";
import DropdownItem from "components/quasar-components/dropdown/DropdownItem.vue"; */
import { storeToRefs } from "pinia";
import { useCartStore } from "src/stores/cart";
import { useLocalStorage } from "src/hooks/useLocalStorage";
import { useMobileStore } from "stores/mobileNav";
export default defineComponent({
name: "user-area",
components: {
IconCart,
// IconUser,
// DropdownGroup,
// DropdownItem,
IconHamburger,
},
setup() {
const { getItem } = useLocalStorage();
const mobileStore = useMobileStore();
const { handleOpenMobileNav } = mobileStore;
const cartStore = useCartStore();
const { cartLength } = storeToRefs(cartStore);
const cartLength = getItem("cart").length;
return { handleOpenMobileNav, cartLength };
},

View File

@ -20,6 +20,7 @@ export default defineComponent({
setup() {
const {
onSubmit,
setValues,
fields: { calendar, calendarAttrs, postalCode, postalCodeAttrs },
errors,
} = usePostalCalendar({ type: "home" });
@ -43,6 +44,7 @@ export default defineComponent({
screenWidth,
errors,
onSubmit,
setValues,
postalCode,
postalCodeAttrs,
@ -86,15 +88,12 @@ export default defineComponent({
</h1>
<p class="carousel-header-paragraph">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>
</header>
<form @submit="onSubmit" class="carousel-content-body">
<div class="carousel-content-item">
<!-- <Calendar /> -->
<Calendar>
<Calendar :setValues="setValues">
<q-input
borderless
class="custom-date-input"
@ -109,7 +108,6 @@ export default defineComponent({
</div>
<div class="carousel-content-item">
<!-- <PostalCode /> -->
<PostalCode>
<q-input
borderless

View File

@ -18,7 +18,7 @@ export default defineComponent({
<template>
<section
class="question-container"
style="background-image: url('../../assets/question-bg.png')"
style="background-image: url('../../assets/question-bg.jpg')"
id="question-section"
>
<Container class="question-container-form">
@ -26,8 +26,6 @@ export default defineComponent({
<LogoWhite />
<p class="question-paragraph">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore...
</p>
</header>

View File

@ -27,7 +27,7 @@ export default defineComponent({
<template>
<section
class="reasons-container"
style="background-image: url('../../assets/reasons-bg.png')"
style="background-image: url('../../assets/reasons-bg.jpg')"
>
<Container class="reasons-container-body container-modded">
<header class="reasons-container-header">

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: "",
},
id: {
type: String,
type: Number,
required: true,
},
},
setup({ price, discount }) {
const isLoaded = ref(false);
const isError = ref(false);
const percent = +discount / 100;
//const priceWithoutLetter = ~~price.replaceAll("", "");
//const priceWithoutLetter = ~~price?.replaceAll("", "");
const priceWithoutLetter = price;
const finalValue = ~~(priceWithoutLetter - priceWithoutLetter * percent);
console.log(price);
jsolis marked this conversation as resolved
Review

borrar console.log

borrar console.log
const onLoad = () => {
isLoaded.value = true;
};
return { onLoad, isLoaded, finalValue, priceWithoutLetter };
const onError = () => {
isError.value = true;
};
return {
onLoad,
onError,
isLoaded,
isError,
finalValue,
priceWithoutLetter,
};
},
});
</script>
@ -82,10 +95,11 @@ export default defineComponent({
<img
class="card-img"
:class="[imgClass]"
:src="imgSrc ? imgSrc.replace('https://drive.google.com/file/d/', 'https://drive.google.com/thumbnail?id=').replace('/view?usp=drive_link', '') : '../../assets/empty-img.jpg'"
:src="imgSrc && !isError ? imgSrc : '../../assets/empty-img.jpg'"
:alt="alt"
:key="imgSrc"
@load="onLoad"
@error="onError"
/>
<q-skeleton
v-if="!isLoaded"
@ -105,7 +119,7 @@ export default defineComponent({
<div class="card-values">
<p class="price" v-if="finalValue">{{ finalValue }}</p>
<p class="price offer tachado" v-if="price !== finalValue">
<p class="price offer tachado" v-if="+price !== finalValue">
{{ price }}
</p>
</div>

View File

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

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",
timeout,
}),
info: () => Notify.create({
message,
color: "info",
position: "top",
icon: "info",
timeout,
}),
default: () => {
console.error(`Type is invalid! TYPE: ${type}`)
}
};
if (type) {
return obj[type]() || console.error(`Type is invalid! TYPE: ${type}`);
return obj[type]() || obj['default']();
}
console.error("Type is required, success, warning or erro");
}

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 { storeToRefs } from "pinia";
import { useForm } from "vee-validate";
import { watch } from "vue";
import { useRouter } from "vue-router";
import { ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { apiBack } from "src/boot/axios";
import { invertDate } from "src/functions/invertDate";
import { quasarNotify } from "src/functions/quasarNotify";
import { useCartStore } from "src/stores/cart";
import { useFormStore } from "src/stores/forms";
@ -13,38 +13,50 @@ import { useRangePriceStore } from "src/stores/rangePrice";
import { availabilitySchema } from "src/utils/zod/schemas";
import { rangePriceSchema } from "src/utils/zod/schemas/rangePriceSchema";
/**
* Custom hook for managing the postal and calendar functionality.
*
* @param {Object} options - The options for the hook.
* @param {string} options.modalItem - The modal item isOpenAvailability || isOpenFilters.
* @param {string} options.type - The type of the hook. home || product || availability || filter
* @returns {Object} - The hook functions and data.
*/
export function usePostalCalendar({ modalItem = "", type = "home" }) {
const { push } = useRouter();
const route = useRoute();
const { push } = useRouter()
const rangePriceStore = useRangePriceStore();
const { rangeValue } = storeToRefs(rangePriceStore);
const modalStore = useModalStore();
const { isOpenAvailability } = storeToRefs(modalStore);
const { openModal } = modalStore
const formStore = useFormStore();
const { availability, sortProductFilters } = storeToRefs(formStore);
const { sortProductFilters } = storeToRefs(formStore);
const cartStore = useCartStore();
const { addToCart, getProducts } = cartStore;
const { currentProduct, products } = storeToRefs(cartStore);
const { products, homeSection } = storeToRefs(cartStore);
const min = 0;
const max = 200;
const category = ref(route.path.split("/")[2])
const { handleSubmit, handleReset, defineField, errors } = useForm({
validationSchema: toTypedSchema(
type !== "filter" ? availabilitySchema : rangePriceSchema
),
initialValues: {
range: {
min,
max,
const { handleSubmit, handleReset, defineField, errors, setValues } = useForm(
{
validationSchema: toTypedSchema(
type !== "filter" ? availabilitySchema : rangePriceSchema
),
initialValues: {
range: {
min,
max,
},
postalCode: "",
date: "",
},
postalCode: "",
date: "",
},
});
}
);
const [calendar, calendarAttrs] = defineField("date");
const [postalCode, postalCodeAttrs] = defineField("postalCode");
const [priceRange, priceRangeAttrs] = defineField("range");
@ -56,6 +68,8 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
quasarNotify({ message: newErrors.postalCode, type: "erro" }),
date: () => quasarNotify({ message: newErrors.date, type: "erro" }),
range: () => quasarNotify({ message: newErrors.range, type: "erro" }),
dedication: () =>
quasarNotify({ message: newErrors.dedication, type: "erro" }),
};
const keys = Object.keys(newErrors);
keys.forEach((key) => {
@ -63,47 +77,73 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
});
});
watch(
[() => route.path, () => sortProductFilters.value],
([newPath]) => {
const categoryPath = newPath.split("/")[2];
category.value = categoryPath;
}
);
const onSubmit = handleSubmit((values) => {
const postalAndDateParams = {
postalCode: values.postalCode,
dateExpired: values.date,
dateExpired: invertDate(values.date),
};
const categoryObj = {
plantas: "Floranet Plantas",
ramos: "Floranet Ramos",
};
const objVal = {
home: async () => {
console.log(type);
getProducts(
await getProducts(
{
itens: 30,
category: 1,
postalCode: values.postalCode,
dateExpired: values.date,
dateExpired: invertDate(values.date),
},
() => push("/categoria/ramos")
() => homeSection.value.scrollIntoView()
);
},
product: async () => {
console.log(type);
try {
const {
data: { data },
} = await apiBack.get(`products/slug/${currentProduct.value.id}`, {
params: postalAndDateParams,
});
} catch (error) {
console.error(error);
push("/");
await getProducts(postalAndDateParams);
const hasProduct = products.value.data.some((item) => {
const date = new Date(item.dateExpired);
const day = date.getDate();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear();
const dateExpired = `${day}/${month}/${year}`;
const id = +route.path.split('/')[2];
return item.postalCode === values.postalCode && item.id === id && values.date <= dateExpired
});
if (!hasProduct) {
push('/categoria/ramos')
quasarNotify({ message: 'Seleccione una nueva fecha y un nuevo código postal.', type: 'warning' })
setTimeout(() => {
openModal({ modal: 'availability' })
}, 2000)
return
}
addToCart(products.value.current, dedication)
},
availability: async () => {
console.log(type);
getProducts({
itens: 20,
await getProducts({
postalCode: values.postalCode,
dateExpired: values.date,
dateExpired: invertDate(values.date),
});
},
filter: async () => {
@ -112,26 +152,21 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
rangeValue.value.max = values.range.max;
rangeValue.value.min = values.range.min;
const category = sortProductFilters.value.category;
const categoryObj = {
plantas: 1,
ramos: 2,
};
const params = {
itens: 20,
category: categoryObj[category],
type: categoryObj[category.value],
minPrice: values.range.min,
maxPrice: values.range.max,
};
getProducts(params);
await getProducts(params);
},
default: () => {
console.error(
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
);
},
};
objVal[type]() ||
console.error(
`INVALID TYPE! TYPE: ${type}, ONLY HOME, PRODUCT AND FILTER ARE VALID!`
);
objVal[type]() || objVal["default"]();
if (modalItem) {
modalStore[modalItem] = false;
@ -141,6 +176,7 @@ export function usePostalCalendar({ modalItem = "", type = "home" }) {
return {
onSubmit,
setValues,
modalStore,
fields: {
calendar,

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>
<mobile-nav />
<q-page-container class="no-padding more product-layout">
<router-view />
</q-page-container>
<q-no-ssr>
<q-page-container class="no-padding more product-layout">
<router-view />
</q-page-container>
</q-no-ssr>
<reasons-section />

View File

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

View File

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

View File

@ -1,14 +1,9 @@
<script>
import { toTypedSchema } from "@vee-validate/zod";
import { storeToRefs } from "pinia";
import { useForm } from "vee-validate";
import { computed, defineComponent, reactive, ref } from "vue";
import { defineComponent, onBeforeMount, ref } from "vue";
import { useRouter } from "vue-router";
import Container from "src/components/ui/Container.vue";
import { useCartStore } from "src/stores/cart";
import { useFormStore } from "src/stores/forms";
import { checkoutSchema } from "src/utils/zod/schemas/checkoutSchema";
import { useCheckoutForm } from "src/hooks/useCheckoutForm";
export default defineComponent({
name: "CheckoutPage",
@ -17,150 +12,75 @@ export default defineComponent({
},
setup() {
const { push } = useRouter();
const cartStore = useCartStore();
const { cart, cartList, totalPrice, cartLength } = storeToRefs(cartStore);
if (cartLength.value === 0) return push("/");
const formStore = useFormStore();
const { handleCheckoutData } = formStore;
const { meta, errors, handleSubmit, defineField, resetForm } = useForm({
validationSchema: toTypedSchema(checkoutSchema),
initialValues: {
paymentMethod: "stripe",
terms: false,
const {
provinceOptions,
handleClickStep,
stepsFormated,
stepList,
checkoutBlock,
cart,
totalPrice,
formState: { errors, meta, onSubmit, submitLoading },
fields: {
name,
nameAttrs,
surname,
surnameAttrs,
address,
addressAttrs,
postalCode,
postalCodeAttrs,
phone,
phoneAttrs,
city,
cityAttrs,
province,
provinceAttrs,
senderName,
senderNameAttrs,
senderCifNif,
senderCifNifAttrs,
senderEmail,
senderEmailAttrs,
senderPhone,
senderPhoneAttrs,
senderNotes,
senderNotesAttrs,
paymentMethod,
paymentMethodAttrs,
terms,
termsAttrs,
},
});
const [name, nameAttrs] = defineField("name");
const [surname, surnameAttrs] = defineField("surname");
const [address, addressAttrs] = defineField("address");
const [postalCode, postalCodeAttrs] = defineField("postalCode");
const [phone, phoneAttrs] = defineField("phone");
const [city, cityAttrs] = defineField("city");
const [province, provinceAttrs] = defineField("province");
const [senderName, senderNameAttrs] = defineField("senderName");
const [senderCifNif, senderCifNifAttrs] = defineField("senderCifNif");
const [senderEmail, senderEmailAttrs] = defineField("senderEmail");
const [senderPhone, senderPhoneAttrs] = defineField("senderPhone");
const [senderNotes, senderNotesAttrs] = defineField("senderNotes");
const [paymentMethod, paymentMethodAttrs] = defineField("paymentMethod");
const [terms, termsAttrs] = defineField("terms");
} = useCheckoutForm();
const stepActive = reactive({ data: 1 });
const stepList = reactive({
data: [
{
value: 1,
name: "Paso 1",
description: "Datos de facturación",
active: true,
},
{
value: 2,
name: "Paso 2",
description: "Confirmación",
active: false,
},
{
value: 3,
name: "Paso 3",
description: "Pago",
active: false,
},
],
});
const checkoutBlock = ref(true);
const onSubmit = handleSubmit((values) => {
handleCheckoutData(values);
stepList.data[2].active = true;
checkoutBlock.value = false;
resetForm();
onBeforeMount(() => {
if (cart.length === 0) return push("/");
});
const handleClickStep = (value) => {
stepActive["data"] = value;
const isError = ref(false);
const onError = () => {
isError.value = true;
};
const stepsFormated = computed(() => {
return stepList["data"].map((step) => {
if (step.value === stepActive["data"]) {
return { ...step, active: true };
}
return step;
});
});
const provinceOptions = ref([
{ code: "01", name: "Araba/Álava" },
{ code: "02", name: "Albacete" },
{ code: "03", name: "Alicante/Alacant" },
{ code: "04", name: "Almería" },
{ code: "05", name: "Ávila" },
{ code: "06", name: "Badajoz" },
{ code: "07", name: "Balears, Illes" },
{ code: "08", name: "Barcelona" },
{ code: "09", name: "Burgos" },
{ code: "10", name: "Cáceres" },
{ code: "11", name: "Cádiz" },
{ code: "12", name: "Castellón/Castelló" },
{ code: "13", name: "Ciudad Real" },
{ code: "14", name: "Córdoba" },
{ code: "15", name: "Coruña, A" },
{ code: "16", name: "Cuenca" },
{ code: "17", name: "Girona" },
{ code: "18", name: "Granada" },
{ code: "19", name: "Guadalajara" },
{ code: "20", name: "Gipuzkoa" },
{ code: "21", name: "Huelva" },
{ code: "22", name: "Huesca" },
{ code: "23", name: "Jaén" },
{ code: "24", name: "León" },
{ code: "25", name: "Lleida" },
{ code: "26", name: "Rioja, La" },
{ code: "27", name: "Lugo" },
{ code: "28", name: "Madrid" },
{ code: "29", name: "Málaga" },
{ code: "30", name: "Murcia" },
{ code: "31", name: "Navarra" },
{ code: "32", name: "Ourense" },
{ code: "33", name: "Asturias" },
{ code: "34", name: "Palencia" },
{ code: "35", name: "Palmas, Las" },
{ code: "36", name: "Pontevedra" },
{ code: "37", name: "Salamanca" },
{ code: "38", name: "Santa Cruz de Tenerife" },
{ code: "39", name: "Cantabria" },
{ code: "40", name: "Segovia" },
{ code: "41", name: "Sevilla" },
{ code: "42", name: "Soria" },
{ code: "43", name: "Tarragona" },
{ code: "44", name: "Teruel" },
{ code: "45", name: "Toledo" },
{ code: "46", name: "Valencia/València" },
{ code: "47", name: "Valladolid" },
{ code: "48", name: "Bizkaia" },
{ code: "49", name: "Zamora" },
{ code: "50", name: "Zaragoza" },
{ code: "51", name: "Ceuta" },
{ code: "52", name: "Melilla" },
]);
return {
handleClickStep,
stepsFormated,
onSubmit,
stepList,
onError,
checkoutBlock,
stepsFormated,
provinceOptions,
totalPrice,
cartList,
step: ref(1),
stepList,
cart,
checkoutBlock,
step: ref(1),
submitLoading,
successURL: ref(""),
cancelURL: ref(""),
meta,
errors,
isError,
name,
nameAttrs,
surname,
@ -199,19 +119,18 @@ export default defineComponent({
<Container tag="section">
<header class="header-title" :class="!checkoutBlock && 'success'">
<h1 class="pege-title" v-if="checkoutBlock">
{{ checkoutBlock && "¿A quién y dónde lo entregamos?" }}
{{ !checkoutBlock && "¡Muchas gracias Jerom!" }}
{{
checkoutBlock
? "¿A quién y dónde lo entregamos?"
: '"¡Muchas gracias Jerom!"'
}}
</h1>
<p class="page-subtitle checkout" v-if="checkoutBlock">
{{
checkoutBlock &&
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
}}
{{
!checkoutBlock &&
"¡Tu pedido se ha realizado con éxito! Gracias por confiar en nosotros, en breves recibirás un correo con la confirmación de tu pedido."
checkoutBlock
? "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
: "¡Tu pedido se ha realizado con éxito! Gracias por confiar en nosotros, en breves recibirás un correo con la confirmación de tu pedido."
}}
</p>
</header>
@ -260,7 +179,6 @@ export default defineComponent({
<div class="checkout-content">
<div class="checkout-form">
<q-form
action=""
method="post"
id="checkout-form"
@submit.prevent="onSubmit"
@ -477,12 +395,12 @@ export default defineComponent({
<ul class="checkout-summary-list">
<li
class="checkout-summary-item"
v-for="({ title, price, quantity }, index) in cartList"
v-for="({ name, price }, index) in cart"
:key="index"
>
<p>
{{ title }} ({{ quantity }})
<span>{{ price }}</span>
{{ name }}
<span>{{ price }}</span>
</p>
</li>
</ul>
@ -545,47 +463,49 @@ export default defineComponent({
</div>
</template>
<div v-if="!checkoutBlock" class="checkout-success" id="success-block">
<h6 class="checkout-success-title green-text">
Has efectuado la siguiente compra
</h6>
<template v-else>
<div class="checkout-success" id="success-block">
<h6 class="checkout-success-title green-text">
Has efectuado la siguiente compra
</h6>
<div class="checkout-success-body">
<div class="checkout-success-content">
<ul class="checkout-success-list">
<li
v-for="({ title, price, quantity }, index) in cartList"
:key="index"
class="checkout-success-item"
>
<div class="checkout-item-content">
<div class="checkout-product-details">
<img
src="../assets/checkout-flower.png"
alt="product"
class="checkout-product-img"
/>
<p class="checkout-product-title">
{{ title }} ({{ quantity }})
</p>
<div class="checkout-success-body">
<div class="checkout-success-content">
<ul class="checkout-success-list">
<li
v-for="({ name, price, image }, index) in cart"
:key="index"
class="checkout-success-item"
>
<div class="checkout-item-content">
<div class="checkout-product-details">
<img
:src="isError ? '../assets/empty-img.jpg' : image"
:alt="name"
class="checkout-product-img"
@error="onError"
/>
<p class="checkout-product-title">
{{ name }}
</p>
</div>
<p class="checkout-product-price">{{ price }}</p>
</div>
</li>
</ul>
</div>
<p class="checkout-product-price">
{{ price }}
</p>
</div>
</li>
</ul>
<footer class="checkout-success-footer">
<p class="checkout-success-paragraph">Total</p>
<p class="checkout-success-paragraph">
{{ totalPrice?.toFixed(2) }}
</p>
</footer>
</div>
<footer class="checkout-success-footer">
<p class="checkout-success-paragraph">Total</p>
<p class="checkout-success-paragraph">
{{ totalPrice?.toFixed(2) }}
</p>
</footer>
</div>
</div>
</template>
</div>
</Container>
</q-page>

View File

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

View File

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

View File

@ -1,8 +1,7 @@
<script>
import { storeToRefs } from "pinia";
import { useMeta } from "quasar";
import { useForm } from "vee-validate";
import { defineComponent, onBeforeMount, reactive, ref, watch } from "vue";
import { defineComponent, onBeforeMount, ref, watch } from "vue";
import { useRoute } from "vue-router";
import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue";
@ -19,7 +18,7 @@ import Card from "components/ui/Card.vue";
import Container from "components/ui/Container.vue";
import Modal from "components/ui/Modal.vue";
import { dedicationSchema } from "src/utils/zod/schemas";
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
import { useCartStore } from "stores/cart";
import { useModalStore } from "stores/modalStore";
@ -43,48 +42,30 @@ export default defineComponent({
setup() {
const route = useRoute();
const {
fields: { dedication, dedicationAttrs },
} = usePostalCalendar({ modalItem: "isOpenAvailability" });
const modalStore = useModalStore();
const { openModal } = modalStore;
const cartStore = useCartStore();
const { getProduct, getProducts, products } = cartStore;
const { prevProduct, currentProduct, nextProduct, addCartLoadingBtn } =
const { getProduct, getProducts } = cartStore;
const { products, featuredProducts, addCartLoadingBtn } =
storeToRefs(cartStore);
onBeforeMount(() => {
getProduct(route.params.id);
getProducts();
});
watch(currentProduct.value, (newValue) => {
useMeta(() => {
return {
title: `${newValue.value?.title}`,
titleTemplate: (title) => `${title} - FloraNet`,
meta: {
description: {
name: "description",
content: `${newValue.value?.description}`,
},
keywords: { name: "keywords", content: `${newValue.value?.title}` },
equiv: {
"http-equiv": "Content-Type",
content: "text/html; charset=UTF-8",
},
ogTitle: {
property: "og:title",
template(ogTitle) {
return `${ogTitle} - FloraNet`;
},
},
noscript: {
default:
"This is content for browsers with no JS (or disabled JS)",
},
},
};
});
});
watch(
() => products.value.current?.type,
(newCategory) => {
getProducts({
// type: newCategory,
});
}
);
watch(
() => route.params.id,
@ -93,45 +74,55 @@ export default defineComponent({
}
);
const currentData = reactive({});
watch(currentProduct.value, (newData) => {
if (newData.value) {
const { id, ...newDataWhithoutId } = newData.value;
currentData.value = {
...newDataWhithoutId,
productId: +route.params.id,
};
useMeta(() => ({
title: `${products.value.current?.title}`,
titleTemplate: (title) => `${title} - FloraNet`,
meta: {
description: {
name: "description",
content: `${products.value.current?.description}`,
},
keywords: {
name: "keywords",
content: `${products.value.current?.title}`,
},
equiv: {
"http-equiv": "Content-Type",
content: "text/html; charset=UTF-8",
},
ogTitle: {
property: "og:title",
template(ogTitle) {
return `${ogTitle} - FloraNet`;
},
},
noscript: {
default: "This is content for browsers with no JS (or disabled JS)",
},
},
}));
const checkImageValidity = (imageLink) => {
const validExtensions = [".jpg", ".jpeg", ".png"];
if (imageLink) {
const extension = imageLink.substring(imageLink.lastIndexOf("."));
return validExtensions.includes(extension.toLowerCase());
}
});
const category = reactive({
1: "Planta",
2: "Ramos",
});
const { handleSubmit, defineField, handleReset } = useForm({
validationSchema: dedicationSchema,
});
const [dedication, dedicationAttrs] = defineField("dedication");
/* const onSubmit = handleSubmit(() => {
openModal({ modal: "availability" });
// addToCart(currentData.value, dedication);
// handleReset();
}); */
return true;
};
return {
openModal,
checkImageValidity,
slide: ref(1),
fullscreen: ref(false),
dedication,
dedicationAttrs,
products,
featuredProducts,
addCartLoadingBtn,
prevProduct,
currentProduct,
nextProduct,
currentData,
category,
openModal,
};
},
});
@ -141,39 +132,33 @@ export default defineComponent({
<q-page>
<Container class="product-container" tag="section">
<ProductCarousel>
<template v-for="(img, i) in currentProduct?.images" :key="i">
<q-carousel-slide
v-if="img"
:img-src="img"
class="product-gallery-item"
:name="i + 1"
/>
<q-carousel-slide
v-else
:img-src="'../assets/empty-img.jpg'"
class="product-gallery-item"
:name="1"
/>
</template>
<q-carousel-slide
:img-src="
checkImageValidity(products.current?.image)
? products.current?.image
: '../assets/empty-img.jpg'
"
class="product-gallery-item"
:name="1"
/>
</ProductCarousel>
<div class="product-content">
<header class="product-content-header">
<h3 class="product-content-title subtitle">
{{ currentProduct?.name }}
<q-skeleton type="rect" v-if="!currentProduct?.name" />
{{ products.current?.name }}
<q-skeleton type="rect" v-if="!products.current?.name" />
</h3>
<div class="product-header-block">
<p class="product-content-paragraph">
SKU:
<span class="green-text" style="display: inline-flex">
{{ currentProduct?.slug }}
{{ products.current?.id }}
<q-skeleton
width="100px"
type="text"
v-if="!currentProduct?.slug"
v-if="!products.current?.id"
/>
</span>
</p>
@ -181,11 +166,11 @@ export default defineComponent({
<p class="product-content-paragraph">
Categoría:
<span class="green-text">
{{ category[currentProduct?.category] }}
{{ products.current?.type }}
<q-skeleton
type="text"
width="50px"
v-if="!currentProduct?.category"
v-if="!products.current?.type"
/>
</span>
</p>
@ -195,19 +180,21 @@ export default defineComponent({
<div class="product-content-body">
<div class="product-content-paragraphs">
<p class="product-price green-text">
{{ currentProduct?.price }}
{{ products.current?.price }}
<q-skeleton
type="text"
height="90px"
width="80px"
v-if="!currentProduct?.price"
v-if="!products.current?.price"
/>
</p>
<p class="product-delivery green-text">Envío Gratuito</p>
<p class="product-description">
{{ currentProduct?.description }}
<q-skeleton type="text" v-if="!currentProduct?.description" />
<q-skeleton type="text" v-if="!currentProduct?.description" />
{{ products.current?.description }}
<q-skeleton type="text" v-if="!products.current?.description" />
<q-skeleton type="text" v-if="!products.current?.description" />
<q-skeleton type="text" v-if="!products.current?.description" />
<q-skeleton type="text" v-if="!products.current?.description" />
</p>
</div>
@ -263,39 +250,32 @@ export default defineComponent({
:style="+$route.params.id === 1 && 'justify-content: flex-end;'"
>
<q-btn
v-if="+$route.params.id > 1"
color="white"
class="btn outlined rounded sm-btn product-pag-item product-prev-btn"
:to="`${+$route.params.id - 1}`"
v-if="+$route.params.id > 1"
@click="currentProduct.value = undefined"
@click="products.current.value = undefined"
>
<IconArrowCircleFilledLeft />
<div class="btn-pag-paragraphs">
<p class="btn-paragraph-top green-text">Produto anterior</p>
<p
class="product-paragraph-bottom"
:title="prevProduct.value?.title"
>
{{ prevProduct.value?.title }}
<p class="product-paragraph-bottom" :title="products.prev?.name">
{{ products.prev?.name }}
</p>
</div>
</q-btn>
<q-btn
v-if="products.next?.id"
color="white"
class="btn outlined rounded sm-btn product-pag-item product-next-btn"
:to="`${+$route.params.id + 1}`"
v-if="nextProduct.value?.id"
@click="currentProduct.value = undefined"
>
<div class="btn-pag-paragraphs">
<p class="btn-paragraph-top green-text">Siguiente producto</p>
<p
class="product-paragraph-bottom"
:title="nextProduct.value?.title"
>
{{ nextProduct.value?.title }}
<p class="product-paragraph-bottom" :title="products.next?.name">
{{ products.next?.name }}
</p>
</div>
@ -314,26 +294,24 @@ export default defineComponent({
</h3>
<p class="like-another-paragraph">
Descripción SEO: Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua.
</p>
</header>
<Container cardContainer class="no-padding">
<template
v-for="({ images, discount, isNew, name, price, slug }, i) in products
.data.products"
v-for="(
{ image, discount, isNew, name, price, id }, i
) in products.data"
:key="id"
>
<Card
v-if="i < 4"
:price="price"
:title="name"
:discount="discount"
:imgSrc="images[0]"
:imgSrc="image"
:isNew="isNew"
:key="slug"
:id="slug"
:id="id"
/>
</template>
</Container>

View File

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

View File

@ -1,105 +1,98 @@
import { defineStore } from "pinia";
import { computed, reactive, ref } from "vue";
import { ref } from "vue";
import { useRouter } from "vue-router";
import { api, apiBack } from "src/boot/axios";
import { apiBack } from "src/boot/axios";
import { quasarNotify } from "src/functions/quasarNotify";
import { useLocalStorage } from "src/hooks/useLocalStorage";
export const useCartStore = defineStore("cart", () => {
const cart = ref([]);
const cartList = ref([]);
const products = ref({
data: {
page: undefined,
productsPerPage: undefined,
products: [],
},
prev: {},
current: {},
next: {},
});
const dedicationTxt = ref("");
const prevProduct = reactive({});
const currentProduct = ref();
const nextProduct = reactive({});
const addCartLoadingBtn = ref(false);
const cartLength = computed(() => cart.value.length);
const routeId = ref(null);
const totalPrice = computed(() => {
return cart.value.reduce((acc, { price }) => {
if (price) {
const priceWithoutLetter = price?.replace("€", "");
return +priceWithoutLetter + acc;
}
}, 0);
});
const { push } = useRouter();
const { addItem, getItem, removeItem } = useLocalStorage()
//! Elements
const checkoutRef = ref(null);
const homeSection = ref(null);
const initialValues = [{
id: null,
name: "",
price: null,
image: "",
description: "",
dateExpired: "",
isNew: null,
type: "",
postalCode: "",
order_position: null,
recommend: null
}]
//! Variables
const cart = ref([]);
(() => {
cart.value = getItem('cart');
})()
const addCartLoadingBtn = ref(false);
const routeId = ref(null);
const products = ref({
data: initialValues,
prev: initialValues,
current: initialValues,
next: initialValues,
});
const featuredProducts = ref({
page: undefined,
productsPerPage: undefined,
products: [],
});
/**
* Transforms options object into params object.
*
* @param debug Allow the data console - boolean
*
* @param {Object} options - The options object.
* @param {number} options.itens - The items array.
* @param {boolean} options.featured - The featured flag.
* @param {number} options.page - The page number.
* @param {string} options.type - The type name.
* @param {string} options.postalCode - The postal code.
* @param {string} options.dateExpired - The expiration date.
* @param {number} options.minPrice - The minimum price.
* @param {number} options.maxPrice - The maximum price.
* @param {number} options.bigPrice - The big price.
* @param {number} options.lowPrice - The low price.
* @param {boolean} options.isNew - The new flag.
* @returns {Object} - The params object.
*/
async function getCart({ debug }) {
try {
const { data } = await api.get("cart");
const cartItems = data.reduce((obj, { title, price, ...rest }) => {
const priceWithoutLetter = +price.replace("€", "");
if (obj[title]) {
obj[title].quantity++;
} else {
obj[title] = {
title,
price: `${priceWithoutLetter}`,
quantity: 1,
...rest,
};
}
return obj;
}, {});
cartList.value = Object.values(cartItems);
cart.value = data;
if (debug) {
console.groupCollapsed("%c Cart is fetched!", "color: green;");
console.table(cart.value);
console.groupEnd();
}
} catch (err) {
new Error(`FATAL ERROR ::: ${err}`);
}
}
getCart({ debug: true });
async function getProducts(
function transformOptionsToParams(
options = {
itens: undefined,
featured: undefined,
page: undefined,
category: undefined,
postalCode: undefined,
dateExpired: undefined,
type: undefined,
minPrice: undefined,
maxPrice: undefined,
bigPrice: undefined,
lowPrice: undefined,
isNew: undefined,
},
navigate
order_crescent: undefined,
order_descending: undefined,
recommend: undefined,
}
) {
const optionsObj = {
itens: options.itens,
featured: options.featured,
page: options.page,
category: options.category,
postalCode: options.postalCode,
dateExpired: options.dateExpired,
type: options.type,
minPrice: options.minPrice,
maxPrice: options.maxPrice,
bigPrice: options.bigPrice,
lowPrice: options.lowPrice,
isNew: options.isNew,
order_crescent: options.order_crescent,
order_descending: options.order_descending,
recommend: options.recommend,
};
const validKeys = Object.keys(options).filter(
(key) => options[key] !== undefined
@ -109,14 +102,51 @@ export const useCartStore = defineStore("cart", () => {
return acc;
}, {});
try {
const {
data: { data },
} = await apiBack.get("products", {
params,
});
return params;
}
if (data[0].products.length === 0) {
/**
* Fetches products based on the provided options.
*
* @param {Object} options - The options for fetching products.
* @param {number} options.itens - The items to fetch.
* @param {boolean} options.featured - Whether to fetch only featured products.
* @param {number} options.page - The page number to fetch.
* @param {string} options.type - The type of products to fetch.
* @param {string} options.postalCode - The postal code for filtering products.
* @param {string} options.dateExpired - The expiration date for filtering products.
* @param {number} options.minPrice - The minimum price for filtering products.
* @param {number} options.maxPrice - The maximum price for filtering products.
* @param {number} options.bigPrice - The big price for filtering products.
* @param {number} options.lowPrice - The low price for filtering products.
* @param {boolean} options.isNew - Whether to fetch only new products.
* @param {Function} navigate - The navigation function to call after fetching products.
* @returns {Promise<void>} - A promise that resolves when the products are fetched.
*/
async function getProducts(
options = {
postalCode: undefined,
dateExpired: undefined,
type: undefined,
minPrice: undefined,
maxPrice: undefined,
bigPrice: undefined,
lowPrice: undefined,
isNew: undefined,
order_crescent: undefined,
order_descending: undefined,
recommend: undefined,
},
scrollIntoView = () => { }
) {
const params = transformOptionsToParams(options);
console.log(params);
try {
const { data: { data } } = await apiBack.get("products", { params });
if (data.length === 0) {
return quasarNotify({
message:
"No hay productos disponibles para la fecha y el código postal seleccionados",
@ -124,22 +154,12 @@ export const useCartStore = defineStore("cart", () => {
});
}
products.value.data = data[0];
products.value.data = data;
if (navigate) {
navigate();
if (scrollIntoView) {
scrollIntoView();
}
const currentProductIndex = data.findIndex(
({ slug }) => slug === routeId.value
);
const prevProductIndex = currentProductIndex - 1;
const nextProductIndex = currentProductIndex + 1;
products.value.prev = data.data[prevProductIndex];
products.value.current = data.data[currentProductIndex];
products.value.next = data.data[nextProductIndex];
console.groupCollapsed("%c PRODUCTS FETCHED!", "color: green;");
console.groupCollapsed("%c PRODUCTS DATA", "color: tomato;");
console.table(products.value.data);
@ -147,7 +167,10 @@ export const useCartStore = defineStore("cart", () => {
console.groupCollapsed("%c PREV PRODUCT", "color: tomato;");
console.table(products.value.prev);
console.groupEnd();
console.groupCollapsed("%c CURRENT PRODUCT", "color: tomato;");
console.groupCollapsed(
`%c CURRENT PRODUCT: ${products.value.current.slug}`,
"color: tomato;"
);
console.table(products.value.current);
console.groupEnd();
console.groupCollapsed("%c NEXT PRODUCT", "color: tomato;");
@ -160,18 +183,34 @@ export const useCartStore = defineStore("cart", () => {
}
/**
* Fetches a product by its ID and updates the cart state.
*
* @param id Id to get product
*
* @param {string} id - The ID of the product to fetch.
* @param {object} options - Additional options for the product fetch.
* @param {string} options.type - The type of the product.
* @param {string} options.postalCode - The postal code for location-based filtering.
* @param {string} options.dateExpired - The expiration date for time-based filtering.
* @param {boolean} debug - Flag indicating whether to enable debug mode.
* @returns {Promise<void>} - A promise that resolves when the product is fetched and the cart state is updated.
*/
async function getProduct(id) {
async function getProduct(
id,
options = {
type: undefined,
postalCode: undefined,
dateExpired: undefined,
},
debug = false
) {
if (id) {
routeId.value = id;
try {
/* const promises = [
api.get(`flowers/${id - 1}`),
api.get(`flowers/${id}`),
api.get(`flowers/${id + 1}`),
const params = transformOptionsToParams(options);
const promises = [
apiBack.get(`products/${+id - 1}`),
apiBack.get(`products/${+id}`),
apiBack.get(`products/${+id + 1}`),
];
const results = await Promise.allSettled(promises);
const [prev, current, next] = results.map((res) => {
@ -180,27 +219,29 @@ export const useCartStore = defineStore("cart", () => {
rejected: res.reason,
};
return result[res.status];
}); */
return result[res.status].data[0];
})
const { data } = await apiBack.get(`products/slug/${id}`);
products.value.prev = prev;
products.value.current = current;
products.value.next = next;
prevProduct.value = {};
currentProduct.value = data.data[0];
nextProduct.value = {};
if (!current) {
push({ name: "NotFound" })
}
console.groupCollapsed(
`%c PRODUCT FETCHED! SLUG: ${routeId.value}`,
"color: green;"
);
console.time();
console.table(prevProduct.value);
console.table(currentProduct.value);
console.table(nextProduct.value);
console.timeEnd();
console.groupEnd();
if (debug) {
console.groupCollapsed(
`%c PRODUCT FETCHED! SLUG: ${routeId.value}`,
"color: green;"
);
console.table(products.value.prev);
console.table(products.value.current);
console.table(products.value.next);
console.groupEnd();
}
if (currentProduct.value.response?.status === 404) {
if (products.value.current.response?.status === 404) {
push({ name: "NotFound" });
}
} catch (err) {
@ -209,46 +250,107 @@ export const useCartStore = defineStore("cart", () => {
}
}
async function addToCart(product, dedication) {
cart.value.push({ ...product.value });
dedicationTxt.value = dedication;
addCartLoadingBtn.value = true;
/**
* Retrieves featured products based on the provided options.
*
* @param {Object} options - The options for retrieving featured products.
* @param {number} options.itens - The number of items to retrieve.
* @param {number} options.featured - The flag indicating if the products should be featured.
* @param {number} [options.page] - The page number for pagination.
* @param {string} [options.type] - The type of the products.
* @param {string} [options.postalCode] - The postal code for location-based filtering.
* @param {string} [options.dateExpired] - The expiration date for filtering.
* @param {number} [options.minPrice] - The minimum price for filtering.
* @param {number} [options.maxPrice] - The maximum price for filtering.
* @param {number} [options.bigPrice] - The big price for filtering.
* @param {number} [options.lowPrice] - The low price for filtering.
* @param {boolean} [options.isNew] - The flag indicating if the products are new.
* @returns {Promise<void>} - A promise that resolves when the featured products are retrieved.
*/
async function getFeaturedProducts(
options = {
postalCode: undefined,
dateExpired: undefined,
type: undefined,
minPrice: undefined,
maxPrice: undefined,
bigPrice: undefined,
lowPrice: undefined,
isNew: undefined,
order_crescent: undefined,
order_descending: undefined,
recommend: 1,
},
debug = false
) {
try {
await api.post("cart", product);
addCartLoadingBtn.value = false;
// push("/checkout");
const params = transformOptionsToParams(options);
console.groupCollapsed("%c Adicionado com sucesso!", "color: green");
console.table(cart.value);
console.groupEnd();
(async () => {
const {
data: { data },
} = await apiBack.get("products", { params });
featuredProducts.value = data[0];
if (debug) {
console.groupCollapsed(
"%c FEATURED PRODUCTS FETCHED!",
"color: green;"
);
console.table(data.products);
console.groupEnd();
}
})();
} catch (err) {
addCartLoadingBtn.value = false;
new Error(`FATAL ERROR ::: ${err}`);
}
}
/**
* Adiciona um produto ao carrinho.
* @param {Object} product - O produto a ser adicionado.
* @param {string} dedication - A dedicação associada ao produto.
*/
function addToCart(product, dedication) {
const existingProduct = cart.value.find(p => p.id === product.id);
console.log(existingProduct)
if (!existingProduct) {
const arr = [...cart.value];
arr.push(product);
console.log(arr)
addItem("cart", JSON.stringify(arr));
quasarNotify({ message: 'Producto añadido al carrito.', type: 'success' })
return
}
quasarNotify({
message: "Este producto ya está en el carrito",
type: "info",
});
}
/**
* Remove an item from the cart by its ID.
* @param {number} id - The ID of the item to be removed.
*/
function removeFromCart(id) {
cart.value = cart.value.filter((p) => id !== p.id);
api.delete(`cart/${id}`);
const newArrRemovedItem = cart.value.filter((p) => id !== p.id);
addItem("cart", JSON.stringify(newArrRemovedItem))
}
return {
checkoutRef,
homeSection,
cart,
cartList,
totalPrice,
dedicationTxt,
cartLength,
prevProduct,
currentProduct,
nextProduct,
addCartLoadingBtn,
products,
featuredProducts,
getFeaturedProducts,
getProducts,
addToCart,
removeFromCart,
getProduct,
getCart,
};
});

View File

@ -1,18 +1,20 @@
import { fullCurrentDate } from "src/constants/date";
import { z } from "zod";
import { postalCode } from "..";
import * as M from "../messages";
const availabilityObj = {
date: z.string({ required_error: M.requiredMessage }).refine((val) => {
const [day, month, year] = val.split("/");
const regex = /\//g;
const regex = /-|\//g;
const [day, month = "", year = ""] = val.split(regex);
const valWithoutSlash = val.replace(regex, "");
const data = new Date(`${year}-${month}-${day}`);
const today = new Date();
return valWithoutSlash.length === 8 && data >= today;
const inputDate = `${year}/${month}/${day}`;
return valWithoutSlash.length === 8 && inputDate >= fullCurrentDate;
}, "La fecha no puede ser inferior al día de hoy!"),
postalCode,
dedication: z.string().optional(),
};
export const availabilitySchema = z.object(availabilityObj);