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