UNPKG

@frak-labs/components

Version:

Frak Wallet components, helping any person to interact with the Frak wallet.

361 lines (360 loc) 12.4 kB
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 };