@standhigher/besttrack-page-extension 0.4.0 → 0.5.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 CHANGED
@@ -25,7 +25,8 @@ should inject `transport` instead of inventing a new request body:
25
25
 
26
26
  That path reuses the original lookup: `POST /track/query?_t=`, snake_case
27
27
  `{ order_number, email, tracking_number, lang }`, one retry, independent
28
- `POST /products/recommend` with `{ page, page_size }`, and the original
28
+ `POST /products/recommend` with `{ page, page_size }`, the original Embla
29
+ recommendation carousel, and the original
29
30
  progress / shipping / empty-state mapping. The host only supplies fetch,
30
31
  App Proxy prefix, and credentials.
31
32
 
package/dist/index.d.ts CHANGED
@@ -307,6 +307,7 @@ type ReadyToGoRuntimeProviderProps = {
307
307
  /**
308
308
  * Shopify Track Page live transport. When `query` is omitted, lookups use the
309
309
  * original `/track/query` body, `_t` cache-bust, retry, and mapping rules.
310
+ * Recommendations still POST `/products/recommend` on mount, even if `query` is set.
310
311
  */
311
312
  transport?: ShopifyTrackPageTransport;
312
313
  /** When omitted, URL deep-link auto-query is on only for the Shopify Track Page `transport` path. */
package/dist/index.js CHANGED
@@ -14,7 +14,8 @@ import {
14
14
  } from "./chunk-KD7KFFFK.js";
15
15
 
16
16
  // src/ready-to-go.tsx
17
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState as useState2 } from "react";
17
+ import { createContext, useCallback as useCallback2, useContext, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2 } from "react";
18
+ import { parseProductReferences } from "@standhigher/puck-page-builder/runtime";
18
19
 
19
20
  // src/shopify-track-page/i18n/locales.ts
20
21
  var SUPPORTED_LOCALES = [
@@ -825,8 +826,10 @@ function createShopifyRecommendationsQuery(post) {
825
826
  }
826
827
 
827
828
  // src/track-page-display.tsx
828
- import { useState } from "react";
829
- import { jsx, jsxs } from "react/jsx-runtime";
829
+ import { useCallback, useEffect, useMemo, useState } from "react";
830
+ import Autoplay from "embla-carousel-autoplay";
831
+ import useEmblaCarousel from "embla-carousel-react";
832
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
830
833
  var pageFont = { fontFamily: "var(--pb-font-family, Inter, system-ui, sans-serif)" };
831
834
  var contentWidth = {
832
835
  width: "min(1248px, 100%)",
@@ -953,22 +956,81 @@ function PackageContents({ items }) {
953
956
  }) });
954
957
  }
