@b3dotfun/sdk 0.1.66-alpha.0 → 0.1.66-alpha.2

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 (127) hide show
  1. package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.d.ts +50 -0
  2. package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.js +30 -0
  3. package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +47 -0
  4. package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckoutTrigger.js +45 -0
  5. package/dist/cjs/anyspend/react/components/checkout/CartItemRow.d.ts +8 -0
  6. package/dist/cjs/anyspend/react/components/checkout/CartItemRow.js +9 -0
  7. package/dist/cjs/anyspend/react/components/checkout/CartSummary.d.ts +8 -0
  8. package/dist/cjs/anyspend/react/components/checkout/CartSummary.js +9 -0
  9. package/dist/cjs/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +12 -0
  10. package/dist/cjs/anyspend/react/components/checkout/CheckoutCartPanel.js +19 -0
  11. package/dist/cjs/anyspend/react/components/checkout/CheckoutLayout.d.ts +10 -0
  12. package/dist/cjs/anyspend/react/components/checkout/CheckoutLayout.js +25 -0
  13. package/dist/cjs/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +20 -0
  14. package/dist/cjs/anyspend/react/components/checkout/CheckoutPaymentPanel.js +45 -0
  15. package/dist/cjs/anyspend/react/components/checkout/CheckoutSuccess.d.ts +10 -0
  16. package/dist/cjs/anyspend/react/components/checkout/CheckoutSuccess.js +11 -0
  17. package/dist/cjs/anyspend/react/components/checkout/CoinbaseCheckoutPanel.d.ts +16 -0
  18. package/dist/cjs/anyspend/react/components/checkout/CoinbaseCheckoutPanel.js +27 -0
  19. package/dist/cjs/anyspend/react/components/checkout/CryptoCheckoutPanel.d.ts +33 -0
  20. package/dist/cjs/anyspend/react/components/checkout/CryptoCheckoutPanel.js +317 -0
  21. package/dist/cjs/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +16 -0
  22. package/dist/cjs/anyspend/react/components/checkout/FiatCheckoutPanel.js +233 -0
  23. package/dist/cjs/anyspend/react/components/checkout/PoweredByBranding.d.ts +8 -0
  24. package/dist/cjs/anyspend/react/components/checkout/PoweredByBranding.js +9 -0
  25. package/dist/cjs/anyspend/react/components/checkout/QRCheckoutPanel.d.ts +17 -0
  26. package/dist/cjs/anyspend/react/components/checkout/QRCheckoutPanel.js +148 -0
  27. package/dist/cjs/anyspend/react/components/index.d.ts +5 -1
  28. package/dist/cjs/anyspend/react/components/index.js +6 -1
  29. package/dist/cjs/anyspend/react/components/types/classes.d.ts +32 -0
  30. package/dist/cjs/app.shared.js +8 -0
  31. package/dist/cjs/global-account/react/components/B3DynamicModal.js +5 -1
  32. package/dist/cjs/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
  33. package/dist/cjs/global-account/react/components/ui/command.d.ts +7 -7
  34. package/dist/cjs/global-account/react/hooks/useAuth.d.ts +1 -1
  35. package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +1 -1
  36. package/dist/cjs/global-account/react/hooks/useFirstEOA.d.ts +4 -4
  37. package/dist/cjs/global-account/react/hooks/useUser.d.ts +1 -1
  38. package/dist/cjs/global-account/react/hooks/useUserQuery.d.ts +1 -1
  39. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +53 -1
  40. package/dist/cjs/shared/constants/chains/b3Chain.d.ts +2 -2
  41. package/dist/cjs/shared/constants/chains/supported.d.ts +3 -3
  42. package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.d.ts +50 -0
  43. package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.js +27 -0
  44. package/dist/esm/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +47 -0
  45. package/dist/esm/anyspend/react/components/checkout/AnySpendCheckoutTrigger.js +42 -0
  46. package/dist/esm/anyspend/react/components/checkout/CartItemRow.d.ts +8 -0
  47. package/dist/esm/anyspend/react/components/checkout/CartItemRow.js +6 -0
  48. package/dist/esm/anyspend/react/components/checkout/CartSummary.d.ts +8 -0
  49. package/dist/esm/anyspend/react/components/checkout/CartSummary.js +6 -0
  50. package/dist/esm/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +12 -0
  51. package/dist/esm/anyspend/react/components/checkout/CheckoutCartPanel.js +16 -0
  52. package/dist/esm/anyspend/react/components/checkout/CheckoutLayout.d.ts +10 -0
  53. package/dist/esm/anyspend/react/components/checkout/CheckoutLayout.js +22 -0
  54. package/dist/esm/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +20 -0
  55. package/dist/esm/anyspend/react/components/checkout/CheckoutPaymentPanel.js +42 -0
  56. package/dist/esm/anyspend/react/components/checkout/CheckoutSuccess.d.ts +10 -0
  57. package/dist/esm/anyspend/react/components/checkout/CheckoutSuccess.js +8 -0
  58. package/dist/esm/anyspend/react/components/checkout/CoinbaseCheckoutPanel.d.ts +16 -0
  59. package/dist/esm/anyspend/react/components/checkout/CoinbaseCheckoutPanel.js +24 -0
  60. package/dist/esm/anyspend/react/components/checkout/CryptoCheckoutPanel.d.ts +33 -0
  61. package/dist/esm/anyspend/react/components/checkout/CryptoCheckoutPanel.js +313 -0
  62. package/dist/esm/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +16 -0
  63. package/dist/esm/anyspend/react/components/checkout/FiatCheckoutPanel.js +230 -0
  64. package/dist/esm/anyspend/react/components/checkout/PoweredByBranding.d.ts +8 -0
  65. package/dist/esm/anyspend/react/components/checkout/PoweredByBranding.js +6 -0
  66. package/dist/esm/anyspend/react/components/checkout/QRCheckoutPanel.d.ts +17 -0
  67. package/dist/esm/anyspend/react/components/checkout/QRCheckoutPanel.js +145 -0
  68. package/dist/esm/anyspend/react/components/index.d.ts +5 -1
  69. package/dist/esm/anyspend/react/components/index.js +3 -0
  70. package/dist/esm/anyspend/react/components/types/classes.d.ts +32 -0
  71. package/dist/esm/app.shared.js +8 -0
  72. package/dist/esm/global-account/react/components/B3DynamicModal.js +5 -1
  73. package/dist/esm/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
  74. package/dist/esm/global-account/react/components/ui/command.d.ts +7 -7
  75. package/dist/esm/global-account/react/hooks/useAuth.d.ts +1 -1
  76. package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +1 -1
  77. package/dist/esm/global-account/react/hooks/useFirstEOA.d.ts +4 -4
  78. package/dist/esm/global-account/react/hooks/useUser.d.ts +1 -1
  79. package/dist/esm/global-account/react/hooks/useUserQuery.d.ts +1 -1
  80. package/dist/esm/global-account/react/stores/useModalStore.d.ts +53 -1
  81. package/dist/esm/shared/constants/chains/b3Chain.d.ts +2 -2
  82. package/dist/esm/shared/constants/chains/supported.d.ts +3 -3
  83. package/dist/styles/index.css +1 -1
  84. package/dist/types/anyspend/react/components/checkout/AnySpendCheckout.d.ts +50 -0
  85. package/dist/types/anyspend/react/components/checkout/AnySpendCheckoutTrigger.d.ts +47 -0
  86. package/dist/types/anyspend/react/components/checkout/CartItemRow.d.ts +8 -0
  87. package/dist/types/anyspend/react/components/checkout/CartSummary.d.ts +8 -0
  88. package/dist/types/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +12 -0
  89. package/dist/types/anyspend/react/components/checkout/CheckoutLayout.d.ts +10 -0
  90. package/dist/types/anyspend/react/components/checkout/CheckoutPaymentPanel.d.ts +20 -0
  91. package/dist/types/anyspend/react/components/checkout/CheckoutSuccess.d.ts +10 -0
  92. package/dist/types/anyspend/react/components/checkout/CoinbaseCheckoutPanel.d.ts +16 -0
  93. package/dist/types/anyspend/react/components/checkout/CryptoCheckoutPanel.d.ts +33 -0
  94. package/dist/types/anyspend/react/components/checkout/FiatCheckoutPanel.d.ts +16 -0
  95. package/dist/types/anyspend/react/components/checkout/PoweredByBranding.d.ts +8 -0
  96. package/dist/types/anyspend/react/components/checkout/QRCheckoutPanel.d.ts +17 -0
  97. package/dist/types/anyspend/react/components/index.d.ts +5 -1
  98. package/dist/types/anyspend/react/components/types/classes.d.ts +32 -0
  99. package/dist/types/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
  100. package/dist/types/global-account/react/components/ui/command.d.ts +7 -7
  101. package/dist/types/global-account/react/hooks/useAuth.d.ts +1 -1
  102. package/dist/types/global-account/react/hooks/useAuthentication.d.ts +1 -1
  103. package/dist/types/global-account/react/hooks/useFirstEOA.d.ts +4 -4
  104. package/dist/types/global-account/react/hooks/useUser.d.ts +1 -1
  105. package/dist/types/global-account/react/hooks/useUserQuery.d.ts +1 -1
  106. package/dist/types/global-account/react/stores/useModalStore.d.ts +53 -1
  107. package/dist/types/shared/constants/chains/b3Chain.d.ts +2 -2
  108. package/dist/types/shared/constants/chains/supported.d.ts +3 -3
  109. package/package.json +1 -1
  110. package/src/anyspend/react/components/checkout/AnySpendCheckout.tsx +127 -0
  111. package/src/anyspend/react/components/checkout/AnySpendCheckoutTrigger.tsx +166 -0
  112. package/src/anyspend/react/components/checkout/CartItemRow.tsx +43 -0
  113. package/src/anyspend/react/components/checkout/CartSummary.tsx +23 -0
  114. package/src/anyspend/react/components/checkout/CheckoutCartPanel.tsx +60 -0
  115. package/src/anyspend/react/components/checkout/CheckoutLayout.tsx +72 -0
  116. package/src/anyspend/react/components/checkout/CheckoutPaymentPanel.tsx +320 -0
  117. package/src/anyspend/react/components/checkout/CheckoutSuccess.tsx +91 -0
  118. package/src/anyspend/react/components/checkout/CoinbaseCheckoutPanel.tsx +90 -0
  119. package/src/anyspend/react/components/checkout/CryptoCheckoutPanel.tsx +643 -0
  120. package/src/anyspend/react/components/checkout/FiatCheckoutPanel.tsx +568 -0
  121. package/src/anyspend/react/components/checkout/PoweredByBranding.tsx +32 -0
  122. package/src/anyspend/react/components/checkout/QRCheckoutPanel.tsx +320 -0
  123. package/src/anyspend/react/components/index.ts +7 -0
  124. package/src/anyspend/react/components/types/classes.ts +48 -0
  125. package/src/app.shared.ts +11 -0
  126. package/src/global-account/react/components/B3DynamicModal.tsx +5 -0
  127. package/src/global-account/react/stores/useModalStore.ts +52 -1
