From 31a01d7fcbdaa25df27491c863de6d7ea9d0c3fd Mon Sep 17 00:00:00 2001 From: Tim Man Date: Thu, 9 Nov 2023 15:28:27 +0800 Subject: [PATCH 1/5] feat: revamp the send nft screen --- .husky/pre-commit | 2 +- src/app/screens/sendNft/index.tsx | 283 ++++++++++++++++---------- src/app/screens/sendOrdinal/index.tsx | 12 +- 3 files changed, 182 insertions(+), 115 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index d24fdfc60..d711fe1f3 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -npx lint-staged +npx lint-staged --no-stash diff --git a/src/app/screens/sendNft/index.tsx b/src/app/screens/sendNft/index.tsx index 77ff87616..575dfe101 100644 --- a/src/app/screens/sendNft/index.tsx +++ b/src/app/screens/sendNft/index.tsx @@ -1,6 +1,5 @@ -import ArrowLeft from '@assets/img/dashboard/arrow_left.svg'; import AccountHeaderComponent from '@components/accountHeader'; -import SendForm from '@components/sendForm'; +import ActionButton from '@components/button'; import BottomBar from '@components/tabBar'; import TopRow from '@components/topRow'; import useStacksCollectibles from '@hooks/queries/useStacksCollectibles'; @@ -8,7 +7,7 @@ import useStxPendingTxData from '@hooks/queries/useStxPendingTxData'; import useNetworkSelector from '@hooks/useNetwork'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; import useWalletSelector from '@hooks/useWalletSelector'; -import NftImage from '@screens/nftDashboard/nftImage'; +import { ArrowLeft } from '@phosphor-icons/react'; import { cvToHex, generateUnsignedTransaction, @@ -18,93 +17,136 @@ import { validateStxAddress, } from '@secretkeylabs/xverse-core'; import { useMutation } from '@tanstack/react-query'; +import Callout from '@ui-library/callout'; +import { StyledHeading, StyledP } from '@ui-library/common.styled'; +import { InputFeedback, InputFeedbackProps, isDangerFeedback } from '@ui-library/inputFeedback'; import { checkNftExists, isLedgerAccount } from '@utils/helper'; import { getNftDataFromNftsCollectionData } from '@utils/nfts'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import styled from 'styled-components'; +import { devices } from 'theme'; -const ScrollContainer = styled.div` +const ScrollContainer = styled.div((props) => ({ + display: 'flex', + flex: 1, + flexDirection: 'column', + ...props.theme.scrollbar, +})); + +const Container = styled.div` display: flex; flex: 1; flex-direction: column; - overflow-y: auto; - &::-webkit-scrollbar { - display: none; - } - width: 360px; margin: auto; + margin-top: ${(props) => props.theme.space.xxs}; + padding: 0 ${(props) => props.theme.space.s}; + justify-content: space-between; + max-width: 360px; + + @media only screen and ${devices.min.s} { + flex: initial; + max-width: 588px; + border: 1px solid ${(props) => props.theme.colors.elevation3}; + border-radius: ${(props) => props.theme.space.s}; + padding: ${(props) => props.theme.space.l} ${(props) => props.theme.space.m}; + padding-bottom: ${(props) => props.theme.space.xxl}; + margin-top: ${(props) => props.theme.space.xxxxl}; + min-height: 600px; + } `; -const Container = styled.div({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - flex: 1, -}); +const FooterContainer = styled.div` + display: flex; + justify-content: center; + margin-bottom: ${(props) => props.theme.space.xxl}; +`; -const NftContainer = styled.div((props) => ({ - maxHeight: 148, - width: 148, - display: 'flex', - aspectRatio: 1, - justifyContent: 'center', - alignItems: 'center', - borderRadius: 8, - marginTop: props.theme.spacing(16), - marginBottom: props.theme.spacing(12), -})); +const StyledSendTo = styled(StyledHeading)` + margin-bottom: ${(props) => props.theme.space.l}; +`; -const NftTitleText = styled.h1((props) => ({ - ...props.theme.typography.headline_s, - color: props.theme.colors.white_0, - textAlign: 'center', +const NextButtonContainer = styled.div((props) => ({ + position: 'sticky', + bottom: 0, + paddingBottom: props.theme.spacing(12), + paddingTop: props.theme.spacing(12), + backgroundColor: props.theme.colors.elevation0, })); -const BottomBarContainer = styled.div({ - marginTop: 'auto', -}); +const InputGroup = styled.div` + margin-top: ${(props) => props.theme.spacing(8)}px; +`; -const ButtonContainer = styled.div((props) => ({ +const Label = styled.label((props) => ({ + ...props.theme.body_medium_m, + color: props.theme.colors.white_200, display: 'flex', - flexDirection: 'row', - marginLeft: '15%', - marginTop: props.theme.spacing(40), + flex: 1, })); -const Button = styled.button((props) => ({ +const AmountInputContainer = styled.div<{ error: boolean }>((props) => ({ display: 'flex', flexDirection: 'row', - justifyContent: 'flex-end', alignItems: 'center', + marginTop: props.theme.spacing(4), + marginBottom: props.theme.spacing(4), + border: props.error + ? `1px solid ${props.theme.colors.danger_dark_200}` + : `1px solid ${props.theme.colors.white_800}`, + backgroundColor: props.theme.colors.elevation_n1, borderRadius: props.theme.radius(1), - backgroundColor: 'transparent', - opacity: 0.8, - marginTop: props.theme.spacing(5), + paddingLeft: props.theme.spacing(5), + paddingRight: props.theme.spacing(5), + height: 44, +})); + +const InputFieldContainer = styled.div(() => ({ + flex: 1, })); -const ButtonText = styled.div((props) => ({ - ...props.theme.typography.body_m, - color: props.theme.colors.white_0, - textAlign: 'center', +const InputField = styled.input((props) => ({ + ...props.theme.body_m, + backgroundColor: 'transparent', + color: props.theme.colors.white['0'], + width: '100%', + border: 'transparent', })); -const ButtonImage = styled.img((props) => ({ - marginRight: props.theme.spacing(3), - alignSelf: 'center', - transform: 'all', +const ErrorContainer = styled.div((props) => ({ + marginTop: props.theme.spacing(3), + marginBottom: props.theme.spacing(12), })); +const RowContainer = styled.div({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', +}); + +const StyledCallout = styled(Callout)` + margin-bottom: ${(props) => props.theme.spacing(14)}px; +`; + +const BottomBarContainer = styled.div({ + marginTop: 'auto', +}); + +const Button = styled.button` + display: flex; + background-color: transparent; + margin-bottom: ${(props) => props.theme.space.l}; +`; + function SendNft() { const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); const navigate = useNavigate(); const location = useLocation(); - let address: string | undefined; - if (location.state) { - address = location.state.recipientAddress; - } + const [recipientError, setRecipientError] = useState(null); + const [recipientAddress, setRecipientAddress] = useState(location.state?.recipientAddress ?? ''); + + useResetUserFlow('/send-nft'); const { id } = useParams(); const stacksNftsQuery = useStacksCollectibles(); @@ -116,21 +158,20 @@ function SendNft() { const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; const { stxAddress, stxPublicKey, network, feeMultipliers, selectedAccount } = useWalletSelector(); - const [error, setError] = useState(''); - const [recipientAddress, setRecipientAddress] = useState(''); + const { isLoading, data, mutate } = useMutation< StacksTransaction, Error, - { tokenId: string; associatedAddress: string } + { tokenId: string; address: string } >({ - mutationFn: async ({ tokenId, associatedAddress }) => { + mutationFn: async ({ tokenId, address }) => { const principal = nft?.fully_qualified_token_id?.split('::')!; const name = principal[1].split(':')[0]; const contractInfo: string[] = principal[0].split('.'); const unsginedTx: UnsignedStacksTransation = { amount: tokenId, senderAddress: stxAddress, - recipientAddress: associatedAddress, + recipientAddress: address, contractAddress: contractInfo[0], contractName: contractInfo[1], assetName: name, @@ -146,7 +187,7 @@ function SendNft() { unsignedTx.auth.spendingCondition.fee * BigInt(feeMultipliers.stxSendTxMultiplier), ); } - setRecipientAddress(associatedAddress); + setRecipientAddress(address); return unsignedTx; }, }); @@ -162,80 +203,102 @@ function SendNft() { } }, [data]); - useResetUserFlow('/send-nft'); - const handleBackButtonClick = () => { navigate(-1); }; - function validateFields(associatedAddress: string): boolean { - if (!associatedAddress) { - setError(t('ERRORS.ADDRESS_REQUIRED')); + const validateRecipientAddress = (address: string): boolean => { + if (!address) { + setRecipientError({ variant: 'danger', message: t('ERRORS.ADDRESS_REQUIRED') }); return false; } - - if (!validateStxAddress({ stxAddress: associatedAddress, network: network.type })) { - setError(t('ERRORS.ADDRESS_INVALID')); + if (!validateStxAddress({ stxAddress: address, network: network.type })) { + setRecipientError({ variant: 'danger', message: t('ERRORS.ADDRESS_INVALID') }); return false; } - - if (associatedAddress === stxAddress) { - setError(t('ERRORS.SEND_TO_SELF')); - return false; + if (address === stxAddress) { + setRecipientError({ variant: 'info', message: t('YOU_ARE_TRANSFERRING_TO_YOURSELF') }); + return true; } - + setRecipientError(null); return true; - } + }; - const onPressSendNFT = async (associatedAddress: string) => { + const onPressNext = async () => { if (stxPendingTxData) { if (checkNftExists(stxPendingTxData?.pendingTransactions, nft!)) { - setError(t('ERRORS.NFT_SEND_DETAIL')); + setRecipientError({ variant: 'danger', message: t('ERRORS.NFT_SEND_DETAIL') }); return; } } - if (validateFields(associatedAddress.trim()) && nft) { - setError(''); + if (validateRecipientAddress(recipientAddress.trim()) && nft) { const tokenId = cvToHex(uintCV(nft?.token_id.toString()!)); - mutate({ tokenId, associatedAddress }); + mutate({ tokenId, address: recipientAddress }); } }; + + const handleAddressChange = (e: React.ChangeEvent) => { + validateRecipientAddress(e.target.value); + setRecipientAddress(e.target.value); + }; + + const isNextEnabled = !isDangerFeedback(recipientError) && !!recipientAddress; + const year = new Date().getFullYear(); + return ( <> {isGalleryOpen && ( - <> - - {!isLedgerAccount(selectedAccount) && ( - - - - )} - + )} + {!isGalleryOpen && } - {!isGalleryOpen && } - - - - - - {nft?.token_metadata?.name} - - - {!isGalleryOpen && } + +
+ {isGalleryOpen && !isLedgerAccount(selectedAccount) && ( + + )} + + {t('SEND_TO')} + + + + + + + + + + + + {recipientError && } + + + +
+ + + +
+ {isGalleryOpen && ( + + + {t('COPYRIGHT', { year })} + + + )}
+ {!isGalleryOpen && } ); } diff --git a/src/app/screens/sendOrdinal/index.tsx b/src/app/screens/sendOrdinal/index.tsx index 12a59bea9..5437b8c08 100644 --- a/src/app/screens/sendOrdinal/index.tsx +++ b/src/app/screens/sendOrdinal/index.tsx @@ -8,14 +8,18 @@ import { useResetUserFlow } from '@hooks/useResetUserFlow'; import useSeedVault from '@hooks/useSeedVault'; import useWalletSelector from '@hooks/useWalletSelector'; import { ArrowLeft } from '@phosphor-icons/react'; -import { isOrdinalOwnedByAccount } from '@secretkeylabs/xverse-core'; -import { getBtcFiatEquivalent } from '@secretkeylabs/xverse-core/currency'; +import { + ErrorCodes, + getBtcFiatEquivalent, + isOrdinalOwnedByAccount, + ResponseError, + UTXO, + validateBtcAddress, +} from '@secretkeylabs/xverse-core'; import { SignedBtcTx, signOrdinalSendTransaction, } from '@secretkeylabs/xverse-core/transactions/btc'; -import { ErrorCodes, ResponseError, UTXO } from '@secretkeylabs/xverse-core/types'; -import { validateBtcAddress } from '@secretkeylabs/xverse-core/wallet'; import { useMutation } from '@tanstack/react-query'; import Callout from '@ui-library/callout'; import { StyledHeading, StyledP } from '@ui-library/common.styled'; From 81dbf5dc6a6fd81139fb3dac8d3e16197582425c Mon Sep 17 00:00:00 2001 From: Tim Man Date: Fri, 10 Nov 2023 14:56:52 +0800 Subject: [PATCH 2/5] fix: back button display should rely on back history not ledger account --- src/app/components/tabBar/index.tsx | 2 +- src/app/components/topRow/index.tsx | 2 +- src/app/layouts/sendLayout.tsx | 101 ++++++++++++++++ src/app/screens/sendNft/index.tsx | 158 ++++++++------------------ src/app/screens/sendOrdinal/index.tsx | 156 ++++++++----------------- 5 files changed, 200 insertions(+), 219 deletions(-) create mode 100644 src/app/layouts/sendLayout.tsx diff --git a/src/app/components/tabBar/index.tsx b/src/app/components/tabBar/index.tsx index 3bd6786fc..92601e9fd 100644 --- a/src/app/components/tabBar/index.tsx +++ b/src/app/components/tabBar/index.tsx @@ -31,7 +31,7 @@ const Button = styled.button({ zIndex: 2, }); -type Tab = 'dashboard' | 'nft' | 'stacking' | 'settings'; +export type Tab = 'dashboard' | 'nft' | 'stacking' | 'settings'; interface Props { tab: Tab; diff --git a/src/app/components/topRow/index.tsx b/src/app/components/topRow/index.tsx index ed4a6b966..38e44039c 100644 --- a/src/app/components/topRow/index.tsx +++ b/src/app/components/topRow/index.tsx @@ -40,7 +40,7 @@ const AnimatedBackButton = styled(BackButton)` interface Props { title: string; - onClick: () => void; + onClick: (e: React.MouseEvent) => void; showBackButton?: boolean; className?: string; } diff --git a/src/app/layouts/sendLayout.tsx b/src/app/layouts/sendLayout.tsx new file mode 100644 index 000000000..8a077ce8b --- /dev/null +++ b/src/app/layouts/sendLayout.tsx @@ -0,0 +1,101 @@ +import AccountHeaderComponent from '@components/accountHeader'; +import BottomBar, { Tab } from '@components/tabBar'; +import TopRow from '@components/topRow'; +import { ArrowLeft } from '@phosphor-icons/react'; +import { StyledP } from '@ui-library/common.styled'; +import { PropsWithChildren } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { breakpoints, devices } from 'theme'; + +const ScrollContainer = styled.div((props) => ({ + display: 'flex', + flex: 1, + flexDirection: 'column', + ...props.theme.scrollbar, +})); + +const Container = styled.div` + display: flex; + flex-direction: column; + margin: auto; + margin-top: ${(props) => props.theme.space.xxs}; + padding: 0 ${(props) => props.theme.space.s}; + width: 100%; + height: 100%; + max-width: ${breakpoints.xs}px; + max-height: 600px; + + @media only screen and ${devices.min.s} { + flex: initial; + max-width: 588px; + border: 1px solid ${(props) => props.theme.colors.elevation3}; + border-radius: ${(props) => props.theme.space.s}; + padding: ${(props) => props.theme.space.l} ${(props) => props.theme.space.m}; + padding-bottom: ${(props) => props.theme.space.xxl}; + margin-top: ${(props) => props.theme.space.xxxxl}; + } +`; + +const FooterContainer = styled.div` + display: flex; + justify-content: center; + margin-bottom: ${(props) => props.theme.space.xxl}; +`; + +const BottomBarContainer = styled.div({ + marginTop: 'auto', +}); + +const Button = styled.button` + display: flex; + background-color: transparent; + margin-bottom: ${(props) => props.theme.space.l}; +`; + +function SendLayout({ + children, + selectedBottomTab, + onClickBack, + hideBackButton = false, +}: PropsWithChildren<{ + selectedBottomTab: Tab; + onClickBack?: (e: React.MouseEvent) => void; + hideBackButton?: boolean; +}>) { + const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); + const isScreenLargerThanXs = document.documentElement.clientWidth > Number(breakpoints.xs); + const year = new Date().getFullYear(); + + return ( + <> + {isScreenLargerThanXs ? ( + + ) : ( + + )} + + + {isScreenLargerThanXs && !hideBackButton && onClickBack && ( + + )} + {children} + + {isScreenLargerThanXs && ( + + + {t('COPYRIGHT', { year })} + + + )} + + + {!isScreenLargerThanXs && } + + + ); +} + +export default SendLayout; diff --git a/src/app/screens/sendNft/index.tsx b/src/app/screens/sendNft/index.tsx index 575dfe101..60594314a 100644 --- a/src/app/screens/sendNft/index.tsx +++ b/src/app/screens/sendNft/index.tsx @@ -1,13 +1,9 @@ -import AccountHeaderComponent from '@components/accountHeader'; import ActionButton from '@components/button'; -import BottomBar from '@components/tabBar'; -import TopRow from '@components/topRow'; import useStacksCollectibles from '@hooks/queries/useStacksCollectibles'; import useStxPendingTxData from '@hooks/queries/useStxPendingTxData'; import useNetworkSelector from '@hooks/useNetwork'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; import useWalletSelector from '@hooks/useWalletSelector'; -import { ArrowLeft } from '@phosphor-icons/react'; import { cvToHex, generateUnsignedTransaction, @@ -17,50 +13,21 @@ import { validateStxAddress, } from '@secretkeylabs/xverse-core'; import { useMutation } from '@tanstack/react-query'; -import Callout from '@ui-library/callout'; -import { StyledHeading, StyledP } from '@ui-library/common.styled'; +import { StyledHeading } from '@ui-library/common.styled'; import { InputFeedback, InputFeedbackProps, isDangerFeedback } from '@ui-library/inputFeedback'; -import { checkNftExists, isLedgerAccount } from '@utils/helper'; +import { checkNftExists } from '@utils/helper'; import { getNftDataFromNftsCollectionData } from '@utils/nfts'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import styled from 'styled-components'; -import { devices } from 'theme'; - -const ScrollContainer = styled.div((props) => ({ - display: 'flex', - flex: 1, - flexDirection: 'column', - ...props.theme.scrollbar, -})); +import SendLayout from '../../layouts/sendLayout'; const Container = styled.div` display: flex; - flex: 1; flex-direction: column; - margin: auto; - margin-top: ${(props) => props.theme.space.xxs}; - padding: 0 ${(props) => props.theme.space.s}; justify-content: space-between; - max-width: 360px; - - @media only screen and ${devices.min.s} { - flex: initial; - max-width: 588px; - border: 1px solid ${(props) => props.theme.colors.elevation3}; - border-radius: ${(props) => props.theme.space.s}; - padding: ${(props) => props.theme.space.l} ${(props) => props.theme.space.m}; - padding-bottom: ${(props) => props.theme.space.xxl}; - margin-top: ${(props) => props.theme.space.xxxxl}; - min-height: 600px; - } -`; - -const FooterContainer = styled.div` - display: flex; - justify-content: center; - margin-bottom: ${(props) => props.theme.space.xxl}; + flex-grow: 1; `; const StyledSendTo = styled(StyledHeading)` @@ -80,7 +47,7 @@ const InputGroup = styled.div` `; const Label = styled.label((props) => ({ - ...props.theme.body_medium_m, + ...props.theme.typography.body_medium_m, color: props.theme.colors.white_200, display: 'flex', flex: 1, @@ -107,9 +74,9 @@ const InputFieldContainer = styled.div(() => ({ })); const InputField = styled.input((props) => ({ - ...props.theme.body_m, + ...props.theme.typography.body_m, backgroundColor: 'transparent', - color: props.theme.colors.white['0'], + color: props.theme.colors.white_0, width: '100%', border: 'transparent', })); @@ -125,20 +92,6 @@ const RowContainer = styled.div({ alignItems: 'center', }); -const StyledCallout = styled(Callout)` - margin-bottom: ${(props) => props.theme.spacing(14)}px; -`; - -const BottomBarContainer = styled.div({ - marginTop: 'auto', -}); - -const Button = styled.button` - display: flex; - background-color: transparent; - margin-bottom: ${(props) => props.theme.space.l}; -`; - function SendNft() { const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); const navigate = useNavigate(); @@ -155,7 +108,6 @@ function SendNft() { const selectedNetwork = useNetworkSelector(); const { data: stxPendingTxData } = useStxPendingTxData(); - const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; const { stxAddress, stxPublicKey, network, feeMultipliers, selectedAccount } = useWalletSelector(); @@ -243,63 +195,49 @@ function SendNft() { }; const isNextEnabled = !isDangerFeedback(recipientError) && !!recipientAddress; - const year = new Date().getFullYear(); + + // hide back button if there is no history + const hideBackButton = location.key === 'default'; return ( - <> - {isGalleryOpen && ( - - )} - {!isGalleryOpen && } - - -
- {isGalleryOpen && !isLedgerAccount(selectedAccount) && ( - - )} - - {t('SEND_TO')} - - - - - - - - - - - - {recipientError && } - - - -
- - - -
- {isGalleryOpen && ( - - - {t('COPYRIGHT', { year })} - - - )} -
- {!isGalleryOpen && } - + + +
+ + {t('SEND_TO')} + + + + + + + + + + + + {recipientError && } + + +
+ + + +
+
); } diff --git a/src/app/screens/sendOrdinal/index.tsx b/src/app/screens/sendOrdinal/index.tsx index 5437b8c08..c4dcc7f03 100644 --- a/src/app/screens/sendOrdinal/index.tsx +++ b/src/app/screens/sendOrdinal/index.tsx @@ -1,13 +1,9 @@ -import AccountHeaderComponent from '@components/accountHeader'; import ActionButton from '@components/button'; -import BottomBar from '@components/tabBar'; -import TopRow from '@components/topRow'; import useNftDataSelector from '@hooks/stores/useNftDataSelector'; import useBtcClient from '@hooks/useBtcClient'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; import useSeedVault from '@hooks/useSeedVault'; import useWalletSelector from '@hooks/useWalletSelector'; -import { ArrowLeft } from '@phosphor-icons/react'; import { ErrorCodes, getBtcFiatEquivalent, @@ -22,48 +18,19 @@ import { } from '@secretkeylabs/xverse-core/transactions/btc'; import { useMutation } from '@tanstack/react-query'; import Callout from '@ui-library/callout'; -import { StyledHeading, StyledP } from '@ui-library/common.styled'; +import { StyledHeading } from '@ui-library/common.styled'; import { InputFeedback, InputFeedbackProps, isDangerFeedback } from '@ui-library/inputFeedback'; -import { isLedgerAccount } from '@utils/helper'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; -import { devices } from 'theme'; - -const ScrollContainer = styled.div((props) => ({ - display: 'flex', - flex: 1, - flexDirection: 'column', - ...props.theme.scrollbar, -})); +import SendLayout from '../../layouts/sendLayout'; const Container = styled.div` display: flex; - flex: 1; flex-direction: column; - margin: auto; - margin-top: ${(props) => props.theme.space.xxs}; - padding: 0 ${(props) => props.theme.space.s}; justify-content: space-between; - max-width: 360px; - - @media only screen and ${devices.min.s} { - flex: initial; - max-width: 588px; - border: 1px solid ${(props) => props.theme.colors.elevation3}; - border-radius: ${(props) => props.theme.space.s}; - padding: ${(props) => props.theme.space.l} ${(props) => props.theme.space.m}; - padding-bottom: ${(props) => props.theme.space.xxl}; - margin-top: ${(props) => props.theme.space.xxxxl}; - min-height: 600px; - } -`; - -const FooterContainer = styled.div` - display: flex; - justify-content: center; - margin-bottom: ${(props) => props.theme.space.xxl}; + flex-grow: 1; `; const StyledSendTo = styled(StyledHeading)` @@ -83,7 +50,7 @@ const InputGroup = styled.div` `; const Label = styled.label((props) => ({ - ...props.theme.body_medium_m, + ...props.theme.typography.body_medium_m, color: props.theme.colors.white_200, display: 'flex', flex: 1, @@ -110,9 +77,9 @@ const InputFieldContainer = styled.div(() => ({ })); const InputField = styled.input((props) => ({ - ...props.theme.body_m, + ...props.theme.typography.body_m, backgroundColor: 'transparent', - color: props.theme.colors.white['0'], + color: props.theme.colors.white_0, width: '100%', border: 'transparent', })); @@ -132,16 +99,6 @@ const StyledCallout = styled(Callout)` margin-bottom: ${(props) => props.theme.spacing(14)}px; `; -const BottomBarContainer = styled.div({ - marginTop: 'auto', -}); - -const Button = styled.button` - display: flex; - background-color: transparent; - margin-bottom: ${(props) => props.theme.space.l}; -`; - function SendOrdinal() { const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); const navigate = useNavigate(); @@ -157,8 +114,6 @@ function SendOrdinal() { useResetUserFlow('/send-ordinal'); - const isGalleryOpen: boolean = useMemo(() => document.documentElement.clientWidth > 360, []); - const { isLoading, data, @@ -261,63 +216,50 @@ function SendOrdinal() { }; const isNextEnabled = !isDangerFeedback(recipientError) && !!recipientAddress; - const year = new Date().getFullYear(); + + // hide back button if there is no history + const hideBackButton = location.key === 'default'; return ( - <> - {isGalleryOpen && ( - - )} - {!isGalleryOpen && } - - -
- {isGalleryOpen && !isLedgerAccount(selectedAccount) && ( - - )} - - {t('SEND_TO')} - - - - - - - - - - - - {recipientError && } - - - -
- - - -
- {isGalleryOpen && ( - - - {t('COPYRIGHT', { year })} - - - )} -
- {!isGalleryOpen && } - + + +
+ + {t('SEND_TO')} + + + + + + + + + + + + {recipientError && } + + + +
+ + + +
+
); } From 7075aec6eda76085d84600452003200d77c6a710 Mon Sep 17 00:00:00 2001 From: Tim Man Date: Fri, 10 Nov 2023 15:34:59 +0800 Subject: [PATCH 3/5] fix: ledger accounts should only open in new tab when not already in full screen otherwise it is annoying --- src/app/components/tokenTile/index.tsx | 2 +- src/app/screens/coinDashboard/coinHeader.tsx | 4 +-- src/app/screens/home/index.tsx | 30 ++++++++++++------- .../screens/nftDashboard/receiveNft/index.tsx | 12 +++++--- src/app/screens/nftDetail/useNftDetail.ts | 4 +-- .../screens/ordinalDetail/useOrdinalDetail.ts | 4 +-- src/app/screens/rareSatsBundle/index.tsx | 4 +-- .../screens/rareSatsDetail/rareSatsDetail.tsx | 9 ++++-- src/app/screens/settings/index.tsx | 4 +-- 9 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/app/components/tokenTile/index.tsx b/src/app/components/tokenTile/index.tsx index 0ba643f25..2fd461572 100644 --- a/src/app/components/tokenTile/index.tsx +++ b/src/app/components/tokenTile/index.tsx @@ -347,7 +347,7 @@ function TokenTile({ onPress({ coin: currency as CurrencyTypes, ft: fungibleToken && fungibleToken.principal, - brc20Ft: !fungibleToken?.principal && fungibleToken?.name, + brc20Ft: !fungibleToken?.principal ? fungibleToken?.name : undefined, }); }; diff --git a/src/app/screens/coinDashboard/coinHeader.tsx b/src/app/screens/coinDashboard/coinHeader.tsx index ce741ad1c..ec53ee20f 100644 --- a/src/app/screens/coinDashboard/coinHeader.tsx +++ b/src/app/screens/coinDashboard/coinHeader.tsx @@ -10,7 +10,7 @@ import useWalletSelector from '@hooks/useWalletSelector'; import { FungibleToken, microstacksToStx, satsToBtc } from '@secretkeylabs/xverse-core'; import { currencySymbolMap } from '@secretkeylabs/xverse-core/types/currency'; import { CurrencyTypes } from '@utils/constants'; -import { isLedgerAccount } from '@utils/helper'; +import { isInOptions, isLedgerAccount } from '@utils/helper'; import { getFtBalance, getFtTicker } from '@utils/tokens'; import BigNumber from 'bignumber.js'; import { useState } from 'react'; @@ -262,7 +262,7 @@ export default function CoinHeader(props: CoinBalanceProps) { }; const goToSendScreen = async () => { - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { switch (coin) { case 'BTC': await chrome.tabs.create({ diff --git a/src/app/screens/home/index.tsx b/src/app/screens/home/index.tsx index e6bd705c5..28574add5 100644 --- a/src/app/screens/home/index.tsx +++ b/src/app/screens/home/index.tsx @@ -29,7 +29,7 @@ import CoinSelectModal from '@screens/home/coinSelectModal'; import type { FungibleToken } from '@secretkeylabs/xverse-core'; import { changeShowDataCollectionAlertAction } from '@stores/wallet/actions/actionCreators'; import { CurrencyTypes } from '@utils/constants'; -import { isLedgerAccount } from '@utils/helper'; +import { isInOptions, isLedgerAccount } from '@utils/helper'; import { optInMixPanel, optOutMixPanel } from '@utils/mixpanel'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -250,7 +250,7 @@ function Home() { }; const onStxSendClick = async () => { - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ url: chrome.runtime.getURL('options.html#/send-stx'), }); @@ -260,7 +260,7 @@ function Home() { }; const onBtcSendClick = async () => { - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ url: chrome.runtime.getURL('options.html#/send-btc'), }); @@ -280,10 +280,14 @@ function Home() { const onSendFtSelect = async (coin: FungibleToken) => { if (coin.protocol === 'brc-20') { if (isLedgerAccount(selectedAccount)) { - await chrome.tabs.create({ - // TODO replace with send-brc20-one-step route, when ledger support is ready - url: chrome.runtime.getURL(`options.html#/send-brc20?coinName=${coin.name}`), - }); + if (!isInOptions()) { + await chrome.tabs.create({ + // TODO replace with send-brc20-one-step route, when ledger support is ready + url: chrome.runtime.getURL(`options.html#/send-brc20?coinName=${coin.name}`), + }); + return; + } + navigate(`send-brc20?coinName=${coin.name}`); return; } navigate('send-brc20-one-step', { @@ -293,7 +297,7 @@ function Home() { }); return; } - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ url: chrome.runtime.getURL(`options.html#/send-ft?coinTicker=${coin.ticker}`), }); @@ -392,9 +396,13 @@ function Home() { icon={} text={t('ADD_STACKS_ADDRESS')} onPress={async () => { - await chrome.tabs.create({ - url: chrome.runtime.getURL(`options.html#/add-stx-address-ledger`), - }); + if (!isInOptions()) { + await chrome.tabs.create({ + url: chrome.runtime.getURL(`options.html#/add-stx-address-ledger`), + }); + } else { + navigate('/add-stx-address-ledger'); + } }} /> )} diff --git a/src/app/screens/nftDashboard/receiveNft/index.tsx b/src/app/screens/nftDashboard/receiveNft/index.tsx index 4da8532ba..99929009d 100644 --- a/src/app/screens/nftDashboard/receiveNft/index.tsx +++ b/src/app/screens/nftDashboard/receiveNft/index.tsx @@ -6,7 +6,7 @@ import ActionButton from '@components/button'; import UpdatedBottomModal from '@components/updatedBottomModal'; import useWalletSelector from '@hooks/useWalletSelector'; import { Plus } from '@phosphor-icons/react'; -import { isLedgerAccount } from '@utils/helper'; +import { isInOptions, isLedgerAccount } from '@utils/helper'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; @@ -171,9 +171,13 @@ function ReceiveNftModal({ visible, onClose, isGalleryOpen, setOrdinalReceiveAle src={plusIcon} text={t('ADD_STACKS_ADDRESS')} onPress={async () => { - await chrome.tabs.create({ - url: chrome.runtime.getURL(`options.html#/add-stx-address-ledger`), - }); + if (!isInOptions()) { + await chrome.tabs.create({ + url: chrome.runtime.getURL(`options.html#/add-stx-address-ledger`), + }); + } else { + navigate('/add-stx-address-ledger'); + } }} /> )} diff --git a/src/app/screens/nftDetail/useNftDetail.ts b/src/app/screens/nftDetail/useNftDetail.ts index 64dba0853..bd2191d1a 100644 --- a/src/app/screens/nftDetail/useNftDetail.ts +++ b/src/app/screens/nftDetail/useNftDetail.ts @@ -3,7 +3,7 @@ import useResetUserFlow from '@hooks/useResetUserFlow'; import useWalletSelector from '@hooks/useWalletSelector'; import { getBnsNftName } from '@secretkeylabs/xverse-core'; import { GAMMA_URL } from '@utils/constants'; -import { getExplorerUrl, isLedgerAccount } from '@utils/helper'; +import { getExplorerUrl, isInOptions, isLedgerAccount } from '@utils/helper'; import { getNftDataFromNftsCollectionData } from '@utils/nfts'; import { useMemo } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; @@ -51,7 +51,7 @@ export default function useNftDetail() { }; const handleOnSendClick = async () => { - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ url: chrome.runtime.getURL(`options.html#/send-nft/${id}`), }); diff --git a/src/app/screens/ordinalDetail/useOrdinalDetail.ts b/src/app/screens/ordinalDetail/useOrdinalDetail.ts index abd694b05..7704e5b78 100644 --- a/src/app/screens/ordinalDetail/useOrdinalDetail.ts +++ b/src/app/screens/ordinalDetail/useOrdinalDetail.ts @@ -8,7 +8,7 @@ import useSatBundleDataReducer from '@hooks/stores/useSatBundleReducer'; import useTextOrdinalContent from '@hooks/useTextOrdinalContent'; import useWalletSelector from '@hooks/useWalletSelector'; import { XVERSE_ORDIVIEW_URL } from '@utils/constants'; -import { getBtcTxStatusUrl, isLedgerAccount } from '@utils/helper'; +import { getBtcTxStatusUrl, isInOptions, isLedgerAccount } from '@utils/helper'; import { getInscriptionsCollectionGridItemSubText, getInscriptionsCollectionGridItemSubTextColor, @@ -83,7 +83,7 @@ export default function useOrdinalDetail() { return; } if (ordinalData) setSelectedOrdinalDetails(ordinalData); - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ url: chrome.runtime.getURL(`options.html#/nft-dashboard/ordinal-detail/${id}/send-ordinal`), }); diff --git a/src/app/screens/rareSatsBundle/index.tsx b/src/app/screens/rareSatsBundle/index.tsx index ed00dc4ef..985bf887b 100644 --- a/src/app/screens/rareSatsBundle/index.tsx +++ b/src/app/screens/rareSatsBundle/index.tsx @@ -14,7 +14,7 @@ import useWalletSelector from '@hooks/useWalletSelector'; import { ArrowRight, ArrowUp } from '@phosphor-icons/react'; import { GridContainer } from '@screens/nftDashboard/collectiblesTabs'; import { StyledHeading, StyledP } from '@ui-library/common.styled'; -import { getBtcTxStatusUrl, isLedgerAccount } from '@utils/helper'; +import { getBtcTxStatusUrl, isInOptions, isLedgerAccount } from '@utils/helper'; import { BundleItem } from '@utils/rareSats'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -171,7 +171,7 @@ function RareSatsBundle() { return setShowSendOrdinalsAlert(true); } - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ url: chrome.runtime.getURL('options.html#/nft-dashboard/send-rare-sat'), }); diff --git a/src/app/screens/rareSatsDetail/rareSatsDetail.tsx b/src/app/screens/rareSatsDetail/rareSatsDetail.tsx index 57e7bef4c..b5bb2b9a0 100644 --- a/src/app/screens/rareSatsDetail/rareSatsDetail.tsx +++ b/src/app/screens/rareSatsDetail/rareSatsDetail.tsx @@ -14,7 +14,12 @@ import useWalletSelector from '@hooks/useWalletSelector'; import { ArrowRight, ArrowUp, Circle } from '@phosphor-icons/react'; import Callout from '@ui-library/callout'; import { XVERSE_ORDIVIEW_URL } from '@utils/constants'; -import { getBtcTxStatusUrl, getTruncatedAddress, isLedgerAccount } from '@utils/helper'; +import { + getBtcTxStatusUrl, + getTruncatedAddress, + isInOptions, + isLedgerAccount, +} from '@utils/helper'; import { BundleItem, getBundleItemId, @@ -292,7 +297,7 @@ function RareSatsDetailScreen() { return showAlert(); } - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ url: chrome.runtime.getURL('options.html#/nft-dashboard/send-rare-sat'), }); diff --git a/src/app/screens/settings/index.tsx b/src/app/screens/settings/index.tsx index 791895106..0ed26fc37 100644 --- a/src/app/screens/settings/index.tsx +++ b/src/app/screens/settings/index.tsx @@ -12,7 +12,7 @@ import { ChangeActivateRareSatsAction, } from '@stores/wallet/actions/actionCreators'; import { PRIVACY_POLICY_LINK, SUPPORT_LINK, TERMS_LINK } from '@utils/constants'; -import { isLedgerAccount } from '@utils/helper'; +import { isInOptions, isLedgerAccount } from '@utils/helper'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; @@ -137,7 +137,7 @@ function Setting() { }; const onRestoreFundClick = async () => { - if (isLedgerAccount(selectedAccount)) { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ url: chrome.runtime.getURL('options.html#/restore-funds'), }); From e85e74c36ee020aa190ce02233fdb4405c50b6b4 Mon Sep 17 00:00:00 2001 From: Tim Man Date: Fri, 10 Nov 2023 18:43:43 +0800 Subject: [PATCH 4/5] fix: add the bns name resolver to send nft form --- .../components/recipientAddressView/index.tsx | 4 +- src/app/components/sendForm/index.tsx | 45 ++++++----- src/app/hooks/queries/useBnsName.ts | 16 ++-- src/app/screens/sendNft/index.tsx | 80 +++++++++++++------ 4 files changed, 87 insertions(+), 58 deletions(-) diff --git a/src/app/components/recipientAddressView/index.tsx b/src/app/components/recipientAddressView/index.tsx index cdc3d5d75..54e71e372 100644 --- a/src/app/components/recipientAddressView/index.tsx +++ b/src/app/components/recipientAddressView/index.tsx @@ -1,6 +1,5 @@ import ArrowSquareOut from '@assets/img/arrow_square_out.svg'; import { useBnsName } from '@hooks/queries/useBnsName'; -import useNetworkSelector from '@hooks/useNetwork'; import { getExplorerUrl } from '@utils/helper'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; @@ -56,9 +55,8 @@ interface Props { recipient: string; } function RecipientAddressView({ recipient }: Props) { - const selectedNetwork = useNetworkSelector(); const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); - const bnsName = useBnsName(recipient, selectedNetwork); + const bnsName = useBnsName(recipient); const handleOnPress = () => { window.open(getExplorerUrl(recipient)); }; diff --git a/src/app/components/sendForm/index.tsx b/src/app/components/sendForm/index.tsx index 38e4e060d..0feb6cfce 100644 --- a/src/app/components/sendForm/index.tsx +++ b/src/app/components/sendForm/index.tsx @@ -1,13 +1,15 @@ import ActionButton from '@components/button'; import InfoContainer from '@components/infoContainer'; import TokenImage from '@components/tokenImage'; -import { useBnsName, useBNSResolver } from '@hooks/queries/useBnsName'; +import { useBnsName, useBnsResolver } from '@hooks/queries/useBnsName'; import useDebounce from '@hooks/useDebounce'; -import useNetworkSelector from '@hooks/useNetwork'; import useWalletSelector from '@hooks/useWalletSelector'; -import { getBtcEquivalent, getStxTokenEquivalent } from '@secretkeylabs/xverse-core'; -import { getFiatEquivalent } from '@secretkeylabs/xverse-core/transactions'; -import { FungibleToken } from '@secretkeylabs/xverse-core/types'; +import { + FungibleToken, + getBtcEquivalent, + getFiatEquivalent, + getStxTokenEquivalent, +} from '@secretkeylabs/xverse-core'; import InputFeedback from '@ui-library/inputFeedback'; import { CurrencyTypes } from '@utils/constants'; import { getCurrencyFlag } from '@utils/currency'; @@ -64,45 +66,45 @@ const MemoContainer = styled.div((props) => ({ marginBottom: props.theme.spacing(6), })); -const ErrorText = styled.h1((props) => ({ - ...props.theme.body_xs, - color: props.theme.colors.feedback.error, +const ErrorText = styled.p((props) => ({ + ...props.theme.typography.body_s, + color: props.theme.colors.danger_medium, })); const InputFieldContainer = styled.div(() => ({ flex: 1, })); -const TitleText = styled.h1((props) => ({ - ...props.theme.body_medium_m, +const TitleText = styled.p((props) => ({ + ...props.theme.typography.body_medium_m, flex: 1, display: 'flex', })); -const Text = styled.h1((props) => ({ - ...props.theme.body_medium_m, +const Text = styled.p((props) => ({ + ...props.theme.typography.body_medium_m, })); -const SubText = styled.h1((props) => ({ - ...props.theme.body_xs, +const SubText = styled.p((props) => ({ + ...props.theme.typography.body_s, display: 'flex', flex: 1, color: props.theme.colors.white_400, })); -const AssociatedText = styled.h1((props) => ({ - ...props.theme.body_xs, +const AssociatedText = styled.p((props) => ({ + ...props.theme.typography.body_s, wordWrap: 'break-word', })); -const BalanceText = styled.h1((props) => ({ - ...props.theme.body_medium_m, +const BalanceText = styled.p((props) => ({ + ...props.theme.typography.body_medium_m, color: props.theme.colors.white_400, marginRight: props.theme.spacing(2), })); const InputField = styled.input((props) => ({ - ...props.theme.body_m, + ...props.theme.typography.body_m, backgroundColor: props.theme.colors.elevation_n1, color: props.theme.colors.white_0, width: '100%', @@ -224,10 +226,9 @@ function SendForm({ const { stxBtcRate, btcFiatRate, fiatCurrency, stxAddress, selectedAccount } = useWalletSelector(); - const network = useNetworkSelector(); const debouncedSearchTerm = useDebounce(recipientAddress, 300); - const associatedBnsName = useBnsName(recipientAddress, network); - const associatedAddress = useBNSResolver(debouncedSearchTerm, stxAddress, currencyType); + const associatedBnsName = useBnsName(recipientAddress); + const associatedAddress = useBnsResolver(debouncedSearchTerm, stxAddress, currencyType); const { isAccountSwitched } = useClearFormOnAccountSwitch(); useEffect(() => { diff --git a/src/app/hooks/queries/useBnsName.ts b/src/app/hooks/queries/useBnsName.ts index 028d1c06c..6f02fe79a 100644 --- a/src/app/hooks/queries/useBnsName.ts +++ b/src/app/hooks/queries/useBnsName.ts @@ -1,10 +1,10 @@ +import { fetchAddressOfBnsName, getBnsName, validateStxAddress } from '@secretkeylabs/xverse-core'; import { useEffect, useState } from 'react'; -import { StacksNetwork, validateStxAddress } from '@secretkeylabs/xverse-core'; -import { fetchAddressOfBnsName, getBnsName } from '@secretkeylabs/xverse-core/api'; -import useWalletSelector from '../useWalletSelector'; import useNetworkSelector from '../useNetwork'; +import useWalletSelector from '../useWalletSelector'; -export const useBnsName = (walletAddress: string, network: StacksNetwork) => { +export const useBnsName = (walletAddress: string) => { + const network = useNetworkSelector(); const [bnsName, setBnsName] = useState(''); useEffect(() => { @@ -12,15 +12,15 @@ export const useBnsName = (walletAddress: string, network: StacksNetwork) => { const name = await getBnsName(walletAddress, network); setBnsName(name ?? ''); })(); - }, [walletAddress]); + }, [walletAddress, network]); return bnsName; }; -export const useBNSResolver = ( +export const useBnsResolver = ( recipientAddress: string, walletAddress: string, - currencyType: string, + currencyType?: string, ) => { const { network } = useWalletSelector(); const selectedNetwork = useNetworkSelector(); @@ -43,7 +43,7 @@ export const useBNSResolver = ( setAssociatedAddress(''); } })(); - }, [recipientAddress]); + }, [recipientAddress, network, currencyType, selectedNetwork, walletAddress]); return associatedAddress; }; diff --git a/src/app/screens/sendNft/index.tsx b/src/app/screens/sendNft/index.tsx index 60594314a..50c511e91 100644 --- a/src/app/screens/sendNft/index.tsx +++ b/src/app/screens/sendNft/index.tsx @@ -1,6 +1,8 @@ import ActionButton from '@components/button'; +import { useBnsName, useBnsResolver } from '@hooks/queries/useBnsName'; import useStacksCollectibles from '@hooks/queries/useStacksCollectibles'; import useStxPendingTxData from '@hooks/queries/useStxPendingTxData'; +import useDebounce from '@hooks/useDebounce'; import useNetworkSelector from '@hooks/useNetwork'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; import useWalletSelector from '@hooks/useWalletSelector'; @@ -13,7 +15,7 @@ import { validateStxAddress, } from '@secretkeylabs/xverse-core'; import { useMutation } from '@tanstack/react-query'; -import { StyledHeading } from '@ui-library/common.styled'; +import { StyledHeading, StyledP } from '@ui-library/common.styled'; import { InputFeedback, InputFeedbackProps, isDangerFeedback } from '@ui-library/inputFeedback'; import { checkNftExists } from '@utils/helper'; import { getNftDataFromNftsCollectionData } from '@utils/nfts'; @@ -108,8 +110,10 @@ function SendNft() { const selectedNetwork = useNetworkSelector(); const { data: stxPendingTxData } = useStxPendingTxData(); - const { stxAddress, stxPublicKey, network, feeMultipliers, selectedAccount } = - useWalletSelector(); + const { stxAddress, stxPublicKey, network, feeMultipliers } = useWalletSelector(); + const debouncedSearchTerm = useDebounce(recipientAddress, 300); + const associatedBnsName = useBnsName(recipientAddress); + const associatedAddress = useBnsResolver(debouncedSearchTerm, stxAddress); const { isLoading, data, mutate } = useMutation< StacksTransaction, @@ -159,23 +163,6 @@ function SendNft() { navigate(-1); }; - const validateRecipientAddress = (address: string): boolean => { - if (!address) { - setRecipientError({ variant: 'danger', message: t('ERRORS.ADDRESS_REQUIRED') }); - return false; - } - if (!validateStxAddress({ stxAddress: address, network: network.type })) { - setRecipientError({ variant: 'danger', message: t('ERRORS.ADDRESS_INVALID') }); - return false; - } - if (address === stxAddress) { - setRecipientError({ variant: 'info', message: t('YOU_ARE_TRANSFERRING_TO_YOURSELF') }); - return true; - } - setRecipientError(null); - return true; - }; - const onPressNext = async () => { if (stxPendingTxData) { if (checkNftExists(stxPendingTxData?.pendingTransactions, nft!)) { @@ -183,17 +170,40 @@ function SendNft() { return; } } - if (validateRecipientAddress(recipientAddress.trim()) && nft) { + if (!recipientError && nft) { const tokenId = cvToHex(uintCV(nft?.token_id.toString()!)); - mutate({ tokenId, address: recipientAddress }); + mutate({ tokenId, address: associatedAddress || recipientAddress }); } }; const handleAddressChange = (e: React.ChangeEvent) => { - validateRecipientAddress(e.target.value); - setRecipientAddress(e.target.value); + setRecipientAddress(e.target.value.trim()); }; + useEffect(() => { + const validateRecipientAddress = (address: string): boolean => { + if (!address) { + setRecipientError({ variant: 'danger', message: t('ERRORS.ADDRESS_REQUIRED') }); + return false; + } + if (!validateStxAddress({ stxAddress: address, network: network.type })) { + setRecipientError({ variant: 'danger', message: t('ERRORS.ADDRESS_INVALID') }); + return false; + } + if (address === stxAddress) { + setRecipientError({ variant: 'info', message: t('YOU_ARE_TRANSFERRING_TO_YOURSELF') }); + return true; + } + setRecipientError(null); + return true; + }; + if (associatedAddress) { + validateRecipientAddress(associatedAddress); + } else if (recipientAddress) { + validateRecipientAddress(recipientAddress); + } + }, [associatedAddress, recipientAddress, network.type, stxAddress, t]); + const isNextEnabled = !isDangerFeedback(recipientError) && !!recipientAddress; // hide back button if there is no history @@ -218,11 +228,31 @@ function SendNft() { + {associatedAddress && ( + <> + + {t('ASSOCIATED_ADDRESS')} + + + {associatedAddress} + + + )} + {associatedBnsName && ( + <> + + {t('ASSOCIATED_BNS_DOMAIN')} + + + {associatedBnsName} + + + )} {recipientError && } From db4e30d9182fb69740291b39dfce05e23abf6272 Mon Sep 17 00:00:00 2001 From: Tim Man Date: Fri, 10 Nov 2023 19:07:42 +0800 Subject: [PATCH 5/5] fix: opening a send-nft from ledger should open responsive screen and debounce the stx address -> bns name resolver --- src/app/routes/index.tsx | 4 ---- src/app/screens/nftDetail/useNftDetail.ts | 2 +- src/app/screens/sendNft/index.tsx | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app/routes/index.tsx b/src/app/routes/index.tsx index 83f632342..dc7d7f22d 100644 --- a/src/app/routes/index.tsx +++ b/src/app/routes/index.tsx @@ -375,10 +375,6 @@ const router = createHashRouter([ ), }, - { - path: 'send-nft/:id', - element: , - }, { path: 'confirm-inscription-request', element: ( diff --git a/src/app/screens/nftDetail/useNftDetail.ts b/src/app/screens/nftDetail/useNftDetail.ts index bd2191d1a..f95f3bcf2 100644 --- a/src/app/screens/nftDetail/useNftDetail.ts +++ b/src/app/screens/nftDetail/useNftDetail.ts @@ -53,7 +53,7 @@ export default function useNftDetail() { const handleOnSendClick = async () => { if (isLedgerAccount(selectedAccount) && !isInOptions()) { await chrome.tabs.create({ - url: chrome.runtime.getURL(`options.html#/send-nft/${id}`), + url: chrome.runtime.getURL(`options.html#/nft-dashboard/nft-detail/${id}/send-nft`), }); return; } diff --git a/src/app/screens/sendNft/index.tsx b/src/app/screens/sendNft/index.tsx index 50c511e91..11b2acabd 100644 --- a/src/app/screens/sendNft/index.tsx +++ b/src/app/screens/sendNft/index.tsx @@ -112,7 +112,7 @@ function SendNft() { const { data: stxPendingTxData } = useStxPendingTxData(); const { stxAddress, stxPublicKey, network, feeMultipliers } = useWalletSelector(); const debouncedSearchTerm = useDebounce(recipientAddress, 300); - const associatedBnsName = useBnsName(recipientAddress); + const associatedBnsName = useBnsName(debouncedSearchTerm); const associatedAddress = useBnsResolver(debouncedSearchTerm, stxAddress); const { isLoading, data, mutate } = useMutation< @@ -170,7 +170,7 @@ function SendNft() { return; } } - if (!recipientError && nft) { + if (!isDangerFeedback(recipientError) && nft) { const tokenId = cvToHex(uintCV(nft?.token_id.toString()!)); mutate({ tokenId, address: associatedAddress || recipientAddress }); }