@frak-labs/components 0.0.26-beta.06c52c98 → 0.0.26-beta.1c24fe7c

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.
@@ -0,0 +1 @@
1
+ import{t as e}from"./loader.js";import{a as t,u as n}from"./jsxRuntime.module.5UNmmhNi.js";function r(r,i,a,o){n(()=>{e.injectBase(r,o??t)},[r]),n(()=>{!i||!a||e.injectPlacement(r,i,a)},[r,i,a])}export{r as t};
@@ -0,0 +1 @@
1
+ import{_ as e,b as t,l as n}from"./loader.js";import{c as r,u as i}from"./jsxRuntime.module.5UNmmhNi.js";import{t as a}from"./formatReward.C8hlSKRj.js";function o(e,t){switch(e.payoutType){case`fixed`:return e.amount[t];case`tiered`:return e.tiers.reduce((e,n)=>Math.max(e,n.amount[t]),0);case`percentage`:return 0}}function s({rewards:n},r,i){let s=(i?n.filter(e=>e.interactionTypeKey===i):n).map(e=>e.referrer).filter(e=>e!==void 0);if(s.length===0)return;let c=t(e(r)),l=s[0],u=o(l,c);for(let e=1;e<s.length;e++){let t=o(s[e],c);t>u&&(l=s[e],u=t)}if(u<=0){let e=s.find(e=>e.payoutType===`percentage`);if(!e)return;l=e}return a(l,r)}function c(e,t){let[a,o]=r(void 0);return i(()=>{if(!e)return;let r=window.FrakSetup?.client;r&&n(r).then(e=>{let n=r.config.metadata?.currency,i=s(e,n,t);i&&o(i)}).catch(()=>{})},[e,t]),{reward:a}}export{c as t};
@@ -0,0 +1 @@
1
+ import{C as e,S as t,u as n,v as r,w as i}from"./loader.js";import{c as a,l as o}from"./jsxRuntime.module.5UNmmhNi.js";function s(s,c,l){let[u,d]=a(void 0),[f,p]=a(!1);return{handleShare:o(async()=>{if(!window.FrakSetup?.client){console.error(`Frak client not found`),d(r.empty().formatDebugInfo(`Frak client not found`)),p(!0);return}let a=n(window.FrakSetup.client,{});try{await a.sharing(l?{link:l}:{}).display(e=>({...e,targetInteraction:s}),c)}catch(n){if(n instanceof i&&n.code===e.clientAborted){console.debug(`User aborted the modal`);return}let r=window.FrakSetup.client.debugInfo.formatDebugInfo(n);t(window.FrakSetup.client,`share_modal_error`,{error:n instanceof Object&&`message`in n?n.message:`Unknown error`,debugInfo:r}),d(r),p(!0),console.error(`Error while opening the modal`,n)}},[s,c,l]),isError:f,debugInfo:u}}export{s as t};
@@ -0,0 +1,73 @@
1
+ import { InteractionTypeKey } from "@frak-labs/core-sdk";
2
+ import * as preact from "preact";
3
+
4
+ //#region src/components/Banner/types.d.ts
5
+ /**
6
+ * The props type for {@link Banner}.
7
+ * @inline
8
+ */
9
+ type BannerProps = {
10
+ /**
11
+ * Placement ID for backend-driven CSS customization.
12
+ */
13
+ placement?: string;
14
+ /**
15
+ * CSS class names passed through to the root element (Light DOM).
16
+ */
17
+ classname?: string;
18
+ /**
19
+ * Filter rewards by interaction type (e.g. "purchase", "referral").
20
+ * When omitted, the best reward across all interaction types is shown.
21
+ */
22
+ interaction?: InteractionTypeKey;
23
+ };
24
+ //#endregion
25
+ //#region src/components/Banner/Banner.d.ts
26
+ /**
27
+ * Auto-detecting notification banner component.
28
+ *
29
+ * Renders an inline banner on the merchant page that auto-detects which
30
+ * message to display:
31
+ *
32
+ * - **Referral mode**: Shown after successful referral link processing.
33
+ * Displays reward info and a dismiss button ("Got it").
34
+ * - **In-app browser mode**: Shown when the page is opened inside a social
35
+ * media in-app browser (Instagram, Facebook). Offers a redirect to the
36
+ * default browser.
37
+ *
38
+ * In-app browser mode takes priority over referral mode.
39
+ * Uses Light DOM to inherit merchant page styles.
40
+ *
41
+ * @group components
42
+ *
43
+ * @example
44
+ * Basic usage (auto-detects mode):
45
+ * ```html
46
+ * <frak-banner></frak-banner>
47
+ * ```
48
+ *
49
+ * @example
50
+ * With a custom class:
51
+ * ```html
52
+ * <frak-banner classname="my-custom-banner"></frak-banner>
53
+ * ```
54
+ */
55
+ declare function Banner({
56
+ placement: placementId,
57
+ classname,
58
+ interaction
59
+ }: BannerProps): preact.JSX.Element | null;
60
+ //#endregion
61
+ //#region src/components/Banner/index.d.ts
62
+ /**
63
+ * Custom element interface for `<frak-banner>`.
64
+ * Combines standard {@link HTMLElement} with {@link BannerProps}.
65
+ */
66
+ interface BannerElement extends HTMLElement, BannerProps {}
67
+ declare global {
68
+ interface HTMLElementTagNameMap {
69
+ "frak-banner": BannerElement;
70
+ }
71
+ }
72
+ //#endregion
73
+ export { Banner, BannerElement };
package/dist/banner.js ADDED
@@ -0,0 +1,171 @@
1
+ import { a as useClientReady, n as bannerBaseCss, o as registerWebComponent, t as usePlacement } from "./usePlacement-BbMuz8_A.js";
2
+ import { t as useLightDomStyles } from "./useLightDomStyles-DukxuNnJ.js";
3
+ import { t as useReward } from "./useReward-CI2yRrCj.js";
4
+ import { isInAppBrowser, redirectToExternalBrowser } from "@frak-labs/core-sdk";
5
+ import { REFERRAL_SUCCESS_EVENT } from "@frak-labs/core-sdk/actions";
6
+ import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
7
+ import { jsx, jsxs } from "preact/jsx-runtime";
8
+
9
+ //#region src/components/Banner/Banner.tsx
10
+ /**
11
+ * Reward/gift icon for referral mode.
12
+ */
13
+ function RewardIcon() {
14
+ return /* @__PURE__ */ jsxs("svg", {
15
+ viewBox: "0 0 24 24",
16
+ fill: "none",
17
+ stroke: "currentColor",
18
+ "stroke-width": "1.5",
19
+ "stroke-linecap": "round",
20
+ "stroke-linejoin": "round",
21
+ "aria-hidden": "true",
22
+ children: [
23
+ /* @__PURE__ */ jsx("rect", {
24
+ x: "3",
25
+ y: "11",
26
+ width: "18",
27
+ height: "11",
28
+ rx: "1"
29
+ }),
30
+ /* @__PURE__ */ jsx("path", { d: "M12 11v11" }),
31
+ /* @__PURE__ */ jsx("rect", {
32
+ x: "5",
33
+ y: "7",
34
+ width: "14",
35
+ height: "4",
36
+ rx: "1"
37
+ }),
38
+ /* @__PURE__ */ jsx("path", { d: "M12 7c0 0-1.5-4-4.5-4S5 5 7.5 7" }),
39
+ /* @__PURE__ */ jsx("path", { d: "M12 7c0 0 1.5-4 4.5-4S19 5 16.5 7" })
40
+ ]
41
+ });
42
+ }
43
+ /**
44
+ * External link icon for in-app browser mode.
45
+ */
46
+ function BrowserIcon() {
47
+ return /* @__PURE__ */ jsxs("svg", {
48
+ viewBox: "0 0 24 24",
49
+ fill: "none",
50
+ stroke: "currentColor",
51
+ "stroke-width": "1.5",
52
+ "stroke-linecap": "round",
53
+ "stroke-linejoin": "round",
54
+ "aria-hidden": "true",
55
+ children: [
56
+ /* @__PURE__ */ jsx("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
57
+ /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
58
+ /* @__PURE__ */ jsx("line", {
59
+ x1: "10",
60
+ y1: "14",
61
+ x2: "21",
62
+ y2: "3"
63
+ })
64
+ ]
65
+ });
66
+ }
67
+ /**
68
+ * Auto-detecting notification banner component.
69
+ *
70
+ * Renders an inline banner on the merchant page that auto-detects which
71
+ * message to display:
72
+ *
73
+ * - **Referral mode**: Shown after successful referral link processing.
74
+ * Displays reward info and a dismiss button ("Got it").
75
+ * - **In-app browser mode**: Shown when the page is opened inside a social
76
+ * media in-app browser (Instagram, Facebook). Offers a redirect to the
77
+ * default browser.
78
+ *
79
+ * In-app browser mode takes priority over referral mode.
80
+ * Uses Light DOM to inherit merchant page styles.
81
+ *
82
+ * @group components
83
+ *
84
+ * @example
85
+ * Basic usage (auto-detects mode):
86
+ * ```html
87
+ * <frak-banner></frak-banner>
88
+ * ```
89
+ *
90
+ * @example
91
+ * With a custom class:
92
+ * ```html
93
+ * <frak-banner classname="my-custom-banner"></frak-banner>
94
+ * ```
95
+ */
96
+ function Banner({ placement: placementId, classname = "", interaction }) {
97
+ const placement = usePlacement(placementId);
98
+ const { shouldRender, isHidden, isClientReady } = useClientReady();
99
+ useLightDomStyles("frak-banner", placementId, placement?.components?.banner?.css, bannerBaseCss);
100
+ const [dismissed, setDismissed] = useState(false);
101
+ const [mode, setMode] = useState(() => isInAppBrowser ? "inapp" : null);
102
+ const { reward } = useReward(mode === "referral" && isClientReady, interaction);
103
+ useEffect(() => {
104
+ if (mode === "inapp") return;
105
+ const handler = () => setMode("referral");
106
+ window.addEventListener(REFERRAL_SUCCESS_EVENT, handler);
107
+ return () => window.removeEventListener(REFERRAL_SUCCESS_EVENT, handler);
108
+ }, [mode]);
109
+ const handleAction = useCallback(() => {
110
+ if (mode === "referral") setDismissed(true);
111
+ else redirectToExternalBrowser(window.location.href);
112
+ }, [mode]);
113
+ const bannerConfig = placement?.components?.banner;
114
+ const texts = useMemo(() => {
115
+ if (mode === "referral") {
116
+ const defaultTitle = reward ? `Earn ${reward} on purchases on this site` : "You've been referred!";
117
+ return {
118
+ title: bannerConfig?.referralTitle ?? defaultTitle,
119
+ description: bannerConfig?.referralDescription ?? "Earn rewards after your purchase via the Frak partner app.",
120
+ cta: bannerConfig?.referralCta ?? "Got it"
121
+ };
122
+ }
123
+ return {
124
+ title: bannerConfig?.inappTitle ?? "Open in your browser",
125
+ description: bannerConfig?.inappDescription ?? "For a better experience and to earn your rewards, open this page in your default browser.",
126
+ cta: bannerConfig?.inappCta ?? "Open browser"
127
+ };
128
+ }, [
129
+ mode,
130
+ reward,
131
+ bannerConfig
132
+ ]);
133
+ if (!shouldRender || isHidden || dismissed || !mode) return null;
134
+ return /* @__PURE__ */ jsxs("div", {
135
+ class: [
136
+ "banner",
137
+ "banner__fadeIn",
138
+ classname
139
+ ].filter(Boolean).join(" "),
140
+ role: "alert",
141
+ children: [
142
+ /* @__PURE__ */ jsx("div", {
143
+ class: "banner__icon",
144
+ children: mode === "referral" ? /* @__PURE__ */ jsx(RewardIcon, {}) : /* @__PURE__ */ jsx(BrowserIcon, {})
145
+ }),
146
+ /* @__PURE__ */ jsxs("div", {
147
+ class: "banner__content",
148
+ children: [/* @__PURE__ */ jsx("p", {
149
+ class: "banner__title",
150
+ children: texts.title
151
+ }), /* @__PURE__ */ jsx("p", {
152
+ class: "banner__description",
153
+ children: texts.description
154
+ })]
155
+ }),
156
+ /* @__PURE__ */ jsx("button", {
157
+ type: "button",
158
+ class: "banner__cta",
159
+ onClick: handleAction,
160
+ children: texts.cta
161
+ })
162
+ ]
163
+ });
164
+ }
165
+
166
+ //#endregion
167
+ //#region src/components/Banner/index.ts
168
+ registerWebComponent(Banner, "frak-banner", ["placement", "classname"], { shadow: false });
169
+
170
+ //#endregion
171
+ export { Banner };
@@ -35,7 +35,7 @@ type ButtonShareProps = {
35
35
  * Which UI to open on click
36
36
  * @defaultValue `"embedded-wallet"`
37
37
  */
38
- clickAction?: "embedded-wallet" | "share-modal";
38
+ clickAction?: "embedded-wallet" | "share-modal" | "sharing-page";
39
39
  };
40
40
  //#endregion
41
41
  //#region src/components/ButtonShare/ButtonShare.d.ts
@@ -1,12 +1,22 @@
1
- import { a as registerWebComponent, i as useClientReady, s as openEmbeddedWallet, t as usePlacement } from "./usePlacement-DD1NUBps.js";
2
- import { t as useLightDomStyles } from "./useLightDomStyles-C029nIFk.js";
3
- import { t as useReward } from "./useReward-DAkT-7wT.js";
4
- import { DebugInfoGatherer, trackEvent } from "@frak-labs/core-sdk";
5
- import { modalBuilder } from "@frak-labs/core-sdk/actions";
1
+ import { a as useClientReady, c as openEmbeddedWallet, o as registerWebComponent, t as usePlacement } from "./usePlacement-BbMuz8_A.js";
2
+ import { t as useLightDomStyles } from "./useLightDomStyles-DukxuNnJ.js";
3
+ import { t as useReward } from "./useReward-CI2yRrCj.js";
4
+ import { t as useShareModal } from "./useShareModal-DHlayNqk.js";
5
+ import { trackEvent } from "@frak-labs/core-sdk";
6
+ import { displaySharingPage } from "@frak-labs/core-sdk/actions";
6
7
  import { useCallback, useMemo, useState } from "preact/hooks";
7
8
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
8
- import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
9
9
 
10
+ //#region src/utils/sharingPage.ts
11
+ async function openSharingPage(targetInteraction, placement) {
12
+ if (!window.FrakSetup?.client) {
13
+ console.error("Frak client not found");
14
+ return;
15
+ }
16
+ await displaySharingPage(window.FrakSetup.client, { metadata: { ...targetInteraction && { targetInteraction } } }, placement);
17
+ }
18
+
19
+ //#endregion
10
20
  //#region src/hooks/useCopyToClipboard.ts
11
21
  function useCopyToClipboard(options = {}) {
12
22
  const { successDuration = 2e3 } = options;
@@ -159,51 +169,6 @@ function ErrorMessage({ debugInfo }) {
159
169
  });
160
170
  }
