From 54aa5329ba3a9cecaf05f428a283b39202aca0d9 Mon Sep 17 00:00:00 2001 From: JCNoguera <88061365+VmMad@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:23:24 +0100 Subject: [PATCH 01/56] feat(wallet-dashboard): add styles for Review & Send screen --- apps/core/package.json | 2 + .../src/components/coin/CoinIcon.tsx} | 43 ++++-- apps/core/src/components/coin/index.ts | 4 + .../src/components/icon/ImageIcon.tsx} | 14 +- apps/core/src/components/icon/index.ts | 4 + apps/core/src/components/index.ts | 3 + .../Dialogs/SendAndReviewDialog.tsx | 131 ++++++++++++++++++ .../components/Dialogs/index.ts | 4 + .../Popups/SendCoinPopup/SendCoinPopup.tsx | 2 + .../views/ReviewValuesFormView.tsx | 43 +++--- .../lib/constants/gas.constants.ts | 4 + apps/wallet-dashboard/lib/constants/index.ts | 1 + apps/wallet-dashboard/tailwind.config.ts | 1 + .../src/ui/app/components/DAppInfoCard.tsx | 2 +- .../components/active-coins-card/CoinItem.tsx | 4 +- apps/wallet/src/ui/app/components/index.ts | 1 - .../ui/app/components/iota-apps/IotaApp.tsx | 2 +- .../app/components/receipt-card/TxnAmount.tsx | 4 +- .../ui/app/pages/home/transfer-coin/index.tsx | 5 +- .../objectSummary/ObjectChangeDisplay.tsx | 3 +- .../src/ui/app/staking/home/StakedCard.tsx | 2 +- .../app/staking/validators/ValidatorLogo.tsx | 3 +- pnpm-lock.yaml | 42 +++--- 23 files changed, 255 insertions(+), 69 deletions(-) rename apps/{wallet/src/ui/app/components/coin-icon/index.tsx => core/src/components/coin/CoinIcon.tsx} (52%) create mode 100644 apps/core/src/components/coin/index.ts rename apps/{wallet/src/ui/app/shared/image-icon/index.tsx => core/src/components/icon/ImageIcon.tsx} (82%) create mode 100644 apps/core/src/components/icon/index.ts create mode 100644 apps/wallet-dashboard/components/Dialogs/SendAndReviewDialog.tsx create mode 100644 apps/wallet-dashboard/components/Dialogs/index.ts create mode 100644 apps/wallet-dashboard/lib/constants/gas.constants.ts diff --git a/apps/core/package.json b/apps/core/package.json index 31cb3e43e8f..ee3fcb5330f 100644 --- a/apps/core/package.json +++ b/apps/core/package.json @@ -29,9 +29,11 @@ "@iota/dapp-kit": "workspace:*", "@iota/iota-sdk": "workspace:*", "@iota/kiosk": "workspace:*", + "@iota/ui-icons": "workspace:*", "@sentry/react": "^7.59.2", "@tanstack/react-query": "^5.50.1", "bignumber.js": "^9.1.1", + "clsx": "^2.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.45.2", diff --git a/apps/wallet/src/ui/app/components/coin-icon/index.tsx b/apps/core/src/components/coin/CoinIcon.tsx similarity index 52% rename from apps/wallet/src/ui/app/components/coin-icon/index.tsx rename to apps/core/src/components/coin/CoinIcon.tsx index 5214a1e7420..c821ffb8168 100644 --- a/apps/wallet/src/ui/app/components/coin-icon/index.tsx +++ b/apps/core/src/components/coin/CoinIcon.tsx @@ -2,10 +2,11 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ImageIcon, ImageIconSize } from '_app/shared/image-icon'; -import { useCoinMetadata } from '@iota/core'; -import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import React from 'react'; +import { useCoinMetadata } from '../../hooks'; import { IotaLogoMark } from '@iota/ui-icons'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import { ImageIcon, ImageIconSize } from '../icon'; import cx from 'clsx'; interface NonIotaCoinProps { @@ -28,19 +29,45 @@ function NonIotaCoin({ coinType, size = ImageIconSize.Full, rounded }: NonIotaCo ); } - export interface CoinIconProps { coinType: string; size?: ImageIconSize; rounded?: boolean; + hasCoinWrapper?: boolean; } -export function CoinIcon({ coinType, size = ImageIconSize.Full, rounded }: CoinIconProps) { +export function CoinIcon({ + coinType, + size = ImageIconSize.Full, + rounded, + hasCoinWrapper, +}: CoinIconProps) { + const Component = hasCoinWrapper ? CoinIconWrapper : React.Fragment; return coinType === IOTA_TYPE_ARG ? ( -
- -
+ +
+ +
+
) : ( ); } + +type CoinIconWrapperProps = React.PropsWithChildren> & { + hasBorder?: boolean; +}; + +export function CoinIconWrapper({ children, size, hasBorder }: CoinIconWrapperProps) { + return ( +
+ {children} +
+ ); +} diff --git a/apps/core/src/components/coin/index.ts b/apps/core/src/components/coin/index.ts new file mode 100644 index 00000000000..f3b244cad16 --- /dev/null +++ b/apps/core/src/components/coin/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +export * from './CoinIcon'; diff --git a/apps/wallet/src/ui/app/shared/image-icon/index.tsx b/apps/core/src/components/icon/ImageIcon.tsx similarity index 82% rename from apps/wallet/src/ui/app/shared/image-icon/index.tsx rename to apps/core/src/components/icon/ImageIcon.tsx index 5ae46570b21..031fbbf13d6 100644 --- a/apps/wallet/src/ui/app/shared/image-icon/index.tsx +++ b/apps/core/src/components/icon/ImageIcon.tsx @@ -1,8 +1,7 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung +// Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { useState } from 'react'; +import React, { useState } from 'react'; import cn from 'clsx'; export enum ImageIconSize { @@ -37,16 +36,17 @@ function FallBackAvatar({ case ImageIconSize.Medium: return 'text-label-md'; case ImageIconSize.Large: - return 'text-title-md'; - case ImageIconSize.Full: return 'text-title-lg'; + case ImageIconSize.Full: + return 'text-display-lg'; } } return (
diff --git a/apps/core/src/components/icon/index.ts b/apps/core/src/components/icon/index.ts new file mode 100644 index 00000000000..3fda2c89887 --- /dev/null +++ b/apps/core/src/components/icon/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +export * from './ImageIcon'; diff --git a/apps/core/src/components/index.ts b/apps/core/src/components/index.ts index 67d6dc1fbc3..fe43fbae4ac 100644 --- a/apps/core/src/components/index.ts +++ b/apps/core/src/components/index.ts @@ -2,3 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 export * from './KioskClientProvider'; + +export * from './coin'; +export * from './icon'; diff --git a/apps/wallet-dashboard/components/Dialogs/SendAndReviewDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendAndReviewDialog.tsx new file mode 100644 index 00000000000..4156acae6d3 --- /dev/null +++ b/apps/wallet-dashboard/components/Dialogs/SendAndReviewDialog.tsx @@ -0,0 +1,131 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { GAS_SYMBOL } from '@/lib/constants'; +import { + Button, + Dialog, + DialogContent, + DialogBody, + Header, + Card, + CardType, + CardImage, + ImageType, + CardBody, + CardAction, + CardActionType, + KeyValueInfo, + Divider, + ButtonType, + DialogPosition, +} from '@iota/apps-ui-kit'; +import { CoinIcon, ImageIconSize, parseAmount, useCoinMetadata, useFormatCoin } from '@iota/core'; +import { formatAddress } from '@iota/iota-sdk/utils'; +import { Loader } from '@iota/ui-icons'; + +export type SendAndReviewDialogProps = { + coinType: string; + to: string; + amount: string; + approximation?: boolean; + gasBudget?: string; + open: boolean; + setOpen?: (open: boolean) => void; + onSend: () => void; + isPending?: boolean; + senderAddress: string; + onClose: () => void; + onBack: () => void; +}; + +export function SendAndReviewDialog({ + coinType, + senderAddress, + to, + amount, + approximation, + gasBudget, + open, + setOpen, + onSend, + isPending, + onClose, + onBack, +}: SendAndReviewDialogProps): React.JSX.Element { + const { data: metadata } = useCoinMetadata(coinType); + const amountWithoutDecimals = parseAmount(amount, metadata?.decimals ?? 0); + const [formatAmount, symbol] = useFormatCoin(Math.abs(Number(amountWithoutDecimals)), coinType); + + return ( + + +
+
+
+
+ +
+
+ {Number(amount) !== 0 ? ( + + + + + + + + ) : null} +
+ + + + + + + +
+
+
+
+ +
+
+
+ +
+ ); +} diff --git a/apps/wallet-dashboard/components/Dialogs/index.ts b/apps/wallet-dashboard/components/Dialogs/index.ts new file mode 100644 index 00000000000..8b7f60796b2 --- /dev/null +++ b/apps/wallet-dashboard/components/Dialogs/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +export * from './SendAndReviewDialog'; diff --git a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/SendCoinPopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/SendCoinPopup.tsx index c3f917df955..1e99fcd3ac5 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/SendCoinPopup.tsx +++ b/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/SendCoinPopup.tsx @@ -120,6 +120,8 @@ function SendCoinPopup({ gasBudget={sendCoinData?.gasBudget?.toString() || '--'} error={error?.message} isPending={isPending} + coinType={selectedCoin.coinType} + onClose={onClose} /> )} diff --git a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/ReviewValuesFormView.tsx b/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/ReviewValuesFormView.tsx index 0732b263cfc..cd59277f542 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/ReviewValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/ReviewValuesFormView.tsx @@ -1,8 +1,9 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { SendAndReviewDialog } from '@/components/Dialogs'; import { FormDataValues } from '../SendCoinPopup'; -import { Button } from '@/components'; +import { useState } from 'react'; interface ReviewValuesFormProps { formData: FormDataValues; @@ -12,36 +13,38 @@ interface ReviewValuesFormProps { isPending: boolean; executeTransfer: () => void; onBack: () => void; + coinType: string; + onClose: () => void; } function ReviewValuesFormView({ formData: { amount, recipientAddress }, senderAddress, gasBudget, - error, isPending, executeTransfer, onBack, + coinType, + onClose, }: ReviewValuesFormProps): JSX.Element { + const [open, setOpen] = useState(true); return ( -
-

Review & Send

-
-

Sending: {amount}

-

From: {senderAddress}

-

To: {recipientAddress}

-

Gas fee: {gasBudget}

-
- {error ? {error} : null} -
- - {isPending ? ( - - ) : ( - - )} -
-
+ { + setOpen(false); + onClose(); + }} + onBack={onBack} + /> ); } export default ReviewValuesFormView; diff --git a/apps/wallet-dashboard/lib/constants/gas.constants.ts b/apps/wallet-dashboard/lib/constants/gas.constants.ts new file mode 100644 index 00000000000..5b45d7e3c8e --- /dev/null +++ b/apps/wallet-dashboard/lib/constants/gas.constants.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +export const GAS_SYMBOL = 'IOTA'; diff --git a/apps/wallet-dashboard/lib/constants/index.ts b/apps/wallet-dashboard/lib/constants/index.ts index a7c8637513f..46a4dcdb069 100644 --- a/apps/wallet-dashboard/lib/constants/index.ts +++ b/apps/wallet-dashboard/lib/constants/index.ts @@ -3,3 +3,4 @@ export * from './time.constants'; export * from './vesting.constants'; +export * from './gas.constants'; diff --git a/apps/wallet-dashboard/tailwind.config.ts b/apps/wallet-dashboard/tailwind.config.ts index e8c9079ca3a..2ca1f1c0824 100644 --- a/apps/wallet-dashboard/tailwind.config.ts +++ b/apps/wallet-dashboard/tailwind.config.ts @@ -12,6 +12,7 @@ export default { './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './../ui-kit/src/lib/**/*.{js,jsx,ts,tsx}', + './../core/src/components/**/*.{js,jsx,ts,tsx}', ], darkMode: 'class', theme: { diff --git a/apps/wallet/src/ui/app/components/DAppInfoCard.tsx b/apps/wallet/src/ui/app/components/DAppInfoCard.tsx index 87cea050c73..6824f2978c5 100644 --- a/apps/wallet/src/ui/app/components/DAppInfoCard.tsx +++ b/apps/wallet/src/ui/app/components/DAppInfoCard.tsx @@ -12,7 +12,7 @@ import { useUnlockAccount } from './accounts/UnlockAccountContext'; import { DAppPermissionList } from './DAppPermissionList'; import { SummaryCard } from './SummaryCard'; import { Link } from 'react-router-dom'; -import { ImageIcon } from '../shared/image-icon'; +import { ImageIcon } from '@iota/core'; export interface DAppInfoCardProps { name: string; diff --git a/apps/wallet/src/ui/app/components/active-coins-card/CoinItem.tsx b/apps/wallet/src/ui/app/components/active-coins-card/CoinItem.tsx index 5e7dce765f5..324d553f997 100644 --- a/apps/wallet/src/ui/app/components/active-coins-card/CoinItem.tsx +++ b/apps/wallet/src/ui/app/components/active-coins-card/CoinItem.tsx @@ -2,8 +2,7 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { CoinIcon } from '_components'; -import { useFormatCoin } from '@iota/core'; +import { useFormatCoin, ImageIconSize, CoinIcon } from '@iota/core'; import { type ReactNode } from 'react'; import { Card, @@ -15,7 +14,6 @@ import { ImageType, } from '@iota/apps-ui-kit'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { ImageIconSize } from '../../shared/image-icon'; interface CoinItemProps { coinType: string; diff --git a/apps/wallet/src/ui/app/components/index.ts b/apps/wallet/src/ui/app/components/index.ts index f162a483b93..dbab474d008 100644 --- a/apps/wallet/src/ui/app/components/index.ts +++ b/apps/wallet/src/ui/app/components/index.ts @@ -15,7 +15,6 @@ export * from './accounts'; export * from './active-coins-card'; export * from './active-coins-card/CoinItem'; export * from './address-input'; -export * from './coin-icon'; export * from './error-boundary'; export * from './explorer-link'; export * from './explorer-link/Explorer'; diff --git a/apps/wallet/src/ui/app/components/iota-apps/IotaApp.tsx b/apps/wallet/src/ui/app/components/iota-apps/IotaApp.tsx index ee69a96a670..aa4c2dc7e4b 100644 --- a/apps/wallet/src/ui/app/components/iota-apps/IotaApp.tsx +++ b/apps/wallet/src/ui/app/components/iota-apps/IotaApp.tsx @@ -2,7 +2,7 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ImageIcon, ImageIconSize } from '_app/shared/image-icon'; +import { ImageIcon, ImageIconSize } from '@iota/core'; import { ExternalLink } from '_components'; import { ampli } from '_src/shared/analytics/ampli'; import { getDAppUrl } from '_src/shared/utils'; diff --git a/apps/wallet/src/ui/app/components/receipt-card/TxnAmount.tsx b/apps/wallet/src/ui/app/components/receipt-card/TxnAmount.tsx index 623ed671b9a..a993b4657ca 100644 --- a/apps/wallet/src/ui/app/components/receipt-card/TxnAmount.tsx +++ b/apps/wallet/src/ui/app/components/receipt-card/TxnAmount.tsx @@ -2,7 +2,7 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { useFormatCoin } from '@iota/core'; +import { useFormatCoin, ImageIconSize, CoinIcon } from '@iota/core'; import { Card, CardAction, @@ -12,8 +12,6 @@ import { CardType, ImageType, } from '@iota/apps-ui-kit'; -import { CoinIcon } from '../coin-icon'; -import { ImageIconSize } from '../../shared/image-icon'; interface TxnAmountProps { amount: string | number | bigint; diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx index 111017d0bb0..84224ad3ae4 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx @@ -2,7 +2,7 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { CoinIcon, Loading, Overlay } from '_components'; +import { Loading, Overlay } from '_components'; import { ampli } from '_src/shared/analytics/ampli'; import { getSignerOperationErrorMessage } from '_src/ui/app/helpers/errorMessages'; import { useActiveAccount } from '_src/ui/app/hooks/useActiveAccount'; @@ -13,6 +13,8 @@ import { filterAndSortTokenBalances, useCoinMetadata, useFormatCoin, + ImageIconSize, + CoinIcon, } from '@iota/core'; // import * as Sentry from '@sentry/react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; @@ -26,7 +28,6 @@ import { Select, Button, type SelectOption, ButtonType } from '@iota/apps-ui-kit import { useActiveAddress, useCoinsReFetchingConfig } from '_src/ui/app/hooks'; import { useIotaClientQuery } from '@iota/dapp-kit'; import type { CoinBalance } from '@iota/iota-sdk/client'; -import { ImageIconSize } from '_src/ui/app/shared/image-icon'; import { Loader } from '@iota/ui-icons'; function TransferCoinPage() { diff --git a/apps/wallet/src/ui/app/shared/transaction-summary/cards/objectSummary/ObjectChangeDisplay.tsx b/apps/wallet/src/ui/app/shared/transaction-summary/cards/objectSummary/ObjectChangeDisplay.tsx index 5289019a18d..ce6bfd0acea 100644 --- a/apps/wallet/src/ui/app/shared/transaction-summary/cards/objectSummary/ObjectChangeDisplay.tsx +++ b/apps/wallet/src/ui/app/shared/transaction-summary/cards/objectSummary/ObjectChangeDisplay.tsx @@ -3,10 +3,9 @@ // SPDX-License-Identifier: Apache-2.0 import { ExplorerLink, ExplorerLinkType } from '_components'; -import { type IotaObjectChangeWithDisplay } from '@iota/core'; +import { type IotaObjectChangeWithDisplay, ImageIcon } from '@iota/core'; import { Card, CardAction, CardActionType, CardBody, CardImage, CardType } from '@iota/apps-ui-kit'; -import { ImageIcon } from '../../../image-icon'; import { ArrowTopRight } from '@iota/ui-icons'; export function ObjectChangeDisplay({ change }: { change: IotaObjectChangeWithDisplay }) { diff --git a/apps/wallet/src/ui/app/staking/home/StakedCard.tsx b/apps/wallet/src/ui/app/staking/home/StakedCard.tsx index 9ed1c130455..b945fbe025b 100644 --- a/apps/wallet/src/ui/app/staking/home/StakedCard.tsx +++ b/apps/wallet/src/ui/app/staking/home/StakedCard.tsx @@ -10,12 +10,12 @@ import { useFormatCoin, useGetTimeBeforeEpochNumber, useTimeAgo, + ImageIcon, } from '@iota/core'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; import { Card, CardImage, CardType, CardBody, CardAction, CardActionType } from '@iota/apps-ui-kit'; import { useMemo } from 'react'; import { Link } from 'react-router-dom'; -import { ImageIcon } from '../../shared/image-icon'; import { useIotaClientQuery } from '@iota/dapp-kit'; diff --git a/apps/wallet/src/ui/app/staking/validators/ValidatorLogo.tsx b/apps/wallet/src/ui/app/staking/validators/ValidatorLogo.tsx index 961cadc895b..dcb8954a274 100644 --- a/apps/wallet/src/ui/app/staking/validators/ValidatorLogo.tsx +++ b/apps/wallet/src/ui/app/staking/validators/ValidatorLogo.tsx @@ -1,7 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ImageIcon, ImageIconSize } from '_app/shared/image-icon'; import { Badge, BadgeType, @@ -15,7 +14,7 @@ import { import { useIotaClientQuery } from '@iota/dapp-kit'; import { formatAddress } from '@iota/iota-sdk/utils'; import { useMemo } from 'react'; -import { formatPercentageDisplay, useGetValidatorsApy } from '@iota/core'; +import { formatPercentageDisplay, useGetValidatorsApy, ImageIcon, ImageIconSize } from '@iota/core'; interface ValidatorLogoProps { validatorAddress: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92e2c8a9a2f..460c79809ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -179,7 +179,7 @@ importers: version: 5.2.1(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + version: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) prettier: specifier: ^3.3.1 version: 3.3.3 @@ -191,7 +191,7 @@ importers: version: 6.3.4 ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2) ts-loader: specifier: ^9.4.4 version: 9.5.1(typescript@5.6.2)(webpack@5.95.0(@swc/core@1.7.28)) @@ -231,6 +231,9 @@ importers: '@iota/kiosk': specifier: workspace:* version: link:../../sdk/kiosk + '@iota/ui-icons': + specifier: workspace:* + version: link:../ui-icons '@sentry/react': specifier: ^7.59.2 version: 7.119.0(react@18.3.1) @@ -240,6 +243,9 @@ importers: bignumber.js: specifier: ^9.1.1 version: 9.1.2 + clsx: + specifier: ^2.1.1 + version: 2.1.1 react: specifier: ^18.3.1 version: 18.3.1 @@ -1045,7 +1051,7 @@ importers: version: 14.2.3(eslint@8.57.1)(typescript@5.6.2) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + version: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) postcss: specifier: ^8.4.31 version: 8.4.47 @@ -1054,7 +1060,7 @@ importers: version: 3.4.13(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2) typescript: specifier: ^5.5.3 version: 5.6.2 @@ -20540,7 +20546,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2))': + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@10.0.0) @@ -20554,7 +20560,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -26471,13 +26477,13 @@ snapshots: crc-32@1.2.2: {} - create-jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)): + create-jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -29686,16 +29692,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)): + jest-cli@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + create-jest: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -29707,7 +29713,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)): + jest-config@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)): dependencies: '@babel/core': 7.25.2 '@jest/test-sequencer': 29.7.0 @@ -29964,12 +29970,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)): + jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest-cli: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) optionalDependencies: node-notifier: 10.0.0 transitivePeerDependencies: @@ -34556,12 +34562,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 From cc2a855362c75322e9f9f4dfef7ca2fd79e730b1 Mon Sep 17 00:00:00 2001 From: JCNoguera <88061365+VmMad@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:23:58 +0100 Subject: [PATCH 02/56] fix: move CoinIcon to core --- .../{ => Send}/SendAndReviewDialog.tsx | 0 .../components/Dialogs/Send/index.ts | 4 ++ .../components/Dialogs/index.ts | 2 +- .../components/coins/CoinIcon.tsx | 45 ------------------- .../components/coins/CoinItem.tsx | 10 +++-- .../components/coins/index.ts | 1 - 6 files changed, 12 insertions(+), 50 deletions(-) rename apps/wallet-dashboard/components/Dialogs/{ => Send}/SendAndReviewDialog.tsx (100%) create mode 100644 apps/wallet-dashboard/components/Dialogs/Send/index.ts delete mode 100644 apps/wallet-dashboard/components/coins/CoinIcon.tsx diff --git a/apps/wallet-dashboard/components/Dialogs/SendAndReviewDialog.tsx b/apps/wallet-dashboard/components/Dialogs/Send/SendAndReviewDialog.tsx similarity index 100% rename from apps/wallet-dashboard/components/Dialogs/SendAndReviewDialog.tsx rename to apps/wallet-dashboard/components/Dialogs/Send/SendAndReviewDialog.tsx diff --git a/apps/wallet-dashboard/components/Dialogs/Send/index.ts b/apps/wallet-dashboard/components/Dialogs/Send/index.ts new file mode 100644 index 00000000000..8b7f60796b2 --- /dev/null +++ b/apps/wallet-dashboard/components/Dialogs/Send/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +export * from './SendAndReviewDialog'; diff --git a/apps/wallet-dashboard/components/Dialogs/index.ts b/apps/wallet-dashboard/components/Dialogs/index.ts index 74898078827..f691a041c8f 100644 --- a/apps/wallet-dashboard/components/Dialogs/index.ts +++ b/apps/wallet-dashboard/components/Dialogs/index.ts @@ -1,5 +1,5 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -export * from './SendAndReviewDialog'; +export * from './Send'; export * from './Staking'; diff --git a/apps/wallet-dashboard/components/coins/CoinIcon.tsx b/apps/wallet-dashboard/components/coins/CoinIcon.tsx deleted file mode 100644 index a5c24107baa..00000000000 --- a/apps/wallet-dashboard/components/coins/CoinIcon.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { useCoinMetadata } from '@iota/core'; -import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { IotaLogoMark } from '@iota/ui-icons'; -import cx from 'clsx'; -import { ImageIcon, ImageIconSize } from '../ImageIcon'; - -interface NonIotaCoinProps { - coinType: string; - size?: ImageIconSize; - rounded?: boolean; -} - -function NonIotaCoin({ coinType, size = ImageIconSize.Full, rounded }: NonIotaCoinProps) { - const { data: coinMeta } = useCoinMetadata(coinType); - return ( -
- -
- ); -} - -export interface CoinIconProps { - coinType: string; - size?: ImageIconSize; - rounded?: boolean; -} - -export function CoinIcon({ coinType, size = ImageIconSize.Full, rounded }: CoinIconProps) { - return coinType === IOTA_TYPE_ARG ? ( -
- -
- ) : ( - - ); -} diff --git a/apps/wallet-dashboard/components/coins/CoinItem.tsx b/apps/wallet-dashboard/components/coins/CoinItem.tsx index 29e8be5aa35..26d6ba35f01 100644 --- a/apps/wallet-dashboard/components/coins/CoinItem.tsx +++ b/apps/wallet-dashboard/components/coins/CoinItem.tsx @@ -10,11 +10,10 @@ import { CardType, ImageType, } from '@iota/apps-ui-kit'; -import { useFormatCoin } from '@iota/core'; +import { useFormatCoin, CoinIcon } from '@iota/core'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; import { type ReactNode } from 'react'; import { ImageIconSize } from '../ImageIcon'; -import { CoinIcon } from './CoinIcon'; interface CoinItemProps { coinType: string; @@ -40,7 +39,12 @@ function CoinItem({
- +
Date: Wed, 30 Oct 2024 18:43:20 +0100 Subject: [PATCH 03/56] feat(wallet-dashboard): style send entry screen WIP --- .../components/Coins/MyCoins.tsx | 30 +-- .../components/Dialogs/SendTokenDialog.tsx | 216 ++++++++++++++++++ .../components/Dialogs/index.ts | 4 + apps/wallet-dashboard/components/index.ts | 1 + apps/wallet-dashboard/package.json | 1 + pnpm-lock.yaml | 39 ++-- 6 files changed, 258 insertions(+), 33 deletions(-) create mode 100644 apps/wallet-dashboard/components/Dialogs/SendTokenDialog.tsx create mode 100644 apps/wallet-dashboard/components/Dialogs/index.ts diff --git a/apps/wallet-dashboard/components/Coins/MyCoins.tsx b/apps/wallet-dashboard/components/Coins/MyCoins.tsx index 6fb6e8eed49..c89a2bc39c6 100644 --- a/apps/wallet-dashboard/components/Coins/MyCoins.tsx +++ b/apps/wallet-dashboard/components/Coins/MyCoins.tsx @@ -1,10 +1,9 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import React from 'react'; +import React, { useState } from 'react'; import { useCurrentAccount, useIotaClientQuery } from '@iota/dapp-kit'; -import { CoinItem, SendCoinPopup } from '@/components'; -import { usePopups } from '@/hooks'; +import { CoinItem, SendTokenDialog } from '@/components'; import { CoinBalance } from '@iota/iota-sdk/client'; import { COINS_QUERY_REFETCH_INTERVAL, @@ -14,9 +13,10 @@ import { } from '@iota/core'; function MyCoins(): React.JSX.Element { - const { openPopup, closePopup } = usePopups(); const account = useCurrentAccount(); const activeAccountAddress = account?.address; + const [isSendTokenDialogOpen, setIsSendTokenDialogOpen] = useState(false); + const [selectedCoinType, setSelectedCoinType] = useState(''); const { data: coinBalances } = useIotaClientQuery( 'getAllBalances', @@ -30,16 +30,10 @@ function MyCoins(): React.JSX.Element { ); const { recognized, unrecognized } = useSortedCoinsByCategories(coinBalances ?? []); - function openSendTokenPopup(coin: CoinBalance, address: string): void { + function openSendTokenPopup(coin: CoinBalance): void { if (coinBalances) { - openPopup( - , - ); + setIsSendTokenDialogOpen(true); + setSelectedCoinType(coin.coinType); } } @@ -52,7 +46,7 @@ function MyCoins(): React.JSX.Element { key={index} coinType={coin.coinType} balance={BigInt(coin.totalBalance)} - onClick={() => openSendTokenPopup(coin, account?.address ?? '')} + onClick={() => openSendTokenPopup(coin)} /> ); })} @@ -63,10 +57,16 @@ function MyCoins(): React.JSX.Element { key={index} coinType={coin.coinType} balance={BigInt(coin.totalBalance)} - onClick={() => openSendTokenPopup(coin, account?.address ?? '')} + onClick={() => openSendTokenPopup(coin)} /> ); })} +
); } diff --git a/apps/wallet-dashboard/components/Dialogs/SendTokenDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendTokenDialog.tsx new file mode 100644 index 00000000000..73773f480a3 --- /dev/null +++ b/apps/wallet-dashboard/components/Dialogs/SendTokenDialog.tsx @@ -0,0 +1,216 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { + InfoBox, + InfoBoxStyle, + InfoBoxType, + ButtonType, + ButtonHtmlType, + Button, + Dialog, + DialogContent, + DialogBody, + Header, + DialogPosition, +} from '@iota/apps-ui-kit'; +import { parseAmount, useCoinMetadata, useGetAllCoins, useIotaAddressValidation } from '@iota/core'; +import { CoinStruct } from '@iota/iota-sdk/client'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import { Exclamation } from '@iota/ui-icons'; +import { Field, Form, Formik, useFormikContext } from 'formik'; +import Input from '../Input'; +import { ChangeEventHandler, useCallback } from 'react'; + +const INITIAL_VALUES = { + to: '', + amount: '', + isPayAllIota: false, + gasBudgetEst: '', +}; + +export type FormValues = typeof INITIAL_VALUES; + +export type SubmitProps = { + to: string; + amount: string; + isPayAllIota: boolean; + coinIds: string[]; + coins: CoinStruct[]; + gasBudgetEst: string; +}; + +export type SendTokenFormProps = { + coinType: string; + activeAddress: string; + setOpen: (bool: boolean) => void; + open: boolean; +}; + +function totalBalance(coins: CoinStruct[]): bigint { + return coins.reduce((partialSum, c) => partialSum + getBalanceFromCoinStruct(c), BigInt(0)); +} +function getBalanceFromCoinStruct(coin: CoinStruct): bigint { + return BigInt(coin.balance); +} + +export function SendTokenDialog({ + coinType, + activeAddress, + setOpen, + open, +}: SendTokenFormProps): React.JSX.Element { + const { data: coinsData } = useGetAllCoins(coinType, activeAddress!); + const { setFieldValue, validateField } = useFormikContext(); + const iotaAddressValidation = useIotaAddressValidation(); + + const { data: iotaCoinsData } = useGetAllCoins(IOTA_TYPE_ARG, activeAddress!); + + const iotaCoins = iotaCoinsData; + const coins = coinsData; + const coinBalance = totalBalance(coins || []); + const iotaBalance = totalBalance(iotaCoins || []); + + const coinMetadata = useCoinMetadata(coinType); + const coinDecimals = coinMetadata.data?.decimals ?? 0; + + // const validationSchemaStepOne = useMemo( + // () => createValidationSchemaStepOne(coinBalance, symbol, coinDecimals), + // [client, coinBalance, symbol, coinDecimals], + // ); + + // remove the comma from the token balance + const initAmountBig = parseAmount('0', coinDecimals); + // const initAmountBig = parseAmount(initialAmount, coinDecimals); + + const handleAddressChange = useCallback>( + (e) => { + const address = e.currentTarget.value; + setFieldValue(activeAddress, iotaAddressValidation.cast(address)).then(() => { + validateField(activeAddress); + }); + }, + [setFieldValue, activeAddress, iotaAddressValidation], + ); + + async function handleFormSubmit({ to, amount, isPayAllIota, gasBudgetEst }: FormValues) { + if (!coins || !iotaCoins) return; + const coinsIDs = [...coins] + .sort((a, b) => Number(b.balance) - Number(a.balance)) + .map(({ coinObjectId }) => coinObjectId); + + const data = { + to, + amount, + isPayAllIota, + coins, + coinIds: coinsIDs, + gasBudgetEst, + }; + console.log('data', data); + + // onSubmit(data); + } + + return ( + + +
setOpen(false)} /> + + + {({ isValid, isSubmitting, setFieldValue, values, submitForm }) => { + const newPayIotaAll = + parseAmount(values.amount, coinDecimals) === coinBalance && + coinType === IOTA_TYPE_ARG; + if (values.isPayAllIota !== newPayIotaAll) { + setFieldValue('isPayAllIota', newPayIotaAll); + } + + const hasEnoughBalance = + values.isPayAllIota || + iotaBalance > + parseAmount(values.gasBudgetEst, coinDecimals) + + parseAmount( + coinType === IOTA_TYPE_ARG ? values.amount : '0', + coinDecimals, + ); + + return ( +
+
+
+ {!hasEnoughBalance ? ( + } + /> + ) : null} + + {/* */} + handleAddressChange(e)} + label="Enter recipient address" + /> + } + allowNegative={false} + name="to" + placeholder="Enter Address" + /> +
+
+ +
+
+
+ ); + }} +
+
+ +
+ ); +} diff --git a/apps/wallet-dashboard/components/Dialogs/index.ts b/apps/wallet-dashboard/components/Dialogs/index.ts new file mode 100644 index 00000000000..ea5b76591ee --- /dev/null +++ b/apps/wallet-dashboard/components/Dialogs/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +export * from './SendTokenDialog'; diff --git a/apps/wallet-dashboard/components/index.ts b/apps/wallet-dashboard/components/index.ts index e7f010c273f..b1cd5fcb8f9 100644 --- a/apps/wallet-dashboard/components/index.ts +++ b/apps/wallet-dashboard/components/index.ts @@ -18,3 +18,4 @@ export * from './Popup'; export * from './AppList'; export * from './Cards'; export * from './Buttons'; +export * from './Dialogs'; diff --git a/apps/wallet-dashboard/package.json b/apps/wallet-dashboard/package.json index e8a4a107e2f..937086581d5 100644 --- a/apps/wallet-dashboard/package.json +++ b/apps/wallet-dashboard/package.json @@ -23,6 +23,7 @@ "@tanstack/react-query": "^5.50.1", "@tanstack/react-virtual": "^3.5.0", "clsx": "^2.1.1", + "formik": "^2.4.2", "next": "14.2.10", "react": "^18.3.1", "react-hot-toast": "^2.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92e2c8a9a2f..9bc6c22f23a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -179,7 +179,7 @@ importers: version: 5.2.1(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + version: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) prettier: specifier: ^3.3.1 version: 3.3.3 @@ -191,7 +191,7 @@ importers: version: 6.3.4 ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2) ts-loader: specifier: ^9.4.4 version: 9.5.1(typescript@5.6.2)(webpack@5.95.0(@swc/core@1.7.28)) @@ -1015,6 +1015,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + formik: + specifier: ^2.4.2 + version: 2.4.6(react@18.3.1) next: specifier: 14.2.10 version: 14.2.10(@babel/core@7.25.2)(@playwright/test@1.47.2)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.3) @@ -1045,7 +1048,7 @@ importers: version: 14.2.3(eslint@8.57.1)(typescript@5.6.2) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + version: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) postcss: specifier: ^8.4.31 version: 8.4.47 @@ -1054,7 +1057,7 @@ importers: version: 3.4.13(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2) typescript: specifier: ^5.5.3 version: 5.6.2 @@ -20540,7 +20543,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2))': + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@10.0.0) @@ -20554,7 +20557,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -26471,13 +26474,13 @@ snapshots: crc-32@1.2.2: {} - create-jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)): + create-jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -29686,16 +29689,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)): + jest-cli@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + create-jest: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest-config: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -29707,7 +29710,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)): + jest-config@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)): dependencies: '@babel/core': 7.25.2 '@jest/test-sequencer': 29.7.0 @@ -29964,12 +29967,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)): + jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest-cli: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) optionalDependencies: node-notifier: 10.0.0 transitivePeerDependencies: @@ -34556,12 +34559,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@swc/core@1.7.28)(@types/node@20.16.9)(typescript@5.6.2)) + jest: 29.7.0(@types/node@20.16.9)(babel-plugin-macros@3.1.0)(node-notifier@10.0.0)(ts-node@10.9.2(@types/node@20.16.9)(typescript@5.6.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 From 79c283b9d0f117922229024250e0833fcf90d0e3 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Thu, 31 Oct 2024 18:31:50 +0100 Subject: [PATCH 04/56] feat(wallet-dashboard): style send entry screen WIP --- apps/core/package.json | 2 + apps/core/src/components/CoinSelector.tsx | 60 +++++ .../src/components/Inputs/AddressInput.tsx | 80 ++++++ apps/core/src/components/Inputs/FormInput.tsx | 73 +++++ .../components/Inputs/SendTokenFormInput.tsx | 63 +++++ apps/core/src/components/Inputs/index.ts | 6 + apps/core/src/components/index.ts | 3 + .../Dialogs/SendToken/SendCoinDialog.tsx | 128 +++++++++ .../SendToken}/index.ts | 4 +- .../SendToken/views/EnterValuesFormView.tsx | 254 ++++++++++++++++++ .../SendToken}/views/ReviewValuesFormView.tsx | 6 +- .../SendToken}/views/index.ts | 0 .../components/Dialogs/SendTokenDialog.tsx | 216 --------------- .../components/Dialogs/index.ts | 2 +- apps/wallet-dashboard/components/Dropdown.tsx | 53 ---- .../Popups/SendCoinPopup/SendCoinPopup.tsx | 129 --------- .../views/EnterValuesFormView.tsx | 74 ----- .../components/Popup/Popups/index.ts | 2 - .../account-balance/AccountBalance.tsx | 28 +- .../components/coins/MyCoins.tsx | 35 ++- apps/wallet-dashboard/components/index.ts | 2 +- apps/wallet-dashboard/tsconfig.json | 8 +- .../ui/app/components/address-input/index.tsx | 76 ------ .../src/ui/app/components/coin-icon/index.tsx | 16 -- .../home/nft-transfer/TransferNFTForm.tsx | 3 +- .../pages/home/transfer-coin/FormInput.tsx | 51 ---- .../home/transfer-coin/SendTokenForm.tsx | 84 +++--- .../ui/app/pages/home/transfer-coin/index.tsx | 111 +++----- pnpm-lock.yaml | 6 + 29 files changed, 784 insertions(+), 791 deletions(-) create mode 100644 apps/core/src/components/CoinSelector.tsx create mode 100644 apps/core/src/components/Inputs/AddressInput.tsx create mode 100644 apps/core/src/components/Inputs/FormInput.tsx create mode 100644 apps/core/src/components/Inputs/SendTokenFormInput.tsx create mode 100644 apps/core/src/components/Inputs/index.ts create mode 100644 apps/wallet-dashboard/components/Dialogs/SendToken/SendCoinDialog.tsx rename apps/wallet-dashboard/components/{Popup/Popups/SendCoinPopup => Dialogs/SendToken}/index.ts (55%) create mode 100644 apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx rename apps/wallet-dashboard/components/{Popup/Popups/SendCoinPopup => Dialogs/SendToken}/views/ReviewValuesFormView.tsx (90%) rename apps/wallet-dashboard/components/{Popup/Popups/SendCoinPopup => Dialogs/SendToken}/views/index.ts (100%) delete mode 100644 apps/wallet-dashboard/components/Dialogs/SendTokenDialog.tsx delete mode 100644 apps/wallet-dashboard/components/Dropdown.tsx delete mode 100644 apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/SendCoinPopup.tsx delete mode 100644 apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/EnterValuesFormView.tsx delete mode 100644 apps/wallet/src/ui/app/components/address-input/index.tsx delete mode 100644 apps/wallet/src/ui/app/pages/home/transfer-coin/FormInput.tsx diff --git a/apps/core/package.json b/apps/core/package.json index 31cb3e43e8f..faf538f5efd 100644 --- a/apps/core/package.json +++ b/apps/core/package.json @@ -27,6 +27,8 @@ "@growthbook/growthbook-react": "^1.0.0", "@hookform/resolvers": "^3.9.0", "@iota/dapp-kit": "workspace:*", + "@iota/apps-ui-kit": "workspace:*", + "@iota/ui-icons": "workspace:*", "@iota/iota-sdk": "workspace:*", "@iota/kiosk": "workspace:*", "@sentry/react": "^7.59.2", diff --git a/apps/core/src/components/CoinSelector.tsx b/apps/core/src/components/CoinSelector.tsx new file mode 100644 index 00000000000..9e011b1a924 --- /dev/null +++ b/apps/core/src/components/CoinSelector.tsx @@ -0,0 +1,60 @@ +import { useIotaClientQuery } from '@iota/dapp-kit'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import { filterAndSortTokenBalances } from '../utils'; +import { COINS_QUERY_REFETCH_INTERVAL, COINS_QUERY_STALE_TIME } from '../constants'; +import { LoadingIndicator, Select, SelectOption } from '@iota/apps-ui-kit'; +import { CoinBalance } from '@iota/iota-sdk/client'; +import { useFormatCoin } from '../hooks'; +import { IotaLogoMark } from '@iota/ui-icons'; + +export function CoinSelector({ + activeCoinType = IOTA_TYPE_ARG, + coins = [], + onClick, +}: { + activeCoinType: string; + coins: CoinBalance[]; + onClick: (coinType: string) => void; +}) { + + const activeCoin = coins?.find(({ coinType }) => coinType === activeCoinType) ?? coins?.[0]; + const initialValue = activeCoin?.coinType; + const coinsOptions: SelectOption[] = + coins?.map((coin) => ({ + id: coin.coinType, + renderLabel: () => , + })) || []; + + return ( + + + + ) : undefined + } + /> + ); +} diff --git a/apps/core/src/components/Inputs/FormInput.tsx b/apps/core/src/components/Inputs/FormInput.tsx new file mode 100644 index 00000000000..b87d53648d2 --- /dev/null +++ b/apps/core/src/components/Inputs/FormInput.tsx @@ -0,0 +1,73 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { Input, InputType, type InputProps, type NumericFormatInputProps } from '@iota/apps-ui-kit'; +import React, { useMemo } from 'react'; + +interface FormInputProps extends Omit { + name: string; + value: string; + suffix: string; + allowNegative: boolean; + onChange: (value: string) => void; + onBlur?: React.FocusEventHandler; + errorMessage?: string; + renderAction?: (isDisabled?: boolean) => React.JSX.Element; + decimals?: boolean; + disabled?: boolean; + isSubmitting?: boolean; +} + +export default function FormInput({ + renderAction, + decimals, + value, + onChange, + onBlur, + errorMessage, + isSubmitting = false, + disabled, + name, + type, + placeholder, + amountCounter, + label, + suffix, + allowNegative, +}: FormInputProps) { + const isInputDisabled = isSubmitting || disabled; + const isNumericFormat = type === InputType.NumericFormat; + + const numericPropsOnly: Partial = useMemo( + () => ({ + decimalScale: decimals ? undefined : 0, + thousandSeparator: true, + onValueChange: (values) => { + onChange(values.value); + }, + }), + [decimals, onChange], + ); + + const isActionButtonDisabled = isInputDisabled || !value || !!errorMessage; + + return ( + onChange(e.currentTarget.value)} + amountCounter={!errorMessage ? amountCounter : undefined} + trailingElement={renderAction?.(isActionButtonDisabled)} + {...(isNumericFormat ? numericPropsOnly : {})} + /> + ); +} + diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx new file mode 100644 index 00000000000..a39e661f7d4 --- /dev/null +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -0,0 +1,63 @@ +import { ButtonPill, InputType } from '@iota/apps-ui-kit'; +import { CoinStruct } from '@iota/iota-sdk/client'; +import FormInput from './FormInput'; + +export interface SendTokenInputProps { + gasBudgetEstimation: string; + coins?: CoinStruct[]; + symbol: string; + values: { + amount: string; + isPayAllIota: boolean; + }; + onActionClick: () => Promise; + isActionButtonDisabled?: boolean | 'auto'; + value: string; + onChange: (value: string) => void; + onBlur?: React.FocusEventHandler; + errorMessage?: string; +} + +export function SendTokenFormInput({ + gasBudgetEstimation, + coins, + values, + symbol, + onActionClick, + isActionButtonDisabled, + value, + onChange, + onBlur, + errorMessage, +}: SendTokenInputProps) { + return ( + ( + + Max + + )} + /> + ); +} diff --git a/apps/core/src/components/Inputs/index.ts b/apps/core/src/components/Inputs/index.ts new file mode 100644 index 00000000000..da5352ee2a5 --- /dev/null +++ b/apps/core/src/components/Inputs/index.ts @@ -0,0 +1,6 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +export { default as AddressInput } from './AddressInput'; +export * from './FormInput'; +export * from './SendTokenFormInput'; diff --git a/apps/core/src/components/index.ts b/apps/core/src/components/index.ts index 67d6dc1fbc3..f4cf5900a8e 100644 --- a/apps/core/src/components/index.ts +++ b/apps/core/src/components/index.ts @@ -2,3 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 export * from './KioskClientProvider'; +export * from './CoinSelector'; + +export * from './Inputs'; diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/SendCoinDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/SendCoinDialog.tsx new file mode 100644 index 00000000000..5a225f04a3c --- /dev/null +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/SendCoinDialog.tsx @@ -0,0 +1,128 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import React, { useState } from 'react'; +import { EnterValuesFormView, ReviewValuesFormView } from './views'; +import { CoinBalance } from '@iota/iota-sdk/client'; +import { useSendCoinTransaction, useNotifications } from '@/hooks'; +import { useSignAndExecuteTransaction } from '@iota/dapp-kit'; +import { NotificationType } from '@/stores/notificationStore'; +import { useGetAllCoins } from '@iota/core'; +import { Dialog, DialogBody, DialogContent, DialogPosition, Header } from '@iota/apps-ui-kit'; + +export interface FormDataValues { + amount: string; + to: string; + isPayAllIota: boolean; + gasBudgetEst: string; +} + +export const INITIAL_VALUES: FormDataValues = { + to: '', + amount: '', + isPayAllIota: false, + gasBudgetEst: '', +}; + +interface SendCoinPopupProps { + coin: CoinBalance; + activeAddress: string; + setOpen: (bool: boolean) => void; + open: boolean; +} + +enum FormStep { + EnterValues, + ReviewValues, +} + +function SendCoinDialog({ + coin, + activeAddress, + setOpen, + open, +}: SendCoinPopupProps): React.JSX.Element { + const [step, setStep] = useState(FormStep.EnterValues); + const [selectedCoin, setSelectedCoin] = useState(coin); + const [formData, setFormData] = useState(INITIAL_VALUES); + const { addNotification } = useNotifications(); + + const { data: coinsData } = useGetAllCoins(selectedCoin.coinType, activeAddress); + + const { + mutateAsync: signAndExecuteTransaction, + error, + isPending, + } = useSignAndExecuteTransaction(); + const { data: sendCoinData } = useSendCoinTransaction( + coinsData || [], + selectedCoin.coinType, + activeAddress, + formData.to, + formData.amount, + selectedCoin.totalBalance === formData.amount, + ); + + function handleTransfer() { + if (!sendCoinData?.transaction) { + addNotification('There was an error with the transaction', NotificationType.Error); + return; + } else { + signAndExecuteTransaction({ + transaction: sendCoinData.transaction, + }) + .then(() => { + setOpen(false); + addNotification('Transfer transaction has been sent'); + }) + .catch(() => { + addNotification('Transfer transaction was not sent', NotificationType.Error); + }); + } + } + + function onNext(): void { + setStep(FormStep.ReviewValues); + } + + function onBack(): void { + setStep(FormStep.EnterValues); + } + + return ( + + +
setOpen(false)} + /> +
+ + {step === FormStep.EnterValues && ( + + )} + {step === FormStep.ReviewValues && ( + + )} + +
+ +
+ ); +} + +export default SendCoinDialog; diff --git a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/index.ts b/apps/wallet-dashboard/components/Dialogs/SendToken/index.ts similarity index 55% rename from apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/index.ts rename to apps/wallet-dashboard/components/Dialogs/SendToken/index.ts index 50cde552014..4b62e506333 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/index.ts +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/index.ts @@ -1,4 +1,6 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -export { default as SendCoinPopup } from './SendCoinPopup'; +export * from './SendCoinDialog'; + +export * from './views'; diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx new file mode 100644 index 00000000000..d74df95d1ac --- /dev/null +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx @@ -0,0 +1,254 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { CoinBalance, CoinStruct } from '@iota/iota-sdk/client'; +import { FormDataValues, INITIAL_VALUES } from '../SendCoinDialog'; +import { + AddressInput, + CoinFormat, + COINS_QUERY_REFETCH_INTERVAL, + COINS_QUERY_STALE_TIME, + CoinSelector, + filterAndSortTokenBalances, + parseAmount, + SendTokenFormInput, + useCoinMetadata, + useFormatCoin, + useGetAllCoins, +} from '@iota/core'; +import { + ButtonHtmlType, + ButtonType, + InfoBox, + InfoBoxType, + Button, + InfoBoxStyle, + LoadingIndicator, +} from '@iota/apps-ui-kit'; +import { useCurrentAccount, useIotaClientQuery } from '@iota/dapp-kit'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; +import { Field, FieldInputProps, Form, Formik } from 'formik'; +import { Exclamation } from '@iota/ui-icons'; + +interface EnterValuesFormProps { + coin: CoinBalance; + gasBudget: string; + setFormData: React.Dispatch>; + setSelectedCoin: React.Dispatch>; + onNext: () => void; +} + +function totalBalance(coins: CoinStruct[]): bigint { + return coins.reduce((partialSum, c) => partialSum + getBalanceFromCoinStruct(c), BigInt(0)); +} +function getBalanceFromCoinStruct(coin: CoinStruct): bigint { + return BigInt(coin.balance); +} + +function EnterValuesFormView({ + coin, + gasBudget, + setFormData, + setSelectedCoin, + onNext, +}: EnterValuesFormProps): JSX.Element { + const account = useCurrentAccount(); + const activeAddress = account?.address; + + // Get all coins of the type + const { data: coinsData, isPending: coinsIsPending } = useGetAllCoins( + coin.coinType, + activeAddress!, + ); + const { data: iotaCoinsData, isPending: iotaCoinsIsPending } = useGetAllCoins( + IOTA_TYPE_ARG, + activeAddress!, + ); + + const { data: coinsBalance, isPending: coinsBalanceIsPending } = useIotaClientQuery( + 'getAllBalances', + { owner: activeAddress! }, + { + enabled: !!activeAddress, + refetchInterval: COINS_QUERY_REFETCH_INTERVAL, + staleTime: COINS_QUERY_STALE_TIME, + select: filterAndSortTokenBalances, + }, + ); + + const iotaCoins = iotaCoinsData; + const coins = coinsData; + const coinBalance = totalBalance(coins || []); + const iotaBalance = totalBalance(iotaCoins || []); + + const [tokenBalance, symbol, queryResult] = useFormatCoin( + coinBalance, + coin.coinType, + CoinFormat.FULL, + ); + + const coinMetadata = useCoinMetadata(coin.coinType); + const coinDecimals = coinMetadata.data?.decimals ?? 0; + + const formattedTokenBalance = tokenBalance.replace(/,/g, ''); + const initAmountBig = parseAmount('', coinDecimals); + + if (coinsBalanceIsPending || coinsIsPending || iotaCoinsIsPending) { + return ( +
+ +
+ ); + } + + async function handleFormSubmit({ to, amount, isPayAllIota, gasBudgetEst }: FormDataValues) { + if (!coins || !iotaCoins) return; + const coinsIDs = [...coins] + .sort((a, b) => Number(b.balance) - Number(a.balance)) + .map(({ coinObjectId }) => coinObjectId); + + const data = { + to, + amount, + isPayAllIota, + coins, + coinIds: coinsIDs, + gasBudgetEst, + }; + setFormData(data); + onNext(); + } + + return ( +
+ { + setFormData(INITIAL_VALUES); + const coin = coinsBalance?.find((coin) => coin.coinType === coinType); + setSelectedCoin(coin!); + }} + /> + + + {({ + isValid, + isSubmitting, + setFieldValue, + values, + submitForm, + touched, + errors, + handleBlur, + }) => { + const newPayIotaAll = + parseAmount(values.amount, coinDecimals) === coinBalance && + coin.coinType === IOTA_TYPE_ARG; + if (values.isPayAllIota !== newPayIotaAll) { + setFieldValue('isPayAllIota', newPayIotaAll); + } + + const hasEnoughBalance = + values.isPayAllIota || + iotaBalance > + parseAmount(values.gasBudgetEst, coinDecimals) + + parseAmount( + coin.coinType === IOTA_TYPE_ARG ? values.amount : '0', + coinDecimals, + ); + + async function onMaxTokenButtonClick() { + await setFieldValue('amount', formattedTokenBalance); + } + + const isMaxActionDisabled = + parseAmount(values?.amount, coinDecimals) === coinBalance || + queryResult.isPending || + !coinBalance; + + return ( +
+
+
+ {!hasEnoughBalance && ( + } + /> + )} + + }) => ( + setFieldValue('amount', value)} + onBlur={handleBlur} + errorMessage={ + touched.amount && errors.amount + ? errors.amount + : undefined + } + /> + )} + /> + + +
+
+ +
+
+
+ ); + }} +
+
+ ); +} + +export default EnterValuesFormView; diff --git a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/ReviewValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx similarity index 90% rename from apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/ReviewValuesFormView.tsx rename to apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx index 0732b263cfc..dc8b64b532c 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/ReviewValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { FormDataValues } from '../SendCoinPopup'; +import { FormDataValues } from '../SendCoinDialog'; import { Button } from '@/components'; interface ReviewValuesFormProps { @@ -15,7 +15,7 @@ interface ReviewValuesFormProps { } function ReviewValuesFormView({ - formData: { amount, recipientAddress }, + formData: { amount, to }, senderAddress, gasBudget, error, @@ -29,7 +29,7 @@ function ReviewValuesFormView({

Sending: {amount}

From: {senderAddress}

-

To: {recipientAddress}

+

To: {to}

Gas fee: {gasBudget}

{error ? {error} : null} diff --git a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/index.ts b/apps/wallet-dashboard/components/Dialogs/SendToken/views/index.ts similarity index 100% rename from apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/index.ts rename to apps/wallet-dashboard/components/Dialogs/SendToken/views/index.ts diff --git a/apps/wallet-dashboard/components/Dialogs/SendTokenDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendTokenDialog.tsx deleted file mode 100644 index 73773f480a3..00000000000 --- a/apps/wallet-dashboard/components/Dialogs/SendTokenDialog.tsx +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { - InfoBox, - InfoBoxStyle, - InfoBoxType, - ButtonType, - ButtonHtmlType, - Button, - Dialog, - DialogContent, - DialogBody, - Header, - DialogPosition, -} from '@iota/apps-ui-kit'; -import { parseAmount, useCoinMetadata, useGetAllCoins, useIotaAddressValidation } from '@iota/core'; -import { CoinStruct } from '@iota/iota-sdk/client'; -import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { Exclamation } from '@iota/ui-icons'; -import { Field, Form, Formik, useFormikContext } from 'formik'; -import Input from '../Input'; -import { ChangeEventHandler, useCallback } from 'react'; - -const INITIAL_VALUES = { - to: '', - amount: '', - isPayAllIota: false, - gasBudgetEst: '', -}; - -export type FormValues = typeof INITIAL_VALUES; - -export type SubmitProps = { - to: string; - amount: string; - isPayAllIota: boolean; - coinIds: string[]; - coins: CoinStruct[]; - gasBudgetEst: string; -}; - -export type SendTokenFormProps = { - coinType: string; - activeAddress: string; - setOpen: (bool: boolean) => void; - open: boolean; -}; - -function totalBalance(coins: CoinStruct[]): bigint { - return coins.reduce((partialSum, c) => partialSum + getBalanceFromCoinStruct(c), BigInt(0)); -} -function getBalanceFromCoinStruct(coin: CoinStruct): bigint { - return BigInt(coin.balance); -} - -export function SendTokenDialog({ - coinType, - activeAddress, - setOpen, - open, -}: SendTokenFormProps): React.JSX.Element { - const { data: coinsData } = useGetAllCoins(coinType, activeAddress!); - const { setFieldValue, validateField } = useFormikContext(); - const iotaAddressValidation = useIotaAddressValidation(); - - const { data: iotaCoinsData } = useGetAllCoins(IOTA_TYPE_ARG, activeAddress!); - - const iotaCoins = iotaCoinsData; - const coins = coinsData; - const coinBalance = totalBalance(coins || []); - const iotaBalance = totalBalance(iotaCoins || []); - - const coinMetadata = useCoinMetadata(coinType); - const coinDecimals = coinMetadata.data?.decimals ?? 0; - - // const validationSchemaStepOne = useMemo( - // () => createValidationSchemaStepOne(coinBalance, symbol, coinDecimals), - // [client, coinBalance, symbol, coinDecimals], - // ); - - // remove the comma from the token balance - const initAmountBig = parseAmount('0', coinDecimals); - // const initAmountBig = parseAmount(initialAmount, coinDecimals); - - const handleAddressChange = useCallback>( - (e) => { - const address = e.currentTarget.value; - setFieldValue(activeAddress, iotaAddressValidation.cast(address)).then(() => { - validateField(activeAddress); - }); - }, - [setFieldValue, activeAddress, iotaAddressValidation], - ); - - async function handleFormSubmit({ to, amount, isPayAllIota, gasBudgetEst }: FormValues) { - if (!coins || !iotaCoins) return; - const coinsIDs = [...coins] - .sort((a, b) => Number(b.balance) - Number(a.balance)) - .map(({ coinObjectId }) => coinObjectId); - - const data = { - to, - amount, - isPayAllIota, - coins, - coinIds: coinsIDs, - gasBudgetEst, - }; - console.log('data', data); - - // onSubmit(data); - } - - return ( - - -
setOpen(false)} /> - - - {({ isValid, isSubmitting, setFieldValue, values, submitForm }) => { - const newPayIotaAll = - parseAmount(values.amount, coinDecimals) === coinBalance && - coinType === IOTA_TYPE_ARG; - if (values.isPayAllIota !== newPayIotaAll) { - setFieldValue('isPayAllIota', newPayIotaAll); - } - - const hasEnoughBalance = - values.isPayAllIota || - iotaBalance > - parseAmount(values.gasBudgetEst, coinDecimals) + - parseAmount( - coinType === IOTA_TYPE_ARG ? values.amount : '0', - coinDecimals, - ); - - return ( -
-
-
- {!hasEnoughBalance ? ( - } - /> - ) : null} - - {/* */} - handleAddressChange(e)} - label="Enter recipient address" - /> - } - allowNegative={false} - name="to" - placeholder="Enter Address" - /> -
-
- -
-
-
- ); - }} -
-
- -
- ); -} diff --git a/apps/wallet-dashboard/components/Dialogs/index.ts b/apps/wallet-dashboard/components/Dialogs/index.ts index 2020a47bf9e..4b335f387ac 100644 --- a/apps/wallet-dashboard/components/Dialogs/index.ts +++ b/apps/wallet-dashboard/components/Dialogs/index.ts @@ -1,5 +1,5 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -export * from './SendTokenDialog'; +export * from './SendToken'; export * from './Staking'; diff --git a/apps/wallet-dashboard/components/Dropdown.tsx b/apps/wallet-dashboard/components/Dropdown.tsx deleted file mode 100644 index 50ac7decf3f..00000000000 --- a/apps/wallet-dashboard/components/Dropdown.tsx +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import React from 'react'; - -interface DropdownProps { - options: T[]; - selectedOption: T | null | undefined; - onChange: (selectedOption: T) => void; - placeholder?: string; - disabled?: boolean; - getOptionId: (option: T) => string | number; -} - -function Dropdown({ - options, - selectedOption, - onChange, - placeholder, - disabled = false, - getOptionId, -}: DropdownProps): JSX.Element { - function handleSelectionChange(e: React.ChangeEvent): void { - const selectedKey = e.target.value; - const selectedOption = options.find((option) => getOptionId(option) === selectedKey); - if (selectedOption) { - onChange(selectedOption); - } - } - - return ( - - ); -} - -export default Dropdown; diff --git a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/SendCoinPopup.tsx b/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/SendCoinPopup.tsx deleted file mode 100644 index c3f917df955..00000000000 --- a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/SendCoinPopup.tsx +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import React, { useState } from 'react'; -import { EnterValuesFormView, ReviewValuesFormView } from './views'; -import { CoinBalance } from '@iota/iota-sdk/client'; -import { useSendCoinTransaction, useNotifications } from '@/hooks'; -import { useSignAndExecuteTransaction } from '@iota/dapp-kit'; -import { NotificationType } from '@/stores/notificationStore'; -import { Dropdown } from '@/components'; -import { useGetAllCoins } from '@iota/core'; - -export interface FormDataValues { - amount: string; - recipientAddress: string; -} - -interface SendCoinPopupProps { - coin: CoinBalance; - senderAddress: string; - onClose: () => void; - coins: CoinBalance[]; -} - -enum FormStep { - EnterValues, - ReviewValues, -} - -function SendCoinPopup({ - coin, - senderAddress, - onClose, - coins, -}: SendCoinPopupProps): React.JSX.Element { - const [step, setStep] = useState(FormStep.EnterValues); - const [selectedCoin, setCoin] = useState(coin); - const [formData, setFormData] = useState({ - amount: '', - recipientAddress: '', - }); - const { addNotification } = useNotifications(); - - const { data: coinsData } = useGetAllCoins(selectedCoin.coinType, senderAddress); - - const { - mutateAsync: signAndExecuteTransaction, - error, - isPending, - } = useSignAndExecuteTransaction(); - const { data: sendCoinData } = useSendCoinTransaction( - coinsData || [], - selectedCoin.coinType, - senderAddress, - formData.recipientAddress, - formData.amount, - selectedCoin.totalBalance === formData.amount, - ); - - function handleTransfer() { - if (!sendCoinData?.transaction) { - addNotification('There was an error with the transaction', NotificationType.Error); - return; - } else { - signAndExecuteTransaction({ - transaction: sendCoinData.transaction, - }) - .then(() => { - onClose(); - addNotification('Transfer transaction has been sent'); - }) - .catch(() => { - addNotification('Transfer transaction was not sent', NotificationType.Error); - }); - } - } - - function onNext(): void { - setStep(FormStep.ReviewValues); - } - - function onBack(): void { - setStep(FormStep.EnterValues); - } - - function handleSelectedCoin(coin: CoinBalance): void { - setCoin(coin); - setFormData({ - amount: '', - recipientAddress: '', - }); - } - - return ( - <> - _selectedCoin.coinType} - /> - {step === FormStep.EnterValues && ( - - )} - {step === FormStep.ReviewValues && ( - - )} - - ); -} - -export default SendCoinPopup; diff --git a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/EnterValuesFormView.tsx deleted file mode 100644 index 8c094ac8a90..00000000000 --- a/apps/wallet-dashboard/components/Popup/Popups/SendCoinPopup/views/EnterValuesFormView.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { CoinBalance } from '@iota/iota-sdk/client'; -import { FormDataValues } from '../SendCoinPopup'; -import { Button } from '@/components'; -import { useFormatCoin } from '@iota/core'; - -interface EnterValuesFormProps { - coin: CoinBalance; - formData: FormDataValues; - gasBudget: string; - setFormData: React.Dispatch>; - onClose: () => void; - onNext: () => void; -} - -function EnterValuesFormView({ - coin: { totalBalance, coinType }, - formData: { amount, recipientAddress }, - gasBudget, - setFormData, - onClose, - onNext, -}: EnterValuesFormProps): JSX.Element { - const handleChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData((prevFormData) => ({ - ...prevFormData, - [name]: value, - })); - }; - const [formattedCoin, coinSymbol, { data: coinMeta }] = useFormatCoin(totalBalance, coinType); - - return ( -
-

Send

-
-

{coinMeta?.name.toUpperCase() ?? coinType}

-

- Balance: {formattedCoin} {coinSymbol} -

- - - - -

Gas fee: {gasBudget}

-
-
- - -
-
- ); -} - -export default EnterValuesFormView; diff --git a/apps/wallet-dashboard/components/Popup/Popups/index.ts b/apps/wallet-dashboard/components/Popup/Popups/index.ts index e91b80061b2..94a6a69a7cf 100644 --- a/apps/wallet-dashboard/components/Popup/Popups/index.ts +++ b/apps/wallet-dashboard/components/Popup/Popups/index.ts @@ -4,8 +4,6 @@ export { default as TransactionDetailsPopup } from './TransactionDetailsPopup'; export { default as StakeDetailsPopup } from './StakeDetailsPopup'; export { default as UnstakePopup } from './UnstakePopup'; -export { default as SendCoinPopup } from './SendCoinPopup/SendCoinPopup'; export { default as SendAssetPopup } from './SendAssetPopup'; -export * from './SendCoinPopup'; export * from './VestingPopup'; diff --git a/apps/wallet-dashboard/components/account-balance/AccountBalance.tsx b/apps/wallet-dashboard/components/account-balance/AccountBalance.tsx index 23707407541..e661ba5bbbf 100644 --- a/apps/wallet-dashboard/components/account-balance/AccountBalance.tsx +++ b/apps/wallet-dashboard/components/account-balance/AccountBalance.tsx @@ -12,14 +12,13 @@ import { } from '@iota/core'; import { Address, Button, ButtonSize, ButtonType, Panel } from '@iota/apps-ui-kit'; import { CoinBalance, getNetwork } from '@iota/iota-sdk/client'; -import { SendCoinPopup } from '../Popup'; -import { usePopups } from '@/hooks'; import toast from 'react-hot-toast'; +import SendCoinDialog from '../Dialogs/SendToken/SendCoinDialog'; +import { useState } from 'react'; export function AccountBalance() { const account = useCurrentAccount(); const address = account?.address; - const { openPopup, closePopup } = usePopups(); const { network } = useIotaClientContext(); const { explorer } = getNetwork(network); const { data: coinBalance, isPending } = useBalance(address!); @@ -35,18 +34,14 @@ export function AccountBalance() { select: filterAndSortTokenBalances, }, ); + const [isSendTokenDialogOpen, setIsSendTokenDialogOpen] = useState(false); + const [selectedCoin, setSelectedCoin] = useState(); const explorerLink = `${explorer}/address/${address}`; function openSendTokenPopup(coin: CoinBalance, address: string): void { if (coinBalances) { - openPopup( - , - ); + setIsSendTokenDialogOpen(true); + setSelectedCoin(coin); } } @@ -77,8 +72,7 @@ export function AccountBalance() {
)} + {selectedCoin && address && ( + + )} ); } diff --git a/apps/wallet-dashboard/components/coins/MyCoins.tsx b/apps/wallet-dashboard/components/coins/MyCoins.tsx index bf3a3d6a8f7..ac70e99b07c 100644 --- a/apps/wallet-dashboard/components/coins/MyCoins.tsx +++ b/apps/wallet-dashboard/components/coins/MyCoins.tsx @@ -3,8 +3,7 @@ import React, { useState } from 'react'; import { useCurrentAccount, useIotaClientQuery } from '@iota/dapp-kit'; -import { CoinItem, SendCoinPopup } from '@/components'; -import { usePopups } from '@/hooks'; +import { CoinItem } from '@/components'; import { CoinBalance } from '@iota/iota-sdk/client'; import { COINS_QUERY_REFETCH_INTERVAL, @@ -20,6 +19,7 @@ import { Title, } from '@iota/apps-ui-kit'; import { RecognizedBadge } from '@iota/ui-icons'; +import SendCoinDialog from '../Dialogs/SendToken/SendCoinDialog'; enum TokenCategory { All = 'All', @@ -44,8 +44,9 @@ const TOKEN_CATEGORIES = [ function MyCoins(): React.JSX.Element { const [selectedTokenCategory, setSelectedTokenCategory] = useState(TokenCategory.All); + const [isSendTokenDialogOpen, setIsSendTokenDialogOpen] = useState(false); + const [selectedCoin, setSelectedCoin] = useState(); - const { openPopup, closePopup } = usePopups(); const account = useCurrentAccount(); const activeAccountAddress = account?.address; @@ -61,16 +62,10 @@ function MyCoins(): React.JSX.Element { ); const { recognized, unrecognized } = useSortedCoinsByCategories(coinBalances ?? []); - function openSendTokenPopup(coin: CoinBalance, address: string): void { + function openSendTokenDialog(coin: CoinBalance): void { if (coinBalances) { - openPopup( - , - ); + setIsSendTokenDialogOpen(true); + setSelectedCoin(coin); } } @@ -111,9 +106,7 @@ function MyCoins(): React.JSX.Element { key={index} coinType={coin.coinType} balance={BigInt(coin.totalBalance)} - onClick={() => - openSendTokenPopup(coin, account?.address ?? '') - } + onClick={() => openSendTokenDialog(coin)} icon={ } @@ -129,15 +122,21 @@ function MyCoins(): React.JSX.Element { key={index} coinType={coin.coinType} balance={BigInt(coin.totalBalance)} - onClick={() => - openSendTokenPopup(coin, account?.address ?? '') - } + onClick={() => openSendTokenDialog(coin)} /> ); })} + {selectedCoin && activeAccountAddress && ( + + )} ); } diff --git a/apps/wallet-dashboard/components/index.ts b/apps/wallet-dashboard/components/index.ts index 3aad048346d..88d191e6750 100644 --- a/apps/wallet-dashboard/components/index.ts +++ b/apps/wallet-dashboard/components/index.ts @@ -10,7 +10,7 @@ export { default as Input } from './Input'; export { default as VirtualList } from './VirtualList'; export { default as ExternalImage } from './ExternalImage'; export { default as TransactionIcon } from './TransactionIcon'; -export { default as Dropdown } from './Dropdown'; +export { default as FormInput } from '@iota/core/src/components/Inputs/FormInput'; export * from './account-balance/AccountBalance'; export * from './coins'; diff --git a/apps/wallet-dashboard/tsconfig.json b/apps/wallet-dashboard/tsconfig.json index d452a8afa13..77538e96889 100644 --- a/apps/wallet-dashboard/tsconfig.json +++ b/apps/wallet-dashboard/tsconfig.json @@ -27,6 +27,12 @@ "@/stores/*": ["./stores/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "../core/src/components/Inputs/FormInput.tsx" + ], "exclude": ["node_modules"] } diff --git a/apps/wallet/src/ui/app/components/address-input/index.tsx b/apps/wallet/src/ui/app/components/address-input/index.tsx deleted file mode 100644 index e5d61c02b22..00000000000 --- a/apps/wallet/src/ui/app/components/address-input/index.tsx +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { useField, useFormikContext } from 'formik'; -import { useCallback, useMemo } from 'react'; -import type { ChangeEventHandler } from 'react'; -import { useIotaAddressValidation } from '@iota/core'; -import { Input, InputType } from '@iota/apps-ui-kit'; -import { Close } from '@iota/ui-icons'; - -export interface AddressInputProps { - disabled?: boolean; - placeholder?: string; - name: string; - label?: string; - shouldValidateManually?: boolean; -} - -export function AddressInput({ - disabled: forcedDisabled, - placeholder = '0x...', - name = 'to', - label = 'Enter Recipient Address', -}: AddressInputProps) { - const [field, meta] = useField(name); - - const { isSubmitting, setFieldValue, validateField } = useFormikContext(); - const iotaAddressValidation = useIotaAddressValidation(); - - const disabled = forcedDisabled !== undefined ? forcedDisabled : isSubmitting; - const handleOnChange = useCallback>( - (e) => { - const address = e.currentTarget.value; - setFieldValue(name, iotaAddressValidation.cast(address)).then(() => { - validateField(name); - }); - }, - [setFieldValue, name, iotaAddressValidation], - ); - const formattedValue = useMemo( - () => iotaAddressValidation.cast(field?.value), - [field?.value, iotaAddressValidation], - ); - - const clearAddress = useCallback(() => { - setFieldValue('to', ''); - }, [setFieldValue]); - - return ( - <> - - - - ) : undefined - } - /> - - ); -} diff --git a/apps/wallet/src/ui/app/components/coin-icon/index.tsx b/apps/wallet/src/ui/app/components/coin-icon/index.tsx index 5214a1e7420..1f3a66e7429 100644 --- a/apps/wallet/src/ui/app/components/coin-icon/index.tsx +++ b/apps/wallet/src/ui/app/components/coin-icon/index.tsx @@ -28,19 +28,3 @@ function NonIotaCoin({ coinType, size = ImageIconSize.Full, rounded }: NonIotaCo ); } - -export interface CoinIconProps { - coinType: string; - size?: ImageIconSize; - rounded?: boolean; -} - -export function CoinIcon({ coinType, size = ImageIconSize.Full, rounded }: CoinIconProps) { - return coinType === IOTA_TYPE_ARG ? ( -
- -
- ) : ( - - ); -} diff --git a/apps/wallet/src/ui/app/pages/home/nft-transfer/TransferNFTForm.tsx b/apps/wallet/src/ui/app/pages/home/nft-transfer/TransferNFTForm.tsx index 59d02cf434e..7806967edd0 100644 --- a/apps/wallet/src/ui/app/pages/home/nft-transfer/TransferNFTForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/nft-transfer/TransferNFTForm.tsx @@ -2,13 +2,12 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { AddressInput } from '_components'; import { ampli } from '_src/shared/analytics/ampli'; import { getSignerOperationErrorMessage } from '_src/ui/app/helpers/errorMessages'; import { useActiveAddress } from '_src/ui/app/hooks'; import { useActiveAccount } from '_src/ui/app/hooks/useActiveAccount'; import { useSigner } from '_src/ui/app/hooks/useSigner'; -import { createNftSendValidationSchema, useGetKioskContents } from '@iota/core'; +import { createNftSendValidationSchema, useGetKioskContents, AddressInput } from '@iota/core'; import { Transaction } from '@iota/iota-sdk/transactions'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Field, Form, Formik } from 'formik'; diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/FormInput.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/FormInput.tsx deleted file mode 100644 index 88200721c5c..00000000000 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/FormInput.tsx +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Input, InputType, type InputProps, type NumericFormatInputProps } from '@iota/apps-ui-kit'; -import { useField, useFormikContext } from 'formik'; - -interface FormInputWithFormixProps { - name: string; - renderAction?: (isDisabled?: boolean) => React.JSX.Element; - decimals?: boolean; -} - -export function FormInput({ - renderAction, - decimals, - ...props -}: InputProps & FormInputWithFormixProps) { - const [field, meta] = useField(props.name); - const form = useFormikContext(); - - const { isSubmitting } = form; - const isInputDisabled = isSubmitting || props.disabled; - - const isActionButtonDisabled = - isInputDisabled || meta?.initialValue === meta?.value || !!meta?.error; - const errorMessage = meta?.error ? meta.error : undefined; - - const isNumericFormat = props.type === InputType.NumericFormat; - const numericPropsOnly: Partial = { - decimalScale: decimals ? undefined : 0, - thousandSeparator: true, - onValueChange: (values) => { - form.setFieldValue(props.name, values.value).then(() => { - form.validateField(props.name); - }); - }, - }; - - return ( - - ); -} diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index 3ac0ad9e320..0426bbf577f 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 import { useActiveAddress } from '_app/hooks/useActiveAddress'; -import { AddressInput, Loading } from '_components'; +import { Loading } from '_components'; import { GAS_SYMBOL } from '_src/ui/app/redux/slices/iota-objects/Coin'; import { useGetAllCoins, @@ -12,12 +12,14 @@ import { useCoinMetadata, useFormatCoin, parseAmount, + AddressInput, + SendTokenFormInput, } from '@iota/core'; import { useIotaClient } from '@iota/dapp-kit'; import { type CoinStruct } from '@iota/iota-sdk/client'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; import { useQuery } from '@tanstack/react-query'; -import { Field, Form, Formik, useFormikContext } from 'formik'; +import { Field, FieldInputProps, Form, Formik, useFormikContext } from 'formik'; import { useEffect, useMemo } from 'react'; import { createValidationSchemaStepOne } from './validation'; @@ -25,13 +27,10 @@ import { InfoBox, InfoBoxStyle, InfoBoxType, - InputType, Button, ButtonType, ButtonHtmlType, - ButtonPill, } from '@iota/apps-ui-kit'; -import { FormInput } from './FormInput'; import { Exclamation } from '@iota/ui-icons'; const INITIAL_VALUES = { @@ -148,6 +147,11 @@ export function SendTokenForm({ const coinMetadata = useCoinMetadata(coinType); const coinDecimals = coinMetadata.data?.decimals ?? 0; + const gasBudgetEstimation = useGasBudgetEstimation({ + coinDecimals: coinDecimals, + coins: coins ?? [], + }); + const [tokenBalance, symbol, queryResult] = useFormatCoin( coinBalance, coinType, @@ -205,7 +209,7 @@ export function SendTokenForm({ validateOnBlur={false} onSubmit={handleFormSubmit} > - {({ isValid, isSubmitting, setFieldValue, values, submitForm }) => { + {({ isValid, isSubmitting, setFieldValue, values, submitForm, handleBlur, touched, errors }) => { const newPayIotaAll = parseAmount(values.amount, coinDecimals) === coinBalance && coinType === IOTA_TYPE_ARG; @@ -244,13 +248,26 @@ export function SendTokenForm({ /> ) : null} - }) => ( + setFieldValue('amount', value)} + onBlur={handleBlur} + errorMessage={ + touched.amount && errors.amount + ? errors.amount + : undefined + } + /> + )} /> Promise; isActionButtonDisabled?: boolean | 'auto'; } - -function SendTokenFormInput({ - coinDecimals, - coins, - values, - symbol, - onActionClick, - isActionButtonDisabled, -}: SendTokenInputProps) { - const gasBudgetEstimation = useGasBudgetEstimation({ - coinDecimals: coinDecimals, - coins: coins ?? [], - }); - - return ( - ( - - Max - - )} - /> - ); -} diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx index 111017d0bb0..2c58581f114 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/index.tsx @@ -2,17 +2,19 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { CoinIcon, Loading, Overlay } from '_components'; +import { Overlay } from '_components'; import { ampli } from '_src/shared/analytics/ampli'; import { getSignerOperationErrorMessage } from '_src/ui/app/helpers/errorMessages'; import { useActiveAccount } from '_src/ui/app/hooks/useActiveAccount'; import { useSigner } from '_src/ui/app/hooks/useSigner'; import { useUnlockedGuard } from '_src/ui/app/hooks/useUnlockedGuard'; import { + COINS_QUERY_REFETCH_INTERVAL, + COINS_QUERY_STALE_TIME, + CoinSelector, createTokenTransferTransaction, filterAndSortTokenBalances, useCoinMetadata, - useFormatCoin, } from '@iota/core'; // import * as Sentry from '@sentry/react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; @@ -21,13 +23,9 @@ import { toast } from 'react-hot-toast'; import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; import { PreviewTransfer } from './PreviewTransfer'; import { SendTokenForm, type SubmitProps } from './SendTokenForm'; -import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { Select, Button, type SelectOption, ButtonType } from '@iota/apps-ui-kit'; -import { useActiveAddress, useCoinsReFetchingConfig } from '_src/ui/app/hooks'; -import { useIotaClientQuery } from '@iota/dapp-kit'; -import type { CoinBalance } from '@iota/iota-sdk/client'; -import { ImageIconSize } from '_src/ui/app/shared/image-icon'; +import { Button, ButtonType, LoadingIndicator } from '@iota/apps-ui-kit'; import { Loader } from '@iota/ui-icons'; +import { useIotaClientQuery } from 'node_modules/@iota/dapp-kit/src'; function TransferCoinPage() { const [searchParams] = useSearchParams(); @@ -41,6 +39,25 @@ function TransferCoinPage() { const address = activeAccount?.address; const queryClient = useQueryClient(); + const { data: coinsBalance, isPending: coinsBalanceIsPending } = useIotaClientQuery( + 'getAllBalances', + { owner: address! }, + { + enabled: !!address, + refetchInterval: COINS_QUERY_REFETCH_INTERVAL, + staleTime: COINS_QUERY_STALE_TIME, + select: filterAndSortTokenBalances, + }, + ); + + if (coinsBalanceIsPending) { + return ( +
+ +
+ ); + } + const transaction = useMemo(() => { if (!coinType || !signer || !formData || !address) return null; @@ -104,7 +121,7 @@ function TransferCoinPage() { return null; } - if (!coinType) { + if (!coinType || !coinsBalance) { return ; } @@ -147,8 +164,12 @@ function TransferCoinPage() { ) : ( <> setFormData(undefined)} activeCoinType={coinType} + coins={coinsBalance || []} + onClick={(coinType) => { + setFormData(undefined) + navigate(`/send?${new URLSearchParams({ type: coinType }).toString()}`); + }} /> void; -}) { - const selectedAddress = useActiveAddress(); - const navigate = useNavigate(); - - const { staleTime, refetchInterval } = useCoinsReFetchingConfig(); - const { data: coins, isPending } = useIotaClientQuery( - 'getAllBalances', - { owner: selectedAddress! }, - { - enabled: !!selectedAddress, - refetchInterval, - staleTime, - select: filterAndSortTokenBalances, - }, - ); - - if (!coins?.length) { - return ; - } - - const activeCoin = coins?.find(({ coinType }) => coinType === activeCoinType) ?? coins?.[0]; - const initialValue = activeCoin?.coinType; - const coinsOptions: SelectOption[] = - coins?.map((coin) => ({ - id: coin.coinType, - renderLabel: () => , - })) || []; - - return ( - - { + setFieldValue('gasBudgetEst', gasBudgetEstimation, false); + }, [gasBudgetEstimation, setFieldValue, values.amount]); + return ( void; } export function useGasBudgetEstimation({ @@ -27,8 +25,7 @@ export function useGasBudgetEstimation({ to, amount, isPayAllIota, - setFieldValue, -}: useGasBudgetEstimation) { +}: UseGasBudgetEstimationOptions) { const client = useIotaClient(); const { data: gasBudget } = useQuery({ // eslint-disable-next-line @tanstack/query/exhaustive-deps @@ -63,11 +60,6 @@ export function useGasBudgetEstimation({ }); const [formattedGas] = useFormatCoin(gasBudget, IOTA_TYPE_ARG); - // gasBudgetEstimation should change when the amount above changes - - useEffect(() => { - setFieldValue('gasBudgetEst', formattedGas, false); - }, [formattedGas, setFieldValue, amount]); return formattedGas ? formattedGas + ' ' + GAS_SYMBOL : '--'; } From b9a659e046d53a693dae07aa25c99a91d6186aad Mon Sep 17 00:00:00 2001 From: cpl121 Date: Tue, 12 Nov 2024 09:43:44 +0100 Subject: [PATCH 19/56] fix(wallet-dashboard): fixes --- .../src/components/Inputs/AddressInput.tsx | 4 +- .../Dialogs/SendToken/SendTokenDialog.tsx | 77 ++++++++++--------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index e43c3001e82..5656f97cdfe 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -14,7 +14,7 @@ export interface AddressInputProps { onChange: (e: React.ChangeEvent) => void; onBlur: (e: React.FocusEvent) => void; }; - formContext: { + form: { setFieldValue: (field: string, value: string, shouldValidate?: boolean) => void; errors: Record; touched: Record; @@ -26,7 +26,7 @@ export interface AddressInputProps { export function AddressInput({ field, - formContext, + form: formContext, disabled, placeholder = '0x...', label = 'Enter Recipient Address', diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx index 49fac690b55..ee325da2220 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx @@ -36,7 +36,7 @@ enum FormStep { ReviewValues, } -function SendTokenDialog({ +function SendTokenDialogBody({ coin, activeAddress, setOpen, @@ -54,6 +54,7 @@ function SendTokenDialog({ error, isPending, } = useSignAndExecuteTransaction(); + const { data: sendCoinData } = useSendCoinTransaction( coinsData || [], selectedCoin?.coinType, @@ -63,12 +64,6 @@ function SendTokenDialog({ selectedCoin?.totalBalance === formData.amount, ); - useEffect(() => { - setSelectedCoin(coin); - setStep(FormStep.EnterValues); - setFormData(INITIAL_VALUES); - }, [open, setOpen, coin]); - function handleTransfer() { if (!sendCoinData?.transaction) { addNotification('There was an error with the transaction', NotificationType.Error); @@ -96,37 +91,45 @@ function SendTokenDialog({ } return ( - + <> +
setOpen(false)} + /> +
+ + {step === FormStep.EnterValues && ( + + )} + {step === FormStep.ReviewValues && ( + + )} + +
+ + ); +} + +function SendTokenDialog(props: SendCoinPopupProps): React.JSX.Element { + return ( + -
setOpen(false)} - /> -
- - {step === FormStep.EnterValues && ( - - )} - {step === FormStep.ReviewValues && ( - - )} - -
+
); From 58415c712916ac10a9695d1108e0edeacbd050b6 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Tue, 12 Nov 2024 13:49:44 +0100 Subject: [PATCH 20/56] fix(wallet-dashboard): move FormInputs to a standalone component --- .../SendToken/views/EnterValuesFormView.tsx | 246 ++++++++++-------- 1 file changed, 135 insertions(+), 111 deletions(-) diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx index 5d520e200ca..de6c598fd17 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { CoinBalance, CoinStruct } from '@iota/iota-sdk/client'; +import { CoinBalance, CoinMetadata, CoinStruct } from '@iota/iota-sdk/client'; import { FormDataValues, INITIAL_VALUES } from '../SendTokenDialog'; import { AddressInput, @@ -28,8 +28,9 @@ import { } from '@iota/apps-ui-kit'; import { useIotaClientQuery } from '@iota/dapp-kit'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { Field, FieldInputProps, Form, Formik } from 'formik'; +import { Field, FieldInputProps, Form, Formik, FormikProps } from 'formik'; import { Exclamation } from '@iota/ui-icons'; +import { UseQueryResult } from '@tanstack/react-query'; interface EnterValuesFormProps { coin: CoinBalance; @@ -40,6 +41,18 @@ interface EnterValuesFormProps { onNext: () => void; } +interface FormInputsProps extends FormikProps { + coinType: string; + coinDecimals: number; + coinBalance: bigint; + iotaBalance: bigint; + formattedTokenBalance: string; + symbol: string; + activeAddress: string; + coins: CoinStruct[]; + queryResult: UseQueryResult; +} + function totalBalance(coins: CoinStruct[]): bigint { return coins.reduce((partialSum, c) => partialSum + getBalanceFromCoinStruct(c), BigInt(0)); } @@ -47,6 +60,112 @@ function getBalanceFromCoinStruct(coin: CoinStruct): bigint { return BigInt(coin.balance); } +function FormInputs({ + isValid, + isSubmitting, + setFieldValue, + values, + submitForm, + touched, + errors, + handleBlur, + coinType, + coinDecimals, + coinBalance, + iotaBalance, + formattedTokenBalance, + symbol, + activeAddress, + coins, + queryResult, +}: FormInputsProps): React.JSX.Element { + const newPayIotaAll = + parseAmount(values.amount, coinDecimals) === coinBalance && coinType === IOTA_TYPE_ARG; + if (values.isPayAllIota !== newPayIotaAll) { + setFieldValue('isPayAllIota', newPayIotaAll); + } + + const hasEnoughBalance = + values.isPayAllIota || + iotaBalance > + parseAmount(values.gasBudgetEst, coinDecimals) + + parseAmount(coinType === IOTA_TYPE_ARG ? values.amount : '0', coinDecimals); + + async function onMaxTokenButtonClick() { + await setFieldValue('amount', formattedTokenBalance); + } + + function handleOnChangeAmountInput(value: string, symbol: string) { + const valueWithoutSuffix = value.replace(symbol, ''); + setFieldValue('amount', valueWithoutSuffix); + } + + const isMaxActionDisabled = + parseAmount(values?.amount, coinDecimals) === coinBalance || + queryResult.isPending || + !coinBalance; + + return ( +
+
+
+ {!hasEnoughBalance && ( + } + /> + )} + + + {({ field }: { field: FieldInputProps }) => { + return ( + handleOnChangeAmountInput(value, symbol)} + onBlur={handleBlur} + errorMessage={ + touched.amount && errors.amount ? errors.amount : undefined + } + /> + ); + }} + + + +
+
+ +
+
+
+ ); +} + function EnterValuesFormView({ coin, activeAddress, @@ -152,115 +271,20 @@ function EnterValuesFormView({ validateOnBlur={false} onSubmit={handleFormSubmit} > - {({ - isValid, - isSubmitting, - setFieldValue, - values, - submitForm, - touched, - errors, - handleBlur, - }) => { - const newPayIotaAll = - parseAmount(values.amount, coinDecimals) === coinBalance && - coin.coinType === IOTA_TYPE_ARG; - if (values.isPayAllIota !== newPayIotaAll) { - setFieldValue('isPayAllIota', newPayIotaAll); - } - - const hasEnoughBalance = - values.isPayAllIota || - iotaBalance > - parseAmount(values.gasBudgetEst, coinDecimals) + - parseAmount( - coin.coinType === IOTA_TYPE_ARG ? values.amount : '0', - coinDecimals, - ); - - async function onMaxTokenButtonClick() { - await setFieldValue('amount', formattedTokenBalance); - } - - function handleOnChangeAmountInput(value: string, symbol: string) { - const valueWithoutSuffix = value.replace(symbol, ''); - setFieldValue('amount', valueWithoutSuffix); - } - - const isMaxActionDisabled = - parseAmount(values?.amount, coinDecimals) === coinBalance || - queryResult.isPending || - !coinBalance; - - return ( -
-
-
- {!hasEnoughBalance && ( - } - /> - )} - - - {({ field }: { field: FieldInputProps }) => { - return ( - - handleOnChangeAmountInput(value, symbol) - } - onBlur={handleBlur} - errorMessage={ - touched.amount && errors.amount - ? errors.amount - : undefined - } - /> - ); - }} - - - -
-
- -
-
-
- ); - }} + {(props: FormikProps) => ( + + )} ); From 3b374253ffb5caaa127edf129299114045eb80a8 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Tue, 12 Nov 2024 13:50:13 +0100 Subject: [PATCH 21/56] fix(wallet-dashboard): improve AddressInputs props --- apps/core/package.json | 1 + .../src/components/Inputs/AddressInput.tsx | 26 +++++++------------ pnpm-lock.yaml | 3 +++ 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/apps/core/package.json b/apps/core/package.json index d91abe24562..0a8cd7acfeb 100644 --- a/apps/core/package.json +++ b/apps/core/package.json @@ -35,6 +35,7 @@ "@tanstack/react-query": "^5.50.1", "bignumber.js": "^9.1.1", "clsx": "^2.1.1", + "formik": "^2.4.2", "qrcode.react": "^4.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index 5656f97cdfe..f3835787832 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -5,20 +5,12 @@ import { Input, InputType } from '@iota/apps-ui-kit'; import { Close } from '@iota/ui-icons'; import { useIotaAddressValidation } from '../../hooks'; -import React, { useCallback } from 'react'; +import React, { ComponentProps, useCallback } from 'react'; +import type { Field, FieldInputProps } from 'formik'; export interface AddressInputProps { - field: { - name: string; - value: string; - onChange: (e: React.ChangeEvent) => void; - onBlur: (e: React.FocusEvent) => void; - }; - form: { - setFieldValue: (field: string, value: string, shouldValidate?: boolean) => void; - errors: Record; - touched: Record; - }; + field: FieldInputProps; + form: ComponentProps; disabled?: boolean; placeholder?: string; label?: string; @@ -26,7 +18,7 @@ export interface AddressInputProps { export function AddressInput({ field, - form: formContext, + form, disabled, placeholder = '0x...', label = 'Enter Recipient Address', @@ -39,16 +31,16 @@ export function AddressInput({ (e: React.ChangeEvent) => { const address = e.currentTarget.value; const validatedValue = iotaAddressValidation.cast(address); - formContext.setFieldValue(field.name, validatedValue, true); + form.setFieldValue(field.name, validatedValue, true); }, - [formContext, field.name, iotaAddressValidation], + [form, field.name, iotaAddressValidation], ); const clearAddress = () => { - formContext.setFieldValue(field.name, ''); + form.setFieldValue(field.name, ''); }; - const errorMessage = formContext.touched[field.name] && formContext.errors[field.name]; + const errorMessage = form.touched[field.name] && form.errors[field.name]; return ( Date: Tue, 12 Nov 2024 13:51:37 +0100 Subject: [PATCH 22/56] fix(wallet-dashboard): linter --- .../components/Dialogs/SendToken/SendTokenDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx index ee325da2220..139b9d0cbe1 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { EnterValuesFormView, ReviewValuesFormView } from './views'; import { CoinBalance } from '@iota/iota-sdk/client'; import { useSendCoinTransaction, useNotifications } from '@/hooks'; From df00b07903d1903a2026fabe1aaaea82a13b1755 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Tue, 12 Nov 2024 13:52:14 +0100 Subject: [PATCH 23/56] fix(wallet-dashboard): format core --- apps/core/src/components/icon/ImageIcon.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/core/src/components/icon/ImageIcon.tsx b/apps/core/src/components/icon/ImageIcon.tsx index d8555c9aa52..6b58dbf715a 100644 --- a/apps/core/src/components/icon/ImageIcon.tsx +++ b/apps/core/src/components/icon/ImageIcon.tsx @@ -26,11 +26,7 @@ interface FallBackAvatarProps { size?: ImageIconSize; } -function FallBackAvatar({ - str, - rounded, - size = ImageIconSize.Large, -}: FallBackAvatarProps) { +function FallBackAvatar({ str, rounded, size = ImageIconSize.Large }: FallBackAvatarProps) { function generateTextSize(size: ImageIconSize) { switch (size) { case ImageIconSize.Small: From 13aaf0e9f278e6ed63f3f150b57db24145e249e4 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Tue, 12 Nov 2024 16:58:24 +0100 Subject: [PATCH 24/56] fix(wallet-dashboard): clean debris --- apps/core/src/components/coin/CoinIcon.tsx | 35 +++------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/apps/core/src/components/coin/CoinIcon.tsx b/apps/core/src/components/coin/CoinIcon.tsx index 7d228bc89c5..77dd5e2a43c 100644 --- a/apps/core/src/components/coin/CoinIcon.tsx +++ b/apps/core/src/components/coin/CoinIcon.tsx @@ -33,41 +33,14 @@ export interface CoinIconProps { coinType: string; size?: ImageIconSize; rounded?: boolean; - hasCoinWrapper?: boolean; } -export function CoinIcon({ - coinType, - size = ImageIconSize.Full, - rounded, - hasCoinWrapper, -}: CoinIconProps) { - const Component = hasCoinWrapper ? CoinIconWrapper : React.Fragment; +export function CoinIcon({ coinType, size = ImageIconSize.Full, rounded }: CoinIconProps) { return coinType === IOTA_TYPE_ARG ? ( - -
- -
-
+
+ +
) : ( ); } - -type CoinIconWrapperProps = React.PropsWithChildren> & { - hasBorder?: boolean; -}; - -export function CoinIconWrapper({ children, size, hasBorder }: CoinIconWrapperProps) { - return ( -
- {children} -
- ); -} From 288483f30fe429415e3cad7b4050ff8dd6247d14 Mon Sep 17 00:00:00 2001 From: JCNoguera <88061365+VmMad@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:16:36 +0100 Subject: [PATCH 25/56] fix: add ExplorerLink component and add missing dialog styles --- .../components/organisms/dialog/Dialog.tsx | 4 +-- apps/wallet-dashboard/app/globals.css | 8 ++++++ .../Dialogs/SendToken/SendTokenDialog.tsx | 2 +- .../SendToken/views/ReviewValuesFormView.tsx | 17 ++++++++++-- .../components/ExplorerLink.tsx | 27 +++++++++++++++++++ apps/wallet-dashboard/components/index.ts | 1 + 6 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 apps/wallet-dashboard/components/ExplorerLink.tsx diff --git a/apps/ui-kit/src/lib/components/organisms/dialog/Dialog.tsx b/apps/ui-kit/src/lib/components/organisms/dialog/Dialog.tsx index ddd77e87df7..53971681633 100644 --- a/apps/ui-kit/src/lib/components/organisms/dialog/Dialog.tsx +++ b/apps/ui-kit/src/lib/components/organisms/dialog/Dialog.tsx @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef< >(({ showCloseIcon, ...props }, ref) => ( @@ -70,7 +70,7 @@ const DialogContent = React.forwardRef< { diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx index e53170c8c08..fabf82dbc30 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx @@ -25,8 +25,10 @@ import { parseAmount, useCoinMetadata, useFormatCoin, + ExplorerLinkType, } from '@iota/core'; import { Loader } from '@iota/ui-icons'; +import { ExplorerLink } from '@/components'; interface ReviewValuesFormProps { formData: FormDataValues; @@ -73,14 +75,25 @@ export function ReviewValuesFormView({
+ {formatAddress(senderAddress)} + + } fullwidth /> + {formatAddress(to || '')} + + } fullwidth /> diff --git a/apps/wallet-dashboard/components/ExplorerLink.tsx b/apps/wallet-dashboard/components/ExplorerLink.tsx new file mode 100644 index 00000000000..2b671cf8cc5 --- /dev/null +++ b/apps/wallet-dashboard/components/ExplorerLink.tsx @@ -0,0 +1,27 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { useExplorerLinkGetter } from '@/hooks'; +import { getExplorerLink } from '@iota/core'; +import Link from 'next/link'; + +type GetExplorerLinkArgs = Parameters[0]; + +type ExplorerLinkProps = GetExplorerLinkArgs & { + isExternal?: boolean; +}; + +export function ExplorerLink({ + children, + isExternal, + ...getLinkProps +}: React.PropsWithChildren): React.JSX.Element { + const getExplorerLink = useExplorerLinkGetter(); + const href = getExplorerLink(getLinkProps) ?? '#'; + + return ( + + {children} + + ); +} diff --git a/apps/wallet-dashboard/components/index.ts b/apps/wallet-dashboard/components/index.ts index a6c298a4941..e545e4f9199 100644 --- a/apps/wallet-dashboard/components/index.ts +++ b/apps/wallet-dashboard/components/index.ts @@ -19,6 +19,7 @@ export * from './Cards'; export * from './Buttons'; export * from './transactions'; export * from './staking-overview'; +export * from './ExplorerLink'; export * from './Dialogs'; export * from './ImageIcon'; export * from './ValidatorStakingData'; From f1699c0047c8bfcaea21c1b8acd96923aa202847 Mon Sep 17 00:00:00 2001 From: JCNoguera <88061365+VmMad@users.noreply.github.com> Date: Wed, 13 Nov 2024 12:37:01 +0100 Subject: [PATCH 26/56] fix: use correct values for keyvalue --- apps/core/src/components/Inputs/SendTokenFormInput.tsx | 4 +++- apps/core/src/hooks/useGasBudgetEstimation.ts | 8 +++++++- .../components/Dialogs/SendToken/SendTokenDialog.tsx | 3 +-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index baee61e29a3..8f957245782 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -6,6 +6,7 @@ import { CoinStruct } from '@iota/iota-sdk/client'; import { useGasBudgetEstimation } from '../../hooks'; import { FormInput } from '..'; import React, { useEffect } from 'react'; +import { GAS_SYMBOL } from '../../constants'; export interface SendTokenInputProps { coins: CoinStruct[]; @@ -47,6 +48,7 @@ export function SendTokenFormInput({ to: values.to, amount: values.amount, isPayAllIota: values.isPayAllIota, + showGasSymbol: false, }); // gasBudgetEstimation should change when the amount above changes @@ -65,7 +67,7 @@ export function SendTokenFormInput({ decimals allowNegative={false} prefix={values.isPayAllIota ? '~ ' : undefined} - amountCounter={coins ? gasBudgetEstimation : '--'} + amountCounter={coins ? `${gasBudgetEstimation} ${GAS_SYMBOL}` : '--'} value={value} onChange={onChange} onBlur={onBlur} diff --git a/apps/core/src/hooks/useGasBudgetEstimation.ts b/apps/core/src/hooks/useGasBudgetEstimation.ts index 5dbe7d2ff1e..be6f6abba5f 100644 --- a/apps/core/src/hooks/useGasBudgetEstimation.ts +++ b/apps/core/src/hooks/useGasBudgetEstimation.ts @@ -16,8 +16,11 @@ interface UseGasBudgetEstimationOptions { to: string; amount: string; isPayAllIota: boolean; + showGasSymbol?: boolean; } +const FALLBACK_GAS_VALUE = '--'; + export function useGasBudgetEstimation({ coinDecimals, coins, @@ -25,6 +28,7 @@ export function useGasBudgetEstimation({ to, amount, isPayAllIota, + showGasSymbol = true, }: UseGasBudgetEstimationOptions) { const client = useIotaClient(); const { data: gasBudget } = useQuery({ @@ -61,5 +65,7 @@ export function useGasBudgetEstimation({ const [formattedGas] = useFormatCoin(gasBudget, IOTA_TYPE_ARG); - return formattedGas ? formattedGas + ' ' + GAS_SYMBOL : '--'; + return formattedGas + ? `${formattedGas}${showGasSymbol ? ` ${GAS_SYMBOL}` : ''}` + : FALLBACK_GAS_VALUE; } diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx index 087a357fa3b..a39d2104a18 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx @@ -114,11 +114,10 @@ function SendTokenDialogBody({ {step === FormStep.ReviewValues && ( )} From 28a9353d27a87be4d246d7e90ed3b02d13ee060e Mon Sep 17 00:00:00 2001 From: cpl121 Date: Wed, 13 Nov 2024 14:50:18 +0100 Subject: [PATCH 27/56] fix(wallet-dashboard): bring back the validation field --- apps/core/src/components/Inputs/AddressInput.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index f3835787832..3b91350ebed 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -30,8 +30,10 @@ export function AddressInput({ const handleOnChange = useCallback( (e: React.ChangeEvent) => { const address = e.currentTarget.value; - const validatedValue = iotaAddressValidation.cast(address); - form.setFieldValue(field.name, validatedValue, true); + iotaAddressValidation.cast(address); + form.setFieldValue(field.name, iotaAddressValidation.cast(address)).then(() => { + form.validateField(field.name); + }); }, [form, field.name, iotaAddressValidation], ); From 72bfb38607cf6d7b7d7545a9a3005e5dc5899826 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Wed, 13 Nov 2024 14:53:46 +0100 Subject: [PATCH 28/56] fix(wallet-dashboard): bad merge removing duplicated image components --- .../wallet-dashboard/components/ImageIcon.tsx | 80 ------------------- .../components/coins/CoinIcon.tsx | 45 ----------- .../components/coins/CoinItem.tsx | 4 +- .../components/coins/index.ts | 1 - apps/wallet-dashboard/components/index.ts | 1 - 5 files changed, 1 insertion(+), 130 deletions(-) delete mode 100644 apps/wallet-dashboard/components/ImageIcon.tsx delete mode 100644 apps/wallet-dashboard/components/coins/CoinIcon.tsx diff --git a/apps/wallet-dashboard/components/ImageIcon.tsx b/apps/wallet-dashboard/components/ImageIcon.tsx deleted file mode 100644 index 06939516971..00000000000 --- a/apps/wallet-dashboard/components/ImageIcon.tsx +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { useState } from 'react'; -import Image from 'next/image'; -import cn from 'clsx'; - -export enum ImageIconSize { - Small = 'w-5 h-5', - Medium = 'w-8 h-8', - Large = 'w-10 h-10', - Full = 'w-full h-full', -} - -interface FallBackAvatarProps { - text: string; - rounded?: boolean; - size?: ImageIconSize; -} -function FallBackAvatar({ text, rounded, size = ImageIconSize.Large }: FallBackAvatarProps) { - const textSize = (() => { - switch (size) { - case ImageIconSize.Small: - return 'text-label-sm'; - case ImageIconSize.Medium: - return 'text-label-md'; - case ImageIconSize.Large: - return 'text-title-md'; - case ImageIconSize.Full: - return 'text-title-lg'; - } - })(); - - return ( -
- {text.slice(0, 2)} -
- ); -} -export interface ImageIconProps { - src: string | null | undefined; - label: string; - fallbackText: string; - alt?: string; - rounded?: boolean; - size?: ImageIconSize; -} - -export function ImageIcon({ - src, - label, - alt = label, - fallbackText, - rounded, - size, -}: ImageIconProps) { - const [error, setError] = useState(false); - return ( -
- {error || !src ? ( - - ) : ( - {alt} setError(true)} - layout="fill" - objectFit="cover" - /> - )} -
- ); -} diff --git a/apps/wallet-dashboard/components/coins/CoinIcon.tsx b/apps/wallet-dashboard/components/coins/CoinIcon.tsx deleted file mode 100644 index a5c24107baa..00000000000 --- a/apps/wallet-dashboard/components/coins/CoinIcon.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { useCoinMetadata } from '@iota/core'; -import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { IotaLogoMark } from '@iota/ui-icons'; -import cx from 'clsx'; -import { ImageIcon, ImageIconSize } from '../ImageIcon'; - -interface NonIotaCoinProps { - coinType: string; - size?: ImageIconSize; - rounded?: boolean; -} - -function NonIotaCoin({ coinType, size = ImageIconSize.Full, rounded }: NonIotaCoinProps) { - const { data: coinMeta } = useCoinMetadata(coinType); - return ( -
- -
- ); -} - -export interface CoinIconProps { - coinType: string; - size?: ImageIconSize; - rounded?: boolean; -} - -export function CoinIcon({ coinType, size = ImageIconSize.Full, rounded }: CoinIconProps) { - return coinType === IOTA_TYPE_ARG ? ( -
- -
- ) : ( - - ); -} diff --git a/apps/wallet-dashboard/components/coins/CoinItem.tsx b/apps/wallet-dashboard/components/coins/CoinItem.tsx index 29e8be5aa35..01528cb5ac1 100644 --- a/apps/wallet-dashboard/components/coins/CoinItem.tsx +++ b/apps/wallet-dashboard/components/coins/CoinItem.tsx @@ -10,11 +10,9 @@ import { CardType, ImageType, } from '@iota/apps-ui-kit'; -import { useFormatCoin } from '@iota/core'; +import { CoinIcon, useFormatCoin, ImageIconSize } from '@iota/core'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; import { type ReactNode } from 'react'; -import { ImageIconSize } from '../ImageIcon'; -import { CoinIcon } from './CoinIcon'; interface CoinItemProps { coinType: string; diff --git a/apps/wallet-dashboard/components/coins/index.ts b/apps/wallet-dashboard/components/coins/index.ts index 51468194c31..d1519105eec 100644 --- a/apps/wallet-dashboard/components/coins/index.ts +++ b/apps/wallet-dashboard/components/coins/index.ts @@ -3,4 +3,3 @@ export { default as MyCoins } from './MyCoins'; export { default as CoinItem } from './CoinItem'; -export * from './CoinIcon'; diff --git a/apps/wallet-dashboard/components/index.ts b/apps/wallet-dashboard/components/index.ts index a6c298a4941..83697f63914 100644 --- a/apps/wallet-dashboard/components/index.ts +++ b/apps/wallet-dashboard/components/index.ts @@ -20,6 +20,5 @@ export * from './Buttons'; export * from './transactions'; export * from './staking-overview'; export * from './Dialogs'; -export * from './ImageIcon'; export * from './ValidatorStakingData'; export * from './tiles'; From 86c27ae148d69e742a225d5f315774cbd813f7a8 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Wed, 13 Nov 2024 14:54:14 +0100 Subject: [PATCH 29/56] fix(wallet-dashboard): remove unnecesary InputForm component --- apps/core/src/components/Inputs/FormInput.tsx | 71 ------------------- .../components/Inputs/SendTokenFormInput.tsx | 64 ++++++++++------- apps/core/src/components/Inputs/index.ts | 1 - apps/core/src/components/index.ts | 1 + .../SendToken/views/EnterValuesFormView.tsx | 2 +- .../home/transfer-coin/SendTokenForm.tsx | 2 +- 6 files changed, 41 insertions(+), 100 deletions(-) delete mode 100644 apps/core/src/components/Inputs/FormInput.tsx diff --git a/apps/core/src/components/Inputs/FormInput.tsx b/apps/core/src/components/Inputs/FormInput.tsx deleted file mode 100644 index eeecd383a5c..00000000000 --- a/apps/core/src/components/Inputs/FormInput.tsx +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Input, InputType, type InputProps, type NumericFormatInputProps } from '@iota/apps-ui-kit'; -import React from 'react'; - -interface FormInputProps extends Omit { - name: string; - value: string; - suffix: string; - allowNegative: boolean; - onChange: (value: string) => void; - onBlur?: React.FocusEventHandler; - errorMessage?: string; - renderAction?: (isDisabled?: boolean) => React.JSX.Element; - decimals?: boolean; - disabled?: boolean; - isSubmitting?: boolean; -} - -export function FormInput({ - renderAction, - decimals, - value, - onChange, - onBlur, - errorMessage, - isSubmitting = false, - disabled, - name, - type, - placeholder, - caption, - amountCounter, - label, - suffix, - allowNegative, -}: FormInputProps) { - const isInputDisabled = isSubmitting || disabled; - const isNumericFormat = type === InputType.NumericFormat; - - const numericPropsOnly: Partial = { - decimalScale: decimals ? undefined : 0, - thousandSeparator: true, - onValueChange: (values) => { - onChange(values.value); - }, - }; - - const isActionButtonDisabled = isInputDisabled || !value || !!errorMessage; - - return ( - onChange(e.currentTarget.value)} - amountCounter={!errorMessage ? amountCounter : undefined} - trailingElement={renderAction?.(isActionButtonDisabled)} - {...(isNumericFormat ? numericPropsOnly : {})} - /> - ); -} diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index baee61e29a3..c07d3edfb33 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -1,10 +1,9 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ButtonPill, InputType } from '@iota/apps-ui-kit'; +import { ButtonPill, Input, InputType, NumericFormatInputProps } from '@iota/apps-ui-kit'; import { CoinStruct } from '@iota/iota-sdk/client'; import { useGasBudgetEstimation } from '../../hooks'; -import { FormInput } from '..'; import React, { useEffect } from 'react'; export interface SendTokenInputProps { @@ -19,7 +18,7 @@ export interface SendTokenInputProps { isPayAllIota: boolean; }; onActionClick: () => Promise; - isActionButtonDisabled?: boolean | 'auto'; + isMaxActionDisabled?: boolean | 'auto'; value: string; onChange: (value: string) => void; onBlur?: React.FocusEventHandler; @@ -34,7 +33,7 @@ export function SendTokenFormInput({ activeAddress, setFieldValue, onActionClick, - isActionButtonDisabled, + isMaxActionDisabled, value, onChange, onBlur, @@ -48,6 +47,30 @@ export function SendTokenFormInput({ amount: values.amount, isPayAllIota: values.isPayAllIota, }); + + const numericPropsOnly: Partial = { + decimalScale: coinDecimals ? undefined : 0, + thousandSeparator: true, + onValueChange: (values) => { + onChange(values.value); + }, + }; + + + const isActionButtonDisabled = !value || !!errorMessage; + + const renderAction = (isButtonDisabled: boolean | undefined) => ( + + Max + + ) // gasBudgetEstimation should change when the amount above changes useEffect(() => { @@ -55,33 +78,22 @@ export function SendTokenFormInput({ }, [gasBudgetEstimation, setFieldValue, values.amount]); return ( - ( - - Max - - )} + onChange={(e) => onChange(e.currentTarget.value)} + amountCounter={!errorMessage ? (coins ? gasBudgetEstimation : '--') : undefined} + trailingElement={renderAction(isActionButtonDisabled)} + {...numericPropsOnly} /> ); } diff --git a/apps/core/src/components/Inputs/index.ts b/apps/core/src/components/Inputs/index.ts index 9f01e814469..5ab7c849a28 100644 --- a/apps/core/src/components/Inputs/index.ts +++ b/apps/core/src/components/Inputs/index.ts @@ -2,5 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 export * from './AddressInput'; -export * from './FormInput'; export * from './SendTokenFormInput'; diff --git a/apps/core/src/components/index.ts b/apps/core/src/components/index.ts index 4dea9923bba..0a8093eeadb 100644 --- a/apps/core/src/components/index.ts +++ b/apps/core/src/components/index.ts @@ -4,5 +4,6 @@ export * from './KioskClientProvider'; export * from './coin'; +export * from './icon'; export * from './Inputs'; export * from './QR'; diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx index de6c598fd17..b7c5e437f61 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx @@ -129,7 +129,7 @@ function FormInputs({ setFieldValue={setFieldValue} values={values} onActionClick={onMaxTokenButtonClick} - isActionButtonDisabled={isMaxActionDisabled} + isMaxActionDisabled={isMaxActionDisabled} value={field.value} onChange={(value) => handleOnChangeAmountInput(value, symbol)} onBlur={handleBlur} diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index 796e62a998e..e60870cd219 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -208,7 +208,7 @@ export function SendTokenForm({ coins={coins ?? []} values={values} onActionClick={onMaxTokenButtonClick} - isActionButtonDisabled={isMaxActionDisabled} + isMaxActionDisabled={isMaxActionDisabled} value={field.value} onChange={(value) => handleOnChangeAmountInput(value, symbol) From 7e6c12d4c87471f6421213510a840f3a25c638a6 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Wed, 13 Nov 2024 14:54:37 +0100 Subject: [PATCH 30/56] fix(wallet-dashboard): adjust to full height the dialog body --- .../components/Dialogs/SendToken/SendTokenDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx index 139b9d0cbe1..35118aa1228 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx @@ -96,7 +96,7 @@ function SendTokenDialogBody({ title={step === FormStep.EnterValues ? 'Send' : 'Review & Send'} onClose={() => setOpen(false)} /> -
+
{step === FormStep.EnterValues && ( Date: Wed, 13 Nov 2024 15:32:25 +0100 Subject: [PATCH 31/56] fix(wallet-dashboard): prettier --- .../components/Inputs/SendTokenFormInput.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index c07d3edfb33..2298d433260 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -47,7 +47,7 @@ export function SendTokenFormInput({ amount: values.amount, isPayAllIota: values.isPayAllIota, }); - + const numericPropsOnly: Partial = { decimalScale: coinDecimals ? undefined : 0, thousandSeparator: true, @@ -56,21 +56,16 @@ export function SendTokenFormInput({ }, }; - const isActionButtonDisabled = !value || !!errorMessage; - + const renderAction = (isButtonDisabled: boolean | undefined) => ( + > Max - ) + ); // gasBudgetEstimation should change when the amount above changes useEffect(() => { @@ -80,7 +75,7 @@ export function SendTokenFormInput({ return ( Date: Wed, 13 Nov 2024 15:43:20 +0100 Subject: [PATCH 32/56] fix: gas approximation --- apps/core/src/components/Inputs/SendTokenFormInput.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index 60266dd52b9..64dd3cae36c 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -89,7 +89,11 @@ export function SendTokenFormInput({ errorMessage={errorMessage} onChange={(e) => onChange(e.currentTarget.value)} amountCounter={ - !errorMessage ? (coins ? `${gasBudgetEstimation} ${GAS_SYMBOL}` : '--') : undefined + !errorMessage + ? coins && gasBudgetEstimation !== '--' + ? `${gasBudgetEstimation} ${GAS_SYMBOL}` + : '--' + : undefined } trailingElement={renderAction(isActionButtonDisabled)} {...numericPropsOnly} From 78e552795d9cfd63d895ad18a2544c5d142778aa Mon Sep 17 00:00:00 2001 From: cpl121 Date: Thu, 14 Nov 2024 12:05:33 +0100 Subject: [PATCH 33/56] fix(wallet-dashboard): max button disabled --- .../components/Inputs/SendTokenFormInput.tsx | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index 2298d433260..43acef9d909 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -4,24 +4,23 @@ import { ButtonPill, Input, InputType, NumericFormatInputProps } from '@iota/apps-ui-kit'; import { CoinStruct } from '@iota/iota-sdk/client'; import { useGasBudgetEstimation } from '../../hooks'; -import React, { useEffect } from 'react'; +import React, { ComponentProps, useEffect } from 'react'; +import { Field, FieldInputProps } from 'formik'; export interface SendTokenInputProps { coins: CoinStruct[]; symbol: string; coinDecimals: number; activeAddress: string; - setFieldValue: (field: string, value: string, shouldValidate?: boolean) => void; values: { amount: string; to: string; isPayAllIota: boolean; }; onActionClick: () => Promise; - isMaxActionDisabled?: boolean | 'auto'; - value: string; - onChange: (value: string) => void; - onBlur?: React.FocusEventHandler; + isMaxActionDisabled?: boolean; + field: FieldInputProps; + form: ComponentProps; errorMessage?: string; } @@ -31,12 +30,10 @@ export function SendTokenFormInput({ symbol, coinDecimals, activeAddress, - setFieldValue, onActionClick, isMaxActionDisabled, - value, - onChange, - onBlur, + field, + form, errorMessage, }: SendTokenInputProps) { const gasBudgetEstimation = useGasBudgetEstimation({ @@ -52,42 +49,39 @@ export function SendTokenFormInput({ decimalScale: coinDecimals ? undefined : 0, thousandSeparator: true, onValueChange: (values) => { - onChange(values.value); + form.setFieldValue(field.name, values.value).then(() => { + form.validateField(field.name); + }); }, }; - const isActionButtonDisabled = !value || !!errorMessage; + const isActionButtonDisabled = form.isSubmitting || !!errorMessage || isMaxActionDisabled; - const renderAction = (isButtonDisabled: boolean | undefined) => ( - + const renderAction = () => ( + Max ); // gasBudgetEstimation should change when the amount above changes useEffect(() => { - setFieldValue('gasBudgetEst', gasBudgetEstimation, false); - }, [gasBudgetEstimation, setFieldValue, values.amount]); + form.setFieldValue('gasBudgetEst', gasBudgetEstimation, false); + }, [gasBudgetEstimation, form.setFieldValue, values.amount]); return ( onChange(e.currentTarget.value)} amountCounter={!errorMessage ? (coins ? gasBudgetEstimation : '--') : undefined} - trailingElement={renderAction(isActionButtonDisabled)} + trailingElement={renderAction()} {...numericPropsOnly} /> ); From 1fe1d0580e8dbf0c3efc06453c01f1174ac9c330 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Thu, 14 Nov 2024 12:05:45 +0100 Subject: [PATCH 34/56] feat(wallet-dashboard): improvements --- .../Dialogs/SendToken/SendTokenDialog.tsx | 11 +++----- .../SendToken/views/EnterValuesFormView.tsx | 28 +++++++++---------- .../SendToken/views/ReviewValuesFormView.tsx | 6 ++-- .../hooks/useSendCoinTransaction.ts | 6 ---- .../home/transfer-coin/SendTokenForm.tsx | 18 ++++++------ 5 files changed, 30 insertions(+), 39 deletions(-) diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx index 35118aa1228..3c8f70b9e66 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx @@ -40,14 +40,13 @@ function SendTokenDialogBody({ coin, activeAddress, setOpen, - open, }: SendCoinPopupProps): React.JSX.Element { const [step, setStep] = useState(FormStep.EnterValues); const [selectedCoin, setSelectedCoin] = useState(coin); const [formData, setFormData] = useState(INITIAL_VALUES); const { addNotification } = useNotifications(); - const { data: coinsData } = useGetAllCoins(selectedCoin?.coinType, activeAddress); + const { data: coinsData } = useGetAllCoins(selectedCoin.coinType, activeAddress); const { mutateAsync: signAndExecuteTransaction, @@ -55,7 +54,7 @@ function SendTokenDialogBody({ isPending, } = useSignAndExecuteTransaction(); - const { data: sendCoinData } = useSendCoinTransaction( + const { data: transaction } = useSendCoinTransaction( coinsData || [], selectedCoin?.coinType, activeAddress, @@ -65,12 +64,12 @@ function SendTokenDialogBody({ ); function handleTransfer() { - if (!sendCoinData?.transaction) { + if (!transaction) { addNotification('There was an error with the transaction', NotificationType.Error); return; } else { signAndExecuteTransaction({ - transaction: sendCoinData.transaction, + transaction, }) .then(() => { setOpen(false); @@ -102,7 +101,6 @@ function SendTokenDialogBody({ diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx index b7c5e437f61..a77f81a36a9 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx @@ -31,11 +31,11 @@ import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; import { Field, FieldInputProps, Form, Formik, FormikProps } from 'formik'; import { Exclamation } from '@iota/ui-icons'; import { UseQueryResult } from '@tanstack/react-query'; +import { ComponentProps } from 'react'; interface EnterValuesFormProps { coin: CoinBalance; activeAddress: string; - gasBudget: string; setFormData: React.Dispatch>; setSelectedCoin: React.Dispatch>; onNext: () => void; @@ -68,7 +68,6 @@ function FormInputs({ submitForm, touched, errors, - handleBlur, coinType, coinDecimals, coinBalance, @@ -95,13 +94,8 @@ function FormInputs({ await setFieldValue('amount', formattedTokenBalance); } - function handleOnChangeAmountInput(value: string, symbol: string) { - const valueWithoutSuffix = value.replace(symbol, ''); - setFieldValue('amount', valueWithoutSuffix); - } - const isMaxActionDisabled = - parseAmount(values?.amount, coinDecimals) === coinBalance || + parseAmount(values.amount, coinDecimals) === coinBalance || queryResult.isPending || !coinBalance; @@ -119,20 +113,24 @@ function FormInputs({ )} - {({ field }: { field: FieldInputProps }) => { + {({ + field, + form, + }: { + field: FieldInputProps; + form: ComponentProps; + }) => { return ( handleOnChangeAmountInput(value, symbol)} - onBlur={handleBlur} errorMessage={ touched.amount && errors.amount ? errors.amount : undefined } @@ -231,9 +229,11 @@ function EnterValuesFormView({ .sort((a, b) => Number(b.balance) - Number(a.balance)) .map(({ coinObjectId }) => coinObjectId); + const formattedAmount = parseAmount(amount, coinDecimals).toString(); + const data = { to, - amount, + amount: formattedAmount, isPayAllIota, coins, coinIds: coinsIDs, diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx index 97151a1cf24..282f0a19be9 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx @@ -7,7 +7,6 @@ import { Button } from '@/components'; interface ReviewValuesFormProps { formData: FormDataValues; senderAddress: string; - gasBudget: string; error: string | undefined; isPending: boolean; executeTransfer: () => void; @@ -15,9 +14,8 @@ interface ReviewValuesFormProps { } function ReviewValuesFormView({ - formData: { amount, to }, + formData: { amount, to, gasBudgetEst }, senderAddress, - gasBudget, error, isPending, executeTransfer, @@ -30,7 +28,7 @@ function ReviewValuesFormView({

Sending: {amount}

From: {senderAddress}

To: {to}

-

Gas fee: {gasBudget}

+

Gas fee: {gasBudgetEst}

{error ? {error} : null}
diff --git a/apps/wallet-dashboard/hooks/useSendCoinTransaction.ts b/apps/wallet-dashboard/hooks/useSendCoinTransaction.ts index 8b793e6405d..b439c27a2df 100644 --- a/apps/wallet-dashboard/hooks/useSendCoinTransaction.ts +++ b/apps/wallet-dashboard/hooks/useSendCoinTransaction.ts @@ -45,11 +45,5 @@ export function useSendCoinTransaction( }, enabled: !!recipientAddress && !!amount && !!coins && !!senderAddress && !!coinType, gcTime: 0, - select: (transaction) => { - return { - transaction, - gasBudget: transaction.getData().gasData.budget, - }; - }, }); } diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index e60870cd219..2c75f497387 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -17,7 +17,7 @@ import { import { type CoinStruct } from '@iota/iota-sdk/client'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; import { Field, type FieldInputProps, Form, Formik } from 'formik'; -import { useMemo } from 'react'; +import { ComponentProps, useMemo } from 'react'; import { InfoBox, @@ -198,22 +198,24 @@ export function SendTokenForm({ ) : null} - {({ field }: { field: FieldInputProps }) => { + {({ + field, + form, + }: { + field: FieldInputProps; + form: ComponentProps; + }) => { return ( - handleOnChangeAmountInput(value, symbol) - } - onBlur={handleBlur} errorMessage={ touched.amount && errors.amount ? errors.amount From 94bcca1f99ad7cc2172a7935c7defa6fc5ad698a Mon Sep 17 00:00:00 2001 From: cpl121 Date: Thu, 14 Nov 2024 13:36:08 +0100 Subject: [PATCH 35/56] fix(wallet-dashboard): improve formik props --- .../src/components/Inputs/AddressInput.tsx | 23 ++++++++--------- .../components/Inputs/SendTokenFormInput.tsx | 21 ++++++++-------- .../SendToken/views/EnterValuesFormView.tsx | 19 ++++---------- .../home/transfer-coin/SendTokenForm.tsx | 25 ++++--------------- 4 files changed, 31 insertions(+), 57 deletions(-) diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index 3b91350ebed..bcfe125eaac 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -5,24 +5,23 @@ import { Input, InputType } from '@iota/apps-ui-kit'; import { Close } from '@iota/ui-icons'; import { useIotaAddressValidation } from '../../hooks'; -import React, { ComponentProps, useCallback } from 'react'; -import type { Field, FieldInputProps } from 'formik'; +import React, { useCallback } from 'react'; +import { useField } from 'formik'; export interface AddressInputProps { - field: FieldInputProps; - form: ComponentProps; + name: string, disabled?: boolean; placeholder?: string; label?: string; } export function AddressInput({ - field, - form, + name, disabled, placeholder = '0x...', label = 'Enter Recipient Address', }: AddressInputProps) { + const [field, meta, helpers] = useField(name) const iotaAddressValidation = useIotaAddressValidation(); const formattedValue = iotaAddressValidation.cast(field.value); @@ -31,18 +30,16 @@ export function AddressInput({ (e: React.ChangeEvent) => { const address = e.currentTarget.value; iotaAddressValidation.cast(address); - form.setFieldValue(field.name, iotaAddressValidation.cast(address)).then(() => { - form.validateField(field.name); - }); + helpers.setValue(iotaAddressValidation.cast(address)); }, - [form, field.name, iotaAddressValidation], + [name, iotaAddressValidation], ); const clearAddress = () => { - form.setFieldValue(field.name, ''); + helpers.setValue(''); }; - const errorMessage = form.touched[field.name] && form.errors[field.name]; + const errorMessage = meta.touched && meta.error; return ( ); -} +} \ No newline at end of file diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index 43acef9d909..3956c18f573 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -4,10 +4,10 @@ import { ButtonPill, Input, InputType, NumericFormatInputProps } from '@iota/apps-ui-kit'; import { CoinStruct } from '@iota/iota-sdk/client'; import { useGasBudgetEstimation } from '../../hooks'; -import React, { ComponentProps, useEffect } from 'react'; -import { Field, FieldInputProps } from 'formik'; +import { useEffect } from 'react'; +import { FormikProps, useField } from 'formik'; -export interface SendTokenInputProps { +export interface SendTokenInputProps { coins: CoinStruct[]; symbol: string; coinDecimals: number; @@ -19,12 +19,11 @@ export interface SendTokenInputProps { }; onActionClick: () => Promise; isMaxActionDisabled?: boolean; - field: FieldInputProps; - form: ComponentProps; - errorMessage?: string; + name: string; + form: FormikProps; } -export function SendTokenFormInput({ +export function SendTokenFormInput({ coins, values, symbol, @@ -32,10 +31,9 @@ export function SendTokenFormInput({ activeAddress, onActionClick, isMaxActionDisabled, - field, + name, form, - errorMessage, -}: SendTokenInputProps) { +}: SendTokenInputProps) { const gasBudgetEstimation = useGasBudgetEstimation({ coinDecimals, coins: coins ?? [], @@ -45,6 +43,8 @@ export function SendTokenFormInput({ isPayAllIota: values.isPayAllIota, }); + const [field, meta] = useField(name); + const numericPropsOnly: Partial = { decimalScale: coinDecimals ? undefined : 0, thousandSeparator: true, @@ -55,6 +55,7 @@ export function SendTokenFormInput({ }, }; + const errorMessage = meta?.error ? meta.error : undefined; const isActionButtonDisabled = form.isSubmitting || !!errorMessage || isMaxActionDisabled; const renderAction = () => ( diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx index a77f81a36a9..91021dfd2e2 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx @@ -31,7 +31,6 @@ import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; import { Field, FieldInputProps, Form, Formik, FormikProps } from 'formik'; import { Exclamation } from '@iota/ui-icons'; import { UseQueryResult } from '@tanstack/react-query'; -import { ComponentProps } from 'react'; interface EnterValuesFormProps { coin: CoinBalance; @@ -118,12 +117,12 @@ function FormInputs({ form, }: { field: FieldInputProps; - form: ComponentProps; + form: FormikProps; }) => { return ( ); }} - +
@@ -267,8 +258,8 @@ function EnterValuesFormView({ }} validationSchema={validationSchemaStepOne} enableReinitialize - validateOnChange={false} - validateOnBlur={false} + validateOnChange={true} + validateOnBlur={true} onSubmit={handleFormSubmit} > {(props: FormikProps) => ( diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index 2c75f497387..edc5fda3a04 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -16,8 +16,8 @@ import { } from '@iota/core'; import { type CoinStruct } from '@iota/iota-sdk/client'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { Field, type FieldInputProps, Form, Formik } from 'formik'; -import { ComponentProps, useMemo } from 'react'; +import { Field, type FieldInputProps, Form, Formik, FormikProps } from 'formik'; +import { useMemo } from 'react'; import { InfoBox, @@ -174,11 +174,6 @@ export function SendTokenForm({ await setFieldValue('amount', formattedTokenBalance); } - function handleOnChangeAmountInput(value: string, symbol: string) { - const valueWithoutSuffix = value.replace(symbol, ''); - setFieldValue('amount', valueWithoutSuffix); - } - const isMaxActionDisabled = parseAmount(values?.amount, coinDecimals) === coinBalance || queryResult.isPending || @@ -203,12 +198,12 @@ export function SendTokenForm({ form, }: { field: FieldInputProps; - form: ComponentProps; + form: FormikProps; }) => { return ( ); }} - +
From 17058ad9be7bd965721c80515c172e537d86ae4c Mon Sep 17 00:00:00 2001 From: cpl121 Date: Thu, 14 Nov 2024 14:00:17 +0100 Subject: [PATCH 36/56] fix(wallet-dashboard): improvements --- .../src/components/Inputs/AddressInput.tsx | 1 - .../components/Inputs/SendTokenFormInput.tsx | 31 +++++++++---------- .../SendToken/views/EnterValuesFormView.tsx | 6 ++-- .../home/transfer-coin/SendTokenForm.tsx | 11 +++---- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index bcfe125eaac..187e773ad06 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -29,7 +29,6 @@ export function AddressInput({ const handleOnChange = useCallback( (e: React.ChangeEvent) => { const address = e.currentTarget.value; - iotaAddressValidation.cast(address); helpers.setValue(iotaAddressValidation.cast(address)); }, [name, iotaAddressValidation], diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index 3956c18f573..c3346cef1f9 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -6,17 +6,16 @@ import { CoinStruct } from '@iota/iota-sdk/client'; import { useGasBudgetEstimation } from '../../hooks'; import { useEffect } from 'react'; import { FormikProps, useField } from 'formik'; +import React from 'react'; export interface SendTokenInputProps { coins: CoinStruct[]; symbol: string; coinDecimals: number; activeAddress: string; - values: { - amount: string; - to: string; - isPayAllIota: boolean; - }; + amount: string; + to: string; + isPayAllIota: boolean; onActionClick: () => Promise; isMaxActionDisabled?: boolean; name: string; @@ -25,7 +24,9 @@ export interface SendTokenInputProps { export function SendTokenFormInput({ coins, - values, + amount, + to, + isPayAllIota, symbol, coinDecimals, activeAddress, @@ -38,20 +39,18 @@ export function SendTokenFormInput({ coinDecimals, coins: coins ?? [], activeAddress, - to: values.to, - amount: values.amount, - isPayAllIota: values.isPayAllIota, + to: to, + amount: amount, + isPayAllIota: isPayAllIota, }); - const [field, meta] = useField(name); + const [field, meta, helpers] = useField(name); const numericPropsOnly: Partial = { decimalScale: coinDecimals ? undefined : 0, thousandSeparator: true, onValueChange: (values) => { - form.setFieldValue(field.name, values.value).then(() => { - form.validateField(field.name); - }); + helpers.setValue(values.value) }, }; @@ -67,18 +66,18 @@ export function SendTokenFormInput({ // gasBudgetEstimation should change when the amount above changes useEffect(() => { form.setFieldValue('gasBudgetEst', gasBudgetEstimation, false); - }, [gasBudgetEstimation, form.setFieldValue, values.amount]); + }, [gasBudgetEstimation, form.setFieldValue, amount]); return ( diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index edc5fda3a04..b6b91783fd3 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -140,8 +140,8 @@ export function SendTokenForm({ }} validationSchema={validationSchemaStepOne} enableReinitialize - validateOnChange={false} - validateOnBlur={false} + validateOnChange={true} + validateOnBlur={true} onSubmit={handleFormSubmit} > {({ @@ -150,9 +150,6 @@ export function SendTokenForm({ setFieldValue, values, submitForm, - handleBlur, - touched, - errors, }) => { const newPayIotaAll = parseAmount(values.amount, coinDecimals) === coinBalance && @@ -204,11 +201,13 @@ export function SendTokenForm({ From ddca44cc60bc16ea6e69f54ab65630678e070a62 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Thu, 14 Nov 2024 15:46:12 +0100 Subject: [PATCH 37/56] refactor: Simplify SendTokenFormInput --- .../components/Inputs/SendTokenFormInput.tsx | 45 ++++++++----------- apps/core/src/forms/index.ts | 1 + apps/core/src/forms/token.ts | 6 +++ apps/core/src/index.ts | 1 + .../SendToken/views/EnterValuesFormView.tsx | 36 +++++---------- .../home/transfer-coin/SendTokenForm.tsx | 36 +++++---------- 6 files changed, 46 insertions(+), 79 deletions(-) create mode 100644 apps/core/src/forms/index.ts create mode 100644 apps/core/src/forms/token.ts diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index c3346cef1f9..932daeb3799 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -1,61 +1,48 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ButtonPill, Input, InputType, NumericFormatInputProps } from '@iota/apps-ui-kit'; +import { ButtonPill, Input, InputType } from '@iota/apps-ui-kit'; import { CoinStruct } from '@iota/iota-sdk/client'; import { useGasBudgetEstimation } from '../../hooks'; import { useEffect } from 'react'; -import { FormikProps, useField } from 'formik'; -import React from 'react'; +import { useField, useFormikContext } from 'formik'; +import { TokenForm } from '../../forms'; -export interface SendTokenInputProps { +export interface SendTokenInputProps { coins: CoinStruct[]; symbol: string; coinDecimals: number; activeAddress: string; - amount: string; to: string; - isPayAllIota: boolean; onActionClick: () => Promise; isMaxActionDisabled?: boolean; name: string; - form: FormikProps; } -export function SendTokenFormInput({ +export function SendTokenFormInput({ coins, - amount, to, - isPayAllIota, symbol, coinDecimals, activeAddress, onActionClick, isMaxActionDisabled, name, - form, -}: SendTokenInputProps) { +}: SendTokenInputProps) { + const { values, setFieldValue, isSubmitting } = useFormikContext(); const gasBudgetEstimation = useGasBudgetEstimation({ coinDecimals, coins: coins ?? [], activeAddress, to: to, - amount: amount, - isPayAllIota: isPayAllIota, + amount: values.amount, + isPayAllIota: values.isPayAllIota, }); const [field, meta, helpers] = useField(name); - const numericPropsOnly: Partial = { - decimalScale: coinDecimals ? undefined : 0, - thousandSeparator: true, - onValueChange: (values) => { - helpers.setValue(values.value) - }, - }; - const errorMessage = meta?.error ? meta.error : undefined; - const isActionButtonDisabled = form.isSubmitting || !!errorMessage || isMaxActionDisabled; + const isActionButtonDisabled = isSubmitting || !!errorMessage || isMaxActionDisabled; const renderAction = () => ( @@ -65,8 +52,8 @@ export function SendTokenFormInput({ // gasBudgetEstimation should change when the amount above changes useEffect(() => { - form.setFieldValue('gasBudgetEst', gasBudgetEstimation, false); - }, [gasBudgetEstimation, form.setFieldValue, amount]); + setFieldValue('gasBudgetEst', gasBudgetEstimation, false); + }, [gasBudgetEstimation, setFieldValue, values.amount]); return ( ({ placeholder="0.00" label="Send Amount" suffix={` ${symbol}`} - prefix={isPayAllIota ? '~ ' : undefined} + prefix={values.isPayAllIota ? '~ ' : undefined} allowNegative={false} errorMessage={errorMessage} amountCounter={!errorMessage ? (coins ? gasBudgetEstimation : '--') : undefined} trailingElement={renderAction()} - {...numericPropsOnly} + decimalScale={coinDecimals ? undefined : 0} + thousandSeparator + onValueChange={(values) => { + helpers.setValue(values.value) + }} /> ); } diff --git a/apps/core/src/forms/index.ts b/apps/core/src/forms/index.ts new file mode 100644 index 00000000000..b00ee65e230 --- /dev/null +++ b/apps/core/src/forms/index.ts @@ -0,0 +1 @@ +export * from './token' \ No newline at end of file diff --git a/apps/core/src/forms/token.ts b/apps/core/src/forms/token.ts new file mode 100644 index 00000000000..dde7d93379c --- /dev/null +++ b/apps/core/src/forms/token.ts @@ -0,0 +1,6 @@ +export type TokenForm = { + amount: string, + to: string, + isPayAllIota: boolean + gasBudgetEst: string, +} \ No newline at end of file diff --git a/apps/core/src/index.ts b/apps/core/src/index.ts index 5aed649aada..231b6bfd581 100644 --- a/apps/core/src/index.ts +++ b/apps/core/src/index.ts @@ -8,3 +8,4 @@ export * from './components'; export * from './utils'; export * from './hooks'; export * from './constants'; +export * from './forms' diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx index d43b4ab5f21..0b5954630b5 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx @@ -109,32 +109,16 @@ function FormInputs({ /> )} - - {({ - field, - form, - }: { - field: FieldInputProps; - form: FormikProps; - }) => { - return ( - - ); - }} - - +
diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index b6b91783fd3..096cb3d0af0 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -188,32 +188,16 @@ export function SendTokenForm({ icon={} /> ) : null} - - - {({ - field, - form, - }: { - field: FieldInputProps; - form: FormikProps; - }) => { - return ( - - ); - }} - + From 76d2e893413c2909a9e8f0835b8958e0fdaf1e4f Mon Sep 17 00:00:00 2001 From: marc2332 Date: Thu, 14 Nov 2024 15:50:20 +0100 Subject: [PATCH 38/56] refactor: prettier:fix --- .../src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index 096cb3d0af0..887689a0f92 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -144,13 +144,7 @@ export function SendTokenForm({ validateOnBlur={true} onSubmit={handleFormSubmit} > - {({ - isValid, - isSubmitting, - setFieldValue, - values, - submitForm, - }) => { + {({ isValid, isSubmitting, setFieldValue, values, submitForm }) => { const newPayIotaAll = parseAmount(values.amount, coinDecimals) === coinBalance && coinType === IOTA_TYPE_ARG; From 6b388b6a93e3eea5636d3451ad584f5986415766 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Thu, 14 Nov 2024 15:51:01 +0100 Subject: [PATCH 39/56] refactor: prettier:fix on apps/core --- apps/core/src/components/Inputs/AddressInput.tsx | 6 +++--- apps/core/src/components/Inputs/SendTokenFormInput.tsx | 2 +- apps/core/src/forms/index.ts | 2 +- apps/core/src/forms/token.ts | 10 +++++----- apps/core/src/index.ts | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index 187e773ad06..d0bf195ba0f 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -9,7 +9,7 @@ import React, { useCallback } from 'react'; import { useField } from 'formik'; export interface AddressInputProps { - name: string, + name: string; disabled?: boolean; placeholder?: string; label?: string; @@ -21,7 +21,7 @@ export function AddressInput({ placeholder = '0x...', label = 'Enter Recipient Address', }: AddressInputProps) { - const [field, meta, helpers] = useField(name) + const [field, meta, helpers] = useField(name); const iotaAddressValidation = useIotaAddressValidation(); const formattedValue = iotaAddressValidation.cast(field.value); @@ -64,4 +64,4 @@ export function AddressInput({ } /> ); -} \ No newline at end of file +} diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index 932daeb3799..1b7f0be19ca 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -72,7 +72,7 @@ export function SendTokenFormInput({ decimalScale={coinDecimals ? undefined : 0} thousandSeparator onValueChange={(values) => { - helpers.setValue(values.value) + helpers.setValue(values.value); }} /> ); diff --git a/apps/core/src/forms/index.ts b/apps/core/src/forms/index.ts index b00ee65e230..6b36029d17c 100644 --- a/apps/core/src/forms/index.ts +++ b/apps/core/src/forms/index.ts @@ -1 +1 @@ -export * from './token' \ No newline at end of file +export * from './token'; diff --git a/apps/core/src/forms/token.ts b/apps/core/src/forms/token.ts index dde7d93379c..e8732381984 100644 --- a/apps/core/src/forms/token.ts +++ b/apps/core/src/forms/token.ts @@ -1,6 +1,6 @@ export type TokenForm = { - amount: string, - to: string, - isPayAllIota: boolean - gasBudgetEst: string, -} \ No newline at end of file + amount: string; + to: string; + isPayAllIota: boolean; + gasBudgetEst: string; +}; diff --git a/apps/core/src/index.ts b/apps/core/src/index.ts index 231b6bfd581..6d113bf483b 100644 --- a/apps/core/src/index.ts +++ b/apps/core/src/index.ts @@ -8,4 +8,4 @@ export * from './components'; export * from './utils'; export * from './hooks'; export * from './constants'; -export * from './forms' +export * from './forms'; From 78af8b44e905318bdfcdd7877eb544769a57c6cb Mon Sep 17 00:00:00 2001 From: marc2332 Date: Thu, 14 Nov 2024 16:05:31 +0100 Subject: [PATCH 40/56] refactor: Add missing license header to token.ts --- apps/core/src/forms/token.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/core/src/forms/token.ts b/apps/core/src/forms/token.ts index e8732381984..abad79c1100 100644 --- a/apps/core/src/forms/token.ts +++ b/apps/core/src/forms/token.ts @@ -1,3 +1,6 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + export type TokenForm = { amount: string; to: string; From 3e9fa71e4f6fa7b7f198110159f73419d35d1ab6 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Thu, 14 Nov 2024 16:13:00 +0100 Subject: [PATCH 41/56] fix: linter --- apps/core/src/forms/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/core/src/forms/index.ts b/apps/core/src/forms/index.ts index 6b36029d17c..220b7ec05d5 100644 --- a/apps/core/src/forms/index.ts +++ b/apps/core/src/forms/index.ts @@ -1 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + export * from './token'; From 012b77304e86277c5ac9da4d35cb9eccd93e29f0 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Thu, 14 Nov 2024 16:36:54 +0100 Subject: [PATCH 42/56] fix(wallet-dashboard): linter --- .../components/Dialogs/SendToken/views/EnterValuesFormView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx index 0b5954630b5..2ad0fdba7cc 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx @@ -28,7 +28,7 @@ import { } from '@iota/apps-ui-kit'; import { useIotaClientQuery } from '@iota/dapp-kit'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { Field, FieldInputProps, Form, Formik, FormikProps } from 'formik'; +import { Form, Formik, FormikProps } from 'formik'; import { Exclamation } from '@iota/ui-icons'; import { UseQueryResult } from '@tanstack/react-query'; From e97e8795c00928d62a415b8fa56d247e3849c991 Mon Sep 17 00:00:00 2001 From: cpl121 Date: Thu, 14 Nov 2024 16:42:37 +0100 Subject: [PATCH 43/56] fix(wallet-dashboard): linter --- .../src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index 887689a0f92..57c8e28730d 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -16,7 +16,7 @@ import { } from '@iota/core'; import { type CoinStruct } from '@iota/iota-sdk/client'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; -import { Field, type FieldInputProps, Form, Formik, FormikProps } from 'formik'; +import { Form, Formik } from 'formik'; import { useMemo } from 'react'; import { From 1a2f009d22f85c2b65802f8490c9c61ff7f8f7ac Mon Sep 17 00:00:00 2001 From: JCNoguera <88061365+VmMad@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:12:11 +0100 Subject: [PATCH 44/56] fix: amount format --- .../SendToken/views/ReviewValuesFormView.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx index fabf82dbc30..015d5087318 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/ReviewValuesFormView.tsx @@ -18,15 +18,7 @@ import { ButtonType, } from '@iota/apps-ui-kit'; import { formatAddress } from '@iota/iota-sdk/utils'; -import { - GAS_SYMBOL, - CoinIcon, - ImageIconSize, - parseAmount, - useCoinMetadata, - useFormatCoin, - ExplorerLinkType, -} from '@iota/core'; +import { GAS_SYMBOL, CoinIcon, ImageIconSize, useFormatCoin, ExplorerLinkType } from '@iota/core'; import { Loader } from '@iota/ui-icons'; import { ExplorerLink } from '@/components'; @@ -45,10 +37,7 @@ export function ReviewValuesFormView({ executeTransfer, coinType, }: ReviewValuesFormProps): JSX.Element { - const { data: metadata } = useCoinMetadata(coinType); - const amountWithoutDecimals = parseAmount(amount, metadata?.decimals ?? 0); - const [formatAmount, symbol] = useFormatCoin(amountWithoutDecimals, coinType); - + const [formatAmount, symbol] = useFormatCoin(amount, coinType); return (
From ed42962839e56b6b4de1864afe26608868047f42 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 15 Nov 2024 15:28:31 +0100 Subject: [PATCH 45/56] feat: Improve validation flow of sent screen --- apps/core/src/components/Inputs/AddressInput.tsx | 14 +++++++++++--- .../src/components/Inputs/SendTokenFormInput.tsx | 15 +++++++++++---- .../SendToken/views/EnterValuesFormView.tsx | 4 ++-- .../pages/home/transfer-coin/SendTokenForm.tsx | 4 ++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index d0bf195ba0f..777acc28cfc 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -5,8 +5,8 @@ import { Input, InputType } from '@iota/apps-ui-kit'; import { Close } from '@iota/ui-icons'; import { useIotaAddressValidation } from '../../hooks'; -import React, { useCallback } from 'react'; -import { useField } from 'formik'; +import React, { useCallback, useEffect } from 'react'; +import { useField, useFormikContext } from 'formik'; export interface AddressInputProps { name: string; @@ -21,6 +21,7 @@ export function AddressInput({ placeholder = '0x...', label = 'Enter Recipient Address', }: AddressInputProps) { + const { validateField } = useFormikContext(); const [field, meta, helpers] = useField(name); const iotaAddressValidation = useIotaAddressValidation(); @@ -29,11 +30,18 @@ export function AddressInput({ const handleOnChange = useCallback( (e: React.ChangeEvent) => { const address = e.currentTarget.value; + helpers.setTouched(true) helpers.setValue(iotaAddressValidation.cast(address)); }, - [name, iotaAddressValidation], + [name, iotaAddressValidation, helpers.setTouched, helpers.setValue, validateField], ); + useEffect(() => { + if(meta.touched) { + validateField(name); + } + }, [field.value]) + const clearAddress = () => { helpers.setValue(''); }; diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index 1b7f0be19ca..5beddf04f11 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -29,7 +29,7 @@ export function SendTokenFormInput({ isMaxActionDisabled, name, }: SendTokenInputProps) { - const { values, setFieldValue, isSubmitting } = useFormikContext(); + const { values, setFieldValue, isSubmitting, validateField } = useFormikContext(); const gasBudgetEstimation = useGasBudgetEstimation({ coinDecimals, coins: coins ?? [], @@ -40,8 +40,7 @@ export function SendTokenFormInput({ }); const [field, meta, helpers] = useField(name); - - const errorMessage = meta?.error ? meta.error : undefined; + const errorMessage = meta.error; const isActionButtonDisabled = isSubmitting || !!errorMessage || isMaxActionDisabled; const renderAction = () => ( @@ -50,6 +49,12 @@ export function SendTokenFormInput({ ); + useEffect(() => { + if(meta.touched) { + validateField(name); + } + }, [field.value, meta.touched]) + // gasBudgetEstimation should change when the amount above changes useEffect(() => { setFieldValue('gasBudgetEst', gasBudgetEstimation, false); @@ -58,7 +63,8 @@ export function SendTokenFormInput({ return ( { + helpers.setTouched(true); helpers.setValue(values.value); }} /> diff --git a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx index 2ad0fdba7cc..9844cd098f1 100644 --- a/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx +++ b/apps/wallet-dashboard/components/Dialogs/SendToken/views/EnterValuesFormView.tsx @@ -242,8 +242,8 @@ function EnterValuesFormView({ }} validationSchema={validationSchemaStepOne} enableReinitialize - validateOnChange={true} - validateOnBlur={true} + validateOnChange={false} + validateOnBlur={false} onSubmit={handleFormSubmit} > {(props: FormikProps) => ( diff --git a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx index 57c8e28730d..4f26a6796fa 100644 --- a/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/transfer-coin/SendTokenForm.tsx @@ -140,8 +140,8 @@ export function SendTokenForm({ }} validationSchema={validationSchemaStepOne} enableReinitialize - validateOnChange={true} - validateOnBlur={true} + validateOnChange={false} + validateOnBlur={false} onSubmit={handleFormSubmit} > {({ isValid, isSubmitting, setFieldValue, values, submitForm }) => { From 69dab33de68220256ac45d614c208654bb7bef07 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 15 Nov 2024 15:30:02 +0100 Subject: [PATCH 46/56] fmt --- apps/core/src/components/Inputs/AddressInput.tsx | 6 +++--- apps/core/src/components/Inputs/SendTokenFormInput.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index 777acc28cfc..12d90d2a91c 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -30,17 +30,17 @@ export function AddressInput({ const handleOnChange = useCallback( (e: React.ChangeEvent) => { const address = e.currentTarget.value; - helpers.setTouched(true) + helpers.setTouched(true); helpers.setValue(iotaAddressValidation.cast(address)); }, [name, iotaAddressValidation, helpers.setTouched, helpers.setValue, validateField], ); useEffect(() => { - if(meta.touched) { + if (meta.touched) { validateField(name); } - }, [field.value]) + }, [field.value]); const clearAddress = () => { helpers.setValue(''); diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index 5beddf04f11..1454a2ca89b 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -50,10 +50,10 @@ export function SendTokenFormInput({ ); useEffect(() => { - if(meta.touched) { + if (meta.touched) { validateField(name); } - }, [field.value, meta.touched]) + }, [field.value, meta.touched]); // gasBudgetEstimation should change when the amount above changes useEffect(() => { From b394e26fbb61841039b2c4f8e2c062a488a5c3cf Mon Sep 17 00:00:00 2001 From: JCNoguera <88061365+VmMad@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:48:33 +0100 Subject: [PATCH 47/56] fix: format gas outside of hook --- .../src/components/Inputs/SendTokenFormInput.tsx | 15 +++++++++------ apps/core/src/hooks/useGasBudgetEstimation.ts | 12 +----------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/apps/core/src/components/Inputs/SendTokenFormInput.tsx b/apps/core/src/components/Inputs/SendTokenFormInput.tsx index 6d0f436b3b2..51beab4ec9f 100644 --- a/apps/core/src/components/Inputs/SendTokenFormInput.tsx +++ b/apps/core/src/components/Inputs/SendTokenFormInput.tsx @@ -3,11 +3,12 @@ import { ButtonPill, Input, InputType } from '@iota/apps-ui-kit'; import { CoinStruct } from '@iota/iota-sdk/client'; -import { useGasBudgetEstimation } from '../../hooks'; +import { useFormatCoin, useGasBudgetEstimation } from '../../hooks'; import React, { useEffect } from 'react'; import { GAS_SYMBOL } from '../../constants'; import { useField, useFormikContext } from 'formik'; import { TokenForm } from '../../forms'; +import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; export interface SendTokenInputProps { coins: CoinStruct[]; @@ -31,7 +32,8 @@ export function SendTokenFormInput({ name, }: SendTokenInputProps) { const { values, setFieldValue, isSubmitting, validateField } = useFormikContext(); - const gasBudgetEstimation = useGasBudgetEstimation({ + + const { data: gasBudgetEstimation } = useGasBudgetEstimation({ coinDecimals, coins: coins ?? [], activeAddress, @@ -40,6 +42,7 @@ export function SendTokenFormInput({ isPayAllIota: values.isPayAllIota, showGasSymbol: false, }); + const [formattedGasBudgetEstimation] = useFormatCoin(gasBudgetEstimation, IOTA_TYPE_ARG); const [field, meta, helpers] = useField(name); const errorMessage = meta.error; @@ -59,8 +62,8 @@ export function SendTokenFormInput({ // gasBudgetEstimation should change when the amount above changes useEffect(() => { - setFieldValue('gasBudgetEst', gasBudgetEstimation, false); - }, [gasBudgetEstimation, setFieldValue, values.amount]); + setFieldValue('gasBudgetEst', formattedGasBudgetEstimation, false); + }, [formattedGasBudgetEstimation, setFieldValue, values.amount]); return ( Date: Tue, 19 Nov 2024 09:38:42 +0100 Subject: [PATCH 48/56] fix(wallet-dashboard): fixes --- .../src/components/Inputs/AddressInput.tsx | 18 +++++------------- .../components/Inputs/SendTokenFormInput.tsx | 14 ++++---------- .../home/nft-transfer/TransferNFTForm.tsx | 9 ++------- 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/apps/core/src/components/Inputs/AddressInput.tsx b/apps/core/src/components/Inputs/AddressInput.tsx index 12d90d2a91c..04cfe38f669 100644 --- a/apps/core/src/components/Inputs/AddressInput.tsx +++ b/apps/core/src/components/Inputs/AddressInput.tsx @@ -28,26 +28,18 @@ export function AddressInput({ const formattedValue = iotaAddressValidation.cast(field.value); const handleOnChange = useCallback( - (e: React.ChangeEvent) => { + async (e: React.ChangeEvent) => { const address = e.currentTarget.value; - helpers.setTouched(true); - helpers.setValue(iotaAddressValidation.cast(address)); + await helpers.setValue(iotaAddressValidation.cast(address)); + validateField(name); }, - [name, iotaAddressValidation, helpers.setTouched, helpers.setValue, validateField], + [name, iotaAddressValidation], ); - useEffect(() => { - if (meta.touched) { - validateField(name); - } - }, [field.value]); - const clearAddress = () => { helpers.setValue(''); }; - const errorMessage = meta.touched && meta.error; - return ( (name); const errorMessage = meta.error; - const isActionButtonDisabled = isSubmitting || !!errorMessage || isMaxActionDisabled; + const isActionButtonDisabled = isSubmitting || isMaxActionDisabled; const renderAction = () => ( @@ -49,12 +49,6 @@ export function SendTokenFormInput({ ); - useEffect(() => { - if (meta.touched) { - validateField(name); - } - }, [field.value, meta.touched]); - // gasBudgetEstimation should change when the amount above changes useEffect(() => { setFieldValue('gasBudgetEst', gasBudgetEstimation, false); @@ -77,9 +71,9 @@ export function SendTokenFormInput({ trailingElement={renderAction()} decimalScale={coinDecimals ? undefined : 0} thousandSeparator - onValueChange={(values) => { - helpers.setTouched(true); - helpers.setValue(values.value); + onValueChange={async (values) => { + await helpers.setValue(values.value); + validateField(name); }} /> ); diff --git a/apps/wallet/src/ui/app/pages/home/nft-transfer/TransferNFTForm.tsx b/apps/wallet/src/ui/app/pages/home/nft-transfer/TransferNFTForm.tsx index 7806967edd0..9e4801aa3cb 100644 --- a/apps/wallet/src/ui/app/pages/home/nft-transfer/TransferNFTForm.tsx +++ b/apps/wallet/src/ui/app/pages/home/nft-transfer/TransferNFTForm.tsx @@ -10,7 +10,7 @@ import { useSigner } from '_src/ui/app/hooks/useSigner'; import { createNftSendValidationSchema, useGetKioskContents, AddressInput } from '@iota/core'; import { Transaction } from '@iota/iota-sdk/transactions'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { Field, Form, Formik } from 'formik'; +import { Form, Formik } from 'formik'; import { toast } from 'react-hot-toast'; import { useNavigate } from 'react-router-dom'; @@ -95,12 +95,7 @@ export function TransferNFTForm({ objectId, objectType }: TransferNFTFormProps) {({ isValid, dirty, isSubmitting }) => (
- +