@aurora-studio/starter-core 0.1.3 → 0.1.5
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/components/AddToCartButton.d.ts +0 -1
- package/dist/components/AddToCartButton.js +1 -46
- package/dist/components/AddToCartFly.d.ts +0 -1
- package/dist/components/AddToCartFly.js +1 -33
- package/dist/components/AuthProvider.d.ts +0 -1
- package/dist/components/AuthProvider.js +1 -79
- package/dist/components/CartLink.d.ts +0 -1
- package/dist/components/CartLink.js +1 -30
- package/dist/components/CartProvider.d.ts +0 -1
- package/dist/components/CartProvider.js +1 -125
- package/dist/components/CatalogueEmptyState.d.ts +0 -1
- package/dist/components/CatalogueEmptyState.js +1 -12
- package/dist/components/CatalogueFilters.d.ts +0 -1
- package/dist/components/CatalogueFilters.js +1 -88
- package/dist/components/CheckoutButton.d.ts +0 -1
- package/dist/components/CheckoutButton.js +1 -70
- package/dist/components/ConditionalHolmesScript.d.ts +0 -1
- package/dist/components/ConditionalHolmesScript.js +1 -17
- package/dist/components/FloatingLabelInput.d.ts +0 -1
- package/dist/components/FloatingLabelInput.js +1 -13
- package/dist/components/HolmesHomeRefresher.d.ts +0 -1
- package/dist/components/HolmesHomeRefresher.js +1 -19
- package/dist/components/HolmesProductViewTracker.d.ts +0 -1
- package/dist/components/HolmesProductViewTracker.js +1 -39
- package/dist/components/HolmesSprinkleIcon.d.ts +0 -1
- package/dist/components/HolmesSprinkleIcon.js +1 -9
- package/dist/components/HolmesTidbits.d.ts +0 -1
- package/dist/components/HolmesTidbits.js +1 -33
- package/dist/components/ProductCardSkeleton.d.ts +0 -1
- package/dist/components/ProductCardSkeleton.js +1 -5
- package/dist/components/ProductDetailTabs.d.ts +0 -1
- package/dist/components/ProductDetailTabs.js +1 -29
- package/dist/components/ProductImage.d.ts +0 -1
- package/dist/components/ProductImage.js +1 -33
- package/dist/components/ProductImageGallery.d.ts +0 -1
- package/dist/components/ProductImageGallery.js +1 -25
- package/dist/components/SearchDropdown.d.ts +7 -2
- package/dist/components/SearchDropdown.js +1 -145
- package/dist/components/SmartCartPanel.d.ts +0 -1
- package/dist/components/SmartCartPanel.js +1 -19
- package/dist/components/SortDropdown.d.ts +0 -1
- package/dist/components/SortDropdown.js +1 -25
- package/dist/components/StoreConfigContext.d.ts +0 -1
- package/dist/components/StoreConfigContext.js +1 -26
- package/dist/components/StoreContext.d.ts +0 -1
- package/dist/components/StoreContext.js +1 -73
- package/dist/components/StoreContextBar.d.ts +0 -1
- package/dist/components/StoreContextBar.js +1 -12
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -31
- package/dist/lib/aurora.d.ts +0 -1
- package/dist/lib/aurora.js +1 -235
- package/dist/lib/format-price.d.ts +0 -1
- package/dist/lib/format-price.js +1 -18
- package/dist/lib/holmes-events.d.ts +0 -1
- package/dist/lib/holmes-events.js +1 -73
- package/dist/lib/image-url.d.ts +0 -1
- package/dist/lib/image-url.js +1 -70
- package/dist/lib/utils.d.ts +0 -1
- package/dist/lib/utils.js +1 -9
- package/package.json +14 -9
- package/dist/components/AddToCartButton.d.ts.map +0 -1
- package/dist/components/AddToCartFly.d.ts.map +0 -1
- package/dist/components/AuthProvider.d.ts.map +0 -1
- package/dist/components/CartLink.d.ts.map +0 -1
- package/dist/components/CartProvider.d.ts.map +0 -1
- package/dist/components/CatalogueEmptyState.d.ts.map +0 -1
- package/dist/components/CatalogueFilters.d.ts.map +0 -1
- package/dist/components/CheckoutButton.d.ts.map +0 -1
- package/dist/components/ConditionalHolmesScript.d.ts.map +0 -1
- package/dist/components/FloatingLabelInput.d.ts.map +0 -1
- package/dist/components/HolmesHomeRefresher.d.ts.map +0 -1
- package/dist/components/HolmesProductViewTracker.d.ts.map +0 -1
- package/dist/components/HolmesSprinkleIcon.d.ts.map +0 -1
- package/dist/components/HolmesTidbits.d.ts.map +0 -1
- package/dist/components/ProductCardSkeleton.d.ts.map +0 -1
- package/dist/components/ProductDetailTabs.d.ts.map +0 -1
- package/dist/components/ProductImage.d.ts.map +0 -1
- package/dist/components/ProductImageGallery.d.ts.map +0 -1
- package/dist/components/SearchDropdown.d.ts.map +0 -1
- package/dist/components/SmartCartPanel.d.ts.map +0 -1
- package/dist/components/SortDropdown.d.ts.map +0 -1
- package/dist/components/StoreConfigContext.d.ts.map +0 -1
- package/dist/components/StoreContext.d.ts.map +0 -1
- package/dist/components/StoreContextBar.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/aurora.d.ts.map +0 -1
- package/dist/lib/format-price.d.ts.map +0 -1
- package/dist/lib/holmes-events.d.ts.map +0 -1
- package/dist/lib/image-url.d.ts.map +0 -1
- package/dist/lib/utils.d.ts.map +0 -1
|
@@ -1,13 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useId, useRef, useState } from "react";
|
|
4
|
-
export function FloatingLabelInput({ label, error, id, className = "", value, ...props }) {
|
|
5
|
-
const generatedId = useId();
|
|
6
|
-
const inputId = id ?? generatedId;
|
|
7
|
-
const inputRef = useRef(null);
|
|
8
|
-
const [focused, setFocused] = useState(false);
|
|
9
|
-
const floatLabel = focused || Boolean(value != null && value !== "");
|
|
10
|
-
return (_jsxs("div", { className: "relative", children: [_jsx("input", { ref: inputRef, id: inputId, value: value, onFocus: (e) => { setFocused(true); props.onFocus?.(e); }, onBlur: (e) => { setFocused(false); props.onBlur?.(e); }, placeholder: " ", className: `peer w-full h-12 px-4 rounded-xl bg-aurora-bg border text-aurora-text placeholder:text-transparent focus:outline-none focus:ring-2 focus:ring-aurora-primary/50 focus:border-aurora-primary transition-colors ${error ? "border-aurora-error" : "border-aurora-border"} ${className}`, ...props }), _jsx("label", { htmlFor: inputId, className: `absolute left-4 transition-all duration-200 pointer-events-none ${floatLabel
|
|
11
|
-
? "-top-1 -translate-y-full text-xs text-aurora-muted bg-aurora-bg px-1"
|
|
12
|
-
: "top-1/2 -translate-y-1/2 text-base text-aurora-muted"}`, children: label }), error && _jsx("p", { className: "mt-1 text-sm text-aurora-error", children: error })] }));
|
|
13
|
-
}
|
|
1
|
+
import{jsx as r,jsxs as e}from"react/jsx-runtime";import{useId as a,useRef as o,useState as t}from"react";export function FloatingLabelInput({label:l,error:n,id:u,className:s="",value:i,...c}){const d=a(),m=u??d,p=o(null),[x,b]=t(!1),f=x||Boolean(null!=i&&""!==i);return e("div",{className:"relative",children:[r("input",{ref:p,id:m,value:i,onFocus:r=>{b(!0),c.onFocus?.(r)},onBlur:r=>{b(!1),c.onBlur?.(r)},placeholder:" ",className:`peer w-full h-12 px-4 rounded-xl bg-aurora-bg border text-aurora-text placeholder:text-transparent focus:outline-none focus:ring-2 focus:ring-aurora-primary/50 focus:border-aurora-primary transition-colors ${n?"border-aurora-error":"border-aurora-border"} ${s}`,...c}),r("label",{htmlFor:m,className:"absolute left-4 transition-all duration-200 pointer-events-none "+(f?"-top-1 -translate-y-full text-xs text-aurora-muted bg-aurora-bg px-1":"top-1/2 -translate-y-1/2 text-base text-aurora-muted"),children:l}),n&&r("p",{className:"mt-1 text-sm text-aurora-error",children:n})]})}
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { useEffect } from "react";
|
|
3
|
-
/**
|
|
4
|
-
* Dispatches holmes:refreshHome so the Holmes script re-fetches home
|
|
5
|
-
* personalization when the user navigates to the home page.
|
|
6
|
-
* Also listens for holmes:ready and re-dispatches refresh - so when the script
|
|
7
|
-
* loads after we mount (e.g. slow network), we trigger the fetch.
|
|
8
|
-
*/
|
|
9
|
-
export function HolmesHomeRefresher() {
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
document.dispatchEvent(new CustomEvent("holmes:refreshHome"));
|
|
12
|
-
const onReady = () => {
|
|
13
|
-
document.dispatchEvent(new CustomEvent("holmes:refreshHome"));
|
|
14
|
-
};
|
|
15
|
-
document.addEventListener("holmes:ready", onReady);
|
|
16
|
-
return () => document.removeEventListener("holmes:ready", onReady);
|
|
17
|
-
}, []);
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
1
|
+
import{useEffect as e}from"react";export function HolmesHomeRefresher(){return e(()=>{document.dispatchEvent(new CustomEvent("holmes:refreshHome"));const e=()=>{document.dispatchEvent(new CustomEvent("holmes:refreshHome"))};return document.addEventListener("holmes:ready",e),()=>document.removeEventListener("holmes:ready",e)},[]),null}
|
|
@@ -1,39 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { useEffect } from "react";
|
|
3
|
-
import { holmesProductView } from "../lib/holmes-events";
|
|
4
|
-
const STORAGE_KEY = "holmes_products_viewed";
|
|
5
|
-
const MAX_VIEWED = 20;
|
|
6
|
-
function getViewedIds() {
|
|
7
|
-
if (typeof window === "undefined")
|
|
8
|
-
return [];
|
|
9
|
-
try {
|
|
10
|
-
const raw = sessionStorage.getItem(STORAGE_KEY);
|
|
11
|
-
if (!raw)
|
|
12
|
-
return [];
|
|
13
|
-
return JSON.parse(raw);
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return [];
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function appendViewed(productId) {
|
|
20
|
-
const current = getViewedIds();
|
|
21
|
-
const filtered = current.filter((id) => id !== productId);
|
|
22
|
-
const next = [productId, ...filtered].slice(0, MAX_VIEWED);
|
|
23
|
-
try {
|
|
24
|
-
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
/* ignore */
|
|
28
|
-
}
|
|
29
|
-
return next;
|
|
30
|
-
}
|
|
31
|
-
export function HolmesProductViewTracker({ productId }) {
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (!productId)
|
|
34
|
-
return;
|
|
35
|
-
const allIds = appendViewed(productId);
|
|
36
|
-
holmesProductView(allIds);
|
|
37
|
-
}, [productId]);
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
1
|
+
import{useEffect as t}from"react";import{holmesProductView as e}from"../lib/holmes-events";const r="holmes_products_viewed";export function HolmesProductViewTracker({productId:n}){return t(()=>{if(!n)return;const t=function(t){const e=function(){if("undefined"==typeof window)return[];try{const t=sessionStorage.getItem(r);return t?JSON.parse(t):[]}catch{return[]}}().filter(e=>e!==t),n=[t,...e].slice(0,20);try{sessionStorage.setItem(r,JSON.stringify(n))}catch{}return n}(n);e(t)},[n]),null}
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
* Small sparkle/sprinkle icon indicating Holmes AI suggestion.
|
|
5
|
-
* Tooltip shows "Holmes suggestion" on hover.
|
|
6
|
-
*/
|
|
7
|
-
export function HolmesSprinkleIcon({ className }) {
|
|
8
|
-
return (_jsx("span", { className: `inline-flex items-center justify-center ${className ?? ""}`, title: "Personalised for you", "aria-label": "Personalised for you", children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-3.5 h-3.5 text-aurora-accent", children: [_jsx("path", { d: "M12 1l1.5 4.5L18 7l-4.5 1.5L12 13l-1.5-4.5L6 7l4.5-1.5L12 1z" }), _jsx("path", { d: "M5 16l1 3 3 1-3 1-1 3-1-3-3-1 3-1 1-3z", opacity: "0.7" }), _jsx("path", { d: "M19 19l1 3 3 1-3 1-1 3-1-3-3-1 3-1 1-3z", opacity: "0.7" })] }) }));
|
|
9
|
-
}
|
|
1
|
+
import{jsx as e,jsxs as l}from"react/jsx-runtime";export function HolmesSprinkleIcon({className:a}){return e("span",{className:`inline-flex items-center justify-center ${a??""}`,title:"Personalised for you","aria-label":"Personalised for you",children:l("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor",className:"w-3.5 h-3.5 text-aurora-accent",children:[e("path",{d:"M12 1l1.5 4.5L18 7l-4.5 1.5L12 13l-1.5-4.5L6 7l4.5-1.5L12 1z"}),e("path",{d:"M5 16l1 3 3 1-3 1-1 3-1-3-3-1 3-1 1-3z",opacity:"0.7"}),e("path",{d:"M19 19l1 3 3 1-3 1-1 3-1-3-3-1 3-1 1-3z",opacity:"0.7"})]})})}
|
|
@@ -1,33 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { holmesTidbits } from "../lib/aurora";
|
|
6
|
-
import { HolmesSprinkleIcon } from "./HolmesSprinkleIcon";
|
|
7
|
-
const CATEGORY_LABELS = {
|
|
8
|
-
origin: "Origin",
|
|
9
|
-
pairing: "Pairs well",
|
|
10
|
-
tip: "Tip",
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Holmes tidbits - origin, pairing, tip for recipes, ingredients, products.
|
|
14
|
-
* Renders nothing if no tidbits. Prominent but unobtrusive.
|
|
15
|
-
*/
|
|
16
|
-
export function HolmesTidbits({ entity, entityType = "recipe", variant = "default", }) {
|
|
17
|
-
const [tidbits, setTidbits] = useState([]);
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!entity?.trim()) {
|
|
20
|
-
setTidbits([]);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
holmesTidbits(entity.trim(), entityType)
|
|
24
|
-
.then((res) => setTidbits(res.tidbits ?? []))
|
|
25
|
-
.catch(() => setTidbits([]));
|
|
26
|
-
}, [entity, entityType]);
|
|
27
|
-
if (tidbits.length === 0)
|
|
28
|
-
return null;
|
|
29
|
-
if (variant === "compact") {
|
|
30
|
-
return (_jsx("div", { className: "flex flex-wrap gap-2", children: tidbits.slice(0, 2).map((t) => (_jsxs("span", { className: "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md bg-aurora-primary/10 text-aurora-muted text-xs border border-aurora-primary/20", children: [_jsx(HolmesSprinkleIcon, {}), _jsx("span", { children: t.content })] }, t.id))) }));
|
|
31
|
-
}
|
|
32
|
-
return (_jsxs("div", { className: "p-4 rounded-xl bg-aurora-surface/60 border border-aurora-border", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2.5", children: [_jsx(HolmesSprinkleIcon, { className: "shrink-0" }), _jsx("span", { className: "text-xs font-medium text-aurora-muted uppercase tracking-wider", children: "Holmes insight" })] }), _jsx("div", { className: "space-y-2.5", children: tidbits.slice(0, 3).map((t) => (_jsxs("div", { children: [_jsx("span", { className: "text-[10px] uppercase tracking-wider text-aurora-muted/80 mr-1.5", children: CATEGORY_LABELS[t.category] ?? t.category }), _jsxs("p", { className: "text-sm text-aurora-text leading-relaxed", children: [t.content, t.source_url && (_jsx(Link, { href: t.source_url, target: "_blank", rel: "noopener noreferrer", className: "ml-1.5 text-aurora-accent hover:underline text-xs", children: "Learn more" }))] })] }, t.id))) })] }));
|
|
33
|
-
}
|
|
1
|
+
import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as a,useEffect as t}from"react";import i from"next/link";import{holmesTidbits as n}from"../lib/aurora";import{HolmesSprinkleIcon as s}from"./HolmesSprinkleIcon";const c={origin:"Origin",pairing:"Pairs well",tip:"Tip"};export function HolmesTidbits({entity:l,entityType:o="recipe",variant:m="default"}){const[d,p]=a([]);return t(()=>{l?.trim()?n(l.trim(),o).then(e=>p(e.tidbits??[])).catch(()=>p([])):p([])},[l,o]),0===d.length?null:"compact"===m?e("div",{className:"flex flex-wrap gap-2",children:d.slice(0,2).map(a=>r("span",{className:"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md bg-aurora-primary/10 text-aurora-muted text-xs border border-aurora-primary/20",children:[e(s,{}),e("span",{children:a.content})]},a.id))}):r("div",{className:"p-4 rounded-xl bg-aurora-surface/60 border border-aurora-border",children:[r("div",{className:"flex items-center gap-2 mb-2.5",children:[e(s,{className:"shrink-0"}),e("span",{className:"text-xs font-medium text-aurora-muted uppercase tracking-wider",children:"Holmes insight"})]}),e("div",{className:"space-y-2.5",children:d.slice(0,3).map(a=>r("div",{children:[e("span",{className:"text-[10px] uppercase tracking-wider text-aurora-muted/80 mr-1.5",children:c[a.category]??a.category}),r("p",{className:"text-sm text-aurora-text leading-relaxed",children:[a.content,a.source_url&&e(i,{href:a.source_url,target:"_blank",rel:"noopener noreferrer",className:"ml-1.5 text-aurora-accent hover:underline text-xs",children:"Learn more"})]})]},a.id))})]})}
|
|
@@ -1,5 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
/** Matches product card structure exactly for loading state - preserves layout width */
|
|
3
|
-
export function ProductCardSkeleton() {
|
|
4
|
-
return (_jsxs("div", { className: "group p-4 rounded-xl bg-aurora-surface border border-aurora-border overflow-hidden w-full min-w-[160px] min-h-[280px] flex flex-col", children: [_jsxs("div", { className: "block", children: [_jsx("div", { className: "aspect-square rounded-lg bg-aurora-surface-hover mb-3 overflow-hidden skeleton-shimmer" }), _jsx("div", { className: "h-4 rounded skeleton-shimmer w-3/4 mb-1" }), _jsx("div", { className: "h-4 rounded skeleton-shimmer w-1/4" })] }), _jsx("div", { className: "mt-auto pt-3", children: _jsx("div", { className: "h-12 rounded-xl skeleton-shimmer w-full" }) })] }));
|
|
5
|
-
}
|
|
1
|
+
import{jsx as e,jsxs as r}from"react/jsx-runtime";export function ProductCardSkeleton(){return r("div",{className:"group p-4 rounded-xl bg-aurora-surface border border-aurora-border overflow-hidden w-full min-w-[160px] min-h-[280px] flex flex-col",children:[r("div",{className:"block",children:[e("div",{className:"aspect-square rounded-lg bg-aurora-surface-hover mb-3 overflow-hidden skeleton-shimmer"}),e("div",{className:"h-4 rounded skeleton-shimmer w-3/4 mb-1"}),e("div",{className:"h-4 rounded skeleton-shimmer w-1/4"})]}),e("div",{className:"mt-auto pt-3",children:e("div",{className:"h-12 rounded-xl skeleton-shimmer w-full"})})]})}
|
|
@@ -1,29 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
export function ProductDetailTabs({ record }) {
|
|
5
|
-
const [active, setActive] = useState("details");
|
|
6
|
-
const features = record.features;
|
|
7
|
-
const featuresList = Array.isArray(features)
|
|
8
|
-
? features
|
|
9
|
-
: typeof features === "string"
|
|
10
|
-
? (() => {
|
|
11
|
-
try {
|
|
12
|
-
const p = JSON.parse(features);
|
|
13
|
-
return Array.isArray(p) ? p : [];
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return [];
|
|
17
|
-
}
|
|
18
|
-
})()
|
|
19
|
-
: [];
|
|
20
|
-
const storageInstructions = record.storage_instructions;
|
|
21
|
-
const tabs = [
|
|
22
|
-
{ id: "details", label: "Product Details" },
|
|
23
|
-
{ id: "nutrition", label: "Nutrition Facts" },
|
|
24
|
-
{ id: "feedback", label: "Customer Feedback" },
|
|
25
|
-
];
|
|
26
|
-
return (_jsxs("div", { className: "pattern-well rounded-xl border border-aurora-border p-6", children: [_jsx("div", { className: "flex gap-4 border-b border-aurora-border", children: tabs.map((t) => (_jsx("button", { type: "button", onClick: () => setActive(t.id), className: `py-3 font-medium border-b-2 transition-colors -mb-[2px] ${active === t.id
|
|
27
|
-
? "border-aurora-accent text-aurora-accent"
|
|
28
|
-
: "border-transparent text-aurora-muted hover:text-aurora-text"}`, children: t.label }, t.id))) }), _jsxs("div", { className: "py-6", children: [active === "details" && (_jsxs("div", { className: "space-y-4", children: [_jsxs("p", { className: "text-aurora-muted", children: [String(record.description ?? record.name ?? ""), " - High quality product."] }), featuresList.length > 0 && (_jsx("ul", { className: "list-disc list-inside space-y-1", children: featuresList.map((f, i) => (_jsx("li", { children: String(f) }, i))) })), storageInstructions && (_jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "Storage Instructions" }), _jsx("p", { className: "text-aurora-muted text-sm", children: storageInstructions })] }))] })), active === "nutrition" && (_jsx("p", { className: "text-aurora-muted", children: "Nutrition information will be displayed here when available." })), active === "feedback" && (_jsx("p", { className: "text-aurora-muted", children: "Customer reviews will be displayed here when available." }))] })] }));
|
|
29
|
-
}
|
|
1
|
+
import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as a}from"react";export function ProductDetailTabs({record:t}){const[i,s]=a("details"),l=t.features,d=Array.isArray(l)?l:"string"==typeof l?(()=>{try{const e=JSON.parse(l);return Array.isArray(e)?e:[]}catch{return[]}})():[],o=t.storage_instructions;return r("div",{className:"pattern-well rounded-xl border border-aurora-border p-6",children:[e("div",{className:"flex gap-4 border-b border-aurora-border",children:[{id:"details",label:"Product Details"},{id:"nutrition",label:"Nutrition Facts"},{id:"feedback",label:"Customer Feedback"}].map(r=>e("button",{type:"button",onClick:()=>s(r.id),className:"py-3 font-medium border-b-2 transition-colors -mb-[2px] "+(i===r.id?"border-aurora-accent text-aurora-accent":"border-transparent text-aurora-muted hover:text-aurora-text"),children:r.label},r.id))}),r("div",{className:"py-6",children:["details"===i&&r("div",{className:"space-y-4",children:[r("p",{className:"text-aurora-muted",children:[String(t.description??t.name??"")," - High quality product."]}),d.length>0&&e("ul",{className:"list-disc list-inside space-y-1",children:d.map((r,a)=>e("li",{children:String(r)},a))}),o&&r("div",{children:[e("h4",{className:"font-semibold mb-2",children:"Storage Instructions"}),e("p",{className:"text-aurora-muted text-sm",children:o})]})]}),"nutrition"===i&&e("p",{className:"text-aurora-muted",children:"Nutrition information will be displayed here when available."}),"feedback"===i&&e("p",{className:"text-aurora-muted",children:"Customer reviews will be displayed here when available."})]})]})}
|
|
@@ -1,33 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { resolveProductImageUrl, getImageUrlFromRecord, getThumbnailImageUrl, } from "../lib/image-url";
|
|
5
|
-
import { useStoreConfigImageBase } from "./StoreConfigContext";
|
|
6
|
-
const DEFAULT_FALLBACK = (_jsx("span", { className: "w-full h-full flex items-center justify-center text-aurora-muted text-2xl", "aria-hidden": true, children: "\u2013" }));
|
|
7
|
-
const objectFitClass = {
|
|
8
|
-
cover: "object-cover",
|
|
9
|
-
contain: "object-contain",
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Product image with onError fallback to avoid broken image icons.
|
|
13
|
-
* Uses imageBaseUrl from store config (or baseUrl prop) for relative URLs.
|
|
14
|
-
* Use objectFit="contain" for product cards to preserve portrait/landscape aspect ratios.
|
|
15
|
-
*/
|
|
16
|
-
export function ProductImage({ src, alt = "", baseUrl, className, fallback = DEFAULT_FALLBACK, record, objectFit = "cover", thumbnail = false, }) {
|
|
17
|
-
const [errored, setErrored] = useState(false);
|
|
18
|
-
const configBase = useStoreConfigImageBase();
|
|
19
|
-
const rawUrl = record !== undefined ? getImageUrlFromRecord(record) : src;
|
|
20
|
-
let resolved = resolveProductImageUrl(rawUrl, baseUrl ?? configBase);
|
|
21
|
-
if (resolved && thumbnail) {
|
|
22
|
-
resolved = getThumbnailImageUrl(resolved) ?? resolved;
|
|
23
|
-
}
|
|
24
|
-
const fitClass = objectFitClass[objectFit];
|
|
25
|
-
const base = (className ?? "w-full h-full")
|
|
26
|
-
.replace(/\bobject-(cover|contain)\b/g, "")
|
|
27
|
-
.trim();
|
|
28
|
-
const mergedClassName = base ? `${base} ${fitClass}`.trim() : `w-full h-full ${fitClass}`;
|
|
29
|
-
if (!resolved || errored) {
|
|
30
|
-
return (_jsx("div", { className: "w-full h-full flex items-center justify-center min-h-[1px]", children: fallback }));
|
|
31
|
-
}
|
|
32
|
-
return (_jsx("img", { src: resolved, alt: alt, className: mergedClassName, onError: () => setErrored(true), suppressHydrationWarning: true }));
|
|
33
|
-
}
|
|
1
|
+
import{jsx as e}from"react/jsx-runtime";import{useState as t}from"react";import{resolveProductImageUrl as r,getImageUrlFromRecord as l,getThumbnailImageUrl as o}from"../lib/image-url";import{useStoreConfigImageBase as c}from"./StoreConfigContext";const a=e("span",{className:"w-full h-full flex items-center justify-center text-aurora-muted text-2xl","aria-hidden":!0,children:"–"}),i={cover:"object-cover",contain:"object-contain"};export function ProductImage({src:n,alt:s="",baseUrl:m,className:f,fallback:u=a,record:b,objectFit:d="cover",thumbnail:p=!1}){const[x,h]=t(!1),j=c(),g=void 0!==b?l(b):n;let v=r(g,m??j);v&&p&&(v=o(v)??v);const w=i[d],N=(f??"w-full h-full").replace(/\bobject-(cover|contain)\b/g,"").trim(),y=N?`${N} ${w}`.trim():`w-full h-full ${w}`;return!v||x?e("div",{className:"w-full h-full flex items-center justify-center min-h-[1px]",children:u}):e("img",{src:v,alt:s,className:y,onError:()=>h(!0),suppressHydrationWarning:!0})}
|
|
@@ -1,25 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { ProductImage } from "./ProductImage";
|
|
5
|
-
function getImageUrls(record) {
|
|
6
|
-
const images = record.images;
|
|
7
|
-
if (Array.isArray(images) && images.length > 0) {
|
|
8
|
-
return [...new Set(images.filter((u) => typeof u === "string"))];
|
|
9
|
-
}
|
|
10
|
-
const primary = ["image_url", "image", "thumbnail", "photo"].find((f) => record[f]);
|
|
11
|
-
const primaryUrl = primary ? String(record[primary]) : null;
|
|
12
|
-
const extras = ["image_2", "image_3", "image_4"].filter((f) => record[f]).map((f) => String(record[f]));
|
|
13
|
-
if (primaryUrl)
|
|
14
|
-
return [primaryUrl, ...extras];
|
|
15
|
-
return extras;
|
|
16
|
-
}
|
|
17
|
-
export function ProductImageGallery({ record }) {
|
|
18
|
-
const urls = getImageUrls(record);
|
|
19
|
-
const [selected, setSelected] = useState(0);
|
|
20
|
-
if (urls.length === 0) {
|
|
21
|
-
return (_jsx("div", { className: "pattern-well rounded-component overflow-hidden aspect-square flex items-center justify-center text-aurora-muted text-6xl", children: "-" }));
|
|
22
|
-
}
|
|
23
|
-
const mainUrl = urls[selected] ?? urls[0];
|
|
24
|
-
return (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "pattern-well rounded-xl overflow-hidden aspect-square shadow-sm ring-1 ring-aurora-border/50 p-4", children: _jsx(ProductImage, { src: mainUrl, className: "w-full h-full object-contain cursor-zoom-in", fallback: _jsx("span", { className: "w-full h-full flex items-center justify-center text-aurora-muted text-4xl", children: "-" }) }) }), urls.length > 1 && (_jsx("div", { className: "flex gap-2 overflow-x-auto pb-1", children: urls.map((url, i) => (_jsx("button", { type: "button", onClick: () => setSelected(i), className: `shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 bg-aurora-surface-hover transition-colors ${selected === i ? "border-aurora-primary" : "border-aurora-border hover:border-aurora-primary/50"}`, children: _jsx(ProductImage, { src: url, className: "w-full h-full object-contain", fallback: _jsx("span", { className: "w-full h-full flex items-center justify-center text-aurora-muted text-sm", children: "-" }) }) }, i))) }))] }));
|
|
25
|
-
}
|
|
1
|
+
import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t}from"react";import{ProductImage as a}from"./ProductImage";export function ProductImageGallery({record:l}){const o=function(e){const r=e.images;if(Array.isArray(r)&&r.length>0)return[...new Set(r.filter(e=>"string"==typeof e))];const t=["image_url","image","thumbnail","photo"].find(r=>e[r]),a=t?String(e[t]):null,l=["image_2","image_3","image_4"].filter(r=>e[r]).map(r=>String(e[r]));return a?[a,...l]:l}(l),[n,s]=t(0);if(0===o.length)return e("div",{className:"pattern-well rounded-component overflow-hidden aspect-square flex items-center justify-center text-aurora-muted text-6xl",children:"-"});const i=o[n]??o[0];return r("div",{className:"space-y-3",children:[e("div",{className:"pattern-well rounded-xl overflow-hidden aspect-square shadow-sm ring-1 ring-aurora-border/50 p-4",children:e(a,{src:i,className:"w-full h-full object-contain cursor-zoom-in",fallback:e("span",{className:"w-full h-full flex items-center justify-center text-aurora-muted text-4xl",children:"-"})})}),o.length>1&&e("div",{className:"flex gap-2 overflow-x-auto pb-1",children:o.map((r,t)=>e("button",{type:"button",onClick:()=>s(t),className:"shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 bg-aurora-surface-hover transition-colors "+(n===t?"border-aurora-primary":"border-aurora-border hover:border-aurora-primary/50"),children:e(a,{src:r,className:"w-full h-full object-contain",fallback:e("span",{className:"w-full h-full flex items-center justify-center text-aurora-muted text-sm",children:"-"})})},t))})]})}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type SearchDropdownVariant = "default" | "embedded";
|
|
2
|
+
export declare function SearchDropdown({ vendorId, placeholder, fullWidth, variant, excludeDietary, getRecipeSuggestion, }: {
|
|
2
3
|
vendorId?: string;
|
|
3
4
|
placeholder?: string;
|
|
4
5
|
/** When true, input fills container (e.g. for CommandSurface hero). */
|
|
5
6
|
fullWidth?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* `default` — bordered field for nav / standalone use.
|
|
9
|
+
* `embedded` — borderless inner field; parent supplies the chrome (e.g. CommandSurface).
|
|
10
|
+
*/
|
|
11
|
+
variant?: SearchDropdownVariant;
|
|
6
12
|
/** Dietary exclusions for search (e.g. from app context). */
|
|
7
13
|
excludeDietary?: string[];
|
|
8
14
|
/** Optional grocery-style recipe line suggestion from query (e.g. cart-intelligence). */
|
|
9
15
|
getRecipeSuggestion?: (query: string) => string | null;
|
|
10
16
|
}): import("react/jsx-runtime").JSX.Element;
|
|
11
|
-
//# sourceMappingURL=SearchDropdown.d.ts.map
|
|
@@ -1,145 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect, useRef, useCallback } from "react";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { Search } from "lucide-react";
|
|
6
|
-
import { formatPrice, toCents } from "../lib/format-price";
|
|
7
|
-
import { search } from "../lib/aurora";
|
|
8
|
-
import { holmesSearch } from "../lib/holmes-events";
|
|
9
|
-
import { useCart } from "./CartProvider";
|
|
10
|
-
import { ProductImage } from "./ProductImage";
|
|
11
|
-
const RECENT_KEY = "aurora-search-recent";
|
|
12
|
-
const RECENT_MAX = 5;
|
|
13
|
-
function loadRecent() {
|
|
14
|
-
if (typeof window === "undefined")
|
|
15
|
-
return [];
|
|
16
|
-
try {
|
|
17
|
-
const stored = localStorage.getItem(RECENT_KEY);
|
|
18
|
-
return stored ? JSON.parse(stored) : [];
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function saveRecent(terms) {
|
|
25
|
-
if (typeof window === "undefined")
|
|
26
|
-
return;
|
|
27
|
-
localStorage.setItem(RECENT_KEY, JSON.stringify(terms.slice(0, RECENT_MAX)));
|
|
28
|
-
}
|
|
29
|
-
export function SearchDropdown({ vendorId, placeholder = "Search milk, bananas, pasta…", fullWidth, excludeDietary = [], getRecipeSuggestion, }) {
|
|
30
|
-
const [query, setQuery] = useState("");
|
|
31
|
-
const [hits, setHits] = useState([]);
|
|
32
|
-
const [loading, setLoading] = useState(false);
|
|
33
|
-
const [open, setOpen] = useState(false);
|
|
34
|
-
const [recentSearches, setRecentSearches] = useState([]);
|
|
35
|
-
const debounceRef = useRef(null);
|
|
36
|
-
const containerRef = useRef(null);
|
|
37
|
-
const { addItem } = useCart();
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
setRecentSearches(loadRecent());
|
|
40
|
-
}, []);
|
|
41
|
-
const addToRecent = useCallback((term) => {
|
|
42
|
-
const t = term.trim().toLowerCase();
|
|
43
|
-
if (!t)
|
|
44
|
-
return;
|
|
45
|
-
setRecentSearches((prev) => {
|
|
46
|
-
const next = [t, ...prev.filter((x) => x !== t)].slice(0, RECENT_MAX);
|
|
47
|
-
saveRecent(next);
|
|
48
|
-
return next;
|
|
49
|
-
});
|
|
50
|
-
}, []);
|
|
51
|
-
const doSearch = useCallback(async (q) => {
|
|
52
|
-
if (!q.trim()) {
|
|
53
|
-
setHits([]);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
holmesSearch(q.trim());
|
|
57
|
-
setLoading(true);
|
|
58
|
-
try {
|
|
59
|
-
const res = await search({
|
|
60
|
-
q: q.trim(),
|
|
61
|
-
limit: 12,
|
|
62
|
-
vendorId,
|
|
63
|
-
excludeDietary: excludeDietary.length ? excludeDietary : undefined,
|
|
64
|
-
});
|
|
65
|
-
setHits(res.hits ?? []);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
setHits([]);
|
|
69
|
-
}
|
|
70
|
-
finally {
|
|
71
|
-
setLoading(false);
|
|
72
|
-
}
|
|
73
|
-
}, [vendorId, excludeDietary]);
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (debounceRef.current)
|
|
76
|
-
clearTimeout(debounceRef.current);
|
|
77
|
-
if (!query.trim()) {
|
|
78
|
-
setHits([]);
|
|
79
|
-
if (!open)
|
|
80
|
-
return;
|
|
81
|
-
setOpen(true);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
setOpen(true);
|
|
85
|
-
debounceRef.current = setTimeout(() => doSearch(query), 180);
|
|
86
|
-
return () => {
|
|
87
|
-
if (debounceRef.current)
|
|
88
|
-
clearTimeout(debounceRef.current);
|
|
89
|
-
};
|
|
90
|
-
}, [query, doSearch, open]);
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
function handleClickOutside(e) {
|
|
93
|
-
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
94
|
-
setOpen(false);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
98
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
99
|
-
}, []);
|
|
100
|
-
const categories = [
|
|
101
|
-
...new Set(hits
|
|
102
|
-
.map((h) => h.category_name ?? h.category)
|
|
103
|
-
.filter(Boolean)),
|
|
104
|
-
].slice(0, 3);
|
|
105
|
-
const brands = [
|
|
106
|
-
...new Set(hits
|
|
107
|
-
.map((h) => h.brand ?? h.brand_name)
|
|
108
|
-
.filter(Boolean)),
|
|
109
|
-
].slice(0, 3);
|
|
110
|
-
const handleProductSelect = (hit, quickAdd) => {
|
|
111
|
-
addToRecent(query);
|
|
112
|
-
if (quickAdd && hit.price != null && Number(hit.price) > 0 && hit.tableSlug) {
|
|
113
|
-
addItem({
|
|
114
|
-
recordId: hit.recordId,
|
|
115
|
-
tableSlug: hit.tableSlug,
|
|
116
|
-
name: hit.name ?? hit.title ?? hit.snippet ?? hit.recordId ?? "",
|
|
117
|
-
unitAmount: toCents(hit.price) ?? 0,
|
|
118
|
-
imageUrl: hit.image_url,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
const showRecent = open && query.trim() && !loading && hits.length === 0 && recentSearches.length > 0;
|
|
123
|
-
const recipeSuggestion = getRecipeSuggestion?.(query) ?? null;
|
|
124
|
-
const showRecipeSuggestion = open && query.trim().length >= 2 && recipeSuggestion && !loading;
|
|
125
|
-
return (_jsxs("div", { ref: containerRef, className: `relative w-full ${fullWidth ? "" : "max-w-[280px]"}`, children: [_jsxs("div", { className: "relative flex items-center min-h-9", children: [_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 flex items-center justify-center pointer-events-none text-aurora-muted", children: _jsx(Search, { className: "w-4 h-4 shrink-0" }) }), _jsx("input", { type: "search", value: query, onChange: (e) => setQuery(e.target.value), onFocus: () => (query.trim() || recentSearches.length) && setOpen(true), placeholder: placeholder, className: "w-full pl-10 pr-3 py-2 h-9 text-sm rounded-lg bg-aurora-surface border border-aurora-border text-aurora-text placeholder:text-aurora-muted focus:outline-none focus:ring-1 focus:ring-aurora-primary/50 focus:border-aurora-primary/70", "aria-label": "Search products" })] }), open && (query.trim() || showRecent) && (_jsx("div", { className: "absolute top-full left-0 right-0 mt-1 rounded-component bg-aurora-surface border border-aurora-border shadow-xl z-[9999] max-h-96 overflow-y-auto", children: loading ? (_jsx("div", { className: "p-4 text-aurora-muted text-sm", children: "Searching\u2026" })) : hits.length === 0 && !showRecent && !showRecipeSuggestion ? (_jsx("div", { className: "p-4 text-aurora-muted text-sm", children: "No results" })) : (_jsxs("div", { className: "py-2", children: [showRecipeSuggestion && (_jsx("div", { className: "px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-b border-aurora-border", children: "Suggestions" })), showRecipeSuggestion && (_jsxs(Link, { href: `/catalogue?q=${encodeURIComponent(recipeSuggestion.replace("?", "").split(" ")[0].toLowerCase())}`, onClick: () => {
|
|
126
|
-
addToRecent(recipeSuggestion);
|
|
127
|
-
setOpen(false);
|
|
128
|
-
}, className: "flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors border-b border-aurora-border", children: [_jsx(Search, { className: "w-4 h-4 text-aurora-muted shrink-0" }), _jsx("span", { className: "font-medium truncate", children: recipeSuggestion })] })), hits.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-b border-aurora-border", children: "Products" }), _jsx("ul", { children: hits.slice(0, 6).map((hit) => (_jsx("li", { className: "group/item", children: _jsxs("div", { className: "flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors", children: [_jsxs(Link, { href: `/catalogue/${hit.recordId}`, onClick: () => {
|
|
129
|
-
handleProductSelect(hit);
|
|
130
|
-
setOpen(false);
|
|
131
|
-
}, className: "flex items-center gap-3 flex-1 min-w-0", children: [_jsx("div", { className: "w-10 h-10 rounded-component bg-aurora-surface-hover shrink-0 overflow-hidden", children: _jsx(ProductImage, { src: hit.image_url, className: "w-full h-full", objectFit: "contain", thumbnail: true, fallback: _jsx("div", { className: "w-full h-full flex items-center justify-center text-aurora-muted text-xs", children: "-" }) }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "font-medium truncate", children: hit.name ?? hit.title ?? hit.snippet ?? hit.recordId }), hit.price != null && Number(hit.price) > 0 && (_jsx("p", { className: "text-sm text-aurora-primary font-semibold", children: formatPrice(toCents(hit.price) ?? 0) }))] })] }), hit.price != null && Number(hit.price) > 0 && (_jsx("button", { type: "button", onClick: (e) => {
|
|
132
|
-
e.preventDefault();
|
|
133
|
-
handleProductSelect(hit, true);
|
|
134
|
-
setOpen(false);
|
|
135
|
-
}, className: "shrink-0 px-3 py-1.5 rounded-lg bg-aurora-primary text-white text-xs font-medium hover:bg-aurora-primary-dark transition-colors opacity-0 group-hover/item:opacity-100", children: "Quick add" }))] }) }, `${hit.tableSlug}-${hit.recordId}`))) }), categories.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-t border-b border-aurora-border mt-1", children: "Categories" }), _jsx("ul", { children: categories.map((cat) => (_jsx("li", { children: _jsx(Link, { href: `/catalogue?category=${encodeURIComponent(String(cat).toLowerCase().replace(/\s+/g, "-"))}`, onClick: () => {
|
|
136
|
-
addToRecent(query);
|
|
137
|
-
setOpen(false);
|
|
138
|
-
}, className: "flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors", children: _jsx("span", { className: "font-medium truncate", children: cat }) }) }, cat))) })] })), brands.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-t border-b border-aurora-border mt-1", children: "Brands" }), _jsx("ul", { children: brands.map((brand) => (_jsx("li", { children: _jsx(Link, { href: `/catalogue?q=${encodeURIComponent(brand)}`, onClick: () => {
|
|
139
|
-
addToRecent(query);
|
|
140
|
-
setOpen(false);
|
|
141
|
-
}, className: "flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors", children: _jsx("span", { className: "font-medium truncate", children: brand }) }) }, brand))) })] }))] })), showRecent && (_jsxs(_Fragment, { children: [_jsx("div", { className: "px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-b border-aurora-border", children: "Recent searches" }), _jsx("ul", { children: recentSearches.map((term) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => {
|
|
142
|
-
setQuery(term);
|
|
143
|
-
doSearch(term);
|
|
144
|
-
}, className: "w-full text-left flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors", children: [_jsx(Search, { className: "w-4 h-4 text-aurora-muted shrink-0" }), _jsx("span", { className: "font-medium truncate", children: term })] }) }, term))) })] }))] })) }))] }));
|
|
145
|
-
}
|
|
1
|
+
import{jsx as e,jsxs as r,Fragment as t}from"react/jsx-runtime";import{useState as a,useEffect as o,useRef as n,useCallback as i}from"react";import l from"next/link";import{Search as c}from"lucide-react";import{formatPrice as s,toCents as d}from"../lib/format-price";import{search as u}from"../lib/aurora";import{holmesSearch as m}from"../lib/holmes-events";import{useCart as p}from"./CartProvider";import{ProductImage as h}from"./ProductImage";const f="aurora-search-recent";export function SearchDropdown({vendorId:b,placeholder:g="Search milk, bananas, pasta…",fullWidth:x,variant:v="default",excludeDietary:w=[],getRecipeSuggestion:N}){const[y,k]=a(""),[S,C]=a([]),[I,j]=a(!1),[$,L]=a(!1),[R,D]=a([]),U=n(null),_=n(null),{addItem:q}=p();o(()=>{D(function(){if("undefined"==typeof window)return[];try{const e=localStorage.getItem(f);return e?JSON.parse(e):[]}catch{return[]}}())},[]);const B=i(e=>{const r=e.trim().toLowerCase();r&&D(e=>{const t=[r,...e.filter(e=>e!==r)].slice(0,5);var a;return a=t,"undefined"!=typeof window&&localStorage.setItem(f,JSON.stringify(a.slice(0,5))),t})},[]),F=i(async e=>{if(e.trim()){m(e.trim()),j(!0);try{const r=await u({q:e.trim(),limit:12,vendorId:b,excludeDietary:w.length?w:void 0});C(r.hits??[])}catch{C([])}finally{j(!1)}}else C([])},[b,w]);o(()=>{if(U.current&&clearTimeout(U.current),y.trim())return L(!0),U.current=setTimeout(()=>F(y),180),()=>{U.current&&clearTimeout(U.current)};C([]),$&&L(!0)},[y,F,$]),o(()=>{function e(e){_.current&&!_.current.contains(e.target)&&L(!1)}return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[]);const P=[...new Set(S.map(e=>e.category_name??e.category).filter(Boolean))].slice(0,3),T=[...new Set(S.map(e=>e.brand??e.brand_name).filter(Boolean))].slice(0,3),E=(e,r)=>{B(y),r&&null!=e.price&&Number(e.price)>0&&e.tableSlug&&q({recordId:e.recordId,tableSlug:e.tableSlug,name:e.name??e.title??e.snippet??e.recordId??"",unitAmount:d(e.price)??0,imageUrl:e.image_url})},J=$&&y.trim()&&!I&&0===S.length&&R.length>0,O=N?.(y)??null,z=$&&y.trim().length>=2&&O&&!I;return r("div",{ref:_,className:"relative w-full "+(x?"":"max-w-[280px]"),children:[r("div",{className:"default"===v?"flex w-full items-center rounded-lg border border-aurora-border/90 bg-aurora-surface shadow-sm transition-colors focus-within:border-aurora-primary/70 focus-within:ring-1 focus-within:ring-aurora-primary/40":"flex w-full items-center rounded-lg bg-transparent",children:[e("span",{className:"flex h-10 w-10 shrink-0 items-center justify-center text-aurora-muted pointer-events-none","aria-hidden":!0,children:e(c,{className:"h-4 w-4 shrink-0"})}),e("input",{type:"search",value:y,onChange:e=>k(e.target.value),onFocus:()=>(y.trim()||R.length)&&L(!0),placeholder:g,className:"flex-1 min-w-0 h-10 py-0 pr-3 text-sm leading-normal text-aurora-text placeholder:text-aurora-muted border-0 bg-transparent shadow-none outline-none ring-0 focus:outline-none focus:ring-0 [&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none","aria-label":"Search products"})]}),$&&(y.trim()||J)&&e("div",{className:"absolute top-full left-0 right-0 mt-1 rounded-component bg-aurora-surface border border-aurora-border shadow-xl z-[9999] max-h-96 overflow-y-auto",children:I?e("div",{className:"p-4 text-aurora-muted text-sm",children:"Searching…"}):0!==S.length||J||z?r("div",{className:"py-2",children:[z&&e("div",{className:"px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-b border-aurora-border",children:"Suggestions"}),z&&r(l,{href:`/catalogue?q=${encodeURIComponent(O.replace("?","").split(" ")[0].toLowerCase())}`,onClick:()=>{B(O),L(!1)},className:"flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors border-b border-aurora-border",children:[e(c,{className:"w-4 h-4 text-aurora-muted shrink-0"}),e("span",{className:"font-medium truncate",children:O})]}),S.length>0&&r(t,{children:[e("div",{className:"px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-b border-aurora-border",children:"Products"}),e("ul",{children:S.slice(0,6).map(t=>e("li",{className:"group/item",children:r("div",{className:"flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors",children:[r(l,{href:`/catalogue/${t.recordId}`,onClick:()=>{E(t),L(!1)},className:"flex items-center gap-3 flex-1 min-w-0",children:[e("div",{className:"w-10 h-10 rounded-component bg-aurora-surface-hover shrink-0 overflow-hidden",children:e(h,{src:t.image_url,className:"w-full h-full",objectFit:"contain",thumbnail:!0,fallback:e("div",{className:"w-full h-full flex items-center justify-center text-aurora-muted text-xs",children:"-"})})}),r("div",{className:"flex-1 min-w-0",children:[e("p",{className:"font-medium truncate",children:t.name??t.title??t.snippet??t.recordId}),null!=t.price&&Number(t.price)>0&&e("p",{className:"text-sm text-aurora-primary font-semibold",children:s(d(t.price)??0)})]})]}),null!=t.price&&Number(t.price)>0&&e("button",{type:"button",onClick:e=>{e.preventDefault(),E(t,!0),L(!1)},className:"shrink-0 px-3 py-1.5 rounded-lg bg-aurora-primary text-white text-xs font-medium hover:bg-aurora-primary-dark transition-colors opacity-0 group-hover/item:opacity-100",children:"Quick add"})]})},`${t.tableSlug}-${t.recordId}`))}),P.length>0&&r(t,{children:[e("div",{className:"px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-t border-b border-aurora-border mt-1",children:"Categories"}),e("ul",{children:P.map(r=>e("li",{children:e(l,{href:`/catalogue?category=${encodeURIComponent(String(r).toLowerCase().replace(/\s+/g,"-"))}`,onClick:()=>{B(y),L(!1)},className:"flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors",children:e("span",{className:"font-medium truncate",children:r})})},r))})]}),T.length>0&&r(t,{children:[e("div",{className:"px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-t border-b border-aurora-border mt-1",children:"Brands"}),e("ul",{children:T.map(r=>e("li",{children:e(l,{href:`/catalogue?q=${encodeURIComponent(r)}`,onClick:()=>{B(y),L(!1)},className:"flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors",children:e("span",{className:"font-medium truncate",children:r})})},r))})]})]}),J&&r(t,{children:[e("div",{className:"px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-aurora-muted border-b border-aurora-border",children:"Recent searches"}),e("ul",{children:R.map(t=>e("li",{children:r("button",{type:"button",onClick:()=>{k(t),F(t)},className:"w-full text-left flex items-center gap-3 px-4 py-2 hover:bg-aurora-surface-hover transition-colors",children:[e(c,{className:"w-4 h-4 text-aurora-muted shrink-0"}),e("span",{className:"font-medium truncate",children:t})]})},t))})]})]}):e("div",{className:"p-4 text-aurora-muted text-sm",children:"No results"})})]})}
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import Link from "next/link";
|
|
4
|
-
import { useCart } from "./CartProvider";
|
|
5
|
-
import { formatPrice } from "../lib/format-price";
|
|
6
|
-
import { ShoppingBag } from "lucide-react";
|
|
7
|
-
const SHIPPING_CENTS = 250;
|
|
8
|
-
const FREE_DELIVERY_THRESHOLD_CENTS = 2500; // £25
|
|
9
|
-
/** Persistent smart cart panel - bridges browsing to conversion */
|
|
10
|
-
export function SmartCartPanel() {
|
|
11
|
-
const { items, total } = useCart();
|
|
12
|
-
const count = items.reduce((s, i) => s + i.quantity, 0);
|
|
13
|
-
const shipping = count > 0 ? SHIPPING_CENTS : 0;
|
|
14
|
-
const grandTotal = total + shipping;
|
|
15
|
-
const toFreeDelivery = Math.max(0, FREE_DELIVERY_THRESHOLD_CENTS - total);
|
|
16
|
-
if (count === 0)
|
|
17
|
-
return null;
|
|
18
|
-
return (_jsx("div", { className: "sticky bottom-0 z-40 mt-10 px-6 py-4 rounded-2xl bg-aurora-surface border border-aurora-border shadow-xl shadow-aurora-primary/5", children: _jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between sm:gap-6 gap-3 max-w-4xl mx-auto", children: [_jsxs("div", { className: "flex items-center gap-4 min-w-0", children: [_jsx("span", { className: "flex items-center justify-center w-11 h-11 shrink-0 rounded-xl bg-aurora-primary/15 text-aurora-primary", children: _jsx(ShoppingBag, { className: "w-5 h-5" }) }), _jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "font-semibold text-aurora-text text-base", children: [count, " ", count === 1 ? "item" : "items", " \u00B7 ", formatPrice(grandTotal)] }), toFreeDelivery > 0 && (_jsxs("p", { className: "text-sm text-aurora-primary font-medium mt-0.5", children: ["Add ", formatPrice(toFreeDelivery), " more for free delivery"] })), toFreeDelivery <= 0 && total > 0 && (_jsx("p", { className: "text-sm text-aurora-primary font-medium mt-0.5", children: "You've unlocked free delivery" }))] })] }), _jsx(Link, { href: "/cart", className: "inline-flex items-center justify-center px-6 py-2.5 rounded-xl bg-aurora-primary text-white font-semibold text-sm hover:bg-aurora-primary-dark transition-colors shrink-0 shadow-lg shadow-aurora-primary/25", children: "View cart \u00B7 Checkout" })] }) }));
|
|
19
|
-
}
|
|
1
|
+
import{jsx as r,jsxs as e}from"react/jsx-runtime";import a from"next/link";import{useCart as t}from"./CartProvider";import{formatPrice as i}from"../lib/format-price";import{ShoppingBag as m}from"lucide-react";export function SmartCartPanel(){const{items:o,total:s}=t(),l=o.reduce((r,e)=>r+e.quantity,0),n=s+(l>0?250:0),c=Math.max(0,2500-s);return 0===l?null:r("div",{className:"sticky bottom-0 z-40 mt-10 px-6 py-4 rounded-2xl bg-aurora-surface border border-aurora-border shadow-xl shadow-aurora-primary/5",children:e("div",{className:"flex flex-col sm:flex-row sm:items-center sm:justify-between sm:gap-6 gap-3 max-w-4xl mx-auto",children:[e("div",{className:"flex items-center gap-4 min-w-0",children:[r("span",{className:"flex items-center justify-center w-11 h-11 shrink-0 rounded-xl bg-aurora-primary/15 text-aurora-primary",children:r(m,{className:"w-5 h-5"})}),e("div",{className:"min-w-0",children:[e("p",{className:"font-semibold text-aurora-text text-base",children:[l," ",1===l?"item":"items"," · ",i(n)]}),c>0&&e("p",{className:"text-sm text-aurora-primary font-medium mt-0.5",children:["Add ",i(c)," more for free delivery"]}),c<=0&&s>0&&r("p",{className:"text-sm text-aurora-primary font-medium mt-0.5",children:"You've unlocked free delivery"})]})]}),r(a,{href:"/cart",className:"inline-flex items-center justify-center px-6 py-2.5 rounded-xl bg-aurora-primary text-white font-semibold text-sm hover:bg-aurora-primary-dark transition-colors shrink-0 shadow-lg shadow-aurora-primary/25",children:"View cart · Checkout"})]})})}
|
|
@@ -1,25 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useRef, useEffect } from "react";
|
|
4
|
-
import { ChevronDown } from "lucide-react";
|
|
5
|
-
import { SORT_OPTIONS } from "./CatalogueFilters";
|
|
6
|
-
export function SortDropdown({ value, onChange }) {
|
|
7
|
-
const [open, setOpen] = useState(false);
|
|
8
|
-
const ref = useRef(null);
|
|
9
|
-
const currentLabel = SORT_OPTIONS.find((o) => o.id === value)?.label ?? "Featured";
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
function handleClickOutside(e) {
|
|
12
|
-
if (ref.current && !ref.current.contains(e.target)) {
|
|
13
|
-
setOpen(false);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
17
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
18
|
-
}, []);
|
|
19
|
-
return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), className: "flex items-center gap-1.5 px-4 py-2 rounded-xl border border-aurora-border bg-aurora-surface text-sm font-medium text-aurora-text hover:border-aurora-primary/40 transition-colors", children: [currentLabel, _jsx(ChevronDown, { className: `w-4 h-4 text-aurora-muted transition-transform ${open ? "rotate-180" : ""}` })] }), open && (_jsx("div", { className: "absolute top-full right-0 mt-1 py-1 rounded-xl bg-aurora-surface border border-aurora-border shadow-xl min-w-[180px] z-50", children: SORT_OPTIONS.map((opt) => (_jsx("button", { type: "button", onClick: () => {
|
|
20
|
-
onChange(opt.id);
|
|
21
|
-
setOpen(false);
|
|
22
|
-
}, className: `w-full text-left px-4 py-2.5 text-sm font-medium transition-colors ${value === opt.id
|
|
23
|
-
? "text-aurora-primary bg-aurora-accent/10"
|
|
24
|
-
: "text-aurora-muted hover:text-aurora-text hover:bg-aurora-surface-hover"}`, children: opt.label }, opt.id))) }))] }));
|
|
25
|
-
}
|
|
1
|
+
import{jsx as r,jsxs as e}from"react/jsx-runtime";import{useState as t,useRef as o,useEffect as a}from"react";import{ChevronDown as n}from"lucide-react";import{SORT_OPTIONS as u}from"./CatalogueFilters";export function SortDropdown({value:i,onChange:s}){const[d,l]=t(!1),m=o(null),c=u.find(r=>r.id===i)?.label??"Featured";return a(()=>{function r(r){m.current&&!m.current.contains(r.target)&&l(!1)}return document.addEventListener("mousedown",r),()=>document.removeEventListener("mousedown",r)},[]),e("div",{ref:m,className:"relative",children:[e("button",{type:"button",onClick:()=>l(r=>!r),className:"flex items-center gap-1.5 px-4 py-2 rounded-xl border border-aurora-border bg-aurora-surface text-sm font-medium text-aurora-text hover:border-aurora-primary/40 transition-colors",children:[c,r(n,{className:"w-4 h-4 text-aurora-muted transition-transform "+(d?"rotate-180":"")})]}),d&&r("div",{className:"absolute top-full right-0 mt-1 py-1 rounded-xl bg-aurora-surface border border-aurora-border shadow-xl min-w-[180px] z-50",children:u.map(e=>r("button",{type:"button",onClick:()=>{s(e.id),l(!1)},className:"w-full text-left px-4 py-2.5 text-sm font-medium transition-colors "+(i===e.id?"text-aurora-primary bg-aurora-accent/10":"text-aurora-muted hover:text-aurora-text hover:bg-aurora-surface-hover"),children:e.label},e.id))})]})}
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext, useEffect, useState, } from "react";
|
|
4
|
-
import { getStoreConfig } from "../lib/aurora";
|
|
5
|
-
const StoreConfigContext = createContext(null);
|
|
6
|
-
export function StoreConfigProvider({ children }) {
|
|
7
|
-
const [imageBaseUrl, setImageBaseUrl] = useState(null);
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
let cancelled = false;
|
|
10
|
-
getStoreConfig()
|
|
11
|
-
.then((config) => {
|
|
12
|
-
if (!cancelled && config) {
|
|
13
|
-
const base = config.imageBaseUrl;
|
|
14
|
-
if (typeof base === "string")
|
|
15
|
-
setImageBaseUrl(base);
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
.catch(() => { });
|
|
19
|
-
return () => { cancelled = true; };
|
|
20
|
-
}, []);
|
|
21
|
-
return (_jsx(StoreConfigContext.Provider, { value: { imageBaseUrl }, children: children }));
|
|
22
|
-
}
|
|
23
|
-
export function useStoreConfigImageBase() {
|
|
24
|
-
const ctx = useContext(StoreConfigContext);
|
|
25
|
-
return ctx?.imageBaseUrl ?? null;
|
|
26
|
-
}
|
|
1
|
+
import{jsx as r}from"react/jsx-runtime";import{createContext as e,useContext as t,useEffect as o,useState as n}from"react";import{getStoreConfig as i}from"../lib/aurora";const a=e(null);export function StoreConfigProvider({children:e}){const[t,l]=n(null);return o(()=>{let r=!1;return i().then(e=>{if(!r&&e){const r=e.imageBaseUrl;"string"==typeof r&&l(r)}}).catch(()=>{}),()=>{r=!0}},[]),r(a.Provider,{value:{imageBaseUrl:t},children:e})}export function useStoreConfigImageBase(){const r=t(a);return r?.imageBaseUrl??null}
|