161
171
 
162
- //#endregion
163
- //#region src/components/ButtonShare/hooks/useShareModal.ts
164
- /**
165
- * Open the share modal
166
- *
167
- * @description
168
- * This function will open the share modal, lazily creating a modal builder on demand.
169
- */
170
- function useShareModal(targetInteraction, placement) {
171
- const [debugInfo, setDebugInfo] = useState(void 0);
172
- const [isError, setIsError] = useState(false);
173
- return {
174
- handleShare: useCallback(async () => {
175
- if (!window.FrakSetup?.client) {
176
- console.error("Frak client not found");
177
- setDebugInfo(DebugInfoGatherer.empty().formatDebugInfo("Frak client not found"));
178
- setIsError(true);
179
- return;
180
- }
181
- const builder = modalBuilder(window.FrakSetup.client, {});
182
- try {
183
- await builder.sharing({}).display((metadata) => ({
184
- ...metadata,
185
- targetInteraction
186
- }), placement);
187
- } catch (e) {
188
- if (e instanceof FrakRpcError && e.code === RpcErrorCodes.clientAborted) {
189
- console.debug("User aborted the modal");
190
- return;
191
- }
192
- const debugInfo = window.FrakSetup.client.debugInfo.formatDebugInfo(e);
193
- trackEvent(window.FrakSetup.client, "share_modal_error", {
194
- error: e instanceof Object && "message" in e ? e.message : "Unknown error",
195
- debugInfo
196
- });
197
- setDebugInfo(debugInfo);
198
- setIsError(true);
199
- console.error("Error while opening the modal", e);
200
- }
201
- }, [targetInteraction, placement]),
202
- isError,
203
- debugInfo
204
- };
205
- }
206
-
207
172
  //#endregion
