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