Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ALEJ4NDRO2025/urban-store/llms.txt

Use this file to discover all available pages before exploring further.

Urban Store’s shopping cart is built on a dual-persistence architecture: a Zustand store backed by localStorage provides instant, lag-free UI updates with zero network round-trips, while a MongoDB backend cart document keeps the cart synchronized across devices and browser sessions. Every mutation — adding, updating, or removing items — is committed to localStorage immediately and then flushed asynchronously to the backend via a full-cart sync operation.

Dual Persistence Architecture

┌────────────────────────────────────┐
│            Zustand Store           │
│  (in-memory, re-hydrated on load)  │
└────────────────┬───────────────────┘
                 │ read / write
        ┌────────▼────────┐
        │   localStorage   │  ← instant, survives page refresh
        │ urban_cart_items │
        └────────┬────────┘
                 │ syncWithBackend() after every mutation
        ┌────────▼────────────────┐
        │  MongoDB (Cart document) │  ← cross-device, server-authoritative
        │  POST /api/cart/sync/    │
        └─────────────────────────┘
When a user first loads the page the store is seeded from localStorage. After login, loadCartFromBackend() is called — if the backend cart contains items, those items overwrite the local copy, reconciling any drift from a different device.

Zustand Store API

The cart store is exported as useCartStore from app/lib/cartStore.js. All mutating methods are async and call syncWithBackend() as a fire-and-forget side effect after updating local state.

addItem(newItem)

Adds a product to the cart. If an identical variant (same slug + size + color) already exists, its quantity is incremented. Updates localStorage and triggers a backend sync.

updateItemQuantity(slug, size, color, qty)

Sets the quantity of a specific variant to qty. Updates localStorage and syncs to backend.

removeItem(slug, size, color)

Removes all units of a specific variant. Updates localStorage and syncs to backend.

clearCart()

Empties the cart completely — sets items to [], clears localStorage, and syncs an empty cart to the backend.

loadCartFromBackend()

Fetches the cart document from GET /api/cart/ and replaces local state if the backend cart is non-empty. Called on login.

syncWithBackend(items)

Posts the full current items array to POST /api/cart/sync/, replacing the backend cart atomically. Called internally after every mutation.

createOrder(shippingAddress, notes, items)

Builds an order payload and posts to POST /api/orders/. On success, clears the cart locally and syncs the empty state to the backend.

Adding an Item — Code Example

import { useCartStore } from '@/app/lib/cartStore';

const addToCart = useCartStore((state) => state.addItem);

await addToCart({
  product_slug: 'hoodie-urbano-negro',
  product_name: 'Hoodie Urbano Negro',
  selected_size: 'M',
  selected_color: 'negro',
  price_at_time: 149900,
  image: 'https://res.cloudinary.com/demo/image/upload/v1/urban-store/hoodie-negro.jpg',
  quantity: 1,
});
addItem will throw Error('No autenticado') if no access token is found in localStorage. Always ensure the user is logged in before calling cart mutations.

Cart Item Structure

Each item in the items array conforms to the CartItem embedded document schema defined in cart/models.py:
{
  "product_slug": "hoodie-urbano-negro",
  "product_name": "Hoodie Urbano Negro",
  "quantity": 2,
  "selected_size": "M",
  "selected_color": "negro",
  "price_at_time": 149900,
  "image": "https://res.cloudinary.com/demo/image/upload/v1/urban-store/hoodie-negro.jpg"
}
The price_at_time field captures the product price at the moment the item was added to the cart, so price changes after the item was added do not affect the cart total.

Backend Sync — POST /api/cart/sync/

The sync endpoint replaces the entire cart document for the authenticated user with whatever items are sent in the request body. This is a full replace, not a merge.
POST /api/cart/sync/
Authorization: Bearer <token>
Content-Type: application/json

{
  "items": [
    {
      "product_slug": "hoodie-urbano-negro",
      "product_name": "Hoodie Urbano Negro",
      "quantity": 1,
      "selected_size": "M",
      "selected_color": "negro",
      "price_at_time": 149900,
      "image": "https://res.cloudinary.com/..."
    }
  ]
}
Response:
{
  "status": "ok",
  "total": 149900.0,
  "item_count": 1
}
The backend recalculates total and item_count via cart.calculate_totals() before saving.

Authentication Requirement

All cart endpoints (GET /api/cart/, POST /api/cart/sync/, etc.) require a valid JWT Bearer token. The backend extracts the user identity from the email field in the JWT payload. Requests without a valid Authorization: Bearer <token> header receive a 401 Unauthorized response.
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Cart Total Calculation

The cart total is computed client-side in the Zustand store’s total getter:
get total() {
  return get().items.reduce((sum, item) => {
    const price = parseFloat(item.price_at_time) || 0;
    return sum + price * item.quantity;
  }, 0);
}
This is price_at_time × quantity summed across all items. null or unparseable prices are treated as 0.

Checkout Flow

1

Review Cart

The customer reviews cart contents and the computed total in the cart sidebar or cart page.
2

Fill Shipping Form

The customer enters their shipping address: email, name, phone, street address, city, department, and country.
3

Create Order

createOrder(shippingAddress, notes, items) is called. The cart items are posted to POST /api/orders/ along with the shipping address. The backend decrements stock and returns an order object with status pending.
4

Stripe Payment

The frontend calls POST /api/payments/create-payment-intent/ with the new order_id to receive a client_secret, then uses @stripe/react-stripe-js to render the PaymentElement and collect card details.
5

Confirmation

After Stripe processes the payment the frontend calls POST /api/payments/confirm-payment/ and the order status transitions to paid. The cart is already empty at this point.

Build docs developers (and LLMs) love