@frak-labs/components 1.0.2 → 1.0.3-beta.64c76510

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.
Files changed (31) hide show
  1. package/cdn/Banner.BaYEToC4.js +170 -0
  2. package/cdn/ButtonShare.BMkk39K_.js +1 -0
  3. package/cdn/{ButtonWallet.Cwz9qFhE.js → ButtonWallet.DT6bWcXS.js} +1 -1
  4. package/cdn/{OpenInAppButton.Hq9EjwJE.js → OpenInAppButton.CBzl8Rgk.js} +1 -1
  5. package/cdn/PostPurchase.Bo_QLHvi.js +51 -0
  6. package/cdn/components.js +1 -1
  7. package/cdn/{formatReward.BaR9pE50.js → formatReward.CEAF0Lox.js} +1 -1
  8. package/cdn/loader.js +2 -2
  9. package/cdn/sharingPage.Do_xfrTN.js +1 -0
  10. package/cdn/{sprinkles.css.ts.vanilla.Ct795MMK.js → sprinkles.css.ts.vanilla.eIpk1s3W.js} +1 -1
  11. package/cdn/useGlobalComponents.UJmjUUxk.js +1 -0
  12. package/cdn/{useLightDomStyles.DqYouFn3.js → useLightDomStyles.Gt7YUMDl.js} +1 -1
  13. package/cdn/{usePlacement.Di6eZ4ty.js → usePlacement.BJ7qe-pw.js} +1 -1
  14. package/cdn/{useReward.DWyyva4u.js → useReward.Bn1RVE3i.js} +1 -1
  15. package/dist/{GiftIcon-4sr9xXyq.js → GiftIcon-2DC0OUeF.js} +33 -16
  16. package/dist/banner.js +41 -24
  17. package/dist/buttonShare.d.ts +9 -4
  18. package/dist/buttonShare.js +13 -177
  19. package/dist/openInApp.js +1 -1
  20. package/dist/postPurchase.d.ts +17 -1
  21. package/dist/postPurchase.js +99 -20
  22. package/dist/sharingPage-DFvQbviS.js +15 -0
  23. package/dist/useLightDomStyles-gbuSWvRx.js +89 -0
  24. package/package.json +3 -3
  25. package/cdn/Banner.1iUbfe7Z.js +0 -162
  26. package/cdn/ButtonShare.APhrT3sb.js +0 -1
  27. package/cdn/PostPurchase.gHQmp1c4.js +0 -52
  28. package/cdn/useGlobalComponents.CLH7id-Y.js +0 -1
  29. package/cdn/useShareModal.pszXJ-rf.js +0 -1
  30. package/dist/useLightDomStyles-C3lcOwY2.js +0 -41
  31. package/dist/useShareModal-BEVkLrBP.js +0 -54
package/dist/banner.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { a as registerWebComponent, i as useClientReady, t as usePlacement } from "./usePlacement-V7NrKoub.js";
2
2
  import { t as useGlobalComponents } from "./useGlobalComponents-Cmfszr7v.js";
3
- import { t as useLightDomStyles } from "./useLightDomStyles-C3lcOwY2.js";
3
+ import { t as useLightDomStyles } from "./useLightDomStyles-gbuSWvRx.js";
4
4
  import { t as useReward } from "./useReward-DU3_yP8Q.js";
5
- import { a as CloseCircleIcon, c as cssSource$5, i as ExternalLinkIcon, n as WarningIcon, o as cssSource$6, s as cssSource$4, t as GiftIcon } from "./GiftIcon-4sr9xXyq.js";
5
+ import { a as CloseCircleIcon, c as cssSource$5, i as ExternalLinkIcon, n as WarningIcon, o as cssSource$6, r as LogoFrakWithName, s as cssSource$4, t as GiftIcon } from "./GiftIcon-2DC0OUeF.js";
6
6
  import { isInAppBrowser, redirectToExternalBrowser, trackEvent } from "@frak-labs/core-sdk";
7
7
  import { REFERRAL_SUCCESS_EVENT, getMergeToken } from "@frak-labs/core-sdk/actions";
8
8
  import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
