@standhigher/besttrack-page-extension 0.4.0 → 0.5.1

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,14 +307,17 @@ 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. */
313
314
  autoQueryFromUrl?: boolean;
314
315
  /** Host-decided display state. Omitted values follow the original powered-by hide rule. */
315
316
  watermark?: TrackingPageWatermark;
317
+ /** Admin-only promotion preview; live storefronts continue to use result.ad. */
318
+ adPreview?: TrackingPageAd | null;
316
319
  };
317
- declare function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecommendations, transport, autoQueryFromUrl, watermark }: ReadyToGoRuntimeProviderProps): react.JSX.Element;
320
+ declare function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecommendations, transport, autoQueryFromUrl, watermark, adPreview }: ReadyToGoRuntimeProviderProps): react.JSX.Element;
318
321
 
319
322
  /**
320
323
  * Compatibility metadata for template-aware hosts.
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,23 +956,95 @@ 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
+ const frame = window.requestAnimationFrame(onSelect);
972
+ emblaApi.on("select", onSelect);
973
+ emblaApi.on("reInit", onSelect);
974
+ return () => {
975
+ window.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
+ update();
985
+ media.addEventListener("change", update);
986
+ return () => media.removeEventListener("change", update);
987
+ }, []);
988
+ if (items.length === 0) return null;
989
+ 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 }) });
990
+ const buttonStyle = {
991
+ position: "absolute",
992
+ top: 115,
993
+ zIndex: 2,
994
+ width: 32,
995
+ height: 32,
996
+ borderRadius: 99,
997
+ border: 0,
998
+ background: "#fff",
999
+ boxShadow: "0 0 0.5px rgba(0, 0, 0, 0.12)",
1000
+ display: "flex",
1001
+ alignItems: "center",
1002
+ justifyContent: "center",
1003
+ cursor: "pointer",
1004
+ color: "#333",
1005
+ padding: 0
1006
+ };
1007
+ return /* @__PURE__ */ jsxs("div", { "aria-label": "Recommended products carousel", style: { position: "relative", marginLeft: 48, marginRight: 48 }, children: [
1008
+ !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,
1009
+ /* @__PURE__ */ jsx("div", { ref: emblaRef, style: { overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { display: "flex" }, children: items.map((item) => {
1010
+ const href = safeHref(item.href);
1011
+ const card = /* @__PURE__ */ jsxs(Fragment, { children: [
1012
+ /* @__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 }) }),
1013
+ /* @__PURE__ */ jsxs("div", { style: { padding: "12px 8px", textAlign: "center", fontSize: 14 }, children: [
1014
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#334155" }, children: item.title }),
1015
+ item.price && formatTrackingPageMoney(item.price)?.amount ? /* @__PURE__ */ jsx("p", { style: { margin: "4px 0 0", fontWeight: 600, color: "#0f172a" }, children: formatTrackingPageMoney(item.price).amount }) : null,
1016
+ item.description ? /* @__PURE__ */ jsx("p", { style: { margin: "4px 0 0", color: "#64748b", fontSize: 13 }, children: item.description }) : null
1017
+ ] })
1018
+ ] });
1019
+ 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);
1020
+ }) }) }),
1021
+ !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
1022
+ ] });
964
1023
  }
