@frak-labs/components
Version:
Frak Wallet components, helping any person to interact with the Frak wallet.
361 lines (360 loc) • 12.4 kB
JavaScript
import { c as openSharingPage, i as useLang, o as useClientReady, s as registerWebComponent, t as usePlacement } from "./usePlacement-Bpo25qjA.js";
import { t as useGlobalComponents } from "./useGlobalComponents-CeTO2xYs.js";
import { t as useLightDomStyles } from "./useLightDomStyles-B2U96tzU.js";
import { i as x, r as b, t as U } from "./rewards-CDS1CYlm.js";
import { componentDefaults } from "./i18n/defaults.js";
import { i as LogoFrakWithName, n as cssSource$2, t as GiftIcon } from "./GiftIcon-BhL0bhVu.js";
import { sanitizeSharingProducts, trackEvent } from "@frak-labs/core-sdk";
import { getMerchantInformation, getUserReferralStatus, trackPurchaseStatus } from "@frak-labs/core-sdk/actions";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { jsx, jsxs } from "preact/jsx-runtime";
import { FrakRpcError, RpcErrorCodes } from "@frak-labs/frame-connector";
//#region \0ve-inline:src/components/PostPurchase/PostPurchase.css.ts.vanilla.js
const cssSource$1 = `.PostPurchase_card__5fv5lh0 {
padding: 16px;
border-radius: 12px;
background-color: var(--surface-background__pbq4akc);
border: 1px solid var(--border-default__pbq4akv);
}
.PostPurchase_cardLayout__5fv5lh1 {
display: flex;
flex-direction: row;
align-items: center;
gap: 32px;
}
.PostPurchase_cardLeft__5fv5lh2 {
flex-grow: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.PostPurchase_cardRight__5fv5lh3 {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8px;
text-align: right;
}
.PostPurchase_badge__5fv5lh4 {
align-self: flex-start;
background-color: #FFF534;
border-radius: 4px;
padding: 4px 8px;
font-size: 12px;
font-weight: 600;
line-height: 12px;
color: var(--text-primary__pbq4ak0);
}
.PostPurchase_message__5fv5lh5 {
margin: 0;
font-size: 16px;
line-height: 22px;
color: var(--text-primary__pbq4ak0);
font-weight: 600;
}
.PostPurchase_cta__5fv5lh6 {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
padding: 12px 16px;
border-radius: 9999px;
background-color: var(--text-primary__pbq4ak0);
color: var(--text-onAction__pbq4ak6);
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.PostPurchase_cta__5fv5lh6:disabled {
opacity: 0.7;
cursor: default;
}
.PostPurchase_icon__5fv5lh7 {
margin: -2px 0;
}
.PostPurchase_giftIcon__5fv5lh8 {
display: block;
flex-shrink: 0;
}
.PostPurchase_imageWrapper__5fv5lh9 {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
overflow: hidden;
}
.PostPurchase_customImage__5fv5lha {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
display: block;
}
.PostPurchase_frakLogo__5fv5lhb {
display: block;
margin-left: auto;
}`;
var card = "PostPurchase_card__5fv5lh0";
var cardLayout = "PostPurchase_cardLayout__5fv5lh1";
var cardLeft = "PostPurchase_cardLeft__5fv5lh2";
var cardRight = "PostPurchase_cardRight__5fv5lh3";
var cta = "PostPurchase_cta__5fv5lh6 sharedBaseCss_buttonReset__7cswil0";
var customImage = "PostPurchase_customImage__5fv5lha";
var frakLogo = "PostPurchase_frakLogo__5fv5lhb";
var giftIcon = "PostPurchase_giftIcon__5fv5lh8";
var icon = "PostPurchase_icon__5fv5lh7";
var imageWrapper = "PostPurchase_imageWrapper__5fv5lh9";
var message = "PostPurchase_message__5fv5lh5";
const cssSource = cssSource$1;
//#endregion
//#region src/components/PostPurchase/PostPurchase.tsx
/**
* Given referral status and merchant info, compute the display variant
* and pick the appropriate purchase reward.
*/
function resolvePostPurchaseContext(referralStatus, merchantInfo, currency, products) {
const audience = referralStatus?.isReferred ? "referee" : "referrer";
const selected = U(merchantInfo.rewards, {
targetInteraction: "purchase",
currency,
audience,
products
});
if (!selected) return null;
const { campaign } = selected;
const variant = referralStatus?.isReferred && campaign.referee ? "referee" : "referrer";
return {
variant,
reward: variant === "referee" ? campaign.referee : campaign.referrer,
merchantDomain: merchantInfo.onChainMetadata.domain
};
}
/**
* Post-purchase card component.
*
* Renders an inline card on the merchant's thank-you / order-status page
* that either congratulates a referee or invites a referrer to share.
*
* Fetches referral status and merchant information via two independent
* RPC calls, then computes the display variant locally.
*
* @group components
*
* @example
* Minimal — just show the card:
* ```html
* <frak-post-purchase></frak-post-purchase>
* ```
*
* @example
* With purchase tracking fallback and custom sharing URL:
* ```html
* <frak-post-purchase
* customer-id="cust_123"
* order-id="ord_456"
* token="checkout_abc"
* sharing-url="https://merchant.com/product/shoes"
* ></frak-post-purchase>
* ```
*/
function PostPurchase({ customerId, orderId, token, sharingUrl, merchantId, placement: placementId, classname = "", variant: forcedVariant, badgeText: propBadgeText, referrerText: propReferrerText, refereeText: propRefereeText, ctaText: propCtaText, preview, previewVariant, products, imageUrl: propImageUrl }) {
const isPreview = !!preview;
const { shouldRender, isHidden, isClientReady } = useClientReady();
const placement = usePlacement(placementId);
const lang = useLang();
const parsedProducts = useMemo(() => sanitizeSharingProducts(products), [products]);
useLightDomStyles("frak-post-purchase", placementId, placement?.components?.postPurchase?.css, cssSource, cssSource$2);
const [fetched, setFetched] = useState(null);
const [hasFetched, setHasFetched] = useState(false);
useEffect(() => {
if (isPreview) return;
if (!isClientReady || !customerId || !orderId || !token) return;
trackPurchaseStatus({
customerId,
orderId,
token,
merchantId
}).catch(() => {});
}, [
isPreview,
isClientReady,
customerId,
orderId,
token,
merchantId
]);
useEffect(() => {
if (isPreview) return;
if (!isClientReady || hasFetched) return;
const client = window.FrakSetup?.client;
if (!client) return;
Promise.all([getUserReferralStatus(client), getMerchantInformation(client)]).then(([referralStatus, merchantInfo]) => {
setHasFetched(true);
setFetched({
referralStatus,
merchantInfo
});
}).catch((e) => {
if (e instanceof FrakRpcError && e.code === RpcErrorCodes.configError) {
setHasFetched(true);
return;
}
console.warn("[Frak] Post-purchase context error", e);
});
}, [
isPreview,
isClientReady,
hasFetched
]);
const context = useMemo(() => {
if (!fetched) return null;
return resolvePostPurchaseContext(fetched.referralStatus, fetched.merchantInfo, window.FrakSetup?.client?.config?.metadata?.currency, parsedProducts);
}, [fetched, parsedProducts]);
const trackedImpressionVariantRef = useRef(null);
const resolvedVariant = forcedVariant ?? context?.variant ?? (isPreview ? previewVariant ?? "referrer" : void 0);
const resolvedSharingUrl = sharingUrl ?? context?.merchantDomain;
const rewardText = useMemo(() => {
if (!context?.reward) return void 0;
const currency = window.FrakSetup?.client?.config?.metadata?.currency;
return b(context.reward, currency);
}, [context?.reward]);
const globalComponents = useGlobalComponents();
const postPurchaseConfig = placement?.components?.postPurchase ?? globalComponents?.postPurchase;
const resolvedBadgeText = propBadgeText ?? postPurchaseConfig?.badgeText;
const imageUrl = propImageUrl ?? postPurchaseConfig?.imageUrl;
const texts = useMemo(() => {
const defaults = componentDefaults[lang].postPurchase;
return {
message: resolvedVariant === "referee" ? rewardText ? x(propRefereeText ?? postPurchaseConfig?.refereeText ?? defaults.refereeText, rewardText) : propRefereeText ?? postPurchaseConfig?.refereeNoRewardText ?? defaults.refereeNoRewardText : rewardText ? x(propReferrerText ?? postPurchaseConfig?.referrerText ?? defaults.referrerText, rewardText) : propReferrerText ?? postPurchaseConfig?.referrerNoRewardText ?? defaults.referrerNoRewardText,
cta: rewardText ? x(propCtaText ?? postPurchaseConfig?.ctaText ?? defaults.ctaText, rewardText) : propCtaText ?? postPurchaseConfig?.ctaNoRewardText ?? defaults.ctaNoRewardText
};
}, [
lang,
resolvedVariant,
rewardText,
postPurchaseConfig,
propReferrerText,
propRefereeText,
propCtaText
]);
useEffect(() => {
if (!resolvedVariant) return;
if (trackedImpressionVariantRef.current === resolvedVariant) return;
if (!isClientReady) return;
if (!isPreview && (!shouldRender || isHidden)) return;
trackEvent(window.FrakSetup?.client, "post_purchase_impression", {
placement: placementId,
variant: resolvedVariant,
has_reward: Boolean(context?.reward)
});
trackedImpressionVariantRef.current = resolvedVariant;
}, [
resolvedVariant,
shouldRender,
isHidden,
isClientReady,
isPreview,
placementId,
context?.reward
]);
const handleClick = useCallback(() => {
if (!resolvedVariant) return;
trackEvent(window.FrakSetup?.client, "post_purchase_clicked", {
placement: placementId,
variant: resolvedVariant
});
openSharingPage(void 0, placementId, {
link: resolvedSharingUrl,
products: parsedProducts
});
}, [
resolvedVariant,
placementId,
resolvedSharingUrl,
parsedProducts
]);
if (!resolvedVariant) return null;
if (!isPreview && (!shouldRender || isHidden || !context)) return null;
return /* @__PURE__ */ jsx("div", {
className: [card, classname].filter(Boolean).join(" "),
children: /* @__PURE__ */ jsxs("div", {
class: cardLayout,
children: [/* @__PURE__ */ jsxs("div", {
class: cardLeft,
children: [
resolvedBadgeText && /* @__PURE__ */ jsx("span", {
class: "PostPurchase_badge__5fv5lh4",
children: resolvedBadgeText
}),
/* @__PURE__ */ jsx("p", {
class: message,
children: texts.message
}),
/* @__PURE__ */ jsxs("button", {
type: "button",
className: `${cta} button`,
disabled: !isPreview && !isClientReady,
onClick: isPreview ? void 0 : handleClick,
children: [texts.cta, /* @__PURE__ */ jsx("svg", {
width: "16",
height: "16",
viewBox: "0 0 16 16",
fill: "none",
"aria-hidden": "true",
className: `${icon} button`,
children: /* @__PURE__ */ jsx("path", {
d: "M13.9 11.14C13.99 11.05 14.15 11.11 14.15 11.24V11.64C14.15 13.05 12.63 14.19 10.75 14.19C8.87 14.19 7.34 13.05 7.34 11.64V11.24C7.34 11.11 7.51 11.05 7.59 11.14C8.35 11.93 9.48 12.43 10.75 12.43C12.01 12.43 13.15 11.93 13.9 11.14ZM1.85 9.65C1.85 9.51 2.01 9.44 2.1 9.54C2.85 10.32 3.99 10.82 5.25 10.82C5.52 10.82 5.79 10.8 6.04 10.76C6.26 10.72 6.47 10.88 6.47 11.1V12.17C6.47 12.32 6.37 12.45 6.22 12.48C5.92 12.55 5.59 12.59 5.25 12.59C3.37 12.59 1.85 11.45 1.85 10.04V9.65ZM10.75 6.21C12.63 6.21 14.15 7.35 14.15 8.75C14.15 10.16 12.63 11.3 10.75 11.3C8.87 11.3 7.34 10.16 7.34 8.75C7.34 7.35 8.87 6.21 10.75 6.21ZM1.85 6.85C1.85 6.71 2.01 6.65 2.1 6.74C2.85 7.53 3.99 8.03 5.25 8.03C5.52 8.03 5.79 8 6.04 7.96C6.26 7.92 6.47 8.09 6.47 8.3V9.37C6.47 9.52 6.37 9.65 6.22 9.69C5.92 9.75 5.59 9.79 5.25 9.79C3.37 9.79 1.85 8.65 1.85 7.24V6.85ZM5.25 1.81C7.13 1.81 8.66 2.95 8.66 4.36C8.66 5.76 7.13 6.9 5.25 6.9C3.37 6.9 1.85 5.76 1.85 4.36C1.85 2.95 3.37 1.81 5.25 1.81Z",
fill: "currentColor"
})
})]
})
]
}), /* @__PURE__ */ jsxs("div", {
class: cardRight,
children: [imageUrl ? /* @__PURE__ */ jsx("span", {
class: imageWrapper,
children: /* @__PURE__ */ jsx("img", {
src: imageUrl,
alt: "",
class: customImage
})
}) : /* @__PURE__ */ jsx(GiftIcon, {
className: giftIcon,
width: 80,
height: 80
}), /* @__PURE__ */ jsx(LogoFrakWithName, {
className: frakLogo,
width: 42,
height: 24
})]
})]
})
});
}
//#endregion
//#region src/components/PostPurchase/index.ts
registerWebComponent(PostPurchase, "frak-post-purchase", [
"customerId",
"orderId",
"token",
"sharingUrl",
"merchantId",
"placement",
"classname",
"variant",
"badgeText",
"referrerText",
"refereeText",
"ctaText",
"products",
"preview",
"previewVariant",
"imageUrl"
], { shadow: false });
//#endregion
export { PostPurchase };