From a2849294792781f4835e2717dbd6b0d9cce8c651 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Thu, 5 Jan 2023 20:22:34 +0100 Subject: [PATCH 01/21] wallet porfolio --- src/common/api/misc.ts | 16 + src/common/app.tsx | 7 + .../engine-tokens-estimated/index.tsx | 1 + .../components/hive-engine-chart/index.tsx | 10 +- src/common/components/token-details/index.tsx | 973 ++++++++++++++++++ .../wallet-portfolio-chart/index.tsx | 133 +++ .../components/wallet-portfolio/asset/hbd.png | Bin 0 -> 13770 bytes .../components/wallet-portfolio/index.tsx | 261 +++++ src/common/pages/profile-functional.tsx | 16 +- src/common/routes.ts | 5 +- 10 files changed, 1413 insertions(+), 9 deletions(-) create mode 100644 src/common/components/token-details/index.tsx create mode 100644 src/common/components/wallet-portfolio-chart/index.tsx create mode 100644 src/common/components/wallet-portfolio/asset/hbd.png create mode 100644 src/common/components/wallet-portfolio/index.tsx diff --git a/src/common/api/misc.ts b/src/common/api/misc.ts index 5b13768e72d..ccda2d8a6ae 100644 --- a/src/common/api/misc.ts +++ b/src/common/api/misc.ts @@ -71,3 +71,19 @@ export const fetchGif = async (query: string | null, limit: string, offset: stri } return gifs; }; + +export const marketInfo = async (): Promise => { + + const url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=hive%2C%20hive_dollar&order=market_cap_desc&per_page=100&page=1&sparkline=false`; + const data = await axios.get(url) + .then((r: any) => r.data) + return data; +}; + +export const marketChart = async (token: string): Promise => { + + const url = `https://api.coingecko.com/api/v3/coins/${token}/market_chart?vs_currency=usd&days=30`; + const data = await axios.get(url) + .then((r: any) => r.data) + return data; +}; diff --git a/src/common/app.tsx b/src/common/app.tsx index fb47877eef5..fd2e99071f0 100644 --- a/src/common/app.tsx +++ b/src/common/app.tsx @@ -23,6 +23,7 @@ import { pageMapDispatchToProps, pageMapStateToProps } from "./pages/common"; import { connect } from "react-redux"; import loadable from "@loadable/component"; import Announcement from "./components/announcement"; +import TokenDetails from "./components/token-details"; // Define lazy pages const ProfileContainer = loadable(() => import("./pages/profile-functional")); @@ -132,6 +133,12 @@ const App = (props: any) => { path={routes.PROPOSAL_DETAIL} component={ProposalDetailContainer} /> + diff --git a/src/common/components/engine-tokens-estimated/index.tsx b/src/common/components/engine-tokens-estimated/index.tsx index 5c0c2d8862e..af358a386e2 100644 --- a/src/common/components/engine-tokens-estimated/index.tsx +++ b/src/common/components/engine-tokens-estimated/index.tsx @@ -4,6 +4,7 @@ import { getMetrics } from "../../api/hive-engine"; export const EngineTokensEstimated = (props: any) => { const { tokens: userTokens, dynamicProps } = props; + // console.log(userTokens) const [estimated, setEstimated] = useState(`${_t("wallet.calculating")}...`); useEffect(() => { diff --git a/src/common/components/hive-engine-chart/index.tsx b/src/common/components/hive-engine-chart/index.tsx index 2012cbf6429..5534f275a28 100644 --- a/src/common/components/hive-engine-chart/index.tsx +++ b/src/common/components/hive-engine-chart/index.tsx @@ -30,8 +30,8 @@ export const HiveEngineChart = (props: any) => { enabled: false }, chart: { - height: "70", - width: "600", + height: "40", + width: "100", zoomType: "x", backgroundColor: "transparent", border: "none", @@ -55,7 +55,7 @@ export const HiveEngineChart = (props: any) => { area: { fillColor: theme === Theme.night ? "#2e3d51" : "#f3f7fb", lineColor: "transparent", - lineWidth: 399 + lineWidth: 150 }, series: { marker: { @@ -119,14 +119,14 @@ export const HiveEngineChart = (props: any) => { series: [ { name: "tokens", - data: prices.length === 0 ? [0, 0] : prices, + data: prices?.length === 0 ? [0, 0] : prices, type: "line", enableMouseTracking: true } ] }; return ( -
+
diff --git a/src/common/components/token-details/index.tsx b/src/common/components/token-details/index.tsx new file mode 100644 index 00000000000..298927bc172 --- /dev/null +++ b/src/common/components/token-details/index.tsx @@ -0,0 +1,973 @@ +import React from "react"; + +import { History } from "history"; + +import { AssetSymbol } from "@hiveio/dhive"; + +import { Global } from "../../store/global/types"; +import { Account } from "../../store/accounts/types"; +import { DynamicProps } from "../../store/dynamic-props/types"; +import { OperationGroup, Transactions } from "../../store/transactions/types"; +import { ActiveUser } from "../../store/active-user/types"; + +import BaseComponent from "../base"; +import Tooltip from "../tooltip"; +import FormattedCurrency from "../formatted-currency"; +import TransactionList from "../transactions"; +import DelegatedVesting from "../delegated-vesting"; +import ReceivedVesting from "../received-vesting"; +import ConversionRequests from "../converts"; +import CollateralizedConversionRequests from "../converts-collateralized"; +import SavingsWithdraw from "../savings-withdraw"; +import OpenOrdersList from "../open-orders-list"; + +import DropDown from "../dropdown"; +import Transfer, { TransferMode, TransferAsset } from "../transfer"; +import { error, success } from "../feedback"; +import WalletMenu from "../wallet-menu"; +import WithdrawRoutes from "../withdraw-routes"; + +import HiveWallet from "../../helper/hive-wallet"; + +import { vestsToHp } from "../../helper/vesting"; + +import { + getAccount, + getConversionRequests, + getSavingsWithdrawFrom, + getOpenOrder, + getCollateralizedConversionRequests +} from "../../api/hive"; + +import { claimRewardBalance, formatError } from "../../api/operations"; + +import formattedNumber from "../../util/formatted-number"; + +import parseAsset from "../../helper/parse-asset"; + +import { _t } from "../../i18n"; + +import { plusCircle } from "../../img/svg"; +import { dayDiff, dateToFullRelative, hourDiff, secondDiff } from "../../helper/parse-date"; +import { useParams } from "react-router"; + +interface Props { + history: History; + global: Global; + dynamicProps: DynamicProps; + activeUser: ActiveUser | null; + transactions: Transactions; + account: Account; + signingKey: string; + addAccount: (data: Account) => void; + updateActiveUser: (data?: Account) => void; + setSigningKey: (key: string) => void; + fetchTransactions: (username: string, group?: OperationGroup | "") => void; + fetchPoints: (username: string, type?: number) => void; + updateWalletValues: () => void; +} + +interface State { + delegatedList: boolean; + convertList: boolean; + cconvertList: boolean; + receivedList: boolean; + savingsWithdrawList: boolean; + openOrdersList: boolean; + tokenType: AssetSymbol; + claiming: boolean; + claimed: boolean; + transfer: boolean; + withdrawRoutes: boolean; + transferMode: null | TransferMode; + transferAsset: null | TransferAsset; + converting: number; + cconverting: number; + withdrawSavings: { hbd: string | number; hive: string | number }; + openOrders: { hbd: string | number; hive: string | number }; + aprs: { hbd: string | number; hp: string | number }; +} + +export class TokenDetails extends BaseComponent { + state: State = { + delegatedList: false, + receivedList: false, + convertList: false, + cconvertList: false, + savingsWithdrawList: false, + openOrdersList: false, + tokenType: "HBD", + claiming: false, + claimed: false, + transfer: false, + withdrawRoutes: false, + transferMode: null, + transferAsset: null, + converting: 0, + cconverting: 0, + withdrawSavings: { hbd: 0, hive: 0 }, + openOrders: { hbd: 0, hive: 0 }, + aprs: { hbd: 0, hp: 0 } + }; + + componentDidMount() { + // const params: any = useParams(); + // console.log(params) + console.log(window.location.href.split('/')[5]) + this.fetchConvertingAmount(); + this.fetchCollateralizedConvertingAmount(); + this.fetchWithdrawFromSavings(); + this.getOrders(); + } + + getCurrentHpApr = (gprops: DynamicProps) => { + // The inflation was set to 9.5% at block 7m + const initialInflationRate = 9.5; + const initialBlock = 7000000; + + // It decreases by 0.01% every 250k blocks + const decreaseRate = 250000; + const decreasePercentPerIncrement = 0.01; + + // How many increments have happened since block 7m? + const headBlock = gprops.headBlock; + const deltaBlocks = headBlock - initialBlock; + const decreaseIncrements = deltaBlocks / decreaseRate; + + // Current inflation rate + let currentInflationRate = + initialInflationRate - decreaseIncrements * decreasePercentPerIncrement; + + // Cannot go lower than 0.95% + if (currentInflationRate < 0.95) { + currentInflationRate = 0.95; + } + + // Now lets calculate the "APR" + const vestingRewardPercent = gprops.vestingRewardPercent / 10000; + const virtualSupply = gprops.virtualSupply; + const totalVestingFunds = gprops.totalVestingFund; + return (virtualSupply * currentInflationRate * vestingRewardPercent) / totalVestingFunds; + }; + + fetchConvertingAmount = async () => { + const { account, dynamicProps } = this.props; + const { aprs } = this.state; + const { hbdInterestRate } = dynamicProps; + + let hp = this.getCurrentHpApr(dynamicProps).toFixed(3); + this.setState({ aprs: { ...aprs, hbd: hbdInterestRate / 100, hp } }); + + const crd = await getConversionRequests(account.name); + if (crd.length === 0) { + return; + } + + let converting = 0; + crd.forEach((x) => { + converting += parseAsset(x.amount).amount; + }); + this.stateSet({ converting }); + }; + + fetchCollateralizedConvertingAmount = async () => { + const { account } = this.props; + + const ccrd = await getCollateralizedConversionRequests(account.name); + if (ccrd.length === 0) { + return; + } + + let cconverting = 0; + ccrd.forEach((x) => { + cconverting += parseAsset(x.collateral_amount).amount; + }); + this.stateSet({ cconverting }); + }; + + fetchWithdrawFromSavings = async () => { + const { account } = this.props; + + const swf = await getSavingsWithdrawFrom(account.name); + if (swf.length === 0) { + return; + } + + let withdrawSavings = { hbd: 0, hive: 0 }; + swf.forEach((x) => { + const aa = x.amount; + if (aa.includes("HIVE")) { + withdrawSavings.hive += parseAsset(x.amount).amount; + } else { + withdrawSavings.hbd += parseAsset(x.amount).amount; + } + }); + + this.stateSet({ withdrawSavings }); + }; + + getOrders = async () => { + const { account } = this.props; + + const oo = await getOpenOrder(account.name); + if (oo.length === 0) { + return; + } + + let openOrders = { hive: 0, hbd: 0 }; + oo.forEach((x) => { + const bb = x.sell_price.base; + if (bb.includes("HIVE")) { + openOrders.hive += parseAsset(bb).amount; + } else { + openOrders.hbd += parseAsset(bb).amount; + } + }); + + this.stateSet({ openOrders }); + }; + + toggleDelegatedList = () => { + const { delegatedList } = this.state; + this.stateSet({ delegatedList: !delegatedList }); + }; + + toggleConvertList = () => { + const { convertList } = this.state; + this.stateSet({ convertList: !convertList }); + }; + + toggleCConvertList = () => { + const { cconvertList } = this.state; + this.stateSet({ cconvertList: !cconvertList }); + }; + + toggleSavingsWithdrawList = (tType: AssetSymbol) => { + const { savingsWithdrawList } = this.state; + this.stateSet({ savingsWithdrawList: !savingsWithdrawList, tokenType: tType }); + }; + + toggleOpenOrdersList = (tType: AssetSymbol) => { + const { openOrdersList } = this.state; + this.stateSet({ openOrdersList: !openOrdersList, tokenType: tType }); + }; + + toggleReceivedList = () => { + const { receivedList } = this.state; + this.stateSet({ receivedList: !receivedList }); + }; + + toggleWithdrawRoutes = () => { + const { withdrawRoutes } = this.state; + this.stateSet({ withdrawRoutes: !withdrawRoutes }); + }; + + toggleClaimInterest = () => { + this.openTransferDialog("claim-interest", "HBD"); + }; + + claimRewardBalance = () => { + const { activeUser, updateActiveUser } = this.props; + const { claiming } = this.state; + + if (claiming || !activeUser) { + return; + } + + this.stateSet({ claiming: true }); + + return getAccount(activeUser?.username!) + .then((account) => { + const { + reward_hive_balance: hiveBalance = account.reward_hive_balance, + reward_hbd_balance: hbdBalance = account.reward_hbd_balance, + reward_vesting_balance: vestingBalance + } = account; + + return claimRewardBalance( + activeUser?.username!, + hiveBalance!, + hbdBalance!, + vestingBalance! + ); + }) + .then(() => getAccount(activeUser.username)) + .then((account) => { + success(_t("wallet.claim-reward-balance-ok")); + this.stateSet({ claiming: false, claimed: true }); + updateActiveUser(account); + }) + .catch((err) => { + error(...formatError(err)); + this.stateSet({ claiming: false }); + }); + }; + + openTransferDialog = (mode: TransferMode, asset: TransferAsset) => { + this.stateSet({ transfer: true, transferMode: mode, transferAsset: asset }); + }; + + closeTransferDialog = () => { + this.stateSet({ transfer: false, transferMode: null, transferAsset: null }); + }; + + render() { + const { global, dynamicProps, account, activeUser, history } = this.props; + const { + claiming, + claimed, + transfer, + transferAsset, + transferMode, + converting, + cconverting, + withdrawSavings, + aprs: { hbd, hp }, + openOrders, + tokenType + } = this.state; + + if (!account.__loaded) { + return null; + } + + const { hivePerMVests, hbdInterestRate } = dynamicProps; + const isMyPage = activeUser && activeUser.username === account.name; + const w = new HiveWallet(account, dynamicProps, converting); + // console.log(w) + const params: string = window.location.href.split('/')[5] + + const lastIPaymentRelative = + account.savings_hbd_last_interest_payment == "1970-01-01T00:00:00" + ? null + : dateToFullRelative(account.savings_hbd_last_interest_payment); + const lastIPaymentDiff = dayDiff( + account.savings_hbd_last_interest_payment == "1970-01-01T00:00:00" + ? account.savings_hbd_seconds_last_update + : account.savings_hbd_last_interest_payment + ); + const remainingHours = + 720 - + hourDiff( + account.savings_hbd_last_interest_payment == "1970-01-01T00:00:00" + ? account.savings_hbd_seconds_last_update + : account.savings_hbd_last_interest_payment + ); + + const secondsSincePayment = secondDiff(account.savings_hbd_seconds_last_update); + + const pendingSeconds = w.savingBalanceHbd * secondsSincePayment; + const secondsToEstimate = w.savingHbdSeconds / 1000 + pendingSeconds; + const estimatedUIn = (secondsToEstimate / (60 * 60 * 24 * 365)) * (hbdInterestRate / 10000); + + const estimatedInterest = formattedNumber(estimatedUIn, { suffix: "$" }); + const remainingDays = 30 - lastIPaymentDiff; + + const totalHP = formattedNumber(vestsToHp(w.vestingShares, hivePerMVests), { suffix: "HP" }); + const totalDelegated = formattedNumber(vestsToHp(w.vestingSharesDelegated, hivePerMVests), { + prefix: "-", + suffix: "HP" + }); + + return ( +
+
+
+ {params === "hive-power" && w.hasUnclaimedRewards && !claimed && ( +
+
{_t("wallet.unclaimed-rewards")}
+
+ {w.rewardHiveBalance > 0 && ( + {`${w.rewardHiveBalance} HIVE`} + )} + {w.rewardHbdBalance > 0 && ( + {`${w.rewardHbdBalance} HBD`} + )} + {w.rewardVestingHive > 0 && ( + {`${w.rewardVestingHive} HP`} + )} + {isMyPage && ( + + + {plusCircle} + + + )} +
+
+ )} + + {params === "hive" &&
+
+
{_t("wallet.hive")}
+
{_t("wallet.hive-description")}
+
+
+
+ {(() => { + let dropDownConfig: any; + if (isMyPage) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.transfer"), + onClick: () => { + this.openTransferDialog("transfer", "HIVE"); + } + }, + { + label: _t("wallet.transfer-to-savings"), + onClick: () => { + this.openTransferDialog("transfer-saving", "HIVE"); + } + }, + { + label: _t("wallet.power-up"), + onClick: () => { + this.openTransferDialog("power-up", "HIVE"); + } + }, + { + label: _t("market-data.trade"), + onClick: () => { + this.props.history.push("/market"); + } + } + ] + }; + } else if (activeUser) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.transfer"), + onClick: () => { + this.openTransferDialog("transfer", "HIVE"); + } + } + ] + }; + } + return ( +
+ +
+ ); + })()} + + {formattedNumber(w.balance, { suffix: "HIVE" })} +
+ {cconverting > 0 && ( +
+ + + {"+"} {formattedNumber(cconverting, { suffix: "HIVE" })} + + +
+ )} + {openOrders && openOrders.hive > 0 && ( +
+ + this.toggleOpenOrdersList("HIVE")} + > + {"+"} {formattedNumber(openOrders.hive, { suffix: "HIVE" })} + + +
+ )} + {withdrawSavings && withdrawSavings.hive > 0 && ( +
+ + this.toggleSavingsWithdrawList("HIVE")} + > + {"+"} {formattedNumber(withdrawSavings.hive, { suffix: "HIVE" })} + + +
+ )} +
+
} + + {params === "hive-power" &&
+
+
{_t("wallet.hive-power")}
+
{_t("wallet.hive-power-description")}
+
+ {_t("wallet.hive-power-apr-rate", { value: hp })} +
+
+ +
+
+ {(() => { + let dropDownConfig: any; + if (isMyPage) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.delegate"), + onClick: () => { + this.openTransferDialog("delegate", "HP"); + } + }, + { + label: _t("wallet.power-down"), + onClick: () => { + this.openTransferDialog("power-down", "HP"); + } + }, + { + label: _t("wallet.withdraw-routes"), + onClick: () => { + this.toggleWithdrawRoutes(); + } + } + ] + }; + } else if (activeUser) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.delegate"), + onClick: () => { + this.openTransferDialog("delegate", "HP"); + } + }, + { + label: _t("wallet.power-up"), + onClick: () => { + this.openTransferDialog("power-up", "HIVE"); + } + } + ] + }; + } + return ( +
+ +
+ ); + })()} + {totalHP} +
+ + {w.vestingSharesDelegated > 0 && ( +
+ + + {formattedNumber(vestsToHp(w.vestingSharesDelegated, hivePerMVests), { + prefix: "-", + suffix: "HP" + })} + + +
+ )} + + {(() => { + if (w.vestingSharesReceived <= 0) { + return null; + } + + const strReceived = formattedNumber( + vestsToHp(w.vestingSharesReceived, hivePerMVests), + { prefix: "+", suffix: "HP" } + ); + + if (global.usePrivate) { + return ( +
+ + + {strReceived} + + +
+ ); + } + + return ( +
+ + {strReceived} + +
+ ); + })()} + + {w.nextVestingSharesWithdrawal > 0 && ( +
+ + + {formattedNumber(vestsToHp(w.nextVestingSharesWithdrawal, hivePerMVests), { + prefix: "-", + suffix: "HP" + })} + + +
+ )} + + {(w.vestingSharesDelegated > 0 || + w.vestingSharesReceived > 0 || + w.nextVestingSharesWithdrawal > 0) && ( +
+ + + {formattedNumber(vestsToHp(w.vestingSharesTotal, hivePerMVests), { + prefix: "=", + suffix: "HP" + })} + + +
+ )} +
+
} + + {params === "hbd" &&
+
+
{_t("wallet.hive-dollars")}
+
{_t("wallet.hive-dollars-description")}
+
+
+
+ {(() => { + let dropDownConfig: any; + if (isMyPage) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.transfer"), + onClick: () => { + this.openTransferDialog("transfer", "HBD"); + } + }, + { + label: _t("wallet.transfer-to-savings"), + onClick: () => { + this.openTransferDialog("transfer-saving", "HBD"); + } + }, + { + label: _t("wallet.convert"), + onClick: () => { + this.openTransferDialog("convert", "HBD"); + } + }, + { + label: _t("market-data.trade"), + onClick: () => { + this.props.history.push("/market"); + } + } + ] + }; + } else if (activeUser) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.transfer"), + onClick: () => { + this.openTransferDialog("transfer", "HBD"); + } + } + ] + }; + } + return ( +
+ +
+ ); + })()} + {formattedNumber(w.hbdBalance, { prefix: "$" })} +
+ + {converting > 0 && ( +
+ + + {"+"} {formattedNumber(converting, { prefix: "$" })} + + +
+ )} + + {withdrawSavings && withdrawSavings.hbd > 0 && ( +
+ + this.toggleSavingsWithdrawList("HBD")} + > + {"+"} {formattedNumber(withdrawSavings.hbd, { prefix: "$" })} + + +
+ )} + + {openOrders && openOrders.hbd > 0 && ( +
+ + this.toggleOpenOrdersList("HBD")}> + {"+"} {formattedNumber(openOrders.hbd, { prefix: "$" })} + + +
+ )} +
+
} + + {params === "hive" &&
+
+
{_t("wallet.savings")}
+
{_t("wallet.savings-description")}
+
+
+
+ {(() => { + let dropDownConfig: any; + if (isMyPage) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.withdraw-hive"), + onClick: () => { + this.openTransferDialog("withdraw-saving", "HIVE"); + } + } + ] + }; + } else if (activeUser) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.transfer"), + onClick: () => { + this.openTransferDialog("transfer-saving", "HIVE"); + } + } + ] + }; + } + return ( +
+ +
+ ); + })()} + {formattedNumber(w.savingBalance, { suffix: "HIVE" })} +
+
+
} + + {params === "hbd" &&
+
+
{_t("wallet.savings")}
+
{_t("wallet.savings-description")}
+
+ {_t("wallet.hive-dollars-apr-rate", { value: hbd })} +
+ {w.savingBalanceHbd > 0 && ( +
+ {_t("wallet.hive-dollars-apr-claim", { value: lastIPaymentRelative })}{" "} + {estimatedInterest} +
+ )} + {isMyPage && w.savingBalanceHbd > 0 && ( + + )} +
+
+
+ {(() => { + let dropDownConfig: any; + if (isMyPage) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.withdraw-hbd"), + onClick: () => { + this.openTransferDialog("withdraw-saving", "HBD"); + } + } + ] + }; + } else if (activeUser) { + dropDownConfig = { + history: this.props.history, + label: "", + items: [ + { + label: _t("wallet.transfer"), + onClick: () => { + this.openTransferDialog("transfer-saving", "HBD"); + } + } + ] + }; + } + return ( +
+ +
+ ); + })()} + + {formattedNumber(w.savingBalanceHbd, { suffix: "$" })} +
+
+
} + + {params === "hive" &&
+
+
{_t("wallet.estimated")}
+
{_t("wallet.estimated-description")}
+
+
+
+ +
+
+
} + + {params === "hive-power" && w.isPoweringDown && ( +
+ {_t("wallet.next-power-down", { + time: dateToFullRelative(w.nextVestingWithdrawalDate.toString()), + amount: formattedNumber(w.nextVestingSharesWithdrawalHive, { suffix: "HIVE" }), + weeks: w.weeksLeft + })} +
+ )} + + {TransactionList({ ...this.props })} +
+ {/* */} +
+ + {transfer && ( + + )} + + {this.state.delegatedList && ( + + )} + + {this.state.receivedList && ( + + )} + + {this.state.convertList && ( + + )} + + {this.state.cconvertList && ( + + )} + + {this.state.savingsWithdrawList && ( + this.toggleSavingsWithdrawList("HBD")} + /> + )} + + {this.state.openOrdersList && ( + this.toggleOpenOrdersList("HBD")} + /> + )} + + {this.state.withdrawRoutes && ( + + )} +
+ ); + } +} + +export default (p: Props) => { + const props = { + history: p.history, + global: p.global, + dynamicProps: p.dynamicProps, + activeUser: p.activeUser, + transactions: p.transactions, + account: p.account, + signingKey: p.signingKey, + addAccount: p.addAccount, + updateActiveUser: p.updateActiveUser, + setSigningKey: p.setSigningKey, + fetchTransactions: p.fetchTransactions, + updateWalletValues: p.updateWalletValues, + fetchPoints: p.fetchPoints + }; + + return ; +}; diff --git a/src/common/components/wallet-portfolio-chart/index.tsx b/src/common/components/wallet-portfolio-chart/index.tsx new file mode 100644 index 00000000000..e5e8691f67c --- /dev/null +++ b/src/common/components/wallet-portfolio-chart/index.tsx @@ -0,0 +1,133 @@ +import React, { useEffect, useState } from 'react' +import ReactHighcharts from "react-highcharts"; +import { Theme } from "../../store/global/types"; +import { _t } from "../../i18n"; +import { marketChart } from '../../api/misc' +import moment from "moment"; + +export const WalletPortfolioChart = (props: any) => { + const { theme } = props; + + const [prices, setPrices] = useState([]) + + useEffect(() => { + marketChartInfo(); + }, []); + + const marketChartInfo = async () => { + const data: any = await marketChart("hive") + console.log(data) + setPrices(data.prices) + } + + const config: any = { + title: { + text: null + }, + credits: { enabled: false }, + legend: { + enabled: false + }, + chart: { + height: "40", + width: "100", + zoomType: "x", + backgroundColor: "transparent", + border: "none", + style: { + fontFamily: "inherit", + border: "none" + }, + plotBorderColor: "transparent", + plotBorderWidth: 0, + plotBackgroundColor: "transparent", + plotShadow: false, + type: "area", + spacingBottom: 0, + spacingTop: 0, + spacingLeft: 0, + spacingRight: 0, + marginTop: 0, + marginBottom: 0 + }, + plotOptions: { + area: { + fillColor: theme === Theme.night ? "#2e3d51" : "#f3f7fb", + lineColor: "transparent", + lineWidth: 150 + }, + series: { + marker: { + enabled: false, + states: { + hover: { + enabled: false + } + } + } + } + }, + tooltip: { + valueDecimals: 2, + useHTML: true, + shadow: false, + formatter: (({ chart }: any) => { + let date = moment(chart.hoverPoint.options.x).calendar(); + let rate = chart.hoverPoint.options.y; + return `
${_t("g.when")}: ${date}
${_t( + "g.price" + )}:${rate.toFixed(3)}
`; + }) as any, + enabled: true + }, + xAxis: { + lineWidth: 0, + minorGridLineWidth: 0, + lineColor: "transparent", + labels: { + enabled: false, + style: { + color: "red" + } + }, + title: { + text: null + }, + minorTickLength: 0, + tickLength: 0, + grid: { + enabled: false + }, + gridLineWidth: 0 + }, + yAxis: { + lineWidth: 0, + minorGridLineWidth: 0, + lineColor: "transparent", + title: { + text: null + }, + labels: { + enabled: false + }, + minorTickLength: 0, + tickLength: 0, + gridLineWidth: 0 + }, + series: [ + { + name: "tokens", + data: prices.length === 0 ? [0, 0] : prices, + type: "line", + enableMouseTracking: true + } + ] + }; + return ( +
+
+ +
+
+ ); + }; diff --git a/src/common/components/wallet-portfolio/asset/hbd.png b/src/common/components/wallet-portfolio/asset/hbd.png new file mode 100644 index 0000000000000000000000000000000000000000..ac9e2f5d995eecf4e0c15350af326f1e4cda8df8 GIT binary patch literal 13770 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 A$^ZZW literal 0 HcmV?d00001 diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx new file mode 100644 index 00000000000..22510be24d4 --- /dev/null +++ b/src/common/components/wallet-portfolio/index.tsx @@ -0,0 +1,261 @@ +import React from "react"; + +import { Global } from "../../store/global/types"; +import { Account } from "../../store/accounts/types"; +import { DynamicProps } from "../../store/dynamic-props/types"; + +import BaseComponent from "../base"; +import HiveEngineToken from "../../helper/hive-engine-wallet"; +import LinearProgress from "../linear-progress"; +import HiveWallet from "../../helper/hive-wallet"; + +import { + getHiveEngineTokenBalances, + getMetrics +} from "../../api/hive-engine"; + +import { + priceUpSvg, + priceDownSvg +} from "../../img/svg"; + +import formattedNumber from "../../util/formatted-number"; +import { _t } from "../../i18n"; +import { HiveEngineChart } from "../hive-engine-chart"; +import { History } from "history"; +import { vestsToHp } from "../../helper/vesting"; +import { marketInfo, } from "../../api/misc"; +import { WalletPortfolioChart } from "../wallet-portfolio-chart"; + +const hbdIcom = require("./asset/hbd.png") + +interface Props { + global: Global; + dynamicProps: DynamicProps; + account: Account; + history: History; + updateActiveUser: (data?: Account) => void; + updateWalletValues: () => void; +} + +interface State { + tokens: HiveEngineToken[]; + loading: boolean; + assetBalance: number; + allTokens: any; + converting: number; + coingeckoData: any; +} + +export class WalletPortfolio extends BaseComponent { + state: State = { + tokens: [], + loading: true, + assetBalance: 0, + allTokens: null, + converting: 0, + coingeckoData: [] + }; + _isMounted = false; + pricePerHive = this.props.dynamicProps.base / this.props.dynamicProps.quote; + + componentDidMount() { + this._isMounted = true; + this._isMounted && this.engineTokensData(); + this._isMounted && this.dataFromCoinGecko(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + dataFromCoinGecko = async () => { + const data = await marketInfo(); + // console.log(data) + this.setState({coingeckoData: data}) + } + + engineTokensData = async () => { + const allMarketTokens = await getMetrics(); + const { account } = this.props; + + const userTokens: any = await getHiveEngineTokenBalances(account.name); + + let balanceMetrics: any = userTokens?.map((item: any) => { + let eachMetric = allMarketTokens?.find((m: any) => m?.symbol === item?.symbol); + return { + ...item, + ...eachMetric + }; + }); + let tokensUsdValues: any = balanceMetrics?.map((w: any) => { + const usd_value = + w?.symbol === "SWAP.HIVE" + ? Number(this.pricePerHive * w.balance) + : w?.lastPrice === 0 + ? 0 + : Number(w?.lastPrice * this.pricePerHive * w?.balance).toFixed(10); + return { + ...w, + usd_value + }; + }); + this.setState({ allTokens: tokensUsdValues }); + return tokensUsdValues; + }; + + handleLink (symbol:string) { + this.props.history.push(`portfolio/${symbol.toLowerCase()}`) + } + + render() { + const { global, dynamicProps, account } = this.props; + const { allTokens, converting, coingeckoData } = this.state; + // console.log(coingeckoData[0]) + const { hivePerMVests } = dynamicProps; + const w = new HiveWallet(account, dynamicProps, converting); + // console.log(w) + + const totalHP: any = formattedNumber(vestsToHp(w.vestingShares, hivePerMVests)); + + return ( +
+
+ Total wallet Value: $1.19 +
+
+ + + + + {!global?.isMobile && + } + + {!global?.isMobile && + + } + + + + + + <> + this.handleLink("hive")}> + + + + + + + + this.handleLink("hive-power")}> + + + + + + + + this.handleLink("hbd")}> + + + + + + + + {allTokens?.map((a: any) =>{ + const changeValue = parseFloat(a?.priceChangePercent); + return( + this.handleLink(a.symbol)}> + + {!global?.isMobile && } + + {!global?.isMobile && } + + + + ) + })} + + +
Token NamePrice% ChangeTrendBalanceValue
+ + HIVE + ${this.pricePerHive} + + {coingeckoData[0]?.price_change_percentage_24h < 0 ? priceDownSvg : priceUpSvg} + + {coingeckoData[0]?.price_change_percentage_24h} + + + {w.balance}${Number(w.balance * this.pricePerHive).toFixed(3)}
+ + HIVE-POWER + ${this.pricePerHive} + + {coingeckoData[0]?.price_change_percentage_24h < 0 ? priceDownSvg : priceUpSvg} + + {coingeckoData[0]?.price_change_percentage_24h} + + + {totalHP}${Number(totalHP * this.pricePerHive).toFixed(3)}
+ + HBD + ${coingeckoData[1]?.current_price} + + {coingeckoData[1]?.price_change_percentage_24h < 0 ? priceDownSvg : priceUpSvg} + + {coingeckoData[1]?.price_change_percentage_24h} + + HBD-CHART + {w.hbdBalance}${Number(w.hbdBalance * coingeckoData[1]?.current_price).toFixed(3)}
+ + {a.symbol} + + ${a.lastPrice} + + + {a?.symbol === a.symbol && ( + + {changeValue < 0 ? priceDownSvg : priceUpSvg} + + )} + {a?.symbol === a.symbol ? a?.priceChangePercent : null} + + + +
+ +
+
+
+ {a.balance} + + ${a.usd_value} +
+
+
+ ); + } +} + +export default (p: Props) => { + const props = { + global: p.global, + dynamicProps: p.dynamicProps, + account: p.account, + // // activeUser: p.activeUser, + // transactions: p.transactions, + // // signingKey: p.signingKey, + // // addAccount: p.addAccount, + updateActiveUser: p.updateActiveUser, + // // setSigningKey: p.setSigningKey, + updateWalletValues: p.updateWalletValues, + // // fetchPoints: p.fetchPoints, + history: p.history + }; + + return ; +}; diff --git a/src/common/pages/profile-functional.tsx b/src/common/pages/profile-functional.tsx index 1d5d38113b2..cb4c5d5eeae 100644 --- a/src/common/pages/profile-functional.tsx +++ b/src/common/pages/profile-functional.tsx @@ -25,6 +25,8 @@ import ProfileCover from "../components/profile-cover"; import ProfileCommunities from "../components/profile-communities"; import ProfileSettings from "../components/profile-settings"; import ProfileReferrals from "../components/profile-referrals"; +import WalletPortfolio from "../components/wallet-portfolio"; +import TokenDetails from "../components/token-details"; import WalletHive from "../components/wallet-hive"; import WalletHiveEngine from "../components/wallet-hive-engine"; import WalletEcency from "../components/wallet-ecency"; @@ -53,6 +55,7 @@ interface MatchParams { username: string; section?: string; search?: string; + symbol?: string; } interface Props extends PageProps { @@ -82,6 +85,7 @@ export const Profile = (props: Props) => { const [account, setAccount] = useState({ __loaded: false } as Account); const [username, setUsername] = useState(""); const [section, setSection] = useState(""); + const [symbol, setSymbol] = useState(""); const [data, setData] = useState({ entries: [], sid: "", @@ -99,7 +103,7 @@ export const Profile = (props: Props) => { await ensureAccount(); - const { username, section } = match.params; + const { username, section, symbol } = match.params; if (!section || (section && Object.keys(ProfileFilter).includes(section))) { // fetch posts fetchEntries(global.filter, global.tag, false); @@ -113,6 +117,7 @@ export const Profile = (props: Props) => { setAccount(account); setSection(section || ProfileFilter.blog); + setSymbol(symbol || "") setUsername(username.replace("@", "")); await initPinnedEntry(username.replace("@", ""), account); @@ -124,9 +129,10 @@ export const Profile = (props: Props) => { }; }, []); useEffect(() => { + setSymbol(props.match.params.symbol); setData(props.entries[makeGroupKey(props.global.filter, props.global.tag)]); setLoading(false); - }, [props.global.filter, props.global.tag, props.entries]); + }, [props.global.filter, props.global.tag, props.entries, props.match]); useAsyncEffect( async (_) => { if (prevSearch !== search) { @@ -510,6 +516,12 @@ export const Profile = (props: Props) => { if (section === "referrals") { return ProfileReferrals({ ...props, account, updateWalletValues: ensureAccount }); } + if (section === "portfolio") { + if (symbol && section === "portfolio") { + return TokenDetails({ ...props, account, updateWalletValues: ensureAccount }); + } + return WalletPortfolio({ ...props, account, updateWalletValues: ensureAccount }); + } if (section === "permissions" && props.activeUser) { if (account.name === props.activeUser.username) { diff --git a/src/common/routes.ts b/src/common/routes.ts index 09da21a6d4f..1cfd7f8b575 100644 --- a/src/common/routes.ts +++ b/src/common/routes.ts @@ -23,7 +23,7 @@ export default { USER_FEED: `/:username(@[\\w\\.\\d-]+)/:section(feed)`, USER_SECTION: `/:username(@[\\w\\.\\d-]+)/:section(${profileFilters.join( "|" - )}|wallet|points|engine|communities|settings|permissions|referrals|followers|following|spk)`, + )}|wallet|points|engine|communities|settings|permissions|referrals|followers|following|portfolio|spk)`, COMMUNITIES: `/communities`, COMMUNITIES_CREATE: `/communities/create`, COMMUNITIES_CREATE_HS: `/communities/create-hs`, @@ -37,5 +37,6 @@ export default { WITNESSES: `/witnesses`, PROPOSALS: `/proposals`, PROPOSAL_DETAIL: `/proposals/:id(\\d+)`, - PURCHASE: "/purchase" + PURCHASE: "/purchase", + TOKEN_DETAIL: `/:username(@[\\w\\.\\d-]+)/:section(portfolio)/:symbol` }; From c328ac8dc06bb7c20597af809aa44cfa923811af Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Thu, 5 Jan 2023 20:50:28 +0100 Subject: [PATCH 02/21] fixed trans --- .../components/wallet-portfolio/index.tsx | 18 ++++++------------ src/common/i18n/locales/en-US.json | 8 ++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 22510be24d4..6aa26dafc8a 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -127,15 +127,15 @@ export class WalletPortfolio extends BaseComponent { - + {!global?.isMobile && - } - + } + {!global?.isMobile && - + } - - + + @@ -246,14 +246,8 @@ export default (p: Props) => { global: p.global, dynamicProps: p.dynamicProps, account: p.account, - // // activeUser: p.activeUser, - // transactions: p.transactions, - // // signingKey: p.signingKey, - // // addAccount: p.addAccount, updateActiveUser: p.updateActiveUser, - // // setSigningKey: p.setSigningKey, updateWalletValues: p.updateWalletValues, - // // fetchPoints: p.fetchPoints, history: p.history }; diff --git a/src/common/i18n/locales/en-US.json b/src/common/i18n/locales/en-US.json index 9301d8d5857..35e9d15bcdf 100644 --- a/src/common/i18n/locales/en-US.json +++ b/src/common/i18n/locales/en-US.json @@ -148,6 +148,14 @@ "vote-power": "Vote Power:", "boost": "Boost" }, + "wallet-portfolio": { + "name": "Token Name", + "price": "Price", + "change": "% Change", + "trend": "Trend", + "balance": "Balance", + "value": "Value" + }, "intro": { "title": "Aspire to greatness", "sub-title": "rewarding communities", From 703288b3f8709cdaede98270ae176a7de82106d5 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Fri, 6 Jan 2023 23:19:29 +0100 Subject: [PATCH 03/21] engine token details --- src/common/components/token-details/index.tsx | 12 +- .../wallet-engine-details/index.tsx | 452 ++++++++++++++++++ .../components/wallet-portfolio/index.scss | 3 + .../components/wallet-portfolio/index.tsx | 16 +- src/style/_components.scss | 1 + 5 files changed, 472 insertions(+), 12 deletions(-) create mode 100644 src/common/components/wallet-engine-details/index.tsx create mode 100644 src/common/components/wallet-portfolio/index.scss diff --git a/src/common/components/token-details/index.tsx b/src/common/components/token-details/index.tsx index 298927bc172..1d037f5d9e0 100644 --- a/src/common/components/token-details/index.tsx +++ b/src/common/components/token-details/index.tsx @@ -24,7 +24,6 @@ import OpenOrdersList from "../open-orders-list"; import DropDown from "../dropdown"; import Transfer, { TransferMode, TransferAsset } from "../transfer"; import { error, success } from "../feedback"; -import WalletMenu from "../wallet-menu"; import WithdrawRoutes from "../withdraw-routes"; import HiveWallet from "../../helper/hive-wallet"; @@ -49,7 +48,7 @@ import { _t } from "../../i18n"; import { plusCircle } from "../../img/svg"; import { dayDiff, dateToFullRelative, hourDiff, secondDiff } from "../../helper/parse-date"; -import { useParams } from "react-router"; +import { EngineTokenDetails } from "../wallet-engine-details"; interface Props { history: History; @@ -881,9 +880,14 @@ export class TokenDetails extends BaseComponent { )} - {TransactionList({ ...this.props })} + {(params === "hive" || params === "hive-power" || params === "hbd") &&
+ {TransactionList({ ...this.props })} +
} + + {!(params === "hive" || params === "hive-power" || params === "hbd") &&
+ +
} - {/* */} {transfer && ( diff --git a/src/common/components/wallet-engine-details/index.tsx b/src/common/components/wallet-engine-details/index.tsx new file mode 100644 index 00000000000..c638b40e958 --- /dev/null +++ b/src/common/components/wallet-engine-details/index.tsx @@ -0,0 +1,452 @@ +import React from "react"; +import { proxifyImageSrc } from "@ecency/render-helper"; + +import { Global } from "../../store/global/types"; +import { Account } from "../../store/accounts/types"; +import { DynamicProps } from "../../store/dynamic-props/types"; +import { OperationGroup, Transactions } from "../../store/transactions/types"; +import { ActiveUser } from "../../store/active-user/types"; + +import BaseComponent from "../base"; +import HiveEngineToken from "../../helper/hive-engine-wallet"; +import LinearProgress from "../linear-progress"; +import { Button, OverlayTrigger, Tooltip } from "react-bootstrap"; +import WalletMenu from "../wallet-menu"; +import { SortEngineTokens } from "../sort-hive-engine-tokens"; +import { EngineTokensEstimated } from "../engine-tokens-estimated"; +import Transfer, { TransferMode } from "../transfer-he"; +import { error, success } from "../feedback"; +import DropDown from "../dropdown"; + +import { + claimRewards, + getHiveEngineTokenBalances, + getUnclaimedRewards, + TokenStatus, + getMetrics +} from "../../api/hive-engine"; + +import { + informationVariantSvg, + plusCircle, + transferOutlineSvg, + lockOutlineSvg, + unlockOutlineSvg, + delegateOutlineSvg, + undelegateOutlineSvg, + priceUpSvg, + priceDownSvg +} from "../../img/svg"; + +import { formatError } from "../../api/operations"; +import formattedNumber from "../../util/formatted-number"; +import { _t } from "../../i18n"; + +interface Props { + global: Global; + dynamicProps: DynamicProps; + account: Account; + activeUser: ActiveUser | null; + transactions: Transactions; + signingKey: string; + addAccount: (data: Account) => void; + updateActiveUser: (data?: Account) => void; + setSigningKey: (key: string) => void; + fetchPoints: (username: string, type?: number) => void; + updateWalletValues: () => void; +} + +interface State { + tokens: HiveEngineToken[]; + utokens: HiveEngineToken[]; + rewards: TokenStatus[]; + loading: boolean; + claiming: boolean; + claimed: boolean; + transfer: boolean; + transferMode: null | TransferMode; + transferAsset: null | string; + assetBalance: number; + allTokens: any; +} + +export class EngineTokenDetails extends BaseComponent { + state: State = { + tokens: [], + utokens: [], + rewards: [], + loading: true, + claiming: false, + claimed: false, + transfer: false, + transferMode: null, + transferAsset: null, + assetBalance: 0, + allTokens: null + }; + _isMounted = false; + + componentDidMount() { + this._isMounted = true; + this._isMounted && this.fetch(); + this._isMounted && this.fetchUnclaimedRewards(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + openTransferDialog = (mode: TransferMode, asset: string, balance: number) => { + this.stateSet({ + transfer: true, + transferMode: mode, + transferAsset: asset, + assetBalance: balance + }); + }; + + closeTransferDialog = () => { + this.stateSet({ transfer: false, transferMode: null, transferAsset: null }); + }; + + fetch = async () => { + const { account } = this.props; + + this.setState({ loading: true }); + let items; + try { + items = await getHiveEngineTokenBalances(account.name); + this.setState({ utokens: items }); + items = items.filter((token) => token.balance !== 0 || token.stakedBalance !== 0); + items = this.sort(items); + this._isMounted && this.setState({ tokens: items }); + } catch (e) { + console.log("engine tokens", e); + } + + this.setState({ loading: false }); + }; + + sort = (items: HiveEngineToken[]) => + items.sort((a: HiveEngineToken, b: HiveEngineToken) => { + if (a.balance !== b.balance) { + return a.balance < b.balance ? 1 : -1; + } + + if (a.stake !== b.stake) { + return a.stake < b.stake ? 1 : -1; + } + + return a.symbol > b.symbol ? 1 : -1; + }); + + fetchUnclaimedRewards = async () => { + const { account } = this.props; + try { + const rewards = await getUnclaimedRewards(account.name); + this._isMounted && this.setState({ rewards }); + } catch (e) { + console.log("fetchUnclaimedRewards", e); + } + }; + + claimRewards = (tokens: TokenStatus[]) => { + const { activeUser } = this.props; + const { claiming } = this.state; + + if (claiming || !activeUser) { + return; + } + + this.setState({ claiming: true }); + + return claimRewards( + activeUser.username, + tokens.map((t) => t.symbol) + ) + .then((account) => { + success(_t("wallet.claim-reward-balance-ok")); + }) + .then(() => { + this.setState({ rewards: [] }); + }) + .catch((err) => { + console.log(err); + error(...formatError(err)); + }) + .finally(() => { + this.setState({ claiming: false }); + }); + }; + + render() { + const { global, account, activeUser } = this.props; + const { rewards, tokens, loading, claiming, claimed } = this.state; + console.log(tokens) + const hasUnclaimedRewards = rewards.length > 0; + const isMyPage = activeUser && activeUser.username === account.name; + let rewardsToShowInTooltip = [...rewards]; + rewardsToShowInTooltip = rewardsToShowInTooltip.splice(0, 10); + + const params: string = window.location.href.split('/')[5]; + + if (!account.__loaded) { + return null; + } + + return ( +
+
+
+ {hasUnclaimedRewards && ( +
+
{_t("wallet.unclaimed-rewards")}
+ {rewards?.map((r, i) => { + const reward: any = r?.pending_token / Math.pow(10, r?.precision); + return ( r?.symbol === params.toUpperCase() && +
+ + {reward < 0.0001 + ? `${reward} ${r?.symbol}` + : formattedNumber(reward, { + fractionDigits: r?.precision, + suffix: r?.symbol + })} + + {isMyPage && ( + this.claimRewards([r])} + > + {plusCircle} + + )} +
+ ); + })} +
+ )} + + {tokens.map((t, i) => { + return ( t?.symbol === params.toUpperCase() && + <> +
+
+
{t?.symbol}
+
+ {t?.symbol} are tradeable tokens that may be transferred at anytime. + {t?.symbol} can be converted to {t?.symbol} POWER in a process called powering unstake. +
+
+
+
+ {(() => { + let dropDownConfig: any; + if (isMyPage) { + dropDownConfig = { + // history: this.props.history, + label: "", + items: [ + { + label: "Transfer", + onClick: () => { + this.openTransferDialog("transfer", t?.symbol, t?.balance) + } + }, + { + label: "Stake", + onClick: () => { + this.openTransferDialog("stake", t?.symbol, t?.balance) + } + }, + { + label: "Trade", + onClick: () => { + // this.openTransferDialog("power-up", "HIVE"); + } + } + ] + }; + } + else if (activeUser) { + dropDownConfig = { + // history: this.props.history, + label: "", + items: [ + { + label: "Transfer", + onClick: () => { + this.openTransferDialog("transfer", t?.symbol, t?.balance) + } + } + ] + }; + } + return ( +
+ +
+ ); + })()} + {t?.balance} +
+
+
+ +
+
+
{t?.symbol} POWER
+
+ Amount of hive engine token ({t.symbol}) staked. It can also be delegated to other users +
+
+ +
+
+ {(() => { + let dropDownConfig: any; + if (isMyPage) { + dropDownConfig = { + // history: this.props.history, + label: "", + items: [ + { + label: "Stake", + onClick: () => { + this.openTransferDialog("stake", t?.symbol, t?.balance) + } + }, + { + label: "Unstake", + onClick: () => { + this.openTransferDialog( + "unstake", + t?.symbol, + t?.stakedBalance + ) + } + }, + { + label: "Delegate", + onClick: () => { + this.openTransferDialog( + "delegate", + t?.symbol, + t?.balance - t?.delegationsOut + ) + } + }, + { + label: "Undelegate", + onClick: () => { + this.openTransferDialog( + "undelegate", + t.symbol, + t.delegationsOut + ) + } + } + ] + }; + } else if (activeUser) { + dropDownConfig = { + // history: this.props.history, + label: "", + items: [ + { + label: "Stake", + onClick: () => { + this.openTransferDialog("stake", t?.symbol, t?.balance) + } + }, + { + label: "Delegate", + onClick: () => { + this.openTransferDialog( + "delegate", + t?.symbol, + t?.balance - t?.delegationsOut + ) + } + } + ] + }; + } + return ( +
+ +
+ ); + })()} + {t?.stakedBalance} +
+ + + {t?.delegationsOut > 0 &&
+ + - {t.delegationsOut} + +
} + + {t?.hasDelegations() && +
+ + + {`${t?.delegationsIn} ${t?.symbol}`} + +
} + +
+ {t?.delegationsIn > 0 ? + {t?.stakedBalance + t?.delegationsIn} + : t.delegationsOut > 0 ? + + {t?.stakedBalance - t?.delegationsOut} + : null} +
+
+
+ + ) + })} +
+ +
+ {/* Fetch tokens history */} +
+ +
+ + {this.state.transfer && ( + + )} +
+ ); + } +} + +export default (p: Props) => { + const props = { + global: p.global, + dynamicProps: p.dynamicProps, + account: p.account, + activeUser: p.activeUser, + transactions: p.transactions, + signingKey: p.signingKey, + addAccount: p.addAccount, + updateActiveUser: p.updateActiveUser, + setSigningKey: p.setSigningKey, + updateWalletValues: p.updateWalletValues, + fetchPoints: p.fetchPoints + }; + + return ; + }; + \ No newline at end of file diff --git a/src/common/components/wallet-portfolio/index.scss b/src/common/components/wallet-portfolio/index.scss new file mode 100644 index 00000000000..155f8f47339 --- /dev/null +++ b/src/common/components/wallet-portfolio/index.scss @@ -0,0 +1,3 @@ +.table-row { + height: 100px; +} diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 6aa26dafc8a..435fd288249 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -140,14 +140,14 @@ export class WalletPortfolio extends BaseComponent {
<> - this.handleLink("hive")}> + this.handleLink("hive")}> - this.handleLink("hive-power")}> + this.handleLink("hive-power")}> - this.handleLink("hbd")}> + this.handleLink("hbd")}> this.handleLink(a.symbol)}> + this.handleLink(a.symbol)}> <> + this.handleLink("points")}> + + + + + + + + this.handleLink("hive")}> + this.handleLink("hive-power")}> + this.handleLink("hbd")}> + {allTokens?.map((a: any) =>{ const changeValue = parseFloat(a?.priceChangePercent); return( this.handleLink(a.symbol)}> {!global?.isMobile && @@ -234,7 +234,7 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[1]?.price_change_percentage_24h} diff --git a/src/common/constants/engine.json b/src/common/constants/engine.json index 54c8769fae9..773c9f74525 100644 --- a/src/common/constants/engine.json +++ b/src/common/constants/engine.json @@ -1,5 +1,7 @@ { "chartApi": "https://info-api.tribaldex.com/market/ohlcv", "engineRpcUrl": "https://api.hive-engine.com/rpc/contracts", - "engineRewardsUrl": "https://scot-api.hive-engine.com" + "engineRewardsUrl": "https://scot-api.hive-engine.com", + "mainTransactionUrl": "https://scot-api.hive-engine.com/get_account_history", + "otherTransactionsUrl": "https://accounts.hive-engine.com/accountHistory" } From c885bc3ebae96cfd5943ba6c08ec2c841821dfae Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Fri, 20 Jan 2023 14:53:39 +0100 Subject: [PATCH 06/21] profile tokens --- src/common/api/operations.ts | 23 +++ .../components/engine-tokens-list/index.scss | 27 ++++ .../components/engine-tokens-list/index.tsx | 34 +++++ .../wallet-portfolio-chart/index.tsx | 2 - .../components/wallet-portfolio/index.tsx | 132 ++++++++++++++---- src/style/_components.scss | 1 + 6 files changed, 191 insertions(+), 28 deletions(-) create mode 100644 src/common/components/engine-tokens-list/index.scss create mode 100644 src/common/components/engine-tokens-list/index.tsx diff --git a/src/common/api/operations.ts b/src/common/api/operations.ts index 69c3266a4f8..928ef24276d 100644 --- a/src/common/api/operations.ts +++ b/src/common/api/operations.ts @@ -1262,6 +1262,7 @@ export const updateProfile = ( cover_image: string; profile_image: string; pinned: string; + profileTokens?: string[] } ): Promise => { const params = { @@ -1764,3 +1765,25 @@ export const unstakeHiveEngineKey = async ( const result = await hiveClient.broadcast.json(op, key); return result; }; + +// export const updateFavoriteTokens = ( +// account: string, +// jsonM: string, +// postingJsonM: any +// ): Promise => { + +// const json = JSON.stringify({ +// account, +// json_metadata: jsonM, +// posting_json_metadata: postingJsonM +// }); + +// const op: Operation[] = [ +// [ +// "account_update2", +// json +// ] +// ] + +// return broadcastPostingOperations(account, op) +// } diff --git a/src/common/components/engine-tokens-list/index.scss b/src/common/components/engine-tokens-list/index.scss new file mode 100644 index 00000000000..bab52893dd6 --- /dev/null +++ b/src/common/components/engine-tokens-list/index.scss @@ -0,0 +1,27 @@ +.container{ + display: flex;align-items: center; + justify-content: space-between; + + .token-list{ + padding: 10px; + gap: 5px; + // flex-wrap: wrap; + + img{ + width: 50px; + height: 50px; + border-radius: 50%; + } + span{ + margin: 0 15px; + } + } + + .add-btn{ + + button{ + padding: 5px; + border-radius: 5px; + } + } +} \ No newline at end of file diff --git a/src/common/components/engine-tokens-list/index.tsx b/src/common/components/engine-tokens-list/index.tsx new file mode 100644 index 00000000000..d07ede1f1a7 --- /dev/null +++ b/src/common/components/engine-tokens-list/index.tsx @@ -0,0 +1,34 @@ +import React, { useEffect, useState} from 'react' +import { _t } from '../../i18n'; + +const EngineTokensList = (props: any) => { + const { token, addToFavorite, hideModal } = props; + + + useEffect(() => { + }, []); + + const addToken = () => { + addToFavorite(token); + hideModal(); + } + return ( + <> +
+
+ + + {token.name} + +
+
+ +
+
+ + ) +} + +export default EngineTokensList; \ No newline at end of file diff --git a/src/common/components/wallet-portfolio-chart/index.tsx b/src/common/components/wallet-portfolio-chart/index.tsx index 2b1434ef58b..8ecb698ac9e 100644 --- a/src/common/components/wallet-portfolio-chart/index.tsx +++ b/src/common/components/wallet-portfolio-chart/index.tsx @@ -16,7 +16,6 @@ export const HiveWalletPortfolioChart = (props: any) => { const marketChartInfo = async () => { const data: any = await marketChart("hive") - console.log(data) setPrices(data.prices) } @@ -143,7 +142,6 @@ export const HbdWalletPortfolioChart = (props: any) => { const marketChartInfo = async () => { const data: any = await marketChart("hive_dollar") - console.log(data) setPrices(data.prices) } diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index e4227d1ff62..482be42c159 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -18,7 +18,8 @@ import { import { priceUpSvg, - priceDownSvg + priceDownSvg, + plusCircle } from "../../img/svg"; import formattedNumber from "../../util/formatted-number"; @@ -29,6 +30,9 @@ import { vestsToHp } from "../../helper/vesting"; import { marketInfo, } from "../../api/misc"; import { HiveWalletPortfolioChart, HbdWalletPortfolioChart } from "../wallet-portfolio-chart"; import { getCurrencyTokenRate } from "../../api/private-api"; +import EngineTokensList from "../engine-tokens-list"; +import { Button, FormControl, Modal } from "react-bootstrap"; +import { updateProfile } from "../../api/operations"; const hbdIcom = require("./asset/hbd.png") const ecencyIcon = require("./asset/ecency.jpeg") @@ -36,7 +40,7 @@ const ecencyIcon = require("./asset/ecency.jpeg") interface Props { global: Global; dynamicProps: DynamicProps; - account: Account; + account: Account | any; history: History; updateActiveUser: (data?: Account) => void; updateWalletValues: () => void; @@ -52,6 +56,9 @@ interface State { coingeckoData: any; estimatedPointsValue: number; estimatedPointsValueLoading: boolean; + search: string; + showTokenList: boolean; + favoriteTokens: HiveEngineToken[] | any; } export class WalletPortfolio extends BaseComponent { @@ -63,17 +70,20 @@ export class WalletPortfolio extends BaseComponent { converting: 0, coingeckoData: [], estimatedPointsValue: 0, - estimatedPointsValueLoading: false + estimatedPointsValueLoading: false, + search: "", + showTokenList: false, + favoriteTokens: [] }; _isMounted = false; pricePerHive = this.props.dynamicProps.base / this.props.dynamicProps.quote; - - componentDidMount() { - - this._isMounted = true; + + componentDidMount() { + this._isMounted = true; this._isMounted && this.engineTokensData(); this._isMounted && this.dataFromCoinGecko(); - this._isMounted && this.getEstimatedPointsValue(); + this._isMounted && this.getEstimatedPointsValue(); + this._isMounted && this.loadFavoritesTokens(); } componentWillUnmount() { @@ -82,7 +92,6 @@ export class WalletPortfolio extends BaseComponent { dataFromCoinGecko = async () => { const data = await marketInfo(); - // console.log(data) this.setState({coingeckoData: data}) } @@ -114,9 +123,9 @@ export class WalletPortfolio extends BaseComponent { this.setState({ allTokens: tokensUsdValues }); return tokensUsdValues; }; - + getEstimatedPointsValue = () => { - const { + const { global: { currency } } = this.props; this.setState({estimatedPointsValueLoading: true}); @@ -134,12 +143,41 @@ export class WalletPortfolio extends BaseComponent { handleLink (symbol:string) { this.props.history.push(`portfolio/${symbol.toLowerCase()}`) + }; + + hideList = () => { + this.setState({showTokenList: false}) + }; + + showList = () => { + this.setState({showTokenList: true}) + }; + + loadFavoritesTokens = async () => { + const { account } = this.props; + this.setState({favoriteTokens: account?.profile?.profileTokens, loading: false}) } + + addToFavorite = async (token: HiveEngineToken | any) => { + const { account } = this.props; + const userProfile = JSON.parse(account.posting_json_metadata) + const profileTokens: any = account?.profile.profileTokens || [] + const { profile } = userProfile; + let mappedTokens: any = profileTokens.map((t: any) => t.symbol === token.symbol) + console.log(mappedTokens) + if (!mappedTokens.includes(true)){ + console.log("Not on list") + profileTokens.push(token) + }else { + console.log("Token already added") + } + const newPostMeta: any = {...profile, profileTokens} + updateProfile(account, newPostMeta) + }; render() { const { global, dynamicProps, account, points } = this.props; - const { allTokens, converting, coingeckoData, estimatedPointsValue } = this.state; - // console.log(estimatedPointsValue) + const { allTokens, converting, coingeckoData, estimatedPointsValue, search, showTokenList, favoriteTokens, loading } = this.state; const { hivePerMVests } = dynamicProps; const w = new HiveWallet(account, dynamicProps, converting); @@ -151,7 +189,7 @@ export class WalletPortfolio extends BaseComponent { Total wallet Value: $1.19
-
Token Name{_t("wallet-portfolio.name")}Price% Change{_t("wallet-portfolio.price")}{_t("wallet-portfolio.change")}Trend{_t("wallet-portfolio.trend")}BalanceValue{_t("wallet-portfolio.balance")}{_t("wallet-portfolio.value")}
HIVE ${this.pricePerHive} - + {coingeckoData[0]?.price_change_percentage_24h < 0 ? priceDownSvg : priceUpSvg} {coingeckoData[0]?.price_change_percentage_24h} @@ -158,14 +158,14 @@ export class WalletPortfolio extends BaseComponent { {w.balance} ${Number(w.balance * this.pricePerHive).toFixed(3)}
HIVE-POWER ${this.pricePerHive} - + {coingeckoData[0]?.price_change_percentage_24h < 0 ? priceDownSvg : priceUpSvg} {coingeckoData[0]?.price_change_percentage_24h} @@ -176,14 +176,14 @@ export class WalletPortfolio extends BaseComponent { {totalHP} ${Number(totalHP * this.pricePerHive).toFixed(3)}
HBD ${coingeckoData[1]?.current_price} - + {coingeckoData[1]?.price_change_percentage_24h < 0 ? priceDownSvg : priceUpSvg} {coingeckoData[1]?.price_change_percentage_24h} @@ -197,7 +197,7 @@ export class WalletPortfolio extends BaseComponent { {allTokens?.map((a: any) =>{ const changeValue = parseFloat(a?.priceChangePercent); return( -
{a.symbol} @@ -209,7 +209,7 @@ export class WalletPortfolio extends BaseComponent { {a?.symbol === a.symbol && ( - + {changeValue < 0 ? priceDownSvg : priceUpSvg} )} diff --git a/src/style/_components.scss b/src/style/_components.scss index 62d2989a1af..e943c064c14 100644 --- a/src/style/_components.scss +++ b/src/style/_components.scss @@ -39,6 +39,7 @@ @import "../common/components/profile-cover"; @import "../common/components/profile-edit"; @import "../common/components/wallet-hive"; +@import "../common/components/wallet-portfolio"; @import "../common/components/wallet-hive-engine"; @import "../common/components/hive-engine-chart"; @import "../common/components/delegated-vesting"; From b282444fa2b944fa00cb4cec5d82b04bdb4a86ad Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Sun, 8 Jan 2023 06:09:46 +0100 Subject: [PATCH 04/21] fixed points --- src/common/components/token-details/index.tsx | 12 ++- src/common/components/wallet-ecency/index.tsx | 2 + .../wallet-portfolio/asset/ecency.jpeg | Bin 0 -> 6744 bytes .../wallet-portfolio/asset/engine.png | Bin 0 -> 1455 bytes .../components/wallet-portfolio/index.scss | 1 + .../components/wallet-portfolio/index.tsx | 69 +++++++++++++++--- 6 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 src/common/components/wallet-portfolio/asset/ecency.jpeg create mode 100644 src/common/components/wallet-portfolio/asset/engine.png diff --git a/src/common/components/token-details/index.tsx b/src/common/components/token-details/index.tsx index 1d037f5d9e0..48d03c2cdfa 100644 --- a/src/common/components/token-details/index.tsx +++ b/src/common/components/token-details/index.tsx @@ -49,6 +49,8 @@ import { _t } from "../../i18n"; import { plusCircle } from "../../img/svg"; import { dayDiff, dateToFullRelative, hourDiff, secondDiff } from "../../helper/parse-date"; import { EngineTokenDetails } from "../wallet-engine-details"; +import { WalletEcency } from "../wallet-ecency"; +import { Points } from "../../store/points/types"; interface Props { history: History; @@ -64,6 +66,7 @@ interface Props { fetchTransactions: (username: string, group?: OperationGroup | "") => void; fetchPoints: (username: string, type?: number) => void; updateWalletValues: () => void; + points: Points; } interface State { @@ -884,9 +887,13 @@ export class TokenDetails extends BaseComponent { {TransactionList({ ...this.props })} } - {!(params === "hive" || params === "hive-power" || params === "hbd") &&
+ {!(params === "hive" || params === "hive-power" || params === "hbd" || params === "points") &&
} + + {params === "points" &&
+ +
}
@@ -970,7 +977,8 @@ export default (p: Props) => { setSigningKey: p.setSigningKey, fetchTransactions: p.fetchTransactions, updateWalletValues: p.updateWalletValues, - fetchPoints: p.fetchPoints + fetchPoints: p.fetchPoints, + points: p.points, }; return ; diff --git a/src/common/components/wallet-ecency/index.tsx b/src/common/components/wallet-ecency/index.tsx index ee7bf8dc8d1..3c01d218182 100644 --- a/src/common/components/wallet-ecency/index.tsx +++ b/src/common/components/wallet-ecency/index.tsx @@ -199,6 +199,7 @@ export const WalletEcency = (props: Props) => { const [showPurchaseDialog, setShowPurchaseDialog] = useState(false); const { global, activeUser, account, points, history, fetchPoints, updateActiveUser } = props; + console.log(points) useEffect(() => { setIsMounted(true); @@ -234,6 +235,7 @@ export const WalletEcency = (props: Props) => { const initiateOnElectron = (username: string) => { if (!isMounted && global.isElectron) { let getPoints = new Promise((res) => fetchPoints(username)); + console.log(getPoints) username && getPoints .then((res) => { diff --git a/src/common/components/wallet-portfolio/asset/ecency.jpeg b/src/common/components/wallet-portfolio/asset/ecency.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..fd93e38308ed5da2fc07187222d8b174768f0aec GIT binary patch literal 6744 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 void; updateWalletValues: () => void; + points: Points; } interface State { @@ -45,6 +50,8 @@ interface State { allTokens: any; converting: number; coingeckoData: any; + estimatedPointsValue: number; + estimatedPointsValueLoading: boolean; } export class WalletPortfolio extends BaseComponent { @@ -54,20 +61,24 @@ export class WalletPortfolio extends BaseComponent { assetBalance: 0, allTokens: null, converting: 0, - coingeckoData: [] + coingeckoData: [], + estimatedPointsValue: 0, + estimatedPointsValueLoading: false }; _isMounted = false; pricePerHive = this.props.dynamicProps.base / this.props.dynamicProps.quote; componentDidMount() { + this._isMounted = true; this._isMounted && this.engineTokensData(); this._isMounted && this.dataFromCoinGecko(); + this._isMounted && this.getEstimatedPointsValue(); } componentWillUnmount() { this._isMounted = false; - } + }; dataFromCoinGecko = async () => { const data = await marketInfo(); @@ -104,22 +115,38 @@ export class WalletPortfolio extends BaseComponent { return tokensUsdValues; }; + getEstimatedPointsValue = () => { + const { + global: { currency } + } = this.props; + this.setState({estimatedPointsValueLoading: true}); + getCurrencyTokenRate(currency, "estm") + .then((res) => { + this.setState({estimatedPointsValue: res}); + this.setState({estimatedPointsValueLoading: false}); + }) + .catch((error) => { + console.log(error) + this.setState({estimatedPointsValueLoading: false}); + this.setState({estimatedPointsValue: 0}); + }); + }; + handleLink (symbol:string) { this.props.history.push(`portfolio/${symbol.toLowerCase()}`) } render() { - const { global, dynamicProps, account } = this.props; - const { allTokens, converting, coingeckoData } = this.state; - // console.log(coingeckoData[0]) + const { global, dynamicProps, account, points } = this.props; + const { allTokens, converting, coingeckoData, estimatedPointsValue } = this.state; + // console.log(estimatedPointsValue) const { hivePerMVests } = dynamicProps; const w = new HiveWallet(account, dynamicProps, converting); - // console.log(w) const totalHP: any = formattedNumber(vestsToHp(w.vestingShares, hivePerMVests)); return ( -
+
Total wallet Value: $1.19
@@ -140,6 +167,22 @@ export class WalletPortfolio extends BaseComponent {
+ + ECENCY POINT + ${estimatedPointsValue} + ------------------- + + ------------------- + {points.points}${Number(estimatedPointsValue * Number(points.points)).toFixed(3)}
@@ -158,6 +201,7 @@ export class WalletPortfolio extends BaseComponent { {w.balance} ${Number(w.balance * this.pricePerHive).toFixed(3)}
@@ -171,11 +215,12 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h} - + ------------------- {totalHP} ${Number(totalHP * this.pricePerHive).toFixed(3)}
@@ -194,12 +239,15 @@ export class WalletPortfolio extends BaseComponent { {w.hbdBalance} ${Number(w.hbdBalance * coingeckoData[1]?.current_price).toFixed(3)}
- + + + {a.symbol} @@ -248,7 +296,8 @@ export default (p: Props) => { account: p.account, updateActiveUser: p.updateActiveUser, updateWalletValues: p.updateWalletValues, - history: p.history + history: p.history, + points: p.points, }; return ; From 80ac66e21170e7c8af93faa2d3fce8e6e2654441 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Mon, 9 Jan 2023 22:54:43 +0100 Subject: [PATCH 05/21] engine transactions history --- src/common/api/hive-engine.ts | 32 +++++ .../hive-engine-transactions/index.tsx | 108 +++++++++++++++ .../wallet-engine-details/index.tsx | 27 ++-- .../wallet-portfolio-chart/index.tsx | 129 +++++++++++++++++- .../components/wallet-portfolio/index.tsx | 6 +- src/common/constants/engine.json | 4 +- 6 files changed, 290 insertions(+), 16 deletions(-) create mode 100644 src/common/components/hive-engine-transactions/index.tsx diff --git a/src/common/api/hive-engine.ts b/src/common/api/hive-engine.ts index 3d367a337b0..87621a10e10 100644 --- a/src/common/api/hive-engine.ts +++ b/src/common/api/hive-engine.ts @@ -188,3 +188,35 @@ export const getMarketData = async (symbol: any) => { }); return history; }; + +export async function getTransactions(symbol: string, account: string, limit: number, offset?: number): Promise { + const url: any = engine.mainTransactionUrl; + return axios({ + url: url, + method: "GET", + params: { + account, + token: symbol, + limit, + offset + } + }).then((response) => { + return response.data; + }); +}; + +export async function getOtherTransactions(account: string, limit: number, symbol: string, offset: number = 0) { + const url: any = engine.otherTransactionsUrl; + const response = await axios({ + url: url, + method: "GET", + params: { + account, + limit, + offset, + type: "user", + symbol + } + }); + return response.data; +} \ No newline at end of file diff --git a/src/common/components/hive-engine-transactions/index.tsx b/src/common/components/hive-engine-transactions/index.tsx new file mode 100644 index 00000000000..26b25e421ae --- /dev/null +++ b/src/common/components/hive-engine-transactions/index.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useState } from 'react' +import { Button, FormControl } from 'react-bootstrap' +import { _t } from '../../i18n' +import { cashCoinSvg } from "../../img/svg"; +import TwoUserAvatar from "../two-user-avatar"; + +import { + getTransactions, + getOtherTransactions + } from "../../api/hive-engine"; +import LinearProgress from '../linear-progress'; + +export const EngineTransactionList = (props: any) => { + + const { global, account, params } = props + + const [transactions, setTransactions] = useState([]) + const [otherTransactions, setOtherTransactions] = useState([]) + const [loading, setLoading] = useState(false); + const [loadLimit, setLoadLimit] = useState(10) + + useEffect(() => { + otherTokenTransactions(); + getMainTransactions() + }, []) + + const getMainTransactions = async () => { + const transactions = await getTransactions(params, account.name, 200); + console.log(transactions) + + }; + + const otherTokenTransactions = async () => { + setLoading(true) + const otherTransactions = await getOtherTransactions(account.name, 200, params.toUpperCase()); + console.log(otherTransactions) + setOtherTransactions(otherTransactions); + setLoading(false) + } + + const getTransactionTime = (timestamp: number) => { + let date: any = new Date(timestamp * 1000) + return date.toDateString(); + } + + const loadMore = () => { + const moreItems = loadLimit + 10; + setLoadLimit(moreItems); + }; + + const optionChanged = (e: React.ChangeEvent) => { + console.log(e.target) + } + + return ( +
+
+

{_t("transactions.title")}

+ + + {["transfers", "market-orders", "interests", "stake-operations", "rewards"].map((x) => ( + + ))} + +
+ {loading && } + {otherTransactions?.slice(0, loadLimit).map((t: any) => { + return ( + otherTransactions?.length === 0 ?

{_t("g.empty-list")}

: +
+
+ {t?.operation === "tokens_transfer" || t?.operation === "tokens_stake" || t?.operation === "tokens_delegate" ? + TwoUserAvatar({ global: global, from: t?.from, to: t?.to, size: "small" }) : + cashCoinSvg } +
+
+
{t?.operation.replace("_", " ")}
+
{getTransactionTime(t?.timestamp)}
+
+
{`${t?.quantity} ${t?.symbol}`}
+
+ {t?.memo} +

+ {t?.operation === "tokens_transfer" ? + + @{t.from} -> @{t.to} + : + + {`Txn Id: ${t.transactionId}`} +

+ {`Block Id: ${t.blockNumber}`} +

+ + } +

+
+
+ ) + })} + {!loading && otherTransactions.length > loadLimit && + } +
+ ) +} diff --git a/src/common/components/wallet-engine-details/index.tsx b/src/common/components/wallet-engine-details/index.tsx index c638b40e958..ebd5b9f1682 100644 --- a/src/common/components/wallet-engine-details/index.tsx +++ b/src/common/components/wallet-engine-details/index.tsx @@ -17,6 +17,7 @@ import { EngineTokensEstimated } from "../engine-tokens-estimated"; import Transfer, { TransferMode } from "../transfer-he"; import { error, success } from "../feedback"; import DropDown from "../dropdown"; +import { EngineTransactionList } from "../hive-engine-transactions"; import { claimRewards, @@ -182,7 +183,7 @@ export class EngineTokenDetails extends BaseComponent { render() { const { global, account, activeUser } = this.props; const { rewards, tokens, loading, claiming, claimed } = this.state; - console.log(tokens) + // console.log(tokens) const hasUnclaimedRewards = rewards.length > 0; const isMyPage = activeUser && activeUser.username === account.name; let rewardsToShowInTooltip = [...rewards]; @@ -200,11 +201,12 @@ export class EngineTokenDetails extends BaseComponent {
{hasUnclaimedRewards && (
-
{_t("wallet.unclaimed-rewards")}
{rewards?.map((r, i) => { - const reward: any = r?.pending_token / Math.pow(10, r?.precision); - return ( r?.symbol === params.toUpperCase() && -
+ const reward: any = r?.pending_token / Math.pow(10, r?.precision); + return ( r?.symbol === params.toUpperCase() && +
+
{_t("wallet.unclaimed-rewards")}
+
{reward < 0.0001 ? `${reward} ${r?.symbol}` @@ -221,7 +223,8 @@ export class EngineTokenDetails extends BaseComponent { {plusCircle} )} -
+
+
); })}
@@ -229,8 +232,8 @@ export class EngineTokenDetails extends BaseComponent { {tokens.map((t, i) => { return ( t?.symbol === params.toUpperCase() && - <> -
+
+
{t?.symbol}
@@ -405,15 +408,17 @@ export class EngineTokenDetails extends BaseComponent {
- +
) })}
- {/* Fetch tokens history */}
- +
{this.state.transfer && ( diff --git a/src/common/components/wallet-portfolio-chart/index.tsx b/src/common/components/wallet-portfolio-chart/index.tsx index e5e8691f67c..2b1434ef58b 100644 --- a/src/common/components/wallet-portfolio-chart/index.tsx +++ b/src/common/components/wallet-portfolio-chart/index.tsx @@ -5,7 +5,7 @@ import { _t } from "../../i18n"; import { marketChart } from '../../api/misc' import moment from "moment"; -export const WalletPortfolioChart = (props: any) => { +export const HiveWalletPortfolioChart = (props: any) => { const { theme } = props; const [prices, setPrices] = useState([]) @@ -131,3 +131,130 @@ export const WalletPortfolioChart = (props: any) => { ); }; + +export const HbdWalletPortfolioChart = (props: any) => { + const { theme } = props; + + const [prices, setPrices] = useState([]) + + useEffect(() => { + marketChartInfo(); + }, []); + + const marketChartInfo = async () => { + const data: any = await marketChart("hive_dollar") + console.log(data) + setPrices(data.prices) + } + + const config: any = { + title: { + text: null + }, + credits: { enabled: false }, + legend: { + enabled: false + }, + chart: { + height: "40", + width: "100", + zoomType: "x", + backgroundColor: "transparent", + border: "none", + style: { + fontFamily: "inherit", + border: "none" + }, + plotBorderColor: "transparent", + plotBorderWidth: 0, + plotBackgroundColor: "transparent", + plotShadow: false, + type: "area", + spacingBottom: 0, + spacingTop: 0, + spacingLeft: 0, + spacingRight: 0, + marginTop: 0, + marginBottom: 0 + }, + plotOptions: { + area: { + fillColor: theme === Theme.night ? "#2e3d51" : "#f3f7fb", + lineColor: "transparent", + lineWidth: 150 + }, + series: { + marker: { + enabled: false, + states: { + hover: { + enabled: false + } + } + } + } + }, + tooltip: { + valueDecimals: 2, + useHTML: true, + shadow: false, + formatter: (({ chart }: any) => { + let date = moment(chart.hoverPoint.options.x).calendar(); + let rate = chart.hoverPoint.options.y; + return `
${_t("g.when")}: ${date}
${_t( + "g.price" + )}:${rate.toFixed(3)}
`; + }) as any, + enabled: true + }, + xAxis: { + lineWidth: 0, + minorGridLineWidth: 0, + lineColor: "transparent", + labels: { + enabled: false, + style: { + color: "red" + } + }, + title: { + text: null + }, + minorTickLength: 0, + tickLength: 0, + grid: { + enabled: false + }, + gridLineWidth: 0 + }, + yAxis: { + lineWidth: 0, + minorGridLineWidth: 0, + lineColor: "transparent", + title: { + text: null + }, + labels: { + enabled: false + }, + minorTickLength: 0, + tickLength: 0, + gridLineWidth: 0 + }, + series: [ + { + name: "tokens", + data: prices.length === 0 ? [0, 0] : prices, + type: "line", + enableMouseTracking: true + } + ] + }; + return ( +
+
+ +
+
+ ); + }; diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 72d35353ba5..e4227d1ff62 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -27,7 +27,7 @@ import { HiveEngineChart } from "../hive-engine-chart"; import { History } from "history"; import { vestsToHp } from "../../helper/vesting"; import { marketInfo, } from "../../api/misc"; -import { WalletPortfolioChart } from "../wallet-portfolio-chart"; +import { HiveWalletPortfolioChart, HbdWalletPortfolioChart } from "../wallet-portfolio-chart"; import { getCurrencyTokenRate } from "../../api/private-api"; const hbdIcom = require("./asset/hbd.png") @@ -196,7 +196,7 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h}
- + {w.balance} ${Number(w.balance * this.pricePerHive).toFixed(3)} - HBD-CHART + {w.hbdBalance} ${Number(w.hbdBalance * coingeckoData[1]?.current_price).toFixed(3)}
+
@@ -183,10 +221,10 @@ export class WalletPortfolio extends BaseComponent { - this.handleLink("hive")}> + this.handleLink("hive-power")}> - - + + - this.handleLink("hive-power")}> + this.handleLink("hive")}> - - + + this.handleLink("hbd")}> @@ -240,7 +278,7 @@ export class WalletPortfolio extends BaseComponent { - {allTokens?.map((a: any) =>{ + {loading ? : favoriteTokens?.map((a: any) =>{ const changeValue = parseFloat(a?.priceChangePercent); return( this.handleLink(a.symbol)}> @@ -282,8 +320,50 @@ export class WalletPortfolio extends BaseComponent { })} -
{_t("wallet-portfolio.name")}${Number(estimatedPointsValue * Number(points.points)).toFixed(3)}
- HIVE + HIVE-POWER ${this.pricePerHive} @@ -196,16 +234,16 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h} - + ------------------- {w.balance}${Number(w.balance * this.pricePerHive).toFixed(3)}{totalHP}${Number(totalHP * this.pricePerHive).toFixed(3)}
- HIVE-POWER + HIVE ${this.pricePerHive} @@ -215,10 +253,10 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h} - ------------------- + {totalHP}${Number(totalHP * this.pricePerHive).toFixed(3)}{w.balance}${Number(w.balance * this.pricePerHive).toFixed(3)}
${Number(w.hbdBalance * coingeckoData[1]?.current_price).toFixed(3)}
+ +
+
+
+ + + + Tokens + + + +
+ this.setState({search: e.target.value})} + style={{width: "50%"}} + /> +
+ {allTokens?.filter((list: any) => + list.name.toLowerCase().startsWith(search) || + list.name.toLowerCase().includes(search) + ).map((token: any, i: any) =>( + + ))} +
+
); } diff --git a/src/style/_components.scss b/src/style/_components.scss index e943c064c14..fabf7c6d822 100644 --- a/src/style/_components.scss +++ b/src/style/_components.scss @@ -40,6 +40,7 @@ @import "../common/components/profile-edit"; @import "../common/components/wallet-hive"; @import "../common/components/wallet-portfolio"; +@import "../common/components/engine-tokens-list"; @import "../common/components/wallet-hive-engine"; @import "../common/components/hive-engine-chart"; @import "../common/components/delegated-vesting"; From 2a9c3efa4942d227a19a5b31f8ee11c170191ddc Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Sun, 29 Jan 2023 14:27:46 +0100 Subject: [PATCH 07/21] profile tokens: engine --- src/common/components/community-card/index.tsx | 4 ++-- src/common/components/profile-edit/index.tsx | 10 ++++++---- .../components/wallet-portfolio/index.tsx | 18 ++++++++++++------ src/common/store/accounts/types.ts | 1 + 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/common/components/community-card/index.tsx b/src/common/components/community-card/index.tsx index a0cb2c1efbe..2271e72f939 100644 --- a/src/common/components/community-card/index.tsx +++ b/src/common/components/community-card/index.tsx @@ -75,7 +75,6 @@ export class EditPic extends BaseComponent { const { addAccount, onUpdate } = this.props; const { profile } = account; - const newProfile = { name: profile?.name || "", about: profile?.about || "", @@ -83,7 +82,8 @@ export class EditPic extends BaseComponent { profile_image: url, website: profile?.website || "", location: profile?.location || "", - pinned: profile?.pinned || "" + pinned: profile?.pinned || "", + profileTokens: profile?.profileTokens || [] }; updateProfile(account, newProfile) diff --git a/src/common/components/profile-edit/index.tsx b/src/common/components/profile-edit/index.tsx index 782fa1724c7..1239e90f8cf 100644 --- a/src/common/components/profile-edit/index.tsx +++ b/src/common/components/profile-edit/index.tsx @@ -32,6 +32,7 @@ interface State { inProgress: boolean; uploading: boolean; changed: boolean; + profileTokens: string[] } const pureState = (props: Props): State => { @@ -50,7 +51,8 @@ const pureState = (props: Props): State => { location: profile.location || "", coverImage: profile.cover_image || "", profileImage: profile.profile_image || "", - pinned: profile.pinned || "" + pinned: profile.pinned || "", + profileTokens: profile?.profileTokens || [] }; }; @@ -81,7 +83,7 @@ export default class ProfileEdit extends BaseComponent { update = () => { const { activeUser, addAccount, updateActiveUser } = this.props; - const { name, about, location, website, coverImage, profileImage, pinned } = this.state; + const { name, about, location, website, coverImage, profileImage, pinned, profileTokens } = this.state; const newProfile = { name, @@ -90,9 +92,9 @@ export default class ProfileEdit extends BaseComponent { profile_image: profileImage, website, location, - pinned + pinned, + profileTokens }; - this.stateSet({ inProgress: true }); updateProfile(activeUser.data, newProfile) .then((r) => { diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 482be42c159..12afc026add 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -3,6 +3,7 @@ import React from "react"; import { Global } from "../../store/global/types"; import { Account } from "../../store/accounts/types"; import { DynamicProps } from "../../store/dynamic-props/types"; +import { ActiveUser } from "../../store/active-user/types"; import BaseComponent from "../base"; import HiveEngineToken from "../../helper/hive-engine-wallet"; @@ -41,6 +42,7 @@ interface Props { global: Global; dynamicProps: DynamicProps; account: Account | any; + activeUser: ActiveUser | null; history: History; updateActiveUser: (data?: Account) => void; updateWalletValues: () => void; @@ -164,21 +166,24 @@ export class WalletPortfolio extends BaseComponent { const profileTokens: any = account?.profile.profileTokens || [] const { profile } = userProfile; let mappedTokens: any = profileTokens.map((t: any) => t.symbol === token.symbol) - console.log(mappedTokens) + console.log(profile) if (!mappedTokens.includes(true)){ console.log("Not on list") profileTokens.push(token) }else { console.log("Token already added") } - const newPostMeta: any = {...profile, profileTokens} + const newPostMeta: any = {...profile, profileTokens} + console.log(newPostMeta) updateProfile(account, newPostMeta) }; render() { - const { global, dynamicProps, account, points } = this.props; + const { global, dynamicProps, account, points, activeUser } = this.props; const { allTokens, converting, coingeckoData, estimatedPointsValue, search, showTokenList, favoriteTokens, loading } = this.state; const { hivePerMVests } = dynamicProps; + + const profileTokens: any = account?.profile?.profileTokens const w = new HiveWallet(account, dynamicProps, converting); const totalHP: any = formattedNumber(vestsToHp(w.vestingShares, hivePerMVests)); @@ -278,7 +283,7 @@ export class WalletPortfolio extends BaseComponent { ${Number(w.hbdBalance * coingeckoData[1]?.current_price).toFixed(3)} - {loading ? : favoriteTokens?.map((a: any) =>{ + {!profileTokens ? : profileTokens?.map((a: any) =>{ const changeValue = parseFloat(a?.priceChangePercent); return( this.handleLink(a.symbol)}> @@ -322,12 +327,12 @@ export class WalletPortfolio extends BaseComponent { -
+ {activeUser?.username === account.name &&
-
+
} { global: p.global, dynamicProps: p.dynamicProps, account: p.account, + activeUser: p.activeUser, updateActiveUser: p.updateActiveUser, updateWalletValues: p.updateWalletValues, history: p.history, diff --git a/src/common/store/accounts/types.ts b/src/common/store/accounts/types.ts index c1f24fea4ba..a88422834a5 100644 --- a/src/common/store/accounts/types.ts +++ b/src/common/store/accounts/types.ts @@ -8,6 +8,7 @@ export interface AccountProfile { profile_image?: string; website?: string; pinned?: string; + profileTokens?: string[]; } export interface AccountFollowStats { From c67a23f1274bbfcb85b93969e5ed38e4e3410ebc Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Tue, 7 Feb 2023 09:15:04 +0100 Subject: [PATCH 08/21] checkbox fixes --- .../components/engine-tokens-list/index.scss | 11 +- .../components/engine-tokens-list/index.tsx | 20 ++-- .../components/wallet-portfolio/index.tsx | 103 +++++++++++------- 3 files changed, 77 insertions(+), 57 deletions(-) diff --git a/src/common/components/engine-tokens-list/index.scss b/src/common/components/engine-tokens-list/index.scss index bab52893dd6..7064f3247e7 100644 --- a/src/common/components/engine-tokens-list/index.scss +++ b/src/common/components/engine-tokens-list/index.scss @@ -1,5 +1,6 @@ -.container{ - display: flex;align-items: center; +.portfolio-list-container{ + display: flex; + align-items: center; justify-content: space-between; .token-list{ @@ -19,9 +20,9 @@ .add-btn{ - button{ - padding: 5px; - border-radius: 5px; + input{ + width: 20px; + height: 20px; } } } \ No newline at end of file diff --git a/src/common/components/engine-tokens-list/index.tsx b/src/common/components/engine-tokens-list/index.tsx index d07ede1f1a7..68a35ece65d 100644 --- a/src/common/components/engine-tokens-list/index.tsx +++ b/src/common/components/engine-tokens-list/index.tsx @@ -2,19 +2,11 @@ import React, { useEffect, useState} from 'react' import { _t } from '../../i18n'; const EngineTokensList = (props: any) => { - const { token, addToFavorite, hideModal } = props; + const { token, handleOnChange } = props; - - useEffect(() => { - }, []); - - const addToken = () => { - addToFavorite(token); - hideModal(); - } return ( <> -
+
@@ -22,9 +14,11 @@ const EngineTokensList = (props: any) => {
- + handleOnChange(e, token)} + />
diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 12afc026add..ec0e8c5794f 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -61,6 +61,7 @@ interface State { search: string; showTokenList: boolean; favoriteTokens: HiveEngineToken[] | any; + selectedTokens: HiveEngineToken[] | any; } export class WalletPortfolio extends BaseComponent { @@ -75,7 +76,8 @@ export class WalletPortfolio extends BaseComponent { estimatedPointsValueLoading: false, search: "", showTokenList: false, - favoriteTokens: [] + favoriteTokens: [], + selectedTokens: [], }; _isMounted = false; pricePerHive = this.props.dynamicProps.base / this.props.dynamicProps.quote; @@ -107,9 +109,11 @@ export class WalletPortfolio extends BaseComponent { let eachMetric = allMarketTokens?.find((m: any) => m?.symbol === item?.symbol); return { ...item, - ...eachMetric + ...eachMetric, + "type": "Engine" }; }); + console.log(balanceMetrics) let tokensUsdValues: any = balanceMetrics?.map((w: any) => { const usd_value = w?.symbol === "SWAP.HIVE" @@ -121,7 +125,7 @@ export class WalletPortfolio extends BaseComponent { ...w, usd_value }; - }); + }); this.setState({ allTokens: tokensUsdValues }); return tokensUsdValues; }; @@ -159,24 +163,36 @@ export class WalletPortfolio extends BaseComponent { const { account } = this.props; this.setState({favoriteTokens: account?.profile?.profileTokens, loading: false}) } - - addToFavorite = async (token: HiveEngineToken | any) => { + + handleOnChange = (e: any, token: any) => { + const { account } = this.props; + const { selectedTokens } = this.state; + const userProfile = JSON.parse(account.posting_json_metadata); + const userTokens = userProfile.profile.profileTokens + const isInProfile = userTokens?.map((item: any) => item.symbol === token.symbol) + const isSelected = selectedTokens.includes(token); + + if (e?.target?.checked && !isSelected && !isInProfile.includes(true)) { + this.setState({selectedTokens: [...selectedTokens, ...token]}) + // console.log(token.name + " added to list") + } else { + this.setState({selectedTokens: [...selectedTokens]}) + // console.log("can't add token") + } + } + + addToProfile= () => { const { account } = this.props; - const userProfile = JSON.parse(account.posting_json_metadata) - const profileTokens: any = account?.profile.profileTokens || [] - const { profile } = userProfile; - let mappedTokens: any = profileTokens.map((t: any) => t.symbol === token.symbol) - console.log(profile) - if (!mappedTokens.includes(true)){ - console.log("Not on list") - profileTokens.push(token) - }else { - console.log("Token already added") - } + const { selectedTokens } = this.state + const userProfile = JSON.parse(account.posting_json_metadata); + const { profile } = userProfile; + let { profileTokens } = profile; + profileTokens = [...profileTokens, ...selectedTokens]; + const newPostMeta: any = {...profile, profileTokens} console.log(newPostMeta) - updateProfile(account, newPostMeta) - }; + updateProfile(account, newPostMeta) + } render() { const { global, dynamicProps, account, points, activeUser } = this.props; @@ -338,7 +354,7 @@ export class WalletPortfolio extends BaseComponent { show={showTokenList} centered={true} animation={false} - size="lg" + // size="lg" scrollable style={{height: "100vh"}} > @@ -347,26 +363,35 @@ export class WalletPortfolio extends BaseComponent { Tokens - -
- this.setState({search: e.target.value})} - style={{width: "50%"}} - /> -
- {allTokens?.filter((list: any) => - list.name.toLowerCase().startsWith(search) || - list.name.toLowerCase().includes(search) - ).map((token: any, i: any) =>( - - ))} + +
+
+ this.setState({search: e.target.value})} + style={{width: "50%"}} + /> +
+ {allTokens?.slice(0, 10).filter((list: any) => + list?.name.toLowerCase().startsWith(search) || + list?.name.toLowerCase().includes(search) + ).map((token: any, i: any) =>( + + ))} +
+ +
+
From 1aa76f6b59b01e945325b10be5de5412692bea06 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Fri, 10 Feb 2023 18:04:12 +0100 Subject: [PATCH 09/21] type logo amd date format --- src/common/api/spk-api.ts | 10 +++ .../components/engine-tokens-list/index.tsx | 3 +- .../hive-engine-transactions/index.tsx | 7 +- .../wallet-portfolio/asset/spklogo.png | Bin 0 -> 62238 bytes .../components/wallet-portfolio/index.scss | 13 +++ .../components/wallet-portfolio/index.tsx | 85 ++++++++++++------ 6 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 src/common/components/wallet-portfolio/asset/spklogo.png diff --git a/src/common/api/spk-api.ts b/src/common/api/spk-api.ts index 3af133abf98..6a4008feb1f 100644 --- a/src/common/api/spk-api.ts +++ b/src/common/api/spk-api.ts @@ -123,6 +123,16 @@ export const getSpkWallet = async (username: string): Promise => { const resp = await axios.get(`${spkNode}/@${username}`); return resp.data; }; +export const getMarketInfo = async (): Promise => { + const resp = await axios.get(`${spkNode}/dex`); + console.log(resp.data) + return resp.data; +}; + +export const getLarynxData = async () => { + fetch(`https://spknode.blocktrades.us/dex`).then((data: any)=> data.json()) + .then((result: any) => console.log(result)) +} export const getMarkets = async (): Promise => { const resp = await axios.get(`${spkNode}/markets`); diff --git a/src/common/components/engine-tokens-list/index.tsx b/src/common/components/engine-tokens-list/index.tsx index 68a35ece65d..a143c554772 100644 --- a/src/common/components/engine-tokens-list/index.tsx +++ b/src/common/components/engine-tokens-list/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState} from 'react' import { _t } from '../../i18n'; const EngineTokensList = (props: any) => { - const { token, handleOnChange } = props; + const { token, handleOnChange, ischecked } = props; return ( <> @@ -17,6 +17,7 @@ const EngineTokensList = (props: any) => { handleOnChange(e, token)} /> diff --git a/src/common/components/hive-engine-transactions/index.tsx b/src/common/components/hive-engine-transactions/index.tsx index 26b25e421ae..f11a9ec10f7 100644 --- a/src/common/components/hive-engine-transactions/index.tsx +++ b/src/common/components/hive-engine-transactions/index.tsx @@ -9,6 +9,7 @@ import { getOtherTransactions } from "../../api/hive-engine"; import LinearProgress from '../linear-progress'; +import { dateToFullRelative } from "../../helper/parse-date"; export const EngineTransactionList = (props: any) => { @@ -21,7 +22,7 @@ export const EngineTransactionList = (props: any) => { useEffect(() => { otherTokenTransactions(); - getMainTransactions() + getMainTransactions(); }, []) const getMainTransactions = async () => { @@ -40,8 +41,8 @@ export const EngineTransactionList = (props: any) => { const getTransactionTime = (timestamp: number) => { let date: any = new Date(timestamp * 1000) - return date.toDateString(); - } + return dateToFullRelative(date.toJSON()); + }; const loadMore = () => { const moreItems = loadLimit + 10; diff --git a/src/common/components/wallet-portfolio/asset/spklogo.png b/src/common/components/wallet-portfolio/asset/spklogo.png new file mode 100644 index 0000000000000000000000000000000000000000..70358f393e5674ea9527a922b1efebc8579ae85c GIT binary patch literal 62238 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

<(m9%)LEid>nB%LC>-*U+eOp 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 literal 0 HcmV?d00001 diff --git a/src/common/components/wallet-portfolio/index.scss b/src/common/components/wallet-portfolio/index.scss index 5e8e50598d3..11a5ea98d35 100644 --- a/src/common/components/wallet-portfolio/index.scss +++ b/src/common/components/wallet-portfolio/index.scss @@ -2,3 +2,16 @@ height: 100px; cursor: pointer; } + +.token-image{ + position: relative; + + .type-image{ + border-radius: 50%; + width: 15px; + height: 15px; + position: absolute; + top: 2px; + left: 30px; + } +} \ No newline at end of file diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index ec0e8c5794f..606f69c2d6b 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -34,9 +34,12 @@ import { getCurrencyTokenRate } from "../../api/private-api"; import EngineTokensList from "../engine-tokens-list"; import { Button, FormControl, Modal } from "react-bootstrap"; import { updateProfile } from "../../api/operations"; +import { getSpkWallet, getMarketInfo, getLarynxData } from "../../api/spk-api"; const hbdIcom = require("./asset/hbd.png") const ecencyIcon = require("./asset/ecency.jpeg") +const spkIcon = require("./asset/spklogo.png") +const engineIcon = require("./asset/engine.png") interface Props { global: Global; @@ -62,6 +65,10 @@ interface State { showTokenList: boolean; favoriteTokens: HiveEngineToken[] | any; selectedTokens: HiveEngineToken[] | any; + isChecked: boolean; + tokenBalance: number; + larynxTokenBalance: number; + larynxPowerBalance: number; } export class WalletPortfolio extends BaseComponent { @@ -78,6 +85,10 @@ export class WalletPortfolio extends BaseComponent { showTokenList: false, favoriteTokens: [], selectedTokens: [], + isChecked: false, + tokenBalance: 0, + larynxTokenBalance: 0, + larynxPowerBalance: 0, }; _isMounted = false; pricePerHive = this.props.dynamicProps.base / this.props.dynamicProps.quote; @@ -87,7 +98,7 @@ export class WalletPortfolio extends BaseComponent { this._isMounted && this.engineTokensData(); this._isMounted && this.dataFromCoinGecko(); this._isMounted && this.getEstimatedPointsValue(); - this._isMounted && this.loadFavoritesTokens(); + this._isMounted && this.getSpkTokens(); } componentWillUnmount() { @@ -101,6 +112,7 @@ export class WalletPortfolio extends BaseComponent { engineTokensData = async () => { const allMarketTokens = await getMetrics(); + const spkTokens = await this.getSpkTokens(); const { account } = this.props; const userTokens: any = await getHiveEngineTokenBalances(account.name); @@ -113,7 +125,7 @@ export class WalletPortfolio extends BaseComponent { "type": "Engine" }; }); - console.log(balanceMetrics) + let tokensUsdValues: any = balanceMetrics?.map((w: any) => { const usd_value = w?.symbol === "SWAP.HIVE" @@ -125,8 +137,9 @@ export class WalletPortfolio extends BaseComponent { ...w, usd_value }; - }); - this.setState({ allTokens: tokensUsdValues }); + }); + console.log(tokensUsdValues) + this.setState({ allTokens: [...tokensUsdValues, ...spkTokens] }); return tokensUsdValues; }; @@ -159,44 +172,60 @@ export class WalletPortfolio extends BaseComponent { this.setState({showTokenList: true}) }; - loadFavoritesTokens = async () => { - const { account } = this.props; - this.setState({favoriteTokens: account?.profile?.profileTokens, loading: false}) - } - handleOnChange = (e: any, token: any) => { const { account } = this.props; - const { selectedTokens } = this.state; + const { selectedTokens, isChecked } = this.state; const userProfile = JSON.parse(account.posting_json_metadata); const userTokens = userProfile.profile.profileTokens const isInProfile = userTokens?.map((item: any) => item.symbol === token.symbol) const isSelected = selectedTokens.includes(token); if (e?.target?.checked && !isSelected && !isInProfile.includes(true)) { - this.setState({selectedTokens: [...selectedTokens, ...token]}) - // console.log(token.name + " added to list") + this.setState({selectedTokens: [...selectedTokens, ...token], isChecked: e.target.checked}) + e.target.checked === true; } else { - this.setState({selectedTokens: [...selectedTokens]}) - // console.log("can't add token") - } + this.setState({selectedTokens: [...selectedTokens], isChecked: e.target.checked}) + } } - addToProfile= () => { + addToProfile= async () => { const { account } = this.props; - const { selectedTokens } = this.state + const { selectedTokens } = this.state; + const spkTokens = await this.getSpkTokens(); + console.log(spkTokens) const userProfile = JSON.parse(account.posting_json_metadata); const { profile } = userProfile; let { profileTokens } = profile; profileTokens = [...profileTokens, ...selectedTokens]; const newPostMeta: any = {...profile, profileTokens} - console.log(newPostMeta) + updateProfile(account, newPostMeta) - } + }; + + getSpkTokens = async () => { + const wallet = await getSpkWallet(this.props.account.name); + const marketData = await getMarketInfo(); + const larynxData = await getLarynxData() + // sample would have to replace this with original data, just adding this as a template for profile tokens + const spkTokens = [ + {"spk": wallet.spk / 1000, "type": "Spk", "name": "SPK", "icon": spkIcon, "symbol": "SPK"}, + {"larynx": wallet.balance / 1000, "type": "Spk", "name": "LARYNX", "icon": spkIcon, "symbol": "LARYNX"}, + {"lp": wallet.poweredUp / 1000, "type": "Spk", "name": "LP", "icon": spkIcon, "symbol": "LP"} + ] + + this.setState({ + tokenBalance: wallet.spk / 1000 , + larynxTokenBalance: wallet.balance / 1000, + larynxPowerBalance: wallet.poweredUp / 1000 + }) + return spkTokens; + + }; render() { const { global, dynamicProps, account, points, activeUser } = this.props; - const { allTokens, converting, coingeckoData, estimatedPointsValue, search, showTokenList, favoriteTokens, loading } = this.state; + const { allTokens, converting, coingeckoData, estimatedPointsValue, search, showTokenList, isChecked, loading } = this.state; const { hivePerMVests } = dynamicProps; const profileTokens: any = account?.profile?.profileTokens @@ -233,10 +262,10 @@ export class WalletPortfolio extends BaseComponent { ${estimatedPointsValue} - ------------------- + ---------- - ------------------- + ---------- {points.points} ${Number(estimatedPointsValue * Number(points.points)).toFixed(3)} @@ -255,7 +284,7 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h} - ------------------- + ---------- {totalHP} ${Number(totalHP * this.pricePerHive).toFixed(3)} @@ -303,10 +332,11 @@ export class WalletPortfolio extends BaseComponent { const changeValue = parseFloat(a?.priceChangePercent); return( this.handleLink(a.symbol)}> - - + +

- + +
{a.symbol} {!global?.isMobile && @@ -373,7 +403,7 @@ export class WalletPortfolio extends BaseComponent { style={{width: "50%"}} /> - {allTokens?.slice(0, 10).filter((list: any) => + {allTokens?.slice(0, 30).filter((list: any) => list?.name.toLowerCase().startsWith(search) || list?.name.toLowerCase().includes(search) ).map((token: any, i: any) =>( @@ -381,6 +411,7 @@ export class WalletPortfolio extends BaseComponent { token={token} showTokenList={showTokenList} handleOnChange={this.handleOnChange} + isChecked={isChecked} /> ))}
From 8833e1a640250154b4e75829f55f978aaaca567b Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Sat, 11 Feb 2023 15:59:26 +0100 Subject: [PATCH 10/21] fix map key --- src/common/components/wallet-portfolio/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 606f69c2d6b..e7590f36b61 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -412,6 +412,7 @@ export class WalletPortfolio extends BaseComponent { showTokenList={showTokenList} handleOnChange={this.handleOnChange} isChecked={isChecked} + key={i} /> ))}
From 4e063973a4e0319e2790cec4b77603eb803272d6 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Mon, 13 Feb 2023 09:59:49 +0100 Subject: [PATCH 11/21] fixed chart toggle --- .../components/wallet-portfolio/index.scss | 61 +++++++++++++++++++ .../components/wallet-portfolio/index.tsx | 51 ++++++++++------ src/common/i18n/locales/en-US.json | 4 +- 3 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/common/components/wallet-portfolio/index.scss b/src/common/components/wallet-portfolio/index.scss index 11a5ea98d35..f73aea709cc 100644 --- a/src/common/components/wallet-portfolio/index.scss +++ b/src/common/components/wallet-portfolio/index.scss @@ -1,3 +1,64 @@ +.table-top{ + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 15px; + + .toggle{ + display: flex; + align-items: center; + gap: 10px; + + label{ + position: relative; + + input{ + position: absolute; + left: 0px; + right: 0px; + margin: auto; + + &:checked + span{ + background-color: #357ce6; + + &::before{ + left: 30px; + + } + } + + } + + span{ + display: flex; + cursor: pointer; + width: 60px; + height: 35px; + border-radius: 100px; + background-color: #585151; + position: relative; + padding: 5px; + transition: background-color 0.5s; + + &::before{ + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 30px; + height: 30px; + border-radius: 50px; + transition: 0.5s; + background: white; + + } + } + } + } + +} + .table-row { height: 100px; cursor: pointer; diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index e7590f36b61..401de4b56d6 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -69,6 +69,7 @@ interface State { tokenBalance: number; larynxTokenBalance: number; larynxPowerBalance: number; + showChart: boolean; } export class WalletPortfolio extends BaseComponent { @@ -89,6 +90,7 @@ export class WalletPortfolio extends BaseComponent { tokenBalance: 0, larynxTokenBalance: 0, larynxPowerBalance: 0, + showChart: false, }; _isMounted = false; pricePerHive = this.props.dynamicProps.base / this.props.dynamicProps.quote; @@ -138,7 +140,6 @@ export class WalletPortfolio extends BaseComponent { usd_value }; }); - console.log(tokensUsdValues) this.setState({ allTokens: [...tokensUsdValues, ...spkTokens] }); return tokensUsdValues; }; @@ -192,7 +193,6 @@ export class WalletPortfolio extends BaseComponent { const { account } = this.props; const { selectedTokens } = this.state; const spkTokens = await this.getSpkTokens(); - console.log(spkTokens) const userProfile = JSON.parse(account.posting_json_metadata); const { profile } = userProfile; let { profileTokens } = profile; @@ -207,7 +207,7 @@ export class WalletPortfolio extends BaseComponent { const wallet = await getSpkWallet(this.props.account.name); const marketData = await getMarketInfo(); const larynxData = await getLarynxData() - // sample would have to replace this with original data, just adding this as a template for profile tokens + // sample would have to replace this with original/non-hardcoded data, just adding this as a template const spkTokens = [ {"spk": wallet.spk / 1000, "type": "Spk", "name": "SPK", "icon": spkIcon, "symbol": "SPK"}, {"larynx": wallet.balance / 1000, "type": "Spk", "name": "LARYNX", "icon": spkIcon, "symbol": "LARYNX"}, @@ -223,9 +223,13 @@ export class WalletPortfolio extends BaseComponent { }; + toggleChart = (e: any)=> { + this.setState({showChart: e.target.checked}) + } + render() { const { global, dynamicProps, account, points, activeUser } = this.props; - const { allTokens, converting, coingeckoData, estimatedPointsValue, search, showTokenList, isChecked, loading } = this.state; + const { allTokens, converting, coingeckoData, estimatedPointsValue, search, showTokenList, isChecked, showChart } = this.state; const { hivePerMVests } = dynamicProps; const profileTokens: any = account?.profile?.profileTokens @@ -235,8 +239,17 @@ export class WalletPortfolio extends BaseComponent { return (
-
- Total wallet Value: $1.19 +
+ {_t("wallet-portfolio.total-value")} $1.19 +
+ + {_t("wallet-portfolio.show-trend")} + + +
@@ -246,7 +259,7 @@ export class WalletPortfolio extends BaseComponent { {!global?.isMobile && } - {!global?.isMobile && + {!global?.isMobile && showChart && } @@ -262,11 +275,11 @@ export class WalletPortfolio extends BaseComponent { - + {!global?.isMobile && showChart && } @@ -283,9 +296,9 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h} - + {!global?.isMobile && showChart && } @@ -302,9 +315,9 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h} - + } @@ -321,9 +334,9 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[1]?.price_change_percentage_24h} - + } @@ -353,7 +366,7 @@ export class WalletPortfolio extends BaseComponent { {a?.symbol === a.symbol ? a?.priceChangePercent : null} - {!global?.isMobile && this.handleLink("points")}> {!global?.isMobile && showChart && } this.handleLink("hive-power")}> } this.handleLink("hive")}> this.handleLink("hbd")}> @@ -451,7 +482,7 @@ export class WalletPortfolio extends BaseComponent { {a.balance} ); @@ -512,7 +543,7 @@ export class WalletPortfolio extends BaseComponent { Date: Mon, 13 Mar 2023 15:45:33 +0100 Subject: [PATCH 16/21] fixed style import --- src/common/components/engine-tokens-list/index.scss | 5 +++++ src/common/components/engine-tokens-list/index.tsx | 1 + src/common/components/wallet-portfolio/index.scss | 5 +++++ src/common/components/wallet-portfolio/index.tsx | 1 + 4 files changed, 12 insertions(+) diff --git a/src/common/components/engine-tokens-list/index.scss b/src/common/components/engine-tokens-list/index.scss index 7064f3247e7..024a38620b0 100644 --- a/src/common/components/engine-tokens-list/index.scss +++ b/src/common/components/engine-tokens-list/index.scss @@ -1,3 +1,8 @@ +@import "src/style/colors"; +@import "src/style/variables"; +@import "src/style/bootstrap_vars"; +@import "src/style/mixins"; + .portfolio-list-container{ display: flex; align-items: center; diff --git a/src/common/components/engine-tokens-list/index.tsx b/src/common/components/engine-tokens-list/index.tsx index bc2a28042b7..79b62a343d7 100644 --- a/src/common/components/engine-tokens-list/index.tsx +++ b/src/common/components/engine-tokens-list/index.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState} from 'react' import { _t } from '../../i18n'; +import "./index.scss"; const EngineTokensList = (props: any) => { const { token, handleChange, isChecked, favoriteToken } = props; diff --git a/src/common/components/wallet-portfolio/index.scss b/src/common/components/wallet-portfolio/index.scss index f73aea709cc..e884ed091e8 100644 --- a/src/common/components/wallet-portfolio/index.scss +++ b/src/common/components/wallet-portfolio/index.scss @@ -1,3 +1,8 @@ +@import "src/style/colors"; +@import "src/style/variables"; +@import "src/style/bootstrap_vars"; +@import "src/style/mixins"; + .table-top{ display: flex; flex-direction: row; diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 78160971d97..bfb36da11bf 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -28,6 +28,7 @@ import { Button, FormControl, Modal } from "react-bootstrap"; import { updateProfile } from "../../api/operations"; import { getSpkWallet, getMarketInfo, getLarynxData } from "../../api/spk-api"; import { findIndex } from "lodash"; +import "./index.scss"; const hbdIcom = require("./asset/hbd.png"); const ecencyIcon = require("./asset/ecency.jpeg"); From 30ec4d70d1947d428fad3a78a99f33e1eb0cc3b3 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Tue, 14 Mar 2023 11:04:29 +0100 Subject: [PATCH 17/21] syntax fixes --- src/common/api/misc.ts | 6 ++---- src/common/components/engine-tokens-estimated/index.tsx | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/common/api/misc.ts b/src/common/api/misc.ts index ccda2d8a6ae..d2b4b5b39d3 100644 --- a/src/common/api/misc.ts +++ b/src/common/api/misc.ts @@ -75,15 +75,13 @@ export const fetchGif = async (query: string | null, limit: string, offset: stri export const marketInfo = async (): Promise => { const url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=hive%2C%20hive_dollar&order=market_cap_desc&per_page=100&page=1&sparkline=false`; - const data = await axios.get(url) - .then((r: any) => r.data) + const { data } = await axios.get(url) return data; }; export const marketChart = async (token: string): Promise => { const url = `https://api.coingecko.com/api/v3/coins/${token}/market_chart?vs_currency=usd&days=30`; - const data = await axios.get(url) - .then((r: any) => r.data) + const { data } = await axios.get(url) return data; }; diff --git a/src/common/components/engine-tokens-estimated/index.tsx b/src/common/components/engine-tokens-estimated/index.tsx index af358a386e2..fa8f71f33e7 100644 --- a/src/common/components/engine-tokens-estimated/index.tsx +++ b/src/common/components/engine-tokens-estimated/index.tsx @@ -4,7 +4,6 @@ import { getMetrics } from "../../api/hive-engine"; export const EngineTokensEstimated = (props: any) => { const { tokens: userTokens, dynamicProps } = props; - // console.log(userTokens) const [estimated, setEstimated] = useState(`${_t("wallet.calculating")}...`); useEffect(() => { @@ -24,8 +23,6 @@ export const EngineTokensEstimated = (props: any) => { }; }); - // const walletTokens = mappedBalanceMetrics.filter((w: any) => w.balance !== 0 || w.stakedBalance !== 0) - const tokens_usd_prices = mappedBalanceMetrics.map((w: any) => { return w.symbol === "SWAP.HIVE" ? Number(pricePerHive * w.balance) From 5a802347c3bb74436902c49d3e61d75799364671 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Wed, 15 Mar 2023 14:39:51 +0100 Subject: [PATCH 18/21] clear consoles --- src/common/api/hive-engine.ts | 7 +---- .../wallet-engine-details/index.tsx | 28 ++----------------- .../components/wallet-portfolio/index.tsx | 8 ------ 3 files changed, 3 insertions(+), 40 deletions(-) diff --git a/src/common/api/hive-engine.ts b/src/common/api/hive-engine.ts index 6782b31ebe7..abf6b5eed0a 100644 --- a/src/common/api/hive-engine.ts +++ b/src/common/api/hive-engine.ts @@ -165,12 +165,7 @@ export const getMetrics: any = async (symbol?: any, account?: any) => { }, id: 1 }; - - // const result = await axios - // .post(HIVE_ENGINE_RPC_URL, data, { - // headers: { "Content-type": "application/json" } - // }) - // return result; + return axios .post(HIVE_ENGINE_RPC_URL, data, { headers: { "Content-type": "application/json" } diff --git a/src/common/components/wallet-engine-details/index.tsx b/src/common/components/wallet-engine-details/index.tsx index ebd5b9f1682..0d596dd1a32 100644 --- a/src/common/components/wallet-engine-details/index.tsx +++ b/src/common/components/wallet-engine-details/index.tsx @@ -1,44 +1,23 @@ import React from "react"; -import { proxifyImageSrc } from "@ecency/render-helper"; - import { Global } from "../../store/global/types"; import { Account } from "../../store/accounts/types"; import { DynamicProps } from "../../store/dynamic-props/types"; import { OperationGroup, Transactions } from "../../store/transactions/types"; import { ActiveUser } from "../../store/active-user/types"; - import BaseComponent from "../base"; import HiveEngineToken from "../../helper/hive-engine-wallet"; import LinearProgress from "../linear-progress"; -import { Button, OverlayTrigger, Tooltip } from "react-bootstrap"; -import WalletMenu from "../wallet-menu"; -import { SortEngineTokens } from "../sort-hive-engine-tokens"; -import { EngineTokensEstimated } from "../engine-tokens-estimated"; import Transfer, { TransferMode } from "../transfer-he"; import { error, success } from "../feedback"; import DropDown from "../dropdown"; import { EngineTransactionList } from "../hive-engine-transactions"; - import { claimRewards, getHiveEngineTokenBalances, getUnclaimedRewards, - TokenStatus, - getMetrics + TokenStatus } from "../../api/hive-engine"; - -import { - informationVariantSvg, - plusCircle, - transferOutlineSvg, - lockOutlineSvg, - unlockOutlineSvg, - delegateOutlineSvg, - undelegateOutlineSvg, - priceUpSvg, - priceDownSvg -} from "../../img/svg"; - +import { plusCircle } from "../../img/svg"; import { formatError } from "../../api/operations"; import formattedNumber from "../../util/formatted-number"; import { _t } from "../../i18n"; @@ -183,7 +162,6 @@ export class EngineTokenDetails extends BaseComponent { render() { const { global, account, activeUser } = this.props; const { rewards, tokens, loading, claiming, claimed } = this.state; - // console.log(tokens) const hasUnclaimedRewards = rewards.length > 0; const isMyPage = activeUser && activeUser.username === account.name; let rewardsToShowInTooltip = [...rewards]; @@ -310,7 +288,6 @@ export class EngineTokenDetails extends BaseComponent { let dropDownConfig: any; if (isMyPage) { dropDownConfig = { - // history: this.props.history, label: "", items: [ { @@ -353,7 +330,6 @@ export class EngineTokenDetails extends BaseComponent { }; } else if (activeUser) { dropDownConfig = { - // history: this.props.history, label: "", items: [ { diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index bfb36da11bf..84b7486c37d 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -179,12 +179,9 @@ export class WalletPortfolio extends BaseComponent { if (index > 0) { profileTokens.splice(index, 1); - // console.log("Splicing"); } this.setState((prevState) => { - const { isChecked } = prevState; - // console.log(e, prevState.selectedTokens); return { isChecked: e ? true : false, selectedTokens: !e @@ -221,7 +218,6 @@ export class WalletPortfolio extends BaseComponent { const wallet = await getSpkWallet(this.props.account.name); const marketData = await getMarketInfo(); const larynxData = await getLarynxData(); - // sample would have to replace this with original/non-hardcoded data, just adding this as a template const spkTokens = [ { spk: wallet.spk / 1000, @@ -309,8 +305,6 @@ export class WalletPortfolio extends BaseComponent {
{_t("wallet-portfolio.total-value")} {this.formatCurrency(estimatedTotal)} - {/* Hive Savings: {w.savingBalance}Hive - Hbd Savings: {this.formatCurrency(w.savingBalanceHbd)} */}
{_t("wallet-portfolio.show-trend")}
); }; +export const DefaultPortfolioChart = (props: any) => { + const { theme } = props; + + const config: any = { + title: { + text: null + }, + credits: { enabled: false }, + legend: { + enabled: false + }, + chart: { + height: "40", + width: "100", + zoomType: "x", + backgroundColor: "transparent", + border: "none", + style: { + fontFamily: "inherit", + border: "none" + }, + plotBorderColor: "transparent", + plotBorderWidth: 0, + plotBackgroundColor: "transparent", + plotShadow: false, + type: "area", + spacingBottom: 0, + spacingTop: 0, + spacingLeft: 0, + spacingRight: 0, + marginTop: 0, + marginBottom: 0 + }, + plotOptions: { + area: { + fillColor: theme === Theme.night ? "#2e3d51" : "#f3f7fb", + lineColor: "transparent", + lineWidth: 150 + }, + series: { + marker: { + enabled: false, + states: { + hover: { + enabled: false + } + } + } + } + }, + tooltip: { + valueDecimals: 2, + useHTML: true, + shadow: false, + formatter: (({ chart }: any) => { + let date = moment(chart.hoverPoint.options.x).calendar(); + let rate = chart.hoverPoint.options.y; + return `
${_t("g.when")}: ${date}
${_t( + "g.price" + )}:${rate.toFixed(3)}
`; + }) as any, + enabled: true + }, + xAxis: { + lineWidth: 0, + minorGridLineWidth: 0, + lineColor: "transparent", + labels: { + enabled: false, + style: { + color: "red" + } + }, + title: { + text: null + }, + minorTickLength: 0, + tickLength: 0, + grid: { + enabled: false + }, + gridLineWidth: 0 + }, + yAxis: { + lineWidth: 0, + minorGridLineWidth: 0, + lineColor: "transparent", + title: { + text: null + }, + labels: { + enabled: false + }, + minorTickLength: 0, + tickLength: 0, + gridLineWidth: 0 + }, + series: [ + { + name: "tokens", + data: [0, 0], + type: "line", + enableMouseTracking: true + } + ] + }; + return ( +
+
+ +
+
+ ); + }; diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 84b7486c37d..32576293d55 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -21,7 +21,7 @@ import { HiveEngineChart } from "../hive-engine-chart"; import { History } from "history"; import { vestsToHp } from "../../helper/vesting"; import { marketInfo } from "../../api/misc"; -import { HiveWalletPortfolioChart, HbdWalletPortfolioChart } from "../wallet-portfolio-chart"; +import { HiveWalletPortfolioChart, HbdWalletPortfolioChart, DefaultPortfolioChart } from "../wallet-portfolio-chart"; import { getCurrencyTokenRate } from "../../api/private-api"; import EngineTokensList from "../engine-tokens-list"; import { Button, FormControl, Modal } from "react-bootstrap"; @@ -254,7 +254,7 @@ export class WalletPortfolio extends BaseComponent { }; formatCurrency = (price: number | string) => { - const formatted = price.toLocaleString("en-US", { + const formatted = price?.toLocaleString("en-US", { style: "currency", currency: "USD" }); @@ -333,8 +333,10 @@ export class WalletPortfolio extends BaseComponent { {_t("wallet-portfolio.points")}
- - {!global?.isMobile && showChart && } + + {!global?.isMobile && showChart && } - {!global?.isMobile && showChart && } + {!global?.isMobile && showChart && } - {!global?.isMobile && showChart && } + + )} - + this.handleLink("hive-power")}> @@ -369,13 +383,13 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h} - {!global?.isMobile && showChart && } + + )} - + this.handleLink("hive")}> @@ -404,9 +418,7 @@ export class WalletPortfolio extends BaseComponent { )} - + this.handleLink("hbd")}> @@ -435,9 +447,7 @@ export class WalletPortfolio extends BaseComponent { )} - + {!profileTokens ? ( @@ -504,68 +514,70 @@ export class WalletPortfolio extends BaseComponent { )} - {activeUser?.username === account.name && - - Tokens - - -
-
- this.setState({ search: e.target.value })} - style={{ width: "50%" }} - /> -
- {allTokens - ?.slice(0, 30) - .filter( - (list: any) => - list?.name.toLowerCase().startsWith(search) || - list?.name.toLowerCase().includes(search) - ) - .map((token: any, i: any) => { - const favoriteToken = - this.state.existingTokens.length > 0 - ? [...this.state.existingTokens, ...this.state.selectedTokens]?.find( - (favorite: any) => favorite.symbol === token.symbol - ) - : [...profileTokens, ...this.state.selectedTokens]?.find( - (favorite: any) => favorite.symbol === token.symbol - ); - return ( - - ); - })} -
- + {activeUser?.username === account.name && ( + + + Tokens + + +
+
+ this.setState({ search: e.target.value })} + style={{ width: "50%" }} + /> +
+ {allTokens + ?.slice(0, 30) + .filter( + (list: any) => + list?.name.toLowerCase().startsWith(search) || + list?.name.toLowerCase().includes(search) + ) + .map((token: any, i: any) => { + const favoriteToken = + this.state.selectedTokens?.length > 0 + ? [...this.state.selectedTokens, ...this.state.selectedTokens]?.find( + (favorite: any) => favorite.symbol === token.symbol + ) + : [...profileTokens, ...this.state.selectedTokens]?.find( + (favorite: any) => favorite.symbol === token.symbol + ); + return ( + + ); + })} +
+ +
-
- - } + + + )}
); } @@ -584,4 +596,27 @@ export default (p: Props) => { }; return ; -}; \ No newline at end of file +}; + +function mergeAndRemoveDuplicates( + arr1: HiveEngineToken[], + arr2: HiveEngineToken[] +): HiveEngineToken[] { + const mergedArray: HiveEngineToken[] = arr1.concat(arr2); + + const uniqueArray: HiveEngineToken[] = mergedArray.reduce( + (accumulator: HiveEngineToken[], current: HiveEngineToken) => { + const key = JSON.stringify(current); // Convert the object to a string for the key + + const existingToken = accumulator.find((token) => JSON.stringify(token) === key); + if (!existingToken) { + accumulator.push(current); + } + + return accumulator; + }, + [] + ); + + return uniqueArray; +} \ No newline at end of file
{_t("wallet-portfolio.price")}{_t("wallet-portfolio.change")}{_t("wallet-portfolio.trend")}{_t("wallet-portfolio.balance")}${estimatedPointsValue} - ---------- - - ---------- + --- + --- + {points.points} ${Number(estimatedPointsValue * Number(points.points)).toFixed(3)}
- ---------- - + --- + {totalHP} ${Number(totalHP * this.pricePerHive).toFixed(3)}
+ {!global?.isMobile && showChart && - {w.balance} ${Number(w.balance * this.pricePerHive).toFixed(3)}
+ {!global?.isMobile && showChart && - {w.hbdBalance} ${Number(w.hbdBalance * coingeckoData[1]?.current_price).toFixed(3)}
+ {!global?.isMobile && showChart &&
diff --git a/src/common/i18n/locales/en-US.json b/src/common/i18n/locales/en-US.json index ab9a6f65b9d..505f41cd5b4 100644 --- a/src/common/i18n/locales/en-US.json +++ b/src/common/i18n/locales/en-US.json @@ -158,7 +158,9 @@ "change": "% Change", "trend": "Trend", "balance": "Balance", - "value": "Value" + "value": "Value", + "show-trend": "Show trend", + "total-value": "Total Wallet Value:" }, "intro": { "title": "Aspire to greatness", From 29079561f5880d7b371d29bbb9f7c8bc9c6d9ac9 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Sat, 18 Feb 2023 12:31:52 +0100 Subject: [PATCH 12/21] chcekbox logics --- src/common/api/spk-api.ts | 6 ++-- .../components/engine-tokens-list/index.tsx | 11 +++++-- .../components/wallet-portfolio/index.tsx | 30 ++++++++++++------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/common/api/spk-api.ts b/src/common/api/spk-api.ts index 6a4008feb1f..5b0b1918a5f 100644 --- a/src/common/api/spk-api.ts +++ b/src/common/api/spk-api.ts @@ -125,13 +125,15 @@ export const getSpkWallet = async (username: string): Promise => { }; export const getMarketInfo = async (): Promise => { const resp = await axios.get(`${spkNode}/dex`); - console.log(resp.data) + // console.log(resp.data) return resp.data; }; export const getLarynxData = async () => { fetch(`https://spknode.blocktrades.us/dex`).then((data: any)=> data.json()) - .then((result: any) => console.log(result)) + .then((result: any) =>{ + // console.log(result) + }) } export const getMarkets = async (): Promise => { diff --git a/src/common/components/engine-tokens-list/index.tsx b/src/common/components/engine-tokens-list/index.tsx index a143c554772..f65bb5437c2 100644 --- a/src/common/components/engine-tokens-list/index.tsx +++ b/src/common/components/engine-tokens-list/index.tsx @@ -2,7 +2,9 @@ import React, { useEffect, useState} from 'react' import { _t } from '../../i18n'; const EngineTokensList = (props: any) => { - const { token, handleOnChange, ischecked } = props; + const { token, handleOnChange, ischecked, favoriteToken } = props; + + const [checked, setChecked] = useState(favoriteToken) return ( <> @@ -17,8 +19,11 @@ const EngineTokensList = (props: any) => { handleOnChange(e, token)} + checked={checked} + onChange={(e) => { + handleOnChange(e, token) + setChecked(!checked) + }} />
diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 401de4b56d6..564e71922bb 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -35,6 +35,7 @@ import EngineTokensList from "../engine-tokens-list"; import { Button, FormControl, Modal } from "react-bootstrap"; import { updateProfile } from "../../api/operations"; import { getSpkWallet, getMarketInfo, getLarynxData } from "../../api/spk-api"; +import { findIndex } from "lodash"; const hbdIcom = require("./asset/hbd.png") const ecencyIcon = require("./asset/ecency.jpeg") @@ -178,14 +179,17 @@ export class WalletPortfolio extends BaseComponent { const { selectedTokens, isChecked } = this.state; const userProfile = JSON.parse(account.posting_json_metadata); const userTokens = userProfile.profile.profileTokens - const isInProfile = userTokens?.map((item: any) => item.symbol === token.symbol) - const isSelected = selectedTokens.includes(token); + const isInProfile = userTokens?.filter((item: any) => item.symbol !== token.symbol) + const index = findIndex((item: any) => item.symbol !== token.symbol) + console.log(index) + // const isSelected = selectedTokens.includes(token); - if (e?.target?.checked && !isSelected && !isInProfile.includes(true)) { - this.setState({selectedTokens: [...selectedTokens, ...token], isChecked: e.target.checked}) - e.target.checked === true; + if (e?.target?.checked) { + this.setState({selectedTokens: [...userTokens, ...token]}) + // console.log([...selectedTokens, ...isInProfile]) } else { - this.setState({selectedTokens: [...selectedTokens], isChecked: e.target.checked}) + const newArray = selectedTokens.splice(index, 1) + this.setState({selectedTokens: [...newArray]}) } } @@ -196,7 +200,7 @@ export class WalletPortfolio extends BaseComponent { const userProfile = JSON.parse(account.posting_json_metadata); const { profile } = userProfile; let { profileTokens } = profile; - profileTokens = [...profileTokens, ...selectedTokens]; + profileTokens = [...selectedTokens]; const newPostMeta: any = {...profile, profileTokens} @@ -419,15 +423,19 @@ export class WalletPortfolio extends BaseComponent { {allTokens?.slice(0, 30).filter((list: any) => list?.name.toLowerCase().startsWith(search) || list?.name.toLowerCase().includes(search) - ).map((token: any, i: any) =>( - { + const favoriteToken = profileTokens?.find((favorite: any) => favorite.symbol === token.symbol ) + // console.log(token) + // console.log(favoriteToken) + return ( - ))} + favoriteToken={favoriteToken} + />) + })}
-
} - - - - Tokens - - - -
-
- this.setState({search: e.target.value})} - style={{width: "50%"}} - /> + {activeUser?.username === account.name && ( +
+
- {allTokens?.slice(0, 30).filter((list: any) => - list?.name.toLowerCase().startsWith(search) || + )} + + + Tokens + + +
+
+ this.setState({ search: e.target.value })} + style={{ width: "50%" }} + /> +
+ {allTokens + ?.slice(0, 30) + .filter( + (list: any) => + list?.name.toLowerCase().startsWith(search) || list?.name.toLowerCase().includes(search) - ).map((token: any, i: any) =>{ - const favoriteToken = profileTokens?.find((favorite: any) => favorite.symbol === token.symbol ) - // console.log(token) - // console.log(favoriteToken) - return () - })} -
- -
-
-
-
+ ) + .map((token: any, i: any) => { + const favoriteToken = + this.state.existingTokens.length > 0 + ? [...this.state.existingTokens, ...this.state.selectedTokens]?.find( + (favorite: any) => favorite.symbol === token.symbol + ) + : [...profileTokens, ...this.state.selectedTokens]?.find( + (favorite: any) => favorite.symbol === token.symbol + ); + // console.log(token) + // console.log(favoriteToken) + return ( + + ); + })} +
+ +
+
+ +
); } @@ -461,8 +546,8 @@ export default (p: Props) => { updateActiveUser: p.updateActiveUser, updateWalletValues: p.updateWalletValues, history: p.history, - points: p.points, + points: p.points }; return ; -}; +}; \ No newline at end of file From ecf9e74da718ccf80366a256d50ff27bbffd5a2f Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Sat, 4 Mar 2023 05:25:31 +0100 Subject: [PATCH 14/21] transactions filtering --- src/common/api/hive-engine.ts | 4 +- .../hive-engine-transactions/index.tsx | 119 ++++++++++++++---- 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/src/common/api/hive-engine.ts b/src/common/api/hive-engine.ts index 87621a10e10..6782b31ebe7 100644 --- a/src/common/api/hive-engine.ts +++ b/src/common/api/hive-engine.ts @@ -192,7 +192,7 @@ export const getMarketData = async (symbol: any) => { export async function getTransactions(symbol: string, account: string, limit: number, offset?: number): Promise { const url: any = engine.mainTransactionUrl; return axios({ - url: url, + url, method: "GET", params: { account, @@ -208,7 +208,7 @@ export async function getTransactions(symbol: string, account: string, limit: nu export async function getOtherTransactions(account: string, limit: number, symbol: string, offset: number = 0) { const url: any = engine.otherTransactionsUrl; const response = await axios({ - url: url, + url, method: "GET", params: { account, diff --git a/src/common/components/hive-engine-transactions/index.tsx b/src/common/components/hive-engine-transactions/index.tsx index f11a9ec10f7..1d3e028a6f1 100644 --- a/src/common/components/hive-engine-transactions/index.tsx +++ b/src/common/components/hive-engine-transactions/index.tsx @@ -3,6 +3,10 @@ import { Button, FormControl } from 'react-bootstrap' import { _t } from '../../i18n' import { cashCoinSvg } from "../../img/svg"; import TwoUserAvatar from "../two-user-avatar"; +import { OperationGroup, Transaction, Transactions } from "../../store/transactions/types"; +import { usePrevious } from "../../util/use-previous"; +// import { fetchTransactions } from "../../../../store/transactions/fetchTransactions"; +import { fetchTransactions } from '../../store/transactions'; import { getTransactions, @@ -10,25 +14,65 @@ import { } from "../../api/hive-engine"; import LinearProgress from '../linear-progress'; import { dateToFullRelative } from "../../helper/parse-date"; +import { DynamicProps } from '../../store/dynamic-props/types'; +import { Account } from '../../store/accounts/types'; -export const EngineTransactionList = (props: any) => { - - const { global, account, params } = props +interface Props { + history: History; + global: Global; + dynamicProps: DynamicProps; + transactions: Transactions; + account: Account; + // fetchTransactions: ( + // username: string, + // group?: OperationGroup | "", + // start?: number, + // limit?: number + // ) => void; +} - const [transactions, setTransactions] = useState([]) - const [otherTransactions, setOtherTransactions] = useState([]) - const [loading, setLoading] = useState(false); - const [loadLimit, setLoadLimit] = useState(10) +export const EngineTransactionList = (props: any) => { + + const { global, account, params } = props + const [loadingLoadMore, setLoadingLoadMore] = useState(false); + const [transactionsList, setTransactionsList] = useState([]); + const [otherTransactions, setOtherTransactions] = useState([]) + const [loading, setLoading] = useState(false); + const [loadLimit, setLoadLimit] = useState(10) + + const previousTransactions = usePrevious(props.transactions); + useEffect(() => { - otherTokenTransactions(); - getMainTransactions(); - }, []) - + const { account, + // fetchTransactions + } = props; + account && account.name && fetchTransactions(account.name); + otherTokenTransactions(); + getMainTransactions(); + }, []); + + useEffect(() => { + const { transactions } = props; + if (previousTransactions && previousTransactions.list !== transactions.list) { + const txs = [ + ...(previousTransactions?.group === transactions?.group ? transactionsList : []), + ...transactions.list + ]; + console.log(txs) + const uniqueTxs = [...new Map(txs.map((item) => [item["num"], item])).values()]; + console.log(uniqueTxs) + setOtherTransactions(uniqueTxs); + } + }, [props.transactions]); + const getMainTransactions = async () => { - const transactions = await getTransactions(params, account.name, 200); + const transactions = await getTransactions(params.toUpperCase(), account.name, 200); + const otherTransactions = await getOtherTransactions(account.name, 200, params.toUpperCase()); + const mappedTransactions = [...transactions, ...otherTransactions] + const test = mappedTransactions.sort((a: any, b:any) => a.timestamp - b.timestamp) console.log(transactions) - + // console.log(otherTransactions) }; const otherTokenTransactions = async () => { @@ -49,15 +93,26 @@ export const EngineTransactionList = (props: any) => { setLoadLimit(moreItems); }; - const optionChanged = (e: React.ChangeEvent) => { - console.log(e.target) - } + const optionChanged = (e: React.ChangeEvent) => { + const { account, + // fetchTransactions + } = props; + const group: any = e.target.value; + + // setLoadingLoadMore(loadingLoadMore); + setOtherTransactions(otherTransactions); + console.log(otherTransactions) + + console.log(group) + + fetchTransactions(account.name, group as OperationGroup); + }; return (

{_t("transactions.title")}

- + {["transfers", "market-orders", "interests", "stake-operations", "rewards"].map((x) => (
- {loading && } {otherTransactions?.slice(0, loadLimit).map((t: any) => { return ( otherTransactions?.length === 0 ?

{_t("g.empty-list")}

: @@ -100,10 +154,29 @@ export const EngineTransactionList = (props: any) => {
) })} - {!loading && otherTransactions.length > loadLimit && - } + + )} - ) -} + ); +}; +export default (p: Props) => { + const props: Props = { + history: p.history, + global: p.global, + dynamicProps: p.dynamicProps, + transactions: p.transactions, + account: p.account, + // fetchTransactions: p.fetchTransactions + }; + + return ; +}; From 43480a1e53cde5b1a13cae0ef48e4dad24dbe1de Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Mon, 13 Mar 2023 13:05:28 +0100 Subject: [PATCH 15/21] filter transactions --- .../components/engine-tokens-list/index.tsx | 4 +- .../hive-engine-transactions/index.tsx | 131 ++++++++++-------- .../components/wallet-portfolio/index.tsx | 55 ++++++-- src/common/i18n/locales/en-US.json | 7 +- 4 files changed, 122 insertions(+), 75 deletions(-) diff --git a/src/common/components/engine-tokens-list/index.tsx b/src/common/components/engine-tokens-list/index.tsx index 135c85715f0..bc2a28042b7 100644 --- a/src/common/components/engine-tokens-list/index.tsx +++ b/src/common/components/engine-tokens-list/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState} from 'react' import { _t } from '../../i18n'; const EngineTokensList = (props: any) => { - const { token, handleOnChange, isChecked, favoriteToken } = props; + const { token, handleChange, isChecked, favoriteToken } = props; const [checked, setChecked] = useState(favoriteToken) @@ -22,7 +22,7 @@ const EngineTokensList = (props: any) => { checked={checked ? true : false} onChange={(e) => { console.log(e.target.checked, checked) - handleOnChange(e.target.checked, token); + handleChange(e.target.checked, token); setChecked(!checked) }} /> diff --git a/src/common/components/hive-engine-transactions/index.tsx b/src/common/components/hive-engine-transactions/index.tsx index 1d3e028a6f1..09240c2aa71 100644 --- a/src/common/components/hive-engine-transactions/index.tsx +++ b/src/common/components/hive-engine-transactions/index.tsx @@ -32,46 +32,28 @@ interface Props { } export const EngineTransactionList = (props: any) => { - - const { global, account, params } = props - const [loadingLoadMore, setLoadingLoadMore] = useState(false); - const [transactionsList, setTransactionsList] = useState([]); - const [otherTransactions, setOtherTransactions] = useState([]) - const [loading, setLoading] = useState(false); - const [loadLimit, setLoadLimit] = useState(10) - - const previousTransactions = usePrevious(props.transactions); - - useEffect(() => { - const { account, - // fetchTransactions - } = props; - account && account.name && fetchTransactions(account.name); - otherTokenTransactions(); - getMainTransactions(); - }, []); - + const { global, account, params } = props + + const [transactions, setTransactions] = useState([]) + const [otherTransactions, setOtherTransactions] = useState([]) + const [loading, setLoading] = useState(false); + const [loadLimit, setLoadLimit] = useState(10) + const [transactionsList, setTransactionsList] = useState([]); + const [filtered, setFiltered] = useState([]) + const previousTransactions = usePrevious(props.transactions); + useEffect(() => { - const { transactions } = props; - if (previousTransactions && previousTransactions.list !== transactions.list) { - const txs = [ - ...(previousTransactions?.group === transactions?.group ? transactionsList : []), - ...transactions.list - ]; - console.log(txs) - const uniqueTxs = [...new Map(txs.map((item) => [item["num"], item])).values()]; - console.log(uniqueTxs) - setOtherTransactions(uniqueTxs); - } - }, [props.transactions]); + otherTokenTransactions(); + getMainTransactions(); + }, []) const getMainTransactions = async () => { const transactions = await getTransactions(params.toUpperCase(), account.name, 200); const otherTransactions = await getOtherTransactions(account.name, 200, params.toUpperCase()); const mappedTransactions = [...transactions, ...otherTransactions] const test = mappedTransactions.sort((a: any, b:any) => a.timestamp - b.timestamp) - console.log(transactions) + // console.log(transactions) // console.log(otherTransactions) }; @@ -93,35 +75,71 @@ export const EngineTransactionList = (props: any) => { setLoadLimit(moreItems); }; - const optionChanged = (e: React.ChangeEvent) => { - const { account, - // fetchTransactions - } = props; - const group: any = e.target.value; - - // setLoadingLoadMore(loadingLoadMore); - setOtherTransactions(otherTransactions); - console.log(otherTransactions) - - console.log(group) + const optionChanged = (e: React.ChangeEvent) => { + const { account, + // fetchTransactions + } = props; + const group: string = e.target.value; + + const filterTransfers = otherTransactions.filter((trx: any) => trx.operation === "tokens_transfer") + const filterDelegate = otherTransactions.filter((trx: any) => trx.operation === "tokens_delegate") + const filterStakes = otherTransactions.filter((trx: any) => trx.operation === "tokens_unstakeDone" || + trx.operation === "tokens_stake" || trx.operation === "tokens_unstakeStart") - fetchTransactions(account.name, group as OperationGroup); - }; + group === "transfers" ? setFiltered(filterTransfers) : + group === "stake-operations" ? setFiltered(filterStakes) : + group === "delegate" ? setFiltered(filterDelegate) : + group === "" ? setFiltered(otherTransactions) : null + // fetchTransactions(account.name, group as OperationGroup); + }; return (

{_t("transactions.title")}

- + - {["transfers", "market-orders", "interests", "stake-operations", "rewards"].map((x) => ( + {["transfers", "market-orders", "interests", "stake-operations", "rewards", "delegate"].map((x) => ( ))}
- {otherTransactions?.slice(0, loadLimit).map((t: any) => { + {loading && } + { filtered.length > 0 ? filtered?.slice(0, loadLimit).map((t: any) => { + return ( + filtered?.length === 0 ?

{_t("g.empty-list")}

: +
+
+ {t?.operation === "tokens_transfer" || t?.operation === "tokens_stake" || t?.operation === "tokens_delegate" ? + TwoUserAvatar({ global: global, from: t?.from, to: t?.to, size: "small" }) : + cashCoinSvg } +
+
+
{t?.operation.replace("_", " ")}
+
{getTransactionTime(t?.timestamp)}
+
+
{`${t?.quantity} ${t?.symbol}`}
+
+ {t?.memo} +

+ {t?.operation === "tokens_transfer" ? + + @{t.from} -> @{t.to} + : + + {`Txn Id: ${t.transactionId}`} +

+ {`Block Id: ${t.blockNumber}`} +

+ + } +

+
+
+ ) + }) : otherTransactions?.slice(0, loadLimit).map((t: any) => { return ( otherTransactions?.length === 0 ?

{_t("g.empty-list")}

:
@@ -154,20 +172,13 @@ export const EngineTransactionList = (props: any) => {
) })} - {!props.transactions?.loading && transactionsList?.length === 0 && ( -

{_t("g.empty-list")}

- )} - {!props.transactions?.loading && - !props.transactions?.loading && - props.transactions?.list.length > 0 && - transactionsList?.length > 0 && ( - - )} + }
- ); -}; + ) +} export default (p: Props) => { const props: Props = { history: p.history, diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 99c1a7b6657..78160971d97 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -168,7 +168,7 @@ export class WalletPortfolio extends BaseComponent { this.setState({ showTokenList: true }); }; - handleOnChange = (e: any, token: any) => { + handleChange = (e: any, token: any) => { const { account } = this.props; const { selectedTokens, isChecked } = this.state; const json_data = JSON.parse(account.posting_json_metadata); @@ -256,6 +256,14 @@ export class WalletPortfolio extends BaseComponent { this.setState({ showChart: e.target.checked }); }; + formatCurrency = (price: number | string) => { + const formatted = price.toLocaleString("en-US", { + style: "currency", + currency: "USD" + }); + return formatted + } + render() { const { global, dynamicProps, account, points, activeUser } = this.props; const { @@ -273,12 +281,35 @@ export class WalletPortfolio extends BaseComponent { const profileTokens: any = account?.profile?.profileTokens; const w = new HiveWallet(account, dynamicProps, converting); + console.log(w) + const totalHP: any = formattedNumber(vestsToHp(w.vestingShares, hivePerMVests)); + const profiletokenValues = profileTokens?.map((w: any) => { + return w.symbol === "SWAP.HIVE" + ? Number(this.pricePerHive * w.balance) + : w.lastPrice === 0 + ? 0 + : Number(w.lastPrice * this.pricePerHive * w.balance); + }); + + const totalProfileTokensValue = profiletokenValues?.reduce((x: any, y: any) => { + const totalValue = +(x + y).toFixed(3); + return totalValue; + }, 0); + + const totalHPValue = Number(totalHP * this.pricePerHive) + const totalPointsValue = Number(estimatedPointsValue * Number(points.points)) + const totalHiveValue = Number(w.balance * this.pricePerHive) + const totalHbdValue = Number(w.hbdBalance * coingeckoData[1]?.current_price) + const estimatedTotal = Number(totalHPValue + totalHiveValue + totalPointsValue + totalHbdValue + totalProfileTokensValue) + return (
- {_t("wallet-portfolio.total-value")} $1.19 + {_t("wallet-portfolio.total-value")} {this.formatCurrency(estimatedTotal)} + {/* Hive Savings: {w.savingBalance}Hive + Hbd Savings: {this.formatCurrency(w.savingBalanceHbd)} */}
{_t("wallet-portfolio.show-trend")}
- ECENCY POINT + {_t("wallet-portfolio.points")} ${estimatedPointsValue} ------{points.points} - ${Number(estimatedPointsValue * Number(points.points)).toFixed(3)} + {this.formatCurrency(totalPointsValue)}
- HIVE-POWER + {_t("wallet-portfolio.hive-power")} ${this.pricePerHive} { {!global?.isMobile && showChart && ---{totalHP} - ${Number(totalHP * this.pricePerHive).toFixed(3)} + {this.formatCurrency(totalHPValue)}
- HIVE + {_t("wallet-portfolio.hive")} ${this.pricePerHive} { )} {w.balance} - ${Number(w.balance * this.pricePerHive).toFixed(3)} + {this.formatCurrency(totalHiveValue)}
- HBD + {_t("wallet-portfolio.hbd")} ${coingeckoData[1]?.current_price} { )} {w.hbdBalance} - ${Number(w.hbdBalance * coingeckoData[1]?.current_price).toFixed(3)} + {this.formatCurrency(totalHbdValue)}
- ${a.usd_value} + {this.formatCurrency(a.usd_value)}
${estimatedPointsValue}------{priceUpSvg}0.00% + + {points.points} {this.formatCurrency(totalPointsValue)} @@ -361,7 +363,9 @@ export class WalletPortfolio extends BaseComponent { {coingeckoData[0]?.price_change_percentage_24h} --- + + {totalHP} {this.formatCurrency(totalHPValue)} @@ -494,7 +498,7 @@ export class WalletPortfolio extends BaseComponent { )} - { - + } ); } From a38aee5e5f614dfe1b5a7e919bdf3df226524d9a Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Tue, 4 Apr 2023 16:45:54 +0100 Subject: [PATCH 20/21] fixed wallet path --- .../components/wallet-portfolio/index.tsx | 8 ++++- src/common/pages/profile-functional.tsx | 36 +++++++++---------- src/common/routes.ts | 4 +-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 32576293d55..968992698a8 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -29,6 +29,12 @@ import { updateProfile } from "../../api/operations"; import { getSpkWallet, getMarketInfo, getLarynxData } from "../../api/spk-api"; import { findIndex } from "lodash"; import "./index.scss"; +// Needs to keep the styles in order, can later be simplified or added to a single directory +import "../wallet-hive/_index.scss"; +import "../wallet-ecency/_index.scss"; +import "../wallet-hive-engine/_index.scss"; +import "../wallet-spk/wallet-spk-delegated-power-dialog.scss"; +import "../wallet-spk/wallet-spk-dialog.scss"; const hbdIcom = require("./asset/hbd.png"); const ecencyIcon = require("./asset/ecency.jpeg"); @@ -158,7 +164,7 @@ export class WalletPortfolio extends BaseComponent { }; handleLink(symbol: string) { - this.props.history.push(`portfolio/${symbol.toLowerCase()}`); + this.props.history.push(`wallet/${symbol.toLowerCase()}`); } hideList = () => { diff --git a/src/common/pages/profile-functional.tsx b/src/common/pages/profile-functional.tsx index 3c27505583f..a8da8fcdf32 100644 --- a/src/common/pages/profile-functional.tsx +++ b/src/common/pages/profile-functional.tsx @@ -495,22 +495,22 @@ export const Profile = (props: Props) => { ) : ( <> {(() => { - if (section === "wallet") { - return WalletHive({ ...props, account, updateWalletValues: ensureAccount }); - } - if (section === "engine") { - return WalletHiveEngine({ ...props, account, updateWalletValues: ensureAccount }); - } - if (section === "spk") { - return WalletSpk({ - ...props, - account, - isActiveUserWallet: account.name === props.activeUser?.username - }); - } - if (section === "points") { - return WalletEcency({ ...props, account, updateWalletValues: ensureAccount }); - } + // if (section === "wallet") { + // return WalletHive({ ...props, account, updateWalletValues: ensureAccount }); + // } + // if (section === "engine") { + // return WalletHiveEngine({ ...props, account, updateWalletValues: ensureAccount }); + // } + // if (section === "spk") { + // return WalletSpk({ + // ...props, + // account, + // isActiveUserWallet: account.name === props.activeUser?.username + // }); + // } + // if (section === "points") { + // return WalletEcency({ ...props, account, updateWalletValues: ensureAccount }); + // } if (section === "communities") { return ProfileCommunities({ ...props, account }); } @@ -520,8 +520,8 @@ export const Profile = (props: Props) => { if (section === "referrals") { return ProfileReferrals({ ...props, account, updateWalletValues: ensureAccount }); } - if (section === "portfolio") { - if (symbol && section === "portfolio") { + if (section === "wallet") { + if (symbol && section === "wallet") { return TokenDetails({ ...props, account, updateWalletValues: ensureAccount }); } return WalletPortfolio({ ...props, account, updateWalletValues: ensureAccount }); diff --git a/src/common/routes.ts b/src/common/routes.ts index 1cfd7f8b575..29847be1acf 100644 --- a/src/common/routes.ts +++ b/src/common/routes.ts @@ -23,7 +23,7 @@ export default { USER_FEED: `/:username(@[\\w\\.\\d-]+)/:section(feed)`, USER_SECTION: `/:username(@[\\w\\.\\d-]+)/:section(${profileFilters.join( "|" - )}|wallet|points|engine|communities|settings|permissions|referrals|followers|following|portfolio|spk)`, + )}|communities|settings|permissions|referrals|followers|following|wallet)`, COMMUNITIES: `/communities`, COMMUNITIES_CREATE: `/communities/create`, COMMUNITIES_CREATE_HS: `/communities/create-hs`, @@ -38,5 +38,5 @@ export default { PROPOSALS: `/proposals`, PROPOSAL_DETAIL: `/proposals/:id(\\d+)`, PURCHASE: "/purchase", - TOKEN_DETAIL: `/:username(@[\\w\\.\\d-]+)/:section(portfolio)/:symbol` + TOKEN_DETAIL: `/:username(@[\\w\\.\\d-]+)/:section(wallet)/:symbol` }; From 23fbe1ed5072efda217cea242a1339a285058b94 Mon Sep 17 00:00:00 2001 From: Adesojisouljay Date: Mon, 29 May 2023 16:59:57 +0100 Subject: [PATCH 21/21] bug fixes --- .../components/engine-tokens-list/index.tsx | 7 +- .../hive-engine-transactions/index.tsx | 9 +- src/common/components/token-details/index.tsx | 14 +- .../wallet-engine-details/index.tsx | 10 +- .../components/wallet-portfolio/index.tsx | 273 ++++++++++-------- 5 files changed, 169 insertions(+), 144 deletions(-) diff --git a/src/common/components/engine-tokens-list/index.tsx b/src/common/components/engine-tokens-list/index.tsx index 79b62a343d7..998a73204d0 100644 --- a/src/common/components/engine-tokens-list/index.tsx +++ b/src/common/components/engine-tokens-list/index.tsx @@ -3,7 +3,7 @@ import { _t } from '../../i18n'; import "./index.scss"; const EngineTokensList = (props: any) => { - const { token, handleChange, isChecked, favoriteToken } = props; + const { token, handleChange, i, favoriteToken } = props; const [checked, setChecked] = useState(favoriteToken) @@ -13,16 +13,15 @@ const EngineTokensList = (props: any) => {
- {token.name} + {token?.name}
{ - console.log(e.target.checked, checked) handleChange(e.target.checked, token); setChecked(!checked) }} diff --git a/src/common/components/hive-engine-transactions/index.tsx b/src/common/components/hive-engine-transactions/index.tsx index 09240c2aa71..b0062ea7b19 100644 --- a/src/common/components/hive-engine-transactions/index.tsx +++ b/src/common/components/hive-engine-transactions/index.tsx @@ -53,14 +53,11 @@ export const EngineTransactionList = (props: any) => { const otherTransactions = await getOtherTransactions(account.name, 200, params.toUpperCase()); const mappedTransactions = [...transactions, ...otherTransactions] const test = mappedTransactions.sort((a: any, b:any) => a.timestamp - b.timestamp) - // console.log(transactions) - // console.log(otherTransactions) }; const otherTokenTransactions = async () => { setLoading(true) const otherTransactions = await getOtherTransactions(account.name, 200, params.toUpperCase()); - console.log(otherTransactions) setOtherTransactions(otherTransactions); setLoading(false) } @@ -99,8 +96,8 @@ export const EngineTransactionList = (props: any) => {

{_t("transactions.title")}

- {["transfers", "market-orders", "interests", "stake-operations", "rewards", "delegate"].map((x) => ( - ))} @@ -190,4 +187,4 @@ export default (p: Props) => { }; return ; -}; +}; \ No newline at end of file diff --git a/src/common/components/token-details/index.tsx b/src/common/components/token-details/index.tsx index 48d03c2cdfa..6e0c120ad38 100644 --- a/src/common/components/token-details/index.tsx +++ b/src/common/components/token-details/index.tsx @@ -113,9 +113,6 @@ export class TokenDetails extends BaseComponent { }; componentDidMount() { - // const params: any = useParams(); - // console.log(params) - console.log(window.location.href.split('/')[5]) this.fetchConvertingAmount(); this.fetchCollateralizedConvertingAmount(); this.fetchWithdrawFromSavings(); @@ -336,7 +333,6 @@ export class TokenDetails extends BaseComponent { const { hivePerMVests, hbdInterestRate } = dynamicProps; const isMyPage = activeUser && activeUser.username === account.name; const w = new HiveWallet(account, dynamicProps, converting); - // console.log(w) const params: string = window.location.href.split('/')[5] const lastIPaymentRelative = @@ -474,7 +470,7 @@ export class TokenDetails extends BaseComponent {
)} - {openOrders && openOrders.hive > 0 && ( + {openOrders && Number(openOrders.hive) > 0 && (
{
)} - {withdrawSavings && withdrawSavings.hive > 0 && ( + {withdrawSavings && Number(withdrawSavings.hive) > 0 && (
{
)} - {withdrawSavings && withdrawSavings.hbd > 0 && ( + {withdrawSavings && Number(withdrawSavings.hbd) > 0 && (
{
)} - {openOrders && openOrders.hbd > 0 && ( + {openOrders && Number(openOrders.hbd) > 0 && (
this.toggleOpenOrdersList("HBD")}> @@ -982,4 +978,4 @@ export default (p: Props) => { }; return ; -}; +}; \ No newline at end of file diff --git a/src/common/components/wallet-engine-details/index.tsx b/src/common/components/wallet-engine-details/index.tsx index 0d596dd1a32..7b6f6e0938f 100644 --- a/src/common/components/wallet-engine-details/index.tsx +++ b/src/common/components/wallet-engine-details/index.tsx @@ -2,11 +2,10 @@ import React from "react"; import { Global } from "../../store/global/types"; import { Account } from "../../store/accounts/types"; import { DynamicProps } from "../../store/dynamic-props/types"; -import { OperationGroup, Transactions } from "../../store/transactions/types"; +import { Transactions } from "../../store/transactions/types"; import { ActiveUser } from "../../store/active-user/types"; import BaseComponent from "../base"; import HiveEngineToken from "../../helper/hive-engine-wallet"; -import LinearProgress from "../linear-progress"; import Transfer, { TransferMode } from "../transfer-he"; import { error, success } from "../feedback"; import DropDown from "../dropdown"; @@ -181,7 +180,7 @@ export class EngineTokenDetails extends BaseComponent {
{rewards?.map((r, i) => { const reward: any = r?.pending_token / Math.pow(10, r?.precision); - return ( r?.symbol === params.toUpperCase() && + return ( r?.symbol === params?.toUpperCase() &&
{_t("wallet.unclaimed-rewards")}
@@ -209,7 +208,7 @@ export class EngineTokenDetails extends BaseComponent { )} {tokens.map((t, i) => { - return ( t?.symbol === params.toUpperCase() && + return ( t?.symbol === params?.toUpperCase() &&
@@ -429,5 +428,4 @@ export default (p: Props) => { }; return ; - }; - \ No newline at end of file + }; \ No newline at end of file diff --git a/src/common/components/wallet-portfolio/index.tsx b/src/common/components/wallet-portfolio/index.tsx index 968992698a8..bd1ebe99fff 100644 --- a/src/common/components/wallet-portfolio/index.tsx +++ b/src/common/components/wallet-portfolio/index.tsx @@ -21,7 +21,11 @@ import { HiveEngineChart } from "../hive-engine-chart"; import { History } from "history"; import { vestsToHp } from "../../helper/vesting"; import { marketInfo } from "../../api/misc"; -import { HiveWalletPortfolioChart, HbdWalletPortfolioChart, DefaultPortfolioChart } from "../wallet-portfolio-chart"; +import { + HiveWalletPortfolioChart, + HbdWalletPortfolioChart, + DefaultPortfolioChart +} from "../wallet-portfolio-chart"; import { getCurrencyTokenRate } from "../../api/private-api"; import EngineTokensList from "../engine-tokens-list"; import { Button, FormControl, Modal } from "react-bootstrap"; @@ -65,7 +69,6 @@ interface State { showTokenList: boolean; favoriteTokens: HiveEngineToken[] | any; selectedTokens: HiveEngineToken[] | any; - existingTokens: HiveEngineToken[] | any; isChecked: boolean; tokenBalance: number; larynxTokenBalance: number; @@ -87,7 +90,6 @@ export class WalletPortfolio extends BaseComponent { showTokenList: false, favoriteTokens: [], selectedTokens: [], - existingTokens: [], isChecked: false, tokenBalance: 0, larynxTokenBalance: 0, @@ -103,6 +105,7 @@ export class WalletPortfolio extends BaseComponent { this._isMounted && this.dataFromCoinGecko(); this._isMounted && this.getEstimatedPointsValue(); this._isMounted && this.getSpkTokens(); + this._isMounted && this.setExistingProfileTokens(); } componentWillUnmount() { @@ -130,7 +133,7 @@ export class WalletPortfolio extends BaseComponent { }; }); - let tokensUsdValues: any = balanceMetrics?.map((w: any) => { + let engineTokens: any = balanceMetrics?.map((w: any) => { const usd_value = w?.symbol === "SWAP.HIVE" ? Number(this.pricePerHive * w.balance) @@ -142,10 +145,17 @@ export class WalletPortfolio extends BaseComponent { usd_value }; }); - this.setState({ allTokens: [...tokensUsdValues, ...spkTokens] }); - return tokensUsdValues; + this.setState({ allTokens: [...engineTokens, ...spkTokens] }); + // return engineTokens; }; + setExistingProfileTokens = () => { + this._isMounted && + this.setState({ + selectedTokens: JSON.parse(this.props.account?.posting_json_metadata)?.profile?.profileTokens || [] + }); + } + getEstimatedPointsValue = () => { const { global: { currency } @@ -180,43 +190,43 @@ export class WalletPortfolio extends BaseComponent { const { selectedTokens, isChecked } = this.state; const json_data = JSON.parse(account.posting_json_metadata); let userProfile = json_data?.profile; - const { profileTokens } = userProfile; + let { profileTokens } = userProfile; const index = findIndex(profileTokens, (t: any) => t?.symbol === token?.symbol); - if (index > 0) { - profileTokens.splice(index, 1); + if (index > -1) { + profileTokens = profileTokens.filter((t: any) => t?.symbol !== token?.symbol); } + this.setState((prevState) => { return { isChecked: e ? true : false, selectedTokens: !e ? selectedTokens.filter((t: any) => t?.symbol !== token?.symbol) : [...prevState?.selectedTokens, token], - existingTokens: profileTokens }; }); }; addToProfile = async () => { const { account } = this.props; - const { selectedTokens, existingTokens } = this.state; + const { selectedTokens } = this.state; - const spkTokens = await this.getSpkTokens(); + // const spkTokens = await this.getSpkTokens(); const json_data = JSON.parse(account.posting_json_metadata); let userProfile = json_data?.profile; - if (userProfile) { - userProfile = json_data?.profile; - } else { - userProfile = { profileTokens: [] }; + if (!userProfile.profileTokens) { + userProfile.profileTokens = []; } const newPostMeta: any = { ...userProfile, - profileTokens: [...existingTokens, ...selectedTokens] + profileTokens: mergeAndRemoveDuplicates([], selectedTokens), }; + //TODO: update UI state + await updateProfile(account, newPostMeta); }; @@ -225,12 +235,13 @@ export class WalletPortfolio extends BaseComponent { const marketData = await getMarketInfo(); const larynxData = await getLarynxData(); const spkTokens = [ - { - spk: wallet.spk / 1000, - type: "Spk", - name: "SPK", - icon: spkIcon, - symbol: "SPK" }, + { + spk: wallet.spk / 1000, + type: "Spk", + name: "SPK", + icon: spkIcon, + symbol: "SPK" + }, { larynx: wallet.balance / 1000, type: "Spk", @@ -238,12 +249,12 @@ export class WalletPortfolio extends BaseComponent { icon: spkIcon, symbol: "LARYNX" }, - { - lp: wallet.poweredUp / 1000, - type: "Spk", - name: "LP", - icon: spkIcon, - symbol: "LP" + { + lp: wallet.poweredUp / 1000, + type: "Spk", + name: "LP", + icon: spkIcon, + symbol: "LP" } ]; @@ -264,8 +275,8 @@ export class WalletPortfolio extends BaseComponent { style: "currency", currency: "USD" }); - return formatted - } + return formatted; + }; render() { const { global, dynamicProps, account, points, activeUser } = this.props; @@ -277,15 +288,14 @@ export class WalletPortfolio extends BaseComponent { search, showTokenList, isChecked, - showChart + showChart, + selectedTokens } = this.state; const { hivePerMVests } = dynamicProps; - const profileTokens: any = account?.profile?.profileTokens; + const profileTokens: any = [...selectedTokens] || []; const w = new HiveWallet(account, dynamicProps, converting); - console.log(w) - const totalHP: any = formattedNumber(vestsToHp(w.vestingShares, hivePerMVests)); const profiletokenValues = profileTokens?.map((w: any) => { @@ -301,16 +311,20 @@ export class WalletPortfolio extends BaseComponent { return totalValue; }, 0); - const totalHPValue = Number(totalHP * this.pricePerHive) - const totalPointsValue = Number(estimatedPointsValue * Number(points.points)) - const totalHiveValue = Number(w.balance * this.pricePerHive) - const totalHbdValue = Number(w.hbdBalance * coingeckoData[1]?.current_price) - const estimatedTotal = Number(totalHPValue + totalHiveValue + totalPointsValue + totalHbdValue + totalProfileTokensValue) + const totalHPValue = Number(totalHP * this.pricePerHive); + const totalPointsValue = Number(estimatedPointsValue * Number(points.points)); + const totalHiveValue = Number(w.balance * this.pricePerHive); + const totalHbdValue = Number(w.hbdBalance * coingeckoData[1]?.current_price); + const estimatedTotal = Number( + totalHPValue + totalHiveValue + totalPointsValue + totalHbdValue + totalProfileTokensValue + ); return (
- {_t("wallet-portfolio.total-value")} {this.formatCurrency(estimatedTotal)} + + {_t("wallet-portfolio.total-value")} {this.formatCurrency(estimatedTotal)} +
{_t("wallet-portfolio.show-trend")}
${estimatedPointsValue} {priceUpSvg}0.00% + {!global?.isMobile && showChart && ( + - {points.points} - {this.formatCurrency(totalPointsValue)} - {this.formatCurrency(totalPointsValue)}
+ {!global?.isMobile && showChart && ( + - {totalHP} - {this.formatCurrency(totalHPValue)} - {this.formatCurrency(totalHPValue)}
{w.balance} - {this.formatCurrency(totalHiveValue)} - {this.formatCurrency(totalHiveValue)}
{w.hbdBalance} - {this.formatCurrency(totalHbdValue)} - {this.formatCurrency(totalHbdValue)}