Skip to content

Cart - Gestione del Carrello ​

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

Architettura ​

Il sistema Γ¨ composto da quattro componenti principali:

1. Store Zustand (app/store/cart/cart.store.ts) ​

Store globale che mantiene lo stato del carrello:

  • counter: numero di articoli nel carrello
  • hasItems: boolean che indica se ci sono articoli
  • isDropdownOpen: stato del dropdown
  • show: boolean per mostrare/nascondere il carrello

2. Componente React (app/components/layout/topbar/cart.tsx) ​

Componente UI per la topbar con:

  • Badge del contatore articoli
  • Dropdown con azioni: Apri, Usa, Offline, Fastlabel, Svuota
  • Gestione click esterni e chiusura automatica

3. Hook di Comunicazione (app/hooks/ui/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

4. Hook di Sincronizzazione (app/hooks/sync/useCartSyncListener.ts) ​

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

Setup ​

1. Importare gli Hook nell'AuthenticatedLayout ​

tsx
import { useTopbarCart } from "@/hooks/ui/useTopbarCart";
import { useCartSyncListener } from "@/hooks/sync/useCartSyncListener";

export function AuthenticatedLayout() {
    useTopbarCart();           // Comunicazione con iframe
    useCartSyncListener();     // Sincronizzazione tra tab

    return (
        <div>
            {/* Layout content */}
        </div>
    );
}

2. Aggiungere il Componente alla Topbar ​

tsx
import { Cart } from "@/components/layout/topbar/cart";

export function Topbar() {
    return (
        <nav>
            <ul>
                <Cart />
                {/* Altri elementi della topbar */}
            </ul>
        </nav>
    );
}

Utilizzo ​

Dall'iframe (legacy code) ​

L'iframe puΓ² comunicare con il carrello attraverso window.parent.cart:

Impostare il contatore ​

javascript
// Imposta il numero di articoli nel carrello
window.parent.cart.setCounter(5);

// Azzera il carrello
window.parent.cart.setCounter(0);

Richiedere il contenuto del carrello ​

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

Aprire il carrello ​

javascript
// Apre la vista del carrello nell'iframe
window.parent.cart.open();

Scaricare/Usare il carrello ​

javascript
// Avvia il download/uso del carrello
window.parent.cart.download();

Svuotare il carrello ​

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

Callback per clipboard ​

javascript
// Chiamato dopo successo copia Fastlabel
window.parent.cart.commoncart_clipboard_success();

// Chiamato dopo successo copia Offline
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>}
            <button onClick={() => setCounter(counter + 1)}>
                Aggiungi articolo
            </button>
        </div>
    );
}

FunzionalitΓ  ​

1. Contatore Dinamico ​

  • Badge con numero articoli
  • Evidenziazione visiva quando hasItems = true
  • Titolo dinamico: "Carrello", "1 articolo", "N articoli"

2. Dropdown Interattivo ​

  • 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

3. Azioni Disponibili ​

Apri ​

Apre la vista dettagliata del carrello nell'iframe.

Usa ​

Avvia il processo di utilizzo/download del carrello.

Offline (opzionale) ​

Copia il contenuto offline nella clipboard. Visibile solo se window.commoncart_btn_offline = true.

Fastlabel (opzionale) ​

Copia il contenuto fastlabel nella clipboard. Visibile solo se window.commoncart_btn_fastlabel = true.

Svuota ​

Svuota completamente il carrello dopo conferma.

4. Sincronizzazione Multi-Tab ​

Quando un utente modifica il carrello in una tab:

  1. Lo store Zustand viene aggiornato
  2. useCartSyncListener invia un messaggio via BroadcastChannel
  3. Tutte le altre tab ricevono il messaggio
  4. Gli store nelle altre tab si sincronizzano automaticamente

5. Comunicazione Bidirezionale ​

  • Iframe β†’ SPA: Chiama window.parent.cart.setCounter(n) per aggiornare il badge
  • SPA β†’ Iframe: Chiama iframe.contentWindow.common_cart.open() per aprire il carrello

Variabili Globali ​

Il componente si aspetta queste variabili su window:

typescript
interface Window {
    base_url?: string;                      // Base URL per i link
    commoncart_btn_offline?: boolean;       // Mostra bottone Offline
    commoncart_btn_fastlabel?: boolean;     // Mostra bottone Fastlabel
}

L'iframe deve esporre:

typescript
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;            // Testo per clipboard Fastlabel
    offline_response?: string;              // Testo per clipboard Offline
}

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

Svuotamento Carrello ​

