Skip to content

Cart System

Sistema completo per la gestione del carrello con sincronizzazione multi-tab e comunicazione bidirezionale con iframe.

Panoramica

Il sistema Cart sostituisce il legacy Cart.js con un'implementazione moderna basata su:

  • Zustand Store per stato globale persistente
  • BroadcastChannel API per sincronizzazione multi-tab
  • TypeScript strict mode per type safety completo
  • React hooks moderni (no class components)

Struttura dei File

app/
├── store/cart/
│   └── cart.store.ts               # Store Zustand con stato globale
├── hooks/
│   ├── ui/
│   │   └── useTopbarCart.ts        # Comunicazione con iframe
│   └── sync/
│       └── useCartSyncListener.ts  # Sincronizzazione multi-tab
└── components/layout/topbar/
    └── cart.tsx                    # Componente UI React

tests/
├── cart.store.test.ts              # 20 test
├── useCartSyncListener.test.ts     # 8 test
├── useTopbarCart.test.ts           # 12+ test
└── Cart.test.tsx                   # 15+ test

Quick Start

Passo 1: Montare gli Hooks

tsx
// app/components/layout/authenticated.tsx
import { useTopbarCart } from "@/hooks/ui/useTopbarCart";
import { useCartSyncListener } from "@/hooks/sync/useCartSyncListener";

export function AuthenticatedLayout({ children }) {
    useTopbarCart();           // Gestisce comunicazione con iframe
    useCartSyncListener();     // Sincronizza tra tab

    return (
        <div>
            <Topbar />
            <main>{children}</main>
        </div>
    );
}

Passo 2: Aggiungere il Componente

tsx
// app/components/layout/topbar/nav-actions.tsx
import { Cart } from "@/components/layout/topbar/cart";

export function NavActions() {
    return (
        <nav className="navbar">
            <ul className="nav-list">
                <Cart />
            </ul>
        </nav>
    );
}

Passo 3: Configurare l'Iframe

javascript
// Nell'iframe (legacy PHP/JavaScript)
function updateCartCounter(count) {
    if (window.parent && window.parent.cart) {
        window.parent.cart.setCounter(count);
    }
}

// Dopo un'azione sul carrello
updateCartCounter(5);

Verifiche Post-Integrazione

  1. Hook montati: Controlla la console per [useTopbarCart] Hook montato
  2. Comunicazione iframe: Esegui window.parent.cart.setCounter(5) dalla console dell'iframe
  3. Multi-tab sync: Apri due tab e modifica il carrello in una
  4. Dropdown: Clicca sul badge e verifica le azioni (Apri, Usa, Offline, Fastlabel, Svuota)

Architettura

Store Zustand (cart.store.ts)

typescript
interface CartStore {
    counter: number;              // Contatore articoli
    hasItems: boolean;            // Flag presenza articoli
    isDropdownOpen: boolean;      // Stato dropdown
    show: boolean;                // Visibilità carrello
    setCounter: (n: number) => void;
    incrementCounter: () => void;
    decrementCounter: () => void;
    setDropdownOpen: (open: boolean) => void;
    toggleDropdown: () => void;
    reset: () => void;
}

Hook di Comunicazione (useTopbarCart.ts)

Gestisce la comunicazione bidirezionale con l'iframe:

  • Registra window.cart per chiamate dall'iframe
  • Invoca metodi common_cart nell'iframe
  • Sincronizza lo stato tra SPA e iframe

Hook di Sincronizzazione (useCartSyncListener.ts)

Sincronizza il carrello tra tutte le tab del browser usando BroadcastChannel API.


Utilizzo

Dall'Iframe (legacy code)

javascript
// Imposta il contatore
window.parent.cart.setCounter(5);

// Richiede il contenuto del carrello
window.parent.cart.read();

// Apre il carrello
window.parent.cart.open();

// Scarica/usa il carrello
window.parent.cart.download();

// Svuota il carrello
window.parent.cart.empty();

// Callback per clipboard
window.parent.cart.commoncart_clipboard_success();
window.parent.cart.commoncart_offline_clipboard_success();

Dalla SPA React

typescript
import { useCartStore } from "@/store/cart/cart.store";

function MyComponent() {
    const counter = useCartStore((state) => state.counter);
    const hasItems = useCartStore((state) => state.hasItems);
    const setCounter = useCartStore((state) => state.setCounter);

    return (
        <div>
            <p>Articoli nel carrello: {counter}</p>
            {hasItems && <p>Hai articoli nel carrello!</p>}
        </div>
    );
}

Variabili Globali

