diff --git a/package-lock.json b/package-lock.json index 0088ed6ad..886cbf705 100644 --- a/package-lock.json +++ b/package-lock.json @@ -99,6 +99,36 @@ "webpack-dev-server": "^4.11.0" } }, + "../../xverse-core/xverse-core": { + "name": "@secretkeylabs/xverse-core", + "version": "0.0.1", + "extraneous": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "@stacks/encryption": "4.3.5", + "@stacks/keychain": "4.3.5", + "@stacks/network": "4.3.5", + "@stacks/transactions": "4.3.5", + "@stacks/wallet-sdk": "^5.0.2", + "axios": "0.27.2", + "bignumber.js": "9.1.0", + "bip32": "^2.0.6", + "bip39": "3.0.3", + "bitcoinjs-lib": "5.2.0", + "buffer": "6.0.3", + "process": "^0.11.10", + "util": "^0.12.4" + }, + "devDependencies": { + "jest": "^29.0.3", + "rimraf": "^3.0.2", + "ts-loader": "^9.4.1", + "typescript": "^4.8.3", + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0" + } + }, "node_modules/@adobe/css-tools": { "version": "4.0.1", "license": "MIT" diff --git a/src/app/screens/confirmStxTransaxtion/confirmStxTransactionComponent/index.tsx b/src/app/components/confirmStxTransactionComponent/index.tsx similarity index 88% rename from src/app/screens/confirmStxTransaxtion/confirmStxTransactionComponent/index.tsx rename to src/app/components/confirmStxTransactionComponent/index.tsx index 4e2a748c9..4966b2c46 100644 --- a/src/app/screens/confirmStxTransaxtion/confirmStxTransactionComponent/index.tsx +++ b/src/app/components/confirmStxTransactionComponent/index.tsx @@ -35,9 +35,8 @@ const Container = styled.div` const ButtonContainer = styled.div((props) => ({ display: 'flex', flexDirection: 'row', - marginLeft: props.theme.spacing(8), - marginRight: props.theme.spacing(8), - marginBottom: props.theme.spacing(8), + marginBottom: props.theme.spacing(20), + marginTop: props.theme.spacing(24), })); const TransparentButtonContainer = styled.div((props) => ({ @@ -49,12 +48,10 @@ const TransparentButtonContainer = styled.div((props) => ({ const Button = styled.button((props) => ({ display: 'flex', flexDirection: 'row', - justifyContent: 'flex-end', - alignItems: 'center', borderRadius: props.theme.radius(1), backgroundColor: 'transparent', width: '100%', - marginTop: props.theme.spacing(5), + marginTop: props.theme.spacing(10), })); const ButtonText = styled.div((props) => ({ @@ -76,6 +73,7 @@ interface Props { onConfirmClick: (transactions: StacksTransaction[]) => void; children: ReactNode; isSponsored?: boolean; + isNft?: boolean; } function ConfirmStxTransationComponent({ @@ -85,6 +83,7 @@ function ConfirmStxTransationComponent({ children, onConfirmClick, onCancelClick, + isNft, }: Props) { const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); const { @@ -98,7 +97,7 @@ function ConfirmStxTransationComponent({ const [buttonLoading, setButtonLoading] = useState(loading); const handleBackButtonClick = () => { - navigate('/send-stx'); + navigate(-1); }; useEffect(() => { @@ -167,7 +166,7 @@ function ConfirmStxTransationComponent({ <> - + {!isNft && } {children} - - - + + + + + - + + - - ); } diff --git a/src/app/components/recipinetAddressView/index.tsx b/src/app/components/recipinetAddressView/index.tsx index b45192061..6ad18d2d6 100644 --- a/src/app/components/recipinetAddressView/index.tsx +++ b/src/app/components/recipinetAddressView/index.tsx @@ -2,6 +2,8 @@ import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import ArrowSquareOut from '@assets/img/arrow_square_out.svg'; import { getExplorerUrl } from '@utils/helper'; +import { useBnsName } from '@hooks/useBnsName'; +import useWalletSelector from '@hooks/useWalletSelector'; const InfoContainer = styled.div((props) => ({ display: 'flex', @@ -47,8 +49,9 @@ interface Props { recipient: string; } function RecipientAddressView({ recipient }: Props) { + const { network } = useWalletSelector(); const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); - + const bnsName = useBnsName(recipient, network); const handleOnPress = () => { window.open(getExplorerUrl(recipient)); }; @@ -56,6 +59,7 @@ function RecipientAddressView({ recipient }: Props) { return ( {t('RECEPIENT_ADDRESS')} + {bnsName} {recipient} diff --git a/src/app/components/sendForm/index.tsx b/src/app/components/sendForm/index.tsx index d94ce3090..166a4dd8b 100644 --- a/src/app/components/sendForm/index.tsx +++ b/src/app/components/sendForm/index.tsx @@ -2,7 +2,9 @@ import { CurrencyTypes } from '@utils/constants'; import { FungibleToken } from '@secretkeylabs/xverse-core/types'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; -import { SetStateAction, useState } from 'react'; +import { + ReactNode, SetStateAction, useEffect, useState, +} from 'react'; import BigNumber from 'bignumber.js'; import IconBitcoin from '@assets/img/send/ic_sats_ticker.svg'; import IconStacks from '@assets/img/dashboard/stack_icon.svg'; @@ -14,6 +16,7 @@ import ActionButton from '@components/button'; import { btcToSats, getBtcFiatEquivalent, getStxFiatEquivalent, stxToMicrostacks, } from '@secretkeylabs/xverse-core/currency'; +import { useBNSResolver, useDebounce } from '@hooks/useBnsName'; const ScrollContainer = styled.div` display: flex; @@ -23,6 +26,8 @@ const ScrollContainer = styled.div` &::-webkit-scrollbar { display: none; } + margin-left: 5%; + margin-right: 5%; `; const OuterContainer = styled.div({ display: 'flex', @@ -39,8 +44,6 @@ const RowContainer = styled.div({ const InfoContainer = styled.div((props) => ({ display: 'flex', flexDirection: 'row', - marginLeft: props.theme.spacing(8), - marginRight: props.theme.spacing(8), padding: props.theme.spacing(8), border: `1px solid ${props.theme.colors.background.elevation3}`, borderRadius: 8, @@ -50,14 +53,10 @@ const Container = styled.div((props) => ({ display: 'flex', flexDirection: 'column', marginTop: props.theme.spacing(11), - marginLeft: props.theme.spacing(8), - marginRight: props.theme.spacing(8), })); const ErrorContainer = styled.div((props) => ({ marginTop: props.theme.spacing(8), - marginLeft: props.theme.spacing(10), - marginRight: props.theme.spacing(10), })); const ErrorText = styled.h1((props) => ({ @@ -94,6 +93,11 @@ const SubText = styled.h1((props) => ({ color: props.theme.colors.white['400'], })); +const AssociatedText = styled.h1((props) => ({ + ...props.theme.body_xs, + wordWrap: 'break-word', +})); + const BalanceText = styled.h1((props) => ({ ...props.theme.body_medium_m, color: props.theme.colors.white['400'], @@ -131,8 +135,6 @@ const TickerImage = styled.img((props) => ({ })); const SendButtonContainer = styled.div((props) => ({ - paddingLeft: props.theme.spacing(8), - paddingRight: props.theme.spacing(8), paddingBottom: props.theme.spacing(8), paddingTop: props.theme.spacing(4), })); @@ -146,6 +148,7 @@ interface Props { hideMemo?: boolean; buttonText?: string; processing?: boolean; + children?: ReactNode; } function SendForm({ @@ -158,16 +161,35 @@ function SendForm({ hideMemo = false, buttonText, processing, + children, }: Props) { const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); const [amount, setAmount] = useState(''); const [memo, setMemo] = useState(''); const [fiatAmount, setFiatAmount] = useState('0'); + const [showError, setShowError] = useState(error); const [recipientAddress, setRecipientAddress] = useState(''); - - const { stxBtcRate, btcFiatRate, fiatCurrency } = useSelector( + const { + stxBtcRate, btcFiatRate, fiatCurrency, stxAddress, + } = useSelector( (state: StoreState) => state.walletState, ); + const debouncedSearchTerm = useDebounce(recipientAddress, 300); + const associatedAddress = useBNSResolver( + debouncedSearchTerm, + stxAddress, + currencyType, + ); + + useEffect(() => { + if (error) { + if (associatedAddress !== '' && error.includes(t('ERRORS.ADDRESS_INVALID'))) { + setShowError(''); + } else { + setShowError(error); + } + } + }, [error, associatedAddress]); function getFiatEquivalent(value: number) { if ((currencyType === 'FT' && !fungibleToken?.tokenFiatRate) || currencyType === 'NFT') { @@ -262,6 +284,10 @@ function SendForm({ ); } + const onAddressInputChange = (e: { target: { value: SetStateAction } }) => { + setRecipientAddress(e.target.value); + }; + function renderEnterRecepientSection() { return ( @@ -270,22 +296,29 @@ function SendForm({ } }) => setRecipientAddress(e.target.value)} + onChange={onAddressInputChange} /> + {associatedAddress && currencyType !== 'BTC' && ( + <> + {t('ASSOCIATED_ADDRESS')} + {associatedAddress} + + )} ); } const handleOnPress = () => { - onPressSend(recipientAddress, amount, memo); + onPressSend(associatedAddress !== '' ? associatedAddress : debouncedSearchTerm, amount, memo); }; return ( {!disableAmountInput && renderEnterAmountSection()} + {children} {renderEnterRecepientSection()} {currencyType !== 'BTC' && currencyType !== 'NFT' && !hideMemo && ( <> @@ -311,7 +344,7 @@ function SendForm({ )} - {error} + {showError} diff --git a/src/app/components/shareNft/index.tsx b/src/app/components/shareNft/index.tsx index 8adad553c..d9389a985 100644 --- a/src/app/components/shareNft/index.tsx +++ b/src/app/components/shareNft/index.tsx @@ -13,9 +13,6 @@ import ShareLinkRow from './shareLinkRow'; const Container = styled.button((props) => ({ display: 'flex', flexDirection: 'column', - position: 'absolute', - top: 0, - right: 0, justifyContent: 'center', paddingLeft: props.theme.spacing(6), paddingRight: props.theme.spacing(8), diff --git a/src/app/components/topRow/index.tsx b/src/app/components/topRow/index.tsx index de6c08909..7f6ba2c09 100644 --- a/src/app/components/topRow/index.tsx +++ b/src/app/components/topRow/index.tsx @@ -27,8 +27,8 @@ const RowContainer = styled.div((props) => ({ flexDirection: 'row', alignItems: 'center', paddingTop: props.theme.spacing(11), - paddingLeft: props.theme.spacing(11), - paddingRight: props.theme.spacing(11), + paddingLeft: '5%', + paddingRight: '5%', })); interface Props { diff --git a/src/app/components/transferFeeView/index.tsx b/src/app/components/transferFeeView/index.tsx index ce8fac7c0..1a96130bd 100644 --- a/src/app/components/transferFeeView/index.tsx +++ b/src/app/components/transferFeeView/index.tsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; const RowContainer = styled.div((props) => ({ display: 'flex', flexDirection: 'row', - marginTop: props.theme.spacing(6), + marginTop: props.theme.spacing(8), })); const FeeText = styled.h1((props) => ({ diff --git a/src/app/hooks/useBnsName.ts b/src/app/hooks/useBnsName.ts new file mode 100644 index 000000000..53f80ce59 --- /dev/null +++ b/src/app/hooks/useBnsName.ts @@ -0,0 +1,62 @@ +import { useEffect, useState } from 'react'; +import { SettingsNetwork, validateStxAddress } from '@secretkeylabs/xverse-core'; +import { + fetchAddressOfBnsName, getBnsName, +} from '@secretkeylabs/xverse-core/api'; +import useWalletSelector from './useWalletSelector'; + +export const useBnsName = (walletAddress: string, network: SettingsNetwork) => { + const [bnsName, setBnsName] = useState(''); + + useEffect(() => { + (async () => { + const name = await getBnsName(walletAddress, network); + setBnsName(name ?? ''); + })(); + }, [walletAddress]); + + return bnsName; +}; + +export const useBNSResolver = ( + recipientAddress: string, + walletAddress: string, + currencyType: string, +) => { + const { network } = useWalletSelector(); + const [associatedAddress, setAssociatedAddress] = useState(''); + + useEffect(() => { + (async () => { + if (currencyType !== 'BTC') { + if (!validateStxAddress({ stxAddress: recipientAddress, network })) { + const address = await fetchAddressOfBnsName( + recipientAddress.toLocaleLowerCase(), + walletAddress.toLocaleLowerCase(), + network, + ); + setAssociatedAddress(address ?? ''); + } else { + setAssociatedAddress(''); + } + } else { + setAssociatedAddress(recipientAddress); + } + })(); + }, [recipientAddress]); + + return associatedAddress; +}; + +export function useDebounce(value: string, delay: number) { + const [debouncedValue, setDebouncedValue] = useState(value); + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + return debouncedValue; +} diff --git a/src/app/hooks/useNftDataSelector.ts b/src/app/hooks/useNftDataSelector.ts new file mode 100644 index 000000000..a9a4309ed --- /dev/null +++ b/src/app/hooks/useNftDataSelector.ts @@ -0,0 +1,14 @@ +import { StoreState } from '@stores/index'; +import { useSelector } from 'react-redux'; + +const useNftDataSelector = () => { + const nftDataState = useSelector((state: StoreState) => ({ + ...state.nftDataState, + })); + + return { + ...nftDataState, + }; +}; + +export default useNftDataSelector; diff --git a/src/app/hooks/useNftReducer.ts b/src/app/hooks/useNftReducer.ts new file mode 100644 index 000000000..dee6e9f3c --- /dev/null +++ b/src/app/hooks/useNftReducer.ts @@ -0,0 +1,21 @@ +import { NftDetailResponse } from '@secretkeylabs/xverse-core/types'; +import { setNftDataAction } from '@stores/nftData/actions/actionCreator'; +import { useDispatch } from 'react-redux'; +import useNftDataSelector from './useNftDataSelector'; + +const useNftDataReducer = () => { + const { nftData } = useNftDataSelector(); + const dispatch = useDispatch(); + const storeNftData = (data:NftDetailResponse) => { + if (data && !nftData.includes(data.data)) { + const modifiedNftList = [...nftData]; + modifiedNftList.push(data.data); + dispatch(setNftDataAction(modifiedNftList)); + } + }; + return { + storeNftData, + }; +}; + +export default useNftDataReducer; diff --git a/src/app/routes/index.tsx b/src/app/routes/index.tsx index 958fe40d3..db1ad2bc5 100644 --- a/src/app/routes/index.tsx +++ b/src/app/routes/index.tsx @@ -22,11 +22,14 @@ import ForgotPassword from '@screens/forgotPassword'; import BackupWalletSteps from '@screens/backupWalletSteps'; import Stacking from '@screens/stacking'; import NftDashboard from '@screens/nftDashboard'; +import NftDetailScreen from '@screens/nftDetail'; import Setting from '@screens/settings'; import FiatCurrencyScreen from '@screens/settings/fiatCurrency'; import ChangePasswordScreen from '@screens/settings/changePassword'; import ChangeNetworkScreen from '@screens/settings/changeNetwork'; import BackupWalletScreen from '@screens/settings/backupWallet'; +import SendNft from '@screens/sendNft'; +import ConfirmNftTransaction from '@screens/confirmNftTransaction'; const router = createHashRouter([ { @@ -73,6 +76,10 @@ const router = createHashRouter([ path: 'send-btc', element: , }, + { + path: 'nft-dashboard/nft-detail/:id/send-nft', + element: , + }, { path: 'confirm-stx-tx', element: , @@ -121,6 +128,14 @@ const router = createHashRouter([ path: 'nft-dashboard', element: , }, + { + path: 'nft-dashboard/nft-detail/:id', + element: , + }, + { + path: 'confirm-nft-tx/:id', + element: , + }, { path: 'settings', element: , diff --git a/src/app/screens/confirmNftTransaction/index.tsx b/src/app/screens/confirmNftTransaction/index.tsx new file mode 100644 index 000000000..1a437556e --- /dev/null +++ b/src/app/screens/confirmNftTransaction/index.tsx @@ -0,0 +1,154 @@ +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { useMutation } from '@tanstack/react-query'; +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { StacksTransaction } from '@secretkeylabs/xverse-core/types'; +import { broadcastSignedTransaction } from '@secretkeylabs/xverse-core/transactions'; +import Seperator from '@components/seperator'; +import { StoreState } from '@stores/index'; +import BottomBar from '@components/tabBar'; +import { fetchStxWalletDataRequestAction } from '@stores/wallet/actions/actionCreators'; +import RecipientAddressView from '@components/recipinetAddressView'; +import ConfirmStxTransationComponent from '@components/confirmStxTransactionComponent'; +import useNftDataSelector from '@hooks/useNftDataSelector'; +import NftImage from '@screens/nftDashboard/nftImage'; + +const InfoContainer = styled.div((props) => ({ + display: 'flex', + flexDirection: 'column', + marginTop: props.theme.spacing(12), +})); + +const TitleText = styled.h1((props) => ({ + ...props.theme.headline_category_s, + color: props.theme.colors.white['400'], + textTransform: 'uppercase', +})); + +const ValueText = styled.h1((props) => ({ + ...props.theme.body_m, + marginTop: props.theme.spacing(2), + wordBreak: 'break-all', +})); + +const Container = styled.div({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', +}); + +const NFtContainer = styled.div((props) => ({ + maxWidth: 450, + width: '60%', + display: 'flex', + aspectRatio: 1, + justifyContent: 'center', + alignItems: 'center', + borderRadius: 8, + padding: props.theme.spacing(5), + marginBottom: props.theme.spacing(6), +})); + +const NftTitleText = styled.h1((props) => ({ + ...props.theme.headline_s, + color: props.theme.colors.white['0'], + textAlign: 'center', +})); + +function ConfirmNftTransaction() { + const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const location = useLocation(); + const { id } = useParams(); + const { nftData } = useNftDataSelector(); + const nftIdDetails = id!.split('::'); + const nft = nftData.find((nftItem) => nftItem?.asset_id === nftIdDetails[1]); + const { unsignedTx, recipientAddress } = location.state; + const { + stxBtcRate, network, stxAddress, fiatCurrency, + } = useSelector( + (state: StoreState) => state.walletState, + ); + + const { + isLoading, + error: txError, + data: stxTxBroadcastData, + mutate, + } = useMutation< + string, + Error, + { signedTx: StacksTransaction }>(async ({ signedTx }) => broadcastSignedTransaction(signedTx, network.type)); + + useEffect(() => { + if (stxTxBroadcastData) { + navigate('/tx-status', { + state: { + txid: stxTxBroadcastData, + currency: 'STX', + error: '', + }, + }); + setTimeout(() => { + dispatch(fetchStxWalletDataRequestAction(stxAddress, network, fiatCurrency, stxBtcRate)); + }, 1000); + } + }, [stxTxBroadcastData]); + + useEffect(() => { + if (txError) { + navigate('/tx-status', { + state: { + txid: '', + currency: 'STX', + error: txError.toString(), + }, + }); + } + }, [txError]); + + const networkInfoSection = ( + + {t('NETWORK')} + {network.type} + + ); + + const handleOnConfirmClick = (txs: StacksTransaction[]) => { + mutate({ signedTx: txs[0] }); + }; + + const handleOnCancelClick = () => { + navigate(-1); + }; + + return ( + <> + + + + + + {nft?.token_metadata.name} + + + {networkInfoSection} + + + + + ); +} +export default ConfirmNftTransaction; diff --git a/src/app/screens/confirmStxTransaxtion/index.tsx b/src/app/screens/confirmStxTransaxtion/index.tsx index a6113b0b4..f12af79bd 100644 --- a/src/app/screens/confirmStxTransaxtion/index.tsx +++ b/src/app/screens/confirmStxTransaxtion/index.tsx @@ -13,7 +13,7 @@ import { StoreState } from '@stores/index'; import BottomBar from '@components/tabBar'; import { fetchStxWalletDataRequestAction } from '@stores/wallet/actions/actionCreators'; import RecipientAddressView from '@components/recipinetAddressView'; -import ConfirmStxTransationComponent from './confirmStxTransactionComponent'; +import ConfirmStxTransationComponent from '../../components/confirmStxTransactionComponent'; const InfoContainer = styled.div((props) => ({ display: 'flex', @@ -51,7 +51,6 @@ function ConfirmStxTransaction() { } = useSelector( (state: StoreState) => state.walletState, ); - const { isLoading, error: txError, @@ -60,7 +59,7 @@ function ConfirmStxTransaction() { } = useMutation< string, Error, - { signedTx: StacksTransaction }>(async ({ signedTx }) => broadcastSignedTransaction(signedTx, network)); + { signedTx: StacksTransaction }>(async ({ signedTx }) => broadcastSignedTransaction(signedTx, network.type)); useEffect(() => { if (stxTxBroadcastData) { diff --git a/src/app/screens/confrimBtcTransaction/confirmBtcTransactionComponent/index.tsx b/src/app/screens/confrimBtcTransaction/confirmBtcTransactionComponent/index.tsx index 4b2ec0365..8bbe8f44d 100644 --- a/src/app/screens/confrimBtcTransaction/confirmBtcTransactionComponent/index.tsx +++ b/src/app/screens/confrimBtcTransaction/confirmBtcTransactionComponent/index.tsx @@ -113,7 +113,7 @@ function ConfirmBtcTransactionComponent({ index: selectedAccount?.id ?? 0, fee: new BigNumber(txFee), seedPhrase, - network, + network: network.type, })); useEffect(() => { diff --git a/src/app/screens/confrimBtcTransaction/index.tsx b/src/app/screens/confrimBtcTransaction/index.tsx index 55edf26a1..361faa4fa 100644 --- a/src/app/screens/confrimBtcTransaction/index.tsx +++ b/src/app/screens/confrimBtcTransaction/index.tsx @@ -43,14 +43,13 @@ function ConfirmBtcTransaction() { const [recipientAddress, setRecipientAddress] = useState(''); const location = useLocation(); const { fee, amount, signedTxHex } = location.state; - const { isLoading, error: txError, data: btcTxBroadcastData, mutate, } = useMutation( - async ({ signedTx }) => broadcastRawBtcTransaction(signedTx, network), + async ({ signedTx }) => broadcastRawBtcTransaction(signedTx, network.type), ); useEffect(() => { diff --git a/src/app/screens/nftDashboard/index.tsx b/src/app/screens/nftDashboard/index.tsx index 1e510a7fa..aebceb523 100644 --- a/src/app/screens/nftDashboard/index.tsx +++ b/src/app/screens/nftDashboard/index.tsx @@ -19,16 +19,16 @@ import ShareDialog from '@components/shareNft'; import Nft from './nft'; const Container = styled.div` -display: flex; -flex-direction: column; -flex: 1; -margin-left: 5%; -margin-right: 5%; -margin-bottom: 5%; -overflow-y: auto; -&::-webkit-scrollbar { - display: none; -} + display: flex; + flex-direction: column; + flex: 1; + margin-left: 5%; + margin-right: 5%; + margin-bottom: 5%; + overflow-y: auto; + &::-webkit-scrollbar { + display: none; + } `; const GridContainer = styled.div((props) => ({ @@ -38,6 +38,12 @@ const GridContainer = styled.div((props) => ({ gridTemplateColumns: 'repeat(auto-fit,minmax(150px,1fr))', })); +const ShareDialogeContainer = styled.div({ + position: 'absolute', + top: 0, + right: 0, +}); + const WebGalleryButtonContainer = styled.div((props) => ({ display: 'flex', flexDirection: 'row', @@ -76,7 +82,7 @@ const ShareButtonContainer = styled.div((props) => ({ width: '100%', })); -const Button = styled.button((props) => ({ +const WebGalleryButton = styled.button((props) => ({ display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', @@ -87,7 +93,7 @@ const Button = styled.button((props) => ({ marginTop: props.theme.spacing(5), })); -const ButtonText = styled.div((props) => ({ +const WebGalleryButtonText = styled.div((props) => ({ ...props.theme.body_xs, fontWeight: 700, color: props.theme.colors.white['0'], @@ -125,8 +131,8 @@ const NoCollectiblesText = styled.h1((props) => ({ })); const BarLoaderContainer = styled.div((props) => ({ - display: 'flex', marginTop: props.theme.spacing(5), + maxWidth: 300, })); function NftDashboard() { @@ -146,12 +152,6 @@ function NftDashboard() { navigate('/account-list'); }; - const loader = ( - - - - ); - const openInGalleryView = async () => { await chrome.tabs.create({ url: chrome.runtime.getURL('options.html#/nft-dashboard'), @@ -193,15 +193,19 @@ function NftDashboard() { {t('COLLECTIBLES')} - {isLoading ? loader + {isLoading ? ( + + + + ) : {`${data?.total} ${t('ITEMS')}`}} - + @@ -214,8 +218,9 @@ function NftDashboard() { transparent /> - - {showShareNftOptions && } + + {showShareNftOptions && } + {isLoading ? ( diff --git a/src/app/screens/nftDashboard/nft.tsx b/src/app/screens/nftDashboard/nft.tsx index 1952fc4ff..e46357577 100644 --- a/src/app/screens/nftDashboard/nft.tsx +++ b/src/app/screens/nftDashboard/nft.tsx @@ -5,6 +5,7 @@ import { NonFungibleToken, getBnsNftName } from '@secretkeylabs/xverse-core/type import { BNS_CONTRACT } from '@utils/constants'; import NftUser from '@assets/img/nftDashboard/nft_user.svg'; import { useNavigate } from 'react-router-dom'; +import useNftDataReducer from '@hooks/useNftReducer'; import NftImage from './nftImage'; interface Props { @@ -42,6 +43,8 @@ const GridItemContainer = styled.button((props) => ({ function Nft({ asset }: Props) { const navigate = useNavigate(); + const { storeNftData } = useNftDataReducer(); + const url = `${asset.asset_identifier}::${asset.value.repr}`; const { data } = useQuery( ['nft-meta-data', asset.asset_identifier], async () => { @@ -64,7 +67,10 @@ function Nft({ asset }: Props) { } const handleOnClick = () => { - + storeNftData(data); + if (asset.asset_identifier !== BNS_CONTRACT) { + navigate(`nft-detail/${url}`); + } }; return ( @@ -80,7 +86,6 @@ function Nft({ asset }: Props) { )} {getName()} - ); } export default Nft; diff --git a/src/app/screens/nftDashboard/nftImage.tsx b/src/app/screens/nftDashboard/nftImage.tsx index b7dd4216d..eff08eb08 100644 --- a/src/app/screens/nftDashboard/nftImage.tsx +++ b/src/app/screens/nftDashboard/nftImage.tsx @@ -1,10 +1,10 @@ import { Suspense } from 'react'; import styled from 'styled-components'; import { Ring } from 'react-spinners-css'; +import Img from 'react-image'; import { TokenMetaData } from '@secretkeylabs/xverse-core/types/api/stacks/assets'; import { getFetchableUrl } from '@utils/helper'; import NftPlaceholderImage from '@assets/img/nftDashboard/ic_nft_diamond.svg'; -import Img from 'react-image'; const ImageContainer = styled.div((props) => ({ padding: props.theme.spacing(10), @@ -39,7 +39,7 @@ function NftImage({ metadata }: Props) { -)} + )} unloader={showNftImagePlaceholder} /> @@ -48,9 +48,7 @@ function NftImage({ metadata }: Props) { if (metadata?.asset_protocol) { return ( - - +