@@ -194,6 +194,8 @@ const cssSource$1 = `@keyframes Banner_fadeIn__1gnumzi0 {
194
194
  padding: 16px;
195
195
  background-color: #ffffff;
196
196
  color: var(--text-primary__pbq4ak0);
197
+ border: 1px solid var(--border-default__pbq4akv);
198
+ border-radius: 12px;
197
199
  }
198
200
  .Banner_referralIconWrapper__1gnumzi4 {
199
201
  flex-shrink: 0;
@@ -231,9 +233,16 @@ const cssSource$1 = `@keyframes Banner_fadeIn__1gnumzi0 {
231
233
  .Banner_referralCta__1gnumzi8:focus-visible {
232
234
  outline: 2px solid #000000;
233
235
  outline-offset: 2px;
236
+ }
237
+ .Banner_frakLogo__1gnumzi9 {
238
+ position: absolute;
239
+ right: 16px;
240
+ bottom: 12px;
241
+ pointer-events: none;
234
242
  }`;
235
243
  //#endregion
236
244
  //#region src/components/Banner/Banner.css.ts
245
+ var frakLogo = "Banner_frakLogo__1gnumzi9";
237
246
  var iconSvg = "Banner_iconSvg__1gnumzi2";
238
247
  var referral = "Banner_referral__1gnumzi3 reset_base__1831jhd0 Banner_rootBase__1gnumzi1";
239
248
  var referralBody = "Banner_referralBody__1gnumzi5";
@@ -421,28 +430,36 @@ function Banner({ placement: placementId, classname = "", interaction, referralT
421
430
  return /* @__PURE__ */ jsxs("div", {
422
431
  class: bannerClass,
423
432
  role: "alert",
424
- children: [/* @__PURE__ */ jsx("div", {
425
- class: `${referralIconWrapper} frak-banner__icon`,
426
- children: /* @__PURE__ */ jsx(GiftIcon, { class: iconSvg })
427
- }), /* @__PURE__ */ jsxs("div", {
428
- class: `${referralBody} frak-banner__text`,
429
- children: [
430
- /* @__PURE__ */ jsx("p", {
431
- class: `${referralTitle} frak-banner__title`,
432
- children: texts.title
433
- }),
434
- /* @__PURE__ */ jsx("p", {
435
- class: `${referralDescription} frak-banner__description`,
436
- children: texts.description
437
- }),
438
- /* @__PURE__ */ jsx("button", {
439
- type: "button",
440
- class: `${referralCta} frak-banner__cta`,
441
- onClick: handleAction,
442
- children: texts.cta
443
- })
444
- ]
445
- })]
433
+ children: [
434
+ /* @__PURE__ */ jsx("div", {
435
+ class: `${referralIconWrapper} frak-banner__icon`,
436
+ children: /* @__PURE__ */ jsx(GiftIcon, { class: iconSvg })
437
+ }),
438
+ /* @__PURE__ */ jsxs("div", {
439
+ class: `${referralBody} frak-banner__text`,
440
+ children: [
441
+ /* @__PURE__ */ jsx("p", {
442
+ class: `${referralTitle} frak-banner__title`,
443
+ children: texts.title
444
+ }),
445
+ /* @__PURE__ */ jsx("p", {
446
+ class: `${referralDescription} frak-banner__description`,
447
+ children: texts.description
448
+ }),
449
+ /* @__PURE__ */ jsx("button", {
450
+ type: "button",
451
+ class: `${referralCta} frak-banner__cta`,
452
+ onClick: handleAction,
453
+ children: texts.cta
454
+ })
455
+ ]
456
+ }),
457
+ /* @__PURE__ */ jsx(LogoFrakWithName, {
458
+ class: `${frakLogo} frak-banner__logo`,
459
+ width: 42,
460
+ height: 24
461
+ })
462
+ ]
446
463
  });
447
464
  }
448
465
  //#endregion
@@ -32,10 +32,15 @@ type ButtonShareProps = {
32
32
  */
33
33
  targetInteraction?: InteractionTypeKey;
34
34
  /**
35
- * Which UI to open on click
36
- * @defaultValue `"embedded-wallet"`
35
+ * Which UI to open on click.
36
+ *
37
+ * Legacy values (e.g. `"share-modal"`) are accepted at runtime and
38
+ * gracefully route to the full-page sharing UI — the modal-flow
39
+ * share path was retired in favour of `displaySharingPage`.
40
+ *
41
+ * @defaultValue `"sharing-page"`
37
42
  */
38
- clickAction?: "embedded-wallet" | "share-modal" | "sharing-page";
43
+ clickAction?: "embedded-wallet" | "sharing-page";
39
44
  /**
40
45
  * When set, renders the button in preview mode (e.g. Shopify/WP editor).
41
46
  * Skips the client-ready gating so the button is always enabled visually,
@@ -84,7 +89,7 @@ type ButtonShareProps = {
84
89
  * <frak-button-share use-reward text="Share and earn up to {REWARD}!" no-reward-text="Share and earn!" target-interaction="custom.customerMeeting"></frak-button-share>
85
90
  * ```
86
91
  *
87
- * @see {@link @frak-labs/core-sdk!actions.modalBuilder | `modalBuilder()`} for more info about the modal display
92
+ * @see {@link @frak-labs/core-sdk!actions.displaySharingPage | `displaySharingPage()`} for more info about the sharing-page flow
88
93
  * @see {@link @frak-labs/core-sdk!actions.getMerchantInformation | `getMerchantInformation()`} for more info about the estimated reward fetching
89
94
  */
90
95
  declare function ButtonShare({
@@ -1,176 +1,12 @@
1
1
  import { a as registerWebComponent, i as useClientReady, s as openEmbeddedWallet, t as usePlacement } from "./usePlacement-V7NrKoub.js";
2
2
  import { t as useGlobalComponents } from "./useGlobalComponents-Cmfszr7v.js";
3
- import { t as useLightDomStyles } from "./useLightDomStyles-C3lcOwY2.js";
3
+ import { t as useLightDomStyles } from "./useLightDomStyles-gbuSWvRx.js";
4
4
  import { t as applyRewardPlaceholder } from "./formatReward-Bub6Z6eY.js";
5
5
  import { t as useReward } from "./useReward-DU3_yP8Q.js";
6
- import { t as useShareModal } from "./useShareModal-BEVkLrBP.js";
6
+ import { t as openSharingPage } from "./sharingPage-DFvQbviS.js";
7
7
  import { trackEvent } from "@frak-labs/core-sdk";
8
- import { displaySharingPage } from "@frak-labs/core-sdk/actions";
9
- import { useCallback, useMemo, useState } from "preact/hooks";
10
- import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
11
- //#region src/utils/sharingPage.ts
12
- async function openSharingPage(targetInteraction, placement) {
13
- if (!window.FrakSetup?.client) {
14
- console.error("Frak client not found");
15
- return;
16
- }
17
- await displaySharingPage(window.FrakSetup.client, { metadata: { ...targetInteraction && { targetInteraction } } }, placement);
18
- }
19
- //#endregion
20
- //#region src/hooks/useCopyToClipboard.ts
21
- function useCopyToClipboard(options = {}) {
22
- const { successDuration = 2e3 } = options;
23
- const [copied, setCopied] = useState(false);
24
- return {
25
- copy: useCallback(async (text) => {
26
- try {
27
- if (navigator.clipboard && window.isSecureContext) {
28
- await navigator.clipboard.writeText(text);
29
- setCopied(true);
30
- } else {
31
- const textArea = document.createElement("textarea");
32
- textArea.value = text;
33
- textArea.style.position = "fixed";
34
- textArea.style.opacity = "0";
35
- document.body.appendChild(textArea);
36
- textArea.focus();
37
- textArea.select();
38
- try {
39
- document.execCommand("copy");
40
- setCopied(true);
41
- } catch (err) {
42
- console.error("Failed to copy text:", err);
43
- return false;
44
- } finally {
45
- textArea.remove();
46
- }
47
- }
48
- setTimeout(() => {
49
- setCopied(false);
50
- }, successDuration);
51
- return true;
52
- } catch (err) {
53
- console.error("Failed to copy text:", err);
54
- return false;
55
- }
56
- }, [successDuration]),
57
- copied
58
- };
59
- }
60
- //#endregion
61
- //#region src/components/ButtonShare/components/ErrorMessage.tsx
62
- const styles = {
63
- errorContainer: {
64
- marginTop: "16px",
65
- padding: "16px",
66
- backgroundColor: "#FEE2E2",
67
- border: "1px solid #FCA5A5",
68
- borderRadius: "4px",
69
- color: "#991B1B"
70
- },
71
- header: {
72
- display: "flex",
73
- alignItems: "center",
74
- gap: "8px",
75
- marginBottom: "12px"
76
- },
77
- title: {
78
- margin: 0,
79
- fontSize: "16px",
80
- fontWeight: 500
81
- },
82
- message: {
83
- fontSize: "14px",
84
- lineHeight: "1.5",
85
- margin: "0 0 12px 0"
86
- },
87
- link: {
88
- color: "#991B1B",
89
- textDecoration: "underline",
90
- textUnderlineOffset: "2px"
91
- },
92
- copyButton: {
93
- display: "inline-flex",
94
- alignItems: "center",
95
- gap: "8px",
96
- marginBottom: "10px",
97
- padding: "8px 12px",
98
- backgroundColor: "white",
99
- border: "1px solid #D1D5DB",
100
- borderRadius: "4px",
101
- color: "black",
102
- fontSize: "14px",
103
- fontWeight: 500
104
- }
105
- };
106
- /**
107
- * Renders a toggleable debug information section
108
- * @param {Object} props - Component props
109
- * @param {string} [props.debugInfo] - Debug information to display in textarea
110
- */
111
- function ToggleMessage({ debugInfo }) {
112
- const [showInfo, setShowInfo] = useState(false);
113
- return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("button", {
114
- type: "button",
115
- style: styles.copyButton,
116
- onClick: () => setShowInfo(!showInfo),
117
- children: "Ouvrir les informations"
118
- }), showInfo && /* @__PURE__ */ jsx("textarea", {
119
- style: {
120
- display: "block",
121
- width: "100%",
122
- height: "200px",
123
- fontSize: "12px"
124
- },
125
- children: debugInfo
126
- })] });
127
- }
128
- /**
129
- * Displays an error message with debug information and copy functionality
130
- * @param {Object} props - Component props
131
- * @param {string} [props.debugInfo] - Debug information that can be copied or displayed
132
- */
133
- function ErrorMessage({ debugInfo }) {
134
- const { copied, copy } = useCopyToClipboard();
135
- const handleCopy = () => {
136
- copy(debugInfo ?? "");
137
- };
138
- return /* @__PURE__ */ jsxs("div", {
139
- style: styles.errorContainer,
140
- children: [
141
- /* @__PURE__ */ jsx("div", {
142
- style: styles.header,
143
- children: /* @__PURE__ */ jsx("h3", {
144
- style: styles.title,
145
- children: "Oups ! Nous avons rencontré un petit problème"
146
- })
147
- }),
148
- /* @__PURE__ */ jsxs("p", {
149
- style: styles.message,
150
- children: [
151
- "Impossible d'ouvrir le menu de partage pour le moment. Si le problème persiste, copiez les informations ci-dessous et collez-les dans votre mail à",
152
- " ",
153
- /* @__PURE__ */ jsx("a", {
154
- href: "mailto:help@frak-labs.com?subject=Debug",
155
- style: styles.link,
156
- children: "help@frak-labs.com"
157
- }),
158
- " ",
159
- /* @__PURE__ */ jsx("br", {}),
160
- "Merci pour votre retour, nous traitons votre demande dans les plus brefs délais."
161
- ]
162
- }),
163
- /* @__PURE__ */ jsx("button", {
164
- type: "button",
165
- onClick: handleCopy,
166
- style: styles.copyButton,
167
- children: copied ? "Informations copiées !" : "Copier les informations de débogage"
168
- }),
169
- /* @__PURE__ */ jsx(ToggleMessage, { debugInfo })
170
- ]
171
- });
172
- }
173
- //#endregion
8
+ import { useCallback, useMemo } from "preact/hooks";
9
+ import { jsx } from "preact/jsx-runtime";
174
10
  //#region src/components/ButtonShare/ButtonShare.tsx
