@frak-labs/components 1.0.2-beta.f45a538e → 1.0.3-beta.9985efe7

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 (32) hide show
  1. package/cdn/Banner.BTj-CQM6.js +162 -0
  2. package/cdn/ButtonShare.DNdo_eQJ.js +1 -0
  3. package/cdn/{ButtonWallet.Cwz9qFhE.js → ButtonWallet.CN2iHSTB.js} +1 -1
  4. package/cdn/{OpenInAppButton.Hq9EjwJE.js → OpenInAppButton.C1Yipwka.js} +1 -1
  5. package/cdn/PostPurchase.BvmSYSpL.js +52 -0
  6. package/cdn/components.js +1 -1
  7. package/cdn/formatReward.C7mU9_cV.js +1 -0
  8. package/cdn/loader.js +1 -1
  9. package/cdn/sharingPage.Do_xfrTN.js +1 -0
  10. package/cdn/{sprinkles.css.ts.vanilla.Ct795MMK.js → sprinkles.css.ts.vanilla.06k5OzG1.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.QsQc2c1D.js} +1 -1
  15. package/dist/{GiftIcon-4sr9xXyq.js → GiftIcon-c28NnqJ7.js} +1 -0
  16. package/dist/banner.js +2 -2
  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 +95 -15
  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.CMAEVXOn.js +0 -1
  27. package/cdn/PostPurchase.BaLtt8Gt.js +0 -52
  28. package/cdn/formatReward.BaR9pE50.js +0 -1
  29. package/cdn/useGlobalComponents.CLH7id-Y.js +0 -1
  30. package/cdn/useShareModal.UYsKX7L2.js +0 -1
  31. package/dist/useLightDomStyles-C3lcOwY2.js +0 -41
  32. 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, s as cssSource$4, t as GiftIcon } from "./GiftIcon-c28NnqJ7.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";
@@ -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 { c as cssSource$5, 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 LogoFrak, s as cssSource$3, t as GiftIcon } from "./GiftIcon-c28NnqJ7.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
@@ -1399,7 +1399,68 @@ var frakLogo = "PostPurchase_frakLogo__5fv5lh6";
1399
1399
  var giftIcon = "PostPurchase_giftIcon__5fv5lh5";
1400
1400
  var icon = "PostPurchase_icon__5fv5lh4";
1401
1401
  var message = "PostPurchase_message__5fv5lh2";
1402
- const cssSource = cssSource$3 + cssSource$4 + cssSource$5 + cssSource$1;
1402
+ const cssSource = cssSource$5 + cssSource$3 + cssSource$4 + cssSource$1;
1403
+ //#endregion
1404
+ //#region src/components/PostPurchase/products.ts
1405
+ /**
1406
+ * Whether `value` is a syntactically valid URL with an `http(s):` scheme.
1407
+ *
1408
+ * Used to gate `imageUrl` / `link` fields coming from the public `products`
1409
+ * prop — the listener-side sharing-page builder calls `new URL(...)` on the
1410
+ * incoming product link, and a `javascript:` URL would be a XSS sink in any
1411
+ * consumer that binds the value to an `href`.
1412
+ */
1413
+ function isHttpUrl(value) {
1414
+ try {
1415
+ const parsed = new URL(value);
1416
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
1417
+ } catch {
1418
+ return false;
1419
+ }
1420
+ }
1421
+ /**
1422
+ * Coerce a raw `products` prop value into a candidate array suitable for
1423
+ * per-item normalisation, or null when it cannot be reduced to one.
1424
+ *
1425
+ * Surfaces that set the prop via the JS property (`el.products = [...]`)
1426
+ * deliver a real array; surfaces that bind it as an HTML attribute
1427
+ * (WP / Magento server-render) deliver a JSON-stringified array. Anything
1428
+ * else (truthy non-array non-string, JSON parse failure, JSON that decodes
1429
+ * to a non-array) is treated as "no products" so the share still works
1430
+ * without the product card section.
1431
+ */
1432
+ function coerceProductCandidates(products) {
1433
+ if (!products) return null;
1434
+ if (Array.isArray(products)) return products;
1435
+ if (typeof products !== "string") return null;
1436
+ try {
1437
+ const parsed = JSON.parse(products);
1438
+ return Array.isArray(parsed) ? parsed : null;
1439
+ } catch {
1440
+ return null;
1441
+ }
1442
+ }
1443
+ /**
1444
+ * Normalise one untrusted candidate into a {@link SharingPageProduct}, or
1445
+ * return null when the candidate has no usable title.
1446
+ *
1447
+ * The `products` prop is a public API boundary (merchants can set it
1448
+ * server-side via WP/Magento or imperatively from arbitrary JS). Each entry
1449
+ * is validated structurally so a malformed `link` reaching `new URL(...)`
1450
+ * downstream would not crash the sharing-page builder, and so a
1451
+ * `javascript:` URL cannot slip through as `imageUrl` / `link`.
1452
+ */
1453
+ function normalizeProductCandidate(candidate) {
1454
+ if (!candidate || typeof candidate !== "object") return null;
1455
+ const item = candidate;
1456
+ const title = typeof item.title === "string" ? item.title.trim() : "";
1457
+ if (title === "") return null;
1458
+ const entry = { title };
1459
+ if (typeof item.imageUrl === "string" && isHttpUrl(item.imageUrl)) entry.imageUrl = item.imageUrl;
1460
+ if (typeof item.link === "string" && isHttpUrl(item.link)) entry.link = item.link;
1461
+ if (typeof item.utmContent === "string" && item.utmContent !== "") entry.utmContent = item.utmContent;
1462
+ return entry;
1463
+ }
1403
1464
  //#endregion
