Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Native Send Component #1874

Draft
wants to merge 96 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
17d6b57
send scaffolding
brendan-defi Jan 22, 2025
29ce8e2
remove ethbalance from provider props
brendan-defi Jan 22, 2025
d5f399f
update utils
brendan-defi Jan 22, 2025
cb64639
refactor to remove sendprovider dep
brendan-defi Jan 22, 2025
971dc6a
starting address selection
brendan-defi Jan 22, 2025
05eb416
update getAddress to support basename reverse res
brendan-defi Jan 23, 2025
a89983c
utils + tests
brendan-defi Jan 23, 2025
033c4b8
early send flows
brendan-defi Jan 23, 2025
6ede224
move send into wallet
brendan-defi Jan 23, 2025
b9bba55
send in walletisland
brendan-defi Jan 23, 2025
c50f9a8
state drives active screen
brendan-defi Jan 23, 2025
ebe1a68
use amount input in send
brendan-defi Jan 23, 2025
b0a97f5
send amount input
brendan-defi Jan 24, 2025
7cb8665
remove comments, unused imports
brendan-defi Jan 24, 2025
f029ffb
fix imports
brendan-defi Jan 24, 2025
c49fe08
fix imports, tests
brendan-defi Jan 24, 2025
8964803
removed duplicate hook
brendan-defi Jan 24, 2025
9e63f2d
add close button
brendan-defi Jan 24, 2025
57f92da
address input reset
brendan-defi Jan 25, 2025
b56268e
send updates
brendan-defi Jan 25, 2025
c936a62
token balance component
brendan-defi Jan 25, 2025
f379001
enable actions on token balance
brendan-defi Jan 25, 2025
f3854da
token selection
brendan-defi Jan 26, 2025
b1df7ac
improved token reselection
brendan-defi Jan 27, 2025
da50b49
improved asset type switch
brendan-defi Jan 27, 2025
393fc34
update send button
brendan-defi Jan 27, 2025
d029daf
header back button
brendan-defi Jan 27, 2025
bfbb95a
fix imports
brendan-defi Jan 27, 2025
ada978b
refactor provider for clarity
brendan-defi Jan 27, 2025
58d9600
update filename
brendan-defi Jan 27, 2025
69e5ab9
fix subtitle
brendan-defi Jan 28, 2025
ad5af09
add fundcard when no eth balance
brendan-defi Jan 28, 2025
35efd87
minor refactor
brendan-defi Jan 28, 2025
620554b
design review
brendan-defi Jan 29, 2025
c3d3e84
fix ethBalance race condition
brendan-defi Jan 29, 2025
77771aa
fix lints
brendan-defi Jan 29, 2025
320f46d
use common files
brendan-defi Jan 29, 2025
8999246
subcomponents use props
brendan-defi Jan 29, 2025
1ed61e6
override classnames
brendan-defi Jan 29, 2025
26aff3a
input field uses names
brendan-defi Jan 29, 2025
671505e
update send fund wallet
brendan-defi Jan 29, 2025
d542d7e
use common files
brendan-defi Jan 29, 2025
a44357a
remove send exportsc
brendan-defi Jan 29, 2025
f15c9b6
update token balance display
brendan-defi Jan 29, 2025
430e3d2
remove max width from token name
brendan-defi Jan 29, 2025
588a8a4
fix use max
brendan-defi Jan 29, 2025
67e9726
hardcode input autocomplete props
brendan-defi Jan 29, 2025
7a25392
send on base and mainnet
brendan-defi Jan 29, 2025
a9cc65b
fix button-within-button bug
brendan-defi Jan 29, 2025
d838f28
implement succcessOverride
brendan-defi Jan 29, 2025
3ad97f7
fix lints
brendan-defi Jan 29, 2025
02aed37
implement lifecycle tracking
brendan-defi Jan 30, 2025
930add1
update types to conform to standard useLifecycleStatus non-null expec…
brendan-defi Jan 30, 2025
66cd248
fix lints
brendan-defi Jan 30, 2025
c1061d0
fix playground package.json
brendan-defi Jan 30, 2025
adbbe2e
remove error export, improve number transforms
brendan-defi Jan 30, 2025
240a3f7
improve number transforms
brendan-defi Jan 30, 2025
84d73d1
improved number transforms
brendan-defi Jan 30, 2025
640615a
improved number transforms
brendan-defi Jan 30, 2025
4f5452e
utils for readability and reuse
brendan-defi Jan 30, 2025
6be3572
remove identity wrapper
brendan-defi Jan 30, 2025
4358eaa
utility for resolving the displayed address/name
brendan-defi Jan 30, 2025
ca305b2
use utility
brendan-defi Jan 30, 2025
be0826e
revert type change
brendan-defi Jan 30, 2025
1bc72ff
update lifecycle types
brendan-defi Jan 30, 2025
00f920d
update and create utils
brendan-defi Jan 30, 2025
441795b
button label
brendan-defi Jan 30, 2025
fe0de1c
update provider and types
brendan-defi Jan 30, 2025
266afbf
clean provider
brendan-defi Jan 30, 2025
4be81d1
handle null args
brendan-defi Jan 30, 2025
d5a9b15
refactor to better leverage TextInput
brendan-defi Jan 30, 2025
b61afce
consolidate address selection
brendan-defi Jan 30, 2025
5e1d699
refactor to avoid rerenders
brendan-defi Jan 30, 2025
b1d9011
handle status updates
brendan-defi Jan 30, 2025
4f88bb0
header handles address reset
brendan-defi Jan 30, 2025
9438101
fix lints
brendan-defi Jan 30, 2025
51947c3
fix spacing
brendan-defi Jan 31, 2025
a89d3f1
fix input resetting
brendan-defi Jan 31, 2025
d0fe8e8
add debounce to value inputs
brendan-defi Jan 31, 2025
98c1207
refactor resolve address input
brendan-defi Jan 31, 2025
1a8c48a
fix walletHasEth logic
brendan-defi Feb 1, 2025
353a8e8
fix design feedback
brendan-defi Feb 1, 2025
35b1343
remove extraneous div
brendan-defi Feb 3, 2025
3018db0
Merge branch 'main' into bf/ock-send
brendan-defi Feb 7, 2025
3210f15
restructure
brendan-defi Feb 7, 2025
7d6fa95
fix amount input
brendan-defi Feb 7, 2025
628bb0b
fns use context, not props
brendan-defi Feb 7, 2025
a9c8b13
add classnames overrides
brendan-defi Feb 7, 2025
7b8ac3c
fix loading state, add const
brendan-defi Feb 7, 2025
f8a46d7
handle checksummed addresses"
brendan-defi Feb 7, 2025
9f960dc
fix lints
brendan-defi Feb 7, 2025
75e966d
dedupe and improve TokenBalance
brendan-defi Feb 7, 2025
6c82789
fix button validation
brendan-defi Feb 7, 2025
3624ea5
fix lints
brendan-defi Feb 7, 2025
3c19c3b
refactor walletadvanced active feature
brendan-defi Feb 7, 2025
e755150
use skeleton for loading state
brendan-defi Feb 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions playground/nextjs-app-router/onchainkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/onchainkit",
"version": "0.36.9",
"version": "0.36.10",
"type": "module",
"repository": "https://github.com/coinbase/onchainkit.git",
"license": "MIT",
Expand Down Expand Up @@ -79,7 +79,7 @@
"tailwindcss": "^3.4.3",
"tscpaths": "^0.0.9",
"tsup": "^8.3.5",
"typescript": "^5.7.3",
"typescript": "~5.3.3",
"vite": "^5.3.3",
"vitest": "^3.0.5"
},
Expand Down Expand Up @@ -140,12 +140,6 @@
"import": "./esm/core/index.js",
"default": "./esm/core/index.js"
},
"./earn": {
"types": "./esm/earn/index.d.ts",
"module": "./esm/earn/index.js",
"import": "./esm/earn/index.js",
"default": "./esm/earn/index.js"
},
"./fund": {
"types": "./esm/fund/index.d.ts",
"module": "./esm/fund/index.js",
Expand Down
3 changes: 3 additions & 0 deletions src/internal/components/amount-input/AmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type AmountInputProps = {
setFiatAmount: (value: string) => void;
setCryptoAmount: (value: string) => void;
exchangeRate: string;
delayMs?: number;
className?: string;
textClassName?: string;
};
Expand All @@ -28,6 +29,7 @@ export function AmountInput({
setFiatAmount,
setCryptoAmount,
exchangeRate,
delayMs,
className,
textClassName,
}: AmountInputProps) {
Expand Down Expand Up @@ -111,6 +113,7 @@ export function AmountInput({
)}
value={value}
onChange={handleAmountChange}
delayMs={delayMs}
inputValidator={isValidAmount}
ref={inputRef}
inputMode="decimal"
Expand Down
2 changes: 1 addition & 1 deletion src/internal/hooks/useExchangeRate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function useExchangeRate({
return;
}

if (token.address === usdcToken.address) {
if (token.address.toLowerCase() === usdcToken.address.toLowerCase()) {
setExchangeRate(1);
return;
}
Expand Down
109 changes: 56 additions & 53 deletions src/token/components/TokenBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,59 @@ import { formatFiatAmount } from '@/internal/utils/formatFiatAmount';
import { truncateDecimalPlaces } from '@/internal/utils/truncateDecimalPlaces';
import { border, cn, color, text } from '@/styles/theme';
import { TokenImage } from '@/token';
import { useCallback } from 'react';
import { useMemo } from 'react';
import { formatUnits } from 'viem';
import type { TokenBalanceProps } from '../types';

export function TokenBalance({
token,
onClick,
onActionPress,
actionText = 'Use max',
classNames,
'aria-label': ariaLabel,
...contentProps
}: TokenBalanceProps) {
if (onClick) {
return (
<button
type="button"
onClick={() => onClick(token)}
className={cn(
'flex w-full items-center justify-start gap-4 px-2 py-1',
classNames?.container,
<div className="relative">
<button
type="button"
aria-label={ariaLabel ?? `${token.name} token balance`}
onClick={() => onClick(token)}
className={cn(
'flex w-full items-center justify-start gap-4 px-2 py-1',
classNames?.container,
)}
data-testid="ockTokenBalanceButton"
>
<TokenBalanceContent
token={token}
classNames={classNames}
onActionPress={onActionPress}
{...contentProps}
/>
</button>
{onActionPress && (
<button
type="button"
data-testid="ockTokenBalanceAction"
aria-label={actionText}
onClick={onActionPress}
className={cn(
text.label2,
color.primary,
border.radius,
'cursor-pointer p-0.5 font-bold',
'border border-transparent hover:border-[--ock-line-primary]',
'-translate-y-1/2 absolute top-1/2 right-2',
classNames?.action,
)}
>
{actionText}
</button>
)}
data-testid="ockTokenBalanceButton"
>
<TokenBalanceContent
token={token}
{...contentProps}
classNames={classNames}
/>
</button>
</div>
);
}

Expand All @@ -53,31 +79,26 @@ function TokenBalanceContent({
token,
subtitle,
showImage = true,
actionText = 'Use max',
onActionPress,
tokenSize = 40,
classNames,
}: TokenBalanceProps) {
const formattedFiatValue = formatFiatAmount({
amount: token.fiatBalance,
currency: 'USD',
});

const formattedCryptoValue = truncateDecimalPlaces(
formatUnits(BigInt(token.cryptoBalance), token.decimals),
3,
const formattedFiatValue = useMemo(
() =>
formatFiatAmount({
amount: token.fiatBalance,
currency: 'USD',
}),
[token.fiatBalance],
);

const handleActionPress = useCallback(
(
e:
| React.MouseEvent<HTMLDivElement, MouseEvent>
| React.KeyboardEvent<HTMLDivElement>,
) => {
e.stopPropagation();
onActionPress?.();
},
[onActionPress],
const formattedCryptoValue = useMemo(
() =>
truncateDecimalPlaces(
formatUnits(BigInt(token.cryptoBalance), token.decimals),
3,
),
[token.cryptoBalance, token.decimals],
);

return (
Expand Down Expand Up @@ -107,25 +128,7 @@ function TokenBalanceContent({
</span>
</div>
<div className="text-right">
{onActionPress ? (
<div
role="button"
data-testid="ockTokenBalanceAction"
aria-label={actionText}
onClick={handleActionPress}
onKeyDown={handleActionPress}
className={cn(
text.label2,
color.primary,
border.radius,
'ml-auto cursor-pointer p-0.5 font-bold',
'border border-transparent hover:border-[--ock-line-primary]',
classNames?.action,
)}
>
{actionText}
</div>
) : (
{!onActionPress && (
<span
className={cn(
text.label2,
Expand Down
34 changes: 23 additions & 11 deletions src/token/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,10 @@ export type TokenBalanceProps = {
token: PortfolioTokenWithFiatValue;
/** Subtitle to display next to the token name (eg. "available") */
subtitle?: string;
/** Show the token image (default: true) */
showImage?: boolean;
/** Click handler for the whole component*/
onClick?: (token: PortfolioTokenWithFiatValue) => void;
/** Size of the token image in px (default: 40) */
tokenSize?: number;
/** Optional aria label for the component */
'aria-label'?: string;
/** Optional additional CSS classes to apply to the component */
classNames?: {
container?: string;
Expand All @@ -146,13 +144,27 @@ export type TokenBalanceProps = {
};
} & (
| {
/** Hide the action button (default)*/
actionText?: never;
onActionPress?: never;
/** Show the token image (default: true) */
showImage?: true;
/** Size of the token image in px (default: 40) */
tokenSize?: number;
}
| {
/** Show an additional action button (eg. "Use max") */
actionText?: string;
onActionPress: () => void;
/** Hide the token image */
showImage: false;
/** Size of the token image in px (default: 40) */
tokenSize?: never;
}
);
) &
(
| {
/** Hide the action button (default)*/
onActionPress?: never;
actionText?: never;
}
| {
/** Show an additional action button (eg. "Use max") */
onActionPress: () => void;
actionText?: string;
}
);
17 changes: 13 additions & 4 deletions src/wallet/components/WalletAdvancedContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useWalletAdvancedContext } from './WalletAdvancedProvider';
import { WalletAdvancedQrReceive } from './WalletAdvancedQrReceive';
import { WalletAdvancedSwap } from './WalletAdvancedSwap';
import { useWalletContext } from './WalletProvider';
import { Send } from './wallet-advanced-send/components/Send';

export function WalletAdvancedContent({
children,
Expand All @@ -23,7 +24,7 @@ export function WalletAdvancedContent({
breakpoint,
} = useWalletContext();

const { showQr, showSwap, tokenBalances, animations } =
const { activeFeature, tokenBalances, animations } =
useWalletAdvancedContext();

const handleBottomSheetClose = useCallback(() => {
Expand All @@ -38,15 +39,23 @@ export function WalletAdvancedContent({
}, [isSubComponentClosing, setIsSubComponentOpen, setIsSubComponentClosing]);

const content = useMemo(() => {
if (showQr) {
if (activeFeature === 'send') {
return (
<ContentWrapper>
<Send className={cn('h-full w-full border-none')} />
</ContentWrapper>
);
}

if (activeFeature === 'qr') {
return (
<ContentWrapper>
<WalletAdvancedQrReceive classNames={classNames?.qr} />
</ContentWrapper>
);
}

if (showSwap) {
if (activeFeature === 'swap') {
return (
<ContentWrapper>
<WalletAdvancedSwap
Expand Down Expand Up @@ -75,7 +84,7 @@ export function WalletAdvancedContent({
}

return <ContentWrapper className="px-4 py-3">{children}</ContentWrapper>;
}, [showQr, showSwap, swappableTokens, tokenBalances, children, classNames]);
}, [activeFeature, swappableTokens, tokenBalances, children, classNames]);

if (breakpoint === 'sm') {
return (
Expand Down
24 changes: 11 additions & 13 deletions src/wallet/components/WalletAdvancedProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { RequestContext } from '@/core/network/constants';
import { useValue } from '@/internal/hooks/useValue';
import { usePortfolio } from '@/wallet/hooks/usePortfolio';
import { type ReactNode, createContext, useContext, useState } from 'react';
import type { WalletAdvancedContextType } from '../types';
import type {
WalletAdvancedContextType,
WalletAdvancedFeature,
} from '../types';
import { useWalletContext } from './WalletProvider';

type WalletAdvancedProviderReact = {
Expand All @@ -29,10 +32,9 @@ export function WalletAdvancedProvider({
}: WalletAdvancedProviderReact) {
const { address, isSubComponentClosing, showSubComponentAbove } =
useWalletContext();
const [showSwap, setShowSwap] = useState(false);
const [isSwapClosing, setIsSwapClosing] = useState(false);
const [showQr, setShowQr] = useState(false);
const [isQrClosing, setIsQrClosing] = useState(false);
const [activeFeature, setActiveFeature] =
useState<WalletAdvancedFeature | null>(null);
const [isActiveFeatureClosing, setIsActiveFeatureClosing] = useState(false);
const {
data: portfolioData,
refetch: refetchPortfolioData,
Expand All @@ -49,14 +51,10 @@ export function WalletAdvancedProvider({
);

const value = useValue({
showSwap,
setShowSwap,
isSwapClosing,
setIsSwapClosing,
showQr,
setShowQr,
isQrClosing,
setIsQrClosing,
activeFeature,
setActiveFeature,
isActiveFeatureClosing,
setIsActiveFeatureClosing,
tokenBalances,
portfolioFiatValue,
isFetchingPortfolioData,
Expand Down
20 changes: 12 additions & 8 deletions src/wallet/components/WalletAdvancedQrReceive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,24 @@
classNames,
}: WalletAdvancedQrReceiveProps) {
const { address } = useWalletContext();
const { setShowQr, isQrClosing, setIsQrClosing } = useWalletAdvancedContext();
const {
setActiveFeature,
isActiveFeatureClosing,
setIsActiveFeatureClosing,
} = useWalletAdvancedContext();
const [copyText, setCopyText] = useState('Copy');
const [copyButtonText, setCopyButtonText] = useState('Copy address');

const handleCloseQr = useCallback(() => {
setIsQrClosing(true);
}, [setIsQrClosing]);
setIsActiveFeatureClosing(true);

Check failure on line 28 in src/wallet/components/WalletAdvancedQrReceive.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

Unhandled error

TypeError: setIsActiveFeatureClosing is not a function ❯ src/wallet/components/WalletAdvancedQrReceive.tsx:28:5 ❯ HTMLUnknownElement.callCallback node_modules/react-dom/cjs/react-dom.development.js:4164:14 ❯ HTMLUnknownElement.callTheUserObjectsOperation node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30 ❯ innerInvokeEventListeners node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25 ❯ invokeEventListeners node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3 ❯ HTMLUnknownElementImpl._dispatch node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9 ❯ HTMLUnknownElementImpl.dispatchEvent node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17 ❯ HTMLUnknownElement.dispatchEvent node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34 ❯ Object.invokeGuardedCallbackDev node_modules/react-dom/cjs/react-dom.development.js:4213:16 ❯ invokeGuardedCallback node_modules/react-dom/cjs/react-dom.development.js:4277:31 This error originated in "src/wallet/components/WalletAdvancedQrReceive.test.tsx" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "should close when back button is clicked". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.
}, [setIsActiveFeatureClosing]);

const handleAnimationEnd = useCallback(() => {
if (isQrClosing) {
setShowQr(false);
setIsQrClosing(false);
if (isActiveFeatureClosing) {
setActiveFeature(null);
setIsActiveFeatureClosing(false);
}
}, [isQrClosing, setShowQr, setIsQrClosing]);
}, [isActiveFeatureClosing, setActiveFeature, setIsActiveFeatureClosing]);

const resetAffordanceText = useCallback(() => {
setTimeout(() => {
Expand Down Expand Up @@ -68,7 +72,7 @@
'flex flex-col items-center justify-between',
'h-full w-full',
'px-4 pt-3 pb-4',
isQrClosing
isActiveFeatureClosing
? 'fade-out slide-out-to-left-5 animate-out fill-mode-forwards ease-in-out'
: 'fade-in slide-in-from-left-5 linear animate-in duration-150',
classNames?.container,
Expand Down
Loading
Loading