@cimplify/sdk 0.38.2 → 0.38.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatcher.mjs +1 -1
- package/dist/mock/cli.mjs +956 -436
- package/dist/mock/library.d.mts +2 -2
- package/dist/mock/library.d.ts +2 -2
- package/dist/mock/library.js +956 -436
- package/dist/mock/library.mjs +956 -436
- package/dist/mock/msw.d.mts +1 -1
- package/dist/mock/msw.d.ts +1 -1
- package/dist/mock/msw.js +956 -436
- package/dist/mock/msw.mjs +956 -436
- package/dist/{server-B8TeE2OD.d.mts → server-BNy6Ri-4.d.mts} +18 -0
- package/dist/{server-B8TeE2OD.d.ts → server-BNy6Ri-4.d.ts} +18 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/templates/storefront/.env.example +11 -2
- package/templates/storefront/README.md +35 -18
- package/templates/storefront/app/cart/page.tsx +9 -0
- package/templates/storefront/app/categories/[slug]/page.tsx +80 -0
- package/templates/storefront/app/checkout/page.tsx +17 -0
- package/templates/storefront/app/collections/[slug]/page.tsx +80 -0
- package/templates/storefront/app/globals.css +47 -0
- package/templates/storefront/app/layout.tsx +45 -0
- package/templates/storefront/app/orders/[id]/page.tsx +21 -0
- package/templates/storefront/app/page.tsx +65 -0
- package/templates/storefront/app/shop/page.tsx +46 -0
- package/templates/storefront/components/category-grid.tsx +28 -0
- package/templates/storefront/components/collection-strip.tsx +45 -0
- package/templates/storefront/components/footer.tsx +19 -0
- package/templates/storefront/components/header.tsx +41 -0
- package/templates/storefront/components/hero.tsx +25 -0
- package/templates/storefront/components/product-modal.tsx +105 -0
- package/templates/storefront/components/providers.tsx +31 -0
- package/templates/storefront/components/store-product-card.tsx +48 -0
- package/templates/storefront/lib/cart.ts +27 -0
- package/templates/storefront/next.config.ts +36 -0
- package/templates/storefront/package.json +13 -14
- package/templates/storefront/postcss.config.mjs +7 -0
- package/templates/storefront/tsconfig.json +15 -11
- package/templates/storefront/index.html +0 -12
- package/templates/storefront/src/App.tsx +0 -118
- package/templates/storefront/src/main.tsx +0 -28
- package/templates/storefront/src/styles.css +0 -65
- package/templates/storefront/vite.config.ts +0 -8
package/dist/mock/library.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,51 +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
|
-
const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
|
|
3330
|
-
const defaultPriceInfo = (price, currency) => ({
|
|
3498
|
+
// src/mock/domain/catalogue/price-info.ts
|
|
3499
|
+
function defaultPriceInfo(price, currency) {
|
|
3500
|
+
return {
|
|
3331
3501
|
base_price: price,
|
|
3332
3502
|
final_price: price,
|
|
3503
|
+
pre_tax_price: price,
|
|
3333
3504
|
markup_percentage: "0.00",
|
|
3334
3505
|
markup_amount: "0.00",
|
|
3335
3506
|
markup_discount_percentage: "0.00",
|
|
3336
3507
|
markup_discount_amount: "0.00",
|
|
3337
|
-
currency
|
|
3338
|
-
|
|
3339
|
-
|
|
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) ───
|
|
3340
3552
|
id: p.id,
|
|
3341
3553
|
business_id: p.business_id,
|
|
3554
|
+
category_id: p.category_ids[0],
|
|
3342
3555
|
name: p.name,
|
|
3343
3556
|
slug: p.slug,
|
|
3344
3557
|
description: p.description ?? "",
|
|
3345
|
-
|
|
3346
|
-
|
|
3558
|
+
image_url: p.image,
|
|
3559
|
+
images: p.images,
|
|
3347
3560
|
default_price: p.base_price,
|
|
3348
|
-
|
|
3349
|
-
|
|
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) ───
|
|
3580
|
+
base_price: p.base_price,
|
|
3581
|
+
default_price_info: priceInfo,
|
|
3582
|
+
price_info: priceInfo,
|
|
3350
3583
|
currency: p.currency,
|
|
3584
|
+
// ─── Compatibility aliases (older callers) ───
|
|
3351
3585
|
image: p.image,
|
|
3352
|
-
|
|
3353
|
-
images: p.images,
|
|
3586
|
+
product_type: p.product_type,
|
|
3354
3587
|
is_available: p.is_available,
|
|
3355
|
-
category_id: p.category_ids[0],
|
|
3356
3588
|
category_ids: p.category_ids,
|
|
3357
3589
|
collection_ids: p.collection_ids,
|
|
3358
3590
|
add_on_ids: p.add_on_ids,
|
|
3359
|
-
|
|
3360
|
-
created_at: p.created_at,
|
|
3361
|
-
updated_at: p.updated_at,
|
|
3591
|
+
variant_ids: p.variant_ids,
|
|
3362
3592
|
custom_attributes: [],
|
|
3363
3593
|
properties: [],
|
|
3364
3594
|
has_variants: p.variant_ids.length > 0,
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
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);
|
|
3368
3715
|
const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
|
|
3369
3716
|
const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
|
|
3370
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];
|
|
3371
3741
|
return {
|
|
3372
3742
|
snapshot(businessId) {
|
|
3373
3743
|
const products = productsForBusiness(businessId);
|
|
@@ -3375,7 +3745,7 @@ function createCatalogueService(opts) {
|
|
|
3375
3745
|
const addOns = registry.addOns.list((a) => a.business_id === businessId);
|
|
3376
3746
|
return {
|
|
3377
3747
|
categories,
|
|
3378
|
-
products: products.map(
|
|
3748
|
+
products: products.map(apiProduct),
|
|
3379
3749
|
add_ons: addOns,
|
|
3380
3750
|
is_complete: true,
|
|
3381
3751
|
total_available: products.length
|
|
@@ -3390,7 +3760,7 @@ function createCatalogueService(opts) {
|
|
|
3390
3760
|
const start = (page - 1) * limit;
|
|
3391
3761
|
const slice = items.slice(start, start + limit);
|
|
3392
3762
|
return {
|
|
3393
|
-
items: slice.map(
|
|
3763
|
+
items: slice.map(apiProduct),
|
|
3394
3764
|
is_complete: start + limit >= items.length,
|
|
3395
3765
|
total_available: items.length,
|
|
3396
3766
|
pagination: {
|
|
@@ -3403,9 +3773,9 @@ function createCatalogueService(opts) {
|
|
|
3403
3773
|
};
|
|
3404
3774
|
},
|
|
3405
3775
|
getProduct(productId) {
|
|
3406
|
-
const p =
|
|
3776
|
+
const p = resolveProduct(productId);
|
|
3407
3777
|
if (!p) throw notFound(`Product ${productId} not found`);
|
|
3408
|
-
return
|
|
3778
|
+
return apiProduct(p);
|
|
3409
3779
|
},
|
|
3410
3780
|
getProductVariants(productId) {
|
|
3411
3781
|
return variantsForProduct(productId);
|
|
@@ -3415,7 +3785,9 @@ function createCatalogueService(opts) {
|
|
|
3415
3785
|
},
|
|
3416
3786
|
getProductVariantById(productId, variantId) {
|
|
3417
3787
|
const v = registry.variants.get(variantId);
|
|
3418
|
-
if (!v || v.product_id !== productId)
|
|
3788
|
+
if (!v || v.product_id !== productId) {
|
|
3789
|
+
throw notFound(`Variant ${variantId} not found on product ${productId}`);
|
|
3790
|
+
}
|
|
3419
3791
|
return v;
|
|
3420
3792
|
},
|
|
3421
3793
|
findProductVariant(productId, properties) {
|
|
@@ -3445,13 +3817,13 @@ function createCatalogueService(opts) {
|
|
|
3445
3817
|
return registry.categories.list((c) => c.business_id === businessId);
|
|
3446
3818
|
},
|
|
3447
3819
|
getCategory(id) {
|
|
3448
|
-
const c = registry.categories.get(id);
|
|
3820
|
+
const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
|
|
3449
3821
|
if (!c) throw notFound(`Category ${id} not found`);
|
|
3450
3822
|
return c;
|
|
3451
3823
|
},
|
|
3452
3824
|
getCategoryProducts(id) {
|
|
3453
3825
|
const c = this.getCategory(id);
|
|
3454
|
-
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);
|
|
3455
3827
|
},
|
|
3456
3828
|
getCategoryAttributes(id) {
|
|
3457
3829
|
const c = registry.categories.get(id);
|
|
@@ -3464,13 +3836,13 @@ function createCatalogueService(opts) {
|
|
|
3464
3836
|
return registry.collections.list((c) => c.business_id === businessId);
|
|
3465
3837
|
},
|
|
3466
3838
|
getCollection(id) {
|
|
3467
|
-
const c = registry.collections.get(id);
|
|
3839
|
+
const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
|
|
3468
3840
|
if (!c) throw notFound(`Collection ${id} not found`);
|
|
3469
3841
|
return c;
|
|
3470
3842
|
},
|
|
3471
3843
|
getCollectionProducts(id) {
|
|
3472
3844
|
const c = this.getCollection(id);
|
|
3473
|
-
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);
|
|
3474
3846
|
},
|
|
3475
3847
|
getCollectionAttributes(id) {
|
|
3476
3848
|
const c = registry.collections.get(id);
|
|
@@ -3483,7 +3855,7 @@ function createCatalogueService(opts) {
|
|
|
3483
3855
|
return registry.bundles.list((b) => b.business_id === businessId);
|
|
3484
3856
|
},
|
|
3485
3857
|
getBundle(id) {
|
|
3486
|
-
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];
|
|
3487
3859
|
if (!b) throw notFound(`Bundle ${id} not found`);
|
|
3488
3860
|
return b;
|
|
3489
3861
|
},
|
|
@@ -3491,12 +3863,16 @@ function createCatalogueService(opts) {
|
|
|
3491
3863
|
return registry.composites.list((c) => c.business_id === businessId);
|
|
3492
3864
|
},
|
|
3493
3865
|
getComposite(id) {
|
|
3494
|
-
const c = registry.composites.get(id);
|
|
3866
|
+
const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
|
|
3495
3867
|
if (!c) throw notFound(`Composite ${id} not found`);
|
|
3496
3868
|
return c;
|
|
3497
3869
|
},
|
|
3498
3870
|
getCompositeByProduct(productId) {
|
|
3499
|
-
|
|
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;
|
|
3500
3876
|
},
|
|
3501
3877
|
calculateCompositePrice(id, body) {
|
|
3502
3878
|
const c = registry.composites.get(id);
|
|
@@ -3504,14 +3880,20 @@ function createCatalogueService(opts) {
|
|
|
3504
3880
|
const sel = body.selections ?? [];
|
|
3505
3881
|
let total = c.base_price;
|
|
3506
3882
|
for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
|
|
3507
|
-
return {
|
|
3883
|
+
return {
|
|
3884
|
+
final_price: total,
|
|
3885
|
+
base_price: c.base_price,
|
|
3886
|
+
components_total: money(parseFloat(total) - parseFloat(c.base_price))
|
|
3887
|
+
};
|
|
3508
3888
|
},
|
|
3509
3889
|
listTaxonomies(businessId) {
|
|
3510
3890
|
return registry.taxonomies.list((t) => t.business_id === businessId);
|
|
3511
3891
|
},
|
|
3512
3892
|
searchTaxonomies(businessId, q) {
|
|
3513
3893
|
const term = q.toLowerCase();
|
|
3514
|
-
return registry.taxonomies.list(
|
|
3894
|
+
return registry.taxonomies.list(
|
|
3895
|
+
(t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
|
|
3896
|
+
);
|
|
3515
3897
|
},
|
|
3516
3898
|
getTaxonomy(id) {
|
|
3517
3899
|
const t = registry.taxonomies.get(id);
|
|
@@ -3545,7 +3927,11 @@ function createCatalogueService(opts) {
|
|
|
3545
3927
|
listPropertyFacets(businessId) {
|
|
3546
3928
|
const products = productsForBusiness(businessId);
|
|
3547
3929
|
const byCategory = /* @__PURE__ */ new Map();
|
|
3548
|
-
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
|
+
}
|
|
3549
3935
|
return [
|
|
3550
3936
|
{
|
|
3551
3937
|
name: "Category",
|
|
@@ -3564,12 +3950,6 @@ function createCatalogueService(opts) {
|
|
|
3564
3950
|
listOnSale() {
|
|
3565
3951
|
return [];
|
|
3566
3952
|
},
|
|
3567
|
-
validateDiscount(code) {
|
|
3568
|
-
if (code === "WELCOME10") {
|
|
3569
|
-
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
3570
|
-
}
|
|
3571
|
-
return { valid: false, reason: "Code not recognized" };
|
|
3572
|
-
},
|
|
3573
3953
|
getProductDeals() {
|
|
3574
3954
|
return [];
|
|
3575
3955
|
},
|
|
@@ -3579,15 +3959,33 @@ function createCatalogueService(opts) {
|
|
|
3579
3959
|
getCollectionDeals() {
|
|
3580
3960
|
return [];
|
|
3581
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
|
+
},
|
|
3582
3968
|
createQuote(businessId, body) {
|
|
3583
3969
|
const b = body;
|
|
3584
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);
|
|
3585
3976
|
const quote = {
|
|
3586
3977
|
id,
|
|
3978
|
+
quote_id: id,
|
|
3587
3979
|
business_id: businessId,
|
|
3588
|
-
product_id:
|
|
3980
|
+
product_id: product?.id ?? productId,
|
|
3589
3981
|
status: "fresh",
|
|
3590
|
-
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 },
|
|
3591
3989
|
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
|
|
3592
3990
|
created_at: clock.iso()
|
|
3593
3991
|
};
|
|
@@ -3603,7 +4001,11 @@ function createCatalogueService(opts) {
|
|
|
3603
4001
|
refreshQuote(id) {
|
|
3604
4002
|
const q = registry.quotes.get(id);
|
|
3605
4003
|
if (!q) throw notFound(`Quote ${id} not found`);
|
|
3606
|
-
const refreshed = {
|
|
4004
|
+
const refreshed = {
|
|
4005
|
+
...q,
|
|
4006
|
+
status: "fresh",
|
|
4007
|
+
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
|
|
4008
|
+
};
|
|
3607
4009
|
registry.quotes.put(id, refreshed);
|
|
3608
4010
|
return refreshed;
|
|
3609
4011
|
}
|
|
@@ -4628,67 +5030,82 @@ function createLiteService(opts) {
|
|
|
4628
5030
|
|
|
4629
5031
|
// src/mock/seeds/default.ts
|
|
4630
5032
|
var ISO = "2026-01-01T00:00:00.000Z";
|
|
4631
|
-
var
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
{
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
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
|
+
}
|
|
4659
5064
|
function seedDefault(registry) {
|
|
4660
|
-
const businessId = "bus_default_akua_bakery";
|
|
4661
5065
|
const business = {
|
|
4662
|
-
id:
|
|
5066
|
+
id: BUSINESS_ID,
|
|
4663
5067
|
name: "Akua's Bakery",
|
|
4664
5068
|
handle: "akua",
|
|
4665
5069
|
business_type: "eatery",
|
|
4666
5070
|
email: "hello@akuasbakery.test",
|
|
4667
5071
|
default_currency: "GHS",
|
|
4668
|
-
default_phone: "+
|
|
5072
|
+
default_phone: "+233 244 000 000",
|
|
4669
5073
|
default_address: "12 Independence Ave, Accra",
|
|
4670
5074
|
default_offers_table_service: true,
|
|
4671
5075
|
default_accepts_online_orders: true,
|
|
4672
|
-
image: "
|
|
5076
|
+
image: img("1568254183919-78a4f43a2877"),
|
|
4673
5077
|
status: "active",
|
|
4674
5078
|
created_at: ISO,
|
|
4675
5079
|
updated_at: ISO,
|
|
4676
5080
|
created_by: "seed",
|
|
4677
|
-
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
|
+
},
|
|
4678
5088
|
is_online_only: false,
|
|
4679
5089
|
enabled_payment_types: ["mobile_money", "card", "cash"],
|
|
4680
5090
|
default_location_settings: {},
|
|
4681
5091
|
country_code: "GH",
|
|
4682
5092
|
timezone: "Africa/Accra"
|
|
4683
5093
|
};
|
|
4684
|
-
registry.businesses.put(
|
|
4685
|
-
const
|
|
4686
|
-
|
|
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) {
|
|
4687
5104
|
const id = `cat_${c.slug}`;
|
|
4688
|
-
|
|
5105
|
+
catIds.set(c.slug, id);
|
|
4689
5106
|
const cat = {
|
|
4690
5107
|
id,
|
|
4691
|
-
business_id:
|
|
5108
|
+
business_id: BUSINESS_ID,
|
|
4692
5109
|
name: c.name,
|
|
4693
5110
|
slug: c.slug,
|
|
4694
5111
|
description: c.description,
|
|
@@ -4700,99 +5117,354 @@ function seedDefault(registry) {
|
|
|
4700
5117
|
};
|
|
4701
5118
|
registry.categories.put(id, cat);
|
|
4702
5119
|
}
|
|
4703
|
-
const
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
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 = {
|
|
4733
5433
|
id: "col_featured",
|
|
4734
|
-
business_id:
|
|
4735
|
-
name: "Featured",
|
|
5434
|
+
business_id: BUSINESS_ID,
|
|
5435
|
+
name: "Signature & Featured",
|
|
4736
5436
|
slug: "featured",
|
|
4737
|
-
description: "Our most
|
|
4738
|
-
product_ids:
|
|
5437
|
+
description: "Our most-loved bakes and a few seasonal stars.",
|
|
5438
|
+
product_ids: featuredIds,
|
|
4739
5439
|
is_active: true,
|
|
4740
5440
|
created_at: ISO,
|
|
4741
5441
|
updated_at: ISO
|
|
4742
5442
|
};
|
|
4743
|
-
registry.collections.put(
|
|
4744
|
-
for (const pid of
|
|
5443
|
+
registry.collections.put(featured.id, featured);
|
|
5444
|
+
for (const pid of featuredIds) {
|
|
4745
5445
|
const p = registry.products.get(pid);
|
|
4746
5446
|
if (p) {
|
|
4747
|
-
p.collection_ids.push(
|
|
5447
|
+
p.collection_ids.push(featured.id);
|
|
4748
5448
|
registry.products.put(pid, p);
|
|
4749
5449
|
}
|
|
4750
5450
|
}
|
|
4751
|
-
const tasting = {
|
|
4752
|
-
id: "svc_tasting",
|
|
4753
|
-
business_id: businessId,
|
|
4754
|
-
name: "Bakery Tasting Session",
|
|
4755
|
-
description: "Walk through our pastry menu with our head baker.",
|
|
4756
|
-
duration_minutes: 60,
|
|
4757
|
-
price: "150.00",
|
|
4758
|
-
is_available: true
|
|
4759
|
-
};
|
|
4760
|
-
registry.services.put(tasting.id, tasting);
|
|
4761
|
-
const cake = {
|
|
4762
|
-
id: "svc_custom_cake",
|
|
4763
|
-
business_id: businessId,
|
|
4764
|
-
name: "Custom Cake Consultation",
|
|
4765
|
-
description: "Design a custom cake for your event.",
|
|
4766
|
-
duration_minutes: 30,
|
|
4767
|
-
price: "0.00",
|
|
4768
|
-
is_available: true
|
|
4769
|
-
};
|
|
4770
|
-
registry.services.put(cake.id, cake);
|
|
4771
|
-
const sizeAxisId = "axis_size";
|
|
4772
|
-
const flavourAxisId = "axis_flavour";
|
|
4773
|
-
const sizeValues = [
|
|
4774
|
-
{ id: "axv_size_small", name: 'Small (8")' },
|
|
4775
|
-
{ id: "axv_size_medium", name: 'Medium (10")' },
|
|
4776
|
-
{ id: "axv_size_large", name: 'Large (12")' }
|
|
4777
|
-
];
|
|
4778
|
-
const flavourValues = [
|
|
4779
|
-
{ id: "axv_flavour_chocolate", name: "Chocolate" },
|
|
4780
|
-
{ id: "axv_flavour_vanilla", name: "Vanilla" },
|
|
4781
|
-
{ id: "axv_flavour_red_velvet", name: "Red Velvet" }
|
|
4782
|
-
];
|
|
4783
5451
|
const sizeAxis = {
|
|
4784
|
-
id:
|
|
4785
|
-
business_id:
|
|
5452
|
+
id: "axis_cake_size",
|
|
5453
|
+
business_id: BUSINESS_ID,
|
|
4786
5454
|
product_id: "prod_birthday-cake",
|
|
4787
5455
|
name: "Size",
|
|
4788
5456
|
display_order: 0,
|
|
4789
5457
|
affects_recipe: false,
|
|
4790
|
-
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) => ({
|
|
4791
5463
|
id: v.id,
|
|
4792
|
-
business_id:
|
|
4793
|
-
axis_id:
|
|
5464
|
+
business_id: BUSINESS_ID,
|
|
5465
|
+
axis_id: "axis_cake_size",
|
|
4794
5466
|
name: v.name,
|
|
4795
|
-
display_order:
|
|
5467
|
+
display_order: v.display,
|
|
4796
5468
|
created_at: ISO,
|
|
4797
5469
|
updated_at: ISO
|
|
4798
5470
|
})),
|
|
@@ -4800,18 +5472,22 @@ function seedDefault(registry) {
|
|
|
4800
5472
|
updated_at: ISO
|
|
4801
5473
|
};
|
|
4802
5474
|
const flavourAxis = {
|
|
4803
|
-
id:
|
|
4804
|
-
business_id:
|
|
5475
|
+
id: "axis_cake_flavour",
|
|
5476
|
+
business_id: BUSINESS_ID,
|
|
4805
5477
|
product_id: "prod_birthday-cake",
|
|
4806
5478
|
name: "Flavour",
|
|
4807
5479
|
display_order: 1,
|
|
4808
5480
|
affects_recipe: true,
|
|
4809
|
-
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) => ({
|
|
4810
5486
|
id: v.id,
|
|
4811
|
-
business_id:
|
|
4812
|
-
axis_id:
|
|
5487
|
+
business_id: BUSINESS_ID,
|
|
5488
|
+
axis_id: "axis_cake_flavour",
|
|
4813
5489
|
name: v.name,
|
|
4814
|
-
display_order:
|
|
5490
|
+
display_order: v.display,
|
|
4815
5491
|
created_at: ISO,
|
|
4816
5492
|
updated_at: ISO
|
|
4817
5493
|
})),
|
|
@@ -4820,40 +5496,42 @@ function seedDefault(registry) {
|
|
|
4820
5496
|
};
|
|
4821
5497
|
registry.variantAxes.put(sizeAxis.id, sizeAxis);
|
|
4822
5498
|
registry.variantAxes.put(flavourAxis.id, flavourAxis);
|
|
4823
|
-
const
|
|
4824
|
-
const
|
|
4825
|
-
let
|
|
4826
|
-
for (
|
|
4827
|
-
for (
|
|
4828
|
-
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}`;
|
|
4829
5507
|
const variant = {
|
|
4830
5508
|
id,
|
|
4831
5509
|
product_id: "prod_birthday-cake",
|
|
4832
|
-
business_id:
|
|
4833
|
-
name: `${
|
|
4834
|
-
sku: `BIRTHDAY-${
|
|
4835
|
-
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",
|
|
4836
5514
|
component_multiplier: "1.00",
|
|
4837
|
-
is_default:
|
|
5515
|
+
is_default: firstVariant,
|
|
4838
5516
|
is_active: true,
|
|
4839
|
-
axis_value_ids: [
|
|
5517
|
+
axis_value_ids: [sz.id, fl.id],
|
|
4840
5518
|
created_at: ISO,
|
|
4841
5519
|
updated_at: ISO
|
|
4842
5520
|
};
|
|
4843
5521
|
registry.variants.put(id, variant);
|
|
4844
|
-
|
|
4845
|
-
|
|
5522
|
+
cakeVariantIds.push(id);
|
|
5523
|
+
firstVariant = false;
|
|
4846
5524
|
}
|
|
4847
5525
|
}
|
|
4848
|
-
const
|
|
4849
|
-
if (
|
|
4850
|
-
|
|
4851
|
-
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);
|
|
4852
5530
|
}
|
|
4853
5531
|
const toppingsAddOn = {
|
|
4854
5532
|
id: "addon_toppings",
|
|
4855
|
-
business_id:
|
|
4856
|
-
name: "
|
|
5533
|
+
business_id: BUSINESS_ID,
|
|
5534
|
+
name: "Cake toppings",
|
|
4857
5535
|
is_multiple_allowed: true,
|
|
4858
5536
|
is_required: false,
|
|
4859
5537
|
is_mutually_exclusive: false,
|
|
@@ -4862,15 +5540,14 @@ function seedDefault(registry) {
|
|
|
4862
5540
|
created_at: ISO,
|
|
4863
5541
|
updated_at: ISO,
|
|
4864
5542
|
options: [
|
|
4865
|
-
{ id: "addopt_strawberry", add_on_id: "addon_toppings", business_id:
|
|
4866
|
-
{ id: "addopt_choc_chips", add_on_id: "addon_toppings", business_id:
|
|
4867
|
-
{ 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 }
|
|
4868
5546
|
]
|
|
4869
5547
|
};
|
|
4870
|
-
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
4871
5548
|
const milkAddOn = {
|
|
4872
5549
|
id: "addon_milk",
|
|
4873
|
-
business_id:
|
|
5550
|
+
business_id: BUSINESS_ID,
|
|
4874
5551
|
name: "Milk",
|
|
4875
5552
|
is_multiple_allowed: false,
|
|
4876
5553
|
is_required: false,
|
|
@@ -4880,17 +5557,17 @@ function seedDefault(registry) {
|
|
|
4880
5557
|
created_at: ISO,
|
|
4881
5558
|
updated_at: ISO,
|
|
4882
5559
|
options: [
|
|
4883
|
-
{ id: "addopt_whole", add_on_id: "addon_milk", business_id:
|
|
4884
|
-
{ id: "addopt_oat", add_on_id: "addon_milk", business_id:
|
|
4885
|
-
{ 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 }
|
|
4886
5563
|
]
|
|
4887
5564
|
};
|
|
5565
|
+
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
4888
5566
|
registry.addOns.put(milkAddOn.id, milkAddOn);
|
|
4889
5567
|
const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
|
|
4890
5568
|
const drinkProductIds = ["prod_coffee", "prod_tea"];
|
|
4891
5569
|
for (const pid of cakeProductIds) {
|
|
4892
|
-
|
|
4893
|
-
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
|
|
5570
|
+
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
|
|
4894
5571
|
const p = registry.products.get(pid);
|
|
4895
5572
|
if (p) {
|
|
4896
5573
|
p.add_on_ids.push(toppingsAddOn.id);
|
|
@@ -4898,8 +5575,7 @@ function seedDefault(registry) {
|
|
|
4898
5575
|
}
|
|
4899
5576
|
}
|
|
4900
5577
|
for (const pid of drinkProductIds) {
|
|
4901
|
-
|
|
4902
|
-
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
|
|
5578
|
+
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
|
|
4903
5579
|
const p = registry.products.get(pid);
|
|
4904
5580
|
if (p) {
|
|
4905
5581
|
p.add_on_ids.push(milkAddOn.id);
|
|
@@ -4908,7 +5584,7 @@ function seedDefault(registry) {
|
|
|
4908
5584
|
}
|
|
4909
5585
|
const breakfastBundle = {
|
|
4910
5586
|
id: "bnd_breakfast_box",
|
|
4911
|
-
business_id:
|
|
5587
|
+
business_id: BUSINESS_ID,
|
|
4912
5588
|
product_id: "prod_breakfast-box",
|
|
4913
5589
|
name: "Breakfast Box",
|
|
4914
5590
|
slug: "breakfast-box",
|
|
@@ -4920,29 +5596,9 @@ function seedDefault(registry) {
|
|
|
4920
5596
|
updated_at: ISO
|
|
4921
5597
|
};
|
|
4922
5598
|
registry.bundles.put(breakfastBundle.id, breakfastBundle);
|
|
4923
|
-
const breakfastProduct = {
|
|
4924
|
-
id: "prod_breakfast-box",
|
|
4925
|
-
business_id: businessId,
|
|
4926
|
-
name: "Breakfast Box",
|
|
4927
|
-
slug: "breakfast-box",
|
|
4928
|
-
description: "Bundled breakfast for one.",
|
|
4929
|
-
product_type: "bundle",
|
|
4930
|
-
base_price: "30.00",
|
|
4931
|
-
currency: "GHS",
|
|
4932
|
-
image: "/img/breakfast-box.jpg",
|
|
4933
|
-
images: ["/img/breakfast-box.jpg"],
|
|
4934
|
-
is_available: true,
|
|
4935
|
-
category_ids: [categoryIdsBySlug.get("pastries")],
|
|
4936
|
-
collection_ids: [],
|
|
4937
|
-
add_on_ids: [],
|
|
4938
|
-
variant_ids: [],
|
|
4939
|
-
created_at: ISO,
|
|
4940
|
-
updated_at: ISO
|
|
4941
|
-
};
|
|
4942
|
-
registry.products.put(breakfastProduct.id, breakfastProduct);
|
|
4943
5599
|
const pastryComposite = {
|
|
4944
5600
|
id: "comp_pastry_box",
|
|
4945
|
-
business_id:
|
|
5601
|
+
business_id: BUSINESS_ID,
|
|
4946
5602
|
product_id: "prod_pastry-box",
|
|
4947
5603
|
base_price: "60.00",
|
|
4948
5604
|
pricing_mode: "additive",
|
|
@@ -4954,43 +5610,51 @@ function seedDefault(registry) {
|
|
|
4954
5610
|
name: "Choose your pastries",
|
|
4955
5611
|
min: 6,
|
|
4956
5612
|
max: 12,
|
|
4957
|
-
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
|
+
]
|
|
4958
5622
|
}
|
|
4959
5623
|
],
|
|
4960
5624
|
created_at: ISO,
|
|
4961
5625
|
updated_at: ISO
|
|
4962
5626
|
};
|
|
4963
5627
|
registry.composites.put(pastryComposite.id, pastryComposite);
|
|
4964
|
-
const
|
|
4965
|
-
id: "
|
|
4966
|
-
business_id:
|
|
4967
|
-
name: "
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
currency: "GHS",
|
|
4973
|
-
image: "/img/pastry-box.jpg",
|
|
4974
|
-
images: ["/img/pastry-box.jpg"],
|
|
4975
|
-
is_available: true,
|
|
4976
|
-
category_ids: [categoryIdsBySlug.get("pastries")],
|
|
4977
|
-
collection_ids: [],
|
|
4978
|
-
add_on_ids: [],
|
|
4979
|
-
variant_ids: [],
|
|
4980
|
-
created_at: ISO,
|
|
4981
|
-
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
|
|
4982
5636
|
};
|
|
4983
|
-
|
|
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
|
|
5645
|
+
};
|
|
5646
|
+
registry.services.put(tasting.id, tasting);
|
|
5647
|
+
registry.services.put(consult.id, consult);
|
|
4984
5648
|
const tags = [
|
|
4985
|
-
{ id: "tag_signature", business_id:
|
|
4986
|
-
{ id: "tag_vegan", business_id:
|
|
4987
|
-
{ id: "tag_gluten_free", business_id:
|
|
4988
|
-
{ 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 }
|
|
4989
5653
|
];
|
|
4990
5654
|
for (const t of tags) registry.tags.put(t.id, t);
|
|
4991
5655
|
const baked = {
|
|
4992
5656
|
id: "tax_baked_goods",
|
|
4993
|
-
business_id:
|
|
5657
|
+
business_id: BUSINESS_ID,
|
|
4994
5658
|
name: "Baked Goods",
|
|
4995
5659
|
slug: "baked-goods",
|
|
4996
5660
|
path: ["Food", "Baked Goods"],
|
|
@@ -5000,7 +5664,7 @@ function seedDefault(registry) {
|
|
|
5000
5664
|
};
|
|
5001
5665
|
const beverages = {
|
|
5002
5666
|
id: "tax_beverages",
|
|
5003
|
-
business_id:
|
|
5667
|
+
business_id: BUSINESS_ID,
|
|
5004
5668
|
name: "Beverages",
|
|
5005
5669
|
slug: "beverages",
|
|
5006
5670
|
path: ["Food", "Beverages"],
|
|
@@ -5013,13 +5677,13 @@ function seedDefault(registry) {
|
|
|
5013
5677
|
const attrs = [
|
|
5014
5678
|
{
|
|
5015
5679
|
id: "attr_allergens",
|
|
5016
|
-
business_id:
|
|
5680
|
+
business_id: BUSINESS_ID,
|
|
5017
5681
|
namespace: "core",
|
|
5018
5682
|
name: "Allergens",
|
|
5019
5683
|
slug: "allergens",
|
|
5020
5684
|
description: "Common allergens present in this item.",
|
|
5021
5685
|
attribute_type: "multi_select",
|
|
5022
|
-
options: ["wheat", "eggs", "dairy", "nuts", "soy"],
|
|
5686
|
+
options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
|
|
5023
5687
|
is_required: false,
|
|
5024
5688
|
is_filterable: true,
|
|
5025
5689
|
visibility: "public",
|
|
@@ -5030,7 +5694,7 @@ function seedDefault(registry) {
|
|
|
5030
5694
|
},
|
|
5031
5695
|
{
|
|
5032
5696
|
id: "attr_shelf_life",
|
|
5033
|
-
business_id:
|
|
5697
|
+
business_id: BUSINESS_ID,
|
|
5034
5698
|
namespace: "core",
|
|
5035
5699
|
name: "Shelf life",
|
|
5036
5700
|
slug: "shelf-life",
|
|
@@ -5045,7 +5709,7 @@ function seedDefault(registry) {
|
|
|
5045
5709
|
},
|
|
5046
5710
|
{
|
|
5047
5711
|
id: "attr_caffeine",
|
|
5048
|
-
business_id:
|
|
5712
|
+
business_id: BUSINESS_ID,
|
|
5049
5713
|
namespace: "core",
|
|
5050
5714
|
name: "Caffeine",
|
|
5051
5715
|
slug: "caffeine",
|
|
@@ -5060,7 +5724,7 @@ function seedDefault(registry) {
|
|
|
5060
5724
|
},
|
|
5061
5725
|
{
|
|
5062
5726
|
id: "attr_seasonal",
|
|
5063
|
-
business_id:
|
|
5727
|
+
business_id: BUSINESS_ID,
|
|
5064
5728
|
namespace: "core",
|
|
5065
5729
|
name: "Seasonal",
|
|
5066
5730
|
slug: "seasonal",
|
|
@@ -5078,10 +5742,10 @@ function seedDefault(registry) {
|
|
|
5078
5742
|
const articles = [
|
|
5079
5743
|
{
|
|
5080
5744
|
id: "kb_delivery",
|
|
5081
|
-
business_id:
|
|
5082
|
-
title: "Delivery &
|
|
5745
|
+
business_id: BUSINESS_ID,
|
|
5746
|
+
title: "Delivery & pickup",
|
|
5083
5747
|
slug: "delivery-pickup",
|
|
5084
|
-
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.",
|
|
5085
5749
|
category: "logistics",
|
|
5086
5750
|
tags: ["delivery", "pickup"],
|
|
5087
5751
|
created_at: ISO,
|
|
@@ -5089,10 +5753,10 @@ function seedDefault(registry) {
|
|
|
5089
5753
|
},
|
|
5090
5754
|
{
|
|
5091
5755
|
id: "kb_custom_cakes",
|
|
5092
|
-
business_id:
|
|
5093
|
-
title: "
|
|
5756
|
+
business_id: BUSINESS_ID,
|
|
5757
|
+
title: "Custom cakes & celebrations",
|
|
5094
5758
|
slug: "custom-cakes",
|
|
5095
|
-
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.",
|
|
5096
5760
|
category: "products",
|
|
5097
5761
|
tags: ["cakes", "custom"],
|
|
5098
5762
|
created_at: ISO,
|
|
@@ -5100,10 +5764,10 @@ function seedDefault(registry) {
|
|
|
5100
5764
|
},
|
|
5101
5765
|
{
|
|
5102
5766
|
id: "kb_allergens",
|
|
5103
|
-
business_id:
|
|
5104
|
-
title: "
|
|
5767
|
+
business_id: BUSINESS_ID,
|
|
5768
|
+
title: "Allergen information",
|
|
5105
5769
|
slug: "allergens",
|
|
5106
|
-
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.",
|
|
5107
5771
|
category: "products",
|
|
5108
5772
|
tags: ["allergens", "safety"],
|
|
5109
5773
|
created_at: ISO,
|
|
@@ -5111,7 +5775,7 @@ function seedDefault(registry) {
|
|
|
5111
5775
|
}
|
|
5112
5776
|
];
|
|
5113
5777
|
for (const a of articles) registry.knowledgeArticles.put(a.id, a);
|
|
5114
|
-
return { businessId };
|
|
5778
|
+
return { businessId: BUSINESS_ID };
|
|
5115
5779
|
}
|
|
5116
5780
|
|
|
5117
5781
|
// src/mock/seeds/empty.ts
|
|
@@ -7214,195 +7878,47 @@ function createDeps(options = {}) {
|
|
|
7214
7878
|
}
|
|
7215
7879
|
|
|
7216
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
|
+
}
|
|
7217
7902
|
function createMockApp(options = {}) {
|
|
7218
7903
|
const deps = createDeps(options);
|
|
7219
7904
|
const app = new Hono2();
|
|
7220
|
-
const
|
|
7221
|
-
if (
|
|
7222
|
-
app.use("*", async (c, next) => {
|
|
7223
|
-
const origin = c.req.header("origin") ?? "";
|
|
7224
|
-
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7225
|
-
const headers = {
|
|
7226
|
-
"Access-Control-Allow-Origin": allow,
|
|
7227
|
-
"Access-Control-Allow-Credentials": "true",
|
|
7228
|
-
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7229
|
-
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7230
|
-
"Access-Control-Expose-Headers": "x-session-token",
|
|
7231
|
-
"Access-Control-Max-Age": "86400",
|
|
7232
|
-
Vary: "Origin"
|
|
7233
|
-
};
|
|
7234
|
-
if (c.req.method === "OPTIONS") {
|
|
7235
|
-
return new Response(null, { status: 204, headers });
|
|
7236
|
-
}
|
|
7237
|
-
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7238
|
-
await next();
|
|
7239
|
-
});
|
|
7240
|
-
}
|
|
7905
|
+
const cors = options.cors ?? "*";
|
|
7906
|
+
if (cors) app.use("*", corsMiddleware(cors));
|
|
7241
7907
|
app.get(
|
|
7242
7908
|
"/",
|
|
7243
7909
|
(c) => c.json({
|
|
7244
|
-
name:
|
|
7245
|
-
version: "0.36.0",
|
|
7910
|
+
name: PACKAGE_NAME,
|
|
7246
7911
|
docs: "https://github.com/cimplify/cimplify",
|
|
7247
7912
|
api: "/api/v1",
|
|
7248
7913
|
admin: "/_mock",
|
|
7249
7914
|
elements: "/elements",
|
|
7250
|
-
|
|
7915
|
+
images: "/img/<slug>",
|
|
7916
|
+
seed_business_id: deps.defaultBusinessId,
|
|
7917
|
+
time: deps.clock.iso()
|
|
7251
7918
|
})
|
|
7252
7919
|
);
|
|
7253
7920
|
app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
|
|
7254
|
-
|
|
7255
|
-
// Akua's Bakery (default seed)
|
|
7256
|
-
sourdough: "1509440159596-0249088772ff",
|
|
7257
|
-
wheat: "1486887396153-fa416526c108",
|
|
7258
|
-
baguette: "1568471173242-461f0a730452",
|
|
7259
|
-
focaccia: "1573472635068-fd9b1abdd8e5",
|
|
7260
|
-
croissant: "1555507036-ab1f4038808a",
|
|
7261
|
-
pac: "1530610476181-d83430b64dcd",
|
|
7262
|
-
danish: "1517433367423-c7e5b0f35086",
|
|
7263
|
-
cinnamon: "1583527976767-50068e72b7a3",
|
|
7264
|
-
"choc-cake": "1565958011703-44f9829ba187",
|
|
7265
|
-
"red-velvet": "1586788680434-30d324b2d46f",
|
|
7266
|
-
carrot: "1568827999250-3f6afff96e66",
|
|
7267
|
-
cheesecake: "1567306226416-28f0efdc88ce",
|
|
7268
|
-
coffee: "1495474472287-4d71bcdd2085",
|
|
7269
|
-
tea: "1576092768241-dec231879fc3",
|
|
7270
|
-
sobolo: "1556679343-c7306c1976bc",
|
|
7271
|
-
oj: "1600271886742-f049b969dc7a",
|
|
7272
|
-
banana: "1632931057819-4eefffa8e007",
|
|
7273
|
-
brownie: "1551024506-0bccd828d307",
|
|
7274
|
-
macaron: "1558326567-98ae2405596b",
|
|
7275
|
-
birthday: "1535254973040-607b474cb50d",
|
|
7276
|
-
"breakfast-box": "1533089860892-a7c6f0a88666",
|
|
7277
|
-
"pastry-box": "1486427944299-d1955d23e34d",
|
|
7278
|
-
// Mama's Kitchen (restaurant seed)
|
|
7279
|
-
kelewele: "1585536830049-aedf5c1b98a4",
|
|
7280
|
-
"spring-rolls": "1606755962773-d324e0a13086",
|
|
7281
|
-
"soup-of-the-day": "1547592180-85f173990554",
|
|
7282
|
-
"jollof-rice": "1604329760661-e71dc83f8f26",
|
|
7283
|
-
"banku-tilapia": "1559847844-5315695dadae",
|
|
7284
|
-
"fufu-light-soup": "1547928576-b822bcced36e",
|
|
7285
|
-
waakye: "1604329760661-e71dc83f8f26",
|
|
7286
|
-
"grilled-chicken": "1532550907401-a500c9a57435",
|
|
7287
|
-
burger: "1568901346375-23c9450c58cd",
|
|
7288
|
-
"yam-fries": "1573080496219-bb080dd4f877",
|
|
7289
|
-
"fried-plantain": "1604329760661-e71dc83f8f26",
|
|
7290
|
-
salad: "1512621776951-a57141f2eefd",
|
|
7291
|
-
"ice-cream": "1497034825429-c343d7c6a68f",
|
|
7292
|
-
"cake-slice": "1565958011703-44f9829ba187",
|
|
7293
|
-
"fruit-bowl": "1490474418585-ba9bad8fd0ea",
|
|
7294
|
-
"soft-drink": "1497534446932-c925b458314e",
|
|
7295
|
-
"fresh-juice": "1622597467836-f3285f2131b8",
|
|
7296
|
-
water: "1548839140-29a749e1cf4d",
|
|
7297
|
-
feast: "1546069901-ba9599a7e63c",
|
|
7298
|
-
byo: "1546069901-ba9599a7e63c",
|
|
7299
|
-
// Currents Electronics
|
|
7300
|
-
"android-flagship": "1511707171634-5f897ff02aa9",
|
|
7301
|
-
"android-mid": "1592899677977-9c10ca588bbd",
|
|
7302
|
-
"phone-budget": "1574944985070-8f3ebc6b79d2",
|
|
7303
|
-
"laptop-pro": "1496181133206-80ce9b88a853",
|
|
7304
|
-
"laptop-air": "1517336714731-489689fd1ca8",
|
|
7305
|
-
"laptop-budget": "1541807084-5c52b6b3adef",
|
|
7306
|
-
"headphones-anc": "1583394838336-acd977736f90",
|
|
7307
|
-
"earbuds-pro": "1606220945770-b5b6c2c55bf1",
|
|
7308
|
-
"speaker-bt": "1608043152269-423dbba4e7e1",
|
|
7309
|
-
"phone-case": "1601593346740-925612772716",
|
|
7310
|
-
"screen-protector": "1592890288564-76628a30a657",
|
|
7311
|
-
"fast-charger": "1583394838336-acd977736f90",
|
|
7312
|
-
"laptop-sleeve": "1525547719571-a2d4ac8945e2",
|
|
7313
|
-
"wireless-mouse": "1527864550417-7fd91fc51a46",
|
|
7314
|
-
"mech-keyboard": "1587829741301-dc798b83add3",
|
|
7315
|
-
"gaming-controller": "1592840496694-26d035b52b48",
|
|
7316
|
-
"gaming-headset": "1591105327764-407a4a06e768",
|
|
7317
|
-
"console-handheld": "1606144042614-b2417e99c4e3",
|
|
7318
|
-
wfh: "1593642632559-0c6d3fc62b89",
|
|
7319
|
-
"game-pack": "1606144042614-b2417e99c4e3",
|
|
7320
|
-
// Reesa apparel
|
|
7321
|
-
"ankara-shirt": "1591047139829-d91aecb6caea",
|
|
7322
|
-
"agbada-set": "1605497788044-5a32c7078486",
|
|
7323
|
-
kaftan: "1554412933-514a83d2f3c8",
|
|
7324
|
-
"leather-tote": "1584917865442-de89df76afd3",
|
|
7325
|
-
sandals: "1543163521-1bf539c55dd2",
|
|
7326
|
-
"beaded-bracelet": "1611591437281-460bfbe1220a",
|
|
7327
|
-
owambe: "1605497788044-5a32c7078486",
|
|
7328
|
-
// FreshMart
|
|
7329
|
-
"tomatoes-1kg": "1546470427-227f1c75a9ef",
|
|
7330
|
-
"onions-1kg": "1620574387735-3624d75b2dbc",
|
|
7331
|
-
"ginger-200g": "1606937330526-f5e6a2a83555",
|
|
7332
|
-
"bananas-bunch": "1571771894821-ce9b6c11b08e",
|
|
7333
|
-
pineapple: "1550258987-190a2d41a8ba",
|
|
7334
|
-
"spinach-bunch": "1576045057995-568f588f82fb",
|
|
7335
|
-
"milk-1l": "1550583724-b2692b85b150",
|
|
7336
|
-
"yogurt-500g": "1571212515416-fef01fc43637",
|
|
7337
|
-
"eggs-12": "1582722872445-44dc5f7e3c8f",
|
|
7338
|
-
"cheese-200g": "1486297678162-eb2a19b0a32d",
|
|
7339
|
-
"chicken-1kg": "1604503468506-a8da13d82791",
|
|
7340
|
-
"beef-mince-500g": "1607623814075-e51df1bdc82f",
|
|
7341
|
-
"tilapia-each": "1546069901-ba9599a7e63c",
|
|
7342
|
-
"rice-5kg": "1586201375761-83865001e31c",
|
|
7343
|
-
"garri-2kg": "1604329760661-e71dc83f8f26",
|
|
7344
|
-
"palm-oil-1l": "1474979266404-7eaacbcd87c5",
|
|
7345
|
-
"vegetable-oil-1l": "1474979266404-7eaacbcd87c5",
|
|
7346
|
-
"salt-1kg": "1612531386530-97286d97c2d2",
|
|
7347
|
-
"sugar-1kg": "1581281697996-3aef0c3b9b12",
|
|
7348
|
-
"tomato-paste": "1546470427-227f1c75a9ef",
|
|
7349
|
-
"soap-bar": "1583912267550-d6c2ac3196c0",
|
|
7350
|
-
"detergent-1kg": "1583912267550-d6c2ac3196c0",
|
|
7351
|
-
"tissue-pack": "1610557892470-55d9e80c0bce",
|
|
7352
|
-
"trash-bags": "1583912267550-d6c2ac3196c0",
|
|
7353
|
-
essentials: "1542838132-92c53300491e",
|
|
7354
|
-
// Serene Spa services
|
|
7355
|
-
"swedish-massage-60": "1544161515-4ab6ce6db874",
|
|
7356
|
-
"swedish-massage-90": "1544161515-4ab6ce6db874",
|
|
7357
|
-
"deep-tissue-60": "1600334129128-685c5582fd35",
|
|
7358
|
-
"couples-massage": "1540555700478-4be289fbecef",
|
|
7359
|
-
"hot-stone-90": "1600334129128-685c5582fd35",
|
|
7360
|
-
"facial-classic": "1570172619644-dfd03ed5d881",
|
|
7361
|
-
"facial-anti-aging": "1571019613454-1cb2f99b2d8b",
|
|
7362
|
-
"facial-acne": "1571019613454-1cb2f99b2d8b",
|
|
7363
|
-
manicure: "1604654894610-df63bc536371",
|
|
7364
|
-
pedicure: "1610701596007-11502861dcfa",
|
|
7365
|
-
"gel-manicure": "1604654894610-df63bc536371",
|
|
7366
|
-
haircut: "1562322140-8baeececf3df",
|
|
7367
|
-
"hair-treatment": "1522337360788-8b13dee7a37e",
|
|
7368
|
-
color: "1562322140-8baeececf3df",
|
|
7369
|
-
"yoga-session": "1545205597-3d9d02c29597",
|
|
7370
|
-
meditation: "1545389336-cf090694435e",
|
|
7371
|
-
"spa-day": "1540555700478-4be289fbecef",
|
|
7372
|
-
"couples-retreat": "1540555700478-4be289fbecef",
|
|
7373
|
-
"custom-experience": "1545389336-cf090694435e"
|
|
7374
|
-
};
|
|
7375
|
-
app.get("/img/*", (c) => {
|
|
7376
|
-
const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
|
|
7377
|
-
const slug = path.split("/").pop() ?? "";
|
|
7378
|
-
const photoId = UNSPLASH_PHOTOS[slug];
|
|
7379
|
-
if (photoId) {
|
|
7380
|
-
return c.redirect(`https://images.unsplash.com/photo-${photoId}?w=600&h=600&fit=crop&auto=format&q=80`, 302);
|
|
7381
|
-
}
|
|
7382
|
-
const label = slug.replace(/[-_]+/g, " ").trim() || "product";
|
|
7383
|
-
let hash = 0;
|
|
7384
|
-
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
7385
|
-
const hue = hash % 360;
|
|
7386
|
-
const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
|
|
7387
|
-
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
7388
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
|
|
7389
|
-
<defs>
|
|
7390
|
-
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
7391
|
-
<stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
|
|
7392
|
-
<stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
|
|
7393
|
-
</linearGradient>
|
|
7394
|
-
</defs>
|
|
7395
|
-
<rect width="400" height="400" fill="url(#g)"/>
|
|
7396
|
-
<text x="200" y="210" font-family="-apple-system, BlinkMacSystemFont, Inter, sans-serif" font-size="22" font-weight="600" fill="white" text-anchor="middle" dominant-baseline="middle" style="text-transform: capitalize">${display.replace(/[<>&]/g, "")}</text>
|
|
7397
|
-
</svg>`;
|
|
7398
|
-
return new Response(svg, {
|
|
7399
|
-
headers: {
|
|
7400
|
-
"Content-Type": "image/svg+xml; charset=utf-8",
|
|
7401
|
-
"Cache-Control": "public, max-age=3600",
|
|
7402
|
-
"Access-Control-Allow-Origin": "*"
|
|
7403
|
-
}
|
|
7404
|
-
});
|
|
7405
|
-
});
|
|
7921
|
+
mountImageRoutes(app);
|
|
7406
7922
|
app.route("/api/v1", buildV1(deps));
|
|
7407
7923
|
app.route("/_mock", buildAdmin(deps));
|
|
7408
7924
|
app.route("/elements", buildElements(deps));
|
|
@@ -7410,7 +7926,11 @@ function createMockApp(options = {}) {
|
|
|
7410
7926
|
(c) => c.json(
|
|
7411
7927
|
{
|
|
7412
7928
|
success: false,
|
|
7413
|
-
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
|
+
},
|
|
7414
7934
|
hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
|
|
7415
7935
|
},
|
|
7416
7936
|
501
|