@@ -0,0 +1,166 @@
1
+ "use client";
2
+
3
+ import { useTokenData } from "@b3dotfun/sdk/global-account/react";
4
+ import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
5
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
6
+ import { useMemo } from "react";
7
+ import { AnySpendFingerprintWrapper, getFingerprintConfig } from "../AnySpendFingerprintWrapper";
8
+ import type { AnySpendCheckoutClasses } from "../types/classes";
9
+ import type { CheckoutItem } from "./AnySpendCheckout";
10
+ import { CheckoutCartPanel } from "./CheckoutCartPanel";
11
+ import { CheckoutPaymentPanel } from "./CheckoutPaymentPanel";
12
+ import { PoweredByBranding } from "./PoweredByBranding";
13
+
14
+ export interface AnySpendCheckoutTriggerProps {
15
+ /** Payment recipient address (hex) */
16
+ recipientAddress: string;
17
+ /** Destination token address */
18
+ destinationTokenAddress: string;
19
+ /** Destination chain ID */
20
+ destinationTokenChainId: number;
21
+ /** Line items to display in the cart (optional — if omitted, only the payment panel is shown) */
22
+ items?: CheckoutItem[];
23
+ /** Total amount in wei — required when items are not provided */
24
+ totalAmount?: string;
25
+ /** Organization name */
26
+ organizationName?: string;
27
+ /** Organization logo URL */
28
+ organizationLogo?: string;
29
+ /** Theme color (hex) */
30
+ themeColor?: string;
31
+ /** Custom button text */
32
+ buttonText?: string;
33
+ /** Workflow ID to trigger on payment */
34
+ workflowId?: string;
35
+ /** Organization ID that owns the workflow */
36
+ orgId?: string;
37
+ /** Optional callback metadata merged into the order */
38
+ callbackMetadata?: {
39
+ /** Passed as trigger result inputs — accessible via {{root.result.inputs.*}} */
40
+ inputs?: Record<string, unknown>;
41
+ } & Record<string, unknown>;
42
+ /** Called on successful payment */
43
+ onSuccess?: (result: { txHash?: string; orderId?: string }) => void;
44
+ /** Called on payment error */
45
+ onError?: (error: Error) => void;
46
+ /** URL to redirect to after payment */
47
+ returnUrl?: string;
48
+ /** Label for the return button */
49
+ returnLabel?: string;
50
+ /** Custom class names */
51
+ classes?: AnySpendCheckoutClasses;
52
+ /** Display mode — set automatically when used inside B3DynamicModal */
53
+ mode?: "modal" | "page";
54
+ }
55
+
56
+ export function AnySpendCheckoutTrigger({
57
+ recipientAddress,
58
+ destinationTokenAddress,
59
+ destinationTokenChainId,
60
+ items,
61
+ totalAmount: totalAmountOverride,
62
+ organizationName,
63
+ organizationLogo,
64
+ themeColor,
65
+ buttonText = "Pay",
66
+ workflowId,
67
+ orgId,
68
+ callbackMetadata,
69
+ onSuccess,
70
+ onError,
71
+ returnUrl,
72
+ returnLabel,
73
+ classes,
74
+ }: AnySpendCheckoutTriggerProps) {
75
+ // Merge workflowId + orgId into callbackMetadata
76
+ const mergedMetadata = useMemo(() => {
77
+ if (!workflowId && !orgId && !callbackMetadata) return undefined;
78
+ return {
79
+ ...(workflowId ? { workflowId } : {}),
80
+ ...(orgId ? { orgId } : {}),
81
+ ...callbackMetadata,
82
+ };
83
+ }, [workflowId, orgId, callbackMetadata]);
84
+
85
+ // Compute total from items or use override
86
+ const computedTotal = useMemo(() => {
87
+ if (totalAmountOverride) return totalAmountOverride;
88
+ if (!items || items.length === 0) return "0";
89
+ let total = BigInt(0);
90
+ for (const item of items) {
91
+ total += BigInt(item.amount) * BigInt(item.quantity);
92
+ }
93
+ return total.toString();
94
+ }, [items, totalAmountOverride]);
95
+
96
+ // Get destination token metadata
97
+ const { data: tokenData } = useTokenData(destinationTokenChainId, destinationTokenAddress);
98
+ const tokenSymbol = tokenData?.symbol || "";
99
+ const tokenDecimals = tokenData?.decimals || 18;
100
+
101
+ const formattedTotal = useMemo(
102
+ () => formatTokenAmount(BigInt(computedTotal || "0"), tokenDecimals),
103
+ [computedTotal, tokenDecimals],
104
+ );
105
+
106
+ const hasItems = items && items.length > 0;
107
+ const fingerprint = getFingerprintConfig();
108
+
109
+ return (
110
+ <AnySpendFingerprintWrapper fingerprint={fingerprint}>
111
+ <div className="anyspend-checkout-trigger flex flex-col">
112
+ {/* Cart summary with items */}
113
+ {hasItems && (
114
+ <div className="border-b border-gray-200 p-5 dark:border-gray-700">
115
+ <CheckoutCartPanel
116
+ items={items}
117
+ totalAmount={computedTotal}
118
+ tokenSymbol={tokenSymbol}
119
+ tokenDecimals={tokenDecimals}
120
+ organizationName={organizationName}
121
+ organizationLogo={organizationLogo}
122
+ classes={classes}
123
+ />
124
+ </div>
125
+ )}
126
+
127
+ {/* Total-only header when no items */}
128
+ {!hasItems && (
129
+ <div className="border-b border-gray-200 p-5 dark:border-gray-700">
130
+ <div className="flex flex-col gap-3">
131
+ <div className={cn("flex items-center justify-between", classes?.cartSummary)}>
132
+ <span className="text-base font-semibold text-gray-900 dark:text-gray-100">Total</span>
133
+ <span className={cn("text-base font-semibold text-gray-900 dark:text-gray-100", classes?.cartTotal)}>
134
+ {formattedTotal} {tokenSymbol}
135
+ </span>
136
+ </div>
137
+ <PoweredByBranding
138
+ organizationName={organizationName}
139
+ organizationLogo={organizationLogo}
140
+ classes={classes}
141
+ />
142
+ </div>
143
+ </div>
144
+ )}
145
+
146
+ {/* Payment methods */}
147
+ <div className="p-5">
148
+ <CheckoutPaymentPanel
149
+ recipientAddress={recipientAddress}
150
+ destinationTokenAddress={destinationTokenAddress}
151
+ destinationTokenChainId={destinationTokenChainId}
152
+ totalAmount={computedTotal}
153
+ buttonText={buttonText}
154
+ themeColor={themeColor}
155
+ returnUrl={returnUrl}
156
+ returnLabel={returnLabel}
157
+ onSuccess={onSuccess}
158
+ onError={onError}
159
+ callbackMetadata={mergedMetadata}
160
+ classes={classes}
161
+ />
162
+ </div>
163
+ </div>
164
+ </AnySpendFingerprintWrapper>
165
+ );
166
+ }
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
4
+ import type { CheckoutItem, AnySpendCheckoutClasses } from "./AnySpendCheckout";
5
+
6
+ interface CartItemRowProps {
7
+ item: CheckoutItem;
8
+ formattedPrice: string;
9
+ classes?: AnySpendCheckoutClasses;
10
+ }
11
+
12
+ export function CartItemRow({ item, formattedPrice, classes }: CartItemRowProps) {
13
+ return (
14
+ <div className={cn("flex items-start gap-3 py-3", classes?.cartItemRow)}>
15
+ {item.imageUrl && (
16
+ <div className={cn("h-14 w-14 shrink-0 overflow-hidden rounded-lg bg-gray-100", classes?.cartItemImage)}>
17
+ <img src={item.imageUrl} alt={item.name} className="h-full w-full object-cover" />
18
+ </div>
19
+ )}
20
+ <div className="flex min-w-0 flex-1 items-start justify-between">
21
+ <div className="min-w-0 flex-1">
22
+ <p className={cn("text-sm font-medium text-gray-900 dark:text-gray-100", classes?.cartItemName)}>
23
+ {item.name}
24
+ </p>
25
+ {item.description && (
26
+ <p
27
+ className={cn(
28
+ "mt-0.5 line-clamp-2 text-xs text-gray-500 dark:text-gray-400",
29
+ classes?.cartItemDescription,
30
+ )}
31
+ >
32
+ {item.description}
33
+ </p>
34
+ )}
35
+ {item.quantity > 1 && <p className="mt-0.5 text-xs text-gray-400 dark:text-gray-500">Qty: {item.quantity}</p>}
36
+ </div>
37
+ <p className={cn("ml-3 text-sm font-medium text-gray-900 dark:text-gray-100", classes?.cartItemPrice)}>
38
+ {formattedPrice}
39
+ </p>
40
+ </div>
41
+ </div>
42
+ );
43
+ }
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
4
+ import type { AnySpendCheckoutClasses } from "./AnySpendCheckout";
5
+
6
+ interface CartSummaryProps {
7
+ total: string;
8
+ tokenSymbol?: string;
9
+ classes?: AnySpendCheckoutClasses;
10
+ }
11
+
12
+ export function CartSummary({ total, tokenSymbol, classes }: CartSummaryProps) {
13
+ return (
14
+ <div className={cn("border-t border-gray-200 pt-3 dark:border-gray-700", classes?.cartSummary)}>
15
+ <div className="flex items-center justify-between">
16
+ <span className="text-base font-semibold text-gray-900 dark:text-gray-100">Total</span>
17
+ <span className={cn("text-base font-semibold text-gray-900 dark:text-gray-100", classes?.cartTotal)}>
18
+ {total} {tokenSymbol}
19
+ </span>
20
+ </div>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,60 @@
1
+ "use client";
2
+
3
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
4
+ import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
5
+ import { useMemo } from "react";
6
+ import type { CheckoutItem, AnySpendCheckoutClasses } from "./AnySpendCheckout";
7
+ import { CartItemRow } from "./CartItemRow";
8
+ import { CartSummary } from "./CartSummary";
9
+ import { PoweredByBranding } from "./PoweredByBranding";
10
+
11
+ interface CheckoutCartPanelProps {
12
+ items: CheckoutItem[];
13
+ totalAmount: string;
14
+ tokenSymbol?: string;
15
+ tokenDecimals?: number;
16
+ organizationName?: string;
17
+ organizationLogo?: string;
18
+ classes?: AnySpendCheckoutClasses;
19
+ }
20
+
21
+ export function CheckoutCartPanel({
22
+ items,
23
+ totalAmount,
24
+ tokenSymbol = "",
25
+ tokenDecimals = 18,
26
+ organizationName,
27
+ organizationLogo,
28
+ classes,
29
+ }: CheckoutCartPanelProps) {
30
+ const formattedTotal = useMemo(
31
+ () => formatTokenAmount(BigInt(totalAmount), tokenDecimals),
32
+ [totalAmount, tokenDecimals],
33
+ );
34
+
35
+ return (
36
+ <div className={cn("anyspend-cart-panel flex flex-col", classes?.cartPanel)}>
37
+ <h2
38
+ className={cn(
39
+ "anyspend-cart-title mb-4 text-lg font-semibold text-gray-900 dark:text-gray-100",
40
+ classes?.cartTitle,
41
+ )}
42
+ >
43
+ Order Summary
44
+ </h2>
45
+
46
+ <div className="anyspend-cart-items divide-y divide-gray-100 dark:divide-gray-800">
47
+ {items.map((item, index) => {
48
+ const itemTotal = BigInt(item.amount) * BigInt(item.quantity);
49
+ const formattedPrice = `${formatTokenAmount(itemTotal, tokenDecimals)} ${tokenSymbol}`;
50
+
51
+ return <CartItemRow key={item.id || index} item={item} formattedPrice={formattedPrice} classes={classes} />;
52
+ })}
53
+ </div>
54
+
55
+ <CartSummary total={formattedTotal} tokenSymbol={tokenSymbol} classes={classes} />
56
+
57
+ <PoweredByBranding organizationName={organizationName} organizationLogo={organizationLogo} classes={classes} />
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
4
+ import type { ReactNode } from "react";
5
+ import type { AnySpendCheckoutClasses } from "./AnySpendCheckout";
6
+
7
+ interface CheckoutLayoutProps {
8
+ mode: "page" | "embedded";
9
+ paymentPanel: ReactNode;
10
+ cartPanel: ReactNode;
11
+ classes?: AnySpendCheckoutClasses;
12
+ }
13
+
14
+ export function CheckoutLayout({ mode, paymentPanel, cartPanel, classes }: CheckoutLayoutProps) {
15
+ const rightColumnWidth = mode === "page" ? 380 : 340;
16
+
17
+ return (
18
+ <div
19
+ className={cn("anyspend-checkout mx-auto w-full", classes?.root)}
20
+ style={mode === "page" ? { maxWidth: 1100, padding: "2rem 1rem" } : undefined}
21
+ >
22
+ {/*
23
+ Use CSS Grid with inline styles to ensure 2-column layout works
24
+ regardless of host app's Tailwind configuration.
25
+ On screens < 768px: single column (cart on top, payment below).
26
+ On screens >= 768px: two columns (payment left, cart right).
27
+ */}
28
+ <style
29
+ dangerouslySetInnerHTML={{
30
+ __html: `
31
+ .anyspend-checkout-grid {
32
+ display: grid;
33
+ gap: 2rem;
34
+ grid-template-columns: 1fr;
35
+ }
36
+ @media (min-width: 768px) {
37
+ .anyspend-checkout-grid {
38
+ grid-template-columns: 1fr ${rightColumnWidth}px;
39
+ }
40
+ .anyspend-checkout-grid > .anyspend-payment-col { order: 1; }
41
+ .anyspend-checkout-grid > .anyspend-cart-col { order: 2; }
42
+ }
43
+ `,
44
+ }}
45
+ />
46
+ <div className={cn("anyspend-checkout-grid", classes?.layout)}>
47
+ {/* LEFT: Payment Methods (appears second on mobile, first on desktop) */}
48
+ <div
49
+ className={cn(
50
+ "anyspend-payment-col order-2",
51
+ "rounded-2xl border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-900",
52
+ classes?.paymentColumn,
53
+ )}
54
+ >
55
+ {paymentPanel}
56
+ </div>
57
+
58
+ {/* RIGHT: Cart / Invoice (appears first on mobile, second on desktop) */}
59
+ <div
60
+ className={cn(
61
+ "anyspend-cart-col order-1",
62
+ "rounded-2xl border border-gray-200 bg-gray-50 p-6 dark:border-gray-700 dark:bg-gray-800/50",
63
+ classes?.cartColumn,
64
+ )}
65
+ style={{ position: "sticky", top: 32, alignSelf: "start" }}
66
+ >
67
+ {cartPanel}
68
+ </div>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,320 @@
1
+ "use client";
2
+
3
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
4
+ import { QrCode, Wallet } from "lucide-react";
5
+ import { AnimatePresence, motion } from "motion/react";
6
+ import { useState } from "react";
7
+ import type { AnySpendCheckoutClasses } from "./AnySpendCheckout";
8
+ import { CheckoutSuccess } from "./CheckoutSuccess";
9
+ import { CoinbaseCheckoutPanel } from "./CoinbaseCheckoutPanel";
10
+ import { CryptoCheckoutPanel } from "./CryptoCheckoutPanel";
11
+ import { FiatCheckoutPanel } from "./FiatCheckoutPanel";
12
+ import { QRCheckoutPanel } from "./QRCheckoutPanel";
13
+
14
+ type PaymentMethod = "crypto" | "qr" | "card" | "coinbase";
15
+
16
+ interface CheckoutPaymentPanelProps {
17
+ recipientAddress: string;
18
+ destinationTokenAddress: string;
19
+ destinationTokenChainId: number;
20
+ totalAmount: string;
21
+ buttonText?: string;
22
+ themeColor?: string;
23
+ returnUrl?: string;
24
+ returnLabel?: string;
25
+ onSuccess?: (result: { txHash?: string; orderId?: string }) => void;
26
+ onError?: (error: Error) => void;
27
+ callbackMetadata?: Record<string, unknown>;
28
+ classes?: AnySpendCheckoutClasses;
29
+ }
30
+
31
+ function RadioCircle({ selected, themeColor }: { selected: boolean; themeColor?: string }) {
32
+ return (
33
+ <div
34
+ className={cn(
35
+ "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-full border-2 transition-colors",
36
+ selected ? "border-blue-600" : "border-gray-300 dark:border-gray-600",
37
+ )}
38
+ style={selected && themeColor ? { borderColor: themeColor } : undefined}
39
+ >
40
+ {selected && (
41
+ <div
42
+ className="h-2 w-2 rounded-full bg-blue-600"
43
+ style={themeColor ? { backgroundColor: themeColor } : undefined}
44
+ />
45
+ )}
46
+ </div>
47
+ );
48
+ }
49
+
50
+ /** Card brand logos - all use consistent 32x20 viewBox */
51
+ function VisaLogo() {
52
+ return (
53
+ <svg viewBox="0 0 32 20" style={{ width: 32, height: 20 }} aria-label="Visa">
54
+ <rect width="32" height="20" rx="3" fill="#1A1F71" />
55
+ <text
56
+ x="16"
57
+ y="13.5"
58
+ textAnchor="middle"
59
+ fill="white"
60
+ fontSize="9"
61
+ fontWeight="bold"
62
+ fontFamily="sans-serif"
63
+ fontStyle="italic"
64
+ >
65
+ VISA
66
+ </text>
67
+ </svg>
68
+ );
69
+ }
70
+
71
+ function MastercardLogo() {
72
+ return (
73
+ <svg viewBox="0 0 32 20" style={{ width: 32, height: 20 }} aria-label="Mastercard">
74
+ <rect width="32" height="20" rx="3" fill="#252525" />
75
+ <circle cx="12.5" cy="10" r="6" fill="#EB001B" />
76
+ <circle cx="19.5" cy="10" r="6" fill="#F79E1B" />
77
+ <path d="M16 5.6a6 6 0 0 1 0 8.8 6 6 0 0 1 0-8.8z" fill="#FF5F00" />
78
+ </svg>
79
+ );
80
+ }
81
+
82
+ function AmexLogo() {
83
+ return (
84
+ <svg viewBox="0 0 32 20" style={{ width: 32, height: 20 }} aria-label="Amex">
85
+ <rect width="32" height="20" rx="3" fill="#006FCF" />
86
+ <text x="16" y="13" textAnchor="middle" fill="white" fontSize="7" fontWeight="bold" fontFamily="sans-serif">
87
+ AMEX
88
+ </text>
89
+ </svg>
90
+ );
91
+ }
92
+
93
+ /** Coinbase mark */
94
+ function CoinbaseLogo() {
95
+ return (
96
+ <svg viewBox="0 0 24 24" style={{ width: 20, height: 20 }} aria-label="Coinbase">
97
+ <circle cx="12" cy="12" r="12" fill="#0052FF" />
98
+ <path
99
+ d="M12 4.5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15zm-1.8 4.8h3.6c.33 0 .6.27.6.6v4.2c0 .33-.27.6-.6.6h-3.6a.6.6 0 0 1-.6-.6V9.9c0-.33.27-.6.6-.6z"
100
+ fill="white"
101
+ />
102
+ </svg>
103
+ );
104
+ }
105
+
106
+ export function CheckoutPaymentPanel({
107
+ recipientAddress,
108
+ destinationTokenAddress,
109
+ destinationTokenChainId,
110
+ totalAmount,
111
+ buttonText,
112
+ themeColor,
113
+ returnUrl,
114
+ returnLabel,
115
+ onSuccess,
116
+ onError,
117
+ callbackMetadata,
118
+ classes,
119
+ }: CheckoutPaymentPanelProps) {
120
+ const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>("crypto");
121
+ const [paymentResult, setPaymentResult] = useState<{ txHash?: string; orderId?: string } | null>(null);
122
+
123
+ const handleSuccess = (result: { txHash?: string; orderId?: string }) => {
124
+ setPaymentResult(result);
125
+ onSuccess?.(result);
126
+ };
127
+
128
+ if (paymentResult) {
129
+ return (
130
+ <CheckoutSuccess
131
+ txHash={paymentResult.txHash}
132
+ orderId={paymentResult.orderId}
133
+ returnUrl={returnUrl}
134
+ returnLabel={returnLabel}
135
+ classes={classes}
136
+ />
137
+ );
138
+ }
139
+
140
+ const accordionButtonClass = (active: boolean) =>
141
+ cn(
142
+ "anyspend-payment-method-btn flex w-full items-center gap-3 px-4 py-4 text-left transition-colors",
143
+ active ? "bg-white dark:bg-gray-900" : "bg-white hover:bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800",
144
+ classes?.paymentMethodButton,
145
+ );
146
+
147
+ const expandedPanelClass = cn(
148
+ "anyspend-payment-method-panel border-t border-gray-100 bg-white px-4 py-4 dark:border-gray-800 dark:bg-gray-900",
149
+ );
150
+
151
+ return (
152
+ <div className={cn("anyspend-payment-panel flex flex-col gap-5", classes?.paymentPanel)}>
153
+ <h2
154
+ className={cn(
155
+ "anyspend-payment-title text-lg font-semibold text-gray-900 dark:text-gray-100",
156
+ classes?.paymentTitle,
157
+ )}
158
+ >
159
+ Payment
160
+ </h2>
161
+
162
+ {/* Accordion-style payment methods */}
163
+ <div
164
+ className={cn(
165
+ "anyspend-payment-methods divide-y divide-gray-200 overflow-hidden rounded-xl border border-gray-200 dark:divide-gray-700 dark:border-gray-700",
166
+ classes?.paymentMethodSelector,
167
+ )}
168
+ >
169
+ {/* Crypto Wallet */}
170
+ <div className="anyspend-method-crypto">
171
+ <button
172
+ onClick={() => setPaymentMethod("crypto")}
173
+ className={accordionButtonClass(paymentMethod === "crypto")}
174
+ >
175
+ <RadioCircle selected={paymentMethod === "crypto"} themeColor={themeColor} />
176
+ <Wallet className="h-5 w-5 text-gray-700 dark:text-gray-300" />
177
+ <span className="text-sm font-medium text-gray-900 dark:text-gray-100">Crypto wallet</span>
178
+ </button>
179
+ <AnimatePresence initial={false}>
180
+ {paymentMethod === "crypto" && (
181
+ <motion.div
182
+ key="crypto-panel"
183
+ initial={{ height: 0, opacity: 0 }}
184
+ animate={{ height: "auto", opacity: 1 }}
185
+ exit={{ height: 0, opacity: 0 }}
186
+ transition={{ duration: 0.2, ease: "easeOut" }}
187
+ style={{ overflow: "hidden" }}
188
+ >
189
+ <div className={expandedPanelClass}>
190
+ <CryptoCheckoutPanel
191
+ recipientAddress={recipientAddress}
192
+ destinationTokenAddress={destinationTokenAddress}
193
+ destinationTokenChainId={destinationTokenChainId}
194
+ totalAmount={totalAmount}
195
+ buttonText={buttonText}
196
+ themeColor={themeColor}
197
+ onSuccess={handleSuccess}
198
+ onError={onError}
199
+ callbackMetadata={callbackMetadata}
200
+ classes={classes}
201
+ />
202
+ </div>
203
+ </motion.div>
204
+ )}
205
+ </AnimatePresence>
206
+ </div>
207
+
208
+ {/* QR Code */}
209
+ <div className="anyspend-method-qr">
210
+ <button onClick={() => setPaymentMethod("qr")} className={accordionButtonClass(paymentMethod === "qr")}>
211
+ <RadioCircle selected={paymentMethod === "qr"} themeColor={themeColor} />
212
+ <QrCode className="h-5 w-5 text-gray-700 dark:text-gray-300" />
213
+ <span className="text-sm font-medium text-gray-900 dark:text-gray-100">QR code</span>
214
+ </button>
215
+ <AnimatePresence initial={false}>
216
+ {paymentMethod === "qr" && (
217
+ <motion.div
218
+ key="qr-panel"
219
+ initial={{ height: 0, opacity: 0 }}
220
+ animate={{ height: "auto", opacity: 1 }}
221
+ exit={{ height: 0, opacity: 0 }}
222
+ transition={{ duration: 0.2, ease: "easeOut" }}
223
+ style={{ overflow: "hidden" }}
224
+ >
225
+ <div className={expandedPanelClass}>
226
+ <QRCheckoutPanel
227
+ recipientAddress={recipientAddress}
228
+ destinationTokenAddress={destinationTokenAddress}
229
+ destinationTokenChainId={destinationTokenChainId}
230
+ totalAmount={totalAmount}
231
+ themeColor={themeColor}
232
+ onSuccess={handleSuccess}
233
+ onError={onError}
234
+ callbackMetadata={callbackMetadata}
235
+ classes={classes}
236
+ />
237
+ </div>
238
+ </motion.div>
239
+ )}
240
+ </AnimatePresence>
241
+ </div>
242
+
243
+ {/* Credit or Debit Card */}
244
+ <div className="anyspend-method-card">
245
+ <button onClick={() => setPaymentMethod("card")} className={accordionButtonClass(paymentMethod === "card")}>
246
+ <RadioCircle selected={paymentMethod === "card"} themeColor={themeColor} />
247
+ <span className="text-sm font-medium text-gray-900 dark:text-gray-100">Credit or debit card</span>
248
+ <div className="ml-auto flex items-center gap-1">
249
+ <VisaLogo />
250
+ <MastercardLogo />
251
+ <AmexLogo />
252
+ </div>
253
+ </button>
254
+ <AnimatePresence initial={false}>
255
+ {paymentMethod === "card" && (
256
+ <motion.div
257
+ key="card-panel"
258
+ initial={{ height: 0, opacity: 0 }}
259
+ animate={{ height: "auto", opacity: 1 }}
260
+ exit={{ height: 0, opacity: 0 }}
261
+ transition={{ duration: 0.2, ease: "easeOut" }}
262
+ style={{ overflow: "hidden" }}
263
+ >
264
+ <div className={expandedPanelClass}>
265
+ <FiatCheckoutPanel
266
+ recipientAddress={recipientAddress}
267
+ destinationTokenAddress={destinationTokenAddress}
268
+ destinationTokenChainId={destinationTokenChainId}
269
+ totalAmount={totalAmount}
270
+ themeColor={themeColor}
271
+ onSuccess={handleSuccess}
272
+ onError={onError}
273
+ classes={classes}
274
+ />
275
+ </div>
276
+ </motion.div>
277
+ )}
278
+ </AnimatePresence>
279
+ </div>
280
+
281
+ {/* Coinbase Pay */}
282
+ <div className="anyspend-method-coinbase">
283
+ <button
284
+ onClick={() => setPaymentMethod("coinbase")}
285
+ className={accordionButtonClass(paymentMethod === "coinbase")}
286
+ >
287
+ <RadioCircle selected={paymentMethod === "coinbase"} themeColor={themeColor} />
288
+ <CoinbaseLogo />
289
+ <span className="text-sm font-medium text-gray-900 dark:text-gray-100">Coinbase Pay</span>
290
+ </button>
291
+ <AnimatePresence initial={false}>
292
+ {paymentMethod === "coinbase" && (
293
+ <motion.div
294
+ key="coinbase-panel"
295
+ initial={{ height: 0, opacity: 0 }}
296
+ animate={{ height: "auto", opacity: 1 }}
297
+ exit={{ height: 0, opacity: 0 }}
298
+ transition={{ duration: 0.2, ease: "easeOut" }}
299
+ style={{ overflow: "hidden" }}
300
+ >
301
+ <div className={expandedPanelClass}>
302
+ <CoinbaseCheckoutPanel
303
+ recipientAddress={recipientAddress}
304
+ destinationTokenAddress={destinationTokenAddress}
305
+ destinationTokenChainId={destinationTokenChainId}
306
+ totalAmount={totalAmount}
307
+ themeColor={themeColor}
308
+ onSuccess={handleSuccess}
309
+ onError={onError}
310
+ classes={classes}
311
+ />
312
+ </div>
313
+ </motion.div>
314
+ )}
315
+ </AnimatePresence>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ );
320
+ }