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