@cimplify/sdk 0.38.1 → 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 +995 -308
- package/dist/mock/library.d.mts +2 -2
- package/dist/mock/library.d.ts +2 -2
- package/dist/mock/library.js +995 -308
- package/dist/mock/library.mjs +995 -308
- package/dist/mock/msw.d.mts +1 -1
- package/dist/mock/msw.d.ts +1 -1
- package/dist/mock/msw.js +995 -308
- package/dist/mock/msw.mjs +995 -308
- package/dist/react.js +12 -2
- package/dist/react.mjs +12 -2
- 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.js
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,36 +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
|
-
|
|
3328
|
-
|
|
3496
|
+
// src/mock/domain/catalogue/price-info.ts
|
|
3497
|
+
function defaultPriceInfo(price, currency) {
|
|
3498
|
+
return {
|
|
3499
|
+
base_price: price,
|
|
3500
|
+
final_price: price,
|
|
3501
|
+
pre_tax_price: price,
|
|
3502
|
+
markup_percentage: "0.00",
|
|
3503
|
+
markup_amount: "0.00",
|
|
3504
|
+
markup_discount_percentage: "0.00",
|
|
3505
|
+
markup_discount_amount: "0.00",
|
|
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) ───
|
|
3329
3550
|
id: p.id,
|
|
3330
3551
|
business_id: p.business_id,
|
|
3552
|
+
category_id: p.category_ids[0],
|
|
3331
3553
|
name: p.name,
|
|
3332
3554
|
slug: p.slug,
|
|
3333
3555
|
description: p.description ?? "",
|
|
3334
|
-
|
|
3556
|
+
image_url: p.image,
|
|
3557
|
+
images: p.images,
|
|
3558
|
+
default_price: p.base_price,
|
|
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) ───
|
|
3335
3578
|
base_price: p.base_price,
|
|
3579
|
+
default_price_info: priceInfo,
|
|
3580
|
+
price_info: priceInfo,
|
|
3336
3581
|
currency: p.currency,
|
|
3582
|
+
// ─── Compatibility aliases (older callers) ───
|
|
3337
3583
|
image: p.image,
|
|
3338
|
-
|
|
3584
|
+
product_type: p.product_type,
|
|
3339
3585
|
is_available: p.is_available,
|
|
3340
3586
|
category_ids: p.category_ids,
|
|
3341
3587
|
collection_ids: p.collection_ids,
|
|
3342
3588
|
add_on_ids: p.add_on_ids,
|
|
3343
|
-
|
|
3344
|
-
created_at: p.created_at,
|
|
3345
|
-
updated_at: p.updated_at,
|
|
3589
|
+
variant_ids: p.variant_ids,
|
|
3346
3590
|
custom_attributes: [],
|
|
3347
3591
|
properties: [],
|
|
3348
3592
|
has_variants: p.variant_ids.length > 0,
|
|
3349
|
-
|
|
3350
|
-
|
|
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);
|
|
3351
3713
|
const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
|
|
3352
3714
|
const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
|
|
3353
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];
|
|
3354
3739
|
return {
|
|
3355
3740
|
snapshot(businessId) {
|
|
3356
3741
|
const products = productsForBusiness(businessId);
|
|
@@ -3358,7 +3743,7 @@ function createCatalogueService(opts) {
|
|
|
3358
3743
|
const addOns = registry.addOns.list((a) => a.business_id === businessId);
|
|
3359
3744
|
return {
|
|
3360
3745
|
categories,
|
|
3361
|
-
products: products.map(
|
|
3746
|
+
products: products.map(apiProduct),
|
|
3362
3747
|
add_ons: addOns,
|
|
3363
3748
|
is_complete: true,
|
|
3364
3749
|
total_available: products.length
|
|
@@ -3373,7 +3758,7 @@ function createCatalogueService(opts) {
|
|
|
3373
3758
|
const start = (page - 1) * limit;
|
|
3374
3759
|
const slice = items.slice(start, start + limit);
|
|
3375
3760
|
return {
|
|
3376
|
-
items: slice.map(
|
|
3761
|
+
items: slice.map(apiProduct),
|
|
3377
3762
|
is_complete: start + limit >= items.length,
|
|
3378
3763
|
total_available: items.length,
|
|
3379
3764
|
pagination: {
|
|
@@ -3386,9 +3771,9 @@ function createCatalogueService(opts) {
|
|
|
3386
3771
|
};
|
|
3387
3772
|
},
|
|
3388
3773
|
getProduct(productId) {
|
|
3389
|
-
const p =
|
|
3774
|
+
const p = resolveProduct(productId);
|
|
3390
3775
|
if (!p) throw notFound(`Product ${productId} not found`);
|
|
3391
|
-
return
|
|
3776
|
+
return apiProduct(p);
|
|
3392
3777
|
},
|
|
3393
3778
|
getProductVariants(productId) {
|
|
3394
3779
|
return variantsForProduct(productId);
|
|
@@ -3398,7 +3783,9 @@ function createCatalogueService(opts) {
|
|
|
3398
3783
|
},
|
|
3399
3784
|
getProductVariantById(productId, variantId) {
|
|
3400
3785
|
const v = registry.variants.get(variantId);
|
|
3401
|
-
if (!v || v.product_id !== productId)
|
|
3786
|
+
if (!v || v.product_id !== productId) {
|
|
3787
|
+
throw notFound(`Variant ${variantId} not found on product ${productId}`);
|
|
3788
|
+
}
|
|
3402
3789
|
return v;
|
|
3403
3790
|
},
|
|
3404
3791
|
findProductVariant(productId, properties) {
|
|
@@ -3428,13 +3815,13 @@ function createCatalogueService(opts) {
|
|
|
3428
3815
|
return registry.categories.list((c) => c.business_id === businessId);
|
|
3429
3816
|
},
|
|
3430
3817
|
getCategory(id) {
|
|
3431
|
-
const c = registry.categories.get(id);
|
|
3818
|
+
const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
|
|
3432
3819
|
if (!c) throw notFound(`Category ${id} not found`);
|
|
3433
3820
|
return c;
|
|
3434
3821
|
},
|
|
3435
3822
|
getCategoryProducts(id) {
|
|
3436
3823
|
const c = this.getCategory(id);
|
|
3437
|
-
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);
|
|
3438
3825
|
},
|
|
3439
3826
|
getCategoryAttributes(id) {
|
|
3440
3827
|
const c = registry.categories.get(id);
|
|
@@ -3447,13 +3834,13 @@ function createCatalogueService(opts) {
|
|
|
3447
3834
|
return registry.collections.list((c) => c.business_id === businessId);
|
|
3448
3835
|
},
|
|
3449
3836
|
getCollection(id) {
|
|
3450
|
-
const c = registry.collections.get(id);
|
|
3837
|
+
const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
|
|
3451
3838
|
if (!c) throw notFound(`Collection ${id} not found`);
|
|
3452
3839
|
return c;
|
|
3453
3840
|
},
|
|
3454
3841
|
getCollectionProducts(id) {
|
|
3455
3842
|
const c = this.getCollection(id);
|
|
3456
|
-
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);
|
|
3457
3844
|
},
|
|
3458
3845
|
getCollectionAttributes(id) {
|
|
3459
3846
|
const c = registry.collections.get(id);
|
|
@@ -3466,7 +3853,7 @@ function createCatalogueService(opts) {
|
|
|
3466
3853
|
return registry.bundles.list((b) => b.business_id === businessId);
|
|
3467
3854
|
},
|
|
3468
3855
|
getBundle(id) {
|
|
3469
|
-
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];
|
|
3470
3857
|
if (!b) throw notFound(`Bundle ${id} not found`);
|
|
3471
3858
|
return b;
|
|
3472
3859
|
},
|
|
@@ -3474,12 +3861,16 @@ function createCatalogueService(opts) {
|
|
|
3474
3861
|
return registry.composites.list((c) => c.business_id === businessId);
|
|
3475
3862
|
},
|
|
3476
3863
|
getComposite(id) {
|
|
3477
|
-
const c = registry.composites.get(id);
|
|
3864
|
+
const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
|
|
3478
3865
|
if (!c) throw notFound(`Composite ${id} not found`);
|
|
3479
3866
|
return c;
|
|
3480
3867
|
},
|
|
3481
3868
|
getCompositeByProduct(productId) {
|
|
3482
|
-
|
|
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;
|
|
3483
3874
|
},
|
|
3484
3875
|
calculateCompositePrice(id, body) {
|
|
3485
3876
|
const c = registry.composites.get(id);
|
|
@@ -3487,14 +3878,20 @@ function createCatalogueService(opts) {
|
|
|
3487
3878
|
const sel = body.selections ?? [];
|
|
3488
3879
|
let total = c.base_price;
|
|
3489
3880
|
for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
|
|
3490
|
-
return {
|
|
3881
|
+
return {
|
|
3882
|
+
final_price: total,
|
|
3883
|
+
base_price: c.base_price,
|
|
3884
|
+
components_total: money(parseFloat(total) - parseFloat(c.base_price))
|
|
3885
|
+
};
|
|
3491
3886
|
},
|
|
3492
3887
|
listTaxonomies(businessId) {
|
|
3493
3888
|
return registry.taxonomies.list((t) => t.business_id === businessId);
|
|
3494
3889
|
},
|
|
3495
3890
|
searchTaxonomies(businessId, q) {
|
|
3496
3891
|
const term = q.toLowerCase();
|
|
3497
|
-
return registry.taxonomies.list(
|
|
3892
|
+
return registry.taxonomies.list(
|
|
3893
|
+
(t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
|
|
3894
|
+
);
|
|
3498
3895
|
},
|
|
3499
3896
|
getTaxonomy(id) {
|
|
3500
3897
|
const t = registry.taxonomies.get(id);
|
|
@@ -3528,7 +3925,11 @@ function createCatalogueService(opts) {
|
|
|
3528
3925
|
listPropertyFacets(businessId) {
|
|
3529
3926
|
const products = productsForBusiness(businessId);
|
|
3530
3927
|
const byCategory = /* @__PURE__ */ new Map();
|
|
3531
|
-
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
|
+
}
|
|
3532
3933
|
return [
|
|
3533
3934
|
{
|
|
3534
3935
|
name: "Category",
|
|
@@ -3547,12 +3948,6 @@ function createCatalogueService(opts) {
|
|
|
3547
3948
|
listOnSale() {
|
|
3548
3949
|
return [];
|
|
3549
3950
|
},
|
|
3550
|
-
validateDiscount(code) {
|
|
3551
|
-
if (code === "WELCOME10") {
|
|
3552
|
-
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
3553
|
-
}
|
|
3554
|
-
return { valid: false, reason: "Code not recognized" };
|
|
3555
|
-
},
|
|
3556
3951
|
getProductDeals() {
|
|
3557
3952
|
return [];
|
|
3558
3953
|
},
|
|
@@ -3562,15 +3957,33 @@ function createCatalogueService(opts) {
|
|
|
3562
3957
|
getCollectionDeals() {
|
|
3563
3958
|
return [];
|
|
3564
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
|
+
},
|
|
3565
3966
|
createQuote(businessId, body) {
|
|
3566
3967
|
const b = body;
|
|
3567
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);
|
|
3568
3974
|
const quote = {
|
|
3569
3975
|
id,
|
|
3976
|
+
quote_id: id,
|
|
3570
3977
|
business_id: businessId,
|
|
3571
|
-
product_id:
|
|
3978
|
+
product_id: product?.id ?? productId,
|
|
3572
3979
|
status: "fresh",
|
|
3573
|
-
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 },
|
|
3574
3987
|
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
|
|
3575
3988
|
created_at: clock.iso()
|
|
3576
3989
|
};
|
|
@@ -3586,7 +3999,11 @@ function createCatalogueService(opts) {
|
|
|
3586
3999
|
refreshQuote(id) {
|
|
3587
4000
|
const q = registry.quotes.get(id);
|
|
3588
4001
|
if (!q) throw notFound(`Quote ${id} not found`);
|
|
3589
|
-
const refreshed = {
|
|
4002
|
+
const refreshed = {
|
|
4003
|
+
...q,
|
|
4004
|
+
status: "fresh",
|
|
4005
|
+
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
|
|
4006
|
+
};
|
|
3590
4007
|
registry.quotes.put(id, refreshed);
|
|
3591
4008
|
return refreshed;
|
|
3592
4009
|
}
|
|
@@ -4611,67 +5028,82 @@ function createLiteService(opts) {
|
|
|
4611
5028
|
|
|
4612
5029
|
// src/mock/seeds/default.ts
|
|
4613
5030
|
var ISO = "2026-01-01T00:00:00.000Z";
|
|
4614
|
-
var
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
{
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
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
|
+
}
|
|
4642
5062
|
function seedDefault(registry) {
|
|
4643
|
-
const businessId = "bus_default_akua_bakery";
|
|
4644
5063
|
const business = {
|
|
4645
|
-
id:
|
|
5064
|
+
id: BUSINESS_ID,
|
|
4646
5065
|
name: "Akua's Bakery",
|
|
4647
5066
|
handle: "akua",
|
|
4648
5067
|
business_type: "eatery",
|
|
4649
5068
|
email: "hello@akuasbakery.test",
|
|
4650
5069
|
default_currency: "GHS",
|
|
4651
|
-
default_phone: "+
|
|
5070
|
+
default_phone: "+233 244 000 000",
|
|
4652
5071
|
default_address: "12 Independence Ave, Accra",
|
|
4653
5072
|
default_offers_table_service: true,
|
|
4654
5073
|
default_accepts_online_orders: true,
|
|
4655
|
-
image: "
|
|
5074
|
+
image: img("1568254183919-78a4f43a2877"),
|
|
4656
5075
|
status: "active",
|
|
4657
5076
|
created_at: ISO,
|
|
4658
5077
|
updated_at: ISO,
|
|
4659
5078
|
created_by: "seed",
|
|
4660
|
-
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
|
+
},
|
|
4661
5086
|
is_online_only: false,
|
|
4662
5087
|
enabled_payment_types: ["mobile_money", "card", "cash"],
|
|
4663
5088
|
default_location_settings: {},
|
|
4664
5089
|
country_code: "GH",
|
|
4665
5090
|
timezone: "Africa/Accra"
|
|
4666
5091
|
};
|
|
4667
|
-
registry.businesses.put(
|
|
4668
|
-
const
|
|
4669
|
-
|
|
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) {
|
|
4670
5102
|
const id = `cat_${c.slug}`;
|
|
4671
|
-
|
|
5103
|
+
catIds.set(c.slug, id);
|
|
4672
5104
|
const cat = {
|
|
4673
5105
|
id,
|
|
4674
|
-
business_id:
|
|
5106
|
+
business_id: BUSINESS_ID,
|
|
4675
5107
|
name: c.name,
|
|
4676
5108
|
slug: c.slug,
|
|
4677
5109
|
description: c.description,
|
|
@@ -4683,99 +5115,354 @@ function seedDefault(registry) {
|
|
|
4683
5115
|
};
|
|
4684
5116
|
registry.categories.put(id, cat);
|
|
4685
5117
|
}
|
|
4686
|
-
const
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
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 = {
|
|
4716
5431
|
id: "col_featured",
|
|
4717
|
-
business_id:
|
|
4718
|
-
name: "Featured",
|
|
5432
|
+
business_id: BUSINESS_ID,
|
|
5433
|
+
name: "Signature & Featured",
|
|
4719
5434
|
slug: "featured",
|
|
4720
|
-
description: "Our most
|
|
4721
|
-
product_ids:
|
|
5435
|
+
description: "Our most-loved bakes and a few seasonal stars.",
|
|
5436
|
+
product_ids: featuredIds,
|
|
4722
5437
|
is_active: true,
|
|
4723
5438
|
created_at: ISO,
|
|
4724
5439
|
updated_at: ISO
|
|
4725
5440
|
};
|
|
4726
|
-
registry.collections.put(
|
|
4727
|
-
for (const pid of
|
|
5441
|
+
registry.collections.put(featured.id, featured);
|
|
5442
|
+
for (const pid of featuredIds) {
|
|
4728
5443
|
const p = registry.products.get(pid);
|
|
4729
5444
|
if (p) {
|
|
4730
|
-
p.collection_ids.push(
|
|
5445
|
+
p.collection_ids.push(featured.id);
|
|
4731
5446
|
registry.products.put(pid, p);
|
|
4732
5447
|
}
|
|
4733
5448
|
}
|
|
4734
|
-
const tasting = {
|
|
4735
|
-
id: "svc_tasting",
|
|
4736
|
-
business_id: businessId,
|
|
4737
|
-
name: "Bakery Tasting Session",
|
|
4738
|
-
description: "Walk through our pastry menu with our head baker.",
|
|
4739
|
-
duration_minutes: 60,
|
|
4740
|
-
price: "150.00",
|
|
4741
|
-
is_available: true
|
|
4742
|
-
};
|
|
4743
|
-
registry.services.put(tasting.id, tasting);
|
|
4744
|
-
const cake = {
|
|
4745
|
-
id: "svc_custom_cake",
|
|
4746
|
-
business_id: businessId,
|
|
4747
|
-
name: "Custom Cake Consultation",
|
|
4748
|
-
description: "Design a custom cake for your event.",
|
|
4749
|
-
duration_minutes: 30,
|
|
4750
|
-
price: "0.00",
|
|
4751
|
-
is_available: true
|
|
4752
|
-
};
|
|
4753
|
-
registry.services.put(cake.id, cake);
|
|
4754
|
-
const sizeAxisId = "axis_size";
|
|
4755
|
-
const flavourAxisId = "axis_flavour";
|
|
4756
|
-
const sizeValues = [
|
|
4757
|
-
{ id: "axv_size_small", name: 'Small (8")' },
|
|
4758
|
-
{ id: "axv_size_medium", name: 'Medium (10")' },
|
|
4759
|
-
{ id: "axv_size_large", name: 'Large (12")' }
|
|
4760
|
-
];
|
|
4761
|
-
const flavourValues = [
|
|
4762
|
-
{ id: "axv_flavour_chocolate", name: "Chocolate" },
|
|
4763
|
-
{ id: "axv_flavour_vanilla", name: "Vanilla" },
|
|
4764
|
-
{ id: "axv_flavour_red_velvet", name: "Red Velvet" }
|
|
4765
|
-
];
|
|
4766
5449
|
const sizeAxis = {
|
|
4767
|
-
id:
|
|
4768
|
-
business_id:
|
|
5450
|
+
id: "axis_cake_size",
|
|
5451
|
+
business_id: BUSINESS_ID,
|
|
4769
5452
|
product_id: "prod_birthday-cake",
|
|
4770
5453
|
name: "Size",
|
|
4771
5454
|
display_order: 0,
|
|
4772
5455
|
affects_recipe: false,
|
|
4773
|
-
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) => ({
|
|
4774
5461
|
id: v.id,
|
|
4775
|
-
business_id:
|
|
4776
|
-
axis_id:
|
|
5462
|
+
business_id: BUSINESS_ID,
|
|
5463
|
+
axis_id: "axis_cake_size",
|
|
4777
5464
|
name: v.name,
|
|
4778
|
-
display_order:
|
|
5465
|
+
display_order: v.display,
|
|
4779
5466
|
created_at: ISO,
|
|
4780
5467
|
updated_at: ISO
|
|
4781
5468
|
})),
|
|
@@ -4783,18 +5470,22 @@ function seedDefault(registry) {
|
|
|
4783
5470
|
updated_at: ISO
|
|
4784
5471
|
};
|
|
4785
5472
|
const flavourAxis = {
|
|
4786
|
-
id:
|
|
4787
|
-
business_id:
|
|
5473
|
+
id: "axis_cake_flavour",
|
|
5474
|
+
business_id: BUSINESS_ID,
|
|
4788
5475
|
product_id: "prod_birthday-cake",
|
|
4789
5476
|
name: "Flavour",
|
|
4790
5477
|
display_order: 1,
|
|
4791
5478
|
affects_recipe: true,
|
|
4792
|
-
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) => ({
|
|
4793
5484
|
id: v.id,
|
|
4794
|
-
business_id:
|
|
4795
|
-
axis_id:
|
|
5485
|
+
business_id: BUSINESS_ID,
|
|
5486
|
+
axis_id: "axis_cake_flavour",
|
|
4796
5487
|
name: v.name,
|
|
4797
|
-
display_order:
|
|
5488
|
+
display_order: v.display,
|
|
4798
5489
|
created_at: ISO,
|
|
4799
5490
|
updated_at: ISO
|
|
4800
5491
|
})),
|
|
@@ -4803,40 +5494,42 @@ function seedDefault(registry) {
|
|
|
4803
5494
|
};
|
|
4804
5495
|
registry.variantAxes.put(sizeAxis.id, sizeAxis);
|
|
4805
5496
|
registry.variantAxes.put(flavourAxis.id, flavourAxis);
|
|
4806
|
-
const
|
|
4807
|
-
const
|
|
4808
|
-
let
|
|
4809
|
-
for (
|
|
4810
|
-
for (
|
|
4811
|
-
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}`;
|
|
4812
5505
|
const variant = {
|
|
4813
5506
|
id,
|
|
4814
5507
|
product_id: "prod_birthday-cake",
|
|
4815
|
-
business_id:
|
|
4816
|
-
name: `${
|
|
4817
|
-
sku: `BIRTHDAY-${
|
|
4818
|
-
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",
|
|
4819
5512
|
component_multiplier: "1.00",
|
|
4820
|
-
is_default:
|
|
5513
|
+
is_default: firstVariant,
|
|
4821
5514
|
is_active: true,
|
|
4822
|
-
axis_value_ids: [
|
|
5515
|
+
axis_value_ids: [sz.id, fl.id],
|
|
4823
5516
|
created_at: ISO,
|
|
4824
5517
|
updated_at: ISO
|
|
4825
5518
|
};
|
|
4826
5519
|
registry.variants.put(id, variant);
|
|
4827
|
-
|
|
4828
|
-
|
|
5520
|
+
cakeVariantIds.push(id);
|
|
5521
|
+
firstVariant = false;
|
|
4829
5522
|
}
|
|
4830
5523
|
}
|
|
4831
|
-
const
|
|
4832
|
-
if (
|
|
4833
|
-
|
|
4834
|
-
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);
|
|
4835
5528
|
}
|
|
4836
5529
|
const toppingsAddOn = {
|
|
4837
5530
|
id: "addon_toppings",
|
|
4838
|
-
business_id:
|
|
4839
|
-
name: "
|
|
5531
|
+
business_id: BUSINESS_ID,
|
|
5532
|
+
name: "Cake toppings",
|
|
4840
5533
|
is_multiple_allowed: true,
|
|
4841
5534
|
is_required: false,
|
|
4842
5535
|
is_mutually_exclusive: false,
|
|
@@ -4845,15 +5538,14 @@ function seedDefault(registry) {
|
|
|
4845
5538
|
created_at: ISO,
|
|
4846
5539
|
updated_at: ISO,
|
|
4847
5540
|
options: [
|
|
4848
|
-
{ id: "addopt_strawberry", add_on_id: "addon_toppings", business_id:
|
|
4849
|
-
{ id: "addopt_choc_chips", add_on_id: "addon_toppings", business_id:
|
|
4850
|
-
{ 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 }
|
|
4851
5544
|
]
|
|
4852
5545
|
};
|
|
4853
|
-
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
4854
5546
|
const milkAddOn = {
|
|
4855
5547
|
id: "addon_milk",
|
|
4856
|
-
business_id:
|
|
5548
|
+
business_id: BUSINESS_ID,
|
|
4857
5549
|
name: "Milk",
|
|
4858
5550
|
is_multiple_allowed: false,
|
|
4859
5551
|
is_required: false,
|
|
@@ -4863,17 +5555,17 @@ function seedDefault(registry) {
|
|
|
4863
5555
|
created_at: ISO,
|
|
4864
5556
|
updated_at: ISO,
|
|
4865
5557
|
options: [
|
|
4866
|
-
{ id: "addopt_whole", add_on_id: "addon_milk", business_id:
|
|
4867
|
-
{ id: "addopt_oat", add_on_id: "addon_milk", business_id:
|
|
4868
|
-
{ 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 }
|
|
4869
5561
|
]
|
|
4870
5562
|
};
|
|
5563
|
+
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
4871
5564
|
registry.addOns.put(milkAddOn.id, milkAddOn);
|
|
4872
5565
|
const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
|
|
4873
5566
|
const drinkProductIds = ["prod_coffee", "prod_tea"];
|
|
4874
5567
|
for (const pid of cakeProductIds) {
|
|
4875
|
-
|
|
4876
|
-
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
|
|
5568
|
+
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
|
|
4877
5569
|
const p = registry.products.get(pid);
|
|
4878
5570
|
if (p) {
|
|
4879
5571
|
p.add_on_ids.push(toppingsAddOn.id);
|
|
@@ -4881,8 +5573,7 @@ function seedDefault(registry) {
|
|
|
4881
5573
|
}
|
|
4882
5574
|
}
|
|
4883
5575
|
for (const pid of drinkProductIds) {
|
|
4884
|
-
|
|
4885
|
-
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
|
|
5576
|
+
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
|
|
4886
5577
|
const p = registry.products.get(pid);
|
|
4887
5578
|
if (p) {
|
|
4888
5579
|
p.add_on_ids.push(milkAddOn.id);
|
|
@@ -4891,7 +5582,7 @@ function seedDefault(registry) {
|
|
|
4891
5582
|
}
|
|
4892
5583
|
const breakfastBundle = {
|
|
4893
5584
|
id: "bnd_breakfast_box",
|
|
4894
|
-
business_id:
|
|
5585
|
+
business_id: BUSINESS_ID,
|
|
4895
5586
|
product_id: "prod_breakfast-box",
|
|
4896
5587
|
name: "Breakfast Box",
|
|
4897
5588
|
slug: "breakfast-box",
|
|
@@ -4903,29 +5594,9 @@ function seedDefault(registry) {
|
|
|
4903
5594
|
updated_at: ISO
|
|
4904
5595
|
};
|
|
4905
5596
|
registry.bundles.put(breakfastBundle.id, breakfastBundle);
|
|
4906
|
-
const breakfastProduct = {
|
|
4907
|
-
id: "prod_breakfast-box",
|
|
4908
|
-
business_id: businessId,
|
|
4909
|
-
name: "Breakfast Box",
|
|
4910
|
-
slug: "breakfast-box",
|
|
4911
|
-
description: "Bundled breakfast for one.",
|
|
4912
|
-
product_type: "bundle",
|
|
4913
|
-
base_price: "30.00",
|
|
4914
|
-
currency: "GHS",
|
|
4915
|
-
image: "https://mock.cimplify.io/img/breakfast-box.jpg",
|
|
4916
|
-
images: ["https://mock.cimplify.io/img/breakfast-box.jpg"],
|
|
4917
|
-
is_available: true,
|
|
4918
|
-
category_ids: [categoryIdsBySlug.get("pastries")],
|
|
4919
|
-
collection_ids: [],
|
|
4920
|
-
add_on_ids: [],
|
|
4921
|
-
variant_ids: [],
|
|
4922
|
-
created_at: ISO,
|
|
4923
|
-
updated_at: ISO
|
|
4924
|
-
};
|
|
4925
|
-
registry.products.put(breakfastProduct.id, breakfastProduct);
|
|
4926
5597
|
const pastryComposite = {
|
|
4927
5598
|
id: "comp_pastry_box",
|
|
4928
|
-
business_id:
|
|
5599
|
+
business_id: BUSINESS_ID,
|
|
4929
5600
|
product_id: "prod_pastry-box",
|
|
4930
5601
|
base_price: "60.00",
|
|
4931
5602
|
pricing_mode: "additive",
|
|
@@ -4937,43 +5608,51 @@ function seedDefault(registry) {
|
|
|
4937
5608
|
name: "Choose your pastries",
|
|
4938
5609
|
min: 6,
|
|
4939
5610
|
max: 12,
|
|
4940
|
-
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
|
+
]
|
|
4941
5620
|
}
|
|
4942
5621
|
],
|
|
4943
5622
|
created_at: ISO,
|
|
4944
5623
|
updated_at: ISO
|
|
4945
5624
|
};
|
|
4946
5625
|
registry.composites.put(pastryComposite.id, pastryComposite);
|
|
4947
|
-
const
|
|
4948
|
-
id: "
|
|
4949
|
-
business_id:
|
|
4950
|
-
name: "
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
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
|
|
5634
|
+
};
|
|
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
|
|
4965
5643
|
};
|
|
4966
|
-
registry.
|
|
5644
|
+
registry.services.put(tasting.id, tasting);
|
|
5645
|
+
registry.services.put(consult.id, consult);
|
|
4967
5646
|
const tags = [
|
|
4968
|
-
{ id: "tag_signature", business_id:
|
|
4969
|
-
{ id: "tag_vegan", business_id:
|
|
4970
|
-
{ id: "tag_gluten_free", business_id:
|
|
4971
|
-
{ 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 }
|
|
4972
5651
|
];
|
|
4973
5652
|
for (const t of tags) registry.tags.put(t.id, t);
|
|
4974
5653
|
const baked = {
|
|
4975
5654
|
id: "tax_baked_goods",
|
|
4976
|
-
business_id:
|
|
5655
|
+
business_id: BUSINESS_ID,
|
|
4977
5656
|
name: "Baked Goods",
|
|
4978
5657
|
slug: "baked-goods",
|
|
4979
5658
|
path: ["Food", "Baked Goods"],
|
|
@@ -4983,7 +5662,7 @@ function seedDefault(registry) {
|
|
|
4983
5662
|
};
|
|
4984
5663
|
const beverages = {
|
|
4985
5664
|
id: "tax_beverages",
|
|
4986
|
-
business_id:
|
|
5665
|
+
business_id: BUSINESS_ID,
|
|
4987
5666
|
name: "Beverages",
|
|
4988
5667
|
slug: "beverages",
|
|
4989
5668
|
path: ["Food", "Beverages"],
|
|
@@ -4996,13 +5675,13 @@ function seedDefault(registry) {
|
|
|
4996
5675
|
const attrs = [
|
|
4997
5676
|
{
|
|
4998
5677
|
id: "attr_allergens",
|
|
4999
|
-
business_id:
|
|
5678
|
+
business_id: BUSINESS_ID,
|
|
5000
5679
|
namespace: "core",
|
|
5001
5680
|
name: "Allergens",
|
|
5002
5681
|
slug: "allergens",
|
|
5003
5682
|
description: "Common allergens present in this item.",
|
|
5004
5683
|
attribute_type: "multi_select",
|
|
5005
|
-
options: ["wheat", "eggs", "dairy", "nuts", "soy"],
|
|
5684
|
+
options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
|
|
5006
5685
|
is_required: false,
|
|
5007
5686
|
is_filterable: true,
|
|
5008
5687
|
visibility: "public",
|
|
@@ -5013,7 +5692,7 @@ function seedDefault(registry) {
|
|
|
5013
5692
|
},
|
|
5014
5693
|
{
|
|
5015
5694
|
id: "attr_shelf_life",
|
|
5016
|
-
business_id:
|
|
5695
|
+
business_id: BUSINESS_ID,
|
|
5017
5696
|
namespace: "core",
|
|
5018
5697
|
name: "Shelf life",
|
|
5019
5698
|
slug: "shelf-life",
|
|
@@ -5028,7 +5707,7 @@ function seedDefault(registry) {
|
|
|
5028
5707
|
},
|
|
5029
5708
|
{
|
|
5030
5709
|
id: "attr_caffeine",
|
|
5031
|
-
business_id:
|
|
5710
|
+
business_id: BUSINESS_ID,
|
|
5032
5711
|
namespace: "core",
|
|
5033
5712
|
name: "Caffeine",
|
|
5034
5713
|
slug: "caffeine",
|
|
@@ -5043,7 +5722,7 @@ function seedDefault(registry) {
|
|
|
5043
5722
|
},
|
|
5044
5723
|
{
|
|
5045
5724
|
id: "attr_seasonal",
|
|
5046
|
-
business_id:
|
|
5725
|
+
business_id: BUSINESS_ID,
|
|
5047
5726
|
namespace: "core",
|
|
5048
5727
|
name: "Seasonal",
|
|
5049
5728
|
slug: "seasonal",
|
|
@@ -5061,10 +5740,10 @@ function seedDefault(registry) {
|
|
|
5061
5740
|
const articles = [
|
|
5062
5741
|
{
|
|
5063
5742
|
id: "kb_delivery",
|
|
5064
|
-
business_id:
|
|
5065
|
-
title: "Delivery &
|
|
5743
|
+
business_id: BUSINESS_ID,
|
|
5744
|
+
title: "Delivery & pickup",
|
|
5066
5745
|
slug: "delivery-pickup",
|
|
5067
|
-
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.",
|
|
5068
5747
|
category: "logistics",
|
|
5069
5748
|
tags: ["delivery", "pickup"],
|
|
5070
5749
|
created_at: ISO,
|
|
@@ -5072,10 +5751,10 @@ function seedDefault(registry) {
|
|
|
5072
5751
|
},
|
|
5073
5752
|
{
|
|
5074
5753
|
id: "kb_custom_cakes",
|
|
5075
|
-
business_id:
|
|
5076
|
-
title: "
|
|
5754
|
+
business_id: BUSINESS_ID,
|
|
5755
|
+
title: "Custom cakes & celebrations",
|
|
5077
5756
|
slug: "custom-cakes",
|
|
5078
|
-
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.",
|
|
5079
5758
|
category: "products",
|
|
5080
5759
|
tags: ["cakes", "custom"],
|
|
5081
5760
|
created_at: ISO,
|
|
@@ -5083,10 +5762,10 @@ function seedDefault(registry) {
|
|
|
5083
5762
|
},
|
|
5084
5763
|
{
|
|
5085
5764
|
id: "kb_allergens",
|
|
5086
|
-
business_id:
|
|
5087
|
-
title: "
|
|
5765
|
+
business_id: BUSINESS_ID,
|
|
5766
|
+
title: "Allergen information",
|
|
5088
5767
|
slug: "allergens",
|
|
5089
|
-
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.",
|
|
5090
5769
|
category: "products",
|
|
5091
5770
|
tags: ["allergens", "safety"],
|
|
5092
5771
|
created_at: ISO,
|
|
@@ -5094,7 +5773,7 @@ function seedDefault(registry) {
|
|
|
5094
5773
|
}
|
|
5095
5774
|
];
|
|
5096
5775
|
for (const a of articles) registry.knowledgeArticles.put(a.id, a);
|
|
5097
|
-
return { businessId };
|
|
5776
|
+
return { businessId: BUSINESS_ID };
|
|
5098
5777
|
}
|
|
5099
5778
|
|
|
5100
5779
|
// src/mock/seeds/empty.ts
|
|
@@ -5140,7 +5819,7 @@ function seedReesa(registry) {
|
|
|
5140
5819
|
default_address: "5 Allen Avenue, Ikeja, Lagos",
|
|
5141
5820
|
default_offers_table_service: false,
|
|
5142
5821
|
default_accepts_online_orders: true,
|
|
5143
|
-
image: "
|
|
5822
|
+
image: "/img/reesa-logo.png",
|
|
5144
5823
|
status: "active",
|
|
5145
5824
|
created_at: ISO3,
|
|
5146
5825
|
updated_at: ISO3,
|
|
@@ -5196,8 +5875,8 @@ function seedReesa(registry) {
|
|
|
5196
5875
|
product_type: "product",
|
|
5197
5876
|
base_price: p.price,
|
|
5198
5877
|
currency: "NGN",
|
|
5199
|
-
image:
|
|
5200
|
-
images: [
|
|
5878
|
+
image: `/img/reesa/${p.slug}.jpg`,
|
|
5879
|
+
images: [`/img/reesa/${p.slug}.jpg`],
|
|
5201
5880
|
is_available: true,
|
|
5202
5881
|
category_ids: [catId],
|
|
5203
5882
|
collection_ids: [],
|
|
@@ -5420,8 +6099,8 @@ function seedReesa(registry) {
|
|
|
5420
6099
|
product_type: "bundle",
|
|
5421
6100
|
base_price: "55000.00",
|
|
5422
6101
|
currency: "NGN",
|
|
5423
|
-
image: "
|
|
5424
|
-
images: ["
|
|
6102
|
+
image: "/img/reesa/owambe.jpg",
|
|
6103
|
+
images: ["/img/reesa/owambe.jpg"],
|
|
5425
6104
|
is_available: true,
|
|
5426
6105
|
category_ids: [catIdsBySlug.get("apparel")],
|
|
5427
6106
|
collection_ids: [],
|
|
@@ -5552,7 +6231,7 @@ function seedRestaurant(registry) {
|
|
|
5552
6231
|
default_address: "8 Ring Road, East Legon, Accra",
|
|
5553
6232
|
default_offers_table_service: true,
|
|
5554
6233
|
default_accepts_online_orders: true,
|
|
5555
|
-
image: "
|
|
6234
|
+
image: "/img/mamas-logo.png",
|
|
5556
6235
|
status: "active",
|
|
5557
6236
|
created_at: ISO4,
|
|
5558
6237
|
updated_at: ISO4,
|
|
@@ -5623,8 +6302,8 @@ function seedRestaurant(registry) {
|
|
|
5623
6302
|
product_type: "product",
|
|
5624
6303
|
base_price: p.price,
|
|
5625
6304
|
currency: "GHS",
|
|
5626
|
-
image:
|
|
5627
|
-
images: [
|
|
6305
|
+
image: `/img/mamas/${p.slug}.jpg`,
|
|
6306
|
+
images: [`/img/mamas/${p.slug}.jpg`],
|
|
5628
6307
|
is_available: true,
|
|
5629
6308
|
category_ids: [catId],
|
|
5630
6309
|
collection_ids: [],
|
|
@@ -5862,8 +6541,8 @@ function seedRestaurant(registry) {
|
|
|
5862
6541
|
product_type: "bundle",
|
|
5863
6542
|
base_price: "180.00",
|
|
5864
6543
|
currency: "GHS",
|
|
5865
|
-
image: "
|
|
5866
|
-
images: ["
|
|
6544
|
+
image: "/img/mamas/feast.jpg",
|
|
6545
|
+
images: ["/img/mamas/feast.jpg"],
|
|
5867
6546
|
is_available: true,
|
|
5868
6547
|
category_ids: [catIds.get("mains")],
|
|
5869
6548
|
collection_ids: [],
|
|
@@ -5898,8 +6577,8 @@ function seedRestaurant(registry) {
|
|
|
5898
6577
|
product_type: "composite",
|
|
5899
6578
|
base_price: "0.00",
|
|
5900
6579
|
currency: "GHS",
|
|
5901
|
-
image: "
|
|
5902
|
-
images: ["
|
|
6580
|
+
image: "/img/mamas/byo.jpg",
|
|
6581
|
+
images: ["/img/mamas/byo.jpg"],
|
|
5903
6582
|
is_available: true,
|
|
5904
6583
|
category_ids: [catIds.get("mains")],
|
|
5905
6584
|
collection_ids: [],
|
|
@@ -6011,7 +6690,7 @@ function seedRetail(registry) {
|
|
|
6011
6690
|
default_address: "Atomic Junction, Accra",
|
|
6012
6691
|
default_offers_table_service: false,
|
|
6013
6692
|
default_accepts_online_orders: true,
|
|
6014
|
-
image: "
|
|
6693
|
+
image: "/img/currents-logo.png",
|
|
6015
6694
|
status: "active",
|
|
6016
6695
|
created_at: ISO5,
|
|
6017
6696
|
updated_at: ISO5,
|
|
@@ -6081,8 +6760,8 @@ function seedRetail(registry) {
|
|
|
6081
6760
|
product_type: "product",
|
|
6082
6761
|
base_price: p.price,
|
|
6083
6762
|
currency: "GHS",
|
|
6084
|
-
image:
|
|
6085
|
-
images: [
|
|
6763
|
+
image: `/img/currents/${p.slug}.jpg`,
|
|
6764
|
+
images: [`/img/currents/${p.slug}.jpg`],
|
|
6086
6765
|
is_available: true,
|
|
6087
6766
|
category_ids: [catId],
|
|
6088
6767
|
collection_ids: [],
|
|
@@ -6345,8 +7024,8 @@ function seedRetail(registry) {
|
|
|
6345
7024
|
product_type: "bundle",
|
|
6346
7025
|
base_price: "11910.00",
|
|
6347
7026
|
currency: "GHS",
|
|
6348
|
-
image: "
|
|
6349
|
-
images: ["
|
|
7027
|
+
image: "/img/currents/wfh.jpg",
|
|
7028
|
+
images: ["/img/currents/wfh.jpg"],
|
|
6350
7029
|
is_available: true,
|
|
6351
7030
|
category_ids: [catIds.get("laptops")],
|
|
6352
7031
|
collection_ids: [],
|
|
@@ -6379,8 +7058,8 @@ function seedRetail(registry) {
|
|
|
6379
7058
|
product_type: "bundle",
|
|
6380
7059
|
base_price: "4200.00",
|
|
6381
7060
|
currency: "GHS",
|
|
6382
|
-
image: "
|
|
6383
|
-
images: ["
|
|
7061
|
+
image: "/img/currents/game-pack.jpg",
|
|
7062
|
+
images: ["/img/currents/game-pack.jpg"],
|
|
6384
7063
|
is_available: true,
|
|
6385
7064
|
category_ids: [catIds.get("gaming")],
|
|
6386
7065
|
collection_ids: [],
|
|
@@ -6483,7 +7162,7 @@ function seedServices(registry) {
|
|
|
6483
7162
|
default_address: "12 Cantonments Road, Accra",
|
|
6484
7163
|
default_offers_table_service: false,
|
|
6485
7164
|
default_accepts_online_orders: true,
|
|
6486
|
-
image: "
|
|
7165
|
+
image: "/img/serene-logo.png",
|
|
6487
7166
|
status: "active",
|
|
6488
7167
|
created_at: ISO6,
|
|
6489
7168
|
updated_at: ISO6,
|
|
@@ -6551,8 +7230,8 @@ function seedServices(registry) {
|
|
|
6551
7230
|
product_type: "service",
|
|
6552
7231
|
base_price: s.price,
|
|
6553
7232
|
currency: "GHS",
|
|
6554
|
-
image:
|
|
6555
|
-
images: [
|
|
7233
|
+
image: `/img/serene/${s.slug}.jpg`,
|
|
7234
|
+
images: [`/img/serene/${s.slug}.jpg`],
|
|
6556
7235
|
is_available: true,
|
|
6557
7236
|
category_ids: [catId],
|
|
6558
7237
|
collection_ids: [],
|
|
@@ -6688,8 +7367,8 @@ function seedServices(registry) {
|
|
|
6688
7367
|
product_type: "bundle",
|
|
6689
7368
|
base_price: "480.00",
|
|
6690
7369
|
currency: "GHS",
|
|
6691
|
-
image: "
|
|
6692
|
-
images: ["
|
|
7370
|
+
image: "/img/serene/spa-day.jpg",
|
|
7371
|
+
images: ["/img/serene/spa-day.jpg"],
|
|
6693
7372
|
is_available: true,
|
|
6694
7373
|
category_ids: [catIds.get("wellness")],
|
|
6695
7374
|
collection_ids: [],
|
|
@@ -6722,8 +7401,8 @@ function seedServices(registry) {
|
|
|
6722
7401
|
product_type: "bundle",
|
|
6723
7402
|
base_price: "850.50",
|
|
6724
7403
|
currency: "GHS",
|
|
6725
|
-
image: "
|
|
6726
|
-
images: ["
|
|
7404
|
+
image: "/img/serene/couples.jpg",
|
|
7405
|
+
images: ["/img/serene/couples.jpg"],
|
|
6727
7406
|
is_available: true,
|
|
6728
7407
|
category_ids: [catIds.get("wellness")],
|
|
6729
7408
|
collection_ids: [],
|
|
@@ -6758,8 +7437,8 @@ function seedServices(registry) {
|
|
|
6758
7437
|
product_type: "composite",
|
|
6759
7438
|
base_price: "0.00",
|
|
6760
7439
|
currency: "GHS",
|
|
6761
|
-
image: "
|
|
6762
|
-
images: ["
|
|
7440
|
+
image: "/img/serene/custom.jpg",
|
|
7441
|
+
images: ["/img/serene/custom.jpg"],
|
|
6763
7442
|
is_available: true,
|
|
6764
7443
|
category_ids: [catIds.get("wellness")],
|
|
6765
7444
|
collection_ids: [],
|
|
@@ -6862,7 +7541,7 @@ function seedGrocery(registry) {
|
|
|
6862
7541
|
default_address: "Spintex Road, Accra",
|
|
6863
7542
|
default_offers_table_service: false,
|
|
6864
7543
|
default_accepts_online_orders: true,
|
|
6865
|
-
image: "
|
|
7544
|
+
image: "/img/freshmart-logo.png",
|
|
6866
7545
|
status: "active",
|
|
6867
7546
|
created_at: ISO7,
|
|
6868
7547
|
updated_at: ISO7,
|
|
@@ -6937,8 +7616,8 @@ function seedGrocery(registry) {
|
|
|
6937
7616
|
product_type: "product",
|
|
6938
7617
|
base_price: p.price,
|
|
6939
7618
|
currency: "GHS",
|
|
6940
|
-
image:
|
|
6941
|
-
images: [
|
|
7619
|
+
image: `/img/freshmart/${p.slug}.jpg`,
|
|
7620
|
+
images: [`/img/freshmart/${p.slug}.jpg`],
|
|
6942
7621
|
is_available: true,
|
|
6943
7622
|
category_ids: [catId],
|
|
6944
7623
|
collection_ids: [],
|
|
@@ -7021,8 +7700,8 @@ function seedGrocery(registry) {
|
|
|
7021
7700
|
product_type: "bundle",
|
|
7022
7701
|
base_price: "199.50",
|
|
7023
7702
|
currency: "GHS",
|
|
7024
|
-
image: "
|
|
7025
|
-
images: ["
|
|
7703
|
+
image: "/img/freshmart/essentials.jpg",
|
|
7704
|
+
images: ["/img/freshmart/essentials.jpg"],
|
|
7026
7705
|
is_available: true,
|
|
7027
7706
|
category_ids: [catIds.get("pantry")],
|
|
7028
7707
|
collection_ids: [],
|
|
@@ -7197,43 +7876,47 @@ function createDeps(options = {}) {
|
|
|
7197
7876
|
}
|
|
7198
7877
|
|
|
7199
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
|
+
}
|
|
7200
7900
|
function createMockApp(options = {}) {
|
|
7201
7901
|
const deps = createDeps(options);
|
|
7202
7902
|
const app = new Hono2();
|
|
7203
|
-
const
|
|
7204
|
-
if (
|
|
7205
|
-
app.use("*", async (c, next) => {
|
|
7206
|
-
const origin = c.req.header("origin") ?? "";
|
|
7207
|
-
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7208
|
-
const headers = {
|
|
7209
|
-
"Access-Control-Allow-Origin": allow,
|
|
7210
|
-
"Access-Control-Allow-Credentials": "true",
|
|
7211
|
-
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7212
|
-
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7213
|
-
"Access-Control-Expose-Headers": "x-session-token",
|
|
7214
|
-
"Access-Control-Max-Age": "86400",
|
|
7215
|
-
Vary: "Origin"
|
|
7216
|
-
};
|
|
7217
|
-
if (c.req.method === "OPTIONS") {
|
|
7218
|
-
return new Response(null, { status: 204, headers });
|
|
7219
|
-
}
|
|
7220
|
-
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7221
|
-
await next();
|
|
7222
|
-
});
|
|
7223
|
-
}
|
|
7903
|
+
const cors = options.cors ?? "*";
|
|
7904
|
+
if (cors) app.use("*", corsMiddleware(cors));
|
|
7224
7905
|
app.get(
|
|
7225
7906
|
"/",
|
|
7226
7907
|
(c) => c.json({
|
|
7227
|
-
name:
|
|
7228
|
-
version: "0.36.0",
|
|
7908
|
+
name: PACKAGE_NAME,
|
|
7229
7909
|
docs: "https://github.com/cimplify/cimplify",
|
|
7230
7910
|
api: "/api/v1",
|
|
7231
7911
|
admin: "/_mock",
|
|
7232
7912
|
elements: "/elements",
|
|
7233
|
-
|
|
7913
|
+
images: "/img/<slug>",
|
|
7914
|
+
seed_business_id: deps.defaultBusinessId,
|
|
7915
|
+
time: deps.clock.iso()
|
|
7234
7916
|
})
|
|
7235
7917
|
);
|
|
7236
7918
|
app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
|
|
7919
|
+
mountImageRoutes(app);
|
|
7237
7920
|
app.route("/api/v1", buildV1(deps));
|
|
7238
7921
|
app.route("/_mock", buildAdmin(deps));
|
|
7239
7922
|
app.route("/elements", buildElements(deps));
|
|
@@ -7241,7 +7924,11 @@ function createMockApp(options = {}) {
|
|
|
7241
7924
|
(c) => c.json(
|
|
7242
7925
|
{
|
|
7243
7926
|
success: false,
|
|
7244
|
-
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
|
+
},
|
|
7245
7932
|
hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
|
|
7246
7933
|
},
|
|
7247
7934
|
501
|