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