955
958
  function RecommendationCards({ items }) {
956
- return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 24, overflowX: "auto", paddingBottom: 8 }, children: items.map((item) => /* @__PURE__ */ jsxs("a", { href: safeHref(item.href), style: { flex: "0 0 262px", width: 262, maxWidth: "100%", textDecoration: "none", color: "inherit" }, children: [
957
- /* @__PURE__ */ jsx("div", { style: { width: 262, height: 262, maxWidth: "100%", borderRadius: "var(--pb-radius, 8px)", border: "1px solid #E3E3E3", overflow: "hidden", background: "#f1f5f9" }, children: /* @__PURE__ */ jsx(ProductImage, { src: item.imageUrl, alt: item.title, size: 262 }) }),
958
- /* @__PURE__ */ jsxs("div", { style: { padding: "12px 8px", textAlign: "center", fontSize: 14 }, children: [
959
- /* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#334155" }, children: item.title }),
960
- item.price && formatTrackingPageMoney(item.price)?.amount ? /* @__PURE__ */ jsx("p", { style: { margin: "4px 0 0", fontWeight: 600, color: "#0f172a" }, children: formatTrackingPageMoney(item.price).amount }) : null,
961
- item.description ? /* @__PURE__ */ jsx("p", { style: { margin: "4px 0 0", color: "#64748b", fontSize: 13 }, children: item.description }) : null
962
- ] })
963
- ] }, item.id)) });
959
+ const autoplay = useMemo(() => Autoplay({ delay: 3e3, stopOnInteraction: false, stopOnMouseEnter: true }), []);
960
+ const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: "start", slidesToScroll: 1 }, [autoplay]);
961
+ const [canScrollPrev, setCanScrollPrev] = useState(false);
962
+ const [canScrollNext, setCanScrollNext] = useState(false);
963
+ const [hideButtons, setHideButtons] = useState(false);
964
+ const onSelect = useCallback(() => {
965
+ if (!emblaApi) return;
966
+ setCanScrollPrev(emblaApi.canScrollPrev());
967
+ setCanScrollNext(emblaApi.canScrollNext());
968
+ }, [emblaApi]);
969
+ useEffect(() => {
970
+ if (!emblaApi) return;
971
+ emblaApi.on("select", onSelect);
972
+ emblaApi.on("reInit", onSelect);
973
+ const frame = requestAnimationFrame(onSelect);
974
+ return () => {
975
+ cancelAnimationFrame(frame);
976
+ emblaApi.off("select", onSelect);
977
+ emblaApi.off("reInit", onSelect);
978
+ };
979
+ }, [emblaApi, onSelect]);
980
+ useEffect(() => {
981
+ if (typeof window.matchMedia !== "function") return;
982
+ const media = window.matchMedia("(max-width: 768px)");
983
+ const update = () => setHideButtons(media.matches);
984
+ const frame = requestAnimationFrame(update);
985
+ media.addEventListener("change", update);
986
+ return () => {
987
+ cancelAnimationFrame(frame);
988
+ media.removeEventListener("change", update);
989
+ };
990
+ }, []);
991
+ if (items.length === 0) return null;
992
+ const arrow = (points) => /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("polyline", { points }) });
993
+ const buttonStyle = {
994
+ position: "absolute",
995
+ top: 115,
996
+ zIndex: 2,
997
+ width: 32,
998
+ height: 32,
999
+ borderRadius: 99,
1000
+ border: 0,
1001
+ background: "#fff",
1002
+ boxShadow: "0 0 0.5px rgba(0, 0, 0, 0.12)",
1003
+ display: "flex",
1004
+ alignItems: "center",
1005
+ justifyContent: "center",
1006
+ cursor: "pointer",
1007
+ color: "#333",
1008
+ padding: 0
1009
+ };
1010
+ return /* @__PURE__ */ jsxs("div", { "aria-label": "Recommended products carousel", style: { position: "relative", marginLeft: 48, marginRight: 48 }, children: [
1011
+ !hideButtons && canScrollPrev ? /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Previous", onClick: () => emblaApi?.scrollPrev(), style: { ...buttonStyle, left: -40 }, children: arrow("15 18 9 12 15 6") }) : null,
1012
+ /* @__PURE__ */ jsx("div", { ref: emblaRef, style: { overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { display: "flex" }, children: items.map((item) => {
1013
+ const href = safeHref(item.href);
1014
+ const card = /* @__PURE__ */ jsxs(Fragment, { children: [
1015
+ /* @__PURE__ */ jsx("div", { style: { width: 262, height: 262, maxWidth: "100%", borderRadius: 8, border: "1px solid #E3E3E3", overflow: "hidden", background: "#f1f5f9" }, children: /* @__PURE__ */ jsx(ProductImage, { src: item.imageUrl, alt: item.title, size: 262 }) }),
1016
+ /* @__PURE__ */ jsxs("div", { style: { padding: "12px 8px", textAlign: "center", fontSize: 14 }, children: [
1017
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#334155" }, children: item.title }),
1018
+ item.price && formatTrackingPageMoney(item.price)?.amount ? /* @__PURE__ */ jsx("p", { style: { margin: "4px 0 0", fontWeight: 600, color: "#0f172a" }, children: formatTrackingPageMoney(item.price).amount }) : null,
1019
+ item.description ? /* @__PURE__ */ jsx("p", { style: { margin: "4px 0 0", color: "#64748b", fontSize: 13 }, children: item.description }) : null
1020
+ ] })
1021
+ ] });
1022
+ return /* @__PURE__ */ jsx("div", { style: { flex: "0 0 286px", minWidth: 0, paddingRight: 24, boxSizing: "border-box" }, children: href ? /* @__PURE__ */ jsx("a", { href, style: { display: "block", width: 262, maxWidth: "100%", textDecoration: "none", color: "inherit" }, children: card }) : /* @__PURE__ */ jsx("div", { style: { width: 262, maxWidth: "100%" }, children: card }) }, item.id);
1023
+ }) }) }),
1024
+ !hideButtons && canScrollNext ? /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Next", onClick: () => emblaApi?.scrollNext(), style: { ...buttonStyle, right: -40 }, children: arrow("9 18 15 12 9 6") }) : null
1025
+ ] });
964
1026
  }
