@weareconceptstudio/cart
v0.0.12
Published
Concept Studio Cart
Readme
@weareconceptstudio/cart
Simple and clean Zustand-based cart management for single and variant products.
Features
✅ Simple API - Easy-to-use hooks for cart operations
✅ Variant Support - Handle products with variants (color, size, etc.)
✅ Guest & Logged-in Users - Cookie-based cart for guests, server-side for authenticated users
✅ TypeScript - Full type safety
✅ Zustand - Fast and minimal state management
✅ Clean Code - Easy to read and maintain
Installation
npm install @weareconceptstudio/cart zustandQuick Start
1. Initialize the cart store (in your app entry point)
import { initCartStore } from '@weareconceptstudio/cart';
// For simple products (no variants)
initCartStore(
{ hasVariants: false },
isLoggedIn,
selectedLang
);
// For products with variants (color, size, etc.)
initCartStore(
{ hasVariants: true },
isLoggedIn,
selectedLang
);2. Use cart in your components
For Simple Products (No Variants)
import { useSimpleCart } from '@weareconceptstudio/cart';
function ProductCard({ productId }) {
const { addProduct, removeProduct, isInCart, getProductQty } = useSimpleCart();
return (
<div>
{isInCart(productId) ? (
<>
<span>Qty: {getProductQty(productId)}</span>
<button onClick={() => removeProduct(productId)}>Remove</button>
</>
) : (
<button onClick={() => addProduct(productId, 1)}>Add to Cart</button>
)}
</div>
);
}For Products with Variants (Color, Size)
import { useVariantCart } from '@weareconceptstudio/cart';
function ProductWithVariants({ productId, variantId, optionId }) {
const { addVariantProduct, isVariantInCart, getVariantQty } = useVariantCart();
const inCart = isVariantInCart(productId, variantId, optionId);
return (
<div>
{inCart ? (
<span>Qty: {getVariantQty(productId, variantId, optionId)}</span>
) : (
<button onClick={() => addVariantProduct(productId, variantId, optionId, 1)}>
Add to Cart
</button>
)}
</div>
);
}Cart Summary / Mini Cart
import { useCart } from '@weareconceptstudio/cart';
function MiniCart() {
const { items, itemsCount, total, currency, clearCart, loading } = useCart();
if (loading) return <div>Loading...</div>;
return (
<div>
<h3>Cart ({itemsCount} items)</h3>
{items.map((item) => (
<div key={item.product.id}>
<span>{item.product.name}</span>
<span>Qty: {item.qty}</span>
<span>{item.product.price} {currency}</span>
</div>
))}
<div>Total: {total} {currency}</div>
<button onClick={clearCart}>Clear Cart</button>
</div>
);
}Checkout
import { useCheckout } from '@weareconceptstudio/cart';
function Checkout() {
const {
checkout,
setAddress,
setPaymentMethod,
setNote,
isReadyForCheckout,
total,
itemsCount,
} = useCheckout();
const handleCheckout = async () => {
if (!isReadyForCheckout()) {
alert('Cart is not ready for checkout');
return;
}
try {
const result = await checkout();
if (result.redirect_url) {
window.location.href = result.redirect_url;
}
} catch (error) {
console.error('Checkout error:', error);
}
};
return (
<div>
<h2>Checkout</h2>
<p>Items: {itemsCount}</p>
<p>Total: {total}</p>
<button onClick={() => setAddress(123)}>Select Address</button>
<button onClick={() => setPaymentMethod('cash_on_delivery')}>
Cash on Delivery
</button>
<textarea onChange={(e) => setNote(e.target.value)} placeholder="Add note" />
<button onClick={handleCheckout} disabled={!isReadyForCheckout()}>
Complete Order
</button>
</div>
);
}API Reference
Hooks
useCart()
Main hook with full cart state and operations.
Returns:
items- Array of cart items with product detailsitemsCount- Total number of itemssubtotal- Cart subtotaltotal- Cart totalcurrency- Currency codeloading- Loading stateaddItem()- Add item to cartremoveItem()- Remove item from cartupdateItem()- Update item quantityclearCart()- Clear entire cartgetCart()- Refresh cart from server
useSimpleCart()
Simplified hook for products without variants.
Returns:
addProduct(productId, qty)- Add simple productremoveProduct(productId)- Remove simple productupdateProductQty(productId, qty)- Update quantityisInCart(productId)- Check if product is in cartgetProductQty(productId)- Get product quantityitems,itemsCount,loading
useVariantCart()
Hook for products with variants (color, size, etc.).
Returns:
addVariantProduct(productId, variantId, optionId, qty)- Add variant productremoveVariantProduct(productId, variantId, optionId)- Remove variant productupdateVariantProductQty(productId, variantId, optionId, qty)- Update quantityisVariantInCart(productId, variantId, optionId)- Check if variant is in cartgetVariantQty(productId, variantId, optionId)- Get variant quantityitems,itemsCount,loading
useCheckout()
Hook for checkout operations.
Returns:
checkout()- Complete checkoutsetAddress(addressId)- Set delivery addresssetPaymentMethod(paymentType, cardId?)- Set payment methodsetNote(note)- Add order noteapplyPromoCode(code)- Apply promotion codeisReadyForCheckout()- Check if ready to checkoutcheckoutData,total,subtotal,itemsCount,loading
Store
initCartStore(config, isLoggedIn, selectedLang)
Initialize the cart store (call once at app start).
Parameters:
config.hasVariants- Whether products have variantsisLoggedIn- User authentication statusselectedLang- Current language (default: 'en')
useCartStore()
Direct access to Zustand store (advanced usage).
Types
CartItem
// Simple product
interface BaseCartItem {
productId: number;
qty: number;
}
// Product with variants
interface VariantCartItem extends BaseCartItem {
variantId: number;
optionId: number;
itemId?: number;
}CartConfig
interface CartConfig {
hasVariants?: boolean;
supportsGuestCart?: boolean;
}supportsGuestCartdefaults totrue. Set it tofalsewhen your backend does not expose guest cart endpoints and you want to skip all guest/cookie logic while the user is logged out.
Migration from Context API
If you're migrating from the old Context-based cart:
Before (Context API)
const { toggleCartItem, items } = useCart();
toggleCartItem({ productId: 123, qty: 1 });After (Zustand)
import { useSimpleCart } from '@weareconceptstudio/cart';
const { addProduct, items } = useSimpleCart();
addProduct(123, 1);Examples
Counter Component
import { useSimpleCart } from '@weareconceptstudio/cart';
function Counter({ productId }) {
const { getProductQty, updateProductQty } = useSimpleCart();
const qty = getProductQty(productId);
return (
<div>
<button onClick={() => updateProductQty(productId, qty - 1)}>-</button>
<span>{qty}</span>
<button onClick={() => updateProductQty(productId, qty + 1)}>+</button>
</div>
);
}Product with Variants
import { useVariantCart } from '@weareconceptstudio/cart';
function ProductPage({ product }) {
const { addVariantProduct } = useVariantCart();
const [selectedVariant, setSelectedVariant] = useState(product.variants[0]);
const [selectedOption, setSelectedOption] = useState(selectedVariant.options[0]);
const handleAddToCart = () => {
addVariantProduct(
product.id,
selectedVariant.id,
selectedOption.id,
1
);
};
return (
<div>
<h1>{product.name}</h1>
{/* Color selection */}
{product.variants.map(variant => (
<button key={variant.id} onClick={() => setSelectedVariant(variant)}>
{variant.color.name}
</button>
))}
{/* Size selection */}
{selectedVariant.options.map(option => (
<button key={option.id} onClick={() => setSelectedOption(option)}>
{option.size.name}
</button>
))}
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
);
}License
ISC © Concept Studio
