Navigation & layout - Replace per-page hardcoded nav/footer with shared nav.js (client-side injection) - Add nginx reverse proxy back to docker-compose for clean localhost routing - Rename /color-picker/ to /color/ across nav, directory, and references eStore admin - Add variation hiding controls (mirrors existing modifier hiding) - Add delivery rate editor (base fee + per-mile per tier, persisted to data/) - Fix all missing BASE prefix on fetch calls (admin PATCH/DELETE, availability, slots, colors) - Mount estore/data/ as a Docker volume so admin config survives rebuilds Booking & calendar - Set pickup calendar events to TRANSPARENT (free) so they don't block delivery slots - Skip CANCELLED events in busy-time calculation - Re-check slot availability at checkout before charging (409 on conflict) Phone & email validation - Auto-format phone as (XXX) XXX-XXXX as user types - Require exactly 10 digits; tighten email regex Confirmation emails (store alert + customer) - Full item detail per line: name, price, add-ons, colors, note - Charges breakdown: subtotal, delivery fee, tax, total - Delivery window: simplified M/D/YY h:mm – h:mm AM/PM format - .ics calendar attachment on customer confirmation Delivery rates - Extract configurable rates to delivery-rates.ts (server-only, no fs in client bundle) - calcDelivery() accepts optional rates param; delivery-quote route passes configured rates Content - Change all "40+ latex colors" references to "70+" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
36 lines
1.1 KiB
TypeScript
36 lines
1.1 KiB
TypeScript
/**
|
|
* Server-only: read/write delivery rate overrides from data/delivery-rates.json.
|
|
* Do NOT import this file from client components.
|
|
*/
|
|
import { readFileSync, existsSync } from 'fs'
|
|
import path from 'path'
|
|
import { atomicWriteJSON } from './file-utils'
|
|
import { RATES, type DeliveryRatesConfig, type DeliveryTier } from './delivery'
|
|
|
|
const RATES_PATH = path.join(process.cwd(), 'data', 'delivery-rates.json')
|
|
|
|
const TIERS: DeliveryTier[] = ['dropoff', 'classic', 'organic']
|
|
|
|
export function readDeliveryRates(): DeliveryRatesConfig {
|
|
const defaults: DeliveryRatesConfig = {
|
|
dropoff: { ...RATES.dropoff },
|
|
classic: { ...RATES.classic },
|
|
organic: { ...RATES.organic },
|
|
}
|
|
if (!existsSync(RATES_PATH)) return defaults
|
|
try {
|
|
const stored = JSON.parse(readFileSync(RATES_PATH, 'utf-8')) as Partial<DeliveryRatesConfig>
|
|
const merged = { ...defaults }
|
|
for (const tier of TIERS) {
|
|
if (stored[tier]) merged[tier] = { ...defaults[tier], ...stored[tier] }
|
|
}
|
|
return merged
|
|
} catch {
|
|
return defaults
|
|
}
|
|
}
|
|
|
|
export function writeDeliveryRates(config: DeliveryRatesConfig): void {
|
|
atomicWriteJSON(RATES_PATH, config)
|
|
}
|