@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/cli.mjs
CHANGED
|
@@ -3618,6 +3618,178 @@ function buildElements(deps) {
|
|
|
3618
3618
|
return app;
|
|
3619
3619
|
}
|
|
3620
3620
|
|
|
3621
|
+
// src/mock/routes/images.ts
|
|
3622
|
+
var SLUG_KEYWORDS = {
|
|
3623
|
+
// ─── Akua's Bakery (default seed) ───
|
|
3624
|
+
sourdough: "sourdough,bread",
|
|
3625
|
+
wheat: "whole-wheat,bread",
|
|
3626
|
+
baguette: "baguette",
|
|
3627
|
+
focaccia: "focaccia,bread",
|
|
3628
|
+
croissant: "croissant",
|
|
3629
|
+
pac: "pain-au-chocolat,croissant",
|
|
3630
|
+
danish: "danish,pastry",
|
|
3631
|
+
cinnamon: "cinnamon-roll",
|
|
3632
|
+
"choc-cake": "chocolate-cake,slice",
|
|
3633
|
+
"red-velvet": "red-velvet-cake",
|
|
3634
|
+
carrot: "carrot-cake",
|
|
3635
|
+
cheesecake: "cheesecake",
|
|
3636
|
+
coffee: "coffee,cup",
|
|
3637
|
+
tea: "tea,cup",
|
|
3638
|
+
sobolo: "hibiscus-drink",
|
|
3639
|
+
oj: "orange-juice,glass",
|
|
3640
|
+
banana: "banana-bread",
|
|
3641
|
+
brownie: "brownie",
|
|
3642
|
+
macaron: "macaron,box",
|
|
3643
|
+
birthday: "birthday-cake",
|
|
3644
|
+
"breakfast-box": "breakfast",
|
|
3645
|
+
"pastry-box": "pastry,assortment",
|
|
3646
|
+
// ─── Mama's Kitchen (restaurant seed) ───
|
|
3647
|
+
kelewele: "fried-plantain,spicy",
|
|
3648
|
+
"spring-rolls": "spring-rolls",
|
|
3649
|
+
"soup-of-the-day": "soup,bowl",
|
|
3650
|
+
"jollof-rice": "jollof-rice",
|
|
3651
|
+
"banku-tilapia": "tilapia,grilled-fish",
|
|
3652
|
+
"fufu-light-soup": "fufu,soup",
|
|
3653
|
+
waakye: "rice,beans,african-food",
|
|
3654
|
+
"grilled-chicken": "grilled-chicken",
|
|
3655
|
+
burger: "burger",
|
|
3656
|
+
"yam-fries": "yam-fries,chips",
|
|
3657
|
+
"fried-plantain": "fried-plantain",
|
|
3658
|
+
salad: "garden-salad",
|
|
3659
|
+
"ice-cream": "ice-cream,scoop",
|
|
3660
|
+
"cake-slice": "chocolate-cake,slice",
|
|
3661
|
+
"fruit-bowl": "fruit-bowl,tropical",
|
|
3662
|
+
"soft-drink": "cola,glass",
|
|
3663
|
+
"fresh-juice": "fresh-juice",
|
|
3664
|
+
water: "water-bottle",
|
|
3665
|
+
feast: "family-meal,buffet",
|
|
3666
|
+
byo: "buffet,plate",
|
|
3667
|
+
// ─── Currents Electronics ───
|
|
3668
|
+
"android-flagship": "smartphone,android",
|
|
3669
|
+
"android-mid": "smartphone",
|
|
3670
|
+
"phone-budget": "phone",
|
|
3671
|
+
"laptop-pro": "laptop,macbook",
|
|
3672
|
+
"laptop-air": "laptop,thin",
|
|
3673
|
+
"laptop-budget": "laptop",
|
|
3674
|
+
"headphones-anc": "headphones",
|
|
3675
|
+
"earbuds-pro": "earbuds,wireless",
|
|
3676
|
+
"speaker-bt": "speaker,bluetooth",
|
|
3677
|
+
"phone-case": "phone-case",
|
|
3678
|
+
"screen-protector": "screen,protector",
|
|
3679
|
+
"fast-charger": "charger,usb",
|
|
3680
|
+
"laptop-sleeve": "laptop-sleeve,case",
|
|
3681
|
+
"wireless-mouse": "wireless-mouse",
|
|
3682
|
+
"mech-keyboard": "mechanical-keyboard",
|
|
3683
|
+
"gaming-controller": "game-controller",
|
|
3684
|
+
"gaming-headset": "gaming-headset",
|
|
3685
|
+
"console-handheld": "handheld-console,gaming",
|
|
3686
|
+
wfh: "desk-setup,laptop",
|
|
3687
|
+
"game-pack": "gaming,console",
|
|
3688
|
+
// ─── Reesa apparel ───
|
|
3689
|
+
"ankara-shirt": "ankara,african-fashion",
|
|
3690
|
+
"agbada-set": "agbada,nigerian-fashion",
|
|
3691
|
+
kaftan: "kaftan,african-dress",
|
|
3692
|
+
"leather-tote": "leather-tote,bag",
|
|
3693
|
+
sandals: "sandals,leather",
|
|
3694
|
+
"beaded-bracelet": "beaded-bracelet",
|
|
3695
|
+
owambe: "african-fashion",
|
|
3696
|
+
// ─── FreshMart ───
|
|
3697
|
+
"tomatoes-1kg": "tomatoes",
|
|
3698
|
+
"onions-1kg": "onions",
|
|
3699
|
+
"ginger-200g": "ginger,root",
|
|
3700
|
+
"bananas-bunch": "bananas",
|
|
3701
|
+
pineapple: "pineapple",
|
|
3702
|
+
"spinach-bunch": "spinach,leaves",
|
|
3703
|
+
"milk-1l": "milk,carton",
|
|
3704
|
+
"yogurt-500g": "yogurt",
|
|
3705
|
+
"eggs-12": "eggs,carton",
|
|
3706
|
+
"cheese-200g": "cheese",
|
|
3707
|
+
"chicken-1kg": "raw-chicken",
|
|
3708
|
+
"beef-mince-500g": "beef,mince",
|
|
3709
|
+
"tilapia-each": "fish,tilapia",
|
|
3710
|
+
"rice-5kg": "rice,bag",
|
|
3711
|
+
"garri-2kg": "rice,grain",
|
|
3712
|
+
"palm-oil-1l": "oil,bottle",
|
|
3713
|
+
"vegetable-oil-1l": "oil,bottle",
|
|
3714
|
+
"salt-1kg": "salt,seasoning",
|
|
3715
|
+
"sugar-1kg": "sugar,white",
|
|
3716
|
+
"tomato-paste": "tomato-paste,can",
|
|
3717
|
+
"soap-bar": "soap,bar",
|
|
3718
|
+
"detergent-1kg": "detergent,laundry",
|
|
3719
|
+
"tissue-pack": "tissue,paper",
|
|
3720
|
+
"trash-bags": "trash-bag",
|
|
3721
|
+
essentials: "groceries,box",
|
|
3722
|
+
// ─── Serene Spa ───
|
|
3723
|
+
"swedish-massage-60": "massage,relaxation",
|
|
3724
|
+
"swedish-massage-90": "massage,relaxation",
|
|
3725
|
+
"deep-tissue-60": "massage,deep-tissue",
|
|
3726
|
+
"couples-massage": "couples-massage",
|
|
3727
|
+
"hot-stone-90": "hot-stone,massage",
|
|
3728
|
+
"facial-classic": "facial,spa",
|
|
3729
|
+
"facial-anti-aging": "facial,skincare",
|
|
3730
|
+
"facial-acne": "facial,skincare",
|
|
3731
|
+
manicure: "manicure,nails",
|
|
3732
|
+
pedicure: "pedicure,feet",
|
|
3733
|
+
"gel-manicure": "manicure,gel-nails",
|
|
3734
|
+
haircut: "haircut,salon",
|
|
3735
|
+
"hair-treatment": "hair-treatment,salon",
|
|
3736
|
+
color: "hair-color,salon",
|
|
3737
|
+
"yoga-session": "yoga,studio",
|
|
3738
|
+
meditation: "meditation,zen",
|
|
3739
|
+
"spa-day": "spa,wellness",
|
|
3740
|
+
"couples-retreat": "spa,couple",
|
|
3741
|
+
"custom-experience": "spa,wellness"
|
|
3742
|
+
};
|
|
3743
|
+
var NO_CACHE = {
|
|
3744
|
+
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
3745
|
+
};
|
|
3746
|
+
function lockFromSlug(slug) {
|
|
3747
|
+
let hash = 0;
|
|
3748
|
+
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
3749
|
+
return hash % 9999 + 1;
|
|
3750
|
+
}
|
|
3751
|
+
function svgFallback(slug) {
|
|
3752
|
+
const label = slug.replace(/[-_]+/g, " ").trim() || "product";
|
|
3753
|
+
const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
|
|
3754
|
+
let hash = 0;
|
|
3755
|
+
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
3756
|
+
const hue = hash % 360;
|
|
3757
|
+
const safe = display.replace(/[<>&]/g, "");
|
|
3758
|
+
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3759
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
|
|
3760
|
+
<defs>
|
|
3761
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
3762
|
+
<stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
|
|
3763
|
+
<stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
|
|
3764
|
+
</linearGradient>
|
|
3765
|
+
</defs>
|
|
3766
|
+
<rect width="400" height="400" fill="url(#g)"/>
|
|
3767
|
+
<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>
|
|
3768
|
+
</svg>`;
|
|
3769
|
+
return new Response(svg, {
|
|
3770
|
+
headers: {
|
|
3771
|
+
"Content-Type": "image/svg+xml; charset=utf-8",
|
|
3772
|
+
...NO_CACHE,
|
|
3773
|
+
"Access-Control-Allow-Origin": "*"
|
|
3774
|
+
}
|
|
3775
|
+
});
|
|
3776
|
+
}
|
|
3777
|
+
function handleImageRequest(c) {
|
|
3778
|
+
const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
|
|
3779
|
+
const slug = path.split("/").pop() ?? "";
|
|
3780
|
+
for (const [k, v] of Object.entries(NO_CACHE)) c.header(k, v);
|
|
3781
|
+
if (!slug) return svgFallback("");
|
|
3782
|
+
const keyword = SLUG_KEYWORDS[slug] ?? slug.replace(/[-_]+/g, ",");
|
|
3783
|
+
const lock = lockFromSlug(slug);
|
|
3784
|
+
return c.redirect(
|
|
3785
|
+
`https://loremflickr.com/600/600/${encodeURIComponent(keyword)}?lock=${lock}`,
|
|
3786
|
+
302
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
3789
|
+
function mountImageRoutes(app) {
|
|
3790
|
+
app.get("/img/*", handleImageRequest);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3621
3793
|
// src/mock/core/clock.ts
|
|
3622
3794
|
function createClock(opts) {
|
|
3623
3795
|
let frozenAt = opts?.frozenAt ? opts.frozenAt.getTime() : null;
|
|
@@ -3955,51 +4127,257 @@ function percent(a, pct) {
|
|
|
3955
4127
|
return toMajor(Math.round(toMinor(a) * pct / 100));
|
|
3956
4128
|
}
|
|
3957
4129
|
|
|
3958
|
-
// src/mock/domain/catalogue.ts
|
|
3959
|
-
function
|
|
3960
|
-
|
|
3961
|
-
const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
|
|
3962
|
-
const defaultPriceInfo = (price, currency) => ({
|
|
4130
|
+
// src/mock/domain/catalogue/price-info.ts
|
|
4131
|
+
function defaultPriceInfo(price, currency) {
|
|
4132
|
+
return {
|
|
3963
4133
|
base_price: price,
|
|
3964
4134
|
final_price: price,
|
|
4135
|
+
pre_tax_price: price,
|
|
3965
4136
|
markup_percentage: "0.00",
|
|
3966
4137
|
markup_amount: "0.00",
|
|
3967
4138
|
markup_discount_percentage: "0.00",
|
|
3968
4139
|
markup_discount_amount: "0.00",
|
|
3969
|
-
currency
|
|
3970
|
-
|
|
3971
|
-
|
|
4140
|
+
currency,
|
|
4141
|
+
tax_info: {
|
|
4142
|
+
tax_rate: "0",
|
|
4143
|
+
tax_amount: "0.00",
|
|
4144
|
+
is_inclusive: true,
|
|
4145
|
+
pre_tax_price: price,
|
|
4146
|
+
components: []
|
|
4147
|
+
}
|
|
4148
|
+
};
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
// src/mock/domain/catalogue/product-shape.ts
|
|
4152
|
+
function toCanonicalProductSummary(p) {
|
|
4153
|
+
return {
|
|
4154
|
+
id: p.id,
|
|
4155
|
+
business_id: p.business_id,
|
|
4156
|
+
category_id: p.category_ids[0],
|
|
4157
|
+
name: p.name,
|
|
4158
|
+
slug: p.slug,
|
|
4159
|
+
description: p.description,
|
|
4160
|
+
image_url: p.image,
|
|
4161
|
+
images: p.images,
|
|
4162
|
+
default_price: p.base_price,
|
|
4163
|
+
type: p.product_type,
|
|
4164
|
+
inventory_type: "none",
|
|
4165
|
+
variant_strategy: "fetch_all",
|
|
4166
|
+
is_active: p.is_available,
|
|
4167
|
+
inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
|
|
4168
|
+
tags: p.tags ?? [],
|
|
4169
|
+
created_at: p.created_at,
|
|
4170
|
+
updated_at: p.updated_at
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
function deriveRenderHint(p) {
|
|
4174
|
+
if (p.render_hint) return p.render_hint;
|
|
4175
|
+
if (p.product_type === "service") return "general";
|
|
4176
|
+
if (p.product_type === "digital") return "general";
|
|
4177
|
+
const foodish = (p.tags ?? []).some(
|
|
4178
|
+
(t) => /food|drink|cake|bread|pastry|dessert|coffee|tea|juice|spice/i.test(t)
|
|
4179
|
+
);
|
|
4180
|
+
return foodish ? "food" : "general";
|
|
4181
|
+
}
|
|
4182
|
+
function toApiProduct(p, ctx) {
|
|
4183
|
+
const { registry, priceInfo, groups, components, composite, bundle } = ctx;
|
|
4184
|
+
return {
|
|
4185
|
+
// ─── Canonical Product fields (SDK type: Product / ProductWithDetails) ───
|
|
3972
4186
|
id: p.id,
|
|
3973
4187
|
business_id: p.business_id,
|
|
4188
|
+
category_id: p.category_ids[0],
|
|
3974
4189
|
name: p.name,
|
|
3975
4190
|
slug: p.slug,
|
|
3976
4191
|
description: p.description ?? "",
|
|
3977
|
-
|
|
3978
|
-
|
|
4192
|
+
image_url: p.image,
|
|
4193
|
+
images: p.images,
|
|
3979
4194
|
default_price: p.base_price,
|
|
3980
|
-
|
|
3981
|
-
|
|
4195
|
+
type: p.product_type,
|
|
4196
|
+
render_hint: deriveRenderHint(p),
|
|
4197
|
+
inventory_type: "none",
|
|
4198
|
+
variant_strategy: p.variant_ids.length > 0 ? "use_axes" : "fetch_all",
|
|
4199
|
+
is_active: p.is_available,
|
|
4200
|
+
inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
|
|
4201
|
+
tags: p.tags ?? [],
|
|
4202
|
+
calories: p.calories,
|
|
4203
|
+
allergies: p.allergies ?? [],
|
|
4204
|
+
display_mode: p.display_mode,
|
|
4205
|
+
digital_product_type: p.digital_product_type,
|
|
4206
|
+
quantity_pricing: p.quantity_pricing,
|
|
4207
|
+
duration_unit: p.duration_unit,
|
|
4208
|
+
duration_minutes: p.duration_minutes,
|
|
4209
|
+
billing_plans: p.billing_plans,
|
|
4210
|
+
created_at: p.created_at,
|
|
4211
|
+
updated_at: p.updated_at,
|
|
4212
|
+
metadata: {
|
|
4213
|
+
is_signature: p.is_signature ?? false,
|
|
4214
|
+
is_new: p.is_new ?? false,
|
|
4215
|
+
ingredients: p.ingredients ?? [],
|
|
4216
|
+
pairings: p.pairings ?? [],
|
|
4217
|
+
...p.metadata ?? {}
|
|
4218
|
+
},
|
|
4219
|
+
// ─── Pricing breakdown (Price / ProductCustomizer) ───
|
|
4220
|
+
base_price: p.base_price,
|
|
4221
|
+
default_price_info: priceInfo,
|
|
4222
|
+
price_info: priceInfo,
|
|
3982
4223
|
currency: p.currency,
|
|
4224
|
+
// ─── Compatibility aliases (older callers) ───
|
|
3983
4225
|
image: p.image,
|
|
3984
|
-
|
|
3985
|
-
images: p.images,
|
|
4226
|
+
product_type: p.product_type,
|
|
3986
4227
|
is_available: p.is_available,
|
|
3987
|
-
category_id: p.category_ids[0],
|
|
3988
4228
|
category_ids: p.category_ids,
|
|
3989
4229
|
collection_ids: p.collection_ids,
|
|
3990
4230
|
add_on_ids: p.add_on_ids,
|
|
3991
|
-
|
|
3992
|
-
created_at: p.created_at,
|
|
3993
|
-
updated_at: p.updated_at,
|
|
4231
|
+
variant_ids: p.variant_ids,
|
|
3994
4232
|
custom_attributes: [],
|
|
3995
4233
|
properties: [],
|
|
3996
4234
|
has_variants: p.variant_ids.length > 0,
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4235
|
+
// ─── Embedded relations (ProductWithDetails) ───
|
|
4236
|
+
category: registry.categories.get(p.category_ids[0]),
|
|
4237
|
+
add_ons: p.add_on_ids.map((aid) => registry.addOns.get(aid)).filter(Boolean),
|
|
4238
|
+
variants: registry.variants.list((v) => v.product_id === p.id),
|
|
4239
|
+
variant_axes: registry.variantAxes.list((a) => a.product_id === p.id),
|
|
4240
|
+
// ─── Composite-specific (CompositeProductLayout reads product.groups) ───
|
|
4241
|
+
groups,
|
|
4242
|
+
composite_id: composite?.id,
|
|
4243
|
+
pricing_mode: composite?.pricing_mode,
|
|
4244
|
+
min_order_quantity: composite?.min_order_quantity,
|
|
4245
|
+
max_order_quantity: composite?.max_order_quantity,
|
|
4246
|
+
// ─── Bundle-specific (BundleProductLayout reads product.components) ───
|
|
4247
|
+
bundle_id: bundle?.id,
|
|
4248
|
+
bundle_pricing_type: bundle?.pricing_type,
|
|
4249
|
+
bundle_price: bundle?.bundle_price,
|
|
4250
|
+
bundle_discount_value: bundle?.discount_value,
|
|
4251
|
+
components
|
|
4252
|
+
};
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
// src/mock/domain/catalogue/composite-shape.ts
|
|
4256
|
+
function expandCompositeGroups(registry, productId, isoNow) {
|
|
4257
|
+
const composite = registry.composites.list((c) => c.product_id === productId)[0];
|
|
4258
|
+
if (!composite) return void 0;
|
|
4259
|
+
return composite.groups.map((g, gIdx) => buildGroup(registry, composite, g, gIdx, isoNow));
|
|
4260
|
+
}
|
|
4261
|
+
function buildGroup(registry, composite, group, groupIdx, isoNow) {
|
|
4262
|
+
return {
|
|
4263
|
+
// ─── Canonical ComponentGroupWithComponents ───
|
|
4264
|
+
id: group.id,
|
|
4265
|
+
composite_id: composite.id,
|
|
4266
|
+
name: group.name,
|
|
4267
|
+
description: void 0,
|
|
4268
|
+
display_order: groupIdx,
|
|
4269
|
+
min_selections: group.min,
|
|
4270
|
+
max_selections: group.max,
|
|
4271
|
+
allow_quantity: true,
|
|
4272
|
+
max_quantity_per_component: group.max,
|
|
4273
|
+
pricing_behavior: "additive",
|
|
4274
|
+
created_at: isoNow,
|
|
4275
|
+
updated_at: isoNow,
|
|
4276
|
+
// ─── Aliases for older selectors ───
|
|
4277
|
+
min: group.min,
|
|
4278
|
+
max: group.max,
|
|
4279
|
+
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))
|
|
4280
|
+
};
|
|
4281
|
+
}
|
|
4282
|
+
function buildComponent(composite, group, p, idx) {
|
|
4283
|
+
return {
|
|
4284
|
+
// ─── Canonical CompositeComponent ───
|
|
4285
|
+
id: `comp_${composite.product_id}_${p.id}`,
|
|
4286
|
+
composite_id: composite.id,
|
|
4287
|
+
group_id: group.id,
|
|
4288
|
+
source_type: "product",
|
|
4289
|
+
source_product_id: p.id,
|
|
4290
|
+
product_id: p.id,
|
|
4291
|
+
display_name: p.name,
|
|
4292
|
+
display_description: p.description,
|
|
4293
|
+
display_image_url: p.image,
|
|
4294
|
+
price: p.base_price,
|
|
4295
|
+
calories: p.calories,
|
|
4296
|
+
allergens: p.allergies ?? [],
|
|
4297
|
+
display_order: idx,
|
|
4298
|
+
is_popular: idx === 0,
|
|
4299
|
+
is_premium: false,
|
|
4300
|
+
is_available: p.is_available,
|
|
4301
|
+
is_archived: false,
|
|
4302
|
+
created_at: p.created_at,
|
|
4303
|
+
updated_at: p.updated_at,
|
|
4304
|
+
// ─── Compatibility aliases ───
|
|
4305
|
+
name: p.name,
|
|
4306
|
+
description: p.description,
|
|
4307
|
+
image_url: p.image,
|
|
4308
|
+
unit_price: p.base_price,
|
|
4309
|
+
default_price: p.base_price,
|
|
4310
|
+
base_price: p.base_price,
|
|
4311
|
+
currency: p.currency,
|
|
4312
|
+
product: toCanonicalProductSummary(p)
|
|
4313
|
+
};
|
|
4314
|
+
}
|
|
4315
|
+
|
|
4316
|
+
// src/mock/domain/catalogue/bundle-shape.ts
|
|
4317
|
+
function expandBundleComponents(registry, productId) {
|
|
4318
|
+
const bundle = registry.bundles.list((b) => b.product_id === productId)[0];
|
|
4319
|
+
if (!bundle) return void 0;
|
|
4320
|
+
return bundle.product_ids.map((cid) => registry.products.get(cid)).filter((p) => Boolean(p)).map((p) => buildBundleComponent(bundle.id, productId, p));
|
|
4321
|
+
}
|
|
4322
|
+
function buildBundleComponent(bundleId, productId, p) {
|
|
4323
|
+
return {
|
|
4324
|
+
id: `bp_${productId}_${p.id}`,
|
|
4325
|
+
bundle_id: bundleId,
|
|
4326
|
+
product_id: p.id,
|
|
4327
|
+
// ─── Fields BundleProductLayout reads ───
|
|
4328
|
+
product_name: p.name,
|
|
4329
|
+
product_image_url: p.image,
|
|
4330
|
+
product_description: p.description,
|
|
4331
|
+
product_slug: p.slug,
|
|
4332
|
+
effective_price: p.base_price,
|
|
4333
|
+
unit_price: p.base_price,
|
|
4334
|
+
default_price: p.base_price,
|
|
4335
|
+
quantity: 1,
|
|
4336
|
+
allow_variant_choice: false,
|
|
4337
|
+
available_variants: [],
|
|
4338
|
+
selected_variant_id: null,
|
|
4339
|
+
// ─── Compatibility aliases ───
|
|
4340
|
+
display_name: p.name,
|
|
4341
|
+
display_description: p.description,
|
|
4342
|
+
name: p.name,
|
|
4343
|
+
description: p.description,
|
|
4344
|
+
image_url: p.image,
|
|
4345
|
+
currency: p.currency,
|
|
4346
|
+
is_available: p.is_available,
|
|
4347
|
+
product: toCanonicalProductSummary(p)
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
// src/mock/domain/catalogue/index.ts
|
|
4352
|
+
function createCatalogueService(opts) {
|
|
4353
|
+
const { registry, clock, ids } = opts;
|
|
4354
|
+
const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
|
|
4000
4355
|
const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
|
|
4001
4356
|
const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
|
|
4002
4357
|
const addOnLinksForProduct = (productId) => registry.productAddOns.list((l) => l.product_id === productId);
|
|
4358
|
+
const apiProduct = (p) => {
|
|
4359
|
+
const composite = p.product_type === "composite" ? registry.composites.list((c) => c.product_id === p.id)[0] : void 0;
|
|
4360
|
+
const bundle = p.product_type === "bundle" ? registry.bundles.list((b) => b.product_id === p.id)[0] : void 0;
|
|
4361
|
+
return toApiProduct(p, {
|
|
4362
|
+
registry,
|
|
4363
|
+
priceInfo: defaultPriceInfo(p.base_price, p.currency),
|
|
4364
|
+
groups: expandCompositeGroups(registry, p.id, clock.iso()),
|
|
4365
|
+
components: expandBundleComponents(registry, p.id),
|
|
4366
|
+
composite: composite ? {
|
|
4367
|
+
id: composite.id,
|
|
4368
|
+
pricing_mode: composite.pricing_mode,
|
|
4369
|
+
min_order_quantity: composite.min_order_quantity,
|
|
4370
|
+
max_order_quantity: composite.max_order_quantity
|
|
4371
|
+
} : void 0,
|
|
4372
|
+
bundle: bundle ? {
|
|
4373
|
+
id: bundle.id,
|
|
4374
|
+
pricing_type: bundle.pricing_type,
|
|
4375
|
+
bundle_price: bundle.bundle_price,
|
|
4376
|
+
discount_value: bundle.discount_value
|
|
4377
|
+
} : void 0
|
|
4378
|
+
});
|
|
4379
|
+
};
|
|
4380
|
+
const resolveProduct = (idOrSlug) => registry.products.get(idOrSlug) ?? registry.products.list((p) => p.slug === idOrSlug)[0];
|
|
4003
4381
|
return {
|
|
4004
4382
|
snapshot(businessId) {
|
|
4005
4383
|
const products = productsForBusiness(businessId);
|
|
@@ -4007,7 +4385,7 @@ function createCatalogueService(opts) {
|
|
|
4007
4385
|
const addOns = registry.addOns.list((a) => a.business_id === businessId);
|
|
4008
4386
|
return {
|
|
4009
4387
|
categories,
|
|
4010
|
-
products: products.map(
|
|
4388
|
+
products: products.map(apiProduct),
|
|
4011
4389
|
add_ons: addOns,
|
|
4012
4390
|
is_complete: true,
|
|
4013
4391
|
total_available: products.length
|
|
@@ -4022,7 +4400,7 @@ function createCatalogueService(opts) {
|
|
|
4022
4400
|
const start = (page - 1) * limit;
|
|
4023
4401
|
const slice = items.slice(start, start + limit);
|
|
4024
4402
|
return {
|
|
4025
|
-
items: slice.map(
|
|
4403
|
+
items: slice.map(apiProduct),
|
|
4026
4404
|
is_complete: start + limit >= items.length,
|
|
4027
4405
|
total_available: items.length,
|
|
4028
4406
|
pagination: {
|
|
@@ -4035,9 +4413,9 @@ function createCatalogueService(opts) {
|
|
|
4035
4413
|
};
|
|
4036
4414
|
},
|
|
4037
4415
|
getProduct(productId) {
|
|
4038
|
-
const p =
|
|
4416
|
+
const p = resolveProduct(productId);
|
|
4039
4417
|
if (!p) throw notFound(`Product ${productId} not found`);
|
|
4040
|
-
return
|
|
4418
|
+
return apiProduct(p);
|
|
4041
4419
|
},
|
|
4042
4420
|
getProductVariants(productId) {
|
|
4043
4421
|
return variantsForProduct(productId);
|
|
@@ -4047,7 +4425,9 @@ function createCatalogueService(opts) {
|
|
|
4047
4425
|
},
|
|
4048
4426
|
getProductVariantById(productId, variantId) {
|
|
4049
4427
|
const v = registry.variants.get(variantId);
|
|
4050
|
-
if (!v || v.product_id !== productId)
|
|
4428
|
+
if (!v || v.product_id !== productId) {
|
|
4429
|
+
throw notFound(`Variant ${variantId} not found on product ${productId}`);
|
|
4430
|
+
}
|
|
4051
4431
|
return v;
|
|
4052
4432
|
},
|
|
4053
4433
|
findProductVariant(productId, properties) {
|
|
@@ -4077,13 +4457,13 @@ function createCatalogueService(opts) {
|
|
|
4077
4457
|
return registry.categories.list((c) => c.business_id === businessId);
|
|
4078
4458
|
},
|
|
4079
4459
|
getCategory(id) {
|
|
4080
|
-
const c = registry.categories.get(id);
|
|
4460
|
+
const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
|
|
4081
4461
|
if (!c) throw notFound(`Category ${id} not found`);
|
|
4082
4462
|
return c;
|
|
4083
4463
|
},
|
|
4084
4464
|
getCategoryProducts(id) {
|
|
4085
4465
|
const c = this.getCategory(id);
|
|
4086
|
-
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(
|
|
4466
|
+
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
|
|
4087
4467
|
},
|
|
4088
4468
|
getCategoryAttributes(id) {
|
|
4089
4469
|
const c = registry.categories.get(id);
|
|
@@ -4096,13 +4476,13 @@ function createCatalogueService(opts) {
|
|
|
4096
4476
|
return registry.collections.list((c) => c.business_id === businessId);
|
|
4097
4477
|
},
|
|
4098
4478
|
getCollection(id) {
|
|
4099
|
-
const c = registry.collections.get(id);
|
|
4479
|
+
const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
|
|
4100
4480
|
if (!c) throw notFound(`Collection ${id} not found`);
|
|
4101
4481
|
return c;
|
|
4102
4482
|
},
|
|
4103
4483
|
getCollectionProducts(id) {
|
|
4104
4484
|
const c = this.getCollection(id);
|
|
4105
|
-
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(
|
|
4485
|
+
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
|
|
4106
4486
|
},
|
|
4107
4487
|
getCollectionAttributes(id) {
|
|
4108
4488
|
const c = registry.collections.get(id);
|
|
@@ -4115,7 +4495,7 @@ function createCatalogueService(opts) {
|
|
|
4115
4495
|
return registry.bundles.list((b) => b.business_id === businessId);
|
|
4116
4496
|
},
|
|
4117
4497
|
getBundle(id) {
|
|
4118
|
-
const b = registry.bundles.get(id);
|
|
4498
|
+
const b = registry.bundles.get(id) ?? registry.bundles.list((x) => x.product_id === id || x.slug === id)[0];
|
|
4119
4499
|
if (!b) throw notFound(`Bundle ${id} not found`);
|
|
4120
4500
|
return b;
|
|
4121
4501
|
},
|
|
@@ -4123,12 +4503,16 @@ function createCatalogueService(opts) {
|
|
|
4123
4503
|
return registry.composites.list((c) => c.business_id === businessId);
|
|
4124
4504
|
},
|
|
4125
4505
|
getComposite(id) {
|
|
4126
|
-
const c = registry.composites.get(id);
|
|
4506
|
+
const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
|
|
4127
4507
|
if (!c) throw notFound(`Composite ${id} not found`);
|
|
4128
4508
|
return c;
|
|
4129
4509
|
},
|
|
4130
4510
|
getCompositeByProduct(productId) {
|
|
4131
|
-
|
|
4511
|
+
const direct = registry.composites.list((c) => c.product_id === productId)[0];
|
|
4512
|
+
if (direct) return direct;
|
|
4513
|
+
const product = registry.products.list((p) => p.slug === productId)[0];
|
|
4514
|
+
if (!product) return null;
|
|
4515
|
+
return registry.composites.list((c) => c.product_id === product.id)[0] ?? null;
|
|
4132
4516
|
},
|
|
4133
4517
|
calculateCompositePrice(id, body) {
|
|
4134
4518
|
const c = registry.composites.get(id);
|
|
@@ -4136,14 +4520,20 @@ function createCatalogueService(opts) {
|
|
|
4136
4520
|
const sel = body.selections ?? [];
|
|
4137
4521
|
let total = c.base_price;
|
|
4138
4522
|
for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
|
|
4139
|
-
return {
|
|
4523
|
+
return {
|
|
4524
|
+
final_price: total,
|
|
4525
|
+
base_price: c.base_price,
|
|
4526
|
+
components_total: money(parseFloat(total) - parseFloat(c.base_price))
|
|
4527
|
+
};
|
|
4140
4528
|
},
|
|
4141
4529
|
listTaxonomies(businessId) {
|
|
4142
4530
|
return registry.taxonomies.list((t) => t.business_id === businessId);
|
|
4143
4531
|
},
|
|
4144
4532
|
searchTaxonomies(businessId, q) {
|
|
4145
4533
|
const term = q.toLowerCase();
|
|
4146
|
-
return registry.taxonomies.list(
|
|
4534
|
+
return registry.taxonomies.list(
|
|
4535
|
+
(t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
|
|
4536
|
+
);
|
|
4147
4537
|
},
|
|
4148
4538
|
getTaxonomy(id) {
|
|
4149
4539
|
const t = registry.taxonomies.get(id);
|
|
@@ -4177,7 +4567,11 @@ function createCatalogueService(opts) {
|
|
|
4177
4567
|
listPropertyFacets(businessId) {
|
|
4178
4568
|
const products = productsForBusiness(businessId);
|
|
4179
4569
|
const byCategory = /* @__PURE__ */ new Map();
|
|
4180
|
-
for (const p of products)
|
|
4570
|
+
for (const p of products) {
|
|
4571
|
+
for (const cid of p.category_ids) {
|
|
4572
|
+
byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4181
4575
|
return [
|
|
4182
4576
|
{
|
|
4183
4577
|
name: "Category",
|
|
@@ -4196,12 +4590,6 @@ function createCatalogueService(opts) {
|
|
|
4196
4590
|
listOnSale() {
|
|
4197
4591
|
return [];
|
|
4198
4592
|
},
|
|
4199
|
-
validateDiscount(code) {
|
|
4200
|
-
if (code === "WELCOME10") {
|
|
4201
|
-
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
4202
|
-
}
|
|
4203
|
-
return { valid: false, reason: "Code not recognized" };
|
|
4204
|
-
},
|
|
4205
4593
|
getProductDeals() {
|
|
4206
4594
|
return [];
|
|
4207
4595
|
},
|
|
@@ -4211,15 +4599,33 @@ function createCatalogueService(opts) {
|
|
|
4211
4599
|
getCollectionDeals() {
|
|
4212
4600
|
return [];
|
|
4213
4601
|
},
|
|
4602
|
+
validateDiscount(code) {
|
|
4603
|
+
if (code === "WELCOME10") {
|
|
4604
|
+
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
4605
|
+
}
|
|
4606
|
+
return { valid: false, reason: "Code not recognized" };
|
|
4607
|
+
},
|
|
4214
4608
|
createQuote(businessId, body) {
|
|
4215
4609
|
const b = body;
|
|
4216
4610
|
const id = ids.quote();
|
|
4611
|
+
const productId = b.product_id ?? "";
|
|
4612
|
+
const product = resolveProduct(productId);
|
|
4613
|
+
const price = b.total_price ?? product?.base_price ?? "0.00";
|
|
4614
|
+
const currency = b.currency ?? product?.currency ?? "GHS";
|
|
4615
|
+
const priceInfo = defaultPriceInfo(price, currency);
|
|
4217
4616
|
const quote = {
|
|
4218
4617
|
id,
|
|
4618
|
+
quote_id: id,
|
|
4219
4619
|
business_id: businessId,
|
|
4220
|
-
product_id:
|
|
4620
|
+
product_id: product?.id ?? productId,
|
|
4221
4621
|
status: "fresh",
|
|
4222
|
-
total_price:
|
|
4622
|
+
total_price: price,
|
|
4623
|
+
currency,
|
|
4624
|
+
unit_price_info: priceInfo,
|
|
4625
|
+
final_price_info: priceInfo,
|
|
4626
|
+
quoted_total_price_info: priceInfo,
|
|
4627
|
+
components: [],
|
|
4628
|
+
breakdown: { base_price: price, components_total: "0.00", final_price: price },
|
|
4223
4629
|
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
|
|
4224
4630
|
created_at: clock.iso()
|
|
4225
4631
|
};
|
|
@@ -4235,7 +4641,11 @@ function createCatalogueService(opts) {
|
|
|
4235
4641
|
refreshQuote(id) {
|
|
4236
4642
|
const q = registry.quotes.get(id);
|
|
4237
4643
|
if (!q) throw notFound(`Quote ${id} not found`);
|
|
4238
|
-
const refreshed = {
|
|
4644
|
+
const refreshed = {
|
|
4645
|
+
...q,
|
|
4646
|
+
status: "fresh",
|
|
4647
|
+
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
|
|
4648
|
+
};
|
|
4239
4649
|
registry.quotes.put(id, refreshed);
|
|
4240
4650
|
return refreshed;
|
|
4241
4651
|
}
|
|
@@ -5260,67 +5670,99 @@ function createLiteService(opts) {
|
|
|
5260
5670
|
|
|
5261
5671
|
// src/mock/seeds/default.ts
|
|
5262
5672
|
var ISO = "2026-01-01T00:00:00.000Z";
|
|
5263
|
-
var
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
{
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5673
|
+
var BUSINESS_ID = "bus_default_akua_bakery";
|
|
5674
|
+
var img = (id) => `https://images.unsplash.com/photo-${id}?w=800&h=800&fit=crop&auto=format&q=80`;
|
|
5675
|
+
function makeProduct(input, categoryIds) {
|
|
5676
|
+
const catId = categoryIds.get(input.category);
|
|
5677
|
+
return {
|
|
5678
|
+
id: input.id,
|
|
5679
|
+
business_id: BUSINESS_ID,
|
|
5680
|
+
name: input.name,
|
|
5681
|
+
slug: input.slug,
|
|
5682
|
+
description: input.description,
|
|
5683
|
+
product_type: input.productType ?? "product",
|
|
5684
|
+
base_price: input.price,
|
|
5685
|
+
currency: "GHS",
|
|
5686
|
+
image: input.imageUrl,
|
|
5687
|
+
images: input.images ?? [input.imageUrl],
|
|
5688
|
+
is_available: true,
|
|
5689
|
+
category_ids: [catId],
|
|
5690
|
+
collection_ids: [],
|
|
5691
|
+
add_on_ids: [],
|
|
5692
|
+
variant_ids: [],
|
|
5693
|
+
tags: input.tags ?? [],
|
|
5694
|
+
calories: input.calories,
|
|
5695
|
+
allergies: input.allergies ?? [],
|
|
5696
|
+
ingredients: input.ingredients ?? [],
|
|
5697
|
+
pairings: input.pairings ?? [],
|
|
5698
|
+
is_signature: input.isSignature ?? false,
|
|
5699
|
+
is_new: input.isNew ?? false,
|
|
5700
|
+
render_hint: input.renderHint,
|
|
5701
|
+
display_mode: input.displayMode,
|
|
5702
|
+
digital_product_type: input.digitalProductType,
|
|
5703
|
+
quantity_pricing: input.quantityPricing,
|
|
5704
|
+
duration_unit: input.durationUnit,
|
|
5705
|
+
duration_minutes: input.durationMinutes,
|
|
5706
|
+
billing_plans: input.billingPlans?.map((b) => ({
|
|
5707
|
+
id: b.id,
|
|
5708
|
+
name: b.name,
|
|
5709
|
+
interval: b.interval,
|
|
5710
|
+
interval_count: b.interval_count,
|
|
5711
|
+
price: b.price,
|
|
5712
|
+
currency: b.currency ?? "GHS",
|
|
5713
|
+
trial_period_days: b.trial_period_days
|
|
5714
|
+
})),
|
|
5715
|
+
created_at: ISO,
|
|
5716
|
+
updated_at: ISO
|
|
5717
|
+
};
|
|
5718
|
+
}
|
|
5291
5719
|
function seedDefault(registry) {
|
|
5292
|
-
const businessId = "bus_default_akua_bakery";
|
|
5293
5720
|
const business = {
|
|
5294
|
-
id:
|
|
5721
|
+
id: BUSINESS_ID,
|
|
5295
5722
|
name: "Akua's Bakery",
|
|
5296
5723
|
handle: "akua",
|
|
5297
5724
|
business_type: "eatery",
|
|
5298
5725
|
email: "hello@akuasbakery.test",
|
|
5299
5726
|
default_currency: "GHS",
|
|
5300
|
-
default_phone: "+
|
|
5727
|
+
default_phone: "+233 244 000 000",
|
|
5301
5728
|
default_address: "12 Independence Ave, Accra",
|
|
5302
5729
|
default_offers_table_service: true,
|
|
5303
5730
|
default_accepts_online_orders: true,
|
|
5304
|
-
image: "
|
|
5731
|
+
image: img("1568254183919-78a4f43a2877"),
|
|
5305
5732
|
status: "active",
|
|
5306
5733
|
created_at: ISO,
|
|
5307
5734
|
updated_at: ISO,
|
|
5308
5735
|
created_by: "seed",
|
|
5309
|
-
preferences: {
|
|
5736
|
+
preferences: {
|
|
5737
|
+
tagline: "Fresh from the oven, every day",
|
|
5738
|
+
description: "Family-run bakery in East Legon. Sourdough, croissants, layered cakes \u2014 handcrafted since 2018, delivered across Accra.",
|
|
5739
|
+
instagram: "https://instagram.com/akuasbakery",
|
|
5740
|
+
theme: "warm",
|
|
5741
|
+
checkout: { allow_guest: true }
|
|
5742
|
+
},
|
|
5310
5743
|
is_online_only: false,
|
|
5311
5744
|
enabled_payment_types: ["mobile_money", "card", "cash"],
|
|
5312
5745
|
default_location_settings: {},
|
|
5313
5746
|
country_code: "GH",
|
|
5314
5747
|
timezone: "Africa/Accra"
|
|
5315
5748
|
};
|
|
5316
|
-
registry.businesses.put(
|
|
5317
|
-
const
|
|
5318
|
-
|
|
5749
|
+
registry.businesses.put(BUSINESS_ID, business);
|
|
5750
|
+
const categoryDefs = [
|
|
5751
|
+
{ slug: "breads", name: "Breads", description: "Naturally leavened sourdough, baguettes, focaccia." },
|
|
5752
|
+
{ slug: "pastries", name: "Pastries", description: "Hand-laminated croissants, danishes, brownies." },
|
|
5753
|
+
{ slug: "cakes", name: "Cakes", description: "Slices and made-to-order showstoppers." },
|
|
5754
|
+
{ slug: "drinks", name: "Drinks", description: "Locally roasted coffee, brewed teas, fresh juice." },
|
|
5755
|
+
{ slug: "boxes", name: "Boxes & Bundles", description: "Curated boxes for sharing." },
|
|
5756
|
+
{ slug: "merch", name: "Merch & More", description: "Branded goods, wholesale flour, downloadable recipes, gift cards." },
|
|
5757
|
+
{ slug: "classes", name: "Classes & Stays", description: "Hands-on baking classes, kitchen rentals, monthly bread subscriptions." }
|
|
5758
|
+
];
|
|
5759
|
+
const catIds = /* @__PURE__ */ new Map();
|
|
5760
|
+
for (const c of categoryDefs) {
|
|
5319
5761
|
const id = `cat_${c.slug}`;
|
|
5320
|
-
|
|
5762
|
+
catIds.set(c.slug, id);
|
|
5321
5763
|
const cat = {
|
|
5322
5764
|
id,
|
|
5323
|
-
business_id:
|
|
5765
|
+
business_id: BUSINESS_ID,
|
|
5324
5766
|
name: c.name,
|
|
5325
5767
|
slug: c.slug,
|
|
5326
5768
|
description: c.description,
|
|
@@ -5332,99 +5774,502 @@ function seedDefault(registry) {
|
|
|
5332
5774
|
};
|
|
5333
5775
|
registry.categories.put(id, cat);
|
|
5334
5776
|
}
|
|
5335
|
-
const
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5777
|
+
const productInputs = [
|
|
5778
|
+
// ── Breads ──
|
|
5779
|
+
{
|
|
5780
|
+
id: "prod_sourdough-loaf",
|
|
5781
|
+
name: "Country Sourdough",
|
|
5782
|
+
slug: "sourdough-loaf",
|
|
5783
|
+
price: "25.00",
|
|
5784
|
+
description: "24-hour fermented country loaf with our heritage starter \u2014 open crumb, blistered crust, naturally tangy. Baked fresh every morning.",
|
|
5785
|
+
category: "breads",
|
|
5786
|
+
imageUrl: img("1509440159596-0249088772ff"),
|
|
5787
|
+
images: [img("1509440159596-0249088772ff"), img("1486887396153-fa416526c108")],
|
|
5788
|
+
tags: ["bread", "vegan", "signature"],
|
|
5789
|
+
isSignature: true,
|
|
5790
|
+
ingredients: ["Wheat flour", "Sourdough starter", "Sea salt", "Filtered water"],
|
|
5791
|
+
pairings: ["Olive oil", "Aged cheese", "Tomato jam"],
|
|
5792
|
+
allergies: ["wheat", "gluten"],
|
|
5793
|
+
calories: 240
|
|
5794
|
+
},
|
|
5795
|
+
{
|
|
5796
|
+
id: "prod_whole-wheat",
|
|
5797
|
+
name: "Whole Wheat Seed Loaf",
|
|
5798
|
+
slug: "whole-wheat",
|
|
5799
|
+
price: "20.00",
|
|
5800
|
+
description: "Hearty 100% whole wheat with sunflower, sesame, and pumpkin seeds. Slow-fermented for a soft crumb and nutty depth.",
|
|
5801
|
+
category: "breads",
|
|
5802
|
+
imageUrl: img("1486887396153-fa416526c108"),
|
|
5803
|
+
tags: ["bread", "vegan", "high-fibre"],
|
|
5804
|
+
ingredients: ["Whole wheat flour", "Sunflower seeds", "Sesame", "Pumpkin seeds"],
|
|
5805
|
+
allergies: ["wheat", "gluten", "sesame"],
|
|
5806
|
+
calories: 220
|
|
5807
|
+
},
|
|
5808
|
+
{
|
|
5809
|
+
id: "prod_baguette",
|
|
5810
|
+
name: "Classic Baguette",
|
|
5811
|
+
slug: "baguette",
|
|
5812
|
+
price: "15.00",
|
|
5813
|
+
description: "Crackling crust, airy crumb. Best eaten warm, within an hour of baking.",
|
|
5814
|
+
category: "breads",
|
|
5815
|
+
imageUrl: img("1568471173242-461f0a730452"),
|
|
5816
|
+
tags: ["bread", "vegan"],
|
|
5817
|
+
ingredients: ["Wheat flour", "Yeast", "Sea salt", "Water"],
|
|
5818
|
+
allergies: ["wheat", "gluten"],
|
|
5819
|
+
calories: 180
|
|
5820
|
+
},
|
|
5821
|
+
{
|
|
5822
|
+
id: "prod_focaccia",
|
|
5823
|
+
name: "Rosemary Focaccia",
|
|
5824
|
+
slug: "focaccia",
|
|
5825
|
+
price: "30.00",
|
|
5826
|
+
description: "High-hydration focaccia drenched in olive oil, scattered with sea salt and fresh rosemary. Sold whole or by the slab.",
|
|
5827
|
+
category: "breads",
|
|
5828
|
+
imageUrl: img("1571167530149-c1105da4c2c7"),
|
|
5829
|
+
tags: ["bread", "vegetarian"],
|
|
5830
|
+
ingredients: ["00 flour", "Olive oil", "Rosemary", "Maldon salt"],
|
|
5831
|
+
pairings: ["Burrata", "Prosciutto", "Caponata"],
|
|
5832
|
+
allergies: ["wheat", "gluten"],
|
|
5833
|
+
calories: 310
|
|
5834
|
+
},
|
|
5835
|
+
{
|
|
5836
|
+
id: "prod_banana-bread",
|
|
5837
|
+
name: "Brown Butter Banana Bread",
|
|
5838
|
+
slug: "banana-bread",
|
|
5839
|
+
price: "18.00",
|
|
5840
|
+
description: "Caramelised brown butter, three over-ripe bananas, walnuts, finished with a salted maple glaze.",
|
|
5841
|
+
category: "breads",
|
|
5842
|
+
imageUrl: img("1632931057819-4eefffa8e007"),
|
|
5843
|
+
tags: ["sweet", "comfort"],
|
|
5844
|
+
ingredients: ["Bananas", "Brown butter", "Walnuts", "Maple syrup"],
|
|
5845
|
+
allergies: ["wheat", "gluten", "dairy", "nuts", "eggs"],
|
|
5846
|
+
calories: 360,
|
|
5847
|
+
isNew: true
|
|
5848
|
+
},
|
|
5849
|
+
// ── Pastries ──
|
|
5850
|
+
{
|
|
5851
|
+
id: "prod_croissant",
|
|
5852
|
+
name: "Butter Croissant",
|
|
5853
|
+
slug: "croissant",
|
|
5854
|
+
price: "12.00",
|
|
5855
|
+
description: "27 layers of laminated dough wrapped around French butter. Crisp, shattering crust and a tender honeycomb interior.",
|
|
5856
|
+
category: "pastries",
|
|
5857
|
+
imageUrl: img("1555507036-ab1f4038808a"),
|
|
5858
|
+
images: [img("1555507036-ab1f4038808a"), img("1530610476181-d83430b64dcd")],
|
|
5859
|
+
tags: ["pastry", "vegetarian", "signature"],
|
|
5860
|
+
isSignature: true,
|
|
5861
|
+
ingredients: ["French butter", "Wheat flour", "Milk", "Eggs"],
|
|
5862
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5863
|
+
calories: 280
|
|
5864
|
+
},
|
|
5865
|
+
{
|
|
5866
|
+
id: "prod_pain-au-chocolat",
|
|
5867
|
+
name: "Pain au Chocolat",
|
|
5868
|
+
slug: "pain-au-chocolat",
|
|
5869
|
+
price: "14.00",
|
|
5870
|
+
description: "Our croissant dough wrapped around two batons of 70% Valrhona dark chocolate.",
|
|
5871
|
+
category: "pastries",
|
|
5872
|
+
imageUrl: img("1530610476181-d83430b64dcd"),
|
|
5873
|
+
tags: ["pastry", "chocolate"],
|
|
5874
|
+
ingredients: ["Croissant dough", "Valrhona chocolate"],
|
|
5875
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5876
|
+
calories: 320
|
|
5877
|
+
},
|
|
5878
|
+
{
|
|
5879
|
+
id: "prod_almond-danish",
|
|
5880
|
+
name: "Almond Danish",
|
|
5881
|
+
slug: "almond-danish",
|
|
5882
|
+
price: "16.00",
|
|
5883
|
+
description: "Flaky pastry filled with almond cream, dusted with toasted slivers and powdered sugar.",
|
|
5884
|
+
category: "pastries",
|
|
5885
|
+
imageUrl: img("1517433367423-c7e5b0f35086"),
|
|
5886
|
+
tags: ["pastry", "nuts"],
|
|
5887
|
+
ingredients: ["Pastry dough", "Almond cream", "Toasted almonds"],
|
|
5888
|
+
allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
|
|
5889
|
+
calories: 340
|
|
5890
|
+
},
|
|
5891
|
+
{
|
|
5892
|
+
id: "prod_cinnamon-roll",
|
|
5893
|
+
name: "Cinnamon Roll",
|
|
5894
|
+
slug: "cinnamon-roll",
|
|
5895
|
+
price: "13.00",
|
|
5896
|
+
description: "Soft enriched dough swirled with butter and Ceylon cinnamon, finished with cream-cheese glaze.",
|
|
5897
|
+
category: "pastries",
|
|
5898
|
+
imageUrl: img("1607920591413-4ec007e70023"),
|
|
5899
|
+
tags: ["pastry", "comfort"],
|
|
5900
|
+
ingredients: ["Enriched dough", "Cinnamon", "Brown sugar", "Cream cheese"],
|
|
5901
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5902
|
+
calories: 380
|
|
5903
|
+
},
|
|
5904
|
+
{
|
|
5905
|
+
id: "prod_brownie",
|
|
5906
|
+
name: "Fudge Brownie",
|
|
5907
|
+
slug: "brownie",
|
|
5908
|
+
price: "12.00",
|
|
5909
|
+
description: "Dark, dense, fudgy brownie with a glossy shell. Chocolate four ways. Best slightly warm.",
|
|
5910
|
+
category: "pastries",
|
|
5911
|
+
imageUrl: img("1606313564200-e75d5e30476c"),
|
|
5912
|
+
tags: ["chocolate", "vegetarian"],
|
|
5913
|
+
ingredients: ["Dark chocolate", "Cocoa", "Brown butter", "Eggs"],
|
|
5914
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5915
|
+
calories: 420
|
|
5916
|
+
},
|
|
5917
|
+
{
|
|
5918
|
+
id: "prod_macaron-box",
|
|
5919
|
+
name: "Macaron Box (6)",
|
|
5920
|
+
slug: "macaron-box",
|
|
5921
|
+
price: "60.00",
|
|
5922
|
+
description: "Six French macarons \u2014 pistachio, raspberry, salted caramel, espresso, vanilla bean, and dark chocolate ganache.",
|
|
5923
|
+
category: "pastries",
|
|
5924
|
+
imageUrl: img("1558326567-98ae2405596b"),
|
|
5925
|
+
tags: ["pastry", "gift", "premium"],
|
|
5926
|
+
ingredients: ["Almond flour", "Egg whites", "Sugar", "Various fillings"],
|
|
5927
|
+
allergies: ["nuts", "eggs", "dairy"],
|
|
5928
|
+
calories: 540
|
|
5929
|
+
},
|
|
5930
|
+
// ── Cakes ──
|
|
5931
|
+
{
|
|
5932
|
+
id: "prod_chocolate-cake",
|
|
5933
|
+
name: "Dark Chocolate Layer Cake",
|
|
5934
|
+
slug: "chocolate-cake",
|
|
5935
|
+
price: "22.00",
|
|
5936
|
+
description: "Three layers of dark chocolate sponge with chocolate ganache and cr\xE8me p\xE2tissi\xE8re, finished with a mirror glaze. (Slice)",
|
|
5937
|
+
category: "cakes",
|
|
5938
|
+
imageUrl: img("1565958011703-44f9829ba187"),
|
|
5939
|
+
tags: ["cake", "chocolate", "signature"],
|
|
5940
|
+
isSignature: true,
|
|
5941
|
+
ingredients: ["Dark chocolate", "Cr\xE8me p\xE2tissi\xE8re", "Cocoa", "Eggs"],
|
|
5942
|
+
pairings: ["Espresso", "Cabernet"],
|
|
5943
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5944
|
+
calories: 480
|
|
5945
|
+
},
|
|
5946
|
+
{
|
|
5947
|
+
id: "prod_red-velvet",
|
|
5948
|
+
name: "Red Velvet (Slice)",
|
|
5949
|
+
slug: "red-velvet",
|
|
5950
|
+
price: "22.00",
|
|
5951
|
+
description: "Classic red velvet with cream cheese frosting and white chocolate shavings.",
|
|
5952
|
+
category: "cakes",
|
|
5953
|
+
imageUrl: img("1586788680434-30d324b2d46f"),
|
|
5954
|
+
tags: ["cake"],
|
|
5955
|
+
ingredients: ["Buttermilk", "Cocoa", "Cream cheese", "Beetroot"],
|
|
5956
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5957
|
+
calories: 460
|
|
5958
|
+
},
|
|
5959
|
+
{
|
|
5960
|
+
id: "prod_carrot-cake",
|
|
5961
|
+
name: "Carrot Cake (Slice)",
|
|
5962
|
+
slug: "carrot-cake",
|
|
5963
|
+
price: "20.00",
|
|
5964
|
+
description: "Spiced carrot cake with toasted walnuts, golden raisins, and cream-cheese frosting.",
|
|
5965
|
+
category: "cakes",
|
|
5966
|
+
imageUrl: img("1568827999250-3f6afff96e66"),
|
|
5967
|
+
tags: ["cake", "spiced"],
|
|
5968
|
+
ingredients: ["Carrot", "Walnuts", "Cinnamon", "Cream cheese"],
|
|
5969
|
+
allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
|
|
5970
|
+
calories: 420
|
|
5971
|
+
},
|
|
5972
|
+
{
|
|
5973
|
+
id: "prod_cheesecake",
|
|
5974
|
+
name: "New York Cheesecake (Slice)",
|
|
5975
|
+
slug: "cheesecake",
|
|
5976
|
+
price: "24.00",
|
|
5977
|
+
description: "Dense, creamy New York cheesecake on a butter graham crust. Topped with seasonal berry compote.",
|
|
5978
|
+
category: "cakes",
|
|
5979
|
+
imageUrl: img("1567306226416-28f0efdc88ce"),
|
|
5980
|
+
tags: ["cake", "premium"],
|
|
5981
|
+
ingredients: ["Cream cheese", "Sour cream", "Graham crust", "Vanilla"],
|
|
5982
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5983
|
+
calories: 520
|
|
5984
|
+
},
|
|
5985
|
+
{
|
|
5986
|
+
id: "prod_birthday-cake",
|
|
5987
|
+
name: "Custom Celebration Cake",
|
|
5988
|
+
slug: "birthday-cake",
|
|
5989
|
+
price: "250.00",
|
|
5990
|
+
description: "Made-to-order three-tier cake. Choose size, flavour, and frosting. 48-hour notice required for personalisation.",
|
|
5991
|
+
category: "cakes",
|
|
5992
|
+
imageUrl: img("1535254973040-607b474cb50d"),
|
|
5993
|
+
images: [img("1535254973040-607b474cb50d"), img("1565958011703-44f9829ba187")],
|
|
5994
|
+
tags: ["cake", "made-to-order", "premium"],
|
|
5995
|
+
ingredients: ["Sponge cake", "Buttercream", "Custom decoration"],
|
|
5996
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5997
|
+
productType: "product"
|
|
5998
|
+
},
|
|
5999
|
+
// ── Drinks ──
|
|
6000
|
+
{
|
|
6001
|
+
id: "prod_coffee",
|
|
6002
|
+
name: "Single-Origin Coffee",
|
|
6003
|
+
slug: "coffee",
|
|
6004
|
+
price: "10.00",
|
|
6005
|
+
description: "Locally roasted Arabica from Volta Region. Notes of cocoa, dark fruit, and brown sugar.",
|
|
6006
|
+
category: "drinks",
|
|
6007
|
+
imageUrl: img("1495474472287-4d71bcdd2085"),
|
|
6008
|
+
tags: ["drink", "coffee", "vegan"],
|
|
6009
|
+
ingredients: ["Single-origin Arabica beans"],
|
|
6010
|
+
pairings: ["Croissant", "Brownie"],
|
|
6011
|
+
calories: 5
|
|
6012
|
+
},
|
|
6013
|
+
{
|
|
6014
|
+
id: "prod_tea",
|
|
6015
|
+
name: "Loose-Leaf Tea",
|
|
6016
|
+
slug: "tea",
|
|
6017
|
+
price: "8.00",
|
|
6018
|
+
description: "Choice of English breakfast, Earl Grey, green sencha, or chamomile.",
|
|
6019
|
+
category: "drinks",
|
|
6020
|
+
imageUrl: img("1576092768241-dec231879fc3"),
|
|
6021
|
+
tags: ["drink", "tea", "vegan"],
|
|
6022
|
+
calories: 0
|
|
6023
|
+
},
|
|
6024
|
+
{
|
|
6025
|
+
id: "prod_sobolo",
|
|
6026
|
+
name: "Hibiscus (Sobolo)",
|
|
6027
|
+
slug: "sobolo",
|
|
6028
|
+
price: "12.00",
|
|
6029
|
+
description: "Iced hibiscus tea with ginger, pineapple peel, and a hint of clove. Tart, refreshing, lightly sweet.",
|
|
6030
|
+
category: "drinks",
|
|
6031
|
+
imageUrl: img("1556679343-c7306c1976bc"),
|
|
6032
|
+
tags: ["drink", "vegan", "ghana"],
|
|
6033
|
+
isSignature: true,
|
|
6034
|
+
ingredients: ["Hibiscus", "Ginger", "Pineapple", "Clove"],
|
|
6035
|
+
calories: 60
|
|
6036
|
+
},
|
|
6037
|
+
{
|
|
6038
|
+
id: "prod_orange-juice",
|
|
6039
|
+
name: "Cold-Pressed Orange Juice",
|
|
6040
|
+
slug: "orange-juice",
|
|
6041
|
+
price: "15.00",
|
|
6042
|
+
description: "Hand-pressed at sunrise from Ghanaian Valencia oranges. No sugar, no concentrate.",
|
|
6043
|
+
category: "drinks",
|
|
6044
|
+
imageUrl: img("1622597467836-f3285f2131b8"),
|
|
6045
|
+
tags: ["drink", "vegan"],
|
|
6046
|
+
calories: 110
|
|
6047
|
+
},
|
|
6048
|
+
// ── Boxes (anchor products for bundle/composite) ──
|
|
6049
|
+
{
|
|
6050
|
+
id: "prod_breakfast-box",
|
|
6051
|
+
name: "Breakfast Box",
|
|
6052
|
+
slug: "breakfast-box",
|
|
6053
|
+
price: "30.00",
|
|
6054
|
+
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.",
|
|
6055
|
+
category: "boxes",
|
|
6056
|
+
imageUrl: img("1533089860892-a7c6f0a88666"),
|
|
6057
|
+
images: [img("1533089860892-a7c6f0a88666"), img("1495474472287-4d71bcdd2085")],
|
|
6058
|
+
tags: ["bundle", "breakfast"],
|
|
6059
|
+
productType: "bundle"
|
|
6060
|
+
},
|
|
6061
|
+
{
|
|
6062
|
+
id: "prod_pastry-box",
|
|
6063
|
+
name: "Build-Your-Own Pastry Box",
|
|
6064
|
+
slug: "pastry-box",
|
|
6065
|
+
price: "60.00",
|
|
6066
|
+
description: "Pick 6 to 12 of our pastries \u2014 mix and match. Freshly boxed for sharing.",
|
|
6067
|
+
category: "boxes",
|
|
6068
|
+
imageUrl: img("1486427944299-d1955d23e34d"),
|
|
6069
|
+
tags: ["composite", "build-your-own", "gift"],
|
|
6070
|
+
productType: "composite"
|
|
6071
|
+
},
|
|
6072
|
+
// ── Merch (Retail card — render_hint=physical) ──
|
|
6073
|
+
{
|
|
6074
|
+
id: "prod_apron",
|
|
6075
|
+
name: "Akua's Bakery Apron",
|
|
6076
|
+
slug: "apron",
|
|
6077
|
+
price: "85.00",
|
|
6078
|
+
description: "Heavy-weight cotton-canvas apron with leather neck strap, embroidered logo, and two front pockets. Made in Accra.",
|
|
6079
|
+
category: "merch",
|
|
6080
|
+
imageUrl: img("1556905055-8f358a7a47b2"),
|
|
6081
|
+
tags: ["merch", "physical"],
|
|
6082
|
+
renderHint: "physical"
|
|
6083
|
+
},
|
|
6084
|
+
{
|
|
6085
|
+
id: "prod_tote",
|
|
6086
|
+
name: "Bakery Tote Bag",
|
|
6087
|
+
slug: "tote-bag",
|
|
6088
|
+
price: "35.00",
|
|
6089
|
+
description: "12oz natural canvas tote, screen-printed with our wheat-stalk crest. Roomy enough for two loaves.",
|
|
6090
|
+
category: "merch",
|
|
6091
|
+
imageUrl: img("1591561954557-26941169b49e"),
|
|
6092
|
+
tags: ["merch", "physical", "gift"],
|
|
6093
|
+
renderHint: "physical",
|
|
6094
|
+
isNew: true
|
|
6095
|
+
},
|
|
6096
|
+
// ── Wholesale (Wholesale card — quantity_pricing tiers) ──
|
|
6097
|
+
{
|
|
6098
|
+
id: "prod_flour-wholesale",
|
|
6099
|
+
name: "Heritage Flour \u2014 25kg Sack",
|
|
6100
|
+
slug: "flour-25kg",
|
|
6101
|
+
price: "320.00",
|
|
6102
|
+
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.",
|
|
6103
|
+
category: "merch",
|
|
6104
|
+
imageUrl: img("1568254183919-78a4f43a2877"),
|
|
6105
|
+
tags: ["wholesale", "physical", "trade"],
|
|
6106
|
+
renderHint: "physical",
|
|
6107
|
+
quantityPricing: [
|
|
6108
|
+
{ min_quantity: 1, unit_price: "320.00" },
|
|
6109
|
+
{ min_quantity: 5, unit_price: "295.00" },
|
|
6110
|
+
{ min_quantity: 10, unit_price: "270.00" },
|
|
6111
|
+
{ min_quantity: 25, unit_price: "245.00" }
|
|
6112
|
+
]
|
|
6113
|
+
},
|
|
6114
|
+
// ── Digital products (Digital card — type=digital) ──
|
|
6115
|
+
{
|
|
6116
|
+
id: "prod_recipe-ebook",
|
|
6117
|
+
name: "Sourdough Starter eBook",
|
|
6118
|
+
slug: "sourdough-ebook",
|
|
6119
|
+
price: "45.00",
|
|
6120
|
+
description: "PDF + EPUB walkthrough of our sourdough programme \u2014 starter, levain, autolyse, bulk fermentation, shaping, scoring. Lifetime updates.",
|
|
6121
|
+
category: "merch",
|
|
6122
|
+
imageUrl: img("1543002588-bfa74002ed7e"),
|
|
6123
|
+
tags: ["digital", "download"],
|
|
6124
|
+
productType: "digital",
|
|
6125
|
+
digitalProductType: "download"
|
|
6126
|
+
},
|
|
6127
|
+
{
|
|
6128
|
+
id: "prod_gift-card",
|
|
6129
|
+
name: "Bakery Gift Card",
|
|
6130
|
+
slug: "gift-card",
|
|
6131
|
+
price: "100.00",
|
|
6132
|
+
description: "Send instantly. Redeemable in-store and online toward any product. Choose any amount from GH\u20B550 upward.",
|
|
6133
|
+
category: "merch",
|
|
6134
|
+
imageUrl: img("1556905055-8f358a7a47b2"),
|
|
6135
|
+
tags: ["digital", "gift"],
|
|
6136
|
+
productType: "digital",
|
|
6137
|
+
digitalProductType: "gift_code",
|
|
6138
|
+
isSignature: true
|
|
6139
|
+
},
|
|
6140
|
+
// ── Services (Standard service card — type=service + duration_minutes) ──
|
|
6141
|
+
{
|
|
6142
|
+
id: "prod_baking-class",
|
|
6143
|
+
name: "Sourdough Baking Class",
|
|
6144
|
+
slug: "baking-class",
|
|
6145
|
+
price: "320.00",
|
|
6146
|
+
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.",
|
|
6147
|
+
category: "classes",
|
|
6148
|
+
imageUrl: img("1556909114-44e3e9399a2e"),
|
|
6149
|
+
tags: ["class", "service", "hands-on"],
|
|
6150
|
+
productType: "service",
|
|
6151
|
+
durationMinutes: 120
|
|
6152
|
+
},
|
|
6153
|
+
{
|
|
6154
|
+
id: "prod_pastry-workshop",
|
|
6155
|
+
name: "Lamination Workshop",
|
|
6156
|
+
slug: "pastry-workshop",
|
|
6157
|
+
price: "480.00",
|
|
6158
|
+
description: "Three-hour deep-dive on laminated dough. We cover croissants, danishes, kouign-amann. Aprons supplied; pastries to take home.",
|
|
6159
|
+
category: "classes",
|
|
6160
|
+
imageUrl: img("1517433367423-c7e5b0f35086"),
|
|
6161
|
+
tags: ["class", "service", "advanced"],
|
|
6162
|
+
productType: "service",
|
|
6163
|
+
durationMinutes: 180,
|
|
6164
|
+
isNew: true
|
|
6165
|
+
},
|
|
6166
|
+
// ── Rental service (Rental card — duration_unit=hours) ──
|
|
6167
|
+
{
|
|
6168
|
+
id: "prod_kitchen-rental",
|
|
6169
|
+
name: "Pro Kitchen Rental",
|
|
6170
|
+
slug: "kitchen-rental",
|
|
6171
|
+
price: "180.00",
|
|
6172
|
+
description: "Rent our commercial bakery after hours. Deck oven, retarder-proofer, mixers, scales \u2014 bring your team or shoot your content. Hourly.",
|
|
6173
|
+
category: "classes",
|
|
6174
|
+
imageUrl: img("1556909114-44e3e9399a2e"),
|
|
6175
|
+
tags: ["rental", "service", "commercial"],
|
|
6176
|
+
productType: "service",
|
|
6177
|
+
durationUnit: "hours"
|
|
6178
|
+
},
|
|
6179
|
+
// ── Accommodation (Accommodation card — duration_unit=nights) ──
|
|
6180
|
+
{
|
|
6181
|
+
id: "prod_loft-stay",
|
|
6182
|
+
name: "Bakery Loft \u2014 Weekend Stay",
|
|
6183
|
+
slug: "loft-stay",
|
|
6184
|
+
price: "650.00",
|
|
6185
|
+
description: "One-bedroom loft above the bakery. Wakes up to fresh croissants, ends with a private dinner. Ghana Tourism-licensed. Per night.",
|
|
6186
|
+
category: "classes",
|
|
6187
|
+
imageUrl: img("1564013799919-ab600027ffc6"),
|
|
6188
|
+
tags: ["stay", "service", "premium"],
|
|
6189
|
+
productType: "service",
|
|
6190
|
+
durationUnit: "nights"
|
|
6191
|
+
},
|
|
6192
|
+
// ── Subscription (Subscription card — billing_plans, no duration_minutes) ──
|
|
6193
|
+
{
|
|
6194
|
+
id: "prod_loaf-subscription",
|
|
6195
|
+
name: "Weekly Loaf Subscription",
|
|
6196
|
+
slug: "loaf-subscription",
|
|
6197
|
+
price: "0.00",
|
|
6198
|
+
description: "A fresh loaf delivered to your door every week. Pause or skip any time. Choose monthly or quarterly billing for a discount.",
|
|
6199
|
+
category: "classes",
|
|
6200
|
+
imageUrl: img("1509440159596-0249088772ff"),
|
|
6201
|
+
tags: ["subscription", "service", "delivery"],
|
|
6202
|
+
productType: "service",
|
|
6203
|
+
billingPlans: [
|
|
6204
|
+
{
|
|
6205
|
+
id: "plan_loaf_monthly",
|
|
6206
|
+
name: "Monthly",
|
|
6207
|
+
interval: "month",
|
|
6208
|
+
interval_count: 1,
|
|
6209
|
+
price: "90.00"
|
|
6210
|
+
},
|
|
6211
|
+
{
|
|
6212
|
+
id: "plan_loaf_quarterly",
|
|
6213
|
+
name: "Quarterly (save 10%)",
|
|
6214
|
+
interval: "month",
|
|
6215
|
+
interval_count: 3,
|
|
6216
|
+
price: "243.00"
|
|
6217
|
+
}
|
|
6218
|
+
]
|
|
6219
|
+
}
|
|
6220
|
+
];
|
|
6221
|
+
for (const input of productInputs) {
|
|
6222
|
+
const product = makeProduct(input, catIds);
|
|
6223
|
+
registry.products.put(product.id, product);
|
|
6224
|
+
const cat = registry.categories.get(catIds.get(input.category));
|
|
6225
|
+
if (cat) {
|
|
6226
|
+
cat.product_ids.push(product.id);
|
|
6227
|
+
registry.categories.put(cat.id, cat);
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
const featuredIds = [
|
|
6231
|
+
"prod_sourdough-loaf",
|
|
6232
|
+
"prod_croissant",
|
|
6233
|
+
"prod_chocolate-cake",
|
|
6234
|
+
"prod_sobolo",
|
|
6235
|
+
"prod_pastry-box"
|
|
6236
|
+
];
|
|
6237
|
+
const featured = {
|
|
5365
6238
|
id: "col_featured",
|
|
5366
|
-
business_id:
|
|
5367
|
-
name: "Featured",
|
|
6239
|
+
business_id: BUSINESS_ID,
|
|
6240
|
+
name: "Signature & Featured",
|
|
5368
6241
|
slug: "featured",
|
|
5369
|
-
description: "Our most
|
|
5370
|
-
product_ids:
|
|
6242
|
+
description: "Our most-loved bakes and a few seasonal stars.",
|
|
6243
|
+
product_ids: featuredIds,
|
|
5371
6244
|
is_active: true,
|
|
5372
6245
|
created_at: ISO,
|
|
5373
6246
|
updated_at: ISO
|
|
5374
6247
|
};
|
|
5375
|
-
registry.collections.put(
|
|
5376
|
-
for (const pid of
|
|
6248
|
+
registry.collections.put(featured.id, featured);
|
|
6249
|
+
for (const pid of featuredIds) {
|
|
5377
6250
|
const p = registry.products.get(pid);
|
|
5378
6251
|
if (p) {
|
|
5379
|
-
p.collection_ids.push(
|
|
6252
|
+
p.collection_ids.push(featured.id);
|
|
5380
6253
|
registry.products.put(pid, p);
|
|
5381
6254
|
}
|
|
5382
6255
|
}
|
|
5383
|
-
const tasting = {
|
|
5384
|
-
id: "svc_tasting",
|
|
5385
|
-
business_id: businessId,
|
|
5386
|
-
name: "Bakery Tasting Session",
|
|
5387
|
-
description: "Walk through our pastry menu with our head baker.",
|
|
5388
|
-
duration_minutes: 60,
|
|
5389
|
-
price: "150.00",
|
|
5390
|
-
is_available: true
|
|
5391
|
-
};
|
|
5392
|
-
registry.services.put(tasting.id, tasting);
|
|
5393
|
-
const cake = {
|
|
5394
|
-
id: "svc_custom_cake",
|
|
5395
|
-
business_id: businessId,
|
|
5396
|
-
name: "Custom Cake Consultation",
|
|
5397
|
-
description: "Design a custom cake for your event.",
|
|
5398
|
-
duration_minutes: 30,
|
|
5399
|
-
price: "0.00",
|
|
5400
|
-
is_available: true
|
|
5401
|
-
};
|
|
5402
|
-
registry.services.put(cake.id, cake);
|
|
5403
|
-
const sizeAxisId = "axis_size";
|
|
5404
|
-
const flavourAxisId = "axis_flavour";
|
|
5405
|
-
const sizeValues = [
|
|
5406
|
-
{ id: "axv_size_small", name: 'Small (8")' },
|
|
5407
|
-
{ id: "axv_size_medium", name: 'Medium (10")' },
|
|
5408
|
-
{ id: "axv_size_large", name: 'Large (12")' }
|
|
5409
|
-
];
|
|
5410
|
-
const flavourValues = [
|
|
5411
|
-
{ id: "axv_flavour_chocolate", name: "Chocolate" },
|
|
5412
|
-
{ id: "axv_flavour_vanilla", name: "Vanilla" },
|
|
5413
|
-
{ id: "axv_flavour_red_velvet", name: "Red Velvet" }
|
|
5414
|
-
];
|
|
5415
6256
|
const sizeAxis = {
|
|
5416
|
-
id:
|
|
5417
|
-
business_id:
|
|
6257
|
+
id: "axis_cake_size",
|
|
6258
|
+
business_id: BUSINESS_ID,
|
|
5418
6259
|
product_id: "prod_birthday-cake",
|
|
5419
6260
|
name: "Size",
|
|
5420
6261
|
display_order: 0,
|
|
5421
6262
|
affects_recipe: false,
|
|
5422
|
-
values:
|
|
6263
|
+
values: [
|
|
6264
|
+
{ id: "axv_size_small", name: 'Small (8")', display: 0 },
|
|
6265
|
+
{ id: "axv_size_medium", name: 'Medium (10")', display: 1 },
|
|
6266
|
+
{ id: "axv_size_large", name: 'Large (12")', display: 2 }
|
|
6267
|
+
].map((v) => ({
|
|
5423
6268
|
id: v.id,
|
|
5424
|
-
business_id:
|
|
5425
|
-
axis_id:
|
|
6269
|
+
business_id: BUSINESS_ID,
|
|
6270
|
+
axis_id: "axis_cake_size",
|
|
5426
6271
|
name: v.name,
|
|
5427
|
-
display_order:
|
|
6272
|
+
display_order: v.display,
|
|
5428
6273
|
created_at: ISO,
|
|
5429
6274
|
updated_at: ISO
|
|
5430
6275
|
})),
|
|
@@ -5432,18 +6277,22 @@ function seedDefault(registry) {
|
|
|
5432
6277
|
updated_at: ISO
|
|
5433
6278
|
};
|
|
5434
6279
|
const flavourAxis = {
|
|
5435
|
-
id:
|
|
5436
|
-
business_id:
|
|
6280
|
+
id: "axis_cake_flavour",
|
|
6281
|
+
business_id: BUSINESS_ID,
|
|
5437
6282
|
product_id: "prod_birthday-cake",
|
|
5438
6283
|
name: "Flavour",
|
|
5439
6284
|
display_order: 1,
|
|
5440
6285
|
affects_recipe: true,
|
|
5441
|
-
values:
|
|
6286
|
+
values: [
|
|
6287
|
+
{ id: "axv_flavour_chocolate", name: "Dark Chocolate", display: 0 },
|
|
6288
|
+
{ id: "axv_flavour_vanilla", name: "Vanilla Bean", display: 1 },
|
|
6289
|
+
{ id: "axv_flavour_red_velvet", name: "Red Velvet", display: 2 }
|
|
6290
|
+
].map((v) => ({
|
|
5442
6291
|
id: v.id,
|
|
5443
|
-
business_id:
|
|
5444
|
-
axis_id:
|
|
6292
|
+
business_id: BUSINESS_ID,
|
|
6293
|
+
axis_id: "axis_cake_flavour",
|
|
5445
6294
|
name: v.name,
|
|
5446
|
-
display_order:
|
|
6295
|
+
display_order: v.display,
|
|
5447
6296
|
created_at: ISO,
|
|
5448
6297
|
updated_at: ISO
|
|
5449
6298
|
})),
|
|
@@ -5452,40 +6301,42 @@ function seedDefault(registry) {
|
|
|
5452
6301
|
};
|
|
5453
6302
|
registry.variantAxes.put(sizeAxis.id, sizeAxis);
|
|
5454
6303
|
registry.variantAxes.put(flavourAxis.id, flavourAxis);
|
|
5455
|
-
const
|
|
5456
|
-
const
|
|
5457
|
-
let
|
|
5458
|
-
for (
|
|
5459
|
-
for (
|
|
5460
|
-
const
|
|
6304
|
+
const sizeAdj = { small: "250.00", medium: "350.00", large: "450.00" };
|
|
6305
|
+
const cakeVariantIds = [];
|
|
6306
|
+
let firstVariant = true;
|
|
6307
|
+
for (const sz of sizeAxis.values) {
|
|
6308
|
+
for (const fl of flavourAxis.values) {
|
|
6309
|
+
const sizeKey = sz.id.replace("axv_size_", "");
|
|
6310
|
+
const flavourKey = fl.id.replace("axv_flavour_", "");
|
|
6311
|
+
const id = `var_size_${sizeKey}_flavour_${flavourKey}`;
|
|
5461
6312
|
const variant = {
|
|
5462
6313
|
id,
|
|
5463
6314
|
product_id: "prod_birthday-cake",
|
|
5464
|
-
business_id:
|
|
5465
|
-
name: `${
|
|
5466
|
-
sku: `BIRTHDAY-${
|
|
5467
|
-
price_adjustment:
|
|
6315
|
+
business_id: BUSINESS_ID,
|
|
6316
|
+
name: `${sz.name} \u2022 ${fl.name}`,
|
|
6317
|
+
sku: `BIRTHDAY-${sizeKey.toUpperCase()}-${flavourKey.toUpperCase()}`,
|
|
6318
|
+
price_adjustment: sizeAdj[sizeKey] ?? "0.00",
|
|
5468
6319
|
component_multiplier: "1.00",
|
|
5469
|
-
is_default:
|
|
6320
|
+
is_default: firstVariant,
|
|
5470
6321
|
is_active: true,
|
|
5471
|
-
axis_value_ids: [
|
|
6322
|
+
axis_value_ids: [sz.id, fl.id],
|
|
5472
6323
|
created_at: ISO,
|
|
5473
6324
|
updated_at: ISO
|
|
5474
6325
|
};
|
|
5475
6326
|
registry.variants.put(id, variant);
|
|
5476
|
-
|
|
5477
|
-
|
|
6327
|
+
cakeVariantIds.push(id);
|
|
6328
|
+
firstVariant = false;
|
|
5478
6329
|
}
|
|
5479
6330
|
}
|
|
5480
|
-
const
|
|
5481
|
-
if (
|
|
5482
|
-
|
|
5483
|
-
registry.products.put(
|
|
6331
|
+
const birthdayProduct = registry.products.get("prod_birthday-cake");
|
|
6332
|
+
if (birthdayProduct) {
|
|
6333
|
+
birthdayProduct.variant_ids = cakeVariantIds;
|
|
6334
|
+
registry.products.put(birthdayProduct.id, birthdayProduct);
|
|
5484
6335
|
}
|
|
5485
6336
|
const toppingsAddOn = {
|
|
5486
6337
|
id: "addon_toppings",
|
|
5487
|
-
business_id:
|
|
5488
|
-
name: "
|
|
6338
|
+
business_id: BUSINESS_ID,
|
|
6339
|
+
name: "Cake toppings",
|
|
5489
6340
|
is_multiple_allowed: true,
|
|
5490
6341
|
is_required: false,
|
|
5491
6342
|
is_mutually_exclusive: false,
|
|
@@ -5494,15 +6345,14 @@ function seedDefault(registry) {
|
|
|
5494
6345
|
created_at: ISO,
|
|
5495
6346
|
updated_at: ISO,
|
|
5496
6347
|
options: [
|
|
5497
|
-
{ id: "addopt_strawberry", add_on_id: "addon_toppings", business_id:
|
|
5498
|
-
{ id: "addopt_choc_chips", add_on_id: "addon_toppings", business_id:
|
|
5499
|
-
{ id: "addopt_walnuts", add_on_id: "addon_toppings", business_id:
|
|
6348
|
+
{ 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 },
|
|
6349
|
+
{ 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 },
|
|
6350
|
+
{ 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 }
|
|
5500
6351
|
]
|
|
5501
6352
|
};
|
|
5502
|
-
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
5503
6353
|
const milkAddOn = {
|
|
5504
6354
|
id: "addon_milk",
|
|
5505
|
-
business_id:
|
|
6355
|
+
business_id: BUSINESS_ID,
|
|
5506
6356
|
name: "Milk",
|
|
5507
6357
|
is_multiple_allowed: false,
|
|
5508
6358
|
is_required: false,
|
|
@@ -5512,17 +6362,17 @@ function seedDefault(registry) {
|
|
|
5512
6362
|
created_at: ISO,
|
|
5513
6363
|
updated_at: ISO,
|
|
5514
6364
|
options: [
|
|
5515
|
-
{ id: "addopt_whole", add_on_id: "addon_milk", business_id:
|
|
5516
|
-
{ id: "addopt_oat", add_on_id: "addon_milk", business_id:
|
|
5517
|
-
{ id: "addopt_almond", add_on_id: "addon_milk", business_id:
|
|
6365
|
+
{ 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 },
|
|
6366
|
+
{ 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 },
|
|
6367
|
+
{ 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 }
|
|
5518
6368
|
]
|
|
5519
6369
|
};
|
|
6370
|
+
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
5520
6371
|
registry.addOns.put(milkAddOn.id, milkAddOn);
|
|
5521
6372
|
const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
|
|
5522
6373
|
const drinkProductIds = ["prod_coffee", "prod_tea"];
|
|
5523
6374
|
for (const pid of cakeProductIds) {
|
|
5524
|
-
|
|
5525
|
-
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
|
|
6375
|
+
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
|
|
5526
6376
|
const p = registry.products.get(pid);
|
|
5527
6377
|
if (p) {
|
|
5528
6378
|
p.add_on_ids.push(toppingsAddOn.id);
|
|
@@ -5530,8 +6380,7 @@ function seedDefault(registry) {
|
|
|
5530
6380
|
}
|
|
5531
6381
|
}
|
|
5532
6382
|
for (const pid of drinkProductIds) {
|
|
5533
|
-
|
|
5534
|
-
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
|
|
6383
|
+
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
|
|
5535
6384
|
const p = registry.products.get(pid);
|
|
5536
6385
|
if (p) {
|
|
5537
6386
|
p.add_on_ids.push(milkAddOn.id);
|
|
@@ -5540,7 +6389,7 @@ function seedDefault(registry) {
|
|
|
5540
6389
|
}
|
|
5541
6390
|
const breakfastBundle = {
|
|
5542
6391
|
id: "bnd_breakfast_box",
|
|
5543
|
-
business_id:
|
|
6392
|
+
business_id: BUSINESS_ID,
|
|
5544
6393
|
product_id: "prod_breakfast-box",
|
|
5545
6394
|
name: "Breakfast Box",
|
|
5546
6395
|
slug: "breakfast-box",
|
|
@@ -5552,29 +6401,9 @@ function seedDefault(registry) {
|
|
|
5552
6401
|
updated_at: ISO
|
|
5553
6402
|
};
|
|
5554
6403
|
registry.bundles.put(breakfastBundle.id, breakfastBundle);
|
|
5555
|
-
const breakfastProduct = {
|
|
5556
|
-
id: "prod_breakfast-box",
|
|
5557
|
-
business_id: businessId,
|
|
5558
|
-
name: "Breakfast Box",
|
|
5559
|
-
slug: "breakfast-box",
|
|
5560
|
-
description: "Bundled breakfast for one.",
|
|
5561
|
-
product_type: "bundle",
|
|
5562
|
-
base_price: "30.00",
|
|
5563
|
-
currency: "GHS",
|
|
5564
|
-
image: "/img/breakfast-box.jpg",
|
|
5565
|
-
images: ["/img/breakfast-box.jpg"],
|
|
5566
|
-
is_available: true,
|
|
5567
|
-
category_ids: [categoryIdsBySlug.get("pastries")],
|
|
5568
|
-
collection_ids: [],
|
|
5569
|
-
add_on_ids: [],
|
|
5570
|
-
variant_ids: [],
|
|
5571
|
-
created_at: ISO,
|
|
5572
|
-
updated_at: ISO
|
|
5573
|
-
};
|
|
5574
|
-
registry.products.put(breakfastProduct.id, breakfastProduct);
|
|
5575
6404
|
const pastryComposite = {
|
|
5576
6405
|
id: "comp_pastry_box",
|
|
5577
|
-
business_id:
|
|
6406
|
+
business_id: BUSINESS_ID,
|
|
5578
6407
|
product_id: "prod_pastry-box",
|
|
5579
6408
|
base_price: "60.00",
|
|
5580
6409
|
pricing_mode: "additive",
|
|
@@ -5586,43 +6415,51 @@ function seedDefault(registry) {
|
|
|
5586
6415
|
name: "Choose your pastries",
|
|
5587
6416
|
min: 6,
|
|
5588
6417
|
max: 12,
|
|
5589
|
-
component_ids: [
|
|
6418
|
+
component_ids: [
|
|
6419
|
+
"prod_croissant",
|
|
6420
|
+
"prod_pain-au-chocolat",
|
|
6421
|
+
"prod_almond-danish",
|
|
6422
|
+
"prod_cinnamon-roll",
|
|
6423
|
+
"prod_brownie",
|
|
6424
|
+
"prod_banana-bread",
|
|
6425
|
+
"prod_macaron-box"
|
|
6426
|
+
]
|
|
5590
6427
|
}
|
|
5591
6428
|
],
|
|
5592
6429
|
created_at: ISO,
|
|
5593
6430
|
updated_at: ISO
|
|
5594
6431
|
};
|
|
5595
6432
|
registry.composites.put(pastryComposite.id, pastryComposite);
|
|
5596
|
-
const
|
|
5597
|
-
id: "
|
|
5598
|
-
business_id:
|
|
5599
|
-
name: "
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
currency: "GHS",
|
|
5605
|
-
image: "/img/pastry-box.jpg",
|
|
5606
|
-
images: ["/img/pastry-box.jpg"],
|
|
5607
|
-
is_available: true,
|
|
5608
|
-
category_ids: [categoryIdsBySlug.get("pastries")],
|
|
5609
|
-
collection_ids: [],
|
|
5610
|
-
add_on_ids: [],
|
|
5611
|
-
variant_ids: [],
|
|
5612
|
-
created_at: ISO,
|
|
5613
|
-
updated_at: ISO
|
|
6433
|
+
const tasting = {
|
|
6434
|
+
id: "svc_tasting",
|
|
6435
|
+
business_id: BUSINESS_ID,
|
|
6436
|
+
name: "Bakery Tasting Session",
|
|
6437
|
+
description: "Sample our pastry menu with our head baker.",
|
|
6438
|
+
duration_minutes: 60,
|
|
6439
|
+
price: "150.00",
|
|
6440
|
+
is_available: true
|
|
5614
6441
|
};
|
|
5615
|
-
|
|
6442
|
+
const consult = {
|
|
6443
|
+
id: "svc_custom_cake",
|
|
6444
|
+
business_id: BUSINESS_ID,
|
|
6445
|
+
name: "Custom Cake Consultation",
|
|
6446
|
+
description: "Design a custom cake for your event.",
|
|
6447
|
+
duration_minutes: 30,
|
|
6448
|
+
price: "0.00",
|
|
6449
|
+
is_available: true
|
|
6450
|
+
};
|
|
6451
|
+
registry.services.put(tasting.id, tasting);
|
|
6452
|
+
registry.services.put(consult.id, consult);
|
|
5616
6453
|
const tags = [
|
|
5617
|
-
{ id: "tag_signature", business_id:
|
|
5618
|
-
{ id: "tag_vegan", business_id:
|
|
5619
|
-
{ id: "tag_gluten_free", business_id:
|
|
5620
|
-
{ id: "tag_seasonal", business_id:
|
|
6454
|
+
{ 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 },
|
|
6455
|
+
{ 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 },
|
|
6456
|
+
{ 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 },
|
|
6457
|
+
{ 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 }
|
|
5621
6458
|
];
|
|
5622
6459
|
for (const t of tags) registry.tags.put(t.id, t);
|
|
5623
6460
|
const baked = {
|
|
5624
6461
|
id: "tax_baked_goods",
|
|
5625
|
-
business_id:
|
|
6462
|
+
business_id: BUSINESS_ID,
|
|
5626
6463
|
name: "Baked Goods",
|
|
5627
6464
|
slug: "baked-goods",
|
|
5628
6465
|
path: ["Food", "Baked Goods"],
|
|
@@ -5632,7 +6469,7 @@ function seedDefault(registry) {
|
|
|
5632
6469
|
};
|
|
5633
6470
|
const beverages = {
|
|
5634
6471
|
id: "tax_beverages",
|
|
5635
|
-
business_id:
|
|
6472
|
+
business_id: BUSINESS_ID,
|
|
5636
6473
|
name: "Beverages",
|
|
5637
6474
|
slug: "beverages",
|
|
5638
6475
|
path: ["Food", "Beverages"],
|
|
@@ -5645,13 +6482,13 @@ function seedDefault(registry) {
|
|
|
5645
6482
|
const attrs = [
|
|
5646
6483
|
{
|
|
5647
6484
|
id: "attr_allergens",
|
|
5648
|
-
business_id:
|
|
6485
|
+
business_id: BUSINESS_ID,
|
|
5649
6486
|
namespace: "core",
|
|
5650
6487
|
name: "Allergens",
|
|
5651
6488
|
slug: "allergens",
|
|
5652
6489
|
description: "Common allergens present in this item.",
|
|
5653
6490
|
attribute_type: "multi_select",
|
|
5654
|
-
options: ["wheat", "eggs", "dairy", "nuts", "soy"],
|
|
6491
|
+
options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
|
|
5655
6492
|
is_required: false,
|
|
5656
6493
|
is_filterable: true,
|
|
5657
6494
|
visibility: "public",
|
|
@@ -5662,7 +6499,7 @@ function seedDefault(registry) {
|
|
|
5662
6499
|
},
|
|
5663
6500
|
{
|
|
5664
6501
|
id: "attr_shelf_life",
|
|
5665
|
-
business_id:
|
|
6502
|
+
business_id: BUSINESS_ID,
|
|
5666
6503
|
namespace: "core",
|
|
5667
6504
|
name: "Shelf life",
|
|
5668
6505
|
slug: "shelf-life",
|
|
@@ -5677,7 +6514,7 @@ function seedDefault(registry) {
|
|
|
5677
6514
|
},
|
|
5678
6515
|
{
|
|
5679
6516
|
id: "attr_caffeine",
|
|
5680
|
-
business_id:
|
|
6517
|
+
business_id: BUSINESS_ID,
|
|
5681
6518
|
namespace: "core",
|
|
5682
6519
|
name: "Caffeine",
|
|
5683
6520
|
slug: "caffeine",
|
|
@@ -5692,7 +6529,7 @@ function seedDefault(registry) {
|
|
|
5692
6529
|
},
|
|
5693
6530
|
{
|
|
5694
6531
|
id: "attr_seasonal",
|
|
5695
|
-
business_id:
|
|
6532
|
+
business_id: BUSINESS_ID,
|
|
5696
6533
|
namespace: "core",
|
|
5697
6534
|
name: "Seasonal",
|
|
5698
6535
|
slug: "seasonal",
|
|
@@ -5710,10 +6547,10 @@ function seedDefault(registry) {
|
|
|
5710
6547
|
const articles = [
|
|
5711
6548
|
{
|
|
5712
6549
|
id: "kb_delivery",
|
|
5713
|
-
business_id:
|
|
5714
|
-
title: "Delivery &
|
|
6550
|
+
business_id: BUSINESS_ID,
|
|
6551
|
+
title: "Delivery & pickup",
|
|
5715
6552
|
slug: "delivery-pickup",
|
|
5716
|
-
content: "Same-day delivery within Accra for orders placed before 2pm. Pickup available 8am\u20138pm.",
|
|
6553
|
+
content: "Same-day delivery within Greater Accra for orders placed before 2pm. Pickup available 8am\u20138pm at our East Legon location.",
|
|
5717
6554
|
category: "logistics",
|
|
5718
6555
|
tags: ["delivery", "pickup"],
|
|
5719
6556
|
created_at: ISO,
|
|
@@ -5721,10 +6558,10 @@ function seedDefault(registry) {
|
|
|
5721
6558
|
},
|
|
5722
6559
|
{
|
|
5723
6560
|
id: "kb_custom_cakes",
|
|
5724
|
-
business_id:
|
|
5725
|
-
title: "
|
|
6561
|
+
business_id: BUSINESS_ID,
|
|
6562
|
+
title: "Custom cakes & celebrations",
|
|
5726
6563
|
slug: "custom-cakes",
|
|
5727
|
-
content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page.",
|
|
6564
|
+
content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page to walk through flavours, sizes, and decoration.",
|
|
5728
6565
|
category: "products",
|
|
5729
6566
|
tags: ["cakes", "custom"],
|
|
5730
6567
|
created_at: ISO,
|
|
@@ -5732,10 +6569,10 @@ function seedDefault(registry) {
|
|
|
5732
6569
|
},
|
|
5733
6570
|
{
|
|
5734
6571
|
id: "kb_allergens",
|
|
5735
|
-
business_id:
|
|
5736
|
-
title: "
|
|
6572
|
+
business_id: BUSINESS_ID,
|
|
6573
|
+
title: "Allergen information",
|
|
5737
6574
|
slug: "allergens",
|
|
5738
|
-
content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible.",
|
|
6575
|
+
content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible \u2014 let us know about severe allergies before ordering.",
|
|
5739
6576
|
category: "products",
|
|
5740
6577
|
tags: ["allergens", "safety"],
|
|
5741
6578
|
created_at: ISO,
|
|
@@ -5743,7 +6580,7 @@ function seedDefault(registry) {
|
|
|
5743
6580
|
}
|
|
5744
6581
|
];
|
|
5745
6582
|
for (const a of articles) registry.knowledgeArticles.put(a.id, a);
|
|
5746
|
-
return { businessId };
|
|
6583
|
+
return { businessId: BUSINESS_ID };
|
|
5747
6584
|
}
|
|
5748
6585
|
|
|
5749
6586
|
// src/mock/seeds/empty.ts
|
|
@@ -7846,195 +8683,47 @@ function createDeps(options = {}) {
|
|
|
7846
8683
|
}
|
|
7847
8684
|
|
|
7848
8685
|
// src/mock/server.ts
|
|
8686
|
+
var PACKAGE_NAME = "@cimplify/sdk/mock";
|
|
8687
|
+
function corsMiddleware(allow) {
|
|
8688
|
+
return async (c, next) => {
|
|
8689
|
+
const origin = c.req.header("origin") ?? "";
|
|
8690
|
+
const allowOrigin = allow === "*" ? "*" : allow.includes(origin) ? origin : allow[0] ?? "*";
|
|
8691
|
+
const headers = {
|
|
8692
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
8693
|
+
"Access-Control-Allow-Credentials": "true",
|
|
8694
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
8695
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
8696
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
8697
|
+
"Access-Control-Max-Age": "86400",
|
|
8698
|
+
Vary: "Origin"
|
|
8699
|
+
};
|
|
8700
|
+
if (c.req.method === "OPTIONS") {
|
|
8701
|
+
return new Response(null, { status: 204, headers });
|
|
8702
|
+
}
|
|
8703
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
8704
|
+
await next();
|
|
8705
|
+
};
|
|
8706
|
+
}
|
|
7849
8707
|
function createMockApp(options = {}) {
|
|
7850
8708
|
const deps = createDeps(options);
|
|
7851
8709
|
const app = new Hono2();
|
|
7852
|
-
const
|
|
7853
|
-
if (
|
|
7854
|
-
app.use("*", async (c, next) => {
|
|
7855
|
-
const origin = c.req.header("origin") ?? "";
|
|
7856
|
-
const allow = corsOrigins === "*" ? "*" : corsOrigins.includes(origin) ? origin : corsOrigins[0] ?? "*";
|
|
7857
|
-
const headers = {
|
|
7858
|
-
"Access-Control-Allow-Origin": allow,
|
|
7859
|
-
"Access-Control-Allow-Credentials": "true",
|
|
7860
|
-
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
7861
|
-
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
7862
|
-
"Access-Control-Expose-Headers": "x-session-token",
|
|
7863
|
-
"Access-Control-Max-Age": "86400",
|
|
7864
|
-
Vary: "Origin"
|
|
7865
|
-
};
|
|
7866
|
-
if (c.req.method === "OPTIONS") {
|
|
7867
|
-
return new Response(null, { status: 204, headers });
|
|
7868
|
-
}
|
|
7869
|
-
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
7870
|
-
await next();
|
|
7871
|
-
});
|
|
7872
|
-
}
|
|
8710
|
+
const cors = options.cors ?? "*";
|
|
8711
|
+
if (cors) app.use("*", corsMiddleware(cors));
|
|
7873
8712
|
app.get(
|
|
7874
8713
|
"/",
|
|
7875
8714
|
(c) => c.json({
|
|
7876
|
-
name:
|
|
7877
|
-
version: "0.36.0",
|
|
8715
|
+
name: PACKAGE_NAME,
|
|
7878
8716
|
docs: "https://github.com/cimplify/cimplify",
|
|
7879
8717
|
api: "/api/v1",
|
|
7880
8718
|
admin: "/_mock",
|
|
7881
8719
|
elements: "/elements",
|
|
7882
|
-
|
|
8720
|
+
images: "/img/<slug>",
|
|
8721
|
+
seed_business_id: deps.defaultBusinessId,
|
|
8722
|
+
time: deps.clock.iso()
|
|
7883
8723
|
})
|
|
7884
8724
|
);
|
|
7885
8725
|
app.get("/healthz", (c) => c.json({ ok: true, time: deps.clock.iso() }));
|
|
7886
|
-
|
|
7887
|
-
// Akua's Bakery (default seed)
|
|
7888
|
-
sourdough: "1509440159596-0249088772ff",
|
|
7889
|
-
wheat: "1486887396153-fa416526c108",
|
|
7890
|
-
baguette: "1568471173242-461f0a730452",
|
|
7891
|
-
focaccia: "1573472635068-fd9b1abdd8e5",
|
|
7892
|
-
croissant: "1555507036-ab1f4038808a",
|
|
7893
|
-
pac: "1530610476181-d83430b64dcd",
|
|
7894
|
-
danish: "1517433367423-c7e5b0f35086",
|
|
7895
|
-
cinnamon: "1583527976767-50068e72b7a3",
|
|
7896
|
-
"choc-cake": "1565958011703-44f9829ba187",
|
|
7897
|
-
"red-velvet": "1586788680434-30d324b2d46f",
|
|
7898
|
-
carrot: "1568827999250-3f6afff96e66",
|
|
7899
|
-
cheesecake: "1567306226416-28f0efdc88ce",
|
|
7900
|
-
coffee: "1495474472287-4d71bcdd2085",
|
|
7901
|
-
tea: "1576092768241-dec231879fc3",
|
|
7902
|
-
sobolo: "1556679343-c7306c1976bc",
|
|
7903
|
-
oj: "1600271886742-f049b969dc7a",
|
|
7904
|
-
banana: "1632931057819-4eefffa8e007",
|
|
7905
|
-
brownie: "1551024506-0bccd828d307",
|
|
7906
|
-
macaron: "1558326567-98ae2405596b",
|
|
7907
|
-
birthday: "1535254973040-607b474cb50d",
|
|
7908
|
-
"breakfast-box": "1533089860892-a7c6f0a88666",
|
|
7909
|
-
"pastry-box": "1486427944299-d1955d23e34d",
|
|
7910
|
-
// Mama's Kitchen (restaurant seed)
|
|
7911
|
-
kelewele: "1585536830049-aedf5c1b98a4",
|
|
7912
|
-
"spring-rolls": "1606755962773-d324e0a13086",
|
|
7913
|
-
"soup-of-the-day": "1547592180-85f173990554",
|
|
7914
|
-
"jollof-rice": "1604329760661-e71dc83f8f26",
|
|
7915
|
-
"banku-tilapia": "1559847844-5315695dadae",
|
|
7916
|
-
"fufu-light-soup": "1547928576-b822bcced36e",
|
|
7917
|
-
waakye: "1604329760661-e71dc83f8f26",
|
|
7918
|
-
"grilled-chicken": "1532550907401-a500c9a57435",
|
|
7919
|
-
burger: "1568901346375-23c9450c58cd",
|
|
7920
|
-
"yam-fries": "1573080496219-bb080dd4f877",
|
|
7921
|
-
"fried-plantain": "1604329760661-e71dc83f8f26",
|
|
7922
|
-
salad: "1512621776951-a57141f2eefd",
|
|
7923
|
-
"ice-cream": "1497034825429-c343d7c6a68f",
|
|
7924
|
-
"cake-slice": "1565958011703-44f9829ba187",
|
|
7925
|
-
"fruit-bowl": "1490474418585-ba9bad8fd0ea",
|
|
7926
|
-
"soft-drink": "1497534446932-c925b458314e",
|
|
7927
|
-
"fresh-juice": "1622597467836-f3285f2131b8",
|
|
7928
|
-
water: "1548839140-29a749e1cf4d",
|
|
7929
|
-
feast: "1546069901-ba9599a7e63c",
|
|
7930
|
-
byo: "1546069901-ba9599a7e63c",
|
|
7931
|
-
// Currents Electronics
|
|
7932
|
-
"android-flagship": "1511707171634-5f897ff02aa9",
|
|
7933
|
-
"android-mid": "1592899677977-9c10ca588bbd",
|
|
7934
|
-
"phone-budget": "1574944985070-8f3ebc6b79d2",
|
|
7935
|
-
"laptop-pro": "1496181133206-80ce9b88a853",
|
|
7936
|
-
"laptop-air": "1517336714731-489689fd1ca8",
|
|
7937
|
-
"laptop-budget": "1541807084-5c52b6b3adef",
|
|
7938
|
-
"headphones-anc": "1583394838336-acd977736f90",
|
|
7939
|
-
"earbuds-pro": "1606220945770-b5b6c2c55bf1",
|
|
7940
|
-
"speaker-bt": "1608043152269-423dbba4e7e1",
|
|
7941
|
-
"phone-case": "1601593346740-925612772716",
|
|
7942
|
-
"screen-protector": "1592890288564-76628a30a657",
|
|
7943
|
-
"fast-charger": "1583394838336-acd977736f90",
|
|
7944
|
-
"laptop-sleeve": "1525547719571-a2d4ac8945e2",
|
|
7945
|
-
"wireless-mouse": "1527864550417-7fd91fc51a46",
|
|
7946
|
-
"mech-keyboard": "1587829741301-dc798b83add3",
|
|
7947
|
-
"gaming-controller": "1592840496694-26d035b52b48",
|
|
7948
|
-
"gaming-headset": "1591105327764-407a4a06e768",
|
|
7949
|
-
"console-handheld": "1606144042614-b2417e99c4e3",
|
|
7950
|
-
wfh: "1593642632559-0c6d3fc62b89",
|
|
7951
|
-
"game-pack": "1606144042614-b2417e99c4e3",
|
|
7952
|
-
// Reesa apparel
|
|
7953
|
-
"ankara-shirt": "1591047139829-d91aecb6caea",
|
|
7954
|
-
"agbada-set": "1605497788044-5a32c7078486",
|
|
7955
|
-
kaftan: "1554412933-514a83d2f3c8",
|
|
7956
|
-
"leather-tote": "1584917865442-de89df76afd3",
|
|
7957
|
-
sandals: "1543163521-1bf539c55dd2",
|
|
7958
|
-
"beaded-bracelet": "1611591437281-460bfbe1220a",
|
|
7959
|
-
owambe: "1605497788044-5a32c7078486",
|
|
7960
|
-
// FreshMart
|
|
7961
|
-
"tomatoes-1kg": "1546470427-227f1c75a9ef",
|
|
7962
|
-
"onions-1kg": "1620574387735-3624d75b2dbc",
|
|
7963
|
-
"ginger-200g": "1606937330526-f5e6a2a83555",
|
|
7964
|
-
"bananas-bunch": "1571771894821-ce9b6c11b08e",
|
|
7965
|
-
pineapple: "1550258987-190a2d41a8ba",
|
|
7966
|
-
"spinach-bunch": "1576045057995-568f588f82fb",
|
|
7967
|
-
"milk-1l": "1550583724-b2692b85b150",
|
|
7968
|
-
"yogurt-500g": "1571212515416-fef01fc43637",
|
|
7969
|
-
"eggs-12": "1582722872445-44dc5f7e3c8f",
|
|
7970
|
-
"cheese-200g": "1486297678162-eb2a19b0a32d",
|
|
7971
|
-
"chicken-1kg": "1604503468506-a8da13d82791",
|
|
7972
|
-
"beef-mince-500g": "1607623814075-e51df1bdc82f",
|
|
7973
|
-
"tilapia-each": "1546069901-ba9599a7e63c",
|
|
7974
|
-
"rice-5kg": "1586201375761-83865001e31c",
|
|
7975
|
-
"garri-2kg": "1604329760661-e71dc83f8f26",
|
|
7976
|
-
"palm-oil-1l": "1474979266404-7eaacbcd87c5",
|
|
7977
|
-
"vegetable-oil-1l": "1474979266404-7eaacbcd87c5",
|
|
7978
|
-
"salt-1kg": "1612531386530-97286d97c2d2",
|
|
7979
|
-
"sugar-1kg": "1581281697996-3aef0c3b9b12",
|
|
7980
|
-
"tomato-paste": "1546470427-227f1c75a9ef",
|
|
7981
|
-
"soap-bar": "1583912267550-d6c2ac3196c0",
|
|
7982
|
-
"detergent-1kg": "1583912267550-d6c2ac3196c0",
|
|
7983
|
-
"tissue-pack": "1610557892470-55d9e80c0bce",
|
|
7984
|
-
"trash-bags": "1583912267550-d6c2ac3196c0",
|
|
7985
|
-
essentials: "1542838132-92c53300491e",
|
|
7986
|
-
// Serene Spa services
|
|
7987
|
-
"swedish-massage-60": "1544161515-4ab6ce6db874",
|
|
7988
|
-
"swedish-massage-90": "1544161515-4ab6ce6db874",
|
|
7989
|
-
"deep-tissue-60": "1600334129128-685c5582fd35",
|
|
7990
|
-
"couples-massage": "1540555700478-4be289fbecef",
|
|
7991
|
-
"hot-stone-90": "1600334129128-685c5582fd35",
|
|
7992
|
-
"facial-classic": "1570172619644-dfd03ed5d881",
|
|
7993
|
-
"facial-anti-aging": "1571019613454-1cb2f99b2d8b",
|
|
7994
|
-
"facial-acne": "1571019613454-1cb2f99b2d8b",
|
|
7995
|
-
manicure: "1604654894610-df63bc536371",
|
|
7996
|
-
pedicure: "1610701596007-11502861dcfa",
|
|
7997
|
-
"gel-manicure": "1604654894610-df63bc536371",
|
|
7998
|
-
haircut: "1562322140-8baeececf3df",
|
|
7999
|
-
"hair-treatment": "1522337360788-8b13dee7a37e",
|
|
8000
|
-
color: "1562322140-8baeececf3df",
|
|
8001
|
-
"yoga-session": "1545205597-3d9d02c29597",
|
|
8002
|
-
meditation: "1545389336-cf090694435e",
|
|
8003
|
-
"spa-day": "1540555700478-4be289fbecef",
|
|
8004
|
-
"couples-retreat": "1540555700478-4be289fbecef",
|
|
8005
|
-
"custom-experience": "1545389336-cf090694435e"
|
|
8006
|
-
};
|
|
8007
|
-
app.get("/img/*", (c) => {
|
|
8008
|
-
const path = c.req.path.replace(/^\/img\//, "").replace(/\.[a-z0-9]+$/i, "");
|
|
8009
|
-
const slug = path.split("/").pop() ?? "";
|
|
8010
|
-
const photoId = UNSPLASH_PHOTOS[slug];
|
|
8011
|
-
if (photoId) {
|
|
8012
|
-
return c.redirect(`https://images.unsplash.com/photo-${photoId}?w=600&h=600&fit=crop&auto=format&q=80`, 302);
|
|
8013
|
-
}
|
|
8014
|
-
const label = slug.replace(/[-_]+/g, " ").trim() || "product";
|
|
8015
|
-
let hash = 0;
|
|
8016
|
-
for (let i = 0; i < slug.length; i++) hash = hash * 31 + slug.charCodeAt(i) >>> 0;
|
|
8017
|
-
const hue = hash % 360;
|
|
8018
|
-
const display = label.length > 20 ? label.slice(0, 20) + "\u2026" : label;
|
|
8019
|
-
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
8020
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
|
|
8021
|
-
<defs>
|
|
8022
|
-
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
8023
|
-
<stop offset="0%" stop-color="hsl(${hue}, 65%, 65%)"/>
|
|
8024
|
-
<stop offset="100%" stop-color="hsl(${(hue + 40) % 360}, 70%, 45%)"/>
|
|
8025
|
-
</linearGradient>
|
|
8026
|
-
</defs>
|
|
8027
|
-
<rect width="400" height="400" fill="url(#g)"/>
|
|
8028
|
-
<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>
|
|
8029
|
-
</svg>`;
|
|
8030
|
-
return new Response(svg, {
|
|
8031
|
-
headers: {
|
|
8032
|
-
"Content-Type": "image/svg+xml; charset=utf-8",
|
|
8033
|
-
"Cache-Control": "public, max-age=3600",
|
|
8034
|
-
"Access-Control-Allow-Origin": "*"
|
|
8035
|
-
}
|
|
8036
|
-
});
|
|
8037
|
-
});
|
|
8726
|
+
mountImageRoutes(app);
|
|
8038
8727
|
app.route("/api/v1", buildV1(deps));
|
|
8039
8728
|
app.route("/_mock", buildAdmin(deps));
|
|
8040
8729
|
app.route("/elements", buildElements(deps));
|
|
@@ -8042,7 +8731,11 @@ function createMockApp(options = {}) {
|
|
|
8042
8731
|
(c) => c.json(
|
|
8043
8732
|
{
|
|
8044
8733
|
success: false,
|
|
8045
|
-
error: {
|
|
8734
|
+
error: {
|
|
8735
|
+
code: "ROUTE_NOT_REGISTERED",
|
|
8736
|
+
message: `Route ${c.req.method} ${c.req.path} not handled by mock`,
|
|
8737
|
+
retryable: false
|
|
8738
|
+
},
|
|
8046
8739
|
hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
|
|
8047
8740
|
},
|
|
8048
8741
|
501
|