typescript
// Window (SPA)
interface Window {
    base_url?: string;
    commoncart_btn_offline?: boolean;   // Mostra bottone Offline
    commoncart_btn_fastlabel?: boolean; // Mostra bottone Fastlabel
}

// Iframe
interface IframeWindow {
    common_cart?: {
        read: () => void;
        open: () => void;
        download: () => void;
        empty: (param: string) => void;
        commoncart_clipboard_success: () => void;
        commoncart_offline_clipboard_success: () => void;
    };
    fastlabel_response?: string;
    offline_response?: string;
}

Funzionalità

Contatore Dinamico

  • Badge con numero articoli
  • Evidenziazione visiva quando hasItems = true
  • Titolo dinamico: "Carrello", "1 articolo", "N articoli"
  • Si apre al click sul badge
  • Richiede automaticamente il contenuto dall'iframe
  • Si chiude al click esterno o su azione
  • Mostra "Il carrello è vuoto" quando counter = 0

Azioni Disponibili

AzioneDescrizioneVisibilità
ApriApre la vista dettagliata del carrelloSempre
UsaAvvia il processo di download/usoSempre
OfflineCopia contenuto offline nella clipboardSe commoncart_btn_offline = true
FastlabelCopia fastlabel nella clipboardSe commoncart_btn_fastlabel = true
SvuotaSvuota completamente il carrelloSempre

Sincronizzazione Multi-Tab

1. Utente modifica carrello in Tab A
2. Store Zustand viene aggiornato
3. useCartSyncListener invia messaggio via BroadcastChannel
4. Tab B riceve il messaggio
5. Store in Tab B si sincronizza automaticamente

Flow Tipici

Aggiunta Articolo

1. Utente aggiunge articolo nell'iframe
2. Iframe chiama window.parent.cart.setCounter(newCount)
3. useTopbarCart aggiorna lo store Zustand
4. useCartSyncListener propaga l'update alle altre tab
5. Componente Cart si ri-renderizza con il nuovo counter

Apertura Carrello

1. Utente clicca sul badge del carrello
2. Cart chiama iframe.contentWindow.common_cart.read()
3. Cart apre il dropdown
4. Utente clicca "Apri"
5. Cart chiama iframe.contentWindow.common_cart.open()
6. Iframe naviga alla pagina del carrello

Testing

Esegui tutti i test del carrello:

bash
npm test -- cart

Coverage

FileTest
cart.store.test.ts20 test - Store operations
useCartSyncListener.test.ts8 test - Multi-tab sync
useTopbarCart.test.ts12+ test - Iframe communication
Cart.test.tsx15+ test - UI component

Debugging

Il sistema include logging dettagliato:

[CartSyncListener] 📤 Invio aggiornamento carrello ad altre tab
[CartSyncListener] 🛒 Ricevuto aggiornamento da altra tab
[useTopbarCart] 🔢 setCounter chiamata dall'iframe: 5
[useTopbarCart] 📖 read chiamata
[Cart] ✅ Fastlabel copiato nella clipboard

Troubleshooting

ProblemaCausaSoluzione
Carrello non appare<Cart /> non montatoAggiungi <Cart /> alla topbar
Contatore non si aggiornaHook non montatoVerifica useTopbarCart() nel layout
Sync non funzionauseCartSyncListener mancanteAggiungi useCartSyncListener() nel layout
Azioni non funzionanocommon_cart non definitoImplementa window.common_cart nell'iframe
Offline/Fastlabel non visibiliFlag mancantiImposta commoncart_btn_offline e commoncart_btn_fastlabel

Performance e Compatibilità

Performance

  • Bundle size: ~3KB
  • Re-renders: Ottimizzati con Zustand selectors
  • Memory: Zero memory leaks (cleanup automatico)
  • Sync: Instant via BroadcastChannel

Browser Supportati

BrowserVersioneSupporto
Chrome>= 54
Firefox>= 38
Safari>= 15.4
Edge>= 79
IE11-

Best Practices

  1. Montare gli hook una sola volta in AuthenticatedLayout
  2. Non modificare lo store direttamente - usare sempre i metodi forniti
  3. Gestire l'assenza dell'iframe - il sistema gestisce gracefully con warning nei log
  4. Testare con più tab per verificare la sincronizzazione
  5. Verificare le variabili globali prima del deploy

Riferimenti

  • Codice: app/store/cart/, app/hooks/ui/useTopbarCart.ts, app/components/layout/topbar/cart.tsx
  • Test: tests/cart*.ts, tests/Cart.test.tsx

Documentazione Elerama Frontend