1404
1465
  //#region src/components/PostPurchase/PostPurchase.tsx
1405
1466
  /**
@@ -1444,7 +1505,7 @@ function resolvePostPurchaseContext(referralStatus, merchantInfo) {
1444
1505
  * ></frak-post-purchase>
1445
1506
  * ```
1446
1507
  */
1447
- function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, placement: placementId, classname = "", variant: forcedVariant, badgeText: propBadgeText, referrerText: propReferrerText, refereeText: propRefereeText, ctaText: propCtaText, preview, previewVariant }) {
1508
+ function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, placement: placementId, classname = "", variant: forcedVariant, badgeText: propBadgeText, referrerText: propReferrerText, refereeText: propRefereeText, ctaText: propCtaText, preview, previewVariant, products }) {
1448
1509
  const isPreview = !!preview;
1449
1510
  const { shouldRender, isHidden, isClientReady } = useClientReady();
1450
1511
  const placement = usePlacement(placementId);
@@ -1531,7 +1592,32 @@ function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, plac
1531
1592
  placementId,
1532
1593
  context?.reward
1533
1594
  ]);
1534
- const { handleShare } = useShareModal(void 0, placementId, resolvedSharingUrl);
1595
+ const parsedProducts = useMemo(() => {
1596
+ const candidates = coerceProductCandidates(products);
1597
+ if (!candidates) return void 0;
1598
+ const sanitized = [];
1599
+ for (const candidate of candidates) {
1600
+ const entry = normalizeProductCandidate(candidate);
1601
+ if (entry) sanitized.push(entry);
1602
+ }
1603
+ return sanitized.length > 0 ? sanitized : void 0;
1604
+ }, [products]);
1605
+ const handleClick = useCallback(() => {
1606
+ if (!resolvedVariant) return;
1607
+ trackEvent(window.FrakSetup?.client, "post_purchase_clicked", {
1608
+ placement: placementId,
1609
+ variant: resolvedVariant
1610
+ });
1611
+ openSharingPage(void 0, placementId, {
1612
+ link: resolvedSharingUrl,
1613
+ products: parsedProducts
1614
+ });
1615
+ }, [
1616
+ resolvedVariant,
1617
+ placementId,
1618
+ resolvedSharingUrl,
1619
+ parsedProducts
1620
+ ]);
1535
1621
  if (!isPreview && (!shouldRender || isHidden)) return null;
1536
1622
  if (!isPreview && (!context || !resolvedVariant)) return null;
1537
1623
  if (!resolvedVariant) return null;
@@ -1557,14 +1643,7 @@ function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, plac
1557
1643
  type: "button",
1558
1644
  className: `${cta} button`,
1559
1645
  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