965
- function TrackingPageAdSlot({ ad, editor = false }) {
1027
+ function TrackingPageAdSlot({ ad }) {
966
1028
  const [failedUrl, setFailedUrl] = useState();
967
1029
  const imageUrl = safeImageUrl(ad?.imageUrl);
968
- if ((!imageUrl || failedUrl === imageUrl) && !editor) return null;
969
- const content = imageUrl && failedUrl !== imageUrl ? /* @__PURE__ */ jsx("img", { src: imageUrl, alt: ad?.alt ?? "Promotion", loading: "lazy", onError: () => setFailedUrl(imageUrl), style: { display: "block", width: "100%", height: "auto", objectFit: "cover" } }) : /* @__PURE__ */ jsx("div", { "aria-label": "Advertisement placeholder", style: { display: "grid", minHeight: 120, placeItems: "center", border: "1px dashed #cbd5e1", background: "#f8fafc", color: "#64748b", fontSize: 13 }, children: "Advertisement space" });
1030
+ if (!imageUrl || failedUrl === imageUrl) return null;
1031
+ const content = /* @__PURE__ */ jsx("img", { src: imageUrl, alt: ad?.alt ?? "Promotion", loading: "lazy", onError: () => setFailedUrl(imageUrl), style: { display: "block", width: "100%", height: "auto", objectFit: "cover" } });
970
1032
  const href = safeHref(ad?.href);
971
- if (!imageUrl || !href) return /* @__PURE__ */ jsx("div", { "data-tracking-page-ad": true, children: content });
1033
+ if (!href) return /* @__PURE__ */ jsx("div", { "data-tracking-page-ad": true, children: content });
972
1034
  return /* @__PURE__ */ jsx("a", { "data-tracking-page-ad": true, href, target: "_blank", rel: "noopener noreferrer", children: content });
973
1035
  }
974
1036
  function EstimatedDeliveryCard({ dateText }) {
@@ -1001,7 +1063,7 @@ function TrackingNotFound() {
1001
1063
  }
1002
1064
 
1003
1065
  // src/ready-to-go.tsx
1004
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1066
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1005
1067
  var initialRuntime = {
1006
1068
  phase: "idle",
1007
1069
  recommendations: { phase: "idle", items: [] },
@@ -1037,23 +1099,20 @@ async function queryMockReadyToGoTracking(request) {
1037
1099
  }
1038
1100
  function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecommendations, transport, autoQueryFromUrl, watermark }) {
1039
1101
  const [state, setState] = useState2({ phase: "idle" });
1040
- const [recommendations, setRecommendations] = useState2(() => ({
1041
- phase: queryRecommendations || transport && !injectedQuery ? "loading" : "idle",
1042
- items: []
1043
- }));
1102
+ const [recommendations, setRecommendations] = useState2(() => queryRecommendations || transport ? { phase: "loading", items: [] } : { phase: "success", items: previewReadyToGoTracking().recommendations ?? [] });
1044
1103
  const requestId = useRef(0);
1045
- const resolvedQuery = useMemo(() => {
1104
+ const resolvedQuery = useMemo2(() => {
1046
1105
  if (injectedQuery) return injectedQuery;
1047
1106
  if (transport) return createShopifyTrackQuery(transport);
1048
1107
  return void 0;
1049
1108
  }, [injectedQuery, transport]);
1050
- const resolvedRecommendations = useMemo(() => {
1109
+ const resolvedRecommendations = useMemo2(() => {
1051
1110
  if (queryRecommendations) return queryRecommendations;
1052
- if (transport && !injectedQuery) return createShopifyRecommendationsQuery(transport.post);
1111
+ if (transport) return createShopifyRecommendationsQuery(transport.post);
1053
1112
  return void 0;
1054
- }, [injectedQuery, queryRecommendations, transport]);
1113
+ }, [queryRecommendations, transport]);
1055
1114
  const live = Boolean(resolvedQuery);
1056
- useEffect(() => {
1115
+ useEffect2(() => {
1057
1116
  if (!resolvedRecommendations) return;
1058
1117
  let active = true;
1059
1118
  void resolvedRecommendations().then((items) => {
@@ -1067,7 +1126,7 @@ function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecomme
1067
1126
  active = false;
1068
1127
  };
1069
1128
  }, [resolvedRecommendations]);
1070
- const query = useCallback(async (request) => {
1129
+ const query = useCallback2(async (request) => {
1071
1130
  const currentRequestId = ++requestId.current;
1072
1131
  setState({ phase: "loading" });
1073
1132
  try {
@@ -1080,11 +1139,11 @@ function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecomme
1080
1139
  }
1081
1140
  }, [live, resolvedQuery]);
1082
1141
  const resolveAutoQuery = autoQueryFromUrl ?? Boolean(transport && !injectedQuery);
1083
- const resolvedWatermark = useMemo(
1142
+ const resolvedWatermark = useMemo2(
1084
1143
  () => watermark ?? { visible: !shouldHidePoweredBy() },
1085
1144
  [watermark]
1086
1145
  );
1087
- const value = useMemo(() => ({ ...state, query, recommendations, autoQueryFromUrl: resolveAutoQuery, watermark: resolvedWatermark }), [query, recommendations, resolveAutoQuery, resolvedWatermark, state]);
1146
+ const value = useMemo2(() => ({ ...state, query, recommendations, autoQueryFromUrl: resolveAutoQuery, watermark: resolvedWatermark }), [query, recommendations, resolveAutoQuery, resolvedWatermark, state]);
1088
1147
  return /* @__PURE__ */ jsx3(ReadyToGoRuntimeContext.Provider, { value, children });
1089
1148
  }
1090
1149
  function useReadyToGoRuntime() {
@@ -1199,7 +1258,7 @@ function eventsFrom(result) {
1199
1258
  }
1200
1259
  function ProgressResult({ result, showEstimatedDelivery = true }) {
1201
1260
  const steps = result.progress?.length ? result.progress : defaultProgress(result.status);
1202
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
1261
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1203
1262
  /* @__PURE__ */ jsxs3("p", { style: { margin: 0, fontSize: 20, lineHeight: 1.4, color: "#000" }, children: [
1204
1263
  "Tracking: ",
1205
1264
  result.trackingNumber
@@ -1209,14 +1268,14 @@ function ProgressResult({ result, showEstimatedDelivery = true }) {
1209
1268
  /* @__PURE__ */ jsx3(TrackingProgress, { steps })
1210
1269
  ] });
1211
1270
  }
1212
- function DeliveryResult({ heading, contentsHeading, carrierHeading, result, editor = false }) {
1271
+ function DeliveryResult({ heading, contentsHeading, carrierHeading, result }) {
1213
1272
  return /* @__PURE__ */ jsxs3("div", { style: { ...contentWidth, display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 280px), 500px))", gap: 40, justifyContent: "center", alignItems: "start" }, children: [
1214
1273
  /* @__PURE__ */ jsxs3("div", { children: [
1215
1274
  /* @__PURE__ */ jsx3("h3", { style: { margin: 0, fontSize: 20, lineHeight: "20px", fontWeight: 700 }, children: heading }),
1216
1275
  /* @__PURE__ */ jsx3(ShippingTimeline, { events: eventsFrom(result) })
1217
1276
  ] }),
1218
1277
  /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: 24 }, children: [
1219
- /* @__PURE__ */ jsx3(TrackingPageAdSlot, { ad: result.ad, editor }),
1278
+ /* @__PURE__ */ jsx3(TrackingPageAdSlot, { ad: result.ad }),
1220
1279
  /* @__PURE__ */ jsxs3("div", { children: [
1221
1280
  /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 700 }, children: contentsHeading }),
1222
1281
  /* @__PURE__ */ jsx3(PackageContents, { items: result.orderItems ?? [] })
@@ -1258,7 +1317,7 @@ function ReadyToGoQueryEditor(block) {
1258
1317
  ] }) });
1259
1318
  }
1260
1319
  function ReadyToGoProgressEditor() {
1261
- return /* @__PURE__ */ jsx3("section", { "aria-label": "Ready-to-go progress editor", style: { ...pageFont, background: "var(--pb-color-background, #fff)", color: "var(--pb-color-text, #0f172a)", borderBottom: "1px solid #f1f5f9" }, children: /* @__PURE__ */ jsx3("div", { style: { ...contentWidth, textAlign: "center", width: "min(1248px, 100%)" }, children: /* @__PURE__ */ jsx3(ProgressResult, { result: previewReadyToGoTracking() }) }) });
1320
+ return /* @__PURE__ */ jsx3("section", { "aria-label": "Ready-to-go progress editor", style: { ...pageFont, background: "var(--pb-color-background, #fff)", color: "var(--pb-color-text, #0f172a)", borderBottom: "1px solid #f1f5f9" }, children: /* @__PURE__ */ jsx3("div", { style: { ...contentWidth, textAlign: "center", width: "min(1248px, 100%)" }, children: /* @__PURE__ */ jsx3(ProgressResult, { result: previewReadyToGoTracking(), showEstimatedDelivery: false }) }) });
1262
1321
  }
1263
1322
  function ReadyToGoDeliveryEditor(block) {
1264
1323
  return /* @__PURE__ */ jsx3("section", { "aria-label": "Ready-to-go delivery editor", style: { ...pageFont, background: "var(--pb-color-background, #fff)", color: "var(--pb-color-text, #0f172a)", borderBottom: "1px solid #f1f5f9" }, children: /* @__PURE__ */ jsx3(
@@ -1267,16 +1326,22 @@ function ReadyToGoDeliveryEditor(block) {
1267
1326
  heading: /* @__PURE__ */ jsx3(InlineText, { block, name: "heading", fallback: "Shipping Details" }),
1268
1327
  contentsHeading: /* @__PURE__ */ jsx3(InlineText, { block, name: "contentsHeading", fallback: "Package Contents" }),
1269
1328
  carrierHeading: /* @__PURE__ */ jsx3(InlineText, { block, name: "carrierHeading", fallback: "Carrier" }),
1270
- result: previewReadyToGoTracking(),
1271
- editor: true
1329
+ result: previewReadyToGoTracking()
1272
1330
  }
1273
1331
  ) });
1274
1332
  }
1275
1333
  function ReadyToGoRecommendationsEditor(block) {
1276
- const preview = previewReadyToGoTracking();
1334
+ const selected = parseProductReferences(block.products).map((product) => ({
1335
+ id: product.id,
1336
+ title: product.title || product.id,
1337
+ description: "",
1338
+ imageUrl: product.imageUrl,
1339
+ href: product.handle ? `/products/${product.handle}` : void 0
1340
+ }));
1341
+ const items = selected.length ? selected : previewReadyToGoTracking().recommendations ?? [];
1277
1342
  return /* @__PURE__ */ jsx3("section", { "aria-label": "Ready-to-go recommendations editor", style: { ...pageFont, background: "var(--pb-color-background, #fff)", color: "var(--pb-color-text, #0f172a)" }, children: /* @__PURE__ */ jsxs3("div", { style: { ...contentWidth, padding: "48px 24px" }, children: [
1278
1343
  /* @__PURE__ */ jsx3("h3", { style: { margin: 0, textAlign: "center", fontSize: 20, lineHeight: "20px", fontWeight: 700 }, children: /* @__PURE__ */ jsx3(InlineText, { block, name: "heading", fallback: "You may also like..." }) }),
1279
- /* @__PURE__ */ jsx3("div", { style: { marginTop: 24 }, children: /* @__PURE__ */ jsx3(RecommendationCards, { items: preview.recommendations ?? [] }) })
1344
+ /* @__PURE__ */ jsx3("div", { style: { marginTop: 24 }, children: /* @__PURE__ */ jsx3(RecommendationCards, { items }) })
1280
1345
  ] }) });
1281
1346
  }
1282
1347
  function ReadyToGoQueryBlock(props) {
@@ -1292,7 +1357,7 @@ function ReadyToGoQueryBlock(props) {
1292
1357
  const submitLabel = text(props, "submitLabel", "Track Your Order");
1293
1358
  const trackingTabLabel = text(props, "trackingTabLabel", "Tracking Number");
1294
1359
  const orderTabLabel = text(props, "orderTabLabel", "Order Number");
1295
- useEffect(() => {
1360
+ useEffect2(() => {
1296
1361
  if (!locationState?.canAutoQuery || autoQueryStarted.current) return;
1297
1362
  autoQueryStarted.current = true;
1298
1363
  void runtime.query(locationState.tab === "tracking" ? { mode: "tracking", trackingNumber: locationState.trackingNumber } : { mode: "order", orderNumber: locationState.orderNumber, email: locationState.email });
@@ -1330,12 +1395,12 @@ function ReadyToGoQueryBlock(props) {
1330
1395
  }, style: tabStyle(mode === "order"), children: orderTabLabel })
1331
1396
  ] }),
1332
1397
  /* @__PURE__ */ jsxs3("form", { onSubmit: submit, style: { marginTop: 24, display: "flex", flexDirection: "column", gap: 16, flex: 1 }, children: [
1333
- mode === "order" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1398
+ mode === "order" ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1334
1399
  /* @__PURE__ */ jsx3("label", { htmlFor: "ready-to-go-order-number", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }, children: "Order number" }),
1335
1400
  /* @__PURE__ */ jsx3("input", { id: "ready-to-go-order-number", "aria-label": "Order number", value: orderNumber, onChange: (event) => setOrderNumber(event.target.value), placeholder: "Order Number", style: inputStyle }),
1336
1401
  /* @__PURE__ */ jsx3("label", { htmlFor: "ready-to-go-email", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }, children: "Email" }),
1337
1402
  /* @__PURE__ */ jsx3("input", { id: "ready-to-go-email", "aria-label": "Email", value: email, onChange: (event) => setEmail(event.target.value), placeholder: "Email", style: inputStyle })
1338
- ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
1403
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1339
1404
  /* @__PURE__ */ jsx3("label", { htmlFor: "ready-to-go-tracking-number", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }, children: "Tracking number" }),
1340
1405
  /* @__PURE__ */ jsx3("input", { id: "ready-to-go-tracking-number", "aria-label": "Tracking number", value: trackingNumber, onChange: (event) => setTrackingNumber(event.target.value), placeholder: "Tracking Number", style: inputStyle })
1341
1406
  ] }),
@@ -1376,25 +1441,10 @@ function ReadyToGoDeliveryBlock(props) {
1376
1441
  function ReadyToGoRecommendationsBlock(props) {
1377
1442
  const runtime = useReadyToGoRuntime();
1378
1443
  const heading = text(props, "heading", "You may also like...");
1379
- const independent = runtime.recommendations.phase !== "idle";
1380
- if (independent) {
1381
- if (runtime.recommendations.phase !== "success" || runtime.recommendations.items.length === 0) return null;
1382
- return /* @__PURE__ */ jsx3(SectionShell, { title: heading, bordered: false, children: /* @__PURE__ */ jsxs3("div", { style: { ...contentWidth, padding: "48px 24px" }, children: [
1383
- /* @__PURE__ */ jsx3("h3", { style: { margin: 0, textAlign: "center", fontSize: 20, lineHeight: "20px", fontWeight: 700 }, children: heading }),
1384
- /* @__PURE__ */ jsx3("div", { style: { marginTop: 24 }, children: /* @__PURE__ */ jsx3(RecommendationCards, { items: runtime.recommendations.items }) })
1385
- ] }) });
1386
- }
1387
- if (runtime.phase === "empty") return null;
1388
- const recommendations = runtime.result?.recommendations ?? [];
1444
+ if (runtime.recommendations.phase !== "success" || runtime.recommendations.items.length === 0) return null;
1389
1445
  return /* @__PURE__ */ jsx3(SectionShell, { title: heading, bordered: false, children: /* @__PURE__ */ jsxs3("div", { style: { ...contentWidth, padding: "48px 24px" }, children: [
1390
1446
  /* @__PURE__ */ jsx3("h3", { style: { margin: 0, textAlign: "center", fontSize: 20, lineHeight: "20px", fontWeight: 700 }, children: heading }),
1391
- /* @__PURE__ */ jsxs3("div", { style: { marginTop: 24 }, children: [
1392
- runtime.phase === "loading" ? /* @__PURE__ */ jsx3(IdleMessage, { children: "Loading recommendations\u2026" }) : null,
1393
- runtime.phase === "error" ? /* @__PURE__ */ jsx3("p", { style: { margin: 0, textAlign: "center", color: "#b42318" }, children: "Recommendations are temporarily unavailable." }) : null,
1394
- runtime.phase === "idle" ? /* @__PURE__ */ jsx3(IdleMessage, { children: "Recommendations appear with your shipment result." }) : null,
1395
- runtime.phase === "success" && recommendations.length ? /* @__PURE__ */ jsx3(RecommendationCards, { items: recommendations }) : null,
1396
- runtime.phase === "success" && !recommendations.length ? /* @__PURE__ */ jsx3(IdleMessage, { children: "No recommendations are available for this shipment." }) : null
1397
- ] })
1447
+ /* @__PURE__ */ jsx3("div", { style: { marginTop: 24 }, children: /* @__PURE__ */ jsx3(RecommendationCards, { items: runtime.recommendations.items }) })
1398
1448
  ] }) });
1399
1449
  }
1400
1450
 
@@ -1476,10 +1526,13 @@ var readyToGoBlocks = withTemplatePolicy([
1476
1526
  label: "Recommended products",
1477
1527
  category: "BestTrack Ready-to-go",
1478
1528
  targets: ["web"],
1479
- defaultProps: { heading: "You may also like..." },
1529
+ defaultProps: { heading: "You may also like...", products: [] },
1480
1530
  defaultVariant: "default",
1481
1531
  variants: [{ id: "default", label: "Default" }, { id: "grid", label: "Grid", theme: { "color.surface": "#ffffff" } }],
1482
- fields: { heading: text2("Heading") },
1532
+ fields: {
1533
+ heading: text2("Heading"),
1534
+ products: { field: "besttrack.ready-to-go.products", label: "\u63A8\u8350\u5546\u54C1 (Shopify)", control: "products", group: "Content" }
1535
+ },
1483
1536
  render: { web: ReadyToGoRecommendationsBlock, editor: ReadyToGoRecommendationsEditor }
1484
1537
  }
1485
1538
  ], ["besttrack.ready-to-go.query"]);
@@ -1537,11 +1590,11 @@ var bestTrackPageExtension = Object.freeze({
1537
1590
  import { createPageDocument as createPageDocument2 } from "@standhigher/puck-page-builder/runtime";
1538
1591
 
1539
1592
  // src/branded.tsx
1540
- import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useMemo as useMemo2, useRef as useRef3, useState as useState4 } from "react";
1593
+ import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
1541
1594
 
1542
1595
  // src/tracking-query-experience.tsx
1543
- import { useEffect as useEffect2, useId, useRef as useRef2, useState as useState3 } from "react";
1544
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1596
+ import { useEffect as useEffect3, useId, useRef as useRef2, useState as useState3 } from "react";
1597
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1545
1598
  var srOnly = {
1546
1599
  position: "absolute",
1547
1600
  width: 1,
@@ -1645,7 +1698,7 @@ function TrackingQueryCard({
1645
1698
  const tabRefs = useRef2({ tracking: null, order: null });
1646
1699
  const loading = phase === "loading";
1647
1700
  const terminalPhase = phase === "success" || phase === "empty" || phase === "error";
1648
- useEffect2(() => {
1701
+ useEffect3(() => {
1649
1702
  if (!terminalPhase) return;
1650
1703
  const card = cardRef.current;
1651
1704
  const resultNode = resultRef.current;
@@ -1722,7 +1775,7 @@ function TrackingQueryCard({
1722
1775
  clearError("tracking");
1723
1776
  }, placeholder: trackingPlaceholder, style: inputWithError("tracking") }),
1724
1777
  errorFor("tracking") ? /* @__PURE__ */ jsx4("p", { id: trackingInputId + "-error", role: "alert", style: { margin: "6px 0 0", color: "#b42318", fontSize: 13 }, children: errorFor("tracking") }) : null
1725
- ] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
1778
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
1726
1779
  /* @__PURE__ */ jsxs4("div", { children: [
1727
1780
  /* @__PURE__ */ jsx4("label", { htmlFor: orderInputId, style: srOnly, children: orderInputLabel }),
1728
1781
  /* @__PURE__ */ jsx4("input", { ref: orderInputRef, id: orderInputId, "aria-label": orderInputLabel, "aria-invalid": Boolean(errorFor("order")), "aria-describedby": errorFor("order") ? orderInputId + "-error" : void 0, value: orderNumber, onChange: (event) => {
@@ -1898,7 +1951,7 @@ function BrandedRuntimeProvider({ children, query: injectedQuery, watermark }) {
1898
1951
  const [state, setState] = useState4({ phase: "idle" });
1899
1952
  const [selectedShipmentId, setSelectedShipmentId] = useState4(null);
1900
1953
  const requestId = useRef3(0);
1901
- const query = useCallback2(async (request) => {
1954
+ const query = useCallback3(async (request) => {
1902
1955
  const currentRequestId = ++requestId.current;
1903
1956
  setState({ phase: "loading" });
1904
1957
  try {
@@ -1912,20 +1965,20 @@ function BrandedRuntimeProvider({ children, query: injectedQuery, watermark }) {
1912
1965
  setState({ phase: "error", error: "tracking-query-failed" });
1913
1966
  }
1914
1967
  }, [injectedQuery]);
1915
- const reset = useCallback2(() => {
1968
+ const reset = useCallback3(() => {
1916
1969
  requestId.current += 1;
1917
1970
  setSelectedShipmentId(null);
1918
1971
  setState({ phase: "idle" });
1919
1972
  }, []);
1920
- const displayedResult = useMemo2(() => {
1973
+ const displayedResult = useMemo3(() => {
1921
1974
  if (!state.result) return void 0;
1922
1975
  const shipment = state.result.shipments?.find((item) => item.id === selectedShipmentId);
1923
1976
  return shipment ? { ...state.result, ...shipment } : state.result;
1924
1977
  }, [selectedShipmentId, state.result]);
1925
- const selectShipment = useCallback2((id) => {
1978
+ const selectShipment = useCallback3((id) => {
1926
1979
  if (state.result?.shipments?.some((shipment) => shipment.id === id)) setSelectedShipmentId(id);
1927
1980
  }, [state.result]);
1928
- const value = useMemo2(
1981
+ const value = useMemo3(
1929
1982
  () => ({ ...state, displayedResult, selectedShipmentId, query, reset, selectShipment, watermark }),
1930
1983
  [displayedResult, query, reset, selectedShipmentId, selectShipment, state, watermark]
1931
1984
  );
@@ -2228,8 +2281,8 @@ var bestTrackBrandedExtension = Object.freeze({ name: "besttrack.branded", versi
2228
2281
  import { createPageDocument as createPageDocument3 } from "@standhigher/puck-page-builder/runtime";
2229
2282
 
2230
2283
  // src/sales.tsx
2231
- import { createContext as createContext3, useCallback as useCallback3, useContext as useContext3, useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
2232
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2284
+ import { createContext as createContext3, useCallback as useCallback4, useContext as useContext3, useMemo as useMemo4, useRef as useRef4, useState as useState5 } from "react";
2285
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2233
2286
  var initialRuntime3 = { phase: "idle", async query() {
2234
2287
  return void 0;
2235
2288
  } };
@@ -2241,7 +2294,7 @@ var gridStyle = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax
2241
2294
  function SalesRuntimeProvider({ children, query: injectedQuery, watermark, resourceResolution }) {
2242
2295
  const [state, setState] = useState5({ phase: "idle" });
2243
2296
  const requestId = useRef4(0);
2244
- const query = useCallback3(async (request) => {
2297
+ const query = useCallback4(async (request) => {
2245
2298
  const currentRequestId = ++requestId.current;
2246
2299
  setState({ phase: "loading" });
2247
2300
  try {
@@ -2254,7 +2307,7 @@ function SalesRuntimeProvider({ children, query: injectedQuery, watermark, resou
2254
2307
  setState({ phase: "error" });
2255
2308
  }
2256
2309
  }, [injectedQuery]);
2257
- const value = useMemo3(() => ({ ...state, query, watermark, resourceResolution }), [query, resourceResolution, state, watermark]);
2310
+ const value = useMemo4(() => ({ ...state, query, watermark, resourceResolution }), [query, resourceResolution, state, watermark]);
2258
2311
  return /* @__PURE__ */ jsx6(SalesRuntimeContext.Provider, { value, children });
2259
2312
  }
2260
2313
  function useSalesRuntime() {
@@ -2455,7 +2508,7 @@ function SalesOrderItemsBlock(props) {
2455
2508
  "Qty ",
2456
2509
  Number.isFinite(item.quantity) && item.quantity > 0 ? item.quantity : 1
2457
2510
  ] }),
2458
- item.description ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
2511
+ item.description ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2459
2512
  /* @__PURE__ */ jsx6("br", {}),
2460
2513
  /* @__PURE__ */ jsx6("small", { style: { color: "var(--pb-color-muted)" }, children: item.description })
2461
2514
  ] }) : null
@@ -2496,7 +2549,7 @@ function SalesProductCategoriesBlock(props) {
2496
2549
  const error = getShopifyResourceResolutionError(collection, runtime.resourceResolution);
2497
2550
  if (error) return /* @__PURE__ */ jsx6(Section, { title, children: /* @__PURE__ */ jsx6(Status, { alert: true, children: error.code === "missing" ? "This collection is no longer available." : "Collection details are temporarily unavailable." }) });
2498
2551
  if (!resolved) return /* @__PURE__ */ jsx6(Section, { title, children: /* @__PURE__ */ jsx6(Status, { children: "Collection details are resolved by the authorized runtime." }) });
2499
- const content = /* @__PURE__ */ jsxs6(Fragment3, { children: [
2552
+ const content = /* @__PURE__ */ jsxs6(Fragment4, { children: [
2500
2553
  /* @__PURE__ */ jsx6("span", { children: resolved.title }),
2501
2554
  /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\u2192" })
2502
2555
  ] });
@@ -2514,7 +2567,7 @@ function SalesFeaturedProductBlock(props) {
2514
2567
  if (error) return /* @__PURE__ */ jsx6(Section, { title, children: /* @__PURE__ */ jsx6(Status, { alert: true, children: error.code === "missing" ? "This product is no longer available." : "Product details are temporarily unavailable." }) });
2515
2568
  if (!resolved) return /* @__PURE__ */ jsx6(Section, { title, children: /* @__PURE__ */ jsx6(Status, { children: "Product details and availability are resolved by the authorized runtime." }) });
2516
2569
  const available = resolved.availability === "available";
2517
- const body = /* @__PURE__ */ jsxs6(Fragment3, { children: [
2570
+ const body = /* @__PURE__ */ jsxs6(Fragment4, { children: [
2518
2571
  /* @__PURE__ */ jsx6(ProductImage3, { src: resolved.imageUrl, alt: resolved.title }),
2519
2572
  /* @__PURE__ */ jsxs6("span", { style: { minWidth: 0, flex: 1 }, children: [
2520
2573
  /* @__PURE__ */ jsx6("strong", { children: resolved.title }),
@@ -2545,7 +2598,7 @@ function SalesRecommendationsBlock(props) {
2545
2598
  }
2546
2599
 
2547
2600
  // src/shopify-resources.tsx
2548
- import { createContext as createContext4, useCallback as useCallback4, useContext as useContext4, useState as useState6 } from "react";
2601
+ import { createContext as createContext4, useCallback as useCallback5, useContext as useContext4, useState as useState6 } from "react";
2549
2602
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2550
2603
  var ShopifyResourceBrowserContext = createContext4(void 0);
2551
2604
  function ShopifyResourcePickerProvider({ browser, children }) {
@@ -2561,7 +2614,7 @@ function ResourceField({ value, onChange, kind }) {
2561
2614
  const [items, setItems] = useState6([]);
2562
2615
  const [cursor, setCursor] = useState6();
2563
2616
  const [state, setState] = useState6("idle");
2564
- const search = useCallback4(async (nextQuery, nextCursor, append = false) => {
2617
+ const search = useCallback5(async (nextQuery, nextCursor, append = false) => {
2565
2618
  if (!browser) return;
2566
2619
  setState("loading");
2567
2620
  try {
@@ -6,15 +6,16 @@ import {
6
6
 
7
7
  // src/validation.ts
8
8
  var textField = { field: "besttrack.validation.text", control: "text" };
9
+ var productsField = { field: "besttrack.validation.products", control: "products" };
9
10
  var strictUrlField = {
10
11
  field: "besttrack.validation.url",
11
12
  control: "url",
12
13
  validation: { allowRelativeUrl: false, allowedUrlProtocols: ["https:"], allowLocalhost: true }
13
14
  };
14
- function fields(keys, urlKeys = [], resourceKeys = []) {
15
- return Object.fromEntries(keys.map((key) => [key, resourceKeys.includes(key) ? { field: "besttrack.validation.resource" } : urlKeys.includes(key) ? strictUrlField : textField]));
15
+ function fields(keys, urlKeys = [], resourceKeys = [], productsKeys = []) {
16
+ return Object.fromEntries(keys.map((key) => [key, productsKeys.includes(key) ? productsField : resourceKeys.includes(key) ? { field: "besttrack.validation.resource" } : urlKeys.includes(key) ? strictUrlField : textField]));
16
17
  }
17
- function block(type, version, keys, variants, validate, urlKeys = [], resourceKeys = []) {
18
+ function block(type, version, keys, variants, validate, urlKeys = [], resourceKeys = [], productsKeys = []) {
18
19
  return {
19
20
  type,
20
21
  version,
@@ -22,7 +23,7 @@ function block(type, version, keys, variants, validate, urlKeys = [], resourceKe
22
23
  category: "BestTrack",
23
24
  targets: ["web"],
24
25
  defaultProps: {},
25
- fields: fields(keys, urlKeys, resourceKeys),
26
+ fields: fields(keys, urlKeys, resourceKeys, productsKeys),
26
27
  defaultVariant: variants[0],
27
28
  variants: variants.map((id) => ({ id, label: id })),
28
29
  validate,
@@ -69,7 +70,7 @@ var readyToGoContracts = [
69
70
  block("besttrack.ready-to-go.query", 1, ["heading", "submitLabel", "defaultTrackingNumber", "defaultOrderNumber", "defaultQueryMode", "trackingTabLabel", "orderTabLabel"], ["default"]),
70
71
  block("besttrack.ready-to-go.progress", 1, [], ["default"]),
71
72
  block("besttrack.ready-to-go.delivery", 1, ["heading", "contentsHeading", "carrierHeading"], ["default", "compact"]),
72
- block("besttrack.ready-to-go.recommendations", 1, ["heading"], ["default", "grid"])
73
+ block("besttrack.ready-to-go.recommendations", 1, ["heading", "products"], ["default", "grid"], void 0, [], [], ["products"])
73
74
  ];
74
75
  var brandedContracts = [
75
76
  block("besttrack.branded.announcement", 1, ["message", "href"], ["brand", "minimal"], void 0, ["href"]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standhigher/besttrack-page-extension",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "BestTrack business extension entry point for @standhigher/puck-page-builder.",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,7 +20,9 @@
20
20
  }
21
21
  },
22
22
  "dependencies": {
23
- "@standhigher/puck-page-builder": "0.11.0"
23
+ "embla-carousel-autoplay": "^8.6.0",
24
+ "embla-carousel-react": "^8.6.0",
25
+ "@standhigher/puck-page-builder": "0.12.0"
24
26
  },
25
27
  "devDependencies": {
26
28
  "tsup": "^8.5.1",