Documentation Index
Fetch the complete documentation index at: https://mintlify.com/fredy-rizo/ecommerce-delivery-frontend/llms.txt
Use this file to discover all available pages before exploring further.
Ecommerce Delivery uses a layered approach to state: Vuex 4 is wired up as the global store but carries no application modules out of the box — the scaffold module (module-example) exists only as a structural guide. In practice, most feature state lives either in Vue 3 component setup() functions using ref and reactive, or in localStorage for data that must survive page refreshes (the user session and the shopping cart). Understanding which layer owns which piece of data makes it straightforward to know where to read or write when building new features.
Vuex store
The root store is created in src/store/index.js using Quasar’s store wrapper:
// src/store/index.js
import { store } from "quasar/wrappers";
import { createStore } from "vuex";
export default store(function (/* { ssrContext } */) {
const Store = createStore({
modules: {},
strict: process.env.DEBUGGING,
});
return Store;
});
The modules object is empty — all Vuex modules must be registered here when added. Strict mode is enabled automatically in development via process.env.DEBUGGING.
module-example
src/store/module-example/ is the canonical template for any new Vuex module. Its index.js wires the four Vuex concerns together and enables namespacing:
// src/store/module-example/index.js
import state from './state'
import * as getters from './getters'
import * as mutations from './mutations'
import * as actions from './actions'
export default {
namespaced: true,
getters,
mutations,
actions,
state
}
The state factory returns an empty object that you populate with module-specific data:
// src/store/module-example/state.js
export default function () {
return {
//
}
}
Keep global cross-route state (e.g. notification counts, current user role once loaded) in a Vuex module registered in src/store/index.js. Keep page-scoped state that is not shared across routes in the component’s own setup() with ref() or reactive().
localStorage persistence
Three keys in localStorage serve as the app’s persistence layer across page loads:
| Key | Value | Written by |
|---|
token | Raw JWT string | Login handler on successful authentication |
user | JSON-serialised user object | Login handler on successful authentication |
productCar | JSON-serialised array of cart items | Cart page on every quantity change or item removal |
Writing session data at login
localStorage.setItem('token', response.token)
localStorage.setItem('user', JSON.stringify(response.user))
Writing cart data
localStorage.setItem('productCar', JSON.stringify(products))
Clearing on logout or session expiry
// On explicit logout (NavBar_users.vue)
const closeSession = () => {
localStorage.clear();
dataUser.value = null;
updateLinks();
};
// On 401/403 API response (src/tools/User.js)
const ValidateSession = (res, router) => {
if (res.code == "403" || res.code == 403 || res.code == "401" || res.code == 401) {
router.push({ path: "/login" });
localStorage.clear();
return false;
}
return true;
};
User state helpers
src/tools/User.js exports three functions that are the single authoritative interface to user session state:
getDataUser()
Reads the user object and token from localStorage, writes the user back to ensure the key always exists, and returns a merged object:
const getDataUser = () => {
const dataUser = JSON.parse(
localStorage.getItem("user")
? localStorage.getItem("user")
: '{"id":"1", "name": "Invitado", "address": "Indefinido", "phone_number": "1234567890", "email": "Invitado@email.com", "type":0, "roles":[{"name":"Invitado", "value":"4"}], "membership": {"status": { "code": 3, "status": "Cancelado" }, "expdate": "", "duedate": "", "value": ""}}'
)
const token = localStorage.getItem("token")
localStorage.setItem("user", JSON.stringify(dataUser))
return { ...dataUser, token }
}
If no user is stored, a guest placeholder object is used so that downstream code never needs to handle null directly.
validateUser({ rol })
Called before any protected action to confirm the current user has the required role. Accepts a single role value or an array of role values:
const validateUser = (data) => {
const dataUser = JSON.parse(
localStorage.getItem("user")
? localStorage.getItem("user")
: '{"id":"1","name":"Invitado","roles":[{"name":"usuario","value":"1"}],...}'
);
const token = localStorage.getItem("token");
if (dataUser.roles && dataUser.roles[0].value == 2) {
return { ...dataUser, token };
}
const userRoles = dataUser.roles.map(r => Number(r.value));
const allowedRoles = Array.isArray(data.rol) ? data.rol.map(Number) : [Number(data.rol)];
const hasRole = allowedRoles.some(r => userRoles.includes(r));
return hasRole ? { ...dataUser, token } : false;
};
Returns the merged user+token object when authorised, or false when the user does not have the required role. Components check the return value before making API calls:
const sessionUser = validateUser({ rol: [1, 3] });
if (!sessionUser) return router.push({ path: '/login' });
ValidateSession(res, router)
Called after every API response to reactively invalidate the session on auth errors. See the API Integration page for the full implementation.
The cart is entirely localStorage-based. No Vuex module is involved. The lifecycle is:
Load on mount
Car.vue reads localStorage.getItem('productCar') inside onMounted() and populates a ref([]) called products.
Sync on change
Every quantity increment, decrement, or removal writes the updated array back with localStorage.setItem('productCar', JSON.stringify(products)).
Notify other components
After each write, the cart dispatches a custom DOM event so the navbar badge updates without a page reload:window.dispatchEvent(new Event('cartUpdated'))
Clear after order
On a successful order placement, the cart key is removed:localStorage.removeItem('productCar')
NavBar_users.vue listens for both the native storage event (cross-tab) and the custom cartUpdated event (same tab) to keep the cart badge in sync:
// src/layouts/NavBar_users.vue
const updateCartCount = () => {
const items = JSON.parse(localStorage.getItem("productCar") || "[]");
cartCount.value = items.reduce((sum, item) => sum + (item.cant || 1), 0);
};
window.addEventListener("storage", (e) => {
if (e.key === "productCar") updateCartCount();
});
onMounted(() => {
updateCartCount();
window.addEventListener("cartUpdated", updateCartCount);
});
onBeforeUnmount(() => {
window.removeEventListener("cartUpdated", updateCartCount);
});
src/tools/ValidateForm.js exports validateRequire, a lightweight client-side validator that checks a set of required fields before submitting a form:
// src/tools/ValidateForm.js
const validateRequire = (data, rules) => {
let fails = []
for (let index = 0; index < rules.length; index++) {
const element = rules[index];
if (data[element] == null || data[element] == "") {
fails.push({ message: "Este campo es requerido.", data: element })
}
}
if (fails.length > 0) {
return { status: false, fails }
}
return { status: true, fails }
}
export { validateRequire }
| Parameter | Type | Description |
|---|
data | object | The form data object to validate |
rules | string[] | Array of field names that must be non-null and non-empty |
Returns { status: true, fails: [] } on success, or { status: false, fails: [...] } with a descriptive entry per failing field. A typical call site:
import { validateRequire } from 'src/tools/ValidateForm'
const result = validateRequire(formData, ['name', 'email', 'phone_number'])
if (!result.status) {
// result.fails is an array of { message, data } objects
console.error(result.fails)
return
}
// proceed with API call
Text utility
src/tools/Functions.js exports searchLinksinText, a utility that converts raw URLs embedded in a plain-text string into clickable HTML anchor tags:
// src/tools/Functions.js
const searchLinksinText = (texto) => {
const regexEnlaces = /https?:\/\/[^\s]+/g;
return texto.replace(regexEnlaces, (enlace) => {
return `<a href="${enlace}" target="_blank">${enlace}</a>`;
});
}
export { searchLinksinText }
This is used when rendering user-generated chat or message content (for example in components/Mensajes/ColumnasChats.vue) to make links inside text bodies interactive without requiring a rich-text editor.
Reactive component state
Feature components use the Vue 3 Composition API exclusively. The typical pattern for a list page is:
// Typical setup() pattern in a list/cart component
import { ref, computed, watch, onMounted } from 'vue'
const products = ref([])
const total = ref(0)
// Recompute total whenever products change
watch(products, (newProducts) => {
total.value = newProducts.reduce((sum, p) => sum + p.price * p.cant, 0)
}, { deep: true })
onMounted(() => {
const stored = localStorage.getItem('productCar')
if (stored) products.value = JSON.parse(stored)
})
computed is used for derived values (totals, filtered lists), watch for side-effects triggered by state changes (persisting to localStorage, recalculating derived values), and onMounted for initial data loading.