diff --git a/src/app/_components/NavBar/NetworkLabel.tsx b/src/app/_components/NavBar/NetworkLabel.tsx index 6c7a139dc..931f10779 100644 --- a/src/app/_components/NavBar/NetworkLabel.tsx +++ b/src/app/_components/NavBar/NetworkLabel.tsx @@ -1,6 +1,6 @@ import { useColorMode, useColorModeValue } from '@chakra-ui/react'; import { Check, Trash } from '@phosphor-icons/react'; -import React, { FC } from 'react'; +import { FC, useMemo } from 'react'; import { ChainID } from '@stacks/transactions'; @@ -52,6 +52,16 @@ export const NetworkLabel: FC<{ network: Network }> = ({ network }) => { const greenBadgeBg = useColorModeValue('green.100', 'green.900'); const badgeBorder = useColorModeValue('purple.300', 'purple.700'); + const isNetworkRemovable = useMemo( + () => + network.isCustomNetwork && + !isDevnet && + !isActive && + !network.label.includes('Nakamoto') && + network.label !== 'https://api.nakamoto.testnet.hiro.so', + [network.isCustomNetwork, isDevnet, isActive, network.label] + ); + return ( = ({ network }) => { ) : !!error ? ( Offline - ) : network.isCustomNetwork && - !isDevnet && - !isActive && - !network.label.includes('Nakamoto') && - network.label !== 'https://api.nakamoto.testnet.hiro.so' ? ( + ) : isNetworkRemovable ? ( - + {children} diff --git a/src/app/sandbox/layout/RightPanelSkeleton.tsx b/src/app/sandbox/layout/RightPanelSkeleton.tsx index b2669972e..5033d7f2b 100644 --- a/src/app/sandbox/layout/RightPanelSkeleton.tsx +++ b/src/app/sandbox/layout/RightPanelSkeleton.tsx @@ -14,6 +14,7 @@ export function RightPanelSkeleton() { width={96} height={'full'} p={7} + overflow="hidden" > diff --git a/src/app/token/[tokenId]/TokenInfo/Price.tsx b/src/app/token/[tokenId]/TokenInfo/Price.tsx index 4ff55b8e3..0a96dd0b7 100644 --- a/src/app/token/[tokenId]/TokenInfo/Price.tsx +++ b/src/app/token/[tokenId]/TokenInfo/Price.tsx @@ -1,4 +1,3 @@ -import { useColorMode } from '@chakra-ui/react'; import { FC } from 'react'; import { Flex } from '../../../../ui/Flex'; @@ -13,7 +12,6 @@ export const Price: FC< currentPriceInBtc: number | null | undefined; } > = ({ currentPrice, priceChangePercentage24h, currentPriceInBtc, ...gridProps }) => { - const colorMode = useColorMode().colorMode; return ( = ({ tx, @@ -19,9 +18,7 @@ export const Recipient: FC<{ tx: TokenTransferTransaction | MempoolTokenTransfer label={'Recipient'} value={ - - - + = ({ tx }) => ( @@ -17,9 +16,7 @@ export const Sender: FC<{ tx: Transaction | MempoolTransaction }> = ({ tx }) => label={getSenderName(tx.tx_type)} value={ - - - + = { [NetworkModes.Mainnet]: MAINNET_BTC_ADDRESS_BASE_URL, [NetworkModes.Testnet]: TESTNET_BTC_ADDRESS_BASE_URL, }; + +export const mainnetNetwork: Network = { + label: 'Stacks Mainnet', + url: NetworkModeUrlMap[NetworkModes.Mainnet], + btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Mainnet], + btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Mainnet], + btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Mainnet], + networkId: ChainID.Mainnet, + mode: NetworkModes.Mainnet, +}; + +export const testnetNetwork: Network = { + label: 'Stacks Testnet (Primary)', + url: NetworkModeUrlMap[NetworkModes.Testnet], + btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Testnet], + btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Testnet], + btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Testnet], + networkId: ChainID.Testnet, + mode: NetworkModes.Testnet, +}; + +export const nakamotoTestnetNetwork: Network = { + label: 'Nakamoto Testnet', + url: 'https://api.nakamoto.testnet.hiro.so', + btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Testnet], + btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Testnet], + btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Testnet], + networkId: ChainID.Testnet, + mode: NetworkModes.Testnet, + isCustomNetwork: true, +}; + +export const oldTestnetNetwork: Network = { + label: 'Stacks Testnet (Archive)', + url: 'https://api.old.testnet.hiro.so', + btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Testnet], + btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Testnet], + btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Testnet], + networkId: ChainID.Testnet, + mode: NetworkModes.Testnet, + isCustomNetwork: true, +}; + +export const devnetNetwork: Network = { + label: 'Devnet', + url: DEFAULT_DEVNET_SERVER, + btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Testnet], + btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Testnet], + btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Testnet], + networkId: ChainID.Testnet, + mode: NetworkModes.Testnet, + isCustomNetwork: true, +}; diff --git a/src/common/context/AppContextProvider.tsx b/src/common/context/AppContextProvider.tsx new file mode 100644 index 000000000..05f7c2489 --- /dev/null +++ b/src/common/context/AppContextProvider.tsx @@ -0,0 +1,288 @@ +'use client'; + +import cookie from 'cookie'; +import { useSearchParams } from 'next/navigation'; +import { FC, ReactNode, createContext, useCallback, useEffect, useState } from 'react'; +import { useCookies } from 'react-cookie'; + +import { + StacksApiSocketClientInfo, + useStacksApiSocketClient, +} from '../../app/_components/BlockList/Sockets/use-stacks-api-socket-client'; +import { buildCustomNetworkUrl, fetchCustomNetworkId } from '../components/modals/AddNetwork/utils'; +import { IS_BROWSER } from '../constants/constants'; +import { + NetworkIdModeMap, + NetworkModeBtcAddressBaseUrlMap, + NetworkModeBtcBlockBaseUrlMap, + NetworkModeBtcTxBaseUrlMap, + NetworkModeUrlMap, + devnetNetwork, + mainnetNetwork, + nakamotoTestnetNetwork, + oldTestnetNetwork, + testnetNetwork, +} from '../constants/network'; +import { ONE_HOUR } from '../queries/query-stale-time'; +import { Network, NetworkModes } from '../types/network'; +import { removeTrailingSlash } from '../utils/utils'; + +function filterNetworks( + networks: Record, + removedNetworks: Record +) { + return Object.fromEntries( + Object.entries(networks).filter(([key]) => !Object.keys(removedNetworks).includes(key)) + ); +} + +interface Props { + cookies: string; + apiUrls: Record; + btcBlockBaseUrls: Record; + btcTxBaseUrls: Record; + btcAddressBaseUrls: Record; + activeNetwork: Network; + activeNetworkKey: string; + addCustomNetwork: (network: Network) => void; + removeCustomNetwork: (network: Network) => void; + networks: Record; + stacksApiSocketClientInfo: StacksApiSocketClientInfo | null; +} + +export const AppContext = createContext({ + cookies: '', + apiUrls: NetworkModeUrlMap, + btcBlockBaseUrls: NetworkModeBtcBlockBaseUrlMap, + btcTxBaseUrls: NetworkModeBtcTxBaseUrlMap, + btcAddressBaseUrls: NetworkModeBtcAddressBaseUrlMap, + activeNetwork: mainnetNetwork, + activeNetworkKey: NetworkModeUrlMap[NetworkModes.Mainnet], // TODO: this is a confusing name as it's actually the url for the api based on the network...Why do we even need this when we have activeNetwork? + addCustomNetwork: (network: Network) => {}, + removeCustomNetwork: (network: Network) => {}, + networks: {}, + stacksApiSocketClientInfo: null, +}); + +export const AppContextProvider: FC<{ + headerCookies: string | null; + children: ReactNode; +}> = ({ headerCookies, children }) => { + const cookies = headerCookies || (IS_BROWSER ? document?.cookie : ''); + + // Parsing search params + const searchParams = useSearchParams(); + const chain = searchParams?.get('chain'); + const api = searchParams?.get('api'); + const subnet = searchParams?.get('subnet'); + const btcBlockBaseUrl = searchParams?.get('btcBlockBaseUrl'); + const btcTxBaseUrl = searchParams?.get('btcTxBaseUrl'); + const btcAddressBaseUrl = searchParams?.get('btcAddressBaseUrl'); + + // Deriving and setting up network state + const queryNetworkMode = ((Array.isArray(chain) ? chain[0] : chain) || + NetworkModes.Mainnet) as NetworkModes; + const queryApiUrl = removeTrailingSlash(Array.isArray(api) ? api[0] : api); + const querySubnet = Array.isArray(subnet) ? subnet[0] : subnet; + const queryBtcBlockBaseUrl = Array.isArray(btcBlockBaseUrl) + ? btcBlockBaseUrl[0] + : btcBlockBaseUrl; + const queryBtcTxBaseUrl = Array.isArray(btcTxBaseUrl) ? btcTxBaseUrl[0] : btcTxBaseUrl; + const queryBtcAddressBaseUrl = Array.isArray(btcAddressBaseUrl) + ? btcAddressBaseUrl[0] + : btcAddressBaseUrl; + const activeNetworkKey = querySubnet || queryApiUrl || NetworkModeUrlMap[queryNetworkMode]; + + // TODO: is this needed anymore? + if (IS_BROWSER && (window as any)?.location?.search?.includes('err=1')) + throw new Error('test error'); + + const addedCustomNetworks: Record = JSON.parse( + cookie.parse(cookies || '').addedCustomNetworks || '{}' + ); + const removedCustomNetworks: Record = JSON.parse( + cookie.parse(cookies || '').removedCustomNetworks || '{}' + ); + const [_, setAddedCustomNetworksCookie] = useCookies(['addedCustomNetworks']); + const [__, setRemovedCustomNetworksCookie] = useCookies(['removedCustomNetworks']); + + const isUrlPassedSubnet = !!querySubnet; + + const [networks, setNetworks] = useState>( + filterNetworks( + { + [mainnetNetwork.url]: mainnetNetwork, + [testnetNetwork.url]: testnetNetwork, + [nakamotoTestnetNetwork.url]: nakamotoTestnetNetwork, + [oldTestnetNetwork.url]: oldTestnetNetwork, + [devnetNetwork.url]: devnetNetwork, + ...addedCustomNetworks, + ...(isUrlPassedSubnet + ? { + [querySubnet]: { + isSubnet: true, + url: querySubnet, + btcBlockBaseUrl: + queryBtcBlockBaseUrl || NetworkModeBtcBlockBaseUrlMap[NetworkModes.Mainnet], + btcTxBaseUrl: queryBtcTxBaseUrl || NetworkModeBtcTxBaseUrlMap[NetworkModes.Mainnet], + btcAddressBaseUrl: + queryBtcAddressBaseUrl || NetworkModeBtcAddressBaseUrlMap[NetworkModes.Mainnet], + label: 'subnet', + networkId: 1, + mode: NetworkModes.Mainnet, + } as Network, + } + : {}), + }, + removedCustomNetworks + ) + ); + + const addCustomNetwork = useCallback( + (network: Network) => { + if (network.url in addedCustomNetworks) return; + + if (network.url in removedCustomNetworks) { + const scrubbedRemovedCustomNetworks = { ...removedCustomNetworks }; + delete scrubbedRemovedCustomNetworks[network.url]; + setRemovedCustomNetworksCookie( + 'removedCustomNetworks', + JSON.stringify(scrubbedRemovedCustomNetworks), + { + path: '/', + maxAge: ONE_HOUR, + sameSite: true, + } + ); + } + + setAddedCustomNetworksCookie( + 'addedCustomNetworks', + JSON.stringify({ ...addedCustomNetworks, [network.url]: network }), + { + path: '/', + maxAge: ONE_HOUR, + sameSite: true, + } + ); + + setNetworks({ + ...networks, + [network.url]: { ...network, isCustomNetwork: true }, + }); + }, + [ + addedCustomNetworks, + setAddedCustomNetworksCookie, + networks, + setNetworks, + removedCustomNetworks, + setRemovedCustomNetworksCookie, + ] + ); + + const removeCustomNetwork = useCallback( + (network: Network) => { + if (!(network.url in networks)) return; + + if (network.url in addedCustomNetworks) { + const scrubbedAddedCustomNetworks = { ...addedCustomNetworks }; + delete scrubbedAddedCustomNetworks[network.url]; + setAddedCustomNetworksCookie( + 'addedCustomNetworks', + JSON.stringify(scrubbedAddedCustomNetworks), + { + path: '/', + maxAge: ONE_HOUR, + sameSite: true, + } + ); + } + + const { [network.url]: omitted, ...remainingNetworks } = networks; + setRemovedCustomNetworksCookie( + 'removedCustomNetworks', + JSON.stringify({ ...removedCustomNetworks, [network.url]: omitted }), + { + path: '/', + maxAge: ONE_HOUR, + sameSite: true, + } + ); + setNetworks(remainingNetworks); + }, + [ + networks, + setNetworks, + setRemovedCustomNetworksCookie, + removedCustomNetworks, + addedCustomNetworks, + setAddedCustomNetworksCookie, + ] + ); + + // If the API URL from the query is not available, add it to custom networks + useEffect(() => { + const addCustomNetworkFromQuery = async () => { + const networkUrl = buildCustomNetworkUrl(queryApiUrl); + const networkId = await fetchCustomNetworkId(networkUrl, false); + if (networkId) { + const network: Network = { + label: queryApiUrl, + url: networkUrl, + btcBlockBaseUrl: + queryBtcBlockBaseUrl || NetworkModeBtcBlockBaseUrlMap[NetworkModes.Mainnet], + btcTxBaseUrl: queryBtcTxBaseUrl || NetworkModeBtcTxBaseUrlMap[NetworkModes.Mainnet], + btcAddressBaseUrl: + queryBtcAddressBaseUrl || NetworkModeBtcAddressBaseUrlMap[NetworkModes.Mainnet], + networkId, + mode: NetworkIdModeMap[networkId], + isCustomNetwork: true, + isSubnet: false, + }; + addCustomNetwork(network); + } + }; + + if (queryApiUrl && !networks[queryApiUrl]) { + addCustomNetworkFromQuery(); + } + }, [ + queryApiUrl, + networks, + queryBtcBlockBaseUrl, + queryBtcTxBaseUrl, + queryBtcAddressBaseUrl, + addCustomNetwork, + ]); + + const { + client: stacksApiSocketClient, + connect: connectStacksApiSocket, + disconnect: disconnectStacksApiSocket, + } = useStacksApiSocketClient(activeNetworkKey); + + return ( + + {children} + + ); +}; diff --git a/src/common/context/GlobalContext.tsx b/src/common/context/GlobalContext.tsx deleted file mode 100644 index 1a43a1a0a..000000000 --- a/src/common/context/GlobalContext.tsx +++ /dev/null @@ -1,277 +0,0 @@ -'use client'; - -import cookie from 'cookie'; -import { useSearchParams } from 'next/navigation'; -import { FC, ReactNode, createContext, useCallback, useEffect, useMemo, useState } from 'react'; -import { useCookies } from 'react-cookie'; - -import { ChainID } from '@stacks/transactions'; - -import { - StacksApiSocketClientInfo, - useStacksApiSocketClient, -} from '../../app/_components/BlockList/Sockets/use-stacks-api-socket-client'; -import { buildCustomNetworkUrl, fetchCustomNetworkId } from '../components/modals/AddNetwork/utils'; -import { DEFAULT_DEVNET_SERVER, IS_BROWSER } from '../constants/constants'; -import { - NetworkIdModeMap, - NetworkModeBtcAddressBaseUrlMap, - NetworkModeBtcBlockBaseUrlMap, - NetworkModeBtcTxBaseUrlMap, - NetworkModeUrlMap, -} from '../constants/network'; -import { Network, NetworkModes } from '../types/network'; -import { removeTrailingSlash } from '../utils/utils'; - -interface GlobalContextProps { - cookies: string; - apiUrls: Record; - btcBlockBaseUrls: Record; - btcTxBaseUrls: Record; - btcAddressBaseUrls: Record; - activeNetwork: Network; - activeNetworkKey: string; - addCustomNetwork: (network: Network) => Promise; - removeCustomNetwork: (network: Network) => void; - networks: Record; - stacksApiSocketClientInfo: StacksApiSocketClientInfo | null; -} - -export const GlobalContext = createContext({ - cookies: '', - apiUrls: NetworkModeUrlMap, - btcBlockBaseUrls: NetworkModeBtcBlockBaseUrlMap, - btcTxBaseUrls: NetworkModeBtcTxBaseUrlMap, - btcAddressBaseUrls: NetworkModeBtcAddressBaseUrlMap, - activeNetwork: { - label: 'Stacks Mainnet', - url: NetworkModeUrlMap[NetworkModes.Mainnet], - btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Mainnet], - btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Mainnet], - btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Mainnet], - networkId: ChainID.Mainnet, - mode: NetworkModes.Mainnet, - }, - activeNetworkKey: NetworkModeUrlMap[NetworkModes.Mainnet], // TODO: this is a confusing name as it's actually the url for the api based on the network... - addCustomNetwork: () => Promise.resolve(), - removeCustomNetwork: () => true, - networks: {}, - stacksApiSocketClientInfo: null, -}); - -export const AppContextProvider: FC<{ - headerCookies: string | null; - apiUrls: Record; - btcBlockBaseUrls: Record; - btcTxBaseUrls: Record; - btcAddressBaseUrls: Record; - children: ReactNode; -}> = ({ - headerCookies, - apiUrls, - btcBlockBaseUrls, - btcTxBaseUrls, - btcAddressBaseUrls, - children, -}) => { - const cookies = headerCookies || (IS_BROWSER ? document?.cookie : ''); - const searchParams = useSearchParams(); - const chain = searchParams?.get('chain'); - const api = searchParams?.get('api'); - const subnet = searchParams?.get('subnet'); - const btcBlockBaseUrl = searchParams?.get('btcBlockBaseUrl'); - const btcTxBaseUrl = searchParams?.get('btcTxBaseUrl'); - const btcAddressBaseUrl = searchParams?.get('btcAddressBaseUrl'); - - const queryNetworkMode = ((Array.isArray(chain) ? chain[0] : chain) || - NetworkModes.Mainnet) as NetworkModes; - const queryApiUrl = removeTrailingSlash(Array.isArray(api) ? api[0] : api); - const querySubnet = Array.isArray(subnet) ? subnet[0] : subnet; - const queryBtcBlockBaseUrl = Array.isArray(btcBlockBaseUrl) - ? btcBlockBaseUrl[0] - : btcBlockBaseUrl; - const queryBtcTxBaseUrl = Array.isArray(btcTxBaseUrl) ? btcTxBaseUrl[0] : btcTxBaseUrl; - const queryBtcAddressBaseUrl = Array.isArray(btcAddressBaseUrl) - ? btcAddressBaseUrl[0] - : btcAddressBaseUrl; - - if (IS_BROWSER && (window as any)?.location?.search?.includes('err=1')) - throw new Error('test error'); - const customNetworksCookie = JSON.parse(cookie.parse(cookies || '').customNetworks || '{}'); - const [_, setCookie] = useCookies(['customNetworks']); - const [customNetworks, setCustomNetworks] = useState(customNetworksCookie); - const activeNetworkKey = querySubnet || queryApiUrl || apiUrls[queryNetworkMode]; - - const { - client: stacksApiSocketClient, - connect: connectStacksApiSocket, - disconnect: disconnectStacksApiSocket, - } = useStacksApiSocketClient(activeNetworkKey); - - const isUrlPassedSubnet = !!querySubnet && !customNetworks[querySubnet]; - const networks: Record = useMemo>( - () => ({ - [apiUrls[NetworkModes.Mainnet]]: { - label: 'Stacks Mainnet', - url: apiUrls[NetworkModes.Mainnet], - btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Mainnet], - btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Mainnet], - btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Mainnet], - networkId: ChainID.Mainnet, - mode: NetworkModes.Mainnet, - }, - [apiUrls[NetworkModes.Testnet]]: { - label: 'Stacks Testnet (Primary)', - url: apiUrls[NetworkModes.Testnet], - btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Testnet], - btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Testnet], - btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Testnet], - networkId: ChainID.Testnet, - mode: NetworkModes.Testnet, - }, - 'https://api.nakamoto.testnet.hiro.so': { - label: 'Nakamoto Testnet', - url: 'https://api.nakamoto.testnet.hiro.so', - btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Testnet], - btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Testnet], - btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Testnet], - networkId: ChainID.Testnet, - mode: NetworkModes.Testnet, - isCustomNetwork: true, - }, - 'https://api.old.testnet.hiro.so': { - label: 'Stacks Testnet (Archive)', - url: 'https://api.old.testnet.hiro.so', - btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Testnet], - btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Testnet], - btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Testnet], - networkId: ChainID.Testnet, - mode: NetworkModes.Testnet, - isCustomNetwork: true, - }, - [DEFAULT_DEVNET_SERVER]: { - label: 'Devnet', - url: DEFAULT_DEVNET_SERVER, - btcBlockBaseUrl: NetworkModeBtcBlockBaseUrlMap[NetworkModes.Testnet], - btcTxBaseUrl: NetworkModeBtcTxBaseUrlMap[NetworkModes.Testnet], - btcAddressBaseUrl: NetworkModeBtcAddressBaseUrlMap[NetworkModes.Testnet], - networkId: ChainID.Testnet, - mode: NetworkModes.Testnet, - isCustomNetwork: true, - }, - ...customNetworks, - ...(isUrlPassedSubnet - ? { - [querySubnet]: { - isSubnet: true, - url: querySubnet, - btcBlockBaseUrl: - queryBtcBlockBaseUrl || NetworkModeBtcBlockBaseUrlMap[NetworkModes.Mainnet], - btcTxBaseUrl: queryBtcTxBaseUrl || NetworkModeBtcTxBaseUrlMap[NetworkModes.Mainnet], - btcAddressBaseUrl: - queryBtcAddressBaseUrl || NetworkModeBtcAddressBaseUrlMap[NetworkModes.Mainnet], - label: 'subnet', - networkId: 0, - mode: NetworkModes.Mainnet, - }, - } - : {}), - }), - [ - apiUrls, - customNetworks, - isUrlPassedSubnet, - queryBtcBlockBaseUrl, - queryBtcTxBaseUrl, - queryBtcAddressBaseUrl, - querySubnet, - ] - ); - - const addCustomNetwork = useCallback( - (network: Network) => { - return new Promise(resolve => { - setCookie('customNetworks', JSON.stringify({ ...customNetworks, [network.url]: network }), { - path: '/', - maxAge: 3600, // Expires after 1hr - sameSite: true, - }); - setTimeout(() => { - setCustomNetworks({ - ...customNetworks, - [network.url]: { ...network, isCustomNetwork: true }, - }); - resolve(true); - }, 100); - }); - }, - [customNetworks, setCookie] - ); - - // If the API URL from the query is not available, add it to custom networks - useEffect(() => { - const addCustomNetworkFromQuery = async () => { - const networkUrl = buildCustomNetworkUrl(queryApiUrl); - const networkId = await fetchCustomNetworkId(networkUrl, false); - if (networkId) { - const network: Network = { - label: queryApiUrl, - url: networkUrl, - btcBlockBaseUrl: - queryBtcBlockBaseUrl || NetworkModeBtcBlockBaseUrlMap[NetworkModes.Mainnet], - btcTxBaseUrl: queryBtcTxBaseUrl || NetworkModeBtcTxBaseUrlMap[NetworkModes.Mainnet], - btcAddressBaseUrl: - queryBtcAddressBaseUrl || NetworkModeBtcAddressBaseUrlMap[NetworkModes.Mainnet], - networkId, - mode: NetworkIdModeMap[networkId], - isCustomNetwork: true, - isSubnet: false, - }; - void addCustomNetwork(network); - } - }; - - if (queryApiUrl && !networks[queryApiUrl]) { - void addCustomNetworkFromQuery(); - } - }, [ - queryApiUrl, - networks, - addCustomNetwork, - queryBtcBlockBaseUrl, - queryBtcTxBaseUrl, - queryBtcAddressBaseUrl, - ]); - - return ( - { - const { [network.url]: omitted, ...remainingCustomNetworks } = customNetworks; - setCookie('customNetworks', JSON.stringify(remainingCustomNetworks), { - path: '/', - maxAge: 3600, // Expires after 1hr - sameSite: true, - }); - setCustomNetworks(remainingCustomNetworks); - }, - networks, - stacksApiSocketClientInfo: { - client: stacksApiSocketClient, - connect: connectStacksApiSocket, - disconnect: disconnectStacksApiSocket, - }, - }} - > - {children} - - ); -}; diff --git a/src/common/context/__tests__/GlobalContext.test.tsx b/src/common/context/__tests__/GlobalContext.test.tsx index 245aa52f9..0b7f562b4 100644 --- a/src/common/context/__tests__/GlobalContext.test.tsx +++ b/src/common/context/__tests__/GlobalContext.test.tsx @@ -8,7 +8,7 @@ import { NetworkModeBtcBlockBaseUrlMap, NetworkModeBtcTxBaseUrlMap, } from '../../constants/network'; -import { AppContextProvider, GlobalContext } from '../GlobalContext'; +import { AppContext, AppContextProvider } from '../AppContextProvider'; const useSearchParams = useSearchParamsActual as jest.MockedFunction; @@ -33,7 +33,7 @@ jest.mock('../../components/modals/AddNetwork/utils', () => ({ const customApiUrl = 'https://my-custom-api-url.com/something'; const GlobalContextTestComponent = () => { - const { activeNetwork, networks } = useContext(GlobalContext); + const { activeNetwork, networks } = useContext(AppContext); return (
Global Context Test
diff --git a/src/common/context/useAppContext.ts b/src/common/context/useAppContext.ts index faa343af6..45958cf74 100644 --- a/src/common/context/useAppContext.ts +++ b/src/common/context/useAppContext.ts @@ -2,8 +2,8 @@ import { useContext } from 'react'; -import { GlobalContext } from './GlobalContext'; +import { AppContext } from './AppContextProvider'; export const useGlobalContext = () => { - return useContext(GlobalContext); + return useContext(AppContext); }; diff --git a/src/common/queries/query-stale-time.ts b/src/common/queries/query-stale-time.ts index 56bba6fe3..65604f58c 100644 --- a/src/common/queries/query-stale-time.ts +++ b/src/common/queries/query-stale-time.ts @@ -4,3 +4,4 @@ export const TWO_MINUTES = 2 * ONE_MINUTE; export const THREE_MINUTES = 3 * ONE_MINUTE; export const FIVE_MINUTES = 5 * ONE_MINUTE; export const TEN_MINUTES = 10 * ONE_MINUTE; +export const ONE_HOUR = 60 * ONE_MINUTE; diff --git a/src/common/types/network.ts b/src/common/types/network.ts index bfb5ddab5..b0c5a0477 100644 --- a/src/common/types/network.ts +++ b/src/common/types/network.ts @@ -19,3 +19,4 @@ export interface Network { isCustomNetwork?: boolean; isSubnet?: boolean; } + diff --git a/src/features/txs-list/ListItem/TxListItem.tsx b/src/features/txs-list/ListItem/TxListItem.tsx index 421789141..3a0713ba1 100644 --- a/src/features/txs-list/ListItem/TxListItem.tsx +++ b/src/features/txs-list/ListItem/TxListItem.tsx @@ -28,7 +28,7 @@ const Icon: FC<{ tx: Transaction }> = memo(({ tx }) => ( )); const LeftTitle: FC<{ tx: Transaction }> = memo(({ tx }) => ( - + )); @@ -59,6 +59,8 @@ const RightTitle: FC<{ tx: Transaction }> = memo(({ tx }) => { divider={∙} flexWrap="wrap" gap={1.5} + alignItems="center" + height={6} > {truncateMiddle(tx.tx_id)} diff --git a/src/features/txs-list/TxTitle.tsx b/src/features/txs-list/TxTitle.tsx index cf8d210cc..7900840bc 100644 --- a/src/features/txs-list/TxTitle.tsx +++ b/src/features/txs-list/TxTitle.tsx @@ -41,7 +41,7 @@ export const TxTitle = ({ ); case 'token_transfer': return ( - + {microToStacksFormatted(tx.token_transfer.amount)} STX