wip coop/[id] page

This commit is contained in:
María
2025-08-18 14:45:18 +02:00
parent f33da4af80
commit 8fedf54470
7 changed files with 1374 additions and 296 deletions

237
components/CoopCard.vue Normal file
View File

@@ -0,0 +1,237 @@
<template>
<div class="productcard_container">
<div class="row productcard_container-basic">
<div class="image_container col-md-2">
<img
v-if="coop.logo"
class="image"
:src="coop.logo"
:alt="coop.company_name"
/>
<img
v-else
class="image"
src="@/assets/img/latienda-product-default.svg"
:alt="coop.company_name"
/>
</div>
<div class="info_container col-md-6" align="left">
<NuxtLink :to="`/c/${this.coop.id}`">
<h2>{{ coop.company_name }}</h2>
</NuxtLink>
<p class="description">{{ coop.description }}</p>
<div class="tags_container">
<NuxtLink
:to="tagRoute(n)"
class="tag_container"
v-for="n in coop.tags"
:key="n"
>
<img class="tag_img" src="@/assets/img/latienda-tag.svg" />
<span>{{ n }}</span>
</NuxtLink>
</div>
</div>
<div class="col-md-4 button_container" align="center">
<a v-if="coop.shop_link" :href="coop.shop_link" class="button_buy">
<img
alt="tienda"
class="button_cart_img"
src="@/assets/img/latienda-tienda.svg"
/>
<span>Tienda online</span>
</a>
<a v-if="coop.web_link" :href="coop.web_link" class="button_buy">
<img
alt="web"
class="button_cart_img"
src="@/assets/img/latienda-web.svg"
/>
<span>Página web</span>
</a>
<div class="smlogos_container">
<div class="smlogo_container">
<a @click="shareFacebook">
<img
class="smlogo_img"
alt="facebook logo"
src="@/assets/img/latienda-smlogo-facebook.svg"
/>
</a>
</div>
<a @click="shareTwitter">
<div class="smlogo_container">
<img
class="smlogo_img"
alt="twitter logo"
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>
</div>
</div>
</template>
<script>
import socialShare from '~/utils/socialShare'
export default {
name: 'CoopCard',
props: ['coop'],
computed: {
coopUrl() {
return `${window.location.origin}/c/${this.coop.id}`
},
},
methods: {
tagRoute(tag) {
return `/busqueda?tags=${tag}`
},
shareFacebook() {
const url = socialShare.facebook(this.coopUrl)
window.open(url, '_blank')
},
shareTwitter() {
const url = socialShare.twitter(this.coopUrl)
window.open(url, '_blank')
},
shareWhatsApp() {
return socialShare.whatsApp(this.coopUrl)
},
},
}
</script>
<style lang="scss" scoped>
.productcard_container {
border: 3px #e9e9e9 solid;
border-radius: 5px;
margin-bottom: 25px;
}
.productcard_container-basic {
padding: 25px 20px;
}
.image_container {
height: 100%;
overflow: hidden;
margin: auto;
}
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
.info_container {
h2 {
outline: none;
font-weight: $medium;
color: $color-navy;
font-size: $m;
}
.price {
font-weight: $bold;
color: $color-navy;
font-size: $m;
}
span {
color: #808080;
}
.description {
margin-top: 8px;
font-family: $font-secondary;
font-size: $s;
color: $color-greytext;
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.button_container {
display: flex;
flex-direction: column;
font-size: $s;
.button_buy {
width: 100%;
color: $color-orange;
font-weight: $bold;
padding: 10px 0;
margin-bottom: 5px;
border: 3px solid $color-orange;
border-radius: 5px;
background-color: $color-light;
&:hover {
box-shadow: 0 4px 16px rgba(99, 99, 99, 0.2);
transition: all 0.2s ease;
}
span {
display: inline-block;
}
}
}
.tag_container {
margin: 25px 6px 0 0;
border: 2px solid $color-greylayout;
border-radius: 5px;
padding: 6px 10px;
display: inline-block;
font-family: $font-secondary;
font-size: $xs;
color: $color-greytext;
.tag_img {
width: 18px;
}
}
.smlogo_container {
display: inline-block;
margin-top: 15px;
.smlogo_img {
width: 35px;
fill: $color-greytext;
margin: 2px;
}
img:hover {
transform: scale(1.1);
transition: all 0.2s ease;
}
}
.button_cart_img {
width: 20px;
margin-right: 10px;
}
.content > h2,
h3,
p {
margin: 0;
}
</style>

View File

@@ -1,276 +0,0 @@
<template>
<div class="nav-menu-container">
<img
class="burger"
src="@/assets/img/latienda-burger-nav.svg"
alt=""
@click="isMenuOpen = !isMenuOpen"
/>
<div :class="isMenuOpen ? `shadow` : ''">
<transition name="slider" mode="out-in">
<div v-if="isMenuOpen" class="nav-menu">
<img
class="close-icon"
src="@/assets/img/latienda-close-nav.svg"
alt=""
@click="isMenuOpen = !isMenuOpen"
/>
<nav class="nav">
<ul class="section-list">
<NuxtLink to="/">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<img
class="section-img"
src="@/assets/img/latienda-ubicacion.svg"
alt=""
/>
<span class="section-text">Inicio</span>
</li>
</NuxtLink>
<NuxtLink to="/c">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<img
class="section-img"
src="@/assets/img/latienda-tienda-nav.svg"
alt=""
/>
<span class="section-text">Cooperativas</span>
</li>
</NuxtLink>
<NuxtLink to="/page/info">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<img
class="section-img"
src="@/assets/img/latienda-bag.svg"
alt=""
/>
<span class="section-text">Sobre nosotros</span>
</li>
</NuxtLink>
<li class="section" @click="isMenuOpen = !isMenuOpen">
<a href="mailto:info@latienda.coop">
<img
class="section-img"
src="@/assets/img/envelope-simple.svg"
alt=""
/>
<span class="section-text">Contacto</span>
</a>
</li>
</ul>
</nav>
<ul class="login-list">
<NuxtLink v-if="!isAuthenticated" to="/login">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<img
class="section-img"
src="@/assets/img/latienda-lock.svg"
alt=""
/>
<span class="section-text">Acceder</span>
</li>
</NuxtLink>
<NuxtLink v-if="isManager" to="/editar/perfil">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<img
class="section-img"
src="@/assets/img/latienda-user.svg"
alt=""
/>
<span class="section-text">Perfil</span>
</li>
</NuxtLink>
<NuxtLink v-if="isManager" to="/editar/cooperativa">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<span class="section-text login">Cooperativa</span>
</li>
</NuxtLink>
<NuxtLink v-if="isManager" to="/editar/productos">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<span class="section-text login">Productos</span>
</li>
</NuxtLink>
<NuxtLink v-if="isManager" to="/editar/productos/importar">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<span class="section-text login">Importar</span>
</li>
</NuxtLink>
<NuxtLink v-if="isAuthenticated" @click.native="logout" to="/">
<li class="section" @click="isMenuOpen = !isMenuOpen">
<img
class="section-img"
src="@/assets/img/latienda-sign-out.svg"
alt=""
/>
<span class="section-text">Cerrar sesión</span>
</li>
</NuxtLink>
</ul>
<ul class="link-list">
<li class="link">
<a href="https://coceta.coop/" target="_blank">
<span class="link-text">Coceta</span>
</a>
</li>
<NuxtLink to="/page/terminos">
<li class="link" @click="isMenuOpen = !isMenuOpen">
<span class="link-text">Términos y condiciones</span>
</li>
</NuxtLink>
<NuxtLink to="/page/legal">
<li class="link" @click="isMenuOpen = !isMenuOpen">
<span class="link-text">Política de privacidad</span>
</li>
</NuxtLink>
<NuxtLink to="/page/cookies">
<li class="link" @click="isMenuOpen = !isMenuOpen">
<span class="link-text">Cookies</span>
</li>
</NuxtLink>
</ul>
<div class="credits">
<span>2021 La Tienda.Coop</span>
<a href="http://enreda.coop/" target="_blank"
>Sitio desarrollado por Enreda</a
>
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isMenuOpen: false,
}
},
computed: {
isAuthenticated() {
return this.$store.getters['auth/isAuthenticated']
},
isManager() {
return this.$store.getters['auth/isManager']
},
},
methods: {
async logout() {
this.isMenuOpen = false
await this.$store.dispatch('auth/logout')
},
},
}
</script>
<style lang="scss" scoped>
.slider-enter-active,
.slider-leave-active {
transition: all 0.5s ease;
}
.slider-enter,
.slider-leave-to {
transform: translateX(-100%);
}
.nav-menu-container {
@include tablet {
display: none;
}
}
.nav-menu {
font-family: $font-primary;
padding: 30px 0 0 20px;
background-color: $color-green;
height: 100vh;
top: 0;
left: 0;
position: fixed;
z-index: 9999999999;
box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);
overflow: scroll;
@include mobile {
width: 70%;
}
@include tablet {
width: 30%;
}
}
.shadow {
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.2);
position: fixed;
top: 0;
left: 0;
z-index: 999999;
}
.burger,
.close-icon {
width: 1.8rem;
cursor: pointer;
}
.close-icon {
margin-bottom: 3rem;
}
.section {
padding: 0.4em 0;
}
.section-list {
padding-bottom: 1rem;
border-bottom: 1px solid $color-navy;
}
.section-text {
font-weight: $medium;
font-size: $s;
}
.section-img {
width: 1.2rem;
margin-right: 0.8rem;
}
ul {
list-style: none;
}
a {
text-decoration: none;
cursor: pointer;
color: $color-navy;
}
.link-list,
.credits {
margin-top: 1rem;
margin-left: 1rem;
padding: 0.1em;
}
.link {
padding: 0.3em 0;
font-size: $xs;
}
.credits {
margin-top: 1.5rem;
span,
a {
display: block;
color: $color-navy;
font-size: $xs;
padding: 0.3em 0;
}
margin-bottom: 4rem;
}
.login {
padding-left: 2rem;
font-weight: $regular;
}
</style>

