UNPKG

@frak-labs/components

Version:

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

341 lines (340 loc) 12.6 kB
import { a as registerWebComponent, i as useClientReady, o as sanitizeProductList, s as openSharingPage, t as usePlacement } from "./usePlacement-5kbU3BKj.js"; import { t as useGlobalComponents } from "./useGlobalComponents-mSs9unyN.js"; import { t as useLightDomStyles } from "./useLightDomStyles-C8giLInY.js"; import { n as formatEstimatedReward, t as applyRewardPlaceholder } from "./formatReward-Cf2KpA3x.js"; import { i as LogoFrakWithName, n as cssSource$2, t as GiftIcon } from "./GiftIcon-BIp9FTJs.js"; import { 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) { const purchaseReward = merchantInfo.rewards.find((r) => r.interactionTypeKey === "purchase" && (r.referrer || r.referee)); if (!purchaseReward) return null; const variant = referralStatus?.isReferred && purchaseReward.referee ? "referee" : "referrer"; return { variant, reward: variant === "referee" ? purchaseReward.referee : purchaseReward.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 }) { const isPreview = !!preview; const { shouldRender, isHidden, isClientReady } = useClientReady(); const placement = usePlacement(placementId); useLightDomStyles("frak-post-purchase", placementId, placement?.components?.postPurchase?.css, cssSource, cssSource$2); const [context, setContext] = 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); setContext(resolvePostPurchaseContext(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 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 formatEstimatedReward(context.reward, currency); }, [context?.reward]); const globalComponents = useGlobalComponents(); const postPurchaseConfig = placement?.components?.postPurchase ?? globalComponents?.postPurchase; const resolvedBadgeText = propBadgeText ?? postPurchaseConfig?.badgeText; const texts = useMemo(() => { return { message: resolvedVariant === "referee" ? rewardText ? applyRewardPlaceholder(propRefereeText ?? postPurchaseConfig?.refereeText ?? "You just earned {REWARD}! Share with friends to earn even more.", rewardText) : propRefereeText ?? postPurchaseConfig?.refereeNoRewardText ?? "You just earned a reward! Share with friends to earn even more." : rewardText ? applyRewardPlaceholder(propReferrerText ?? postPurchaseConfig?.referrerText ?? "Earn {REWARD} by sharing this with your friends!", rewardText) : propReferrerText ?? postPurchaseConfig?.referrerNoRewardText ?? "Share this with your friends and earn rewards!", cta: rewardText ? applyRewardPlaceholder(propCtaText ?? postPurchaseConfig?.ctaText ?? "Share & earn {REWARD}", rewardText) : propCtaText ?? postPurchaseConfig?.ctaNoRewardText ?? "Share & earn" }; }, [ resolvedVariant, rewardText, postPurchaseConfig, propReferrerText, propRefereeText, propCtaText ]); useEffect(() => { if (!resolvedVariant) return; if (trackedImpressionVariantRef.current === resolvedVariant) return; if (!isPreview && (!shouldRender || isHidden || !isClientReady)) 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 parsedProducts = useMemo(() => sanitizeProductList(products), [products]); 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.8984 11.144C13.9864 11.052 14.1543 11.1114 14.1543 11.2388V11.644C14.1543 13.0509 12.6288 14.1918 10.7471 14.1919C8.86523 14.1919 7.33984 13.051 7.33984 11.644V11.2388C7.33984 11.1114 7.50675 11.052 7.59473 11.144C8.3452 11.9295 9.47906 12.4292 10.7471 12.4292C12.0149 12.4291 13.148 11.9293 13.8984 11.144ZM1.8457 9.64795C1.8457 9.51169 2.01094 9.44452 2.10254 9.54053C2.85304 10.3238 3.98586 10.8247 5.25293 10.8247C5.52246 10.8247 5.78608 10.8026 6.04102 10.7593C6.25744 10.7225 6.46582 10.8816 6.46582 11.1011V12.1704C6.46564 12.319 6.36769 12.4507 6.22266 12.4829C5.91535 12.5512 5.58981 12.5874 5.25293 12.5874C3.3711 12.5874 1.8457 11.4469 1.8457 10.0396V9.64795ZM10.7471 6.20654C12.6288 6.20666 14.1543 7.3475 14.1543 8.75439C14.1541 10.1612 12.6287 11.3012 10.7471 11.3013C8.86535 11.3013 7.34004 10.1612 7.33984 8.75439C7.33984 7.34743 8.86523 6.20654 10.7471 6.20654ZM1.8457 6.8501C1.84597 6.71385 2.01208 6.64848 2.10352 6.74365C2.85393 7.52827 3.98602 8.0278 5.25293 8.02783C5.52282 8.02783 5.78667 8.00448 6.04199 7.96143C6.258 7.92499 6.46582 8.08514 6.46582 8.3042V9.37256C6.46582 9.52127 6.36783 9.65378 6.22266 9.68604C5.91537 9.75429 5.58979 9.79053 5.25293 9.79053C3.3711 9.79048 1.8457 8.64863 1.8457 7.24268V6.8501ZM5.25293 1.80811C7.13481 1.80811 8.66016 2.94856 8.66016 4.35596C8.66008 5.76331 7.13476 6.90381 5.25293 6.90381C3.37115 6.90376 1.84578 5.76328 1.8457 4.35596C1.8457 2.94858 3.3711 1.80815 5.25293 1.80811Z", 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 };