@cimplify/sdk 0.38.2 → 0.38.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatcher.mjs +1 -1
- package/dist/mock/cli.mjs +956 -436
- package/dist/mock/library.d.mts +2 -2
- package/dist/mock/library.d.ts +2 -2
- package/dist/mock/library.js +956 -436
- package/dist/mock/library.mjs +956 -436
- package/dist/mock/msw.d.mts +1 -1
- package/dist/mock/msw.d.ts +1 -1
- package/dist/mock/msw.js +956 -436
- package/dist/mock/msw.mjs +956 -436
- package/dist/{server-B8TeE2OD.d.mts → server-BNy6Ri-4.d.mts} +18 -0
- package/dist/{server-B8TeE2OD.d.ts → server-BNy6Ri-4.d.ts} +18 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/templates/storefront/.env.example +11 -2
- package/templates/storefront/README.md +35 -18
- package/templates/storefront/app/cart/page.tsx +9 -0
- package/templates/storefront/app/categories/[slug]/page.tsx +80 -0
- package/templates/storefront/app/checkout/page.tsx +17 -0
- package/templates/storefront/app/collections/[slug]/page.tsx +80 -0
- package/templates/storefront/app/globals.css +47 -0
- package/templates/storefront/app/layout.tsx +45 -0
- package/templates/storefront/app/orders/[id]/page.tsx +21 -0
- package/templates/storefront/app/page.tsx +65 -0
- package/templates/storefront/app/shop/page.tsx +46 -0
- package/templates/storefront/components/category-grid.tsx +28 -0
- package/templates/storefront/components/collection-strip.tsx +45 -0
- package/templates/storefront/components/footer.tsx +19 -0
- package/templates/storefront/components/header.tsx +41 -0
- package/templates/storefront/components/hero.tsx +25 -0
- package/templates/storefront/components/product-modal.tsx +105 -0
- package/templates/storefront/components/providers.tsx +31 -0
- package/templates/storefront/components/store-product-card.tsx +48 -0
- package/templates/storefront/lib/cart.ts +27 -0
- package/templates/storefront/next.config.ts +36 -0
- package/templates/storefront/package.json +13 -14
- package/templates/storefront/postcss.config.mjs +7 -0
- package/templates/storefront/tsconfig.json +15 -11
- package/templates/storefront/index.html +0 -12
- package/templates/storefront/src/App.tsx +0 -118
- package/templates/storefront/src/main.tsx +0 -28
- package/templates/storefront/src/styles.css +0 -65
- package/templates/storefront/vite.config.ts +0 -8
package/dist/mock/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,249 @@ 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.product_type === "service") return "general";
|
|
4175
|
+
const foodish = (p.tags ?? []).some(
|
|
4176
|
+
(t) => /food|drink|cake|bread|pastry|dessert|coffee|tea|juice|spice/i.test(t)
|
|
4177
|
+
);
|
|
4178
|
+
return foodish ? "food" : "general";
|
|
4179
|
+
}
|
|
4180
|
+
function toApiProduct(p, ctx) {
|
|
4181
|
+
const { registry, priceInfo, groups, components, composite, bundle } = ctx;
|
|
4182
|
+
return {
|
|
4183
|
+
// ─── Canonical Product fields (SDK type: Product / ProductWithDetails) ───
|
|
3972
4184
|
id: p.id,
|
|
3973
4185
|
business_id: p.business_id,
|
|
4186
|
+
category_id: p.category_ids[0],
|
|
3974
4187
|
name: p.name,
|
|
3975
4188
|
slug: p.slug,
|
|
3976
4189
|
description: p.description ?? "",
|
|
3977
|
-
|
|
3978
|
-
|
|
4190
|
+
image_url: p.image,
|
|
4191
|
+
images: p.images,
|
|
3979
4192
|
default_price: p.base_price,
|
|
3980
|
-
|
|
3981
|
-
|
|
4193
|
+
type: p.product_type,
|
|
4194
|
+
render_hint: deriveRenderHint(p),
|
|
4195
|
+
inventory_type: "none",
|
|
4196
|
+
variant_strategy: p.variant_ids.length > 0 ? "use_axes" : "fetch_all",
|
|
4197
|
+
is_active: p.is_available,
|
|
4198
|
+
inventory_status: { in_stock: p.is_available, stock_level: 50, low_stock: false },
|
|
4199
|
+
tags: p.tags ?? [],
|
|
4200
|
+
calories: p.calories,
|
|
4201
|
+
allergies: p.allergies ?? [],
|
|
4202
|
+
created_at: p.created_at,
|
|
4203
|
+
updated_at: p.updated_at,
|
|
4204
|
+
metadata: {
|
|
4205
|
+
is_signature: p.is_signature ?? false,
|
|
4206
|
+
is_new: p.is_new ?? false,
|
|
4207
|
+
ingredients: p.ingredients ?? [],
|
|
4208
|
+
pairings: p.pairings ?? [],
|
|
4209
|
+
...p.metadata ?? {}
|
|
4210
|
+
},
|
|
4211
|
+
// ─── Pricing breakdown (Price / ProductCustomizer) ───
|
|
4212
|
+
base_price: p.base_price,
|
|
4213
|
+
default_price_info: priceInfo,
|
|
4214
|
+
price_info: priceInfo,
|
|
3982
4215
|
currency: p.currency,
|
|
4216
|
+
// ─── Compatibility aliases (older callers) ───
|
|
3983
4217
|
image: p.image,
|
|
3984
|
-
|
|
3985
|
-
images: p.images,
|
|
4218
|
+
product_type: p.product_type,
|
|
3986
4219
|
is_available: p.is_available,
|
|
3987
|
-
category_id: p.category_ids[0],
|
|
3988
4220
|
category_ids: p.category_ids,
|
|
3989
4221
|
collection_ids: p.collection_ids,
|
|
3990
4222
|
add_on_ids: p.add_on_ids,
|
|
3991
|
-
|
|
3992
|
-
created_at: p.created_at,
|
|
3993
|
-
updated_at: p.updated_at,
|
|
4223
|
+
variant_ids: p.variant_ids,
|
|
3994
4224
|
custom_attributes: [],
|
|
3995
4225
|
properties: [],
|
|
3996
4226
|
has_variants: p.variant_ids.length > 0,
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4227
|
+
// ─── Embedded relations (ProductWithDetails) ───
|
|
4228
|
+
category: registry.categories.get(p.category_ids[0]),
|
|
4229
|
+
add_ons: p.add_on_ids.map((aid) => registry.addOns.get(aid)).filter(Boolean),
|
|
4230
|
+
variants: registry.variants.list((v) => v.product_id === p.id),
|
|
4231
|
+
variant_axes: registry.variantAxes.list((a) => a.product_id === p.id),
|
|
4232
|
+
// ─── Composite-specific (CompositeProductLayout reads product.groups) ───
|
|
4233
|
+
groups,
|
|
4234
|
+
composite_id: composite?.id,
|
|
4235
|
+
pricing_mode: composite?.pricing_mode,
|
|
4236
|
+
min_order_quantity: composite?.min_order_quantity,
|
|
4237
|
+
max_order_quantity: composite?.max_order_quantity,
|
|
4238
|
+
// ─── Bundle-specific (BundleProductLayout reads product.components) ───
|
|
4239
|
+
bundle_id: bundle?.id,
|
|
4240
|
+
bundle_pricing_type: bundle?.pricing_type,
|
|
4241
|
+
bundle_price: bundle?.bundle_price,
|
|
4242
|
+
bundle_discount_value: bundle?.discount_value,
|
|
4243
|
+
components
|
|
4244
|
+
};
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
// src/mock/domain/catalogue/composite-shape.ts
|
|
4248
|
+
function expandCompositeGroups(registry, productId, isoNow) {
|
|
4249
|
+
const composite = registry.composites.list((c) => c.product_id === productId)[0];
|
|
4250
|
+
if (!composite) return void 0;
|
|
4251
|
+
return composite.groups.map((g, gIdx) => buildGroup(registry, composite, g, gIdx, isoNow));
|
|
4252
|
+
}
|
|
4253
|
+
function buildGroup(registry, composite, group, groupIdx, isoNow) {
|
|
4254
|
+
return {
|
|
4255
|
+
// ─── Canonical ComponentGroupWithComponents ───
|
|
4256
|
+
id: group.id,
|
|
4257
|
+
composite_id: composite.id,
|
|
4258
|
+
name: group.name,
|
|
4259
|
+
description: void 0,
|
|
4260
|
+
display_order: groupIdx,
|
|
4261
|
+
min_selections: group.min,
|
|
4262
|
+
max_selections: group.max,
|
|
4263
|
+
allow_quantity: true,
|
|
4264
|
+
max_quantity_per_component: group.max,
|
|
4265
|
+
pricing_behavior: "additive",
|
|
4266
|
+
created_at: isoNow,
|
|
4267
|
+
updated_at: isoNow,
|
|
4268
|
+
// ─── Aliases for older selectors ───
|
|
4269
|
+
min: group.min,
|
|
4270
|
+
max: group.max,
|
|
4271
|
+
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))
|
|
4272
|
+
};
|
|
4273
|
+
}
|
|
4274
|
+
function buildComponent(composite, group, p, idx) {
|
|
4275
|
+
return {
|
|
4276
|
+
// ─── Canonical CompositeComponent ───
|
|
4277
|
+
id: `comp_${composite.product_id}_${p.id}`,
|
|
4278
|
+
composite_id: composite.id,
|
|
4279
|
+
group_id: group.id,
|
|
4280
|
+
source_type: "product",
|
|
4281
|
+
source_product_id: p.id,
|
|
4282
|
+
product_id: p.id,
|
|
4283
|
+
display_name: p.name,
|
|
4284
|
+
display_description: p.description,
|
|
4285
|
+
display_image_url: p.image,
|
|
4286
|
+
price: p.base_price,
|
|
4287
|
+
calories: p.calories,
|
|
4288
|
+
allergens: p.allergies ?? [],
|
|
4289
|
+
display_order: idx,
|
|
4290
|
+
is_popular: idx === 0,
|
|
4291
|
+
is_premium: false,
|
|
4292
|
+
is_available: p.is_available,
|
|
4293
|
+
is_archived: false,
|
|
4294
|
+
created_at: p.created_at,
|
|
4295
|
+
updated_at: p.updated_at,
|
|
4296
|
+
// ─── Compatibility aliases ───
|
|
4297
|
+
name: p.name,
|
|
4298
|
+
description: p.description,
|
|
4299
|
+
image_url: p.image,
|
|
4300
|
+
unit_price: p.base_price,
|
|
4301
|
+
default_price: p.base_price,
|
|
4302
|
+
base_price: p.base_price,
|
|
4303
|
+
currency: p.currency,
|
|
4304
|
+
product: toCanonicalProductSummary(p)
|
|
4305
|
+
};
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
// src/mock/domain/catalogue/bundle-shape.ts
|
|
4309
|
+
function expandBundleComponents(registry, productId) {
|
|
4310
|
+
const bundle = registry.bundles.list((b) => b.product_id === productId)[0];
|
|
4311
|
+
if (!bundle) return void 0;
|
|
4312
|
+
return bundle.product_ids.map((cid) => registry.products.get(cid)).filter((p) => Boolean(p)).map((p) => buildBundleComponent(bundle.id, productId, p));
|
|
4313
|
+
}
|
|
4314
|
+
function buildBundleComponent(bundleId, productId, p) {
|
|
4315
|
+
return {
|
|
4316
|
+
id: `bp_${productId}_${p.id}`,
|
|
4317
|
+
bundle_id: bundleId,
|
|
4318
|
+
product_id: p.id,
|
|
4319
|
+
// ─── Fields BundleProductLayout reads ───
|
|
4320
|
+
product_name: p.name,
|
|
4321
|
+
product_image_url: p.image,
|
|
4322
|
+
product_description: p.description,
|
|
4323
|
+
product_slug: p.slug,
|
|
4324
|
+
effective_price: p.base_price,
|
|
4325
|
+
unit_price: p.base_price,
|
|
4326
|
+
default_price: p.base_price,
|
|
4327
|
+
quantity: 1,
|
|
4328
|
+
allow_variant_choice: false,
|
|
4329
|
+
available_variants: [],
|
|
4330
|
+
selected_variant_id: null,
|
|
4331
|
+
// ─── Compatibility aliases ───
|
|
4332
|
+
display_name: p.name,
|
|
4333
|
+
display_description: p.description,
|
|
4334
|
+
name: p.name,
|
|
4335
|
+
description: p.description,
|
|
4336
|
+
image_url: p.image,
|
|
4337
|
+
currency: p.currency,
|
|
4338
|
+
is_available: p.is_available,
|
|
4339
|
+
product: toCanonicalProductSummary(p)
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
// src/mock/domain/catalogue/index.ts
|
|
4344
|
+
function createCatalogueService(opts) {
|
|
4345
|
+
const { registry, clock, ids } = opts;
|
|
4346
|
+
const productsForBusiness = (businessId) => registry.products.list((p) => p.business_id === businessId);
|
|
4000
4347
|
const variantsForProduct = (productId) => registry.variants.list((v) => v.product_id === productId);
|
|
4001
4348
|
const axesForProduct = (productId) => registry.variantAxes.list((a) => a.product_id === productId);
|
|
4002
4349
|
const addOnLinksForProduct = (productId) => registry.productAddOns.list((l) => l.product_id === productId);
|
|
4350
|
+
const apiProduct = (p) => {
|
|
4351
|
+
const composite = p.product_type === "composite" ? registry.composites.list((c) => c.product_id === p.id)[0] : void 0;
|
|
4352
|
+
const bundle = p.product_type === "bundle" ? registry.bundles.list((b) => b.product_id === p.id)[0] : void 0;
|
|
4353
|
+
return toApiProduct(p, {
|
|
4354
|
+
registry,
|
|
4355
|
+
priceInfo: defaultPriceInfo(p.base_price, p.currency),
|
|
4356
|
+
groups: expandCompositeGroups(registry, p.id, clock.iso()),
|
|
4357
|
+
components: expandBundleComponents(registry, p.id),
|
|
4358
|
+
composite: composite ? {
|
|
4359
|
+
id: composite.id,
|
|
4360
|
+
pricing_mode: composite.pricing_mode,
|
|
4361
|
+
min_order_quantity: composite.min_order_quantity,
|
|
4362
|
+
max_order_quantity: composite.max_order_quantity
|
|
4363
|
+
} : void 0,
|
|
4364
|
+
bundle: bundle ? {
|
|
4365
|
+
id: bundle.id,
|
|
4366
|
+
pricing_type: bundle.pricing_type,
|
|
4367
|
+
bundle_price: bundle.bundle_price,
|
|
4368
|
+
discount_value: bundle.discount_value
|
|
4369
|
+
} : void 0
|
|
4370
|
+
});
|
|
4371
|
+
};
|
|
4372
|
+
const resolveProduct = (idOrSlug) => registry.products.get(idOrSlug) ?? registry.products.list((p) => p.slug === idOrSlug)[0];
|
|
4003
4373
|
return {
|
|
4004
4374
|
snapshot(businessId) {
|
|
4005
4375
|
const products = productsForBusiness(businessId);
|
|
@@ -4007,7 +4377,7 @@ function createCatalogueService(opts) {
|
|
|
4007
4377
|
const addOns = registry.addOns.list((a) => a.business_id === businessId);
|
|
4008
4378
|
return {
|
|
4009
4379
|
categories,
|
|
4010
|
-
products: products.map(
|
|
4380
|
+
products: products.map(apiProduct),
|
|
4011
4381
|
add_ons: addOns,
|
|
4012
4382
|
is_complete: true,
|
|
4013
4383
|
total_available: products.length
|
|
@@ -4022,7 +4392,7 @@ function createCatalogueService(opts) {
|
|
|
4022
4392
|
const start = (page - 1) * limit;
|
|
4023
4393
|
const slice = items.slice(start, start + limit);
|
|
4024
4394
|
return {
|
|
4025
|
-
items: slice.map(
|
|
4395
|
+
items: slice.map(apiProduct),
|
|
4026
4396
|
is_complete: start + limit >= items.length,
|
|
4027
4397
|
total_available: items.length,
|
|
4028
4398
|
pagination: {
|
|
@@ -4035,9 +4405,9 @@ function createCatalogueService(opts) {
|
|
|
4035
4405
|
};
|
|
4036
4406
|
},
|
|
4037
4407
|
getProduct(productId) {
|
|
4038
|
-
const p =
|
|
4408
|
+
const p = resolveProduct(productId);
|
|
4039
4409
|
if (!p) throw notFound(`Product ${productId} not found`);
|
|
4040
|
-
return
|
|
4410
|
+
return apiProduct(p);
|
|
4041
4411
|
},
|
|
4042
4412
|
getProductVariants(productId) {
|
|
4043
4413
|
return variantsForProduct(productId);
|
|
@@ -4047,7 +4417,9 @@ function createCatalogueService(opts) {
|
|
|
4047
4417
|
},
|
|
4048
4418
|
getProductVariantById(productId, variantId) {
|
|
4049
4419
|
const v = registry.variants.get(variantId);
|
|
4050
|
-
if (!v || v.product_id !== productId)
|
|
4420
|
+
if (!v || v.product_id !== productId) {
|
|
4421
|
+
throw notFound(`Variant ${variantId} not found on product ${productId}`);
|
|
4422
|
+
}
|
|
4051
4423
|
return v;
|
|
4052
4424
|
},
|
|
4053
4425
|
findProductVariant(productId, properties) {
|
|
@@ -4077,13 +4449,13 @@ function createCatalogueService(opts) {
|
|
|
4077
4449
|
return registry.categories.list((c) => c.business_id === businessId);
|
|
4078
4450
|
},
|
|
4079
4451
|
getCategory(id) {
|
|
4080
|
-
const c = registry.categories.get(id);
|
|
4452
|
+
const c = registry.categories.get(id) ?? registry.categories.list((x) => x.slug === id)[0];
|
|
4081
4453
|
if (!c) throw notFound(`Category ${id} not found`);
|
|
4082
4454
|
return c;
|
|
4083
4455
|
},
|
|
4084
4456
|
getCategoryProducts(id) {
|
|
4085
4457
|
const c = this.getCategory(id);
|
|
4086
|
-
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(
|
|
4458
|
+
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
|
|
4087
4459
|
},
|
|
4088
4460
|
getCategoryAttributes(id) {
|
|
4089
4461
|
const c = registry.categories.get(id);
|
|
@@ -4096,13 +4468,13 @@ function createCatalogueService(opts) {
|
|
|
4096
4468
|
return registry.collections.list((c) => c.business_id === businessId);
|
|
4097
4469
|
},
|
|
4098
4470
|
getCollection(id) {
|
|
4099
|
-
const c = registry.collections.get(id);
|
|
4471
|
+
const c = registry.collections.get(id) ?? registry.collections.list((x) => x.slug === id)[0];
|
|
4100
4472
|
if (!c) throw notFound(`Collection ${id} not found`);
|
|
4101
4473
|
return c;
|
|
4102
4474
|
},
|
|
4103
4475
|
getCollectionProducts(id) {
|
|
4104
4476
|
const c = this.getCollection(id);
|
|
4105
|
-
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(
|
|
4477
|
+
return c.product_ids.map((pid) => registry.products.get(pid)).filter((p) => Boolean(p)).map(apiProduct);
|
|
4106
4478
|
},
|
|
4107
4479
|
getCollectionAttributes(id) {
|
|
4108
4480
|
const c = registry.collections.get(id);
|
|
@@ -4115,7 +4487,7 @@ function createCatalogueService(opts) {
|
|
|
4115
4487
|
return registry.bundles.list((b) => b.business_id === businessId);
|
|
4116
4488
|
},
|
|
4117
4489
|
getBundle(id) {
|
|
4118
|
-
const b = registry.bundles.get(id);
|
|
4490
|
+
const b = registry.bundles.get(id) ?? registry.bundles.list((x) => x.product_id === id || x.slug === id)[0];
|
|
4119
4491
|
if (!b) throw notFound(`Bundle ${id} not found`);
|
|
4120
4492
|
return b;
|
|
4121
4493
|
},
|
|
@@ -4123,12 +4495,16 @@ function createCatalogueService(opts) {
|
|
|
4123
4495
|
return registry.composites.list((c) => c.business_id === businessId);
|
|
4124
4496
|
},
|
|
4125
4497
|
getComposite(id) {
|
|
4126
|
-
const c = registry.composites.get(id);
|
|
4498
|
+
const c = registry.composites.get(id) ?? registry.composites.list((x) => x.product_id === id)[0];
|
|
4127
4499
|
if (!c) throw notFound(`Composite ${id} not found`);
|
|
4128
4500
|
return c;
|
|
4129
4501
|
},
|
|
4130
4502
|
getCompositeByProduct(productId) {
|
|
4131
|
-
|
|
4503
|
+
const direct = registry.composites.list((c) => c.product_id === productId)[0];
|
|
4504
|
+
if (direct) return direct;
|
|
4505
|
+
const product = registry.products.list((p) => p.slug === productId)[0];
|
|
4506
|
+
if (!product) return null;
|
|
4507
|
+
return registry.composites.list((c) => c.product_id === product.id)[0] ?? null;
|
|
4132
4508
|
},
|
|
4133
4509
|
calculateCompositePrice(id, body) {
|
|
4134
4510
|
const c = registry.composites.get(id);
|
|
@@ -4136,14 +4512,20 @@ function createCatalogueService(opts) {
|
|
|
4136
4512
|
const sel = body.selections ?? [];
|
|
4137
4513
|
let total = c.base_price;
|
|
4138
4514
|
for (const s of sel) total = add(total, money(parseFloat(s.unit_price ?? "0") * (s.quantity ?? 1)));
|
|
4139
|
-
return {
|
|
4515
|
+
return {
|
|
4516
|
+
final_price: total,
|
|
4517
|
+
base_price: c.base_price,
|
|
4518
|
+
components_total: money(parseFloat(total) - parseFloat(c.base_price))
|
|
4519
|
+
};
|
|
4140
4520
|
},
|
|
4141
4521
|
listTaxonomies(businessId) {
|
|
4142
4522
|
return registry.taxonomies.list((t) => t.business_id === businessId);
|
|
4143
4523
|
},
|
|
4144
4524
|
searchTaxonomies(businessId, q) {
|
|
4145
4525
|
const term = q.toLowerCase();
|
|
4146
|
-
return registry.taxonomies.list(
|
|
4526
|
+
return registry.taxonomies.list(
|
|
4527
|
+
(t) => t.business_id === businessId && t.name.toLowerCase().includes(term)
|
|
4528
|
+
);
|
|
4147
4529
|
},
|
|
4148
4530
|
getTaxonomy(id) {
|
|
4149
4531
|
const t = registry.taxonomies.get(id);
|
|
@@ -4177,7 +4559,11 @@ function createCatalogueService(opts) {
|
|
|
4177
4559
|
listPropertyFacets(businessId) {
|
|
4178
4560
|
const products = productsForBusiness(businessId);
|
|
4179
4561
|
const byCategory = /* @__PURE__ */ new Map();
|
|
4180
|
-
for (const p of products)
|
|
4562
|
+
for (const p of products) {
|
|
4563
|
+
for (const cid of p.category_ids) {
|
|
4564
|
+
byCategory.set(cid, (byCategory.get(cid) ?? 0) + 1);
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4181
4567
|
return [
|
|
4182
4568
|
{
|
|
4183
4569
|
name: "Category",
|
|
@@ -4196,12 +4582,6 @@ function createCatalogueService(opts) {
|
|
|
4196
4582
|
listOnSale() {
|
|
4197
4583
|
return [];
|
|
4198
4584
|
},
|
|
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
4585
|
getProductDeals() {
|
|
4206
4586
|
return [];
|
|
4207
4587
|
},
|
|
@@ -4211,15 +4591,33 @@ function createCatalogueService(opts) {
|
|
|
4211
4591
|
getCollectionDeals() {
|
|
4212
4592
|
return [];
|
|
4213
4593
|
},
|
|
4594
|
+
validateDiscount(code) {
|
|
4595
|
+
if (code === "WELCOME10") {
|
|
4596
|
+
return { valid: true, discount_id: "disc_welcome10", discount_value: "10.00", discount_type: "percentage" };
|
|
4597
|
+
}
|
|
4598
|
+
return { valid: false, reason: "Code not recognized" };
|
|
4599
|
+
},
|
|
4214
4600
|
createQuote(businessId, body) {
|
|
4215
4601
|
const b = body;
|
|
4216
4602
|
const id = ids.quote();
|
|
4603
|
+
const productId = b.product_id ?? "";
|
|
4604
|
+
const product = resolveProduct(productId);
|
|
4605
|
+
const price = b.total_price ?? product?.base_price ?? "0.00";
|
|
4606
|
+
const currency = b.currency ?? product?.currency ?? "GHS";
|
|
4607
|
+
const priceInfo = defaultPriceInfo(price, currency);
|
|
4217
4608
|
const quote = {
|
|
4218
4609
|
id,
|
|
4610
|
+
quote_id: id,
|
|
4219
4611
|
business_id: businessId,
|
|
4220
|
-
product_id:
|
|
4612
|
+
product_id: product?.id ?? productId,
|
|
4221
4613
|
status: "fresh",
|
|
4222
|
-
total_price:
|
|
4614
|
+
total_price: price,
|
|
4615
|
+
currency,
|
|
4616
|
+
unit_price_info: priceInfo,
|
|
4617
|
+
final_price_info: priceInfo,
|
|
4618
|
+
quoted_total_price_info: priceInfo,
|
|
4619
|
+
components: [],
|
|
4620
|
+
breakdown: { base_price: price, components_total: "0.00", final_price: price },
|
|
4223
4621
|
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString(),
|
|
4224
4622
|
created_at: clock.iso()
|
|
4225
4623
|
};
|
|
@@ -4235,7 +4633,11 @@ function createCatalogueService(opts) {
|
|
|
4235
4633
|
refreshQuote(id) {
|
|
4236
4634
|
const q = registry.quotes.get(id);
|
|
4237
4635
|
if (!q) throw notFound(`Quote ${id} not found`);
|
|
4238
|
-
const refreshed = {
|
|
4636
|
+
const refreshed = {
|
|
4637
|
+
...q,
|
|
4638
|
+
status: "fresh",
|
|
4639
|
+
expires_at: new Date(clock.nowMs() + 10 * 60 * 1e3).toISOString()
|
|
4640
|
+
};
|
|
4239
4641
|
registry.quotes.put(id, refreshed);
|
|
4240
4642
|
return refreshed;
|
|
4241
4643
|
}
|
|
@@ -5260,67 +5662,82 @@ function createLiteService(opts) {
|
|
|
5260
5662
|
|
|
5261
5663
|
// src/mock/seeds/default.ts
|
|
5262
5664
|
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
|
-
|
|
5665
|
+
var BUSINESS_ID = "bus_default_akua_bakery";
|
|
5666
|
+
var img = (id) => `https://images.unsplash.com/photo-${id}?w=800&h=800&fit=crop&auto=format&q=80`;
|
|
5667
|
+
function makeProduct(input, categoryIds) {
|
|
5668
|
+
const catId = categoryIds.get(input.category);
|
|
5669
|
+
return {
|
|
5670
|
+
id: input.id,
|
|
5671
|
+
business_id: BUSINESS_ID,
|
|
5672
|
+
name: input.name,
|
|
5673
|
+
slug: input.slug,
|
|
5674
|
+
description: input.description,
|
|
5675
|
+
product_type: input.productType ?? "product",
|
|
5676
|
+
base_price: input.price,
|
|
5677
|
+
currency: "GHS",
|
|
5678
|
+
image: input.imageUrl,
|
|
5679
|
+
images: input.images ?? [input.imageUrl],
|
|
5680
|
+
is_available: true,
|
|
5681
|
+
category_ids: [catId],
|
|
5682
|
+
collection_ids: [],
|
|
5683
|
+
add_on_ids: [],
|
|
5684
|
+
variant_ids: [],
|
|
5685
|
+
tags: input.tags ?? [],
|
|
5686
|
+
calories: input.calories,
|
|
5687
|
+
allergies: input.allergies ?? [],
|
|
5688
|
+
ingredients: input.ingredients ?? [],
|
|
5689
|
+
pairings: input.pairings ?? [],
|
|
5690
|
+
is_signature: input.isSignature ?? false,
|
|
5691
|
+
is_new: input.isNew ?? false,
|
|
5692
|
+
created_at: ISO,
|
|
5693
|
+
updated_at: ISO
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5291
5696
|
function seedDefault(registry) {
|
|
5292
|
-
const businessId = "bus_default_akua_bakery";
|
|
5293
5697
|
const business = {
|
|
5294
|
-
id:
|
|
5698
|
+
id: BUSINESS_ID,
|
|
5295
5699
|
name: "Akua's Bakery",
|
|
5296
5700
|
handle: "akua",
|
|
5297
5701
|
business_type: "eatery",
|
|
5298
5702
|
email: "hello@akuasbakery.test",
|
|
5299
5703
|
default_currency: "GHS",
|
|
5300
|
-
default_phone: "+
|
|
5704
|
+
default_phone: "+233 244 000 000",
|
|
5301
5705
|
default_address: "12 Independence Ave, Accra",
|
|
5302
5706
|
default_offers_table_service: true,
|
|
5303
5707
|
default_accepts_online_orders: true,
|
|
5304
|
-
image: "
|
|
5708
|
+
image: img("1568254183919-78a4f43a2877"),
|
|
5305
5709
|
status: "active",
|
|
5306
5710
|
created_at: ISO,
|
|
5307
5711
|
updated_at: ISO,
|
|
5308
5712
|
created_by: "seed",
|
|
5309
|
-
preferences: {
|
|
5713
|
+
preferences: {
|
|
5714
|
+
tagline: "Fresh from the oven, every day",
|
|
5715
|
+
description: "Family-run bakery in East Legon. Sourdough, croissants, layered cakes \u2014 handcrafted since 2018, delivered across Accra.",
|
|
5716
|
+
instagram: "https://instagram.com/akuasbakery",
|
|
5717
|
+
theme: "warm",
|
|
5718
|
+
checkout: { allow_guest: true }
|
|
5719
|
+
},
|
|
5310
5720
|
is_online_only: false,
|
|
5311
5721
|
enabled_payment_types: ["mobile_money", "card", "cash"],
|
|
5312
5722
|
default_location_settings: {},
|
|
5313
5723
|
country_code: "GH",
|
|
5314
5724
|
timezone: "Africa/Accra"
|
|
5315
5725
|
};
|
|
5316
|
-
registry.businesses.put(
|
|
5317
|
-
const
|
|
5318
|
-
|
|
5726
|
+
registry.businesses.put(BUSINESS_ID, business);
|
|
5727
|
+
const categoryDefs = [
|
|
5728
|
+
{ slug: "breads", name: "Breads", description: "Naturally leavened sourdough, baguettes, focaccia." },
|
|
5729
|
+
{ slug: "pastries", name: "Pastries", description: "Hand-laminated croissants, danishes, brownies." },
|
|
5730
|
+
{ slug: "cakes", name: "Cakes", description: "Slices and made-to-order showstoppers." },
|
|
5731
|
+
{ slug: "drinks", name: "Drinks", description: "Locally roasted coffee, brewed teas, fresh juice." },
|
|
5732
|
+
{ slug: "boxes", name: "Boxes & Bundles", description: "Curated boxes for sharing." }
|
|
5733
|
+
];
|
|
5734
|
+
const catIds = /* @__PURE__ */ new Map();
|
|
5735
|
+
for (const c of categoryDefs) {
|
|
5319
5736
|
const id = `cat_${c.slug}`;
|
|
5320
|
-
|
|
5737
|
+
catIds.set(c.slug, id);
|
|
5321
5738
|
const cat = {
|
|
5322
5739
|
id,
|
|
5323
|
-
business_id:
|
|
5740
|
+
business_id: BUSINESS_ID,
|
|
5324
5741
|
name: c.name,
|
|
5325
5742
|
slug: c.slug,
|
|
5326
5743
|
description: c.description,
|
|
@@ -5332,99 +5749,354 @@ function seedDefault(registry) {
|
|
|
5332
5749
|
};
|
|
5333
5750
|
registry.categories.put(id, cat);
|
|
5334
5751
|
}
|
|
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
|
-
|
|
5752
|
+
const productInputs = [
|
|
5753
|
+
// ── Breads ──
|
|
5754
|
+
{
|
|
5755
|
+
id: "prod_sourdough-loaf",
|
|
5756
|
+
name: "Country Sourdough",
|
|
5757
|
+
slug: "sourdough-loaf",
|
|
5758
|
+
price: "25.00",
|
|
5759
|
+
description: "24-hour fermented country loaf with our heritage starter \u2014 open crumb, blistered crust, naturally tangy. Baked fresh every morning.",
|
|
5760
|
+
category: "breads",
|
|
5761
|
+
imageUrl: img("1509440159596-0249088772ff"),
|
|
5762
|
+
images: [img("1509440159596-0249088772ff"), img("1486887396153-fa416526c108")],
|
|
5763
|
+
tags: ["bread", "vegan", "signature"],
|
|
5764
|
+
isSignature: true,
|
|
5765
|
+
ingredients: ["Wheat flour", "Sourdough starter", "Sea salt", "Filtered water"],
|
|
5766
|
+
pairings: ["Olive oil", "Aged cheese", "Tomato jam"],
|
|
5767
|
+
allergies: ["wheat", "gluten"],
|
|
5768
|
+
calories: 240
|
|
5769
|
+
},
|
|
5770
|
+
{
|
|
5771
|
+
id: "prod_whole-wheat",
|
|
5772
|
+
name: "Whole Wheat Seed Loaf",
|
|
5773
|
+
slug: "whole-wheat",
|
|
5774
|
+
price: "20.00",
|
|
5775
|
+
description: "Hearty 100% whole wheat with sunflower, sesame, and pumpkin seeds. Slow-fermented for a soft crumb and nutty depth.",
|
|
5776
|
+
category: "breads",
|
|
5777
|
+
imageUrl: img("1486887396153-fa416526c108"),
|
|
5778
|
+
tags: ["bread", "vegan", "high-fibre"],
|
|
5779
|
+
ingredients: ["Whole wheat flour", "Sunflower seeds", "Sesame", "Pumpkin seeds"],
|
|
5780
|
+
allergies: ["wheat", "gluten", "sesame"],
|
|
5781
|
+
calories: 220
|
|
5782
|
+
},
|
|
5783
|
+
{
|
|
5784
|
+
id: "prod_baguette",
|
|
5785
|
+
name: "Classic Baguette",
|
|
5786
|
+
slug: "baguette",
|
|
5787
|
+
price: "15.00",
|
|
5788
|
+
description: "Crackling crust, airy crumb. Best eaten warm, within an hour of baking.",
|
|
5789
|
+
category: "breads",
|
|
5790
|
+
imageUrl: img("1568471173242-461f0a730452"),
|
|
5791
|
+
tags: ["bread", "vegan"],
|
|
5792
|
+
ingredients: ["Wheat flour", "Yeast", "Sea salt", "Water"],
|
|
5793
|
+
allergies: ["wheat", "gluten"],
|
|
5794
|
+
calories: 180
|
|
5795
|
+
},
|
|
5796
|
+
{
|
|
5797
|
+
id: "prod_focaccia",
|
|
5798
|
+
name: "Rosemary Focaccia",
|
|
5799
|
+
slug: "focaccia",
|
|
5800
|
+
price: "30.00",
|
|
5801
|
+
description: "High-hydration focaccia drenched in olive oil, scattered with sea salt and fresh rosemary. Sold whole or by the slab.",
|
|
5802
|
+
category: "breads",
|
|
5803
|
+
imageUrl: img("1571167530149-c1105da4c2c7"),
|
|
5804
|
+
tags: ["bread", "vegetarian"],
|
|
5805
|
+
ingredients: ["00 flour", "Olive oil", "Rosemary", "Maldon salt"],
|
|
5806
|
+
pairings: ["Burrata", "Prosciutto", "Caponata"],
|
|
5807
|
+
allergies: ["wheat", "gluten"],
|
|
5808
|
+
calories: 310
|
|
5809
|
+
},
|
|
5810
|
+
{
|
|
5811
|
+
id: "prod_banana-bread",
|
|
5812
|
+
name: "Brown Butter Banana Bread",
|
|
5813
|
+
slug: "banana-bread",
|
|
5814
|
+
price: "18.00",
|
|
5815
|
+
description: "Caramelised brown butter, three over-ripe bananas, walnuts, finished with a salted maple glaze.",
|
|
5816
|
+
category: "breads",
|
|
5817
|
+
imageUrl: img("1632931057819-4eefffa8e007"),
|
|
5818
|
+
tags: ["sweet", "comfort"],
|
|
5819
|
+
ingredients: ["Bananas", "Brown butter", "Walnuts", "Maple syrup"],
|
|
5820
|
+
allergies: ["wheat", "gluten", "dairy", "nuts", "eggs"],
|
|
5821
|
+
calories: 360,
|
|
5822
|
+
isNew: true
|
|
5823
|
+
},
|
|
5824
|
+
// ── Pastries ──
|
|
5825
|
+
{
|
|
5826
|
+
id: "prod_croissant",
|
|
5827
|
+
name: "Butter Croissant",
|
|
5828
|
+
slug: "croissant",
|
|
5829
|
+
price: "12.00",
|
|
5830
|
+
description: "27 layers of laminated dough wrapped around French butter. Crisp, shattering crust and a tender honeycomb interior.",
|
|
5831
|
+
category: "pastries",
|
|
5832
|
+
imageUrl: img("1555507036-ab1f4038808a"),
|
|
5833
|
+
images: [img("1555507036-ab1f4038808a"), img("1530610476181-d83430b64dcd")],
|
|
5834
|
+
tags: ["pastry", "vegetarian", "signature"],
|
|
5835
|
+
isSignature: true,
|
|
5836
|
+
ingredients: ["French butter", "Wheat flour", "Milk", "Eggs"],
|
|
5837
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5838
|
+
calories: 280
|
|
5839
|
+
},
|
|
5840
|
+
{
|
|
5841
|
+
id: "prod_pain-au-chocolat",
|
|
5842
|
+
name: "Pain au Chocolat",
|
|
5843
|
+
slug: "pain-au-chocolat",
|
|
5844
|
+
price: "14.00",
|
|
5845
|
+
description: "Our croissant dough wrapped around two batons of 70% Valrhona dark chocolate.",
|
|
5846
|
+
category: "pastries",
|
|
5847
|
+
imageUrl: img("1530610476181-d83430b64dcd"),
|
|
5848
|
+
tags: ["pastry", "chocolate"],
|
|
5849
|
+
ingredients: ["Croissant dough", "Valrhona chocolate"],
|
|
5850
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5851
|
+
calories: 320
|
|
5852
|
+
},
|
|
5853
|
+
{
|
|
5854
|
+
id: "prod_almond-danish",
|
|
5855
|
+
name: "Almond Danish",
|
|
5856
|
+
slug: "almond-danish",
|
|
5857
|
+
price: "16.00",
|
|
5858
|
+
description: "Flaky pastry filled with almond cream, dusted with toasted slivers and powdered sugar.",
|
|
5859
|
+
category: "pastries",
|
|
5860
|
+
imageUrl: img("1517433367423-c7e5b0f35086"),
|
|
5861
|
+
tags: ["pastry", "nuts"],
|
|
5862
|
+
ingredients: ["Pastry dough", "Almond cream", "Toasted almonds"],
|
|
5863
|
+
allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
|
|
5864
|
+
calories: 340
|
|
5865
|
+
},
|
|
5866
|
+
{
|
|
5867
|
+
id: "prod_cinnamon-roll",
|
|
5868
|
+
name: "Cinnamon Roll",
|
|
5869
|
+
slug: "cinnamon-roll",
|
|
5870
|
+
price: "13.00",
|
|
5871
|
+
description: "Soft enriched dough swirled with butter and Ceylon cinnamon, finished with cream-cheese glaze.",
|
|
5872
|
+
category: "pastries",
|
|
5873
|
+
imageUrl: img("1607920591413-4ec007e70023"),
|
|
5874
|
+
tags: ["pastry", "comfort"],
|
|
5875
|
+
ingredients: ["Enriched dough", "Cinnamon", "Brown sugar", "Cream cheese"],
|
|
5876
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5877
|
+
calories: 380
|
|
5878
|
+
},
|
|
5879
|
+
{
|
|
5880
|
+
id: "prod_brownie",
|
|
5881
|
+
name: "Fudge Brownie",
|
|
5882
|
+
slug: "brownie",
|
|
5883
|
+
price: "12.00",
|
|
5884
|
+
description: "Dark, dense, fudgy brownie with a glossy shell. Chocolate four ways. Best slightly warm.",
|
|
5885
|
+
category: "pastries",
|
|
5886
|
+
imageUrl: img("1606313564200-e75d5e30476c"),
|
|
5887
|
+
tags: ["chocolate", "vegetarian"],
|
|
5888
|
+
ingredients: ["Dark chocolate", "Cocoa", "Brown butter", "Eggs"],
|
|
5889
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5890
|
+
calories: 420
|
|
5891
|
+
},
|
|
5892
|
+
{
|
|
5893
|
+
id: "prod_macaron-box",
|
|
5894
|
+
name: "Macaron Box (6)",
|
|
5895
|
+
slug: "macaron-box",
|
|
5896
|
+
price: "60.00",
|
|
5897
|
+
description: "Six French macarons \u2014 pistachio, raspberry, salted caramel, espresso, vanilla bean, and dark chocolate ganache.",
|
|
5898
|
+
category: "pastries",
|
|
5899
|
+
imageUrl: img("1558326567-98ae2405596b"),
|
|
5900
|
+
tags: ["pastry", "gift", "premium"],
|
|
5901
|
+
ingredients: ["Almond flour", "Egg whites", "Sugar", "Various fillings"],
|
|
5902
|
+
allergies: ["nuts", "eggs", "dairy"],
|
|
5903
|
+
calories: 540
|
|
5904
|
+
},
|
|
5905
|
+
// ── Cakes ──
|
|
5906
|
+
{
|
|
5907
|
+
id: "prod_chocolate-cake",
|
|
5908
|
+
name: "Dark Chocolate Layer Cake",
|
|
5909
|
+
slug: "chocolate-cake",
|
|
5910
|
+
price: "22.00",
|
|
5911
|
+
description: "Three layers of dark chocolate sponge with chocolate ganache and cr\xE8me p\xE2tissi\xE8re, finished with a mirror glaze. (Slice)",
|
|
5912
|
+
category: "cakes",
|
|
5913
|
+
imageUrl: img("1565958011703-44f9829ba187"),
|
|
5914
|
+
tags: ["cake", "chocolate", "signature"],
|
|
5915
|
+
isSignature: true,
|
|
5916
|
+
ingredients: ["Dark chocolate", "Cr\xE8me p\xE2tissi\xE8re", "Cocoa", "Eggs"],
|
|
5917
|
+
pairings: ["Espresso", "Cabernet"],
|
|
5918
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5919
|
+
calories: 480
|
|
5920
|
+
},
|
|
5921
|
+
{
|
|
5922
|
+
id: "prod_red-velvet",
|
|
5923
|
+
name: "Red Velvet (Slice)",
|
|
5924
|
+
slug: "red-velvet",
|
|
5925
|
+
price: "22.00",
|
|
5926
|
+
description: "Classic red velvet with cream cheese frosting and white chocolate shavings.",
|
|
5927
|
+
category: "cakes",
|
|
5928
|
+
imageUrl: img("1586788680434-30d324b2d46f"),
|
|
5929
|
+
tags: ["cake"],
|
|
5930
|
+
ingredients: ["Buttermilk", "Cocoa", "Cream cheese", "Beetroot"],
|
|
5931
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5932
|
+
calories: 460
|
|
5933
|
+
},
|
|
5934
|
+
{
|
|
5935
|
+
id: "prod_carrot-cake",
|
|
5936
|
+
name: "Carrot Cake (Slice)",
|
|
5937
|
+
slug: "carrot-cake",
|
|
5938
|
+
price: "20.00",
|
|
5939
|
+
description: "Spiced carrot cake with toasted walnuts, golden raisins, and cream-cheese frosting.",
|
|
5940
|
+
category: "cakes",
|
|
5941
|
+
imageUrl: img("1568827999250-3f6afff96e66"),
|
|
5942
|
+
tags: ["cake", "spiced"],
|
|
5943
|
+
ingredients: ["Carrot", "Walnuts", "Cinnamon", "Cream cheese"],
|
|
5944
|
+
allergies: ["wheat", "gluten", "dairy", "eggs", "nuts"],
|
|
5945
|
+
calories: 420
|
|
5946
|
+
},
|
|
5947
|
+
{
|
|
5948
|
+
id: "prod_cheesecake",
|
|
5949
|
+
name: "New York Cheesecake (Slice)",
|
|
5950
|
+
slug: "cheesecake",
|
|
5951
|
+
price: "24.00",
|
|
5952
|
+
description: "Dense, creamy New York cheesecake on a butter graham crust. Topped with seasonal berry compote.",
|
|
5953
|
+
category: "cakes",
|
|
5954
|
+
imageUrl: img("1567306226416-28f0efdc88ce"),
|
|
5955
|
+
tags: ["cake", "premium"],
|
|
5956
|
+
ingredients: ["Cream cheese", "Sour cream", "Graham crust", "Vanilla"],
|
|
5957
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5958
|
+
calories: 520
|
|
5959
|
+
},
|
|
5960
|
+
{
|
|
5961
|
+
id: "prod_birthday-cake",
|
|
5962
|
+
name: "Custom Celebration Cake",
|
|
5963
|
+
slug: "birthday-cake",
|
|
5964
|
+
price: "250.00",
|
|
5965
|
+
description: "Made-to-order three-tier cake. Choose size, flavour, and frosting. 48-hour notice required for personalisation.",
|
|
5966
|
+
category: "cakes",
|
|
5967
|
+
imageUrl: img("1535254973040-607b474cb50d"),
|
|
5968
|
+
images: [img("1535254973040-607b474cb50d"), img("1565958011703-44f9829ba187")],
|
|
5969
|
+
tags: ["cake", "made-to-order", "premium"],
|
|
5970
|
+
ingredients: ["Sponge cake", "Buttercream", "Custom decoration"],
|
|
5971
|
+
allergies: ["wheat", "gluten", "dairy", "eggs"],
|
|
5972
|
+
productType: "product"
|
|
5973
|
+
},
|
|
5974
|
+
// ── Drinks ──
|
|
5975
|
+
{
|
|
5976
|
+
id: "prod_coffee",
|
|
5977
|
+
name: "Single-Origin Coffee",
|
|
5978
|
+
slug: "coffee",
|
|
5979
|
+
price: "10.00",
|
|
5980
|
+
description: "Locally roasted Arabica from Volta Region. Notes of cocoa, dark fruit, and brown sugar.",
|
|
5981
|
+
category: "drinks",
|
|
5982
|
+
imageUrl: img("1495474472287-4d71bcdd2085"),
|
|
5983
|
+
tags: ["drink", "coffee", "vegan"],
|
|
5984
|
+
ingredients: ["Single-origin Arabica beans"],
|
|
5985
|
+
pairings: ["Croissant", "Brownie"],
|
|
5986
|
+
calories: 5
|
|
5987
|
+
},
|
|
5988
|
+
{
|
|
5989
|
+
id: "prod_tea",
|
|
5990
|
+
name: "Loose-Leaf Tea",
|
|
5991
|
+
slug: "tea",
|
|
5992
|
+
price: "8.00",
|
|
5993
|
+
description: "Choice of English breakfast, Earl Grey, green sencha, or chamomile.",
|
|
5994
|
+
category: "drinks",
|
|
5995
|
+
imageUrl: img("1576092768241-dec231879fc3"),
|
|
5996
|
+
tags: ["drink", "tea", "vegan"],
|
|
5997
|
+
calories: 0
|
|
5998
|
+
},
|
|
5999
|
+
{
|
|
6000
|
+
id: "prod_sobolo",
|
|
6001
|
+
name: "Hibiscus (Sobolo)",
|
|
6002
|
+
slug: "sobolo",
|
|
6003
|
+
price: "12.00",
|
|
6004
|
+
description: "Iced hibiscus tea with ginger, pineapple peel, and a hint of clove. Tart, refreshing, lightly sweet.",
|
|
6005
|
+
category: "drinks",
|
|
6006
|
+
imageUrl: img("1556679343-c7306c1976bc"),
|
|
6007
|
+
tags: ["drink", "vegan", "ghana"],
|
|
6008
|
+
isSignature: true,
|
|
6009
|
+
ingredients: ["Hibiscus", "Ginger", "Pineapple", "Clove"],
|
|
6010
|
+
calories: 60
|
|
6011
|
+
},
|
|
6012
|
+
{
|
|
6013
|
+
id: "prod_orange-juice",
|
|
6014
|
+
name: "Cold-Pressed Orange Juice",
|
|
6015
|
+
slug: "orange-juice",
|
|
6016
|
+
price: "15.00",
|
|
6017
|
+
description: "Hand-pressed at sunrise from Ghanaian Valencia oranges. No sugar, no concentrate.",
|
|
6018
|
+
category: "drinks",
|
|
6019
|
+
imageUrl: img("1622597467836-f3285f2131b8"),
|
|
6020
|
+
tags: ["drink", "vegan"],
|
|
6021
|
+
calories: 110
|
|
6022
|
+
},
|
|
6023
|
+
// ── Boxes (anchor products for bundle/composite) ──
|
|
6024
|
+
{
|
|
6025
|
+
id: "prod_breakfast-box",
|
|
6026
|
+
name: "Breakfast Box",
|
|
6027
|
+
slug: "breakfast-box",
|
|
6028
|
+
price: "30.00",
|
|
6029
|
+
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.",
|
|
6030
|
+
category: "boxes",
|
|
6031
|
+
imageUrl: img("1533089860892-a7c6f0a88666"),
|
|
6032
|
+
images: [img("1533089860892-a7c6f0a88666"), img("1495474472287-4d71bcdd2085")],
|
|
6033
|
+
tags: ["bundle", "breakfast"],
|
|
6034
|
+
productType: "bundle"
|
|
6035
|
+
},
|
|
6036
|
+
{
|
|
6037
|
+
id: "prod_pastry-box",
|
|
6038
|
+
name: "Build-Your-Own Pastry Box",
|
|
6039
|
+
slug: "pastry-box",
|
|
6040
|
+
price: "60.00",
|
|
6041
|
+
description: "Pick 6 to 12 of our pastries \u2014 mix and match. Freshly boxed for sharing.",
|
|
6042
|
+
category: "boxes",
|
|
6043
|
+
imageUrl: img("1486427944299-d1955d23e34d"),
|
|
6044
|
+
tags: ["composite", "build-your-own", "gift"],
|
|
6045
|
+
productType: "composite"
|
|
6046
|
+
}
|
|
6047
|
+
];
|
|
6048
|
+
for (const input of productInputs) {
|
|
6049
|
+
const product = makeProduct(input, catIds);
|
|
6050
|
+
registry.products.put(product.id, product);
|
|
6051
|
+
const cat = registry.categories.get(catIds.get(input.category));
|
|
6052
|
+
if (cat) {
|
|
6053
|
+
cat.product_ids.push(product.id);
|
|
6054
|
+
registry.categories.put(cat.id, cat);
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
const featuredIds = [
|
|
6058
|
+
"prod_sourdough-loaf",
|
|
6059
|
+
"prod_croissant",
|
|
6060
|
+
"prod_chocolate-cake",
|
|
6061
|
+
"prod_sobolo",
|
|
6062
|
+
"prod_pastry-box"
|
|
6063
|
+
];
|
|
6064
|
+
const featured = {
|
|
5365
6065
|
id: "col_featured",
|
|
5366
|
-
business_id:
|
|
5367
|
-
name: "Featured",
|
|
6066
|
+
business_id: BUSINESS_ID,
|
|
6067
|
+
name: "Signature & Featured",
|
|
5368
6068
|
slug: "featured",
|
|
5369
|
-
description: "Our most
|
|
5370
|
-
product_ids:
|
|
6069
|
+
description: "Our most-loved bakes and a few seasonal stars.",
|
|
6070
|
+
product_ids: featuredIds,
|
|
5371
6071
|
is_active: true,
|
|
5372
6072
|
created_at: ISO,
|
|
5373
6073
|
updated_at: ISO
|
|
5374
6074
|
};
|
|
5375
|
-
registry.collections.put(
|
|
5376
|
-
for (const pid of
|
|
6075
|
+
registry.collections.put(featured.id, featured);
|
|
6076
|
+
for (const pid of featuredIds) {
|
|
5377
6077
|
const p = registry.products.get(pid);
|
|
5378
6078
|
if (p) {
|
|
5379
|
-
p.collection_ids.push(
|
|
6079
|
+
p.collection_ids.push(featured.id);
|
|
5380
6080
|
registry.products.put(pid, p);
|
|
5381
6081
|
}
|
|
5382
6082
|
}
|
|
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
6083
|
const sizeAxis = {
|
|
5416
|
-
id:
|
|
5417
|
-
business_id:
|
|
6084
|
+
id: "axis_cake_size",
|
|
6085
|
+
business_id: BUSINESS_ID,
|
|
5418
6086
|
product_id: "prod_birthday-cake",
|
|
5419
6087
|
name: "Size",
|
|
5420
6088
|
display_order: 0,
|
|
5421
6089
|
affects_recipe: false,
|
|
5422
|
-
values:
|
|
6090
|
+
values: [
|
|
6091
|
+
{ id: "axv_size_small", name: 'Small (8")', display: 0 },
|
|
6092
|
+
{ id: "axv_size_medium", name: 'Medium (10")', display: 1 },
|
|
6093
|
+
{ id: "axv_size_large", name: 'Large (12")', display: 2 }
|
|
6094
|
+
].map((v) => ({
|
|
5423
6095
|
id: v.id,
|
|
5424
|
-
business_id:
|
|
5425
|
-
axis_id:
|
|
6096
|
+
business_id: BUSINESS_ID,
|
|
6097
|
+
axis_id: "axis_cake_size",
|
|
5426
6098
|
name: v.name,
|
|
5427
|
-
display_order:
|
|
6099
|
+
display_order: v.display,
|
|
5428
6100
|
created_at: ISO,
|
|
5429
6101
|
updated_at: ISO
|
|
5430
6102
|
})),
|
|
@@ -5432,18 +6104,22 @@ function seedDefault(registry) {
|
|
|
5432
6104
|
updated_at: ISO
|
|
5433
6105
|
};
|
|
5434
6106
|
const flavourAxis = {
|
|
5435
|
-
id:
|
|
5436
|
-
business_id:
|
|
6107
|
+
id: "axis_cake_flavour",
|
|
6108
|
+
business_id: BUSINESS_ID,
|
|
5437
6109
|
product_id: "prod_birthday-cake",
|
|
5438
6110
|
name: "Flavour",
|
|
5439
6111
|
display_order: 1,
|
|
5440
6112
|
affects_recipe: true,
|
|
5441
|
-
values:
|
|
6113
|
+
values: [
|
|
6114
|
+
{ id: "axv_flavour_chocolate", name: "Dark Chocolate", display: 0 },
|
|
6115
|
+
{ id: "axv_flavour_vanilla", name: "Vanilla Bean", display: 1 },
|
|
6116
|
+
{ id: "axv_flavour_red_velvet", name: "Red Velvet", display: 2 }
|
|
6117
|
+
].map((v) => ({
|
|
5442
6118
|
id: v.id,
|
|
5443
|
-
business_id:
|
|
5444
|
-
axis_id:
|
|
6119
|
+
business_id: BUSINESS_ID,
|
|
6120
|
+
axis_id: "axis_cake_flavour",
|
|
5445
6121
|
name: v.name,
|
|
5446
|
-
display_order:
|
|
6122
|
+
display_order: v.display,
|
|
5447
6123
|
created_at: ISO,
|
|
5448
6124
|
updated_at: ISO
|
|
5449
6125
|
})),
|
|
@@ -5452,40 +6128,42 @@ function seedDefault(registry) {
|
|
|
5452
6128
|
};
|
|
5453
6129
|
registry.variantAxes.put(sizeAxis.id, sizeAxis);
|
|
5454
6130
|
registry.variantAxes.put(flavourAxis.id, flavourAxis);
|
|
5455
|
-
const
|
|
5456
|
-
const
|
|
5457
|
-
let
|
|
5458
|
-
for (
|
|
5459
|
-
for (
|
|
5460
|
-
const
|
|
6131
|
+
const sizeAdj = { small: "250.00", medium: "350.00", large: "450.00" };
|
|
6132
|
+
const cakeVariantIds = [];
|
|
6133
|
+
let firstVariant = true;
|
|
6134
|
+
for (const sz of sizeAxis.values) {
|
|
6135
|
+
for (const fl of flavourAxis.values) {
|
|
6136
|
+
const sizeKey = sz.id.replace("axv_size_", "");
|
|
6137
|
+
const flavourKey = fl.id.replace("axv_flavour_", "");
|
|
6138
|
+
const id = `var_size_${sizeKey}_flavour_${flavourKey}`;
|
|
5461
6139
|
const variant = {
|
|
5462
6140
|
id,
|
|
5463
6141
|
product_id: "prod_birthday-cake",
|
|
5464
|
-
business_id:
|
|
5465
|
-
name: `${
|
|
5466
|
-
sku: `BIRTHDAY-${
|
|
5467
|
-
price_adjustment:
|
|
6142
|
+
business_id: BUSINESS_ID,
|
|
6143
|
+
name: `${sz.name} \u2022 ${fl.name}`,
|
|
6144
|
+
sku: `BIRTHDAY-${sizeKey.toUpperCase()}-${flavourKey.toUpperCase()}`,
|
|
6145
|
+
price_adjustment: sizeAdj[sizeKey] ?? "0.00",
|
|
5468
6146
|
component_multiplier: "1.00",
|
|
5469
|
-
is_default:
|
|
6147
|
+
is_default: firstVariant,
|
|
5470
6148
|
is_active: true,
|
|
5471
|
-
axis_value_ids: [
|
|
6149
|
+
axis_value_ids: [sz.id, fl.id],
|
|
5472
6150
|
created_at: ISO,
|
|
5473
6151
|
updated_at: ISO
|
|
5474
6152
|
};
|
|
5475
6153
|
registry.variants.put(id, variant);
|
|
5476
|
-
|
|
5477
|
-
|
|
6154
|
+
cakeVariantIds.push(id);
|
|
6155
|
+
firstVariant = false;
|
|
5478
6156
|
}
|
|
5479
6157
|
}
|
|
5480
|
-
const
|
|
5481
|
-
if (
|
|
5482
|
-
|
|
5483
|
-
registry.products.put(
|
|
6158
|
+
const birthdayProduct = registry.products.get("prod_birthday-cake");
|
|
6159
|
+
if (birthdayProduct) {
|
|
6160
|
+
birthdayProduct.variant_ids = cakeVariantIds;
|
|
6161
|
+
registry.products.put(birthdayProduct.id, birthdayProduct);
|
|
5484
6162
|
}
|
|
5485
6163
|
const toppingsAddOn = {
|
|
5486
6164
|
id: "addon_toppings",
|
|
5487
|
-
business_id:
|
|
5488
|
-
name: "
|
|
6165
|
+
business_id: BUSINESS_ID,
|
|
6166
|
+
name: "Cake toppings",
|
|
5489
6167
|
is_multiple_allowed: true,
|
|
5490
6168
|
is_required: false,
|
|
5491
6169
|
is_mutually_exclusive: false,
|
|
@@ -5494,15 +6172,14 @@ function seedDefault(registry) {
|
|
|
5494
6172
|
created_at: ISO,
|
|
5495
6173
|
updated_at: ISO,
|
|
5496
6174
|
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:
|
|
6175
|
+
{ 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 },
|
|
6176
|
+
{ 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 },
|
|
6177
|
+
{ 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
6178
|
]
|
|
5501
6179
|
};
|
|
5502
|
-
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
5503
6180
|
const milkAddOn = {
|
|
5504
6181
|
id: "addon_milk",
|
|
5505
|
-
business_id:
|
|
6182
|
+
business_id: BUSINESS_ID,
|
|
5506
6183
|
name: "Milk",
|
|
5507
6184
|
is_multiple_allowed: false,
|
|
5508
6185
|
is_required: false,
|
|
@@ -5512,17 +6189,17 @@ function seedDefault(registry) {
|
|
|
5512
6189
|
created_at: ISO,
|
|
5513
6190
|
updated_at: ISO,
|
|
5514
6191
|
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:
|
|
6192
|
+
{ 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 },
|
|
6193
|
+
{ 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 },
|
|
6194
|
+
{ 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
6195
|
]
|
|
5519
6196
|
};
|
|
6197
|
+
registry.addOns.put(toppingsAddOn.id, toppingsAddOn);
|
|
5520
6198
|
registry.addOns.put(milkAddOn.id, milkAddOn);
|
|
5521
6199
|
const cakeProductIds = ["prod_chocolate-cake", "prod_red-velvet", "prod_carrot-cake", "prod_cheesecake", "prod_birthday-cake"];
|
|
5522
6200
|
const drinkProductIds = ["prod_coffee", "prod_tea"];
|
|
5523
6201
|
for (const pid of cakeProductIds) {
|
|
5524
|
-
|
|
5525
|
-
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, link);
|
|
6202
|
+
registry.productAddOns.put(`${pid}:${toppingsAddOn.id}`, { product_id: pid, add_on_id: toppingsAddOn.id });
|
|
5526
6203
|
const p = registry.products.get(pid);
|
|
5527
6204
|
if (p) {
|
|
5528
6205
|
p.add_on_ids.push(toppingsAddOn.id);
|
|
@@ -5530,8 +6207,7 @@ function seedDefault(registry) {
|
|
|
5530
6207
|
}
|
|
5531
6208
|
}
|
|
5532
6209
|
for (const pid of drinkProductIds) {
|
|
5533
|
-
|
|
5534
|
-
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, link);
|
|
6210
|
+
registry.productAddOns.put(`${pid}:${milkAddOn.id}`, { product_id: pid, add_on_id: milkAddOn.id });
|
|
5535
6211
|
const p = registry.products.get(pid);
|
|
5536
6212
|
if (p) {
|
|
5537
6213
|
p.add_on_ids.push(milkAddOn.id);
|
|
@@ -5540,7 +6216,7 @@ function seedDefault(registry) {
|
|
|
5540
6216
|
}
|
|
5541
6217
|
const breakfastBundle = {
|
|
5542
6218
|
id: "bnd_breakfast_box",
|
|
5543
|
-
business_id:
|
|
6219
|
+
business_id: BUSINESS_ID,
|
|
5544
6220
|
product_id: "prod_breakfast-box",
|
|
5545
6221
|
name: "Breakfast Box",
|
|
5546
6222
|
slug: "breakfast-box",
|
|
@@ -5552,29 +6228,9 @@ function seedDefault(registry) {
|
|
|
5552
6228
|
updated_at: ISO
|
|
5553
6229
|
};
|
|
5554
6230
|
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
6231
|
const pastryComposite = {
|
|
5576
6232
|
id: "comp_pastry_box",
|
|
5577
|
-
business_id:
|
|
6233
|
+
business_id: BUSINESS_ID,
|
|
5578
6234
|
product_id: "prod_pastry-box",
|
|
5579
6235
|
base_price: "60.00",
|
|
5580
6236
|
pricing_mode: "additive",
|
|
@@ -5586,43 +6242,51 @@ function seedDefault(registry) {
|
|
|
5586
6242
|
name: "Choose your pastries",
|
|
5587
6243
|
min: 6,
|
|
5588
6244
|
max: 12,
|
|
5589
|
-
component_ids: [
|
|
6245
|
+
component_ids: [
|
|
6246
|
+
"prod_croissant",
|
|
6247
|
+
"prod_pain-au-chocolat",
|
|
6248
|
+
"prod_almond-danish",
|
|
6249
|
+
"prod_cinnamon-roll",
|
|
6250
|
+
"prod_brownie",
|
|
6251
|
+
"prod_banana-bread",
|
|
6252
|
+
"prod_macaron-box"
|
|
6253
|
+
]
|
|
5590
6254
|
}
|
|
5591
6255
|
],
|
|
5592
6256
|
created_at: ISO,
|
|
5593
6257
|
updated_at: ISO
|
|
5594
6258
|
};
|
|
5595
6259
|
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
|
|
6260
|
+
const tasting = {
|
|
6261
|
+
id: "svc_tasting",
|
|
6262
|
+
business_id: BUSINESS_ID,
|
|
6263
|
+
name: "Bakery Tasting Session",
|
|
6264
|
+
description: "Sample our pastry menu with our head baker.",
|
|
6265
|
+
duration_minutes: 60,
|
|
6266
|
+
price: "150.00",
|
|
6267
|
+
is_available: true
|
|
5614
6268
|
};
|
|
5615
|
-
|
|
6269
|
+
const consult = {
|
|
6270
|
+
id: "svc_custom_cake",
|
|
6271
|
+
business_id: BUSINESS_ID,
|
|
6272
|
+
name: "Custom Cake Consultation",
|
|
6273
|
+
description: "Design a custom cake for your event.",
|
|
6274
|
+
duration_minutes: 30,
|
|
6275
|
+
price: "0.00",
|
|
6276
|
+
is_available: true
|
|
6277
|
+
};
|
|
6278
|
+
registry.services.put(tasting.id, tasting);
|
|
6279
|
+
registry.services.put(consult.id, consult);
|
|
5616
6280
|
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:
|
|
6281
|
+
{ 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 },
|
|
6282
|
+
{ 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 },
|
|
6283
|
+
{ 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 },
|
|
6284
|
+
{ 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
6285
|
];
|
|
5622
6286
|
for (const t of tags) registry.tags.put(t.id, t);
|
|
5623
6287
|
const baked = {
|
|
5624
6288
|
id: "tax_baked_goods",
|
|
5625
|
-
business_id:
|
|
6289
|
+
business_id: BUSINESS_ID,
|
|
5626
6290
|
name: "Baked Goods",
|
|
5627
6291
|
slug: "baked-goods",
|
|
5628
6292
|
path: ["Food", "Baked Goods"],
|
|
@@ -5632,7 +6296,7 @@ function seedDefault(registry) {
|
|
|
5632
6296
|
};
|
|
5633
6297
|
const beverages = {
|
|
5634
6298
|
id: "tax_beverages",
|
|
5635
|
-
business_id:
|
|
6299
|
+
business_id: BUSINESS_ID,
|
|
5636
6300
|
name: "Beverages",
|
|
5637
6301
|
slug: "beverages",
|
|
5638
6302
|
path: ["Food", "Beverages"],
|
|
@@ -5645,13 +6309,13 @@ function seedDefault(registry) {
|
|
|
5645
6309
|
const attrs = [
|
|
5646
6310
|
{
|
|
5647
6311
|
id: "attr_allergens",
|
|
5648
|
-
business_id:
|
|
6312
|
+
business_id: BUSINESS_ID,
|
|
5649
6313
|
namespace: "core",
|
|
5650
6314
|
name: "Allergens",
|
|
5651
6315
|
slug: "allergens",
|
|
5652
6316
|
description: "Common allergens present in this item.",
|
|
5653
6317
|
attribute_type: "multi_select",
|
|
5654
|
-
options: ["wheat", "eggs", "dairy", "nuts", "soy"],
|
|
6318
|
+
options: ["wheat", "gluten", "eggs", "dairy", "nuts", "soy", "sesame"],
|
|
5655
6319
|
is_required: false,
|
|
5656
6320
|
is_filterable: true,
|
|
5657
6321
|
visibility: "public",
|
|
@@ -5662,7 +6326,7 @@ function seedDefault(registry) {
|
|
|
5662
6326
|
},
|
|
5663
6327
|
{
|
|
5664
6328
|
id: "attr_shelf_life",
|
|
5665
|
-
business_id:
|
|
6329
|
+
business_id: BUSINESS_ID,
|
|
5666
6330
|
namespace: "core",
|
|
5667
6331
|
name: "Shelf life",
|
|
5668
6332
|
slug: "shelf-life",
|
|
@@ -5677,7 +6341,7 @@ function seedDefault(registry) {
|
|
|
5677
6341
|
},
|
|
5678
6342
|
{
|
|
5679
6343
|
id: "attr_caffeine",
|
|
5680
|
-
business_id:
|
|
6344
|
+
business_id: BUSINESS_ID,
|
|
5681
6345
|
namespace: "core",
|
|
5682
6346
|
name: "Caffeine",
|
|
5683
6347
|
slug: "caffeine",
|
|
@@ -5692,7 +6356,7 @@ function seedDefault(registry) {
|
|
|
5692
6356
|
},
|
|
5693
6357
|
{
|
|
5694
6358
|
id: "attr_seasonal",
|
|
5695
|
-
business_id:
|
|
6359
|
+
business_id: BUSINESS_ID,
|
|
5696
6360
|
namespace: "core",
|
|
5697
6361
|
name: "Seasonal",
|
|
5698
6362
|
slug: "seasonal",
|
|
@@ -5710,10 +6374,10 @@ function seedDefault(registry) {
|
|
|
5710
6374
|
const articles = [
|
|
5711
6375
|
{
|
|
5712
6376
|
id: "kb_delivery",
|
|
5713
|
-
business_id:
|
|
5714
|
-
title: "Delivery &
|
|
6377
|
+
business_id: BUSINESS_ID,
|
|
6378
|
+
title: "Delivery & pickup",
|
|
5715
6379
|
slug: "delivery-pickup",
|
|
5716
|
-
content: "Same-day delivery within Accra for orders placed before 2pm. Pickup available 8am\u20138pm.",
|
|
6380
|
+
content: "Same-day delivery within Greater Accra for orders placed before 2pm. Pickup available 8am\u20138pm at our East Legon location.",
|
|
5717
6381
|
category: "logistics",
|
|
5718
6382
|
tags: ["delivery", "pickup"],
|
|
5719
6383
|
created_at: ISO,
|
|
@@ -5721,10 +6385,10 @@ function seedDefault(registry) {
|
|
|
5721
6385
|
},
|
|
5722
6386
|
{
|
|
5723
6387
|
id: "kb_custom_cakes",
|
|
5724
|
-
business_id:
|
|
5725
|
-
title: "
|
|
6388
|
+
business_id: BUSINESS_ID,
|
|
6389
|
+
title: "Custom cakes & celebrations",
|
|
5726
6390
|
slug: "custom-cakes",
|
|
5727
|
-
content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page.",
|
|
6391
|
+
content: "Custom cakes require 48 hours notice. Book a consultation through the bookings page to walk through flavours, sizes, and decoration.",
|
|
5728
6392
|
category: "products",
|
|
5729
6393
|
tags: ["cakes", "custom"],
|
|
5730
6394
|
created_at: ISO,
|
|
@@ -5732,10 +6396,10 @@ function seedDefault(registry) {
|
|
|
5732
6396
|
},
|
|
5733
6397
|
{
|
|
5734
6398
|
id: "kb_allergens",
|
|
5735
|
-
business_id:
|
|
5736
|
-
title: "
|
|
6399
|
+
business_id: BUSINESS_ID,
|
|
6400
|
+
title: "Allergen information",
|
|
5737
6401
|
slug: "allergens",
|
|
5738
|
-
content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible.",
|
|
6402
|
+
content: "Our kitchen handles wheat, dairy, eggs, and nuts. Cross-contamination is possible \u2014 let us know about severe allergies before ordering.",
|
|
5739
6403
|
category: "products",
|
|
5740
6404
|
tags: ["allergens", "safety"],
|
|
5741
6405
|
created_at: ISO,
|
|
@@ -5743,7 +6407,7 @@ function seedDefault(registry) {
|
|
|
5743
6407
|
}
|
|
5744
6408
|
];
|
|
5745
6409
|
for (const a of articles) registry.knowledgeArticles.put(a.id, a);
|
|
5746
|
-
return { businessId };
|
|
6410
|
+
return { businessId: BUSINESS_ID };
|
|
5747
6411
|
}
|
|
5748
6412
|
|
|
5749
6413
|
// src/mock/seeds/empty.ts
|
|
@@ -7846,195 +8510,47 @@ function createDeps(options = {}) {
|
|
|
7846
8510
|
}
|
|
7847
8511
|
|
|
7848
8512
|
// src/mock/server.ts
|
|
8513
|
+
var PACKAGE_NAME = "@cimplify/sdk/mock";
|
|
8514
|
+
function corsMiddleware(allow) {
|
|
8515
|
+
return async (c, next) => {
|
|
8516
|
+
const origin = c.req.header("origin") ?? "";
|
|
8517
|
+
const allowOrigin = allow === "*" ? "*" : allow.includes(origin) ? origin : allow[0] ?? "*";
|
|
8518
|
+
const headers = {
|
|
8519
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
8520
|
+
"Access-Control-Allow-Credentials": "true",
|
|
8521
|
+
"Access-Control-Allow-Headers": c.req.header("access-control-request-headers") ?? "content-type,authorization,x-session-token,idempotency-key",
|
|
8522
|
+
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
8523
|
+
"Access-Control-Expose-Headers": "x-session-token",
|
|
8524
|
+
"Access-Control-Max-Age": "86400",
|
|
8525
|
+
Vary: "Origin"
|
|
8526
|
+
};
|
|
8527
|
+
if (c.req.method === "OPTIONS") {
|
|
8528
|
+
return new Response(null, { status: 204, headers });
|
|
8529
|
+
}
|
|
8530
|
+
for (const [k, v] of Object.entries(headers)) c.header(k, v);
|
|
8531
|
+
await next();
|
|
8532
|
+
};
|
|
8533
|
+
}
|
|
7849
8534
|
function createMockApp(options = {}) {
|
|
7850
8535
|
const deps = createDeps(options);
|
|
7851
8536
|
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
|
-
}
|
|
8537
|
+
const cors = options.cors ?? "*";
|
|
8538
|
+
if (cors) app.use("*", corsMiddleware(cors));
|
|
7873
8539
|
app.get(
|
|
7874
8540
|
"/",
|
|
7875
8541
|
(c) => c.json({
|
|
7876
|
-
name:
|
|
7877
|
-
version: "0.36.0",
|
|
8542
|
+
name: PACKAGE_NAME,
|
|
7878
8543
|
docs: "https://github.com/cimplify/cimplify",
|
|
7879
8544
|
api: "/api/v1",
|
|
7880
8545
|
admin: "/_mock",
|
|
7881
8546
|
elements: "/elements",
|
|
7882
|
-
|
|
8547
|
+
images: "/img/<slug>",
|
|
8548
|
+
seed_business_id: deps.defaultBusinessId,
|
|
8549
|
+
time: deps.clock.iso()
|
|
7883
8550
|
})
|
|
7884
8551
|
);
|
|
7885
8552
|
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
|
-
});
|
|
8553
|
+
mountImageRoutes(app);
|
|
8038
8554
|
app.route("/api/v1", buildV1(deps));
|
|
8039
8555
|
app.route("/_mock", buildAdmin(deps));
|
|
8040
8556
|
app.route("/elements", buildElements(deps));
|
|
@@ -8042,7 +8558,11 @@ function createMockApp(options = {}) {
|
|
|
8042
8558
|
(c) => c.json(
|
|
8043
8559
|
{
|
|
8044
8560
|
success: false,
|
|
8045
|
-
error: {
|
|
8561
|
+
error: {
|
|
8562
|
+
code: "ROUTE_NOT_REGISTERED",
|
|
8563
|
+
message: `Route ${c.req.method} ${c.req.path} not handled by mock`,
|
|
8564
|
+
retryable: false
|
|
8565
|
+
},
|
|
8046
8566
|
hint: "See /api/v1 for the SDK surface and /_mock for the admin surface."
|
|
8047
8567
|
},
|
|
8048
8568
|
501
|