@replohq/sdk 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/LICENSE +11 -0
- package/_vendor/replo-utils/lib/crypto.d.ts +30 -0
- package/_vendor/replo-utils/lib/errors.d.ts +83 -0
- package/_vendor/replo-utils/lib/json.d.ts +6 -0
- package/_vendor/replo-utils/lib/json.mjs +43 -0
- package/_vendor/replo-utils/lib/misc.d.ts +145 -0
- package/_vendor/replo-utils/lib/object.d.ts +31 -0
- package/_vendor/schemas/errors.d.ts +459 -0
- package/_vendor/schemas/generated/consent.d.ts +55 -0
- package/_vendor/schemas/generated/errors.d.ts +61 -0
- package/_vendor/schemas/loaderKeys.d.ts +1 -0
- package/_vendor/schemas/loaderKeys.mjs +27 -0
- package/_vendor/schemas/money.d.ts +60 -0
- package/_vendor/schemas/money.mjs +2051 -0
- package/analytics/analytics-provider.d.ts +16 -0
- package/analytics/analytics-provider.js +113 -0
- package/analytics/analytics-provider.js.map +7 -0
- package/analytics/analytics-sink.d.ts +26 -0
- package/analytics/analytics-sink.js +1 -0
- package/analytics/analytics-sink.js.map +7 -0
- package/analytics/get-analytics-sinks.d.ts +27 -0
- package/analytics/get-analytics-sinks.js +52 -0
- package/analytics/get-analytics-sinks.js.map +7 -0
- package/analytics/hooks/use-product-viewed-analytics.d.ts +23 -0
- package/analytics/hooks/use-product-viewed-analytics.js +34 -0
- package/analytics/hooks/use-product-viewed-analytics.js.map +7 -0
- package/analytics/replo-first-party-pixel.d.ts +12 -0
- package/analytics/replo-first-party-pixel.js +31 -0
- package/analytics/replo-first-party-pixel.js.map +7 -0
- package/analytics/replo-pixel-script.d.ts +13 -0
- package/analytics/replo-pixel-script.js +30 -0
- package/analytics/replo-pixel-script.js.map +7 -0
- package/analytics/sinks/ga4-sink.d.ts +15 -0
- package/analytics/sinks/ga4-sink.js +60 -0
- package/analytics/sinks/ga4-sink.js.map +7 -0
- package/analytics/sinks/meta-sink.d.ts +14 -0
- package/analytics/sinks/meta-sink.js +56 -0
- package/analytics/sinks/meta-sink.js.map +7 -0
- package/analytics/sinks/pinterest-sink.d.ts +14 -0
- package/analytics/sinks/pinterest-sink.js +76 -0
- package/analytics/sinks/pinterest-sink.js.map +7 -0
- package/analytics/sinks/reddit-sink.d.ts +14 -0
- package/analytics/sinks/reddit-sink.js +80 -0
- package/analytics/sinks/reddit-sink.js.map +7 -0
- package/analytics/sinks/replo-sink.d.ts +14 -0
- package/analytics/sinks/replo-sink.js +57 -0
- package/analytics/sinks/replo-sink.js.map +7 -0
- package/analytics/sinks/snapchat-sink.d.ts +14 -0
- package/analytics/sinks/snapchat-sink.js +67 -0
- package/analytics/sinks/snapchat-sink.js.map +7 -0
- package/analytics/sinks/tiktok-sink.d.ts +14 -0
- package/analytics/sinks/tiktok-sink.js +79 -0
- package/analytics/sinks/tiktok-sink.js.map +7 -0
- package/analytics/sinks/window-sink.d.ts +18 -0
- package/analytics/sinks/window-sink.js +46 -0
- package/analytics/sinks/window-sink.js.map +7 -0
- package/analytics/utils/analytics-utils.d.ts +7 -0
- package/analytics/utils/analytics-utils.js +130 -0
- package/analytics/utils/analytics-utils.js.map +7 -0
- package/analytics/utils/navigation-tracker.d.ts +16 -0
- package/analytics/utils/navigation-tracker.js +44 -0
- package/analytics/utils/navigation-tracker.js.map +7 -0
- package/analytics/utils/shopify-funnel-gate.d.ts +8 -0
- package/analytics/utils/shopify-funnel-gate.js +22 -0
- package/analytics/utils/shopify-funnel-gate.js.map +7 -0
- package/cart/buy-now-action.d.ts +21 -0
- package/cart/buy-now-action.js +104 -0
- package/cart/buy-now-action.js.map +7 -0
- package/cart/cart-actions.d.ts +63 -0
- package/cart/cart-actions.js +90 -0
- package/cart/cart-actions.js.map +7 -0
- package/cart/cart-loader.d.ts +9 -0
- package/cart/cart-loader.js +11 -0
- package/cart/cart-loader.js.map +7 -0
- package/cart/cart-provider.d.ts +85 -0
- package/cart/cart-provider.js +372 -0
- package/cart/cart-provider.js.map +7 -0
- package/cart/cart-schema.d.ts +74 -0
- package/cart/cart-schema.js +69 -0
- package/cart/cart-schema.js.map +7 -0
- package/cart/cart-types.d.ts +132 -0
- package/cart/cart-types.js +8 -0
- package/cart/cart-types.js.map +7 -0
- package/cart/gateways/cart-constants.d.ts +1 -0
- package/cart/gateways/cart-constants.js +5 -0
- package/cart/gateways/cart-constants.js.map +7 -0
- package/cart/gateways/cart-gateway.d.ts +10 -0
- package/cart/gateways/cart-gateway.js +19 -0
- package/cart/gateways/cart-gateway.js.map +7 -0
- package/cart/gateways/cookie-cart-gateway.d.ts +28 -0
- package/cart/gateways/cookie-cart-gateway.js +109 -0
- package/cart/gateways/cookie-cart-gateway.js.map +7 -0
- package/cart/gateways/default-cart-gateway.d.ts +19 -0
- package/cart/gateways/default-cart-gateway.js +30 -0
- package/cart/gateways/default-cart-gateway.js.map +7 -0
- package/cart/gateways/resolver-cart-gateway.d.ts +2 -0
- package/cart/gateways/resolver-cart-gateway.js +109 -0
- package/cart/gateways/resolver-cart-gateway.js.map +7 -0
- package/cart/gateways/stripe-cart-gateway.d.ts +19 -0
- package/cart/gateways/stripe-cart-gateway.js +28 -0
- package/cart/gateways/stripe-cart-gateway.js.map +7 -0
- package/cart/hooks/use-add-to-cart.d.ts +63 -0
- package/cart/hooks/use-add-to-cart.js +124 -0
- package/cart/hooks/use-add-to-cart.js.map +7 -0
- package/cart/hooks/use-buy-now.d.ts +49 -0
- package/cart/hooks/use-buy-now.js +62 -0
- package/cart/hooks/use-buy-now.js.map +7 -0
- package/cart/utils/cart-utils.d.ts +43 -0
- package/cart/utils/cart-utils.js +46 -0
- package/cart/utils/cart-utils.js.map +7 -0
- package/cart/utils/cookie-cart-persistence.d.ts +55 -0
- package/cart/utils/cookie-cart-persistence.js +166 -0
- package/cart/utils/cookie-cart-persistence.js.map +7 -0
- package/cart/utils/variant-to-cart-line.d.ts +74 -0
- package/cart/utils/variant-to-cart-line.js +162 -0
- package/cart/utils/variant-to-cart-line.js.map +7 -0
- package/chunk-UJCSKKID.mjs +27 -0
- package/consent/consent-action.d.ts +9 -0
- package/consent/consent-action.js +25 -0
- package/consent/consent-action.js.map +7 -0
- package/consent/consent-actions.d.ts +28 -0
- package/consent/consent-actions.js +36 -0
- package/consent/consent-actions.js.map +7 -0
- package/consent/consent-store.d.ts +69 -0
- package/consent/consent-store.js +194 -0
- package/consent/consent-store.js.map +7 -0
- package/consent/cookie.d.ts +36 -0
- package/consent/cookie.js +82 -0
- package/consent/cookie.js.map +7 -0
- package/consent/replo-scripts.d.ts +23 -0
- package/consent/replo-scripts.js +102 -0
- package/consent/replo-scripts.js.map +7 -0
- package/consent/script-registration-store.d.ts +25 -0
- package/consent/script-registration-store.js +61 -0
- package/consent/script-registration-store.js.map +7 -0
- package/consent/script-snippets.d.ts +24 -0
- package/consent/script-snippets.js +122 -0
- package/consent/script-snippets.js.map +7 -0
- package/consent/types.d.ts +34 -0
- package/consent/types.js +1 -0
- package/consent/types.js.map +7 -0
- package/consent/window-api.d.ts +41 -0
- package/consent/window-api.js +73 -0
- package/consent/window-api.js.map +7 -0
- package/env.d.ts +9 -0
- package/env.js +42 -0
- package/env.js.map +7 -0
- package/gateways/canopy-api.d.ts +83 -0
- package/gateways/canopy-api.js +197 -0
- package/gateways/canopy-api.js.map +7 -0
- package/hooks/use-client-value.d.ts +10 -0
- package/hooks/use-client-value.js +12 -0
- package/hooks/use-client-value.js.map +7 -0
- package/hooks/use-formatted-price.d.ts +15 -0
- package/hooks/use-formatted-price.js +20 -0
- package/hooks/use-formatted-price.js.map +7 -0
- package/lib/canopy-error.d.ts +25 -0
- package/lib/canopy-error.js +24 -0
- package/lib/canopy-error.js.map +7 -0
- package/lib/get-query-client.d.ts +2 -0
- package/lib/get-query-client.js +34 -0
- package/lib/get-query-client.js.map +7 -0
- package/lib/integration-utils.d.ts +2 -0
- package/lib/integration-utils.js +9 -0
- package/lib/integration-utils.js.map +7 -0
- package/lib/shopify-utils.d.ts +10 -0
- package/lib/shopify-utils.js +7 -0
- package/lib/shopify-utils.js.map +7 -0
- package/lib/url-utils.d.ts +4 -0
- package/lib/url-utils.js +8 -0
- package/lib/url-utils.js.map +7 -0
- package/loaders/collection-loader.d.ts +11 -0
- package/loaders/collection-loader.js +55 -0
- package/loaders/collection-loader.js.map +7 -0
- package/loaders/collection-products-loader.d.ts +12 -0
- package/loaders/collection-products-loader.js +58 -0
- package/loaders/collection-products-loader.js.map +7 -0
- package/loaders/contentful-entries-loader.d.ts +17 -0
- package/loaders/contentful-entries-loader.js +78 -0
- package/loaders/contentful-entries-loader.js.map +7 -0
- package/loaders/contentful-entry-loader.d.ts +14 -0
- package/loaders/contentful-entry-loader.js +60 -0
- package/loaders/contentful-entry-loader.js.map +7 -0
- package/loaders/invoke-loader-action.d.ts +11 -0
- package/loaders/invoke-loader-action.js +14 -0
- package/loaders/invoke-loader-action.js.map +7 -0
- package/loaders/invoke-loader-server.d.ts +18 -0
- package/loaders/invoke-loader-server.js +40 -0
- package/loaders/invoke-loader-server.js.map +7 -0
- package/loaders/invoke-loader.d.ts +16 -0
- package/loaders/invoke-loader.js +37 -0
- package/loaders/invoke-loader.js.map +7 -0
- package/loaders/levanta-product-loader.d.ts +11 -0
- package/loaders/levanta-product-loader.js +53 -0
- package/loaders/levanta-product-loader.js.map +7 -0
- package/loaders/loader-error-boundary.d.ts +25 -0
- package/loaders/loader-error-boundary.js +24 -0
- package/loaders/loader-error-boundary.js.map +7 -0
- package/loaders/loader-keys.d.ts +34 -0
- package/loaders/loader-keys.js +26 -0
- package/loaders/loader-keys.js.map +7 -0
- package/loaders/loader-schemas.d.ts +946 -0
- package/loaders/loader-schemas.js +353 -0
- package/loaders/loader-schemas.js.map +7 -0
- package/loaders/metaobject-loader.d.ts +12 -0
- package/loaders/metaobject-loader.js +54 -0
- package/loaders/metaobject-loader.js.map +7 -0
- package/loaders/metaobjects-loader.d.ts +14 -0
- package/loaders/metaobjects-loader.js +60 -0
- package/loaders/metaobjects-loader.js.map +7 -0
- package/loaders/okendo-review-aggregate-loader.d.ts +17 -0
- package/loaders/okendo-review-aggregate-loader.js +56 -0
- package/loaders/okendo-review-aggregate-loader.js.map +7 -0
- package/loaders/okendo-reviews-loader.d.ts +23 -0
- package/loaders/okendo-reviews-loader.js +54 -0
- package/loaders/okendo-reviews-loader.js.map +7 -0
- package/loaders/prefetch-loaders.d.ts +33 -0
- package/loaders/prefetch-loaders.js +46 -0
- package/loaders/prefetch-loaders.js.map +7 -0
- package/loaders/preview-placeholder.d.ts +25 -0
- package/loaders/preview-placeholder.js +222 -0
- package/loaders/preview-placeholder.js.map +7 -0
- package/loaders/product-loader-args.d.ts +21 -0
- package/loaders/product-loader-args.js +17 -0
- package/loaders/product-loader-args.js.map +7 -0
- package/loaders/product-loader.d.ts +10 -0
- package/loaders/product-loader.js +52 -0
- package/loaders/product-loader.js.map +7 -0
- package/loaders/rebuy-recommendations-loader.d.ts +165 -0
- package/loaders/rebuy-recommendations-loader.js +57 -0
- package/loaders/rebuy-recommendations-loader.js.map +7 -0
- package/loaders/reviews-io-review-aggregate-loader.d.ts +12 -0
- package/loaders/reviews-io-review-aggregate-loader.js +43 -0
- package/loaders/reviews-io-review-aggregate-loader.js.map +7 -0
- package/loaders/reviews-io-reviews-loader.d.ts +14 -0
- package/loaders/reviews-io-reviews-loader.js +65 -0
- package/loaders/reviews-io-reviews-loader.js.map +7 -0
- package/loaders/smile-earning-rules-loader.d.ts +19 -0
- package/loaders/smile-earning-rules-loader.js +44 -0
- package/loaders/smile-earning-rules-loader.js.map +7 -0
- package/loaders/smile-rewards-loader.d.ts +19 -0
- package/loaders/smile-rewards-loader.js +44 -0
- package/loaders/smile-rewards-loader.js.map +7 -0
- package/loaders/smile-vip-tiers-loader.d.ts +18 -0
- package/loaders/smile-vip-tiers-loader.js +43 -0
- package/loaders/smile-vip-tiers-loader.js.map +7 -0
- package/loaders/statsig-experiment-loader.d.ts +20 -0
- package/loaders/statsig-experiment-loader.js +81 -0
- package/loaders/statsig-experiment-loader.js.map +7 -0
- package/loaders/yotpo-review-aggregate-loader.d.ts +12 -0
- package/loaders/yotpo-review-aggregate-loader.js +50 -0
- package/loaders/yotpo-review-aggregate-loader.js.map +7 -0
- package/loaders/yotpo-reviews-loader.d.ts +14 -0
- package/loaders/yotpo-reviews-loader.js +65 -0
- package/loaders/yotpo-reviews-loader.js.map +7 -0
- package/money.d.ts +9 -0
- package/money.js +13 -0
- package/money.js.map +7 -0
- package/next/config.d.ts +4 -0
- package/next/config.js +91 -0
- package/next/config.js.map +7 -0
- package/next/image-loader.d.ts +2 -0
- package/next/image-loader.js +52 -0
- package/next/image-loader.js.map +7 -0
- package/next/open-next.d.ts +2 -0
- package/next/open-next.js +24 -0
- package/next/open-next.js.map +7 -0
- package/orders/order-status-schema.d.ts +161 -0
- package/orders/order-status-schema.js +83 -0
- package/orders/order-status-schema.js.map +7 -0
- package/orders/order-status.d.ts +11 -0
- package/orders/order-status.js +41 -0
- package/orders/order-status.js.map +7 -0
- package/package.json +216 -0
- package/providers/query-provider.d.ts +1 -0
- package/providers/query-provider.js +12 -0
- package/providers/query-provider.js.map +7 -0
- package/providers/replo-provider.d.ts +1 -0
- package/providers/replo-provider.js +22 -0
- package/providers/replo-provider.js.map +7 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
declare const okendoReviewsOutputSchema: z.ZodObject<{
|
|
4
|
+
reviews: z.ZodArray<z.ZodObject<{
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
title: z.ZodOptional<z.ZodString>;
|
|
7
|
+
body: z.ZodOptional<z.ZodString>;
|
|
8
|
+
rating: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
productName: z.ZodOptional<z.ZodString>;
|
|
10
|
+
reviewerName: z.ZodOptional<z.ZodString>;
|
|
11
|
+
dateCreated: z.ZodOptional<z.ZodString>;
|
|
12
|
+
status: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, z.core.$strip>>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
type OkendoReviewsData = z.infer<typeof okendoReviewsOutputSchema>;
|
|
16
|
+
type OkendoReviewsLoaderProps = {
|
|
17
|
+
loaderKey: string;
|
|
18
|
+
shopifyProductId: string;
|
|
19
|
+
children: (data: OkendoReviewsData) => ReactNode;
|
|
20
|
+
fallback?: ReactNode;
|
|
21
|
+
};
|
|
22
|
+
export declare function OkendoReviewsLoader({ loaderKey, shopifyProductId, children, fallback, }: OkendoReviewsLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Suspense } from "react";
|
|
4
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { invokeLoader } from "./invoke-loader";
|
|
7
|
+
import { LoaderErrorBoundary } from "./loader-error-boundary";
|
|
8
|
+
import { okendoReviewSchema } from "./loader-schemas";
|
|
9
|
+
const okendoReviewsOutputSchema = z.object({
|
|
10
|
+
reviews: z.array(okendoReviewSchema)
|
|
11
|
+
});
|
|
12
|
+
function OkendoReviewsLoaderInner({
|
|
13
|
+
loaderKey,
|
|
14
|
+
shopifyProductId,
|
|
15
|
+
children
|
|
16
|
+
}) {
|
|
17
|
+
const queryResult = useSuspenseQuery({
|
|
18
|
+
queryKey: [loaderKey, { shopifyProductId }],
|
|
19
|
+
queryFn: async () => {
|
|
20
|
+
const loadedData = await invokeLoader({
|
|
21
|
+
loaderKey,
|
|
22
|
+
args: { shopifyProductId }
|
|
23
|
+
});
|
|
24
|
+
return okendoReviewsOutputSchema.parse(loadedData);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(queryResult.data) });
|
|
28
|
+
}
|
|
29
|
+
function OkendoReviewsLoader({
|
|
30
|
+
loaderKey,
|
|
31
|
+
shopifyProductId,
|
|
32
|
+
children,
|
|
33
|
+
fallback
|
|
34
|
+
}) {
|
|
35
|
+
return /* @__PURE__ */ jsx(
|
|
36
|
+
LoaderErrorBoundary,
|
|
37
|
+
{
|
|
38
|
+
fallback: fallback ?? null,
|
|
39
|
+
resetKey: `${loaderKey}:${shopifyProductId}`,
|
|
40
|
+
children: /* @__PURE__ */ jsx(Suspense, { fallback: fallback ?? null, children: /* @__PURE__ */ jsx(
|
|
41
|
+
OkendoReviewsLoaderInner,
|
|
42
|
+
{
|
|
43
|
+
loaderKey,
|
|
44
|
+
shopifyProductId,
|
|
45
|
+
children
|
|
46
|
+
}
|
|
47
|
+
) })
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
OkendoReviewsLoader
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=okendo-reviews-loader.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../loaders/okendo-reviews-loader.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { Suspense } from \"react\";\n\nimport { useSuspenseQuery } from \"@tanstack/react-query\";\nimport { z } from \"zod\";\n\nimport { invokeLoader } from \"./invoke-loader\";\nimport { LoaderErrorBoundary } from \"./loader-error-boundary\";\nimport { okendoReviewSchema } from \"./loader-schemas\";\n\nconst okendoReviewsOutputSchema = z.object({\n reviews: z.array(okendoReviewSchema),\n});\n\ntype OkendoReviewsData = z.infer<typeof okendoReviewsOutputSchema>;\n\ntype OkendoReviewsLoaderProps = {\n loaderKey: string;\n shopifyProductId: string;\n children: (data: OkendoReviewsData) => ReactNode;\n fallback?: ReactNode;\n};\n\nfunction OkendoReviewsLoaderInner({\n loaderKey,\n shopifyProductId,\n children,\n}: Omit<OkendoReviewsLoaderProps, \"fallback\">) {\n const queryResult = useSuspenseQuery({\n queryKey: [loaderKey, { shopifyProductId }],\n queryFn: async () => {\n const loadedData = await invokeLoader({\n loaderKey,\n args: { shopifyProductId },\n });\n return okendoReviewsOutputSchema.parse(loadedData);\n },\n });\n\n return <>{children(queryResult.data)}</>;\n}\n\nexport function OkendoReviewsLoader({\n loaderKey,\n shopifyProductId,\n children,\n fallback,\n}: OkendoReviewsLoaderProps) {\n return (\n <LoaderErrorBoundary\n fallback={fallback ?? null}\n resetKey={`${loaderKey}:${shopifyProductId}`}\n >\n <Suspense fallback={fallback ?? null}>\n <OkendoReviewsLoaderInner\n loaderKey={loaderKey}\n shopifyProductId={shopifyProductId}\n >\n {children}\n </OkendoReviewsLoaderInner>\n </Suspense>\n </LoaderErrorBoundary>\n );\n}\n"],
|
|
5
|
+
"mappings": ";AA0CS;AAtCT,SAAS,gBAAgB;AAEzB,SAAS,wBAAwB;AACjC,SAAS,SAAS;AAElB,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AAEnC,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,SAAS,EAAE,MAAM,kBAAkB;AACrC,CAAC;AAWD,SAAS,yBAAyB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,GAA+C;AAC7C,QAAM,cAAc,iBAAiB;AAAA,IACnC,UAAU,CAAC,WAAW,EAAE,iBAAiB,CAAC;AAAA,IAC1C,SAAS,YAAY;AACnB,YAAM,aAAa,MAAM,aAAa;AAAA,QACpC;AAAA,QACA,MAAM,EAAE,iBAAiB;AAAA,MAC3B,CAAC;AACD,aAAO,0BAA0B,MAAM,UAAU;AAAA,IACnD;AAAA,EACF,CAAC;AAED,SAAO,gCAAG,mBAAS,YAAY,IAAI,GAAE;AACvC;AAEO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,YAAY;AAAA,MACtB,UAAU,GAAG,SAAS,IAAI,gBAAgB;AAAA,MAE1C,8BAAC,YAAS,UAAU,YAAY,MAC9B;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UAEC;AAAA;AAAA,MACH,GACF;AAAA;AAAA,EACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type PrefetchLoaderConfig = {
|
|
2
|
+
loaderKey: string;
|
|
3
|
+
args: Record<string, unknown>;
|
|
4
|
+
/** How long the server-side cache entry is fresh. Defaults to 60s. Use `false` for indefinite (tag-based invalidation only). */
|
|
5
|
+
revalidateSeconds?: number | false;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Server component that prefetches one or more loader queries and seeds the
|
|
9
|
+
* React Query cache via HydrationBoundary. Client components that use
|
|
10
|
+
* useSuspenseQuery with the same queryKey will get the data immediately
|
|
11
|
+
* in the initial HTML — no loading state on first paint.
|
|
12
|
+
*
|
|
13
|
+
* The queryKey is derived internally as `[loaderKey, args]`, matching the
|
|
14
|
+
* convention used by all loader components.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* import { DATA_LOADER_KEYS } from "@replohq/sdk/loaders/loader-keys";
|
|
18
|
+
*
|
|
19
|
+
* <PrefetchedLoaders
|
|
20
|
+
* queries={[
|
|
21
|
+
* {
|
|
22
|
+
* loaderKey: DATA_LOADER_KEYS.SHOPIFY_PRODUCT,
|
|
23
|
+
* args: { productId: "gid://shopify/Product/123" },
|
|
24
|
+
* },
|
|
25
|
+
* ]}
|
|
26
|
+
* >
|
|
27
|
+
* <ProductPreview productId="gid://shopify/Product/123" />
|
|
28
|
+
* </PrefetchedLoaders>
|
|
29
|
+
*/
|
|
30
|
+
export declare function PrefetchedLoaders({ queries, children, }: React.PropsWithChildren<{
|
|
31
|
+
queries: PrefetchLoaderConfig[];
|
|
32
|
+
}>): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import {
|
|
3
|
+
dehydrate,
|
|
4
|
+
HydrationBoundary,
|
|
5
|
+
QueryClient
|
|
6
|
+
} from "@tanstack/react-query";
|
|
7
|
+
import { invokeLoaderServer } from "./invoke-loader-server";
|
|
8
|
+
async function PrefetchedLoaders({
|
|
9
|
+
queries,
|
|
10
|
+
children
|
|
11
|
+
}) {
|
|
12
|
+
const queryClient = new QueryClient({
|
|
13
|
+
defaultOptions: {
|
|
14
|
+
queries: { staleTime: 60 * 1e3 }
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
await Promise.all(
|
|
18
|
+
queries.map((query) => {
|
|
19
|
+
return queryClient.prefetchQuery({
|
|
20
|
+
queryKey: [query.loaderKey, query.args],
|
|
21
|
+
queryFn: () => {
|
|
22
|
+
return invokeLoaderServer({
|
|
23
|
+
loaderKey: query.loaderKey,
|
|
24
|
+
args: query.args,
|
|
25
|
+
revalidateSeconds: query.revalidateSeconds
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
HydrationBoundary,
|
|
33
|
+
{
|
|
34
|
+
state: dehydrate(queryClient, {
|
|
35
|
+
shouldDehydrateQuery: (query) => {
|
|
36
|
+
return query.state.status === "success" || query.state.status === "pending";
|
|
37
|
+
}
|
|
38
|
+
}),
|
|
39
|
+
children
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
PrefetchedLoaders
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=prefetch-loaders.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../loaders/prefetch-loaders.tsx"],
|
|
4
|
+
"sourcesContent": ["import {\n dehydrate,\n HydrationBoundary,\n QueryClient,\n} from \"@tanstack/react-query\";\n\nimport { invokeLoaderServer } from \"./invoke-loader-server\";\n\ntype PrefetchLoaderConfig = {\n loaderKey: string;\n args: Record<string, unknown>;\n /** How long the server-side cache entry is fresh. Defaults to 60s. Use `false` for indefinite (tag-based invalidation only). */\n revalidateSeconds?: number | false;\n};\n\n/**\n * Server component that prefetches one or more loader queries and seeds the\n * React Query cache via HydrationBoundary. Client components that use\n * useSuspenseQuery with the same queryKey will get the data immediately\n * in the initial HTML \u2014 no loading state on first paint.\n *\n * The queryKey is derived internally as `[loaderKey, args]`, matching the\n * convention used by all loader components.\n *\n * Usage:\n * import { DATA_LOADER_KEYS } from \"@replohq/sdk/loaders/loader-keys\";\n *\n * <PrefetchedLoaders\n * queries={[\n * {\n * loaderKey: DATA_LOADER_KEYS.SHOPIFY_PRODUCT,\n * args: { productId: \"gid://shopify/Product/123\" },\n * },\n * ]}\n * >\n * <ProductPreview productId=\"gid://shopify/Product/123\" />\n * </PrefetchedLoaders>\n */\nexport async function PrefetchedLoaders({\n queries,\n children,\n}: React.PropsWithChildren<{\n queries: PrefetchLoaderConfig[];\n}>) {\n const queryClient = new QueryClient({\n defaultOptions: {\n queries: { staleTime: 60 * 1000 },\n },\n });\n\n await Promise.all(\n queries.map((query) => {\n return queryClient.prefetchQuery({\n queryKey: [query.loaderKey, query.args],\n queryFn: () => {\n return invokeLoaderServer({\n loaderKey: query.loaderKey,\n args: query.args,\n revalidateSeconds: query.revalidateSeconds,\n });\n },\n });\n }),\n );\n\n return (\n <HydrationBoundary\n state={dehydrate(queryClient, {\n shouldDehydrateQuery: (query) => {\n return (\n query.state.status === \"success\" || query.state.status === \"pending\"\n );\n },\n })}\n >\n {children}\n </HydrationBoundary>\n );\n}\n"],
|
|
5
|
+
"mappings": "AAkEI;AAlEJ;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,0BAA0B;AAgCnC,eAAsB,kBAAkB;AAAA,EACtC;AAAA,EACA;AACF,GAEI;AACF,QAAM,cAAc,IAAI,YAAY;AAAA,IAClC,gBAAgB;AAAA,MACd,SAAS,EAAE,WAAW,KAAK,IAAK;AAAA,IAClC;AAAA,EACF,CAAC;AAED,QAAM,QAAQ;AAAA,IACZ,QAAQ,IAAI,CAAC,UAAU;AACrB,aAAO,YAAY,cAAc;AAAA,QAC/B,UAAU,CAAC,MAAM,WAAW,MAAM,IAAI;AAAA,QACtC,SAAS,MAAM;AACb,iBAAO,mBAAmB;AAAA,YACxB,WAAW,MAAM;AAAA,YACjB,MAAM,MAAM;AAAA,YACZ,mBAAmB,MAAM;AAAA,UAC3B,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,UAAU,aAAa;AAAA,QAC5B,sBAAsB,CAAC,UAAU;AAC/B,iBACE,MAAM,MAAM,WAAW,aAAa,MAAM,MAAM,WAAW;AAAA,QAE/D;AAAA,MACF,CAAC;AAAA,MAEA;AAAA;AAAA,EACH;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare function hasUnfilledRouteParam(args: Record<string, unknown>): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Sample data for a loader invoked with an unfilled dynamic-route param, or
|
|
4
|
+
* null when the invocation should proceed normally.
|
|
5
|
+
*
|
|
6
|
+
* On the server, substitution happens only on dev servers (`next dev` — how
|
|
7
|
+
* every sandbox preview runs); production builds keep fetching so bracket
|
|
8
|
+
* URLs keep their real miss/404 semantics. Substituting during SSR keeps the
|
|
9
|
+
* doomed fetch from erroring the dev overlay mid-stream. In the browser,
|
|
10
|
+
* substitution happens only inside an editor iframe.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolvePreviewPlaceholderData({ loaderKey, args, }: {
|
|
13
|
+
loaderKey: string;
|
|
14
|
+
args: Record<string, unknown>;
|
|
15
|
+
}): unknown;
|
|
16
|
+
/** Whether loader output is one of this module's sample fixtures. */
|
|
17
|
+
export declare function isPreviewPlaceholderData(data: unknown): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Notify the embedding editor when a loader component rendered sample data.
|
|
20
|
+
* The single reporting path: inspecting rendered data (rather than hooking
|
|
21
|
+
* loader invocation) covers client fetches and SSR-hydrated data alike —
|
|
22
|
+
* hydration never re-invokes the loader on the client. Receivers treat the
|
|
23
|
+
* message as idempotent, so no dedupe is needed.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useReportPreviewPlaceholderRender(data: unknown): void;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { DATA_LOADER_KEYS } from "./loader-keys";
|
|
4
|
+
const PLACEHOLDER_RENDER_MESSAGE_TYPE = "replo:preview:placeholder-render";
|
|
5
|
+
const UNFILLED_ROUTE_PARAM_RE = /^\[{1,2}(?:\.\.\.)?[^\]]+\]{1,2}$/;
|
|
6
|
+
const SAMPLE_ID_PREFIX = "replo-sample-";
|
|
7
|
+
const SAMPLE_SIZES = [
|
|
8
|
+
{ value: "Small", priceAmount: "42.00" },
|
|
9
|
+
{ value: "Medium", priceAmount: "46.00" },
|
|
10
|
+
{ value: "Large", priceAmount: "50.00" }
|
|
11
|
+
];
|
|
12
|
+
const SAMPLE_COLORS = [
|
|
13
|
+
{ value: "Slate", swatchColor: "#64748b" },
|
|
14
|
+
{ value: "Sand", swatchColor: "#d6bc8a" }
|
|
15
|
+
];
|
|
16
|
+
const SOLD_OUT_COMBINATION = { size: "Large", color: "Sand" };
|
|
17
|
+
function toSampleSlug(value) {
|
|
18
|
+
return value.toLowerCase().replace(/\s+/g, "-");
|
|
19
|
+
}
|
|
20
|
+
const SAMPLE_VARIANTS = SAMPLE_SIZES.flatMap(
|
|
21
|
+
(size) => {
|
|
22
|
+
return SAMPLE_COLORS.map((color) => {
|
|
23
|
+
const isSoldOut = size.value === SOLD_OUT_COMBINATION.size && color.value === SOLD_OUT_COMBINATION.color;
|
|
24
|
+
const slug = `${toSampleSlug(size.value)}-${toSampleSlug(color.value)}`;
|
|
25
|
+
return {
|
|
26
|
+
id: `replo-sample-variant-${slug}`,
|
|
27
|
+
title: `${size.value} / ${color.value}`,
|
|
28
|
+
availableForSale: !isSoldOut,
|
|
29
|
+
sku: `REPLO-SAMPLE-${slug.toUpperCase()}`,
|
|
30
|
+
selectedOptions: [
|
|
31
|
+
{ name: "Size", value: size.value },
|
|
32
|
+
{ name: "Color", value: color.value }
|
|
33
|
+
],
|
|
34
|
+
image: null,
|
|
35
|
+
price: { amount: size.priceAmount, currencyCode: "USD" },
|
|
36
|
+
// Every variant is "on sale" so strikethrough pricing renders.
|
|
37
|
+
compareAtPrice: { amount: "56.00", currencyCode: "USD" },
|
|
38
|
+
sellingPlanIds: []
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
const SAMPLE_PRODUCT = {
|
|
44
|
+
id: "replo-sample-product",
|
|
45
|
+
handle: "sample-product",
|
|
46
|
+
title: "Sample product",
|
|
47
|
+
description: "This is sample product data for previewing this page. Open the page from a real product link to see live details. Sizes, colors, sale pricing, and a sold-out variant are included so every part of the layout has something to show.",
|
|
48
|
+
descriptionHtml: "<p>This is sample product data for previewing this page. Open the page from a real product link to see live details.</p><p>The sample includes:</p><ul><li>Three sizes and two colors to try the option pickers</li><li>Sale pricing with a compare-at price</li><li>One sold-out combination (Large / Sand)</li></ul>",
|
|
49
|
+
availableForSale: true,
|
|
50
|
+
featuredImage: null,
|
|
51
|
+
// `id` is optional in the schema but always present on real data, and
|
|
52
|
+
// generated pages key option lists on it — omit it and previews log React
|
|
53
|
+
// "missing key" console errors, which the dev overlay counts as page errors.
|
|
54
|
+
options: [
|
|
55
|
+
{
|
|
56
|
+
id: "replo-sample-option-size",
|
|
57
|
+
name: "Size",
|
|
58
|
+
values: SAMPLE_SIZES.map((size) => size.value),
|
|
59
|
+
optionValues: SAMPLE_SIZES.map((size) => ({
|
|
60
|
+
id: `replo-sample-option-value-${toSampleSlug(size.value)}`,
|
|
61
|
+
value: size.value,
|
|
62
|
+
swatch: null
|
|
63
|
+
}))
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "replo-sample-option-color",
|
|
67
|
+
name: "Color",
|
|
68
|
+
values: SAMPLE_COLORS.map((color) => color.value),
|
|
69
|
+
optionValues: SAMPLE_COLORS.map((color) => ({
|
|
70
|
+
id: `replo-sample-option-value-${toSampleSlug(color.value)}`,
|
|
71
|
+
value: color.value,
|
|
72
|
+
swatch: { color: color.swatchColor, imageUrl: null }
|
|
73
|
+
}))
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
variants: SAMPLE_VARIANTS,
|
|
77
|
+
images: [],
|
|
78
|
+
media: [],
|
|
79
|
+
vendor: "Sample Supply Co.",
|
|
80
|
+
productType: "Sample",
|
|
81
|
+
tags: ["sample-data", "new-arrival", "bestseller"],
|
|
82
|
+
sellingPlanGroups: [],
|
|
83
|
+
metafields: []
|
|
84
|
+
};
|
|
85
|
+
const SAMPLE_COLLECTION = {
|
|
86
|
+
id: "replo-sample-collection",
|
|
87
|
+
handle: "sample-collection",
|
|
88
|
+
title: "Sample collection",
|
|
89
|
+
descriptionHtml: "<p>This is sample collection data for previewing this page.</p>",
|
|
90
|
+
image: null
|
|
91
|
+
};
|
|
92
|
+
const SAMPLE_GRID_CARDS = [
|
|
93
|
+
{
|
|
94
|
+
slug: "range",
|
|
95
|
+
title: "Sample product with a price range",
|
|
96
|
+
minAmount: "42.00",
|
|
97
|
+
maxAmount: "50.00",
|
|
98
|
+
compareAtAmount: "0.0",
|
|
99
|
+
availableForSale: true
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
slug: "sale",
|
|
103
|
+
title: "Sample product on sale",
|
|
104
|
+
minAmount: "42.00",
|
|
105
|
+
maxAmount: "42.00",
|
|
106
|
+
compareAtAmount: "56.00",
|
|
107
|
+
availableForSale: true
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
slug: "sold-out",
|
|
111
|
+
title: "Sample product that sold out",
|
|
112
|
+
minAmount: "46.00",
|
|
113
|
+
maxAmount: "46.00",
|
|
114
|
+
compareAtAmount: "0.0",
|
|
115
|
+
availableForSale: false
|
|
116
|
+
}
|
|
117
|
+
];
|
|
118
|
+
const SAMPLE_PAGINATED_PRODUCTS = {
|
|
119
|
+
products: SAMPLE_GRID_CARDS.map((card) => ({
|
|
120
|
+
id: `replo-sample-product-${card.slug}`,
|
|
121
|
+
handle: `sample-product-${card.slug}`,
|
|
122
|
+
title: card.title,
|
|
123
|
+
availableForSale: card.availableForSale,
|
|
124
|
+
featuredImage: null,
|
|
125
|
+
options: [
|
|
126
|
+
{
|
|
127
|
+
id: `replo-sample-option-color-${card.slug}`,
|
|
128
|
+
name: "Color",
|
|
129
|
+
values: SAMPLE_COLORS.map((color) => color.value),
|
|
130
|
+
optionValues: SAMPLE_COLORS.map((color) => ({
|
|
131
|
+
id: `replo-sample-option-value-${card.slug}-${toSampleSlug(color.value)}`,
|
|
132
|
+
value: color.value,
|
|
133
|
+
swatch: { color: color.swatchColor, imageUrl: null }
|
|
134
|
+
}))
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
variants: SAMPLE_COLORS.map((color) => ({
|
|
138
|
+
id: `replo-sample-variant-${card.slug}-${toSampleSlug(color.value)}`,
|
|
139
|
+
title: color.value,
|
|
140
|
+
availableForSale: card.availableForSale,
|
|
141
|
+
sku: `REPLO-SAMPLE-${card.slug.toUpperCase()}-${toSampleSlug(color.value).toUpperCase()}`,
|
|
142
|
+
selectedOptions: [{ name: "Color", value: color.value }],
|
|
143
|
+
image: null,
|
|
144
|
+
price: { amount: card.minAmount, currencyCode: "USD" },
|
|
145
|
+
compareAtPrice: Number(card.compareAtAmount) > 0 ? { amount: card.compareAtAmount, currencyCode: "USD" } : null
|
|
146
|
+
})),
|
|
147
|
+
minPrice: { amount: card.minAmount, currencyCode: "USD" },
|
|
148
|
+
maxPrice: { amount: card.maxAmount, currencyCode: "USD" },
|
|
149
|
+
compareAtMinPrice: { amount: card.compareAtAmount, currencyCode: "USD" },
|
|
150
|
+
compareAtMaxPrice: { amount: card.compareAtAmount, currencyCode: "USD" }
|
|
151
|
+
})),
|
|
152
|
+
pageInfo: { hasNextPage: false, endCursor: null }
|
|
153
|
+
};
|
|
154
|
+
const PLACEHOLDER_DATA_BY_LOADER_KEY = {
|
|
155
|
+
[DATA_LOADER_KEYS.SHOPIFY_PRODUCT]: SAMPLE_PRODUCT,
|
|
156
|
+
[DATA_LOADER_KEYS.REPLO_PRODUCT]: SAMPLE_PRODUCT,
|
|
157
|
+
[DATA_LOADER_KEYS.SHOPIFY_COLLECTION]: SAMPLE_COLLECTION,
|
|
158
|
+
[DATA_LOADER_KEYS.SHOPIFY_COLLECTION_PRODUCTS]: SAMPLE_PAGINATED_PRODUCTS
|
|
159
|
+
};
|
|
160
|
+
function isUnfilledRouteParamValue(value) {
|
|
161
|
+
if (UNFILLED_ROUTE_PARAM_RE.test(value)) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
return UNFILLED_ROUTE_PARAM_RE.test(decodeURIComponent(value));
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function hasUnfilledRouteParam(args) {
|
|
171
|
+
return Object.values(args).some(
|
|
172
|
+
(value) => typeof value === "string" && isUnfilledRouteParamValue(value)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
function resolvePreviewPlaceholderData({
|
|
176
|
+
loaderKey,
|
|
177
|
+
args
|
|
178
|
+
}) {
|
|
179
|
+
if (!hasUnfilledRouteParam(args)) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const placeholderData = PLACEHOLDER_DATA_BY_LOADER_KEY[loaderKey];
|
|
183
|
+
if (placeholderData === void 0) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
if (typeof window === "undefined") {
|
|
187
|
+
return process.env.NODE_ENV === "development" ? placeholderData : null;
|
|
188
|
+
}
|
|
189
|
+
return window.parent === window ? null : placeholderData;
|
|
190
|
+
}
|
|
191
|
+
const placeholderIdSchema = z.object({ id: z.string() });
|
|
192
|
+
const placeholderProductsSchema = z.object({
|
|
193
|
+
products: z.array(z.object({ id: z.string() })).min(1)
|
|
194
|
+
});
|
|
195
|
+
function isPreviewPlaceholderData(data) {
|
|
196
|
+
const withId = placeholderIdSchema.safeParse(data);
|
|
197
|
+
if (withId.success) {
|
|
198
|
+
return withId.data.id.startsWith(SAMPLE_ID_PREFIX);
|
|
199
|
+
}
|
|
200
|
+
const withProducts = placeholderProductsSchema.safeParse(data);
|
|
201
|
+
return withProducts.success && (withProducts.data.products[0]?.id.startsWith(SAMPLE_ID_PREFIX) ?? false);
|
|
202
|
+
}
|
|
203
|
+
function useReportPreviewPlaceholderRender(data) {
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (window.parent !== window && isPreviewPlaceholderData(data)) {
|
|
206
|
+
window.parent.postMessage(
|
|
207
|
+
{
|
|
208
|
+
type: PLACEHOLDER_RENDER_MESSAGE_TYPE,
|
|
209
|
+
route: window.location.pathname
|
|
210
|
+
},
|
|
211
|
+
"*"
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}, [data]);
|
|
215
|
+
}
|
|
216
|
+
export {
|
|
217
|
+
hasUnfilledRouteParam,
|
|
218
|
+
isPreviewPlaceholderData,
|
|
219
|
+
resolvePreviewPlaceholderData,
|
|
220
|
+
useReportPreviewPlaceholderRender
|
|
221
|
+
};
|
|
222
|
+
//# sourceMappingURL=preview-placeholder.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../loaders/preview-placeholder.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Sample-data substitution for previewing dynamic routes inside the editor.\n *\n * When the editor previews an unfilled Next.js dynamic route (the literal\n * `/products/[handle]` URL), the page receives the bracket token itself as its\n * param, every lookup misses, and the preview 404s. Loader invocations detect\n * that exact case \u2014 editor iframe + a bracket-token arg \u2014 and substitute a\n * type-correct sample fixture so the page renders its real layout with sample\n * data. A `replo:preview:placeholder-render` message tells the embedding\n * editor the preview is showing sample data (mirrors `replo:preview:status`\n * from NextErrorReporter).\n *\n * Substitution is gated to editor iframes only: production traffic to a\n * bracket URL still misses lookups and 404s, so this can never soft-404 a\n * published site.\n */\nimport type {\n FullCollection,\n FullProduct,\n PaginatedProducts,\n} from \"./loader-schemas\";\n\nimport { useEffect } from \"react\";\n\nimport { z } from \"zod\";\n\nimport { DATA_LOADER_KEYS } from \"./loader-keys\";\n\nconst PLACEHOLDER_RENDER_MESSAGE_TYPE = \"replo:preview:placeholder-render\";\n\n// Matches a literal Next.js dynamic segment passed through as a param value:\n// `[handle]`, `[...slug]`, or `[[...slug]]`.\nconst UNFILLED_ROUTE_PARAM_RE = /^\\[{1,2}(?:\\.\\.\\.)?[^\\]]+\\]{1,2}$/;\n\n// Every fixture id carries this prefix so substituted data is recognizable\n// after it round-trips through SSR + React Query hydration (where the client\n// never re-invokes the loader and can't observe the substitution directly).\nconst SAMPLE_ID_PREFIX = \"replo-sample-\";\n\n// The sample option matrix. Sizes carry the price so switching variants\n// visibly updates pricing; colors carry swatches so pickers render them.\nconst SAMPLE_SIZES = [\n { value: \"Small\", priceAmount: \"42.00\" },\n { value: \"Medium\", priceAmount: \"46.00\" },\n { value: \"Large\", priceAmount: \"50.00\" },\n];\nconst SAMPLE_COLORS = [\n { value: \"Slate\", swatchColor: \"#64748b\" },\n { value: \"Sand\", swatchColor: \"#d6bc8a\" },\n];\n// One sold-out combination so disabled buttons / \"Sold out\" states render.\nconst SOLD_OUT_COMBINATION = { size: \"Large\", color: \"Sand\" };\n\nfunction toSampleSlug(value: string): string {\n return value.toLowerCase().replace(/\\s+/g, \"-\");\n}\n\n// Generated as the full size x color cartesian product: agent-built pages\n// match every selected-options combination to a variant, and a combination\n// with no variant breaks their pickers mid-interaction.\nconst SAMPLE_VARIANTS: FullProduct[\"variants\"] = SAMPLE_SIZES.flatMap(\n (size) => {\n return SAMPLE_COLORS.map((color) => {\n const isSoldOut =\n size.value === SOLD_OUT_COMBINATION.size &&\n color.value === SOLD_OUT_COMBINATION.color;\n const slug = `${toSampleSlug(size.value)}-${toSampleSlug(color.value)}`;\n return {\n id: `replo-sample-variant-${slug}`,\n title: `${size.value} / ${color.value}`,\n availableForSale: !isSoldOut,\n sku: `REPLO-SAMPLE-${slug.toUpperCase()}`,\n selectedOptions: [\n { name: \"Size\", value: size.value },\n { name: \"Color\", value: color.value },\n ],\n image: null,\n price: { amount: size.priceAmount, currencyCode: \"USD\" },\n // Every variant is \"on sale\" so strikethrough pricing renders.\n compareAtPrice: { amount: \"56.00\", currencyCode: \"USD\" },\n sellingPlanIds: [],\n };\n });\n },\n);\n\nconst SAMPLE_PRODUCT: FullProduct = {\n id: \"replo-sample-product\",\n handle: \"sample-product\",\n title: \"Sample product\",\n description:\n \"This is sample product data for previewing this page. Open the page from a real product link to see live details. Sizes, colors, sale pricing, and a sold-out variant are included so every part of the layout has something to show.\",\n descriptionHtml:\n \"<p>This is sample product data for previewing this page. Open the page from a real product link to see live details.</p><p>The sample includes:</p><ul><li>Three sizes and two colors to try the option pickers</li><li>Sale pricing with a compare-at price</li><li>One sold-out combination (Large / Sand)</li></ul>\",\n availableForSale: true,\n featuredImage: null,\n // `id` is optional in the schema but always present on real data, and\n // generated pages key option lists on it \u2014 omit it and previews log React\n // \"missing key\" console errors, which the dev overlay counts as page errors.\n options: [\n {\n id: \"replo-sample-option-size\",\n name: \"Size\",\n values: SAMPLE_SIZES.map((size) => size.value),\n optionValues: SAMPLE_SIZES.map((size) => ({\n id: `replo-sample-option-value-${toSampleSlug(size.value)}`,\n value: size.value,\n swatch: null,\n })),\n },\n {\n id: \"replo-sample-option-color\",\n name: \"Color\",\n values: SAMPLE_COLORS.map((color) => color.value),\n optionValues: SAMPLE_COLORS.map((color) => ({\n id: `replo-sample-option-value-${toSampleSlug(color.value)}`,\n value: color.value,\n swatch: { color: color.swatchColor, imageUrl: null },\n })),\n },\n ],\n variants: SAMPLE_VARIANTS,\n images: [],\n media: [],\n vendor: \"Sample Supply Co.\",\n productType: \"Sample\",\n tags: [\"sample-data\", \"new-arrival\", \"bestseller\"],\n sellingPlanGroups: [],\n metafields: [],\n};\n\nconst SAMPLE_COLLECTION: FullCollection = {\n id: \"replo-sample-collection\",\n handle: \"sample-collection\",\n title: \"Sample collection\",\n descriptionHtml:\n \"<p>This is sample collection data for previewing this page.</p>\",\n image: null,\n};\n\n// Three grid cards, each exercising a distinct card state: a price range, a\n// compare-at sale (grids gate strikethroughs on `amount > 0`, per the loader\n// contract where \"0.0\" means no discount), and a sold-out product.\nconst SAMPLE_GRID_CARDS = [\n {\n slug: \"range\",\n title: \"Sample product with a price range\",\n minAmount: \"42.00\",\n maxAmount: \"50.00\",\n compareAtAmount: \"0.0\",\n availableForSale: true,\n },\n {\n slug: \"sale\",\n title: \"Sample product on sale\",\n minAmount: \"42.00\",\n maxAmount: \"42.00\",\n compareAtAmount: \"56.00\",\n availableForSale: true,\n },\n {\n slug: \"sold-out\",\n title: \"Sample product that sold out\",\n minAmount: \"46.00\",\n maxAmount: \"46.00\",\n compareAtAmount: \"0.0\",\n availableForSale: false,\n },\n];\n\nconst SAMPLE_PAGINATED_PRODUCTS: PaginatedProducts = {\n products: SAMPLE_GRID_CARDS.map((card) => ({\n id: `replo-sample-product-${card.slug}`,\n handle: `sample-product-${card.slug}`,\n title: card.title,\n availableForSale: card.availableForSale,\n featuredImage: null,\n options: [\n {\n id: `replo-sample-option-color-${card.slug}`,\n name: \"Color\",\n values: SAMPLE_COLORS.map((color) => color.value),\n optionValues: SAMPLE_COLORS.map((color) => ({\n id: `replo-sample-option-value-${card.slug}-${toSampleSlug(color.value)}`,\n value: color.value,\n swatch: { color: color.swatchColor, imageUrl: null },\n })),\n },\n ],\n variants: SAMPLE_COLORS.map((color) => ({\n id: `replo-sample-variant-${card.slug}-${toSampleSlug(color.value)}`,\n title: color.value,\n availableForSale: card.availableForSale,\n sku: `REPLO-SAMPLE-${card.slug.toUpperCase()}-${toSampleSlug(color.value).toUpperCase()}`,\n selectedOptions: [{ name: \"Color\", value: color.value }],\n image: null,\n price: { amount: card.minAmount, currencyCode: \"USD\" },\n compareAtPrice:\n Number(card.compareAtAmount) > 0\n ? { amount: card.compareAtAmount, currencyCode: \"USD\" }\n : null,\n })),\n minPrice: { amount: card.minAmount, currencyCode: \"USD\" },\n maxPrice: { amount: card.maxAmount, currencyCode: \"USD\" },\n compareAtMinPrice: { amount: card.compareAtAmount, currencyCode: \"USD\" },\n compareAtMaxPrice: { amount: card.compareAtAmount, currencyCode: \"USD\" },\n })),\n pageInfo: { hasNextPage: false, endCursor: null },\n};\n\nconst PLACEHOLDER_DATA_BY_LOADER_KEY: Record<string, unknown> = {\n [DATA_LOADER_KEYS.SHOPIFY_PRODUCT]: SAMPLE_PRODUCT,\n [DATA_LOADER_KEYS.REPLO_PRODUCT]: SAMPLE_PRODUCT,\n [DATA_LOADER_KEYS.SHOPIFY_COLLECTION]: SAMPLE_COLLECTION,\n [DATA_LOADER_KEYS.SHOPIFY_COLLECTION_PRODUCTS]: SAMPLE_PAGINATED_PRODUCTS,\n};\n\nfunction isUnfilledRouteParamValue(value: string): boolean {\n if (UNFILLED_ROUTE_PARAM_RE.test(value)) {\n return true;\n }\n // Next.js hands params over URL-encoded, so the literal `[handle]` segment\n // arrives as `%5Bhandle%5D`.\n try {\n return UNFILLED_ROUTE_PARAM_RE.test(decodeURIComponent(value));\n } catch {\n return false;\n }\n}\n\nexport function hasUnfilledRouteParam(args: Record<string, unknown>): boolean {\n return Object.values(args).some(\n (value) => typeof value === \"string\" && isUnfilledRouteParamValue(value),\n );\n}\n\n/**\n * Sample data for a loader invoked with an unfilled dynamic-route param, or\n * null when the invocation should proceed normally.\n *\n * On the server, substitution happens only on dev servers (`next dev` \u2014 how\n * every sandbox preview runs); production builds keep fetching so bracket\n * URLs keep their real miss/404 semantics. Substituting during SSR keeps the\n * doomed fetch from erroring the dev overlay mid-stream. In the browser,\n * substitution happens only inside an editor iframe.\n */\nexport function resolvePreviewPlaceholderData({\n loaderKey,\n args,\n}: {\n loaderKey: string;\n args: Record<string, unknown>;\n}): unknown {\n if (!hasUnfilledRouteParam(args)) {\n return null;\n }\n const placeholderData = PLACEHOLDER_DATA_BY_LOADER_KEY[loaderKey];\n if (placeholderData === undefined) {\n return null;\n }\n if (typeof window === \"undefined\") {\n return process.env.NODE_ENV === \"development\" ? placeholderData : null;\n }\n return window.parent === window ? null : placeholderData;\n}\n\nconst placeholderIdSchema = z.object({ id: z.string() });\nconst placeholderProductsSchema = z.object({\n products: z.array(z.object({ id: z.string() })).min(1),\n});\n\n/** Whether loader output is one of this module's sample fixtures. */\nexport function isPreviewPlaceholderData(data: unknown): boolean {\n const withId = placeholderIdSchema.safeParse(data);\n if (withId.success) {\n return withId.data.id.startsWith(SAMPLE_ID_PREFIX);\n }\n const withProducts = placeholderProductsSchema.safeParse(data);\n return (\n withProducts.success &&\n (withProducts.data.products[0]?.id.startsWith(SAMPLE_ID_PREFIX) ?? false)\n );\n}\n\n/**\n * Notify the embedding editor when a loader component rendered sample data.\n * The single reporting path: inspecting rendered data (rather than hooking\n * loader invocation) covers client fetches and SSR-hydrated data alike \u2014\n * hydration never re-invokes the loader on the client. Receivers treat the\n * message as idempotent, so no dedupe is needed.\n */\nexport function useReportPreviewPlaceholderRender(data: unknown) {\n // eslint-disable-next-line replo/no-use-effect -- Posts to the embedding editor window, an external system.\n useEffect(() => {\n if (window.parent !== window && isPreviewPlaceholderData(data)) {\n window.parent.postMessage(\n {\n type: PLACEHOLDER_RENDER_MESSAGE_TYPE,\n route: window.location.pathname,\n },\n \"*\",\n );\n }\n }, [data]);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type MetafieldIdentifier = {
|
|
2
|
+
namespace: string;
|
|
3
|
+
key: string;
|
|
4
|
+
};
|
|
5
|
+
export type ProductLoaderArgs = {
|
|
6
|
+
handle?: string;
|
|
7
|
+
productId?: string;
|
|
8
|
+
productMetafieldIdentifiers?: MetafieldIdentifier[];
|
|
9
|
+
variantMetafieldIdentifiers?: MetafieldIdentifier[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Builds the single object used as BOTH the React Query key and the loader
|
|
13
|
+
* payload. They must stay identical: `PrefetchedLoaders` seeds the cache under
|
|
14
|
+
* `[loaderKey, args]`, so an arg sent to the loader but missing from the key
|
|
15
|
+
* (or vice versa) silently misses the prefetch — the client refetches without
|
|
16
|
+
* it and the page renders incomplete data instead of erroring.
|
|
17
|
+
*
|
|
18
|
+
* Keys left `undefined` drop out of React Query's key hash, so omitting a field
|
|
19
|
+
* here hashes the same as a prefetch that never passed it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildProductLoaderArgs({ handle, productId, productMetafieldIdentifiers, variantMetafieldIdentifiers, }: ProductLoaderArgs): ProductLoaderArgs;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function buildProductLoaderArgs({
|
|
2
|
+
handle,
|
|
3
|
+
productId,
|
|
4
|
+
productMetafieldIdentifiers,
|
|
5
|
+
variantMetafieldIdentifiers
|
|
6
|
+
}) {
|
|
7
|
+
return {
|
|
8
|
+
handle,
|
|
9
|
+
productId,
|
|
10
|
+
productMetafieldIdentifiers,
|
|
11
|
+
variantMetafieldIdentifiers
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
buildProductLoaderArgs
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=product-loader-args.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../loaders/product-loader-args.ts"],
|
|
4
|
+
"sourcesContent": ["export type MetafieldIdentifier = {\n namespace: string;\n key: string;\n};\n\nexport type ProductLoaderArgs = {\n handle?: string;\n productId?: string;\n productMetafieldIdentifiers?: MetafieldIdentifier[];\n variantMetafieldIdentifiers?: MetafieldIdentifier[];\n};\n\n/**\n * Builds the single object used as BOTH the React Query key and the loader\n * payload. They must stay identical: `PrefetchedLoaders` seeds the cache under\n * `[loaderKey, args]`, so an arg sent to the loader but missing from the key\n * (or vice versa) silently misses the prefetch \u2014 the client refetches without\n * it and the page renders incomplete data instead of erroring.\n *\n * Keys left `undefined` drop out of React Query's key hash, so omitting a field\n * here hashes the same as a prefetch that never passed it.\n */\nexport function buildProductLoaderArgs({\n handle,\n productId,\n productMetafieldIdentifiers,\n variantMetafieldIdentifiers,\n}: ProductLoaderArgs): ProductLoaderArgs {\n return {\n handle,\n productId,\n productMetafieldIdentifiers,\n variantMetafieldIdentifiers,\n };\n}\n"],
|
|
5
|
+
"mappings": "AAsBO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyC;AACvC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { FullProduct } from "./loader-schemas";
|
|
3
|
+
import type { ProductLoaderArgs } from "./product-loader-args";
|
|
4
|
+
type ProductLoaderProps = ProductLoaderArgs & {
|
|
5
|
+
loaderKey: string;
|
|
6
|
+
children: (data: FullProduct) => ReactNode;
|
|
7
|
+
fallback?: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export declare function ProductLoader({ loaderKey, handle, productId, productMetafieldIdentifiers, variantMetafieldIdentifiers, children, fallback, }: ProductLoaderProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Suspense } from "react";
|
|
4
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
5
|
+
import { invokeLoader } from "./invoke-loader";
|
|
6
|
+
import { LoaderErrorBoundary } from "./loader-error-boundary";
|
|
7
|
+
import { fullProductSchema } from "./loader-schemas";
|
|
8
|
+
import { useReportPreviewPlaceholderRender } from "./preview-placeholder";
|
|
9
|
+
import { buildProductLoaderArgs } from "./product-loader-args";
|
|
10
|
+
function ProductLoaderInner({
|
|
11
|
+
loaderKey,
|
|
12
|
+
args,
|
|
13
|
+
children
|
|
14
|
+
}) {
|
|
15
|
+
const queryResult = useSuspenseQuery({
|
|
16
|
+
queryKey: [loaderKey, args],
|
|
17
|
+
queryFn: async () => {
|
|
18
|
+
const loadedData = await invokeLoader({ loaderKey, args });
|
|
19
|
+
return fullProductSchema.parse(loadedData);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
useReportPreviewPlaceholderRender(queryResult.data);
|
|
23
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children(queryResult.data) });
|
|
24
|
+
}
|
|
25
|
+
function ProductLoader({
|
|
26
|
+
loaderKey,
|
|
27
|
+
handle,
|
|
28
|
+
productId,
|
|
29
|
+
productMetafieldIdentifiers,
|
|
30
|
+
variantMetafieldIdentifiers,
|
|
31
|
+
children,
|
|
32
|
+
fallback
|
|
33
|
+
}) {
|
|
34
|
+
const args = buildProductLoaderArgs({
|
|
35
|
+
handle,
|
|
36
|
+
productId,
|
|
37
|
+
productMetafieldIdentifiers,
|
|
38
|
+
variantMetafieldIdentifiers
|
|
39
|
+
});
|
|
40
|
+
return /* @__PURE__ */ jsx(
|
|
41
|
+
LoaderErrorBoundary,
|
|
42
|
+
{
|
|
43
|
+
fallback: fallback ?? null,
|
|
44
|
+
resetKey: `${loaderKey}:${JSON.stringify(args)}`,
|
|
45
|
+
children: /* @__PURE__ */ jsx(Suspense, { fallback: fallback ?? null, children: /* @__PURE__ */ jsx(ProductLoaderInner, { loaderKey, args, children }) })
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
ProductLoader
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=product-loader.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../loaders/product-loader.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport type { FullProduct } from \"./loader-schemas\";\nimport type { ProductLoaderArgs } from \"./product-loader-args\";\n\nimport { Suspense } from \"react\";\n\nimport { useSuspenseQuery } from \"@tanstack/react-query\";\n\nimport { invokeLoader } from \"./invoke-loader\";\nimport { LoaderErrorBoundary } from \"./loader-error-boundary\";\nimport { fullProductSchema } from \"./loader-schemas\";\nimport { useReportPreviewPlaceholderRender } from \"./preview-placeholder\";\nimport { buildProductLoaderArgs } from \"./product-loader-args\";\n\ntype ProductLoaderProps = ProductLoaderArgs & {\n loaderKey: string;\n children: (data: FullProduct) => ReactNode;\n fallback?: ReactNode;\n};\n\nfunction ProductLoaderInner({\n loaderKey,\n args,\n children,\n}: {\n loaderKey: string;\n args: ProductLoaderArgs;\n children: (data: FullProduct) => ReactNode;\n}) {\n const queryResult = useSuspenseQuery({\n queryKey: [loaderKey, args],\n queryFn: async () => {\n const loadedData = await invokeLoader({ loaderKey, args });\n return fullProductSchema.parse(loadedData);\n },\n });\n useReportPreviewPlaceholderRender(queryResult.data);\n\n return <>{children(queryResult.data)}</>;\n}\n\nexport function ProductLoader({\n loaderKey,\n handle,\n productId,\n productMetafieldIdentifiers,\n variantMetafieldIdentifiers,\n children,\n fallback,\n}: ProductLoaderProps) {\n const args = buildProductLoaderArgs({\n handle,\n productId,\n productMetafieldIdentifiers,\n variantMetafieldIdentifiers,\n });\n\n return (\n <LoaderErrorBoundary\n fallback={fallback ?? null}\n resetKey={`${loaderKey}:${JSON.stringify(args)}`}\n >\n <Suspense fallback={fallback ?? null}>\n <ProductLoaderInner loaderKey={loaderKey} args={args}>\n {children}\n </ProductLoaderInner>\n </Suspense>\n </LoaderErrorBoundary>\n );\n}\n"],
|
|
5
|
+
"mappings": ";AAwCS;AAlCT,SAAS,gBAAgB;AAEzB,SAAS,wBAAwB;AAEjC,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,yBAAyB;AAClC,SAAS,yCAAyC;AAClD,SAAS,8BAA8B;AAQvC,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,cAAc,iBAAiB;AAAA,IACnC,UAAU,CAAC,WAAW,IAAI;AAAA,IAC1B,SAAS,YAAY;AACnB,YAAM,aAAa,MAAM,aAAa,EAAE,WAAW,KAAK,CAAC;AACzD,aAAO,kBAAkB,MAAM,UAAU;AAAA,IAC3C;AAAA,EACF,CAAC;AACD,oCAAkC,YAAY,IAAI;AAElD,SAAO,gCAAG,mBAAS,YAAY,IAAI,GAAE;AACvC;AAEO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,OAAO,uBAAuB;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,YAAY;AAAA,MACtB,UAAU,GAAG,SAAS,IAAI,KAAK,UAAU,IAAI,CAAC;AAAA,MAE9C,8BAAC,YAAS,UAAU,YAAY,MAC9B,8BAAC,sBAAmB,WAAsB,MACvC,UACH,GACF;AAAA;AAAA,EACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|