@aurora-studio/starter-core 0.1.0
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/README.md +5 -0
- package/dist/components/AddToCartButton.d.ts +15 -0
- package/dist/components/AddToCartButton.d.ts.map +1 -0
- package/dist/components/AddToCartButton.js +46 -0
- package/dist/components/AddToCartFly.d.ts +8 -0
- package/dist/components/AddToCartFly.d.ts.map +1 -0
- package/dist/components/AddToCartFly.js +33 -0
- package/dist/components/AuthProvider.d.ts +23 -0
- package/dist/components/AuthProvider.d.ts.map +1 -0
- package/dist/components/AuthProvider.js +79 -0
- package/dist/components/CartLink.d.ts +2 -0
- package/dist/components/CartLink.d.ts.map +1 -0
- package/dist/components/CartLink.js +30 -0
- package/dist/components/CartProvider.d.ts +30 -0
- package/dist/components/CartProvider.d.ts.map +1 -0
- package/dist/components/CartProvider.js +125 -0
- package/dist/components/CatalogueEmptyState.d.ts +11 -0
- package/dist/components/CatalogueEmptyState.d.ts.map +1 -0
- package/dist/components/CatalogueEmptyState.js +12 -0
- package/dist/components/CatalogueFilters.d.ts +24 -0
- package/dist/components/CatalogueFilters.d.ts.map +1 -0
- package/dist/components/CatalogueFilters.js +88 -0
- package/dist/components/CheckoutButton.d.ts +2 -0
- package/dist/components/CheckoutButton.d.ts.map +1 -0
- package/dist/components/CheckoutButton.js +70 -0
- package/dist/components/ConditionalHolmesScript.d.ts +7 -0
- package/dist/components/ConditionalHolmesScript.d.ts.map +1 -0
- package/dist/components/ConditionalHolmesScript.js +17 -0
- package/dist/components/FloatingLabelInput.d.ts +7 -0
- package/dist/components/FloatingLabelInput.d.ts.map +1 -0
- package/dist/components/FloatingLabelInput.js +13 -0
- package/dist/components/HolmesHomeRefresher.d.ts +8 -0
- package/dist/components/HolmesHomeRefresher.d.ts.map +1 -0
- package/dist/components/HolmesHomeRefresher.js +19 -0
- package/dist/components/HolmesProductViewTracker.d.ts +4 -0
- package/dist/components/HolmesProductViewTracker.d.ts.map +1 -0
- package/dist/components/HolmesProductViewTracker.js +39 -0
- package/dist/components/HolmesSprinkleIcon.d.ts +8 -0
- package/dist/components/HolmesSprinkleIcon.d.ts.map +1 -0
- package/dist/components/HolmesSprinkleIcon.js +9 -0
- package/dist/components/HolmesTidbits.d.ts +13 -0
- package/dist/components/HolmesTidbits.d.ts.map +1 -0
- package/dist/components/HolmesTidbits.js +33 -0
- package/dist/components/ProductCardSkeleton.d.ts +3 -0
- package/dist/components/ProductCardSkeleton.d.ts.map +1 -0
- package/dist/components/ProductCardSkeleton.js +5 -0
- package/dist/components/ProductDetailTabs.d.ts +4 -0
- package/dist/components/ProductDetailTabs.d.ts.map +1 -0
- package/dist/components/ProductDetailTabs.js +29 -0
- package/dist/components/ProductImage.d.ts +27 -0
- package/dist/components/ProductImage.d.ts.map +1 -0
- package/dist/components/ProductImage.js +33 -0
- package/dist/components/ProductImageGallery.d.ts +6 -0
- package/dist/components/ProductImageGallery.d.ts.map +1 -0
- package/dist/components/ProductImageGallery.js +25 -0
- package/dist/components/SearchDropdown.d.ts +11 -0
- package/dist/components/SearchDropdown.d.ts.map +1 -0
- package/dist/components/SearchDropdown.js +145 -0
- package/dist/components/SmartCartPanel.d.ts +3 -0
- package/dist/components/SmartCartPanel.d.ts.map +1 -0
- package/dist/components/SmartCartPanel.js +19 -0
- package/dist/components/SortDropdown.d.ts +8 -0
- package/dist/components/SortDropdown.d.ts.map +1 -0
- package/dist/components/SortDropdown.js +25 -0
- package/dist/components/StoreConfigContext.d.ts +6 -0
- package/dist/components/StoreConfigContext.d.ts.map +1 -0
- package/dist/components/StoreConfigContext.js +26 -0
- package/dist/components/StoreContext.d.ts +25 -0
- package/dist/components/StoreContext.d.ts.map +1 -0
- package/dist/components/StoreContext.js +73 -0
- package/dist/components/StoreContextBar.d.ts +2 -0
- package/dist/components/StoreContextBar.d.ts.map +1 -0
- package/dist/components/StoreContextBar.js +12 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/lib/aurora.d.ts +119 -0
- package/dist/lib/aurora.d.ts.map +1 -0
- package/dist/lib/aurora.js +235 -0
- package/dist/lib/format-price.d.ts +10 -0
- package/dist/lib/format-price.d.ts.map +1 -0
- package/dist/lib/format-price.js +18 -0
- package/dist/lib/holmes-events.d.ts +53 -0
- package/dist/lib/holmes-events.d.ts.map +1 -0
- package/dist/lib/holmes-events.js +73 -0
- package/dist/lib/image-url.d.ts +23 -0
- package/dist/lib/image-url.d.ts.map +1 -0
- package/dist/lib/image-url.js +70 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +9 -0
- package/package.json +56 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { AuroraClient, } from "@aurora-studio/sdk";
|
|
2
|
+
const baseUrl = process.env.AURORA_API_URL ??
|
|
3
|
+
process.env.NEXT_PUBLIC_AURORA_API_URL ??
|
|
4
|
+
"";
|
|
5
|
+
const apiKey = process.env.AURORA_API_KEY ??
|
|
6
|
+
process.env.NEXT_PUBLIC_AURORA_API_KEY ??
|
|
7
|
+
"";
|
|
8
|
+
const tenantSlug = process.env.NEXT_PUBLIC_TENANT_SLUG ?? "";
|
|
9
|
+
/** Optional spec URL so the SDK can adjust from the tenant OpenAPI spec (default: baseUrl + /v1/openapi.json) */
|
|
10
|
+
const specUrl = baseUrl ? `${baseUrl.replace(/\/$/, "")}/v1/openapi.json` : undefined;
|
|
11
|
+
export function createAuroraClient() {
|
|
12
|
+
if (!baseUrl || baseUrl.startsWith("/")) {
|
|
13
|
+
throw new Error("Aurora API URL is not configured. Set AURORA_API_URL (or NEXT_PUBLIC_AURORA_API_URL) to your Aurora API root (e.g. https://api.youraurora.com).");
|
|
14
|
+
}
|
|
15
|
+
return new AuroraClient({ baseUrl, apiKey, specUrl });
|
|
16
|
+
}
|
|
17
|
+
export function getApiBase() {
|
|
18
|
+
return baseUrl.replace(/\/$/, "");
|
|
19
|
+
}
|
|
20
|
+
export function getTenantSlug() {
|
|
21
|
+
return tenantSlug;
|
|
22
|
+
}
|
|
23
|
+
/** Store config - safe for client: fetches via /api/store/config (keeps API key server-side). */
|
|
24
|
+
export async function getStoreConfig() {
|
|
25
|
+
if (typeof window !== "undefined") {
|
|
26
|
+
const res = await fetch("/api/store/config");
|
|
27
|
+
if (!res.ok)
|
|
28
|
+
return null;
|
|
29
|
+
return res.json();
|
|
30
|
+
}
|
|
31
|
+
const client = createAuroraClient();
|
|
32
|
+
return client.store.config();
|
|
33
|
+
}
|
|
34
|
+
/** Exclude offers from search - offers are checkout-only discounts, not products. */
|
|
35
|
+
function excludeOffersFromSearch(result) {
|
|
36
|
+
const hits = result.hits ?? [];
|
|
37
|
+
const filtered = hits.filter((h) => h.tableSlug !== "offers");
|
|
38
|
+
return { ...result, hits: filtered };
|
|
39
|
+
}
|
|
40
|
+
/** Meilisearch-powered product search. Uses API route from client (keeps API key server-side). */
|
|
41
|
+
export async function search(params) {
|
|
42
|
+
let result;
|
|
43
|
+
if (typeof window !== "undefined") {
|
|
44
|
+
const qs = new URLSearchParams();
|
|
45
|
+
if (params.q != null && params.q !== "")
|
|
46
|
+
qs.set("q", params.q);
|
|
47
|
+
if (params.limit != null)
|
|
48
|
+
qs.set("limit", String(params.limit));
|
|
49
|
+
if (params.offset != null)
|
|
50
|
+
qs.set("offset", String(params.offset));
|
|
51
|
+
if (params.vendorId)
|
|
52
|
+
qs.set("vendorId", params.vendorId);
|
|
53
|
+
if (params.category)
|
|
54
|
+
qs.set("category", params.category);
|
|
55
|
+
if (params.sort)
|
|
56
|
+
qs.set("sort", params.sort);
|
|
57
|
+
if (params.order)
|
|
58
|
+
qs.set("order", params.order);
|
|
59
|
+
if (params.excludeDietary?.length) {
|
|
60
|
+
qs.set("excludeDietary", params.excludeDietary.join(","));
|
|
61
|
+
}
|
|
62
|
+
const res = await fetch(`/api/search?${qs.toString()}`);
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
const err = (await res.json().catch(() => ({})));
|
|
65
|
+
throw new Error(err.error ?? "Search failed");
|
|
66
|
+
}
|
|
67
|
+
result = (await res.json());
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const client = createAuroraClient();
|
|
71
|
+
result = await client.site.search(params);
|
|
72
|
+
}
|
|
73
|
+
return excludeOffersFromSearch(result);
|
|
74
|
+
}
|
|
75
|
+
/** Fetch delivery slots for a location */
|
|
76
|
+
export async function getDeliverySlots(lat, lng) {
|
|
77
|
+
const client = createAuroraClient();
|
|
78
|
+
return client.store.deliverySlots(lat, lng);
|
|
79
|
+
}
|
|
80
|
+
/** List stores/vendors */
|
|
81
|
+
export async function getStores() {
|
|
82
|
+
const client = createAuroraClient();
|
|
83
|
+
return client.site.stores();
|
|
84
|
+
}
|
|
85
|
+
/** Create checkout session (Stripe or ACME) */
|
|
86
|
+
export async function createCheckoutSession(params) {
|
|
87
|
+
const client = createAuroraClient();
|
|
88
|
+
return client.store.checkout.sessions.create(params);
|
|
89
|
+
}
|
|
90
|
+
/** Fetch ACME checkout session */
|
|
91
|
+
export async function getAcmeSession(sessionId) {
|
|
92
|
+
const client = createAuroraClient();
|
|
93
|
+
return client.store.checkout.acme.get(sessionId);
|
|
94
|
+
}
|
|
95
|
+
/** Complete ACME checkout */
|
|
96
|
+
export async function completeAcmeCheckout(sessionId, shippingAddress) {
|
|
97
|
+
const client = createAuroraClient();
|
|
98
|
+
return client.store.checkout.acme.complete(sessionId, shippingAddress);
|
|
99
|
+
}
|
|
100
|
+
/** Holmes mission inference */
|
|
101
|
+
export async function holmesInfer(sessionId) {
|
|
102
|
+
const client = createAuroraClient();
|
|
103
|
+
return client.holmes.infer(sessionId);
|
|
104
|
+
}
|
|
105
|
+
/** Holmes insights: products for a recipe (paella, curry, pasta). Uses holmes_insights.recipe_ideas. */
|
|
106
|
+
export async function holmesRecipeProducts(recipe, limit = 12, opts) {
|
|
107
|
+
if (typeof window !== "undefined") {
|
|
108
|
+
const qs = new URLSearchParams({ recipe, limit: String(limit) });
|
|
109
|
+
if (opts?.excludeDietary?.length)
|
|
110
|
+
qs.set("excludeDietary", opts.excludeDietary.join(","));
|
|
111
|
+
const res = await fetch(`/api/holmes/recipe-products?${qs.toString()}`);
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
const err = (await res.json().catch(() => ({})));
|
|
114
|
+
throw new Error(err.error ?? "Recipe products failed");
|
|
115
|
+
}
|
|
116
|
+
return res.json();
|
|
117
|
+
}
|
|
118
|
+
const client = createAuroraClient();
|
|
119
|
+
return client.store.holmesRecipeProducts(recipe, limit, opts);
|
|
120
|
+
}
|
|
121
|
+
/** Holmes recent recipes from cache. Ordered by most recently updated. Optional timeOfDay filters by morning/afternoon/evening. excludeDietary filters out recipes whose ingredients match excluded types. */
|
|
122
|
+
export async function holmesRecentRecipes(limit = 8, timeOfDay, opts) {
|
|
123
|
+
if (typeof window !== "undefined") {
|
|
124
|
+
const qs = new URLSearchParams({ limit: String(limit) });
|
|
125
|
+
if (timeOfDay)
|
|
126
|
+
qs.set("time_of_day", timeOfDay);
|
|
127
|
+
if (opts?.excludeDietary?.length)
|
|
128
|
+
qs.set("excludeDietary", opts.excludeDietary.join(","));
|
|
129
|
+
const res = await fetch(`/api/holmes/recipes?${qs.toString()}`);
|
|
130
|
+
if (!res.ok)
|
|
131
|
+
return { recipes: [] };
|
|
132
|
+
return res.json();
|
|
133
|
+
}
|
|
134
|
+
const client = createAuroraClient();
|
|
135
|
+
return client.store.holmesRecentRecipes(limit, timeOfDay, opts);
|
|
136
|
+
}
|
|
137
|
+
/** Holmes cached recipe. Fetches via AI on cache miss. */
|
|
138
|
+
export async function holmesRecipe(slug) {
|
|
139
|
+
if (typeof window !== "undefined") {
|
|
140
|
+
const res = await fetch(`/api/holmes/recipe/${encodeURIComponent(slug)}`);
|
|
141
|
+
if (!res.ok)
|
|
142
|
+
return null;
|
|
143
|
+
return res.json();
|
|
144
|
+
}
|
|
145
|
+
const client = createAuroraClient();
|
|
146
|
+
return client.store.holmesRecipe(slug);
|
|
147
|
+
}
|
|
148
|
+
/** Holmes tidbits for entity (recipe, ingredient, product). */
|
|
149
|
+
export async function holmesTidbits(entity, entityType = "recipe") {
|
|
150
|
+
if (typeof window !== "undefined") {
|
|
151
|
+
const qs = new URLSearchParams({ entity, entity_type: entityType });
|
|
152
|
+
const res = await fetch(`/api/holmes/tidbits?${qs.toString()}`);
|
|
153
|
+
if (!res.ok)
|
|
154
|
+
return { tidbits: [] };
|
|
155
|
+
return res.json();
|
|
156
|
+
}
|
|
157
|
+
const client = createAuroraClient();
|
|
158
|
+
return client.store.holmesTidbits(entity, entityType);
|
|
159
|
+
}
|
|
160
|
+
/** Holmes insights: products that go well with a given product. Uses holmes_insights.goes_well_with. */
|
|
161
|
+
export async function holmesGoesWith(productId, limit = 8, opts) {
|
|
162
|
+
if (typeof window !== "undefined") {
|
|
163
|
+
const qs = new URLSearchParams({ product_id: productId, limit: String(limit) });
|
|
164
|
+
if (opts?.excludeDietary?.length)
|
|
165
|
+
qs.set("excludeDietary", opts.excludeDietary.join(","));
|
|
166
|
+
const res = await fetch(`/api/holmes/goes-with?${qs.toString()}`);
|
|
167
|
+
if (!res.ok) {
|
|
168
|
+
const err = (await res.json().catch(() => ({})));
|
|
169
|
+
throw new Error(err.error ?? "Goes-with failed");
|
|
170
|
+
}
|
|
171
|
+
return res.json();
|
|
172
|
+
}
|
|
173
|
+
const client = createAuroraClient();
|
|
174
|
+
return client.store.holmesGoesWith(productId, limit, opts);
|
|
175
|
+
}
|
|
176
|
+
/** Holmes bundle/recipe options for cart. Returns 1–3 combos when cart has 2+ items. */
|
|
177
|
+
export async function holmesCombosForCart(params) {
|
|
178
|
+
const qs = new URLSearchParams({ cart_ids: params.cartIds.join(",") });
|
|
179
|
+
if (params.cartNames?.length)
|
|
180
|
+
qs.set("cart_names", params.cartNames.join(","));
|
|
181
|
+
if (params.limit)
|
|
182
|
+
qs.set("limit", String(params.limit));
|
|
183
|
+
if (params.sid)
|
|
184
|
+
qs.set("sid", params.sid);
|
|
185
|
+
const res = await fetch(`/api/holmes/combos-for-cart?${qs.toString()}`);
|
|
186
|
+
if (!res.ok)
|
|
187
|
+
return { combos: [] };
|
|
188
|
+
return res.json();
|
|
189
|
+
}
|
|
190
|
+
/** Set selected combo for session (from recipe picker). */
|
|
191
|
+
export async function holmesSelectCombo(params) {
|
|
192
|
+
const res = await fetch("/api/holmes/select-combo", {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: { "Content-Type": "application/json" },
|
|
195
|
+
body: JSON.stringify(params),
|
|
196
|
+
});
|
|
197
|
+
if (!res.ok)
|
|
198
|
+
throw new Error("Failed to set selected combo");
|
|
199
|
+
return res.json();
|
|
200
|
+
}
|
|
201
|
+
/** Holmes insights: similar products by type (what_it_is). For substitutions - same product type, not complementary. */
|
|
202
|
+
export async function holmesSimilarProducts(productId, limit = 8, productName, opts) {
|
|
203
|
+
if (typeof window !== "undefined") {
|
|
204
|
+
const qs = new URLSearchParams({ product_id: productId, limit: String(limit) });
|
|
205
|
+
if (productName?.trim())
|
|
206
|
+
qs.set("product_name", productName.trim());
|
|
207
|
+
if (opts?.excludeDietary?.length)
|
|
208
|
+
qs.set("excludeDietary", opts.excludeDietary.join(","));
|
|
209
|
+
const res = await fetch(`/api/holmes/similar?${qs.toString()}`);
|
|
210
|
+
if (!res.ok) {
|
|
211
|
+
const err = (await res.json().catch(() => ({})));
|
|
212
|
+
throw new Error(err.error ?? "Similar products failed");
|
|
213
|
+
}
|
|
214
|
+
return res.json();
|
|
215
|
+
}
|
|
216
|
+
const client = createAuroraClient();
|
|
217
|
+
return client.store.holmesSimilar(productId, limit, productName, opts);
|
|
218
|
+
}
|
|
219
|
+
/** Home personalization - sections for SSR fallback. sid optional (omit for default sections). excludeDietary filters products and sections by dietary preferences. */
|
|
220
|
+
export async function getHomePersonalization(sid, opts) {
|
|
221
|
+
try {
|
|
222
|
+
const client = createAuroraClient();
|
|
223
|
+
const result = await client.store.homePersonalization(sid ?? "", undefined, opts);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Holmes offers & chat: available in SDK 0.2.7+ via client.holmes.offers(), client.holmes.chat.send(), client.holmes.chat.list()
|
|
231
|
+
/** Current user metadata and related data (e.g. addresses) when userId is provided. Uses GET /me from tenant spec. */
|
|
232
|
+
export async function getMe(userId) {
|
|
233
|
+
const client = createAuroraClient();
|
|
234
|
+
return client.me({ userId });
|
|
235
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format price for display. Aurora stores prices as decimal major units (e.g. 2.00 = £2).
|
|
3
|
+
* This helper expects cents (integer) and formats for display.
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatPrice(cents: number, currency?: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Convert Aurora DB price (decimal major units, e.g. 2.00) to cents for display/cart.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toCents(rawPrice: number | null | undefined): number | undefined;
|
|
10
|
+
//# sourceMappingURL=format-price.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-price.d.ts","sourceRoot":"","sources":["../../src/lib/format-price.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAQ,GAAG,MAAM,CAKnE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG/E"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format price for display. Aurora stores prices as decimal major units (e.g. 2.00 = £2).
|
|
3
|
+
* This helper expects cents (integer) and formats for display.
|
|
4
|
+
*/
|
|
5
|
+
export function formatPrice(cents, currency = "GBP") {
|
|
6
|
+
return new Intl.NumberFormat("en-GB", {
|
|
7
|
+
style: "currency",
|
|
8
|
+
currency,
|
|
9
|
+
}).format(cents / 100);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Convert Aurora DB price (decimal major units, e.g. 2.00) to cents for display/cart.
|
|
13
|
+
*/
|
|
14
|
+
export function toCents(rawPrice) {
|
|
15
|
+
if (rawPrice == null || !Number.isFinite(rawPrice))
|
|
16
|
+
return undefined;
|
|
17
|
+
return Math.round(Number(rawPrice) * 100);
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holmes event bridge - push storefront signals to the Holmes script.
|
|
3
|
+
* Works for any storefront: call directly or dispatch DOM events.
|
|
4
|
+
* The Holmes script (loaded via layout) listens and sends data to Aurora.
|
|
5
|
+
*
|
|
6
|
+
* Integration: paste <script src=".../holmes/v1/script.js?site=X"></script>
|
|
7
|
+
* then call these helpers or dispatch the equivalent CustomEvents.
|
|
8
|
+
*/
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
holmes?: {
|
|
12
|
+
setSearch: (q: string | string[]) => void;
|
|
13
|
+
setProductsViewed: (ids: string[]) => void;
|
|
14
|
+
setCartCount: (n: number) => void;
|
|
15
|
+
setCartItems: (items: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
price: number;
|
|
19
|
+
}>) => void;
|
|
20
|
+
setRecipeViewed?: (slug: string, title: string) => void;
|
|
21
|
+
setRecipeMissionLock?: (key: string) => void;
|
|
22
|
+
clearRecipeMissionLock?: () => void;
|
|
23
|
+
getSessionId?: () => string;
|
|
24
|
+
getMissionStartTimestamp?: () => number | null;
|
|
25
|
+
addBundleToCart?: (products: Array<{
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
price: number;
|
|
29
|
+
image?: string;
|
|
30
|
+
}>, tableSlug: string) => void;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export declare function holmesRecipeView(slug: string, title: string): void;
|
|
35
|
+
/** Dispatch event for Holmes to add a bundle to cart. CartProvider listens and calls addItem. */
|
|
36
|
+
export declare function holmesAddBundle(products: Array<{
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
price: number;
|
|
40
|
+
image?: string;
|
|
41
|
+
}>, tableSlug: string): void;
|
|
42
|
+
/** Lock recipe/combo mission on the server until reset (meal searches, mission pills). */
|
|
43
|
+
export declare function holmesMissionLockCombo(): void;
|
|
44
|
+
/** Clear sticky recipe mission (e.g. mission bar reset). */
|
|
45
|
+
export declare function holmesMissionLockClear(): void;
|
|
46
|
+
export declare function holmesSearch(query: string): void;
|
|
47
|
+
export declare function holmesProductView(productIds: string[]): void;
|
|
48
|
+
export declare function holmesCartUpdate(count: number, items?: Array<{
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
price: number;
|
|
52
|
+
}>, bootstrap?: boolean): void;
|
|
53
|
+
//# sourceMappingURL=holmes-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"holmes-events.d.ts","sourceRoot":"","sources":["../../src/lib/holmes-events.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,MAAM,CAAC,EAAE;YACP,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;YAC1C,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;YAC3C,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;YAClC,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,MAAM,CAAA;aAAE,CAAC,KAAK,IAAI,CAAC;YAClF,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;YACxD,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;YAC7C,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;YACpC,YAAY,CAAC,EAAE,MAAM,MAAM,CAAC;YAC5B,wBAAwB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;YAC/C,eAAe,CAAC,EAAE,CAChB,QAAQ,EAAE,KAAK,CAAC;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,KAAK,EAAE,MAAM,CAAC;gBAAC,KAAK,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC,EAC5E,SAAS,EAAE,MAAM,KACd,IAAI,CAAC;SACX,CAAC;KACH;CACF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAUlE;AAED,iGAAiG;AACjG,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAC5E,SAAS,EAAE,MAAM,GAChB,IAAI,CAKN;AAKD,0FAA0F;AAC1F,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C;AAED,4DAA4D;AAC5D,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAOhD;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAM5D;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1D,SAAS,CAAC,EAAE,OAAO,GAClB,IAAI,CAWN"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holmes event bridge - push storefront signals to the Holmes script.
|
|
3
|
+
* Works for any storefront: call directly or dispatch DOM events.
|
|
4
|
+
* The Holmes script (loaded via layout) listens and sends data to Aurora.
|
|
5
|
+
*
|
|
6
|
+
* Integration: paste <script src=".../holmes/v1/script.js?site=X"></script>
|
|
7
|
+
* then call these helpers or dispatch the equivalent CustomEvents.
|
|
8
|
+
*/
|
|
9
|
+
export function holmesRecipeView(slug, title) {
|
|
10
|
+
if (typeof document === "undefined")
|
|
11
|
+
return;
|
|
12
|
+
const s = String(slug || "").trim();
|
|
13
|
+
const t = String(title || "").trim();
|
|
14
|
+
if (!s || !t)
|
|
15
|
+
return;
|
|
16
|
+
holmesMissionLockCombo();
|
|
17
|
+
if (window.holmes?.setRecipeViewed)
|
|
18
|
+
window.holmes.setRecipeViewed(s, t);
|
|
19
|
+
document.dispatchEvent(new CustomEvent("holmes:recipeView", { detail: { slug: s, title: t } }));
|
|
20
|
+
}
|
|
21
|
+
/** Dispatch event for Holmes to add a bundle to cart. CartProvider listens and calls addItem. */
|
|
22
|
+
export function holmesAddBundle(products, tableSlug) {
|
|
23
|
+
if (typeof document === "undefined")
|
|
24
|
+
return;
|
|
25
|
+
document.dispatchEvent(new CustomEvent("holmes:addBundle", { detail: { products, tableSlug } }));
|
|
26
|
+
}
|
|
27
|
+
const MEAL_SEARCH_LOCK_RE = /dinner|lunch|breakfast|brunch|supper|recipe|cook\b|meal\b|paella|curry|pasta|steak|roast|ingredient|grill|bbq|beef|lamb|pork|salmon|chicken|fish/i;
|
|
28
|
+
/** Lock recipe/combo mission on the server until reset (meal searches, mission pills). */
|
|
29
|
+
export function holmesMissionLockCombo() {
|
|
30
|
+
if (typeof document === "undefined")
|
|
31
|
+
return;
|
|
32
|
+
document.dispatchEvent(new CustomEvent("holmes:missionLock", { detail: { key: "combo_mission" } }));
|
|
33
|
+
}
|
|
34
|
+
/** Clear sticky recipe mission (e.g. mission bar reset). */
|
|
35
|
+
export function holmesMissionLockClear() {
|
|
36
|
+
if (typeof document === "undefined")
|
|
37
|
+
return;
|
|
38
|
+
document.dispatchEvent(new CustomEvent("holmes:missionLockClear"));
|
|
39
|
+
}
|
|
40
|
+
export function holmesSearch(query) {
|
|
41
|
+
if (typeof window === "undefined")
|
|
42
|
+
return;
|
|
43
|
+
const q = String(query || "").trim();
|
|
44
|
+
if (!q)
|
|
45
|
+
return;
|
|
46
|
+
if (MEAL_SEARCH_LOCK_RE.test(q))
|
|
47
|
+
holmesMissionLockCombo();
|
|
48
|
+
if (window.holmes)
|
|
49
|
+
window.holmes.setSearch(q);
|
|
50
|
+
document.dispatchEvent(new CustomEvent("holmes:search", { detail: { q } }));
|
|
51
|
+
}
|
|
52
|
+
export function holmesProductView(productIds) {
|
|
53
|
+
if (typeof window === "undefined")
|
|
54
|
+
return;
|
|
55
|
+
const ids = Array.isArray(productIds) ? productIds : [];
|
|
56
|
+
if (ids.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
if (window.holmes)
|
|
59
|
+
window.holmes.setProductsViewed(ids);
|
|
60
|
+
document.dispatchEvent(new CustomEvent("holmes:productView", { detail: { productIds: ids } }));
|
|
61
|
+
}
|
|
62
|
+
export function holmesCartUpdate(count, items, bootstrap) {
|
|
63
|
+
if (typeof window === "undefined")
|
|
64
|
+
return;
|
|
65
|
+
if (window.holmes) {
|
|
66
|
+
window.holmes.setCartCount(count);
|
|
67
|
+
if (items)
|
|
68
|
+
window.holmes.setCartItems(items);
|
|
69
|
+
}
|
|
70
|
+
document.dispatchEvent(new CustomEvent("holmes:cartUpdate", {
|
|
71
|
+
detail: { count, items: items ?? [], bootstrap: bootstrap === true },
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized product image URL resolution.
|
|
3
|
+
* Ensures relative URLs get correct base (storefront or CDN) so images load reliably.
|
|
4
|
+
* Supports Contentful CDN resize params to request appropriately-sized thumbnails.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* For Contentful CDN URLs, append resize params so the CDN returns an appropriately-sized
|
|
8
|
+
* image. Uses fit=pad to preserve aspect ratio and avoid cropping portrait/landscape images.
|
|
9
|
+
* Other URLs are returned unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getThumbnailImageUrl(url: string | null | undefined, width?: number, height?: number): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Extract image URL from a product/record.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getImageUrlFromRecord(record: Record<string, unknown>): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve product image URL for display.
|
|
18
|
+
* - Empty/invalid URLs return null.
|
|
19
|
+
* - Absolute URLs (http/https) returned as-is.
|
|
20
|
+
* - Relative URLs get baseUrl prepended (storefront origin or imageBaseUrl from config).
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveProductImageUrl(url: string | null | undefined, baseUrl?: string | null): string | null;
|
|
23
|
+
//# sourceMappingURL=image-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-url.d.ts","sourceRoot":"","sources":["../../src/lib/image-url.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,KAAK,SAAM,EACX,MAAM,SAAM,GACX,MAAM,GAAG,IAAI,CAiBf;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,GAAG,IAAI,CAGf;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,GAAG,IAAI,CAyBf"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized product image URL resolution.
|
|
3
|
+
* Ensures relative URLs get correct base (storefront or CDN) so images load reliably.
|
|
4
|
+
* Supports Contentful CDN resize params to request appropriately-sized thumbnails.
|
|
5
|
+
*/
|
|
6
|
+
const IMAGE_FIELDS = ["image_url", "image", "thumbnail", "photo"];
|
|
7
|
+
/** Matches Contentful CDN URLs (images.ctfassets.net or images.eu.ctfassets.net), with or without protocol */
|
|
8
|
+
const CONTENTFUL_CDN_RE = /^(https?:)?\/\/images\.(eu\.)?ctfassets\.net\//i;
|
|
9
|
+
/**
|
|
10
|
+
* For Contentful CDN URLs, append resize params so the CDN returns an appropriately-sized
|
|
11
|
+
* image. Uses fit=pad to preserve aspect ratio and avoid cropping portrait/landscape images.
|
|
12
|
+
* Other URLs are returned unchanged.
|
|
13
|
+
*/
|
|
14
|
+
export function getThumbnailImageUrl(url, width = 400, height = 400) {
|
|
15
|
+
const raw = typeof url === "string" ? url.trim() : "";
|
|
16
|
+
if (!raw)
|
|
17
|
+
return null;
|
|
18
|
+
if (!CONTENTFUL_CDN_RE.test(raw))
|
|
19
|
+
return raw;
|
|
20
|
+
try {
|
|
21
|
+
const u = new URL(raw.startsWith("//") ? `https:${raw}` : raw);
|
|
22
|
+
const params = u.searchParams;
|
|
23
|
+
if (params.has("w") || params.has("h"))
|
|
24
|
+
return raw.startsWith("//") ? `https:${raw}` : raw;
|
|
25
|
+
params.set("w", String(Math.min(4000, width)));
|
|
26
|
+
params.set("h", String(Math.min(4000, height)));
|
|
27
|
+
params.set("fit", "pad");
|
|
28
|
+
params.set("fm", "webp");
|
|
29
|
+
return u.toString();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return raw;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Extract image URL from a product/record.
|
|
37
|
+
*/
|
|
38
|
+
export function getImageUrlFromRecord(record) {
|
|
39
|
+
const field = IMAGE_FIELDS.find((f) => record[f]);
|
|
40
|
+
return field ? String(record[field]).trim() || null : null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve product image URL for display.
|
|
44
|
+
* - Empty/invalid URLs return null.
|
|
45
|
+
* - Absolute URLs (http/https) returned as-is.
|
|
46
|
+
* - Relative URLs get baseUrl prepended (storefront origin or imageBaseUrl from config).
|
|
47
|
+
*/
|
|
48
|
+
export function resolveProductImageUrl(url, baseUrl) {
|
|
49
|
+
const raw = typeof url === "string" ? url.trim() : "";
|
|
50
|
+
if (!raw)
|
|
51
|
+
return null;
|
|
52
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
53
|
+
return raw;
|
|
54
|
+
}
|
|
55
|
+
if (raw.startsWith("//")) {
|
|
56
|
+
return `https:${raw}`;
|
|
57
|
+
}
|
|
58
|
+
const base = baseUrl ??
|
|
59
|
+
process.env.NEXT_PUBLIC_IMAGE_BASE_URL?.trim() ??
|
|
60
|
+
(typeof window !== "undefined"
|
|
61
|
+
? window.location?.origin
|
|
62
|
+
: null) ??
|
|
63
|
+
process.env.NEXT_PUBLIC_APP_URL?.trim() ??
|
|
64
|
+
"";
|
|
65
|
+
if (!base)
|
|
66
|
+
return raw.startsWith("/") ? raw : `/${raw}`;
|
|
67
|
+
const normalized = base.replace(/\/$/, "");
|
|
68
|
+
const path = raw.startsWith("/") ? raw : `/${raw}`;
|
|
69
|
+
return `${normalized}${path}`;
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,wBAAgB,YAAY,IAAI,SAAS,GAAG,WAAW,GAAG,SAAS,CAKlE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Returns time-of-day label (morning, afternoon, evening) for copy like "Recipe ideas for morning". */
|
|
2
|
+
export function getTimeOfDay() {
|
|
3
|
+
const hour = new Date().getHours();
|
|
4
|
+
if (hour >= 5 && hour < 12)
|
|
5
|
+
return "morning";
|
|
6
|
+
if (hour >= 12 && hour < 17)
|
|
7
|
+
return "afternoon";
|
|
8
|
+
return "evening";
|
|
9
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aurora-studio/starter-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"build": "tsc -p tsconfig.build.json",
|
|
6
|
+
"prepare": "tsc -p tsconfig.build.json",
|
|
7
|
+
"typecheck": "tsc --noEmit"
|
|
8
|
+
},
|
|
9
|
+
"description": "Shared Aurora storefront primitives (SDK helpers, Holmes bridge, commerce UI) for Hippo templates.",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/Purple-Napkin/aurora-starter-core.git"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@aurora-studio/sdk": "^0.2.27",
|
|
34
|
+
"next": ">=14",
|
|
35
|
+
"react": "^18 || ^19",
|
|
36
|
+
"react-dom": "^18 || ^19",
|
|
37
|
+
"lucide-react": ">=0.400"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@aurora-studio/sdk": "^0.2.27",
|
|
41
|
+
"@types/node": "^22.0.0",
|
|
42
|
+
"@types/react": "^19.0.0",
|
|
43
|
+
"@types/react-dom": "^19.0.0",
|
|
44
|
+
"lucide-react": "^0.454.0",
|
|
45
|
+
"next": "15.0.7",
|
|
46
|
+
"react": "^19.0.0",
|
|
47
|
+
"react-dom": "^19.0.0",
|
|
48
|
+
"typescript": "^5.3.3"
|
|
49
|
+
},
|
|
50
|
+
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
|
|
51
|
+
"author": "Marcel du Preez - @Purple-Napkin",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/Purple-Napkin/aurora-starter-core/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/Purple-Napkin/aurora-starter-core#readme"
|
|
56
|
+
}
|