@cimplify/sdk 0.38.2 → 0.38.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatcher.mjs +1 -1
- package/dist/mock/cli.mjs +956 -436
- package/dist/mock/library.d.mts +2 -2
- package/dist/mock/library.d.ts +2 -2
- package/dist/mock/library.js +956 -436
- package/dist/mock/library.mjs +956 -436
- package/dist/mock/msw.d.mts +1 -1
- package/dist/mock/msw.d.ts +1 -1
- package/dist/mock/msw.js +956 -436
- package/dist/mock/msw.mjs +956 -436
- package/dist/{server-B8TeE2OD.d.mts → server-BNy6Ri-4.d.mts} +18 -0
- package/dist/{server-B8TeE2OD.d.ts → server-BNy6Ri-4.d.ts} +18 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/templates/storefront/.env.example +11 -2
- package/templates/storefront/README.md +35 -18
- package/templates/storefront/app/cart/page.tsx +9 -0
- package/templates/storefront/app/categories/[slug]/page.tsx +80 -0
- package/templates/storefront/app/checkout/page.tsx +17 -0
- package/templates/storefront/app/collections/[slug]/page.tsx +80 -0
- package/templates/storefront/app/globals.css +47 -0
- package/templates/storefront/app/layout.tsx +45 -0
- package/templates/storefront/app/orders/[id]/page.tsx +21 -0
- package/templates/storefront/app/page.tsx +65 -0
- package/templates/storefront/app/shop/page.tsx +46 -0
- package/templates/storefront/components/category-grid.tsx +28 -0
- package/templates/storefront/components/collection-strip.tsx +45 -0
- package/templates/storefront/components/footer.tsx +19 -0
- package/templates/storefront/components/header.tsx +41 -0
- package/templates/storefront/components/hero.tsx +25 -0
- package/templates/storefront/components/product-modal.tsx +105 -0
- package/templates/storefront/components/providers.tsx +31 -0
- package/templates/storefront/components/store-product-card.tsx +48 -0
- package/templates/storefront/lib/cart.ts +27 -0
- package/templates/storefront/next.config.ts +36 -0
- package/templates/storefront/package.json +13 -14
- package/templates/storefront/postcss.config.mjs +7 -0
- package/templates/storefront/tsconfig.json +15 -11
- package/templates/storefront/index.html +0 -12
- package/templates/storefront/src/App.tsx +0 -118
- package/templates/storefront/src/main.tsx +0 -28
- package/templates/storefront/src/styles.css +0 -65
- package/templates/storefront/vite.config.ts +0 -8
package/dist/mock/library.mjs
CHANGED
|
@@ -2984,6 +2984,178 @@ function buildElements(deps) {
|
|
|
2984
2984
|
return app;
|
|
2985
2985
|
}
|
|
2986
2986
|
|
|
2987
|
+
// src/mock/routes/images.ts
|
|
2988
|
+
var SLUG_KEYWORDS = {
|
|
2989
|
+
// ─── Akua's Bakery (default seed) ───
|
|
2990
|
+
sourdough: "sourdough,bread",
|
|
2991
|
+
wheat: "whole-wheat,bread",
|
|
2992
|
+
baguette: "baguette",
|
|
2993
|
+
focaccia: "focaccia,bread",
|
|
2994
|
+
croissant: "croissant",
|
|
2995
|
+
pac: "pain-au-chocolat,croissant",
|
|
2996
|
+
danish: "danish,pastry",
|
|
2997
|
+
cinnamon: "cinnamon-roll",
|
|
2998
|
+
"choc-cake": "chocolate-cake,slice",
|
|
2999
|
+
"red-velvet": "red-velvet-cake",
|
|
3000
|
+
carrot: "carrot-cake",
|
|
3001
|
+
cheesecake: "cheesecake",
|
|
3002
|
+
coffee: "coffee,cup",
|
|
3003
|
+
tea: "tea,cup",
|
|
3004
|
+
sobolo: "hibiscus-drink",
|
|
3005
|
+
oj: "orange-juice,glass",
|
|
3006
|
+
banana: "banana-bread",
|
|
3007
|
+
brownie: "brownie",
|
|
3008
|
+
macaron: "macaron,box",
|
|
3009
|
+
birthday: "birthday-cake",
|
|
3010
|
+
"breakfast-box": "breakfast",
|
|
3011
|
+
"pastry-box": "pastry,assortment",
|
|
3012
|
+
// ─── Mama's Kitchen (restaurant seed) ───
|
|
3013
|
+
kelewele: "fried-plantain,spicy",
|
|
3014
|
+
"spring-rolls": "spring-rolls",
|
|
3015
|
+
"soup-of-the-day": "soup,bowl",
|
|
3016
|
+
"jollof-rice": "jollof-rice",
|
|
3017
|
+
"banku-tilapia": "tilapia,grilled-fish",
|
|
3018
|
+
"fufu-light-soup": "fufu,soup",
|
|
3019
|
+
waakye: "rice,beans,african-food",
|
|
3020
|
+
"grilled-chicken": "grilled-chicken",
|
|
3021
|
+
burger: "burger",
|
|
3022
|
+
"yam-fries": "yam-fries,chips",
|
|
3023
|
+
"fried-plantain": "fried-plantain",
|
|
3024
|
+
salad: "garden-salad",
|
|
3025
|
+
"ice-cream": "ice-cream,scoop",
|
|
3026
|
+
"cake-slice": "chocolate-cake,slice",
|
|
3027
|
+
"fruit-bowl": "fruit-bowl,tropical",
|
|
3028
|
+
"soft-drink": "cola,glass",
|
|
3029
|
+
"fresh-juice": "fresh-juice",
|
|
3030
|
+
water: "water-bottle",
|
|
3031
|
+
feast: "family-meal,buffet",
|
|
3032
|
+
byo: "buffet,plate",
|
|
3033
|
+
// ─── Currents Electronics ───
|
|
3034
|
+
"android-flagship": "smartphone,android",
|
|
3035
|
+
"android-mid": "smartphone",
|
|
3036
|
+
"phone-budget": "phone",
|
|
3037
|
+
"laptop-pro": "laptop,macbook",
|
|
3038
|
+
"laptop-air": "laptop,thin",
|
|
3039
|
+
"laptop-budget": "laptop",
|
|
3040
|
+
"headphones-anc": "headphones",
|
|
3041
|
+
"earbuds-pro": "earbuds,wireless",
|
|
3042
|
+
"speaker-bt": "speaker,bluetooth",
|
|
3043
|
+
"phone-case": "phone-case",
|
|
3044
|
+
"screen-protector": "screen,protector",
|
|
3045
|
+
"fast-charger": "charger,usb",
|
|
3046
|
+
"laptop-sleeve": "laptop-sleeve,case",
|
|
3047
|
+
"wireless-mouse": "wireless-mouse",
|
|
3048
|
+
"mech-keyboard": "mechanical-keyboard",
|
|
3049
|
+
"gaming-controller": "game-controller",
|
|
3050
|
+
"gaming-headset": "gaming-headset",
|
|
3051
|
+
"console-handheld": "handheld-console,gaming",
|
|
3052
|
+
wfh: "desk-setup,laptop",
|
|
3053
|
+
"game-pack": "gaming,console",
|
|
3054
|
+
// ─── Reesa apparel ───
|
|
3055
|
+
"ankara-shirt": "ankara,african-fashion",
|
|
3056
|
+
"agbada-set": "agbada,nigerian-fashion",
|
|
3057
|
+
kaftan: "kaftan,african-dress",
|
|
3058
|
+
"leather-tote": "leather-tote,bag",
|
|
3059
|
+
sandals: "sandals,leather",
|
|
3060
|
+
"beaded-bracelet": "beaded-bracelet",
|
|
3061
|
+
owambe: "african-fashion",
|
|
3062
|
+
// ─── FreshMart ───
|
|
3063
|
+
"tomatoes-1kg": "tomatoes",
|
|
3064
|
+
"onions-1kg": "onions",
|
|
3065
|
+
"ginger-200g": "ginger,root",
|
|
3066
|
+
"bananas-bunch": "bananas",
|
|
3067
|
+
pineapple: "pineapple",
|
|
3068
|
+
"spinach-bunch": "spinach,leaves",
|
|
3069
|
+
"milk-1l": "milk,carton",
|
|
3070
|
+
"yogurt-500g": "yogurt",
|
|
3071
|
+
"eggs-12": "eggs,carton",
|
|
3072
|
+
"cheese-200g": "cheese",
|
|
3073
|
+
"chicken-1kg": "raw-chicken",
|
|
3074
|
+
"beef-mince-500g": "beef,mince",
|
|
3075
|
+
"tilapia-each": "fish,tilapia",
|
|
3076
|
+
"rice-5kg": "rice,bag",
|
|
3077
|
+
"garri-2kg": "rice,grain",
|
|
3078
|
+
"palm-oil-1l": "oil,bottle",
|
|
3079
|
+
"vegetable-oil-1l": "oil,bottle",
|
|
3080
|
+
"salt-1kg": "salt,seasoning",
|
|
3081
|
+
"sugar-1kg": "sugar,white",
|
|
3082
|
+
"tomato-paste": "tomato-paste,can",
|
|
3083
|
+
"soap-bar": "soap,bar",
|
|
3084
|
+
"detergent-1kg": "detergent,laundry",
|
|
3085
|
+
"tissue-pack": "tissue,paper",
|
|
3086
|
+
"trash-bags": "trash-bag",
|
|
3087
|
+
essentials: "groceries,box",
|
|
3088
|
+
// ─── Serene Spa ───
|
|
3089
|
+
"swedish-massage-60": "massage,relaxation",
|
|
3090
|
+
"swedish-massage-90": "massage,relaxation",
|
|
3091
|
+
"deep-tissue-60": "massage,deep-tissue",
|
|
3092
|
+
"couples-massage": "couples-massage",
|
|
3093
|
+
"hot-stone-90": "hot-stone,massage",
|
|
3094
|
+
"facial-classic": "facial,spa",
|
|
3095
|
+
"facial-anti-aging": "facial,skincare",
|
|
3096
|
+
"facial-acne": "facial,skincare",
|
|
3097
|
+
manicure: "manicure,nails",
|
|
3098
|
+
pedicure: "pedicure,feet",
|
|
3099
|
+
"gel-manicure": "manicure,gel-nails",
|
|
3100
|
+
haircut: "haircut,salon",
|
|
3101
|
+
"hair-treatment": "hair-treatment,salon",
|
|
3102
|
+
color: "hair-color,salon",
|
|
3103
|
+
"yoga-session": "yoga,studio",
|
|
3104
|
+
meditation: "meditation,zen",
|
|
3105
|
+
"spa-day": "spa,wellness",
|
|
3106
|
+
"couples-retreat": "spa,couple",
|
|
3107
|
+
"custom-experience": "spa,wellness"
|
|
3108
|
+
};
|
|
3109
|
+
var NO_CACHE = {
|
|
3110
|
+
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
3111
|
+
};
|
|
3112
|
+
function lockFromSlug(slug) {
|
|
3113
|
+
let hash = 0;
|
|
3114
|
+
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
3115
|
+
return hash % 9999 + 1;
|
|
3116
|
+
}
|
|
3117
|
+
function svgFallback(slug) {
|
|
3118
|
+
const label = slug.replace(/[-_]+/g, " ").trim() || "product";
|
|
3119
|
+
const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
|
|
3120
|
+
let hash = 0;
|
|
3121
|
+
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
3122
|
+
const hue = hash % 360;
|
|
3123
|
+
const safe = display.replace(/[<>&]/g, "");
|
|
3124
|
+
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3125
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
|
|
3126
|
+
<defs>
|
|
3127
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
3128
|
+
<stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
|
|
3129
|
+
<stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
|
|
3130
|
+
</linearGradient>
|
|
3131
|
+
</defs>
|
|
3132
|
+
<rect width="400" height="400" fill="url(#g)"/>
|
|
3133
|
+
<text x="200" y="210" font-family="-apple-system, BlinkMacSystemFont, Inter, sans-serif" font-size="22" font-weight="600" fill="white" text-anchor="middle" dominant-baseline="middle" style="text-transform: capitalize">${safe}</text>
|
|
3134
|
+
</svg>`;
|
|
3135
|
+
return new Response(svg, {
|
|
3136
|
+
headers: {
|
|
3137
|
+
"Content-Type": "image/svg+xml; charset=utf-8",
|
|
3138
|
+
...NO_CACHE,
|
|
3139
|
+
"Access-Control-Allow-Origin": "*"
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
3143
|
+
function handleImageRequest(c) {
|
|
3144
|
+
const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
|
|
3145
|
+
const slug = path.split("/").pop() ?? "";
|
|
3146
|
+
for (const [k, v] of Object.entries(NO_CACHE)) c.header(k, v);
|
|
3147
|
+
if (!slug) return svgFallback("");
|
|
3148
|
+
const keyword = SLUG_KEYWORDS[slug] ?? slug.replace(/[-_]+/g, ",");
|
|
3149
|
+
const lock = lockFromSlug(slug);
|
|
3150
|
+
return c.redirect(
|
|
3151
|
+
`https://loremflickr.com/600/600/${encodeURIComponent(keyword)}?lock=${lock}`,
|
|
3152
|
+
302
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
function mountImageRoutes(app) {
|
|
3156
|
+
app.get("/img/*", handleImageRequest);
|
|
3157
|
+
}
|
|
3158
|
+
|
|
2987
3159
|
// src/mock/core/clock.ts
|
|
2988
3160
|
function createClock(opts) {
|
|
2989
3161
|
let frozenAt = opts?.frozenAt ? opts.frozenAt.getTime() : null;
|
|
@@ -3321,51 +3493,249 @@ function percent(a, pct) {
|
|
|
3321
3493
|
return toMajor(Math.round(toMinor(a) * pct / 100));
|
|
3322
3494
|
}
|
|
3323
3495
|
|
|
3324
|
-
// src/mock/domain/catalogue.ts
|
|
3325
|
-
function
|
|
3326
|
-
|
|
3327
|
-
const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
|
|
3328
|
-
const defaultPriceInfo = (price, currency) => ({
|
|
3496
|
+
// src/mock/domain/catalogue/price-info.ts
|
|
3497
|
+
function defaultPriceInfo(price, currency) {
|
|
3498
|
+
return {
|
|
3329
3499
|
base_price: price,
|
|
3330
3500
|
final_price: price,
|
|
3501
|
+
pre_tax_price: price,
|
|
3331
3502
|
markup_percentage: "0.00",
|
|
3332
3503
|
markup_amount: "0.00",
|
|
3333
3504
|
markup_discount_percentage: "0.00",
|
|
3334
3505
|
markup_discount_amount: "0.00",
|
|
3335
|
-
currency
|
|
3336
|
-
|
|
3337
|
-
|
|
3506
|
+
currency,
|
|
3507
|
+
tax_info: {
|
|
3508
|
+
tax_rate: "0",
|
|
3509
|
+
tax_amount: "0.00",
|
|
3510
|
+
is_inclusive: true,
|
|
3511
|
+
pre_tax_price: price,
|
|
3512
|
+
components: []
|
|
3513
|
+
}
|
|
3514
|
+
};
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
// src/mock/domain/catalogue/product-shape.ts
|
|
3518
|
+
function toCanonicalProductSummary(p) {
|
|
3519
|
+
return {
|
|
3520
|
+
id: p.id,
|
|
3521
|
+
business_id: p.business_id,
|
|
3522
|
+
category_id: p.category_ids[0],
|
|
3523
|
+
name: p.name,
|
|
3524
|
+
slug: p.slug,
|
|
3525
|
+
description: p.description,
|
|
3526
|
+
image_url: p.image,
|
|
3527
|
+
images: p.images,
|
|
3528
|
+
default_price: p.base_price,
|
|
3529
|
+
type: p.product_type,
|
|
3530
|
+
inventory_type: "none",
|
|
3531
|
+
variant_strategy: "fetch_all",
|
|
3532
|
+
is_active: p.is_available,
|
|
3533
|
+
inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
|
|
3534
|
+
tags: p.tags ?? [],
|
|
3535
|
+
created_at: p.created_at,
|
|
3536
|
+
updated_at: p.updated_at
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
function deriveRenderHint(p) {
|
|
3540
|
+
if (p.product_type === "service") return "general";
|
|
3541
|
+
const foodish = (p.tags ?? []).some(
|
|
3542
|
+
(t) => /food|drink|cake|bread|pastry|dessert|coffee|tea|juice|spice/i.test(t)
|
|
3543
|
+
);
|
|
3544
|
+
return foodish ? "food" : "general";
|
|
3545
|
+
}
|
|
3546
|
+
function toApiProduct(p, ctx) {
|
|
3547
|
+
const { registry, priceInfo, groups, components, composite, bundle } = ctx;
|
|
3548
|
+
return {
|
|
3549
|
+
// ─── Canonical Product fields (SDK type: Product / ProductWithDetails) ───
|
|
3338
3550
|
id: p.id,
|
|
3339
3551
|
business_id: p.business_id,
|
|
3552
|
+
category_id: p.category_ids[0],
|
|
3340
3553
|
name: p.name,
|
|
3341
3554
|
slug: p.slug,
|
|
3342
3555
|
description: p.description ?? "",
|
|
3343
|
-
|
|
3344
|
-
|
|
3556
|
+
image_url: p.image,
|
|
3557
|
+
images: p.images,
|
|
3345
3558
|
default_price: p.base_price,
|
|
3346
|
-
|
|
3347
|
-
|
|
3559
|
+
type: p.product_type,
|
|
3560
|
+
render_hint: deriveRenderHint(p),
|
|
3561
|
+
inventory_type: "none",
|
|
3562
|
+
variant_strategy: p.variant_ids.length > 0 ? "use_axes" : "fetch_all",
|
|
3563
|
+
is_active: p.is_available,
|
|
3564
|
+
inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
|
|
3565
|
+
tags: p.tags ?? [],
|
|
3566
|
+
calories: p.calories,
|
|
3567
|
+
allergies: p.allergies ?? [],
|
|
3568
|
+
created_at: p.created_at,
|
|
3569
|
+
updated_at: p.updated_at,
|
|
3570
|
+
metadata: {
|
|
3571
|
+
is_signature: p.is_signature ?? false,
|
|
3572
|
+
is_new: p.is_new ?? false,
|
|
3573
|
+
ingredients: p.ingredients ?? [],
|
|
3574
|
+
pairings: p.pairings ?? [],
|
|
3575
|
+
...p.metadata ?? {}
|
|
3576
|
+
},
|
|
3577
|
+
// ─── Pricing breakdown (Price / ProductCustomizer) ───
|
|
3578
|
+
base_price: p.base_price,
|
|
3579
|
+
default_price_info: priceInfo,
|
|
3580
|
+
price_info: priceInfo,
|
|
3348
3581
|
currency: p.currency,
|
|
3582
|
+
// ─── Compatibility aliases (older callers) ───
|
|
3349
3583
|
image: p.image,
|
|
3350
|
-
|
|
3351
|
-
images: p.images,
|
|
3584
|
+
product_type: p.product_type,
|
|
3352
3585
|
is_available: p.is_available,
|
|
3353
|
-
category_id: p.category_ids[0],
|
|
3354
3586
|
category_ids: p.category_ids,
|
|
3355
3587
|
collection_ids: p.collection_ids,
|
|
3356
3588
|
add_on_ids: p.add_on_ids,
|
|
3357
|
-
|
|
3358
|
-
created_at: p.created_at,
|
|
3359
|
-
updated_at: p.updated_at,
|
|
3589
|
+
variant_ids: p.variant_ids,
|
|
3360
3590
|
custom_attributes: [],
|
|
3361
3591
|
properties: [],
|
|
3362
3592
|
has_variants: p.variant_ids.length > 0,
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3593
|
+
// ─── Embedded relations (ProductWithDetails) ───
|
|
3594
|
+
category: registry.categories.get(p.category_ids[0]),
|
|
3595
|
+
add_ons: p.add_on_ids.map((aid) => registry.addOns.get(aid)).filter(Boolean),
|
|
3596
|
+
variants: registry.variants.list((v) => v.product_id === p.id),
|
|
3597
|
+
variant_axes: registry.variantAxes.list((a) => a.product_id === p.id),
|
|
3598
|
+
// ─── Composite-specific (CompositeProductLayout reads product.groups) ───
|
|
3599
|
+
groups,
|
|
3600
|
+
composite_id: composite?.id,
|
|
3601
|
+
pricing_mode: composite?.pricing_mode,
|
|
3602
|
+
min_order_quantity: composite?.min_order_quantity,
|
|
3603
|
+
max_order_quantity: composite?.max_order_quantity,
|
|
3604
|
+
// ─── Bundle-specific (BundleProductLayout reads product.components) ───
|
|
3605
|
+
bundle_id: bundle?.id,
|
|
3606
|
+
bundle_pricing_type: bundle?.pricing_type,
|
|
3607
|
+
bundle_price: bundle?.bundle_price,
|
|
3608
|
+
bundle_discount_value: bundle?.discount_value,
|
|
3609
|
+
components
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
// src/mock/domain/catalogue/composite-shape.ts
|
|
3614
|
+
function expandCompositeGroups(registry, productId, isoNow) {
|
|
3615
|
+
const composite = registry.composites.list((c) => c.product_id === productId)[0];
|
|
3616
|
+
if (!composite) return void 0;
|
|
3617
|
+
return composite.groups.map((g, gIdx) => buildGroup(registry, composite, g, gIdx, isoNow));
|
|
3618
|
+
}
|
|
3619
|
+
function buildGroup(registry, composite, group, groupIdx, isoNow) {
|
|
3620
|
+
return {
|
|
3621
|
+
// ─── Canonical ComponentGroupWithComponents ───
|
|
3622
|
+
id: group.id,
|
|
3623
|
+
composite_id: composite.id,
|
|
3624
|
+
name: group.name,
|
|
3625
|
+
description: void 0,
|
|
3626
|
+
display_order: groupIdx,
|
|
3627
|
+
min_selections: group.min,
|
|
3628
|
+
max_selections: group.max,
|
|
3629
|
+
allow_quantity: true,
|
|
3630
|
+
max_quantity_per_component: group.max,
|
|
3631
|
+
pricing_behavior: "additive",
|
|
3632
|
+
created_at: isoNow,
|
|
3633
|
+
updated_at: isoNow,
|
|
3634
|
+
// ─── Aliases for older selectors ───
|
|
3635
|
+
min: group.min,
|
|
3636
|
+
max: group.max,
|
|
3637
|
+
components: group.component_ids.map((cid, idx) => ({ p: registry.products.get(cid), idx })).filter((x) => Boolean(x.p)).map(({ p, idx }) => buildComponent(composite, group, p, idx))
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
function buildComponent(composite, group, p, idx) {
|
|
3641
|
+
return {
|
|
3642
|
+
// ─── Canonical CompositeComponent ───
|
|
3643
|
+
id: `comp_${composite.product_id}_${p.id}`,
|
|
3644
|
+
composite_id: composite.id,
|
|
3645
|
+
group_id: group.id,
|
|
3646
|
+
source_type: "product",
|
|
3647
|
+
source_product_id: p.id,
|
|
3648
|
+
product_id: p.id,
|
|
3649
|
+
display_name: p.name,
|
|
3650
|
+
display_description: p.description,
|
|
3651
|
+
display_image_url: p.image,
|
|
3652
|
+
price: p.base_price,
|
|
3653
|
+
calories: p.calories,
|
|
3654
|
+
allergens: p.allergies ?? [],
|
|
3655
|
+
display_order: idx,
|
|
3656
|
+
is_popular: idx === 0,
|
|
3657
|
+
is_premium: false,
|
|
3658
|
+
is_available: p.is_available,
|
|
3659
|
+
is_archived: false,
|
|
3660
|
+
created_at: p.created_at,
|
|
3661
|
+
updated_at: p.updated_at,
|
|
3662
|
+
// ─── Compatibility aliases ───
|
|
3663
|
+
name: p.name,
|
|
3664
|
+
description: p.description,
|
|
3665
|
+
image_url: p.image,
|
|
3666
|
+
unit_price: p.base_price,
|
|
3667
|
+
default_price: p.base_price,
|
|
3668
|
+
base_price: p.base_price,
|
|
3669
|
+
currency: p.currency,
|
|
3670
|
+
product: toCanonicalProductSummary(p)
|
|
3671
|
+
};
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
// src/mock/domain/catalogue/bundle-shape.ts
|
|
3675
|
+
function expandBundleComponents(registry, productId) {
|
|
3676
|
+
const bundle = registry.bundles.list((b) => b.product_id === productId)[0];
|
|
3677
|
+
if (!bundle) return void 0;
|
|
3678
|
+
return bundle.product_ids.map((cid) => registry.products.get(cid)).filter((p) => Boolean(p)).map((p) => buildBundleComponent(bundle.id, productId, p));
|
|
3679
|
+
}
|
|
3680
|
+
function buildBundleComponent(bundleId, productId, p) {
|
|
3681
|
+
return {
|
|
3682
|
+
id: `bp_${productId}_${p.id}`,
|
|
3683
|
+
bundle_id: bundleId,
|
|
3684
|
+
product_id: p.id,
|
|
3685
|
+
// ─── Fields BundleProductLayout reads ───
|
|
3686
|
+
product_name: p.name,
|
|
3687
|
+
product_image_url: p.image,
|
|
3688
|
+
product_description: p.description,
|
|
3689
|
+
product_slug: p.slug,
|
|
3690
|
+
effective_price: p.base_price,
|
|
3691
|
+
unit_price: p.base_price,
|
|
3692
|
+
default_price: p.base_price,
|
|
3693
|
+
quantity: 1,
|
|
3694
|
+
allow_variant_choice: false,
|
|
3695
|
+
available_variants: [],
|
|
3696
|
+
selected_variant_id: null,
|
|
3697
|
+
// ─── Compatibility aliases ───
|
|
3698
|
+
display_name: p.name,
|
|
3699
|
+
display_description: p.description,
|
|
3700
|
+
name: p.name,
|
|
3701
|
+
description: p.description,
|
|
3702
|
+
image_url: p.image,
|
|
3703
|
+
currency: p.currency,
|
|
3704
|
+
is_available: p.is_available,
|
|
3705
|
+
product: toCanonicalProductSummary(p)
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
// src/mock/domain/catalogue/index.ts
|
|
3710
|
+
function createCatalogueService(opts) {
|
|
3711
|
+
const { registry, clock, ids } = opts;
|
|
3712
|
+
const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
|
|
3366
3713
|
const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
|
|
3367
3714
|
const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
|
|
3368
3715
|
const addOnLinksForProduct = (productId) => registry.productAddOns.list((l) => l.product_id === productId);
|
|
3716
|
+
const apiProduct = (p) => {
|
|
3717
|
+
const composite = p.product_type === "composite" ? registry.composites.list((c) => c.product_id === p.id)[0] : void 0;
|
|
3718
|
+
const bundle = p.product_type === "bundle" ? registry.bundles.list((b) => b.product_id === p.id)[0] : void 0;
|
|
3719
|
+
return toApiProduct(p, {
|
|
3720
|
+
registry,
|
|
3721
|
+
priceInfo: defaultPriceInfo(p.base_price, p.currency),
|
|
3722
|
+
groups: expandCompositeGroups(registry, p.id, clock.iso()),
|
|
3723
|
+
components: expandBundleComponents(registry, p.id),
|
|
3724
|
+
composite: composite ? {
|
|
3725
|
+
id: composite.id,
|
|
3726
|
+
pricing_mode: composite.pricing_mode,
|
|
3727
|
+
min_order_quantity: composite.min_order_quantity,
|
|
3728
|
+
max_order_quantity: composite.max_order_quantity
|
|
3729
|
+
} : void 0,
|
|
3730
|
+
bundle: bundle ? {
|
|
3731
|
+
id: bundle.id,
|
|
3732
|
+
pricing_type: bundle.pricing_type,
|
|
3733
|
+
bundle_price: bundle.bundle_price,
|
|
3734
|
+
discount_value: bundle.discount_value
|
|
3735
|
+
} : void 0
|
|
3736
|
+
});
|
|
3737
|
+
};
|
|
3738
|
+
const resolveProduct = (idOrSlug) => registry.products.get(idOrSlug) ?? registry.products.list((p) => p.slug === idOrSlug)[0];
|
|
3369
3739
|
return {
|
|
3370
3740
|
snapshot(businessId) {
|
|
3371
3741
|
const products = productsForBusiness(businessId);
|
|
@@ -3373,7 +3743,7 @@ function createCatalogueService(opts) {
|
|
|
3373
3743
|
const addOns = registry.addOns.list((a) => a.business_id === businessId);
|
|
3374
3744
|
return {
|
|
3375
3745
|
categories,
|
|
3376
|
-
products: products.map(
|
|
3746
|
+
products: products.map(apiProduct),
|
|
3377
3747
|
add_ons: addOns,
|
|
3378
3748
|
is_complete: true,
|
|
3379
3749
|
total_available: products.length
|
|
@@ -3388,7 +3758,7 @@ function createCatalogueService(opts) {
|
|
|
3388
3758
|
const start = (page - 1) * limit;
|
|
3389
3759
|
const slice = items.slice(start, start + limit);
|
|
3390
3760
|
return {
|
|
3391
|
-
items: slice.map(
|
|
3761
|
+
items: slice.map(apiProduct),
|
|
3392
3762
|
is_complete: start + limit >= items.length,
|
|
3393
3763
|
total_available: items.length,
|
|
3394
3764
|
pagination: {
|
|
@@ -3401,9 +3771,9 @@ function createCatalogueService(opts) {
|
|
|
3401
3771
|
};
|
|
3402
3772
|
},
|
|
3403
3773
|
getProduct(productId) {
|
|
3404
|
-
const p =
|
|
3774
|
+
const p = resolveProduct(productId);
|
|
3405
3775
|
if (!p) throw notFound(`Product ${productId} not found`);
|
|
3406
|
-
return
|
|
3776
|
+
return apiProduct(p);
|
|
3407
3777
|
},
|
|
3408
3778
|
getProductVariants(productId) {
|
|
3409
3779
|
return variantsForProduct(productId);
|
|
@@ -3413,7 +3783,9 @@ function createCatalogueService(opts) {
|
|
|
3413
3783
|
},
|
|
3414
3784
|
getProductVariantById(productId, variantId) {
|
|
3415
3785
|
const v = registry.variants.get(variantId);
|
|
3416
|
-
if (!v || v.product_id !== productId)
|
|
3786
|
+
if (!v || v.product_id !== productId) {
|
|
3787
|
+
throw notFound(`Variant ${variantId} not found on product ${productId}`);
|
|
3788
|
+
}
|
|
3417
3789
|
return v;
|
|
3418
3790
|
},
|
|
3419
3791
|
findProductVariant(productId, properties) {
|
|
@@ -3443,13 +3815,13 @@ function createCatalogueService(opts) {
|
|
|
3443
3815
|
return registry.categories.list((c) => c.business_id === businessId);
|
|
3444
3816
|
},
|
|
3445
3817
|
getCategory(id) {
|
|
3446
|
-
const c = registry.categories.get(id);
|
|
3818
|
+
const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
|
|
3447
3819
|
if (!c) throw notFound(`Category ${id} not found`);
|
|
3448
3820
|
return c;
|
|
3449
3821
|
},
|
|
3450
3822
|
getCategoryProducts(id) {
|
|
3451
3823
|
const c = this.getCategory(id);
|
|
3452
|
-
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(
|
|
3824
|
+
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
|
|
3453
3825
|
},
|
|
3454
3826
|
getCategoryAttributes(id) {
|
|
3455
3827
|
const c = registry.categories.get(id);
|
|
@@ -3462,13 +3834,13 @@ function createCatalogueService(opts) {
|
|
|
3462
3834
|
return registry.collections.list((c) => c.business_id === businessId);
|
|
3463
3835
|
},
|
|
3464
3836
|
getCollection(id) {
|
|
3465
|
-
const c = registry.collections.get(id);
|
|
3837
|
+
const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
|
|
3466
3838
|
if (!c) throw notFound(`Collection ${id} not found`);
|
|
3467
3839
|
return c;
|
|
3468
3840
|
},
|
|
3469
3841
|
getCollectionProducts(id) {
|
|
3470
3842
|
const c = this.getCollection(id);
|
|
3471
|
-
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(
|
|
3843
|
+
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
|
|
3472
3844
|
},
|
|
3473
3845
|
getCollectionAttributes(id) {
|
|
3474
3846
|
const c = registry.collections.get(id);
|
|
@@ -3481,7 +3853,7 @@ function createCatalogueService(opts) {
|
|
|
3481
3853
|
return registry.bundles.list((b) => b.business_id === businessId);
|
|
3482
3854
|
},
|
|
3483
3855
|
getBundle(id) {
|
|
3484
|
-
const b = registry.bundles.get(id);
|
|
3856
|
+
const b = registry.bundles.get(id) ?? registry.bundles.list((x) => x.product_id === id || x.slug === id)[0];
|
|
3485
3857
|
if (!b) throw notFound(`Bundle ${id} not found`);
|
|
3486
3858
|
return b;
|
|
3487
3859
|
},
|
|
@@ -3489,12 +3861,16 @@ function createCatalogueService(opts) {
|
|
|
3489
3861
|
return registry.composites.list((c) => c.business_id === businessId);
|
|
3490
3862
|
},
|
|
3491
3863
|
getComposite(id) {
|
|
3492
|
-
const c = registry.composites.get(id);
|
|
3864
|
+
const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
|
|
3493
3865
|
if (!c) throw notFound(`Composite ${id} not found`);
|
|
3494
3866
|
return c;
|
|
3495
3867
|
},
|
|
3496
3868
|
getCompositeByProduct(productId) {
|
|
3497
|
-
|
|
3869
|
+
const direct = registry.composites.list((c) => c.product_id === productId)[0];
|
|
3870
|
+
if (direct) return direct;
|
|
3871
|
+
const product = registry.products.list((p) => p.slug === productId)[0];
|
|
3872
|
+
if (!product) return null;
|
|
3873
|
+
return registry.composites.list((c) => c.product_id === product.id)[0] ?? null;
|
|
3498
3874
|
},
|
|
3499
3875
|
calculateCompositePrice(id, body) {
|
|
3500
3876
|
const c = registry.composites.get(id);
|
|
@@ -3502,14 +3878,20 @@ function createCatalogueService(opts) {
|
|
|
3502
3878
|
const sel = body.selections ?? [];
|
|
3503
3879
|
let total = c.base_price;
|
|
3504
3880
|
for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
|
|
3505
|
-
return {
|
|
3881
|
+
return {
|
|
3882
|
+
final_price: total,
|
|
3883
|
+
base_price: c.base_price,
|
|
3884
|
+
components_total: money(parseFloat(total) - parseFloat(c.base_price))
|
|
3885
|
+
};
|
|
3506
3886
|
},
|
|
3507
3887
|
listTaxonomies(businessId) {
|
|
3508
3888
|
return registry.taxonomies.list((t) => t.business_id === businessId);
|
|
3509
3889
|
},
|
|
3510
3890
|
searchTaxonomies(businessId, q) {
|
|
3511
3891
|
const term = q.toLowerCase();
|
|
3512
|
-
return registry.taxonomies.list(
|
|
3892
|
+
return registry.taxonomies.list(
|
|
3893
|
+
(t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
|
|
3894
|
+
);
|
|
3513
3895
|
},
|
|
3514
3896
|
getTaxonomy(id) {
|
|
3515
3897
|
const t = registry.taxonomies.get(id);
|
|
@@ -3543,7 +3925,11 @@ function createCatalogueService(opts) {
|
|
|
3543
3925
|
listPropertyFacets(businessId) {
|
|
3544
3926
|
const products = productsForBusiness(businessId);
|
|
3545
3927
|
const byCategory = /* @__PURE__ */ new Map();
|
|
3546
|
-
for (const p of products)
|
|
3928
|
+
for (const p of products) {
|
|
3929
|
+
for (const cid of p.category_ids) {
|
|
3930
|
+
byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3547
3933
|
return [
|
|
3548
3934
|
{
|
|
3549
3935
|
name: "Category",
|
|
@@ -3562,12 +3948,6 @@ function createCatalogueService(opts) {
|
|
|
3562
3948
|
listOnSale() {
|
|
3563
3949
|
return [];
|
|
3564
3950
|
},
|
|
3565
|
-
validateDiscount(code) {
|
|
3566
|
-
if (code === "WELCOME10") {
|
|
3567
|
-
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
3568
|
-
}
|
|
3569
|
-
return { valid: false, reason: "Code not recognized" };
|
|
3570
|
-
},
|
|
3571
3951
|
getProductDeals() {
|
|
3572
3952
|
return [];
|
|
3573
3953
|
},
|
|
@@ -3577,15 +3957,33 @@ function createCatalogueService(opts) {
|
|
|
3577
3957
|
getCollectionDeals() {
|
|
3578
3958
|
return [];
|
|
3579
3959
|
},
|
|
3960
|
+
validateDiscount(code) {
|
|
3961
|
+
if (code === "WELCOME10") {
|
|
3962
|
+
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
3963
|
+
}
|
|
3964
|
+
return { valid: false, reason: "Code not recognized" };
|
|
3965
|
+
},
|
|
3580
3966
|
createQuote(businessId, body) {
|
|
3581
3967
|
const b = body;
|
|
3582
3968
|
const id = ids.quote();
|
|
3969
|
+
const productId = b.product_id ?? "";
|
|
3970
|
+
const product = resolveProduct(productId);
|
|
3971
|
+
const price = b.total_price ?? product?.base_price ?? "0.00";
|
|
3972
|
+
const currency = b.currency ?? product?.currency ?? "GHS";
|
|
3973
|
+
const priceInfo = defaultPriceInfo(price, currency);
|
|
3583
3974
|
const quote = {
|
|
3584
3975
|
id,
|
|
3976
|
+
quote_id: id,
|
|
3585
3977
|
business_id: businessId,
|
|
3586
|
-
product_id:
|
|
3978
|
+
product_id: product?.id ?? productId,
|
|
3587
3979
|
status: "fresh",
|
|
3588
|
-
total_price:
|
|
3980
|
+
total_price: price,
|
|
3981
|
+
currency,
|
|
3982
|
+
unit_price_info: priceInfo,
|
|
3983
|
+
final_price_info: priceInfo,
|
|
3984
|
+
quoted_total_price_info: priceInfo,
|
|
3985
|
+
components: [],
|
|
3986
|
+
breakdown: { base_price: price, components_total: "0.00", final_price: price },
|
|
3589
3987
|
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
|
|
3590
3988
|
created_at: clock.iso()
|
|
3591
3989
|
};
|
|
@@ -3601,7 +3999,11 @@ function createCatalogueService(opts) {
|
|
|
3601
3999
|
refreshQuote(id) {
|
|
3602
4000
|
const q = registry.quotes.get(id);
|
|
3603
4001
|
if (!q) throw notFound(`Quote ${id} not found`);
|
|
3604
|
-
const refreshed = {
|
|
4002
|
+
const refreshed = {
|
|
4003
|
+
...q,
|
|
4004
|
+
status: "fresh",
|
|
4005
|
+
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
|
|
4006
|
+
};
|
|
3605
4007
|
registry.quotes.put(id, refreshed);
|
|
3606
4008
|
return refreshed;
|
|
3607
4009
|
}
|
|
@@ -4626,67 +5028,82 @@ function createLiteService(opts) {
|
|
|
4626
5028
|
|
|
4627
5029
|
// src/mock/seeds/default.ts
|
|
4628
5030
|
var ISO = "2026-01-01T00:00:00.000Z";
|
|
4629
|
-
var
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
{
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
5031
|
+
var BUSINESS_ID = "bus_default_akua_bakery";
|
|
5032
|
+
var img = (id) => `https://images.unsplash.com/photo-${id}?w=800&h=800&fit=crop&auto=format&q=80`;
|
|
5033
|
+
function makeProduct(input, categoryIds) {
|
|
5034
|
+
const catId = categoryIds.get(input.category);
|
|
5035
|
+
return {
|
|
5036
|
+
id: input.id,
|
|
5037
|
+
business_id: BUSINESS_ID,
|
|
5038
|
+
name: input.name,
|
|
5039
|
+
slug: input.slug,
|
|
5040
|
+
description: input.description,
|
|
5041
|
+
product_type: input.productType ?? "product",
|
|
5042
|
+
base_price: input.price,
|
|
5043
|
+
currency: "GHS",
|
|
5044
|
+
image: input.imageUrl,
|
|
5045
|
+
images: input.images ?? [input.imageUrl],
|
|
5046
|
+
is_available: true,
|
|
5047
|
+
category_ids: [catId],
|
|
5048
|
+
collection_ids: [],
|
|
5049
|
+
add_on_ids: [],
|
|
5050
|
+
variant_ids: [],
|
|
5051
|
+
tags: input.tags ?? [],
|
|
5052
|
+
calories: input.calories,
|
|
5053
|
+
allergies: input.allergies ?? [],
|
|
5054
|
+
ingredients: input.ingredients ?? [],
|
|
5055
|
+
pairings: input.pairings ?? [],
|
|
5056
|
+
is_signature: input.isSignature ?? false,
|
|
5057
|
+
is_new: input.isNew ?? false,
|
|
5058
|
+
created_at: ISO,
|
|
5059
|
+
updated_at: ISO
|
|
5060
|
+
};
|
|
5061
|
+
}
|
|
4657
5062
|
function seedDefault(registry) {
|
|
4658
|
-
const businessId = "bus_default_akua_bakery";
|
|
4659
5063
|
const business = {
|
|
4660
|
-
id:
|
|
5064
|
+
id: BUSINESS_ID,
|
|
4661
5065
|
name: "Akua's Bakery",
|
|
4662
5066
|
handle: "akua",
|
|
4663
5067
|
business_type: "eatery",
|
|
4664
5068
|
email: "hello@akuasbakery.test",
|
|
4665
5069
|
default_currency: "GHS",
|
|
4666
|
-
default_phone: "+
|
|
5070
|
+
default_phone: "+233 244 000 000",
|
|
4667
5071
|
default_address: "12 Independence Ave, Accra",
|
|
4668
5072
|
default_offers_table_service: true,
|
|
4669
5073
|
default_accepts_online_orders: true,
|
|
4670
|
-
image: "
|
|
5074
|
+
image: img("1568254183919-78a4f43a2877"),
|
|
4671
5075
|
status: "active",
|
|
4672
5076
|
created_at: ISO,
|
|
4673
5077
|
updated_at: ISO,
|
|
4674
5078
|
created_by: "seed",
|
|
4675
|
-
preferences: {
|
|
5079
|
+
preferences: {
|
|
5080
|
+
tagline: "Fresh from the oven, every day",
|
|
5081
|
+
description: "Family-run bakery in East Legon. Sourdough, croissants, layered cakes \u2014 handcrafted since 2018, delivered across Accra.",
|
|
5082
|
+
instagram: "https://instagram.com/akuasbakery",
|
|
5083
|
+
theme: "warm",
|
|
5084
|
+
checkout: { allow_guest: true }
|
|
5085
|
+
},
|
|
4676
5086
|
is_online_only: false,
|
|
4677
5087
|
enabled_payment_types: ["mobile_money", "card", "cash"],
|
|
4678
5088
|
default_location_settings: {},
|
|
4679
5089
|
country_code: "GH",
|
|
4680
5090
|
timezone: "Africa/Accra"
|
|
4681
5091
|
};
|
|
4682
|
-
registry.businesses.put(
|
|
4683
|
-
const
|
|
4684
|
-
|
|
5092
|
+
registry.businesses.put(BUSINESS_ID, business);
|
|
5093
|
+
const categoryDefs = [
|
|
5094
|
+
{ slug: "breads", name: "Breads", description: "Naturally leavened sourdough, baguettes, focaccia." },
|
|
5095
|
+
{ slug: "pastries", name: "Pastries", description: "Hand-laminated croissants, danishes, brownies." },
|
|
5096
|
+
{ slug: "cakes", name: "Cakes", description: "Slices and made-to-order showstoppers." },
|
|
5097
|
+
{ slug: "drinks", name: "Drinks", description: "Locally roasted coffee, brewed teas, fresh juice." },
|
|
5098
|
+
{ slug: "boxes", name: "Boxes & Bundles", description: "Curated boxes for sharing." }
|
|
5099
|
+
];
|
|
5100
|
+
const catIds = /* @__PURE__ */ new Map();
|
|
5101
|
+
for (const c of categoryDefs) {
|
|
4685
5102
|
const id = `cat_${c.slug}`;
|
|
4686
|
-
|
|
5103
|
+
catIds.set(c.slug, id);
|
|
4687
5104
|
const cat = {
|
|
4688
5105
|
id,
|
|
4689
|
-
business_id:
|
|
5106
|
+
business_id: BUSINESS_ID,
|
|
4690
5107
|
name: c.name,
|
|
4691
5108
|
slug: c.slug,
|
|
4692
5109
|
description: c.description,
|
|
@@ -4698,99 +5115,354 @@ function seedDefault(registry) {
|
|
|
4698
5115
|
};
|
|
4699
5116
|
registry.categories.put(id, cat);
|
|
4700
5117
|
}
|
|
4701
|
-
const
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
5118
|
+
const productInputs = [
|
|
5119
|
+
// ── Breads ──
|
|
5120
|
+
{
|
|
5121
|
+
id: "prod_sourdough-loaf",
|
|
5122
|
+
name: "Country Sourdough",
|
|
5123
|
+
slug: "sourdough-loaf",
|
|
5124
|
+
price: "25.00",
|
|
5125
|
+
description: "24-hour fermented country loaf with our heritage starter \u2014 open crumb, blistered crust, naturally tangy. Baked fresh every morning.",
|
|
5126
|
+
category: "breads",
|
|
5127
|
+
imageUrl: img("1509440159596-0249088772ff"),
|
|
5128
|
+
images: [img("1509440159596-0249088772ff"), img("1486887396153-fa416526c108")],
|
|
5129
|
+
tags: ["bread", "vegan", "signature"],
|
|
5130
|
+
isSignature: true,
|
|
5131
|
+
ingredients: ["Wheat flour", "Sourdough starter", "Sea salt", "Filtered water"],
|
|
5132
|
+
pairings: ["Olive oil", "Aged cheese", "Tomato jam"],
|
|
5133
|
+
allergies: ["wheat", "gluten"],
|
|
5134
|
+
calories: 240
|
|
5135
|
+
},
|
|
5136
|
+
{
|
|
5137
|
+
id: "prod_whole-wheat",
|
|
5138
|
+
name: "Whole Wheat Seed Loaf",
|
|
5139
|
+
slug: "whole-wheat",
|
|
5140
|
+
price: "20.00",
|
|
5141
|
+
description: "Hearty 100% whole wheat with sunflower, sesame, and pumpkin seeds. Slow-fermented for a soft crumb and nutty depth.",
|
|
5142
|
+
category: "breads",
|
|
5143
|
+
imageUrl: img("1486887396153-fa416526c108"),
|
|
5144
|
+
tags: ["bread", "vegan", "high-fibre"],
|
|
5145
|
+
ingredients: ["Whole wheat flour", "Sunflower seeds", "Sesame", "Pumpkin seeds"],
|
|
5146
|
+
allergies: ["wheat", "gluten", "sesame"],
|
|
5147
|
+
calories: 220
|
|
5148
|
+
},
|
|
5149
|
+
{
|
|
5150
|
+
id: "prod_baguette",
|
|
5151
|
+
name: "Classic Baguette",
|
|
5152
|
+
slug: "baguette",
|
|
5153
|
+
price: "15.00",
|
|
5154
|
+
description: "Crackling crust, airy crumb. Best eaten warm, within an hour of baking.",
|
|
5155
|
+
category: "breads",
|
|
5156
|
+
imageUrl: img("1568471173242-461f0a730452"),
|
|
5157
|
+
tags: ["bread", "vegan"],
|
|
5158
|
+
ingredients: ["Wheat flour", "Yeast", "Sea salt", "Water"],
|
|
5159
|
+
allergies: ["wheat", "gluten"],
|
|
5160
|
+
calories: 180
|
|
5161
|
+
},
|
|
5162
|
+
{
|
|
5163
|
+
id: "prod_focaccia",
|
|
5164
|
+
name: "Rosemary Focaccia",
|
|
5165
|
+
slug: "focaccia",
|
|
5166
|
+
price: "30.00",
|
|
5167
|
+
description: "High-hydration focaccia drenched in olive oil, scattered with sea salt and fresh rosemary. Sold whole or by the slab.",
|
|
5168
|
+
category: "breads",
|
|
5169
|
+
imageUrl: img("1571167530149-c1105da4c2c7"),
|
|
5170
|
+
tags: ["bread", "vegetarian"],
|
|
5171
|
+
ingredients: ["00 flour", "Olive oil", "Rosemary", "Maldon salt"],
|
|
5172
|
+
pairings: ["Burrata", "Prosciutto", "Caponata"],
|
|
5173
|
+
allergies: ["wheat", "gluten"],
|
|
5174
|
+
calories: 310
|
|
5175
|
+
},
|
|
5176
|
+
{
|
|
5177
|
+
id: "prod_banana-bread",
|
|
5178
|
+
name: "Brown Butter Banana Bread",
|
|
5179
|
+
slug: "banana-bread",
|
|
5180
|
+
price: "18.00",
|
|
5181
|
+
description: "Caramelised brown butter, three over-ripe bananas, walnuts, finished with a salted maple glaze.",
|
|
5182
|
+
category: "breads",
|
|
5183
|
+
imageUrl: img("1632931057819-4eefffa8e007"),
|
|
5184
|
+
tags: ["sweet", "comfort"],
|
|
5185
|
+
ingredients: ["Bananas", "Brown butter", "Walnuts", "Maple syrup"],
|
|
5186
|
+
allergies: ["wheat", "gluten", "dairy", "nuts", "eggs"],
|
|
5187
|
+
calories: 360,
|
|
5188
|
+
isNew: true
|
|
5189
|
+
},
|
|
5190
|
+
// ── Pastries ──
|
|
5191
|
+
{
|
|
5192
|
+
id: "prod_croissant",
|
|
5193
|
+
name: "Butter Croissant",
|
|
5194
|
+
slug: "croissant",
|
|
5195
|
+
price: "12.00",
|
|
5196
|
+
description: "27 layers of laminated dough wrapped around French butter. Crisp, shattering crust and a tender honeycomb interior.",
|
|
5197
|
+
category: "pastries",
|
|
5198
|
+
imageUrl: img("1555507036-ab1f4038808a"),
|
|
5199
|
+
images: [img("1555507036-ab1f4038808a"), img("1530610476181-d83430b64dcd")],
|
|
5200
|
+
tags: ["pastry", "vegetarian", "signature"],
|
|
5201
|
+
isSignature: true,
|
|
5202
|
+
ingredients: ["French butter", "Wheat flour", "Milk", "Eggs"],
|
|
5203
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5204
|
+
calories: 280
|
|
5205
|
+
},
|
|
5206
|
+
{
|
|
5207
|
+
id: "prod_pain-au-chocolat",
|
|
5208
|
+
name: "Pain au Chocolat",
|
|
5209
|
+
slug: "pain-au-chocolat",
|
|
5210
|
+
price: "14.00",
|
|
5211
|
+
description: "Our croissant dough wrapped around two batons of 70% Valrhona dark chocolate.",
|
|
5212
|
+
category: "pastries",
|
|
5213
|
+
imageUrl: img("1530610476181-d83430b64dcd"),
|
|
5214
|
+
tags: ["pastry", "chocolate"],
|
|
5215
|
+
ingredients: ["Croissant dough", "Valrhona chocolate"],
|
|
5216
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5217
|
+
calories: 320
|
|
5218
|
+
},
|
|
5219
|
+
{
|
|
5220
|
+
id: "prod_almond-danish",
|
|
5221
|
+
name: "Almond Danish",
|
|
5222
|
+
slug: "almond-danish",
|
|
5223
|
+
price: "16.00",
|
|
5224
|
+
description: "Flaky pastry filled with almond cream, dusted with toasted slivers and powdered sugar.",
|
|
5225
|
+
category: "pastries",
|
|
5226
|
+
imageUrl: img("1517433367423-c7e5b0f35086"),
|
|
5227
|
+
tags: ["pastry", "nuts"],
|
|
5228
|
+
ingredients: ["Pastry dough", "Almond cream", "Toasted almonds"],
|
|
5229
|
+
allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
|
|
5230
|
+
calories: 340
|
|
5231
|
+
},
|
|
5232
|
+
{
|
|
5233
|
+
id: "prod_cinnamon-roll",
|
|
5234
|
+
name: "Cinnamon Roll",
|
|
5235
|
+
slug: "cinnamon-roll",
|
|
5236
|
+
price: "13.00",
|
|
5237
|
+
description: "Soft enriched dough swirled with butter and Ceylon cinnamon, finished with cream-cheese glaze.",
|
|
5238
|
+
category: "pastries",
|
|
5239
|
+
imageUrl: img("1607920591413-4ec007e70023"),
|
|
5240
|
+
tags: ["pastry", "comfort"],
|
|
5241
|
+
ingredients: ["Enriched dough", "Cinnamon", "Brown sugar", "Cream cheese"],
|
|
5242
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5243
|
+
calories: 380
|
|
5244
|
+
},
|
|
5245
|
+
{
|
|
5246
|
+
id: "prod_brownie",
|
|
5247
|
+
name: "Fudge Brownie",
|
|
5248
|
+
slug: "brownie",
|
|
5249
|
+
price: "12.00",
|
|
5250
|
+
description: "Dark, dense, fudgy brownie with a glossy shell. Chocolate four ways. Best slightly warm.",
|
|
5251
|
+
category: "pastries",
|
|
5252
|
+
imageUrl: img("1606313564200-e75d5e30476c"),
|
|
5253
|
+
tags: ["chocolate", "vegetarian"],
|
|
5254
|
+
ingredients: ["Dark chocolate", "Cocoa", "Brown butter", "Eggs"],
|
|
5255
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5256
|
+
calories: 420
|
|
5257
|
+
},
|
|
5258
|
+
{
|
|
5259
|
+
id: "prod_macaron-box",
|
|
5260
|
+
name: "Macaron Box (6)",
|
|
5261
|
+
slug: "macaron-box",
|
|
5262
|
+
price: "60.00",
|
|
5263
|
+
description: "Six French macarons \u2014 pistachio, raspberry, salted caramel, espresso, vanilla bean, and dark chocolate ganache.",
|
|
5264
|
+
category: "pastries",
|
|
5265
|
+
imageUrl: img("1558326567-98ae2405596b"),
|
|
5266
|
+
tags: ["pastry", "gift", "premium"],
|
|
5267
|
+
ingredients: ["Almond flour", "Egg whites", "Sugar", "Various fillings"],
|
|
5268
|
+
allergies: ["nuts", "eggs", "dairy"],
|
|
5269
|
+
calories: 540
|
|
5270
|
+
},
|
|
5271
|
+
// ── Cakes ──
|
|
5272
|
+
{
|
|
5273
|
+
id: "prod_chocolate-cake",
|
|
5274
|
+
name: "Dark Chocolate Layer Cake",
|
|
5275
|
+
slug: "chocolate-cake",
|
|
5276
|
+
price: "22.00",
|
|
5277
|
+
description: "Three layers of dark chocolate sponge with chocolate ganache and cr\xE8me p\xE2tissi\xE8re, finished with a mirror glaze. (Slice)",
|
|
5278
|
+
category: "cakes",
|
|
5279
|
+
imageUrl: img("1565958011703-44f9829ba187"),
|
|
5280
|
+
tags: ["cake", "chocolate", "signature"],
|
|
5281
|
+
isSignature: true,
|
|
5282
|
+
ingredients: ["Dark chocolate", "Cr\xE8me p\xE2tissi\xE8re", "Cocoa", "Eggs"],
|
|
5283
|
+
pairings: ["Espresso", "Cabernet"],
|
|
5284
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5285
|
+
calories: 480
|
|
5286
|
+
},
|
|
5287
|
+
{
|
|
5288
|
+
id: "prod_red-velvet",
|
|
5289
|
+
name: "Red Velvet (Slice)",
|
|
5290
|
+
slug: "red-velvet",
|
|
5291
|
+
price: "22.00",
|
|
5292
|
+
description: "Classic red velvet with cream cheese frosting and white chocolate shavings.",
|
|
5293
|
+
category: "cakes",
|
|
5294
|
+
imageUrl: img("1586788680434-30d324b2d46f"),
|
|
5295
|
+
tags: ["cake"],
|
|
5296
|
+
ingredients: ["Buttermilk", "Cocoa", "Cream cheese", "Beetroot"],
|
|
5297
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5298
|
+
calories: 460
|
|
5299
|
+
},
|
|
5300
|
+
{
|
|
5301
|
+
id: "prod_carrot-cake",
|
|
5302
|
+
name: "Carrot Cake (Slice)",
|
|
5303
|
+
slug: "carrot-cake",
|
|
5304
|
+
price: "20.00",
|
|
5305
|
+
description: "Spiced carrot cake with toasted walnuts, golden raisins, and cream-cheese frosting.",
|
|
5306
|
+
category: "cakes",
|
|
5307
|
+
imageUrl: img("1568827999250-3f6afff96e66"),
|
|
5308
|
+
tags: ["cake", "spiced"],
|
|
5309
|
+
ingredients: ["Carrot", "Walnuts", "Cinnamon", "Cream cheese"],
|
|
5310
|
+
allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
|
|
5311
|
+
calories: 420
|
|
5312
|
+
},
|
|
5313
|
+
{
|
|
5314
|
+
id: "prod_cheesecake",
|
|
5315
|
+
name: "New York Cheesecake (Slice)",
|
|
5316
|
+
slug: "cheesecake",
|
|
5317
|
+
price: "24.00",
|
|
5318
|
+
description: "Dense, creamy New York cheesecake on a butter graham crust. Topped with seasonal berry compote.",
|
|
5319
|
+
category: "cakes",
|
|
5320
|
+
imageUrl: img("1567306226416-28f0efdc88ce"),
|
|
5321
|
+
tags: ["cake", "premium"],
|
|
5322
|
+
ingredients: ["Cream cheese", "Sour cream", "Graham crust", "Vanilla"],
|
|
5323
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5324
|
+
calories: 520
|
|
5325
|
+
},
|
|
5326
|
+
{
|
|
5327
|
+
id: "prod_birthday-cake",
|
|
5328
|
+
name: "Custom Celebration Cake",
|
|
5329
|
+
slug: "birthday-cake",
|
|
5330
|
+
price: "250.00",
|
|
5331
|
+
description: "Made-to-order three-tier cake. Choose size, flavour, and frosting. 48-hour notice required for personalisation.",
|
|
5332
|
+
category: "cakes",
|
|
5333
|
+
imageUrl: img("1535254973040-607b474cb50d"),
|
|
5334
|
+
images: [img("1535254973040-607b474cb50d"), img("1565958011703-44f9829ba187")],
|
|
5335
|
+
tags: ["cake", "made-to-order", "premium"],
|
|
5336
|
+
ingredients: ["Sponge cake", "Buttercream", "Custom decoration"],
|
|
5337
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5338
|
+
productType: "product"
|
|
5339
|
+
},
|
|
5340
|
+
// ── Drinks ──
|
|
5341
|
+
{
|
|
5342
|
+
id: "prod_coffee",
|
|
5343
|
+
name: "Single-Origin Coffee",
|
|
5344
|
+
slug: "coffee",
|
|
5345
|
+
price: "10.00",
|
|
5346
|
+
description: "Locally roasted Arabica from Volta Region. Notes of cocoa, dark fruit, and brown sugar.",
|
|
5347
|
+
category: "drinks",
|
|
5348
|
+
imageUrl: img("1495474472287-4d71bcdd2085"),
|
|
5349
|
+
tags: ["drink", "coffee", "vegan"],
|
|
5350
|
+
ingredients: ["Single-origin Arabica beans"],
|
|
5351
|
+
pairings: ["Croissant", "Brownie"],
|
|
5352
|
+
calories: 5
|
|
5353
|
+
},
|
|
5354
|
+
{
|
|
5355
|
+
id: "prod_tea",
|
|
5356
|
+
name: "Loose-Leaf Tea",
|
|
5357
|
+
slug: "tea",
|
|
5358
|
+
price: "8.00",
|
|
5359
|
+
description: "Choice of English breakfast, Earl Grey, green sencha, or chamomile.",
|
|
5360
|
+
category: "drinks",
|
|
5361
|
+
imageUrl: img("1576092768241-dec231879fc3"),
|
|
5362
|
+
tags: ["drink", "tea", "vegan"],
|
|
5363
|
+
calories: 0
|
|
5364
|
+
},
|
|
5365
|
+
{
|
|
5366
|
+
id: "prod_sobolo",
|
|
5367
|
+
name: "Hibiscus (Sobolo)",
|
|
5368
|
+
slug: "sobolo",
|
|
5369
|
+
price: "12.00",
|
|
5370
|
+
description: "Iced hibiscus tea with ginger, pineapple peel, and a hint of clove. Tart, refreshing, lightly sweet.",
|
|
5371
|
+
category: "drinks",
|
|
5372
|
+
imageUrl: img("1556679343-c7306c1976bc"),
|
|
5373
|
+
tags: ["drink", "vegan", "ghana"],
|
|
5374
|
+
isSignature: true,
|
|
5375
|
+
ingredients: ["Hibiscus", "Ginger", "Pineapple", "Clove"],
|
|
5376
|
+
calories: 60
|
|
5377
|
+
},
|
|
5378
|
+
{
|
|
5379
|
+
id: "prod_orange-juice",
|
|
5380
|
+
name: "Cold-Pressed Orange Juice",
|
|
5381
|
+
slug: "orange-juice",
|
|
5382
|
+
price: "15.00",
|
|
5383
|
+
description: "Hand-pressed at sunrise from Ghanaian Valencia oranges. No sugar, no concentrate.",
|
|
5384
|
+
category: "drinks",
|
|
5385
|
+
imageUrl: img("1622597467836-f3285f2131b8"),
|
|
5386
|
+
tags: ["drink", "vegan"],
|
|
5387
|
+
calories: 110
|
|
5388
|
+
},
|
|
5389
|
+
// ── Boxes (anchor products for bundle/composite) ──
|
|
5390
|
+
{
|
|
5391
|
+
id: "prod_breakfast-box",
|
|
5392
|
+
name: "Breakfast Box",
|
|
5393
|
+
slug: "breakfast-box",
|
|
5394
|
+
price: "30.00",
|
|
5395
|
+
description: "A buttery croissant, a single-origin coffee, and a fresh-pressed orange juice. Everything you need to start the day. Save GH\u20B57 vs buying separately.",
|
|
5396
|
+
category: "boxes",
|
|
5397
|
+
imageUrl: img("1533089860892-a7c6f0a88666"),
|
|
5398
|
+
images: [img("1533089860892-a7c6f0a88666"), img("1495474472287-4d71bcdd2085")],
|
|
5399
|
+
tags: ["bundle", "breakfast"],
|
|
5400
|
+
productType: "bundle"
|
|
5401
|
+
},
|
|
5402
|
+
{
|
|
5403
|
+
id: "prod_pastry-box",
|
|
5404
|
+
name: "Build-Your-Own Pastry Box",
|
|
5405
|
+
slug: "pastry-box",
|
|
5406
|
+
price: "60.00",
|
|
5407
|
+
description: "Pick 6 to 12 of our pastries \u2014 mix and match. Freshly boxed for sharing.",
|
|
5408
|
+
category: "boxes",
|
|
5409
|
+
imageUrl: img("1486427944299-d1955d23e34d"),
|
|
5410
|
+
tags: ["composite", "build-your-own", "gift"],
|
|
5411
|
+
productType: "composite"
|
|
5412
|
+
}
|
|
5413
|
+
];
|
|
5414
|
+
for (const input of productInputs) {
|
|
5415
|
+
const product = makeProduct(input, catIds);
|
|
5416
|
+
registry.products.put(product.id, product);
|
|
5417
|
+
const cat = registry.categories.get(catIds.get(input.category));
|
|
5418
|
+
if (cat) {
|
|
5419
|
+
cat.product_ids.push(product.id);
|
|
5420
|
+
registry.categories.put(cat.id, cat);
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
const featuredIds = [
|
|
5424
|
+
"prod_sourdough-loaf",
|
|
5425
|
+
"prod_croissant",
|
|
5426
|
+
"prod_chocolate-cake",
|
|
5427
|
+
"prod_sobolo",
|
|
5428
|
+
"prod_pastry-box"
|
|
5429
|
+
];
|
|
5430
|
+
const featured = {
|
|
4731
5431
|
id: "col_featured",
|
|
4732
|
-
business_id:
|
|
4733
|
-
name: "Featured",
|
|
5432
|
+
business_id: BUSINESS_ID,
|
|
5433
|
+
name: "Signature & Featured",
|
|
4734
5434
|
slug: "featured",
|
|
4735
|
-
description: "Our most
|
|
4736
|
-
product_ids:
|
|
5435
|
+
description: "Our most-loved bakes and a few seasonal stars.",
|
|
5436
|
+
product_ids: featuredIds,
|
|
4737
5437
|
is_active: true,
|
|
4738
5438
|
created_at: ISO,
|
|
4739
5439
|
updated_at: ISO
|
|
4740
5440
|
};
|
|
4741
|
-
registry.collections.put(
|
|
4742
|
-
for (const pid of
|
|
5441
|
+
registry.collections.put(featured.id, featured);
|
|
5442
|
+
for (const pid of featuredIds) {
|
|
4743
5443
|
const p = registry.products.get(pid);
|
|
4744
5444
|
if (p) {
|
|
4745
|
-
p.collection_ids.push(
|
|
5445
|
+
p.collection_ids.push(featured.id);
|
|
4746
5446
|
registry.products.put(pid, p);
|
|
4747
5447
|
}
|
|
4748
5448
|
}
|
|
4749
|
-
const tasting = {
|
|
4750
|
-
id: "svc_tasting",
|
|
4751
|
-
business_id: businessId,
|
|
4752
|
-
name: "Bakery Tasting Session",
|
|
4753
|
-
description: "Walk through our pastry menu with our head baker.",
|
|
4754
|
-
duration_minutes: 60,
|
|
4755
|
-
price: "150.00",
|
|
4756
|
-
is_available: true
|
|
4757
|
-
};
|
|
4758
|
-
registry.services.put(tasting.id, tasting);
|
|
4759
|
-
const cake = {
|
|
4760
|
-
id: "svc_custom_cake",
|
|
4761
|
-
business_id: businessId,
|
|
4762
|
-
name: "Custom Cake Consultation",
|
|
4763
|
-
description: "Design a custom cake for your event.",
|
|
4764
|
-
duration_minutes: 30,
|
|
4765
|
-
price: "0.00",
|
|
4766
|
-
is_available: true
|
|
4767
|
-
};
|
|
4768
|
-
registry.services.put(cake.id, cake);
|
|
4769
|
-
const sizeAxisId = "axis_size";
|
|
4770
|
-
const flavourAxisId = "axis_flavour";
|
|
4771
|
-
const sizeValues = [
|
|
4772
|
-
{ id: "axv_size_small", name: 'Small (8")' },
|
|
4773
|
-
{ id: "axv_size_medium", name: 'Medium (10")' },
|
|
4774
|
-
{ id: "axv_size_large", name: 'Large (12")' }
|
|
4775
|
-
];
|
|
4776
|
-
const flavourValues = [
|
|
4777
|
-
{ id: "axv_flavour_chocolate", name: "Chocolate" },
|
|
4778
|
-
{ id: "axv_flavour_vanilla", name: "Vanilla" },
|
|
4779
|
-
{ id: "axv_flavour_red_velvet", name: "Red Velvet" }
|
|
4780
|
-
];
|
|
4781
5449
|
const sizeAxis = {
|
|
4782
|
-
id:
|
|
4783
|
-
business_id:
|
|
5450
|
+
id: "axis_cake_size",
|
|
5451
|
+
business_id: BUSINESS_ID,
|
|
4784
5452
|
product_id: "prod_birthday-cake",
|
|
4785
5453
|
name: "Size",
|
|
4786
5454
|
display_order: 0,
|
|
4787
5455
|
affects_recipe: false,
|
|
4788
|
-
values:
|
|
5456
|
+
values: [
|
|
5457
|
+
{ id: "axv_size_small", name: 'Small (8")', display: 0 },
|
|
5458
|
+
{ id: "axv_size_medium", name: 'Medium (10")', display: 1 },
|
|
5459
|
+
{ id: "axv_size_large", name: 'Large (12")', display: 2 }
|
|
5460
|
+
].map((v) => ({
|
|
4789
5461
|
id: v.id,
|
|
4790
|
-
business_id:
|
|
4791
|
-
axis_id:
|
|
5462
|
+
business_id: BUSINESS_ID,
|
|
5463
|
+
axis_id: "axis_cake_size",
|
|
4792
5464
|
name: v.name,
|
|
4793
|
-
display_order:
|
|
5465
|
+
display_order: v.display,
|
|
4794
5466
|
created_at: ISO,
|
|
4795
5467
|
updated_at: ISO
|
|
4796
5468
|
})),
|
|
@@ -4798,18 +5470,22 @@ function seedDefault(registry) {
|
|
|
4798
5470
|
updated_at: ISO
|
|
4799
5471
|
};
|
|
4800
5472
|
const flavourAxis = {
|
|
4801
|
-
id:
|
|
4802
|
-
business_id:
|
|
5473
|
+
id: "axis_cake_flavour",
|
|
5474
|
+
business_id: BUSINESS_ID,
|
|
4803
5475
|
product_id: "prod_birthday-cake",
|
|
4804
5476
|
name: "Flavour",
|
|
4805
5477
|
display_order: 1,
|
|
4806
5478
|
affects_recipe: true,
|
|
4807
|
-
values:
|
|
5479
|
+
values: [
|
|
5480
|
+
{ id: "axv_flavour_chocolate", name: "Dark Chocolate", display: 0 },
|
|
5481
|
+
{ id: "axv_flavour_vanilla", name: "Vanilla Bean", display: 1 },
|
|
5482
|
+
{ id: "axv_flavour_red_velvet", name: "Red Velvet", display: 2 }
|
|
5483
|
+
].map((v) => ({
|
|
4808
5484
|
id: v.id,
|
|
4809
|
-
business_id:
|
|
4810
|
-
axis_id:
|
|
5485
|
+
business_id: BUSINESS_ID,
|
|
5486
|
+
axis_id: "axis_cake_flavour",
|
|
4811
5487
|
name: v.name,
|
|
4812
|
-
display_order:
|
|
5488
|
+
display_order: v.display,
|
|
4813
5489
|
created_at: ISO,
|
|
4814
5490
|
updated_at: ISO
|
|
4815
5491
|
})),
|
|
@@ -4818,40 +5494,42 @@ function seedDefault(registry) {
|
|
|
4818
5494
|
};
|
|
4819
5495
|
registry.variantAxes.put(sizeAxis.id, sizeAxis);
|
|
4820
5496
|
registry.variantAxes.put(flavourAxis.id, flavourAxis);
|
|
4821
|
-
const
|
|
4822
|
-
const
|
|
4823
|
-
let
|
|
4824
|
-
for (
|
|
4825
|
-
for (
|
|
4826
|
-
const
|
|
5497
|
+
const sizeAdj = { small: "250.00", medium: "350.00", large: "450.00" };
|
|
5498
|
+
const cakeVariantIds = [];
|
|
5499
|
+
let firstVariant = true;
|
|
5500
|
+
for (const sz of sizeAxis.values) {
|
|
5501
|
+
for (const fl of flavourAxis.values) {
|
|
5502
|
+
const sizeKey = sz.id.replace("axv_size_", "");
|
|
5503
|
+
const flavourKey = fl.id.replace("axv_flavour_", "");
|
|
5504
|
+
const id = `var_size_${sizeKey}_flavour_${flavourKey}`;
|
|
4827
5505
|
const variant = {
|
|
4828
5506
|
id,
|
|
4829
5507
|
product_id: "prod_birthday-cake",
|
|
4830
|
-
business_id:
|
|
4831
|
-
name: `${
|
|
4832
|
-
sku: `BIRTHDAY-${
|
|
4833
|
-
price_adjustment:
|
|
5508
|
+
business_id: BUSINESS_ID,
|
|
5509
|
+
name: `${sz.name} \u2022 ${fl.name}`,
|
|
5510
|
+
sku: `BIRTHDAY-${sizeKey.toUpperCase()}-${flavourKey.toUpperCase()}`,
|
|
5511
|
+
price_adjustment: sizeAdj[sizeKey] ?? "0.00",
|
|
4834
5512
|
component_multiplier: "1.00",
|
|
4835
|
-
is_default:
|
|
5513
|
+
is_default: firstVariant,
|
|
4836
5514
|
is_active: true,
|
|
4837
|
-
axis_value_ids: [
|
|
5515
|
+
axis_value_ids: [sz.id, fl.id],
|
|
4838
5516
|
created_at: ISO,
|
|
4839
5517
|
updated_at: ISO
|
|
4840
5518
|
};
|
|
4841
5519
|
registry.variants.put(id, variant);
|
|
4842
|
-
|
|
4843
|
-
|
|
5520
|
+
cakeVariantIds.push(id);
|
|
5521
|
+
firstVariant = false;
|
|
4844
5522
|
}
|
|
4845
5523
|
}
|
|
4846
|
-
const
|
|
4847
|
-
if (
|
|
4848
|
-
|
|
4849
|
-
registry.products.put(
|
|
5524
|
+
const birthdayProduct = registry.products.get("prod_birthday-cake");
|
|
5525
|
+
if (birthdayProduct) {
|
|
5526
|
+
birthdayProduct.variant_ids = cakeVariantIds;
|
|
5527
|
+
registry.products.put(birthdayProduct.id, birthdayProduct);
|
|
4850
5528
|
}
|
|
4851
5529
|
const toppingsAddOn = {
|
|
4852
5530
|
id: "addon_toppings",
|
|
4853
|
-
business_id:
|
|
4854
|
-
name: "
|
|
5531
|
+
business_id: BUSINESS_ID,
|
|
5532
|
+
name: "Cake toppings",
|
|
4855
5533
|
is_multiple_allowed: true,
|
|
4856
5534
|
is_required: false,
|
|
4857
5535
|
is_mutually_exclusive: false,
|
|
@@ -4860,15 +5538,14 @@ function seedDefault(registry) {
|
|
|
4860
5538
|
created_at: ISO,
|
|
4861
5539
|
updated_at: ISO,
|
|
4862
5540
|
options: [
|
|
4863
|
-
{ id: "addopt_strawberry", add_on_id: "addon_toppings", business_id:
|
|
4864
|
-
{ id: "addopt_choc_chips", add_on_id: "addon_toppings", business_id:
|
|
4865
|
-
{ id: "addopt_walnuts", add_on_id: "addon_toppings", business_id:
|
|
5541
|
+
{ id: "addopt_strawberry", add_on_id: "addon_toppings", business_id: BUSINESS_ID, name: "Fresh strawberries", default_price: "8.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO },
|
|
5542
|
+
{ id: "addopt_choc_chips", add_on_id: "addon_toppings", business_id: BUSINESS_ID, name: "Dark chocolate shards", default_price: "5.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO },
|
|
5543
|
+
{ id: "addopt_walnuts", add_on_id: "addon_toppings", business_id: BUSINESS_ID, name: "Toasted walnuts", default_price: "6.00", is_required: false, is_mutually_exclusive: false, created_at: ISO, updated_at: ISO }
|
|
4866
5544
|
]
|
|
4867
5545
|
};
|
|
4868
|
-
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
4869
5546
|
const milkAddOn = {
|
|
4870
5547
|
id: "addon_milk",
|
|
4871
|
-
business_id:
|
|
5548
|
+
business_id: BUSINESS_ID,
|
|
4872
5549
|
name: "Milk",
|
|
4873
5550
|
is_multiple_allowed: false,
|
|
4874
5551
|
is_required: false,
|
|
@@ -4878,17 +5555,17 @@ function seedDefault(registry) {
|
|
|
4878
5555
|
created_at: ISO,
|
|
4879
5556
|
updated_at: ISO,
|
|
4880
5557
|
options: [
|
|
4881
|
-
{ id: "addopt_whole", add_on_id: "addon_milk", business_id:
|
|
4882
|
-
{ id: "addopt_oat", add_on_id: "addon_milk", business_id:
|
|
4883
|
-
{ id: "addopt_almond", add_on_id: "addon_milk", business_id:
|
|
5558
|
+
{ id: "addopt_whole", add_on_id: "addon_milk", business_id: BUSINESS_ID, name: "Whole milk", default_price: "0.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO },
|
|
5559
|
+
{ id: "addopt_oat", add_on_id: "addon_milk", business_id: BUSINESS_ID, name: "Oat milk", default_price: "3.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO },
|
|
5560
|
+
{ id: "addopt_almond", add_on_id: "addon_milk", business_id: BUSINESS_ID, name: "Almond milk", default_price: "3.00", is_required: false, is_mutually_exclusive: true, created_at: ISO, updated_at: ISO }
|
|
4884
5561
|
]
|
|
4885
5562
|
};
|
|
5563
|
+
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
4886
5564
|
registry.addOns.put(milkAddOn.id, milkAddOn);
|
|
4887
5565
|
const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
|
|
4888
5566
|
const drinkProductIds = ["prod_coffee", "prod_tea"];
|
|
4889
5567
|
for (const pid of cakeProductIds) {
|
|
4890
|
-
|
|
4891
|
-
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
|
|
5568
|
+
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
|
|
4892
5569
|
const p = registry.products.get(pid);
|
|
4893
5570
|
if (p) {
|
|
4894
5571
|
p.add_on_ids.push(toppingsAddOn.id);
|
|
@@ -4896,8 +5573,7 @@ function seedDefault(registry) {
|
|
|
4896
5573
|
}
|
|
4897
5574
|
}
|
|
4898
5575
|
for (const pid of drinkProductIds) {
|
|
4899
|
-
|
|
4900
|
-
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
|
|
5576
|
+
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
|
|
4901
5577
|
const p = registry.products.get(pid);
|
|
4902
5578
|
if (p) {
|
|
4903
5579
|
p.add_on_ids.push(milkAddOn.id);
|
|
@@ -4906,7 +5582,7 @@ function seedDefault(registry) {
|
|
|
4906
5582
|
}
|
|
4907
5583
|
const breakfastBundle = {
|
|
4908
5584
|
id: "bnd_breakfast_box",
|
|
4909
|
-
business_id:
|
|
5585
|
+
business_id: BUSINESS_ID,
|
|
4910
5586
|
product_id: "prod_breakfast-box",
|
|
4911
5587
|
name: "Breakfast Box",
|
|
4912
5588
|
slug: "breakfast-box",
|
|
@@ -4918,29 +5594,9 @@ function seedDefault(registry) {
|
|
|
4918
5594
|
updated_at: ISO
|
|
4919
5595
|
};
|
|
4920
5596
|
registry.bundles.put(breakfastBundle.id, breakfastBundle);
|
|
4921
|
-
const breakfastProduct = {
|
|
4922
|
-
id: "prod_breakfast-box",
|
|
4923
|
-
business_id: businessId,
|
|
4924
|
-
name: "Breakfast Box",
|
|
4925
|
-
slug: "breakfast-box",
|
|
4926
|
-
description: "Bundled breakfast for one.",
|
|
4927
|
-
product_type: "bundle",
|
|
4928
|
-
base_price: "30.00",
|
|
4929
|
-
currency: "GHS",
|
|
4930
|
-
image: "/img/breakfast-box.jpg",
|
|
4931
|
-
images: ["/img/breakfast-box.jpg"],
|
|
4932
|
-
is_available: true,
|
|
4933
|
-
category_ids: [categoryIdsBySlug.get("pastries")],
|
|
4934
|
-
collection_ids: [],
|
|
4935
|
-
add_on_ids: [],
|
|
4936
|
-
variant_ids: [],
|
|
4937
|
-
created_at: ISO,
|
|
4938
|
-
updated_at: ISO
|
|
4939
|
-
};
|
|
4940
|
-
registry.products.put(breakfastProduct.id, breakfastProduct);
|
|
4941
5597
|
const pastryComposite = {
|
|
4942
5598
|
id: "comp_pastry_box",
|
|
4943
|
-
business_id:
|
|
5599
|
+
business_id: BUSINESS_ID,
|
|
4944
5600
|
product_id: "prod_pastry-box",
|
|
4945
5601
|
base_price: "60.00",
|
|
4946
5602
|
pricing_mode: "additive",
|
|
@@ -4952,43 +5608,51 @@ function seedDefault(registry) {
|
|
|
4952
5608
|
name: "Choose your pastries",
|
|
4953
5609
|
min: 6,
|
|
4954
5610
|
max: 12,
|
|
4955
|
-
component_ids: [
|
|
5611
|
+
component_ids: [
|
|
5612
|
+
"prod_croissant",
|
|
5613
|
+
"prod_pain-au-chocolat",
|
|
5614
|
+
"prod_almond-danish",
|
|
5615
|
+
"prod_cinnamon-roll",
|
|
5616
|
+
"prod_brownie",
|
|
5617
|
+
"prod_banana-bread",
|
|
5618
|
+
"prod_macaron-box"
|
|
5619
|
+
]
|
|
4956
5620
|
}
|
|
4957
5621
|
],
|
|
4958
5622
|
created_at: ISO,
|
|
4959
5623
|
updated_at: ISO
|
|
4960
5624
|
};
|
|
4961
5625
|
registry.composites.put(pastryComposite.id, pastryComposite);
|
|
4962
|
-
const
|
|
4963
|
-
id: "
|
|
4964
|
-
business_id:
|
|
4965
|
-
name: "
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
currency: "GHS",
|
|
4971
|
-
image: "/img/pastry-box.jpg",
|
|
4972
|
-
images: ["/img/pastry-box.jpg"],
|
|
4973
|
-
is_available: true,
|
|
4974
|
-
category_ids: [categoryIdsBySlug.get("pastries")],
|
|
4975
|
-
collection_ids: [],
|
|
4976
|
-
add_on_ids: [],
|
|
4977
|
-
variant_ids: [],
|
|
4978
|
-
created_at: ISO,
|
|
4979
|
-
updated_at: ISO
|
|
5626
|
+
const tasting = {
|
|
5627
|
+
id: "svc_tasting",
|
|
5628
|
+
business_id: BUSINESS_ID,
|
|
5629
|
+
name: "Bakery Tasting Session",
|
|
5630
|
+
description: "Sample our pastry menu with our head baker.",
|
|
5631
|
+
duration_minutes: 60,
|
|
5632
|
+
price: "150.00",
|
|
5633
|
+
is_available: true
|
|
4980
5634
|
};
|
|
4981
|
-
|
|
5635
|
+
const consult = {
|
|
5636
|
+
id: "svc_custom_cake",
|
|
5637
|
+
business_id: BUSINESS_ID,
|
|
5638
|
+
name: "Custom Cake Consultation",
|
|
5639
|
+
description: "Design a custom cake for your event.",
|
|
5640
|
+
duration_minutes: 30,
|
|
5641
|
+
price: "0.00",
|
|
5642
|
+
is_available: true
|
|
5643
|
+
};
|
|
5644
|
+
registry.services.put(tasting.id, tasting);
|
|
5645
|
+
registry.services.put(consult.id, consult);
|
|
4982
5646
|
const tags = [
|
|
4983
|
-
{ id: "tag_signature", business_id:
|
|
4984
|
-
{ id: "tag_vegan", business_id:
|
|
4985
|
-
{ id: "tag_gluten_free", business_id:
|
|
4986
|
-
{ id: "tag_seasonal", business_id:
|
|
5647
|
+
{ id: "tag_signature", business_id: BUSINESS_ID, name: "Signature", slug: "signature", color: "#d97706", sort_order: 0, usage_count: 5, created_at: ISO, updated_at: ISO },
|
|
5648
|
+
{ id: "tag_vegan", business_id: BUSINESS_ID, name: "Vegan", slug: "vegan", color: "#16a34a", sort_order: 1, usage_count: 4, created_at: ISO, updated_at: ISO },
|
|
5649
|
+
{ id: "tag_gluten_free", business_id: BUSINESS_ID, name: "Gluten-Free", slug: "gluten-free", color: "#2563eb", sort_order: 2, usage_count: 0, created_at: ISO, updated_at: ISO },
|
|
5650
|
+
{ id: "tag_seasonal", business_id: BUSINESS_ID, name: "Seasonal", slug: "seasonal", color: "#dc2626", sort_order: 3, usage_count: 1, created_at: ISO, updated_at: ISO }
|
|
4987
5651
|
];
|
|
4988
5652
|
for (const t of tags) registry.tags.put(t.id, t);
|
|
4989
5653
|
const baked = {
|
|
4990
5654
|
id: "tax_baked_goods",
|
|
4991
|
-
business_id:
|
|
5655
|
+
business_id: BUSINESS_ID,
|
|
4992
5656
|
name: "Baked Goods",
|
|
4993
5657
|
slug: "baked-goods",
|
|
4994
5658
|
path: ["Food", "Baked Goods"],
|
|
@@ -4998,7 +5662,7 @@ function seedDefault(registry) {
|
|
|
4998
5662
|
};
|
|
4999
5663
|
const beverages = {
|
|
5000
5664
|
id: "tax_beverages",
|
|
5001
|
-
business_id:
|
|
5665
|
+
business_id: BUSINESS_ID,
|
|
5002
5666
|
name: "Beverages",
|
|
5003
5667
|
slug: "beverages",
|
|
5004
5668
|
path: ["Food", "Beverages"],
|
|
@@ -5011,13 +5675,13 @@ function seedDefault(registry) {
|
|
|
5011
5675
|
const attrs = [
|
|
5012
5676
|
{
|
|
5013
5677
|
id: "attr_allergens",
|
|
5014
|
-
business_id:
|
|
5678
|
+
business_id: BUSINESS_ID,
|
|
5015
5679
|
namespace: "core",
|
|
5016
5680
|
name: "Allergens",
|
|
5017
5681
|
slug: "allergens",
|
|
5018
5682
|
description: "Common allergens present in this item.",
|
|
5019
5683
|
attribute_type: "multi_select",
|
|
5020
|
-
options: ["wheat", "eggs", "dairy", "nuts", "soy"],
|
|
5684
|
+
options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
|
|
5021
5685
|
is_required: false,
|
|
5022
5686
|
is_filterable: true,
|
|
5023
5687
|
visibility: "public",
|
|
@@ -5028,7 +5692,7 @@ function seedDefault(registry) {
|
|
|
5028
5692
|
},
|
|
5029
5693
|
{
|
|
5030
5694
|
id: "attr_shelf_life",
|
|
5031
|
-
business_id:
|
|
5695
|
+
business_id: BUSINESS_ID,
|
|
5032
5696
|
namespace: "core",
|
|
5033
5697
|
name: "Shelf life",
|
|
5034
5698
|
slug: "shelf-life",
|
|
@@ -5043,7 +5707,7 @@ function seedDefault(registry) {
|
|
|
5043
5707
|
},
|
|
5044
5708
|
{
|
|
5045
5709
|
id: "attr_caffeine",
|
|
5046
|
-
business_id:
|
|
5710
|
+
business_id: BUSINESS_ID,
|
|
5047
5711
|
namespace: "core",
|
|
5048
5712
|
name: "Caffeine",
|
|
5049
5713
|
slug: "caffeine",
|
|
@@ -5058,7 +5722,7 @@ function seedDefault(registry) {
|
|
|
5058
5722
|
},
|
|
5059
5723
|
{
|
|
5060
5724
|
id: "attr_seasonal",
|
|
5061
|
-
business_id:
|
|
5725
|
+
business_id: BUSINESS_ID,
|
|
5062
5726
|
namespace: "core",
|
|
5063
5727
|
name: "Seasonal",
|
|
5064
5728
|
slug: "seasonal",
|
|
@@ -5076,10 +5740,10 @@ function seedDefault(registry) {
|
|
|
5076
5740
|
const articles = [
|
|
5077
5741
|
{
|
|
5078
5742
|
id: "kb_delivery",
|
|
5079
|
-
business_id:
|
|
5080
|
-
title: "Delivery &
|
|
5743
|
+
business_id: BUSINESS_ID,
|
|
5744
|
+
title: "Delivery & pickup",
|
|
5081
5745
|
slug: "delivery-pickup",
|
|
5082
|
-
content: "Same-day delivery within Accra for orders placed before 2pm. Pickup available 8am\u20138pm.",
|
|
5746
|
+
content: "Same-day delivery within Greater Accra for orders placed before 2pm. Pickup available 8am\u20138pm at our East Legon location.",
|
|
5083
5747
|
category: "logistics",
|
|
5084
5748
|
tags: ["delivery", "pickup"],
|
|
5085
5749
|
created_at: ISO,
|
|
@@ -5087,10 +5751,10 @@ function seedDefault(registry) {
|
|
|
5087
5751
|
},
|
|
5088
5752
|
{
|
|
5089
5753
|
id: "kb_custom_cakes",
|
|
5090
|
-
business_id:
|
|
5091
|
-
title: "
|
|
5754
|
+
business_id: BUSINESS_ID,
|
|
5755
|
+
title: "Custom cakes & celebrations",
|
|
5092
5756
|
slug: "custom-cakes",
|
|
5093
|
-
content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page.",
|
|
5757
|
+
content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page to walk through flavours, sizes, and decoration.",
|
|
5094
5758
|
category: "products",
|
|
5095
5759
|
tags: ["cakes", "custom"],
|
|
5096
5760
|
created_at: ISO,
|
|
@@ -5098,10 +5762,10 @@ function seedDefault(registry) {
|
|
|
5098
5762
|
},
|
|
5099
5763
|
{
|
|
5100
5764
|
id: "kb_allergens",
|
|
5101
|
-
business_id:
|
|
5102
|
-
title: "
|
|
5765
|
+
business_id: BUSINESS_ID,
|
|
5766
|
+
title: "Allergen information",
|
|
5103
5767
|
slug: "allergens",
|
|
5104
|
-
content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible.",
|
|
5768
|
+
content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible \u2014 let us know about severe allergies before ordering.",
|
|
5105
5769
|
category: "products",
|
|
5106
5770
|
tags: ["allergens", "safety"],
|
|
5107
5771
|
created_at: ISO,
|
|
@@ -5109,7 +5773,7 @@ function seedDefault(registry) {
|
|
|
5109
5773
|
}
|
|
5110
5774
|
];
|
|
5111
5775
|
for (const a of articles) registry.knowledgeArticles.put(a.id, a);
|
|
5112
|
-
return { businessId };
|
|
5776
|
+
return { businessId: BUSINESS_ID };
|
|
5113
5777
|
}
|
|
5114
5778
|
|
|
5115
5779
|
// src/mock/seeds/empty.ts
|
|
@@ -7212,195 +7876,47 @@ function createDeps(options = {}) {
|
|
|
7212
7876
|
}
|
|
7213
7877
|
|
|
7214
7878
|
// src/mock/server.ts
|
|
7879
|
+
var PACKAGE_NAME = "@cimplify/sdk/mock";
|
|
7880
|
+
function corsMiddleware(allow) {
|
|
7881
|
+
return async (c, next) => {
|
|
7882
|
+
const origin = c.req.header("origin") ?? "";
|
|
7883
|
+
const allowOrigin = allow === "*" ? "*" : allow.includes(origin) ? origin : allow[0] ?? "*";
|
|
7884
|
+
const headers = {
|
|
7885
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
7886
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7887
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7888
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7889
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
7890
|
+
"Access-Control-Max-Age": "86400",
|
|
7891
|
+
Vary: "Origin"
|
|
7892
|
+
};
|
|
7893
|
+
if (c.req.method === "OPTIONS") {
|
|
7894
|
+
return new Response(null, { status: 204, headers });
|
|
7895
|
+
}
|
|
7896
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7897
|
+
await next();
|
|
7898
|
+
};
|
|
7899
|
+
}
|
|
7215
7900
|
function createMockApp(options = {}) {
|
|
7216
7901
|
const deps = createDeps(options);
|
|
7217
7902
|
const app = new Hono2();
|
|
7218
|
-
const
|
|
7219
|
-
if (
|
|
7220
|
-
app.use("*", async (c, next) => {
|
|
7221
|
-
const origin = c.req.header("origin") ?? "";
|
|
7222
|
-
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7223
|
-
const headers = {
|
|
7224
|
-
"Access-Control-Allow-Origin": allow,
|
|
7225
|
-
"Access-Control-Allow-Credentials": "true",
|
|
7226
|
-
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7227
|
-
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7228
|
-
"Access-Control-Expose-Headers": "x-session-token",
|
|
7229
|
-
"Access-Control-Max-Age": "86400",
|
|
7230
|
-
Vary: "Origin"
|
|
7231
|
-
};
|
|
7232
|
-
if (c.req.method === "OPTIONS") {
|
|
7233
|
-
return new Response(null, { status: 204, headers });
|
|
7234
|
-
}
|
|
7235
|
-
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7236
|
-
await next();
|
|
7237
|
-
});
|
|
7238
|
-
}
|
|
7903
|
+
const cors = options.cors ?? "*";
|
|
7904
|
+
if (cors) app.use("*", corsMiddleware(cors));
|
|
7239
7905
|
app.get(
|
|
7240
7906
|
"/",
|
|
7241
7907
|
(c) => c.json({
|
|
7242
|
-
name:
|
|
7243
|
-
version: "0.36.0",
|
|
7908
|
+
name: PACKAGE_NAME,
|
|
7244
7909
|
docs: "https://github.com/cimplify/cimplify",
|
|
7245
7910
|
api: "/api/v1",
|
|
7246
7911
|
admin: "/_mock",
|
|
7247
7912
|
elements: "/elements",
|
|
7248
|
-
|
|
7913
|
+
images: "/img/<slug>",
|
|
7914
|
+
seed_business_id: deps.defaultBusinessId,
|
|
7915
|
+
time: deps.clock.iso()
|
|
7249
7916
|
})
|
|
7250
7917
|
);
|
|
7251
7918
|
app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
|
|
7252
|
-
|
|
7253
|
-
// Akua's Bakery (default seed)
|
|
7254
|
-
sourdough: "1509440159596-0249088772ff",
|
|
7255
|
-
wheat: "1486887396153-fa416526c108",
|
|
7256
|
-
baguette: "1568471173242-461f0a730452",
|
|
7257
|
-
focaccia: "1573472635068-fd9b1abdd8e5",
|
|
7258
|
-
croissant: "1555507036-ab1f4038808a",
|
|
7259
|
-
pac: "1530610476181-d83430b64dcd",
|
|
7260
|
-
danish: "1517433367423-c7e5b0f35086",
|
|
7261
|
-
cinnamon: "1583527976767-50068e72b7a3",
|
|
7262
|
-
"choc-cake": "1565958011703-44f9829ba187",
|
|
7263
|
-
"red-velvet": "1586788680434-30d324b2d46f",
|
|
7264
|
-
carrot: "1568827999250-3f6afff96e66",
|
|
7265
|
-
cheesecake: "1567306226416-28f0efdc88ce",
|
|
7266
|
-
coffee: "1495474472287-4d71bcdd2085",
|
|
7267
|
-
tea: "1576092768241-dec231879fc3",
|
|
7268
|
-
sobolo: "1556679343-c7306c1976bc",
|
|
7269
|
-
oj: "1600271886742-f049b969dc7a",
|
|
7270
|
-
banana: "1632931057819-4eefffa8e007",
|
|
7271
|
-
brownie: "1551024506-0bccd828d307",
|
|
7272
|
-
macaron: "1558326567-98ae2405596b",
|
|
7273
|
-
birthday: "1535254973040-607b474cb50d",
|
|
7274
|
-
"breakfast-box": "1533089860892-a7c6f0a88666",
|
|
7275
|
-
"pastry-box": "1486427944299-d1955d23e34d",
|
|
7276
|
-
// Mama's Kitchen (restaurant seed)
|
|
7277
|
-
kelewele: "1585536830049-aedf5c1b98a4",
|
|
7278
|
-
"spring-rolls": "1606755962773-d324e0a13086",
|
|
7279
|
-
"soup-of-the-day": "1547592180-85f173990554",
|
|
7280
|
-
"jollof-rice": "1604329760661-e71dc83f8f26",
|
|
7281
|
-
"banku-tilapia": "1559847844-5315695dadae",
|
|
7282
|
-
"fufu-light-soup": "1547928576-b822bcced36e",
|
|
7283
|
-
waakye: "1604329760661-e71dc83f8f26",
|
|
7284
|
-
"grilled-chicken": "1532550907401-a500c9a57435",
|
|
7285
|
-
burger: "1568901346375-23c9450c58cd",
|
|
7286
|
-
"yam-fries": "1573080496219-bb080dd4f877",
|
|
7287
|
-
"fried-plantain": "1604329760661-e71dc83f8f26",
|
|
7288
|
-
salad: "1512621776951-a57141f2eefd",
|
|
7289
|
-
"ice-cream": "1497034825429-c343d7c6a68f",
|
|
7290
|
-
"cake-slice": "1565958011703-44f9829ba187",
|
|
7291
|
-
"fruit-bowl": "1490474418585-ba9bad8fd0ea",
|
|
7292
|
-
"soft-drink": "1497534446932-c925b458314e",
|
|
7293
|
-
"fresh-juice": "1622597467836-f3285f2131b8",
|
|
7294
|
-
water: "1548839140-29a749e1cf4d",
|
|
7295
|
-
feast: "1546069901-ba9599a7e63c",
|
|
7296
|
-
byo: "1546069901-ba9599a7e63c",
|
|
7297
|
-
// Currents Electronics
|
|
7298
|
-
"android-flagship": "1511707171634-5f897ff02aa9",
|
|
7299
|
-
"android-mid": "1592899677977-9c10ca588bbd",
|
|
7300
|
-
"phone-budget": "1574944985070-8f3ebc6b79d2",
|
|
7301
|
-
"laptop-pro": "1496181133206-80ce9b88a853",
|
|
7302
|
-
"laptop-air": "1517336714731-489689fd1ca8",
|
|
7303
|
-
"laptop-budget": "1541807084-5c52b6b3adef",
|
|
7304
|
-
"headphones-anc": "1583394838336-acd977736f90",
|
|
7305
|
-
"earbuds-pro": "1606220945770-b5b6c2c55bf1",
|
|
7306
|
-
"speaker-bt": "1608043152269-423dbba4e7e1",
|
|
7307
|
-
"phone-case": "1601593346740-925612772716",
|
|
7308
|
-
"screen-protector": "1592890288564-76628a30a657",
|
|
7309
|
-
"fast-charger": "1583394838336-acd977736f90",
|
|
7310
|
-
"laptop-sleeve": "1525547719571-a2d4ac8945e2",
|
|
7311
|
-
"wireless-mouse": "1527864550417-7fd91fc51a46",
|
|
7312
|
-
"mech-keyboard": "1587829741301-dc798b83add3",
|
|
7313
|
-
"gaming-controller": "1592840496694-26d035b52b48",
|
|
7314
|
-
"gaming-headset": "1591105327764-407a4a06e768",
|
|
7315
|
-
"console-handheld": "1606144042614-b2417e99c4e3",
|
|
7316
|
-
wfh: "1593642632559-0c6d3fc62b89",
|
|
7317
|
-
"game-pack": "1606144042614-b2417e99c4e3",
|
|
7318
|
-
// Reesa apparel
|
|
7319
|
-
"ankara-shirt": "1591047139829-d91aecb6caea",
|
|
7320
|
-
"agbada-set": "1605497788044-5a32c7078486",
|
|
7321
|
-
kaftan: "1554412933-514a83d2f3c8",
|
|
7322
|
-
"leather-tote": "1584917865442-de89df76afd3",
|
|
7323
|
-
sandals: "1543163521-1bf539c55dd2",
|
|
7324
|
-
"beaded-bracelet": "1611591437281-460bfbe1220a",
|
|
7325
|
-
owambe: "1605497788044-5a32c7078486",
|
|
7326
|
-
// FreshMart
|
|
7327
|
-
"tomatoes-1kg": "1546470427-227f1c75a9ef",
|
|
7328
|
-
"onions-1kg": "1620574387735-3624d75b2dbc",
|
|
7329
|
-
"ginger-200g": "1606937330526-f5e6a2a83555",
|
|
7330
|
-
"bananas-bunch": "1571771894821-ce9b6c11b08e",
|
|
7331
|
-
pineapple: "1550258987-190a2d41a8ba",
|
|
7332
|
-
"spinach-bunch": "1576045057995-568f588f82fb",
|
|
7333
|
-
"milk-1l": "1550583724-b2692b85b150",
|
|
7334
|
-
"yogurt-500g": "1571212515416-fef01fc43637",
|
|
7335
|
-
"eggs-12": "1582722872445-44dc5f7e3c8f",
|
|
7336
|
-
"cheese-200g": "1486297678162-eb2a19b0a32d",
|
|
7337
|
-
"chicken-1kg": "1604503468506-a8da13d82791",
|
|
7338
|
-
"beef-mince-500g": "1607623814075-e51df1bdc82f",
|
|
7339
|
-
"tilapia-each": "1546069901-ba9599a7e63c",
|
|
7340
|
-
"rice-5kg": "1586201375761-83865001e31c",
|
|
7341
|
-
"garri-2kg": "1604329760661-e71dc83f8f26",
|
|
7342
|
-
"palm-oil-1l": "1474979266404-7eaacbcd87c5",
|
|
7343
|
-
"vegetable-oil-1l": "1474979266404-7eaacbcd87c5",
|
|
7344
|
-
"salt-1kg": "1612531386530-97286d97c2d2",
|
|
7345
|
-
"sugar-1kg": "1581281697996-3aef0c3b9b12",
|
|
7346
|
-
"tomato-paste": "1546470427-227f1c75a9ef",
|
|
7347
|
-
"soap-bar": "1583912267550-d6c2ac3196c0",
|
|
7348
|
-
"detergent-1kg": "1583912267550-d6c2ac3196c0",
|
|
7349
|
-
"tissue-pack": "1610557892470-55d9e80c0bce",
|
|
7350
|
-
"trash-bags": "1583912267550-d6c2ac3196c0",
|
|
7351
|
-
essentials: "1542838132-92c53300491e",
|
|
7352
|
-
// Serene Spa services
|
|
7353
|
-
"swedish-massage-60": "1544161515-4ab6ce6db874",
|
|
7354
|
-
"swedish-massage-90": "1544161515-4ab6ce6db874",
|
|
7355
|
-
"deep-tissue-60": "1600334129128-685c5582fd35",
|
|
7356
|
-
"couples-massage": "1540555700478-4be289fbecef",
|
|
7357
|
-
"hot-stone-90": "1600334129128-685c5582fd35",
|
|
7358
|
-
"facial-classic": "1570172619644-dfd03ed5d881",
|
|
7359
|
-
"facial-anti-aging": "1571019613454-1cb2f99b2d8b",
|
|
7360
|
-
"facial-acne": "1571019613454-1cb2f99b2d8b",
|
|
7361
|
-
manicure: "1604654894610-df63bc536371",
|
|
7362
|
-
pedicure: "1610701596007-11502861dcfa",
|
|
7363
|
-
"gel-manicure": "1604654894610-df63bc536371",
|
|
7364
|
-
haircut: "1562322140-8baeececf3df",
|
|
7365
|
-
"hair-treatment": "1522337360788-8b13dee7a37e",
|
|
7366
|
-
color: "1562322140-8baeececf3df",
|
|
7367
|
-
"yoga-session": "1545205597-3d9d02c29597",
|
|
7368
|
-
meditation: "1545389336-cf090694435e",
|
|
7369
|
-
"spa-day": "1540555700478-4be289fbecef",
|
|
7370
|
-
"couples-retreat": "1540555700478-4be289fbecef",
|
|
7371
|
-
"custom-experience": "1545389336-cf090694435e"
|
|
7372
|
-
};
|
|
7373
|
-
app.get("/img/*", (c) => {
|
|
7374
|
-
const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
|
|
7375
|
-
const slug = path.split("/").pop() ?? "";
|
|
7376
|
-
const photoId = UNSPLASH_PHOTOS[slug];
|
|
7377
|
-
if (photoId) {
|
|
7378
|
-
return c.redirect(`https://images.unsplash.com/photo-${photoId}?w=600&h=600&fit=crop&auto=format&q=80`, 302);
|
|
7379
|
-
}
|
|
7380
|
-
const label = slug.replace(/[-_]+/g, " ").trim() || "product";
|
|
7381
|
-
let hash = 0;
|
|
7382
|
-
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
7383
|
-
const hue = hash % 360;
|
|
7384
|
-
const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
|
|
7385
|
-
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
7386
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
|
|
7387
|
-
<defs>
|
|
7388
|
-
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
7389
|
-
<stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
|
|
7390
|
-
<stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
|
|
7391
|
-
</linearGradient>
|
|
7392
|
-
</defs>
|
|
7393
|
-
<rect width="400" height="400" fill="url(#g)"/>
|
|
7394
|
-
<text x="200" y="210" font-family="-apple-system, BlinkMacSystemFont, Inter, sans-serif" font-size="22" font-weight="600" fill="white" text-anchor="middle" dominant-baseline="middle" style="text-transform: capitalize">${display.replace(/[<>&]/g, "")}</text>
|
|
7395
|
-
</svg>`;
|
|
7396
|
-
return new Response(svg, {
|
|
7397
|
-
headers: {
|
|
7398
|
-
"Content-Type": "image/svg+xml; charset=utf-8",
|
|
7399
|
-
"Cache-Control": "public, max-age=3600",
|
|
7400
|
-
"Access-Control-Allow-Origin": "*"
|
|
7401
|
-
}
|
|
7402
|
-
});
|
|
7403
|
-
});
|
|
7919
|
+
mountImageRoutes(app);
|
|
7404
7920
|
app.route("/api/v1", buildV1(deps));
|
|
7405
7921
|
app.route("/_mock", buildAdmin(deps));
|
|
7406
7922
|
app.route("/elements", buildElements(deps));
|
|
@@ -7408,7 +7924,11 @@ function createMockApp(options = {}) {
|
|
|
7408
7924
|
(c) => c.json(
|
|
7409
7925
|
{
|
|
7410
7926
|
success: false,
|
|
7411
|
-
error: {
|
|
7927
|
+
error: {
|
|
7928
|
+
code: "ROUTE_NOT_REGISTERED",
|
|
7929
|
+
message: `Route ${c.req.method} ${c.req.path} not handled by mock`,
|
|
7930
|
+
retryable: false
|
|
7931
|
+
},
|
|
7412
7932
|
hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
|
|
7413
7933
|
},
|
|
7414
7934
|
501
|