import React, { useEffect, useState } from 'react';
import { ToastContainer, toast } from 'react-toastify';
import ReactTooltip from 'react-tooltip';
import { Role, NetworkURL, Network as NetworkLabel, AccountType, Environment, Constants, TransactionType, ButtonText, ContractState, OperationStatus } from '../util/enum';
import { convertQaToCommaStr, getAddressLink } from '../util/utils';
import StakingPortfolio from './staking-portfolio';
import SsnTable from './ssn-table';
import Alert from './alert';
import { toBech32Address } from '@zilliqa-js/crypto';
import WithdrawCommModal from './contract-calls/withdraw-comm';
import UpdateReceiverAddress from './contract-calls/update-receiver-address';
import UpdateCommRateModal from './contract-calls/update-commission-rate';
import DelegateStakeModal from './contract-calls/delegate-stake';
import ReDelegateStakeModal from './contract-calls/redeleg';
import WithdrawStakeModal from './contract-calls/withdraw-stake';
import WithdrawRewardModal from './contract-calls/withdraw-reward';
import CompleteWithdrawModal from './contract-calls/complete-withdraw';
import SwapDelegModal from './contract-calls/swap-deleg';
import logo from "../static/logo.png";
import DisclaimerModal from './disclaimer';
import DelegatorStatsTable from './delegator-stats-table';
import OperatorStatsTable from './operator-stats-table';
import CompleteWithdrawalTable from './complete-withdrawal-table';
import IconShuffle from './icons/shuffle';
import IconQuestionCircle from './icons/question-circle';
import IconRefresh from './icons/refresh';
import IconBell from './icons/bell';
import IconCheckboxBlankCircle from './icons/checkbox-blank-circle';
import IconSun from './icons/sun';
import IconMoon from './icons/moon';
import useDarkMode from '../util/use-dark-mode';
import { getLocalItem, storeLocalItem } from '../util/use-local-storage';
import Footer from './footer';
import RecentTxnDropdown from './recent-txn';
import Tippy from '@tippyjs/react';
import '../tippy.css';
import 'tippy.js/animations/shift-away-subtle.css';
import WarningDashboardBanner from './warning-dashboard-banner';
import { POLL_USER_DATA_STOP, QUERY_AND_UPDATE_USER_STATS, RESET_USER_STATE, UPDATE_ADDRESS } from '../store/userSlice';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import { logger } from '../util/logger';
import { getEnvironment, getNetworks, NetworkConfig, Networks } from '../util/config-json-helper';
import { RESET_BLOCKCHAIN_STATE, UPDATE_CHAIN_INFO } from '../store/blockchainSlice';
import { ZilSigner } from '../zilliqa-signer';
import { QUERY_AND_UPDATE_STAKING_STATS } from '../store/stakingSlice';
function Dashboard(props: any) {
const dispatch = useAppDispatch();
const userState = useAppSelector(state => state.user);
const isDelegStatsLoading = useAppSelector(state => state.user.is_deleg_stats_loading);
const blockchainState = useAppSelector(state => state.blockchain);
// config.js from public folder
const env = getEnvironment();
const networks: Networks = getNetworks();
const proxy = useAppSelector(state => state.blockchain.proxy);
const networkURL = useAppSelector(state => state.blockchain.blockchain);
const [walletAddress, setWalletAddress] = useState(userState.address_base16 || '');
const [blockchain, setBlockchain] = useState(blockchainState.blockchain || '');
const [isRefreshDisabled, setIsRefreshDisabled] = useState(false);
const [isTxnNotify, setIsTxnNotify] = useState(false);
const [ariaExpanded, setAriaExpanded] = useState(false);
const [recentTransactions, setRecentTransactions] = useState([] as any)
const darkMode = useDarkMode(true);
const cleanUp = () => {
logger("directing to main");
dispatch(POLL_USER_DATA_STOP());
dispatch(RESET_USER_STATE());
dispatch(RESET_BLOCKCHAIN_STATE());
props.history.push("/");
}
// set recent txn indicator icon
const handleTxnNotify = () => {
if (!isTxnNotify) {
return;
}
setIsTxnNotify(false);
}
const toggleTheme = () => {
if (darkMode.value === true) {
darkMode.disable();
} else {
darkMode.enable();
}
}
const updateRecentTransactions = (type: TransactionType, txnId: string) => {
let temp = JSON.parse(JSON.stringify(recentTransactions));
if ((temp.length + 1) > 10) {
// suppose we add a new element
// restrict number of elements as local storage has limits
// recent txn is always in newest to oldest
// remove last element - last element = oldest txn
temp.pop();
}
// reverse so that order is oldest to newest
// add new item as last element
temp = temp.reverse();
temp.push({type: type, txnId: txnId});
// restore order back
setRecentTransactions([...temp].reverse());
storeLocalItem(userState.address_bech32, proxy, networkURL, 'recent-txn', temp.reverse());
// set recent txn indicator icon
setIsTxnNotify(true);
}
// re-hydrate data from localstorage
useEffect(() => {
let txns = getLocalItem(userState.address_bech32, proxy, networkURL, 'recent-txn', [] as any);
setRecentTransactions(txns);
}, [userState.address_bech32, proxy, networkURL]);
const timeout = (delay: number) => {
return new Promise(res => setTimeout(res, delay));
}
const pollData = async () => {
console.log("polling data...")
setIsRefreshDisabled(true);
dispatch(QUERY_AND_UPDATE_USER_STATS());
dispatch(QUERY_AND_UPDATE_STAKING_STATS());
await timeout(Constants.MANUAL_REFRESH_DELAY);
setIsRefreshDisabled(false);
}
// for zilpay to toggle different network
const networkChanger = (net: string) => {
let label;
switch (net) {
case NetworkLabel.MAINNET:
// do nothing
Alert("info", "Info", "You are on Mainnet.");
label = NetworkLabel.MAINNET;
break;
case NetworkLabel.TESTNET:
label = NetworkLabel.TESTNET;
if (env === Environment.PROD) {
// warn users not to switch to testnet on production
Alert("warn", "Testnet not supported", "Please switch to Mainnet via ZilPay.");
}
break;
case NetworkLabel.ISOLATED_SERVER:
case NetworkLabel.PRIVATE:
label = NetworkLabel.ISOLATED_SERVER;
if (env === Environment.PROD) {
// warn users not to switch to testnet on production
Alert("warn", "Private network not supported", "Please switch to Mainnet via ZilPay.");
}
break;
default:
label = NetworkLabel.TESTNET;
break;
}
const networkConfig: NetworkConfig = networks[label];
dispatch(UPDATE_CHAIN_INFO({
proxy: networkConfig.proxy || '',
impl: networkConfig.impl || '',
blockchain: networkConfig.blockchain || '',
staking_viewer: networkConfig.node_status || '',
api_list: networkConfig.api_list || [],
}));
}
/**
* When document has loaded, it start to observable network form zilpay.
*/
useEffect(() => {
if (userState.account_type === AccountType.ZILPAY) {
const zilPay = (window as any).zilPay;
if (zilPay) {
// switch to the zilpay network on load
networkChanger(zilPay.wallet.net);
const accountStreamChanged = zilPay.wallet.observableAccount().subscribe((account: any) => {
console.log("zil pay account changing...");
const bech32 = toBech32Address(account.base16);
dispatch(UPDATE_ADDRESS({ address_base16: account.base16, address_bech32: bech32 }));
});
const networkStreamChanged = zilPay.wallet.observableNetwork().subscribe((net: string) => networkChanger(net));
return () => {
accountStreamChanged.unsubscribe();
networkStreamChanged.unsubscribe();
};
}
}
// must only run once due to global listener
// eslint-disable-next-line
}, []);
useEffect(() => {
if (env === Environment.DEV) {
// disable auth check for development
return;
}
if (!userState.authenticated) {
// redirect to login request
dispatch(POLL_USER_DATA_STOP());
dispatch(RESET_USER_STATE());
dispatch(RESET_BLOCKCHAIN_STATE());
props.history.push("/oops");
}
}, [env, userState.authenticated, props.history, dispatch]);
// change to correct role
useEffect(() => {
console.log("change wallet")
if (walletAddress !== userState.address_base16) {
// address changed
dispatch(QUERY_AND_UPDATE_USER_STATS());
}
setWalletAddress(userState.address_base16);
}, [walletAddress, userState.address_base16, dispatch]);
useEffect(() => {
console.log("change network")
if (blockchain !== blockchainState.blockchain) {
// network changed
dispatch(QUERY_AND_UPDATE_USER_STATS());
dispatch(QUERY_AND_UPDATE_STAKING_STATS());
ZilSigner.changeNetwork(blockchainState.blockchain);
}
setBlockchain(blockchainState.blockchain);
}, [blockchain, blockchainState.blockchain, dispatch]);
// prevent user from refreshing
useEffect(() => {
window.onbeforeunload = (e: any) => {
e.preventDefault();
e.returnValue = 'The page auto retrieves data periodically. Please do not force refresh as you will lose your wallet connection.';
setTimeout(() => {
toast.dismiss();
}, 8000);
return (
Alert("warn", "Warning", "The app auto retrieves data periodically. Please do not force refresh as you will lose your wallet connection.")
);
}
}, []);
useEffect(() => {
if (userState.selected_role === Role.OPERATOR &&
userState.role === Role.DELEGATOR) {
Alert("warn", "Warning", "You have been redirected to the delegator dashboard.");
}
}, [userState.selected_role, userState.role]);
// eslint-disable-next-line
return (
<>
Refresh
{/* delegator section */}
{/* complete withdrawal */}
{
(userState.role === Role.DELEGATOR) &&
}
{
(userState.role === Role.OPERATOR) &&
<>
{/* node operator section */}
Hi {userState.operator_stats.name ? userState.operator_stats.name : 'Operator'}! What would you like to do today?
>
}
{
(userState.role === Role.DELEGATOR) &&
<>
{/* delegator statistics */}
>
}
{
(userState.role === Role.DELEGATOR) &&
<>
{/* delegator portfolio */}
My Staking Portfolio
This shows you the list of nodes which you have staked your deposit in.
>
}
{/* operator statistics */}
{
(userState.role === Role.OPERATOR) &&
}
Staked Seed Nodes
Please refer to our
Staking Viewer
for more information on the nodes' statuses.
>
);
}
export default Dashboard;