@b3dotfun/sdk 0.1.69-alpha.20 → 0.1.69-alpha.22
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.
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.js +6 -5
- package/dist/cjs/anyspend/react/components/checkout/CartItemRow.d.ts +2 -1
- package/dist/cjs/anyspend/react/components/checkout/CartSummary.d.ts +6 -4
- package/dist/cjs/anyspend/react/components/checkout/CartSummary.js +13 -11
- package/dist/cjs/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/checkout/CheckoutCartPanel.js +5 -4
- package/dist/cjs/anyspend/react/components/checkout/CheckoutFormPanel.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/checkout/CheckoutFormPanel.js +2 -2
- package/dist/cjs/anyspend/react/components/checkout/DiscountCodeInput.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/checkout/DiscountCodeInput.js +3 -6
- package/dist/cjs/anyspend/react/components/checkout/PriceSkeleton.d.ts +5 -0
- package/dist/cjs/anyspend/react/components/checkout/PriceSkeleton.js +9 -0
- package/dist/cjs/anyspend/react/components/checkout/ShippingSelector.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/checkout/ShippingSelector.js +3 -2
- package/dist/cjs/global-account/react/components/SignInWithB3/BetterAuthResetPassword.js +3 -2
- package/dist/cjs/global-account/react/components/SignInWithB3/BetterAuthSignIn.js +13 -3
- package/dist/cjs/global-account/react/components/SignInWithB3/components/PasswordInput.d.ts +10 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/components/PasswordInput.js +10 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.js +6 -3
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.js +6 -5
- package/dist/esm/anyspend/react/components/checkout/CartItemRow.d.ts +2 -1
- package/dist/esm/anyspend/react/components/checkout/CartSummary.d.ts +6 -4
- package/dist/esm/anyspend/react/components/checkout/CartSummary.js +13 -11
- package/dist/esm/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +3 -1
- package/dist/esm/anyspend/react/components/checkout/CheckoutCartPanel.js +5 -4
- package/dist/esm/anyspend/react/components/checkout/CheckoutFormPanel.d.ts +3 -1
- package/dist/esm/anyspend/react/components/checkout/CheckoutFormPanel.js +2 -2
- package/dist/esm/anyspend/react/components/checkout/DiscountCodeInput.d.ts +3 -1
- package/dist/esm/anyspend/react/components/checkout/DiscountCodeInput.js +3 -6
- package/dist/esm/anyspend/react/components/checkout/PriceSkeleton.d.ts +5 -0
- package/dist/esm/anyspend/react/components/checkout/PriceSkeleton.js +6 -0
- package/dist/esm/anyspend/react/components/checkout/ShippingSelector.d.ts +3 -1
- package/dist/esm/anyspend/react/components/checkout/ShippingSelector.js +3 -2
- package/dist/esm/global-account/react/components/SignInWithB3/BetterAuthResetPassword.js +4 -3
- package/dist/esm/global-account/react/components/SignInWithB3/BetterAuthSignIn.js +14 -4
- package/dist/esm/global-account/react/components/SignInWithB3/components/PasswordInput.d.ts +10 -0
- package/dist/esm/global-account/react/components/SignInWithB3/components/PasswordInput.js +7 -0
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.js +6 -3
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/components/checkout/CartItemRow.d.ts +2 -1
- package/dist/types/anyspend/react/components/checkout/CartSummary.d.ts +6 -4
- package/dist/types/anyspend/react/components/checkout/CheckoutCartPanel.d.ts +3 -1
- package/dist/types/anyspend/react/components/checkout/CheckoutFormPanel.d.ts +3 -1
- package/dist/types/anyspend/react/components/checkout/DiscountCodeInput.d.ts +3 -1
- package/dist/types/anyspend/react/components/checkout/PriceSkeleton.d.ts +5 -0
- package/dist/types/anyspend/react/components/checkout/ShippingSelector.d.ts +3 -1
- package/dist/types/global-account/react/components/SignInWithB3/components/PasswordInput.d.ts +10 -0
- package/package.json +1 -1
- package/src/anyspend/react/components/checkout/AnySpendCheckout.tsx +10 -4
- package/src/anyspend/react/components/checkout/CartItemRow.tsx +2 -1
- package/src/anyspend/react/components/checkout/CartSummary.tsx +24 -20
- package/src/anyspend/react/components/checkout/CheckoutCartPanel.tsx +12 -3
- package/src/anyspend/react/components/checkout/CheckoutFormPanel.tsx +5 -0
- package/src/anyspend/react/components/checkout/DiscountCodeInput.tsx +15 -5
- package/src/anyspend/react/components/checkout/PriceSkeleton.tsx +19 -0
- package/src/anyspend/react/components/checkout/ShippingSelector.tsx +5 -1
- package/src/global-account/react/components/SignInWithB3/BetterAuthResetPassword.tsx +6 -7
- package/src/global-account/react/components/SignInWithB3/BetterAuthSignIn.tsx +20 -6
- package/src/global-account/react/components/SignInWithB3/components/PasswordInput.tsx +62 -0
- package/src/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.tsx +9 -4
|
@@ -5,7 +5,9 @@ interface ShippingSelectorProps {
|
|
|
5
5
|
onSelect: (option: ShippingOption) => void;
|
|
6
6
|
tokenSymbol?: string;
|
|
7
7
|
tokenDecimals?: number;
|
|
8
|
+
/** True while token decimals/symbol are still loading — renders price as skeleton. */
|
|
9
|
+
pricesLoading?: boolean;
|
|
8
10
|
className?: string;
|
|
9
11
|
}
|
|
10
|
-
export declare function ShippingSelector({ options, selectedId, onSelect, tokenSymbol, tokenDecimals, className, }: ShippingSelectorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
+
export declare function ShippingSelector({ options, selectedId, onSelect, tokenSymbol, tokenDecimals, pricesLoading, className, }: ShippingSelectorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
11
13
|
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface PasswordInputProps {
|
|
2
|
+
value: string;
|
|
3
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function PasswordInput({ value, onChange, disabled, placeholder, onKeyDown, className, }: PasswordInputProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
package/package.json
CHANGED
|
@@ -295,10 +295,14 @@ export function AnySpendCheckout({
|
|
|
295
295
|
variablePricingAmount,
|
|
296
296
|
]);
|
|
297
297
|
|
|
298
|
-
// Get destination token metadata
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
298
|
+
// Get destination token metadata. While loading, skip price-dependent UI
|
|
299
|
+
// since the 18-decimal fallback is wrong for non-18 tokens (e.g. USDC).
|
|
300
|
+
const { data: tokenData, isLoading: isTokenDataLoading } = useTokenData(
|
|
301
|
+
destinationTokenChainId,
|
|
302
|
+
destinationTokenAddress,
|
|
303
|
+
);
|
|
304
|
+
const tokenSymbol = tokenData?.symbol ?? "";
|
|
305
|
+
const tokenDecimals = tokenData?.decimals ?? 18;
|
|
302
306
|
|
|
303
307
|
// Resolve USD equivalent for non-stablecoin tokens (shown in cart summary)
|
|
304
308
|
const isStablecoin = useMemo(() => {
|
|
@@ -412,6 +416,7 @@ export function AnySpendCheckout({
|
|
|
412
416
|
validateDiscount={validateDiscount}
|
|
413
417
|
tokenSymbol={tokenSymbol}
|
|
414
418
|
tokenDecimals={tokenDecimals}
|
|
419
|
+
pricesLoading={isTokenDataLoading}
|
|
415
420
|
classes={classes}
|
|
416
421
|
formData={formData}
|
|
417
422
|
onFormDataChange={handleFormDataChange}
|
|
@@ -456,6 +461,7 @@ export function AnySpendCheckout({
|
|
|
456
461
|
totalAmount={computedTotal}
|
|
457
462
|
tokenSymbol={tokenSymbol}
|
|
458
463
|
tokenDecimals={tokenDecimals}
|
|
464
|
+
pricesLoading={isTokenDataLoading}
|
|
459
465
|
organizationName={organizationName}
|
|
460
466
|
organizationLogo={organizationLogo}
|
|
461
467
|
classes={classes}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
4
5
|
import type { CheckoutItem, AnySpendCheckoutClasses } from "./AnySpendCheckout";
|
|
5
6
|
|
|
6
7
|
interface CartItemRowProps {
|
|
7
8
|
item: CheckoutItem;
|
|
8
|
-
formattedPrice:
|
|
9
|
+
formattedPrice: ReactNode;
|
|
9
10
|
classes?: AnySpendCheckoutClasses;
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
4
4
|
import { formatTokenAmount, safeBigInt } from "@b3dotfun/sdk/shared/utils/number";
|
|
5
|
-
import { useMemo } from "react";
|
|
6
|
-
import type { AnySpendCheckoutClasses } from "./AnySpendCheckout";
|
|
7
|
-
import type { CheckoutSummaryLine } from "./AnySpendCheckout";
|
|
5
|
+
import { useMemo, type ReactNode } from "react";
|
|
6
|
+
import type { AnySpendCheckoutClasses, CheckoutSummaryLine } from "./AnySpendCheckout";
|
|
8
7
|
|
|
9
8
|
interface CartSummaryProps {
|
|
10
9
|
/** Formatted total (final amount after all adjustments) */
|
|
11
|
-
total:
|
|
10
|
+
total: ReactNode;
|
|
12
11
|
tokenSymbol?: string;
|
|
13
12
|
classes?: AnySpendCheckoutClasses;
|
|
14
13
|
/** Formatted subtotal (sum of items only — shown when adjustments exist) */
|
|
@@ -20,6 +19,8 @@ interface CartSummaryProps {
|
|
|
20
19
|
summaryLines?: CheckoutSummaryLine[];
|
|
21
20
|
/** Formatted USD equivalent (e.g. "$5.56") — shown for non-stablecoin tokens */
|
|
22
21
|
usdEquivalent?: string | null;
|
|
22
|
+
/** When true, hide adjustment rows and USD equivalent (caller passes a skeleton as `total`). */
|
|
23
|
+
pricesLoading?: boolean;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function CartSummary({
|
|
@@ -33,35 +34,38 @@ export function CartSummary({
|
|
|
33
34
|
discount,
|
|
34
35
|
summaryLines,
|
|
35
36
|
usdEquivalent,
|
|
37
|
+
pricesLoading = false,
|
|
36
38
|
}: CartSummaryProps) {
|
|
37
|
-
const hasAdjustments =
|
|
38
|
-
!!shipping?.amount || !!tax?.amount || !!discount?.amount || (summaryLines && summaryLines.length > 0);
|
|
39
|
-
|
|
40
39
|
const formattedShipping = useMemo(
|
|
41
|
-
() => (shipping?.amount ? formatTokenAmount(safeBigInt(shipping.amount), tokenDecimals) : null),
|
|
42
|
-
[shipping?.amount, tokenDecimals],
|
|
40
|
+
() => (!pricesLoading && shipping?.amount ? formatTokenAmount(safeBigInt(shipping.amount), tokenDecimals) : null),
|
|
41
|
+
[pricesLoading, shipping?.amount, tokenDecimals],
|
|
43
42
|
);
|
|
44
43
|
|
|
45
44
|
const formattedTax = useMemo(
|
|
46
|
-
() => (tax?.amount ? formatTokenAmount(safeBigInt(tax.amount), tokenDecimals) : null),
|
|
47
|
-
[tax?.amount, tokenDecimals],
|
|
45
|
+
() => (!pricesLoading && tax?.amount ? formatTokenAmount(safeBigInt(tax.amount), tokenDecimals) : null),
|
|
46
|
+
[pricesLoading, tax?.amount, tokenDecimals],
|
|
48
47
|
);
|
|
49
48
|
|
|
50
49
|
const formattedDiscount = useMemo(
|
|
51
|
-
() => (discount?.amount ? formatTokenAmount(safeBigInt(discount.amount), tokenDecimals) : null),
|
|
52
|
-
[discount?.amount, tokenDecimals],
|
|
50
|
+
() => (!pricesLoading && discount?.amount ? formatTokenAmount(safeBigInt(discount.amount), tokenDecimals) : null),
|
|
51
|
+
[pricesLoading, discount?.amount, tokenDecimals],
|
|
53
52
|
);
|
|
54
53
|
|
|
55
54
|
const formattedSummaryLines = useMemo(
|
|
56
55
|
() =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
pricesLoading
|
|
57
|
+
? undefined
|
|
58
|
+
: summaryLines?.map(line => ({
|
|
59
|
+
...line,
|
|
60
|
+
formattedAmount: formatTokenAmount(safeBigInt(line.amount), tokenDecimals),
|
|
61
|
+
isNegative: safeBigInt(line.amount) < BigInt(0),
|
|
62
|
+
})),
|
|
63
|
+
[pricesLoading, summaryLines, tokenDecimals],
|
|
63
64
|
);
|
|
64
65
|
|
|
66
|
+
const hasAdjustments =
|
|
67
|
+
!!formattedShipping || !!formattedTax || !!formattedDiscount || !!formattedSummaryLines?.length;
|
|
68
|
+
|
|
65
69
|
return (
|
|
66
70
|
<div className={cn("border-t border-gray-200 pt-3 dark:border-neutral-700", classes?.cartSummary)}>
|
|
67
71
|
{/* Subtotal — only shown when adjustments exist */}
|
|
@@ -147,7 +151,7 @@ export function CartSummary({
|
|
|
147
151
|
{total} {tokenSymbol}
|
|
148
152
|
</span>
|
|
149
153
|
</div>
|
|
150
|
-
{usdEquivalent && (
|
|
154
|
+
{!pricesLoading && usdEquivalent && (
|
|
151
155
|
<div className="flex justify-end">
|
|
152
156
|
<span className="text-xs text-gray-400 dark:text-gray-500">~{usdEquivalent} USD</span>
|
|
153
157
|
</div>
|
|
@@ -7,12 +7,15 @@ import type { CheckoutItem, CheckoutSummaryLine, AnySpendCheckoutClasses } from
|
|
|
7
7
|
import { CartItemRow } from "./CartItemRow";
|
|
8
8
|
import { CartSummary } from "./CartSummary";
|
|
9
9
|
import { PoweredByBranding } from "./PoweredByBranding";
|
|
10
|
+
import { PriceSkeleton } from "./PriceSkeleton";
|
|
10
11
|
|
|
11
12
|
interface CheckoutCartPanelProps {
|
|
12
13
|
items: CheckoutItem[];
|
|
13
14
|
totalAmount: string;
|
|
14
15
|
tokenSymbol?: string;
|
|
15
16
|
tokenDecimals?: number;
|
|
17
|
+
/** True while token decimals/symbol are still loading — prevents rendering mis-decimalized prices. */
|
|
18
|
+
pricesLoading?: boolean;
|
|
16
19
|
organizationName?: string;
|
|
17
20
|
organizationLogo?: string;
|
|
18
21
|
classes?: AnySpendCheckoutClasses;
|
|
@@ -31,6 +34,7 @@ export function CheckoutCartPanel({
|
|
|
31
34
|
totalAmount,
|
|
32
35
|
tokenSymbol = "",
|
|
33
36
|
tokenDecimals = 18,
|
|
37
|
+
pricesLoading = false,
|
|
34
38
|
organizationName,
|
|
35
39
|
organizationLogo,
|
|
36
40
|
classes,
|
|
@@ -56,7 +60,7 @@ export function CheckoutCartPanel({
|
|
|
56
60
|
}, [items, tokenDecimals]);
|
|
57
61
|
|
|
58
62
|
return (
|
|
59
|
-
<div className={cn("anyspend-cart-panel flex flex-col", classes?.cartPanel)}>
|
|
63
|
+
<div className={cn("anyspend-cart-panel flex flex-col", classes?.cartPanel)} aria-busy={pricesLoading || undefined}>
|
|
60
64
|
<h2
|
|
61
65
|
className={cn(
|
|
62
66
|
"anyspend-cart-title mb-4 text-lg font-semibold text-gray-900 dark:text-gray-100",
|
|
@@ -69,14 +73,18 @@ export function CheckoutCartPanel({
|
|
|
69
73
|
<div className="anyspend-cart-items divide-y divide-gray-100 dark:divide-gray-800">
|
|
70
74
|
{items.map((item, index) => {
|
|
71
75
|
const itemTotal = safeBigInt(item.amount) * BigInt(item.quantity);
|
|
72
|
-
const formattedPrice =
|
|
76
|
+
const formattedPrice: ReactNode = pricesLoading ? (
|
|
77
|
+
<PriceSkeleton />
|
|
78
|
+
) : (
|
|
79
|
+
`${formatTokenAmount(itemTotal, tokenDecimals)} ${tokenSymbol}`
|
|
80
|
+
);
|
|
73
81
|
|
|
74
82
|
return <CartItemRow key={item.id || index} item={item} formattedPrice={formattedPrice} classes={classes} />;
|
|
75
83
|
})}
|
|
76
84
|
</div>
|
|
77
85
|
|
|
78
86
|
<CartSummary
|
|
79
|
-
total={formattedTotal}
|
|
87
|
+
total={pricesLoading ? <PriceSkeleton className="w-20" /> : formattedTotal}
|
|
80
88
|
tokenSymbol={tokenSymbol}
|
|
81
89
|
classes={classes}
|
|
82
90
|
subtotal={formattedSubtotal}
|
|
@@ -86,6 +94,7 @@ export function CheckoutCartPanel({
|
|
|
86
94
|
discount={discount}
|
|
87
95
|
summaryLines={summaryLines}
|
|
88
96
|
usdEquivalent={usdEquivalent}
|
|
97
|
+
pricesLoading={pricesLoading}
|
|
89
98
|
/>
|
|
90
99
|
|
|
91
100
|
{footer === undefined ? (
|
|
@@ -31,6 +31,8 @@ interface CheckoutFormPanelProps {
|
|
|
31
31
|
/** Token info for display */
|
|
32
32
|
tokenSymbol?: string;
|
|
33
33
|
tokenDecimals?: number;
|
|
34
|
+
/** True while token decimals/symbol are still loading — prevents rendering mis-decimalized prices. */
|
|
35
|
+
pricesLoading?: boolean;
|
|
34
36
|
/** CSS class overrides */
|
|
35
37
|
classes?: AnySpendCheckoutClasses;
|
|
36
38
|
/** Current form data (lifted state) */
|
|
@@ -62,6 +64,7 @@ export function CheckoutFormPanel({
|
|
|
62
64
|
validateDiscount,
|
|
63
65
|
tokenSymbol,
|
|
64
66
|
tokenDecimals,
|
|
67
|
+
pricesLoading = false,
|
|
65
68
|
classes,
|
|
66
69
|
formData,
|
|
67
70
|
onFormDataChange,
|
|
@@ -144,6 +147,7 @@ export function CheckoutFormPanel({
|
|
|
144
147
|
onSelect={onShippingChange}
|
|
145
148
|
tokenSymbol={tokenSymbol}
|
|
146
149
|
tokenDecimals={tokenDecimals}
|
|
150
|
+
pricesLoading={pricesLoading}
|
|
147
151
|
/>
|
|
148
152
|
)}
|
|
149
153
|
{enableDiscountCode && validateDiscount && (
|
|
@@ -153,6 +157,7 @@ export function CheckoutFormPanel({
|
|
|
153
157
|
onRemove={onDiscountRemoved}
|
|
154
158
|
tokenSymbol={tokenSymbol}
|
|
155
159
|
tokenDecimals={tokenDecimals}
|
|
160
|
+
pricesLoading={pricesLoading}
|
|
156
161
|
/>
|
|
157
162
|
)}
|
|
158
163
|
</>
|
|
@@ -5,6 +5,7 @@ import { formatTokenAmount, safeBigInt } from "@b3dotfun/sdk/shared/utils/number
|
|
|
5
5
|
import { X, Loader2, Check } from "lucide-react";
|
|
6
6
|
import { useState, useCallback } from "react";
|
|
7
7
|
import type { DiscountResult } from "../../../types/forms";
|
|
8
|
+
import { PriceSkeleton } from "./PriceSkeleton";
|
|
8
9
|
|
|
9
10
|
interface DiscountCodeInputProps {
|
|
10
11
|
onApply: (code: string) => Promise<DiscountResult>;
|
|
@@ -13,6 +14,8 @@ interface DiscountCodeInputProps {
|
|
|
13
14
|
loading?: boolean;
|
|
14
15
|
tokenSymbol?: string;
|
|
15
16
|
tokenDecimals?: number;
|
|
17
|
+
/** True while token decimals/symbol are still loading — renders applied amount as skeleton. */
|
|
18
|
+
pricesLoading?: boolean;
|
|
16
19
|
className?: string;
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -29,6 +32,7 @@ export function DiscountCodeInput({
|
|
|
29
32
|
loading = false,
|
|
30
33
|
tokenSymbol = "",
|
|
31
34
|
tokenDecimals = 6,
|
|
35
|
+
pricesLoading = false,
|
|
32
36
|
className,
|
|
33
37
|
}: DiscountCodeInputProps) {
|
|
34
38
|
const [code, setCode] = useState("");
|
|
@@ -67,11 +71,17 @@ export function DiscountCodeInput({
|
|
|
67
71
|
<div className="flex items-center gap-2">
|
|
68
72
|
<Check className="h-4 w-4 text-green-600 dark:text-green-400" />
|
|
69
73
|
<span className="anyspend-discount-value text-sm font-medium text-green-700 dark:text-green-300">
|
|
70
|
-
{appliedDiscount.discount_type === "percentage"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
{appliedDiscount.discount_type === "percentage" ? (
|
|
75
|
+
`${appliedDiscount.discount_value}% off`
|
|
76
|
+
) : appliedDiscount.discount_amount ? (
|
|
77
|
+
pricesLoading ? (
|
|
78
|
+
<PriceSkeleton />
|
|
79
|
+
) : (
|
|
80
|
+
`-${formatAmount(appliedDiscount.discount_amount, tokenDecimals, tokenSymbol)}`
|
|
81
|
+
)
|
|
82
|
+
) : (
|
|
83
|
+
"Discount applied"
|
|
84
|
+
)}
|
|
75
85
|
</span>
|
|
76
86
|
</div>
|
|
77
87
|
<button
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
4
|
+
|
|
5
|
+
interface PriceSkeletonProps {
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function PriceSkeleton({ className }: PriceSkeletonProps) {
|
|
10
|
+
return (
|
|
11
|
+
<span
|
|
12
|
+
className={cn(
|
|
13
|
+
"animate-pulse-fade bg-b3-react-background inline-block h-4 w-16 rounded-md align-middle",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
4
4
|
import { formatTokenAmount, safeBigInt } from "@b3dotfun/sdk/shared/utils/number";
|
|
5
5
|
import type { ShippingOption } from "../../../types/forms";
|
|
6
|
+
import { PriceSkeleton } from "./PriceSkeleton";
|
|
6
7
|
|
|
7
8
|
interface ShippingSelectorProps {
|
|
8
9
|
options: ShippingOption[];
|
|
@@ -10,6 +11,8 @@ interface ShippingSelectorProps {
|
|
|
10
11
|
onSelect: (option: ShippingOption) => void;
|
|
11
12
|
tokenSymbol?: string;
|
|
12
13
|
tokenDecimals?: number;
|
|
14
|
+
/** True while token decimals/symbol are still loading — renders price as skeleton. */
|
|
15
|
+
pricesLoading?: boolean;
|
|
13
16
|
className?: string;
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -25,6 +28,7 @@ export function ShippingSelector({
|
|
|
25
28
|
onSelect,
|
|
26
29
|
tokenSymbol = "",
|
|
27
30
|
tokenDecimals = 6,
|
|
31
|
+
pricesLoading = false,
|
|
28
32
|
className,
|
|
29
33
|
}: ShippingSelectorProps) {
|
|
30
34
|
if (options.length === 0) return null;
|
|
@@ -63,7 +67,7 @@ export function ShippingSelector({
|
|
|
63
67
|
</div>
|
|
64
68
|
</div>
|
|
65
69
|
<div className="anyspend-shipping-option-price text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
66
|
-
{formatAmount(option.amount, tokenDecimals, tokenSymbol)}
|
|
70
|
+
{pricesLoading ? <PriceSkeleton /> : formatAmount(option.amount, tokenDecimals, tokenSymbol)}
|
|
67
71
|
</div>
|
|
68
72
|
</label>
|
|
69
73
|
))}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Button
|
|
1
|
+
import { Button } from "@b3dotfun/sdk/global-account/react";
|
|
2
2
|
import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { useBetterAuth } from "../../hooks/useBetterAuth";
|
|
5
|
+
import { PasswordInput } from "./components/PasswordInput";
|
|
5
6
|
|
|
6
7
|
const debug = debugB3React("BetterAuthResetPassword");
|
|
7
8
|
|
|
@@ -103,13 +104,12 @@ export function BetterAuthResetPassword({ token, onSuccess, onError, className }
|
|
|
103
104
|
<label className="mb-2 block text-xs font-medium uppercase tracking-wide text-gray-700 dark:text-gray-300">
|
|
104
105
|
New password
|
|
105
106
|
</label>
|
|
106
|
-
<
|
|
107
|
-
type="password"
|
|
107
|
+
<PasswordInput
|
|
108
108
|
placeholder="At least 8 characters"
|
|
109
109
|
value={password}
|
|
110
110
|
onChange={e => setPassword(e.target.value)}
|
|
111
111
|
disabled={isLoading}
|
|
112
|
-
className="h-11 px-4 text-[15px]"
|
|
112
|
+
className="h-11 px-4 pr-11 text-[15px]"
|
|
113
113
|
/>
|
|
114
114
|
</div>
|
|
115
115
|
|
|
@@ -117,8 +117,7 @@ export function BetterAuthResetPassword({ token, onSuccess, onError, className }
|
|
|
117
117
|
<label className="mb-2 block text-xs font-medium uppercase tracking-wide text-gray-700 dark:text-gray-300">
|
|
118
118
|
Confirm password
|
|
119
119
|
</label>
|
|
120
|
-
<
|
|
121
|
-
type="password"
|
|
120
|
+
<PasswordInput
|
|
122
121
|
placeholder="Repeat your password"
|
|
123
122
|
value={confirmPassword}
|
|
124
123
|
onChange={e => setConfirmPassword(e.target.value)}
|
|
@@ -126,7 +125,7 @@ export function BetterAuthResetPassword({ token, onSuccess, onError, className }
|
|
|
126
125
|
onKeyDown={e => {
|
|
127
126
|
if (e.key === "Enter") handleSubmit();
|
|
128
127
|
}}
|
|
129
|
-
className="h-11 px-4 text-[15px]"
|
|
128
|
+
className="h-11 px-4 pr-11 text-[15px]"
|
|
130
129
|
/>
|
|
131
130
|
</div>
|
|
132
131
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Input, useAuthStore } from "@b3dotfun/sdk/global-account/react";
|
|
1
|
+
import { Button, Input, Loading, useAuthStore } from "@b3dotfun/sdk/global-account/react";
|
|
2
2
|
import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type BetterAuthSocialProvider,
|
|
7
7
|
useBetterAuth,
|
|
8
8
|
} from "../../hooks/useBetterAuth";
|
|
9
|
+
import { PasswordInput } from "./components/PasswordInput";
|
|
9
10
|
import { strategyIcons, strategyLabels } from "./utils/signInUtils";
|
|
10
11
|
|
|
11
12
|
const debug = debugB3React("BetterAuthSignIn");
|
|
@@ -66,6 +67,8 @@ export function BetterAuthSignIn({
|
|
|
66
67
|
}: BetterAuthSignInProps) {
|
|
67
68
|
const { signInWithEmail, signUpWithEmail, signInWithSocial, requestPasswordReset } = useBetterAuth();
|
|
68
69
|
const isAuthenticated = useAuthStore(state => state.isAuthenticated);
|
|
70
|
+
const isAuthenticating = useAuthStore(state => state.isAuthenticating);
|
|
71
|
+
const hasStartedConnecting = useAuthStore(state => state.hasStartedConnecting);
|
|
69
72
|
|
|
70
73
|
const [mode, setMode] = useState<"sign-in" | "sign-up" | "forgot-password">("sign-in");
|
|
71
74
|
const [email, setEmail] = useState("");
|
|
@@ -83,7 +86,9 @@ export function BetterAuthSignIn({
|
|
|
83
86
|
|
|
84
87
|
const resolvedSubtitle =
|
|
85
88
|
mode === "forgot-password"
|
|
86
|
-
?
|
|
89
|
+
? resetEmailSent
|
|
90
|
+
? "We've sent a password reset link to your email"
|
|
91
|
+
: "Enter your email and we'll send you a reset link"
|
|
87
92
|
: subtitle || "Enter your credentials to access your account";
|
|
88
93
|
|
|
89
94
|
const handleForgotPassword = async () => {
|
|
@@ -111,6 +116,16 @@ export function BetterAuthSignIn({
|
|
|
111
116
|
.map(id => DEFAULT_SOCIAL_PROVIDERS.find(p => p.id === id))
|
|
112
117
|
.filter((p): p is SocialProviderConfig => !!p);
|
|
113
118
|
|
|
119
|
+
// Show loading during session restore (before any user interaction) to prevent
|
|
120
|
+
// the login form from flashing briefly after OAuth redirect.
|
|
121
|
+
if (isAuthenticating && !hasStartedConnecting) {
|
|
122
|
+
return (
|
|
123
|
+
<div className={`flex w-full max-w-[400px] items-center justify-center px-6 py-20 ${className || ""}`}>
|
|
124
|
+
<Loading variant="primary" size="lg" />
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
114
129
|
if (isAuthenticated) {
|
|
115
130
|
return null;
|
|
116
131
|
}
|
|
@@ -334,16 +349,15 @@ export function BetterAuthSignIn({
|
|
|
334
349
|
</button>
|
|
335
350
|
)}
|
|
336
351
|
</div>
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
placeholder="Password"
|
|
352
|
+
<PasswordInput
|
|
353
|
+
key={mode}
|
|
340
354
|
value={password}
|
|
341
355
|
onChange={e => setPassword(e.target.value)}
|
|
342
356
|
disabled={isLoading}
|
|
343
357
|
onKeyDown={e => {
|
|
344
358
|
if (e.key === "Enter") handleEmailSubmit();
|
|
345
359
|
}}
|
|
346
|
-
className="h-11 px-4 text-[15px]"
|
|
360
|
+
className="h-11 px-4 pr-11 text-[15px]"
|
|
347
361
|
/>
|
|
348
362
|
</div>
|
|
349
363
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Input } from "@b3dotfun/sdk/global-account/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
interface PasswordInputProps {
|
|
5
|
+
value: string;
|
|
6
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function PasswordInput({
|
|
14
|
+
value,
|
|
15
|
+
onChange,
|
|
16
|
+
disabled,
|
|
17
|
+
placeholder = "Password",
|
|
18
|
+
onKeyDown,
|
|
19
|
+
className,
|
|
20
|
+
}: PasswordInputProps) {
|
|
21
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="relative">
|
|
25
|
+
<Input
|
|
26
|
+
type={showPassword ? "text" : "password"}
|
|
27
|
+
placeholder={placeholder}
|
|
28
|
+
value={value}
|
|
29
|
+
onChange={onChange}
|
|
30
|
+
disabled={disabled}
|
|
31
|
+
onKeyDown={onKeyDown}
|
|
32
|
+
className={className}
|
|
33
|
+
/>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
37
|
+
aria-label={showPassword ? "Hide password" : "Show password"}
|
|
38
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
|
|
39
|
+
tabIndex={-1}
|
|
40
|
+
>
|
|
41
|
+
{showPassword ? (
|
|
42
|
+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
43
|
+
<path
|
|
44
|
+
strokeLinecap="round"
|
|
45
|
+
strokeLinejoin="round"
|
|
46
|
+
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
|
|
47
|
+
/>
|
|
48
|
+
</svg>
|
|
49
|
+
) : (
|
|
50
|
+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
51
|
+
<path
|
|
52
|
+
strokeLinecap="round"
|
|
53
|
+
strokeLinejoin="round"
|
|
54
|
+
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
|
|
55
|
+
/>
|
|
56
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
57
|
+
</svg>
|
|
58
|
+
)}
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
useBetterAuth,
|
|
8
8
|
} from "../../../hooks/useBetterAuth";
|
|
9
9
|
import { AuthButton } from "../components/AuthButton";
|
|
10
|
+
import { PasswordInput } from "../components/PasswordInput";
|
|
10
11
|
|
|
11
12
|
const debug = debugB3React("LoginStepBetterAuth");
|
|
12
13
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -160,7 +161,11 @@ export function LoginStepBetterAuth({ onSuccess, onError }: LoginStepBetterAuthP
|
|
|
160
161
|
{mode === "forgot-password" ? (
|
|
161
162
|
<div className="mb-6 w-full space-y-3 px-3">
|
|
162
163
|
<p className="text-center text-sm font-medium text-gray-900 dark:text-gray-100">Reset password</p>
|
|
163
|
-
<p className="text-center text-xs text-gray-500">
|
|
164
|
+
<p className="text-center text-xs text-gray-500">
|
|
165
|
+
{resetEmailSent
|
|
166
|
+
? "We've sent a password reset link to your email"
|
|
167
|
+
: "Enter your email and we'll send you a reset link"}
|
|
168
|
+
</p>
|
|
164
169
|
|
|
165
170
|
{resetEmailSent ? (
|
|
166
171
|
<div className="space-y-3 py-4 text-center">
|
|
@@ -223,15 +228,15 @@ export function LoginStepBetterAuth({ onSuccess, onError }: LoginStepBetterAuthP
|
|
|
223
228
|
disabled={isLoading}
|
|
224
229
|
/>
|
|
225
230
|
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
placeholder="Password"
|
|
231
|
+
<PasswordInput
|
|
232
|
+
key={mode}
|
|
229
233
|
value={password}
|
|
230
234
|
onChange={event => setPassword(event.target.value)}
|
|
231
235
|
disabled={isLoading}
|
|
232
236
|
onKeyDown={event => {
|
|
233
237
|
if (event.key === "Enter") handleEmailSubmit();
|
|
234
238
|
}}
|
|
239
|
+
className="pr-11"
|
|
235
240
|
/>
|
|
236
241
|
|
|
237
242
|
{error && <p className="text-sm text-red-500">{error}</p>}
|