floranet/src/pages/ProductPage.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>