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+ testQuick 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
- Hook montati: Controlla la console per
[useTopbarCart] Hook montato - Comunicazione iframe: Esegui
window.parent.cart.setCounter(5)dalla console dell'iframe - Multi-tab sync: Apri due tab e modifica il carrello in una
- 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.cartper chiamate dall'iframe - Invoca metodi
common_cartnell'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"
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
Azioni Disponibili
| Azione | Descrizione | Visibilità |
|---|---|---|
| Apri | Apre la vista dettagliata del carrello | Sempre |
| Usa | Avvia il processo di download/uso | Sempre |
| Offline | Copia contenuto offline nella clipboard | Se commoncart_btn_offline = true |
| Fastlabel | Copia fastlabel nella clipboard | Se commoncart_btn_fastlabel = true |
| Svuota | Svuota completamente il carrello | Sempre |
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 automaticamenteFlow 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 counterApertura 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 carrelloTesting
Esegui tutti i test del carrello:
bash
npm test -- cartCoverage
| File | Test |
|---|---|
cart.store.test.ts | 20 test - Store operations |
useCartSyncListener.test.ts | 8 test - Multi-tab sync |
useTopbarCart.test.ts | 12+ test - Iframe communication |
Cart.test.tsx | 15+ 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 clipboardTroubleshooting
| Problema | Causa | Soluzione |
|---|---|---|
| Carrello non appare | <Cart /> non montato | Aggiungi <Cart /> alla topbar |
| Contatore non si aggiorna | Hook non montato | Verifica useTopbarCart() nel layout |
| Sync non funziona | useCartSyncListener mancante | Aggiungi useCartSyncListener() nel layout |
| Azioni non funzionano | common_cart non definito | Implementa window.common_cart nell'iframe |
| Offline/Fastlabel non visibili | Flag mancanti | Imposta 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
| Browser | Versione | Supporto |
|---|---|---|
| Chrome | >= 54 | ✅ |
| Firefox | >= 38 | ✅ |
| Safari | >= 15.4 | ✅ |
| Edge | >= 79 | ✅ |
| IE11 | - | ❌ |
Best Practices
- Montare gli hook una sola volta in
AuthenticatedLayout - Non modificare lo store direttamente - usare sempre i metodi forniti
- Gestire l'assenza dell'iframe - il sistema gestisce gracefully con warning nei log
- Testare con più tab per verificare la sincronizzazione
- 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