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