208
173
  //#region src/components/ButtonShare/ButtonShare.tsx
209
174
  /**
@@ -255,7 +220,7 @@ function ButtonShare({ placement: placementId, text = "Share and earn!", classna
255
220
  const resolvedText = componentConfig?.text ?? text;
256
221
  const resolvedNoRewardText = componentConfig?.noRewardText ?? noRewardText;
257
222
  const shouldUseReward = useMemo(() => componentConfig?.useReward ?? rawUseReward === true, [componentConfig?.useReward, rawUseReward]);
258
- const resolvedClickAction = useMemo(() => componentConfig?.clickAction ?? rawClickAction ?? "embedded-wallet", [componentConfig?.clickAction, rawClickAction]);
223
+ const resolvedClickAction = useMemo(() => componentConfig?.clickAction ?? rawClickAction ?? "sharing-page", [componentConfig?.clickAction, rawClickAction]);
259
224
  const { shouldRender, isHidden, isClientReady } = useClientReady();
260
225
  const { reward } = useReward(shouldUseReward && isClientReady, resolvedTargetInteraction);
261
226
  const { handleShare, isError, debugInfo } = useShareModal(resolvedTargetInteraction, placementId);
@@ -271,8 +236,9 @@ function ButtonShare({ placement: placementId, text = "Share and earn!", classna
271
236
  ]);
272
237
  const onClick = useCallback(async () => {
273
238
  trackEvent(window.FrakSetup.client, "share_button_clicked");
274
- if (resolvedClickAction === "share-modal") await handleShare();
275
- else openEmbeddedWallet(resolvedTargetInteraction, placementId);
239
+ if (resolvedClickAction === "embedded-wallet") openEmbeddedWallet(resolvedTargetInteraction, placementId);
240
+ else if (resolvedClickAction === "share-modal") await handleShare();
241
+ else openSharingPage(resolvedTargetInteraction, placementId);
276
242
  }, [
277
243
  resolvedClickAction,
278
244
  handleShare,
@@ -1,5 +1,5 @@
1
- import { a as registerWebComponent, i as useClientReady, n as buildStyleContent, o as openWalletModal, t as usePlacement } from "./usePlacement-DD1NUBps.js";
2
- import { t as useReward } from "./useReward-DAkT-7wT.js";
1
+ import { a as useClientReady, o as registerWebComponent, r as buildStyleContent, s as openWalletModal, t as usePlacement } from "./usePlacement-BbMuz8_A.js";
2
+ import { t as useReward } from "./useReward-CI2yRrCj.js";
3
3
  import { trackEvent } from "@frak-labs/core-sdk";
4
4
  import { useEffect, useMemo, useState } from "preact/hooks";
5
5
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
@@ -0,0 +1,28 @@
1
+ import { formatAmount, getCurrencyAmountKey, getSupportedCurrency } from "@frak-labs/core-sdk";
2
+
3
+ //#region src/utils/formatReward.ts
4
+ /**
5
+ * Format an {@link EstimatedReward} into a human-readable string.
6
+ *
7
+ * - `fixed` → e.g. `"5 €"`
8
+ * - `percentage` → if `basketAmount` is provided, computes the actual value
9
+ * (e.g. `"10 €"`), otherwise returns `"10 %"`
10
+ * - `tiered` → max tier value, e.g. `"50 €"`
11
+ */
12
+ function formatEstimatedReward(reward, currency, basketAmount) {
13
+ const supportedCurrency = getSupportedCurrency(currency);
14
+ const key = getCurrencyAmountKey(supportedCurrency);
15
+ switch (reward.payoutType) {
16
+ case "fixed": return formatAmount(Math.round(reward.amount[key]), supportedCurrency);
17
+ case "percentage":
18
+ if (basketAmount !== void 0) return formatAmount(Math.round(reward.percent * basketAmount / 100), supportedCurrency);
19
+ return `${reward.percent} %`;
20
+ case "tiered": {
21
+ const max = reward.tiers.reduce((acc, tier) => Math.max(acc, tier.amount[key]), 0);
22
+ return formatAmount(Math.round(max), supportedCurrency);
23
+ }
24
+ }
25
+ }
26
+
27
+ //#endregion
28
+ export { formatEstimatedReward as t };
package/dist/openInApp.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a as registerWebComponent, i as useClientReady, t as usePlacement } from "./usePlacement-DD1NUBps.js";
2
- import { t as useLightDomStyles } from "./useLightDomStyles-C029nIFk.js";
1
+ import { a as useClientReady, o as registerWebComponent, t as usePlacement } from "./usePlacement-BbMuz8_A.js";
2
+ import { t as useLightDomStyles } from "./useLightDomStyles-DukxuNnJ.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";
@@ -0,0 +1,98 @@
1
+ import * as preact from "preact";
2
+
3
+ //#region src/components/PostPurchase/types.d.ts
4
+ /**
5
+ * Props for the {@link PostPurchase} component.
6
+ * @inline
7
+ */
8
+ type PostPurchaseProps = {
9
+ /**
10
+ * Merchant customer ID for purchase tracking fallback.
11
+ * All three tracking props (`customerId`, `orderId`, `token`) must be
12
+ * present for tracking to fire.
13
+ */
14
+ customerId?: string;
15
+ /**
16
+ * Merchant order ID for purchase tracking fallback.
17
+ */
18
+ orderId?: string;
19
+ /**
20
+ * Checkout token for purchase tracking fallback.
21
+ */
22
+ token?: string;
23
+ /**
24
+ * Base URL to share. Falls back to the merchant domain returned by
25
+ * the backend when omitted.
26
+ */
27
+ sharingUrl?: string;
28
+ /**
29
+ * Override the merchant ID resolved from the SDK config.
30
+ */
31
+ merchantId?: string;
32
+ /**
33
+ * Placement ID for backend-driven CSS customization.
34
+ */
35
+ placement?: string;
36
+ /**
37
+ * CSS class names passed through to the root element (Light DOM).
38
+ */
39
+ classname?: string;
40
+ /**
41
+ * Force a display variant instead of relying on the backend evaluation.
42
+ */
43
+ variant?: "referrer" | "referee";
44
+ };
45
+ //#endregion
46
+ //#region src/components/PostPurchase/PostPurchase.d.ts
47
+ /**
48
+ * Post-purchase card component.
49
+ *
50
+ * Renders an inline card on the merchant's thank-you / order-status page
51
+ * that either congratulates a referee or invites a referrer to share.
52
+ *
53
+ * Fetches referral status and merchant information via two independent
54
+ * RPC calls, then computes the display variant locally.
55
+ *
56
+ * @group components
57
+ *
58
+ * @example
59
+ * Minimal — just show the card:
60
+ * ```html
61
+ * <frak-post-purchase></frak-post-purchase>
62
+ * ```
63
+ *
64
+ * @example
65
+ * With purchase tracking fallback and custom sharing URL:
66
+ * ```html
67
+ * <frak-post-purchase
68
+ * customer-id="cust_123"
69
+ * order-id="ord_456"
70
+ * token="checkout_abc"
71
+ * sharing-url="https://merchant.com/product/shoes"
72
+ * ></frak-post-purchase>
73
+ * ```
74
+ */
75
+ declare function PostPurchase({
76
+ customerId,
77
+ orderId,
78
+ token,
79
+ sharingUrl,
80
+ merchantId,
81
+ placement: placementId,
82
+ classname,
83
+ variant: forcedVariant
84
+ }: PostPurchaseProps): preact.JSX.Element | null;
85
+ //#endregion
86
+ //#region src/components/PostPurchase/index.d.ts
87
+ /**
88
+ * Custom element interface for `<frak-post-purchase>`.
89
+ * Combines standard {@link HTMLElement} with {@link PostPurchaseProps}.
90
+ */
91
+ interface PostPurchaseElement extends HTMLElement, PostPurchaseProps {}
92
+ declare global {
93
+ interface HTMLElementTagNameMap {
94
+ "frak-post-purchase": PostPurchaseElement;
95
+ }
96
+ }
97
+ //#endregion
98
+ export { PostPurchase, PostPurchaseElement };
@@ -0,0 +1,140 @@
1
+ import { a as useClientReady, o as registerWebComponent, t as usePlacement } from "./usePlacement-BbMuz8_A.js";
2
+ import { t as useLightDomStyles } from "./useLightDomStyles-DukxuNnJ.js";
3
+ import { t as formatEstimatedReward } from "./formatReward-6JQldDEC.js";
4
+ import { t as useShareModal } from "./useShareModal-DHlayNqk.js";
5
+ import { getMerchantInformation, getUserReferralStatus, trackPurchaseStatus } from "@frak-labs/core-sdk/actions";
6
+ import { useEffect, useMemo, useState } from "preact/hooks";
7
+ import { jsx, jsxs } from "preact/jsx-runtime";
8
+ import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
9
+
10
+ //#region src/components/PostPurchase/PostPurchase.tsx
11
+ /**
12
+ * Given referral status and merchant info, compute the display variant
13
+ * and pick the appropriate purchase reward.
14
+ */
15
+ function resolvePostPurchaseContext(referralStatus, merchantInfo) {
16
+ const purchaseReward = merchantInfo.rewards.find((r) => r.interactionTypeKey === "purchase" && (r.referrer || r.referee));
17
+ if (!purchaseReward) return null;
18
+ const variant = referralStatus?.isReferred && purchaseReward.referee ? "referee" : "referrer";
19
+ return {
20
+ variant,
21
+ reward: variant === "referee" ? purchaseReward.referee : purchaseReward.referrer,
22
+ merchantDomain: merchantInfo.onChainMetadata.domain
23
+ };
24
+ }
25
+ /**
26
+ * Post-purchase card component.
27
+ *
28
+ * Renders an inline card on the merchant's thank-you / order-status page
29
+ * that either congratulates a referee or invites a referrer to share.
30
+ *
31
+ * Fetches referral status and merchant information via two independent
32
+ * RPC calls, then computes the display variant locally.
33
+ *
34
+ * @group components
35
+ *
36
+ * @example
37
+ * Minimal — just show the card:
38
+ * ```html
39
+ * <frak-post-purchase></frak-post-purchase>
40
+ * ```
41
+ *
42
+ * @example
43
+ * With purchase tracking fallback and custom sharing URL:
44
+ * ```html
45
+ * <frak-post-purchase
46
+ * customer-id="cust_123"
47
+ * order-id="ord_456"
48
+ * token="checkout_abc"
49
+ * sharing-url="https://merchant.com/product/shoes"
50
+ * ></frak-post-purchase>
51
+ * ```
52
+ */
53
+ function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, placement: placementId, classname = "", variant: forcedVariant }) {
54
+ const { shouldRender, isHidden, isClientReady } = useClientReady();
55
+ const placement = usePlacement(placementId);
56
+ useLightDomStyles("frak-post-purchase", placementId, placement?.components?.postPurchase?.css);
57
+ const [context, setContext] = useState(null);
58
+ const [hasFetched, setHasFetched] = useState(false);
59
+ useEffect(() => {
60
+ if (!isClientReady || !customerId || !orderId || !token) return;
61
+ trackPurchaseStatus({
62
+ customerId,
63
+ orderId,
64
+ token,
65
+ merchantId
66
+ }).catch(() => {});
67
+ }, [
68
+ isClientReady,
69
+ customerId,
70
+ orderId,
71
+ token,
72
+ merchantId
73
+ ]);
74
+ useEffect(() => {
75
+ if (!isClientReady || hasFetched) return;
76
+ const client = window.FrakSetup?.client;
77
+ if (!client) return;
78
+ setHasFetched(true);
79
+ Promise.all([getUserReferralStatus(client), getMerchantInformation(client)]).then(([referralStatus, merchantInfo]) => {
80
+ setContext(resolvePostPurchaseContext(referralStatus, merchantInfo));
81
+ }).catch((e) => {
82
+ if (e instanceof FrakRpcError && e.code === RpcErrorCodes.configError) return;
83
+ console.warn("[Frak] Post-purchase context error", e);
84
+ });
85
+ }, [isClientReady, hasFetched]);
86
+ const resolvedVariant = forcedVariant ?? context?.variant;
87
+ const resolvedSharingUrl = sharingUrl ?? context?.merchantDomain;
88
+ const rewardText = useMemo(() => {
89
+ if (!context?.reward) return void 0;
90
+ const currency = window.FrakSetup?.client?.config?.metadata?.currency;
91
+ return formatEstimatedReward(context.reward, currency);
92
+ }, [context?.reward]);
93
+ const postPurchaseConfig = placement?.components?.postPurchase;
94
+ const texts = useMemo(() => {
95
+ const applyReward = (text) => rewardText ? text.replace("{REWARD}", rewardText) : text;
96
+ return {
97
+ message: resolvedVariant === "referee" ? rewardText ? applyReward(postPurchaseConfig?.refereeText ?? "You just earned {REWARD}! Share with friends to earn even more.") : postPurchaseConfig?.refereeNoRewardText ?? "You just earned a reward! Share with friends to earn even more." : rewardText ? applyReward(postPurchaseConfig?.referrerText ?? "Earn {REWARD} by sharing this with your friends!") : postPurchaseConfig?.referrerNoRewardText ?? "Share this with your friends and earn rewards!",
98
+ cta: rewardText ? applyReward(postPurchaseConfig?.ctaText ?? "Share & earn {REWARD}") : postPurchaseConfig?.ctaNoRewardText ?? "Share & earn"
99
+ };
100
+ }, [
101
+ resolvedVariant,
102
+ rewardText,
103
+ postPurchaseConfig
104
+ ]);
105
+ const { handleShare } = useShareModal(void 0, placementId, resolvedSharingUrl);
106
+ if (!shouldRender || isHidden) return null;
107
+ if (!context || !resolvedVariant) return null;
108
+ return /* @__PURE__ */ jsxs("div", {
109
+ class: ["post-purchase", classname].filter(Boolean).join(" "),
110
+ children: [/* @__PURE__ */ jsx("div", {
111
+ class: "post-purchase__content",
112
+ children: /* @__PURE__ */ jsx("p", {
113
+ class: "post-purchase__message",
114
+ children: texts.message
115
+ })
116
+ }), /* @__PURE__ */ jsx("button", {
117
+ type: "button",
118
+ class: "post-purchase__cta button",
119
+ disabled: !isClientReady,
120
+ onClick: handleShare,
121
+ children: texts.cta
122
+ })]
123
+ });
124
+ }
125
+
126
+ //#endregion
127
+ //#region src/components/PostPurchase/index.ts
128
+ registerWebComponent(PostPurchase, "frak-post-purchase", [
129
+ "customerId",
130
+ "orderId",
131
+ "token",
132
+ "sharingUrl",
133
+ "merchantId",
134
+ "placement",
135
+ "classname",
136
+ "variant"
137
+ ], { shadow: false });
138
+
139
+ //#endregion
140
+ export { PostPurchase };
@@ -1,4 +1,4 @@
1
- import { r as lightDomBaseCss } from "./usePlacement-DD1NUBps.js";
1
+ import { i as lightDomBaseCss } from "./usePlacement-BbMuz8_A.js";
2
2
  import { useEffect } from "preact/hooks";
3
3
 
4
4
  //#region src/utils/styleManager.ts
@@ -26,9 +26,9 @@ const styleManager = {
26
26
 
27
27
  //#endregion
28
28
  //#region src/hooks/useLightDomStyles.ts
29
- function useLightDomStyles(tag, placementId, placementCss) {
29
+ function useLightDomStyles(tag, placementId, placementCss, baseCss) {
30
30
  useEffect(() => {
31
- styleManager.injectBase(tag, lightDomBaseCss);
31
+ styleManager.injectBase(tag, baseCss ?? lightDomBaseCss);
32
32
  }, [tag]);
33
33
  useEffect(() => {
34
34
  if (!placementId || !placementCss) return;