diff --git a/src/AppV2.js b/src/AppV2.js
index fef4402b..401ae04e 100644
--- a/src/AppV2.js
+++ b/src/AppV2.js
@@ -16,6 +16,8 @@ try {
}
const Dashboard = lazy(() => import('v2/components/Dashboard'));
+const Validators = lazy(() => import('v2/components/Validators'));
+const ValidatorsAll = lazy(() => import('v2/components/Validators/All'));
const useStyles = makeStyles(theme => ({
root: {
@@ -26,6 +28,11 @@ const useStyles = makeStyles(theme => ({
flexGrow: 1,
marginLeft: 50,
padding: '50px 24px 0 24px',
+ maxWidth: '100%',
+ [theme.breakpoints.down('sm')]: {
+ marginLeft: 0,
+ padding: 0,
+ },
},
toolbar: {
display: 'flex',
@@ -49,6 +56,12 @@ const App = () => {
Loading...}>
+
+
diff --git a/src/index.js b/src/index.js
index c7fa0ad7..f2be12a0 100755
--- a/src/index.js
+++ b/src/index.js
@@ -1,18 +1,24 @@
-import React from 'react';
+import React, {lazy, Suspense} from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
import './index.css';
import App from './App';
-import AppV2 from './AppV2';
import * as serviceWorker from './serviceWorker';
import * as EndpointConfig from './EndpointConfig';
+const AppV2 = lazy(() => import('./AppV2'));
async function main() {
await EndpointConfig.load();
ReactDOM.render(
- {window.location.pathname.includes('rc') ? : }
+ {window.location.pathname.includes('rc') ? (
+ Loading...}>
+
+
+ ) : (
+
+ )}
,
document.getElementById('root'),
);
diff --git a/src/v2/api/socket.js b/src/v2/api/socket.js
index 6b912dc5..9d58a1b3 100644
--- a/src/v2/api/socket.js
+++ b/src/v2/api/socket.js
@@ -1,6 +1,7 @@
import RobustWebSocket from 'robust-websocket';
-import {BLOCK_EXPLORER_WS_API_BASE} from 'v2/const';
+
+import * as EndpointConfig from '../../EndpointConfig';
export const initSocket = () => {
- return new RobustWebSocket(BLOCK_EXPLORER_WS_API_BASE);
+ return new RobustWebSocket(EndpointConfig.getApiWebsocketUrl());
};
diff --git a/src/v2/assets/icons/question.svg b/src/v2/assets/icons/question.svg
new file mode 100644
index 00000000..d6ba0a84
--- /dev/null
+++ b/src/v2/assets/icons/question.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/v2/components/Dashboard/NetworkOverview/NodesMap/index.jsx b/src/v2/components/Dashboard/NetworkOverview/NodesMap/index.jsx
index b2404458..24bf2682 100644
--- a/src/v2/components/Dashboard/NetworkOverview/NodesMap/index.jsx
+++ b/src/v2/components/Dashboard/NetworkOverview/NodesMap/index.jsx
@@ -74,18 +74,15 @@ const NodesMap = () => {
geography={`${process.env.PUBLIC_URL}/resources/world-50m-simplified.json`}
>
{(geographies, projection) =>
- geographies.map(
- (geography, i) =>
- geography.id !== 'ATA' && (
-
- ),
- )
+ geographies.map((geography, i) => (
+
+ ))
}
{map(renderMarker)(mapMarkers)}
diff --git a/src/v2/components/Footer/Newsletter/styles.js b/src/v2/components/Footer/Newsletter/styles.js
index 5c6c7737..3687d9bd 100644
--- a/src/v2/components/Footer/Newsletter/styles.js
+++ b/src/v2/components/Footer/Newsletter/styles.js
@@ -20,6 +20,9 @@ export default makeStyles(theme => ({
padding: 5,
display: 'flex',
marginBottom: 132,
+ [theme.breakpoints.down('md')]: {
+ marginBottom: 0,
+ },
},
input: {
border: 'none',
diff --git a/src/v2/components/Footer/assets/bg_sm.svg b/src/v2/components/Footer/assets/bg_sm.svg
new file mode 100644
index 00000000..b8cf7a56
--- /dev/null
+++ b/src/v2/components/Footer/assets/bg_sm.svg
@@ -0,0 +1,24 @@
+
diff --git a/src/v2/components/Footer/index.jsx b/src/v2/components/Footer/index.jsx
index 7dc3b341..38b2daf6 100644
--- a/src/v2/components/Footer/index.jsx
+++ b/src/v2/components/Footer/index.jsx
@@ -7,24 +7,28 @@ import Newsletter from './Newsletter';
import Partnership from './Partnership';
import useStyles from './styles';
import bg from './assets/bg.svg';
+import bgSm from './assets/bg_sm.svg';
const Footer = () => {
const classes = useStyles();
return (
-
+
-
+
© Copyright Solana Labs, Inc. All rights reserved.
-
+
-
+
diff --git a/src/v2/components/Footer/styles.js b/src/v2/components/Footer/styles.js
index 24097c2d..3c7f585b 100644
--- a/src/v2/components/Footer/styles.js
+++ b/src/v2/components/Footer/styles.js
@@ -6,24 +6,41 @@ export default makeStyles(theme => ({
paddingTop: 100,
position: 'relative',
marginLeft: 28,
+ [theme.breakpoints.down('md')]: {
+ paddingTop: 0,
+ marginLeft: 0,
+ },
},
left: {
background: getColor('main')(theme),
padding: '45px 80px 35px',
display: 'flex',
flexDirection: 'column',
+ [theme.breakpoints.down('md')]: {
+ padding: '40px 22px',
+ },
},
right: {
background: getColor('grey2')(theme),
padding: '170px 80px 35px',
+ [theme.breakpoints.down('md')]: {
+ padding: '40px 22px',
+ },
},
copyright: {
marginTop: 'auto',
fontSize: 10,
color: getColor('dark')(theme),
+ [theme.breakpoints.down('md')]: {
+ display: 'none',
+ },
},
bg: {
position: 'absolute',
left: -160,
+ [theme.breakpoints.down('md')]: {
+ position: 'static',
+ width: '100%',
+ },
},
}));
diff --git a/src/v2/components/Header/index.jsx b/src/v2/components/Header/index.jsx
index c02f809d..bb2e1001 100644
--- a/src/v2/components/Header/index.jsx
+++ b/src/v2/components/Header/index.jsx
@@ -1,17 +1,35 @@
// @flow
+import Select from '@material-ui/core/Select';
import React from 'react';
+import {observer} from 'mobx-react-lite';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Logo from 'v2/components/UI/Logo';
import Search from 'v2/components/Search';
+import socketActions from 'v2/stores/socket';
+import {map} from 'lodash/fp';
+import * as EndpointConfig from '../../../EndpointConfig';
import {ReactComponent as LiveIcon} from './assets/liveIcon.svg';
import useStyles from './styles';
const Header = () => {
+ const {endpointName, updateEndpointName} = socketActions;
const classes = useStyles();
const onSearch = () => {};
+ const handleEndpointChange = (event: SyntheticEvent) => {
+ const endpointName = event.currentTarget.value;
+ EndpointConfig.setEndpointName(endpointName);
+ updateEndpointName(endpointName);
+ socketActions.init();
+ };
+ const endPointsList = EndpointConfig.getEndpoints();
+ const renderEndpointOption = endpoint => (
+
+ );
return (
@@ -25,11 +43,12 @@ const Header = () => {
Every 5 sec
+
);
};
-Header.propTypes = {};
-
-export default Header;
+export default observer(Header);
diff --git a/src/v2/components/Header/styles.js b/src/v2/components/Header/styles.js
index d8d162a4..625dc1b6 100644
--- a/src/v2/components/Header/styles.js
+++ b/src/v2/components/Header/styles.js
@@ -22,6 +22,7 @@ export default makeStyles(theme => ({
textTransform: 'uppercase',
fontSize: 12,
letterSpacing: 2.5,
+ marginRight: 25,
'& div': {
background: 'transparent',
border: `1px solid ${getColor('main')(theme)}`,
diff --git a/src/v2/components/NavBar/index.jsx b/src/v2/components/NavBar/index.jsx
index 93a519b3..9fed8e01 100644
--- a/src/v2/components/NavBar/index.jsx
+++ b/src/v2/components/NavBar/index.jsx
@@ -1,6 +1,8 @@
// @flow
import {Drawer, List, ListItem, ListItemIcon} from '@material-ui/core';
+import useMediaQuery from '@material-ui/core/useMediaQuery';
+import {useTheme} from '@material-ui/core/styles';
import {RouterHistory, withRouter} from 'react-router-dom';
import React from 'react';
import {map, propEq, eq} from 'lodash/fp';
@@ -32,6 +34,8 @@ const NavBar = ({
history: RouterHistory,
}) => {
const classes = useStyles();
+ const theme = useTheme();
+ const showDriver = useMediaQuery(theme.breakpoints.up('md'));
const routes = [
'dashboard',
'transactions',
@@ -47,7 +51,7 @@ const NavBar = ({
const selected =
propEq('pathname', `/${link}`)(location) ||
(propEq('pathname', '/')(location) && isDashboard);
- const changeRoute = () => history.push(`/v2/${isDashboard ? '' : link}`);
+ const changeRoute = () => history.push(`/rc/${isDashboard ? '' : link}`);
return (
({
root: {
width: 102,
flexShrink: 0,
+ [theme.breakpoints.down('sm')]: {
+ width: 0,
+ },
},
toolbar: {
display: 'flex',
@@ -25,5 +28,8 @@ export default makeStyles(theme => ({
drawerRoot: {
width: 102,
flexShrink: 0,
+ [theme.breakpoints.down('sm')]: {
+ width: 0,
+ },
},
}));
diff --git a/src/v2/components/Social/styles.js b/src/v2/components/Social/styles.js
index b7e54dcc..2dc3e769 100644
--- a/src/v2/components/Social/styles.js
+++ b/src/v2/components/Social/styles.js
@@ -7,6 +7,10 @@ export default makeStyles(theme => ({
flexDirection: 'column',
alignItems: 'center',
marginTop: 80,
+ [theme.breakpoints.down('md')]: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ },
},
link: {
background: getColor('main')(theme),
@@ -20,5 +24,9 @@ export default makeStyles(theme => ({
width: 17,
height: 'auto',
},
+ [theme.breakpoints.down('md')]: {
+ marginBottom: 0,
+ marginRight: 22,
+ },
},
}));
diff --git a/src/v2/components/UI/StatCard/index.jsx b/src/v2/components/UI/StatCard/index.jsx
index 22566607..c74fe8f6 100644
--- a/src/v2/components/UI/StatCard/index.jsx
+++ b/src/v2/components/UI/StatCard/index.jsx
@@ -14,6 +14,7 @@ const StatCard = (props: StatCardProps) => {
const classes = useStyles();
const {title, value, changes} = props;
const renderValue = () => {
+ if (!value) return;
if (typeof value === 'function') {
return value();
}
diff --git a/src/v2/components/Validators/All/index.jsx b/src/v2/components/Validators/All/index.jsx
new file mode 100644
index 00000000..e6572a50
--- /dev/null
+++ b/src/v2/components/Validators/All/index.jsx
@@ -0,0 +1,14 @@
+import {Container} from '@material-ui/core';
+import React from 'react';
+
+import ValidatorsTable from '../Table';
+
+const ValidatorsAll = () => {
+ return (
+
+
+
+ );
+};
+
+export default ValidatorsAll;
diff --git a/src/v2/components/Validators/Table/index.jsx b/src/v2/components/Validators/Table/index.jsx
new file mode 100644
index 00000000..9808a871
--- /dev/null
+++ b/src/v2/components/Validators/Table/index.jsx
@@ -0,0 +1,100 @@
+import React from 'react';
+import {
+ Typography,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ Grid,
+} from '@material-ui/core';
+import useMediaQuery from '@material-ui/core/useMediaQuery';
+import {useTheme} from '@material-ui/core/styles';
+import {observer} from 'mobx-react-lite';
+import {Link} from 'react-router-dom';
+import {map} from 'lodash/fp';
+import NodesStore from 'v2/stores/nodes';
+
+import useStyles from './styles';
+
+const ValidatorsTable = () => {
+ const classes = useStyles();
+ const theme = useTheme();
+ const showTable = useMediaQuery(theme.breakpoints.up('md'));
+ const {
+ cluster: {nodes},
+ } = NodesStore;
+ const renderRow = row => {
+ return (
+
+
+
+
+ {row.pubkey}
+
+
+ total sol
+ Commission
+ Uptime
+
+ );
+ };
+ const renderCard = card => {
+ return (
+
+
+
+ {card.pubkey}
+
+
+
+ Total sol
+ Total sol
+
+
+ Commission
+ Commission
+
+
+ Uptime
+ Uptime
+
+
+
+ );
+ };
+ return (
+
+
+ Validators
+ 129,948
+
+ See all >
+
+
+ {showTable ? (
+
+
+
+ Name/Moniker
+ Total Sol
+ Commission
+ Uptime
+
+
+
+ {map(renderRow)(nodes)}
+
+
+ ) : (
+
{map(renderCard)(nodes)}
+ )}
+
+ );
+};
+
+export default observer(ValidatorsTable);
diff --git a/src/v2/components/Validators/Table/styles.js b/src/v2/components/Validators/Table/styles.js
new file mode 100644
index 00000000..62d8afb0
--- /dev/null
+++ b/src/v2/components/Validators/Table/styles.js
@@ -0,0 +1,88 @@
+import {makeStyles} from '@material-ui/core';
+import getColor from 'v2/utils/getColor';
+
+export default makeStyles(theme => ({
+ root: {
+ marginTop: 19,
+ background: getColor('grey2')(theme),
+ padding: '25px 44px',
+ [theme.breakpoints.down('md')]: {
+ padding: 0,
+ paddingBottom: 27,
+ },
+ },
+ head: {
+ border: '1px solid #979797',
+ '& th': {
+ textTransform: 'uppercase',
+ fontSize: 15,
+ letterSpacing: 2,
+ fontWeight: 'bold',
+ borderBottom: 'none',
+ },
+ },
+ body: {
+ '& td': {
+ fontSize: 15,
+ paddingTop: 18,
+ paddingBottom: 18,
+ },
+ },
+ header: {
+ display: 'flex',
+ alignItems: 'baseline',
+ flexWrap: 'wrap',
+ '& *:first-child': {
+ marginRight: 35,
+ },
+ marginBottom: 23,
+ [theme.breakpoints.down('md')]: {
+ padding: '10px 27px 0',
+ marginBottom: 10,
+ },
+ },
+ link: {
+ marginLeft: 'auto',
+ textTransform: 'uppercase',
+ color: getColor('main')(theme),
+ fontSize: 15,
+ textDecoration: 'none',
+ },
+ name: {
+ display: 'flex',
+ alignItems: 'center',
+ color: getColor('main')(theme),
+ whiteSpace: 'nowrap',
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ '& span': {
+ width: 33,
+ height: 33,
+ flexShrink: 0,
+ background: getColor('main')(theme),
+ borderRadius: '50%',
+ marginRight: 22,
+ },
+ [theme.breakpoints.down('md')]: {
+ marginBottom: 22,
+ },
+ },
+ list: {
+ display: 'flex',
+ width: '100%',
+ overflowX: 'auto',
+ },
+ card: {
+ padding: 17,
+ background: '#505050',
+ marginRight: 12,
+ maxWidth: 326,
+ },
+ cardTitle: {
+ fontSize: 12,
+ textTransform: 'uppercase',
+ color: '#C4C4C4',
+ letterSpacing: 2,
+ fontWight: 'bold',
+ },
+}));
diff --git a/src/v2/components/Validators/ValidatorsMap/index.jsx b/src/v2/components/Validators/ValidatorsMap/index.jsx
new file mode 100644
index 00000000..5882054e
--- /dev/null
+++ b/src/v2/components/Validators/ValidatorsMap/index.jsx
@@ -0,0 +1,98 @@
+import {Typography} from '@material-ui/core';
+import {observer} from 'mobx-react-lite';
+import React from 'react';
+import {
+ ComposableMap,
+ Geographies,
+ Geography,
+ Marker,
+ Markers,
+ ZoomableGroup,
+} from 'react-simple-maps';
+import {map} from 'lodash/fp';
+import nodesStore from 'v2/stores/nodes';
+import MapTooltip from 'v2/components/UI/MapTooltip';
+import {mapStyle, markerStyle} from 'v2/theme';
+
+import useStyles from './styles';
+
+const mapStyles = {
+ default: mapStyle,
+ hover: mapStyle,
+ pressed: mapStyle,
+};
+
+const ValidatorsMap = () => {
+ const classes = useStyles();
+ const {mapMarkers} = nodesStore;
+
+ const mapConfig = {
+ projection: {
+ scale: 150,
+ rotation: [-11, 0, 0],
+ },
+ style: {
+ width: '100%',
+ height: 'auto',
+ },
+ center: [0, 20],
+ };
+
+ const renderMarker = marker => (
+
+ (
+ <>
+ NODE: {marker.name}
+ Gossip: {marker.gossip}
+ >
+ )}
+ >
+
+
+
+ );
+ return (
+
+ Active Validators Map
+
+
+
+ {(geographies, projection) =>
+ geographies.map(
+ (geography, i) =>
+ geography.id !== 'ATA' && (
+
+ ),
+ )
+ }
+
+ {map(renderMarker)(mapMarkers)}
+
+
+
+ );
+};
+
+export default observer(ValidatorsMap);
diff --git a/src/v2/components/Validators/ValidatorsMap/styles.js b/src/v2/components/Validators/ValidatorsMap/styles.js
new file mode 100644
index 00000000..81f1aef7
--- /dev/null
+++ b/src/v2/components/Validators/ValidatorsMap/styles.js
@@ -0,0 +1,28 @@
+import {makeStyles} from '@material-ui/core';
+import getColor from 'v2/utils/getColor';
+
+export default makeStyles(theme => ({
+ card: {
+ background: getColor('grey2')(theme),
+ maxHeight: 558,
+ overflow: 'hidden',
+ padding: '14px 35px',
+ },
+ tooltip: {
+ background: '#fff',
+ color: getColor('dark')(theme),
+ padding: 10,
+ borderRadius: 0,
+ },
+ tooltipTitle: {
+ fontSize: 15,
+ marginBottom: 4,
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ },
+ tooltipDesc: {
+ fontSize: 12,
+ color: getColor('grey3')(theme),
+ },
+}));
diff --git a/src/v2/components/Validators/index.jsx b/src/v2/components/Validators/index.jsx
new file mode 100644
index 00000000..70abd832
--- /dev/null
+++ b/src/v2/components/Validators/index.jsx
@@ -0,0 +1,95 @@
+// @flow
+import {Container, Grid} from '@material-ui/core';
+import React from 'react';
+import {observer} from 'mobx-react-lite';
+import {map} from 'lodash/fp';
+import Card from 'v2/components/UI/StatCard';
+import SectionHeader from 'v2/components/UI/SectionHeader';
+import NodesStore from 'v2/stores/nodes';
+
+import Button from '../UI/Button';
+import ValidatorsMap from './ValidatorsMap';
+import ValidatorsTable from './Table';
+import useStyles from './styles';
+
+const Validators = () => {
+ const classes = useStyles();
+ const {cluster, clusterChanges} = NodesStore;
+ const cards = [
+ {
+ title: 'Total Circulating SOL',
+ value: cluster.supply / Math.pow(2, 34).toFixed(2),
+ changes: clusterChanges.supply,
+ period: 'since yesterday',
+ },
+ {
+ title: 'Total Bonded Tokens',
+ value: '100,000',
+ changes: '-2.45',
+ period: 'since yesterday',
+ },
+ {
+ title: '# Active Validators',
+ value: cluster.nodes.length,
+ changes: clusterChanges.nodes,
+ period: 'since yesterday',
+ },
+ ];
+
+ const renderStats = ({
+ title,
+ value,
+ changes = null,
+ period,
+ }: {
+ title: string,
+ value: string | (() => React$Node),
+ changes: () => React$Node,
+ period: string,
+ }) => (
+
+
(
+
+
{changes}%
+
{period}
+
+ )}
+ />
+
+ );
+
+ return (
+
+
+
+ Validators Overview
+
+
+
+
+
+
+
+
+
+ {map(renderStats)(cards)}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default observer(Validators);
diff --git a/src/v2/components/Validators/styles.js b/src/v2/components/Validators/styles.js
new file mode 100644
index 00000000..85de48d9
--- /dev/null
+++ b/src/v2/components/Validators/styles.js
@@ -0,0 +1,32 @@
+import {makeStyles} from '@material-ui/core';
+import getColor from 'v2/utils/getColor';
+
+export default makeStyles(theme => ({
+ root: {
+ padding: '40px 0',
+ },
+ changes: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ width: '100%',
+ padding: '0 20px',
+ },
+ period: {
+ fontSize: 12,
+ textTransform: 'uppercase',
+ color: getColor('grey4')(theme),
+ fontWeight: 'normal',
+ },
+ card: {
+ '&:not(:last-child)': {
+ marginBottom: 15,
+ },
+ },
+ becomeBtn: {
+ marginLeft: 'auto',
+ [theme.breakpoints.down('md')]: {
+ display: 'none',
+ },
+ },
+}));
diff --git a/src/v2/stores/nodes.js b/src/v2/stores/nodes.js
index e48d9785..b87ab090 100644
--- a/src/v2/stores/nodes.js
+++ b/src/v2/stores/nodes.js
@@ -5,7 +5,6 @@ import {parseClusterInfo} from 'v2/utils/parseMessage';
import calcChanges from '../utils/calcChanges';
class NodesStore {
- nodes = [];
cluster = {
nodes: [],
};
@@ -42,7 +41,6 @@ class NodesStore {
}
decorate(NodesStore, {
- nodes: observable,
cluster: observable,
updateClusterInfo: action.bound,
mapMarkers: computed,
diff --git a/src/v2/stores/socket.js b/src/v2/stores/socket.js
index 180a8186..48e94a91 100644
--- a/src/v2/stores/socket.js
+++ b/src/v2/stores/socket.js
@@ -1,7 +1,10 @@
+import {observable, decorate, action} from 'mobx';
import {initSocket} from 'v2/api/socket';
import networkOverview from 'v2/stores/networkOverview';
import nodes from 'v2/stores/nodes';
+import * as EndpointConfig from '../../EndpointConfig';
+
const socketActions = {
actions: {
'global-info': networkOverview.updateGlobalStats,
@@ -9,14 +12,27 @@ const socketActions = {
'cluster-info': nodes.updateClusterInfo,
'txns-by-prgid': networkOverview.addTransaction,
},
+ endpointName: EndpointConfig.getEndpointName(),
onMessage(event) {
const {t, m} = JSON.parse(event.data);
this.actions[t](m);
},
init() {
+ if (this.ws) {
+ this.ws.close();
+ this.ws = null;
+ }
this.ws = initSocket();
this.ws.onmessage = event => this.onMessage(event);
},
+ updateEndpointName(endpointName) {
+ this.endpointName = endpointName;
+ },
};
+decorate(socketActions, {
+ endpointName: observable,
+ updateEndpointName: action.bound,
+});
+
export default socketActions;