175
11
  /**
176
12
  * Button to share the current page
@@ -210,7 +46,7 @@ function ErrorMessage({ debugInfo }) {
210
46
  * <frak-button-share use-reward text="Share and earn up to {REWARD}!" no-reward-text="Share and earn!" target-interaction="custom.customerMeeting"></frak-button-share>
211
47
  * ```
212
48
  *
213
- * @see {@link @frak-labs/core-sdk!actions.modalBuilder | `modalBuilder()`} for more info about the modal display
49
+ * @see {@link @frak-labs/core-sdk!actions.displaySharingPage | `displaySharingPage()`} for more info about the sharing-page flow
214
50
  * @see {@link @frak-labs/core-sdk!actions.getMerchantInformation | `getMerchantInformation()`} for more info about the estimated reward fetching
215
51
  */
216
52
  function ButtonShare({ placement: placementId, text = "Share and earn!", classname = "", useReward: rawUseReward, noRewardText, targetInteraction, clickAction: rawClickAction, preview }) {
@@ -226,7 +62,6 @@ function ButtonShare({ placement: placementId, text = "Share and earn!", classna
226
62
  const resolvedClickAction = useMemo(() => componentConfig?.clickAction ?? rawClickAction ?? "sharing-page", [componentConfig?.clickAction, rawClickAction]);
227
63
  const { shouldRender, isHidden, isClientReady } = useClientReady();
228
64
  const { reward } = useReward(shouldUseReward && isClientReady, resolvedTargetInteraction);
229
- const { handleShare, isError, debugInfo } = useShareModal(resolvedTargetInteraction, placementId);
230
65
  const btnText = useMemo(() => {
231
66
  if (!shouldUseReward) return resolvedText;
232
67
  if (!reward) return resolvedNoRewardText ?? applyRewardPlaceholder(resolvedText, void 0);
@@ -237,7 +72,7 @@ function ButtonShare({ placement: placementId, text = "Share and earn!", classna
237
72
  resolvedNoRewardText,
238
73
  reward
239
74
  ]);
240
- const onClick = useCallback(async () => {
75
+ const onClick = useCallback(() => {
241
76
  if (isPreview) return;
242
77
  trackEvent(window.FrakSetup.client, "share_button_clicked", {
243
78
  placement: placementId,
@@ -245,13 +80,14 @@ function ButtonShare({ placement: placementId, text = "Share and earn!", classna
245
80
  has_reward: Boolean(reward),
246
81
  click_action: resolvedClickAction
247
82
  });
248
- if (resolvedClickAction === "embedded-wallet") openEmbeddedWallet(resolvedTargetInteraction, placementId);
249
- else if (resolvedClickAction === "share-modal") await handleShare();
250
- else openSharingPage(resolvedTargetInteraction, placementId);
83
+ if (resolvedClickAction === "embedded-wallet") {
84
+ openEmbeddedWallet(resolvedTargetInteraction, placementId);
85
+ return;
86
+ }
87
+ openSharingPage(resolvedTargetInteraction, placementId);
251
88
  }, [
252
89
  isPreview,
253
90
  resolvedClickAction,
254
- handleShare,
255
91
  resolvedTargetInteraction,
256
92
  placementId,
257
93
  reward
@@ -262,13 +98,13 @@ function ButtonShare({ placement: placementId, text = "Share and earn!", classna
262
98
  "button__fadeIn",
263
99
  classname
264
100
  ].filter(Boolean).join(" ");
265
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
101
+ return /* @__PURE__ */ jsx("button", {
266
102
  type: "button",
267
103
  disabled: !isPreview && !isClientReady,
268
104
  class: buttonClass,
269
105
  onClick,
270
106
  children: btnText
271
- }), isError && /* @__PURE__ */ jsx(ErrorMessage, { debugInfo })] });
107
+ });
272
108
  }
273
109
  //#endregion
274
110
  //#region src/components/ButtonShare/index.ts
package/dist/openInApp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as registerWebComponent, i as useClientReady, t as usePlacement } from "./usePlacement-V7NrKoub.js";
2
- import { t as useLightDomStyles } from "./useLightDomStyles-C3lcOwY2.js";
2
+ import { t as useLightDomStyles } from "./useLightDomStyles-gbuSWvRx.js";
3
3
  import { DEEP_LINK_SCHEME, trackEvent, triggerDeepLinkWithFallback } from "@frak-labs/core-sdk";
4
4
  import { useMemo } from "preact/hooks";
5
5
  import { jsx } from "preact/jsx-runtime";
@@ -1,3 +1,4 @@
1
+ import { SharingPageProduct } from "@frak-labs/core-sdk";
1
2
  import * as _$preact from "preact";
2
3
 
3
4
  //#region src/components/PostPurchase/types.d.ts
@@ -61,6 +62,20 @@ type PostPurchaseProps = {
61
62
  * Use `{REWARD}` as placeholder for the reward amount.
62
63
  */
63
64
  ctaText?: string;
65
+ /**
66
+ * Optional product cards forwarded to the sharing page when the user
67
+ * clicks the CTA. Accepts either a real {@link SharingPageProduct}
68
+ * array (when set imperatively via the JS property,
69
+ * `el.products = [...]`) or a JSON-stringified array (when set as an
70
+ * HTML attribute, `<frak-post-purchase products='[...]'>`). The HTML
71
+ * attribute path is required for server-rendered surfaces — e.g.
72
+ * WooCommerce / Magento plugins — because `preact-custom-element`
73
+ * delivers attribute values as raw strings.
74
+ *
75
+ * Empty arrays / unparseable strings are treated as "no products" so
76
+ * the sharing page renders without the product card section.
77
+ */
78
+ products?: SharingPageProduct[] | string;
64
79
  /**
65
80
  * When set, renders the card in preview mode (e.g. Shopify/WP editor).
66
81
  * Bypasses the client-ready / RPC gates that normally hide the card
@@ -118,7 +133,8 @@ declare function PostPurchase({
118
133
  refereeText: propRefereeText,
119
134
  ctaText: propCtaText,
120
135
  preview,
121
- previewVariant
136
+ previewVariant,
137
+ products
122
138
  }: PostPurchaseProps): _$preact.JSX.Element | null;
123
139
  //#endregion
124
140
  //#region src/components/PostPurchase/index.d.ts
@@ -1,12 +1,12 @@
1
1
  import { a as registerWebComponent, i as useClientReady, t as usePlacement } from "./usePlacement-V7NrKoub.js";
2
2
  import { t as useGlobalComponents } from "./useGlobalComponents-Cmfszr7v.js";
3
- import { t as useLightDomStyles } from "./useLightDomStyles-C3lcOwY2.js";
3
+ import { t as useLightDomStyles } from "./useLightDomStyles-gbuSWvRx.js";
4
4
  import { n as formatEstimatedReward, t as applyRewardPlaceholder } from "./formatReward-Bub6Z6eY.js";
5
- import { t as useShareModal } from "./useShareModal-BEVkLrBP.js";
6
- import { l as createElement, o as cssSource$4, r as LogoFrak, s as cssSource$3, t as GiftIcon } from "./GiftIcon-4sr9xXyq.js";
5
+ import { t as openSharingPage } from "./sharingPage-DFvQbviS.js";
6
+ import { c as cssSource$5, l as createElement, o as cssSource$4, r as LogoFrakWithName, s as cssSource$3, t as GiftIcon } from "./GiftIcon-2DC0OUeF.js";
7
7
  import { trackEvent } from "@frak-labs/core-sdk";
8
8
  import { getMerchantInformation, getUserReferralStatus, trackPurchaseStatus } from "@frak-labs/core-sdk/actions";
9
- import { useEffect, useMemo, useRef, useState } from "preact/hooks";
9
+ import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
10
10
  import { jsx, jsxs } from "preact/jsx-runtime";
11
11
  import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
12
12
  //#region ../../node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
@@ -1391,15 +1391,75 @@ const cssSource$1 = `.PostPurchase_card__5fv5lh0 {
1391
1391
  .PostPurchase_frakLogo__5fv5lh6 {
1392
1392
  display: block;
1393
1393
  margin-left: auto;
1394
- color: var(--surface-primary__pbq4aka);
1395
1394
  }`;
1396
1395
  var card = "PostPurchase_card__5fv5lh0";
1397
- var cta = "PostPurchase_cta__5fv5lh3";
1396
+ var cta = "PostPurchase_cta__5fv5lh3 reset_element_button__1831jhd6 reset_fieldAppearance__1831jhd2 reset_focusRing__1831jhd1";
1398
1397
  var frakLogo = "PostPurchase_frakLogo__5fv5lh6";
1399
1398
  var giftIcon = "PostPurchase_giftIcon__5fv5lh5";
1400
1399
  var icon = "PostPurchase_icon__5fv5lh4";
1401
1400
  var message = "PostPurchase_message__5fv5lh2";
1402
- const cssSource = cssSource$3 + cssSource$4 + cssSource$1;
1401
+ const cssSource = cssSource$5 + cssSource$3 + cssSource$4 + cssSource$1;
1402
+ //#endregion
1403
+ //#region src/components/PostPurchase/products.ts
1404
+ /**
1405
+ * Whether `value` is a syntactically valid URL with an `http(s):` scheme.
1406
+ *
1407
+ * Used to gate `imageUrl` / `link` fields coming from the public `products`
1408
+ * prop — the listener-side sharing-page builder calls `new URL(...)` on the
1409
+ * incoming product link, and a `javascript:` URL would be a XSS sink in any
1410
+ * consumer that binds the value to an `href`.
1411
+ */
1412
+ function isHttpUrl(value) {
1413
+ try {
1414
+ const parsed = new URL(value);
1415
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
1416
+ } catch {
1417
+ return false;
1418
+ }
1419
+ }
1420
+ /**
1421
+ * Coerce a raw `products` prop value into a candidate array suitable for
1422
+ * per-item normalisation, or null when it cannot be reduced to one.
1423
+ *
1424
+ * Surfaces that set the prop via the JS property (`el.products = [...]`)
1425
+ * deliver a real array; surfaces that bind it as an HTML attribute
1426
+ * (WP / Magento server-render) deliver a JSON-stringified array. Anything
1427
+ * else (truthy non-array non-string, JSON parse failure, JSON that decodes
1428
+ * to a non-array) is treated as "no products" so the share still works
1429
+ * without the product card section.
1430
+ */
1431
+ function coerceProductCandidates(products) {
1432
+ if (!products) return null;
1433
+ if (Array.isArray(products)) return products;
1434
+ if (typeof products !== "string") return null;
1435
+ try {
1436
+ const parsed = JSON.parse(products);
1437
+ return Array.isArray(parsed) ? parsed : null;
1438
+ } catch {
1439
+ return null;
1440
+ }
1441
+ }
1442
+ /**
1443
+ * Normalise one untrusted candidate into a {@link SharingPageProduct}, or
1444
+ * return null when the candidate has no usable title.
1445
+ *
1446
+ * The `products` prop is a public API boundary (merchants can set it
1447
+ * server-side via WP/Magento or imperatively from arbitrary JS). Each entry
1448
+ * is validated structurally so a malformed `link` reaching `new URL(...)`
1449
+ * downstream would not crash the sharing-page builder, and so a
1450
+ * `javascript:` URL cannot slip through as `imageUrl` / `link`.
1451
+ */
1452
+ function normalizeProductCandidate(candidate) {
1453
+ if (!candidate || typeof candidate !== "object") return null;
1454
+ const item = candidate;
1455
+ const title = typeof item.title === "string" ? item.title.trim() : "";
1456
+ if (title === "") return null;
1457
+ const entry = { title };
1458
+ if (typeof item.imageUrl === "string" && isHttpUrl(item.imageUrl)) entry.imageUrl = item.imageUrl;
1459
+ if (typeof item.link === "string" && isHttpUrl(item.link)) entry.link = item.link;
1460
+ if (typeof item.utmContent === "string" && item.utmContent !== "") entry.utmContent = item.utmContent;
1461
+ return entry;
1462
+ }
1403
1463
  //#endregion
1404
1464
  //#region src/components/PostPurchase/PostPurchase.tsx
1405
1465
  /**
@@ -1444,7 +1504,7 @@ function resolvePostPurchaseContext(referralStatus, merchantInfo) {
1444
1504
  * ></frak-post-purchase>
1445
1505
  * ```
1446
1506
  */
1447
- function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, placement: placementId, classname = "", variant: forcedVariant, badgeText: propBadgeText, referrerText: propReferrerText, refereeText: propRefereeText, ctaText: propCtaText, preview, previewVariant }) {
1507
+ function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, placement: placementId, classname = "", variant: forcedVariant, badgeText: propBadgeText, referrerText: propReferrerText, refereeText: propRefereeText, ctaText: propCtaText, preview, previewVariant, products }) {
1448
1508
  const isPreview = !!preview;
1449
1509
  const { shouldRender, isHidden, isClientReady } = useClientReady();
1450
1510
  const placement = usePlacement(placementId);
@@ -1531,7 +1591,32 @@ function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, plac
1531
1591
  placementId,
1532
1592
  context?.reward
1533
1593
  ]);
1534
- const { handleShare } = useShareModal(void 0, placementId, resolvedSharingUrl);
1594
+ const parsedProducts = useMemo(() => {
1595
+ const candidates = coerceProductCandidates(products);
1596
+ if (!candidates) return void 0;
1597
+ const sanitized = [];
1598
+ for (const candidate of candidates) {
1599
+ const entry = normalizeProductCandidate(candidate);
1600
+ if (entry) sanitized.push(entry);
1601
+ }
1602
+ return sanitized.length > 0 ? sanitized : void 0;
1603
+ }, [products]);
1604
+ const handleClick = useCallback(() => {
1605
+ if (!resolvedVariant) return;
1606
+ trackEvent(window.FrakSetup?.client, "post_purchase_clicked", {
1607
+ placement: placementId,
1608
+ variant: resolvedVariant
1609
+ });
1610
+ openSharingPage(void 0, placementId, {
1611
+ link: resolvedSharingUrl,
1612
+ products: parsedProducts
1613
+ });
1614
+ }, [
1615
+ resolvedVariant,
1616
+ placementId,
1617
+ resolvedSharingUrl,
1618
+ parsedProducts
1619
+ ]);
1535
1620
  if (!isPreview && (!shouldRender || isHidden)) return null;
1536
1621
  if (!isPreview && (!context || !resolvedVariant)) return null;
1537
1622
  if (!resolvedVariant) return null;
@@ -1557,14 +1642,7 @@ function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, plac
1557
1642
  type: "button",
1558
1643
  className: `${cta} button`,
1559
1644
  disabled: !isPreview && !isClientReady,
1560
- onClick: isPreview ? void 0 : () => {
1561
- if (!resolvedVariant) return;
1562
- trackEvent(window.FrakSetup?.client, "post_purchase_clicked", {
1563
- placement: placementId,
1564
- variant: resolvedVariant
1565
- });
1566
- handleShare();
1567
- },
1645
+ onClick: isPreview ? void 0 : handleClick,
1568
1646
  children: [texts.cta, /* @__PURE__ */ jsx("svg", {
1569
1647
  width: "16",
1570
1648
  height: "16",
@@ -1588,10 +1666,10 @@ function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, plac
1588
1666
  className: giftIcon,
1589
1667
  width: 80,
1590
1668
  height: 80
1591
- }), /* @__PURE__ */ jsx(LogoFrak, {
1669
+ }), /* @__PURE__ */ jsx(LogoFrakWithName, {
1592
1670
  className: frakLogo,
1593
- width: 14,
1594
- height: 14
1671
+ width: 42,
1672
+ height: 24
1595
1673
  })]
1596
1674
  })
1597
1675
  })]
@@ -1613,6 +1691,7 @@ registerWebComponent(PostPurchase, "frak-post-purchase", [
1613
1691
  "referrerText",
1614
1692
  "refereeText",
1615
1693
  "ctaText",
1694
+ "products",
1616
1695
  "preview",
1617
1696
  "previewVariant"
1618
1697
  ], { shadow: false });
@@ -0,0 +1,15 @@
1
+ import { displaySharingPage } from "@frak-labs/core-sdk/actions";
2
+ //#region src/utils/sharingPage.ts
3
+ async function openSharingPage(targetInteraction, placement, options) {
4
+ if (!window.FrakSetup?.client) {
5
+ console.error("Frak client not found");
6
+ return;
7
+ }
8
+ await displaySharingPage(window.FrakSetup.client, {
9
+ ...options?.link && { link: options.link },
10
+ ...options?.products?.length && { products: options.products },
11
+ ...targetInteraction && { metadata: { targetInteraction } }
12
+ }, placement);
13
+ }
14
+ //#endregion
15
+ export { openSharingPage as t };