1. Utente clicca "Svuota" nel dropdown
2. Cart chiama iframe.contentWindow.common_cart.empty("")
3. Iframe svuota il carrello e chiama window.parent.cart.setCounter(0)
4. useTopbarCart resetta lo store
5. useCartSyncListener sincronizza le altre tab

Testing ​

Il sistema include test completi per:

Store (tests/cart.store.test.ts) ​

  • Stato iniziale
  • setCounter, incrementCounter, decrementCounter
  • Gestione dropdown
  • Reset
  • Scenari realistici

Sincronizzazione (tests/useCartSyncListener.test.ts) ​

  • Sincronizzazione contatore
  • Sincronizzazione hasItems
  • Gestione cambiamenti rapidi
  • Logging

Comunicazione (tests/useTopbarCart.test.ts) ​

  • Registrazione window.cart
  • Chiamate verso iframe
  • Gestione errori
  • Cleanup

Componente (tests/Cart.test.tsx) ​

  • Rendering condizionale
  • Interazioni utente
  • Dropdown
  • Azioni
  • Click esterni
  • Clipboard API

Debugging ​

Il sistema include logging dettagliato:

typescript
// Store updates
[CartSyncListener] πŸ“€ Invio aggiornamento carrello ad altre tab: {...}
[CartSyncListener] πŸ›’ Ricevuto aggiornamento carrello da altra tab: {...}

// Hook communication
[useTopbarCart] πŸ”’ setCounter chiamata dall'iframe: 5
[useTopbarCart] πŸ“– read chiamata: richiesta contenuto carrello
[useTopbarCart] πŸ”“ open chiamata: apertura carrello
[useTopbarCart] ⬇️ download chiamata: download carrello
[useTopbarCart] πŸ—‘οΈ empty chiamata: svuotamento carrello

// Component actions
[Cart] βœ… Fastlabel text copiato nella clipboard
[Cart] βœ… Offline text copiato nella clipboard
[Cart] ❌ Errore nella copia: ...

Migrazione dal Legacy ​

Prima (legacy Cart.js) ​

jsx
// Class component con stato locale
// Nessuna sincronizzazione tra tab
// Uso di react-clipboard.js
// Dipendenza da jQuery/DOM diretto

Dopo (nuovo sistema) ​

typescript
// Zustand store globale
// Sincronizzazione multi-tab automatica
// Clipboard API nativa
// React hooks moderni
// TypeScript con type safety
// Test coverage completo

Best Practices ​

  1. Montare gli hook una sola volta: useTopbarCart e useCartSyncListener devono essere montati in un componente parent comune (es. AuthenticatedLayout)

  2. Non modificare lo store direttamente: Usare sempre i metodi forniti (setCounter, incrementCounter, etc.)

  3. Gestire l'assenza dell'iframe: Il sistema gestisce gracefully l'assenza dell'iframe con warning nei log

  4. Testare con piΓΉ tab: Aprire piΓΉ tab per verificare la sincronizzazione

  5. Verificare le variabili globali: Assicurarsi che window.commoncart_btn_offline e window.commoncart_btn_fastlabel siano impostate correttamente

Troubleshooting ​

Il contatore non si aggiorna ​

  • Verificare che useTopbarCart sia montato
  • Controllare la console per errori
  • Verificare che l'iframe chiami window.parent.cart.setCounter()

Il dropdown non si apre ​

  • Verificare che il componente <Cart /> sia renderizzato
  • Controllare la console del browser per errori JavaScript

La sincronizzazione tra tab non funziona ​

  • Verificare che useCartSyncListener sia montato
  • Controllare che BroadcastChannel API sia supportata
  • Verificare che entrambe le tab siano sullo stesso dominio

Le azioni non funzionano ​

  • Verificare che l'iframe sia caricato (document.getElementById('main-iframe'))
  • Controllare che common_cart sia definito nell'iframe
  • Verificare i log della console per messaggi di errore

Performance ​

  • Bundle size: ~3KB (store + hooks + componente)
  • Re-renders: Ottimizzati con selettori Zustand specifici
  • Memory: Nessun memory leak grazie a cleanup nei useEffect
  • Sincronizzazione: Debounced automaticamente da BroadcastChannel

CompatibilitΓ  ​

  • React: 19+
  • TypeScript: 5+
  • Browser: Moderni con BroadcastChannel API
    • Chrome/Edge: βœ…
    • Firefox: βœ…
    • Safari: βœ… (15.4+)
    • IE11: ❌ (non supportato)

Documentazione Elerama Frontend