- },
1646
+ onClick: isPreview ? void 0 : handleClick,
1568
1647
  children: [texts.cta, /* @__PURE__ */ jsx("svg", {
1569
1648
  width: "16",
1570
1649
  height: "16",
@@ -1613,6 +1692,7 @@ registerWebComponent(PostPurchase, "frak-post-purchase", [
1613
1692
  "referrerText",
1614
1693
  "refereeText",
1615
1694
  "ctaText",
1695
+ "products",
1616
1696
  "preview",
1617
1697
  "previewVariant"
1618
1698
  ], { 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 };
@@ -0,0 +1,89 @@
1
+ import { r as lightDomBaseCss } from "./usePlacement-V7NrKoub.js";
2
+ import { useEffect } from "preact/hooks";
3
+ //#region src/utils/styleManager.ts
4
+ /**
5
+ * Tracks every base CSS rule (by exact cssText) already injected into <head>.
6
+ *
7
+ * Each Light DOM component (frak-banner, frak-post-purchase, …) ships a
8
+ * `cssSource` string that contains both component-specific rules AND shared
9
+ * design-system rules (reset, sprinkles, theme tokens) pulled in transitively
10
+ * by vanilla-extract. Without dedup, every component re-emits the same reset
11
+ * class definitions in its own <style> tag, and whichever stylesheet is
12
+ * appended LAST wins for those shared selectors — flipping the cascade order
13
+ * non-deterministically across mount orders.
14
+ *
15
+ * Deduplicating rule-by-rule guarantees that shared rules appear exactly once
16
+ * (in the first component's stylesheet) and component-specific rules always
17
+ * come AFTER them in document order, so component styles win deterministically.
18
+ */
19
+ const injectedRules = /* @__PURE__ */ new Set();
20
+ function ensureStyle(id, css) {
21
+ const existing = document.getElementById(id);
22
+ if (existing) {
23
+ if (existing.textContent !== css) existing.textContent = css;
24
+ return;
25
+ }
26
+ const style = document.createElement("style");
27
+ style.id = id;
28
+ style.textContent = css;
29
+ document.head.appendChild(style);
30
+ }
31
+ /**
32
+ * Parses `css` and returns a new string containing only rules that have not
33
+ * already been injected into <head>. Tracks injected rules in `injectedRules`.
34
+ *
35
+ * Falls back to the raw input if the browser lacks the constructable
36
+ * `CSSStyleSheet` API or parsing fails — preserves correctness over dedup.
37
+ */
38
+ function dedupeAgainstInjected(css) {
39
+ if (typeof CSSStyleSheet !== "function") return css;
40
+ let sheet;
41
+ try {
42
+ sheet = new CSSStyleSheet();
43
+ sheet.replaceSync(css);
44
+ } catch {
45
+ return css;
46
+ }
47
+ let out = "";
48
+ for (let i = 0; i < sheet.cssRules.length; i++) {
49
+ const ruleText = sheet.cssRules[i].cssText;
50
+ if (injectedRules.has(ruleText)) continue;
51
+ injectedRules.add(ruleText);
52
+ out += `${ruleText}\n`;
53
+ }
54
+ return out;
55
+ }
56
+ function injectBase(tag, css) {
57
+ const id = `frak-base-${tag}`;
58
+ if (document.getElementById(id)) return;
59
+ const deduped = dedupeAgainstInjected(css);
60
+ if (!deduped) return;
61
+ const style = document.createElement("style");
62
+ style.id = id;
63
+ style.textContent = deduped;
64
+ document.head.appendChild(style);
65
+ }
66
+ function injectPlacement(tag, placementId, scopedCss) {
67
+ ensureStyle(`frak-placement-${tag}-${placementId}`, scopedCss);
68
+ }
69
+ const styleManager = {
70
+ injectBase,
71
+ injectPlacement
72
+ };
73
+ //#endregion
74
+ //#region src/hooks/useLightDomStyles.ts
75
+ function useLightDomStyles(tag, placementId, placementCss, baseCss) {
76
+ useEffect(() => {
77
+ styleManager.injectBase(tag, baseCss ?? lightDomBaseCss);
78
+ }, [tag]);
79
+ useEffect(() => {
80
+ if (!placementId || !placementCss) return;
81
+ styleManager.injectPlacement(tag, placementId, placementCss);
82
+ }, [
83
+ tag,
84
+ placementId,
85
+ placementCss
86
+ ]);
87
+ }
88
+ //#endregion
89
+ export { useLightDomStyles as t };
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "url": "https://twitter.com/QNivelais"
12
12
  }
13
13
  ],
14
- "version": "1.0.2-beta.f45a538e",
14
+ "version": "1.0.3-beta.9985efe7",
15
15
  "description": "Frak Wallet components, helping any person to interact with the Frak wallet.",
16
16
  "repository": {
17
17
  "url": "https://github.com/frak-id/wallet",
@@ -86,8 +86,8 @@
86
86
  "publish": "echo 'Publishing components...'"
87
87
  },
88
88
  "dependencies": {
89
- "@frak-labs/core-sdk": "1.0.2-beta.f45a538e",
90
- "@frak-labs/frame-connector": "0.2.0-beta.f45a538e",
89
+ "@frak-labs/core-sdk": "1.0.2-beta.9985efe7",
90
+ "@frak-labs/frame-connector": "0.2.0-beta.9985efe7",
91
91
  "preact": "^10.29.0",
92
92
  "preact-custom-element": "^4.6.0",
93
93
  "@frak-labs/design-system": "0.0.0"