673 lines
17 KiB
Vue
673 lines
17 KiB
Vue
<script>
|
|
import { storeToRefs } from "pinia";
|
|
import { useMeta } from "quasar";
|
|
import { computed, defineComponent, onBeforeMount, ref, watch } from "vue";
|
|
import { useRoute } from "vue-router";
|
|
|
|
import IconArrowCircleFilledLeft from "components/icons/IconArrowCircleFilledLeft.vue";
|
|
import IconArrowCircleFilledRight from "components/icons/IconArrowCircleFilledRight.vue";
|
|
import IconPencilGreen from "components/icons/IconPencilGreen.vue";
|
|
import IconEmail from "components/icons/social/IconEmail.vue";
|
|
import IconLinkedin from "components/icons/social/IconLinkedin.vue";
|
|
import IconShare from "components/icons/social/IconShare.vue";
|
|
import IconTwitter from "components/icons/social/IconTwitter.vue";
|
|
import IconWhatsapp from "components/icons/social/IconWhatsapp.vue";
|
|
import ProductCarousel from "components/quasar-components/carousel/ProductCarousel.vue";
|
|
import DudasSection from "components/sections/DudasSection.vue";
|
|
import Card from "components/ui/Card.vue";
|
|
import Container from "components/ui/Container.vue";
|
|
import Modal from "components/ui/Modal.vue";
|
|
|
|
import { useLocalStorage } from "src/hooks/useLocalStorage";
|
|
import { usePostalCalendar } from "src/hooks/usePostalCalendar";
|
|
import { useCartStore } from "stores/cart";
|
|
import { useFormStore } from "stores/forms";
|
|
import { useModalStore } from "stores/modalStore";
|
|
|
|
export default defineComponent({
|
|
name: "ProductPage",
|
|
components: {
|
|
IconPencilGreen,
|
|
IconWhatsapp,
|
|
IconLinkedin,
|
|
IconTwitter,
|
|
IconShare,
|
|
IconEmail,
|
|
DudasSection,
|
|
Container,
|
|
Card,
|
|
IconArrowCircleFilledRight,
|
|
IconArrowCircleFilledLeft,
|
|
ProductCarousel,
|
|
Modal,
|
|
},
|
|
setup() {
|
|
const route = useRoute();
|
|
const { getItem } = useLocalStorage();
|
|
|
|
const formStore = useFormStore();
|
|
const { availability: availabilityForm } = storeToRefs(formStore);
|
|
|
|
const availability = ref(getItem("availability"));
|
|
const availabilityFormKeys = computed(() => {
|
|
return Object.fromEntries(
|
|
Object.entries(availabilityForm.value).filter(
|
|
([key, value]) => value !== ""
|
|
)
|
|
);
|
|
});
|
|
|
|
const isAvailabilityEmpty = computed(() => {
|
|
return (
|
|
Object.keys(availabilityFormKeys.value || availability.value).length ===
|
|
0
|
|
);
|
|
});
|
|
|
|
const {
|
|
handleReset,
|
|
fields: { dedication, dedicationAttrs },
|
|
} = usePostalCalendar({ modalItem: "isOpenAvailability", type: "product" });
|
|
|
|
const modalStore = useModalStore();
|
|
const { openModal } = modalStore;
|
|
|
|
const cartStore = useCartStore();
|
|
const { getProduct, getProducts, addToCart } = cartStore;
|
|
const { products, addCartLoadingBtn } = storeToRefs(cartStore);
|
|
|
|
onBeforeMount(async () => {
|
|
await getProduct(route.params.id, availability.value);
|
|
});
|
|
|
|
watch(
|
|
() => products.value.current?.type,
|
|
(newCategory) => {
|
|
getProducts({
|
|
type: newCategory,
|
|
});
|
|
}
|
|
);
|
|
|
|
watch(
|
|
() => route.params.id,
|
|
(newId) => {
|
|
getProduct(newId);
|
|
}
|
|
);
|
|
|
|
useMeta(() => {
|
|
return {
|
|
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());
|
|
}*/
|
|
|
|
return true;
|
|
};
|
|
|
|
const addModal = () => {
|
|
if (!isAvailabilityEmpty.value) {
|
|
addToCart(products.value.current, dedication);
|
|
return;
|
|
}
|
|
openModal({ modal: "availability" });
|
|
};
|
|
|
|
const handlePagClick = () => {
|
|
handleReset();
|
|
};
|
|
|
|
return {
|
|
addModal,
|
|
openModal,
|
|
handlePagClick,
|
|
checkImageValidity,
|
|
slide: ref(1),
|
|
fullscreen: ref(false),
|
|
dedication,
|
|
dedicationAttrs,
|
|
products,
|
|
addCartLoadingBtn,
|
|
};
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<q-page>
|
|
<Container class="product-container" tag="section">
|
|
<ProductCarousel>
|
|
<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">
|
|
{{ 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">
|
|
{{ products.current?.itemFk }}
|
|
<q-skeleton
|
|
v-if="!products.current?.itemFk"
|
|
width="100px"
|
|
type="text"
|
|
/>
|
|
</span>
|
|
</p>
|
|
|
|
<p class="product-content-paragraph">
|
|
Categoría:
|
|
<span class="green-text">
|
|
{{ products.current?.type }}
|
|
<q-skeleton
|
|
v-if="!products.current?.type"
|
|
type="text"
|
|
width="50px"
|
|
/>
|
|
</span>
|
|
</p>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="product-content-body">
|
|
<div class="product-content-paragraphs">
|
|
<p class="product-price green-text">
|
|
{{ products.current?.price }}€
|
|
<q-skeleton
|
|
v-if="!products.current?.price"
|
|
type="text"
|
|
height="90px"
|
|
width="80px"
|
|
/>
|
|
</p>
|
|
<p class="product-delivery green-text">Envío Gratuito</p>
|
|
<p class="product-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>
|
|
|
|
<div class="product-form">
|
|
<div class="product-dedication">
|
|
<header class="product-dedication-header">
|
|
<IconPencilGreen />
|
|
<label
|
|
class="product-dedication-paragraph"
|
|
for="dedication-btn"
|
|
>
|
|
¿Deseas añadir una dedicatoria?
|
|
</label>
|
|
</header>
|
|
|
|
<div class="product-dedication-body">
|
|
<q-input
|
|
v-model="dedication"
|
|
v-bind="dedicationAttrs"
|
|
bg-color="white"
|
|
color="primary"
|
|
outlined
|
|
id="dedication-btn"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<q-btn
|
|
:loading="addCartLoadingBtn"
|
|
color="primary"
|
|
class="btn sm-btn"
|
|
label="COMPRAR"
|
|
@click="addModal"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<footer class="product-content-footer">
|
|
<p class="product-share-paragraph">Conmparte con tus amigos</p>
|
|
|
|
<div class="product-share-content">
|
|
<a href="#" class="product-share-item"><IconShare /></a>
|
|
<a href="#" class="product-share-item"><IconLinkedin /></a>
|
|
<a href="#" class="product-share-item"><IconTwitter /></a>
|
|
<a href="#" class="product-share-item"><IconEmail /></a>
|
|
<a href="#" class="product-share-item"><IconWhatsapp /></a>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
|
|
<div
|
|
class="product-pag-block"
|
|
: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}`"
|
|
@click="handlePagClick"
|
|
>
|
|
<IconArrowCircleFilledLeft />
|
|
|
|
<div class="btn-pag-paragraphs">
|
|
<p class="btn-paragraph-top green-text">Produto anterior</p>
|
|
<p class="product-paragraph-bottom" :title="products.prev?.name">
|
|
{{ products.prev?.name }}
|
|
</p>
|
|
</div>
|
|
</q-btn>
|
|
|
|
<q-btn
|
|
v-if="products.next?.itemFk"
|
|
color="white"
|
|
class="btn outlined rounded sm-btn product-pag-item product-next-btn"
|
|
:to="`${+$route.params.id + 1}`"
|
|
@click="handlePagClick"
|
|
>
|
|
<div class="btn-pag-paragraphs">
|
|
<p class="btn-paragraph-top green-text">Siguiente producto</p>
|
|
<p class="product-paragraph-bottom" :title="products.next?.name">
|
|
{{ products.next?.name }}
|
|
</p>
|
|
</div>
|
|
|
|
<IconArrowCircleFilledRight />
|
|
</q-btn>
|
|
</div>
|
|
</Container>
|
|
|
|
<DudasSection isWhite />
|
|
<Modal modalItem="isOpenAvailability" typeModal="product" />
|
|
|
|
<Container class="like-another-container gray-bg" tag="section">
|
|
<header class="like-another-header">
|
|
<h3 class="like-another-title subtitle">
|
|
Quizás también te gusten estos ramos
|
|
</h3>
|
|
|
|
<p class="like-another-paragraph"></p>
|
|
</header>
|
|
|
|
<Container cardContainer class="no-padding">
|
|
<template
|
|
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="image"
|
|
:isNew="isNew"
|
|
:id="id"
|
|
/>
|
|
</template>
|
|
</Container>
|
|
</Container>
|
|
</q-page>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
.product-container {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 136px;
|
|
margin-bottom: 100px;
|
|
& .product-gallery {
|
|
/* flex: 1 0 min(100%, 562px); */
|
|
height: 622px;
|
|
flex: 1 0 min(100%, 500px);
|
|
overflow: initial;
|
|
& .q-carousel__slides-container .q-panel {
|
|
border-radius: 15px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
& .q-carousel__navigation {
|
|
bottom: -127px;
|
|
left: 0;
|
|
right: 0;
|
|
& .q-carousel__navigation-inner {
|
|
justify-content: flex-start;
|
|
gap: 19px;
|
|
& .q-carousel__thumbnail {
|
|
width: 97px;
|
|
height: 107px;
|
|
object-fit: cover;
|
|
border-radius: 10px;
|
|
opacity: 1;
|
|
flex: 0 0 97px;
|
|
position: relative;
|
|
&.q-carousel__thumbnail--active {
|
|
border: 1px solid $primary;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@media only screen and (max-width: $med-md) {
|
|
height: 396px;
|
|
& .q-carousel__navigation {
|
|
bottom: -83px;
|
|
display: block;
|
|
& .q-carousel__navigation-inner {
|
|
gap: 12px;
|
|
& .q-carousel__thumbnail {
|
|
width: 61px;
|
|
height: 68px;
|
|
flex: 0 0 61px;
|
|
&::after {
|
|
content: "";
|
|
}
|
|
|
|
&.q-carousel__thumbnail--active {
|
|
border: 1px solid $primary;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
& .product-content {
|
|
flex: 1 0 min(100%, 466px);
|
|
margin-bottom: 27px;
|
|
& .product-content-header {
|
|
margin-bottom: 20px;
|
|
& .product-content-title {
|
|
margin-bottom: 14px;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
& .product-header-block {
|
|
& .product-content-paragraph {
|
|
font-size: $font-14;
|
|
line-height: 21px;
|
|
letter-spacing: 0.28px;
|
|
color: $text-gray;
|
|
& .green-text {
|
|
display: inline-flex;
|
|
}
|
|
}
|
|
}
|
|
|
|
@media only screen and (max-width: $med-md) {
|
|
margin-bottom: 30px;
|
|
}
|
|
}
|
|
|
|
& .product-content-body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-bottom: 36px;
|
|
gap: 60px;
|
|
& .product-content-paragraphs {
|
|
& .product-price {
|
|
font-weight: 500;
|
|
letter-spacing: 0.8px;
|
|
line-height: 21px;
|
|
font-family: $font-lora;
|
|
font-size: $font-40;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
& .product-delivery {
|
|
font-size: $font-14;
|
|
line-height: 24px;
|
|
letter-spacing: 0.28px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
& .product-description {
|
|
font-size: $font-14;
|
|
line-height: 21px;
|
|
letter-spacing: 0.28px;
|
|
color: $text-gray;
|
|
}
|
|
}
|
|
|
|
& .product-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 22px;
|
|
& .product-dedication {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 22px;
|
|
background-color: $secondary-10;
|
|
border-radius: 10px;
|
|
padding: 18px 31px 28px;
|
|
& .product-dedication-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
& .product-dedication-body {
|
|
border: 1px solid $secondary-40;
|
|
}
|
|
}
|
|
}
|
|
|
|
@media only screen and (max-width: $med-md) {
|
|
margin-bottom: 30px;
|
|
gap: 24px;
|
|
& .product-content-paragraphs {
|
|
& .product-price {
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
& .product-delivery {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
& .product-form .product-dedication {
|
|
padding: 18px 26px 28px;
|
|
}
|
|
}
|
|
}
|
|
|
|
& .product-content-footer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
& .product-share-paragraph {
|
|
color: $text-muted-one;
|
|
font-size: $font-14;
|
|
line-height: 21px;
|
|
letter-spacing: 0.28px;
|
|
}
|
|
|
|
& .product-share-content {
|
|
display: flex;
|
|
gap: 6px;
|
|
}
|
|
|
|
@media only screen and (max-width: $med-md) {
|
|
align-items: center;
|
|
}
|
|
}
|
|
}
|
|
|
|
& .product-pag-block {
|
|
flex: 1 0 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 84px;
|
|
& .product-pag-item {
|
|
margin-top: 76px;
|
|
&::before {
|
|
content: "";
|
|
display: none;
|
|
}
|
|
|
|
& .q-focus-helper {
|
|
display: none;
|
|
}
|
|
|
|
& .q-btn__content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: start;
|
|
gap: 14px;
|
|
flex-wrap: nowrap;
|
|
}
|
|
|
|
& .btn-pag-paragraphs {
|
|
& .product-paragraph-bottom {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
width: 15ch;
|
|
}
|
|
}
|
|
|
|
&.product-prev-btn {
|
|
padding: 10px 55px 10px 20px;
|
|
}
|
|
|
|
&.product-next-btn {
|
|
padding: 10px 20px 10px 55px;
|
|
& .q-btn__content {
|
|
text-align: end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@media only screen and (max-width: $med-md) {
|
|
display: block;
|
|
gap: 130px;
|
|
& .product-gallery {
|
|
margin-bottom: 130px;
|
|
}
|
|
|
|
& .product-pag-block {
|
|
flex: initial;
|
|
& .product-pag-item {
|
|
margin: initial;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 20px 29px 14px 20px !important;
|
|
|
|
& .q-btn__content {
|
|
gap: 12px;
|
|
|
|
& > svg {
|
|
flex: 0 0 23px;
|
|
}
|
|
}
|
|
|
|
&.product-prev-btn,
|
|
&.product-next-btn {
|
|
padding: 17px 29px 14px 20px;
|
|
}
|
|
|
|
& .btn-pag-paragraphs {
|
|
font-size: $font-14;
|
|
& p {
|
|
font-size: inherit;
|
|
}
|
|
|
|
& .product-paragraph-bottom {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.like-another-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 82px;
|
|
padding-block: 129px 126px;
|
|
text-align: center;
|
|
|
|
& .like-another-header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 25px;
|
|
|
|
& .like-another-title {
|
|
font-weight: 500;
|
|
font-size: $font-35;
|
|
line-height: 30px;
|
|
}
|
|
|
|
& .like-another-paragraph {
|
|
color: $text-default;
|
|
line-height: 26px;
|
|
}
|
|
}
|
|
|
|
@media only screen and (max-width: $med-md) {
|
|
gap: 61px;
|
|
padding-block: 62px 108px;
|
|
|
|
& .like-another-header {
|
|
& .like-another-title {
|
|
font-size: $font-24;
|
|
}
|
|
|
|
& .like-another-paragraph {
|
|
font-size: $font-14;
|
|
line-height: 18px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|