@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/library.js
CHANGED
|
@@ -2986,6 +2986,178 @@ function buildElements(deps) {
|
|
|
2986
2986
|
return app;
|
|
2987
2987
|
}
|
|
2988
2988
|
|
|
2989
|
+
// src/mock/routes/images.ts
|
|
2990
|
+
var SLUG_KEYWORDS = {
|
|
2991
|
+
// ─── Akua's Bakery (default seed) ───
|
|
2992
|
+
sourdough: "sourdough,bread",
|
|
2993
|
+
wheat: "whole-wheat,bread",
|
|
2994
|
+
baguette: "baguette",
|
|
2995
|
+
focaccia: "focaccia,bread",
|
|
2996
|
+
croissant: "croissant",
|
|
2997
|
+
pac: "pain-au-chocolat,croissant",
|
|
2998
|
+
danish: "danish,pastry",
|
|
2999
|
+
cinnamon: "cinnamon-roll",
|
|
3000
|
+
"choc-cake": "chocolate-cake,slice",
|
|
3001
|
+
"red-velvet": "red-velvet-cake",
|
|
3002
|
+
carrot: "carrot-cake",
|
|
3003
|
+
cheesecake: "cheesecake",
|
|
3004
|
+
coffee: "coffee,cup",
|
|
3005
|
+
tea: "tea,cup",
|
|
3006
|
+
sobolo: "hibiscus-drink",
|
|
3007
|
+
oj: "orange-juice,glass",
|
|
3008
|
+
banana: "banana-bread",
|
|
3009
|
+
brownie: "brownie",
|
|
3010
|
+
macaron: "macaron,box",
|
|
3011
|
+
birthday: "birthday-cake",
|
|
3012
|
+
"breakfast-box": "breakfast",
|
|
3013
|
+
"pastry-box": "pastry,assortment",
|
|
3014
|
+
// ─── Mama's Kitchen (restaurant seed) ───
|
|
3015
|
+
kelewele: "fried-plantain,spicy",
|
|
3016
|
+
"spring-rolls": "spring-rolls",
|
|
3017
|
+
"soup-of-the-day": "soup,bowl",
|
|
3018
|
+
"jollof-rice": "jollof-rice",
|
|
3019
|
+
"banku-tilapia": "tilapia,grilled-fish",
|
|
3020
|
+
"fufu-light-soup": "fufu,soup",
|
|
3021
|
+
waakye: "rice,beans,african-food",
|
|
3022
|
+
"grilled-chicken": "grilled-chicken",
|
|
3023
|
+
burger: "burger",
|
|
3024
|
+
"yam-fries": "yam-fries,chips",
|
|
3025
|
+
"fried-plantain": "fried-plantain",
|
|
3026
|
+
salad: "garden-salad",
|
|
3027
|
+
"ice-cream": "ice-cream,scoop",
|
|
3028
|
+
"cake-slice": "chocolate-cake,slice",
|
|
3029
|
+
"fruit-bowl": "fruit-bowl,tropical",
|
|
3030
|
+
"soft-drink": "cola,glass",
|
|
3031
|
+
"fresh-juice": "fresh-juice",
|
|
3032
|
+
water: "water-bottle",
|
|
3033
|
+
feast: "family-meal,buffet",
|
|
3034
|
+
byo: "buffet,plate",
|
|
3035
|
+
// ─── Currents Electronics ───
|
|
3036
|
+
"android-flagship": "smartphone,android",
|
|
3037
|
+
"android-mid": "smartphone",
|
|
3038
|
+
"phone-budget": "phone",
|
|
3039
|
+
"laptop-pro": "laptop,macbook",
|
|
3040
|
+
"laptop-air": "laptop,thin",
|
|
3041
|
+
"laptop-budget": "laptop",
|
|
3042
|
+
"headphones-anc": "headphones",
|
|
3043
|
+
"earbuds-pro": "earbuds,wireless",
|
|
3044
|
+
"speaker-bt": "speaker,bluetooth",
|
|
3045
|
+
"phone-case": "phone-case",
|
|
3046
|
+
"screen-protector": "screen,protector",
|
|
3047
|
+
"fast-charger": "charger,usb",
|
|
3048
|
+
"laptop-sleeve": "laptop-sleeve,case",
|
|
3049
|
+
"wireless-mouse": "wireless-mouse",
|
|
3050
|
+
"mech-keyboard": "mechanical-keyboard",
|
|
3051
|
+
"gaming-controller": "game-controller",
|
|
3052
|
+
"gaming-headset": "gaming-headset",
|
|
3053
|
+
"console-handheld": "handheld-console,gaming",
|
|
3054
|
+
wfh: "desk-setup,laptop",
|
|
3055
|
+
"game-pack": "gaming,console",
|
|
3056
|
+
// ─── Reesa apparel ───
|
|
3057
|
+
"ankara-shirt": "ankara,african-fashion",
|
|
3058
|
+
"agbada-set": "agbada,nigerian-fashion",
|
|
3059
|
+
kaftan: "kaftan,african-dress",
|
|
3060
|
+
"leather-tote": "leather-tote,bag",
|
|
3061
|
+
sandals: "sandals,leather",
|
|
3062
|
+
"beaded-bracelet": "beaded-bracelet",
|
|
3063
|
+
owambe: "african-fashion",
|
|
3064
|
+
// ─── FreshMart ───
|
|
3065
|
+
"tomatoes-1kg": "tomatoes",
|
|
3066
|
+
"onions-1kg": "onions",
|
|
3067
|
+
"ginger-200g": "ginger,root",
|
|
3068
|
+
"bananas-bunch": "bananas",
|
|
3069
|
+
pineapple: "pineapple",
|
|
3070
|
+
"spinach-bunch": "spinach,leaves",
|
|
3071
|
+
"milk-1l": "milk,carton",
|
|
3072
|
+
"yogurt-500g": "yogurt",
|
|
3073
|
+
"eggs-12": "eggs,carton",
|
|
3074
|
+
"cheese-200g": "cheese",
|
|
3075
|
+
"chicken-1kg": "raw-chicken",
|
|
3076
|
+
"beef-mince-500g": "beef,mince",
|
|
3077
|
+
"tilapia-each": "fish,tilapia",
|
|
3078
|
+
"rice-5kg": "rice,bag",
|
|
3079
|
+
"garri-2kg": "rice,grain",
|
|
3080
|
+
"palm-oil-1l": "oil,bottle",
|
|
3081
|
+
"vegetable-oil-1l": "oil,bottle",
|
|
3082
|
+
"salt-1kg": "salt,seasoning",
|
|
3083
|
+
"sugar-1kg": "sugar,white",
|
|
3084
|
+
"tomato-paste": "tomato-paste,can",
|
|
3085
|
+
"soap-bar": "soap,bar",
|
|
3086
|
+
"detergent-1kg": "detergent,laundry",
|
|
3087
|
+
"tissue-pack": "tissue,paper",
|
|
3088
|
+
"trash-bags": "trash-bag",
|
|
3089
|
+
essentials: "groceries,box",
|
|
3090
|
+
// ─── Serene Spa ───
|
|
3091
|
+
"swedish-massage-60": "massage,relaxation",
|
|
3092
|
+
"swedish-massage-90": "massage,relaxation",
|
|
3093
|
+
"deep-tissue-60": "massage,deep-tissue",
|
|
3094
|
+
"couples-massage": "couples-massage",
|
|
3095
|
+
"hot-stone-90": "hot-stone,massage",
|
|
3096
|
+
"facial-classic": "facial,spa",
|
|
3097
|
+
"facial-anti-aging": "facial,skincare",
|
|
3098
|
+
"facial-acne": "facial,skincare",
|
|
3099
|
+
manicure: "manicure,nails",
|
|
3100
|
+
pedicure: "pedicure,feet",
|
|
3101
|
+
"gel-manicure": "manicure,gel-nails",
|
|
3102
|
+
haircut: "haircut,salon",
|
|
3103
|
+
"hair-treatment": "hair-treatment,salon",
|
|
3104
|
+
color: "hair-color,salon",
|
|
3105
|
+
"yoga-session": "yoga,studio",
|
|
3106
|
+
meditation: "meditation,zen",
|
|
3107
|
+
"spa-day": "spa,wellness",
|
|
3108
|
+
"couples-retreat": "spa,couple",
|
|
3109
|
+
"custom-experience": "spa,wellness"
|
|
3110
|
+
};
|
|
3111
|
+
var NO_CACHE = {
|
|
3112
|
+
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
3113
|
+
};
|
|
3114
|
+
function lockFromSlug(slug) {
|
|
3115
|
+
let hash = 0;
|
|
3116
|
+
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
3117
|
+
return hash % 9999 + 1;
|
|
3118
|
+
}
|
|
3119
|
+
function svgFallback(slug) {
|
|
3120
|
+
const label = slug.replace(/[-_]+/g, " ").trim() || "product";
|
|
3121
|
+
const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
|
|
3122
|
+
let hash = 0;
|
|
3123
|
+
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
3124
|
+
const hue = hash % 360;
|
|
3125
|
+
const safe = display.replace(/[<>&]/g, "");
|
|
3126
|
+
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3127
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
|
|
3128
|
+
<defs>
|
|
3129
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
3130
|
+
<stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
|
|
3131
|
+
<stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
|
|
3132
|
+
</linearGradient>
|
|
3133
|
+
</defs>
|
|
3134
|
+
<rect width="400" height="400" fill="url(#g)"/>
|
|
3135
|
+
<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>
|
|
3136
|
+
</svg>`;
|
|
3137
|
+
return new Response(svg, {
|
|
3138
|
+
headers: {
|
|
3139
|
+
"Content-Type": "image/svg+xml; charset=utf-8",
|
|
3140
|
+
...NO_CACHE,
|
|
3141
|
+
"Access-Control-Allow-Origin": "*"
|
|
3142
|
+
}
|
|
3143
|
+
});
|
|
3144
|
+
}
|
|
3145
|
+
function handleImageRequest(c) {
|
|
3146
|
+
const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
|
|
3147
|
+
const slug = path.split("/").pop() ?? "";
|
|
3148
|
+
for (const [k, v] of Object.entries(NO_CACHE)) c.header(k, v);
|
|
3149
|
+
if (!slug) return svgFallback("");
|
|
3150
|
+
const keyword = SLUG_KEYWORDS[slug] ?? slug.replace(/[-_]+/g, ",");
|
|
3151
|
+
const lock = lockFromSlug(slug);
|
|
3152
|
+
return c.redirect(
|
|
3153
|
+
`https://loremflickr.com/600/600/${encodeURIComponent(keyword)}?lock=${lock}`,
|
|
3154
|
+
302
|
|
3155
|
+
);
|
|
3156
|
+
}
|
|
3157
|
+
function mountImageRoutes(app) {
|
|
3158
|
+
app.get("/img/*", handleImageRequest);
|
|
3159
|
+
}
|
|
3160
|
+
|
|
2989
3161
|
// src/mock/core/clock.ts
|
|
2990
3162
|
function createClock(opts) {
|
|
2991
3163
|
let frozenAt = opts?.frozenAt ? opts.frozenAt.getTime() : null;
|
|
@@ -3323,36 +3495,249 @@ function percent(a, pct) {
|
|
|
3323
3495
|
return toMajor(Math.round(toMinor(a) * pct / 100));
|
|
3324
3496
|
}
|
|
3325
3497
|
|
|
3326
|
-
// src/mock/domain/catalogue.ts
|
|
3327
|
-
function
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3498
|
+
// src/mock/domain/catalogue/price-info.ts
|
|
3499
|
+
function defaultPriceInfo(price, currency) {
|
|
3500
|
+
return {
|
|
3501
|
+
base_price: price,
|
|
3502
|
+
final_price: price,
|
|
3503
|
+
pre_tax_price: price,
|
|
3504
|
+
markup_percentage: "0.00",
|
|
3505
|
+
markup_amount: "0.00",
|
|
3506
|
+
markup_discount_percentage: "0.00",
|
|
3507
|
+
markup_discount_amount: "0.00",
|
|
3508
|
+
currency,
|
|
3509
|
+
tax_info: {
|
|
3510
|
+
tax_rate: "0",
|
|
3511
|
+
tax_amount: "0.00",
|
|
3512
|
+
is_inclusive: true,
|
|
3513
|
+
pre_tax_price: price,
|
|
3514
|
+
components: []
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
// src/mock/domain/catalogue/product-shape.ts
|
|
3520
|
+
function toCanonicalProductSummary(p) {
|
|
3521
|
+
return {
|
|
3522
|
+
id: p.id,
|
|
3523
|
+
business_id: p.business_id,
|
|
3524
|
+
category_id: p.category_ids[0],
|
|
3525
|
+
name: p.name,
|
|
3526
|
+
slug: p.slug,
|
|
3527
|
+
description: p.description,
|
|
3528
|
+
image_url: p.image,
|
|
3529
|
+
images: p.images,
|
|
3530
|
+
default_price: p.base_price,
|
|
3531
|
+
type: p.product_type,
|
|
3532
|
+
inventory_type: "none",
|
|
3533
|
+
variant_strategy: "fetch_all",
|
|
3534
|
+
is_active: p.is_available,
|
|
3535
|
+
inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
|
|
3536
|
+
tags: p.tags ?? [],
|
|
3537
|
+
created_at: p.created_at,
|
|
3538
|
+
updated_at: p.updated_at
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
function deriveRenderHint(p) {
|
|
3542
|
+
if (p.product_type === "service") return "general";
|
|
3543
|
+
const foodish = (p.tags ?? []).some(
|
|
3544
|
+
(t) => /food|drink|cake|bread|pastry|dessert|coffee|tea|juice|spice/i.test(t)
|
|
3545
|
+
);
|
|
3546
|
+
return foodish ? "food" : "general";
|
|
3547
|
+
}
|
|
3548
|
+
function toApiProduct(p, ctx) {
|
|
3549
|
+
const { registry, priceInfo, groups, components, composite, bundle } = ctx;
|
|
3550
|
+
return {
|
|
3551
|
+
// ─── Canonical Product fields (SDK type: Product / ProductWithDetails) ───
|
|
3331
3552
|
id: p.id,
|
|
3332
3553
|
business_id: p.business_id,
|
|
3554
|
+
category_id: p.category_ids[0],
|
|
3333
3555
|
name: p.name,
|
|
3334
3556
|
slug: p.slug,
|
|
3335
3557
|
description: p.description ?? "",
|
|
3336
|
-
|
|
3558
|
+
image_url: p.image,
|
|
3559
|
+
images: p.images,
|
|
3560
|
+
default_price: p.base_price,
|
|
3561
|
+
type: p.product_type,
|
|
3562
|
+
render_hint: deriveRenderHint(p),
|
|
3563
|
+
inventory_type: "none",
|
|
3564
|
+
variant_strategy: p.variant_ids.length > 0 ? "use_axes" : "fetch_all",
|
|
3565
|
+
is_active: p.is_available,
|
|
3566
|
+
inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
|
|
3567
|
+
tags: p.tags ?? [],
|
|
3568
|
+
calories: p.calories,
|
|
3569
|
+
allergies: p.allergies ?? [],
|
|
3570
|
+
created_at: p.created_at,
|
|
3571
|
+
updated_at: p.updated_at,
|
|
3572
|
+
metadata: {
|
|
3573
|
+
is_signature: p.is_signature ?? false,
|
|
3574
|
+
is_new: p.is_new ?? false,
|
|
3575
|
+
ingredients: p.ingredients ?? [],
|
|
3576
|
+
pairings: p.pairings ?? [],
|
|
3577
|
+
...p.metadata ?? {}
|
|
3578
|
+
},
|
|
3579
|
+
// ─── Pricing breakdown (Price / ProductCustomizer) ───
|
|
3337
3580
|
base_price: p.base_price,
|
|
3581
|
+
default_price_info: priceInfo,
|
|
3582
|
+
price_info: priceInfo,
|
|
3338
3583
|
currency: p.currency,
|
|
3584
|
+
// ─── Compatibility aliases (older callers) ───
|
|
3339
3585
|
image: p.image,
|
|
3340
|
-
|
|
3586
|
+
product_type: p.product_type,
|
|
3341
3587
|
is_available: p.is_available,
|
|
3342
3588
|
category_ids: p.category_ids,
|
|
3343
3589
|
collection_ids: p.collection_ids,
|
|
3344
3590
|
add_on_ids: p.add_on_ids,
|
|
3345
|
-
|
|
3346
|
-
created_at: p.created_at,
|
|
3347
|
-
updated_at: p.updated_at,
|
|
3591
|
+
variant_ids: p.variant_ids,
|
|
3348
3592
|
custom_attributes: [],
|
|
3349
3593
|
properties: [],
|
|
3350
3594
|
has_variants: p.variant_ids.length > 0,
|
|
3351
|
-
|
|
3352
|
-
|
|
3595
|
+
// ─── Embedded relations (ProductWithDetails) ───
|
|
3596
|
+
category: registry.categories.get(p.category_ids[0]),
|
|
3597
|
+
add_ons: p.add_on_ids.map((aid) => registry.addOns.get(aid)).filter(Boolean),
|
|
3598
|
+
variants: registry.variants.list((v) => v.product_id === p.id),
|
|
3599
|
+
variant_axes: registry.variantAxes.list((a) => a.product_id === p.id),
|
|
3600
|
+
// ─── Composite-specific (CompositeProductLayout reads product.groups) ───
|
|
3601
|
+
groups,
|
|
3602
|
+
composite_id: composite?.id,
|
|
3603
|
+
pricing_mode: composite?.pricing_mode,
|
|
3604
|
+
min_order_quantity: composite?.min_order_quantity,
|
|
3605
|
+
max_order_quantity: composite?.max_order_quantity,
|
|
3606
|
+
// ─── Bundle-specific (BundleProductLayout reads product.components) ───
|
|
3607
|
+
bundle_id: bundle?.id,
|
|
3608
|
+
bundle_pricing_type: bundle?.pricing_type,
|
|
3609
|
+
bundle_price: bundle?.bundle_price,
|
|
3610
|
+
bundle_discount_value: bundle?.discount_value,
|
|
3611
|
+
components
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
// src/mock/domain/catalogue/composite-shape.ts
|
|
3616
|
+
function expandCompositeGroups(registry, productId, isoNow) {
|
|
3617
|
+
const composite = registry.composites.list((c) => c.product_id === productId)[0];
|
|
3618
|
+
if (!composite) return void 0;
|
|
3619
|
+
return composite.groups.map((g, gIdx) => buildGroup(registry, composite, g, gIdx, isoNow));
|
|
3620
|
+
}
|
|
3621
|
+
function buildGroup(registry, composite, group, groupIdx, isoNow) {
|
|
3622
|
+
return {
|
|
3623
|
+
// ─── Canonical ComponentGroupWithComponents ───
|
|
3624
|
+
id: group.id,
|
|
3625
|
+
composite_id: composite.id,
|
|
3626
|
+
name: group.name,
|
|
3627
|
+
description: void 0,
|
|
3628
|
+
display_order: groupIdx,
|
|
3629
|
+
min_selections: group.min,
|
|
3630
|
+
max_selections: group.max,
|
|
3631
|
+
allow_quantity: true,
|
|
3632
|
+
max_quantity_per_component: group.max,
|
|
3633
|
+
pricing_behavior: "additive",
|
|
3634
|
+
created_at: isoNow,
|
|
3635
|
+
updated_at: isoNow,
|
|
3636
|
+
// ─── Aliases for older selectors ───
|
|
3637
|
+
min: group.min,
|
|
3638
|
+
max: group.max,
|
|
3639
|
+
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))
|
|
3640
|
+
};
|
|
3641
|
+
}
|
|
3642
|
+
function buildComponent(composite, group, p, idx) {
|
|
3643
|
+
return {
|
|
3644
|
+
// ─── Canonical CompositeComponent ───
|
|
3645
|
+
id: `comp_${composite.product_id}_${p.id}`,
|
|
3646
|
+
composite_id: composite.id,
|
|
3647
|
+
group_id: group.id,
|
|
3648
|
+
source_type: "product",
|
|
3649
|
+
source_product_id: p.id,
|
|
3650
|
+
product_id: p.id,
|
|
3651
|
+
display_name: p.name,
|
|
3652
|
+
display_description: p.description,
|
|
3653
|
+
display_image_url: p.image,
|
|
3654
|
+
price: p.base_price,
|
|
3655
|
+
calories: p.calories,
|
|
3656
|
+
allergens: p.allergies ?? [],
|
|
3657
|
+
display_order: idx,
|
|
3658
|
+
is_popular: idx === 0,
|
|
3659
|
+
is_premium: false,
|
|
3660
|
+
is_available: p.is_available,
|
|
3661
|
+
is_archived: false,
|
|
3662
|
+
created_at: p.created_at,
|
|
3663
|
+
updated_at: p.updated_at,
|
|
3664
|
+
// ─── Compatibility aliases ───
|
|
3665
|
+
name: p.name,
|
|
3666
|
+
description: p.description,
|
|
3667
|
+
image_url: p.image,
|
|
3668
|
+
unit_price: p.base_price,
|
|
3669
|
+
default_price: p.base_price,
|
|
3670
|
+
base_price: p.base_price,
|
|
3671
|
+
currency: p.currency,
|
|
3672
|
+
product: toCanonicalProductSummary(p)
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
// src/mock/domain/catalogue/bundle-shape.ts
|
|
3677
|
+
function expandBundleComponents(registry, productId) {
|
|
3678
|
+
const bundle = registry.bundles.list((b) => b.product_id === productId)[0];
|
|
3679
|
+
if (!bundle) return void 0;
|
|
3680
|
+
return bundle.product_ids.map((cid) => registry.products.get(cid)).filter((p) => Boolean(p)).map((p) => buildBundleComponent(bundle.id, productId, p));
|
|
3681
|
+
}
|
|
3682
|
+
function buildBundleComponent(bundleId, productId, p) {
|
|
3683
|
+
return {
|
|
3684
|
+
id: `bp_${productId}_${p.id}`,
|
|
3685
|
+
bundle_id: bundleId,
|
|
3686
|
+
product_id: p.id,
|
|
3687
|
+
// ─── Fields BundleProductLayout reads ───
|
|
3688
|
+
product_name: p.name,
|
|
3689
|
+
product_image_url: p.image,
|
|
3690
|
+
product_description: p.description,
|
|
3691
|
+
product_slug: p.slug,
|
|
3692
|
+
effective_price: p.base_price,
|
|
3693
|
+
unit_price: p.base_price,
|
|
3694
|
+
default_price: p.base_price,
|
|
3695
|
+
quantity: 1,
|
|
3696
|
+
allow_variant_choice: false,
|
|
3697
|
+
available_variants: [],
|
|
3698
|
+
selected_variant_id: null,
|
|
3699
|
+
// ─── Compatibility aliases ───
|
|
3700
|
+
display_name: p.name,
|
|
3701
|
+
display_description: p.description,
|
|
3702
|
+
name: p.name,
|
|
3703
|
+
description: p.description,
|
|
3704
|
+
image_url: p.image,
|
|
3705
|
+
currency: p.currency,
|
|
3706
|
+
is_available: p.is_available,
|
|
3707
|
+
product: toCanonicalProductSummary(p)
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
// src/mock/domain/catalogue/index.ts
|
|
3712
|
+
function createCatalogueService(opts) {
|
|
3713
|
+
const { registry, clock, ids } = opts;
|
|
3714
|
+
const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
|
|
3353
3715
|
const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
|
|
3354
3716
|
const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
|
|
3355
3717
|
const addOnLinksForProduct = (productId) => registry.productAddOns.list((l) => l.product_id === productId);
|
|
3718
|
+
const apiProduct = (p) => {
|
|
3719
|
+
const composite = p.product_type === "composite" ? registry.composites.list((c) => c.product_id === p.id)[0] : void 0;
|
|
3720
|
+
const bundle = p.product_type === "bundle" ? registry.bundles.list((b) => b.product_id === p.id)[0] : void 0;
|
|
3721
|
+
return toApiProduct(p, {
|
|
3722
|
+
registry,
|
|
3723
|
+
priceInfo: defaultPriceInfo(p.base_price, p.currency),
|
|
3724
|
+
groups: expandCompositeGroups(registry, p.id, clock.iso()),
|
|
3725
|
+
components: expandBundleComponents(registry, p.id),
|
|
3726
|
+
composite: composite ? {
|
|
3727
|
+
id: composite.id,
|
|
3728
|
+
pricing_mode: composite.pricing_mode,
|
|
3729
|
+
min_order_quantity: composite.min_order_quantity,
|
|
3730
|
+
max_order_quantity: composite.max_order_quantity
|
|
3731
|
+
} : void 0,
|
|
3732
|
+
bundle: bundle ? {
|
|
3733
|
+
id: bundle.id,
|
|
3734
|
+
pricing_type: bundle.pricing_type,
|
|
3735
|
+
bundle_price: bundle.bundle_price,
|
|
3736
|
+
discount_value: bundle.discount_value
|
|
3737
|
+
} : void 0
|
|
3738
|
+
});
|
|
3739
|
+
};
|
|
3740
|
+
const resolveProduct = (idOrSlug) => registry.products.get(idOrSlug) ?? registry.products.list((p) => p.slug === idOrSlug)[0];
|
|
3356
3741
|
return {
|
|
3357
3742
|
snapshot(businessId) {
|
|
3358
3743
|
const products = productsForBusiness(businessId);
|
|
@@ -3360,7 +3745,7 @@ function createCatalogueService(opts) {
|
|
|
3360
3745
|
const addOns = registry.addOns.list((a) => a.business_id === businessId);
|
|
3361
3746
|
return {
|
|
3362
3747
|
categories,
|
|
3363
|
-
products: products.map(
|
|
3748
|
+
products: products.map(apiProduct),
|
|
3364
3749
|
add_ons: addOns,
|
|
3365
3750
|
is_complete: true,
|
|
3366
3751
|
total_available: products.length
|
|
@@ -3375,7 +3760,7 @@ function createCatalogueService(opts) {
|
|
|
3375
3760
|
const start = (page - 1) * limit;
|
|
3376
3761
|
const slice = items.slice(start, start + limit);
|
|
3377
3762
|
return {
|
|
3378
|
-
items: slice.map(
|
|
3763
|
+
items: slice.map(apiProduct),
|
|
3379
3764
|
is_complete: start + limit >= items.length,
|
|
3380
3765
|
total_available: items.length,
|
|
3381
3766
|
pagination: {
|
|
@@ -3388,9 +3773,9 @@ function createCatalogueService(opts) {
|
|
|
3388
3773
|
};
|
|
3389
3774
|
},
|
|
3390
3775
|
getProduct(productId) {
|
|
3391
|
-
const p =
|
|
3776
|
+
const p = resolveProduct(productId);
|
|
3392
3777
|
if (!p) throw notFound(`Product ${productId} not found`);
|
|
3393
|
-
return
|
|
3778
|
+
return apiProduct(p);
|
|
3394
3779
|
},
|
|
3395
3780
|
getProductVariants(productId) {
|
|
3396
3781
|
return variantsForProduct(productId);
|
|
@@ -3400,7 +3785,9 @@ function createCatalogueService(opts) {
|
|
|
3400
3785
|
},
|
|
3401
3786
|
getProductVariantById(productId, variantId) {
|
|
3402
3787
|
const v = registry.variants.get(variantId);
|
|
3403
|
-
if (!v || v.product_id !== productId)
|
|
3788
|
+
if (!v || v.product_id !== productId) {
|
|
3789
|
+
throw notFound(`Variant ${variantId} not found on product ${productId}`);
|
|
3790
|
+
}
|
|
3404
3791
|
return v;
|
|
3405
3792
|
},
|
|
3406
3793
|
findProductVariant(productId, properties) {
|
|
@@ -3430,13 +3817,13 @@ function createCatalogueService(opts) {
|
|
|
3430
3817
|
return registry.categories.list((c) => c.business_id === businessId);
|
|
3431
3818
|
},
|
|
3432
3819
|
getCategory(id) {
|
|
3433
|
-
const c = registry.categories.get(id);
|
|
3820
|
+
const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
|
|
3434
3821
|
if (!c) throw notFound(`Category ${id} not found`);
|
|
3435
3822
|
return c;
|
|
3436
3823
|
},
|
|
3437
3824
|
getCategoryProducts(id) {
|
|
3438
3825
|
const c = this.getCategory(id);
|
|
3439
|
-
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(
|
|
3826
|
+
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
|
|
3440
3827
|
},
|
|
3441
3828
|
getCategoryAttributes(id) {
|
|
3442
3829
|
const c = registry.categories.get(id);
|
|
@@ -3449,13 +3836,13 @@ function createCatalogueService(opts) {
|
|
|
3449
3836
|
return registry.collections.list((c) => c.business_id === businessId);
|
|
3450
3837
|
},
|
|
3451
3838
|
getCollection(id) {
|
|
3452
|
-
const c = registry.collections.get(id);
|
|
3839
|
+
const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
|
|
3453
3840
|
if (!c) throw notFound(`Collection ${id} not found`);
|
|
3454
3841
|
return c;
|
|
3455
3842
|
},
|
|
3456
3843
|
getCollectionProducts(id) {
|
|
3457
3844
|
const c = this.getCollection(id);
|
|
3458
|
-
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(
|
|
3845
|
+
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
|
|
3459
3846
|
},
|
|
3460
3847
|
getCollectionAttributes(id) {
|
|
3461
3848
|
const c = registry.collections.get(id);
|
|
@@ -3468,7 +3855,7 @@ function createCatalogueService(opts) {
|
|
|
3468
3855
|
return registry.bundles.list((b) => b.business_id === businessId);
|
|
3469
3856
|
},
|
|
3470
3857
|
getBundle(id) {
|
|
3471
|
-
const b = registry.bundles.get(id);
|
|
3858
|
+
const b = registry.bundles.get(id) ?? registry.bundles.list((x) => x.product_id === id || x.slug === id)[0];
|
|
3472
3859
|
if (!b) throw notFound(`Bundle ${id} not found`);
|
|
3473
3860
|
return b;
|
|
3474
3861
|
},
|
|
@@ -3476,12 +3863,16 @@ function createCatalogueService(opts) {
|
|
|
3476
3863
|
return registry.composites.list((c) => c.business_id === businessId);
|
|
3477
3864
|
},
|
|
3478
3865
|
getComposite(id) {
|
|
3479
|
-
const c = registry.composites.get(id);
|
|
3866
|
+
const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
|
|
3480
3867
|
if (!c) throw notFound(`Composite ${id} not found`);
|
|
3481
3868
|
return c;
|
|
3482
3869
|
},
|
|
3483
3870
|
getCompositeByProduct(productId) {
|
|
3484
|
-
|
|
3871
|
+
const direct = registry.composites.list((c) => c.product_id === productId)[0];
|
|
3872
|
+
if (direct) return direct;
|
|
3873
|
+
const product = registry.products.list((p) => p.slug === productId)[0];
|
|
3874
|
+
if (!product) return null;
|
|
3875
|
+
return registry.composites.list((c) => c.product_id === product.id)[0] ?? null;
|
|
3485
3876
|
},
|
|
3486
3877
|
calculateCompositePrice(id, body) {
|
|
3487
3878
|
const c = registry.composites.get(id);
|
|
@@ -3489,14 +3880,20 @@ function createCatalogueService(opts) {
|
|
|
3489
3880
|
const sel = body.selections ?? [];
|
|
3490
3881
|
let total = c.base_price;
|
|
3491
3882
|
for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
|
|
3492
|
-
return {
|
|
3883
|
+
return {
|
|
3884
|
+
final_price: total,
|
|
3885
|
+
base_price: c.base_price,
|
|
3886
|
+
components_total: money(parseFloat(total) - parseFloat(c.base_price))
|
|
3887
|
+
};
|
|
3493
3888
|
},
|
|
3494
3889
|
listTaxonomies(businessId) {
|
|
3495
3890
|
return registry.taxonomies.list((t) => t.business_id === businessId);
|
|
3496
3891
|
},
|
|
3497
3892
|
searchTaxonomies(businessId, q) {
|
|
3498
3893
|
const term = q.toLowerCase();
|
|
3499
|
-
return registry.taxonomies.list(
|
|
3894
|
+
return registry.taxonomies.list(
|
|
3895
|
+
(t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
|
|
3896
|
+
);
|
|
3500
3897
|
},
|
|
3501
3898
|
getTaxonomy(id) {
|
|
3502
3899
|
const t = registry.taxonomies.get(id);
|
|
@@ -3530,7 +3927,11 @@ function createCatalogueService(opts) {
|
|
|
3530
3927
|
listPropertyFacets(businessId) {
|
|
3531
3928
|
const products = productsForBusiness(businessId);
|
|
3532
3929
|
const byCategory = /* @__PURE__ */ new Map();
|
|
3533
|
-
for (const p of products)
|
|
3930
|
+
for (const p of products) {
|
|
3931
|
+
for (const cid of p.category_ids) {
|
|
3932
|
+
byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3534
3935
|
return [
|
|
3535
3936
|
{
|
|
3536
3937
|
name: "Category",
|
|
@@ -3549,12 +3950,6 @@ function createCatalogueService(opts) {
|
|
|
3549
3950
|
listOnSale() {
|
|
3550
3951
|
return [];
|
|
3551
3952
|
},
|
|
3552
|
-
validateDiscount(code) {
|
|
3553
|
-
if (code === "WELCOME10") {
|
|
3554
|
-
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
3555
|
-
}
|
|
3556
|
-
return { valid: false, reason: "Code not recognized" };
|
|
3557
|
-
},
|
|
3558
3953
|
getProductDeals() {
|
|
3559
3954
|
return [];
|
|
3560
3955
|
},
|
|
@@ -3564,15 +3959,33 @@ function createCatalogueService(opts) {
|
|
|
3564
3959
|
getCollectionDeals() {
|
|
3565
3960
|
return [];
|
|
3566
3961
|
},
|
|
3962
|
+
validateDiscount(code) {
|
|
3963
|
+
if (code === "WELCOME10") {
|
|
3964
|
+
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
3965
|
+
}
|
|
3966
|
+
return { valid: false, reason: "Code not recognized" };
|
|
3967
|
+
},
|
|
3567
3968
|
createQuote(businessId, body) {
|
|
3568
3969
|
const b = body;
|
|
3569
3970
|
const id = ids.quote();
|
|
3971
|
+
const productId = b.product_id ?? "";
|
|
3972
|
+
const product = resolveProduct(productId);
|
|
3973
|
+
const price = b.total_price ?? product?.base_price ?? "0.00";
|
|
3974
|
+
const currency = b.currency ?? product?.currency ?? "GHS";
|
|
3975
|
+
const priceInfo = defaultPriceInfo(price, currency);
|
|
3570
3976
|
const quote = {
|
|
3571
3977
|
id,
|
|
3978
|
+
quote_id: id,
|
|
3572
3979
|
business_id: businessId,
|
|
3573
|
-
product_id:
|
|
3980
|
+
product_id: product?.id ?? productId,
|
|
3574
3981
|
status: "fresh",
|
|
3575
|
-
total_price:
|
|
3982
|
+
total_price: price,
|
|
3983
|
+
currency,
|
|
3984
|
+
unit_price_info: priceInfo,
|
|
3985
|
+
final_price_info: priceInfo,
|
|
3986
|
+
quoted_total_price_info: priceInfo,
|
|
3987
|
+
components: [],
|
|
3988
|
+
breakdown: { base_price: price, components_total: "0.00", final_price: price },
|
|
3576
3989
|
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
|
|
3577
3990
|
created_at: clock.iso()
|
|
3578
3991
|
};
|
|
@@ -3588,7 +4001,11 @@ function createCatalogueService(opts) {
|
|
|
3588
4001
|
refreshQuote(id) {
|
|
3589
4002
|
const q = registry.quotes.get(id);
|
|
3590
4003
|
if (!q) throw notFound(`Quote ${id} not found`);
|
|
3591
|
-
const refreshed = {
|
|
4004
|
+
const refreshed = {
|
|
4005
|
+
...q,
|
|
4006
|
+
status: "fresh",
|
|
4007
|
+
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
|
|
4008
|
+
};
|
|
3592
4009
|
registry.quotes.put(id, refreshed);
|
|
3593
4010
|
return refreshed;
|
|
3594
4011
|
}
|
|
@@ -4613,67 +5030,82 @@ function createLiteService(opts) {
|
|
|
4613
5030
|
|
|
4614
5031
|
// src/mock/seeds/default.ts
|
|
4615
5032
|
var ISO = "2026-01-01T00:00:00.000Z";
|
|
4616
|
-
var
|
|
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
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
5033
|
+
var BUSINESS_ID = "bus_default_akua_bakery";
|
|
5034
|
+
var img = (id) => `https://images.unsplash.com/photo-${id}?w=800&h=800&fit=crop&auto=format&q=80`;
|
|
5035
|
+
function makeProduct(input, categoryIds) {
|
|
5036
|
+
const catId = categoryIds.get(input.category);
|
|
5037
|
+
return {
|
|
5038
|
+
id: input.id,
|
|
5039
|
+
business_id: BUSINESS_ID,
|
|
5040
|
+
name: input.name,
|
|
5041
|
+
slug: input.slug,
|
|
5042
|
+
description: input.description,
|
|
5043
|
+
product_type: input.productType ?? "product",
|
|
5044
|
+
base_price: input.price,
|
|
5045
|
+
currency: "GHS",
|
|
5046
|
+
image: input.imageUrl,
|
|
5047
|
+
images: input.images ?? [input.imageUrl],
|
|
5048
|
+
is_available: true,
|
|
5049
|
+
category_ids: [catId],
|
|
5050
|
+
collection_ids: [],
|
|
5051
|
+
add_on_ids: [],
|
|
5052
|
+
variant_ids: [],
|
|
5053
|
+
tags: input.tags ?? [],
|
|
5054
|
+
calories: input.calories,
|
|
5055
|
+
allergies: input.allergies ?? [],
|
|
5056
|
+
ingredients: input.ingredients ?? [],
|
|
5057
|
+
pairings: input.pairings ?? [],
|
|
5058
|
+
is_signature: input.isSignature ?? false,
|
|
5059
|
+
is_new: input.isNew ?? false,
|
|
5060
|
+
created_at: ISO,
|
|
5061
|
+
updated_at: ISO
|
|
5062
|
+
};
|
|
5063
|
+
}
|
|
4644
5064
|
function seedDefault(registry) {
|
|
4645
|
-
const businessId = "bus_default_akua_bakery";
|
|
4646
5065
|
const business = {
|
|
4647
|
-
id:
|
|
5066
|
+
id: BUSINESS_ID,
|
|
4648
5067
|
name: "Akua's Bakery",
|
|
4649
5068
|
handle: "akua",
|
|
4650
5069
|
business_type: "eatery",
|
|
4651
5070
|
email: "hello@akuasbakery.test",
|
|
4652
5071
|
default_currency: "GHS",
|
|
4653
|
-
default_phone: "+
|
|
5072
|
+
default_phone: "+233 244 000 000",
|
|
4654
5073
|
default_address: "12 Independence Ave, Accra",
|
|
4655
5074
|
default_offers_table_service: true,
|
|
4656
5075
|
default_accepts_online_orders: true,
|
|
4657
|
-
image: "
|
|
5076
|
+
image: img("1568254183919-78a4f43a2877"),
|
|
4658
5077
|
status: "active",
|
|
4659
5078
|
created_at: ISO,
|
|
4660
5079
|
updated_at: ISO,
|
|
4661
5080
|
created_by: "seed",
|
|
4662
|
-
preferences: {
|
|
5081
|
+
preferences: {
|
|
5082
|
+
tagline: "Fresh from the oven, every day",
|
|
5083
|
+
description: "Family-run bakery in East Legon. Sourdough, croissants, layered cakes \u2014 handcrafted since 2018, delivered across Accra.",
|
|
5084
|
+
instagram: "https://instagram.com/akuasbakery",
|
|
5085
|
+
theme: "warm",
|
|
5086
|
+
checkout: { allow_guest: true }
|
|
5087
|
+
},
|
|
4663
5088
|
is_online_only: false,
|
|
4664
5089
|
enabled_payment_types: ["mobile_money", "card", "cash"],
|
|
4665
5090
|
default_location_settings: {},
|
|
4666
5091
|
country_code: "GH",
|
|
4667
5092
|
timezone: "Africa/Accra"
|
|
4668
5093
|
};
|
|
4669
|
-
registry.businesses.put(
|
|
4670
|
-
const
|
|
4671
|
-
|
|
5094
|
+
registry.businesses.put(BUSINESS_ID, business);
|
|
5095
|
+
const categoryDefs = [
|
|
5096
|
+
{ slug: "breads", name: "Breads", description: "Naturally leavened sourdough, baguettes, focaccia." },
|
|
5097
|
+
{ slug: "pastries", name: "Pastries", description: "Hand-laminated croissants, danishes, brownies." },
|
|
5098
|
+
{ slug: "cakes", name: "Cakes", description: "Slices and made-to-order showstoppers." },
|
|
5099
|
+
{ slug: "drinks", name: "Drinks", description: "Locally roasted coffee, brewed teas, fresh juice." },
|
|
5100
|
+
{ slug: "boxes", name: "Boxes & Bundles", description: "Curated boxes for sharing." }
|
|
5101
|
+
];
|
|
5102
|
+
const catIds = /* @__PURE__ */ new Map();
|
|
5103
|
+
for (const c of categoryDefs) {
|
|
4672
5104
|
const id = `cat_${c.slug}`;
|
|
4673
|
-
|
|
5105
|
+
catIds.set(c.slug, id);
|
|
4674
5106
|
const cat = {
|
|
4675
5107
|
id,
|
|
4676
|
-
business_id:
|
|
5108
|
+
business_id: BUSINESS_ID,
|
|
4677
5109
|
name: c.name,
|
|
4678
5110
|
slug: c.slug,
|
|
4679
5111
|
description: c.description,
|
|
@@ -4685,99 +5117,354 @@ function seedDefault(registry) {
|
|
|
4685
5117
|
};
|
|
4686
5118
|
registry.categories.put(id, cat);
|
|
4687
5119
|
}
|
|
4688
|
-
const
|
|
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
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
5120
|
+
const productInputs = [
|
|
5121
|
+
// ── Breads ──
|
|
5122
|
+
{
|
|
5123
|
+
id: "prod_sourdough-loaf",
|
|
5124
|
+
name: "Country Sourdough",
|
|
5125
|
+
slug: "sourdough-loaf",
|
|
5126
|
+
price: "25.00",
|
|
5127
|
+
description: "24-hour fermented country loaf with our heritage starter \u2014 open crumb, blistered crust, naturally tangy. Baked fresh every morning.",
|
|
5128
|
+
category: "breads",
|
|
5129
|
+
imageUrl: img("1509440159596-0249088772ff"),
|
|
5130
|
+
images: [img("1509440159596-0249088772ff"), img("1486887396153-fa416526c108")],
|
|
5131
|
+
tags: ["bread", "vegan", "signature"],
|
|
5132
|
+
isSignature: true,
|
|
5133
|
+
ingredients: ["Wheat flour", "Sourdough starter", "Sea salt", "Filtered water"],
|
|
5134
|
+
pairings: ["Olive oil", "Aged cheese", "Tomato jam"],
|
|
5135
|
+
allergies: ["wheat", "gluten"],
|
|
5136
|
+
calories: 240
|
|
5137
|
+
},
|
|
5138
|
+
{
|
|
5139
|
+
id: "prod_whole-wheat",
|
|
5140
|
+
name: "Whole Wheat Seed Loaf",
|
|
5141
|
+
slug: "whole-wheat",
|
|
5142
|
+
price: "20.00",
|
|
5143
|
+
description: "Hearty 100% whole wheat with sunflower, sesame, and pumpkin seeds. Slow-fermented for a soft crumb and nutty depth.",
|
|
5144
|
+
category: "breads",
|
|
5145
|
+
imageUrl: img("1486887396153-fa416526c108"),
|
|
5146
|
+
tags: ["bread", "vegan", "high-fibre"],
|
|
5147
|
+
ingredients: ["Whole wheat flour", "Sunflower seeds", "Sesame", "Pumpkin seeds"],
|
|
5148
|
+
allergies: ["wheat", "gluten", "sesame"],
|
|
5149
|
+
calories: 220
|
|
5150
|
+
},
|
|
5151
|
+
{
|
|
5152
|
+
id: "prod_baguette",
|
|
5153
|
+
name: "Classic Baguette",
|
|
5154
|
+
slug: "baguette",
|
|
5155
|
+
price: "15.00",
|
|
5156
|
+
description: "Crackling crust, airy crumb. Best eaten warm, within an hour of baking.",
|
|
5157
|
+
category: "breads",
|
|
5158
|
+
imageUrl: img("1568471173242-461f0a730452"),
|
|
5159
|
+
tags: ["bread", "vegan"],
|
|
5160
|
+
ingredients: ["Wheat flour", "Yeast", "Sea salt", "Water"],
|
|
5161
|
+
allergies: ["wheat", "gluten"],
|
|
5162
|
+
calories: 180
|
|
5163
|
+
},
|
|
5164
|
+
{
|
|
5165
|
+
id: "prod_focaccia",
|
|
5166
|
+
name: "Rosemary Focaccia",
|
|
5167
|
+
slug: "focaccia",
|
|
5168
|
+
price: "30.00",
|
|
5169
|
+
description: "High-hydration focaccia drenched in olive oil, scattered with sea salt and fresh rosemary. Sold whole or by the slab.",
|
|
5170
|
+
category: "breads",
|
|
5171
|
+
imageUrl: img("1571167530149-c1105da4c2c7"),
|
|
5172
|
+
tags: ["bread", "vegetarian"],
|
|
5173
|
+
ingredients: ["00 flour", "Olive oil", "Rosemary", "Maldon salt"],
|
|
5174
|
+
pairings: ["Burrata", "Prosciutto", "Caponata"],
|
|
5175
|
+
allergies: ["wheat", "gluten"],
|
|
5176
|
+
calories: 310
|
|
5177
|
+
},
|
|
5178
|
+
{
|
|
5179
|
+
id: "prod_banana-bread",
|
|
5180
|
+
name: "Brown Butter Banana Bread",
|
|
5181
|
+
slug: "banana-bread",
|
|
5182
|
+
price: "18.00",
|
|
5183
|
+
description: "Caramelised brown butter, three over-ripe bananas, walnuts, finished with a salted maple glaze.",
|
|
5184
|
+
category: "breads",
|
|
5185
|
+
imageUrl: img("1632931057819-4eefffa8e007"),
|
|
5186
|
+
tags: ["sweet", "comfort"],
|
|
5187
|
+
ingredients: ["Bananas", "Brown butter", "Walnuts", "Maple syrup"],
|
|
5188
|
+
allergies: ["wheat", "gluten", "dairy", "nuts", "eggs"],
|
|
5189
|
+
calories: 360,
|
|
5190
|
+
isNew: true
|
|
5191
|
+
},
|
|
5192
|
+
// ── Pastries ──
|
|
5193
|
+
{
|
|
5194
|
+
id: "prod_croissant",
|
|
5195
|
+
name: "Butter Croissant",
|
|
5196
|
+
slug: "croissant",
|
|
5197
|
+
price: "12.00",
|
|
5198
|
+
description: "27 layers of laminated dough wrapped around French butter. Crisp, shattering crust and a tender honeycomb interior.",
|
|
5199
|
+
category: "pastries",
|
|
5200
|
+
imageUrl: img("1555507036-ab1f4038808a"),
|
|
5201
|
+
images: [img("1555507036-ab1f4038808a"), img("1530610476181-d83430b64dcd")],
|
|
5202
|
+
tags: ["pastry", "vegetarian", "signature"],
|
|
5203
|
+
isSignature: true,
|
|
5204
|
+
ingredients: ["French butter", "Wheat flour", "Milk", "Eggs"],
|
|
5205
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5206
|
+
calories: 280
|
|
5207
|
+
},
|
|
5208
|
+
{
|
|
5209
|
+
id: "prod_pain-au-chocolat",
|
|
5210
|
+
name: "Pain au Chocolat",
|
|
5211
|
+
slug: "pain-au-chocolat",
|
|
5212
|
+
price: "14.00",
|
|
5213
|
+
description: "Our croissant dough wrapped around two batons of 70% Valrhona dark chocolate.",
|
|
5214
|
+
category: "pastries",
|
|
5215
|
+
imageUrl: img("1530610476181-d83430b64dcd"),
|
|
5216
|
+
tags: ["pastry", "chocolate"],
|
|
5217
|
+
ingredients: ["Croissant dough", "Valrhona chocolate"],
|
|
5218
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5219
|
+
calories: 320
|
|
5220
|
+
},
|
|
5221
|
+
{
|
|
5222
|
+
id: "prod_almond-danish",
|
|
5223
|
+
name: "Almond Danish",
|
|
5224
|
+
slug: "almond-danish",
|
|
5225
|
+
price: "16.00",
|
|
5226
|
+
description: "Flaky pastry filled with almond cream, dusted with toasted slivers and powdered sugar.",
|
|
5227
|
+
category: "pastries",
|
|
5228
|
+
imageUrl: img("1517433367423-c7e5b0f35086"),
|
|
5229
|
+
tags: ["pastry", "nuts"],
|
|
5230
|
+
ingredients: ["Pastry dough", "Almond cream", "Toasted almonds"],
|
|
5231
|
+
allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
|
|
5232
|
+
calories: 340
|
|
5233
|
+
},
|
|
5234
|
+
{
|
|
5235
|
+
id: "prod_cinnamon-roll",
|
|
5236
|
+
name: "Cinnamon Roll",
|
|
5237
|
+
slug: "cinnamon-roll",
|
|
5238
|
+
price: "13.00",
|
|
5239
|
+
description: "Soft enriched dough swirled with butter and Ceylon cinnamon, finished with cream-cheese glaze.",
|
|
5240
|
+
category: "pastries",
|
|
5241
|
+
imageUrl: img("1607920591413-4ec007e70023"),
|
|
5242
|
+
tags: ["pastry", "comfort"],
|
|
5243
|
+
ingredients: ["Enriched dough", "Cinnamon", "Brown sugar", "Cream cheese"],
|
|
5244
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5245
|
+
calories: 380
|
|
5246
|
+
},
|
|
5247
|
+
{
|
|
5248
|
+
id: "prod_brownie",
|
|
5249
|
+
name: "Fudge Brownie",
|
|
5250
|
+
slug: "brownie",
|
|
5251
|
+
price: "12.00",
|
|
5252
|
+
description: "Dark, dense, fudgy brownie with a glossy shell. Chocolate four ways. Best slightly warm.",
|
|
5253
|
+
category: "pastries",
|
|
5254
|
+
imageUrl: img("1606313564200-e75d5e30476c"),
|
|
5255
|
+
tags: ["chocolate", "vegetarian"],
|
|
5256
|
+
ingredients: ["Dark chocolate", "Cocoa", "Brown butter", "Eggs"],
|
|
5257
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5258
|
+
calories: 420
|
|
5259
|
+
},
|
|
5260
|
+
{
|
|
5261
|
+
id: "prod_macaron-box",
|
|
5262
|
+
name: "Macaron Box (6)",
|
|
5263
|
+
slug: "macaron-box",
|
|
5264
|
+
price: "60.00",
|
|
5265
|
+
description: "Six French macarons \u2014 pistachio, raspberry, salted caramel, espresso, vanilla bean, and dark chocolate ganache.",
|
|
5266
|
+
category: "pastries",
|
|
5267
|
+
imageUrl: img("1558326567-98ae2405596b"),
|
|
5268
|
+
tags: ["pastry", "gift", "premium"],
|
|
5269
|
+
ingredients: ["Almond flour", "Egg whites", "Sugar", "Various fillings"],
|
|
5270
|
+
allergies: ["nuts", "eggs", "dairy"],
|
|
5271
|
+
calories: 540
|
|
5272
|
+
},
|
|
5273
|
+
// ── Cakes ──
|
|
5274
|
+
{
|
|
5275
|
+
id: "prod_chocolate-cake",
|
|
5276
|
+
name: "Dark Chocolate Layer Cake",
|
|
5277
|
+
slug: "chocolate-cake",
|
|
5278
|
+
price: "22.00",
|
|
5279
|
+
description: "Three layers of dark chocolate sponge with chocolate ganache and cr\xE8me p\xE2tissi\xE8re, finished with a mirror glaze. (Slice)",
|
|
5280
|
+
category: "cakes",
|
|
5281
|
+
imageUrl: img("1565958011703-44f9829ba187"),
|
|
5282
|
+
tags: ["cake", "chocolate", "signature"],
|
|
5283
|
+
isSignature: true,
|
|
5284
|
+
ingredients: ["Dark chocolate", "Cr\xE8me p\xE2tissi\xE8re", "Cocoa", "Eggs"],
|
|
5285
|
+
pairings: ["Espresso", "Cabernet"],
|
|
5286
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5287
|
+
calories: 480
|
|
5288
|
+
},
|
|
5289
|
+
{
|
|
5290
|
+
id: "prod_red-velvet",
|
|
5291
|
+
name: "Red Velvet (Slice)",
|
|
5292
|
+
slug: "red-velvet",
|
|
5293
|
+
price: "22.00",
|
|
5294
|
+
description: "Classic red velvet with cream cheese frosting and white chocolate shavings.",
|
|
5295
|
+
category: "cakes",
|
|
5296
|
+
imageUrl: img("1586788680434-30d324b2d46f"),
|
|
5297
|
+
tags: ["cake"],
|
|
5298
|
+
ingredients: ["Buttermilk", "Cocoa", "Cream cheese", "Beetroot"],
|
|
5299
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5300
|
+
calories: 460
|
|
5301
|
+
},
|
|
5302
|
+
{
|
|
5303
|
+
id: "prod_carrot-cake",
|
|
5304
|
+
name: "Carrot Cake (Slice)",
|
|
5305
|
+
slug: "carrot-cake",
|
|
5306
|
+
price: "20.00",
|
|
5307
|
+
description: "Spiced carrot cake with toasted walnuts, golden raisins, and cream-cheese frosting.",
|
|
5308
|
+
category: "cakes",
|
|
5309
|
+
imageUrl: img("1568827999250-3f6afff96e66"),
|
|
5310
|
+
tags: ["cake", "spiced"],
|
|
5311
|
+
ingredients: ["Carrot", "Walnuts", "Cinnamon", "Cream cheese"],
|
|
5312
|
+
allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
|
|
5313
|
+
calories: 420
|
|
5314
|
+
},
|
|
5315
|
+
{
|
|
5316
|
+
id: "prod_cheesecake",
|
|
5317
|
+
name: "New York Cheesecake (Slice)",
|
|
5318
|
+
slug: "cheesecake",
|
|
5319
|
+
price: "24.00",
|
|
5320
|
+
description: "Dense, creamy New York cheesecake on a butter graham crust. Topped with seasonal berry compote.",
|
|
5321
|
+
category: "cakes",
|
|
5322
|
+
imageUrl: img("1567306226416-28f0efdc88ce"),
|
|
5323
|
+
tags: ["cake", "premium"],
|
|
5324
|
+
ingredients: ["Cream cheese", "Sour cream", "Graham crust", "Vanilla"],
|
|
5325
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5326
|
+
calories: 520
|
|
5327
|
+
},
|
|
5328
|
+
{
|
|
5329
|
+
id: "prod_birthday-cake",
|
|
5330
|
+
name: "Custom Celebration Cake",
|
|
5331
|
+
slug: "birthday-cake",
|
|
5332
|
+
price: "250.00",
|
|
5333
|
+
description: "Made-to-order three-tier cake. Choose size, flavour, and frosting. 48-hour notice required for personalisation.",
|
|
5334
|
+
category: "cakes",
|
|
5335
|
+
imageUrl: img("1535254973040-607b474cb50d"),
|
|
5336
|
+
images: [img("1535254973040-607b474cb50d"), img("1565958011703-44f9829ba187")],
|
|
5337
|
+
tags: ["cake", "made-to-order", "premium"],
|
|
5338
|
+
ingredients: ["Sponge cake", "Buttercream", "Custom decoration"],
|
|
5339
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5340
|
+
productType: "product"
|
|
5341
|
+
},
|
|
5342
|
+
// ── Drinks ──
|
|
5343
|
+
{
|
|
5344
|
+
id: "prod_coffee",
|
|
5345
|
+
name: "Single-Origin Coffee",
|
|
5346
|
+
slug: "coffee",
|
|
5347
|
+
price: "10.00",
|
|
5348
|
+
description: "Locally roasted Arabica from Volta Region. Notes of cocoa, dark fruit, and brown sugar.",
|
|
5349
|
+
category: "drinks",
|
|
5350
|
+
imageUrl: img("1495474472287-4d71bcdd2085"),
|
|
5351
|
+
tags: ["drink", "coffee", "vegan"],
|
|
5352
|
+
ingredients: ["Single-origin Arabica beans"],
|
|
5353
|
+
pairings: ["Croissant", "Brownie"],
|
|
5354
|
+
calories: 5
|
|
5355
|
+
},
|
|
5356
|
+
{
|
|
5357
|
+
id: "prod_tea",
|
|
5358
|
+
name: "Loose-Leaf Tea",
|
|
5359
|
+
slug: "tea",
|
|
5360
|
+
price: "8.00",
|
|
5361
|
+
description: "Choice of English breakfast, Earl Grey, green sencha, or chamomile.",
|
|
5362
|
+
category: "drinks",
|
|
5363
|
+
imageUrl: img("1576092768241-dec231879fc3"),
|
|
5364
|
+
tags: ["drink", "tea", "vegan"],
|
|
5365
|
+
calories: 0
|
|
5366
|
+
},
|
|
5367
|
+
{
|
|
5368
|
+
id: "prod_sobolo",
|
|
5369
|
+
name: "Hibiscus (Sobolo)",
|
|
5370
|
+
slug: "sobolo",
|
|
5371
|
+
price: "12.00",
|
|
5372
|
+
description: "Iced hibiscus tea with ginger, pineapple peel, and a hint of clove. Tart, refreshing, lightly sweet.",
|
|
5373
|
+
category: "drinks",
|
|
5374
|
+
imageUrl: img("1556679343-c7306c1976bc"),
|
|
5375
|
+
tags: ["drink", "vegan", "ghana"],
|
|
5376
|
+
isSignature: true,
|
|
5377
|
+
ingredients: ["Hibiscus", "Ginger", "Pineapple", "Clove"],
|
|
5378
|
+
calories: 60
|
|
5379
|
+
},
|
|
5380
|
+
{
|
|
5381
|
+
id: "prod_orange-juice",
|
|
5382
|
+
name: "Cold-Pressed Orange Juice",
|
|
5383
|
+
slug: "orange-juice",
|
|
5384
|
+
price: "15.00",
|
|
5385
|
+
description: "Hand-pressed at sunrise from Ghanaian Valencia oranges. No sugar, no concentrate.",
|
|
5386
|
+
category: "drinks",
|
|
5387
|
+
imageUrl: img("1622597467836-f3285f2131b8"),
|
|
5388
|
+
tags: ["drink", "vegan"],
|
|
5389
|
+
calories: 110
|
|
5390
|
+
},
|
|
5391
|
+
// ── Boxes (anchor products for bundle/composite) ──
|
|
5392
|
+
{
|
|
5393
|
+
id: "prod_breakfast-box",
|
|
5394
|
+
name: "Breakfast Box",
|
|
5395
|
+
slug: "breakfast-box",
|
|
5396
|
+
price: "30.00",
|
|
5397
|
+
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.",
|
|
5398
|
+
category: "boxes",
|
|
5399
|
+
imageUrl: img("1533089860892-a7c6f0a88666"),
|
|
5400
|
+
images: [img("1533089860892-a7c6f0a88666"), img("1495474472287-4d71bcdd2085")],
|
|
5401
|
+
tags: ["bundle", "breakfast"],
|
|
5402
|
+
productType: "bundle"
|
|
5403
|
+
},
|
|
5404
|
+
{
|
|
5405
|
+
id: "prod_pastry-box",
|
|
5406
|
+
name: "Build-Your-Own Pastry Box",
|
|
5407
|
+
slug: "pastry-box",
|
|
5408
|
+
price: "60.00",
|
|
5409
|
+
description: "Pick 6 to 12 of our pastries \u2014 mix and match. Freshly boxed for sharing.",
|
|
5410
|
+
category: "boxes",
|
|
5411
|
+
imageUrl: img("1486427944299-d1955d23e34d"),
|
|
5412
|
+
tags: ["composite", "build-your-own", "gift"],
|
|
5413
|
+
productType: "composite"
|
|
5414
|
+
}
|
|
5415
|
+
];
|
|
5416
|
+
for (const input of productInputs) {
|
|
5417
|
+
const product = makeProduct(input, catIds);
|
|
5418
|
+
registry.products.put(product.id, product);
|
|
5419
|
+
const cat = registry.categories.get(catIds.get(input.category));
|
|
5420
|
+
if (cat) {
|
|
5421
|
+
cat.product_ids.push(product.id);
|
|
5422
|
+
registry.categories.put(cat.id, cat);
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
const featuredIds = [
|
|
5426
|
+
"prod_sourdough-loaf",
|
|
5427
|
+
"prod_croissant",
|
|
5428
|
+
"prod_chocolate-cake",
|
|
5429
|
+
"prod_sobolo",
|
|
5430
|
+
"prod_pastry-box"
|
|
5431
|
+
];
|
|
5432
|
+
const featured = {
|
|
4718
5433
|
id: "col_featured",
|
|
4719
|
-
business_id:
|
|
4720
|
-
name: "Featured",
|
|
5434
|
+
business_id: BUSINESS_ID,
|
|
5435
|
+
name: "Signature & Featured",
|
|
4721
5436
|
slug: "featured",
|
|
4722
|
-
description: "Our most
|
|
4723
|
-
product_ids:
|
|
5437
|
+
description: "Our most-loved bakes and a few seasonal stars.",
|
|
5438
|
+
product_ids: featuredIds,
|
|
4724
5439
|
is_active: true,
|
|
4725
5440
|
created_at: ISO,
|
|
4726
5441
|
updated_at: ISO
|
|
4727
5442
|
};
|
|
4728
|
-
registry.collections.put(
|
|
4729
|
-
for (const pid of
|
|
5443
|
+
registry.collections.put(featured.id, featured);
|
|
5444
|
+
for (const pid of featuredIds) {
|
|
4730
5445
|
const p = registry.products.get(pid);
|
|
4731
5446
|
if (p) {
|
|
4732
|
-
p.collection_ids.push(
|
|
5447
|
+
p.collection_ids.push(featured.id);
|
|
4733
5448
|
registry.products.put(pid, p);
|
|
4734
5449
|
}
|
|
4735
5450
|
}
|
|
4736
|
-
const tasting = {
|
|
4737
|
-
id: "svc_tasting",
|
|
4738
|
-
business_id: businessId,
|
|
4739
|
-
name: "Bakery Tasting Session",
|
|
4740
|
-
description: "Walk through our pastry menu with our head baker.",
|
|
4741
|
-
duration_minutes: 60,
|
|
4742
|
-
price: "150.00",
|
|
4743
|
-
is_available: true
|
|
4744
|
-
};
|
|
4745
|
-
registry.services.put(tasting.id, tasting);
|
|
4746
|
-
const cake = {
|
|
4747
|
-
id: "svc_custom_cake",
|
|
4748
|
-
business_id: businessId,
|
|
4749
|
-
name: "Custom Cake Consultation",
|
|
4750
|
-
description: "Design a custom cake for your event.",
|
|
4751
|
-
duration_minutes: 30,
|
|
4752
|
-
price: "0.00",
|
|
4753
|
-
is_available: true
|
|
4754
|
-
};
|
|
4755
|
-
registry.services.put(cake.id, cake);
|
|
4756
|
-
const sizeAxisId = "axis_size";
|
|
4757
|
-
const flavourAxisId = "axis_flavour";
|
|
4758
|
-
const sizeValues = [
|
|
4759
|
-
{ id: "axv_size_small", name: 'Small (8")' },
|
|
4760
|
-
{ id: "axv_size_medium", name: 'Medium (10")' },
|
|
4761
|
-
{ id: "axv_size_large", name: 'Large (12")' }
|
|
4762
|
-
];
|
|
4763
|
-
const flavourValues = [
|
|
4764
|
-
{ id: "axv_flavour_chocolate", name: "Chocolate" },
|
|
4765
|
-
{ id: "axv_flavour_vanilla", name: "Vanilla" },
|
|
4766
|
-
{ id: "axv_flavour_red_velvet", name: "Red Velvet" }
|
|
4767
|
-
];
|
|
4768
5451
|
const sizeAxis = {
|
|
4769
|
-
id:
|
|
4770
|
-
business_id:
|
|
5452
|
+
id: "axis_cake_size",
|
|
5453
|
+
business_id: BUSINESS_ID,
|
|
4771
5454
|
product_id: "prod_birthday-cake",
|
|
4772
5455
|
name: "Size",
|
|
4773
5456
|
display_order: 0,
|
|
4774
5457
|
affects_recipe: false,
|
|
4775
|
-
values:
|
|
5458
|
+
values: [
|
|
5459
|
+
{ id: "axv_size_small", name: 'Small (8")', display: 0 },
|
|
5460
|
+
{ id: "axv_size_medium", name: 'Medium (10")', display: 1 },
|
|
5461
|
+
{ id: "axv_size_large", name: 'Large (12")', display: 2 }
|
|
5462
|
+
].map((v) => ({
|
|
4776
5463
|
id: v.id,
|
|
4777
|
-
business_id:
|
|
4778
|
-
axis_id:
|
|
5464
|
+
business_id: BUSINESS_ID,
|
|
5465
|
+
axis_id: "axis_cake_size",
|
|
4779
5466
|
name: v.name,
|
|
4780
|
-
display_order:
|
|
5467
|
+
display_order: v.display,
|
|
4781
5468
|
created_at: ISO,
|
|
4782
5469
|
updated_at: ISO
|
|
4783
5470
|
})),
|
|
@@ -4785,18 +5472,22 @@ function seedDefault(registry) {
|
|
|
4785
5472
|
updated_at: ISO
|
|
4786
5473
|
};
|
|
4787
5474
|
const flavourAxis = {
|
|
4788
|
-
id:
|
|
4789
|
-
business_id:
|
|
5475
|
+
id: "axis_cake_flavour",
|
|
5476
|
+
business_id: BUSINESS_ID,
|
|
4790
5477
|
product_id: "prod_birthday-cake",
|
|
4791
5478
|
name: "Flavour",
|
|
4792
5479
|
display_order: 1,
|
|
4793
5480
|
affects_recipe: true,
|
|
4794
|
-
values:
|
|
5481
|
+
values: [
|
|
5482
|
+
{ id: "axv_flavour_chocolate", name: "Dark Chocolate", display: 0 },
|
|
5483
|
+
{ id: "axv_flavour_vanilla", name: "Vanilla Bean", display: 1 },
|
|
5484
|
+
{ id: "axv_flavour_red_velvet", name: "Red Velvet", display: 2 }
|
|
5485
|
+
].map((v) => ({
|
|
4795
5486
|
id: v.id,
|
|
4796
|
-
business_id:
|
|
4797
|
-
axis_id:
|
|
5487
|
+
business_id: BUSINESS_ID,
|
|
5488
|
+
axis_id: "axis_cake_flavour",
|
|
4798
5489
|
name: v.name,
|
|
4799
|
-
display_order:
|
|
5490
|
+
display_order: v.display,
|
|
4800
5491
|
created_at: ISO,
|
|
4801
5492
|
updated_at: ISO
|
|
4802
5493
|
})),
|
|
@@ -4805,40 +5496,42 @@ function seedDefault(registry) {
|
|
|
4805
5496
|
};
|
|
4806
5497
|
registry.variantAxes.put(sizeAxis.id, sizeAxis);
|
|
4807
5498
|
registry.variantAxes.put(flavourAxis.id, flavourAxis);
|
|
4808
|
-
const
|
|
4809
|
-
const
|
|
4810
|
-
let
|
|
4811
|
-
for (
|
|
4812
|
-
for (
|
|
4813
|
-
const
|
|
5499
|
+
const sizeAdj = { small: "250.00", medium: "350.00", large: "450.00" };
|
|
5500
|
+
const cakeVariantIds = [];
|
|
5501
|
+
let firstVariant = true;
|
|
5502
|
+
for (const sz of sizeAxis.values) {
|
|
5503
|
+
for (const fl of flavourAxis.values) {
|
|
5504
|
+
const sizeKey = sz.id.replace("axv_size_", "");
|
|
5505
|
+
const flavourKey = fl.id.replace("axv_flavour_", "");
|
|
5506
|
+
const id = `var_size_${sizeKey}_flavour_${flavourKey}`;
|
|
4814
5507
|
const variant = {
|
|
4815
5508
|
id,
|
|
4816
5509
|
product_id: "prod_birthday-cake",
|
|
4817
|
-
business_id:
|
|
4818
|
-
name: `${
|
|
4819
|
-
sku: `BIRTHDAY-${
|
|
4820
|
-
price_adjustment:
|
|
5510
|
+
business_id: BUSINESS_ID,
|
|
5511
|
+
name: `${sz.name} \u2022 ${fl.name}`,
|
|
5512
|
+
sku: `BIRTHDAY-${sizeKey.toUpperCase()}-${flavourKey.toUpperCase()}`,
|
|
5513
|
+
price_adjustment: sizeAdj[sizeKey] ?? "0.00",
|
|
4821
5514
|
component_multiplier: "1.00",
|
|
4822
|
-
is_default:
|
|
5515
|
+
is_default: firstVariant,
|
|
4823
5516
|
is_active: true,
|
|
4824
|
-
axis_value_ids: [
|
|
5517
|
+
axis_value_ids: [sz.id, fl.id],
|
|
4825
5518
|
created_at: ISO,
|
|
4826
5519
|
updated_at: ISO
|
|
4827
5520
|
};
|
|
4828
5521
|
registry.variants.put(id, variant);
|
|
4829
|
-
|
|
4830
|
-
|
|
5522
|
+
cakeVariantIds.push(id);
|
|
5523
|
+
firstVariant = false;
|
|
4831
5524
|
}
|
|
4832
5525
|
}
|
|
4833
|
-
const
|
|
4834
|
-
if (
|
|
4835
|
-
|
|
4836
|
-
registry.products.put(
|
|
5526
|
+
const birthdayProduct = registry.products.get("prod_birthday-cake");
|
|
5527
|
+
if (birthdayProduct) {
|
|
5528
|
+
birthdayProduct.variant_ids = cakeVariantIds;
|
|
5529
|
+
registry.products.put(birthdayProduct.id, birthdayProduct);
|
|
4837
5530
|
}
|
|
4838
5531
|
const toppingsAddOn = {
|
|
4839
5532
|
id: "addon_toppings",
|
|
4840
|
-
business_id:
|
|
4841
|
-
name: "
|
|
5533
|
+
business_id: BUSINESS_ID,
|
|
5534
|
+
name: "Cake toppings",
|
|
4842
5535
|
is_multiple_allowed: true,
|
|
4843
5536
|
is_required: false,
|
|
4844
5537
|
is_mutually_exclusive: false,
|
|
@@ -4847,15 +5540,14 @@ function seedDefault(registry) {
|
|
|
4847
5540
|
created_at: ISO,
|
|
4848
5541
|
updated_at: ISO,
|
|
4849
5542
|
options: [
|
|
4850
|
-
{ id: "addopt_strawberry", add_on_id: "addon_toppings", business_id:
|
|
4851
|
-
{ id: "addopt_choc_chips", add_on_id: "addon_toppings", business_id:
|
|
4852
|
-
{ id: "addopt_walnuts", add_on_id: "addon_toppings", business_id:
|
|
5543
|
+
{ 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 },
|
|
5544
|
+
{ 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 },
|
|
5545
|
+
{ 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 }
|
|
4853
5546
|
]
|
|
4854
5547
|
};
|
|
4855
|
-
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
4856
5548
|
const milkAddOn = {
|
|
4857
5549
|
id: "addon_milk",
|
|
4858
|
-
business_id:
|
|
5550
|
+
business_id: BUSINESS_ID,
|
|
4859
5551
|
name: "Milk",
|
|
4860
5552
|
is_multiple_allowed: false,
|
|
4861
5553
|
is_required: false,
|
|
@@ -4865,17 +5557,17 @@ function seedDefault(registry) {
|
|
|
4865
5557
|
created_at: ISO,
|
|
4866
5558
|
updated_at: ISO,
|
|
4867
5559
|
options: [
|
|
4868
|
-
{ id: "addopt_whole", add_on_id: "addon_milk", business_id:
|
|
4869
|
-
{ id: "addopt_oat", add_on_id: "addon_milk", business_id:
|
|
4870
|
-
{ id: "addopt_almond", add_on_id: "addon_milk", business_id:
|
|
5560
|
+
{ 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 },
|
|
5561
|
+
{ 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 },
|
|
5562
|
+
{ 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 }
|
|
4871
5563
|
]
|
|
4872
5564
|
};
|
|
5565
|
+
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
4873
5566
|
registry.addOns.put(milkAddOn.id, milkAddOn);
|
|
4874
5567
|
const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
|
|
4875
5568
|
const drinkProductIds = ["prod_coffee", "prod_tea"];
|
|
4876
5569
|
for (const pid of cakeProductIds) {
|
|
4877
|
-
|
|
4878
|
-
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
|
|
5570
|
+
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
|
|
4879
5571
|
const p = registry.products.get(pid);
|
|
4880
5572
|
if (p) {
|
|
4881
5573
|
p.add_on_ids.push(toppingsAddOn.id);
|
|
@@ -4883,8 +5575,7 @@ function seedDefault(registry) {
|
|
|
4883
5575
|
}
|
|
4884
5576
|
}
|
|
4885
5577
|
for (const pid of drinkProductIds) {
|
|
4886
|
-
|
|
4887
|
-
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
|
|
5578
|
+
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
|
|
4888
5579
|
const p = registry.products.get(pid);
|
|
4889
5580
|
if (p) {
|
|
4890
5581
|
p.add_on_ids.push(milkAddOn.id);
|
|
@@ -4893,7 +5584,7 @@ function seedDefault(registry) {
|
|
|
4893
5584
|
}
|
|
4894
5585
|
const breakfastBundle = {
|
|
4895
5586
|
id: "bnd_breakfast_box",
|
|
4896
|
-
business_id:
|
|
5587
|
+
business_id: BUSINESS_ID,
|
|
4897
5588
|
product_id: "prod_breakfast-box",
|
|
4898
5589
|
name: "Breakfast Box",
|
|
4899
5590
|
slug: "breakfast-box",
|
|
@@ -4905,29 +5596,9 @@ function seedDefault(registry) {
|
|
|
4905
5596
|
updated_at: ISO
|
|
4906
5597
|
};
|
|
4907
5598
|
registry.bundles.put(breakfastBundle.id, breakfastBundle);
|
|
4908
|
-
const breakfastProduct = {
|
|
4909
|
-
id: "prod_breakfast-box",
|
|
4910
|
-
business_id: businessId,
|
|
4911
|
-
name: "Breakfast Box",
|
|
4912
|
-
slug: "breakfast-box",
|
|
4913
|
-
description: "Bundled breakfast for one.",
|
|
4914
|
-
product_type: "bundle",
|
|
4915
|
-
base_price: "30.00",
|
|
4916
|
-
currency: "GHS",
|
|
4917
|
-
image: "https://mock.cimplify.io/img/breakfast-box.jpg",
|
|
4918
|
-
images: ["https://mock.cimplify.io/img/breakfast-box.jpg"],
|
|
4919
|
-
is_available: true,
|
|
4920
|
-
category_ids: [categoryIdsBySlug.get("pastries")],
|
|
4921
|
-
collection_ids: [],
|
|
4922
|
-
add_on_ids: [],
|
|
4923
|
-
variant_ids: [],
|
|
4924
|
-
created_at: ISO,
|
|
4925
|
-
updated_at: ISO
|
|
4926
|
-
};
|
|
4927
|
-
registry.products.put(breakfastProduct.id, breakfastProduct);
|
|
4928
5599
|
const pastryComposite = {
|
|
4929
5600
|
id: "comp_pastry_box",
|
|
4930
|
-
business_id:
|
|
5601
|
+
business_id: BUSINESS_ID,
|
|
4931
5602
|
product_id: "prod_pastry-box",
|
|
4932
5603
|
base_price: "60.00",
|
|
4933
5604
|
pricing_mode: "additive",
|
|
@@ -4939,43 +5610,51 @@ function seedDefault(registry) {
|
|
|
4939
5610
|
name: "Choose your pastries",
|
|
4940
5611
|
min: 6,
|
|
4941
5612
|
max: 12,
|
|
4942
|
-
component_ids: [
|
|
5613
|
+
component_ids: [
|
|
5614
|
+
"prod_croissant",
|
|
5615
|
+
"prod_pain-au-chocolat",
|
|
5616
|
+
"prod_almond-danish",
|
|
5617
|
+
"prod_cinnamon-roll",
|
|
5618
|
+
"prod_brownie",
|
|
5619
|
+
"prod_banana-bread",
|
|
5620
|
+
"prod_macaron-box"
|
|
5621
|
+
]
|
|
4943
5622
|
}
|
|
4944
5623
|
],
|
|
4945
5624
|
created_at: ISO,
|
|
4946
5625
|
updated_at: ISO
|
|
4947
5626
|
};
|
|
4948
5627
|
registry.composites.put(pastryComposite.id, pastryComposite);
|
|
4949
|
-
const
|
|
4950
|
-
id: "
|
|
4951
|
-
business_id:
|
|
4952
|
-
name: "
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
updated_at: ISO
|
|
5628
|
+
const tasting = {
|
|
5629
|
+
id: "svc_tasting",
|
|
5630
|
+
business_id: BUSINESS_ID,
|
|
5631
|
+
name: "Bakery Tasting Session",
|
|
5632
|
+
description: "Sample our pastry menu with our head baker.",
|
|
5633
|
+
duration_minutes: 60,
|
|
5634
|
+
price: "150.00",
|
|
5635
|
+
is_available: true
|
|
5636
|
+
};
|
|
5637
|
+
const consult = {
|
|
5638
|
+
id: "svc_custom_cake",
|
|
5639
|
+
business_id: BUSINESS_ID,
|
|
5640
|
+
name: "Custom Cake Consultation",
|
|
5641
|
+
description: "Design a custom cake for your event.",
|
|
5642
|
+
duration_minutes: 30,
|
|
5643
|
+
price: "0.00",
|
|
5644
|
+
is_available: true
|
|
4967
5645
|
};
|
|
4968
|
-
registry.
|
|
5646
|
+
registry.services.put(tasting.id, tasting);
|
|
5647
|
+
registry.services.put(consult.id, consult);
|
|
4969
5648
|
const tags = [
|
|
4970
|
-
{ id: "tag_signature", business_id:
|
|
4971
|
-
{ id: "tag_vegan", business_id:
|
|
4972
|
-
{ id: "tag_gluten_free", business_id:
|
|
4973
|
-
{ id: "tag_seasonal", business_id:
|
|
5649
|
+
{ 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 },
|
|
5650
|
+
{ 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 },
|
|
5651
|
+
{ 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 },
|
|
5652
|
+
{ 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 }
|
|
4974
5653
|
];
|
|
4975
5654
|
for (const t of tags) registry.tags.put(t.id, t);
|
|
4976
5655
|
const baked = {
|
|
4977
5656
|
id: "tax_baked_goods",
|
|
4978
|
-
business_id:
|
|
5657
|
+
business_id: BUSINESS_ID,
|
|
4979
5658
|
name: "Baked Goods",
|
|
4980
5659
|
slug: "baked-goods",
|
|
4981
5660
|
path: ["Food", "Baked Goods"],
|
|
@@ -4985,7 +5664,7 @@ function seedDefault(registry) {
|
|
|
4985
5664
|
};
|
|
4986
5665
|
const beverages = {
|
|
4987
5666
|
id: "tax_beverages",
|
|
4988
|
-
business_id:
|
|
5667
|
+
business_id: BUSINESS_ID,
|
|
4989
5668
|
name: "Beverages",
|
|
4990
5669
|
slug: "beverages",
|
|
4991
5670
|
path: ["Food", "Beverages"],
|
|
@@ -4998,13 +5677,13 @@ function seedDefault(registry) {
|
|
|
4998
5677
|
const attrs = [
|
|
4999
5678
|
{
|
|
5000
5679
|
id: "attr_allergens",
|
|
5001
|
-
business_id:
|
|
5680
|
+
business_id: BUSINESS_ID,
|
|
5002
5681
|
namespace: "core",
|
|
5003
5682
|
name: "Allergens",
|
|
5004
5683
|
slug: "allergens",
|
|
5005
5684
|
description: "Common allergens present in this item.",
|
|
5006
5685
|
attribute_type: "multi_select",
|
|
5007
|
-
options: ["wheat", "eggs", "dairy", "nuts", "soy"],
|
|
5686
|
+
options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
|
|
5008
5687
|
is_required: false,
|
|
5009
5688
|
is_filterable: true,
|
|
5010
5689
|
visibility: "public",
|
|
@@ -5015,7 +5694,7 @@ function seedDefault(registry) {
|
|
|
5015
5694
|
},
|
|
5016
5695
|
{
|
|
5017
5696
|
id: "attr_shelf_life",
|
|
5018
|
-
business_id:
|
|
5697
|
+
business_id: BUSINESS_ID,
|
|
5019
5698
|
namespace: "core",
|
|
5020
5699
|
name: "Shelf life",
|
|
5021
5700
|
slug: "shelf-life",
|
|
@@ -5030,7 +5709,7 @@ function seedDefault(registry) {
|
|
|
5030
5709
|
},
|
|
5031
5710
|
{
|
|
5032
5711
|
id: "attr_caffeine",
|
|
5033
|
-
business_id:
|
|
5712
|
+
business_id: BUSINESS_ID,
|
|
5034
5713
|
namespace: "core",
|
|
5035
5714
|
name: "Caffeine",
|
|
5036
5715
|
slug: "caffeine",
|
|
@@ -5045,7 +5724,7 @@ function seedDefault(registry) {
|
|
|
5045
5724
|
},
|
|
5046
5725
|
{
|
|
5047
5726
|
id: "attr_seasonal",
|
|
5048
|
-
business_id:
|
|
5727
|
+
business_id: BUSINESS_ID,
|
|
5049
5728
|
namespace: "core",
|
|
5050
5729
|
name: "Seasonal",
|
|
5051
5730
|
slug: "seasonal",
|
|
@@ -5063,10 +5742,10 @@ function seedDefault(registry) {
|
|
|
5063
5742
|
const articles = [
|
|
5064
5743
|
{
|
|
5065
5744
|
id: "kb_delivery",
|
|
5066
|
-
business_id:
|
|
5067
|
-
title: "Delivery &
|
|
5745
|
+
business_id: BUSINESS_ID,
|
|
5746
|
+
title: "Delivery & pickup",
|
|
5068
5747
|
slug: "delivery-pickup",
|
|
5069
|
-
content: "Same-day delivery within Accra for orders placed before 2pm. Pickup available 8am\u20138pm.",
|
|
5748
|
+
content: "Same-day delivery within Greater Accra for orders placed before 2pm. Pickup available 8am\u20138pm at our East Legon location.",
|
|
5070
5749
|
category: "logistics",
|
|
5071
5750
|
tags: ["delivery", "pickup"],
|
|
5072
5751
|
created_at: ISO,
|
|
@@ -5074,10 +5753,10 @@ function seedDefault(registry) {
|
|
|
5074
5753
|
},
|
|
5075
5754
|
{
|
|
5076
5755
|
id: "kb_custom_cakes",
|
|
5077
|
-
business_id:
|
|
5078
|
-
title: "
|
|
5756
|
+
business_id: BUSINESS_ID,
|
|
5757
|
+
title: "Custom cakes & celebrations",
|
|
5079
5758
|
slug: "custom-cakes",
|
|
5080
|
-
content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page.",
|
|
5759
|
+
content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page to walk through flavours, sizes, and decoration.",
|
|
5081
5760
|
category: "products",
|
|
5082
5761
|
tags: ["cakes", "custom"],
|
|
5083
5762
|
created_at: ISO,
|
|
@@ -5085,10 +5764,10 @@ function seedDefault(registry) {
|
|
|
5085
5764
|
},
|
|
5086
5765
|
{
|
|
5087
5766
|
id: "kb_allergens",
|
|
5088
|
-
business_id:
|
|
5089
|
-
title: "
|
|
5767
|
+
business_id: BUSINESS_ID,
|
|
5768
|
+
title: "Allergen information",
|
|
5090
5769
|
slug: "allergens",
|
|
5091
|
-
content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible.",
|
|
5770
|
+
content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible \u2014 let us know about severe allergies before ordering.",
|
|
5092
5771
|
category: "products",
|
|
5093
5772
|
tags: ["allergens", "safety"],
|
|
5094
5773
|
created_at: ISO,
|
|
@@ -5096,7 +5775,7 @@ function seedDefault(registry) {
|
|
|
5096
5775
|
}
|
|
5097
5776
|
];
|
|
5098
5777
|
for (const a of articles) registry.knowledgeArticles.put(a.id, a);
|
|
5099
|
-
return { businessId };
|
|
5778
|
+
return { businessId: BUSINESS_ID };
|
|
5100
5779
|
}
|
|
5101
5780
|
|
|
5102
5781
|
// src/mock/seeds/empty.ts
|
|
@@ -5142,7 +5821,7 @@ function seedReesa(registry) {
|
|
|
5142
5821
|
default_address: "5 Allen Avenue, Ikeja, Lagos",
|
|
5143
5822
|
default_offers_table_service: false,
|
|
5144
5823
|
default_accepts_online_orders: true,
|
|
5145
|
-
image: "
|
|
5824
|
+
image: "/img/reesa-logo.png",
|
|
5146
5825
|
status: "active",
|
|
5147
5826
|
created_at: ISO3,
|
|
5148
5827
|
updated_at: ISO3,
|
|
@@ -5198,8 +5877,8 @@ function seedReesa(registry) {
|
|
|
5198
5877
|
product_type: "product",
|
|
5199
5878
|
base_price: p.price,
|
|
5200
5879
|
currency: "NGN",
|
|
5201
|
-
image:
|
|
5202
|
-
images: [
|
|
5880
|
+
image: `/img/reesa/${p.slug}.jpg`,
|
|
5881
|
+
images: [`/img/reesa/${p.slug}.jpg`],
|
|
5203
5882
|
is_available: true,
|
|
5204
5883
|
category_ids: [catId],
|
|
5205
5884
|
collection_ids: [],
|
|
@@ -5422,8 +6101,8 @@ function seedReesa(registry) {
|
|
|
5422
6101
|
product_type: "bundle",
|
|
5423
6102
|
base_price: "55000.00",
|
|
5424
6103
|
currency: "NGN",
|
|
5425
|
-
image: "
|
|
5426
|
-
images: ["
|
|
6104
|
+
image: "/img/reesa/owambe.jpg",
|
|
6105
|
+
images: ["/img/reesa/owambe.jpg"],
|
|
5427
6106
|
is_available: true,
|
|
5428
6107
|
category_ids: [catIdsBySlug.get("apparel")],
|
|
5429
6108
|
collection_ids: [],
|
|
@@ -5554,7 +6233,7 @@ function seedRestaurant(registry) {
|
|
|
5554
6233
|
default_address: "8 Ring Road, East Legon, Accra",
|
|
5555
6234
|
default_offers_table_service: true,
|
|
5556
6235
|
default_accepts_online_orders: true,
|
|
5557
|
-
image: "
|
|
6236
|
+
image: "/img/mamas-logo.png",
|
|
5558
6237
|
status: "active",
|
|
5559
6238
|
created_at: ISO4,
|
|
5560
6239
|
updated_at: ISO4,
|
|
@@ -5625,8 +6304,8 @@ function seedRestaurant(registry) {
|
|
|
5625
6304
|
product_type: "product",
|
|
5626
6305
|
base_price: p.price,
|
|
5627
6306
|
currency: "GHS",
|
|
5628
|
-
image:
|
|
5629
|
-
images: [
|
|
6307
|
+
image: `/img/mamas/${p.slug}.jpg`,
|
|
6308
|
+
images: [`/img/mamas/${p.slug}.jpg`],
|
|
5630
6309
|
is_available: true,
|
|
5631
6310
|
category_ids: [catId],
|
|
5632
6311
|
collection_ids: [],
|
|
@@ -5864,8 +6543,8 @@ function seedRestaurant(registry) {
|
|
|
5864
6543
|
product_type: "bundle",
|
|
5865
6544
|
base_price: "180.00",
|
|
5866
6545
|
currency: "GHS",
|
|
5867
|
-
image: "
|
|
5868
|
-
images: ["
|
|
6546
|
+
image: "/img/mamas/feast.jpg",
|
|
6547
|
+
images: ["/img/mamas/feast.jpg"],
|
|
5869
6548
|
is_available: true,
|
|
5870
6549
|
category_ids: [catIds.get("mains")],
|
|
5871
6550
|
collection_ids: [],
|
|
@@ -5900,8 +6579,8 @@ function seedRestaurant(registry) {
|
|
|
5900
6579
|
product_type: "composite",
|
|
5901
6580
|
base_price: "0.00",
|
|
5902
6581
|
currency: "GHS",
|
|
5903
|
-
image: "
|
|
5904
|
-
images: ["
|
|
6582
|
+
image: "/img/mamas/byo.jpg",
|
|
6583
|
+
images: ["/img/mamas/byo.jpg"],
|
|
5905
6584
|
is_available: true,
|
|
5906
6585
|
category_ids: [catIds.get("mains")],
|
|
5907
6586
|
collection_ids: [],
|
|
@@ -6013,7 +6692,7 @@ function seedRetail(registry) {
|
|
|
6013
6692
|
default_address: "Atomic Junction, Accra",
|
|
6014
6693
|
default_offers_table_service: false,
|
|
6015
6694
|
default_accepts_online_orders: true,
|
|
6016
|
-
image: "
|
|
6695
|
+
image: "/img/currents-logo.png",
|
|
6017
6696
|
status: "active",
|
|
6018
6697
|
created_at: ISO5,
|
|
6019
6698
|
updated_at: ISO5,
|
|
@@ -6083,8 +6762,8 @@ function seedRetail(registry) {
|
|
|
6083
6762
|
product_type: "product",
|
|
6084
6763
|
base_price: p.price,
|
|
6085
6764
|
currency: "GHS",
|
|
6086
|
-
image:
|
|
6087
|
-
images: [
|
|
6765
|
+
image: `/img/currents/${p.slug}.jpg`,
|
|
6766
|
+
images: [`/img/currents/${p.slug}.jpg`],
|
|
6088
6767
|
is_available: true,
|
|
6089
6768
|
category_ids: [catId],
|
|
6090
6769
|
collection_ids: [],
|
|
@@ -6347,8 +7026,8 @@ function seedRetail(registry) {
|
|
|
6347
7026
|
product_type: "bundle",
|
|
6348
7027
|
base_price: "11910.00",
|
|
6349
7028
|
currency: "GHS",
|
|
6350
|
-
image: "
|
|
6351
|
-
images: ["
|
|
7029
|
+
image: "/img/currents/wfh.jpg",
|
|
7030
|
+
images: ["/img/currents/wfh.jpg"],
|
|
6352
7031
|
is_available: true,
|
|
6353
7032
|
category_ids: [catIds.get("laptops")],
|
|
6354
7033
|
collection_ids: [],
|
|
@@ -6381,8 +7060,8 @@ function seedRetail(registry) {
|
|
|
6381
7060
|
product_type: "bundle",
|
|
6382
7061
|
base_price: "4200.00",
|
|
6383
7062
|
currency: "GHS",
|
|
6384
|
-
image: "
|
|
6385
|
-
images: ["
|
|
7063
|
+
image: "/img/currents/game-pack.jpg",
|
|
7064
|
+
images: ["/img/currents/game-pack.jpg"],
|
|
6386
7065
|
is_available: true,
|
|
6387
7066
|
category_ids: [catIds.get("gaming")],
|
|
6388
7067
|
collection_ids: [],
|
|
@@ -6485,7 +7164,7 @@ function seedServices(registry) {
|
|
|
6485
7164
|
default_address: "12 Cantonments Road, Accra",
|
|
6486
7165
|
default_offers_table_service: false,
|
|
6487
7166
|
default_accepts_online_orders: true,
|
|
6488
|
-
image: "
|
|
7167
|
+
image: "/img/serene-logo.png",
|
|
6489
7168
|
status: "active",
|
|
6490
7169
|
created_at: ISO6,
|
|
6491
7170
|
updated_at: ISO6,
|
|
@@ -6553,8 +7232,8 @@ function seedServices(registry) {
|
|
|
6553
7232
|
product_type: "service",
|
|
6554
7233
|
base_price: s.price,
|
|
6555
7234
|
currency: "GHS",
|
|
6556
|
-
image:
|
|
6557
|
-
images: [
|
|
7235
|
+
image: `/img/serene/${s.slug}.jpg`,
|
|
7236
|
+
images: [`/img/serene/${s.slug}.jpg`],
|
|
6558
7237
|
is_available: true,
|
|
6559
7238
|
category_ids: [catId],
|
|
6560
7239
|
collection_ids: [],
|
|
@@ -6690,8 +7369,8 @@ function seedServices(registry) {
|
|
|
6690
7369
|
product_type: "bundle",
|
|
6691
7370
|
base_price: "480.00",
|
|
6692
7371
|
currency: "GHS",
|
|
6693
|
-
image: "
|
|
6694
|
-
images: ["
|
|
7372
|
+
image: "/img/serene/spa-day.jpg",
|
|
7373
|
+
images: ["/img/serene/spa-day.jpg"],
|
|
6695
7374
|
is_available: true,
|
|
6696
7375
|
category_ids: [catIds.get("wellness")],
|
|
6697
7376
|
collection_ids: [],
|
|
@@ -6724,8 +7403,8 @@ function seedServices(registry) {
|
|
|
6724
7403
|
product_type: "bundle",
|
|
6725
7404
|
base_price: "850.50",
|
|
6726
7405
|
currency: "GHS",
|
|
6727
|
-
image: "
|
|
6728
|
-
images: ["
|
|
7406
|
+
image: "/img/serene/couples.jpg",
|
|
7407
|
+
images: ["/img/serene/couples.jpg"],
|
|
6729
7408
|
is_available: true,
|
|
6730
7409
|
category_ids: [catIds.get("wellness")],
|
|
6731
7410
|
collection_ids: [],
|
|
@@ -6760,8 +7439,8 @@ function seedServices(registry) {
|
|
|
6760
7439
|
product_type: "composite",
|
|
6761
7440
|
base_price: "0.00",
|
|
6762
7441
|
currency: "GHS",
|
|
6763
|
-
image: "
|
|
6764
|
-
images: ["
|
|
7442
|
+
image: "/img/serene/custom.jpg",
|
|
7443
|
+
images: ["/img/serene/custom.jpg"],
|
|
6765
7444
|
is_available: true,
|
|
6766
7445
|
category_ids: [catIds.get("wellness")],
|
|
6767
7446
|
collection_ids: [],
|
|
@@ -6864,7 +7543,7 @@ function seedGrocery(registry) {
|
|
|
6864
7543
|
default_address: "Spintex Road, Accra",
|
|
6865
7544
|
default_offers_table_service: false,
|
|
6866
7545
|
default_accepts_online_orders: true,
|
|
6867
|
-
image: "
|
|
7546
|
+
image: "/img/freshmart-logo.png",
|
|
6868
7547
|
status: "active",
|
|
6869
7548
|
created_at: ISO7,
|
|
6870
7549
|
updated_at: ISO7,
|
|
@@ -6939,8 +7618,8 @@ function seedGrocery(registry) {
|
|
|
6939
7618
|
product_type: "product",
|
|
6940
7619
|
base_price: p.price,
|
|
6941
7620
|
currency: "GHS",
|
|
6942
|
-
image:
|
|
6943
|
-
images: [
|
|
7621
|
+
image: `/img/freshmart/${p.slug}.jpg`,
|
|
7622
|
+
images: [`/img/freshmart/${p.slug}.jpg`],
|
|
6944
7623
|
is_available: true,
|
|
6945
7624
|
category_ids: [catId],
|
|
6946
7625
|
collection_ids: [],
|
|
@@ -7023,8 +7702,8 @@ function seedGrocery(registry) {
|
|
|
7023
7702
|
product_type: "bundle",
|
|
7024
7703
|
base_price: "199.50",
|
|
7025
7704
|
currency: "GHS",
|
|
7026
|
-
image: "
|
|
7027
|
-
images: ["
|
|
7705
|
+
image: "/img/freshmart/essentials.jpg",
|
|
7706
|
+
images: ["/img/freshmart/essentials.jpg"],
|
|
7028
7707
|
is_available: true,
|
|
7029
7708
|
category_ids: [catIds.get("pantry")],
|
|
7030
7709
|
collection_ids: [],
|
|
@@ -7199,43 +7878,47 @@ function createDeps(options = {}) {
|
|
|
7199
7878
|
}
|
|
7200
7879
|
|
|
7201
7880
|
// src/mock/server.ts
|
|
7881
|
+
var PACKAGE_NAME = "@cimplify/sdk/mock";
|
|
7882
|
+
function corsMiddleware(allow) {
|
|
7883
|
+
return async (c, next) => {
|
|
7884
|
+
const origin = c.req.header("origin") ?? "";
|
|
7885
|
+
const allowOrigin = allow === "*" ? "*" : allow.includes(origin) ? origin : allow[0] ?? "*";
|
|
7886
|
+
const headers = {
|
|
7887
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
7888
|
+
"Access-Control-Allow-Credentials": "true",
|
|
7889
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7890
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7891
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
7892
|
+
"Access-Control-Max-Age": "86400",
|
|
7893
|
+
Vary: "Origin"
|
|
7894
|
+
};
|
|
7895
|
+
if (c.req.method === "OPTIONS") {
|
|
7896
|
+
return new Response(null, { status: 204, headers });
|
|
7897
|
+
}
|
|
7898
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7899
|
+
await next();
|
|
7900
|
+
};
|
|
7901
|
+
}
|
|
7202
7902
|
function createMockApp(options = {}) {
|
|
7203
7903
|
const deps = createDeps(options);
|
|
7204
7904
|
const app = new Hono2();
|
|
7205
|
-
const
|
|
7206
|
-
if (
|
|
7207
|
-
app.use("*", async (c, next) => {
|
|
7208
|
-
const origin = c.req.header("origin") ?? "";
|
|
7209
|
-
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7210
|
-
const headers = {
|
|
7211
|
-
"Access-Control-Allow-Origin": allow,
|
|
7212
|
-
"Access-Control-Allow-Credentials": "true",
|
|
7213
|
-
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7214
|
-
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7215
|
-
"Access-Control-Expose-Headers": "x-session-token",
|
|
7216
|
-
"Access-Control-Max-Age": "86400",
|
|
7217
|
-
Vary: "Origin"
|
|
7218
|
-
};
|
|
7219
|
-
if (c.req.method === "OPTIONS") {
|
|
7220
|
-
return new Response(null, { status: 204, headers });
|
|
7221
|
-
}
|
|
7222
|
-
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7223
|
-
await next();
|
|
7224
|
-
});
|
|
7225
|
-
}
|
|
7905
|
+
const cors = options.cors ?? "*";
|
|
7906
|
+
if (cors) app.use("*", corsMiddleware(cors));
|
|
7226
7907
|
app.get(
|
|
7227
7908
|
"/",
|
|
7228
7909
|
(c) => c.json({
|
|
7229
|
-
name:
|
|
7230
|
-
version: "0.36.0",
|
|
7910
|
+
name: PACKAGE_NAME,
|
|
7231
7911
|
docs: "https://github.com/cimplify/cimplify",
|
|
7232
7912
|
api: "/api/v1",
|
|
7233
7913
|
admin: "/_mock",
|
|
7234
7914
|
elements: "/elements",
|
|
7235
|
-
|
|
7915
|
+
images: "/img/<slug>",
|
|
7916
|
+
seed_business_id: deps.defaultBusinessId,
|
|
7917
|
+
time: deps.clock.iso()
|
|
7236
7918
|
})
|
|
7237
7919
|
);
|
|
7238
7920
|
app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
|
|
7921
|
+
mountImageRoutes(app);
|
|
7239
7922
|
app.route("/api/v1", buildV1(deps));
|
|
7240
7923
|
app.route("/_mock", buildAdmin(deps));
|
|
7241
7924
|
app.route("/elements", buildElements(deps));
|
|
@@ -7243,7 +7926,11 @@ function createMockApp(options = {}) {
|
|
|
7243
7926
|
(c) => c.json(
|
|
7244
7927
|
{
|
|
7245
7928
|
success: false,
|
|
7246
|
-
error: {
|
|
7929
|
+
error: {
|
|
7930
|
+
code: "ROUTE_NOT_REGISTERED",
|
|
7931
|
+
message: `Route ${c.req.method} ${c.req.path} not handled by mock`,
|
|
7932
|
+
retryable: false
|
|
7933
|
+
},
|
|
7247
7934
|
hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
|
|
7248
7935
|
},
|
|
7249
7936
|
501
|