506
components/ProductCard.vue Normal file
View File

@@ -0,0 +1,506 @@
<template>
<div class="productcard_container">
<div class="row productcard_container-basic">
<div class="image_container" :class="expanded ? 'col-md-5' : 'col-md-2'">
<img
v-if="product.image"
class="image"
:src="product.image"
:alt="product.name"
/>
<img
v-else
class="image"
src="@/assets/img/latienda-product-default.svg"
:alt="product.name"
/>
</div>
<div class="info_container" :class="expanded ? 'col-md-5' : 'col-md-6'">
<h2 v-b-toggle="'collapse-' + product.id" 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>
<div
class="description"
:class="{ 'not-expanded-description': !expanded }"
>
<p
v-if="product.description"
v-sanitize="[
{
allowedTags: ['p'],
allowedAttributes: {},
},
product.description,
]"
>
<span v-if="product.shipping_terms">{{
product.shipping_terms
}}</span>
</p>
</div>
<b-collapse :id="'collapse-' + product.id" accordion="my-accordion">
<div class="tags_container">
<NuxtLink
:to="tagRoute(n)"
class="tag_container"
v-for="n in product.tags"
:key="n"
>
<img
alt="tag image"
class="tag_img"
src="@/assets/img/latienda-tag.svg"
/>
<span>{{ n }}</span>
</NuxtLink>
</div>
<div class="smlogos_container">
<p class="share-text">Comparte:</p>
<div class="smlogo_container">
<a class="smlogo_link" @click="shareFacebook">
<img
class="smlogo_img"
alt="facebook logo"
src="@/assets/img/latienda-smlogo-facebook.svg"
/>
</a>
</div>
<!-- <a @click="shareTwitter"> -->
<a :href="shareTwitter()">
<div class="smlogo_container">
<img
class="smlogo_img"
alt="twitter logo"
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 v-if="product.company" class="coop_info">
<NuxtLink :to="`c/${product.company.id}`">
<h2>{{ product.company.company_name }}</h2>
</NuxtLink>
<p>{{ product.company.description }}</p>
<a :href="product.company.web_link">{{
product.company.web_link
}}</a>
</div>
</b-collapse>
</div>
<div
:class="
expanded
? 'col-md-2 button_container-detail'
: 'col-md-4 button_container'
"
align="center"
>
<button
@click="buyIntent"
:class="expanded ? 'button_buy-simple' : 'button_buy'"
>
<img
class="button_cart_img"
alt="cart"
src="@/assets/img/latienda-carrito.svg"
/>
<span v-show="!expanded">Comprar</span>
</button>
<div
v-if="product.discount && product.discount > 0"
class="discount-tag"
>
{{ `Descuento ${product.discount}%` }}
</div>
<span v-if="product.company" v-show="!expanded">{{
product.company.company_name
}}</span>
</div>
</div>
<div v-if="expanded && relatedProducts" class="related_products">
<h2>Productos relacionados</h2>
<ProductsRelated :relatedProducts="relatedProducts" />
</div>
<ProductModal v-if="modal" :product="product" @close="closeModal" />
</div>
</template>
<script>
import { mapState } from 'pinia'
import ProductModal from './ProductModal.vue'
import ProductsRelated from './ProductsRelated.vue'
import socialShare from '~/utils/socialShare'
export default {
components: { ProductsRelated, ProductModal },
props: {
product: {
type: Object,
default: () => ({}),
},
},
data() {
return {
expanded: false,
modal: false,
productUrl: null,
relatedProducts: null,
}
},
computed: {
...mapState(useAuthStore, ['access']),
},
mounted() {
this.productUrl = window.location.origin + `/productos/${this.product.id}`
this.$root.$on('bv::collapse::state', async (collapseId, isJustShown) => {
if (collapseId === `collapse-${this.product.id}`) {
this.expanded = isJustShown
if (this.expanded) {
await this.getRelatedProducts()
await this.sendLog('view')
}
}
})
},
methods: {
tagRoute(tag) {
return `/busqueda?tags=${tag}`
},
async getRelatedProducts() {
try {
const { data } = await this.$api.get(
`/products/${this.product.id}/related/`
)
this.relatedProducts = data
} catch {
this.relatedProducts = null
}
},
openUrl(url) {
window.open(url)
},
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 data = await $fetch('https://api.ipify.org?format=json', {
baseURL: config.public.baseURL,
method: 'GET'
})
const ip = data.ip
const object = {
action: action,
action_object: {
model: 'product',
id: this.product.id,
},
}
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)
}
},
closeModal(value) {
this.modal = false
if (value === 200) {
this.$bvToast.toast(`Email enviado correctamente`, {
title: 'latienda.coop',
autoHideDelay: 5000,
appendToast: true,
variant: 'success',
})
} else if (value) {
this.$bvToast.toast(`Se ha producido un error en el envío`, {
title: 'latienda.coop',
autoHideDelay: 5000,
appendToast: true,
variant: 'danger',
})
}
},
shareFacebook() {
const url = socialShare.facebook(this.productUrl)
window.open(url, '_blank')
},
shareTwitter() {
return socialShare.twitter(this.productUrl)
},
shareWhatsApp() {
return socialShare.whatsApp(this.productUrl)
},
},
}
</script>
<style lang="scss" scoped>
.productcard_container {
border: 3px $color-grey-nav solid;
border-radius: 5px;
margin-bottom: 12px;
}
.productcard_container-basic {
padding: 25px 20px;
}
.image_container {
height: 100%;
overflow: hidden;
margin: auto;
}
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
.info_container {
h2 {
outline: none;
font-weight: $regular;
color: $color-navy;
font-size: $m;
}
.price {
font-weight: $bold;
color: $color-navy;
font-size: $m;
}
span {
color: $color-greytext;
}
.description {
margin-top: 8px;
font-family: $font-secondary;
font-size: $s;
color: $color-greytext;
}
}
.not-expanded-description {
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
overflow: hidden;
@include mobile {
-webkit-line-clamp: 3;
}
}
.button_container {
display: flex;
flex-direction: column;
font-size: $m;
span {
margin-top: 15px;
text-align: center;
font-weight: medium;
font-size: $m;
color: $color-navy;
}
.button_buy {
border: 3px solid $color-orange;
border-radius: 5px;
background-color: $color-light;
&:hover {
box-shadow: 0 4px 16px rgba(99, 99, 99, 0.2);
transition: all 0.2s ease;
}
span {
color: $color-orange;
font-weight: $bold;
display: inline-block;
margin-bottom: 15px;
}
}
}
.button_buy-simple {
border: 3px solid $color-orange;
border-radius: 8px;
background-color: $color-light;
padding: 10px 20px;
&:hover {
box-shadow: 0 4px 16px rgba(99, 99, 99, 0.2);
transition: all 0.2s ease;
}
.button_cart_img {
margin-right: 0;
}
}
.tag_container {
margin: 25px 6px 0 0;
border: 2px solid $color-greylayout;
border-radius: 5px;
padding: 6px 10px;
display: inline-block;
font-family: $font-secondary;
font-size: $xs;
color: $color-greytext;
.tag_img {
width: 18px;
}
}
.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;
}
.share-text {
color: $color-navy;
font-size: $m;
margin-top: 2rem;
padding-bottom: 0.2em;
}
.smlogo_container {
cursor: pointer;
display: inline-block;
margin-bottom: 0.5rem;
.smlogo_img {
width: 40px;
fill: $color-greytext;
}
img:hover {
transform: scale(1.1);
transition: all 0.2s ease;
}
}
.button_cart_img {
width: 20px;
margin-right: 10px;
}
.coop_info {
margin-top: 25px;
p,
a {
margin-top: 8px;
font-family: $font-secondary;
font-size: $s;
color: $color-greytext;
}
a {
text-decoration: underline;
}
p {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.related_products {
background-color: $color-lighter-green;
text-align: center;
padding: 0 15px;
h2 {
margin: 35px auto;
font-weight: $regular;
color: $color-navy;
font-size: $m;
display: inline-block;
}
}
.content > h2,
h3,
p {
margin: 0;
}
</style>

View File

@@ -110,6 +110,7 @@
</template>
<script>
import { mapState } from 'pinia'
import DOMPurify from 'dompurify'
import socialShare from '~/utils/socialShare'
export default {
@@ -138,7 +139,9 @@ export default {
geolocation: null,
}
},
computed: {
...mapState(useAuthStore, ['id', 'access']),
},
mounted() {
this.productUrl = window.location.href
this.sendLog('view')
@@ -173,6 +176,7 @@ export default {
})
}
},
// TODO: implement buyIntent (review functionality)
buyIntent() {
this.sendLog('shop')
return this.product.url
@@ -208,10 +212,13 @@ export default {
async sendLog(action) {
const geo = await this.getPosition()
try {
const { data } = await this.$axios.get(
'https://api.ipify.org?format=json'
)
const ip = data.ip
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: {
@@ -219,10 +226,21 @@ export default {
id: this.product.id,
},
}
console.log('Sending log OBJECT:', object)
if (ip) object.ip = ip
if (geo) object.geo = geo
await this.$api.post(`/stats/me/`, object)
} catch {}
//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() {

View File

@@ -1,15 +1,426 @@
<template>
<div class="container">
<div class="row c-description">
<div class="col-md-4">
<div class="c-image-container">
<img v-if="coop?.logo" :src="coop?.logo" alt="" />
<img v-else src="@/assets/img/latienda-product-default.svg" alt="" />
</div>
</div>
<div class="col-md-6">
<h1 class="coop-name">{{ coop?.company_name }}</h1>
<p class="coop-text">
{{ coop?.description }}
</p>
<div class="tags_container">
<NuxtLink
v-for="n in coop?.tags"
:key="n"
:to="tagRoute(n)"
class="tag_container"
>
<img class="tag_img" src="@/assets/img/latienda-tag.svg" />
<span>{{ n }}</span>
</NuxtLink>
</div>
</div>
</div>
<div class="row coop-links">
<div class="col-md-4">
<BButton
v-if="coop?.shop_link"
class="div-link"
align="center"
:href="coop?.shop_link"
target="_blank"
>
<img class="div-link-img" src="@/assets/img/latienda-tienda.svg" />
<span>Tienda online</span>
</BButton>
<BButton
v-if="coop?.web_link"
class="div-link"
align="center"
:href="coop?.web_link"
target="_blank"
>
<img class="div-link-img" src="@/assets/img/latienda-web.svg" />
<span>Página web</span>
</BButton>
</div>
<div class="col-md-4">
<div v-if="coop?.mobile || coop?.phone" class="div-action tel">
<img
class="div-action-img"
src="@/assets/img/latienda-telefono.svg"
/>
<span
>{{ coop?.phone }} <br />
{{ coop?.mobile }}</span
>
</div>
<div v-if="coop?.email" class="div-action mail">
<img class="div-action-img" src="@/assets/img/latienda-email.svg" />
<span>{{ coop?.email }}</span>
</div>
</div>
<div class="col-md-4">
<div v-if="coop?.address" class="div-action address">
<img class="div-action-img" src="@/assets/img/latienda-casa.svg" />
<span>{{ coop?.address }}</span>
</div>
<div v-if="coop?.city" class="div-action location">
<img
class="div-action-img"
src="@/assets/img/latienda-ubicacion.svg"
/>
<span>{{ coop?.city }}</span>
</div>
</div>
</div>
<div class="row">
<div class="col-12 c-tabs">
<div>
c/id page
<BCard no-body>
<BTabs card>
<BTab title="Devoluciones, garantías y reembolsos" active>
<BCardText>
{{
coop?.sale_terms ||
'Consultar con la cooperativa para más información'
}}
</BCardText>
</BTab>
<BTab title="Envío">
<BCardText>
{{
coop?.shipping_terms ||
'Consultar con la cooperativa para más información'
}}
</BCardText>
</BTab>
</BTabs>
</BCard>
</div>
</div>
</div>
<div v-if="slicedProducts.length !== 0">
{{ slicedProducts }} productos encontrados
<div v-for="product in slicedProducts" :key="product.id">
<ProductCard :key="product.key" :product="product" />
</div>
<BPagination
v-model="currentPage"
:v-if="products"
class="pagination"
:total-rows="rows"
:per-page="perPage"
/>
</div>
</div>
</template>
<script>
import { mapState } from 'pinia'
export default {
setup(){
definePageMeta({
layout: 'main',
})
},
//TODO: implement head() method
// head() {
// return {
// title: `latienda.coop | ${this.coop?.company_name}`,
// meta: [
// {
// hid: 'description',
// name: 'description',
// content: this.coop?.description,
// },
// { property: 'og:title', content: this.coop?.company_name },
// { property: 'og:description', content: this.coop?.description },
// { property: 'og:image', content: this.coop?.logo },
// { property: 'og:url', content: this.coop?.web_link },
// { name: 'twitter:card', content: 'summary_large_image' },
// ],
// }
// },
data() {
return {
coop: null,
products: null,
currentPage: 1,
perPage: 10,
}
},
computed: {
...mapState(useAuthStore, ['id', 'access']),
rows() {
return this.products?.length
},
slicedProducts() {
const initial = (this.currentPage - 1) * this.perPage
const final = this.currentPage * this.perPage
const items = this.products ? this.products?.slice(initial, final) : []
return items
},
},
async created() {
try {
const config = useRuntimeConfig()
const $route = useRoute()
this.coop = await $fetch(`/companies/${$route.params.id}/`,
{
baseURL: config.public.baseURL,
method: 'GET',
}
)
this.products = await $fetch(
`/products?company=${route.params.id}`,
{
baseURL: config.public.baseURL,
method: 'GET',
}
)
} catch (error) {
console.error(error)
}
},
mounted() {
this.sendLog('view')
},
methods: {
tagRoute(tag) {
return `/busqueda?tags=${tag}`
},
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 data = await $fetch('https://api.ipify.org?format=json', {
baseURL: config.public.baseURL,
method: 'GET'
})
const ip = data.ip
const object = {
action: action,
action_object: {
model: 'company',
id: this.coop?.id,
},
}
//console.log('Sending log OBJECT:', object)
if (ip) object.ip = ip
if (geo) object.geo = geo
try {
//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 /stats:', error)
}
} catch (error) {
console.error('Error sending log:', error)
}
},
},
}
</script>
<style lang="scss" scoped>
//global classes for Bootstrap styling
ul.nav > li.nav-item {
font-weight: bolder;
:hover {
color: $color-navy;
}
.active {
border-top: 4px solid $color-navy;
color: $color-navy;
}
}
</style>
<style lang="scss" scoped>
.container {
margin-top: 80px;
margin-bottom: 80px;
@include mobile {
padding: 0.4em 2em;
}
}
.c-description {
margin-top: 40px;
margin-bottom: 40px;
@include mobile {
margin-bottom: 0;
}
}
.coop-links {
margin-bottom: 40px;
.div-action {
display: flex;
justify-content: flex-start;
align-items: center;
padding-left: 8px;
overflow: hidden;
.img-tel {
width: 16px;
}
}
.div-link-img,
.div-action-img {
width: 20px;
margin-right: 8px;
}
.div-action-img {
margin-left: 5px;
}
}
.c-image-container {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 100%;
height: auto;
object-fit: cover;
}
}
.coop-name {
font-size: $xl;
color: $color-navy;
text-transform: capitalize;
}
.coop-text {
margin-top: 8px;
font-family: $font-secondary;
font-size: $s;
color: $color-greytext;
}
.div-link:hover {
box-shadow: 0 4px 16px rgba(99, 99, 99, 0.2);
transition: all 0.2s ease;
}
.div-link {
width: 100%;
border: 3px solid $color-orange;
border-radius: 5px;
background-color: $color-light;
font-weight: $bold;
padding: 15px 0;
margin-bottom: 5px;
span {
color: $color-orange;
}
}
.div-action {
width: 100%;
background-color: $color-grey-nav;
border: none;
color: $color-greytext;
font-family: $font-secondary;
font-size: $s;
padding: 20px 0;
margin-bottom: 5px;
}
.tag_container {
margin: 5px 6px 0 0;
border: 2px solid $color-greylayout;
border-radius: 5px;
padding: 6px 10px;
display: inline-block;
font-family: $font-secondary;
font-size: $xs;
color: $color-greytext;
.tag_img {
width: 18px;
}
}
.c-tabs {
margin-bottom: 40px;
.b-tabs {
font-weight: $bold;
color: $color-navy;
font-size: $m;
}
h2 {
font-weight: 700;
color: $color-navy;
font-size: $l;
margin: 20px auto;
}
p {
margin-top: 8px;
font-family: Noto Sans, sans-serif;
font-size: $s;
color: $color-greytext;
}
}
.pagination {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
</style>

View File

@@ -1,15 +1,192 @@
<template>
<div>
pagina cooperativas c/index
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-10 coopcard-list">
<h1 class="title">Últimas cooperativas añadidas</h1>
<p class="help">
Si quieres que tu cooperativa forme parte de este gran proyecto
registrate en el siguiente
<NuxtLink to="/registro/cooperativa"><b>formulario</b></NuxtLink
>.
</p>
<div class="form-container">
<form class="search-container" @submit.prevent="search">
<input
v-model="searchText"
class="search-text"
type="text"
autocomplete="off"
placeholder="Buscar cooperativas"
/>
<img
class="search-icon"
src="@/assets/img/latienda-search-blue.svg"
alt="latienda.coop-search"
@click="search"
/>
</form>
</div>
<div v-for="coop in companyList" :key="coop.id">
<CoopCard :coop="coop" />
</div>
</div>
</div>
<!-- <BPagination
v-model="currentPage"
class="pagination"
:total-rows="rows"
:per-page="perPage"
/> -->
</div>
</template>
<script>
export default {
setup(){
definePageMeta({
layout: 'mainbanner',
})
},
data() {
return {
currentPage: 1,
perPage: 10,
searchText: '',
companyList: null
}
},
// computed: {
// rows() {
// return this.companyList?.length
// },
// slicedCompanies() {
// const initial = (this.currentPage - 1) * this.perPage
// const final = this.currentPage * this.perPage
// const items = this.companyList ? this.companyList.slice(initial, final) : []
// return items
// },
// },
mounted() {
this.getCompanies()
},
methods: {
async getCompanies() {
const config = useRuntimeConfig()
try {
const response = await $fetch(`/companies/sample/`, {
baseURL: config.public.baseURL,
method: 'GET'
})
this.companyList = response
} catch (error) {
console.error('Error fetching companies:', error)
}
},
async search() {
const config = useRuntimeConfig()
if (this.searchText) {
const response = await $fetch(`/search/companies/?search=${this.searchText}`, {
baseURL: config.public.baseURL,
method: 'GET'
})
this.companyList = response
} else {
const response = await $fetch(`/companies/sample/`, {
baseURL: config.public.baseURL,
method: 'GET'
})
this.companyList = response
}
},
},
}
</script>
<style lang="scss" scoped>
.container {
margin-top: 80px;
margin-bottom: 80px;
}
.title {
margin-top: 60px;
margin-bottom: 40px;
font-size: $xxl;
color: $color-navy;
}
.form-container {
display: flex;
justify-content: flex-start;
}
.coopcard-list {
margin-bottom: 40px;
}
.pagination {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.help {
color: $color-navy;
margin-bottom: 0px;
font-size: $s;
}
.search-container {
border: none;
border-radius: 5px;
background: $color-light-green;
width: 50%;
height: 32px;
overflow: hidden;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 50px;
margin-bottom: 50px;
@include mobile {
width: 100%;
}
}
.search-container img,
input,
select {
padding: 6px 10px;
margin-right: 16px;
background: transparent;
font-size: $xl;
border: none;
}
.search-icon {
float: right;
height: 90%;
}
.search-text {
outline: none;
width: 100%;
text-align: center;
color: $color-navy;
font-size: $s;
font-weight: $regular;
}
::placeholder {
color: $color-navy;
font-size: $s;
}
select {
--webkit-appearance: auto;
}
</style>

View File

@@ -29,6 +29,7 @@ export const useAuthStore = defineStore('auth', {
method: 'POST',
body: { email, password }
})
console.log('Login payload:', payload)
this.setPayload(payload)
},
@@ -41,7 +42,11 @@ export const useAuthStore = defineStore('auth', {
Authorization: `Bearer ${this.access}`
}
})
try {
this.setUserData(data)
} catch (error) {
console.error('Error setting user data:', error)
}
},
async refresh() {