Files
automat/src/examples/components/CartManager.jsx
2026-09-19 01:43:24 +02:00

228 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { PureComponent } from 'react';
import { cartAutomat } from '../automats/cartAutomat.js';
/**
* CartManager — left column of the Partial Subscription example.
*
* Full subscriber to cartAutomat.
* Allows adding/removing items (changing list length) AND modifying
* existing item content (quantity, name, coupon) without changing list length.
*/
class CartManager extends PureComponent {
_renderCount = 0;
constructor(props) {
super(props);
this.state = cartAutomat.state;
}
componentDidMount() {
this.unsubscribe = cartAutomat.subscribe(this);
}
componentWillUnmount() {
if (this.unsubscribe) this.unsubscribe();
}
handleAddItem = (name, price) => {
cartAutomat.actions.addItem(name, price);
};
handleRemoveItem = (id) => {
cartAutomat.actions.removeItem(id);
};
handleQuantityChange = (id, delta) => {
cartAutomat.actions.updateQuantity(id, delta);
};
handleCouponChange = (e) => {
cartAutomat.actions.setCouponCode(e.target.value);
};
handleReset = () => {
cartAutomat.actions.resetCart();
};
render() {
this._renderCount++;
const { items, couponCode } = this.state;
return (
<div className="card">
<div className="card-header">
<span className="card-icon">🛒</span>
<h3>Shopping Cart Manager</h3>
<div style={{ display: 'flex', gap: 6 }}>
<span className="badge badge-success" title="State persists across full page reloads">💾 IndexedDB</span>
<span className="badge badge-local">Full Subscriber</span>
</div>
</div>
<div className="card-body">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14 }}>
<span style={{ fontSize: 13, color: 'var(--text-3)' }}>
Component Renders: <strong style={{ color: 'var(--accent)' }}>#{this._renderCount}</strong>
</span>
<div style={{ display: 'flex', gap: 6 }}>
<button
type="button"
className="btn btn-sm btn-ghost"
onClick={this.handleReset}
title="Reset items to initial state"
>
Reset List
</button>
<button
type="button"
className="btn btn-sm btn-ghost"
onClick={async () => {
await cartAutomat.clearPersistence();
this.handleReset();
}}
title="Clear persisted state from IndexedDB"
>
Clear IndexedDB
</button>
</div>
</div>
{/* Quick Add Buttons (Changes length) */}
<div style={{ marginBottom: 14 }}>
<span style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.08em', display: 'block', marginBottom: 6 }}>
Add Item (Changes List Length):
</span>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<button
type="button"
className="btn btn-sm btn-primary"
onClick={() => this.handleAddItem('Keycaps Set', 45)}
>
+ Keycaps ($45)
</button>
<button
type="button"
className="btn btn-sm btn-primary"
onClick={() => this.handleAddItem('Wrist Rest', 25)}
>
+ Wrist Rest ($25)
</button>
<button
type="button"
className="btn btn-sm btn-primary"
onClick={() => this.handleAddItem('Coiled Cable', 32)}
>
+ Cable ($32)
</button>
</div>
</div>
{/* Items List */}
<div style={{ marginBottom: 16 }}>
<span style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.08em', display: 'block', marginBottom: 6 }}>
Cart Items ({items.length}):
</span>
{items.length === 0 ? (
<div style={{ padding: '16px', textAlign: 'center', color: 'var(--text-3)', background: 'var(--surface-1)', borderRadius: 'var(--r-md)' }}>
Cart is empty. Click an add button above.
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{items.map((item) => (
<div
key={item.id}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
background: 'var(--surface-1)',
border: '1px solid var(--border)',
borderRadius: 'var(--r-md)',
padding: '8px 12px',
}}
>
<div style={{ flex: 1, marginRight: 8 }}>
<div style={{ fontWeight: 600, fontSize: 13, color: 'var(--text-1)' }}>
{item.name}
</div>
<div style={{ fontSize: 11, color: 'var(--text-3)' }}>
${item.price} each
</div>
</div>
{/* Quantity controls (Changes content, NOT length!) */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 11, color: 'var(--text-3)', marginRight: 2 }}>Qty:</span>
<button
type="button"
className="btn btn-sm"
style={{ padding: '2px 8px', fontSize: 12, minWidth: 26 }}
onClick={() => this.handleQuantityChange(item.id, -1)}
title="Decrease quantity (content changed, length unchanged)"
>
</button>
<span style={{ fontWeight: 700, minWidth: 20, textAlign: 'center', fontSize: 13 }}>
{item.quantity}
</span>
<button
type="button"
className="btn btn-sm"
style={{ padding: '2px 8px', fontSize: 12, minWidth: 26 }}
onClick={() => this.handleQuantityChange(item.id, 1)}
title="Increase quantity (content changed, length unchanged)"
>
+
</button>
{/* Remove item (Changes length!) */}
<button
type="button"
className="btn btn-sm btn-danger"
style={{ padding: '2px 8px', fontSize: 12, marginLeft: 6 }}
onClick={() => this.handleRemoveItem(item.id)}
title="Remove item (changes length)"
>
</button>
</div>
</div>
))}
</div>
)}
</div>
{/* Unrelated field: Coupon Code */}
<div style={{ background: 'var(--surface-1)', padding: '10px 12px', borderRadius: 'var(--r-md)', border: '1px solid var(--border)' }}>
<label style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.08em', display: 'block', marginBottom: 6 }}>
Unrelated State Field (Coupon Code):
</label>
<input
type="text"
value={couponCode}
onChange={this.handleCouponChange}
placeholder="Type coupon (e.g. GRUVBOX20)..."
style={{
width: '100%',
padding: '6px 10px',
background: 'var(--surface-0)',
border: '1px solid var(--border)',
borderRadius: 'var(--r-sm)',
color: 'var(--text-1)',
fontFamily: 'var(--font-mono)',
fontSize: 12,
}}
/>
<span style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 4, display: 'block' }}>
Typing here calls <code>cartAutomat.setState({`{ couponCode }`})</code>. Notice how the badge on the right ignores this completely!
</span>
</div>
</div>
</div>
);
}
}
export default CartManager;