Compare commits
12 Commits
fix/Buscad
...
2b00b2556a
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b00b2556a | |||
| 24fa4800a9 | |||
| 5aaaac1e45 | |||
| 5b54f0e20d | |||
| 7a7939e309 | |||
|
|
523d0b9d08 | ||
|
|
0806731a1f | ||
|
|
beb5e153f3 | ||
|
|
3e19b3d272 | ||
|
|
b21d3a0ca6 | ||
|
|
3029e6d4b9 | ||
| 7a75f93f67 |
@@ -1,2 +1,3 @@
|
||||
VITE_API_IMPLEMENTATION=django
|
||||
VITE_DJANGO_BASE_URL=http://localhost:7000
|
||||
VITE_CONTACT_PHONE=3023567797
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
VITE_API_IMPLEMENTATION=django
|
||||
VITE_DJANGO_BASE_URL=http://localhost:7000
|
||||
VITE_CONTACT_PHONE=
|
||||
|
||||
@@ -3,9 +3,11 @@ FROM node:20-alpine AS builder
|
||||
|
||||
ARG VITE_DJANGO_BASE_URL
|
||||
ARG VITE_API_IMPLEMENTATION
|
||||
ARG VITE_CONTACT_PHONE
|
||||
|
||||
ENV VITE_DJANGO_BASE_URL=$VITE_DJANGO_BASE_URL
|
||||
ENV VITE_API_IMPLEMENTATION=$VITE_API_IMPLEMENTATION
|
||||
ENV VITE_CONTACT_PHONE=$VITE_CONTACT_PHONE
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ set +a
|
||||
|
||||
BUILD_ARGS="--build-arg VITE_DJANGO_BASE_URL=$VITE_DJANGO_BASE_URL"
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg VITE_API_IMPLEMENTATION=$VITE_API_IMPLEMENTATION"
|
||||
BUILD_ARGS="$BUILD_ARGS --build-arg VITE_CONTACT_PHONE=$VITE_CONTACT_PHONE"
|
||||
|
||||
FULL_IMAGE_NAME="$GITEA_REGISTRY/$GITEA_USER/$IMAGE_NAME"
|
||||
|
||||
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"@mdi/font": "7.4.47",
|
||||
"axios": "^1.13.5",
|
||||
"core-js": "^3.37.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"roboto-fontface": "*",
|
||||
"vee-validate": "^4.14.6",
|
||||
"vue": "^3.4.31",
|
||||
@@ -3863,6 +3864,12 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"@mdi/font": "7.4.47",
|
||||
"axios": "^1.13.5",
|
||||
"core-js": "^3.37.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"roboto-fontface": "*",
|
||||
"vee-validate": "^4.14.6",
|
||||
"vue": "^3.4.31",
|
||||
|
||||
@@ -1,39 +1,62 @@
|
||||
<template>
|
||||
<v-footer height="40" app>
|
||||
<a
|
||||
v-for="item in items"
|
||||
:key="item.title"
|
||||
:href="item.href"
|
||||
:title="item.title"
|
||||
class="d-inline-block mx-2 social-link"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
<div>
|
||||
<!-- Footer completo: visible siempre en desktop, toggle en mobile -->
|
||||
<v-footer
|
||||
v-if="expanded || !isMobile"
|
||||
height="40"
|
||||
app
|
||||
class="app-footer"
|
||||
:class="{ 'footer-overlay': isMobile }"
|
||||
>
|
||||
<v-icon
|
||||
:icon="item.icon"
|
||||
:size="item.icon === '$vuetify' ? 24 : 16"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<div
|
||||
class="text-caption text-disabled"
|
||||
style="position: absolute; right: 16px;"
|
||||
>
|
||||
© 2016-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">Vuetify, LLC</span>
|
||||
—
|
||||
<a
|
||||
class="text-decoration-none on-surface"
|
||||
href="https://vuetifyjs.com/about/licensing/"
|
||||
v-for="item in items"
|
||||
:key="item.title"
|
||||
:href="item.href"
|
||||
:title="item.title"
|
||||
class="d-inline-block mx-2 social-link"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
MIT License
|
||||
<v-icon
|
||||
:icon="item.icon"
|
||||
:size="item.icon === '$vuetify' ? 24 : 16"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</v-footer>
|
||||
|
||||
<div
|
||||
class="text-caption text-disabled"
|
||||
style="position: absolute; right: 16px;"
|
||||
>
|
||||
© 2016-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">Vuetify, LLC</span>
|
||||
—
|
||||
<a
|
||||
class="text-decoration-none on-surface"
|
||||
href="https://vuetifyjs.com/about/licensing/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
MIT License
|
||||
</a>
|
||||
</div>
|
||||
</v-footer>
|
||||
|
||||
<!-- Botón flotante: solo en mobile -->
|
||||
<v-btn
|
||||
v-if="isMobile"
|
||||
icon
|
||||
size="small"
|
||||
class="footer-toggle-btn"
|
||||
:color="expanded ? 'primary' : undefined"
|
||||
@click="expanded = !expanded"
|
||||
>
|
||||
<v-icon :icon="expanded ? 'mdi-close' : '$vuetify'" :size="expanded ? 20 : 24" />
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: 'Vuetify Documentation',
|
||||
@@ -65,15 +88,48 @@
|
||||
icon: `mdi-reddit`,
|
||||
href: 'https://reddit.com/r/vuetifyjs',
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
const expanded = ref(false);
|
||||
const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1200);
|
||||
|
||||
const isMobile = computed(() => windowWidth.value < 960);
|
||||
|
||||
const onResize = () => {
|
||||
windowWidth.value = window.innerWidth;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', onResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass">
|
||||
.social-link :deep(.v-icon)
|
||||
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
|
||||
text-decoration: none
|
||||
transition: .2s ease-in-out
|
||||
.social-link :deep(.v-icon)
|
||||
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
|
||||
text-decoration: none
|
||||
transition: .2s ease-in-out
|
||||
|
||||
&:hover
|
||||
color: rgba(25, 118, 210, 1)
|
||||
&:hover
|
||||
color: rgba(25, 118, 210, 1)
|
||||
|
||||
/* Botón flotante para mobile */
|
||||
.footer-toggle-btn
|
||||
position: fixed
|
||||
bottom: 12px
|
||||
right: 12px
|
||||
z-index: 2000
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25)
|
||||
|
||||
/* Overlay del footer en mobile */
|
||||
.footer-overlay
|
||||
position: fixed !important
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
z-index: 1999
|
||||
</style>
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
<div class="glow-bubble bubble-red"></div>
|
||||
|
||||
<div class="login-card">
|
||||
<v-img
|
||||
:src="logo"
|
||||
alt="Don Confiao"
|
||||
max-width="140"
|
||||
class="mx-auto mb-4"
|
||||
/>
|
||||
<SiteLogo max-width="140" class="mx-auto mb-4" />
|
||||
<h1 class="text-h5 text-sm-h4 font-weight-bold text-center mb-1">
|
||||
Iniciar Sesión
|
||||
</h1>
|
||||
@@ -74,7 +69,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AuthService from '@/services/auth'
|
||||
import logo from '@/assets/logo_colorful.png'
|
||||
import SiteLogo from '@/components/SiteLogo.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<v-dialog v-model="show" max-width="400">
|
||||
<v-card>
|
||||
<v-card-title class="headline">Iniciar sesión</v-card-title>
|
||||
<v-card-title class="headline d-flex align-center">
|
||||
Iniciar sesión
|
||||
<v-spacer />
|
||||
<v-btn icon size="small" variant="text" @click="show = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="form" @submit.prevent="onSubmit">
|
||||
<v-text-field
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
{ title: 'Compra adm', route: '/compra_admin', icon: 'mdi-cart'},
|
||||
{ title: 'Gestión de Productos', route: '/admin/products', icon: 'mdi-package-variant'},
|
||||
{ title: 'Imágenes de Catálogo', route: '/admin/catalogue-images', icon: 'mdi-image-multiple'},
|
||||
{ title: 'Datos de la Tienda', route: '/admin/store-settings', icon: 'mdi-map-marker'},
|
||||
{ title: 'Ver Ventas por Catálogo', route: '/admin/catalog-sales', icon: 'mdi-cart-arrow-down'},
|
||||
{ divider: true },
|
||||
{ header: 'Sincronización Tryton' },
|
||||
|
||||
39
src/components/SiteLogo.vue
Normal file
39
src/components/SiteLogo.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<v-img
|
||||
v-if="logoUrl && !broken"
|
||||
:src="logoUrl"
|
||||
:alt="alt"
|
||||
:max-width="maxWidth"
|
||||
@error="broken = true"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, inject, onMounted, ref } from 'vue';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
|
||||
const props = defineProps({
|
||||
maxWidth: {
|
||||
type: [Number, String],
|
||||
default: 180,
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
default: 'Don Confiao',
|
||||
},
|
||||
});
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const api = inject('api');
|
||||
const broken = ref(false);
|
||||
|
||||
const logoUrl = computed(() => settingsStore.logo);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await settingsStore.fetchSettings(api);
|
||||
} catch (e) {
|
||||
broken.value = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
167
src/components/StoreLocation.vue
Normal file
167
src/components/StoreLocation.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<v-container v-if="settings && settings.address">
|
||||
<v-sheet
|
||||
class="store-location-section rounded-xl pa-6 pa-md-8"
|
||||
elevation="2"
|
||||
>
|
||||
<div class="d-flex flex-column align-center text-center mb-4">
|
||||
<div class="d-flex align-center mb-1">
|
||||
<v-icon color="primary" size="36" class="mr-2">mdi-map-marker</v-icon>
|
||||
<h2 class="text-h4 font-weight-bold mb-0">Visítanos</h2>
|
||||
</div>
|
||||
<p class="text-body-1 text-medium-emphasis mb-0">
|
||||
Encuentra nuestra tienda física y adquiere nuestros productos
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-4">
|
||||
<div class="d-flex align-center justify-center">
|
||||
<v-icon color="red" class="mr-2">mdi-map-marker</v-icon>
|
||||
<span class="text-h6 font-weight-medium">
|
||||
{{ settings.address }}
|
||||
</span>
|
||||
</div>
|
||||
<v-btn
|
||||
v-if="hasCoordinates"
|
||||
:href="directionsLink"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="mdi-directions"
|
||||
class="mt-2"
|
||||
>
|
||||
Cómo llegar
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
:href="searchLink"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="mdi-map-search"
|
||||
class="mt-2"
|
||||
>
|
||||
Ver en el mapa
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-if="hasCoordinates" ref="mapEl" class="map-wrapper"></div>
|
||||
<v-alert v-else type="info" variant="tonal" class="ma-0">
|
||||
La ubicación en el mapa aún no está configurada.
|
||||
</v-alert>
|
||||
</v-sheet>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, inject, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
|
||||
const api = inject('api');
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const settings = computed(() => settingsStore.settings);
|
||||
const mapEl = ref(null);
|
||||
|
||||
let map = null;
|
||||
let marker = null;
|
||||
|
||||
const hasCoordinates = computed(
|
||||
() =>
|
||||
settings.value &&
|
||||
settings.value.latitude != null &&
|
||||
settings.value.longitude != null
|
||||
);
|
||||
|
||||
const redPinIcon = L.divIcon({
|
||||
className: 'store-map-pin',
|
||||
html: '<i class="mdi mdi-map-marker" style="font-size:48px;line-height:1;color:#f44336;"></i>',
|
||||
iconSize: [48, 48],
|
||||
iconAnchor: [24, 48],
|
||||
});
|
||||
|
||||
const directionsLink = computed(() => {
|
||||
const lat = settings.value.latitude;
|
||||
const lng = settings.value.longitude;
|
||||
return `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lng}#map=16/${lat}/${lng}`;
|
||||
});
|
||||
|
||||
const searchLink = computed(
|
||||
() =>
|
||||
`https://www.openstreetmap.org/search?query=${encodeURIComponent(
|
||||
settings.value.address
|
||||
)}`
|
||||
);
|
||||
|
||||
function initMap() {
|
||||
if (!hasCoordinates.value || !mapEl.value || map) return;
|
||||
|
||||
const lat = settings.value.latitude;
|
||||
const lng = settings.value.longitude;
|
||||
|
||||
map = L.map(mapEl.value, {
|
||||
scrollWheelZoom: false,
|
||||
}).setView([lat, lng], 16);
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
}).addTo(map);
|
||||
|
||||
marker = L.marker([lat, lng], { icon: redPinIcon }).addTo(map);
|
||||
marker.bindPopup(settings.value.address);
|
||||
}
|
||||
|
||||
function handleResize() {
|
||||
if (map) {
|
||||
map.invalidateSize();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await settingsStore.fetchSettings(api);
|
||||
await nextTick();
|
||||
initMap();
|
||||
} catch (error) {
|
||||
console.error('Error al cargar la información de la tienda:', error);
|
||||
}
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (map) {
|
||||
map.remove();
|
||||
map = null;
|
||||
marker = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.store-location-section {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.map-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.map-wrapper :deep(.leaflet-control-attribution) {
|
||||
font-size: 10px;
|
||||
}
|
||||
</style>
|
||||
235
src/components/StoreSettingsManagement.vue
Normal file
235
src/components/StoreSettingsManagement.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="6">
|
||||
<h1 class="text-h4">Datos de la Tienda</h1>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" class="text-md-right">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-content-save"
|
||||
:loading="submitting"
|
||||
:disabled="submitting"
|
||||
@click="submitForm"
|
||||
>
|
||||
Guardar Cambios
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<v-form ref="formRef">
|
||||
<v-text-field
|
||||
v-model="form.address"
|
||||
label="Dirección de la tienda"
|
||||
:rules="[(v) => !!v || 'La dirección es obligatoria']"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-map-marker"
|
||||
class="mb-3"
|
||||
/>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model.number="form.latitude"
|
||||
label="Latitud (opcional)"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-crosshairs-gps"
|
||||
type="number"
|
||||
step="any"
|
||||
class="mb-3"
|
||||
hint="Ej: 4.6097"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model.number="form.longitude"
|
||||
label="Longitud (opcional)"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-crosshairs-gps"
|
||||
type="number"
|
||||
step="any"
|
||||
class="mb-3"
|
||||
hint="Ej: -74.0817"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider class="my-4" />
|
||||
|
||||
<div>
|
||||
<h3 class="text-h6 mb-2">Logo del sitio</h3>
|
||||
<v-img
|
||||
v-if="currentLogo"
|
||||
:src="currentLogo"
|
||||
alt="Logo actual"
|
||||
max-width="180"
|
||||
class="mb-3"
|
||||
/>
|
||||
<v-file-input
|
||||
v-model="logoFile"
|
||||
label="Seleccionar nueva imagen del logo"
|
||||
accept="image/*"
|
||||
prepend-icon="mdi-image"
|
||||
variant="outlined"
|
||||
class="mb-3"
|
||||
clearable
|
||||
/>
|
||||
<v-btn
|
||||
v-if="currentLogo"
|
||||
variant="tonal"
|
||||
color="error"
|
||||
prepend-icon="mdi-image-off"
|
||||
:loading="removingLogo"
|
||||
:disabled="removingLogo || submitting"
|
||||
@click="removeLogo"
|
||||
>
|
||||
Quitar logo actual
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
:timeout="3000"
|
||||
location="top"
|
||||
>
|
||||
{{ snackbar.message }}
|
||||
<template #actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false">Cerrar</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, onMounted } from "vue";
|
||||
import { useSettingsStore } from "@/stores/settings";
|
||||
|
||||
const api = inject("api");
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const formRef = ref(null);
|
||||
const submitting = ref(false);
|
||||
const removingLogo = ref(false);
|
||||
const logoFile = ref(null);
|
||||
const currentLogo = ref(null);
|
||||
|
||||
const form = ref({
|
||||
address: "",
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
});
|
||||
|
||||
const snackbar = ref({ show: false, message: "", color: "success" });
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const data = await api.getStoreSettings();
|
||||
settingsStore.setSettings(data);
|
||||
form.value = {
|
||||
address: data.address || "",
|
||||
latitude: data.latitude ?? null,
|
||||
longitude: data.longitude ?? null,
|
||||
};
|
||||
currentLogo.value = data.logo || null;
|
||||
} catch (error) {
|
||||
console.error("Error al cargar la información de la tienda:", error);
|
||||
showSnackbar("Error al cargar la información de la tienda", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
const formComponent = formRef.value;
|
||||
if (formComponent) {
|
||||
const { valid } = await formComponent.validate();
|
||||
if (!valid) return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
if (logoFile.value) {
|
||||
const payload = new FormData();
|
||||
payload.append("address", form.value.address);
|
||||
if (form.value.latitude !== "" && form.value.latitude != null) {
|
||||
payload.append("latitude", toNumberOrNull(form.value.latitude));
|
||||
}
|
||||
if (form.value.longitude !== "" && form.value.longitude != null) {
|
||||
payload.append("longitude", toNumberOrNull(form.value.longitude));
|
||||
}
|
||||
payload.append("logo", logoFile.value);
|
||||
await api.updateStoreSettings(payload);
|
||||
} else {
|
||||
await api.updateStoreSettings({
|
||||
address: form.value.address,
|
||||
latitude: toNumberOrNull(form.value.latitude),
|
||||
longitude: toNumberOrNull(form.value.longitude),
|
||||
});
|
||||
}
|
||||
logoFile.value = null;
|
||||
await refreshLogo();
|
||||
showSnackbar("Información de la tienda actualizada", "success");
|
||||
} catch (error) {
|
||||
console.error("Error al guardar la información de la tienda:", error);
|
||||
showSnackbar("Error al guardar la información de la tienda", "error");
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function removeLogo() {
|
||||
removingLogo.value = true;
|
||||
try {
|
||||
await api.updateStoreSettings({ logo: null });
|
||||
logoFile.value = null;
|
||||
await refreshLogo();
|
||||
showSnackbar("Logo eliminado", "success");
|
||||
} catch (error) {
|
||||
console.error("Error al quitar el logo:", error);
|
||||
showSnackbar("Error al quitar el logo", "error");
|
||||
} finally {
|
||||
removingLogo.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshLogo() {
|
||||
const data = await api.getStoreSettings();
|
||||
settingsStore.setSettings(data);
|
||||
currentLogo.value = data.logo || null;
|
||||
}
|
||||
|
||||
function toNumberOrNull(value) {
|
||||
if (value === null || value === undefined || value === "") return null;
|
||||
const number = Number(value);
|
||||
return Number.isNaN(number) ? null : number;
|
||||
}
|
||||
|
||||
function showSnackbar(message, color) {
|
||||
snackbar.value = { show: true, message, color };
|
||||
}
|
||||
|
||||
onMounted(loadSettings);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-md-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.text-md-right {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,7 @@
|
||||
</v-container>
|
||||
<v-container v-show="id">
|
||||
<v-toolbar>
|
||||
<v-toolbar-title> Resumen de la compra {{ id }}</v-toolbar-title>
|
||||
<v-toolbar-title> {{ type === 'catalog' ? 'Resumen del pedido' : 'Resumen de la compra' }} {{ id }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
@@ -39,7 +39,7 @@
|
||||
{{ currencyFormat(calculateSubtotal(item.unit_price, item.quantity)) }}
|
||||
</template>
|
||||
</v-data-table-virtual>
|
||||
<v-alert type="info" class="my-4">
|
||||
<v-alert v-if="type !== 'catalog'" type="info" class="my-4">
|
||||
Recuerda adicionar a la planilla física lo siguiente
|
||||
<v-data-table
|
||||
:headers="headersTemplate"
|
||||
|
||||
@@ -8,12 +8,7 @@
|
||||
<div class="glow-bubble bubble-yellow"></div>
|
||||
<div class="glow-bubble bubble-red"></div>
|
||||
<div class="hero-content">
|
||||
<v-img
|
||||
:src="logo"
|
||||
alt="Don Confiao"
|
||||
max-width="180"
|
||||
class="mx-auto mb-4"
|
||||
/>
|
||||
<SiteLogo max-width="180" class="mx-auto mb-4" />
|
||||
<h1 class="text-h4 font-weight-bold mb-2">Don Confiao te atiende</h1>
|
||||
<p class="text-subtitle-1 font-italic font-weight-bold">
|
||||
Economía solidaria, mercado justo, alimentación sana
|
||||
@@ -21,6 +16,10 @@
|
||||
</div>
|
||||
</v-sheet>
|
||||
|
||||
<div class="py-6">
|
||||
<StoreLocation />
|
||||
</div>
|
||||
|
||||
<v-container class="py-6">
|
||||
<v-row>
|
||||
<v-col cols="12" md="4">
|
||||
@@ -45,25 +44,39 @@
|
||||
<v-card class="h-100" elevation="2">
|
||||
<v-card-item>
|
||||
<template #prepend>
|
||||
<v-icon color="orange-darken-2" size="48"
|
||||
>mdi-progress-wrench</v-icon
|
||||
>
|
||||
<v-icon color="green" size="48">mdi-code-tags</v-icon>
|
||||
</template>
|
||||
<v-card-title class="font-weight-bold"
|
||||
>En Desarrollo</v-card-title
|
||||
>Software Libre</v-card-title
|
||||
>
|
||||
</v-card-item>
|
||||
<v-card-text>
|
||||
Don Confiao apenas está entendiendo cómo funciona esta tienda y
|
||||
por ahora
|
||||
<ResaltedText
|
||||
>solo puede atender las compras de contado</ResaltedText
|
||||
>, ya sea en efectivo o consignación.
|
||||
<v-alert type="warning" class="mt-3" density="compact">
|
||||
Si no vas a pagar tu compra recuerda que debes hacerlo en la
|
||||
planilla manual
|
||||
</v-alert>
|
||||
Don Confiao es un proyecto de
|
||||
<ResaltedText>Software Libre</ResaltedText>. Su desarrollo está
|
||||
dividido en dos repositorios: el backend y el frontend.
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
href="https://gitea.onecluster.org/OneTeam/don_confiao_backend"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
prepend-icon="mdi-server"
|
||||
variant="text"
|
||||
color="primary"
|
||||
>
|
||||
Backend
|
||||
</v-btn>
|
||||
<v-btn
|
||||
href="https://gitea.onecluster.org/OneTeam/don_confiao_frontend"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
prepend-icon="mdi-web"
|
||||
variant="text"
|
||||
color="primary"
|
||||
>
|
||||
Frontend
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
@@ -117,8 +130,9 @@
|
||||
|
||||
<script setup>
|
||||
import ResaltedText from "@/components/ResaltedText.vue";
|
||||
import StoreLocation from "@/components/StoreLocation.vue";
|
||||
import SiteLogo from "@/components/SiteLogo.vue";
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import logo from "@/assets/logo_colorful.png";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
</script>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Precio Total -->
|
||||
<div class="price-row">
|
||||
<div v-if="product.quantity > 0" class="price-row">
|
||||
<span class="price-label text-caption">Precio total</span>
|
||||
<v-chip
|
||||
color="success"
|
||||
@@ -63,52 +63,64 @@
|
||||
|
||||
<!-- Footer con Controles de Cantidad -->
|
||||
<v-card-actions class="product-actions pa-2 pb-3 justify-center">
|
||||
<div class="quantity-controls">
|
||||
<template v-if="disabled">
|
||||
<v-btn
|
||||
icon
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
color="error"
|
||||
class="qty-btn"
|
||||
@click="decrease(product)"
|
||||
:disabled="disabled || product.quantity === 0"
|
||||
prepend-icon="mdi-cart-plus"
|
||||
class="login-add-btn"
|
||||
@click="$emit('request-login')"
|
||||
>
|
||||
<v-icon size="20">mdi-minus</v-icon>
|
||||
Agregar
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="quantity-controls">
|
||||
<v-btn
|
||||
icon
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="error"
|
||||
class="qty-btn"
|
||||
@click="decrease(product)"
|
||||
:disabled="product.quantity === 0"
|
||||
>
|
||||
<v-icon size="20">mdi-minus</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-text-field
|
||||
v-model.number="product.quantity"
|
||||
type="number"
|
||||
min="0"
|
||||
class="quantity-input mx-1"
|
||||
variant="solo-filled"
|
||||
density="compact"
|
||||
hide-details
|
||||
single-line
|
||||
flat
|
||||
aria-label="Cantidad"
|
||||
:disabled="disabled"
|
||||
@input="handleQuantityChange"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model.number="product.quantity"
|
||||
type="number"
|
||||
min="0"
|
||||
class="quantity-input mx-1"
|
||||
variant="solo-filled"
|
||||
density="compact"
|
||||
hide-details
|
||||
single-line
|
||||
flat
|
||||
aria-label="Cantidad"
|
||||
@input="handleQuantityChange"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
icon
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="success"
|
||||
class="qty-btn"
|
||||
@click="handleIncrease"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<v-icon size="20">mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
icon
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="success"
|
||||
class="qty-btn"
|
||||
@click="handleIncrease"
|
||||
>
|
||||
<v-icon size="20">mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['add-to-cart', 'request-login'],
|
||||
props: {
|
||||
product: {
|
||||
type: Object,
|
||||
@@ -312,6 +324,13 @@ export default {
|
||||
RESPONSIVE BREAKPOINTS
|
||||
============================================ */
|
||||
|
||||
/* Botón de agregar (no autenticado) */
|
||||
.login-add-btn {
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Móvil pequeño (< 375px) */
|
||||
@media (max-width: 374px) {
|
||||
.product-content {
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="$emit('toggle-collapse')"
|
||||
@click.stop="$emit('toggle-collapse')"
|
||||
:title="isCollapsed ? 'Expandir carrito' : 'Contraer carrito'"
|
||||
>
|
||||
<v-icon>{{ isCollapsed ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||
@@ -133,7 +133,7 @@
|
||||
|
||||
<v-card-actions v-if="cartItems.length > 0" class="cart-checkout-section">
|
||||
<v-btn color="primary" block @click="$emit('checkout')">
|
||||
Finalizar Compra
|
||||
Finalizar Pedido
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
|
||||
10
src/pages/admin/store-settings.vue
Normal file
10
src/pages/admin/store-settings.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<StoreSettingsManagement v-if="authStore.isAdmin" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import StoreSettingsManagement from '@/components/StoreSettingsManagement.vue';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
</script>
|
||||
@@ -49,20 +49,6 @@
|
||||
</div>
|
||||
</v-sheet>
|
||||
|
||||
<!-- Controles de paginación superiores -->
|
||||
<PaginationControls
|
||||
v-if="filteredItems.length > 0"
|
||||
:current-page="currentPage"
|
||||
:total-pages="totalPages"
|
||||
:items-per-page="itemsPerPage"
|
||||
:items-per-page-options="itemsPerPageOptions"
|
||||
:pagination-info="paginationInfo"
|
||||
:total-visible-pages="totalVisiblePages"
|
||||
@page-change="handlePageChange"
|
||||
@items-per-page-change="handleItemsPerPageChange"
|
||||
position="top"
|
||||
/>
|
||||
|
||||
<!-- Grid de productos paginados -->
|
||||
<v-row class="product-grid" v-if="paginatedItems.length > 0">
|
||||
<v-col
|
||||
@@ -82,6 +68,7 @@
|
||||
:updateQuantity="updateQuantity"
|
||||
:disabled="!isAuthenticated"
|
||||
@add-to-cart="addToCart"
|
||||
@request-login="showLogin"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -136,11 +123,54 @@
|
||||
@update-quantity="updateCartQuantity"
|
||||
@toggle-collapse="toggleCart"
|
||||
/>
|
||||
<v-card v-else class="login-prompt-card pa-4 text-center">
|
||||
<v-card v-else-if="!isMobile || showLoginPrompt" class="login-prompt-card pa-4 text-center">
|
||||
<v-btn
|
||||
icon
|
||||
size="x-small"
|
||||
variant="text"
|
||||
class="login-prompt-close"
|
||||
@click="showLoginPrompt = false"
|
||||
>
|
||||
<v-icon size="18">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<v-icon size="48" color="primary" class="mb-2">mdi-cart-lock</v-icon>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||
Inicia sesión para agregar productos al carrito y finalizar tu compra
|
||||
Para hacer pedidos debes de tener usuario y clave, si no lo tienes comunicate con nosotros al
|
||||
<strong class="text-primary">{{ contactPhone }}</strong>
|
||||
para solicitarlo. Esto es necesario porque no podemos procesar tus pedidos todos los días del mes.
|
||||
Comunicate con nosotros para contarte todos los detalles y que puedas apoyar este proceso.
|
||||
</p>
|
||||
<div class="d-flex justify-center ga-2 mb-3">
|
||||
<v-btn
|
||||
icon
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
:href="'tel:' + contactPhone"
|
||||
>
|
||||
<v-icon size="18">mdi-phone</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
size="small"
|
||||
color="success"
|
||||
variant="tonal"
|
||||
:href="'https://wa.me/' + contactPhone"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon size="18">mdi-whatsapp</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
size="small"
|
||||
color="info"
|
||||
variant="tonal"
|
||||
:href="'https://t.me/+57' + contactPhone"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon size="18">mdi-send</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
@@ -157,7 +187,7 @@
|
||||
<!-- Modal 1: Confirmación de productos -->
|
||||
<v-dialog v-model="checkoutDialog" max-width="600" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="headline">Confirmar Compra</v-card-title>
|
||||
<v-card-title class="headline">Confirmar Pedido</v-card-title>
|
||||
<v-card-text>
|
||||
<v-list v-if="cartItems.length > 0" class="product-list-scroll">
|
||||
<v-list-item v-for="item in cartItems" :key="item.id">
|
||||
@@ -192,7 +222,7 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Modal 2: Datos personales -->
|
||||
<!-- Modal 2: Datos personales + aviso de coordinación -->
|
||||
<v-dialog v-model="personalDataDialog" max-width="500" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="headline">Datos de Contacto</v-card-title>
|
||||
@@ -229,6 +259,48 @@
|
||||
variant="outlined"
|
||||
></v-select>
|
||||
</v-form>
|
||||
|
||||
<v-divider class="my-3"></v-divider>
|
||||
|
||||
<v-alert type="info" variant="tonal" class="mb-0">
|
||||
<div class="text-body-2 mb-2">
|
||||
Para coordinar la entrega de tu pedido escríbenos:
|
||||
</div>
|
||||
<div class="d-flex ga-2">
|
||||
<v-btn
|
||||
icon
|
||||
size="x-small"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
:href="'tel:' + contactPhone"
|
||||
>
|
||||
<v-icon size="16">mdi-phone</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
size="x-small"
|
||||
color="success"
|
||||
variant="tonal"
|
||||
:href="'https://wa.me/57' + contactPhone"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon size="16">mdi-whatsapp</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
size="x-small"
|
||||
color="info"
|
||||
variant="tonal"
|
||||
:href="'https://t.me/+57' + contactPhone"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon size="16">mdi-send</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-body-2 font-weight-bold ml-1 d-flex align-center">
|
||||
{{ contactPhone }}
|
||||
</span>
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
@@ -240,7 +312,7 @@
|
||||
:loading="isSubmitting"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Finalizar Compra
|
||||
Finalizar Pedido
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@@ -274,7 +346,8 @@ export default {
|
||||
setup() {
|
||||
const cartStore = useCartStore();
|
||||
const authStore = useAuthStore();
|
||||
const cartCollapsed = ref(false); // Cambiado a false para que inicie expandido en desktop
|
||||
const cartCollapsed = ref(false);
|
||||
const showLoginPrompt = ref(false);
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
|
||||
const isMobile = computed(() => windowWidth.value < 960); // Cambiado de 680 a 960
|
||||
@@ -295,6 +368,7 @@ export default {
|
||||
cartStore,
|
||||
authStore,
|
||||
cartCollapsed,
|
||||
showLoginPrompt,
|
||||
isMobile,
|
||||
windowWidth,
|
||||
};
|
||||
@@ -339,6 +413,9 @@ export default {
|
||||
isAuthenticated() {
|
||||
return this.authStore.isAuthenticated;
|
||||
},
|
||||
contactPhone() {
|
||||
return import.meta.env.VITE_CONTACT_PHONE || '';
|
||||
},
|
||||
// Búsqueda
|
||||
filteredItems() {
|
||||
if (!this.searchQuery) return this.items;
|
||||
@@ -471,7 +548,6 @@ export default {
|
||||
},
|
||||
async onSubmitPurchase() {
|
||||
const form = this.$refs.personalForm;
|
||||
|
||||
if (form) {
|
||||
const { valid } = await form.validate();
|
||||
if (!valid) return;
|
||||
@@ -493,7 +569,6 @@ export default {
|
||||
customer_phone: this.customerPhone,
|
||||
pickup_method: this.pickupMethod,
|
||||
};
|
||||
|
||||
this.api
|
||||
.createCatalogPurchase(payload)
|
||||
.then((data) => {
|
||||
@@ -501,7 +576,7 @@ export default {
|
||||
this.personalDataDialog = false;
|
||||
this.$router.push({
|
||||
path: "/summary_purchase",
|
||||
query: {
|
||||
query: {
|
||||
id: parseInt(data.id),
|
||||
type: 'catalog'
|
||||
},
|
||||
@@ -553,7 +628,14 @@ export default {
|
||||
openLoginDialog() {
|
||||
this.$refs.loginDialogRef.open();
|
||||
},
|
||||
showLogin() {
|
||||
this.showLoginPrompt = true;
|
||||
if (this.isMobile) {
|
||||
this.cartCollapsed = false;
|
||||
}
|
||||
},
|
||||
onLoginSuccess() {
|
||||
this.showLoginPrompt = false;
|
||||
this.api.getCurrentUser().then((user) => {
|
||||
this.authStore.setUser(user);
|
||||
});
|
||||
@@ -769,6 +851,15 @@ export default {
|
||||
.login-prompt-card {
|
||||
border: 2px dashed rgba(0, 0, 0, 0.12);
|
||||
background: #fafafa !important;
|
||||
position: relative;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.login-prompt-close {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
|
||||
@@ -24,6 +24,7 @@ const ADMIN_ROUTES = [
|
||||
'/admin/products',
|
||||
'/admin/catalog-sales',
|
||||
'/admin/catalogue-images',
|
||||
'/admin/store-settings',
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@@ -98,6 +98,14 @@ class Api {
|
||||
deleteCatalogueImage(id) {
|
||||
return this.apiImplementation.deleteCatalogueImage(id);
|
||||
}
|
||||
|
||||
getStoreSettings() {
|
||||
return this.apiImplementation.getStoreSettings();
|
||||
}
|
||||
|
||||
updateStoreSettings(data) {
|
||||
return this.apiImplementation.updateStoreSettings(data);
|
||||
}
|
||||
}
|
||||
|
||||
export default Api;
|
||||
|
||||
@@ -154,6 +154,18 @@ class DjangoApi {
|
||||
const url = this.base + `/don_confiao/api/catalogue_images/${id}/`;
|
||||
return http.delete(url).then((r) => r.data);
|
||||
}
|
||||
|
||||
getStoreSettings() {
|
||||
const url = this.base + "/don_confiao/api/store_settings";
|
||||
return this.getRequest(url);
|
||||
}
|
||||
|
||||
updateStoreSettings(data) {
|
||||
const url = this.base + "/don_confiao/api/store_settings";
|
||||
return http.patch(url, data, {
|
||||
headers: { 'Content-Type': undefined },
|
||||
}).then((r) => r.data);
|
||||
}
|
||||
}
|
||||
|
||||
export default DjangoApi;
|
||||
|
||||
26
src/stores/settings.js
Normal file
26
src/stores/settings.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useSettingsStore = defineStore('settings', {
|
||||
state: () => ({
|
||||
settings: null,
|
||||
loaded: false
|
||||
}),
|
||||
getters: {
|
||||
logo: (state) => state.settings?.logo || null,
|
||||
address: (state) => state.settings?.address || '',
|
||||
latitude: (state) => state.settings?.latitude,
|
||||
longitude: (state) => state.settings?.longitude
|
||||
},
|
||||
actions: {
|
||||
async fetchSettings(api) {
|
||||
if (this.loaded) return this.settings
|
||||
this.settings = await api.getStoreSettings()
|
||||
this.loaded = true
|
||||
return this.settings
|
||||
},
|
||||
setSettings(settings) {
|
||||
this.settings = settings
|
||||
this.loaded = true
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user