Shopping Cart Examples
Examples of implementing shopping cart functionality with Strata Storage.
Basic Shopping Cart
import { Strata } from 'strata-storage';
class ShoppingCart {
private storage: Strata;
private cartKey = 'shopping-cart';
constructor() {
this.storage = new Strata({
sync: { enabled: true } // Sync across tabs
});
}
async addItem(product: Product, quantity = 1) {
const cart = await this.getCart();
const existingItem = cart.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
cart.items.push({
id: product.id,
name: product.name,
price: product.price,
quantity,
image: product.image
});
}
cart.updatedAt = Date.now();
await this.saveCart(cart);
}
async removeItem(productId: string) {
const cart = await this.getCart();
cart.items = cart.items.filter(item => item.id !== productId);
cart.updatedAt = Date.now();
await this.saveCart(cart);
}
async updateQuantity(productId: string, quantity: number) {
const cart = await this.getCart();
const item = cart.items.find(i => i.id === productId);
if (item) {
if (quantity <= 0) {
await this.removeItem(productId);
} else {
item.quantity = quantity;
cart.updatedAt = Date.now();
await this.saveCart(cart);
}
}
}
async getCart(): Promise<Cart> {
const cart = await this.storage.get(this.cartKey);
return cart || { items: [], updatedAt: Date.now() };
}
private async saveCart(cart: Cart) {
await this.storage.set(this.cartKey, cart);
}
}
Cart with Persistence
class PersistentCart {
private storage: Strata;
private tempCartKey = 'temp-cart';
private savedCartKey = 'saved-cart';
async saveForLater(productId: string) {
const tempCart = await this.getCart(this.tempCartKey);
const savedCart = await this.getCart(this.savedCartKey);
const item = tempCart.items.find(i => i.id === productId);
if (item) {
// Move from temp to saved
tempCart.items = tempCart.items.filter(i => i.id !== productId);
savedCart.items.push(item);
await Promise.all([
this.storage.set(this.tempCartKey, tempCart),
this.storage.set(this.savedCartKey, savedCart)
]);
}
}
async moveToCart(productId: string) {
const tempCart = await this.getCart(this.tempCartKey);
const savedCart = await this.getCart(this.savedCartKey);
const item = savedCart.items.find(i => i.id === productId);
if (item) {
savedCart.items = savedCart.items.filter(i => i.id !== productId);
tempCart.items.push(item);
await Promise.all([
this.storage.set(this.tempCartKey, tempCart),
this.storage.set(this.savedCartKey, savedCart)
]);
}
}
}
Cart Analytics
class CartAnalytics {
private storage: Strata;
async trackAddToCart(product: Product, quantity: number) {
await this.storage.set(`analytics:cart:${Date.now()}`, {
event: 'add_to_cart',
product: {
id: product.id,
name: product.name,
category: product.category,
price: product.price
},
quantity,
timestamp: Date.now()
}, {
ttl: 30 * 24 * 60 * 60 * 1000 // 30 days
});
}
async getCartStats() {
const events = await this.storage.query({
key: { $regex: '^analytics:cart:' },
'value.timestamp': { $gte: Date.now() - 7 * 24 * 60 * 60 * 1000 }
});
const stats = {
totalAdds: 0,
uniqueProducts: new Set(),
totalValue: 0,
averageQuantity: 0
};
for (const event of events) {
if (event.value.event === 'add_to_cart') {
stats.totalAdds++;
stats.uniqueProducts.add(event.value.product.id);
stats.totalValue += event.value.product.price * event.value.quantity;
stats.averageQuantity += event.value.quantity;
}
}
stats.averageQuantity /= stats.totalAdds || 1;
return {
...stats,
uniqueProducts: stats.uniqueProducts.size
};
}
}
Cart Recovery
class CartRecovery {
private storage: Strata;
async saveAbandonedCart(email: string) {
const cart = await this.storage.get('shopping-cart');
if (!cart || cart.items.length === 0) return;
await this.storage.set(`abandoned:${email}`, {
cart,
email,
abandonedAt: Date.now(),
recovered: false
}, {
ttl: 30 * 24 * 60 * 60 * 1000 // 30 days
});
}
async recoverCart(recoveryToken: string) {
const abandoned = await this.storage.get(`abandoned:${recoveryToken}`);
if (abandoned && !abandoned.recovered) {
// Restore cart
await this.storage.set('shopping-cart', abandoned.cart);
// Mark as recovered
abandoned.recovered = true;
abandoned.recoveredAt = Date.now();
await this.storage.set(`abandoned:${recoveryToken}`, abandoned);
return abandoned.cart;
}
return null;
}
}
Cart Calculations
class CartCalculator {
private storage: Strata;
async calculateTotals() {
const cart = await this.storage.get('shopping-cart') || { items: [] };
const discounts = await this.getActiveDiscounts();
let subtotal = 0;
let discount = 0;
let tax = 0;
// Calculate subtotal
for (const item of cart.items) {
subtotal += item.price * item.quantity;
}
// Apply discounts
for (const disc of discounts) {
if (disc.type === 'percentage') {
discount += subtotal * (disc.value / 100);
} else if (disc.type === 'fixed') {
discount += disc.value;
}
}
// Calculate tax (example: 8%)
const taxRate = 0.08;
tax = (subtotal - discount) * taxRate;
const total = subtotal - discount + tax;
// Cache the calculation
await this.storage.set('cart-totals', {
subtotal,
discount,
tax,
total,
calculatedAt: Date.now()
}, {
ttl: 300000 // 5 minutes
});
return { subtotal, discount, tax, total };
}
}
Multi-Currency Cart
class MultiCurrencyCart {
private storage: Strata;
private exchangeRates = new Map<string, number>();
async setItemCurrency(productId: string, currency: string) {
const cart = await this.getCart();
const item = cart.items.find(i => i.id === productId);
if (item) {
const rate = await this.getExchangeRate(item.currency, currency);
item.originalPrice = item.price;
item.originalCurrency = item.currency;
item.price = item.price * rate;
item.currency = currency;
await this.saveCart(cart);
}
}
async getExchangeRate(from: string, to: string): Promise<number> {
const key = `${from}-${to}`;
// Check cache
const cached = await this.storage.get(`exchange:${key}`);
if (cached) return cached.rate;
// Fetch fresh rate
const response = await fetch(`/api/exchange/${from}/${to}`);
const data = await response.json();
// Cache for 1 hour
await this.storage.set(`exchange:${key}`, {
rate: data.rate,
timestamp: Date.now()
}, {
ttl: 3600000
});
return data.rate;
}
}
Cart Validation
class CartValidator {
private storage: Strata;
async validateCart(): Promise<ValidationResult> {
const cart = await this.storage.get('shopping-cart');
const errors: string[] = [];
const warnings: string[] = [];
if (!cart || cart.items.length === 0) {
errors.push('Cart is empty');
return { valid: false, errors, warnings };
}
// Check stock availability
for (const item of cart.items) {
const stock = await this.checkStock(item.id);
if (stock === 0) {
errors.push(`${item.name} is out of stock`);
} else if (stock < item.quantity) {
warnings.push(`Only ${stock} ${item.name} available`);
item.quantity = stock; // Adjust quantity
}
}
// Check price changes
for (const item of cart.items) {
const currentPrice = await this.getCurrentPrice(item.id);
if (currentPrice !== item.price) {
warnings.push(`Price changed for ${item.name}`);
item.price = currentPrice;
}
}
// Save updated cart
if (warnings.length > 0) {
await this.storage.set('shopping-cart', cart);
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
}
Guest Cart Merge
class CartMerger {
private storage: Strata;
async mergeGuestCart(userId: string) {
const guestCart = await this.storage.get('guest-cart');
const userCart = await this.storage.get(`user-cart:${userId}`);
if (!guestCart || guestCart.items.length === 0) {
return userCart;
}
if (!userCart || userCart.items.length === 0) {
// Just use guest cart
await this.storage.set(`user-cart:${userId}`, guestCart);
await this.storage.remove('guest-cart');
return guestCart;
}
// Merge carts
const merged = { ...userCart };
for (const guestItem of guestCart.items) {
const existingItem = merged.items.find(i => i.id === guestItem.id);
if (existingItem) {
existingItem.quantity += guestItem.quantity;
} else {
merged.items.push(guestItem);
}
}
merged.updatedAt = Date.now();
// Save merged cart
await this.storage.set(`user-cart:${userId}`, merged);
await this.storage.remove('guest-cart');
return merged;
}
}