Files
consumocuidado/components/ProductCardDetails.vue
2025-09-15 09:08:01 +02:00

545 lines
12 KiB
Vue

<template>
<div class="productcard_container">
<div class="productcard_container-basic">
<div class="image_container">
<img :src="getProductImg(product)" class="image" alt="" />
</div>
<div class="info_container">
<h2 variant="primary">
{{ product?.name }}
</h2>
<span v-if="product?.price" class="price">{{
`${product?.price}`
}}</span>
<span v-else class="price">Precio a consultar</span>
<span v-if="Number(product?.shipping_cost)"
>| {{ `Gastos de envío ${product?.shipping_cost}` }}</span
>
<!-- <span v-else>| Sin gastos de envío</span> -->
<span v-if="product?.stock">| {{ `Stock ${product?.stock}` }} </span>
<p
v-if="product?.description"
class="description"
v-html="sanitize(product?.description)"
></p>
<span v-if="product?.shipping_terms">{{ product?.shipping_terms }}</span>
<BCollapse visible accordion="my-accordion">
<div class="tags_container">
<NuxtLink
v-for="n in product?.tags"
:key="n"
:to="tagRoute(n)"
class="tag_container"
>
<span>{{ n }}</span>
</NuxtLink>
</div>
<BButton
v-if="company?.shop_link"
class="div-link"
align="center"
target="_blank"
@click="buyIntent"
>
<img class="div-link-img" src="@/assets/img/shopping-bag-white.svg" />
COMPRA EN SU TIENDA
</BButton>
<div class="smlogos_container">
<p class="share-text">Comparte:</p>
<div class="smlogo_container">
<a @click="shareFacebook">
<img
alt="facebook logo"
class="smlogo_img"
src="@/assets/img/latienda-smlogo-facebook.svg"
/>
</a>
</div>
<a :href="shareTwitter()">
<div class="smlogo_container">
<img
alt="twitter logo"
class="smlogo_img"
src="@/assets/img/latienda-smlogo-twitter.svg"
/>
</div>
</a>
<a
:href="shareWhatsApp()"
data-action="share/whatsapp/share"
target="_blank"
title="latiendacoop"
>
<div class="smlogo_container">
<img
alt="whatsapp logo"
class="smlogo_img"
src="@/assets/img/latienda-smlogo-whatsapp.svg"
/>
</div>
</a>
</div>
<div class="coop_info">
<p class="share-text">Vendido por:</p>
<h4>{{ company?.company_name }}</h4>
<p class="description">{{ company?.description }}</p>
<NuxtLink :to="`/productoras/${company?.id}`" class="link-saber">
<p>Saber más</p>
</NuxtLink>
</div>
</BCollapse>
</div>
</div>
<BModal
id="modal-center"
v-model="active"
centered
title="latienda.coop"
:ok-variant="modalColor"> {{ modalText }}
</BModal>
</div>
<div class="related_products">
<div class="title-container">
<div class="title-lines"></div>
<h2 v-if="related" class="items-title">Productos relacionados</h2>
<h2 v-else class="items-title">Otros productos</h2>
<div class="title-lines"></div>
</div>
<ProductsRelated :related-products="relatedProducts" />
</div>
</template>
<script>
import { mapState } from 'pinia'
import DOMPurify from 'dompurify'
import socialShare from '~/utils/socialShare'
export default {
props: {
product: {
type: Object,
default: () => ({}),
},
company: {
type: Object,
default: () => ({}),
},
relatedProducts: {
type: Array,
default: () => [],
},
related: {
type: Boolean,
default: false,
},
},
data() {
return {
modal: true,
productUrl: null,
geolocation: null,
active: false,
modalText: '',
modalColor: 'info',
}
},
computed: {
...mapState(useAuthStore, ['id', 'access']),
},
mounted() {
this.productUrl = window.location.href
this.sendLog('view')
},
methods: {
getProductImg(product) {
if (product && product.image)
return product.image
return `@/assets/img/consumo-default.png`
},
tagRoute(tag) {
return `/busqueda?tags=${tag}`
},
openUrl(url) {
window.open(url)
},
closeModal(value) {
this.modal = false
if (value === 200 || value === 201) {
this.modalText = 'Actualizado correctamente'
this.modalColor = 'success'
} else if (value) {
this.modalText = 'Se ha producido un error en el envío'
this.modalColor = 'danger'
}
},
// TODO: implement buyIntent (review functionality, because sendLog is not working)
buyIntent() {
this.sendLog('shop')
return this.product.url
? this.openUrl(this.product.url)
: (this.modal = true)
},
async getPosition() {
const geoLocation = () =>
new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(
(posData) => {
resolve(posData)
},
(error) => {
reject(error)
}
)
)
try {
const position = await geoLocation()
const geo = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
}
return geo
} catch {
const geo = null
return geo
}
},
async sendLog(action) {
const geo = await this.getPosition()
try {
const config = useRuntimeConfig()
const accessToken = this.access
const response = await $fetch('https://api.ipify.org?format=json', {
baseURL: config.public.baseURL,
method: 'GET'
})
const ip = response.ip
const object = {
action: action,
action_object: {
model: 'product',
id: this.product.id,
},
}
console.log('Sending log OBJECT:', object)
if (ip) object.ip = ip
if (geo) object.geo = geo
//TODO: review problems with 406 error backend
await $fetch(`/stats/me/`, {
baseURL: config.public.baseURL,
method: 'POST',
body: object,
headers: {
Authorization: `Bearer ${accessToken}`
}
})
} catch (error) {
console.error('Error sending log:', error)
}
},
shareFacebook() {
const url = socialShare.facebook(this.productUrl)
window.open(url, '_blank')
},
shareTwitter() {
return socialShare.twitter(this.productUrl)
},
shareWhatsApp() {
return socialShare.whatsApp(this.productUrl)
},
sanitize(dirtyHtml) {
return DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: [
'b',
'i',
'em',
'strong',
'a',
'p',
'br',
'ul',
'li',
'ol',
'span',
'h1',
'h2',
'h3',
],
ALLOWED_ATTR: ['href', 'target', 'rel', 'class'],
})
},
},
}
</script>
<style lang="scss" scoped>
.productcard_container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient($color-consumo-base-light, $color-bg-light);
border-radius: 1rem;
padding: 4rem;
margin-bottom: 2rem;
gap: 3rem;
color: $color-primary;
@include mobile {
margin-top: 7rem;
}
}
.productcard_container-basic {
display: flex;
flex-direction: row;
align-items: start;
justify-content: start;
gap: 4rem;
width: 100%;
@media screen and (max-width: 1024px) {
flex-direction: column;
gap: 2rem;
margin-top: 4rem;
padding: 3rem 2rem;
}
@include mobile {
flex-direction: column;
gap: 1rem;
margin-top: 5rem;
padding: 2rem 1rem;
}
}
.image_container {
height: 400px;
width: 300px;
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
justify-content: center;
background-color: white;
border-radius: 16px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
@include mobile {
width: 200px;
height: 200px;
margin: 0 auto;
}
}
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
.info_container {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
@include mobile {
margin-top: 2rem;
}
h2 {
font-size: $h2;
font-weight: $bold;
text-transform: capitalize;
}
.price {
font-weight: $medium;
color: $color-primary;
font-size: $h5;
}
span {
color: $color-greytext;
}
.description {
margin-top: 8px;
font-size: $m;
width: 80%;
@include tablet {
width: 100%;
}
@include mobile {
width: 100%;
}
}
}
.div-link:hover {
background-color: white;
transition: all 0.2s ease;
color: $color-button;
}
.div-link {
display: flex;
justify-content: center;
align-items: center;
gap: 0.7rem;
width: auto;
border: 1px solid $color-button;
border-radius: 5px;
background-color: $color-button;
font-weight: $bold;
padding: 15px 20px;
color: white;
margin-top: 2rem;
&:hover {
cursor: pointer;
}
.div-link-img {
width: 20px;
}
}
.tags_container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
gap: 8px;
margin-top: 1rem;
.tag_container {
margin: 5px 6px 0 0;
border-radius: 1rem;
padding: 8px;
display: inline-block;
font-size: $xs;
background-color: white;
color: $color-primary;
text-align: center;
text-transform: uppercase;
text-decoration: none;
}
}
.share-text {
color: $color-primary;
font-size: $h5;
font-weight: $medium;
margin-top: 2rem;
padding-bottom: 0.5em;
}
.smlogo_container {
cursor: pointer;
display: inline-block;
margin-bottom: 0.5rem;
.smlogo_img {
cursor: pointer;
width: 40px;
fill: $color-button;
}
img:hover {
transform: scale(1.1);
transition: all 0.2s ease;
}
}
.button_cart_img {
width: 20px;
margin-right: 10px;
}
.coop_info {
margin-top: 25px;
h4 {
font-size: $h4;
font-weight: $medium;
color: $color-navy;
margin-top: 5px;
text-transform: uppercase;
text-decoration: none;
}
.description {
font-size: $m;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.link-saber {
font-weight: $bold;
color: $color-button;
text-decoration: none;
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
}
.related_products {
display: flex;
flex-direction: column;
justify-content: center;
border-radius: 24px;
padding: 4rem 4rem;
background: $color-bg-light;
width: 100%;
&-title {
text-align: center;
}
.title-container {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
.title-lines {
width: 34px;
height: 2px;
background: $color-consumo-base;
margin: 0 8px;
}
.items-title {
font-size: $h5;
text-transform: uppercase;
padding-top: 10px;
}
}
}
.discount-tag {
margin: 5px;
border: none;
background-color: $color-green;
border-radius: 5px;
padding: 6px 10px;
display: inline-block;
font-family: $font-secondary;
font-size: $xs;
color: $color-greytext;
}
.content > h2,
h3,
p {
margin: 0;
}
</style>