Skip to main content

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.

The shopping cart is where customers review selected items, adjust quantities, enter delivery details, and place their order. The cart state is persisted entirely in localStorage — no server session is required to add items. When the user is ready to buy, all cart items and client details are submitted to the REST API in a single request, and a payment dialog guides the user through uploading a bank-transfer proof.

Route

/shoppingcar renders pages/shoppingCar/Car.vue, which imports DialogPaymentProcess for the post-order payment flow.

Cart storage

Products are saved to localStorage under the key productCar as a JSON array. Each item has the following shape:
{
  "_id": "64a1f3b2c9e77b001e3d8f12",
  "title": "Camiseta Oversize Premium",
  "colorsSize": { "label": "Azul marino", "value": "6" },
  "sizes": { "label": "L", "value": "4" },
  "typeShirt": "Overside",
  "cant": 0,
  "price": 50000,
  "subTotal": 50000
}
When a product is added from the store via ProductCard, the addToCart function either pushes a new entry or increments the cant field of an existing matching entry (same _id, same color, same size):
const idx = stored.findIndex(
  (p) =>
    p._id === newProduct._id &&
    p.colorsSize?.value === newProduct.colorsSize.value &&
    p.sizes?.value === newProduct.sizes.value
);

if (idx !== -1) {
  stored[idx].cant = (stored[idx].cant || 0) + 1;
} else {
  stored.push(newProduct);
}

localStorage.setItem("productCar", JSON.stringify(stored));
window.dispatchEvent(new Event("cartUpdated"));

Managing quantities

Increment / decrement buttons

Each cart row has + and buttons that call increaseCant(index) and decreaseCant(index). Both functions update the reactive products array and sync back to localStorage:
const increaseCant = (index) => {
  const product = products.value[index];
  product.cant += 1;
  product.subTotal = product.cant * product.pric;

  const productstemp = JSON.parse(localStorage.getItem("productCar")) || [];
  productstemp[index].cant = product.cant;
  localStorage.setItem("productCar", JSON.stringify(productstemp));
  recalcTotal();
};

const decreaseCant = (index) => {
  const product = products.value[index];
  if (product.cant > 0) {
    product.cant -= 1;
    product.subTotal = product.cant * product.pric;

    const productstemp = JSON.parse(localStorage.getItem("productCar")) || [];
    productstemp[index].cant = product.cant;
    localStorage.setItem("productCar", JSON.stringify(productstemp));
    recalcTotal();
  } else {
    $q.notify({ color: "warning", message: "Cantidad minima es 1" });
  }
};

Manual quantity input

Each row also contains a number input bound to props.row.cant. Changes fire updateManualCant(index), which prevents negative values and recalculates the running total:
const updateManualCant = (index) => {
  const product = products.value[index];
  if (product.cant < 0) product.cant = 0;
  product.subTotal = product.pric * (product.cant ?? 0);

  const productstemp = JSON.parse(localStorage.getItem("productCar")) || [];
  if (productstemp[index]) {
    productstemp[index].cant = product.cant;
  }
  localStorage.setItem("productCar", JSON.stringify(productstemp));
  recalcTotal();
};

Removing items

removeProduct(rowIndex) splices the item from both the reactive array and localStorage, then fires the cartUpdated DOM event so other components (e.g. the cart badge in the header) can refresh their count:
const removeProduct = (rowIndex) => {
  const productstemp = JSON.parse(localStorage.getItem("productCar") || "[]");
  productstemp.splice(rowIndex, 1);
  localStorage.setItem("productCar", JSON.stringify(productstemp));

  loadProducts();
  window.dispatchEvent(new Event("cartUpdated"));
};

Order total

The total is computed reactively by watching the products array:
watch(products, () => {
  total.value = products.value.reduce(
    (sum, el) => sum + el.pric * el.cant,
    0
  );
});
It is displayed using Intl.NumberFormat for locale-aware formatting:
<span class="text-primary">
  {{ new Intl.NumberFormat().format(total) }}
</span>

Checkout form fields

All fields are required and validated before the order is submitted. When a user is logged in, fields are pre-populated from getDataUser():
const client = ref({
  userId:             dataUser.id             || "",
  name_client:        dataUser.name           || "",
  lastName_client:    dataUser.name           || "",
  typeIdentification: dataUser.typeIdentification || "",
  identification:     dataUser.identification || "",
  phone_number:       dataUser.phone_number   || "",
  address:            dataUser.address        || "",
  email:              dataUser.email          || "",
  zipCode:            dataUser.zipCode        || "",
  city:               dataUser.city           || "",
});
The typeIdentification field is a select with four options:
const type_dentifications = ref([
  { name: "Nit" },
  { name: "CC" },
  { name: "Pasaporte" },
  { name: "CC extranjera" },
]);

Placing an order

1

Validate session

validateUser({ rol: 1 }) checks for a valid session token. If the user is not authenticated, the cart redirects to /login.
2

Validate form fields

validateRequire(client, [...fields]) ensures every required field is filled. A Quasar notification is shown if any field is missing.
3

Validate minimum quantity

The cart checks that every product has cant >= 1. Orders with zero-quantity items are blocked.
4

Submit order

A POST /api/sale/pay/:productId request is sent with the payload below and an Authorization: Bearer <token> header.
5

Handle response

On success, idsale is set from res.saving._id, confirmPay is set to true (opening DialogPaymentProcess), and productCar is removed from localStorage.

Request payload

{
  "cantBuy": 2,
  "colorsSize": { "label": "Azul marino", "value": "6" },
  "sizes": { "label": "L", "value": "4" },
  "typeShirt": "Overside",
  "client": {
    "userId": "64a1f3b2c9e77b001e3d8f01",
    "name_client": "Juan",
    "lastName_client": "Pérez",
    "typeIdentification": "CC",
    "identification": "1234567890",
    "phone_number": "3001234567",
    "address": "Calle 45 #12-34, Barrio Centro",
    "email": "juan@example.com",
    "zipCode": "050001",
    "city": "Medellín"
  }
}
let res = await fetch(
  process.env.API_SERVER + `/api/sale/pay/${product._id}`,
  {
    method: "POST",
    mode: "cors",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${sessionUser.token}`,
    },
    body: JSON.stringify(payload),
  }
);

Payment dialog

After a successful order, DialogPaymentProcess opens. It displays Bancolombia bank-transfer instructions (account holder, account number, account type) and embeds the ConfimPayment component, which lets the user upload their payment proof image:
// ConfimPayment.vue — sendProod()
const formData = new FormData();
formData.append("imgPay", dataimg.value);

const res = await fetch(
  `${process.env.API_SERVER}/api/sale/laod-proof/${props.idsale}`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${sessionUser.token}`,
    },
    body: formData,
  }
);
On a successful upload the user is redirected to /shoppingList to monitor their order status.
The cart is ephemeral. Cart contents live only in localStorage. Clearing browser storage or opening the app in a different browser or device will result in an empty cart. Complete your order before closing the tab to avoid losing your selection.

Build docs developers (and LLMs) love