diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2e98b3f --- /dev/null +++ b/.env.example @@ -0,0 +1,54 @@ +# LOCAL +VITE_APP_LOCAL_CAPTCHA_SITE_KEY='' +VITE_APP_LOCAL_FAUCET_ADDRESS='' +VITE_APP_LOCAL_BRIDGE_URL='' +VITE_APP_LOCAL_GRAPHQL_URL='' +VITE_APP_LOCAL_GRAPHQL_WS='' +VITE_APP_LOCAL_RPC='' +VITE_APP_LOCAL_API='' +VITE_APP_LOCAL_EXPLORER_URL='' +VITE_APP_LOCAL_STAKING_URL='' +VITE_APP_LOCAL_CHAIN_NAME='' +VITE_APP_LOCAL_CHAIN_ID='' + +# PRIVATE +VITE_APP_PRIVATE_CAPTCHA_SITE_KEY='' +VITE_APP_PRIVATE_FAUCET_ADDRESS='' +VITE_APP_PRIVATE_BRIDGE_URL='' +VITE_APP_PRIVATE_GRAPHQL_URL='' +VITE_APP_PRIVATE_GRAPHQL_WS='' +VITE_APP_PRIVATE_RPC='' +VITE_APP_PRIVATE_API='' +VITE_APP_PRIVATE_EXPLORER_URL='' +VITE_APP_PRIVATE_STAKING_URL='' +VITE_APP_PRIVATE_CHAIN_NAME='' +VITE_APP_PRIVATE_CHAIN_ID='' + +# PUBLIC +VITE_APP_PUBLIC_CAPTCHA_SITE_KEY='' +VITE_APP_PUBLIC_FAUCET_ADDRESS='' +VITE_APP_PUBLIC_BRIDGE_URL='' +VITE_APP_PUBLIC_GRAPHQL_UR='' +VITE_APP_PUBLIC_GRAPHQL_WS='' +VITE_APP_PUBLIC_RPC='' +VITE_APP_PUBLIC_API='' +VITE_APP_PUBLIC_EXPLORER_URL='' +VITE_APP_PUBLIC_STAKING_URL='' +VITE_APP_PUBLIC_CHAIN_NAME='' +VITE_APP_PUBLIC_CHAIN_ID='' + +# MAINNET +VITE_APP_MAINNET_BRIDGE_URL='' +VITE_APP_MAINNET_GRAPHQL_URL='' +VITE_APP_MAINNET_GRAPHQL_WS='' +VITE_APP_MAINNET_RPC='' +VITE_APP_MAINNET_API='' +VITE_APP_MAINNET_EXPLORER_URL='' +VITE_APP_MAINNET_STAKING_URL='' +VITE_APP_MAINNET_CHAIN_NAME='' +VITE_APP_MAINNET_CHAIN_ID='' + +# GENERAL SETTINGS +VITE_NODE_ENV='' #Production or empty +VITE_APP_DEFAULT_NETWORK='' # LOCAL | PRIVATE | PUBLIC | MAINNET +VITE_APP_GAS_PRICE=5000000000000 diff --git a/codegen.yaml b/codegen.yaml index 663c3fa..8b441b6 100644 --- a/codegen.yaml +++ b/codegen.yaml @@ -11,10 +11,6 @@ generates: - 'typescript' - 'typescript-operations' - 'typescript-react-apollo' # To generate custom hooks per query - ./src/graphql/desmos_profile.ts: - schema: https://gql.mainnet.desmos.network/v1/graphql - documents: - - 'src/graphql/desmos_profile_graphql.ts' plugins: - 'typescript' - 'typescript-operations' diff --git a/index.html b/index.html index 71e197b..6c4f905 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,8 @@ - CUDOS Dashboard + CUDOS Dashboard | Stake your CUDOS & vote on proposals + diff --git a/package.json b/package.json index 8a20c43..627948d 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@apollo/client": "^3.5.10", - "@cosmostation/cosmos-client": "^0.0.1", - "@cosmostation/extension-client": "0.1.7", + "@cosmostation/cosmos-client": "^0.0.4", + "@cosmostation/extension-client": "0.1.11", "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", "@fontsource/poppins": "^4.5.5", @@ -25,8 +25,10 @@ "copy-to-clipboard": "^3.3.1", "cosmjs-types": "^0.4.1", "cudosjs": "^1.1.1", + "detect-browser": "^5.3.0", "dompurify": "^2.3.8", "graphql": "^16.3.0", + "graphql-tag": "^2.0.0", "graphql-ws": "^5.6.4", "jdenticon": "^3.1.1", "lodash": "^4.17.21", @@ -37,31 +39,35 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-google-recaptcha": "^2.1.0", + "react-markdown": "^8.0.3", "react-redux": "^7.2.6", + "react-responsive": "^9.0.0", "react-router-dom": "^6.2.2", "redux": "^4.1.2", "redux-persist": "^6.0.0", + "remark-gfm": "^3.0.1", "uint8-to-base64": "^0.2.0", "vite": "^2.9.8" }, "devDependencies": { + "@babel/core": "^7.0.0", "@graphql-codegen/cli": "^2.6.2", - "@graphql-codegen/typescript": "^2.4.8", - "@graphql-codegen/typescript-operations": "^2.3.5", - "@graphql-codegen/typescript-react-apollo": "^3.2.11", + "@graphql-codegen/typescript": "^2.8.1", + "@graphql-codegen/typescript-operations": "^2.5.6", + "@graphql-codegen/typescript-react-apollo": "^3.3.6", "@honkhonk/vite-plugin-svgr": "^1.1.0", - "@rollup/plugin-alias": "^3.1.9", + "@rollup/plugin-alias": "^4.0.2", "@types/big.js": "^6.1.3", "@types/dompurify": "^2.3.3", "@types/lodash": "^4.14.182", "@types/long": "^4.0.2", - "@types/node": "^17.0.23", + "@types/node": "^18.11.9", "@types/numeral": "^2.0.2", "@types/ramda": "^0.28.7", "@types/react": "^17.0.33", "@types/react-dom": "^17.0.10", - "@typescript-eslint/eslint-plugin": "^5.17.0", - "@typescript-eslint/parser": "^5.17.0", + "@typescript-eslint/eslint-plugin": "^4.0.1", + "@typescript-eslint/parser": "^4.0.0", "@vitejs/plugin-react": "^1.0.7", "eslint": "^7.32.0", "eslint-config-airbnb": "^19.0.2", @@ -72,6 +78,7 @@ "eslint-plugin-import": "^2.25.3", "eslint-plugin-jest": "^25.3.0", "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-promise": "^5.1.0", "eslint-plugin-react": "^7.27.1", diff --git a/src/App.tsx b/src/App.tsx index 1fafdfc..43bad6a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,24 +1,16 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useState } from 'react' import { ThemeProvider } from '@mui/material/styles' import { useDispatch, useSelector } from 'react-redux' -import { CssBaseline } from '@mui/material' -import { ApolloProvider } from '@apollo/client' +import { Box, CssBaseline } from '@mui/material' +import { ApolloClient, ApolloProvider, NormalizedCacheObject } from '@apollo/client' import { Routes, Route, useLocation, Navigate } from 'react-router-dom' -import { fetchRedelegations } from 'api/getAccountRedelegations' -import { fetchUndedelegations } from 'api/getAccountUndelegations' -import BigNumber from 'bignumber.js' import { updateUser } from 'store/profile' import { updateUserTransactions } from 'store/userTransactions' -import { fetchRewards } from 'api/getRewards' import NotificationPopup from 'components/NotificationPopup' -import { fetchDelegations } from 'api/getAccountDelegations' import CosmosNetworkConfig from 'ledgers/CosmosNetworkConfig' -import { connectKeplrLedger } from 'ledgers/KeplrLedger' -import { connectCosmostationLedger } from 'ledgers/CosmoStationLedger' import { switchLedgerType } from 'ledgers/utils' -import { getUnbondingBalance } from 'api/getUnbondingBalance' -import { getStakedBalance, getWalletBalance } from './utils/projectUtils' +import { connectUser } from './utils/projectUtils' import { useApollo } from './graphql/client' import Layout from './components/Layout' import RequireLedger from './components/RequireLedger/RequireLedger' @@ -33,19 +25,31 @@ import theme from './theme' import { RootState } from './store' import '@fontsource/poppins' +import { ApolloLinks, defaultApolloLinks } from 'graphql/helpers' +import { CHAIN_DETAILS } from 'utils/constants' +import NetworkChangingLoading from 'components/NetworkChangeLoading' +import { networkLoadingStyles } from 'components/NetworkChangeLoading/styles' const App = () => { const location = useLocation() - const themeColor = useSelector((state: RootState) => state.settings.theme) - const apolloClient = useApollo(null) - const { lastLoggedAddress } = useSelector((state: RootState) => state.profile) + const newApolloClient = useApollo(null) + const [currentApolloClient, setCurrentApolloClient] = useState>( + newApolloClient(defaultApolloLinks) + ) + const { + lastLoggedAddress, + chosenNetwork: currentNetwork, + connectedLedger, + loadingState + } = useSelector((state: RootState) => state.profile) const dispatch = useDispatch() - const connectAccount = useCallback(async (ledgerType: string) => { + const connectAccount = useCallback(async (chosenNetwork: string, ledgerType: string) => { try { - const { address, accountName } = await switchLedgerType(ledgerType) + + const { address } = await switchLedgerType(chosenNetwork!, ledgerType) if (address !== lastLoggedAddress || lastLoggedAddress === '') { dispatch( updateUserTransactions({ @@ -56,36 +60,10 @@ const App = () => { }) ) } - const balance = await getWalletBalance(address!) - - const stakedAmountBalance = await getStakedBalance(address!) - - const { totalRewards, validatorArray } = await fetchRewards(address!) - - const { delegationsArray } = await fetchDelegations(address) - - const { redelegationsArray } = await fetchRedelegations(address) - const { undelegationsArray } = await fetchUndedelegations(address) + const connectedUser = await connectUser(chosenNetwork, ledgerType) + dispatch(updateUser(connectedUser)) - const { unbondingBalance } = await getUnbondingBalance(address) - - dispatch( - updateUser({ - address, - lastLoggedAddress: address, - connectedLedger: ledgerType, - accountName, - balance: new BigNumber(balance), - availableRewards: new BigNumber(totalRewards), - stakedValidators: validatorArray, - stakedBalance: new BigNumber(stakedAmountBalance), - unbondingBalance: new BigNumber(unbondingBalance), - delegations: delegationsArray, - redelegations: redelegationsArray, - undelegations: undelegationsArray - }) - ) } catch (e) { throw new Error('Failed to connect!') } @@ -103,28 +81,47 @@ const App = () => { }) ) - await connectAccount(CosmosNetworkConfig.KEPLR_LEDGER) + await connectAccount(currentNetwork, CosmosNetworkConfig.KEPLR_LEDGER) }) if (window.cosmostation) { window.cosmostation.cosmos.on('accountChanged', async () => { - await connectAccount(CosmosNetworkConfig.COSMOSTATION_LEDGER) + await connectAccount(currentNetwork, CosmosNetworkConfig.COSMOSTATION_LEDGER) }) } return () => { window.removeEventListener('keplr_keystorechange', async () => { - await connectAccount(CosmosNetworkConfig.KEPLR_LEDGER) + await connectAccount(currentNetwork, CosmosNetworkConfig.KEPLR_LEDGER) }) window.removeEventListener('accountChanged', async () => { - await connectAccount(CosmosNetworkConfig.COSMOSTATION_LEDGER) + await connectAccount(currentNetwork, CosmosNetworkConfig.COSMOSTATION_LEDGER) }) } }, []) + + useEffect(() => { + dispatch(updateUser({ loadingState: true }) + ) + const newApolloLinks: ApolloLinks = { + uri: CHAIN_DETAILS.GRAPHQL_URL[currentNetwork! as keyof typeof CHAIN_DETAILS.GRAPHQL_URL], + url: CHAIN_DETAILS.GRAPHQL_WS[currentNetwork! as keyof typeof CHAIN_DETAILS.GRAPHQL_WS] + } + + setCurrentApolloClient(newApolloClient(newApolloLinks)) + + if (connectedLedger) { + connectAccount(currentNetwork, connectedLedger) + } + + setTimeout(() => { dispatch(updateUser({ loadingState: false })) }, 4000) + + }, [currentNetwork]) + return ( - - + + {location.pathname !== '/' ? null : ( <> @@ -136,31 +133,38 @@ const App = () => { )} {location.pathname === '/' ? null : ( - - }> - - } /> + {loadingState ? : null} + + + }> + + } /> + + + } /> + } /> + + + } /> + } /> + - - } /> - } /> - - - } /> - } /> - - - {import.meta.env.VITE_CHAIN_STATUS !== 'mainnet' && ( - - } /> - - )} - } /> - - - - + { + CHAIN_DETAILS.CHAIN_ID[currentNetwork! as keyof typeof CHAIN_DETAILS.CHAIN_ID] + === CHAIN_DETAILS.CHAIN_ID.MAINNET ? null : ( + + } /> + + )} + } /> + + + + + + + )} diff --git a/src/api/config.ts b/src/api/config.ts index fd81436..292b46b 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -1,5 +1,3 @@ /* eslint-disable import/prefer-default-export */ export const GET_CURRENCY_RATE_URL = (currency: string) => `https://api.coingecko.com/api/v3/simple/price?ids=CUDOS&vs_currencies=${currency}` - -export const GET_FAUCET_TOKENS = import.meta.env.VITE_FAUCET_URL diff --git a/src/api/getAccountDelegations.ts b/src/api/getAccountDelegations.ts index 250e1ab..abba20c 100644 --- a/src/api/getAccountDelegations.ts +++ b/src/api/getAccountDelegations.ts @@ -1,7 +1,9 @@ import axios from 'axios' +import { CHAIN_DETAILS } from 'utils/constants' import { AccountDelegationsDocument } from '../graphql/account_actions' export const fetchDelegations = async ( + chosenNetwork: string, address: string, signal?: AbortSignal ) => { @@ -9,7 +11,7 @@ export const fetchDelegations = async ( try { const { data } = await axios.post( - import.meta.env.VITE_GRAPHQL_URL?.toString(), + CHAIN_DETAILS.GRAPHQL_URL[chosenNetwork! as keyof typeof CHAIN_DETAILS.GRAPHQL_URL].toString(), { variables: { address }, query: AccountDelegationsDocument diff --git a/src/api/getAccountRedelegations.ts b/src/api/getAccountRedelegations.ts index a9da0fc..ec0915c 100644 --- a/src/api/getAccountRedelegations.ts +++ b/src/api/getAccountRedelegations.ts @@ -1,8 +1,10 @@ import axios from 'axios' import BigNumber from 'bignumber.js' +import { CHAIN_DETAILS } from 'utils/constants' import { AccountRedelegationsDocument } from '../graphql/account_actions' export const fetchRedelegations = async ( + chosenNetwork: string, address: string, signal?: AbortSignal ) => { @@ -14,7 +16,7 @@ export const fetchRedelegations = async ( try { const { data } = await axios.post( - import.meta.env.VITE_GRAPHQL_URL?.toString(), + CHAIN_DETAILS.GRAPHQL_URL[chosenNetwork! as keyof typeof CHAIN_DETAILS.GRAPHQL_URL].toString(), { variables: { address }, query: AccountRedelegationsDocument diff --git a/src/api/getAccountUndelegations.ts b/src/api/getAccountUndelegations.ts index 1c6f9b4..55739f2 100644 --- a/src/api/getAccountUndelegations.ts +++ b/src/api/getAccountUndelegations.ts @@ -1,7 +1,9 @@ import axios from 'axios' +import { CHAIN_DETAILS } from 'utils/constants' import { AccountUndelegationsDocument } from '../graphql/account_actions' export const fetchUndedelegations = async ( + chosenNetwork: string, address: string, signal?: AbortSignal ) => { @@ -13,7 +15,7 @@ export const fetchUndedelegations = async ( try { const { data } = await axios.post( - import.meta.env.VITE_GRAPHQL_URL?.toString(), + CHAIN_DETAILS.GRAPHQL_URL[chosenNetwork! as keyof typeof CHAIN_DETAILS.GRAPHQL_URL].toString(), { variables: { address }, query: AccountUndelegationsDocument diff --git a/src/api/getFaucetTokens.ts b/src/api/getFaucetTokens.ts index e7d247a..8913d55 100644 --- a/src/api/getFaucetTokens.ts +++ b/src/api/getFaucetTokens.ts @@ -1,12 +1,11 @@ import axios from 'axios' -import { GET_FAUCET_TOKENS } from './config' -const getFaucetTokens = async (data: { +const getFaucetTokens = async (faucetAddress: string, data: { address: string coins: Array captchaResponse: string }) => { - return axios.post(GET_FAUCET_TOKENS, data) + return axios.post(faucetAddress, data) } export default getFaucetTokens diff --git a/src/api/getRewards.ts b/src/api/getRewards.ts index cabee6e..8d92dc8 100644 --- a/src/api/getRewards.ts +++ b/src/api/getRewards.ts @@ -3,14 +3,15 @@ import axios from 'axios' import BigNumber from 'bignumber.js' import { formatBigNum } from '../utils/projectUtils' import { AccountDelegationRewardsDocument } from '../graphql/account_actions' +import { CHAIN_DETAILS } from 'utils/constants' -export const fetchRewards = async (address: string, signal?: AbortSignal) => { +export const fetchRewards = async (chosenNetwork: string, address: string, signal?: AbortSignal) => { const defaultReturnValue = new BigNumber(0) const rewardArray: Array = [] const validatorArray: { address: string; amount: string }[] = [] try { const { data } = await axios.post( - import.meta.env.VITE_GRAPHQL_URL?.toString(), + CHAIN_DETAILS.GRAPHQL_URL[chosenNetwork! as keyof typeof CHAIN_DETAILS.GRAPHQL_URL].toString(), { variables: { address }, query: AccountDelegationRewardsDocument diff --git a/src/api/getUnbondingBalance.ts b/src/api/getUnbondingBalance.ts index afa4c31..64d458f 100644 --- a/src/api/getUnbondingBalance.ts +++ b/src/api/getUnbondingBalance.ts @@ -1,9 +1,11 @@ import axios from 'axios' import BigNumber from 'bignumber.js' +import { CHAIN_DETAILS } from 'utils/constants' import { formatBigNum } from 'utils/projectUtils' import { AccountUnbondingBalanceDocument } from '../graphql/account_actions' export const getUnbondingBalance = async ( + chosenNetwork: string, address: string, signal?: AbortSignal ) => { @@ -12,7 +14,7 @@ export const getUnbondingBalance = async ( try { const { data } = await axios.post( - import.meta.env.VITE_GRAPHQL_URL?.toString(), + CHAIN_DETAILS.GRAPHQL_URL[chosenNetwork! as keyof typeof CHAIN_DETAILS.GRAPHQL_URL]?.toString(), { variables: { address }, query: AccountUnbondingBalanceDocument diff --git a/src/assets/vectors/arrow-down.svg b/src/assets/vectors/arrow-down.svg index 95daeb2..937bc8d 100644 --- a/src/assets/vectors/arrow-down.svg +++ b/src/assets/vectors/arrow-down.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/assets/vectors/globus-icon.svg b/src/assets/vectors/globus-icon.svg new file mode 100644 index 0000000..320d491 --- /dev/null +++ b/src/assets/vectors/globus-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/vectors/link-icon.svg b/src/assets/vectors/link-icon.svg index f375e61..ec5fbd0 100644 --- a/src/assets/vectors/link-icon.svg +++ b/src/assets/vectors/link-icon.svg @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/assets/vectors/logo-header.svg b/src/assets/vectors/logo-header.svg index 9f33d24..e212154 100644 --- a/src/assets/vectors/logo-header.svg +++ b/src/assets/vectors/logo-header.svg @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/vectors/testnet-logo-header.svg b/src/assets/vectors/testnet-logo-header.svg new file mode 100644 index 0000000..fcc99a5 --- /dev/null +++ b/src/assets/vectors/testnet-logo-header.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Dialog/components/DelegationModal/Delegation.tsx b/src/components/Dialog/components/DelegationModal/Delegation.tsx index 30a6c10..67310ca 100644 --- a/src/components/Dialog/components/DelegationModal/Delegation.tsx +++ b/src/components/Dialog/components/DelegationModal/Delegation.tsx @@ -12,7 +12,12 @@ import { ArrowCircleRightRounded as ArrowCircleRightRoundedIcon } from '@mui/icons-material' import { MsgDelegate } from 'cosmjs-types/cosmos/staking/v1beta1/tx' -import { coin, GasPrice, MsgDelegateEncodeObject } from 'cudosjs' +import { + coin, + DEFAULT_GAS_MULTIPLIER, + GasPrice, + MsgDelegateEncodeObject +} from 'cudosjs' import { ModalStatus, DelegationModalProps, @@ -30,8 +35,10 @@ import { formatNumber, formatToken } from 'utils/format_token' import _ from 'lodash' import { signingClient } from 'ledgers/utils' import { updateUser } from 'store/profile' -import { getStakedBalance } from 'utils/projectUtils' +import { getStakedBalance, getWalletBalance } from 'utils/projectUtils' import { fetchDelegations } from 'api/getAccountDelegations' +import { CHAIN_DETAILS } from 'utils/constants' + import { ModalContainer, StyledTextField, @@ -39,9 +46,8 @@ import { CancelRoundedIcon } from '../styles' -const feeMultiplier = import.meta.env.VITE_APP_FEE_MULTIPLIER const gasPrice = GasPrice.fromString( - `${import.meta.env.VITE_APP_GAS_PRICE}${CosmosNetworkConfig.CURRENCY_DENOM}` + `${CHAIN_DETAILS.GAS_PRICE}${CosmosNetworkConfig.CURRENCY_DENOM}` ) type DelegationProps = { @@ -54,28 +60,33 @@ const Delegation: React.FC = ({ modalProps, handleModal }) => { const [delegationAmount, setDelegationAmount] = useState('') const { validator, amount, fee } = modalProps - const { address, connectedLedger } = useSelector( + const { address, connectedLedger, chosenNetwork } = useSelector( ({ profile }: RootState) => profile ) const dispatch = useDispatch() useEffect(() => { + let isMounted = true const loadBalance = async () => { - const client = await signingClient(connectedLedger) + const client = await signingClient(chosenNetwork, connectedLedger) const walletBalance = await client.getBalance( address, CosmosNetworkConfig.CURRENCY_DENOM ) - - setBalance( - new BigNumber(walletBalance.amount) - .dividedBy(CosmosNetworkConfig.CURRENCY_1_CUDO) - .toString(10) - ) + if (isMounted) { + setBalance( + new BigNumber(walletBalance.amount) + .dividedBy(CosmosNetworkConfig.CURRENCY_1_CUDO) + .toString(10) + ) + } } loadBalance() + return () => { + isMounted = false + } }, [address]) const getEstimatedFee = async (amount: string) => { @@ -95,11 +106,11 @@ const Delegation: React.FC = ({ modalProps, handleModal }) => { value: msg } - const client = await signingClient(connectedLedger) + const client = await signingClient(chosenNetwork, connectedLedger) const gasUsed = await client.simulate(address, [msgAny], 'memo') - const gasLimit = Math.round(gasUsed * feeMultiplier) + const gasLimit = Math.round(gasUsed * DEFAULT_GAS_MULTIPLIER) const calculatedFee = calculateFee(gasLimit, gasPrice).amount[0] @@ -159,6 +170,7 @@ const Delegation: React.FC = ({ modalProps, handleModal }) => { try { const delegationResult = await delegate( + chosenNetwork, address, validator?.address || '', amount || '', @@ -172,11 +184,19 @@ const Delegation: React.FC = ({ modalProps, handleModal }) => { txHash: delegationResult.transactionHash }) - const { delegationsArray } = await fetchDelegations(address) - const stakedAmountBalance = await getStakedBalance(address) + const walletBalance = await getWalletBalance(chosenNetwork!, address) + const { delegationsArray } = await fetchDelegations( + chosenNetwork!, + address + ) + const stakedAmountBalance = await getStakedBalance( + chosenNetwork!, + address + ) dispatch( updateUser({ + balance: walletBalance, delegations: delegationsArray, stakedBalance: new BigNumber(stakedAmountBalance) }) @@ -244,7 +264,11 @@ const Delegation: React.FC = ({ modalProps, handleModal }) => { fontWeight={700} color="primary.main" > - {import.meta.env.VITE_APP_CHAIN_NAME} + { + CHAIN_DETAILS.CHAIN_NAME[ + chosenNetwork as keyof typeof CHAIN_DETAILS.CHAIN_NAME + ] + } @@ -394,9 +418,12 @@ const Delegation: React.FC = ({ modalProps, handleModal }) => { sx={() => ({ width: '50%' })} - onClick={handleSubmit} + onClick={() => handleSubmit()} disabled={ - Number(amount) > Number(balance) || !amount || Number(amount) <= 0 + Number(amount) > Number(balance) || + !amount || + Number(amount) <= 0 || + fee.length <= 0 } > Submit diff --git a/src/components/Dialog/components/DelegationModal/Success.tsx b/src/components/Dialog/components/DelegationModal/Success.tsx index 2c1b5cd..accefe8 100644 --- a/src/components/Dialog/components/DelegationModal/Success.tsx +++ b/src/components/Dialog/components/DelegationModal/Success.tsx @@ -4,6 +4,9 @@ import { DelegationModalProps, initialDelegationModalState } from 'store/modal' import numeral from 'numeral' import SuccessIcon from 'assets/vectors/success.svg' import { ModalContainer, CancelRoundedIcon } from '../styles' +import { RootState } from 'store' +import { useSelector } from 'react-redux' +import { CHAIN_DETAILS } from 'utils/constants' type SuccessProps = { modalProps: DelegationModalProps @@ -12,6 +15,7 @@ type SuccessProps = { const Success: React.FC = ({ modalProps, handleModal }) => { const { validator, gasUsed, txHash, fee } = modalProps + const { chosenNetwork } = useSelector((state: RootState) => state.profile) const handleClose = () => { handleModal({ @@ -75,7 +79,7 @@ const Success: React.FC = ({ modalProps, handleModal }) => { window .open( `${ - import.meta.env.VITE_APP_EXPLORER_V2 + CHAIN_DETAILS.EXPLORER_URL[chosenNetwork as keyof typeof CHAIN_DETAILS.EXPLORER_URL] }/transactions/${txHash}`, '_blank' ) diff --git a/src/components/Layout/Footer.tsx b/src/components/Layout/Footer.tsx index b7d2d55..ef56242 100644 --- a/src/components/Layout/Footer.tsx +++ b/src/components/Layout/Footer.tsx @@ -1,4 +1,4 @@ -import { Box, Grid, Typography } from '@mui/material' +import { Box, Container, Grid, Typography } from '@mui/material' import moment from 'moment' import TwitterIcon from 'assets/vectors/twitter.svg?component' import TelegramIcon from 'assets/vectors/telegram.svg?component' @@ -11,7 +11,10 @@ import SpotifyIcon from 'assets/vectors/spotify.svg?component' import { styles } from './styles' const linksLeft = [ - { text: 'Terms & Conditions', url: 'https://www.cudos.org/terms-and-conditions/' }, + { + text: 'Terms & Conditions', + url: 'https://www.cudos.org/terms-and-conditions/' + }, { text: 'Privacy Policy', url: 'https://www.cudos.org/privacy-policy' }, { text: 'cudos.org', url: 'https://www.cudos.org/' }, { text: `License © 2018 - ${moment().year()}`, url: 'https://www.cudos.org/' } @@ -25,59 +28,66 @@ const linksRight = [ { icon: , url: 'https://medium.com/cudos' }, { icon: , url: 'https://www.youtube.com/c/CUDOS' }, { icon: , url: 'https://www.facebook.com/cudos.org' }, - { icon: , url: 'https://open.spotify.com/show/2lZuBXJ270g7taK06tnK35' } + { + icon: , + url: 'https://open.spotify.com/show/2lZuBXJ270g7taK06tnK35' + } ] const Footer = () => { return ( - - {linksLeft.map((link) => ( - ({ - padding: `0 0.5rem`, - '&:not(:last-child)': { - borderRight: `1px solid ${palette.text.secondary}` - }, - cursor: 'pointer', - color: palette.text.secondary, - '&:hover': { - color: palette.primary.main, - textShadow: `0 0 3px ${palette.primary.main}` - } - })} - onClick={() => window.open(link.url, '_blank')?.focus()} + + + + {linksLeft.map((link) => ( + ({ + padding: `0 0.5rem`, + '&:not(:last-child)': { + borderRight: `1px solid ${palette.text.secondary}` + }, + cursor: 'pointer', + color: palette.text.secondary, + '&:hover': { + color: palette.primary.main, + textShadow: `0 0 3px ${palette.primary.main}` + } + })} + onClick={() => window.open(link.url, '_blank')?.focus()} + > + + {link.text} + + + ))} + + - - {link.text} - - - ))} - - - {linksRight.map((link) => ( - window.open(link.url, '_blank')?.focus()} - sx={({ palette }) => ({ - cursor: 'pointer', - color: palette.text.secondary, - '&:hover': { - color: palette.primary.main - } - })} - > - {link.icon} - - ))} - + {linksRight.map((link) => ( + window.open(link.url, '_blank')?.focus()} + sx={({ palette }) => ({ + cursor: 'pointer', + color: palette.text.secondary, + '&:hover': { + color: palette.primary.main + } + })} + > + {link.icon} + + ))} + + + ) } diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 4ddf31b..e6d9381 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -1,58 +1,71 @@ import { Box, Typography } from '@mui/material' -import { useSelector, useDispatch } from 'react-redux' -import { useLocation } from 'react-router-dom' +import { useSelector } from 'react-redux' import { RootState } from 'store' -import { ThemeType, updateSettings } from 'store/settings' -import LogoHeader from 'assets/vectors/logo-header.svg' -import CudosLogo from 'assets/vectors/cudos-logo.svg?component' -import LinkIcon from 'assets/vectors/link-icon.svg?component' +import { headerStyles } from './headerstyles' +import { useMidLowerResCheck, useMidlowResCheck } from './hooks/useScreenChecks' +import { useLocation, useNavigate } from 'react-router-dom' +import { CHAIN_DETAILS } from 'utils/constants' import NetworkInfo from './NetworkInfo' - import UserInfo from './UserInfo' +import LogoHeader from 'assets/vectors/logo-header.svg?component' +import TestNetLogoHeader from 'assets/vectors/testnet-logo-header.svg?component' +import CudosLogo from 'assets/vectors/cudos-logo.svg?component' +import LinkIcon from 'assets/vectors/link-icon.svg?component' +import { useEffect, useState } from 'react' const Header = () => { - const { theme } = useSelector((state: RootState) => state.settings) - const dispatch = useDispatch() + const location = useLocation() + const nagivate = useNavigate() + const isMidLowewRes = useMidLowerResCheck() + const isMidLowRes = useMidlowResCheck() + const { chosenNetwork, loadingState } = useSelector((state: RootState) => state.profile) + const isMainnet = CHAIN_DETAILS.CHAIN_ID[chosenNetwork as keyof typeof CHAIN_DETAILS.CHAIN_ID] === CHAIN_DETAILS.CHAIN_ID.MAINNET + const isWelcomePage = location.pathname === '/' + const [logoComponent, setLogoComponent] = useState() + + useEffect(() => { + + if (isWelcomePage) { + setLogoComponent() + return + } + + if (!loadingState) { + setLogoComponent( + isMainnet ? : + ) + } - const switchTheme = () => { - dispatch( - updateSettings({ - theme: theme === ThemeType.DARK ? ThemeType.LIGHT : ThemeType.DARK - }) - ) - } + }, [loadingState, location.pathname]) return ( - - logo - {location.pathname === '/' ? null : ( - - - window.open(import.meta.env.VITE_BRIDGE_URL, '_blank')?.focus() - } - > - - - Cudos Bridge - - - - - + + + nagivate(isWelcomePage ? '/' : 'dashboard')} sx={headerStyles.logoHolder}> + {logoComponent} - )} + {isWelcomePage ? null : + + window + .open(CHAIN_DETAILS.BRIDGE_URL[chosenNetwork as keyof typeof CHAIN_DETAILS.BRIDGE_URL], '_blank') + ?.focus()} + > + + + Cudos Bridge + + + + + + + + } + ) } diff --git a/src/components/Layout/LeftMenu.tsx b/src/components/Layout/LeftMenu.tsx index bcd5d5e..53a90ea 100644 --- a/src/components/Layout/LeftMenu.tsx +++ b/src/components/Layout/LeftMenu.tsx @@ -1,51 +1,62 @@ import { useEffect, useState } from 'react' -import { Box, ToggleButton, Tooltip } from '@mui/material' +import { Box, ToggleButton, Tooltip, Typography } from '@mui/material' import { Link, useLocation } from 'react-router-dom' -import DashboardIcon from 'assets/vectors/dashboard.svg?component' -import ProposalsIcon from 'assets/vectors/proposals.svg?component' -import StakingIcon from 'assets/vectors/staking.svg?component' -import FaucetIcon from 'assets/vectors/faucet.svg?component' +import { useSelector } from 'react-redux' +import { RootState } from 'store' +import { getMenuItems, MenuItems } from './menuHelpers' +import { useMidlowResCheck } from './hooks/useScreenChecks' import { styles } from './styles' -const MenuItems = [ - { icon: , link: '/dashboard', text: 'Dashboard' }, - { icon: , link: '/staking', text: 'Staking' }, - { icon: , link: '/proposals', text: 'Proposals' } -] - -if (import.meta.env.VITE_CHAIN_STATUS !== 'mainnet') { - MenuItems.push({ icon: , link: '/faucet', text: 'Faucet' }) -} - const Menu = () => { const [selected, setSelected] = useState(0) + const [menuItems, setMenuItems] = useState([]) + const { chosenNetwork, loadingState } = useSelector((state: RootState) => state.profile) const { pathname } = useLocation() + const isMidLowRes = useMidlowResCheck() + + useEffect(() => { + + setMenuItems(getMenuItems(chosenNetwork, loadingState)) + + }, [chosenNetwork, loadingState]) useEffect(() => { - const selectedIndex = MenuItems.findIndex( - (menuItem) => menuItem.link === pathname - ) - setSelected(selectedIndex) + + if (menuItems.length) { + const selectedIndex = menuItems.findIndex( + (menuItem) => menuItem.link === pathname + ) + setSelected(selectedIndex) + return + } + + setSelected(0) + }, [pathname]) return ( - {MenuItems.map((item, index) => ( + {menuItems.map((item, index) => ( { onClick={() => setSelected(index)} > {item.icon} + {isMidLowRes ? null : + {item.text} + } diff --git a/src/components/Layout/NetworkInfo.tsx b/src/components/Layout/NetworkInfo.tsx index 8686f58..fe78ccc 100644 --- a/src/components/Layout/NetworkInfo.tsx +++ b/src/components/Layout/NetworkInfo.tsx @@ -1,28 +1,111 @@ -import { Box, Typography } from '@mui/material' -import OnlineStatus from 'assets/vectors/online-status.svg' +import { useState } from 'react' +import { StyledNetwork, styles } from './networkStyles' +import ArrowIcon from 'assets/vectors/arrow-down.svg?component' +import { CHAIN_DETAILS } from 'utils/constants' +import { COLORS_DARK_THEME } from 'theme/colors' +import GlobusIcon from 'assets/vectors/globus-icon.svg?component' +import { Typography, Box, Collapse } from '@mui/material' +import { RootState } from 'store' +import { useDispatch, useSelector } from 'react-redux' +import { handleAvailableNetworks } from 'utils/projectUtils' +import { updateUser } from 'store/profile' +import Card from 'components/Card' -import { styles } from './styles' +const NetworkLinkComponent = ({ network, setChosenNetwork }: { + network: networkToDisplay, + setChosenNetwork: (selectedNetwork: string) => void +}): JSX.Element => { + + const [hovered, setHovered] = useState(false) -const NetworkInfo = () => { return ( - - setHovered(true)} + onMouseOut={() => setHovered(false)} + onClick={() => setChosenNetwork(network.SHORT_NAMES[0].toUpperCase())} + > + - {import.meta.env.VITE_APP_CHAIN_NAME} - Online Status + /> + + {network.ALIAS_NAME} ) } +const NetworkInfo = () => { + + const dispatch = useDispatch() + const [open, setOpen] = useState(false) + const { chosenNetwork, loadingState } = useSelector((state: RootState) => state.profile) + const aliasChainName = CHAIN_DETAILS[chosenNetwork as keyof typeof CHAIN_DETAILS].ALIAS_NAME + const networksToDisplayInMenu = handleAvailableNetworks(CHAIN_DETAILS.DEFAULT_NETWORK) + + const setChosenNetwork = (selectedNetwork: string) => { + dispatch(updateUser({ chosenNetwork: selectedNetwork })) + setOpen(false) + } + + const collapsable = networksToDisplayInMenu.length > 1 + + return ( + + + { } : () => setOpen(true)} + onMouseLeave={() => setOpen(false)} + style={styles.userContainer} + > + + + + {!loadingState ? {aliasChainName} : + + {aliasChainName} + } + + {collapsable ? + + + : null} + + + { } : () => setOpen(true)} + onMouseLeave={() => setOpen(false)} + style={{ marginTop: '-28px', zIndex: '-1' }} + in={open} + > + + {networksToDisplayInMenu.map((NETWORK, idx) => { + return aliasChainName !== NETWORK.ALIAS_NAME ? + : null + })} + + + + ) +} + export default NetworkInfo diff --git a/src/components/Layout/UserInfo.tsx b/src/components/Layout/UserInfo.tsx index 51d244f..3b7efeb 100644 --- a/src/components/Layout/UserInfo.tsx +++ b/src/components/Layout/UserInfo.tsx @@ -8,9 +8,11 @@ import { updateUser } from 'store/profile' import { copyToClipboard, formatAddress } from 'utils/projectUtils' import KeplrLogo from 'assets/vectors/keplr-logo.svg' import CosmostationLogo from 'assets/vectors/cosmostation-logo.svg' -import LinkIcon from 'assets/vectors/link-icon.svg' -import CopyIcon from 'assets/vectors/copy-icon.svg' -import ArrowIcon from 'assets/vectors/arrow-down.svg' +import LinkIcon from 'assets/vectors/link-icon.svg?component' +import CopyIcon from 'assets/vectors/copy-icon.svg?component' +import ArrowIcon from 'assets/vectors/arrow-down.svg?component' +import { CHAIN_DETAILS } from 'utils/constants' +import { COLORS_DARK_THEME } from 'theme/colors' import getMiddleEllipsis from 'utils/get_middle_ellipsis' import { styles } from './styles' @@ -18,7 +20,7 @@ import { styles } from './styles' const UserInfo = () => { const navigate = useNavigate() const dispatch = useDispatch() - const { address, accountName, connectedLedger } = useSelector( + const { address, accountName, connectedLedger, chosenNetwork } = useSelector( (state: RootState) => state.profile ) @@ -35,7 +37,7 @@ const UserInfo = () => { } const handleExplorer = () => { - window.open(`${import.meta.env.VITE_APP_EXPLORER_V2?.toString()}`, '_blank') + window.open(`${CHAIN_DETAILS.EXPLORER_URL[chosenNetwork as keyof typeof CHAIN_DETAILS.EXPLORER_URL].toString()}`, '_blank') } const handleDisconnect = () => { @@ -51,7 +53,8 @@ const UserInfo = () => { delegations: [], redelegations: [], undelegations: [], - connectedLedger: '' + connectedLedger: '', + chosenNetwork: CHAIN_DETAILS.DEFAULT_NETWORK }) ) navigate('/') @@ -78,21 +81,18 @@ const UserInfo = () => { - Arrow Icon - + @@ -116,18 +116,26 @@ const UserInfo = () => { onClick={() => handleCopy(address)} title={copied ? 'Copied' : 'Copy to clipboard'} > - Copy + + + handleExplorer()} title="Go to Explorer"> - Link + + + { + return useMediaQuery({ query: `(max-${measure}: ${pixels})` }) +} + +//WIDTH +export const useHighResCheck = () => { + return useMediaQuery({ query: `(max-width: ${SCREEN_RESOLUTIONS.HIGH}px)` }) +} + +export const useMidlowResCheck = () => { + return useMediaQuery({ query: `(max-width: ${SCREEN_RESOLUTIONS.MID_LOW}px)` }) +} + +export const useLowResCheck = () => { + return useMediaQuery({ query: `(max-width: ${SCREEN_RESOLUTIONS.LOW}px)` }) +} + +export const useMidLowerResCheck = () => { + return useMediaQuery({ query: `(max-width: ${SCREEN_RESOLUTIONS.MID_LOWER}px)` }) +} + +export const useLowerResCheck = () => { + return useMediaQuery({ query: `(max-width: ${SCREEN_RESOLUTIONS.LOWER}px)` }) +} + +export const useMidLowestResCheck = () => { + return useMediaQuery({ query: `(max-width: ${SCREEN_RESOLUTIONS.MID_LOWEST}px)` }) +} + +export const useLowestResCheck = () => { + return useMediaQuery({ query: `(max-width: ${SCREEN_RESOLUTIONS.LOWEST}px)` }) +} + + +//HEIGHT +export const useMidLowestHeight = () => { + return useMediaQuery({ query: `(max-height: ${SCREEN_RESOLUTIONS.MID_LOWEST}px)` }) +} diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index bbbc2c4..cc785a3 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -1,10 +1,10 @@ import { Box } from '@mui/material' import Footer from './Footer' - import Header from './Header' import LeftMenu from './LeftMenu' const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => { + return ( = ({ children }) => { }} >
- + {children} +