diff --git a/src/components/Elements/Balance/index.module.scss b/src/components/Elements/Balance/index.module.scss new file mode 100644 index 00000000..492ca772 --- /dev/null +++ b/src/components/Elements/Balance/index.module.scss @@ -0,0 +1,22 @@ +.container { + display: flex; + gap: 1rem; +} + +.balanceItem { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.balanceWrapper { + display: flex; + align-items: center; + justify-content: center; + line-height: 1.5; + height: fit-content; + padding: 0.5rem 1.25rem; + border-radius: 2rem; + min-width: 8rem; + font-weight: 500; +} diff --git a/src/components/Elements/Balance/index.tsx b/src/components/Elements/Balance/index.tsx index 49b80f03..076173e3 100644 --- a/src/components/Elements/Balance/index.tsx +++ b/src/components/Elements/Balance/index.tsx @@ -1,7 +1,11 @@ -import { Typography, useTheme } from '@mui/material'; +import { Box, Typography, useTheme } from '@mui/material'; import { formatBalance } from '@/utils/functions'; +import { useAccounts } from '@/contexts/account'; + +import styles from './index.module.scss'; + interface BalanceProps { coretimeBalance: number; relayBalance: number; @@ -10,16 +14,42 @@ interface BalanceProps { const Balance = ({ relayBalance, coretimeBalance, symbol }: BalanceProps) => { const theme = useTheme(); + const { + state: { activeAccount }, + } = useAccounts(); + + const items = [ + { + label: 'Relay Chain', + value: relayBalance, + }, + { + label: 'Coretime Chain', + value: coretimeBalance, + }, + ]; - return ( -
- - {`Relay chain: ${formatBalance(relayBalance.toString(), false)} ${symbol}`} - - - {`Coretime chain: ${formatBalance(coretimeBalance.toString(), false)} ${symbol}`} - -
+ return activeAccount ? ( + + {items.map(({ label, value }, index) => ( + + + {label} + + + {`${formatBalance(value.toString(), false)} ${symbol}`} + + + ))} + + ) : ( + <> ); }; diff --git a/src/components/Elements/Selectors/AssetSelector/index.module.scss b/src/components/Elements/Selectors/AssetSelector/index.module.scss index 521136b4..c994ef44 100644 --- a/src/components/Elements/Selectors/AssetSelector/index.module.scss +++ b/src/components/Elements/Selectors/AssetSelector/index.module.scss @@ -3,7 +3,24 @@ justify-content: center; } +.option, +.activeOption { + min-width: 15rem; + margin: 1rem 5rem; + border-radius: 0.5rem !important; + text-transform: capitalize; +} + +.activeOption { + border: 1px solid #e84d68; + background: linear-gradient(180deg, #e84d68 0%, #ad2b49 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + .option { - min-width: 250px; - margin: 1em 5em; + border: 1px solid #cccccc !important; + background-color: white; + color: black; } diff --git a/src/components/Elements/Selectors/AssetSelector/index.tsx b/src/components/Elements/Selectors/AssetSelector/index.tsx index aeabfadd..640583c9 100644 --- a/src/components/Elements/Selectors/AssetSelector/index.tsx +++ b/src/components/Elements/Selectors/AssetSelector/index.tsx @@ -1,4 +1,4 @@ -import { ToggleButton, ToggleButtonGroup, useTheme } from '@mui/material'; +import { ToggleButton, ToggleButtonGroup } from '@mui/material'; import FormControl from '@mui/material/FormControl'; import { AssetType } from '@/models'; @@ -16,7 +16,6 @@ export default function AssetSelector({ setAsset, symbol, }: AssetSelectorProps) { - const theme = useTheme(); const items = [ { value: AssetType.TOKEN, @@ -38,11 +37,7 @@ export default function AssetSelector({ > {items.map(({ label, value }, index) => ( diff --git a/src/components/Elements/Selectors/ChainSelector/index.module.scss b/src/components/Elements/Selectors/ChainSelector/index.module.scss new file mode 100644 index 00000000..ca1a9ba4 --- /dev/null +++ b/src/components/Elements/Selectors/ChainSelector/index.module.scss @@ -0,0 +1,16 @@ +.chainItem { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.icon { + width: 1.5rem; + height: 1.5rem; + border-radius: 100%; +} + +.label { + font-size: 1rem; + line-height: 1.5; +} diff --git a/src/components/Elements/Selectors/ChainSelector/index.tsx b/src/components/Elements/Selectors/ChainSelector/index.tsx index ced01401..59bcd5d3 100644 --- a/src/components/Elements/Selectors/ChainSelector/index.tsx +++ b/src/components/Elements/Selectors/ChainSelector/index.tsx @@ -12,6 +12,8 @@ import Image from 'next/image'; import { useCoretimeApi, useRelayApi } from '@/contexts/apis'; import { ChainType, NetworkType } from '@/models'; +import styles from './index.module.scss'; + interface ChainSelectorProps { chain: ChainType; setChain: (_: ChainType) => void; @@ -80,18 +82,12 @@ export const ChainSelector = ({ chain, setChain }: ChainSelectorProps) => { > {menuItems.map(({ icon, label, value, loading }, index) => ( - - icon + + icon {loading ? ( ) : ( - - {label} - + {label} )} diff --git a/src/components/Elements/Selectors/index.ts b/src/components/Elements/Selectors/index.ts index 4b666636..3246a937 100644 --- a/src/components/Elements/Selectors/index.ts +++ b/src/components/Elements/Selectors/index.ts @@ -1,3 +1,4 @@ +export * from './AssetSelector'; export * from './ChainSelector'; export * from './RecipientSelector'; export * from './RegionSelector'; diff --git a/src/contexts/balance/index.tsx b/src/contexts/balance/index.tsx new file mode 100644 index 00000000..2df705d7 --- /dev/null +++ b/src/contexts/balance/index.tsx @@ -0,0 +1,123 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +import { useAccounts } from '../account'; +import { useCoretimeApi, useRelayApi } from '../apis'; +import { ApiState } from '../apis/types'; +import { useToast } from '../toast'; + +interface Props { + children: React.ReactNode; +} + +interface BalanceData { + balance: { + coretime: number; + relay: number; + }; +} + +const defaultBalanceData: BalanceData = { + balance: { + coretime: 0, + relay: 0, + }, +}; + +const BalanceContext = createContext(defaultBalanceData); + +const BalanceProvider = ({ children }: Props) => { + const { + state: { activeAccount }, + } = useAccounts(); + const { toastWarning } = useToast(); + const { + state: { + api: coretimeApi, + apiState: coretimeApiState, + symbol: coretimeSymbol, + name: coretimeChain, + }, + } = useCoretimeApi(); + const { + state: { + api: relayApi, + apiState: relayApiState, + symbol: relaySymbol, + name: relayChain, + }, + } = useRelayApi(); + + const [coretimeBalance, setCoretimeBalance] = useState(0); + const [relayBalance, setRelayBalance] = useState(0); + + useEffect(() => { + const subscribeBalances = async () => { + if (!activeAccount) { + setCoretimeBalance(0); + setRelayBalance(0); + return; + } + if ( + coretimeApiState !== ApiState.READY || + relayApiState !== ApiState.READY + ) + return; + + if (!coretimeApi || !relayApi) return; + + const { address } = activeAccount; + + const unsubscribeCoretime = await coretimeApi.queryMulti( + [[coretimeApi.query.system.account, address]], + ([ + { + data: { free }, + }, + ]: [any]) => { + setCoretimeBalance(free as number); + free === 0 && + toastWarning( + `The selected account does not have any ${coretimeSymbol} tokens on ${coretimeChain}.` + ); + } + ); + + const unsubscribeRelay = await relayApi.queryMulti( + [[relayApi.query.system.account, address]], + ([ + { + data: { free }, + }, + ]: [any]) => { + setRelayBalance(free as number); + free === 0 && + toastWarning( + `The selected account does not have any ${relaySymbol} tokens on ${relayChain}.` + ); + } + ); + return () => { + unsubscribeCoretime(); + unsubscribeRelay(); + }; + }; + subscribeBalances(); + }, [activeAccount, coretimeApi, coretimeApiState, relayApi, relayApiState]); + + return ( + + {children} + + ); +}; + +const useBalances = () => useContext(BalanceContext); + +export { BalanceProvider, useBalances }; diff --git a/src/hooks/balance.tsx b/src/hooks/balance.tsx deleted file mode 100644 index 166c0daf..00000000 --- a/src/hooks/balance.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { ApiPromise } from '@polkadot/api'; -import { useCallback, useEffect, useState } from 'react'; - -import { parseHNString } from '@/utils/functions'; - -import { useAccounts } from '@/contexts/account'; -import { useCoretimeApi, useRelayApi } from '@/contexts/apis'; -import { ApiState } from '@/contexts/apis/types'; -import { useToast } from '@/contexts/toast'; - -// React hook for fetching balance. -const useBalance = () => { - const { - state: { api: coretimeApi, apiState: coretimeApiState, symbol }, - } = useCoretimeApi(); - const { - state: { api: relayApi, apiState: relayApiState }, - } = useRelayApi(); - - const { - state: { activeAccount }, - } = useAccounts(); - - const [coretimeBalance, setCoretimeBalance] = useState(0); - const [relayBalance, setRelayBalance] = useState(0); - - const { toastWarning } = useToast(); - - const fetchBalance = useCallback( - async (api: ApiPromise): Promise => { - if (!activeAccount) { - setCoretimeBalance(0); - setRelayBalance(0); - return; - } - - const accountData: any = ( - await api.query.system.account(activeAccount.address) - ).toHuman(); - const balance = parseHNString(accountData.data.free.toString()); - - return balance; - }, - [activeAccount] - ); - - const fetchBalances = useCallback(() => { - if (coretimeApi && coretimeApiState == ApiState.READY) { - fetchBalance(coretimeApi).then((balance) => { - balance !== undefined && setCoretimeBalance(balance); - balance === 0 && - toastWarning( - `The selected account does not have any ${symbol} tokens on the Coretime chain.` - ); - }); - } - if (relayApi && relayApiState == ApiState.READY) { - fetchBalance(relayApi).then((balance) => { - balance !== undefined && setRelayBalance(balance); - }); - } - }, [ - fetchBalance, - toastWarning, - symbol, - coretimeApi, - coretimeApiState, - relayApi, - relayApiState, - ]); - - useEffect(() => { - fetchBalances(); - }, [fetchBalances]); - - return { coretimeBalance, relayBalance, fetchBalances }; -}; - -export default useBalance; diff --git a/src/hooks/salePhase.tsx b/src/hooks/salePhase.tsx index 75eaf9e7..59eac2e0 100644 --- a/src/hooks/salePhase.tsx +++ b/src/hooks/salePhase.tsx @@ -29,6 +29,7 @@ const useSalePhase = () => { const { saleInfo, config } = useSaleInfo(); const [currentPhase, setCurrentPhase] = useState(null); + const [loading, setLoading] = useState(false); const [saleEndTimestamp, setSaleEndTimestamp] = useState(0); const [saleStartTimestamp, setSaleStartTimestamp] = useState(0); @@ -96,7 +97,12 @@ const useSalePhase = () => { useEffect(() => { if (!api || apiState !== ApiState.READY) return; - fetchCurrentPhase(api); + const asyncFetchCurrentPhase = async () => { + setLoading(true); + await fetchCurrentPhase(api); + setLoading(false); + }; + asyncFetchCurrentPhase(); }, [fetchCurrentPhase, api, apiState]); return { @@ -105,6 +111,7 @@ const useSalePhase = () => { saleEndTimestamp, progress, saleSections, + loading, }; }; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index e038f765..97a42b01 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -17,6 +17,7 @@ import { CoretimeApiContextProvider, RelayApiContextProvider, } from '@/contexts/apis'; +import { BalanceProvider } from '@/contexts/balance'; import { ContextDataProvider } from '@/contexts/common'; import { MarketProvider } from '@/contexts/market'; import { NetworkProvider } from '@/contexts/network'; @@ -52,17 +53,19 @@ export default function MyApp(props: MyAppProps) { - - - - - - {getLayout()} - - - - - + + + + + + + {getLayout()} + + + + + + diff --git a/src/pages/purchase.tsx b/src/pages/purchase.tsx index 76ac42ed..5e22656d 100644 --- a/src/pages/purchase.tsx +++ b/src/pages/purchase.tsx @@ -1,10 +1,16 @@ -import { Box, Button, Typography, useTheme } from '@mui/material'; +import { + Backdrop, + Box, + Button, + CircularProgress, + Typography, + useTheme, +} from '@mui/material'; import TimeAgo from 'javascript-time-ago'; import en from 'javascript-time-ago/locale/en.json'; import Link from 'next/link'; import { useState } from 'react'; -import useBalance from '@/hooks/balance'; import useSalePhase from '@/hooks/salePhase'; import useSalePrice from '@/hooks/salePrice'; import { sendTx } from '@/utils/functions'; @@ -15,6 +21,7 @@ import Balance from '@/components/Elements/Balance'; import { useAccounts } from '@/contexts/account'; import { useCoretimeApi } from '@/contexts/apis'; import { ApiState } from '@/contexts/apis/types'; +import { useBalances } from '@/contexts/balance'; import { useRegions } from '@/contexts/regions'; import { useSaleInfo } from '@/contexts/sales'; import { useToast } from '@/contexts/toast'; @@ -38,10 +45,15 @@ const Purchase = () => { const { fetchRegions } = useRegions(); - const { coretimeBalance, relayBalance } = useBalance(); + const { balance } = useBalances(); const currentPrice = useSalePrice(); - const { currentPhase, progress, saleStartTimestamp, saleEndTimestamp } = - useSalePhase(); + const { + currentPhase, + progress, + saleStartTimestamp, + saleEndTimestamp, + loading: loadingSalePhase, + } = useSalePhase(); const purchase = async () => { if (!api || apiState !== ApiState.READY || !activeAccount || !activeSigner) @@ -87,17 +99,20 @@ const Purchase = () => { - {loading || - !currentPhase || - !progress || - !saleStartTimestamp || - !saleEndTimestamp ? ( + {loading || loadingSalePhase ? ( + + + + ) : !currentPhase || + !progress || + !saleStartTimestamp || + !saleEndTimestamp ? ( <> Check your network conection and connect your wallet @@ -119,7 +134,8 @@ const Purchase = () => { diff --git a/src/pages/regions.tsx b/src/pages/regions.tsx index 5446d679..f93bf446 100644 --- a/src/pages/regions.tsx +++ b/src/pages/regions.tsx @@ -22,6 +22,7 @@ import { import { SellModal } from '@/components/Modals/Sell'; import { UnlistModal } from '@/components/Modals/Unlist'; +import { useAccounts } from '@/contexts/account'; import { useRegions } from '@/contexts/regions'; import { useToast } from '@/contexts/toast'; import { @@ -34,6 +35,9 @@ import { RegionLocation } from '@/models'; const Dashboard = () => { const theme = useTheme(); + const { + state: { activeAccount }, + } = useAccounts(); const { regions, loading, updateRegionName } = useRegions(); const [currentRegionIndex, setCurrentRegionIndex] = useState(); @@ -155,13 +159,13 @@ const Dashboard = () => { )} - {regions.length === 0 ? ( - <> - - No regions owned. Go to bulk sales{' '} - to make a purchase - - + {!activeAccount ? ( + Please connect your wallet. + ) : regions.length === 0 ? ( + + No regions owned. Go to bulk sales{' '} + to make a purchase + ) : ( <> {regions.map((region, index) => ( diff --git a/src/pages/transfer.tsx b/src/pages/transfer.tsx index 6493f294..130e0c83 100644 --- a/src/pages/transfer.tsx +++ b/src/pages/transfer.tsx @@ -1,5 +1,4 @@ import ArrowDownward from '@mui/icons-material/ArrowDownwardOutlined'; -import { LoadingButton } from '@mui/lab'; import { Box, Button, @@ -12,7 +11,6 @@ import { Keyring } from '@polkadot/api'; import { Region } from 'coretime-utils'; import { useEffect, useState } from 'react'; -import useBalance from '@/hooks/balance'; import { transferTokensFromCoretimeToRelay, transferTokensFromRelayToCoretime, @@ -26,6 +24,7 @@ import { import { AmountInput, ChainSelector, + ProgressButton, RecipientSelector, RegionCard, RegionSelector, @@ -36,6 +35,7 @@ import AssetSelector from '@/components/Elements/Selectors/AssetSelector'; import { useAccounts } from '@/contexts/account'; import { useCoretimeApi, useRelayApi } from '@/contexts/apis'; import { ApiState } from '@/contexts/apis/types'; +import { useBalances } from '@/contexts/balance'; import { useRegions } from '@/contexts/regions'; import { useToast } from '@/contexts/toast'; import { @@ -77,14 +77,13 @@ const TransferPage = () => { const [asset, setAsset] = useState(AssetType.TOKEN); const [transferAmount, setTransferAmount] = useState(''); - const { coretimeBalance, relayBalance, fetchBalances } = useBalance(); + const { balance } = useBalances(); const defaultHandler = { ready: () => toastInfo('Transaction was initiated.'), inBlock: () => toastInfo(`In Block`), finalized: () => setWorking(false), success: () => { - fetchBalances(); toastSuccess('Successfully transferred.'); }, error: () => { @@ -229,8 +228,8 @@ const TransferPage = () => { { }} > - Origin chain: + + Origin chain: + - Destination chain: + + Destination chain: + { - Transfer to: + + Transfer to: + {asset === AssetType.TOKEN && @@ -310,15 +321,22 @@ const TransferPage = () => { - + - - Transfer - + />