@standhigher/besttrack-page-extension 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @standhigher/besttrack-page-extension
2
+
3
+ V0.7.1 adds the besttrack.branded built-in template. Its announcement, order
4
+ query, order items, recommendations, quick links and Blog blocks use the same
5
+ host-injected tracking query contract as Ready-to-go. Logo and content are
6
+ JSON-only block props; brand colour, font and radius use the template Theme
7
+ Tokens and may be overridden through PageDocument.theme.
8
+
9
+ V0.7.0 provides the Ready-to-go built-in template: order query, shipment progress, delivery information and recommendations. Live tracking is injected by the host Runtime; this package never stores credentials or network endpoints in a `PageDocument`.
@@ -0,0 +1,70 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { PageBuilderExtension, TemplateDefinition } from '@standhigher/puck-page-builder/runtime';
4
+
5
+ type ReadyToGoTrackingResult = {
6
+ trackingNumber: string;
7
+ status: string;
8
+ carrier?: string;
9
+ latestEvent?: string;
10
+ updatedAt?: string;
11
+ deliveryAddress?: string;
12
+ orderItems?: Array<{
13
+ id: string;
14
+ title: string;
15
+ quantity: number;
16
+ imageUrl?: string;
17
+ }>;
18
+ recommendations?: Array<{
19
+ id: string;
20
+ title: string;
21
+ description: string;
22
+ }>;
23
+ };
24
+ type ReadyToGoTrackingQuery = (trackingNumber: string) => Promise<ReadyToGoTrackingResult>;
25
+ type ReadyToGoRuntimeState = {
26
+ phase: "idle" | "loading" | "success" | "error";
27
+ result?: ReadyToGoTrackingResult;
28
+ error?: string;
29
+ };
30
+ type ReadyToGoRuntimeProviderProps = {
31
+ children: ReactNode;
32
+ queryTracking?: ReadyToGoTrackingQuery;
33
+ };
34
+ declare function ReadyToGoRuntimeProvider({ children, queryTracking }: ReadyToGoRuntimeProviderProps): react.JSX.Element;
35
+
36
+ declare function createReadyToGoTemplate(): TemplateDefinition;
37
+ declare const bestTrackPageExtension: PageBuilderExtension;
38
+
39
+ declare function createBrandedTemplate(): TemplateDefinition;
40
+ declare const bestTrackBrandedExtension: PageBuilderExtension;
41
+
42
+ type BrandedRuntimeState = {
43
+ phase: "idle" | "loading" | "success" | "error";
44
+ result?: ReadyToGoTrackingResult;
45
+ error?: string;
46
+ };
47
+ type BrandedRuntimeProviderProps = {
48
+ children: ReactNode;
49
+ queryTracking: ReadyToGoTrackingQuery;
50
+ };
51
+ /** Keeps one standard tracking result available to every Branded result block. */
52
+ declare function BrandedRuntimeProvider({ children, queryTracking }: BrandedRuntimeProviderProps): react.JSX.Element;
53
+
54
+ declare function createSalesTemplate(): TemplateDefinition;
55
+ declare const bestTrackSalesExtension: PageBuilderExtension;
56
+
57
+ type SalesPhase = "idle" | "loading" | "success" | "error";
58
+ type SalesRuntimeState = {
59
+ phase: SalesPhase;
60
+ result?: ReadyToGoTrackingResult;
61
+ error?: string;
62
+ };
63
+ type SalesRuntimeProviderProps = {
64
+ children: ReactNode;
65
+ queryTracking: ReadyToGoTrackingQuery;
66
+ };
67
+ /** The host injects a validated query; Sales never calls a DataSource itself. */
68
+ declare function SalesRuntimeProvider({ children, queryTracking }: SalesRuntimeProviderProps): react.JSX.Element;
69
+
70
+ export { BrandedRuntimeProvider, type BrandedRuntimeProviderProps, type BrandedRuntimeState, ReadyToGoRuntimeProvider, type ReadyToGoRuntimeProviderProps, type ReadyToGoRuntimeState, type ReadyToGoTrackingQuery, type ReadyToGoTrackingResult, SalesRuntimeProvider, type SalesRuntimeProviderProps, type SalesRuntimeState, bestTrackBrandedExtension, bestTrackPageExtension, bestTrackSalesExtension, createBrandedTemplate, createReadyToGoTemplate, createSalesTemplate };
package/dist/index.js ADDED
@@ -0,0 +1,384 @@
1
+ // src/ready-to-go.tsx
2
+ import { createContext, useCallback, useContext, useMemo, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ var initialRuntime = { phase: "idle", async query() {
5
+ return void 0;
6
+ } };
7
+ var ReadyToGoRuntimeContext = createContext(initialRuntime);
8
+ async function queryMockReadyToGoTracking(trackingNumber) {
9
+ return {
10
+ trackingNumber,
11
+ status: "In transit",
12
+ carrier: "BestTrack demo carrier",
13
+ latestEvent: "Shipment accepted at the regional hub",
14
+ updatedAt: "2026-09-17T10:00:00.000Z",
15
+ deliveryAddress: "Demo recipient \xB7 Shanghai",
16
+ orderItems: [{ id: "demo-order-item", title: "Demo shipment item", quantity: 1 }],
17
+ recommendations: [{ id: "shipping-protection", title: "Shipping protection", description: "Extra assurance for your next delivery." }, { id: "delivery-alerts", title: "Delivery alerts", description: "Receive an update at every milestone." }]
18
+ };
19
+ }
20
+ function ReadyToGoRuntimeProvider({ children, queryTracking = queryMockReadyToGoTracking }) {
21
+ const [state, setState] = useState({ phase: "idle" });
22
+ const query = useCallback(async (trackingNumber) => {
23
+ setState({ phase: "loading" });
24
+ try {
25
+ setState({ phase: "success", result: await queryTracking(trackingNumber) });
26
+ } catch (error) {
27
+ setState({ phase: "error", error: error instanceof Error ? error.message : "tracking-query-failed" });
28
+ }
29
+ }, [queryTracking]);
30
+ const value = useMemo(() => ({ ...state, query }), [query, state]);
31
+ return /* @__PURE__ */ jsx(ReadyToGoRuntimeContext.Provider, { value, children });
32
+ }
33
+ function useReadyToGoRuntime() {
34
+ return useContext(ReadyToGoRuntimeContext);
35
+ }
36
+ var cardStyle = { border: "1px solid var(--pb-color-border)", borderRadius: "var(--pb-radius)", padding: "var(--pb-spacing)", background: "var(--pb-color-surface)", color: "var(--pb-color-text)" };
37
+ function ResultCard({ title, children }) {
38
+ return /* @__PURE__ */ jsxs("section", { "aria-label": title, style: cardStyle, children: [
39
+ /* @__PURE__ */ jsx("h2", { style: { marginTop: 0 }, children: title }),
40
+ children
41
+ ] });
42
+ }
43
+ function ReadyToGoTextField({ value, onChange }) {
44
+ return /* @__PURE__ */ jsx("input", { "aria-label": "Ready-to-go text", value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.target.value) });
45
+ }
46
+ function ReadyToGoQueryBlock(props) {
47
+ const runtime = useReadyToGoRuntime();
48
+ const [trackingNumber, setTrackingNumber] = useState(typeof props.defaultTrackingNumber === "string" ? props.defaultTrackingNumber : "BT-2048-DEMO");
49
+ const heading = typeof props.heading === "string" ? props.heading : "Track your order";
50
+ const submitLabel = typeof props.submitLabel === "string" ? props.submitLabel : "Track package";
51
+ const submit = (event) => {
52
+ event.preventDefault();
53
+ void runtime.query(trackingNumber);
54
+ };
55
+ return /* @__PURE__ */ jsxs("section", { "aria-label": "Ready-to-go tracking query", style: { ...cardStyle, maxWidth: 720, margin: "0 auto" }, children: [
56
+ /* @__PURE__ */ jsx("h1", { style: { marginTop: 0 }, children: heading }),
57
+ /* @__PURE__ */ jsxs("form", { onSubmit: submit, style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
58
+ /* @__PURE__ */ jsx("input", { "aria-label": "Tracking number", value: trackingNumber, onChange: (event) => setTrackingNumber(event.target.value), required: true, minLength: 4, maxLength: 64, style: { flex: "1 1 220px", padding: 10, borderRadius: "var(--pb-radius)", border: "1px solid var(--pb-color-border)" } }),
59
+ /* @__PURE__ */ jsx("button", { type: "submit", disabled: runtime.phase === "loading", style: { padding: "10px 16px", border: 0, borderRadius: "var(--pb-radius)", background: "var(--pb-color-primary)", color: "white" }, children: runtime.phase === "loading" ? "Tracking\u2026" : submitLabel })
60
+ ] }),
61
+ runtime.phase === "error" ? /* @__PURE__ */ jsx("p", { role: "alert", children: runtime.error }) : null
62
+ ] });
63
+ }
64
+ function ReadyToGoProgressBlock(props) {
65
+ const runtime = useReadyToGoRuntime();
66
+ const heading = typeof props.heading === "string" ? props.heading : "Shipment progress";
67
+ return /* @__PURE__ */ jsx(ResultCard, { title: heading, children: runtime.phase === "success" ? /* @__PURE__ */ jsxs(Fragment, { children: [
68
+ /* @__PURE__ */ jsx("strong", { children: runtime.result?.status }),
69
+ /* @__PURE__ */ jsx("p", { children: runtime.result?.latestEvent }),
70
+ /* @__PURE__ */ jsx("small", { children: runtime.result?.updatedAt })
71
+ ] }) : /* @__PURE__ */ jsx("p", { children: runtime.phase === "loading" ? "Loading shipment progress\u2026" : "Enter a tracking number to see shipment progress." }) });
72
+ }
73
+ function ReadyToGoDeliveryBlock(props) {
74
+ const runtime = useReadyToGoRuntime();
75
+ const heading = typeof props.heading === "string" ? props.heading : "Delivery information";
76
+ return /* @__PURE__ */ jsx(ResultCard, { title: heading, children: runtime.phase === "success" ? /* @__PURE__ */ jsxs("dl", { children: [
77
+ /* @__PURE__ */ jsx("dt", { children: "Carrier" }),
78
+ /* @__PURE__ */ jsx("dd", { children: runtime.result?.carrier ?? "Not available" }),
79
+ /* @__PURE__ */ jsx("dt", { children: "Delivery" }),
80
+ /* @__PURE__ */ jsx("dd", { children: runtime.result?.deliveryAddress ?? "Delivery details are not available." })
81
+ ] }) : /* @__PURE__ */ jsx("p", { children: "Delivery details will appear after a successful query." }) });
82
+ }
83
+ function ReadyToGoRecommendationsBlock(props) {
84
+ const runtime = useReadyToGoRuntime();
85
+ const heading = typeof props.heading === "string" ? props.heading : "You may also like";
86
+ const recommendations = runtime.result?.recommendations ?? [];
87
+ return /* @__PURE__ */ jsx(ResultCard, { title: heading, children: runtime.phase === "success" && recommendations.length > 0 ? /* @__PURE__ */ jsx("ul", { children: recommendations.map((item) => /* @__PURE__ */ jsxs("li", { children: [
88
+ /* @__PURE__ */ jsx("strong", { children: item.title }),
89
+ /* @__PURE__ */ jsx("br", {}),
90
+ /* @__PURE__ */ jsx("small", { children: item.description })
91
+ ] }, item.id)) }) : /* @__PURE__ */ jsx("p", { children: runtime.phase === "success" ? "No recommendations are available for this shipment." : "Recommendations appear with your shipment result." }) });
92
+ }
93
+
94
+ // src/ready-to-go-definition.ts
95
+ import { createPageDocument } from "@standhigher/puck-page-builder/runtime";
96
+ var readyToGoBlocks = [
97
+ { type: "besttrack.ready-to-go.query", version: 1, label: "Order query", category: "BestTrack Ready-to-go", targets: ["web"], defaultProps: { heading: "Track your order", submitLabel: "Track package", defaultTrackingNumber: "BT-2048-DEMO" }, defaultVariant: "default", variants: [{ id: "default", label: "Default" }], fields: { heading: { field: "besttrack.ready-to-go.text", label: "Heading" }, submitLabel: { field: "besttrack.ready-to-go.text", label: "Button label" }, defaultTrackingNumber: { field: "besttrack.ready-to-go.text", label: "Default tracking number" } }, render: { web: ReadyToGoQueryBlock } },
98
+ { type: "besttrack.ready-to-go.progress", version: 1, label: "Shipment progress", category: "BestTrack Ready-to-go", targets: ["web"], defaultProps: { heading: "Shipment progress" }, defaultVariant: "default", variants: [{ id: "default", label: "Default" }], fields: { heading: { field: "besttrack.ready-to-go.text", label: "Heading" } }, render: { web: ReadyToGoProgressBlock } },
99
+ { type: "besttrack.ready-to-go.delivery", version: 1, label: "Delivery information", category: "BestTrack Ready-to-go", targets: ["web"], defaultProps: { heading: "Delivery information" }, defaultVariant: "default", variants: [{ id: "default", label: "Default" }], fields: { heading: { field: "besttrack.ready-to-go.text", label: "Heading" } }, render: { web: ReadyToGoDeliveryBlock } },
100
+ { type: "besttrack.ready-to-go.recommendations", version: 1, label: "Recommended products", category: "BestTrack Ready-to-go", targets: ["web"], defaultProps: { heading: "You may also like" }, defaultVariant: "default", variants: [{ id: "default", label: "Default" }], fields: { heading: { field: "besttrack.ready-to-go.text", label: "Heading" } }, render: { web: ReadyToGoRecommendationsBlock } }
101
+ ];
102
+ function createReadyToGoTemplate() {
103
+ return { id: "besttrack.ready-to-go", version: 1, name: "Ready-to-go", target: "web", source: "built-in", requiredBlocks: readyToGoBlocks.map((block) => block.type), theme: { "color.primary": "#005bd3", "color.background": "#f6f6f7", radius: "12px", spacing: "20px" }, create: () => createPageDocument({ pageId: "besttrack-ready-to-go", target: "web", templateId: "besttrack.ready-to-go", templateVersion: 1, settings: { locale: "en", seoTitle: "Track your order" }, blocks: readyToGoBlocks.map((block, index) => ({ id: `ready-to-go-${index + 1}`, type: block.type, version: block.version, props: block.defaultProps, variant: block.defaultVariant, style: {} })) }) };
104
+ }
105
+ var bestTrackPageExtension = Object.freeze({ name: "besttrack.ready-to-go", version: "0.7.0", fields: [{ type: "besttrack.ready-to-go.text", component: ReadyToGoTextField }], blocks: readyToGoBlocks, templates: [createReadyToGoTemplate()] });
106
+
107
+ // src/branded-definition.ts
108
+ import { createPageDocument as createPageDocument2 } from "@standhigher/puck-page-builder/runtime";
109
+
110
+ // src/branded.tsx
111
+ import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useMemo as useMemo2, useState as useState2 } from "react";
112
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
113
+ var cardStyle2 = { border: "1px solid var(--pb-color-border)", borderRadius: "var(--pb-radius)", padding: "var(--pb-spacing)", background: "var(--pb-color-surface)", color: "var(--pb-color-text)", fontFamily: "var(--pb-font-family)" };
114
+ var gridStyle = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: "var(--pb-spacing)" };
115
+ var initialRuntime2 = { phase: "idle", async query() {
116
+ return void 0;
117
+ } };
118
+ var BrandedRuntimeContext = createContext2(initialRuntime2);
119
+ function BrandedRuntimeProvider({ children, queryTracking }) {
120
+ const [state, setState] = useState2({ phase: "idle" });
121
+ const query = useCallback2(async (trackingNumber) => {
122
+ setState({ phase: "loading" });
123
+ try {
124
+ setState({ phase: "success", result: await queryTracking(trackingNumber) });
125
+ } catch (error) {
126
+ setState({ phase: "error", error: error instanceof Error ? error.message : "tracking-query-failed" });
127
+ }
128
+ }, [queryTracking]);
129
+ const value = useMemo2(() => ({ ...state, query }), [query, state]);
130
+ return /* @__PURE__ */ jsx2(BrandedRuntimeContext.Provider, { value, children });
131
+ }
132
+ function text(props, key, fallback) {
133
+ return typeof props[key] === "string" ? props[key] : fallback;
134
+ }
135
+ function useBrandedRuntime() {
136
+ return useContext2(BrandedRuntimeContext);
137
+ }
138
+ function safeHref(value) {
139
+ if (typeof value !== "string") return "#";
140
+ if (value.startsWith("/")) return value;
141
+ try {
142
+ const url = new URL(value);
143
+ return url.protocol === "https:" || url.protocol === "http:" ? url.toString() : "#";
144
+ } catch {
145
+ return "#";
146
+ }
147
+ }
148
+ function Section({ title, children }) {
149
+ return /* @__PURE__ */ jsxs2("section", { "aria-label": title, style: cardStyle2, children: [
150
+ /* @__PURE__ */ jsx2("h2", { style: { marginTop: 0 }, children: title }),
151
+ children
152
+ ] });
153
+ }
154
+ function BrandedTextField({ value, onChange }) {
155
+ return /* @__PURE__ */ jsx2("input", { "aria-label": "Branded text", value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.target.value) });
156
+ }
157
+ function BrandedAnnouncementBlock(props) {
158
+ const logoUrl = text(props, "logoUrl", "");
159
+ return /* @__PURE__ */ jsxs2("section", { "aria-label": "Branded announcement", style: { ...cardStyle2, display: "flex", alignItems: "center", gap: 12, background: "var(--pb-color-primary)", color: "white" }, children: [
160
+ logoUrl ? /* @__PURE__ */ jsx2("img", { src: logoUrl, alt: text(props, "brandName", "Brand"), style: { width: 36, height: 36, borderRadius: "50%", objectFit: "cover", background: "white" } }) : null,
161
+ /* @__PURE__ */ jsxs2("div", { children: [
162
+ /* @__PURE__ */ jsx2("strong", { children: text(props, "brandName", "BestTrack") }),
163
+ /* @__PURE__ */ jsx2("p", { style: { margin: "4px 0 0" }, children: text(props, "message", "Track every order with confidence.") })
164
+ ] })
165
+ ] });
166
+ }
167
+ function BrandedQueryBlock(props) {
168
+ const runtime = useBrandedRuntime();
169
+ const [trackingNumber, setTrackingNumber] = useState2(text(props, "defaultTrackingNumber", "BT-2048-DEMO"));
170
+ const submit = (event) => {
171
+ event.preventDefault();
172
+ void runtime.query(trackingNumber);
173
+ };
174
+ return /* @__PURE__ */ jsxs2("section", { "aria-label": "Branded tracking query", style: { ...cardStyle2, boxShadow: "0 16px 40px rgb(0 0 0 / 8%)" }, children: [
175
+ /* @__PURE__ */ jsx2("p", { style: { margin: 0, color: "var(--pb-color-muted)" }, children: text(props, "eyebrow", "ORDER SUPPORT") }),
176
+ /* @__PURE__ */ jsx2("h1", { style: { margin: "6px 0 16px" }, children: text(props, "heading", "Where is my order?") }),
177
+ /* @__PURE__ */ jsxs2("form", { onSubmit: submit, style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
178
+ /* @__PURE__ */ jsx2("input", { "aria-label": "Branded tracking number", value: trackingNumber, onChange: (event) => setTrackingNumber(event.target.value), required: true, minLength: 4, maxLength: 64, style: { flex: "1 1 220px", padding: 12, borderRadius: "var(--pb-radius)", border: "1px solid var(--pb-color-border)" } }),
179
+ /* @__PURE__ */ jsx2("button", { type: "submit", disabled: runtime.phase === "loading", style: { padding: "12px 18px", border: 0, borderRadius: "var(--pb-radius)", background: "var(--pb-color-primary)", color: "white" }, children: runtime.phase === "loading" ? "Looking up\u2026" : text(props, "submitLabel", "Find my order") })
180
+ ] }),
181
+ runtime.phase === "error" ? /* @__PURE__ */ jsx2("p", { role: "alert", children: runtime.error }) : null
182
+ ] });
183
+ }
184
+ function BrandedOrderItemsBlock(props) {
185
+ const runtime = useBrandedRuntime();
186
+ const title = text(props, "heading", "Your order");
187
+ const items = runtime.result?.orderItems ?? [];
188
+ return /* @__PURE__ */ jsx2(Section, { title, children: runtime.phase === "success" && items.length ? /* @__PURE__ */ jsx2("ul", { style: { paddingLeft: 20, marginBottom: 0 }, children: items.map((item) => /* @__PURE__ */ jsxs2("li", { children: [
189
+ /* @__PURE__ */ jsx2("strong", { children: item.title }),
190
+ " \xB7 Qty ",
191
+ item.quantity
192
+ ] }, item.id)) }) : /* @__PURE__ */ jsx2("p", { children: runtime.phase === "loading" ? "Loading order items\u2026" : "Order items will appear after a successful query." }) });
193
+ }
194
+ function BrandedRecommendationsBlock(props) {
195
+ const runtime = useBrandedRuntime();
196
+ const title = text(props, "heading", "Selected for you");
197
+ const recommendations = runtime.result?.recommendations ?? [];
198
+ return /* @__PURE__ */ jsx2(Section, { title, children: runtime.phase === "success" && recommendations.length ? /* @__PURE__ */ jsx2("div", { style: gridStyle, children: recommendations.map((item) => /* @__PURE__ */ jsxs2("article", { style: { borderTop: "3px solid var(--pb-color-primary)", paddingTop: 8 }, children: [
199
+ /* @__PURE__ */ jsx2("strong", { children: item.title }),
200
+ /* @__PURE__ */ jsx2("p", { children: item.description })
201
+ ] }, item.id)) }) : /* @__PURE__ */ jsx2("p", { children: runtime.phase === "success" ? "No recommendations are available for this order." : "Recommendations appear with your order result." }) });
202
+ }
203
+ function BrandedQuickLinksBlock(props) {
204
+ const title = text(props, "heading", "Need help?");
205
+ return /* @__PURE__ */ jsx2(Section, { title, children: /* @__PURE__ */ jsxs2("div", { style: gridStyle, children: [
206
+ /* @__PURE__ */ jsx2("a", { href: safeHref(props.primaryHref), style: { color: "var(--pb-color-primary)" }, children: text(props, "primaryLabel", "Shipping help") }),
207
+ /* @__PURE__ */ jsx2("a", { href: safeHref(props.secondaryHref), style: { color: "var(--pb-color-primary)" }, children: text(props, "secondaryLabel", "Contact us") })
208
+ ] }) });
209
+ }
210
+ function BrandedBlogBlock(props) {
211
+ const title = text(props, "heading", "From our journal");
212
+ return /* @__PURE__ */ jsx2(Section, { title, children: /* @__PURE__ */ jsxs2("article", { children: [
213
+ /* @__PURE__ */ jsx2("strong", { children: text(props, "articleTitle", "Delivery tips for every season") }),
214
+ /* @__PURE__ */ jsx2("p", { children: text(props, "excerpt", "Simple ways to make every delivery feel considered.") }),
215
+ /* @__PURE__ */ jsx2("a", { href: safeHref(props.articleHref), style: { color: "var(--pb-color-primary)" }, children: text(props, "linkLabel", "Read the story") })
216
+ ] }) });
217
+ }
218
+
219
+ // src/branded-definition.ts
220
+ var text2 = (label) => ({ field: "besttrack.branded.text", label });
221
+ var brandedBlocks = [
222
+ { type: "besttrack.branded.announcement", version: 1, label: "Announcement", category: "BestTrack Branded", targets: ["web"], defaultProps: { brandName: "BestTrack Studio", logoUrl: "", message: "Track every order with confidence." }, defaultVariant: "brand", variants: [{ id: "brand", label: "Brand" }, { id: "minimal", label: "Minimal", theme: { "color.primary": "#202223" } }], fields: { brandName: text2("Brand name"), logoUrl: text2("Logo URL"), message: text2("Announcement") }, render: { web: BrandedAnnouncementBlock } },
223
+ { type: "besttrack.branded.query", version: 1, label: "Order query", category: "BestTrack Branded", targets: ["web"], defaultProps: { eyebrow: "ORDER SUPPORT", heading: "Where is my order?", submitLabel: "Find my order", defaultTrackingNumber: "BT-2048-DEMO" }, defaultVariant: "brand", variants: [{ id: "brand", label: "Brand" }, { id: "compact", label: "Compact", theme: { spacing: "12px" } }], fields: { eyebrow: text2("Eyebrow"), heading: text2("Heading"), submitLabel: text2("Button label"), defaultTrackingNumber: text2("Default tracking number") }, render: { web: BrandedQueryBlock } },
224
+ { type: "besttrack.branded.order-items", version: 1, label: "Order items", category: "BestTrack Branded", targets: ["web"], defaultProps: { heading: "Your order" }, defaultVariant: "brand", variants: [{ id: "brand", label: "Brand" }, { id: "minimal", label: "Minimal" }], fields: { heading: text2("Heading") }, render: { web: BrandedOrderItemsBlock } },
225
+ { type: "besttrack.branded.recommendations", version: 1, label: "Recommended products", category: "BestTrack Branded", targets: ["web"], defaultProps: { heading: "Selected for you" }, defaultVariant: "brand", variants: [{ id: "brand", label: "Brand" }, { id: "editorial", label: "Editorial", theme: { "color.surface": "#fff7ed" } }], fields: { heading: text2("Heading") }, render: { web: BrandedRecommendationsBlock } },
226
+ { type: "besttrack.branded.quick-links", version: 1, label: "Quick links", category: "BestTrack Branded", targets: ["web"], defaultProps: { heading: "Need help?", primaryLabel: "Shipping help", primaryHref: "/pages/shipping", secondaryLabel: "Contact us", secondaryHref: "/pages/contact" }, defaultVariant: "brand", variants: [{ id: "brand", label: "Brand" }], fields: { heading: text2("Heading"), primaryLabel: text2("Primary link label"), primaryHref: text2("Primary link URL"), secondaryLabel: text2("Secondary link label"), secondaryHref: text2("Secondary link URL") }, render: { web: BrandedQuickLinksBlock } },
227
+ { type: "besttrack.branded.blog", version: 1, label: "Blog", category: "BestTrack Branded", targets: ["web"], defaultProps: { heading: "From our journal", articleTitle: "Delivery tips for every season", excerpt: "Simple ways to make every delivery feel considered.", articleHref: "/blogs/news", linkLabel: "Read the story" }, defaultVariant: "brand", variants: [{ id: "brand", label: "Brand" }], fields: { heading: text2("Heading"), articleTitle: text2("Article title"), excerpt: text2("Excerpt"), articleHref: text2("Article URL"), linkLabel: text2("Link label") }, render: { web: BrandedBlogBlock } }
228
+ ];
229
+ function createBrandedTemplate() {
230
+ return { id: "besttrack.branded", version: 1, name: "Branded", target: "web", source: "built-in", requiredBlocks: brandedBlocks.map((block) => block.type), theme: { "color.primary": "#7c3aed", "color.background": "#f8f7ff", "color.surface": "#ffffff", "color.text": "#231942", "color.border": "#ddd6fe", "font.family": "Georgia, serif", radius: "18px", spacing: "24px" }, create: () => createPageDocument2({ pageId: "besttrack-branded", target: "web", templateId: "besttrack.branded", templateVersion: 1, settings: { locale: "en", seoTitle: "Track your order with BestTrack Studio" }, blocks: brandedBlocks.map((block, index) => ({ id: `branded-${index + 1}`, type: block.type, version: block.version, props: block.defaultProps, variant: block.defaultVariant, style: {} })) }) };
231
+ }
232
+ var bestTrackBrandedExtension = Object.freeze({ name: "besttrack.branded", version: "0.7.1", fields: [{ type: "besttrack.branded.text", component: BrandedTextField }], blocks: brandedBlocks, templates: [createBrandedTemplate()] });
233
+
234
+ // src/sales-definition.ts
235
+ import { createPageDocument as createPageDocument3 } from "@standhigher/puck-page-builder/runtime";
236
+
237
+ // src/sales.tsx
238
+ import { createContext as createContext3, useCallback as useCallback3, useContext as useContext3, useMemo as useMemo3, useState as useState3 } from "react";
239
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
240
+ var initialRuntime3 = { phase: "idle", async query() {
241
+ return void 0;
242
+ } };
243
+ var SalesRuntimeContext = createContext3(initialRuntime3);
244
+ var cardStyle3 = { border: "1px solid var(--pb-color-border)", borderRadius: "var(--pb-radius)", padding: "var(--pb-spacing)", background: "var(--pb-color-surface)", color: "var(--pb-color-text)", fontFamily: "var(--pb-font-family)" };
245
+ var gridStyle2 = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: "var(--pb-spacing)" };
246
+ function SalesRuntimeProvider({ children, queryTracking }) {
247
+ const [state, setState] = useState3({ phase: "idle" });
248
+ const query = useCallback3(async (trackingNumber) => {
249
+ setState({ phase: "loading" });
250
+ try {
251
+ setState({ phase: "success", result: await queryTracking(trackingNumber) });
252
+ } catch (error) {
253
+ setState({ phase: "error", error: error instanceof Error ? error.message : "tracking-query-failed" });
254
+ }
255
+ }, [queryTracking]);
256
+ const value = useMemo3(() => ({ ...state, query }), [query, state]);
257
+ return /* @__PURE__ */ jsx3(SalesRuntimeContext.Provider, { value, children });
258
+ }
259
+ function useSalesRuntime() {
260
+ return useContext3(SalesRuntimeContext);
261
+ }
262
+ function text3(props, key, fallback) {
263
+ return typeof props[key] === "string" ? props[key] : fallback;
264
+ }
265
+ function safeHref2(value) {
266
+ if (typeof value !== "string") return "#";
267
+ if (value.startsWith("/")) return value;
268
+ try {
269
+ const url = new URL(value);
270
+ return url.protocol === "https:" || url.protocol === "http:" ? url.toString() : "#";
271
+ } catch {
272
+ return "#";
273
+ }
274
+ }
275
+ function Section2({ title, children }) {
276
+ return /* @__PURE__ */ jsxs3("section", { "aria-label": title, style: cardStyle3, children: [
277
+ /* @__PURE__ */ jsx3("h2", { style: { marginTop: 0 }, children: title }),
278
+ children
279
+ ] });
280
+ }
281
+ function resourceState(value) {
282
+ return typeof value !== "string" ? "invalid" : value.trim() ? "ready" : "empty";
283
+ }
284
+ function SalesTextField({ value, onChange }) {
285
+ return /* @__PURE__ */ jsx3("input", { "aria-label": "Sales text", value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.target.value) });
286
+ }
287
+ function SalesAnnouncementBlock(props) {
288
+ return /* @__PURE__ */ jsx3("section", { "aria-label": "Sales announcement", style: { ...cardStyle3, background: "var(--pb-color-primary)", color: "white", textAlign: "center" }, children: /* @__PURE__ */ jsx3("strong", { children: text3(props, "message", "Free delivery on orders over $50") }) });
289
+ }
290
+ function SalesQueryBlock(props) {
291
+ const runtime = useSalesRuntime();
292
+ const [trackingNumber, setTrackingNumber] = useState3(text3(props, "defaultTrackingNumber", "BT-2048-DEMO"));
293
+ const submit = (event) => {
294
+ event.preventDefault();
295
+ void runtime.query(trackingNumber);
296
+ };
297
+ return /* @__PURE__ */ jsxs3("section", { "aria-label": "Sales tracking query", style: { ...cardStyle3, boxShadow: "0 12px 32px rgb(0 0 0 / 8%)" }, children: [
298
+ /* @__PURE__ */ jsx3("h1", { style: { marginTop: 0 }, children: text3(props, "heading", "Track an order") }),
299
+ /* @__PURE__ */ jsxs3("form", { onSubmit: submit, style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
300
+ /* @__PURE__ */ jsx3("input", { "aria-label": "Sales tracking number", value: trackingNumber, onChange: (event) => setTrackingNumber(event.target.value), minLength: 4, maxLength: 64, required: true, style: { flex: "1 1 220px", padding: 12, borderRadius: "var(--pb-radius)", border: "1px solid var(--pb-color-border)" } }),
301
+ /* @__PURE__ */ jsx3("button", { type: "submit", disabled: runtime.phase === "loading", style: { padding: "12px 18px", border: 0, borderRadius: "var(--pb-radius)", color: "white", background: "var(--pb-color-primary)" }, children: runtime.phase === "loading" ? "Checking\u2026" : text3(props, "submitLabel", "Track order") })
302
+ ] }),
303
+ runtime.phase === "error" ? /* @__PURE__ */ jsxs3("p", { role: "alert", children: [
304
+ "We could not retrieve this order: ",
305
+ runtime.error
306
+ ] }) : null
307
+ ] });
308
+ }
309
+ function SalesOrderItemsBlock(props) {
310
+ const runtime = useSalesRuntime();
311
+ const title = text3(props, "heading", "Items in your order");
312
+ const items = runtime.result?.orderItems ?? [];
313
+ return /* @__PURE__ */ jsx3(Section2, { title, children: runtime.phase === "error" ? /* @__PURE__ */ jsx3("p", { role: "alert", children: "Order items are temporarily unavailable." }) : runtime.phase === "success" && items.length ? /* @__PURE__ */ jsx3("ul", { style: { marginBottom: 0, paddingLeft: 20 }, children: items.map((item) => /* @__PURE__ */ jsxs3("li", { children: [
314
+ /* @__PURE__ */ jsx3("strong", { children: item.title }),
315
+ " \xB7 Qty ",
316
+ item.quantity
317
+ ] }, item.id)) }) : /* @__PURE__ */ jsx3("p", { children: runtime.phase === "loading" ? "Loading order items\u2026" : "Order items appear after a successful query." }) });
318
+ }
319
+ function SalesOtherTrackingBlock(props) {
320
+ const runtime = useSalesRuntime();
321
+ const title = text3(props, "heading", "Other shipments");
322
+ return /* @__PURE__ */ jsx3(Section2, { title, children: runtime.phase === "error" ? /* @__PURE__ */ jsx3("p", { role: "alert", children: "Additional shipments are unavailable." }) : runtime.phase === "success" ? /* @__PURE__ */ jsx3("p", { children: text3(props, "emptyMessage", "No other shipments are linked to this order.") }) : /* @__PURE__ */ jsx3("p", { children: "Other tracking numbers appear with your order result." }) });
323
+ }
324
+ function SalesServiceCardsBlock(props) {
325
+ const title = text3(props, "heading", "Shop with confidence");
326
+ return /* @__PURE__ */ jsx3(Section2, { title, children: /* @__PURE__ */ jsxs3("div", { style: gridStyle2, children: [
327
+ /* @__PURE__ */ jsxs3("article", { children: [
328
+ /* @__PURE__ */ jsx3("strong", { children: text3(props, "firstTitle", "Easy returns") }),
329
+ /* @__PURE__ */ jsx3("p", { children: text3(props, "firstDescription", "Simple support when plans change.") })
330
+ ] }),
331
+ /* @__PURE__ */ jsxs3("article", { children: [
332
+ /* @__PURE__ */ jsx3("strong", { children: text3(props, "secondTitle", "Secure delivery") }),
333
+ /* @__PURE__ */ jsx3("p", { children: text3(props, "secondDescription", "Follow every milestone in one place.") })
334
+ ] })
335
+ ] }) });
336
+ }
337
+ function SalesProductCategoriesBlock(props) {
338
+ const title = text3(props, "heading", "Shop by category");
339
+ const collectionId = text3(props, "collectionId", "");
340
+ const state = resourceState(collectionId);
341
+ return /* @__PURE__ */ jsx3(Section2, { title, children: state === "invalid" ? /* @__PURE__ */ jsx3("p", { role: "alert", children: "Collection reference is invalid." }) : state === "empty" ? /* @__PURE__ */ jsx3("p", { children: "No collection selected. Choose a collection through the authorized resource BFF." }) : /* @__PURE__ */ jsxs3("div", { style: gridStyle2, children: [
342
+ /* @__PURE__ */ jsx3("a", { href: safeHref2(props.collectionHref), style: { color: "var(--pb-color-primary)" }, children: text3(props, "collectionLabel", "Featured collection") }),
343
+ /* @__PURE__ */ jsxs3("small", { children: [
344
+ "Collection reference: ",
345
+ collectionId
346
+ ] })
347
+ ] }) });
348
+ }
349
+ function SalesRecommendationsBlock(props) {
350
+ const runtime = useSalesRuntime();
351
+ const title = text3(props, "heading", "Complete your order");
352
+ const recommendations = runtime.result?.recommendations ?? [];
353
+ return /* @__PURE__ */ jsx3(Section2, { title, children: runtime.phase === "error" ? /* @__PURE__ */ jsx3("p", { role: "alert", children: "Recommendations are temporarily unavailable." }) : runtime.phase === "success" && recommendations.length ? /* @__PURE__ */ jsx3("div", { style: gridStyle2, children: recommendations.map((item) => /* @__PURE__ */ jsxs3("article", { style: { borderTop: "3px solid var(--pb-color-primary)", paddingTop: 8 }, children: [
354
+ /* @__PURE__ */ jsx3("strong", { children: item.title }),
355
+ /* @__PURE__ */ jsx3("p", { children: item.description })
356
+ ] }, item.id)) }) : /* @__PURE__ */ jsx3("p", { children: runtime.phase === "success" ? "No recommendations are available for this order." : "Recommended products appear with your order result." }) });
357
+ }
358
+
359
+ // src/sales-definition.ts
360
+ var text4 = (label) => ({ field: "besttrack.sales.text", label });
361
+ var salesBlocks = [
362
+ { type: "besttrack.sales.announcement", version: 1, label: "Announcement", category: "BestTrack Sales", targets: ["web"], defaultProps: { message: "Free delivery on orders over $50" }, defaultVariant: "commerce", variants: [{ id: "commerce", label: "Commerce" }, { id: "minimal", label: "Minimal", theme: { "color.primary": "#202223" } }], fields: { message: text4("Announcement") }, render: { web: SalesAnnouncementBlock } },
363
+ { type: "besttrack.sales.query", version: 1, label: "Order query", category: "BestTrack Sales", targets: ["web"], defaultProps: { heading: "Track an order", submitLabel: "Track order", defaultTrackingNumber: "BT-2048-DEMO" }, defaultVariant: "commerce", variants: [{ id: "commerce", label: "Commerce" }, { id: "compact", label: "Compact", theme: { spacing: "12px" } }], fields: { heading: text4("Heading"), submitLabel: text4("Button label"), defaultTrackingNumber: text4("Default tracking number") }, render: { web: SalesQueryBlock } },
364
+ { type: "besttrack.sales.order-items", version: 1, label: "Order items", category: "BestTrack Sales", targets: ["web"], defaultProps: { heading: "Items in your order" }, defaultVariant: "commerce", variants: [{ id: "commerce", label: "Commerce" }], fields: { heading: text4("Heading") }, render: { web: SalesOrderItemsBlock } },
365
+ { type: "besttrack.sales.other-tracking", version: 1, label: "Other tracking", category: "BestTrack Sales", targets: ["web"], defaultProps: { heading: "Other shipments", emptyMessage: "No other shipments are linked to this order." }, defaultVariant: "commerce", variants: [{ id: "commerce", label: "Commerce" }], fields: { heading: text4("Heading"), emptyMessage: text4("Empty message") }, render: { web: SalesOtherTrackingBlock } },
366
+ { type: "besttrack.sales.service-cards", version: 1, label: "Service cards", category: "BestTrack Sales", targets: ["web"], defaultProps: { heading: "Shop with confidence", firstTitle: "Easy returns", firstDescription: "Simple support when plans change.", secondTitle: "Secure delivery", secondDescription: "Follow every milestone in one place." }, defaultVariant: "commerce", variants: [{ id: "commerce", label: "Commerce" }], fields: { heading: text4("Heading"), firstTitle: text4("First card title"), firstDescription: text4("First card description"), secondTitle: text4("Second card title"), secondDescription: text4("Second card description") }, render: { web: SalesServiceCardsBlock } },
367
+ { type: "besttrack.sales.product-categories", version: 1, label: "Product categories", category: "BestTrack Sales", targets: ["web"], defaultProps: { heading: "Shop by category", collectionId: "gid://shopify/Collection/featured", collectionLabel: "Featured collection", collectionHref: "/collections/featured" }, defaultVariant: "commerce", variants: [{ id: "commerce", label: "Commerce" }, { id: "grid", label: "Grid", theme: { "color.surface": "#f8fafc" } }], fields: { heading: text4("Heading"), collectionId: text4("Collection ID"), collectionLabel: text4("Collection label"), collectionHref: text4("Collection URL") }, render: { web: SalesProductCategoriesBlock } },
368
+ { type: "besttrack.sales.recommendations", version: 1, label: "Recommended products", category: "BestTrack Sales", targets: ["web"], defaultProps: { heading: "Complete your order" }, defaultVariant: "commerce", variants: [{ id: "commerce", label: "Commerce" }, { id: "grid", label: "Product grid", theme: { "color.surface": "#f8fafc" } }], fields: { heading: text4("Heading") }, render: { web: SalesRecommendationsBlock } }
369
+ ];
370
+ function createSalesTemplate() {
371
+ return { id: "besttrack.sales", version: 1, name: "Sales", target: "web", source: "built-in", requiredBlocks: salesBlocks.map((block) => block.type), theme: { "color.primary": "#dc2626", "color.background": "#fffaf5", "color.surface": "#ffffff", "color.text": "#1c1917", "color.border": "#fed7aa", "font.family": "system-ui, sans-serif", radius: "10px", spacing: "20px" }, create: () => createPageDocument3({ pageId: "besttrack-sales", target: "web", templateId: "besttrack.sales", templateVersion: 1, settings: { locale: "en", seoTitle: "Shop and track your order" }, blocks: salesBlocks.map((block, index) => ({ id: "sales-" + (index + 1), type: block.type, version: block.version, props: block.defaultProps, variant: block.defaultVariant, style: {} })) }) };
372
+ }
373
+ var bestTrackSalesExtension = Object.freeze({ name: "besttrack.sales", version: "0.7.2", fields: [{ type: "besttrack.sales.text", component: SalesTextField }], blocks: salesBlocks, templates: [createSalesTemplate()] });
374
+ export {
375
+ BrandedRuntimeProvider,
376
+ ReadyToGoRuntimeProvider,
377
+ SalesRuntimeProvider,
378
+ bestTrackBrandedExtension,
379
+ bestTrackPageExtension,
380
+ bestTrackSalesExtension,
381
+ createBrandedTemplate,
382
+ createReadyToGoTemplate,
383
+ createSalesTemplate
384
+ };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@standhigher/besttrack-page-extension",
3
+ "version": "0.1.0",
4
+ "description": "BestTrack business extension entry point for @standhigher/puck-page-builder.",
5
+ "type": "module",
6
+ "files": [
7
+ "dist",
8
+ "README.md"
9
+ ],
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "dependencies": {
19
+ "@standhigher/puck-page-builder": "0.7.2"
20
+ },
21
+ "devDependencies": {
22
+ "tsup": "^8.5.1",
23
+ "typescript": "^5.9.3"
24
+ },
25
+ "peerDependencies": {
26
+ "react": "^18.0.0"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup src/index.ts --format esm --dts --clean",
30
+ "typecheck": "tsc --noEmit"
31
+ }
32
+ }