965
- function TrackingPageAdSlot({ ad, editor = false }) {
1024
+ var trackingPageAdSlotStyle = {
1025
+ display: "block",
1026
+ width: "100%",
1027
+ maxWidth: 500,
1028
+ aspectRatio: "20 / 9",
1029
+ borderRadius: 8,
1030
+ overflow: "hidden",
1031
+ textDecoration: "none"
1032
+ };
1033
+ var trackingPageAdImageStyle = {
1034
+ display: "block",
1035
+ width: "100%",
1036
+ height: "100%",
1037
+ objectFit: "fill",
1038
+ objectPosition: "center"
1039
+ };
1040
+ function TrackingPageAdSlot({ ad, disableLink = false }) {
966
1041
  const [failedUrl, setFailedUrl] = useState();
967
1042
  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" });
1043
+ if (!imageUrl || failedUrl === imageUrl) return null;
1044
+ const content = /* @__PURE__ */ jsx("img", { src: imageUrl, alt: ad?.alt ?? "Promotion", loading: "lazy", onError: () => setFailedUrl(imageUrl), style: trackingPageAdImageStyle });
970
1045
  const href = safeHref(ad?.href);
971
- if (!imageUrl || !href) return /* @__PURE__ */ jsx("div", { "data-tracking-page-ad": true, children: content });
972
- return /* @__PURE__ */ jsx("a", { "data-tracking-page-ad": true, href, target: "_blank", rel: "noopener noreferrer", children: content });
1046
+ if (!href || disableLink) return /* @__PURE__ */ jsx("div", { "data-tracking-page-ad": true, style: trackingPageAdSlotStyle, children: content });
1047
+ return /* @__PURE__ */ jsx("a", { "data-tracking-page-ad": true, href, target: "_blank", rel: "noopener noreferrer", style: trackingPageAdSlotStyle, children: content });
973
1048
  }
974
1049
  function EstimatedDeliveryCard({ dateText }) {
975
1050
  return /* @__PURE__ */ jsxs("div", { "aria-label": "Est. Delivery", style: { margin: "16px auto 0", width: "100%", boxSizing: "border-box", border: "1px solid transparent", borderRadius: 10, backgroundColor: "#eaf4ff", padding: "20px 22px 18px", textAlign: "left" }, children: [
@@ -1001,7 +1076,7 @@ function TrackingNotFound() {
1001
1076
  }
1002
1077
 
1003
1078
  // src/ready-to-go.tsx
1004
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1079
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1005
1080
  var initialRuntime = {
1006
1081
  phase: "idle",
1007
1082
  recommendations: { phase: "idle", items: [] },
@@ -1035,25 +1110,22 @@ function previewReadyToGoTracking(trackingNumber = "BT-2048-DEMO") {
1035
1110
  async function queryMockReadyToGoTracking(request) {
1036
1111
  return previewReadyToGoTracking(request.mode === "tracking" ? request.trackingNumber : request.orderNumber);
1037
1112
  }
1038
- function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecommendations, transport, autoQueryFromUrl, watermark }) {
1113
+ function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecommendations, transport, autoQueryFromUrl, watermark, adPreview }) {
1039
1114
  const [state, setState] = useState2({ phase: "idle" });
1040
- const [recommendations, setRecommendations] = useState2(() => ({
1041
- phase: queryRecommendations || transport && !injectedQuery ? "loading" : "idle",
1042
- items: []
1043
- }));
1115
+ const [recommendations, setRecommendations] = useState2(() => queryRecommendations || transport ? { phase: "loading", items: [] } : { phase: "success", items: previewReadyToGoTracking().recommendations ?? [] });
1044
1116
  const requestId = useRef(0);
1045
- const resolvedQuery = useMemo(() => {
1117
+ const resolvedQuery = useMemo2(() => {
1046
1118
  if (injectedQuery) return injectedQuery;
1047
1119
  if (transport) return createShopifyTrackQuery(transport);
1048
1120
  return void 0;
1049
1121
  }, [injectedQuery, transport]);
1050
- const resolvedRecommendations = useMemo(() => {
1122
+ const resolvedRecommendations = useMemo2(() => {
1051
1123
  if (queryRecommendations) return queryRecommendations;
1052
- if (transport && !injectedQuery) return createShopifyRecommendationsQuery(transport.post);
1124
+ if (transport) return createShopifyRecommendationsQuery(transport.post);
1053
1125
  return void 0;
1054
- }, [injectedQuery, queryRecommendations, transport]);
1126
+ }, [queryRecommendations, transport]);
1055
1127
  const live = Boolean(resolvedQuery);
1056
- useEffect(() => {
1128
+ useEffect2(() => {
1057
1129
  if (!resolvedRecommendations) return;
1058
1130
  let active = true;
1059
1131
  void resolvedRecommendations().then((items) => {
@@ -1067,7 +1139,7 @@ function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecomme
1067
1139
  active = false;
1068
1140
  };
1069
1141
  }, [resolvedRecommendations]);
1070
- const query = useCallback(async (request) => {
1142
+ const query = useCallback2(async (request) => {
1071
1143
  const currentRequestId = ++requestId.current;
1072
1144
  setState({ phase: "loading" });
1073
1145
  try {
@@ -1080,11 +1152,11 @@ function ReadyToGoRuntimeProvider({ children, query: injectedQuery, queryRecomme
1080
1152
  }
1081
1153
  }, [live, resolvedQuery]);
1082
1154
  const resolveAutoQuery = autoQueryFromUrl ?? Boolean(transport && !injectedQuery);
1083
- const resolvedWatermark = useMemo(
1155
+ const resolvedWatermark = useMemo2(
1084
1156
  () => watermark ?? { visible: !shouldHidePoweredBy() },
1085
1157
  [watermark]
1086
1158
  );
1087
- const value = useMemo(() => ({ ...state, query, recommendations, autoQueryFromUrl: resolveAutoQuery, watermark: resolvedWatermark }), [query, recommendations, resolveAutoQuery, resolvedWatermark, state]);
1159
+ const value = useMemo2(() => ({ ...state, adPreview, query, recommendations, autoQueryFromUrl: resolveAutoQuery, watermark: resolvedWatermark }), [adPreview, query, recommendations, resolveAutoQuery, resolvedWatermark, state]);
1088
1160
  return /* @__PURE__ */ jsx3(ReadyToGoRuntimeContext.Provider, { value, children });
1089
1161
  }
1090
1162
  function useReadyToGoRuntime() {
@@ -1199,7 +1271,7 @@ function eventsFrom(result) {
1199
1271
  }
1200
1272
  function ProgressResult({ result, showEstimatedDelivery = true }) {
1201
1273
  const steps = result.progress?.length ? result.progress : defaultProgress(result.status);
1202
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
1274
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1203
1275
  /* @__PURE__ */ jsxs3("p", { style: { margin: 0, fontSize: 20, lineHeight: 1.4, color: "#000" }, children: [
1204
1276
  "Tracking: ",
1205
1277
  result.trackingNumber
@@ -1209,14 +1281,14 @@ function ProgressResult({ result, showEstimatedDelivery = true }) {
1209
1281
  /* @__PURE__ */ jsx3(TrackingProgress, { steps })
1210
1282
  ] });
1211
1283
  }
1212
- function DeliveryResult({ heading, contentsHeading, carrierHeading, result, editor = false }) {
1284
+ function DeliveryResult({ heading, contentsHeading, carrierHeading, result, editing }) {
1213
1285
  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
1286
  /* @__PURE__ */ jsxs3("div", { children: [
1215
1287
  /* @__PURE__ */ jsx3("h3", { style: { margin: 0, fontSize: 20, lineHeight: "20px", fontWeight: 700 }, children: heading }),
1216
1288
  /* @__PURE__ */ jsx3(ShippingTimeline, { events: eventsFrom(result) })
1217
1289
  ] }),
1218
1290
  /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: 24 }, children: [
1219
- /* @__PURE__ */ jsx3(TrackingPageAdSlot, { ad: result.ad, editor }),
1291
+ /* @__PURE__ */ jsx3(TrackingPageAdSlot, { ad: result.ad, disableLink: editing }),
1220
1292
  /* @__PURE__ */ jsxs3("div", { children: [
1221
1293
  /* @__PURE__ */ jsx3("h3", { style: { margin: "0 0 12px", fontSize: 18, fontWeight: 700 }, children: contentsHeading }),
1222
1294
  /* @__PURE__ */ jsx3(PackageContents, { items: result.orderItems ?? [] })
@@ -1258,25 +1330,33 @@ function ReadyToGoQueryEditor(block) {
1258
1330
  ] }) });
1259
1331
  }
1260
1332
  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() }) }) });
1333
+ 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
1334
  }
1263
1335
  function ReadyToGoDeliveryEditor(block) {
1336
+ const { adPreview } = useReadyToGoRuntime();
1264
1337
  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(
1265
1338
  DeliveryResult,
1266
1339
  {
1267
1340
  heading: /* @__PURE__ */ jsx3(InlineText, { block, name: "heading", fallback: "Shipping Details" }),
1268
1341
  contentsHeading: /* @__PURE__ */ jsx3(InlineText, { block, name: "contentsHeading", fallback: "Package Contents" }),
1269
1342
  carrierHeading: /* @__PURE__ */ jsx3(InlineText, { block, name: "carrierHeading", fallback: "Carrier" }),
1270
- result: previewReadyToGoTracking(),
1271
- editor: true
1343
+ editing: true,
1344
+ result: { ...previewReadyToGoTracking(), ad: adPreview ?? void 0 }
1272
1345
  }
1273
1346
  ) });
1274
1347
  }
1275
1348
  function ReadyToGoRecommendationsEditor(block) {
1276
- const preview = previewReadyToGoTracking();
1349
+ const selected = parseProductReferences(block.products).map((product) => ({
1350
+ id: product.id,
1351
+ title: product.title || product.id,
1352
+ description: "",
1353
+ imageUrl: product.imageUrl,
1354
+ href: product.handle ? `/products/${product.handle}` : void 0
1355
+ }));
1356
+ const items = selected.length ? selected : previewReadyToGoTracking().recommendations ?? [];
1277
1357
  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
1358
  /* @__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 ?? [] }) })
1359
+ /* @__PURE__ */ jsx3("div", { style: { marginTop: 24 }, children: /* @__PURE__ */ jsx3(RecommendationCards, { items }) })
1280
1360
  ] }) });
1281
1361
  }
1282
1362
  function ReadyToGoQueryBlock(props) {
@@ -1292,7 +1372,7 @@ function ReadyToGoQueryBlock(props) {
1292
1372
  const submitLabel = text(props, "submitLabel", "Track Your Order");
1293
1373
  const trackingTabLabel = text(props, "trackingTabLabel", "Tracking Number");
1294
1374
  const orderTabLabel = text(props, "orderTabLabel", "Order Number");
1295
- useEffect(() => {
1375
+ useEffect2(() => {
1296
1376
  if (!locationState?.canAutoQuery || autoQueryStarted.current) return;
1297
1377
  autoQueryStarted.current = true;
1298
1378
  void runtime.query(locationState.tab === "tracking" ? { mode: "tracking", trackingNumber: locationState.trackingNumber } : { mode: "order", orderNumber: locationState.orderNumber, email: locationState.email });
@@ -1330,12 +1410,12 @@ function ReadyToGoQueryBlock(props) {
1330
1410
  }, style: tabStyle(mode === "order"), children: orderTabLabel })
1331
1411
  ] }),
1332
1412
  /* @__PURE__ */ jsxs3("form", { onSubmit: submit, style: { marginTop: 24, display: "flex", flexDirection: "column", gap: 16, flex: 1 }, children: [
1333
- mode === "order" ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1413
+ mode === "order" ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1334
1414
  /* @__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
1415
  /* @__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
1416
  /* @__PURE__ */ jsx3("label", { htmlFor: "ready-to-go-email", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }, children: "Email" }),
1337
1417
  /* @__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: [
1418
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1339
1419
  /* @__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
1420
  /* @__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
1421
  ] }),
@@ -1376,25 +1456,10 @@ function ReadyToGoDeliveryBlock(props) {
1376
1456
  function ReadyToGoRecommendationsBlock(props) {
1377
1457
  const runtime = useReadyToGoRuntime();
1378
1458
  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 ?? [];
1459
+ if (runtime.recommendations.phase !== "success" || runtime.recommendations.items.length === 0) return null;
1389
1460
  return /* @__PURE__ */ jsx3(SectionShell, { title: heading, bordered: false, children: /* @__PURE__ */ jsxs3("div", { style: { ...contentWidth, padding: "48px 24px" }, children: [
1390
1461
  /* @__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
- ] })
1462
+ /* @__PURE__ */ jsx3("div", { style: { marginTop: 24 }, children: /* @__PURE__ */ jsx3(RecommendationCards, { items: runtime.recommendations.items }) })
1398
1463
  ] }) });
1399
1464
  }
1400
1465
 
@@ -1467,7 +1532,13 @@ var readyToGoBlocks = withTemplatePolicy([
1467
1532
  defaultProps: { heading: "Shipping Details", contentsHeading: "Package Contents", carrierHeading: "Carrier" },
1468
1533
  defaultVariant: "default",
1469
1534
  variants: [{ id: "default", label: "Default" }, { id: "compact", label: "Compact", theme: { spacing: "12px" } }],
1470
- fields: { heading: text2("Heading"), contentsHeading: text2("Package contents heading"), carrierHeading: text2("Carrier heading") },
1535
+ fields: {
1536
+ heading: text2("Heading"),
1537
+ contentsHeading: text2("Package contents heading"),
1538
+ carrierHeading: text2("Carrier heading"),
1539
+ adImageUrl: { field: "besttrack.ready-to-go.asset", label: "Advertisement image", control: "asset", persist: false, group: "Advertisement" },
1540
+ adLinkUrl: { field: "besttrack.ready-to-go.url", label: "Advertisement link", control: "url", persist: false, group: "Advertisement", validation: { allowRelativeUrl: true, allowedUrlProtocols: ["https:", "http:"] } }
1541
+ },
1471
1542
  render: { web: ReadyToGoDeliveryBlock, editor: ReadyToGoDeliveryEditor }
1472
1543
  },
1473
1544
  {
@@ -1476,10 +1547,13 @@ var readyToGoBlocks = withTemplatePolicy([
1476
1547
  label: "Recommended products",
1477
1548
  category: "BestTrack Ready-to-go",
1478
1549
  targets: ["web"],
1479
- defaultProps: { heading: "You may also like..." },
1550
+ defaultProps: { heading: "You may also like...", products: [] },
1480
1551
  defaultVariant: "default",
1481
1552
  variants: [{ id: "default", label: "Default" }, { id: "grid", label: "Grid", theme: { "color.surface": "#ffffff" } }],
1482
- fields: { heading: text2("Heading") },
1553
+ fields: {
1554
+ heading: text2("Heading"),
1555
+ products: { field: "besttrack.ready-to-go.products", label: "\u63A8\u8350\u5546\u54C1 (Shopify)", control: "products", group: "Content" }
1556
+ },
1483
1557
  render: { web: ReadyToGoRecommendationsBlock, editor: ReadyToGoRecommendationsEditor }
1484
1558
  }
1485
1559
  ], ["besttrack.ready-to-go.query"]);
@@ -1537,11 +1611,11 @@ var bestTrackPageExtension = Object.freeze({
1537
1611
  import { createPageDocument as createPageDocument2 } from "@standhigher/puck-page-builder/runtime";
1538
1612
 
1539
1613
  // 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";
1614
+ import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
1541
1615
 
1542
1616
  // 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";
1617
+ import { useEffect as useEffect3, useId, useRef as useRef2, useState as useState3 } from "react";
1618
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1545
1619
  var srOnly = {
1546
1620
  position: "absolute",
1547
1621
  width: 1,
@@ -1645,7 +1719,7 @@ function TrackingQueryCard({
1645
1719
  const tabRefs = useRef2({ tracking: null, order: null });
1646
1720
  const loading = phase === "loading";
1647
1721
  const terminalPhase = phase === "success" || phase === "empty" || phase === "error";
1648
- useEffect2(() => {
1722
+ useEffect3(() => {
1649
1723
  if (!terminalPhase) return;
1650
1724
  const card = cardRef.current;
1651
1725
  const resultNode = resultRef.current;
@@ -1722,7 +1796,7 @@ function TrackingQueryCard({
1722
1796
  clearError("tracking");
1723
1797
  }, placeholder: trackingPlaceholder, style: inputWithError("tracking") }),
1724
1798
  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: [
1799
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
1726
1800
  /* @__PURE__ */ jsxs4("div", { children: [
1727
1801
  /* @__PURE__ */ jsx4("label", { htmlFor: orderInputId, style: srOnly, children: orderInputLabel }),
1728
1802
  /* @__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 +1972,7 @@ function BrandedRuntimeProvider({ children, query: injectedQuery, watermark }) {
1898
1972
  const [state, setState] = useState4({ phase: "idle" });
1899
1973
  const [selectedShipmentId, setSelectedShipmentId] = useState4(null);
1900
1974
  const requestId = useRef3(0);
1901
- const query = useCallback2(async (request) => {
1975
+ const query = useCallback3(async (request) => {
1902
1976
  const currentRequestId = ++requestId.current;
1903
1977
  setState({ phase: "loading" });
1904
1978
  try {
@@ -1912,20 +1986,20 @@ function BrandedRuntimeProvider({ children, query: injectedQuery, watermark }) {
1912
1986
  setState({ phase: "error", error: "tracking-query-failed" });
1913
1987
  }
1914
1988
  }, [injectedQuery]);
1915
- const reset = useCallback2(() => {
1989
+ const reset = useCallback3(() => {
1916
1990
  requestId.current += 1;
1917
1991
  setSelectedShipmentId(null);
1918
1992
  setState({ phase: "idle" });
1919
1993
  }, []);
1920
- const displayedResult = useMemo2(() => {
1994
+ const displayedResult = useMemo3(() => {
1921
1995
  if (!state.result) return void 0;
1922
1996
  const shipment = state.result.shipments?.find((item) => item.id === selectedShipmentId);
1923
1997
  return shipment ? { ...state.result, ...shipment } : state.result;
1924
1998
  }, [selectedShipmentId, state.result]);
1925
- const selectShipment = useCallback2((id) => {
1999
+ const selectShipment = useCallback3((id) => {
1926
2000
  if (state.result?.shipments?.some((shipment) => shipment.id === id)) setSelectedShipmentId(id);
1927
2001
  }, [state.result]);
1928
- const value = useMemo2(
2002
+ const value = useMemo3(
1929
2003
  () => ({ ...state, displayedResult, selectedShipmentId, query, reset, selectShipment, watermark }),
1930
2004
  [displayedResult, query, reset, selectedShipmentId, selectShipment, state, watermark]
1931
2005
  );
@@ -2228,8 +2302,8 @@ var bestTrackBrandedExtension = Object.freeze({ name: "besttrack.branded", versi
2228
2302
  import { createPageDocument as createPageDocument3 } from "@standhigher/puck-page-builder/runtime";
2229
2303
 
2230
2304
  // 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";
2305
+ import { createContext as createContext3, useCallback as useCallback4, useContext as useContext3, useMemo as useMemo4, useRef as useRef4, useState as useState5 } from "react";
2306
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2233
2307
  var initialRuntime3 = { phase: "idle", async query() {
2234
2308
  return void 0;
2235
2309
  } };
@@ -2241,7 +2315,7 @@ var gridStyle = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax
2241
2315
  function SalesRuntimeProvider({ children, query: injectedQuery, watermark, resourceResolution }) {
2242
2316
  const [state, setState] = useState5({ phase: "idle" });
2243
2317
  const requestId = useRef4(0);
2244
- const query = useCallback3(async (request) => {
2318
+ const query = useCallback4(async (request) => {
2245
2319
  const currentRequestId = ++requestId.current;
2246
2320
  setState({ phase: "loading" });
2247
2321
  try {
@@ -2254,7 +2328,7 @@ function SalesRuntimeProvider({ children, query: injectedQuery, watermark, resou
2254
2328
  setState({ phase: "error" });
2255
2329
  }
2256
2330
  }, [injectedQuery]);
2257
- const value = useMemo3(() => ({ ...state, query, watermark, resourceResolution }), [query, resourceResolution, state, watermark]);
2331
+ const value = useMemo4(() => ({ ...state, query, watermark, resourceResolution }), [query, resourceResolution, state, watermark]);
2258
2332
  return /* @__PURE__ */ jsx6(SalesRuntimeContext.Provider, { value, children });
2259
2333
  }
2260
2334
  function useSalesRuntime() {
@@ -2455,7 +2529,7 @@ function SalesOrderItemsBlock(props) {
2455
2529
  "Qty ",
2456
2530
  Number.isFinite(item.quantity) && item.quantity > 0 ? item.quantity : 1
2457
2531
  ] }),
2458
- item.description ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
2532
+ item.description ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2459
2533
  /* @__PURE__ */ jsx6("br", {}),
2460
2534
  /* @__PURE__ */ jsx6("small", { style: { color: "var(--pb-color-muted)" }, children: item.description })
2461
2535
  ] }) : null
@@ -2496,7 +2570,7 @@ function SalesProductCategoriesBlock(props) {
2496
2570
  const error = getShopifyResourceResolutionError(collection, runtime.resourceResolution);
2497
2571
  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
2572
  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: [
2573
+ const content = /* @__PURE__ */ jsxs6(Fragment4, { children: [
2500
2574
  /* @__PURE__ */ jsx6("span", { children: resolved.title }),
2501
2575
  /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", children: "\u2192" })
2502
2576
  ] });
@@ -2514,7 +2588,7 @@ function SalesFeaturedProductBlock(props) {
2514
2588
  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
2589
  if (!resolved) return /* @__PURE__ */ jsx6(Section, { title, children: /* @__PURE__ */ jsx6(Status, { children: "Product details and availability are resolved by the authorized runtime." }) });
2516
2590
  const available = resolved.availability === "available";
2517
- const body = /* @__PURE__ */ jsxs6(Fragment3, { children: [
2591
+ const body = /* @__PURE__ */ jsxs6(Fragment4, { children: [
2518
2592
  /* @__PURE__ */ jsx6(ProductImage3, { src: resolved.imageUrl, alt: resolved.title }),
2519
2593
  /* @__PURE__ */ jsxs6("span", { style: { minWidth: 0, flex: 1 }, children: [
2520
2594
  /* @__PURE__ */ jsx6("strong", { children: resolved.title }),
@@ -2545,7 +2619,7 @@ function SalesRecommendationsBlock(props) {
2545
2619
  }
2546
2620
 
2547
2621
  // src/shopify-resources.tsx
2548
- import { createContext as createContext4, useCallback as useCallback4, useContext as useContext4, useState as useState6 } from "react";
2622
+ import { createContext as createContext4, useCallback as useCallback5, useContext as useContext4, useState as useState6 } from "react";
2549
2623
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2550
2624
  var ShopifyResourceBrowserContext = createContext4(void 0);
2551
2625
  function ShopifyResourcePickerProvider({ browser, children }) {
@@ -2561,7 +2635,7 @@ function ResourceField({ value, onChange, kind }) {
2561
2635
  const [items, setItems] = useState6([]);
2562
2636
  const [cursor, setCursor] = useState6();
2563
2637
  const [state, setState] = useState6("idle");
2564
- const search = useCallback4(async (nextQuery, nextCursor, append = false) => {
2638
+ const search = useCallback5(async (nextQuery, nextCursor, append = false) => {
2565
2639
  if (!browser) return;
2566
2640
  setState("loading");
2567
2641
  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.1",
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.1"
24
26
  },
25
27
  "devDependencies": {
26
28
  "tsup": "^8.5.1",