diff --git a/CHANGELOG.md b/CHANGELOG.md index d254eb5e9a..d50ccd7d05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ -# Unreleased +# base-v1.1.0 - 2021-09-13 ## Changes - Centered desmos profile cover photo ([\#285](https://github.com/forbole/big-dipper-2.0-cosmos/issues/285)) - Add License to footer ([\#287](https://github.com/forbole/big-dipper-2.0-cosmos/issues/287)) - Changed position of desmos profile +- Fix avatar images not loading correctly ([\#296](https://github.com/forbole/big-dipper-2.0-cosmos/issues/296)) +- Fix rendering issue on account and validtor details page ([\#297](https://github.com/forbole/big-dipper-2.0-cosmos/issues/297)) +- Add validator status to account delegation component ([\#307](https://github.com/forbole/big-dipper-2.0-cosmos/issues/307)) # base-v1.0.9 - 2021-09-03 diff --git a/README.md b/README.md index bc4c37d60c..dfe30c1ab2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,16 @@ # Big Dipper Interface ✨ Cosmos 2.0 Big Dipper is an open-source block explorer and token management tool serving over 10 proof-of-stake blockchains. It has been forked more than 100 times on GitHub and has served audiences from 140 countries and regions. +**This repo contains the UI of big dipper 2.0 only** + ## Documentation Read our official documentation at [http://docs.bigdipper.live/](http://docs.bigdipper.live/) +## Issue Reporting +For UI related issues please report it here [https://github.com/forbole/big-dipper-2.0-cosmos/issues](https://github.com/forbole/big-dipper-2.0-cosmos/issues). + +For Hasura and BdJuno issues please report it here [https://github.com/forbole/bdjuno/issues](https://github.com/forbole/bdjuno/issues) + ## License Read our license at [https://raw.githubusercontent.com/forbole/big-dipper-2.0-cosmos/master/LICENSE](https://raw.githubusercontent.com/forbole/big-dipper-2.0-cosmos/master/LICENSE) diff --git a/declarations.d.ts b/declarations.d.ts index 99f296224b..a2993022c9 100644 --- a/declarations.d.ts +++ b/declarations.d.ts @@ -35,11 +35,11 @@ type DesmosProfile = { imageUrl: string; coverUrl: string; bio: string; - connections: ConnectionType[]; + connections: ProfileConnectionType[]; validator?: ValidatorProfile; } -type ConnectionType = { +type ProfileConnectionType = { network: string; identifier: string; creationTime: string; diff --git a/i18n.js b/i18n.js index 04251bda5f..a813774fda 100644 --- a/i18n.js +++ b/i18n.js @@ -9,7 +9,7 @@ module.exports = { 'rgx:^/transactions': ['transactions', 'message_labels', 'message_contents'], 'rgx:^/proposals': ['proposals'], 'rgx:^/validators': ['validators', 'transactions', 'accounts'], - 'rgx:^/accounts': ['accounts', 'transactions'], + 'rgx:^/accounts': ['accounts', 'transactions', 'validators'], 'rgx:^/params': ['params'], }, loadLocaleFrom: (lang, ns) => import(`./public/locales/${lang}/${ns}.json`).then((m) => m.default), diff --git a/public/locales/en/accounts.json b/public/locales/en/accounts.json index 4db269c73a..e87f3ddef0 100644 --- a/public/locales/en/accounts.json +++ b/public/locales/en/accounts.json @@ -31,5 +31,6 @@ "network": "Network", "identifier": "Identifier", "creationTime": "Creation Time", - "bio": "Bio" + "bio": "Bio", + "status": "Status" } diff --git a/public/locales/en/params.json b/public/locales/en/params.json index 4374f78904..7a63b5706d 100644 --- a/public/locales/en/params.json +++ b/public/locales/en/params.json @@ -13,7 +13,7 @@ "minSignedPerWindow": "Min Signed Per Window", "signedBlockWindow": "Signed Block Window", "slashFractionDoubleSign": "Slash Fraction Double Sign", - "slashFractionDowntime": "Slash Traction Downtime", + "slashFractionDowntime": "Slash Fraction Downtime", "minting": "Minting", "blocksPerYear": "Blocks Per Year", "goalBonded": "Goal Bonded", diff --git a/src/components/avatar/index.tsx b/src/components/avatar/index.tsx index c3a573d38b..f29828c5cd 100644 --- a/src/components/avatar/index.tsx +++ b/src/components/avatar/index.tsx @@ -18,16 +18,17 @@ const Avatar: React.FC<{ const [error, setError] = useState(false); useEffect(() => { jdenticon.update(icon.current, address); - }, [address, error]); + }, [address, error, imageUrl]); const handleError = () => { setError(true); }; const classes = useStyles(); + return (
- {imageUrl && !error ? ( + {(imageUrl && !error) ? ( address avatar = ({ className, items, diff --git a/src/components/desmos_profile/components/connections/components/mobile/index.tsx b/src/components/desmos_profile/components/connections/components/mobile/index.tsx index 2d134e507e..40333ad226 100644 --- a/src/components/desmos_profile/components/connections/components/mobile/index.tsx +++ b/src/components/desmos_profile/components/connections/components/mobile/index.tsx @@ -11,7 +11,7 @@ import { useStyles } from './styles'; const Mobile: React.FC<{ className?: string; - items?: ConnectionType[]; + items?: ProfileConnectionType[]; }> = ({ className, items, }) => { diff --git a/src/components/desmos_profile/components/connections/index.tsx b/src/components/desmos_profile/components/connections/index.tsx index 7520ebbef2..79ae30f052 100644 --- a/src/components/desmos_profile/components/connections/index.tsx +++ b/src/components/desmos_profile/components/connections/index.tsx @@ -22,7 +22,7 @@ const Mobile = dynamic(() => import('./components/mobile')); const Connections: React.FC<{ handleClose: () => void; open: boolean; - data: ConnectionType[]; + data: ProfileConnectionType[]; }> = ({ handleClose, open, diff --git a/src/components/desmos_profile/styles.ts b/src/components/desmos_profile/styles.ts index d3066a4d22..3cc4e77e3c 100644 --- a/src/components/desmos_profile/styles.ts +++ b/src/components/desmos_profile/styles.ts @@ -8,7 +8,7 @@ export const useStyles = (coverUrl?: string) => { overflow: 'hidden', }, cover: { - height: '125px', + height: '150px', background: theme.palette.custom.fonts.fontFour, backgroundImage: 'url("/images/default_cover_pattern.png")', backgroundRepeat: 'repeat', @@ -24,11 +24,14 @@ export const useStyles = (coverUrl?: string) => { backgroundSize: 'cover', backgroundPosition: 'center center', }, - [theme.breakpoints.up('md')]: { + [theme.breakpoints.up('sm')]: { height: '200px', }, + [theme.breakpoints.up('md')]: { + height: '300px', + }, [theme.breakpoints.up('lg')]: { - height: '250px', + height: '360px', }, }, avatarContainer: { @@ -37,7 +40,7 @@ export const useStyles = (coverUrl?: string) => { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - padding: theme.spacing(1.5, 0), + padding: theme.spacing(1, 0), '& .hide': { visibility: 'hidden', }, @@ -55,7 +58,7 @@ export const useStyles = (coverUrl?: string) => { minHeight: '75px', minWidth: '75px', border: `solid 3px ${theme.palette.background.paper}`, - top: theme.spacing(-3), + top: theme.spacing(-4), left: 0, [theme.breakpoints.up('md')]: { width: '115px', diff --git a/src/graphql/account.graphql b/src/graphql/account.graphql index f09bab72d7..a6a00be91c 100644 --- a/src/graphql/account.graphql +++ b/src/graphql/account.graphql @@ -16,6 +16,10 @@ query Account($address: String, $utc: timestamp) { validatorCommissions: validator_commissions(limit: 1, order_by: {height: desc}) { commission } + validatorStatuses: validator_statuses (limit: 1, order_by: {height: desc}) { + status + jailed + } } } unbonding: unbonding_delegations(where: {completion_timestamp: {_gt: $utc}}) { diff --git a/src/graphql/types.tsx b/src/graphql/types.tsx index 6b36ee02fc..9b539451d6 100644 --- a/src/graphql/types.tsx +++ b/src/graphql/types.tsx @@ -17966,6 +17966,9 @@ export type AccountQuery = { stakingParams: Array<( )>, validatorCommissions: Array<( { __typename?: 'validator_commission' } & Pick + )>, validatorStatuses: Array<( + { __typename?: 'validator_status' } + & Pick )> } ) } )>, unbonding: Array<( @@ -18574,6 +18577,10 @@ export const AccountDocument = gql` validatorCommissions: validator_commissions(limit: 1, order_by: {height: desc}) { commission } + validatorStatuses: validator_statuses(limit: 1, order_by: {height: desc}) { + status + jailed + } } } unbonding: unbonding_delegations(where: {completion_timestamp: {_gt: $utc}}) { diff --git a/src/screens/account_details/__snapshots__/index.test.tsx.snap b/src/screens/account_details/__snapshots__/index.test.tsx.snap index aa0dabe13d..724a4c1600 100644 --- a/src/screens/account_details/__snapshots__/index.test.tsx.snap +++ b/src/screens/account_details/__snapshots__/index.test.tsx.snap @@ -124,6 +124,10 @@ exports[`screen: BlockDetails matches snapshot 1`] = ` "imageUrl": null, "name": "moniker", }, + "validatorStatus": Object { + "jailed": false, + "status": 3, + }, }, ], } diff --git a/src/screens/account_details/components/staking/components/delegations/components/desktop/index.tsx b/src/screens/account_details/components/staking/components/delegations/components/desktop/index.tsx index 48c4303310..dc74e5532c 100644 --- a/src/screens/account_details/components/staking/components/delegations/components/desktop/index.tsx +++ b/src/screens/account_details/components/staking/components/delegations/components/desktop/index.tsx @@ -13,6 +13,8 @@ import { AvatarName, } from '@components'; import { DelegationType } from '@src/screens/account_details/types'; +import { getValidatorStatus } from '@utils/get_validator_status'; +import { useStyles } from './styles'; import { columns } from './utils'; const Desktop: React.FC<{ @@ -23,8 +25,9 @@ const Desktop: React.FC<{ items, }) => { const { t } = useTranslation('accounts'); - + const classes = useStyles(); const formattedItems = items.map((x) => { + const statusTheme = getValidatorStatus(x.validatorStatus.status, x.validatorStatus.jailed); return ({ validator: ( ), + status: ( + + {t(`validators:${statusTheme.status}`)} + + ), commission: `${numeral(x.commission * 100).format('0.00')}%`, amount: `${numeral(x.amount.value).format('0,0.[0000]')} ${x.amount.denom.toUpperCase()}`, reward: `${numeral(x.reward.value).format('0,0.[0000]')} ${x.reward.denom.toUpperCase()}`, diff --git a/src/screens/account_details/components/staking/components/delegations/components/desktop/styles.ts b/src/screens/account_details/components/staking/components/delegations/components/desktop/styles.ts new file mode 100644 index 0000000000..57ab15488b --- /dev/null +++ b/src/screens/account_details/components/staking/components/delegations/components/desktop/styles.ts @@ -0,0 +1,31 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = () => { + const styles = makeStyles( + (theme) => { + return ({ + status: { + color: theme.palette.custom.fonts.fontTwo, + '&.unknown': { + color: theme.palette.custom.condition.zero, + }, + '&.unbonded': { + color: theme.palette.custom.condition.zero, + }, + '&.active': { + color: theme.palette.custom.condition.one, + }, + '&.jailed': { + color: theme.palette.custom.condition.two, + }, + '&.unbonding': { + color: theme.palette.custom.condition.three, + }, + + }, + }); + }, + )(); + + return styles; +}; diff --git a/src/screens/account_details/components/staking/components/delegations/components/desktop/utils.ts b/src/screens/account_details/components/staking/components/delegations/components/desktop/utils.ts index 0b2c98fcbb..c1865fb4d4 100644 --- a/src/screens/account_details/components/staking/components/delegations/components/desktop/utils.ts +++ b/src/screens/account_details/components/staking/components/delegations/components/desktop/utils.ts @@ -7,9 +7,13 @@ export const columns:{ key: 'validator', width: 25, }, + { + key: 'status', + width: 15, + }, { key: 'commission', - width: 25, + width: 10, align: 'right', }, { diff --git a/src/screens/account_details/components/staking/components/delegations/components/mobile/index.tsx b/src/screens/account_details/components/staking/components/delegations/components/mobile/index.tsx index 0c18ab9cf6..f1de9a3a42 100644 --- a/src/screens/account_details/components/staking/components/delegations/components/mobile/index.tsx +++ b/src/screens/account_details/components/staking/components/delegations/components/mobile/index.tsx @@ -6,9 +6,8 @@ import { Divider, Typography, } from '@material-ui/core'; -import { - AvatarName, -} from '@components'; +import { getValidatorStatus } from '@utils/get_validator_status'; +import { AvatarName } from '@components'; import { DelegationType } from '@src/screens/account_details/types'; import { useStyles } from './styles'; @@ -24,6 +23,7 @@ const Mobile: React.FC<{ return (
{items.map((x, i) => { + const statusTheme = getValidatorStatus(x.validatorStatus.status, x.validatorStatus.jailed); return (
@@ -37,6 +37,14 @@ const Mobile: React.FC<{ imageUrl={x.validator.imageUrl} />
+
+ + {t('validators:status')} + + + {t(`validators:${statusTheme.status}`)} + +
{t('commission')} diff --git a/src/screens/account_details/components/staking/components/delegations/components/mobile/styles.ts b/src/screens/account_details/components/staking/components/delegations/components/mobile/styles.ts index 6cece6efa1..033ec3b23f 100644 --- a/src/screens/account_details/components/staking/components/delegations/components/mobile/styles.ts +++ b/src/screens/account_details/components/staking/components/delegations/components/mobile/styles.ts @@ -15,6 +15,21 @@ export const useStyles = () => { }, '& p.value': { color: theme.palette.custom.fonts.fontTwo, + '&.unknown': { + color: theme.palette.custom.condition.zero, + }, + '&.unbonded': { + color: theme.palette.custom.condition.zero, + }, + '&.active': { + color: theme.palette.custom.condition.one, + }, + '&.jailed': { + color: theme.palette.custom.condition.two, + }, + '&.unbonding': { + color: theme.palette.custom.condition.three, + }, }, '& a': { color: theme.palette.custom.fonts.highlight, diff --git a/src/screens/account_details/hooks.ts b/src/screens/account_details/hooks.ts index 182d7daa8b..61ae7fc591 100644 --- a/src/screens/account_details/hooks.ts +++ b/src/screens/account_details/hooks.ts @@ -99,10 +99,7 @@ export const useAccountDetails = () => { }); useEffect(() => { - handleSetState({ - loading: true, - exists: true, - }); + handleSetState(initialState); if (chainConfig.extra.desmosProfile) { fetchDesmosProfile(R.pathOr('', ['query', 'address'], router)); } @@ -357,6 +354,10 @@ export const useAccountDetails = () => { imageUrl: validator.imageUrl, name: validator.moniker, }, + validatorStatus: { + status: R.pathOr(3, ['validator', 'validatorStatuses', 0, 'status'], x), + jailed: R.pathOr(false, ['validator', 'validatorStatuses', 0, 'jailed'], x), + }, reward: rewardsDict[validatorAddress], amount: formatDenom(x.amount.amount, x.amount.denom), commission: R.pathOr(0, ['validator', 'validatorCommissions', 0, 'commission'], x), diff --git a/src/screens/account_details/index.test.tsx b/src/screens/account_details/index.test.tsx index 6a9b71d964..4370f75ade 100644 --- a/src/screens/account_details/index.test.tsx +++ b/src/screens/account_details/index.test.tsx @@ -86,6 +86,12 @@ const mockAccount = jest.fn().mockResolvedValue({ commission: 0.0999, }, ], + validatorStatuses: [ + { + status: 3, + jailed: false, + }, + ], }, }, ], diff --git a/src/screens/account_details/types.ts b/src/screens/account_details/types.ts index b7e1255d85..2dcc743daa 100644 --- a/src/screens/account_details/types.ts +++ b/src/screens/account_details/types.ts @@ -29,6 +29,10 @@ export type TransactionType = { export type DelegationType = { validator: AvatarName; + validatorStatus: { + status: number; + jailed: boolean; + } commission: number; amount: TokenUnit; reward: TokenUnit; diff --git a/src/screens/validator_details/components/validator_overview/index.tsx b/src/screens/validator_details/components/validator_overview/index.tsx index c855779251..83add61749 100644 --- a/src/screens/validator_details/components/validator_overview/index.tsx +++ b/src/screens/validator_details/components/validator_overview/index.tsx @@ -18,11 +18,9 @@ import { import Link from 'next/link'; import { ACCOUNT_DETAILS } from '@utils/go_to_page'; import { useSettingsContext } from '@contexts'; +import { getValidatorStatus } from '@utils/get_validator_status'; import { useStyles } from './styles'; -import { - getStatusTheme, - getCondition, -} from './utils'; +import { getCondition } from './utils'; import { StatusType } from '../../types'; import { useAddress } from './hooks'; @@ -42,7 +40,7 @@ const ValidatorOverview: React.FC { - const results = { - status: 'na', - theme: 'zero', - }; +import { getValidatorStatus } from '@utils/get_validator_status'; - if (status === 3) { - results.status = 'active'; - results.theme = 'one'; - } else if (status === 2 && jailed) { - results.status = 'jailed'; - results.theme = 'two'; - } else if (status === 2 && !jailed) { - results.status = 'unbonding'; - results.theme = 'three'; - } else if (status === 1) { - results.status = 'unbonded'; - results.theme = 'zero'; - } else { - results.status = 'unknown'; - results.theme = 'zero'; - } - - return results; -}; +export const getStatusTheme = getValidatorStatus; export const getCondition = (condition: number, status: number) => { let result = 'na'; diff --git a/src/screens/validator_details/hooks.ts b/src/screens/validator_details/hooks.ts index 97948e0a36..7de672a9a8 100644 --- a/src/screens/validator_details/hooks.ts +++ b/src/screens/validator_details/hooks.ts @@ -24,6 +24,62 @@ import { } from '@models'; import { ValidatorDetailsState } from './types'; +const initialState = { + loading: true, + exists: true, + desmosProfile: null, + overview: { + validator: { + imageUrl: '', + moniker: '', + }, + operatorAddress: '', + selfDelegateAddress: '', + description: '', + website: '', + }, + status: { + status: 0, + jailed: false, + condition: 0, + commission: 0, + missedBlockCounter: 0, + signedBlockWindow: 0, + lastSeen: '', + }, + votingPower: { + height: 0, + overall: { + value: 0, + denom: '', + }, + self: 0, + selfDelegatePercent: 0, + selfDelegate: { + value: 0, + denom: '', + }, + }, + delegations: { + count: 0, + data: [], + }, + redelegations: { + count: 0, + data: [], + }, + undelegations: { + count: 0, + data: [], + }, + transactions: { + data: [], + hasNextPage: false, + isNextPageLoading: false, + offsetCount: 0, + }, +}; + export const useValidatorDetails = () => { const router = useRouter(); const { @@ -31,61 +87,7 @@ export const useValidatorDetails = () => { findOperator, validatorToDelegatorAddress, } = useChainContext(); - const [state, setState] = useState({ - loading: true, - exists: true, - desmosProfile: null, - overview: { - validator: { - imageUrl: '', - moniker: '', - }, - operatorAddress: '', - selfDelegateAddress: '', - description: '', - website: '', - }, - status: { - status: 0, - jailed: false, - condition: 0, - commission: 0, - missedBlockCounter: 0, - signedBlockWindow: 0, - lastSeen: '', - }, - votingPower: { - height: 0, - overall: { - value: 0, - denom: '', - }, - self: 0, - selfDelegatePercent: 0, - selfDelegate: { - value: 0, - denom: '', - }, - }, - delegations: { - count: 0, - data: [], - }, - redelegations: { - count: 0, - data: [], - }, - undelegations: { - count: 0, - data: [], - }, - transactions: { - data: [], - hasNextPage: false, - isNextPageLoading: false, - offsetCount: 0, - }, - }); + const [state, setState] = useState(initialState); const handleSetState = (stateChange: any) => { setState((prevState) => R.mergeDeepLeft(stateChange, prevState)); @@ -105,10 +107,7 @@ export const useValidatorDetails = () => { }); useEffect(() => { - handleSetState({ - loading: true, - exists: true, - }); + handleSetState(initialState); if (chainConfig.extra.desmosProfile) { const address = validatorToDelegatorAddress(R.pathOr('', ['query', 'address'], router)); @@ -237,9 +236,11 @@ export const useValidatorDetails = () => { const operatorAddress = R.pathOr('', ['validator', 0, 'validatorInfo', 'operatorAddress'], data); const selfDelegateAddress = R.pathOr('', ['validator', 0, 'validatorInfo', 'selfDelegateAddress'], data); const validator = findAddress(operatorAddress); - const profile = { - validator, + validator: { + moniker: validator.moniker, + imageUrl: R.pathOr('', ['imageUrl'], validator), + }, operatorAddress, selfDelegateAddress, description: R.pathOr('', ['validatorDescriptions', 0, 'details'], data.validator[0]), diff --git a/src/utils/get_validator_status.ts b/src/utils/get_validator_status.ts new file mode 100644 index 0000000000..bb0c772701 --- /dev/null +++ b/src/utils/get_validator_status.ts @@ -0,0 +1,25 @@ +export const getValidatorStatus = (status: number, jailed: boolean) => { + const results = { + status: 'na', + theme: 'zero', + }; + + if (status === 3) { + results.status = 'active'; + results.theme = 'one'; + } else if (status === 2 && jailed) { + results.status = 'jailed'; + results.theme = 'two'; + } else if (status === 2 && !jailed) { + results.status = 'unbonding'; + results.theme = 'three'; + } else if (status === 1) { + results.status = 'unbonded'; + results.theme = 'zero'; + } else { + results.status = 'unknown'; + results.theme = 'zero'; + } + + return results; +};