From a76d448bd2d25fe3148df9ddef17bc0acdcb8b3b Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Fri, 29 Sep 2023 14:38:05 -0600 Subject: [PATCH 01/41] feat(webapp): add new componets for BPs page redesign --- webapp/public/empty-states/Alert.webp | Bin 0 -> 758 bytes webapp/src/components/ComplianceBar/index.js | 47 ++++ webapp/src/components/ComplianceBar/styles.js | 10 + .../InformationCard/EmptyStateRow.js | 26 ++ .../InformationCard/MainSocialLinks.js | 29 +++ .../src/components/InformationCard/Media.js | 25 -- .../src/components/InformationCard/Nodes.js | 65 ----- .../InformationCard/ProducerInformation.js | 121 --------- .../src/components/InformationCard/Social.js | 31 --- .../src/components/InformationCard/Stats.js | 68 ------ .../src/components/InformationCard/index.js | 130 +++------- .../src/components/InformationCard/styles.js | 230 ++---------------- webapp/src/components/ProducerName/index.js | 23 ++ webapp/src/components/ProducerName/styles.js | 27 ++ webapp/src/routes/Faucet/index.js | 7 +- webapp/src/utils/formatData.js | 6 +- webapp/src/utils/validate-account-name.js | 7 + 17 files changed, 229 insertions(+), 623 deletions(-) create mode 100755 webapp/public/empty-states/Alert.webp create mode 100644 webapp/src/components/ComplianceBar/index.js create mode 100644 webapp/src/components/ComplianceBar/styles.js create mode 100644 webapp/src/components/InformationCard/EmptyStateRow.js create mode 100644 webapp/src/components/InformationCard/MainSocialLinks.js delete mode 100644 webapp/src/components/InformationCard/Media.js delete mode 100644 webapp/src/components/InformationCard/Nodes.js delete mode 100644 webapp/src/components/InformationCard/ProducerInformation.js delete mode 100644 webapp/src/components/InformationCard/Social.js delete mode 100644 webapp/src/components/InformationCard/Stats.js create mode 100644 webapp/src/components/ProducerName/index.js create mode 100644 webapp/src/components/ProducerName/styles.js create mode 100644 webapp/src/utils/validate-account-name.js diff --git a/webapp/public/empty-states/Alert.webp b/webapp/public/empty-states/Alert.webp new file mode 100755 index 0000000000000000000000000000000000000000..7860a007c3cec85ce4df5ff0f97d7961d955f196 GIT binary patch literal 758 zcmVYk*(zY$sjce-60HHr;_4ZH?g1FM77!5Kgqz!*XplA9n1tY6X1U;_YFP&gni0ssKe z4FH`1DmMT&06u{@l1HT@p`kVVn=qgd32Fen8NmKa`75%+%W1LsSzw=V2S5)${-NJM z4=XQX-?R^8mn@rbOgiz7@-Nu0U=Pp%&#-00nk>hD;O9=(&Bf95LF-ZI1ktO=TL0sA zo9k^)YHZoo#s8BT6@8-l&i~-bGD7Nw{(+3k13-+Ko#;`SDmKYx<(H(B^lL zaRwxKbB8}Ux)?$#c@SAp9`o0kyVTJCM#+EOgL@kcKM5}4&YILE=gQxA0ma5t zd8Iw>A!pC!oVZ2u%PhCIVSB_v2_otT3^RfVc8UMKy_Y8g+j)rMk2M~+(KKCw(Pv@2 zkY7p6gVH$I)WF^oUiQXiDsc~oGI6RnSus;K7m|DHr?ZAnRkMB*y!-Vz3*G##TP6l5 z>+`_K6ap3b5uxAUQ5od5R!lYWS7^Fy9<{9 literal 0 HcmV?d00001 diff --git a/webapp/src/components/ComplianceBar/index.js b/webapp/src/components/ComplianceBar/index.js new file mode 100644 index 000000000..69a33dfc7 --- /dev/null +++ b/webapp/src/components/ComplianceBar/index.js @@ -0,0 +1,47 @@ +import React from 'react' +import { makeStyles } from '@mui/styles' +import styles from './styles' +import styled from 'styled-components' +import Typography from '@mui/material/Typography' + +const useStyles = makeStyles(styles) + +const ComplianceBar = ({ pass, total }) => { + const classes = useStyles() + + const maxWidth = 120 + const percentage = pass / total ?? 0 + + const Bar = styled.div` + width: ${maxWidth}px; + height: 8px; + & div { + border-radius: ${(props) => props.theme.spacing(4)}; + position: relative; + height: 100%; + max-width: 100%; + } + ` + const CompliancePercentageBar = styled.div` + width: ${percentage * maxWidth}px; + top: -100%; + background-color: ${(props) => + percentage >= 0.8 + ? props.theme.palette.success.main + : percentage >= 0.5 + ? props.theme.palette.warning.main + : props.theme.palette.error.main}; + ` + + return ( +
+ {`${pass}/${total}`} + +
+ +
+
+ ) +} + +export default ComplianceBar diff --git a/webapp/src/components/ComplianceBar/styles.js b/webapp/src/components/ComplianceBar/styles.js new file mode 100644 index 000000000..9d4b769c4 --- /dev/null +++ b/webapp/src/components/ComplianceBar/styles.js @@ -0,0 +1,10 @@ +export default (theme) => ({ + container: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + bar: { + backgroundColor: '#E0E0E0', + }, +}) diff --git a/webapp/src/components/InformationCard/EmptyStateRow.js b/webapp/src/components/InformationCard/EmptyStateRow.js new file mode 100644 index 000000000..195bb4217 --- /dev/null +++ b/webapp/src/components/InformationCard/EmptyStateRow.js @@ -0,0 +1,26 @@ +import React, { memo } from 'react' +import PropTypes from 'prop-types' + +const EmptyStateRow = ({ classes, t }) => { + return ( +
+ + {t('emptyState')} +
+ ) +} + +EmptyStateRow.propTypes = { + classes: PropTypes.object, + t: PropTypes.func, +} + +EmptyStateRow.defaultProps = { + classes: {}, +} + +export default memo(EmptyStateRow) diff --git a/webapp/src/components/InformationCard/MainSocialLinks.js b/webapp/src/components/InformationCard/MainSocialLinks.js new file mode 100644 index 000000000..f9506650f --- /dev/null +++ b/webapp/src/components/InformationCard/MainSocialLinks.js @@ -0,0 +1,29 @@ +import React from 'react' +import { makeStyles } from '@mui/styles' +import styles from './styles' +import TwitterIcon from '@mui/icons-material/Twitter' +import GitHubIcon from '@mui/icons-material/GitHub' +import TelegramIcon from '@mui/icons-material/Telegram' +import Link from '@mui/material/Link' + +const useStyles = makeStyles(styles) + +const MainSocialLinks = ({ social }) => { + const classes = useStyles() + + return ( +
+ + + + + + + + + +
+ ) +} + +export default MainSocialLinks diff --git a/webapp/src/components/InformationCard/Media.js b/webapp/src/components/InformationCard/Media.js deleted file mode 100644 index afb9123bf..000000000 --- a/webapp/src/components/InformationCard/Media.js +++ /dev/null @@ -1,25 +0,0 @@ -import React, { memo } from 'react' -import PropTypes from 'prop-types' -import Typography from '@mui/material/Typography' - -import ProducerAvatar from '../ProducerAvatar' - -const Media = ({ media }) => { - return ( - <> - - {media.name} - {media.account?.toString()} - - ) -} - -Media.propTypes = { - media: PropTypes.object, -} - -Media.defaultProps = { - media: {}, -} - -export default memo(Media) diff --git a/webapp/src/components/InformationCard/Nodes.js b/webapp/src/components/InformationCard/Nodes.js deleted file mode 100644 index e285f15e8..000000000 --- a/webapp/src/components/InformationCard/Nodes.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, { memo } from 'react' -import PropTypes from 'prop-types' -import Typography from '@mui/material/Typography' - -import NodeCard from '../../components/NodeCard' -import MoreInfoModal from '../../components/MoreInfoModal' - -const Nodes = ({ nodes, producer, t, classes }) => { - if (!nodes?.length) { - return ( -
- {t('nodes')} -
- {t('noData')} -
-
- ) - } - - return ( -
- {t('nodes')} -
- <> - {nodes.map((node, index) => ( -
-
- - {node.name || node?.node_type?.toString() || 'node'}{' '} - - - - -
-
- ))} - -
-
- ) -} - -Nodes.propTypes = { - nodes: PropTypes.array, - producer: PropTypes.object, - onNodeClick: PropTypes.func, - classes: PropTypes.object, - t: PropTypes.func, -} - -Nodes.defaultProps = { - nodes: [], - producer: {}, - onNodeClick: () => {}, - classes: {}, -} - -export default memo(Nodes) diff --git a/webapp/src/components/InformationCard/ProducerInformation.js b/webapp/src/components/InformationCard/ProducerInformation.js deleted file mode 100644 index 463b1d377..000000000 --- a/webapp/src/components/InformationCard/ProducerInformation.js +++ /dev/null @@ -1,121 +0,0 @@ -import React, { memo } from 'react' -import PropTypes from 'prop-types' -import Link from '@mui/material/Link' -import Typography from '@mui/material/Typography' - -import CountryFlag from '../CountryFlag' -import MoreInfoModal from '../MoreInfoModal' -import VisitSite from '../VisitSite' - -const ProducerInformation = ({ info, classes, t }) => { - const URLModal = ({ data }) => { - if (!Array.isArray(data)) return <> - return ( - - {data.map((url, index) => ( -
- - {url} - -
- ))} -
- ) - } - - return ( - <> -
-
- {info?.location && info?.location !== 'N/A' && ( - - {`${t('location')}: ${info?.location} `} - - - )} -
-
- {!!info?.website ? ( - <> - - {t('website')}: - - - - ) : ( - - {t('website')}: N/A - - )} -
-
- {!!info?.email ? ( - <> - - {t('email')}: - - - - ) : ( - - {t('email')}: N/A - - )} -
-
- {!!info?.ownership ? ( - <> - - {t('ownershipDisclosure')}: - - - - ) : null} -
-
- {!!info?.code_of_conduct ? ( - <> - - {t('codeofconduct')}: - - - - ) : null} -
-
- {!!info?.chain ? ( - <> - - {t('chainResources')}: - - - - ) : null} -
-
- {!!info?.otherResources.length && ( - <> - - {t('otherResources')}: - - - - )} -
-
- - ) -} - -ProducerInformation.propTypes = { - info: PropTypes.object, - classes: PropTypes.object, - t: PropTypes.func, -} - -ProducerInformation.defaultProps = { - info: {}, - classes: {}, -} - -export default memo(ProducerInformation) diff --git a/webapp/src/components/InformationCard/Social.js b/webapp/src/components/InformationCard/Social.js deleted file mode 100644 index f2b26ff45..000000000 --- a/webapp/src/components/InformationCard/Social.js +++ /dev/null @@ -1,31 +0,0 @@ -import React, { memo } from 'react' -import PropTypes from 'prop-types' -import Typography from '@mui/material/Typography' - -import ProducerSocialLinks from '../ProducerSocialLinks' - -const Social = ({ social, type, t, classes }) => { - return ( -
- {t('social')} -
- -
-
- ) -} - -Social.propTypes = { - social: PropTypes.object, - t: PropTypes.func, - classes: PropTypes.object, - type: PropTypes.string, -} - -Social.defaultProps = { - type: '', - social: {}, - classes: {}, -} - -export default memo(Social) diff --git a/webapp/src/components/InformationCard/Stats.js b/webapp/src/components/InformationCard/Stats.js deleted file mode 100644 index 1386cd951..000000000 --- a/webapp/src/components/InformationCard/Stats.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { memo } from 'react' -import PropTypes from 'prop-types' -import Typography from '@mui/material/Typography' - -import { eosConfig, generalConfig } from '../../config' -import VisitSite from '../VisitSite' - -const Stats = ({ missedBlocks, t, classes, votes, rewards, eosRate }) => { - if (eosConfig.networkName === 'lacchain') return <> - - return ( -
- {t('stats')} -
-
- {`${t('votes')}: ${votes}`} -
- -
- {`${t('rewards')}: ${rewards} ${ - eosConfig.tokenSymbol - }`} -
- - {!!eosRate && ( -
- - {`${t('EOSRate')}: - ${eosRate.average.toFixed(2)} ${t('average')} - (${eosRate.ratings_cntr} ${t('ratings')})`} - - -
- )} - - {!!generalConfig.historyEnabled && ( -
- - {`${t('missedBlocks')}: `} - {missedBlocks || 0} - -
- )} -
-
- ) -} - -Stats.propTypes = { - missedBlocks: PropTypes.number, - t: PropTypes.func, - classes: PropTypes.object, - votes: PropTypes.string, - rewards: PropTypes.string, - type: PropTypes.string, -} - -Stats.defaultProps = { - updatedAt: '', - classes: {}, - votes: '0', - rewards: '0', -} - -export default memo(Stats) diff --git a/webapp/src/components/InformationCard/index.js b/webapp/src/components/InformationCard/index.js index 86132282b..4f6f0645d 100644 --- a/webapp/src/components/InformationCard/index.js +++ b/webapp/src/components/InformationCard/index.js @@ -1,6 +1,5 @@ /* eslint camelcase: 0 */ import React, { memo, useState, useEffect } from 'react' -import clsx from 'clsx' import PropTypes from 'prop-types' import { useTheme } from '@mui/material/styles' import { makeStyles } from '@mui/styles' @@ -16,16 +15,14 @@ import 'flag-icon-css/css/flag-icons.css' import { formatData, formatWithThousandSeparator } from '../../utils' import { eosConfig } from '../../config' -import ProducerHealthIndicators from '../ProducerHealthIndicators' import NodesCard from '../NodeCard/NodesCard' -import EmptyState from './EmptyState' -import ProducerInformation from './ProducerInformation' -import Nodes from './Nodes' -import Social from './Social' -import Media from './Media' -import Stats from './Stats' import styles from './styles' +import ProducerName from 'components/ProducerName' +import ComplianceBar from 'components/ComplianceBar' +import CountryFlag from 'components/CountryFlag' +import EmptyStateRow from './EmptyStateRow' +import MainSocialLinks from './MainSocialLinks' const useStyles = makeStyles(styles) @@ -41,83 +38,33 @@ const InformationCard = ({ producer, rank, type }) => { setExpanded(!expanded) } - const missedBlock = (producer, nodeType, type) => { - if (eosConfig.networkName !== 'lacchain') return <> - - if (type !== 'node' || nodeType !== 'validator') return <> - - return ( -
- - {`${t('missedBlocks')}: `} - {producer.missed_blocks || 0} - -
- ) - } - const BlockProducerInfo = () => { const bpJsonHealthStatus = producerOrg.healthStatus.find( (status) => status.name === 'bpJson', ) if (!bpJsonHealthStatus?.valid && eosConfig.networkName !== 'lacchain') - return + return return ( -
-
- {t('info')} - -
- + {' '} + {producerOrg?.info?.location} + {producerOrg?.info?.website} + {formatWithThousandSeparator( + producer?.total_votes_eos || '0', 0, - )} - rewards={formatWithThousandSeparator( - producer.total_rewards || '0', + )} + {`${formatWithThousandSeparator( + producer?.total_rewards || '0', 0, - )} - eosRate={producer?.eosRate} - /> - -
- {t('health')} -
- {missedBlock(producer, producerOrg?.media?.account, type)} - -
-
- + -
+ + ) } @@ -145,31 +92,34 @@ const InformationCard = ({ producer, rank, type }) => { return ( - +
-
- -
- - {type === 'node' ? ( + {`#${producer?.rank}`} + + {type === 'node' ? ( +
{' '}
- ) : ( - - )} -
+
+ ) : ( + + )}
- {!matches && ( + {type === 'node' && !matches && (
+ ) +} + +export default ViewBPProfile diff --git a/webapp/src/routes/ProducerProfile/index.js b/webapp/src/routes/ProducerProfile/index.js new file mode 100644 index 000000000..45ff8bea6 --- /dev/null +++ b/webapp/src/routes/ProducerProfile/index.js @@ -0,0 +1,35 @@ +import React from 'react' +import { useParams, Navigate } from 'react-router-dom' +import Helmet from 'react-helmet' +import { useTranslation } from 'react-i18next' + +import useProducerProfileState from './useProducerProfileState' +import EmptyState from 'components/InformationCard/EmptyState' +import { eosConfig } from 'config' + +const ProducerProfile = () => { + const { t } = useTranslation('producerCardComponent') + let { bpName } = useParams() + const [{ ldJson, producer }] = useProducerProfileState(bpName) + + if (!producer) return <> + + if (!producer?.owner) return + + const bpJsonHealthStatus = producer?.health_status?.find( + (status) => status.name === 'bpJson', + ) + + if (!bpJsonHealthStatus?.valid && eosConfig.networkName !== 'lacchain') + return + + return ( + <> + + {ldJson && } + + + ) +} + +export default ProducerProfile diff --git a/webapp/src/routes/ProducerProfile/useProducerProfileState.js b/webapp/src/routes/ProducerProfile/useProducerProfileState.js new file mode 100644 index 000000000..4880f50f3 --- /dev/null +++ b/webapp/src/routes/ProducerProfile/useProducerProfileState.js @@ -0,0 +1,60 @@ +import { useState, useEffect } from 'react' +import { useLazyQuery } from '@apollo/client' + +import { PRODUCERS_QUERY } from '../../gql' +import isValidAccountName from 'utils/validate-account-name' + +const useProducerProfileState = (name) => { + const defaultVariables = { + limit: 1, + offset: 0, + endpointFilter: undefined, + where: { + owner: { _eq: name }, + }, + } + const [ldJson, setLdJson] = useState() + const [loadProducers, { loading, data: { producers } = {} }] = + useLazyQuery(PRODUCERS_QUERY) + + const isValidName = isValidAccountName(name) + + useEffect(() => { + if (isValidName) { + loadProducers({ variables: defaultVariables }) + } + // eslint-disable-next-line + }, []) + + useEffect(() => { + if (!producers || !producers.length) return + + const bp = producers.at(0)?.bp_json?.org + + if (!bp) return + + setLdJson( + JSON.stringify({ + '@context': 'http://schema.org', + '@type': 'Organization', + name: bp.candidate_name, + url: bp.website, + contactPoint: { + '@type': 'ContactPoint', + email: bp.email, + contactType: 'customer service', + }, + logo: bp.branding.logo_256, + }), + ) + }, [producers]) + + const getProducer = (producers) => + !isValidName || (Array.isArray(producers) && !producers.length) + ? {} + : producers?.at(0) + + return [{ loading, producer: getProducer(producers), ldJson }, {}] +} + +export default useProducerProfileState diff --git a/webapp/src/routes/index.js b/webapp/src/routes/index.js index 40c306d55..f064c02ad 100644 --- a/webapp/src/routes/index.js +++ b/webapp/src/routes/index.js @@ -53,6 +53,7 @@ const EVMEndpointsList = lazy(() => import('./EVMEndpointsList')) const LacchainNetwork = lazy(() => import('./Lacchain/LacchainNetwork')) const LacchainManagement = lazy(() => import('./Lacchain/LacchainManagement')) const LacchainNodeConfig = lazy(() => import('./Lacchain/LacchainNodeConfig')) +const ProducerProfile = lazy(() => import('./ProducerProfile')) const defaultRoutes = [ { @@ -70,6 +71,11 @@ const defaultRoutes = [ path: '/block-producers', exact: true, }, + { + component: ProducerProfile, + path: '/block-producers/:bpName', + exact: true, + }, { name: 'nonCompliantBPs', icon: , From 94a6ffbdb9887e467f9adc821c25ed4bc81ce53a Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 2 Oct 2023 13:22:35 -0600 Subject: [PATCH 03/41] feat(webapp): add BP profile data and reduce the data needed for BPs list page --- .../ProducerHealthIndicators/index.js | 6 +- .../components/ProducerSocialLinks/index.js | 40 +++- .../src/components/SimpleDataCard/styles.js | 3 +- webapp/src/components/VisitSite/index.js | 1 + webapp/src/gql/producer.gql.js | 55 +++++- .../customHooks/useBlockProducerState.js | 41 +--- webapp/src/routes/BlockProducers/index.js | 4 +- webapp/src/routes/ProducerProfile/index.js | 186 +++++++++++++++++- webapp/src/routes/ProducerProfile/styles.js | 48 +++++ 9 files changed, 320 insertions(+), 64 deletions(-) create mode 100644 webapp/src/routes/ProducerProfile/styles.js diff --git a/webapp/src/components/ProducerHealthIndicators/index.js b/webapp/src/components/ProducerHealthIndicators/index.js index 2a498afe6..bf9bbc3a8 100644 --- a/webapp/src/components/ProducerHealthIndicators/index.js +++ b/webapp/src/components/ProducerHealthIndicators/index.js @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next' import Typography from '@mui/material/Typography' import LightIcon from '../HealthCheck/LightIcon' +import { generalConfig } from '../../config' import styles from './styles' @@ -14,6 +15,7 @@ const useStyles = makeStyles(styles) const ProducerHealthIndicators = ({ producer, message }) => { const classes = useStyles() const { t } = useTranslation('producerHealthIndicatorsComponent') + const { healthLights } = generalConfig if (!producer.health_status.length) return {message} @@ -25,8 +27,8 @@ const ProducerHealthIndicators = ({ producer, message }) => { key={`health-indicator-${producer?.owner || ''}-${index}`} > {`${t(`hs_${item.name}`)}`} - {item.valid && } - {!item.valid && } + {item.valid && } + {!item.valid && }
))} diff --git a/webapp/src/components/ProducerSocialLinks/index.js b/webapp/src/components/ProducerSocialLinks/index.js index 0346cf9ca..e81e100ae 100644 --- a/webapp/src/components/ProducerSocialLinks/index.js +++ b/webapp/src/components/ProducerSocialLinks/index.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { memo } from 'react' import PropTypes from 'prop-types' import Link from '@mui/material/Link' import LanguageIcon from '@mui/icons-material/Language' @@ -8,7 +8,6 @@ import FacebookIcon from '@mui/icons-material/Facebook' import GitHubIcon from '@mui/icons-material/GitHub' import RedditIcon from '@mui/icons-material/Reddit' import TelegramIcon from '@mui/icons-material/Telegram' -import Typography from '@mui/material/Typography' const prefix = { hive: 'https://hive.blog/@', @@ -34,26 +33,47 @@ const icons = { telegram: , } -const ProducerSocialLinks = ({ items, message }) => { - const itemsArray = Object.keys(items).filter((key) => !!items[key]) +const order = { + twitter: 0, + github: 1, + telegram: 2, + medium: 3, + youtube: 4, + reddit: 5, + wechat: 6, + keybase: 7, + steemit: 8, + hive: 9, + facebook: 10, +} + +const getOrder = name => order[name] ?? Infinity - if (!itemsArray?.length) return {message} +const ProducerSocialLinks = ({ items }) => { + const itemsArray = Object.keys(items).sort((a,b) => getOrder(a) - getOrder(b)).flatMap((key) => + !!items[key] + ? { + name: key, + url: `${prefix[key] ?? 'https://' + key + '/'}${items[key]}`, + } + : [], + ) - return itemsArray.map((key, i) => ( + return itemsArray.map((items, i) => ( - {icons[key] || } {key} + {icons[items.name] || } )) } ProducerSocialLinks.propTypes = { items: PropTypes.object, - message: PropTypes.string, } -export default ProducerSocialLinks +export default memo(ProducerSocialLinks) diff --git a/webapp/src/components/SimpleDataCard/styles.js b/webapp/src/components/SimpleDataCard/styles.js index 1fe7acf4f..c7e6826c3 100644 --- a/webapp/src/components/SimpleDataCard/styles.js +++ b/webapp/src/components/SimpleDataCard/styles.js @@ -38,7 +38,8 @@ export default (theme) => ({ display: 'flex', justifyContent: 'center', textAlign: 'center', - marginTop: `${theme.spacing(2)} !important`, + height: '100%', + alignItems: 'center', }, svgLink: { fontSize: 18, diff --git a/webapp/src/components/VisitSite/index.js b/webapp/src/components/VisitSite/index.js index 0004149be..530af28b6 100644 --- a/webapp/src/components/VisitSite/index.js +++ b/webapp/src/components/VisitSite/index.js @@ -17,6 +17,7 @@ const VisitSite = ({ title, url, placement = 'left' }) => { target="_blank" rel="noopener noreferrer" className={classes.link} + aria-label={url} > diff --git a/webapp/src/gql/producer.gql.js b/webapp/src/gql/producer.gql.js index 1c3dfe601..57720aa27 100644 --- a/webapp/src/gql/producer.gql.js +++ b/webapp/src/gql/producer.gql.js @@ -35,6 +35,52 @@ export const PRODUCERS_QUERY = gql` type value } + nodes { + type + full + location + node_info { + features + version + } + endpoints(order_by: { type: asc }) { + value + type + response + updated_at + } + } + + } + } +` + +export const SMALL_PRODUCERS_QUERY = gql` + query producer( + $offset: Int = 0 + $limit: Int = 21 + $where: producer_bool_exp + $endpointFilter: endpoints_by_producer_id_bool_exp + ) { + info: producer_aggregate(where: $where) { + producers: aggregate { + count + } + } + producers: producer( + where: $where + order_by: { total_votes_percent: desc } + offset: $offset + limit: $limit + ) { + id + owner + url + bp_json + total_votes_eos + total_rewards + health_status + rank } } ` @@ -153,15 +199,6 @@ export const BLOCK_TRANSACTIONS_HISTORY = gql` } ` -export const MISSED_BLOCKS_SUBSCRIPTION = gql` - subscription { - stats: stat(limit: 1) { - id - missed_blocks - } - } -` - export const NODES_SUMMARY_QUERY = gql` query { stats: stat(limit: 1) { diff --git a/webapp/src/hooks/customHooks/useBlockProducerState.js b/webapp/src/hooks/customHooks/useBlockProducerState.js index 3acb80810..4050c0c08 100644 --- a/webapp/src/hooks/customHooks/useBlockProducerState.js +++ b/webapp/src/hooks/customHooks/useBlockProducerState.js @@ -1,11 +1,7 @@ import { useState, useEffect } from 'react' -import { useLazyQuery, useSubscription } from '@apollo/client' +import { useLazyQuery } from '@apollo/client' -import { - PRODUCERS_QUERY, - MISSED_BLOCKS_SUBSCRIPTION, - EOSRATE_STATS_QUERY, -} from '../../gql' +import { SMALL_PRODUCERS_QUERY } from '../../gql' import { eosConfig } from '../../config' import useSearchState from './useSearchState' @@ -33,24 +29,17 @@ const useBlockProducerState = () => { where: { owner: { _like: '%%' }, nodes: { endpoints: { value: { _gt: '' } } }, - } + }, } const [variables, setVariables] = useState(defaultVariables) - const [loadProducers, { data: { info, producers } = {} }] = useLazyQuery(PRODUCERS_QUERY, { variables }) - const { data: dataHistory } = useSubscription(MISSED_BLOCKS_SUBSCRIPTION) - const [loadStats, { loading = true, data: { eosrate_stats: stats } = {} }] = - useLazyQuery(EOSRATE_STATS_QUERY) + const [loadProducers, { loading, data: { info, producers } = {} }] = + useLazyQuery(SMALL_PRODUCERS_QUERY, { variables }) const [items, setItems] = useState([]) - const [missedBlocks, setMissedBlocks] = useState({}) const [ { filters, pagination }, { handleOnSearch, handleOnPageChange, setPagination }, ] = useSearchState({ loadProducers, info, variables, setVariables }) - useEffect(() => { - loadStats({}) - }, [loadStats]) - const chips = CHIPS_NAMES.map((e) => { return { name: e } }) @@ -82,25 +71,8 @@ const useBlockProducerState = () => { ) } - if (newItems?.length && stats?.length) { - newItems = newItems.map((producer) => { - return { - ...producer, - eosRate: Object.keys(producer.bp_json).length - ? stats.find((rate) => rate.bp === producer.owner) - : undefined, - } - }) - } - setItems(newItems) - }, [filters.name, stats, producers]) - - useEffect(() => { - if (dataHistory?.stats.length) { - setMissedBlocks(dataHistory?.stats[0].missed_blocks) - } - }, [dataHistory]) + }, [filters.name, producers]) return [ { @@ -108,7 +80,6 @@ const useBlockProducerState = () => { chips, items, loading, - missedBlocks, pagination, }, { diff --git a/webapp/src/routes/BlockProducers/index.js b/webapp/src/routes/BlockProducers/index.js index 6d927c232..8873865a0 100644 --- a/webapp/src/routes/BlockProducers/index.js +++ b/webapp/src/routes/BlockProducers/index.js @@ -47,7 +47,7 @@ PaginationWrapper.propTypes = { const Producers = () => { const classes = useStyles() const [ - { filters, chips, items, loading, missedBlocks, pagination }, + { filters, chips, items, loading, pagination }, { handleOnSearch, handleOnPageChange }, ] = useBlockProducerState() @@ -71,7 +71,7 @@ const Producers = () => {
diff --git a/webapp/src/routes/ProducerProfile/index.js b/webapp/src/routes/ProducerProfile/index.js index 45ff8bea6..dc9c6e263 100644 --- a/webapp/src/routes/ProducerProfile/index.js +++ b/webapp/src/routes/ProducerProfile/index.js @@ -2,19 +2,36 @@ import React from 'react' import { useParams, Navigate } from 'react-router-dom' import Helmet from 'react-helmet' import { useTranslation } from 'react-i18next' +import { makeStyles } from '@mui/styles' +import Typography from '@mui/material/Typography' +import Link from '@mui/material/Link' -import useProducerProfileState from './useProducerProfileState' +import { eosConfig, generalConfig } from '../../config' +import ProducerName from 'components/ProducerName' +import SimpleDataCard from 'components/SimpleDataCard' +import NodesCard from 'components/NodeCard/NodesCard' +import ProducerSocialLinks from 'components/ProducerSocialLinks' import EmptyState from 'components/InformationCard/EmptyState' -import { eosConfig } from 'config' +import { formatWithThousandSeparator } from 'utils' +import LightIcon from 'components/HealthCheck/LightIcon' +import VisitSite from 'components/VisitSite' +import MoreInfoModal from 'components/MoreInfoModal' + +import styles from './styles' +import useProducerProfileState from './useProducerProfileState' + +const useStyles = makeStyles(styles) const ProducerProfile = () => { + const classes = useStyles() const { t } = useTranslation('producerCardComponent') let { bpName } = useParams() const [{ ldJson, producer }] = useProducerProfileState(bpName) + const { healthLights } = generalConfig - if (!producer) return <> + if (!producer) return <> - if (!producer?.owner) return + if (!producer?.owner) return const bpJsonHealthStatus = producer?.health_status?.find( (status) => status.name === 'bpJson', @@ -23,11 +40,170 @@ const ProducerProfile = () => { if (!bpJsonHealthStatus?.valid && eosConfig.networkName !== 'lacchain') return + const GeneralInformation = ({ producer }) => { + const eosRate = producer?.eosRate + + return ( + <> + + + + {eosRate && ( + + + + {` ${t('average')} (${eosRate.ratings_cntr} ${t('ratings')})`} + + + + + )} + +
+ {producer?.health_status?.map((item, index) => ( + + +

{t(item.name)}

+
+ ))} +
+
+ + ) + } + + const WrapperContainer = ({ title, children }) => { + return ( +
+ {title} +
{children}
+
+ ) + } + + const URLModal = ({ data }) => { + if (!Array.isArray(data)) return <> + return ( + + {data.map((url, index) => ( + + ))} + + ) + } + + const OrganizationData = ({ producer }) => { + return ( +
+ +

+ {t('location')}
+ {producer?.bp_json?.org?.location?.name} +

+
+ +

+ {t('email')}
+ + {producer?.bp_json?.org?.email} + +

+
+ +

+ {t('website')}
+ + {producer?.bp_json?.org?.website} + +

+
+ +

+ {t('codeofconduct')}
+ +

+
+ +

+ {t('ownershipDisclosure')}
+ +

+
+ +

+ {t('chainResources')}
+ +

+
+ {producer?.bp_json?.org?.other_resources?.length && ( + +

+ {t('otherResources')}
+ +

+
+ )} +
+ ) + } + return ( <> - + {ldJson && } +
+ {Object.keys(producer?.bp_json?.org?.social || {})?.length && ( +
+ +
+ )} + +
+ + + + + + + ) } diff --git a/webapp/src/routes/ProducerProfile/styles.js b/webapp/src/routes/ProducerProfile/styles.js new file mode 100644 index 000000000..b90a72158 --- /dev/null +++ b/webapp/src/routes/ProducerProfile/styles.js @@ -0,0 +1,48 @@ +export default (theme) => ({ + card: {}, + container: { + display: 'flex', + flexWrap: 'nowrap', + gap: theme.spacing(4), + }, + profile: { + display: 'flex', + flexDirection: 'column', + padding: theme.spacing(6), + }, + socialLinks: { + display: 'flex', + gap: theme.spacing(5), + alignSelf: 'end', + }, + healthContainer: { + display: 'flex', + flexWrap: 'wrap', + marginTop: theme.spacing(4), + }, + healthIndicator: { + display: 'flex', + flexDirection: 'column', + width: 'calc( 100% / 3 )', + textAlign: 'center', + '& > *': { + alignSelf: 'center', + }, + '& > p': { + margin: 0, + }, + '& > svg': { + width: '30px', + height: '30px', + }, + }, + OrgDataContainer: { + display: 'flex', + }, + OrgDataItem: { + display: 'flex', + justifyContent: 'center', + textAlign: 'center', + flexGrow: '1', + } +}) From d1c54ec36e000cb3ec68f17c641274192da640c7 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 2 Oct 2023 14:28:01 -0600 Subject: [PATCH 04/41] chore(webapp): add cards and resize BP logo --- .../src/components/InformationCard/index.js | 4 +- webapp/src/components/NodeCard/NodesCard.js | 21 +-- webapp/src/components/ProducerName/index.js | 13 +- webapp/src/components/ProducerName/styles.js | 12 +- webapp/src/routes/ProducerProfile/index.js | 131 +++++++++--------- webapp/src/routes/ProducerProfile/styles.js | 13 +- 6 files changed, 111 insertions(+), 83 deletions(-) diff --git a/webapp/src/components/InformationCard/index.js b/webapp/src/components/InformationCard/index.js index 542b5927e..7b63fb86c 100644 --- a/webapp/src/components/InformationCard/index.js +++ b/webapp/src/components/InformationCard/index.js @@ -103,7 +103,7 @@ const InformationCard = ({ producer, rank, type }) => { {`#${producer?.rank}`} {type === 'node' ? ( @@ -114,7 +114,7 @@ const InformationCard = ({ producer, rank, type }) => { className={classes.collapse} >
- {' '} + {' '}
) : ( diff --git a/webapp/src/components/NodeCard/NodesCard.js b/webapp/src/components/NodeCard/NodesCard.js index 02fd61c87..f3043c331 100644 --- a/webapp/src/components/NodeCard/NodesCard.js +++ b/webapp/src/components/NodeCard/NodesCard.js @@ -21,7 +21,7 @@ import EndpointsChips from './EndpointsChips' const useStyles = makeStyles(styles) -const NodesCard = ({ nodes }) => { +const NodesCard = ({ nodes, hideFeatures = false }) => { const classes = useStyles() const { t } = useTranslation('nodeCardComponent') @@ -61,7 +61,7 @@ const NodesCard = ({ nodes }) => { ) } - const NodeInfo = ({ node }) => { + const NodeInfo = ({ node, hideFeatures }) => { return ( <> @@ -77,11 +77,15 @@ const NodesCard = ({ nodes }) => { )} - - + {!hideFeatures && ( + <> + + + + )} @@ -143,7 +147,7 @@ const NodesCard = ({ nodes }) => { subheader={showLocations(node)} /> - + ))} @@ -153,5 +157,6 @@ const NodesCard = ({ nodes }) => { NodesCard.propTypes = { nodes: PropTypes.array, + hideFeatures: PropTypes.bool } export default NodesCard diff --git a/webapp/src/components/ProducerName/index.js b/webapp/src/components/ProducerName/index.js index 2bff8633a..e09bab3e4 100644 --- a/webapp/src/components/ProducerName/index.js +++ b/webapp/src/components/ProducerName/index.js @@ -6,15 +6,22 @@ import Typography from '@mui/material/Typography' const useStyles = makeStyles(styles) -const ProducerName = ({ name, logo, position }) => { +const ProducerName = ({ name, logo, text, account = '', size = 'small' }) => { const classes = useStyles() return (
- +
+ {account && {account}} {name} - {position} + {text}
) diff --git a/webapp/src/components/ProducerName/styles.js b/webapp/src/components/ProducerName/styles.js index ed917d800..6bb5a14c4 100644 --- a/webapp/src/components/ProducerName/styles.js +++ b/webapp/src/components/ProducerName/styles.js @@ -4,17 +4,15 @@ export default (theme) => ({ gap: theme.spacing(2), '& img': { borderRadius: '50%', - width: '56px', - height: '56px', aspectRatio: '1 / 1', }, }, nameContainer: { display: 'flex', flexDirection: 'column', + justifyContent: 'center', width: '200px', }, - socialContainer: { display: 'flex', gap: theme.spacing(4), @@ -24,4 +22,12 @@ export default (theme) => ({ cursor: 'pointer', }, }, + smallAvatar: { + width: '56px', + height: '56px', + }, + bigAvatar: { + width: '104px', + height: '104px', + }, }) diff --git a/webapp/src/routes/ProducerProfile/index.js b/webapp/src/routes/ProducerProfile/index.js index dc9c6e263..4928d0668 100644 --- a/webapp/src/routes/ProducerProfile/index.js +++ b/webapp/src/routes/ProducerProfile/index.js @@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next' import { makeStyles } from '@mui/styles' import Typography from '@mui/material/Typography' import Link from '@mui/material/Link' +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' import { eosConfig, generalConfig } from '../../config' import ProducerName from 'components/ProducerName' @@ -99,10 +101,12 @@ const ProducerProfile = () => { const WrapperContainer = ({ title, children }) => { return ( -
- {title} -
{children}
-
+ + + {title} +
{children}
+
+
) } @@ -111,7 +115,7 @@ const ProducerProfile = () => { return ( {data.map((url, index) => ( -
+
{url} @@ -121,66 +125,65 @@ const ProducerProfile = () => { ) } - const OrganizationData = ({ producer }) => { + const OrganizationItem = ({ title, value, url = '', type = 'text' }) => { return ( -
- -

- {t('location')}
- {producer?.bp_json?.org?.location?.name} -

-
- -

- {t('email')}
- - {producer?.bp_json?.org?.email} - -

-
- -

- {t('website')}
- - {producer?.bp_json?.org?.website} + +

+ {title}
+ {type === 'text' && value} + {type === 'hiddenLink' && } + {type === 'modal' && } + {type === 'link' && ( + + {value} -

-
- -

- {t('codeofconduct')}
- -

-
- -

- {t('ownershipDisclosure')}
- -

-
- -

- {t('chainResources')}
- -

-
+ )} +

+ + ) + } + + const OrganizationData = ({ producer }) => { + return ( + + + + + + + {producer?.bp_json?.org?.other_resources?.length && ( - -

- {t('otherResources')}
- -

-
+ )} -
+ ) } @@ -189,14 +192,14 @@ const ProducerProfile = () => { {ldJson && } -
+ {Object.keys(producer?.bp_json?.org?.social || {})?.length && (
)} - -
+ + diff --git a/webapp/src/routes/ProducerProfile/styles.js b/webapp/src/routes/ProducerProfile/styles.js index b90a72158..0176b241f 100644 --- a/webapp/src/routes/ProducerProfile/styles.js +++ b/webapp/src/routes/ProducerProfile/styles.js @@ -1,14 +1,17 @@ export default (theme) => ({ - card: {}, container: { display: 'flex', - flexWrap: 'nowrap', + flexWrap: 'wrap', gap: theme.spacing(4), + marginTop: theme.spacing(5), }, profile: { display: 'flex', flexDirection: 'column', padding: theme.spacing(6), + '& > div + div': { + padding: theme.spacing(0, 6), + } }, socialLinks: { display: 'flex', @@ -38,11 +41,15 @@ export default (theme) => ({ }, OrgDataContainer: { display: 'flex', + flexWrap: 'wrap', }, OrgDataItem: { display: 'flex', justifyContent: 'center', textAlign: 'center', flexGrow: '1', - } + '& > p > span': { + textTransform: 'uppercase', + }, + }, }) From fad53c8899ded6ac90a5eba47d1d17b372efb428 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 2 Oct 2023 15:36:10 -0600 Subject: [PATCH 05/41] feat(webapp): adjust nodes and flags styles --- webapp/src/components/CountryFlag/styles.js | 10 ++++++++-- webapp/src/components/InformationCard/index.js | 7 +++++-- .../src/components/InformationCard/styles.js | 7 +------ webapp/src/components/NodeCard/styles.js | 7 +++++-- webapp/src/components/ProducerName/index.js | 2 +- webapp/src/components/ProducerName/styles.js | 5 +++-- webapp/src/components/ViewBPProfile/index.js | 9 ++++++--- webapp/src/language/en.json | 4 +++- webapp/src/language/en.lacchain.json | 4 +++- webapp/src/language/es.json | 4 +++- webapp/src/language/es.lacchain.json | 4 +++- webapp/src/routes/ProducerProfile/index.js | 18 +++++++++++++++--- webapp/src/routes/ProducerProfile/styles.js | 10 +++++++--- 13 files changed, 63 insertions(+), 28 deletions(-) diff --git a/webapp/src/components/CountryFlag/styles.js b/webapp/src/components/CountryFlag/styles.js index 3506c5341..164837b39 100644 --- a/webapp/src/components/CountryFlag/styles.js +++ b/webapp/src/components/CountryFlag/styles.js @@ -1,6 +1,12 @@ export default (theme) => ({ country: { marginRight: theme.spacing(0.5), - marginLeft: theme.spacing(1) - } + marginLeft: theme.spacing(1), + '& .flag-icon': { + borderRadius: '50%', + width: '24px !important', + height: '24px !important', + top: '-5px', + }, + }, }) diff --git a/webapp/src/components/InformationCard/index.js b/webapp/src/components/InformationCard/index.js index 7b63fb86c..f768b806e 100644 --- a/webapp/src/components/InformationCard/index.js +++ b/webapp/src/components/InformationCard/index.js @@ -49,8 +49,8 @@ const InformationCard = ({ producer, rank, type }) => { return ( <> - {' '} {producerOrg?.info?.location} + {' '} {producerOrg?.info?.website} {formatWithThousandSeparator( producer?.total_votes_eos || '0', @@ -65,7 +65,7 @@ const InformationCard = ({ producer, rank, type }) => { pass={producerOrg?.compliance?.pass} /> - + ) } @@ -107,6 +107,7 @@ const InformationCard = ({ producer, rank, type }) => { name={producerOrg?.media?.name} /> {type === 'node' ? ( + <> { {' '}
+ + ) : ( )} diff --git a/webapp/src/components/InformationCard/styles.js b/webapp/src/components/InformationCard/styles.js index e41ba3ea2..65064613b 100644 --- a/webapp/src/components/InformationCard/styles.js +++ b/webapp/src/components/InformationCard/styles.js @@ -37,11 +37,6 @@ export default (theme) => ({ lineHeight: '0', }, }, - '& .flag-icon': { - borderRadius: '50%', - width: '24px !important', - height: '24px !important', - }, }, nodesContainer: { width: '100%', @@ -100,7 +95,7 @@ export default (theme) => ({ flexDirection: 'column !important', }, collapse: { - width: '100%', + width: '50%', }, infoIcon: { cursor: 'pointer', diff --git a/webapp/src/components/NodeCard/styles.js b/webapp/src/components/NodeCard/styles.js index e6826f4f0..5d56ec73d 100644 --- a/webapp/src/components/NodeCard/styles.js +++ b/webapp/src/components/NodeCard/styles.js @@ -33,15 +33,18 @@ export default (theme) => ({ width: '280px', padding: theme.spacing(0, 3, 0), overflowX: 'auto', - borderLeft: '1px solid rgba(0, 0, 0, 0.2)', '& .MuiCardContent-root:last-child': { paddingBottom: theme.spacing(4), }, + boxShadow: '2px 3px 4px 0px #0000002E', + backgroundColor: '#f6f9fd', + borderRadius: theme.spacing(3), }, nodesWrapper: { display: 'flex', width: 'max-content', - flexFlow: 'row nowrap', + flexFlow: 'row wrap', + gap: theme.spacing(2), padding: theme.spacing(0, 2, 0), [theme.breakpoints.up('lg')]: { paddingRight: '250px', diff --git a/webapp/src/components/ProducerName/index.js b/webapp/src/components/ProducerName/index.js index e09bab3e4..176788aa3 100644 --- a/webapp/src/components/ProducerName/index.js +++ b/webapp/src/components/ProducerName/index.js @@ -10,7 +10,7 @@ const ProducerName = ({ name, logo, text, account = '', size = 'small' }) => { const classes = useStyles() return ( -
+
({ - container: { + producerNameContainer: { display: 'flex', - gap: theme.spacing(2), + gap: theme.spacing(3), '& img': { borderRadius: '50%', aspectRatio: '1 / 1', @@ -29,5 +29,6 @@ export default (theme) => ({ bigAvatar: { width: '104px', height: '104px', + border: `solid 2px ${theme.palette.primary.main}`, }, }) diff --git a/webapp/src/components/ViewBPProfile/index.js b/webapp/src/components/ViewBPProfile/index.js index a62c78f6f..818879000 100644 --- a/webapp/src/components/ViewBPProfile/index.js +++ b/webapp/src/components/ViewBPProfile/index.js @@ -2,19 +2,22 @@ import React from 'react' import Button from '@mui/material/Button' import { Link } from 'react-router-dom' import LaunchIcon from '@mui/icons-material/Launch' +import { useTranslation } from 'react-i18next' + +const ViewBPProfile = ({ producer }) => { + const { t } = useTranslation('producerCardComponent') -const ViewBPProfile = ({ name, producer }) => { return ( ) } diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json index 5909724c1..f87b867ef 100644 --- a/webapp/src/language/en.json +++ b/webapp/src/language/en.json @@ -328,7 +328,9 @@ "invalidUrl": "Invalid URL", "viewList": "View undiscoverable BPs list", "bpJson": "BP.json", - "bpJsonGenerator": "BP JSON Generator" + "bpJsonGenerator": "BP JSON Generator", + "viewProfile": "View BP Profile", + "BPonNetwork": "Block Producer on {{networkName}}" }, "nodeCardComponent": { "features": "Features", diff --git a/webapp/src/language/en.lacchain.json b/webapp/src/language/en.lacchain.json index 35860aeef..22c143aa1 100644 --- a/webapp/src/language/en.lacchain.json +++ b/webapp/src/language/en.lacchain.json @@ -46,7 +46,9 @@ "producerCardComponent": { "entityType": "Entity type", "entityType1": "Partner", - "entityType2": "Non-Partner" + "entityType2": "Non-Partner", + "viewProfile": "View Entity Profile", + "BPonNetwork": "Entity on {{networkName}}" }, "lacchainAddEntityActionEntityTypeFieldComponent": { "entityType1": "Partner", diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json index f010cc1fa..6cbe331ca 100644 --- a/webapp/src/language/es.json +++ b/webapp/src/language/es.json @@ -331,7 +331,9 @@ "invalidUrl": "URL inválida", "viewList": "Ver lista de BPs indetectables", "bpJson": "BP.json", - "bpJsonGenerator": "Generar BP JSON" + "bpJsonGenerator": "Generar BP JSON", + "viewProfile": "Ver Perfil del BP", + "BPonNetwork": "Productor de Bloques en {{networkName}}" }, "nodeCardComponent": { "features": "Características", diff --git a/webapp/src/language/es.lacchain.json b/webapp/src/language/es.lacchain.json index dafad57c6..c357e15b6 100644 --- a/webapp/src/language/es.lacchain.json +++ b/webapp/src/language/es.lacchain.json @@ -32,7 +32,9 @@ "producerCardComponent": { "entityType": "Tipo de entidad", "entityType1": "Partner", - "entityType2": "Non-Partner" + "entityType2": "Non-Partner", + "viewProfile": "View Perfil de la entidad", + "BPonNetwork": "Entidad en {{networkName}}" }, "lacchainAddEntityActionEntityTypeFieldComponent": { "entityType1": "Partner", diff --git a/webapp/src/routes/ProducerProfile/index.js b/webapp/src/routes/ProducerProfile/index.js index 4928d0668..8c8e10279 100644 --- a/webapp/src/routes/ProducerProfile/index.js +++ b/webapp/src/routes/ProducerProfile/index.js @@ -18,6 +18,7 @@ import { formatWithThousandSeparator } from 'utils' import LightIcon from 'components/HealthCheck/LightIcon' import VisitSite from 'components/VisitSite' import MoreInfoModal from 'components/MoreInfoModal' +import CountryFlag from 'components/CountryFlag' import styles from './styles' import useProducerProfileState from './useProducerProfileState' @@ -104,7 +105,7 @@ const ProducerProfile = () => { {title} -
{children}
+
{children}
) @@ -148,7 +149,12 @@ const ProducerProfile = () => { + {producer?.bp_json?.org?.location?.name} + {' '} + + } /> {
)} - + diff --git a/webapp/src/routes/ProducerProfile/styles.js b/webapp/src/routes/ProducerProfile/styles.js index 0176b241f..1f7db46c6 100644 --- a/webapp/src/routes/ProducerProfile/styles.js +++ b/webapp/src/routes/ProducerProfile/styles.js @@ -1,17 +1,21 @@ export default (theme) => ({ - container: { + dataContainer: { display: 'flex', flexWrap: 'wrap', gap: theme.spacing(4), marginTop: theme.spacing(5), + '& > div': { + padding: 0, + gap: theme.spacing(4), + }, }, profile: { display: 'flex', flexDirection: 'column', padding: theme.spacing(6), '& > div + div': { - padding: theme.spacing(0, 6), - } + padding: theme.spacing(0, 6), + }, }, socialLinks: { display: 'flex', From 727c9011a9c91659b14a09111f46d901ad784008 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Tue, 3 Oct 2023 17:21:01 -0600 Subject: [PATCH 06/41] feat(webapp): refactor formatData, subscribe to nodes changes and query eosRate stats --- .../InformationCard/MainSocialLinks.js | 39 +++-- .../src/components/InformationCard/index.js | 80 +++++---- webapp/src/components/NodeCard/styles.js | 8 +- webapp/src/components/ProducerName/index.js | 5 +- webapp/src/components/ProducerName/styles.js | 5 + .../components/ProducerSocialLinks/index.js | 48 +----- webapp/src/gql/producer.gql.js | 50 +++++- webapp/src/hooks/customHooks/useNodeState.js | 42 +---- webapp/src/language/en.json | 5 +- webapp/src/language/es.json | 4 +- webapp/src/routes/ProducerProfile/index.js | 91 +++++----- webapp/src/routes/ProducerProfile/styles.js | 14 ++ .../useProducerProfileState.js | 102 +++++++++--- webapp/src/utils/formatData.js | 155 +++++++++--------- webapp/src/utils/sort-nodes.js | 46 ++++++ 15 files changed, 414 insertions(+), 280 deletions(-) create mode 100644 webapp/src/utils/sort-nodes.js diff --git a/webapp/src/components/InformationCard/MainSocialLinks.js b/webapp/src/components/InformationCard/MainSocialLinks.js index f9506650f..f3361d3c3 100644 --- a/webapp/src/components/InformationCard/MainSocialLinks.js +++ b/webapp/src/components/InformationCard/MainSocialLinks.js @@ -4,24 +4,41 @@ import styles from './styles' import TwitterIcon from '@mui/icons-material/Twitter' import GitHubIcon from '@mui/icons-material/GitHub' import TelegramIcon from '@mui/icons-material/Telegram' -import Link from '@mui/material/Link' +import Link from '@mui/material/Link' const useStyles = makeStyles(styles) -const MainSocialLinks = ({ social }) => { +const MainSocialLinks = ({ social, name }) => { const classes = useStyles() + const socialMediaNames = ['twitter', 'github', 'telegram'] + const links = {} + + const icons = { + twitter: , + github: , + telegram: , + } + + social.forEach((item, index) => { + if (index > socialMediaNames.length || !socialMediaNames.includes(item?.name)) return + links[item?.name] = item.url + }) return (
- - - - - - - - - + {socialMediaNames.map( + (socialMedia) => + links[socialMedia] && ( + + {icons[socialMedia]} + + ), + )}
) } diff --git a/webapp/src/components/InformationCard/index.js b/webapp/src/components/InformationCard/index.js index f768b806e..e98a1bdcc 100644 --- a/webapp/src/components/InformationCard/index.js +++ b/webapp/src/components/InformationCard/index.js @@ -40,52 +40,43 @@ const InformationCard = ({ producer, rank, type }) => { } const BlockProducerInfo = () => { - const bpJsonHealthStatus = producerOrg.healthStatus.find( - (status) => status.name === 'bpJson', - ) - - if (!bpJsonHealthStatus?.valid && eosConfig.networkName !== 'lacchain') + if (producerOrg?.hasEmptyBPJson) return return ( <> - {producerOrg?.info?.location} - {' '} - {producerOrg?.info?.website} - {formatWithThousandSeparator( - producer?.total_votes_eos || '0', - 0, - )} + {producerOrg?.location} + {' '} + {producerOrg?.media?.website} + + {formatWithThousandSeparator(producer?.total_votes_eos || '0', 0)} + {`${formatWithThousandSeparator( - producer?.total_rewards || '0', - 0, - )} ${eosConfig.tokenSymbol}`} + producer?.total_rewards || '0', + 0, + )} ${eosConfig.tokenSymbol}`} - - + ) } useEffect(() => { setProducerOrg( - formatData( - { - data: producer.bp_json?.org || {}, - rank, - owner: producer.owner, - updatedAt: producer.updated_at, - nodes: producer.bp_json?.nodes || [], - healthStatus: producer.health_status, - dataType: producer.bp_json?.type, - totalRewards: producer.total_rewards, - }, - type, - t, - ), + formatData({ + data: producer.bp_json?.org || {}, + rank, + owner: producer.owner, + healthStatus: producer.health_status, + dataType: producer.bp_json?.type, + totalRewards: producer.total_rewards, + }), ) // eslint-disable-next-line }, [producer]) @@ -100,7 +91,10 @@ const InformationCard = ({ producer, rank, type }) => { type === 'node' ? classes.hideScroll : '' }`} > - {`#${producer?.rank}`} + {`#${producer?.rank}`} { /> {type === 'node' ? ( <> - -
- {' '} -
-
- + +
+ {' '} +
+
) : ( )} +
{type === 'node' && !matches && ( diff --git a/webapp/src/components/NodeCard/styles.js b/webapp/src/components/NodeCard/styles.js index 5d56ec73d..f2f006291 100644 --- a/webapp/src/components/NodeCard/styles.js +++ b/webapp/src/components/NodeCard/styles.js @@ -30,7 +30,7 @@ export default (theme) => ({ wordBreak: 'break-word', }, nodes: { - width: '280px', + width: '260px', padding: theme.spacing(0, 3, 0), overflowX: 'auto', '& .MuiCardContent-root:last-child': { @@ -39,6 +39,9 @@ export default (theme) => ({ boxShadow: '2px 3px 4px 0px #0000002E', backgroundColor: '#f6f9fd', borderRadius: theme.spacing(3), + [theme.breakpoints.down('sm')]: { + width: '230px', + }, }, nodesWrapper: { display: 'flex', @@ -46,9 +49,6 @@ export default (theme) => ({ flexFlow: 'row wrap', gap: theme.spacing(2), padding: theme.spacing(0, 2, 0), - [theme.breakpoints.up('lg')]: { - paddingRight: '250px', - }, }, endpointsTitle: { display: 'flex', diff --git a/webapp/src/components/ProducerName/index.js b/webapp/src/components/ProducerName/index.js index 176788aa3..ffde26428 100644 --- a/webapp/src/components/ProducerName/index.js +++ b/webapp/src/components/ProducerName/index.js @@ -8,6 +8,7 @@ const useStyles = makeStyles(styles) const ProducerName = ({ name, logo, text, account = '', size = 'small' }) => { const classes = useStyles() + const isBig = size === 'big' return (
@@ -15,10 +16,10 @@ const ProducerName = ({ name, logo, text, account = '', size = 'small' }) => { logo={logo} name={name} classes={{ - avatar: size === 'big' ? classes.bigAvatar : classes.smallAvatar, + avatar: isBig ? classes.bigAvatar : classes.smallAvatar, }} /> -
+
{account && {account}} {name} {text} diff --git a/webapp/src/components/ProducerName/styles.js b/webapp/src/components/ProducerName/styles.js index 5f5d6f4d2..830c20399 100644 --- a/webapp/src/components/ProducerName/styles.js +++ b/webapp/src/components/ProducerName/styles.js @@ -11,6 +11,11 @@ export default (theme) => ({ display: 'flex', flexDirection: 'column', justifyContent: 'center', + }, + bigContainer:{ + width: '100%', + }, + smallContainer: { width: '200px', }, socialContainer: { diff --git a/webapp/src/components/ProducerSocialLinks/index.js b/webapp/src/components/ProducerSocialLinks/index.js index e81e100ae..919596e99 100644 --- a/webapp/src/components/ProducerSocialLinks/index.js +++ b/webapp/src/components/ProducerSocialLinks/index.js @@ -9,21 +9,6 @@ import GitHubIcon from '@mui/icons-material/GitHub' import RedditIcon from '@mui/icons-material/Reddit' import TelegramIcon from '@mui/icons-material/Telegram' -const prefix = { - hive: 'https://hive.blog/@', - twitter: 'https://twitter.com/', - youtube: 'https://youtube.com/', - facebook: 'https://facebook.com/', - github: 'https://github.com/', - reddit: 'https://www.reddit.com/user/', - keybase: 'https://keybase.io/', - telegram: 'https://t.me/', - wechat: 'https://wechat.com/', - steemit: 'https://steemit.com/@', - discord: 'https://discord/', - medium: 'https://medium.com/@', -} - const icons = { twitter: , youtube: , @@ -33,41 +18,16 @@ const icons = { telegram: , } -const order = { - twitter: 0, - github: 1, - telegram: 2, - medium: 3, - youtube: 4, - reddit: 5, - wechat: 6, - keybase: 7, - steemit: 8, - hive: 9, - facebook: 10, -} - -const getOrder = name => order[name] ?? Infinity - const ProducerSocialLinks = ({ items }) => { - const itemsArray = Object.keys(items).sort((a,b) => getOrder(a) - getOrder(b)).flatMap((key) => - !!items[key] - ? { - name: key, - url: `${prefix[key] ?? 'https://' + key + '/'}${items[key]}`, - } - : [], - ) - - return itemsArray.map((items, i) => ( + return items.map((item, i) => ( - {icons[items.name] || } + {icons[item.name] || } )) } diff --git a/webapp/src/gql/producer.gql.js b/webapp/src/gql/producer.gql.js index 57720aa27..203548158 100644 --- a/webapp/src/gql/producer.gql.js +++ b/webapp/src/gql/producer.gql.js @@ -35,6 +35,28 @@ export const PRODUCERS_QUERY = gql` type value } + } + } +` + +export const PRODUCER_INFO_QUERY = gql` + query producer( + $where: producer_bool_exp + ) { + producers: producer( + where: $where + offset: 0 + limit: 1 + ) { + id + owner + url + bp_json + total_votes_eos + total_rewards + health_status + rank + producer_key nodes { type full @@ -50,7 +72,6 @@ export const PRODUCERS_QUERY = gql` updated_at } } - } } ` @@ -81,6 +102,7 @@ export const SMALL_PRODUCERS_QUERY = gql` total_rewards health_status rank + producer_key } } ` @@ -97,6 +119,28 @@ export const PRODUCERS_COUNT_QUERY = gql` } ` +export const NODES_BY_PRODUCER_SUBSCRIPTION = gql` + subscription ($where: node_bool_exp) { + nodes: node( + where: $where + ) { + type + full + location + node_info { + features + version + } + endpoints(order_by: { type: asc }) { + value + type + response + updated_at + } + } + } +` + export const NODES_SUBSCRIPTION = gql` subscription ($offset: Int = 0, $limit: Int = 21, $where: producer_bool_exp) { producers: producer( @@ -110,6 +154,10 @@ export const NODES_SUBSCRIPTION = gql` rank producer_key bp_json + total_votes_eos + total_rewards + health_status + rank nodes { type full diff --git a/webapp/src/hooks/customHooks/useNodeState.js b/webapp/src/hooks/customHooks/useNodeState.js index 8f4d1e501..37b7f3893 100644 --- a/webapp/src/hooks/customHooks/useNodeState.js +++ b/webapp/src/hooks/customHooks/useNodeState.js @@ -3,6 +3,7 @@ import { useSubscription, useLazyQuery } from '@apollo/client' import { NODES_SUBSCRIPTION, PRODUCERS_COUNT_QUERY } from '../../gql' import { eosConfig } from '../../config' +import sortNodes from 'utils/sort-nodes' import useSearchState from './useSearchState' @@ -34,15 +35,6 @@ const useNodeState = () => { const chips = [{ name: 'all' }, ...eosConfig.nodeChips] - const getOrderNode = (node) => { - return ( - (node.type?.includes('full') ? 0.5 : 0) + - (node.endpoints?.length || 0) + - Boolean(node?.node_info[0]?.version?.length) + - (node.node_info[0]?.features?.list?.length || 0) - ) - } - const isFeature = (filter) => { return ( filter !== 'all' && @@ -89,37 +81,7 @@ const useNodeState = () => { ) return [] - producer.nodes.sort((a, b) => { - return getOrderNode(a) - getOrderNode(b) - }) - - let nodes = [] - let producerNode - - for (const node in producer.nodes) { - const current = producer.nodes[node] - - if (current?.type[0] === 'producer') { - if (!producerNode) { - const features = { keys: { producer_key: producer.producer_key } } - - producerNode = { - ...current, - locations: [], - node_info: [{ features }], - } - } - - producerNode.locations.push(current.location) - } else { - nodes = JSON.parse(JSON.stringify(producer.nodes.slice(node))) - nodes.reverse() - - if (producerNode) nodes.push(producerNode) - - break - } - } + const nodes = sortNodes(producer.nodes, producer.producer_key) return nodes.length ? { diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json index f87b867ef..0a66efad2 100644 --- a/webapp/src/language/en.json +++ b/webapp/src/language/en.json @@ -330,7 +330,10 @@ "bpJson": "BP.json", "bpJsonGenerator": "BP JSON Generator", "viewProfile": "View BP Profile", - "BPonNetwork": "Block Producer on {{networkName}}" + "BPonNetwork": "{{position}} Block Producer on {{networkName}}", + "generalInformation": "General Information", + "logo_256": "Logo", + "organization_name": "Name" }, "nodeCardComponent": { "features": "Features", diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json index 6cbe331ca..96d1a2642 100644 --- a/webapp/src/language/es.json +++ b/webapp/src/language/es.json @@ -333,7 +333,9 @@ "bpJson": "BP.json", "bpJsonGenerator": "Generar BP JSON", "viewProfile": "Ver Perfil del BP", - "BPonNetwork": "Productor de Bloques en {{networkName}}" + "BPonNetwork": "{{position}} Productor de Bloques en {{networkName}}", + "generalInformation": "Información General", + "organization_name": "Nombre" }, "nodeCardComponent": { "features": "Características", diff --git a/webapp/src/routes/ProducerProfile/index.js b/webapp/src/routes/ProducerProfile/index.js index 8c8e10279..d8606f2fc 100644 --- a/webapp/src/routes/ProducerProfile/index.js +++ b/webapp/src/routes/ProducerProfile/index.js @@ -1,5 +1,5 @@ import React from 'react' -import { useParams, Navigate } from 'react-router-dom' +import { useParams, Navigate, useLocation } from 'react-router-dom' import Helmet from 'react-helmet' import { useTranslation } from 'react-i18next' import { makeStyles } from '@mui/styles' @@ -29,20 +29,16 @@ const ProducerProfile = () => { const classes = useStyles() const { t } = useTranslation('producerCardComponent') let { bpName } = useParams() - const [{ ldJson, producer }] = useProducerProfileState(bpName) + const location = useLocation() + const [{ ldJson, producer }] = useProducerProfileState(bpName,location?.state?.producer) const { healthLights } = generalConfig + window.history.replaceState({}, document.title) + if (!producer) return <> if (!producer?.owner) return - const bpJsonHealthStatus = producer?.health_status?.find( - (status) => status.name === 'bpJson', - ) - - if (!bpJsonHealthStatus?.valid && eosConfig.networkName !== 'lacchain') - return - const GeneralInformation = ({ producer }) => { const eosRate = producer?.eosRate @@ -131,13 +127,23 @@ const ProducerProfile = () => {

{title}
- {type === 'text' && value} - {type === 'hiddenLink' && } - {type === 'modal' && } - {type === 'link' && ( - - {value} - + {value ? ( + <> + {type === 'text' && value} + {type === 'hiddenLink' && } + {type === 'modal' && } + {type === 'link' && ( + + {value} + + )} + + ) : ( + )}

@@ -151,41 +157,41 @@ const ProducerProfile = () => { title={t('location')} value={ <> - {producer?.bp_json?.org?.location?.name} - {' '} + {producer?.location} + {' '} } /> - {producer?.bp_json?.org?.other_resources?.length && ( + {producer?.info?.otherResources?.length && ( )} @@ -199,26 +205,35 @@ const ProducerProfile = () => { {ldJson && } - {Object.keys(producer?.bp_json?.org?.social || {})?.length && ( -
- -
- )} + {producer?.social?.length && ( +
+ +
+ )}
- + - - - + {!producer?.hasEmptyBPJson ? ( + + + + ) : ( + + + + )} ) } diff --git a/webapp/src/routes/ProducerProfile/styles.js b/webapp/src/routes/ProducerProfile/styles.js index 1f7db46c6..55f297031 100644 --- a/webapp/src/routes/ProducerProfile/styles.js +++ b/webapp/src/routes/ProducerProfile/styles.js @@ -7,6 +7,9 @@ export default (theme) => ({ '& > div': { padding: 0, gap: theme.spacing(4), + [theme.breakpoints.down('sm')]: { + justifyContent: 'center', + } }, }, profile: { @@ -21,10 +24,17 @@ export default (theme) => ({ display: 'flex', gap: theme.spacing(5), alignSelf: 'end', + [theme.breakpoints.down('sm')]: { + marginBottom: theme.spacing(4), + flexWrap: 'wrap', + justifyContent: 'center', + alignSelf: 'center', + }, }, healthContainer: { display: 'flex', flexWrap: 'wrap', + justifyContent: 'center', marginTop: theme.spacing(4), }, healthIndicator: { @@ -32,6 +42,9 @@ export default (theme) => ({ flexDirection: 'column', width: 'calc( 100% / 3 )', textAlign: 'center', + [theme.breakpoints.down('sm')]: { + width: 'calc( 100% / 2 )', + }, '& > *': { alignSelf: 'center', }, @@ -46,6 +59,7 @@ export default (theme) => ({ OrgDataContainer: { display: 'flex', flexWrap: 'wrap', + padding: theme.spacing(4), }, OrgDataItem: { display: 'flex', diff --git a/webapp/src/routes/ProducerProfile/useProducerProfileState.js b/webapp/src/routes/ProducerProfile/useProducerProfileState.js index 4880f50f3..f45db956a 100644 --- a/webapp/src/routes/ProducerProfile/useProducerProfileState.js +++ b/webapp/src/routes/ProducerProfile/useProducerProfileState.js @@ -1,60 +1,120 @@ import { useState, useEffect } from 'react' -import { useLazyQuery } from '@apollo/client' +import { useLazyQuery, useSubscription } from '@apollo/client' -import { PRODUCERS_QUERY } from '../../gql' +import { + PRODUCER_INFO_QUERY, + NODES_BY_PRODUCER_SUBSCRIPTION, + EOSRATE_STATS_QUERY, +} from '../../gql' import isValidAccountName from 'utils/validate-account-name' +import { formatData } from '../../utils/formatData' +import sortNodes from 'utils/sort-nodes' -const useProducerProfileState = (name) => { +const useProducerProfileState = (name, previousData) => { const defaultVariables = { - limit: 1, - offset: 0, - endpointFilter: undefined, where: { - owner: { _eq: name }, + _and: [{ owner: { _eq: name } }, { bp_json: { _is_null: false } }], }, } + const [producer, setProducer] = useState() + const [producerKey, setProducerKey] = useState() const [ldJson, setLdJson] = useState() const [loadProducers, { loading, data: { producers } = {} }] = - useLazyQuery(PRODUCERS_QUERY) - + useLazyQuery(PRODUCER_INFO_QUERY) + const { data } = useSubscription(NODES_BY_PRODUCER_SUBSCRIPTION, { + variables: { where: { producer: defaultVariables.where } }, + }) + const [loadStats, { data: { eosrate_stats: stats } = {} }] = + useLazyQuery(EOSRATE_STATS_QUERY) + const isValidName = isValidAccountName(name) useEffect(() => { + loadStats({}) + }, [loadStats]) + + useEffect(()=>{ + if(!stats?.length) return + + const eosRate = stats.find(x=>x.bp === name) + + setProducer(prev => ({...prev, eosRate})) + },[stats]) + + useEffect(() => { + if (!data?.nodes || !producerKey) return + + setProducer((prev) => ({ + ...prev, + nodes: sortNodes(data?.nodes, producerKey), + })) + }, [data, producerKey]) + + const getProducerData = (bpData) => { + return bpData + ? { + ...bpData, + ...formatData({ + data: bpData?.bp_json?.org || {}, + rank: bpData.rank, + dataType: bpData.bp_json?.type, + totalRewards: bpData.total_rewards, + url: bpData.url, + }), + ...(bpData?.nodes && { + nodes: sortNodes(bpData?.nodes, bpData?.producer_key), + }), + } + : bpData + } + + useEffect(() => { + if (previousData) { + setProducer(getProducerData(previousData)) + setProducerKey(previousData?.producer_key) + + return + } if (isValidName) { loadProducers({ variables: defaultVariables }) + } else { + setProducer({}) } // eslint-disable-next-line }, []) useEffect(() => { + if (Array.isArray(producers) && !producers.length) { + setProducer({}) + } + if (!producers || !producers.length) return - const bp = producers.at(0)?.bp_json?.org + if (!producers.at(0)?.bp_json) return + + const bp = getProducerData(producers?.at(0)) - if (!bp) return + setProducerKey(bp.producer_key) setLdJson( JSON.stringify({ '@context': 'http://schema.org', '@type': 'Organization', - name: bp.candidate_name, - url: bp.website, + name: bp?.media?.name, + url: bp?.media?.website, contactPoint: { '@type': 'ContactPoint', - email: bp.email, + email: bp?.media?.email, contactType: 'customer service', }, - logo: bp.branding.logo_256, + logo: bp?.media?.logo, }), ) - }, [producers]) - const getProducer = (producers) => - !isValidName || (Array.isArray(producers) && !producers.length) - ? {} - : producers?.at(0) + setProducer((prev) => ({ ...prev, ...bp })) + }, [producers]) - return [{ loading, producer: getProducer(producers), ldJson }, {}] + return [{ loading, producer, ldJson }, {}] } export default useProducerProfileState diff --git a/webapp/src/utils/formatData.js b/webapp/src/utils/formatData.js index 4ab7a8900..eb4faecb1 100644 --- a/webapp/src/utils/formatData.js +++ b/webapp/src/utils/formatData.js @@ -1,37 +1,18 @@ /* eslint complexity: 0 */ /* eslint camelcase: 0 */ -import moment from 'moment' - import { eosConfig } from '../config' -export const formatData = ( - { - data, - rank, - owner, - updatedAt, - nodes, - healthStatus, - dataType, - totalRewards, - }, - type, - t, -) => { - let newData = { - title: '', - media: {}, - info: {}, - stats: {}, - nodes: [], - healthStatus: [], - social: {}, - endpoints: {}, - } - +export const formatData = ({ + data, + rank, + owner, + healthStatus, + dataType, + totalRewards, + url +}) => { const getSubTitle = () => { - if (eosConfig.networkName === 'lacchain') - return `${t(`entityType${dataType}`)} Entity` + if (eosConfig.networkName === 'lacchain') return `${dataType} Entity` if (rank <= 21) return 'Top 21' @@ -40,56 +21,82 @@ export const formatData = ( return 'Non-Paid Standby' } - if (!data?.social?.github && typeof data?.github_user === 'string') { - data.social.github = data.github_user + if (!Object.keys(data || {}).length && eosConfig.networkName !== 'lacchain') { + return { hasEmptyBPJson: true, health_status: [{ name: "bpJson", valid: false }], media: { name: owner, account: getSubTitle(), website: url } } + } + + let newData = { + media: {}, + info: {}, + social: {}, } - switch (type) { - case 'entity': - case 'node': - if (eosConfig.networkName === 'lacchain') { - newData.title = owner - } else { - newData.title = rank ? `#${rank} - ${owner}` : owner - } + const prefix = { + keybase: 'https://keybase.io/', + telegram: 'https://t.me/', + twitter: 'https://twitter.com/', + github: 'https://github.com/', + youtube: 'https://youtube.com/', + facebook: 'https://facebook.com/', + hive: 'https://hive.blog/@', + reddit: 'https://www.reddit.com/user/', + wechat: 'https://wechat.com/', + } - newData = { - ...newData, - media: { - logo: data.branding?.logo_256, - name: data.candidate_name || data.organization_name || owner, - account: getSubTitle(), - }, - info: { - location: data.location?.name || 'N/A', - country: data.location?.country || null, - website: data?.website || '', - email: data.email, - code_of_conduct: data?.code_of_conduct || null, - ownership: data?.ownership_disclosure || null, - bussinesContact: data.bussines_contact || null, - technicalContact: data.technical_contact || null, - chain: data?.chain_resources || null, - otherResources: data?.other_resources || [], - }, - stats: { - votes: 'N/A', - rewards: 0, - lastChecked: moment(new Date()).diff(moment(updatedAt), 'seconds'), - }, - nodes, - healthStatus, - social: data.social, - compliance: { - total: healthStatus?.length, - pass: healthStatus?.reduce((a,b)=>a+Number(b.valid),0) - } - } + + const order = { + twitter: 0, + github: 1, + telegram: 2, + youtube: 3, + reddit: 4, + wechat: 5, + keybase: 6, + hive: 7, + facebook: 8, + } + + const getOrder = name => order[name] ?? Infinity + + if (!data?.social?.github && typeof data?.github_user === 'string') { + data.social.github = data.github_user + } - break + const socialArray = Object.keys(prefix || {}) + .sort((a, b) => getOrder(a) - getOrder(b)) + .flatMap((key) => + !!data.social[key] + ? { + name: key, + url: `${prefix[key] ?? 'https://' + key + '/'}${data.social[key]}`, + } + : [], + ) - default: - break + newData = { + ...newData, + media: { + logo: data?.branding?.logo_256, + name: data?.candidate_name || data?.organization_name || owner, + account: getSubTitle(), + website: data?.website || '', + email: data?.email, + }, + location: data?.location?.name || 'N/A', + country: data?.location?.country || null, + info: { + codeOfConduct: data?.code_of_conduct, + ownership: data?.ownership_disclosure, + chainResources: data?.chain_resources, + otherResources: data?.other_resources, + }, + social: socialArray, + ...(healthStatus && { + compliance: { + total: healthStatus?.length, + pass: healthStatus?.reduce((a, b) => a + Number(b.valid), 0), + }, + }), } return newData diff --git a/webapp/src/utils/sort-nodes.js b/webapp/src/utils/sort-nodes.js new file mode 100644 index 000000000..6bf94a5f2 --- /dev/null +++ b/webapp/src/utils/sort-nodes.js @@ -0,0 +1,46 @@ +const getOrderNode = (node) => { + return ( + (node.type?.includes('full') ? 0.5 : 0) + + (node.endpoints?.length || 0) + + Boolean(node?.node_info[0]?.version?.length) + + (node.node_info[0]?.features?.list?.length || 0) + ) +} + +const sortNodes = (unsortedNodes, key) => { + let nodes = [] + let producerNode + + unsortedNodes.sort((a, b) => { + return getOrderNode(a) - getOrderNode(b) + }) + + for (const node in unsortedNodes) { + const current = unsortedNodes[node] + + if (current?.type[0] === 'producer') { + if (!producerNode) { + const features = { keys: { producer_key: key } } + + producerNode = { + ...current, + locations: [], + node_info: [{ features }], + } + } + + producerNode.locations.push(current.location) + } else { + nodes = JSON.parse(JSON.stringify(unsortedNodes.slice(node))) + nodes.reverse() + + if (producerNode) nodes.push(producerNode) + + break + } + } + + return nodes +} + +export default sortNodes From c17fc350c0279ef52b65a0f683b6a64865dd0f30 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 4 Oct 2023 10:44:20 -0600 Subject: [PATCH 07/41] feat(webapp): add route for entities profile and apply styles --- .../components/InformationCard/EmptyState.js | 37 ++++----- .../src/components/InformationCard/index.js | 2 +- webapp/src/components/ProducerName/index.js | 4 +- webapp/src/components/ProducerName/styles.js | 3 + webapp/src/components/ViewBPProfile/index.js | 4 +- webapp/src/routes/ProducerProfile/index.js | 82 ++++++++++--------- webapp/src/routes/ProducerProfile/styles.js | 74 ++++++++++++++++- .../useProducerProfileState.js | 2 +- webapp/src/routes/index.js | 5 ++ 9 files changed, 146 insertions(+), 67 deletions(-) diff --git a/webapp/src/components/InformationCard/EmptyState.js b/webapp/src/components/InformationCard/EmptyState.js index 4e361062c..3d1d90939 100644 --- a/webapp/src/components/InformationCard/EmptyState.js +++ b/webapp/src/components/InformationCard/EmptyState.js @@ -5,26 +5,23 @@ import PropTypes from 'prop-types' const EmptyState = ({ classes, t }) => { return ( -
-
-
- - {t('emptyState')} - - {t('viewList')} - -
+
+ + {t('emptyState')} + + {t('viewList')} +
) } diff --git a/webapp/src/components/InformationCard/index.js b/webapp/src/components/InformationCard/index.js index e98a1bdcc..bf35558d0 100644 --- a/webapp/src/components/InformationCard/index.js +++ b/webapp/src/components/InformationCard/index.js @@ -94,7 +94,7 @@ const InformationCard = ({ producer, rank, type }) => { {`#${producer?.rank}`} + >{`${producer?.rank}`} { }} />
- {account && {account}} + {account && {account}} {name} - {text} + {text}
) diff --git a/webapp/src/components/ProducerName/styles.js b/webapp/src/components/ProducerName/styles.js index 830c20399..03487f3dc 100644 --- a/webapp/src/components/ProducerName/styles.js +++ b/webapp/src/components/ProducerName/styles.js @@ -36,4 +36,7 @@ export default (theme) => ({ height: '104px', border: `solid 2px ${theme.palette.primary.main}`, }, + bold: { + fontWeight: 'bold !important', + } }) diff --git a/webapp/src/components/ViewBPProfile/index.js b/webapp/src/components/ViewBPProfile/index.js index 818879000..e57c8678a 100644 --- a/webapp/src/components/ViewBPProfile/index.js +++ b/webapp/src/components/ViewBPProfile/index.js @@ -3,14 +3,16 @@ import Button from '@mui/material/Button' import { Link } from 'react-router-dom' import LaunchIcon from '@mui/icons-material/Launch' import { useTranslation } from 'react-i18next' +import { eosConfig } from '../../config' const ViewBPProfile = ({ producer }) => { const { t } = useTranslation('producerCardComponent') + const route = eosConfig.networkName !== 'lacchain' ? 'block-producers' : 'entities' return (
- + {t('filterEndpoints')} {
- + {t('endpointsResponding')} { {data.map((url, index) => ( @@ -50,7 +50,7 @@ const ProfileCard = ({ producer }) => { {value} diff --git a/webapp/src/routes/ProducerProfile/styles.js b/webapp/src/routes/ProducerProfile/styles.js index 0e98a3d31..999aa3e1a 100644 --- a/webapp/src/routes/ProducerProfile/styles.js +++ b/webapp/src/routes/ProducerProfile/styles.js @@ -87,9 +87,6 @@ export default (theme) => ({ textAlign: 'center', flexGrow: '1', '& > p': { - '& span': { - textTransform: 'uppercase', - }, '& > a': { margin: 0, }, diff --git a/webapp/src/routes/ProducerProfile/useProducerProfileState.js b/webapp/src/routes/ProducerProfile/useProducerProfileState.js index 89a2017cd..8e32cec92 100644 --- a/webapp/src/routes/ProducerProfile/useProducerProfileState.js +++ b/webapp/src/routes/ProducerProfile/useProducerProfileState.js @@ -23,13 +23,16 @@ const useProducerProfileState = (name, previousData) => { useLazyQuery(PRODUCER_INFO_QUERY) const [loadStats, { data: { eosrate_stats: eosRate } = {} }] = useLazyQuery(EOSRATE_STATS_QUERY) - const { data: nodesSubscription } = useSubscription(NODES_BY_PRODUCER_SUBSCRIPTION, { - variables: { where: { producer: defaultVariables.where } }, - }) + const { data: nodesSubscription } = useSubscription( + NODES_BY_PRODUCER_SUBSCRIPTION, + { + variables: { where: { producer: defaultVariables.where } }, + }, + ) const isValidName = isValidAccountName(name) - const getProducerData = (bpData) => { + const getProducerData = bpData => { return bpData ? { ...bpData, @@ -50,7 +53,7 @@ const useProducerProfileState = (name, previousData) => { useEffect(() => { if (isValidName) { loadStats({ - variables: { bp: name } + variables: { bp: name }, }) if (previousData) { @@ -74,7 +77,7 @@ const useProducerProfileState = (name, previousData) => { useEffect(() => { if (!nodesSubscription?.nodes || !producerKey) return - setProducer((prev) => ({ + setProducer(prev => ({ ...prev, nodes: sortNodes(nodesSubscription?.nodes, producerKey), })) @@ -92,24 +95,27 @@ const useProducerProfileState = (name, previousData) => { const bp = getProducerData(producers?.at(0)) setProducerKey(bp.producer_key) + setProducer(prev => ({ ...prev, ...bp })) + }, [producers]) + + useEffect(() => { + if (!producer || !Object?.keys(producer).length) return setLdJson( JSON.stringify({ '@context': 'http://schema.org', '@type': 'Organization', - name: bp?.media?.name, - url: bp?.media?.website, + name: producer?.media?.name, + url: producer?.media?.website, contactPoint: { '@type': 'ContactPoint', - email: bp?.media?.email, + email: producer?.media?.email, contactType: 'customer service', }, - logo: bp?.media?.logo, + logo: producer?.media?.logo, }), ) - - setProducer((prev) => ({ ...prev, ...bp })) - }, [producers]) + }, [producer]) return [{ loading, producer, ldJson }, {}] } diff --git a/webapp/src/theme/typography.js b/webapp/src/theme/typography.js index 434fb561d..624a18f8f 100644 --- a/webapp/src/theme/typography.js +++ b/webapp/src/theme/typography.js @@ -46,12 +46,20 @@ const typography = { fontWeight: 600, lineHeight: 1.2 }, + caption: { + color: '#4e4e4e', + textTransform: 'uppercase', + fontWeight: 700, + lineHeight: 1.2, + textAlign: 'center', + letterSpacing: '0.83px' + }, body1: { fontSize: 14 }, button: { textTransform: 'none' - } + }, } export default typography From afc648f91e0f02a22e34f1507a05c86144d0aad5 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 9 Oct 2023 10:34:56 -0600 Subject: [PATCH 18/41] feat(webapp): allow pagination as url parameters in bps page --- webapp/src/components/ViewBPProfile/index.js | 3 +- webapp/src/config/eos.config.js | 1 + webapp/src/gql/producer.gql.js | 6 -- .../src/hooks/customHooks/useBPSearchState.js | 82 +++++++++++++++++++ .../customHooks/useBlockProducerState.js | 52 +++++++----- webapp/src/routes/BlockProducers/index.js | 12 +++ 6 files changed, 128 insertions(+), 28 deletions(-) create mode 100644 webapp/src/hooks/customHooks/useBPSearchState.js diff --git a/webapp/src/components/ViewBPProfile/index.js b/webapp/src/components/ViewBPProfile/index.js index 38b8bb2ca..e45725542 100644 --- a/webapp/src/components/ViewBPProfile/index.js +++ b/webapp/src/components/ViewBPProfile/index.js @@ -14,13 +14,12 @@ const useStyles = makeStyles(styles) const ViewBPProfile = ({ producer }) => { const classes = useStyles() const { t } = useTranslation('producerCardComponent') - const route = eosConfig.networkName !== 'lacchain' ? 'block-producers' : 'entities' return (
- ) : ( - )}
diff --git a/webapp/src/components/InformationCard/styles.js b/webapp/src/components/InformationCard/styles.js index 6841f8a75..3630c44f6 100644 --- a/webapp/src/components/InformationCard/styles.js +++ b/webapp/src/components/InformationCard/styles.js @@ -45,16 +45,6 @@ export default (theme) => ({ hideScroll: { overflowX: 'hidden', }, - expand: { - transform: 'rotate(0deg)', - marginLeft: 'auto', - transition: theme.transitions.create('transform', { - duration: theme.transitions.duration.shortest, - }), - }, - expandOpen: { - transform: 'rotate(180deg)', - }, expandMore: { width: '100%', display: 'flex', @@ -63,37 +53,12 @@ export default (theme) => ({ textTransform: 'capitalize', }, }, - node: { - [theme.breakpoints.up('lg')]: { - width: 260, - marginBottom: 0, - }, - }, - textWrap: { - width: 210, - wordWrap: 'break-word', - display: 'block !important', - overflow: 'visible !important', - whiteSpace: 'normal !important', - [theme.breakpoints.up('lg')]: { - width: 290, - }, - }, cardActions: { display: 'flex', [theme.breakpoints.up('lg')]: { display: 'none', }, }, - breakLine: { - wordBreak: 'break-word', - }, - boxLabel: { - alignItems: 'baseline !important', - }, - flexColumn: { - flexDirection: 'column !important', - }, collapse: { width: '100%', marginRight: theme.spacing(4), @@ -101,56 +66,6 @@ export default (theme) => ({ width: '55%', }, }, - infoIcon: { - cursor: 'pointer', - flexDirection: 'flex-end', - }, - dt: { - maxWidth: 100, - }, - shadow: { - '& .MuiPaper-root': { - boxShadow: '0px 1px 3px 1px rgba(0, 0, 0, 0.15)', - padding: theme.spacing(3), - maxWidth: '250px', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - overflow: 'hidden', - borderRadius: '5px', - }, - }, - infoItems: { - display: 'flex', - flexDirection: 'column', - '& .listBox': { - marginLeft: theme.spacing(1), - }, - '& .listLabel': { - height: '100%', - '& .MuiSvgIcon-root': { - marginRight: theme.spacing(1), - fontSize: 15, - }, - '& .MuiTypography-body1': { - whiteSpace: 'nowrap', - }, - }, - [theme.breakpoints.up('lg')]: { - minWidth: 150, - }, - }, - clickableIcon: { - cursor: 'pointer', - '&:hover': { - color: theme.palette.primary.main, - }, - }, - popoverStyle: { - paddingRight: theme.spacing(2), - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - overflow: 'hidden', - }, emptyStateRow: { display: 'flex', alignItems: 'center', diff --git a/webapp/src/components/NetworkSelector/styles.js b/webapp/src/components/NetworkSelector/styles.js index ea85f5838..c920f197f 100644 --- a/webapp/src/components/NetworkSelector/styles.js +++ b/webapp/src/components/NetworkSelector/styles.js @@ -71,6 +71,9 @@ export default (theme) => ({ width: '50%', }, }, + [theme.breakpoints.down('sm')]: { + top: 85, + }, [theme.breakpoints.up('sm')]: { left: -189, width: 320, @@ -110,6 +113,13 @@ export default (theme) => ({ height: 50, borderRadius: theme.spacing(2), color: theme.palette.common.black, + [theme.breakpoints.down('sm')]: { + margin: theme.spacing(6, 0, 4), + textAlign: 'center', + '& > p':{ + maxWidth: 130, + }, + }, [theme.breakpoints.up('sm')]: { paddingLeft: 22, borderRadius: theme.spacing(2, 4, 4, 2), diff --git a/webapp/src/components/ProducerCard/index.js b/webapp/src/components/ProducerCard/index.js deleted file mode 100644 index d958f7616..000000000 --- a/webapp/src/components/ProducerCard/index.js +++ /dev/null @@ -1,362 +0,0 @@ -/* eslint camelcase: 0 */ -import React, { memo, useEffect, useState } from 'react' -import PropTypes from 'prop-types' -import { useTranslation } from 'react-i18next' -import { makeStyles } from '@mui/styles' -import Card from '@mui/material/Card' -import CardHeader from '@mui/material/CardHeader' -import CardContent from '@mui/material/CardContent' -import CardActions from '@mui/material/CardActions' -import Link from '@mui/material/Link' -import Typography from '@mui/material/Typography' -import moment from 'moment' -import 'flag-icon-css/css/flag-icon.min.css' - -import { generalConfig } from '../../config' -import { formatWithThousandSeparator, onImgError } from '../../utils' - -import CountryFlag from '../CountryFlag' -import ProducerHealthIndicators from '../ProducerHealthIndicators' -import ProducerSocialLinks from '../ProducerSocialLinks' - -import styles from './styles' - -const useStyles = makeStyles(styles) - -const ProducerCard = ({ producer, onNodeClick, rank }) => { - const classes = useStyles() - const { t } = useTranslation('producerCardComponent') - const [producerOrg, setProducerOrg] = useState({}) - const [producerNodes, setProducerNodes] = useState([]) - - useEffect(() => { - setProducerOrg(producer.bp_json?.org || {}) - setProducerNodes(producer.bp_json?.nodes || []) - }, [producer]) - - const Avatar = () => { - const logo = producerOrg.branding?.logo_256 - - return ( - avatar - ) - } - const SubHeader = () => { - return ( - <> - - - {producerOrg.location?.name || 'N/A'} - - - ) - } - const Rank = () => { - return ( - <> - {generalConfig.useVotes && rank > 0 && ( - <> -
{t('rank')}:
-
#{rank}
- - )} - - ) - } - const EntityType = () => { - return ( - <> - {producer.bp_json?.type && ( - <> -
{t('entityType')}:
-
{t(`entityType${producer.bp_json.type}`)}
- - )} - - ) - } - const BusinessContact = () => { - return ( - <> - {producerOrg.business_contact && ( - <> -
{t('businessContact')}:
-
{producerOrg.business_contact}
- - )} - - ) - } - const TechnicalContact = () => { - return ( - <> - {producerOrg.technical_contact && ( - <> -
{t('technicalContact')}:
-
{producerOrg.technical_contact}
- - )} - - ) - } - const Email = () => { - return ( - <> - {producerOrg.email && ( - <> -
{t('email')}:
-
- - {producerOrg.email} - -
- - )} - - ) - } - const Website = () => { - return ( - <> - {producerOrg.website && ( - <> -
{t('website')}:
-
- - {producerOrg.website} - -
- - )} - - ) - } - const Social = () => { - return ( - <> - {producerOrg.social && ( - <> -
{t('social')}:
-
- -
- - )} - - ) - } - const OwnershipDisclosure = () => { - return ( - <> - {producerOrg.ownership_disclosure && ( - <> -
{t('ownershipDisclosure')}:
-
- - {producerOrg.ownership_disclosure} - -
- - )} - - ) - } - const ChainResources = () => { - return ( - <> - {producerOrg.chain_resources && ( - <> -
{t('chainResources')}:
-
- - {producerOrg.chain_resources} - -
- - )} - - ) - } - const OtherResources = () => { - return ( - <> - {producerOrg.other_resources?.length > 0 && ( - <> -
{t('otherResources')}:
- {producerOrg.other_resources.map((url, i) => ( -
- - {url} - -
- ))} - - )} - - ) - } - const ServerVersion = () => { - return ( - <> - {producer.server_version_string && ( - <> -
{t('serverVersion')}:
-
{producer.server_version_string}
- - )} - - ) - } - const Ping = () => { - return ( - <> - {producer.ping && ( - <> -
{t('pingFromCostaRica')}:
-
{producer.ping}ms
- - )} - - ) - } - const Votes = () => { - return ( - <> - {generalConfig.useVotes && ( - <> -
{t('votes')}:
-
{formatWithThousandSeparator(producer.total_votes_eos, 2)}
- - )} - - ) - } - const Rewards = () => { - return ( - <> - {generalConfig.useRewards && ( - <> -
{t('rewards')}:
-
{formatWithThousandSeparator(producer.total_rewards, 2)}
- - )} - - ) - } - const MissedBlocks = () => { - return ( - <> -
{t('missedBlocks')}
-
- {(producer.missed_blocks || []).reduce( - (result, current) => result + current.value, - 0 - )} -
- - ) - } - const LastTimeChecked = () => { - return ( - <> -
{t('lastTimeChecked')}
-
- {moment(new Date()).diff(moment(producer.updated_at), 'seconds')} - {t('secondsAgo')} -
- - ) - } - const HealthStatus = () => { - if (!producer?.health_status?.length) { - return <> - } - - return ( - <> -
{t('healthStatus')}
-
- -
- - ) - } - - return ( - - } - title={ - producerOrg.candidate_name || - producerOrg.organization_name || - producer.owner - } - subheader={} - /> - -
- - - - - - - - - - - {producerNodes.length > 0 && ( - <> -
{t('nodes')}:
- {producerNodes.map((node, i) => ( -
- - {node.node || node.node_type} - -
- ))} - - )} - - - - - - - -
-
- -
- ) -} - -ProducerCard.propTypes = { - producer: PropTypes.any, - rank: PropTypes.number, - onNodeClick: PropTypes.func -} -export default memo(ProducerCard) diff --git a/webapp/src/components/ProducerCard/styles.js b/webapp/src/components/ProducerCard/styles.js deleted file mode 100644 index 8e2568d75..000000000 --- a/webapp/src/components/ProducerCard/styles.js +++ /dev/null @@ -1,39 +0,0 @@ -export default (theme) => ({ - root: { - maxWidth: 345, - height: '100%', - display: 'flex', - flexFlow: 'column' - }, - content: { - flex: 1 - }, - avatar: { - width: '3em', - height: '3em', - borderRadius: '100%', - backgroundColor: theme.palette.primary.contrastText - }, - dl: { - marginTop: -16, - marginBottom: -16 - }, - dt: { - fontWeight: 'bold' - }, - social: { - '& a': { - display: 'flex' - }, - '& svg': { - marginRight: theme.spacing(1) - } - }, - action: { - cursor: 'pointer', - color: theme.palette.secondary.main, - '&:hover': { - textDecoration: 'underline' - } - } -}) diff --git a/webapp/src/theme/overrides.js b/webapp/src/theme/overrides.js deleted file mode 100644 index 779b9f258..000000000 --- a/webapp/src/theme/overrides.js +++ /dev/null @@ -1,79 +0,0 @@ -const overrides = { - MuiCardHeader: { - action: { - marginTop: '-4px', - marginRight: '-4px' - } - }, - MuiPickersDay: { - day: { - fontWeight: '300' - } - }, - MuiPickersYear: { - root: { - height: '64px' - } - }, - MuiPickersCalendar: { - transitionContainer: { - marginTop: '6px' - } - }, - MuiPickersCalendarHeader: { - iconButton: { - backgroundColor: 'transparent', - '& > *': { - backgroundColor: 'transparent' - } - }, - switchHeader: { - marginTop: '2px', - marginBottom: '4px' - } - }, - MuiPickersClock: { - container: { - margin: `32px 0 4px` - } - }, - MuiPickersClockNumber: { - clockNumber: { - left: `calc(50% - 16px)`, - width: '32px', - height: '32px' - } - }, - MuiPickerDTHeader: { - dateHeader: { - '& h4': { - fontSize: '2.125rem', - fontWeight: 400 - } - }, - timeHeader: { - '& h3': { - fontSize: '3rem', - fontWeight: 400 - } - } - }, - MuiPickersTimePicker: { - hourMinuteLabel: { - '& h2': { - fontSize: '3.75rem', - fontWeight: 300 - } - } - }, - MuiPickersToolbar: { - toolbar: { - '& h4': { - fontSize: '2.125rem', - fontWeight: 400 - } - } - } -} - -export default overrides From 071dbc7e17a229a42f0106d81cae356dc0481025 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 9 Oct 2023 14:16:21 -0600 Subject: [PATCH 21/41] chore(webapp): apply adjustments for lacchain entities page --- webapp/src/components/InformationCard/ProducerRow.js | 4 ++-- webapp/src/utils/formatData.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/webapp/src/components/InformationCard/ProducerRow.js b/webapp/src/components/InformationCard/ProducerRow.js index dd704e80a..b2c96c140 100644 --- a/webapp/src/components/InformationCard/ProducerRow.js +++ b/webapp/src/components/InformationCard/ProducerRow.js @@ -28,7 +28,7 @@ const ProducerRow = ({ producer, index }) => { const BlockProducerInfo = () => { if (producerOrg?.hasEmptyBPJson) return ( - + ) @@ -114,7 +114,7 @@ const ProducerRow = ({ producer, index }) => { 10} /> diff --git a/webapp/src/utils/formatData.js b/webapp/src/utils/formatData.js index d3803aec2..270dc4957 100644 --- a/webapp/src/utils/formatData.js +++ b/webapp/src/utils/formatData.js @@ -1,6 +1,7 @@ /* eslint complexity: 0 */ /* eslint camelcase: 0 */ import { eosConfig } from '../config' +import { ENTITY_TYPE } from './lacchain' export const formatData = ({ data, @@ -12,7 +13,7 @@ export const formatData = ({ url }) => { const getSubTitle = () => { - if (eosConfig.networkName === 'lacchain') return `${dataType} Entity` + if (eosConfig.networkName === 'lacchain') return `${ENTITY_TYPE[dataType]}` if (rank <= 21) return 'Top 21' @@ -21,7 +22,7 @@ export const formatData = ({ return 'Non-Paid Standby' } - if (!Object.keys(data || {}).length && eosConfig.networkName !== 'lacchain') { + if (!Object.keys(data || {}).length) { return { hasEmptyBPJson: true, health_status: [{ name: "bpJson", valid: false }], media: { name: owner, account: getSubTitle(), website: url } } } From 65b49a6b202b515d28bc0caf8cfa7be25a4119eb Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Mon, 9 Oct 2023 20:54:47 +0000 Subject: [PATCH 22/41] [CodeFactor] Apply fixes --- webapp/src/components/InformationCard/styles.js | 2 +- webapp/src/components/NetworkSelector/styles.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/InformationCard/styles.js b/webapp/src/components/InformationCard/styles.js index 3630c44f6..d5561cf3b 100644 --- a/webapp/src/components/InformationCard/styles.js +++ b/webapp/src/components/InformationCard/styles.js @@ -127,7 +127,7 @@ export default (theme) => ({ [theme.breakpoints.down('xl')]: { whiteSpace: 'nowrap', overflow: 'hidden', - textOverflow: 'ellipsis', + textOverflow: 'ellipsis', width: '140px', }, }, diff --git a/webapp/src/components/NetworkSelector/styles.js b/webapp/src/components/NetworkSelector/styles.js index c920f197f..dfdca46c5 100644 --- a/webapp/src/components/NetworkSelector/styles.js +++ b/webapp/src/components/NetworkSelector/styles.js @@ -116,7 +116,7 @@ export default (theme) => ({ [theme.breakpoints.down('sm')]: { margin: theme.spacing(6, 0, 4), textAlign: 'center', - '& > p':{ + '& > p': { maxWidth: 130, }, }, From d29ef43286c882c06df2f6f265dde30222d50797 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Tue, 10 Oct 2023 16:31:46 -0600 Subject: [PATCH 23/41] feat(webapp): redesign nodes page and refactor code --- .../components/EmptyState/EmptyStateRow.js | 22 +++ webapp/src/components/EmptyState/index.js | 2 +- webapp/src/components/EmptyState/styles.js | 17 ++- .../InformationCard/EmptyStateRow.js | 24 --- .../src/components/InformationCard/index.js | 113 -------------- .../src/components/InformationCard/styles.js | 139 ------------------ webapp/src/components/NodeCard/NodesCard.js | 3 +- webapp/src/components/NodeCard/index.js | 3 +- webapp/src/components/NodeCard/styles.js | 14 +- webapp/src/components/NodesList/NodesRow.js | 109 ++++++++++++++ webapp/src/components/NodesList/index.js | 55 +++++++ webapp/src/components/NodesList/styles.js | 46 ++++++ webapp/src/components/ProducerName/index.js | 4 +- webapp/src/components/ProducerName/styles.js | 8 + .../MainSocialLinks.js | 35 +++-- .../ProducerRow.js | 2 +- .../index.js} | 0 .../src/components/ProducersTable/styles.js | 53 +++++++ webapp/src/components/ViewBPProfile/index.js | 2 - webapp/src/components/ViewBPProfile/styles.js | 2 +- webapp/src/language/en.json | 3 +- webapp/src/language/es.json | 3 +- webapp/src/routes/BlockProducers/index.js | 2 +- webapp/src/routes/Nodes/index.js | 12 +- webapp/src/routes/Nodes/styles.js | 17 +-- webapp/src/routes/ProducerProfile/styles.js | 8 +- .../useProducerProfileState.js | 11 +- 27 files changed, 360 insertions(+), 349 deletions(-) create mode 100644 webapp/src/components/EmptyState/EmptyStateRow.js delete mode 100644 webapp/src/components/InformationCard/EmptyStateRow.js delete mode 100644 webapp/src/components/InformationCard/index.js delete mode 100644 webapp/src/components/InformationCard/styles.js create mode 100644 webapp/src/components/NodesList/NodesRow.js create mode 100644 webapp/src/components/NodesList/index.js create mode 100644 webapp/src/components/NodesList/styles.js rename webapp/src/components/{InformationCard => ProducersTable}/MainSocialLinks.js (59%) rename webapp/src/components/{InformationCard => ProducersTable}/ProducerRow.js (98%) rename webapp/src/components/{InformationCard/ProducersTable.js => ProducersTable/index.js} (100%) create mode 100644 webapp/src/components/ProducersTable/styles.js diff --git a/webapp/src/components/EmptyState/EmptyStateRow.js b/webapp/src/components/EmptyState/EmptyStateRow.js new file mode 100644 index 000000000..da9572565 --- /dev/null +++ b/webapp/src/components/EmptyState/EmptyStateRow.js @@ -0,0 +1,22 @@ +import React from 'react' +import { makeStyles } from '@mui/styles' +import { useTranslation } from 'react-i18next' + +import styles from './styles' +import AlertSvg from 'components/Icons/Alert' + +const useStyles = makeStyles(styles) + +const EmptyStateRow = () => { + const classes = useStyles() + const { t } = useTranslation('producerCardComponent') + + return ( +
+ + {t('emptyState')} +
+ ) +} + +export default EmptyStateRow diff --git a/webapp/src/components/EmptyState/index.js b/webapp/src/components/EmptyState/index.js index b197bdf0f..a2211fdec 100644 --- a/webapp/src/components/EmptyState/index.js +++ b/webapp/src/components/EmptyState/index.js @@ -13,7 +13,7 @@ const EmptyState = () => { const { t } = useTranslation('producerCardComponent') return ( -
+
({ flexDirection: 'column', alignItems: 'center', width: '100%', + '& a': { + color: theme.palette.primary.main, + textDecorationColor: theme.palette.primary.main, + }, + }, + emptyStateContainer: { '& span': { width: '16em', height: '45px', @@ -16,9 +22,14 @@ export default (theme) => ({ textAlign: 'center', color: '#3d3d3dde', }, - '& a': { - color: theme.palette.primary.main, - textDecorationColor: theme.palette.primary.main, + }, + emptyStateRow: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + '& span': { + marginLeft: theme.spacing(5), + marginTop: theme.spacing(1), }, }, imgError: { diff --git a/webapp/src/components/InformationCard/EmptyStateRow.js b/webapp/src/components/InformationCard/EmptyStateRow.js deleted file mode 100644 index 97d5c77f9..000000000 --- a/webapp/src/components/InformationCard/EmptyStateRow.js +++ /dev/null @@ -1,24 +0,0 @@ -import React, { memo } from 'react' -import PropTypes from 'prop-types' - -import AlertSvg from 'components/Icons/Alert' - -const EmptyStateRow = ({ classes, t }) => { - return ( -
- - {t('emptyState')} -
- ) -} - -EmptyStateRow.propTypes = { - classes: PropTypes.object, - t: PropTypes.func, -} - -EmptyStateRow.defaultProps = { - classes: {}, -} - -export default memo(EmptyStateRow) diff --git a/webapp/src/components/InformationCard/index.js b/webapp/src/components/InformationCard/index.js deleted file mode 100644 index 9b51757ac..000000000 --- a/webapp/src/components/InformationCard/index.js +++ /dev/null @@ -1,113 +0,0 @@ -/* eslint camelcase: 0 */ -import React, { memo, useState, useEffect } from 'react' -import PropTypes from 'prop-types' -import { useTheme } from '@mui/material/styles' -import { makeStyles } from '@mui/styles' -import Card from '@mui/material/Card' -import CardHeader from '@mui/material/CardHeader' -import CardActions from '@mui/material/CardActions' -import Collapse from '@mui/material/Collapse' -import { useTranslation } from 'react-i18next' -import Typography from '@mui/material/Typography' -import Button from '@mui/material/Button' -import useMediaQuery from '@mui/material/useMediaQuery' - -import { formatData } from '../../utils' -import NodesCard from '../NodeCard/NodesCard' -import ProducerName from 'components/ProducerName' -import ViewBPProfile from 'components/ViewBPProfile' - -import styles from './styles' - -const useStyles = makeStyles(styles) - -const InformationCard = ({ producer, rank, type }) => { - const classes = useStyles() - const theme = useTheme() - const { t } = useTranslation('producerCardComponent') - const matches = useMediaQuery(theme.breakpoints.up('lg')) - const [expanded, setExpanded] = useState(false) - const [producerOrg, setProducerOrg] = useState({}) - - const handleExpandClick = () => { - setExpanded(!expanded) - } - - useEffect(() => { - setProducerOrg( - formatData({ - data: producer.bp_json?.org || {}, - rank, - owner: producer.owner, - healthStatus: producer.health_status, - dataType: producer.bp_json?.type, - totalRewards: producer.total_rewards, - }), - ) - // eslint-disable-next-line - }, [producer]) - - if (!producerOrg || !Object.keys(producerOrg)?.length) return <> - - return ( - - -
- {producer?.rank && ( - {`${producer?.rank}`} - )} - 5} - /> - {type === 'node' && ( - <> - -
- {' '} -
-
- - )} - -
- {type === 'node' && !matches && ( - -
- -
-
- )} -
- ) -} - -InformationCard.propTypes = { - producer: PropTypes.any, - rank: PropTypes.number, - type: PropTypes.string, -} - -InformationCard.defaultProps = { - producer: {}, - rank: 0, - type: '', -} - -export default memo(InformationCard) diff --git a/webapp/src/components/InformationCard/styles.js b/webapp/src/components/InformationCard/styles.js deleted file mode 100644 index d5561cf3b..000000000 --- a/webapp/src/components/InformationCard/styles.js +++ /dev/null @@ -1,139 +0,0 @@ -export default (theme) => ({ - root: { - width: '100%', - marginBottom: theme.spacing(2), - paddingBottom: 0, - boxShadow: '0px 1px 3px 1px rgba(0, 0, 0, 0.15) !important', - '& .MuiCardHeader-title': { - textTransform: 'lowercase', - }, - '& .MuiCardHeader-root': { - padding: theme.spacing(2, 4, 0), - }, - [theme.breakpoints.up('sm')]: { - width: 300, - }, - [theme.breakpoints.up('lg')]: { - width: '100%', - paddingBottom: theme.spacing(4), - }, - }, - wrapper: { - display: 'flex', - flexDirection: 'column', - width: '100%', - padding: theme.spacing(0, 4, 0), - '& .MuiTypography-overline': { - marginLeft: 0, - fontWeight: '700', - }, - [theme.breakpoints.up('lg')]: { - minWidth: 980, - overflowY: 'hidden', - flexDirection: 'row', - alignItems: 'center', - '& .MuiTypography-overline': { - marginLeft: theme.spacing(3), - lineHeight: '0', - }, - }, - }, - nodesContainer: { - width: '100%', - overflowX: 'auto', - }, - hideScroll: { - overflowX: 'hidden', - }, - expandMore: { - width: '100%', - display: 'flex', - justifyContent: 'center', - '& .MuiButtonBase-root': { - textTransform: 'capitalize', - }, - }, - cardActions: { - display: 'flex', - [theme.breakpoints.up('lg')]: { - display: 'none', - }, - }, - collapse: { - width: '100%', - marginRight: theme.spacing(4), - [theme.breakpoints.up('lg')]: { - width: '55%', - }, - }, - emptyStateRow: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - '& span': { - width: '16em', - height: '45px', - fontSize: '1.2em', - fontWeight: 'bold', - fontStretch: 'normal', - fontStyle: 'normal', - lineHeight: '1.12', - letterSpacing: '-0.22px', - textAlign: 'center', - color: '#3d3d3dde', - marginLeft: theme.spacing(5), - marginTop: theme.spacing(1), - }, - }, - socialLinksContainer: { - display: 'grid', - gridTemplateColumns: 'repeat(3, 1fr)', - gap: theme.spacing(4), - '& svg': { - width: '30px', - height: '30px', - cursor: 'pointer', - '&:hover': { - transform: 'scale(1.3)', - }, - }, - }, - tableRow: { - '& .MuiTableCell-root': { - padding: `${theme.spacing(1, 2)} !important`, - }, - '& .MuiTypography-h2': { - fontSize: '1.5rem', - [theme.breakpoints.down('md')]: { - fontSize: '1.2rem', - }, - }, - '&:hover': { - backgroundColor: '#f0f3fa', - }, - }, - tableHead: { - borderBottom: `2px solid ${theme.palette.primary.main} !important`, - '& .MuiTableCell-root': { - padding: `${theme.spacing(0, 2, 2)} !important`, - }, - }, - hideOnMobile: { - [theme.breakpoints.down('md')]: { - display: 'none', - }, - }, - website: { - [theme.breakpoints.down('xl')]: { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - width: '140px', - }, - }, - country: { - [theme.breakpoints.down('md')]: { - textAlign: 'center' - }, - } -}) diff --git a/webapp/src/components/NodeCard/NodesCard.js b/webapp/src/components/NodeCard/NodesCard.js index f3043c331..ed7b6f7de 100644 --- a/webapp/src/components/NodeCard/NodesCard.js +++ b/webapp/src/components/NodeCard/NodesCard.js @@ -7,7 +7,6 @@ import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import Chip from '@mui/material/Chip' import KeyOutlinedIcon from '@mui/icons-material/KeyOutlined'; -import 'flag-icon-css/css/flag-icons.css' import ChipList from '../ChipList' import CountryFlag from '../CountryFlag' @@ -113,7 +112,7 @@ const NodesCard = ({ nodes, hideFeatures = false }) => { const Location = ({ location }) => { return ( <> - {location?.name || 'N/A'} + {location?.name || 'N/A'} ) diff --git a/webapp/src/components/NodeCard/index.js b/webapp/src/components/NodeCard/index.js index 67ad77d3e..2b81431e7 100644 --- a/webapp/src/components/NodeCard/index.js +++ b/webapp/src/components/NodeCard/index.js @@ -8,7 +8,6 @@ import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import CardActions from '@mui/material/CardActions' import { useQuery } from '@apollo/client' -import 'flag-icon-css/css/flag-icons.css' import { NODE_CPU_BENCHMARK } from '../../gql' @@ -87,7 +86,7 @@ const NodeCard = ({ producer, node }) => { title={title} subheader={ <> - + {node.location?.name || 'N/A'} diff --git a/webapp/src/components/NodeCard/styles.js b/webapp/src/components/NodeCard/styles.js index f2f006291..be0de8b4d 100644 --- a/webapp/src/components/NodeCard/styles.js +++ b/webapp/src/components/NodeCard/styles.js @@ -13,12 +13,6 @@ export default (theme) => ({ textTransform: 'unset !important', }, }, - avatar: { - width: 30, - height: 30, - borderRadius: '100%', - backgroundColor: theme.palette.primary.contrastText, - }, dl: { marginTop: -16, marginBottom: -16, @@ -48,7 +42,13 @@ export default (theme) => ({ width: 'max-content', flexFlow: 'row wrap', gap: theme.spacing(2), - padding: theme.spacing(0, 2, 0), + padding: theme.spacing(0, 2, 2), + [theme.breakpoints.down('md')]: { + justifyContent: 'center', + width: 'auto', + padding: 0, + marginBottom: theme.spacing(8), + }, }, endpointsTitle: { display: 'flex', diff --git a/webapp/src/components/NodesList/NodesRow.js b/webapp/src/components/NodesList/NodesRow.js new file mode 100644 index 000000000..ae2b38441 --- /dev/null +++ b/webapp/src/components/NodesList/NodesRow.js @@ -0,0 +1,109 @@ +/* eslint camelcase: 0 */ + +import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { makeStyles } from '@mui/styles' +import { useTranslation } from 'react-i18next' +import Typography from '@mui/material/Typography' +import Button from '@mui/material/Button' + +import { formatData } from '../../utils' +import NodesCard from '../NodeCard/NodesCard' +import ProducerName from 'components/ProducerName' +import ViewBPProfile from 'components/ViewBPProfile' + +import styles from './styles' +import Tooltip from 'components/Tooltip' +import { eosConfig } from 'config' + +const useStyles = makeStyles(styles) + +const NodesRow = ({ producer }) => { + const classes = useStyles() + const { t } = useTranslation('producerCardComponent') + const [producerOrg, setProducerOrg] = useState({}) + const [anchorEl, setAnchorEl] = useState(null) + + const handlePopoverOpen = (target) => { + setAnchorEl(target) + } + + const handlePopoverClose = () => { + setAnchorEl(null) + } + + const openPopOver = (event) => { + handlePopoverOpen(event.target) + } + + useEffect(() => { + setProducerOrg( + formatData({ + data: producer.bp_json?.org || {}, + rank: producer.rank, + owner: producer.owner, + healthStatus: producer.health_status, + dataType: producer.bp_json?.type, + totalRewards: producer.total_rewards, + }), + ) + // eslint-disable-next-line + }, [producer]) + + if (!producerOrg || !Object.keys(producerOrg)?.length) return <> + + return ( +
+ + <> + + {t('nodes')} + + {' '} + + + {producer?.rank && eosConfig.producerColumns?.includes('rank') ? ( + {`${producer?.rank}`} + ) : ( + + )} + + 5} + /> +
+ +
+
+ +
+
+
+ {' '} +
+
+ ) +} + +NodesRow.propTypes = { + producer: PropTypes.object, +} + +NodesRow.defaultProps = { + producer: {}, +} + +export default NodesRow diff --git a/webapp/src/components/NodesList/index.js b/webapp/src/components/NodesList/index.js new file mode 100644 index 000000000..3f7148de1 --- /dev/null +++ b/webapp/src/components/NodesList/index.js @@ -0,0 +1,55 @@ +/* eslint camelcase: 0 */ + +import React from 'react' +import PropTypes from 'prop-types' +import { makeStyles } from '@mui/styles' +import { useTranslation } from 'react-i18next' +import Typography from '@mui/material/Typography' + +import { eosConfig } from 'config' + +import styles from './styles' +import NodesRow from './NodesRow' + +const useStyles = makeStyles(styles) + +const NodeList = ({ producers }) => { + const classes = useStyles() + const { t } = useTranslation('producerCardComponent') + + const Header = () => { + return ( +
+ {eosConfig.producerColumns?.includes('rank') ? ( + {t('rank')} + ) : ( + + )} + {t('producerName')} + {t('nodes')} + +
+ ) + } + + return ( + <> +
+ {producers.map((producer, index) => ( + + ))} + + ) +} + +NodeList.propTypes = { + producers: PropTypes.array, +} + +NodeList.defaultProps = { + producers: [], +} + +export default NodeList diff --git a/webapp/src/components/NodesList/styles.js b/webapp/src/components/NodesList/styles.js new file mode 100644 index 000000000..817d7edb5 --- /dev/null +++ b/webapp/src/components/NodesList/styles.js @@ -0,0 +1,46 @@ +export default (theme) => ({ + buttonContainer: { + display: 'flex', + justifyContent: 'center', + padding: theme.spacing(2, 0, 2), + }, + nodesContainer: { + width: '100%', + overflowX: 'auto', + }, + hideOnMobile: { + [theme.breakpoints.down('md')]: { + display: 'none !important', + }, + }, + hideOnDesktop: { + [theme.breakpoints.up('md')]: { + display: 'none !important', + }, + }, + cardRow: { + display: 'grid', + gridTemplateColumns: '60px 240px 4fr', + alignItems: 'center', + width: '100%', + [theme.breakpoints.down('md')]: { + display: 'flex', + flexDirection: 'column', + }, + }, + nodesRow: { + marginBottom: theme.spacing(4), + [theme.breakpoints.down('md')]: { + margin: 0, + flexGrow: 1, + width: 'auto', + }, + '&:hover': { + backgroundColor: '#f0f3fa', + }, + }, + columnsContainer: { + height: '50px', + borderBottom: `2px solid ${theme.palette.primary.main}`, + }, +}) diff --git a/webapp/src/components/ProducerName/index.js b/webapp/src/components/ProducerName/index.js index 48efef2ad..62387b58f 100644 --- a/webapp/src/components/ProducerName/index.js +++ b/webapp/src/components/ProducerName/index.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { memo } from 'react' import { makeStyles } from '@mui/styles' import Typography from '@mui/material/Typography' @@ -39,4 +39,4 @@ const ProducerName = ({ name, logo, text, lazy = true, account = '', size = 'sma ) } -export default ProducerName +export default memo(ProducerName) diff --git a/webapp/src/components/ProducerName/styles.js b/webapp/src/components/ProducerName/styles.js index a54edf31f..f19fcceaa 100644 --- a/webapp/src/components/ProducerName/styles.js +++ b/webapp/src/components/ProducerName/styles.js @@ -14,12 +14,20 @@ export default (theme) => ({ }, bigContainer: { width: '100%', + padding: theme.spacing(0, 4, 0) }, smallContainer: { width: '170px', [theme.breakpoints.down('md')]: { width: '150px', }, + '& .MuiTypography-h2': { + fontWeight: 'bold', + fontSize: '1.5rem', + [theme.breakpoints.down('xl')]: { + fontSize: '1rem', + }, + }, }, socialContainer: { display: 'flex', diff --git a/webapp/src/components/InformationCard/MainSocialLinks.js b/webapp/src/components/ProducersTable/MainSocialLinks.js similarity index 59% rename from webapp/src/components/InformationCard/MainSocialLinks.js rename to webapp/src/components/ProducersTable/MainSocialLinks.js index b33a8d1e4..049256aa6 100644 --- a/webapp/src/components/InformationCard/MainSocialLinks.js +++ b/webapp/src/components/ProducersTable/MainSocialLinks.js @@ -21,27 +21,30 @@ const MainSocialLinks = ({ social, name }) => { } social.forEach((item, index) => { - if (index > socialMediaNames.length || !socialMediaNames.includes(item?.name)) return + if ( + index > socialMediaNames.length || + !socialMediaNames.includes(item?.name) + ) + return links[item?.name] = item.url }) return (
- {socialMediaNames.map( - (socialMedia, index) => - links[socialMedia] ? ( - - {icons[socialMedia]} - - ) : ( - - ), + {socialMediaNames.map((socialMedia, index) => + links[socialMedia] ? ( + + {icons[socialMedia]} + + ) : ( + + ), )}
) diff --git a/webapp/src/components/InformationCard/ProducerRow.js b/webapp/src/components/ProducersTable/ProducerRow.js similarity index 98% rename from webapp/src/components/InformationCard/ProducerRow.js rename to webapp/src/components/ProducersTable/ProducerRow.js index b2c96c140..60c2c3c48 100644 --- a/webapp/src/components/InformationCard/ProducerRow.js +++ b/webapp/src/components/ProducersTable/ProducerRow.js @@ -16,7 +16,7 @@ import ViewBPProfile from 'components/ViewBPProfile' import styles from './styles' import MainSocialLinks from './MainSocialLinks' -import EmptyStateRow from './EmptyStateRow' +import EmptyStateRow from 'components/EmptyState/EmptyStateRow' const useStyles = makeStyles(styles) diff --git a/webapp/src/components/InformationCard/ProducersTable.js b/webapp/src/components/ProducersTable/index.js similarity index 100% rename from webapp/src/components/InformationCard/ProducersTable.js rename to webapp/src/components/ProducersTable/index.js diff --git a/webapp/src/components/ProducersTable/styles.js b/webapp/src/components/ProducersTable/styles.js new file mode 100644 index 000000000..07c77777f --- /dev/null +++ b/webapp/src/components/ProducersTable/styles.js @@ -0,0 +1,53 @@ +export default (theme) => ({ + socialLinksContainer: { + display: 'grid', + gridTemplateColumns: 'repeat(3, 1fr)', + gap: theme.spacing(4), + '& svg': { + width: '24px', + height: '24px', + cursor: 'pointer', + '&:hover': { + transform: 'scale(1.3)', + }, + }, + }, + tableRow: { + '& .MuiTableCell-root': { + padding: `${theme.spacing(1, 2)} !important`, + }, + '&:hover': { + backgroundColor: '#f0f3fa', + }, + }, + tableHead: { + borderBottom: `2px solid ${theme.palette.primary.main} !important`, + '& .MuiTableCell-root': { + padding: `${theme.spacing(0, 2, 2)} !important`, + }, + }, + hideOnMobile: { + [theme.breakpoints.down('md')]: { + display: 'none !important', + }, + }, + hideOnDesktop: { + [theme.breakpoints.up('md')]: { + display: 'none !important', + }, + }, + website: { + [theme.breakpoints.down('xl')]: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + width: '140px', + }, + }, + country: { + [theme.breakpoints.down('md')]: { + textAlign: 'center' + }, + }, + }) + \ No newline at end of file diff --git a/webapp/src/components/ViewBPProfile/index.js b/webapp/src/components/ViewBPProfile/index.js index a47e682f6..4a5bf2045 100644 --- a/webapp/src/components/ViewBPProfile/index.js +++ b/webapp/src/components/ViewBPProfile/index.js @@ -3,7 +3,6 @@ import { Link } from 'react-router-dom' import { makeStyles } from '@mui/styles' import { useTranslation } from 'react-i18next' import Button from '@mui/material/Button' -import LaunchIcon from '@mui/icons-material/Launch' import { eosConfig } from '../../config' @@ -24,7 +23,6 @@ const ViewBPProfile = ({ producer }) => { variant="contained" color="primary" mt={2} - endIcon={} > {t('viewProfile')} diff --git a/webapp/src/components/ViewBPProfile/styles.js b/webapp/src/components/ViewBPProfile/styles.js index b6f2de29a..9ee446f95 100644 --- a/webapp/src/components/ViewBPProfile/styles.js +++ b/webapp/src/components/ViewBPProfile/styles.js @@ -1,5 +1,5 @@ export default (theme) => ({ button: { - minWidth: '156px !important', + minWidth: '132px !important', }, }) diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json index 0c3a4a653..9bdf1c2b2 100644 --- a/webapp/src/language/en.json +++ b/webapp/src/language/en.json @@ -373,7 +373,8 @@ "BPonNetwork": "{{position}} Block Producer on {{networkName}}", "generalInformation": "General Information", "logo_256": "Logo", - "organization_name": "Name" + "organization_name": "Name", + "viewNodes": "View {{totalNodes}} nodes" }, "nodeCardComponent": { "features": "Features", diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json index a2331b3ec..7a27fcfaf 100644 --- a/webapp/src/language/es.json +++ b/webapp/src/language/es.json @@ -371,7 +371,8 @@ "viewProfile": "Ver Perfil del BP", "BPonNetwork": "{{position}} Productor de Bloques en {{networkName}}", "generalInformation": "Información General", - "organization_name": "Nombre" + "organization_name": "Nombre", + "viewNodes": "Ver {{totalNodes}} nodos" }, "nodeCardComponent": { "features": "Características", diff --git a/webapp/src/routes/BlockProducers/index.js b/webapp/src/routes/BlockProducers/index.js index f875e988d..a97950b1f 100644 --- a/webapp/src/routes/BlockProducers/index.js +++ b/webapp/src/routes/BlockProducers/index.js @@ -12,7 +12,7 @@ import SearchBar from '../../components/SearchBar' import useBlockProducerState from '../../hooks/customHooks/useBlockProducerState' import NoResults from '../../components/NoResults' import ProducersUpdateLog from '../../components/ProducersUpdateLog' -import ProducersTable from '../../components/InformationCard/ProducersTable' +import ProducersTable from '../../components/ProducersTable' import styles from './styles' diff --git a/webapp/src/routes/Nodes/index.js b/webapp/src/routes/Nodes/index.js index be5cc39ac..f06ebc751 100644 --- a/webapp/src/routes/Nodes/index.js +++ b/webapp/src/routes/Nodes/index.js @@ -1,15 +1,14 @@ /* eslint camelcase: 0 */ import React, { lazy, memo } from 'react' import { makeStyles } from '@mui/styles' - import useNodeState from '../../hooks/customHooks/useNodeState' import styles from './styles' const LinearProgress = lazy(() => import('@mui/material/LinearProgress')) const Pagination = lazy(() => import('@mui/material/Pagination')) +const NodesList = lazy(() => import('../../components/NodesList')) const SearchBar = lazy(() => import('../../components/SearchBar')) -const InformationCard = lazy(() => import('../../components/InformationCard')) const NoResults = lazy(() => import('../../components/NoResults')) const ProducersUpdateLog = lazy(() => import('../../components/ProducersUpdateLog'), @@ -41,14 +40,7 @@ const Nodes = () => { <>
{!!items?.length ? ( - items.map((producer, index) => ( - - )) + ) : ( )} diff --git a/webapp/src/routes/Nodes/styles.js b/webapp/src/routes/Nodes/styles.js index 9c5faa733..27443d66b 100644 --- a/webapp/src/routes/Nodes/styles.js +++ b/webapp/src/routes/Nodes/styles.js @@ -2,19 +2,10 @@ export default (theme) => ({ container: { display: 'flex', flexFlow: 'row wrap', - justifyContent: 'space-between', - paddingTop: theme.spacing(2), - }, - card: { - width: '100%', - display: 'flex', - marginRight: theme.spacing(2), - [theme.breakpoints.up('sm')]: { - justifyContent: 'center', - flex: 'content', - }, - [theme.breakpoints.down('sm')]: { - flex: 'auto', + marginTop: theme.spacing(4), + [theme.breakpoints.down('md')]: { + gap: theme.spacing(4), + justifyContent: 'space-between', }, }, pagination: { diff --git a/webapp/src/routes/ProducerProfile/styles.js b/webapp/src/routes/ProducerProfile/styles.js index 999aa3e1a..30c9e60e3 100644 --- a/webapp/src/routes/ProducerProfile/styles.js +++ b/webapp/src/routes/ProducerProfile/styles.js @@ -9,19 +9,15 @@ export default (theme) => ({ gap: theme.spacing(4), marginTop: theme.spacing(5), '& > div': { - padding: 0, gap: theme.spacing(4), - [theme.breakpoints.down('sm')]: { - justifyContent: 'center', - } }, }, profile: { display: 'flex', - flexDirection: 'column', + justifyContent: 'space-between', padding: theme.spacing(6), '& > div + div': { - padding: theme.spacing(0, 6), + padding: theme.spacing(0, 6, 2), }, }, socialLinks: { diff --git a/webapp/src/routes/ProducerProfile/useProducerProfileState.js b/webapp/src/routes/ProducerProfile/useProducerProfileState.js index 8e32cec92..5d64dec4c 100644 --- a/webapp/src/routes/ProducerProfile/useProducerProfileState.js +++ b/webapp/src/routes/ProducerProfile/useProducerProfileState.js @@ -6,8 +6,9 @@ import { NODES_BY_PRODUCER_SUBSCRIPTION, EOSRATE_STATS_QUERY, } from '../../gql' -import isValidAccountName from 'utils/validate-account-name' +import { generalConfig } from '../../config' import { formatData } from '../../utils/formatData' +import isValidAccountName from 'utils/validate-account-name' import sortNodes from 'utils/sort-nodes' const useProducerProfileState = (name, previousData) => { @@ -52,9 +53,11 @@ const useProducerProfileState = (name, previousData) => { useEffect(() => { if (isValidName) { - loadStats({ - variables: { bp: name }, - }) + if (generalConfig.eosRateLink){ + loadStats({ + variables: { bp: name }, + }) + } if (previousData) { setProducer(getProducerData(previousData)) From 18e74d71a4a4cd0fdc7fba06e98973d6cf86e307 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Tue, 10 Oct 2023 17:17:02 -0600 Subject: [PATCH 24/41] chore(webapp): change empty state icon --- webapp/src/components/Icons/Alert.js | 19 ++++++++++--------- webapp/src/components/NodesList/NodesRow.js | 2 +- webapp/src/language/en.json | 3 ++- webapp/src/language/es.json | 3 ++- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/webapp/src/components/Icons/Alert.js b/webapp/src/components/Icons/Alert.js index e9bd641c7..1b02082fd 100644 --- a/webapp/src/components/Icons/Alert.js +++ b/webapp/src/components/Icons/Alert.js @@ -2,23 +2,24 @@ import React from 'react' const AlertSvg = () => ( ) diff --git a/webapp/src/components/NodesList/NodesRow.js b/webapp/src/components/NodesList/NodesRow.js index ae2b38441..4fa06210b 100644 --- a/webapp/src/components/NodesList/NodesRow.js +++ b/webapp/src/components/NodesList/NodesRow.js @@ -61,7 +61,7 @@ const NodesRow = ({ producer }) => { > <> - {t('nodes')} + {t('bpNodes', {bpName: producerOrg?.media?.name })} {' '} diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json index 9bdf1c2b2..f083eee93 100644 --- a/webapp/src/language/en.json +++ b/webapp/src/language/en.json @@ -374,7 +374,8 @@ "generalInformation": "General Information", "logo_256": "Logo", "organization_name": "Name", - "viewNodes": "View {{totalNodes}} nodes" + "viewNodes": "View {{totalNodes}} nodes", + "bpNodes": "{{bpName}} Nodes" }, "nodeCardComponent": { "features": "Features", diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json index 7a27fcfaf..12ecdcf40 100644 --- a/webapp/src/language/es.json +++ b/webapp/src/language/es.json @@ -372,7 +372,8 @@ "BPonNetwork": "{{position}} Productor de Bloques en {{networkName}}", "generalInformation": "Información General", "organization_name": "Nombre", - "viewNodes": "Ver {{totalNodes}} nodos" + "viewNodes": "Ver {{totalNodes}} nodos", + "bpNodes": "Nodos de {{bpName}}" }, "nodeCardComponent": { "features": "Características", From 20208c917176a239e33330af0ea28ea9d5002c35 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 09:37:37 -0600 Subject: [PATCH 25/41] chore(webapp): apply styles adjustments --- webapp/src/components/ComplianceBar/index.js | 2 +- webapp/src/components/EmptyState/styles.js | 1 - webapp/src/components/NodesList/NodesRow.js | 2 +- webapp/src/components/NodesList/index.js | 2 +- webapp/src/components/ProducerName/styles.js | 4 +-- .../components/ProducersTable/ProducerRow.js | 27 ++++++++++++++----- .../src/components/ProducersTable/styles.js | 9 +++++-- webapp/src/components/VisitSite/index.js | 8 ++++-- webapp/src/routes/BlockProducers/index.js | 23 +++++++++------- 9 files changed, 52 insertions(+), 26 deletions(-) diff --git a/webapp/src/components/ComplianceBar/index.js b/webapp/src/components/ComplianceBar/index.js index c536900f7..25d2703a3 100644 --- a/webapp/src/components/ComplianceBar/index.js +++ b/webapp/src/components/ComplianceBar/index.js @@ -8,7 +8,7 @@ import styles from './styles' const useStyles = makeStyles(styles) const PercentageBar = styled.div` - width: 100%; + width: 80%; height: 8px; & div { border-radius: ${props => props.theme.spacing(4)}; diff --git a/webapp/src/components/EmptyState/styles.js b/webapp/src/components/EmptyState/styles.js index e3d933bff..3d23cb0ba 100644 --- a/webapp/src/components/EmptyState/styles.js +++ b/webapp/src/components/EmptyState/styles.js @@ -28,7 +28,6 @@ export default (theme) => ({ alignItems: 'center', justifyContent: 'center', '& span': { - marginLeft: theme.spacing(5), marginTop: theme.spacing(1), }, }, diff --git a/webapp/src/components/NodesList/NodesRow.js b/webapp/src/components/NodesList/NodesRow.js index 4fa06210b..a3bd60f35 100644 --- a/webapp/src/components/NodesList/NodesRow.js +++ b/webapp/src/components/NodesList/NodesRow.js @@ -53,7 +53,7 @@ const NodesRow = ({ producer }) => { if (!producerOrg || !Object.keys(producerOrg)?.length) return <> return ( -
+
{ const Header = () => { return (
{eosConfig.producerColumns?.includes('rank') ? ( {t('rank')} diff --git a/webapp/src/components/ProducerName/styles.js b/webapp/src/components/ProducerName/styles.js index f19fcceaa..35a9be770 100644 --- a/webapp/src/components/ProducerName/styles.js +++ b/webapp/src/components/ProducerName/styles.js @@ -17,9 +17,9 @@ export default (theme) => ({ padding: theme.spacing(0, 4, 0) }, smallContainer: { - width: '170px', + width: '140px', [theme.breakpoints.down('md')]: { - width: '150px', + width: '120px', }, '& .MuiTypography-h2': { fontWeight: 'bold', diff --git a/webapp/src/components/ProducersTable/ProducerRow.js b/webapp/src/components/ProducersTable/ProducerRow.js index 60c2c3c48..22ba697a4 100644 --- a/webapp/src/components/ProducersTable/ProducerRow.js +++ b/webapp/src/components/ProducersTable/ProducerRow.js @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next' import Link from '@mui/material/Link' import Typography from '@mui/material/Typography' import TableCell from '@mui/material/TableCell' +import LanguageIcon from '@mui/icons-material/Language' import { formatData, formatWithThousandSeparator } from '../../utils' import { eosConfig } from '../../config' @@ -13,10 +14,11 @@ import ProducerName from 'components/ProducerName' import ComplianceBar from 'components/ComplianceBar' import CountryFlag from 'components/CountryFlag' import ViewBPProfile from 'components/ViewBPProfile' +import EmptyStateRow from 'components/EmptyState/EmptyStateRow' +import VisitSite from 'components/VisitSite' import styles from './styles' import MainSocialLinks from './MainSocialLinks' -import EmptyStateRow from 'components/EmptyState/EmptyStateRow' const useStyles = makeStyles(styles) @@ -28,19 +30,23 @@ const ProducerRow = ({ producer, index }) => { const BlockProducerInfo = () => { if (producerOrg?.hasEmptyBPJson) return ( - + ) return ( <> - - + + + +
{producerOrg?.location}
-
@@ -49,9 +55,18 @@ const ProducerRow = ({ producer, index }) => { target="_blank" rel="nofollow noopener noreferrer" > - + {producerOrg?.media?.website} + + + {eosConfig.producerColumns?.includes('votes') && ( diff --git a/webapp/src/components/ProducersTable/styles.js b/webapp/src/components/ProducersTable/styles.js index 07c77777f..823900ee6 100644 --- a/webapp/src/components/ProducersTable/styles.js +++ b/webapp/src/components/ProducersTable/styles.js @@ -36,15 +36,20 @@ export default (theme) => ({ display: 'none !important', }, }, - website: { + websiteContainer: { [theme.breakpoints.down('xl')]: { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', width: '140px', }, + [theme.breakpoints.down('md')]: { + '& > a': { + margin: 0, + }, + }, }, - country: { + countryContainer: { [theme.breakpoints.down('md')]: { textAlign: 'center' }, diff --git a/webapp/src/components/VisitSite/index.js b/webapp/src/components/VisitSite/index.js index c83295b3c..1bbaeb80e 100644 --- a/webapp/src/components/VisitSite/index.js +++ b/webapp/src/components/VisitSite/index.js @@ -7,7 +7,7 @@ import styles from './styles' const useStyles = makeStyles(styles) -const VisitSite = ({ title, url, placement = 'left' }) => { +const VisitSite = ({ title, url, Icon, placement = 'left' }) => { const classes = useStyles() return ( @@ -19,7 +19,11 @@ const VisitSite = ({ title, url, placement = 'left' }) => { className={classes.link} aria-label={url} > - + {Icon ? ( + + ) : ( + + )} ) diff --git a/webapp/src/routes/BlockProducers/index.js b/webapp/src/routes/BlockProducers/index.js index a97950b1f..8a0c4f7f1 100644 --- a/webapp/src/routes/BlockProducers/index.js +++ b/webapp/src/routes/BlockProducers/index.js @@ -77,19 +77,22 @@ const Producers = () => { {loading ? ( ) : !!items?.length ? ( -
- -
+ <> +
+ +
+ + ) : ( )} - + ) } From 864b82f888afc86eafdf062eae1ee47659feb895 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 11:44:29 -0600 Subject: [PATCH 26/41] chore(webapp): add background image for BP profile page --- docs/images/profile-bg-image.webp | Bin 0 -> 17590 bytes webapp/src/components/EmptyState/styles.js | 3 +-- webapp/src/components/ProducerName/styles.js | 7 ++++++- webapp/src/routes/ProducerProfile/index.js | 16 ++++++---------- webapp/src/routes/ProducerProfile/styles.js | 18 +++++++++++++----- 5 files changed, 26 insertions(+), 18 deletions(-) create mode 100755 docs/images/profile-bg-image.webp diff --git a/docs/images/profile-bg-image.webp b/docs/images/profile-bg-image.webp new file mode 100755 index 0000000000000000000000000000000000000000..80fbdc288e6f4ccbf3cdc7f5f8bded63cc9b2918 GIT binary patch literal 17590 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 z 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 UQMNoZ=W>CJ$v^l&sB-`S0NTj3PXGV_ literal 0 HcmV?d00001 diff --git a/webapp/src/components/EmptyState/styles.js b/webapp/src/components/EmptyState/styles.js index 3d23cb0ba..022679074 100644 --- a/webapp/src/components/EmptyState/styles.js +++ b/webapp/src/components/EmptyState/styles.js @@ -13,11 +13,10 @@ export default (theme) => ({ '& span': { width: '16em', height: '45px', - fontSize: '1.2em', + fontSize: '1em', fontWeight: 'bold', fontStretch: 'normal', fontStyle: 'normal', - lineHeight: '1.12', letterSpacing: '-0.22px', textAlign: 'center', color: '#3d3d3dde', diff --git a/webapp/src/components/ProducerName/styles.js b/webapp/src/components/ProducerName/styles.js index 35a9be770..cbe459ebd 100644 --- a/webapp/src/components/ProducerName/styles.js +++ b/webapp/src/components/ProducerName/styles.js @@ -5,6 +5,7 @@ export default (theme) => ({ '& img': { borderRadius: '50%', aspectRatio: '1 / 1', + backgroundColor: '#FFF', }, }, nameContainer: { @@ -14,7 +15,11 @@ export default (theme) => ({ }, bigContainer: { width: '100%', - padding: theme.spacing(0, 4, 0) + minWidth: '270px', + padding: theme.spacing(0, 4, 0), + [theme.breakpoints.down('sm')]: { + minWidth: '150px', + }, }, smallContainer: { width: '140px', diff --git a/webapp/src/routes/ProducerProfile/index.js b/webapp/src/routes/ProducerProfile/index.js index ca5f58e94..4e0d0231c 100644 --- a/webapp/src/routes/ProducerProfile/index.js +++ b/webapp/src/routes/ProducerProfile/index.js @@ -2,8 +2,6 @@ import React, { lazy, Suspense } from 'react' import { useParams, Navigate, useLocation } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { makeStyles } from '@mui/styles' -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' import CircularProgress from '@mui/material/CircularProgress' import Typography from '@mui/material/Typography' @@ -48,12 +46,10 @@ const ProducerProfile = () => { const WrapperContainer = ({ title, children }) => { return ( - - - {title} -
{children}
-
-
+
+ {title} +
{children}
+
) } @@ -65,9 +61,9 @@ const ProducerProfile = () => { metaDescription={metaDescription} ldJson={ldJson} /> - +
- +
{producer?.hasEmptyBPJson && ( diff --git a/webapp/src/routes/ProducerProfile/styles.js b/webapp/src/routes/ProducerProfile/styles.js index 30c9e60e3..1e0c125fe 100644 --- a/webapp/src/routes/ProducerProfile/styles.js +++ b/webapp/src/routes/ProducerProfile/styles.js @@ -1,7 +1,9 @@ export default (theme) => ({ - card: { + container: { margin: theme.spacing(4, 0), - boxShadow: '0px 1px 5px rgba(0, 0, 0, 0.15) !important', + }, + profileContainer: { + padding: '0 !important', }, dataContainer: { display: 'flex', @@ -16,17 +18,23 @@ export default (theme) => ({ display: 'flex', justifyContent: 'space-between', padding: theme.spacing(6), - '& > div + div': { - padding: theme.spacing(0, 6, 2), + backgroundImage: 'url(https://antelope.tools/images/profile-bg-image.webp)', + backgroundRepeat: 'no-repeat', + backgroundSize: 'cover', + [theme.breakpoints.down('sm')]: { + flexDirection: 'column' }, }, socialLinks: { display: 'flex', gap: theme.spacing(5), alignSelf: 'end', + padding: theme.spacing(0, 0, 2), + [theme.breakpoints.down('md')]: { + flexWrap: 'wrap', + }, [theme.breakpoints.down('sm')]: { marginTop: theme.spacing(8), - flexWrap: 'wrap', justifyContent: 'center', alignSelf: 'center', }, From a1b81e1b450c2cb1601fb2fa64cf106eba44fed1 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 14:04:17 -0600 Subject: [PATCH 27/41] refactor(webapp): use theme to set the colors and add a class for cards --- webapp/src/components/ComplianceBar/styles.js | 2 +- webapp/src/components/EmptyState/styles.js | 2 +- webapp/src/components/NodeCard/styles.js | 2 +- webapp/src/components/NodesList/styles.js | 2 +- .../src/components/ProducersTable/styles.js | 2 +- webapp/src/components/Sidebar/styles.js | 2 +- .../src/components/SimpleDataCard/styles.js | 2 +- webapp/src/components/VisitSite/styles.js | 2 +- webapp/src/layouts/styles.js | 2 +- webapp/src/routes/BlockProducers/index.js | 2 +- webapp/src/routes/BlockProducers/styles.js | 5 -- webapp/src/routes/NonCompliantBPs/styles.js | 2 +- webapp/src/routes/ProducerProfile/styles.js | 4 +- webapp/src/theme/components.js | 14 +++ webapp/src/theme/typography.js | 1 - webapp/src/theme/variants.js | 88 +++++++++++++------ 16 files changed, 86 insertions(+), 48 deletions(-) diff --git a/webapp/src/components/ComplianceBar/styles.js b/webapp/src/components/ComplianceBar/styles.js index 9d4b769c4..4ffd2a3ba 100644 --- a/webapp/src/components/ComplianceBar/styles.js +++ b/webapp/src/components/ComplianceBar/styles.js @@ -5,6 +5,6 @@ export default (theme) => ({ alignItems: 'center', }, bar: { - backgroundColor: '#E0E0E0', + backgroundColor: theme.palette.neutral.light, }, }) diff --git a/webapp/src/components/EmptyState/styles.js b/webapp/src/components/EmptyState/styles.js index 022679074..d92168157 100644 --- a/webapp/src/components/EmptyState/styles.js +++ b/webapp/src/components/EmptyState/styles.js @@ -19,7 +19,7 @@ export default (theme) => ({ fontStyle: 'normal', letterSpacing: '-0.22px', textAlign: 'center', - color: '#3d3d3dde', + color: theme.palette.neutral.darker, }, }, emptyStateRow: { diff --git a/webapp/src/components/NodeCard/styles.js b/webapp/src/components/NodeCard/styles.js index be0de8b4d..cedbd27e3 100644 --- a/webapp/src/components/NodeCard/styles.js +++ b/webapp/src/components/NodeCard/styles.js @@ -31,7 +31,7 @@ export default (theme) => ({ paddingBottom: theme.spacing(4), }, boxShadow: '2px 3px 4px 0px #0000002E', - backgroundColor: '#f6f9fd', + backgroundColor: theme.palette.background.light, borderRadius: theme.spacing(3), [theme.breakpoints.down('sm')]: { width: '230px', diff --git a/webapp/src/components/NodesList/styles.js b/webapp/src/components/NodesList/styles.js index 817d7edb5..52d8686a4 100644 --- a/webapp/src/components/NodesList/styles.js +++ b/webapp/src/components/NodesList/styles.js @@ -36,7 +36,7 @@ export default (theme) => ({ width: 'auto', }, '&:hover': { - backgroundColor: '#f0f3fa', + backgroundColor: `${theme.palette.neutral.lighter}`, }, }, columnsContainer: { diff --git a/webapp/src/components/ProducersTable/styles.js b/webapp/src/components/ProducersTable/styles.js index 823900ee6..5fd4b3f39 100644 --- a/webapp/src/components/ProducersTable/styles.js +++ b/webapp/src/components/ProducersTable/styles.js @@ -17,7 +17,7 @@ export default (theme) => ({ padding: `${theme.spacing(1, 2)} !important`, }, '&:hover': { - backgroundColor: '#f0f3fa', + backgroundColor: `${theme.palette.neutral.lighter}`, }, }, tableHead: { diff --git a/webapp/src/components/Sidebar/styles.js b/webapp/src/components/Sidebar/styles.js index bc8b6919e..aebe0ef42 100644 --- a/webapp/src/components/Sidebar/styles.js +++ b/webapp/src/components/Sidebar/styles.js @@ -98,7 +98,7 @@ export default (theme, rgba) => ({ divider: { height: '1px', margin: theme.spacing(4), - backgroundColor: '#e0e0e0', + backgroundColor: theme.palette.neutral.light, }, sidebarSection: { color: theme.sidebar.color, diff --git a/webapp/src/components/SimpleDataCard/styles.js b/webapp/src/components/SimpleDataCard/styles.js index c7e6826c3..bc7fcd7bc 100644 --- a/webapp/src/components/SimpleDataCard/styles.js +++ b/webapp/src/components/SimpleDataCard/styles.js @@ -72,7 +72,7 @@ export default (theme) => ({ tooltip: { width: '18px !important', height: '18px !important', - color: '#3d3d3dde', + color: theme.palette.neutral.darker, '&:hover': { cursor: 'pointer', color: theme.palette.primary.main, diff --git a/webapp/src/components/VisitSite/styles.js b/webapp/src/components/VisitSite/styles.js index 43fc4a1c4..f5b9c15cd 100644 --- a/webapp/src/components/VisitSite/styles.js +++ b/webapp/src/components/VisitSite/styles.js @@ -6,7 +6,7 @@ export default (theme) => ({ marginLeft: theme.spacing(3), }, clickableIcon: { - color: 'black', + color: theme.palette.text.primary, cursor: 'pointer', '&:hover': { color: theme.palette.primary.main, diff --git a/webapp/src/layouts/styles.js b/webapp/src/layouts/styles.js index 92663b3ac..20b456593 100644 --- a/webapp/src/layouts/styles.js +++ b/webapp/src/layouts/styles.js @@ -51,7 +51,7 @@ export default (theme) => ({ alignItems: 'center', marginBottom: theme.spacing(4), paddingBottom: theme.spacing(4), - borderBottom: '1px solid #e0e0e0', + borderBottom: `1px solid ${theme.palette.neutral.light}`, width: '100%', '& h3': { marginTop: theme.spacing(4), diff --git a/webapp/src/routes/BlockProducers/index.js b/webapp/src/routes/BlockProducers/index.js index 8a0c4f7f1..fa68ac97a 100644 --- a/webapp/src/routes/BlockProducers/index.js +++ b/webapp/src/routes/BlockProducers/index.js @@ -78,7 +78,7 @@ const Producers = () => { ) : !!items?.length ? ( <> -
+
({ - container: { - padding: theme.spacing(4), - backgroundColor: '#FFF', - borderRadius: '4px !impotant', - }, searchWrapper: { marginTop: theme.spacing(3), marginBottom: theme.spacing(3), diff --git a/webapp/src/routes/NonCompliantBPs/styles.js b/webapp/src/routes/NonCompliantBPs/styles.js index d62e85f82..1bd987509 100644 --- a/webapp/src/routes/NonCompliantBPs/styles.js +++ b/webapp/src/routes/NonCompliantBPs/styles.js @@ -14,7 +14,7 @@ export default (theme) => ({ gap: theme.spacing(6), margin: `${theme.spacing(6)} 24px ${theme.spacing(4)}`, paddingBottom: theme.spacing(4), - borderBottom: '1px solid #e0e0e0', + borderBottom: `1px solid ${theme.palette.neutral.light}`, [theme.breakpoints.down('lg')]: { margin: theme.spacing(6, 0, 4), }, diff --git a/webapp/src/routes/ProducerProfile/styles.js b/webapp/src/routes/ProducerProfile/styles.js index 1e0c125fe..36ef3ff6c 100644 --- a/webapp/src/routes/ProducerProfile/styles.js +++ b/webapp/src/routes/ProducerProfile/styles.js @@ -39,7 +39,7 @@ export default (theme) => ({ alignSelf: 'center', }, '& svg': { - color: '#000000DE', + color: theme.palette.neutral.dark, '&:hover': { color: theme.palette.primary.main, transform: 'scale(1.3)', @@ -77,7 +77,7 @@ export default (theme) => ({ padding: theme.spacing(4), borderBottom: `2px solid ${theme.palette.primary.main}`, boxShadow: '0px -2px 8px 0px #0000004D', - backgroundColor: '#F6F9FD', + backgroundColor: theme.palette.background.light, [theme.breakpoints.down('md')]: { gap: theme.spacing(4), }, diff --git a/webapp/src/theme/components.js b/webapp/src/theme/components.js index 181cf4fb1..40c4d31e1 100644 --- a/webapp/src/theme/components.js +++ b/webapp/src/theme/components.js @@ -1,4 +1,18 @@ const components = { + MuiCssBaseline: { + styleOverrides: theme => + ` + .simple-card { + background-color: ${theme.palette.background.main}; + box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.15); + border-radius: 4px; + padding: ${theme.spacing(4)}; + }, + .MuiTypography-caption { + color: ${theme.palette.neutral.dark}; + } + `, + }, MuiButton: { styleOverrides: { root: { diff --git a/webapp/src/theme/typography.js b/webapp/src/theme/typography.js index 624a18f8f..b87b573e9 100644 --- a/webapp/src/theme/typography.js +++ b/webapp/src/theme/typography.js @@ -47,7 +47,6 @@ const typography = { lineHeight: 1.2 }, caption: { - color: '#4e4e4e', textTransform: 'uppercase', fontWeight: 700, lineHeight: 1.2, diff --git a/webapp/src/theme/variants.js b/webapp/src/theme/variants.js index 609f07bf7..2ad4e38a7 100644 --- a/webapp/src/theme/variants.js +++ b/webapp/src/theme/variants.js @@ -5,26 +5,39 @@ const lightVariant = { palette: { primary: { main: blue[800], - contrastText: '#FFF' + contrastText: '#FFF', }, secondary: { main: blue[600], - contrastText: '#FFF' + contrastText: '#FFF', }, tertiary: { main: '#00C853', - contrastText: '#FFF' + contrastText: '#FFF', + }, + text: { + primary: '#000', + }, + background: { + main: '#FFF', + light: '#F6F9FD', + }, + neutral: { + lighter: '#F0F3FA', + light: '#E0E0E0', + dark: '#4E4E4E', + darker: '#3D3D3DDE' } }, header: { color: grey[500], background: '#FFF', search: { - color: grey[800] + color: grey[800], }, indicator: { - background: blue[600] - } + background: blue[600], + }, }, sidebar: { color: grey[900], @@ -33,23 +46,23 @@ const lightVariant = { color: blue[800], background: '#FFF', brand: { - color: blue[800] - } + color: blue[800], + }, }, footer: { color: '#424242', - background: '#FFF' + background: '#FFF', }, category: { - fontWeight: 'normal' + fontWeight: 'normal', }, badge: { - color: '#FFF' - } + color: '#FFF', + }, }, body: { - background: '#F7F9FC' - } + background: '#F7F9FC', + }, } const darkVariant = { @@ -57,22 +70,39 @@ const darkVariant = { palette: { primary: { main: blue[700], - contrastText: '#FFF' + contrastText: '#FFF', }, secondary: { - main: blue[500], - contrastText: '#FFF' + main: blue[600], + contrastText: '#FFF', + }, + tertiary: { + main: '#00C853', + contrastText: '#FFF', + }, + text: { + primary: '#FFF', + }, + background: { + main: '#1B2430', + light: '#F6F9FD', + }, + neutral: { + lighter: '#f0f3fa', + light: '#E0E0E0', + dark: '#4E4E4E', + darker: '#3D3D3DDE' } }, header: { color: grey[500], background: '#FFFFFF', search: { - color: grey[800] + color: grey[800], }, indicator: { - background: blue[600] - } + background: blue[600], + }, }, sidebar: { color: grey[200], @@ -81,27 +111,27 @@ const darkVariant = { color: grey[200], background: '#232f3e', brand: { - color: blue[500] - } + color: blue[500], + }, }, footer: { color: grey[200], background: '#232f3e', online: { - background: green[500] - } + background: green[500], + }, }, category: { - fontWeight: 400 + fontWeight: 400, }, badge: { color: '#FFF', - background: blue[500] - } + background: blue[500], + }, }, body: { - background: '#F7F9FC' - } + background: '#F7F9FC', + }, } const variants = [lightVariant, darkVariant] From d3e19da8085dee02ada4e6a05fab7c90bd3e24af Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 14:05:00 -0600 Subject: [PATCH 28/41] chore(webapp): fix console errors --- webapp/src/components/Icons/Alert.js | 2 +- .../components/ProducersTable/MainSocialLinks.js | 2 +- .../src/components/ProducersTable/ProducerRow.js | 15 +++++---------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/webapp/src/components/Icons/Alert.js b/webapp/src/components/Icons/Alert.js index 1b02082fd..4ee024808 100644 --- a/webapp/src/components/Icons/Alert.js +++ b/webapp/src/components/Icons/Alert.js @@ -11,7 +11,7 @@ const AlertSvg = () => ( { {icons[socialMedia]} ) : ( - + ), )}
diff --git a/webapp/src/components/ProducersTable/ProducerRow.js b/webapp/src/components/ProducersTable/ProducerRow.js index 22ba697a4..ece48e123 100644 --- a/webapp/src/components/ProducersTable/ProducerRow.js +++ b/webapp/src/components/ProducersTable/ProducerRow.js @@ -54,20 +54,15 @@ const ProducerRow = ({ producer, index }) => { href={producerOrg?.media?.website} target="_blank" rel="nofollow noopener noreferrer" + className={classes.hideOnMobile} > - + {producerOrg?.media?.website} - - - + + +
{eosConfig.producerColumns?.includes('votes') && ( From 49c3af69bd3e02ea953a8507d68c6186a16b8c97 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 14:23:32 -0600 Subject: [PATCH 29/41] chore(webapp): format code --- .../components/EmptyState/EmptyStateRow.js | 3 +- webapp/src/components/NodesList/NodesRow.js | 4 +- .../src/components/ProducersTable/styles.js | 95 +++++++++---------- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/webapp/src/components/EmptyState/EmptyStateRow.js b/webapp/src/components/EmptyState/EmptyStateRow.js index da9572565..77f89b0cf 100644 --- a/webapp/src/components/EmptyState/EmptyStateRow.js +++ b/webapp/src/components/EmptyState/EmptyStateRow.js @@ -2,9 +2,10 @@ import React from 'react' import { makeStyles } from '@mui/styles' import { useTranslation } from 'react-i18next' -import styles from './styles' import AlertSvg from 'components/Icons/Alert' +import styles from './styles' + const useStyles = makeStyles(styles) const EmptyStateRow = () => { diff --git a/webapp/src/components/NodesList/NodesRow.js b/webapp/src/components/NodesList/NodesRow.js index a3bd60f35..531775c76 100644 --- a/webapp/src/components/NodesList/NodesRow.js +++ b/webapp/src/components/NodesList/NodesRow.js @@ -7,14 +7,14 @@ import { useTranslation } from 'react-i18next' import Typography from '@mui/material/Typography' import Button from '@mui/material/Button' +import { eosConfig } from '../../config' import { formatData } from '../../utils' import NodesCard from '../NodeCard/NodesCard' import ProducerName from 'components/ProducerName' import ViewBPProfile from 'components/ViewBPProfile' +import Tooltip from 'components/Tooltip' import styles from './styles' -import Tooltip from 'components/Tooltip' -import { eosConfig } from 'config' const useStyles = makeStyles(styles) diff --git a/webapp/src/components/ProducersTable/styles.js b/webapp/src/components/ProducersTable/styles.js index 5fd4b3f39..73368b825 100644 --- a/webapp/src/components/ProducersTable/styles.js +++ b/webapp/src/components/ProducersTable/styles.js @@ -1,58 +1,57 @@ export default (theme) => ({ - socialLinksContainer: { - display: 'grid', - gridTemplateColumns: 'repeat(3, 1fr)', - gap: theme.spacing(4), - '& svg': { - width: '24px', - height: '24px', - cursor: 'pointer', - '&:hover': { - transform: 'scale(1.3)', - }, - }, - }, - tableRow: { - '& .MuiTableCell-root': { - padding: `${theme.spacing(1, 2)} !important`, - }, + socialLinksContainer: { + display: 'grid', + gridTemplateColumns: 'repeat(3, 1fr)', + gap: theme.spacing(4), + '& svg': { + width: '24px', + height: '24px', + cursor: 'pointer', '&:hover': { - backgroundColor: `${theme.palette.neutral.lighter}`, + transform: 'scale(1.3)', }, }, - tableHead: { - borderBottom: `2px solid ${theme.palette.primary.main} !important`, - '& .MuiTableCell-root': { - padding: `${theme.spacing(0, 2, 2)} !important`, - }, + }, + tableRow: { + '& .MuiTableCell-root': { + padding: `${theme.spacing(1, 2)} !important`, }, - hideOnMobile: { - [theme.breakpoints.down('md')]: { - display: 'none !important', - }, + '&:hover': { + backgroundColor: `${theme.palette.neutral.lighter}`, }, - hideOnDesktop: { - [theme.breakpoints.up('md')]: { - display: 'none !important', - }, + }, + tableHead: { + borderBottom: `2px solid ${theme.palette.primary.main} !important`, + '& .MuiTableCell-root': { + padding: `${theme.spacing(0, 2, 2)} !important`, }, - websiteContainer: { - [theme.breakpoints.down('xl')]: { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - width: '140px', - }, - [theme.breakpoints.down('md')]: { - '& > a': { - margin: 0, - }, - }, + }, + hideOnMobile: { + [theme.breakpoints.down('md')]: { + display: 'none !important', + }, + }, + hideOnDesktop: { + [theme.breakpoints.up('md')]: { + display: 'none !important', }, - countryContainer: { - [theme.breakpoints.down('md')]: { - textAlign: 'center' + }, + websiteContainer: { + [theme.breakpoints.down('xl')]: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + width: '140px', + }, + [theme.breakpoints.down('md')]: { + '& > a': { + margin: 0, }, }, - }) - \ No newline at end of file + }, + countryContainer: { + [theme.breakpoints.down('md')]: { + textAlign: 'center', + }, + }, +}) From 4a2310eafd17076dae117f3d9f4bd5e79f9e9685 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 14:42:10 -0600 Subject: [PATCH 30/41] chore(webapp): use new variant for headers in the BPs table --- webapp/src/components/NodesList/index.js | 6 +++--- webapp/src/components/ProducersTable/index.js | 2 +- webapp/src/routes/ProducerProfile/ProfileCard.js | 2 +- webapp/src/theme/components.js | 9 ++++++++- webapp/src/theme/typography.js | 3 ++- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/webapp/src/components/NodesList/index.js b/webapp/src/components/NodesList/index.js index f88b01162..bc21611e4 100644 --- a/webapp/src/components/NodesList/index.js +++ b/webapp/src/components/NodesList/index.js @@ -23,12 +23,12 @@ const NodeList = ({ producers }) => { className={`simple-card ${classes.columnsContainer} ${classes.cardRow} ${classes.hideOnMobile}`} > {eosConfig.producerColumns?.includes('rank') ? ( - {t('rank')} + {t('rank')} ) : ( )} - {t('producerName')} - {t('nodes')} + {t('producerName')} + {t('nodes')}
) diff --git a/webapp/src/components/ProducersTable/index.js b/webapp/src/components/ProducersTable/index.js index 76ddadc22..bb1ff822d 100644 --- a/webapp/src/components/ProducersTable/index.js +++ b/webapp/src/components/ProducersTable/index.js @@ -29,7 +29,7 @@ const ProducersTable = ({ producers }) => { {eosConfig.producerColumns.map((name) => ( - + {t(name)} diff --git a/webapp/src/routes/ProducerProfile/ProfileCard.js b/webapp/src/routes/ProducerProfile/ProfileCard.js index f459f3798..43228ad9b 100644 --- a/webapp/src/routes/ProducerProfile/ProfileCard.js +++ b/webapp/src/routes/ProducerProfile/ProfileCard.js @@ -40,7 +40,7 @@ const ProfileCard = ({ producer }) => { return (

- {title}
+ {title}
{value ? ( <> {type === 'text' && value} diff --git a/webapp/src/theme/components.js b/webapp/src/theme/components.js index 40c4d31e1..2c2556eae 100644 --- a/webapp/src/theme/components.js +++ b/webapp/src/theme/components.js @@ -8,11 +8,18 @@ const components = { border-radius: 4px; padding: ${theme.spacing(4)}; }, - .MuiTypography-caption { + .MuiTypography-capSubtitle { color: ${theme.palette.neutral.dark}; } `, }, + MuiTypography: { + defaultProps: { + variantMapping: { + capSubtitle: 'span', + }, + }, + }, MuiButton: { styleOverrides: { root: { diff --git a/webapp/src/theme/typography.js b/webapp/src/theme/typography.js index b87b573e9..b69bf401c 100644 --- a/webapp/src/theme/typography.js +++ b/webapp/src/theme/typography.js @@ -46,7 +46,8 @@ const typography = { fontWeight: 600, lineHeight: 1.2 }, - caption: { + capSubtitle: { + fontSize: '0.75rem', textTransform: 'uppercase', fontWeight: 700, lineHeight: 1.2, From 481bd9798ae6010d159b35bc84134f1fda8280f1 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 14:55:50 -0600 Subject: [PATCH 31/41] chore(webapp): fix classname --- webapp/src/components/ProducersTable/ProducerRow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/components/ProducersTable/ProducerRow.js b/webapp/src/components/ProducersTable/ProducerRow.js index ece48e123..c986caad5 100644 --- a/webapp/src/components/ProducersTable/ProducerRow.js +++ b/webapp/src/components/ProducersTable/ProducerRow.js @@ -60,7 +60,7 @@ const ProducerRow = ({ producer, index }) => { {producerOrg?.media?.website} - + From beac5035ae2f24e3a0623599da0e1960333dac11 Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Wed, 11 Oct 2023 20:58:13 +0000 Subject: [PATCH 32/41] [CodeFactor] Apply fixes --- webapp/src/routes/ProducerProfile/useProducerProfileState.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/routes/ProducerProfile/useProducerProfileState.js b/webapp/src/routes/ProducerProfile/useProducerProfileState.js index 5d64dec4c..f36d024ce 100644 --- a/webapp/src/routes/ProducerProfile/useProducerProfileState.js +++ b/webapp/src/routes/ProducerProfile/useProducerProfileState.js @@ -53,7 +53,7 @@ const useProducerProfileState = (name, previousData) => { useEffect(() => { if (isValidName) { - if (generalConfig.eosRateLink){ + if (generalConfig.eosRateLink) { loadStats({ variables: { bp: name }, }) From d5b1b89a96ee81b2c530f52ad55ae4642b523eb6 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 16:35:40 -0600 Subject: [PATCH 33/41] fix(hapi): fix false negative of libre endpoints --- hapi/src/config/eos.config.js | 9 +++++---- hapi/src/services/producer.service.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/hapi/src/config/eos.config.js b/hapi/src/config/eos.config.js index d61c547d4..53d2a5ad2 100644 --- a/hapi/src/config/eos.config.js +++ b/hapi/src/config/eos.config.js @@ -52,10 +52,11 @@ module.exports = { wax: 'wax' }, healthCheckAPIs: [ - { name: 'chain-api', api: '/v1/chain/get_info' }, - { name: 'atomic-assets-api', api: '/atomicassets/v1/config' }, - { name: 'hyperion-v2', api: '/v2/health' }, - { name: 'light-api', api: '/api/status' } + { pattern: /^chain-api$/, api: '/v1/chain/get_info' }, + { pattern: /^atomic-assets-api$/, api: '/atomicassets/v1/config' }, + { pattern: /^hyperion-v2$/, api: '/v2/health' }, + { pattern: /^light-api$/, api: '/api/status' }, + { pattern: /^libre-.+$/, api: ''}, ], rewardsToken: process.env.HAPI_REWARDS_TOKEN, eosRateUrl: process.env.HAPI_EOSRATE_GET_STATS_URL, diff --git a/hapi/src/services/producer.service.js b/hapi/src/services/producer.service.js index 7ce3168f2..78d1886dc 100644 --- a/hapi/src/services/producer.service.js +++ b/hapi/src/services/producer.service.js @@ -279,7 +279,7 @@ const getHealthCheckResponse = async endpoint => { if (endpoint?.features?.length) { for (const API of eosConfig.healthCheckAPIs) { - if (endpoint.features?.some(feature => feature === API.name)) { + if (endpoint.features?.some(feature => API.pattern?.test(feature))) { startTime = new Date() response = await producerUtil.getNodeInfo(endpoint.value, API.api) From ff24ad30415fae92d387571522c5588af9644afd Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 16:36:11 -0600 Subject: [PATCH 34/41] fix(webapp): fix missing title in home page --- webapp/src/layouts/Dashboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/layouts/Dashboard.js b/webapp/src/layouts/Dashboard.js index 97ed821f6..d1227b043 100644 --- a/webapp/src/layouts/Dashboard.js +++ b/webapp/src/layouts/Dashboard.js @@ -60,7 +60,7 @@ const Dashboard = ({ children }) => { const removeParam = route => route.substring(0,route.lastIndexOf('/')) useEffect(() => { - const path = location.pathname.replace(/\/$/,'') + const path = location.pathname.replace(/\/$/,'') || '/' const route = routes.find(route => route.useParams ? removeParam(route.path) === removeParam(path) From 70c9d4f39e411faccda8958cf4202d2968319b43 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Wed, 11 Oct 2023 16:39:28 -0600 Subject: [PATCH 35/41] fix(hapi): fix calculation of avg daily txs in the last week --- hapi/src/services/stats.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi/src/services/stats.service.js b/hapi/src/services/stats.service.js index bd682b2a0..4094e449c 100644 --- a/hapi/src/services/stats.service.js +++ b/hapi/src/services/stats.service.js @@ -425,7 +425,7 @@ const syncTransactionsInfo = async () => { } payload.average_daily_transactions_in_last_week = - payload.transactions_in_last_day / 7 || 0 + payload.transactions_in_last_week / 7 || 0 const stats = await getStats() From e32558268aede0b9697525232830fd4918612154 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Thu, 12 Oct 2023 10:44:57 -0600 Subject: [PATCH 36/41] chore(webapp): remove connect wallet button from pages that don't require it --- webapp/src/components/ContractTables/index.js | 2 +- webapp/src/components/Header/index.js | 29 ++++++++++++++----- webapp/src/language/es.lacchain.json | 2 +- webapp/src/layouts/Dashboard.js | 3 +- webapp/src/routes/index.js | 10 +++++-- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/webapp/src/components/ContractTables/index.js b/webapp/src/components/ContractTables/index.js index 0e0f2b0c5..38a28e81f 100644 --- a/webapp/src/components/ContractTables/index.js +++ b/webapp/src/components/ContractTables/index.js @@ -234,8 +234,8 @@ const ContractTables = ({ color="primary" className={classes.refreshButton} onClick={() => handleSubmit()} + startIcon={} > - {t('refreshData')} )} diff --git a/webapp/src/components/Header/index.js b/webapp/src/components/Header/index.js index 3a3eae5cd..99f77cdb1 100644 --- a/webapp/src/components/Header/index.js +++ b/webapp/src/components/Header/index.js @@ -1,7 +1,12 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, lazy, Suspense } from 'react' import PropTypes from 'prop-types' import { makeStyles } from '@mui/styles' -import { Hidden, Menu, MenuItem, AppBar, IconButton } from '@mui/material' +import Hidden from '@mui/material/Hidden' +import Menu from '@mui/material/Menu' +import MenuItem from '@mui/material/MenuItem' +import AppBar from '@mui/material/AppBar' +import IconButton from '@mui/material/IconButton' +import Skeleton from '@mui/material/Skeleton' import Button from '@mui/material/Button' import Toolbar from '@mui/material/Toolbar' import MenuIcon from '@mui/icons-material/Menu' @@ -10,9 +15,10 @@ import { useTranslation } from 'react-i18next' import moment from 'moment' import 'moment/locale/es' -import AuthButton from './AuthButton' import styles from './styles' +const AuthButton = lazy(() => import('./AuthButton')) + const useStyles = makeStyles(styles) const languages = [ @@ -72,9 +78,7 @@ const LanguageMenu = () => { onClick={toggleMenu} className={classes.btnLanguage} > - - {currentLanguaje.toUpperCase()} - + {currentLanguaje.toUpperCase()}

{ ) } -const Header = ({ onDrawerToggle }) => { +const Header = ({ onDrawerToggle, useConnectWallet = false }) => { const classes = useStyles() return ( @@ -118,7 +122,15 @@ const Header = ({ onDrawerToggle }) => {
- + {useConnectWallet && ( + + } + > + + + )}
@@ -128,6 +140,7 @@ const Header = ({ onDrawerToggle }) => { Header.propTypes = { onDrawerToggle: PropTypes.func, + useConnectWallet: PropTypes.bool, } export default Header diff --git a/webapp/src/language/es.lacchain.json b/webapp/src/language/es.lacchain.json index c357e15b6..620d46d49 100644 --- a/webapp/src/language/es.lacchain.json +++ b/webapp/src/language/es.lacchain.json @@ -12,7 +12,7 @@ "/node-config>sidebar": "Configuración de nodo", "/node-config>title": "Configuración de nodo - Panel de red de {{networkName}}", "/node-config>heading": "Configurar un nuevo nodo", - "/ricardian-contract>sidebar": "Acuerdo de Nodos Validadores - Panel de red de {{networkName}}", + "/ricardian-contract>sidebar": "Acuerdo de Nodos Validadores", "/ricardian-contract>heading": "", "/entities>moreDescription": "Una lista de entidades que forman parte de la red. Pueden ser entidades socias o no socias.", "/lacchain/network>moreDescription": "Una representación visual de los nodos actuales de la red.", diff --git a/webapp/src/layouts/Dashboard.js b/webapp/src/layouts/Dashboard.js index d1227b043..bbfe7de65 100644 --- a/webapp/src/layouts/Dashboard.js +++ b/webapp/src/layouts/Dashboard.js @@ -83,6 +83,7 @@ const Dashboard = ({ children }) => { pageTitle: t(`${pathName}>title`, { networkName: eosConfig.networkLabel, }), + useConnectWallet: Boolean(route.useConnectWallet) }) } else { setRouteName(INIT_VALUES) @@ -104,7 +105,7 @@ const Dashboard = ({ children }) => { } />
-
+
, + icon: , component: RicardianContract, path: '/ricardian-contract', exact: true, @@ -175,6 +176,7 @@ const defaultRoutes = [ icon: , component: Accounts, path: '/accounts', + useConnectWallet: true, exact: true, }, { @@ -182,6 +184,7 @@ const defaultRoutes = [ icon: , component: BPJson, path: '/bpjson', + useConnectWallet: true, exact: true, }, ] @@ -266,7 +269,7 @@ const lacchainRoutes = [ }, { name: 'ricardianContract', - icon: , + icon: , component: RicardianContract, path: '/ricardian-contract', exact: true, @@ -277,6 +280,7 @@ const lacchainRoutes = [ icon: , component: Accounts, path: '/accounts', + useConnectWallet: true, exact: true, }, { @@ -284,6 +288,7 @@ const lacchainRoutes = [ icon: , component: LacchainManagement, path: '/management', + useConnectWallet: true, exact: true, }, { @@ -291,6 +296,7 @@ const lacchainRoutes = [ icon: , component: LacchainNodeConfig, path: '/node-config', + useConnectWallet: true, exact: true, }, ] From 36a5d5e326841583524b423f7c7caad607f9be94 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Thu, 12 Oct 2023 15:41:38 -0600 Subject: [PATCH 37/41] feat(webapp): add location and sameAs to structured data --- .../src/components/NonCompliantCard/index.js | 6 +-- .../components/ProducersTable/ProducerRow.js | 4 +- .../src/routes/ProducerProfile/ProfileCard.js | 11 ++-- .../useProducerProfileState.js | 54 +++++++++++++++---- webapp/src/utils/formatData.js | 3 +- webapp/src/utils/validate-url.js | 4 +- 6 files changed, 57 insertions(+), 25 deletions(-) diff --git a/webapp/src/components/NonCompliantCard/index.js b/webapp/src/components/NonCompliantCard/index.js index 5eabd27e4..48560f176 100644 --- a/webapp/src/components/NonCompliantCard/index.js +++ b/webapp/src/components/NonCompliantCard/index.js @@ -11,7 +11,7 @@ import { eosConfig, generalConfig } from '../../config' import { formatWithThousandSeparator } from '../../utils' import HealthCheck from '../HealthCheck' import HealthCheckInfo from 'components/HealthCheck/HealthCheckInfo' -import isUrlValid from '../../utils/validate-url' +import isValidUrl from '../../utils/validate-url' import VisitSite from 'components/VisitSite' import styles from './styles' @@ -67,7 +67,7 @@ const NonCompliantCard = ({ producer, stats }) => { > {t('website')}: - {isUrlValid(producer.url) ? ( + {isValidUrl(producer.url) ? ( <>
@@ -80,7 +80,7 @@ const NonCompliantCard = ({ producer, stats }) => { {t('invalidUrl')} )}
- {isUrlValid(producer.url) && ( + {isValidUrl(producer.url) && (
{ <> - +
- {producerOrg?.location} + {producerOrg?.location?.name}
diff --git a/webapp/src/routes/ProducerProfile/ProfileCard.js b/webapp/src/routes/ProducerProfile/ProfileCard.js index 43228ad9b..62dd38cc0 100644 --- a/webapp/src/routes/ProducerProfile/ProfileCard.js +++ b/webapp/src/routes/ProducerProfile/ProfileCard.js @@ -5,6 +5,7 @@ import Typography from '@mui/material/Typography' import Link from '@mui/material/Link' import { eosConfig, generalConfig } from '../../config' +import isValidUrl from '../../utils/validate-url' import ProducerName from 'components/ProducerName' import ProducerSocialLinks from 'components/ProducerSocialLinks' import LightIcon from 'components/HealthCheck/LightIcon' @@ -71,8 +72,8 @@ const ProfileCard = ({ producer }) => { title={t('location')} value={ <> - {producer?.location} - {' '} + {producer?.location?.name} + {' '} } /> @@ -84,7 +85,7 @@ const ProfileCard = ({ producer }) => { /> { value={producer?.info?.chainResources} type="hiddenLink" /> - {producer?.info?.otherResources?.length && ( + {!!producer?.info?.otherResources?.length && ( { lazy={false} size="big" /> - {producer?.social?.length && ( + {!!producer?.social?.length && (
diff --git a/webapp/src/routes/ProducerProfile/useProducerProfileState.js b/webapp/src/routes/ProducerProfile/useProducerProfileState.js index f36d024ce..fbc21917b 100644 --- a/webapp/src/routes/ProducerProfile/useProducerProfileState.js +++ b/webapp/src/routes/ProducerProfile/useProducerProfileState.js @@ -102,22 +102,54 @@ const useProducerProfileState = (name, previousData) => { }, [producers]) useEffect(() => { - if (!producer || !Object?.keys(producer).length) return + if (!producer || !Object?.keys(producer).length || !producer?.media?.name) + return - setLdJson( - JSON.stringify({ + setLdJson(prev => { + if (prev) return prev + + const sameAs = producer.social?.map(socialMedia => socialMedia.url) + const hasValidLocation = Object.keys(producer.location || {}).every(key => !!producer.location[key]) + + if (producer.info?.codeOfConduct) { + sameAs.push(producer.info?.codeOfConduct) + } + + if (producer.info?.ownership) { + sameAs.push(producer.info?.ownership) + } + + return JSON.stringify({ '@context': 'http://schema.org', '@type': 'Organization', name: producer?.media?.name, url: producer?.media?.website, - contactPoint: { - '@type': 'ContactPoint', - email: producer?.media?.email, - contactType: 'customer service', - }, - logo: producer?.media?.logo, - }), - ) + ...(producer?.media?.logo && { logo: producer.media?.logo }), + ...(producer.media?.email && { + contactPoint: { + '@type': 'ContactPoint', + email: producer?.media?.email, + contactType: 'customer service', + }, + }), + ...(hasValidLocation && { + location: { + '@type': 'Place', + address: { + '@type': 'PostalAddress', + addressLocality: producer.location.name, + addressCountry: producer.location.country, + }, + geo: { + '@type': 'GeoCoordinates', + latitude: producer.location.latitude, + longitude: producer.location.longitude, + }, + }, + }), + ...(sameAs.length && { sameAs }), + }) + }) }, [producer]) return [{ loading, producer, ldJson }, {}] diff --git a/webapp/src/utils/formatData.js b/webapp/src/utils/formatData.js index 270dc4957..ae9833fa9 100644 --- a/webapp/src/utils/formatData.js +++ b/webapp/src/utils/formatData.js @@ -82,8 +82,7 @@ export const formatData = ({ website: data?.website || '', email: data?.email, }, - location: data?.location?.name || 'N/A', - country: data?.location?.country || null, + location: data?.location, info: { codeOfConduct: data?.code_of_conduct, ownership: data?.ownership_disclosure, diff --git a/webapp/src/utils/validate-url.js b/webapp/src/utils/validate-url.js index af2f3cf73..94c20c7ca 100644 --- a/webapp/src/utils/validate-url.js +++ b/webapp/src/utils/validate-url.js @@ -1,8 +1,8 @@ -const isUrlValid = (url) => { +const isValidUrl = url => { const urlRegex = /(http|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])/ return url && urlRegex.test(url) } -export default isUrlValid +export default isValidUrl From 15198050a67ce5b0967499e03378f6e944a64495 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Fri, 13 Oct 2023 13:47:24 -0600 Subject: [PATCH 38/41] feat(webapp): add endpoints in the structured data --- .../useProducerProfileState.js | 55 ++-------- webapp/src/utils/get-bp-structured-data.js | 100 ++++++++++++++++++ webapp/src/utils/index.js | 11 +- webapp/src/utils/sort-nodes.js | 4 +- webapp/src/utils/validate-account-name.js | 2 +- 5 files changed, 117 insertions(+), 55 deletions(-) create mode 100644 webapp/src/utils/get-bp-structured-data.js diff --git a/webapp/src/routes/ProducerProfile/useProducerProfileState.js b/webapp/src/routes/ProducerProfile/useProducerProfileState.js index fbc21917b..441fbb0b5 100644 --- a/webapp/src/routes/ProducerProfile/useProducerProfileState.js +++ b/webapp/src/routes/ProducerProfile/useProducerProfileState.js @@ -7,9 +7,12 @@ import { EOSRATE_STATS_QUERY, } from '../../gql' import { generalConfig } from '../../config' -import { formatData } from '../../utils/formatData' -import isValidAccountName from 'utils/validate-account-name' -import sortNodes from 'utils/sort-nodes' +import { + getBPStructuredData, + formatData, + sortNodes, + isValidAccountName, +} from '../../utils' const useProducerProfileState = (name, previousData) => { const defaultVariables = { @@ -105,51 +108,7 @@ const useProducerProfileState = (name, previousData) => { if (!producer || !Object?.keys(producer).length || !producer?.media?.name) return - setLdJson(prev => { - if (prev) return prev - - const sameAs = producer.social?.map(socialMedia => socialMedia.url) - const hasValidLocation = Object.keys(producer.location || {}).every(key => !!producer.location[key]) - - if (producer.info?.codeOfConduct) { - sameAs.push(producer.info?.codeOfConduct) - } - - if (producer.info?.ownership) { - sameAs.push(producer.info?.ownership) - } - - return JSON.stringify({ - '@context': 'http://schema.org', - '@type': 'Organization', - name: producer?.media?.name, - url: producer?.media?.website, - ...(producer?.media?.logo && { logo: producer.media?.logo }), - ...(producer.media?.email && { - contactPoint: { - '@type': 'ContactPoint', - email: producer?.media?.email, - contactType: 'customer service', - }, - }), - ...(hasValidLocation && { - location: { - '@type': 'Place', - address: { - '@type': 'PostalAddress', - addressLocality: producer.location.name, - addressCountry: producer.location.country, - }, - geo: { - '@type': 'GeoCoordinates', - latitude: producer.location.latitude, - longitude: producer.location.longitude, - }, - }, - }), - ...(sameAs.length && { sameAs }), - }) - }) + setLdJson(prev => prev ? prev : JSON.stringify(getBPStructuredData(producer))) }, [producer]) return [{ loading, producer, ldJson }, {}] diff --git a/webapp/src/utils/get-bp-structured-data.js b/webapp/src/utils/get-bp-structured-data.js new file mode 100644 index 000000000..ca1d6701c --- /dev/null +++ b/webapp/src/utils/get-bp-structured-data.js @@ -0,0 +1,100 @@ +import { eosConfig } from '../config' + +const getNodesStructuredData = producer => { + if (!producer.bp_json?.nodes?.length) return [] + + const structuredData = { nodes: [], inserted: [] } + const nodesTypes = [ + { + type: 'seed', + endpoint: 'p2p_endpoint', + name: `${eosConfig.networkLabel} P2P Endpoint`, + description: 'P2P connectivity endpoints', + }, + { + type: 'query', + endpoint: 'ssl_endpoint', + name: `${eosConfig.networkLabel} API Endpoint`, + description: 'HTTPS API access point', + }, + ] + + for (const node of producer.bp_json?.nodes) { + if (structuredData.inserted.includes(node.node_type)) continue + + const nodeType = nodesTypes.find( + item => item.type === node.node_type && node[item.endpoint], + ) + + if (nodeType) { + structuredData.inserted.push(node.node_type) + structuredData.nodes.push({ + '@type': 'webAPI', + name: nodeType.name, + description: nodeType.description, + url: node[nodeType.endpoint], + provider: { + '@type': 'Organization', + name: producer?.media?.name, + }, + }) + } + + if (structuredData.inserted.length >= nodesTypes.length) { + break + } + } + + return structuredData.nodes +} + +export const getBPStructuredData = producer => { + const sameAs = producer.social?.map(socialMedia => socialMedia.url) + const hasValidLocation = Object.keys(producer.location || {}).every( + (key) => !!producer.location[key], + ) + + if (producer.info?.codeOfConduct) { + sameAs.push(producer.info?.codeOfConduct) + } + + if (producer.info?.ownership) { + sameAs.push(producer.info?.ownership) + } + + const owns = getNodesStructuredData(producer) + + return { + '@context': 'http://schema.org', + '@type': 'Organization', + name: producer?.media?.name, + url: producer?.media?.website, + ...(producer?.media?.logo && { logo: producer.media?.logo }), + ...(producer.media?.email && { + contactPoint: { + '@type': 'ContactPoint', + email: producer?.media?.email, + contactType: 'customer service', + }, + }), + ...(hasValidLocation && { + location: { + '@type': 'Place', + address: { + '@type': 'PostalAddress', + addressLocality: producer.location.name, + addressCountry: producer.location.country, + }, + geo: { + '@type': 'GeoCoordinates', + latitude: producer.location.latitude, + longitude: producer.location.longitude, + }, + }, + }), + ...(sameAs.length && { sameAs }), + ...(owns.length && { owns }), + } +} + +export default getBPStructuredData diff --git a/webapp/src/utils/index.js b/webapp/src/utils/index.js index 5a0a74650..fd17b35c5 100644 --- a/webapp/src/utils/index.js +++ b/webapp/src/utils/index.js @@ -1,10 +1,13 @@ -export * from './format-with-thousand-separator' -export * from './on-img-error' export * from './eos' +export * from './format-with-thousand-separator' export * from './formatData' export * from './get-blocknum-url' +export * from './get-bp-structured-data' +export * from './get-endpoint-health-status' +export * from './get-evmblocknum-url' export * from './get-range-options' export * from './get-transaction-url' +export * from './on-img-error' +export * from './sort-nodes' +export * from './validate-account-name' export * from './validate-url' -export * from './get-evmblocknum-url' -export * from './get-endpoint-health-status' diff --git a/webapp/src/utils/sort-nodes.js b/webapp/src/utils/sort-nodes.js index 6bf94a5f2..2e21eeab2 100644 --- a/webapp/src/utils/sort-nodes.js +++ b/webapp/src/utils/sort-nodes.js @@ -1,4 +1,4 @@ -const getOrderNode = (node) => { +const getOrderNode = node => { return ( (node.type?.includes('full') ? 0.5 : 0) + (node.endpoints?.length || 0) + @@ -7,7 +7,7 @@ const getOrderNode = (node) => { ) } -const sortNodes = (unsortedNodes, key) => { +export const sortNodes = (unsortedNodes, key) => { let nodes = [] let producerNode diff --git a/webapp/src/utils/validate-account-name.js b/webapp/src/utils/validate-account-name.js index 8d42b9de8..09aa1d0e3 100644 --- a/webapp/src/utils/validate-account-name.js +++ b/webapp/src/utils/validate-account-name.js @@ -1,4 +1,4 @@ -const isValidAccountName = name => { +export const isValidAccountName = name => { const regex = /^[.12345abcdefghijklmnopqrstuvwxyz]+$/ return name?.length < 13 && regex.test(name) From 7d32633dd992059577953172a5716cbf5f610e26 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Fri, 13 Oct 2023 13:48:50 -0600 Subject: [PATCH 39/41] chore(webapp): allow to load no square logos --- webapp/src/hooks/customHooks/useBPLogoState.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webapp/src/hooks/customHooks/useBPLogoState.js b/webapp/src/hooks/customHooks/useBPLogoState.js index d63ab0d85..ba80729e8 100644 --- a/webapp/src/hooks/customHooks/useBPLogoState.js +++ b/webapp/src/hooks/customHooks/useBPLogoState.js @@ -9,7 +9,11 @@ const useBPLogoState = (logo, defaultLogo) => { const { naturalHeight, naturalWidth } = logoRef.current - if (naturalHeight < 100 || naturalHeight !== naturalWidth) { + const ratio = + Math.min(naturalHeight, naturalWidth) / + Math.max(naturalHeight, naturalWidth) + + if (naturalHeight < 100 || naturalWidth < 100 || ratio < 0.8) { setSrc(defaultLogo) } } From b3b72be42caee8be1cb6f4a407748b3b8678f826 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Fri, 13 Oct 2023 13:58:25 -0600 Subject: [PATCH 40/41] fix(webapp): fix accessibility errors --- webapp/src/components/NodeCard/EndpointsChips.js | 4 ++-- webapp/src/components/NodeCard/NodesCard.js | 2 +- webapp/src/routes/ProducerProfile/ProfileCard.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webapp/src/components/NodeCard/EndpointsChips.js b/webapp/src/components/NodeCard/EndpointsChips.js index c0653284f..afcfe0c62 100644 --- a/webapp/src/components/NodeCard/EndpointsChips.js +++ b/webapp/src/components/NodeCard/EndpointsChips.js @@ -98,10 +98,10 @@ const EndpointsChips = ({ node }) => { return ( <> -
+ {t('endpoints')} {!!node.endpoints.length && } -
+ { return ( diff --git a/webapp/src/components/NodeCard/NodesCard.js b/webapp/src/components/NodeCard/NodesCard.js index ed7b6f7de..fdd56d204 100644 --- a/webapp/src/components/NodeCard/NodesCard.js +++ b/webapp/src/components/NodeCard/NodesCard.js @@ -47,7 +47,7 @@ const NodesCard = ({ nodes, hideFeatures = false }) => { return ( <> -
{t('keys')}
+ {t('keys')} {Object.keys(keys).map((key, i) => (

{key}:

diff --git a/webapp/src/routes/ProducerProfile/ProfileCard.js b/webapp/src/routes/ProducerProfile/ProfileCard.js index 62dd38cc0..b6dc18d00 100644 --- a/webapp/src/routes/ProducerProfile/ProfileCard.js +++ b/webapp/src/routes/ProducerProfile/ProfileCard.js @@ -118,8 +118,8 @@ const ProfileCard = ({ producer }) => { <>
Date: Fri, 13 Oct 2023 20:15:45 +0000 Subject: [PATCH 41/41] [CodeFactor] Apply fixes --- webapp/src/routes/ProducerProfile/useProducerProfileState.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/routes/ProducerProfile/useProducerProfileState.js b/webapp/src/routes/ProducerProfile/useProducerProfileState.js index 441fbb0b5..11831b7cb 100644 --- a/webapp/src/routes/ProducerProfile/useProducerProfileState.js +++ b/webapp/src/routes/ProducerProfile/useProducerProfileState.js @@ -108,7 +108,7 @@ const useProducerProfileState = (name, previousData) => { if (!producer || !Object?.keys(producer).length || !producer?.media?.name) return - setLdJson(prev => prev ? prev : JSON.stringify(getBPStructuredData(producer))) + setLdJson(prev => prev || JSON.stringify(getBPStructuredData(producer))) }, [producer]) return [{ loading, producer, ldJson }, {}]