diff --git a/src/common/api/hive-engine.ts b/src/common/api/hive-engine.ts index 3d367a337b0..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" } @@ -188,3 +183,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, + 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, + method: "GET", + params: { + account, + limit, + offset, + type: "user", + symbol + } + }); + return response.data; +} \ No newline at end of file diff --git a/src/common/api/misc.ts b/src/common/api/misc.ts index 5b13768e72d..d2b4b5b39d3 100644 --- a/src/common/api/misc.ts +++ b/src/common/api/misc.ts @@ -71,3 +71,17 @@ 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) + 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) + return data; +}; diff --git a/src/common/api/spk-api.ts b/src/common/api/spk-api.ts index 337cc2a8412..04b5816bc39 100644 --- a/src/common/api/spk-api.ts +++ b/src/common/api/spk-api.ts @@ -123,6 +123,18 @@ 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/app.tsx b/src/common/app.tsx index d2a8ac7e09a..5cd23307bb7 100644 --- a/src/common/app.tsx +++ b/src/common/app.tsx @@ -143,6 +143,12 @@ const App = (props: any) => { path={routes.PROPOSAL_DETAIL} component={ProposalDetailContainer} /> + diff --git a/src/common/components/community-card/index.tsx b/src/common/components/community-card/index.tsx index e75734be5e9..106d0f18cb2 100644 --- a/src/common/components/community-card/index.tsx +++ b/src/common/components/community-card/index.tsx @@ -76,7 +76,6 @@ export class EditPic extends BaseComponent { const { addAccount, onUpdate } = this.props; const { profile } = account; - const newProfile = { profile_image: url }; diff --git a/src/common/components/engine-tokens-estimated/index.tsx b/src/common/components/engine-tokens-estimated/index.tsx index 5c0c2d8862e..fa8f71f33e7 100644 --- a/src/common/components/engine-tokens-estimated/index.tsx +++ b/src/common/components/engine-tokens-estimated/index.tsx @@ -23,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) 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..024a38620b0 --- /dev/null +++ b/src/common/components/engine-tokens-list/index.scss @@ -0,0 +1,33 @@ +@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; + 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{ + + 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 new file mode 100644 index 00000000000..998a73204d0 --- /dev/null +++ b/src/common/components/engine-tokens-list/index.tsx @@ -0,0 +1,35 @@ +import React, { useEffect, useState} from 'react' +import { _t } from '../../i18n'; +import "./index.scss"; + +const EngineTokensList = (props: any) => { + const { token, handleChange, i, favoriteToken } = props; + + const [checked, setChecked] = useState(favoriteToken) + + return ( + <> +
+
+ + + {token?.name} + +
+
+ { + handleChange(e.target.checked, token); + setChecked(!checked) + }} + /> +
+
+ + ) +} + +export default EngineTokensList; \ No newline at end of file diff --git a/src/common/components/hive-engine-chart/index.tsx b/src/common/components/hive-engine-chart/index.tsx index 4f8e4c19e15..2596b32e1a7 100644 --- a/src/common/components/hive-engine-chart/index.tsx +++ b/src/common/components/hive-engine-chart/index.tsx @@ -31,8 +31,8 @@ export const HiveEngineChart = (props: any) => { enabled: false }, chart: { - height: "70", - width: "600", + height: "40", + width: "100", zoomType: "x", backgroundColor: "transparent", border: "none", @@ -56,7 +56,7 @@ export const HiveEngineChart = (props: any) => { area: { fillColor: theme === Theme.night ? "#2e3d51" : "#f3f7fb", lineColor: "transparent", - lineWidth: 399 + lineWidth: 150 }, series: { marker: { @@ -120,14 +120,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/hive-engine-transactions/index.tsx b/src/common/components/hive-engine-transactions/index.tsx new file mode 100644 index 00000000000..b0062ea7b19 --- /dev/null +++ b/src/common/components/hive-engine-transactions/index.tsx @@ -0,0 +1,190 @@ +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 { 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, + getOtherTransactions + } 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'; + +interface Props { + history: History; + global: Global; + dynamicProps: DynamicProps; + transactions: Transactions; + account: Account; + // fetchTransactions: ( + // username: string, + // group?: OperationGroup | "", + // start?: number, + // limit?: number + // ) => void; +} + +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) + const [transactionsList, setTransactionsList] = useState([]); + const [filtered, setFiltered] = useState([]) + const previousTransactions = usePrevious(props.transactions); + + useEffect(() => { + 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) + }; + + const otherTokenTransactions = async () => { + setLoading(true) + const otherTransactions = await getOtherTransactions(account.name, 200, params.toUpperCase()); + setOtherTransactions(otherTransactions); + setLoading(false) + } + + const getTransactionTime = (timestamp: number) => { + let date: any = new Date(timestamp * 1000) + return dateToFullRelative(date.toJSON()); + }; + + const loadMore = () => { + const moreItems = loadLimit + 10; + setLoadLimit(moreItems); + }; + + 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") + + 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", "delegate"].map((x, i) => ( + + ))} + +
+ {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")}

: +
+
+ {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 && + } +
+ ) +} +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 ; +}; \ No newline at end of file diff --git a/src/common/components/profile-edit/index.tsx b/src/common/components/profile-edit/index.tsx index 0e359cf9ee4..36ae510e2f7 100644 --- a/src/common/components/profile-edit/index.tsx +++ b/src/common/components/profile-edit/index.tsx @@ -33,6 +33,7 @@ interface State { inProgress: boolean; uploading: boolean; changed: boolean; + profileTokens: string[] } const pureState = (props: Props): State => { @@ -51,7 +52,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 || [] }; }; @@ -82,7 +84,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, @@ -91,9 +93,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/token-details/index.tsx b/src/common/components/token-details/index.tsx new file mode 100644 index 00000000000..6e0c120ad38 --- /dev/null +++ b/src/common/components/token-details/index.tsx @@ -0,0 +1,981 @@ +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 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 { EngineTokenDetails } from "../wallet-engine-details"; +import { WalletEcency } from "../wallet-ecency"; +import { Points } from "../../store/points/types"; + +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; + points: Points; +} + +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() { + 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); + 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 && Number(openOrders.hive) > 0 && ( +
+ + this.toggleOpenOrdersList("HIVE")} + > + {"+"} {formattedNumber(openOrders.hive, { suffix: "HIVE" })} + + +
+ )} + {withdrawSavings && Number(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 && Number(withdrawSavings.hbd) > 0 && ( +
+ + this.toggleSavingsWithdrawList("HBD")} + > + {"+"} {formattedNumber(withdrawSavings.hbd, { prefix: "$" })} + + +
+ )} + + {openOrders && Number(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 + })} +
+ )} + + {(params === "hive" || params === "hive-power" || params === "hbd") &&
+ {TransactionList({ ...this.props })} +
} + + {!(params === "hive" || params === "hive-power" || params === "hbd" || params === "points") &&
+ +
} + + {params === "points" &&
+ +
} +
+
+ + {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, + points: p.points, + }; + + return ; +}; \ No newline at end of file diff --git a/src/common/components/wallet-ecency/index.tsx b/src/common/components/wallet-ecency/index.tsx index 0bcc12b812b..a35012bc1ef 100644 --- a/src/common/components/wallet-ecency/index.tsx +++ b/src/common/components/wallet-ecency/index.tsx @@ -200,6 +200,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); @@ -235,6 +236,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-engine-details/index.tsx b/src/common/components/wallet-engine-details/index.tsx new file mode 100644 index 00000000000..7b6f6e0938f --- /dev/null +++ b/src/common/components/wallet-engine-details/index.tsx @@ -0,0 +1,431 @@ +import React from "react"; +import { Global } from "../../store/global/types"; +import { Account } from "../../store/accounts/types"; +import { DynamicProps } from "../../store/dynamic-props/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 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 +} from "../../api/hive-engine"; +import { plusCircle } 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; + 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 && ( +
+ {rewards?.map((r, i) => { + 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}` + : 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 = { + 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 = { + 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} +
+
+
+
+ ) + })} +
+ +
+
+ +
+ + {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-chart/index.tsx b/src/common/components/wallet-portfolio-chart/index.tsx new file mode 100644 index 00000000000..9286b3a56a9 --- /dev/null +++ b/src/common/components/wallet-portfolio-chart/index.tsx @@ -0,0 +1,372 @@ +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 HiveWalletPortfolioChart = (props: any) => { + const { theme } = props; + + const [prices, setPrices] = useState([]); + + useEffect(() => { + marketChartInfo(); + }, []); + + const marketChartInfo = async () => { + const data: any = await marketChart("hive"); + 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 ( +
+
+ +
+
+ ); + }; + +export const HbdWalletPortfolioChart = (props: any) => { + const { theme } = props; + + const [prices, setPrices] = useState([]); + + useEffect(() => { + marketChartInfo(); + }, []); + + const marketChartInfo = async () => { + const data: any = await marketChart("hive_dollar"); + 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 ( +
+
+ +
+
+ ); + }; +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/asset/ecency.jpeg b/src/common/components/wallet-portfolio/asset/ecency.jpeg new file mode 100644 index 00000000000..fd93e38308e Binary files /dev/null and b/src/common/components/wallet-portfolio/asset/ecency.jpeg differ diff --git a/src/common/components/wallet-portfolio/asset/engine.png b/src/common/components/wallet-portfolio/asset/engine.png new file mode 100644 index 00000000000..24e817357a8 Binary files /dev/null and b/src/common/components/wallet-portfolio/asset/engine.png differ 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 00000000000..ac9e2f5d995 Binary files /dev/null and b/src/common/components/wallet-portfolio/asset/hbd.png differ 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 00000000000..70358f393e5 Binary files /dev/null and b/src/common/components/wallet-portfolio/asset/spklogo.png differ diff --git a/src/common/components/wallet-portfolio/index.scss b/src/common/components/wallet-portfolio/index.scss new file mode 100644 index 00000000000..e884ed091e8 --- /dev/null +++ b/src/common/components/wallet-portfolio/index.scss @@ -0,0 +1,83 @@ +@import "src/style/colors"; +@import "src/style/variables"; +@import "src/style/bootstrap_vars"; +@import "src/style/mixins"; + +.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; +} + +.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 new file mode 100644 index 00000000000..bd1ebe99fff --- /dev/null +++ b/src/common/components/wallet-portfolio/index.tsx @@ -0,0 +1,622 @@ +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"; +import LinearProgress from "../linear-progress"; +import HiveWallet from "../../helper/hive-wallet"; +import { Points } from "../../store/points/types"; + +import { getHiveEngineTokenBalances, getMetrics } from "../../api/hive-engine"; + +import { priceUpSvg, priceDownSvg, plusCircle } 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 { + 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"; +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"); +const spkIcon = require("./asset/spklogo.png"); +const engineIcon = require("./asset/engine.png"); + +interface Props { + global: Global; + dynamicProps: DynamicProps; + account: Account | any; + activeUser: ActiveUser | null; + history: History; + updateActiveUser: (data?: Account) => void; + updateWalletValues: () => void; + points: Points; +} + +interface State { + tokens: HiveEngineToken[]; + loading: boolean; + assetBalance: number; + allTokens: any; + converting: number; + coingeckoData: any; + estimatedPointsValue: number; + estimatedPointsValueLoading: boolean; + search: string; + showTokenList: boolean; + favoriteTokens: HiveEngineToken[] | any; + selectedTokens: HiveEngineToken[] | any; + isChecked: boolean; + tokenBalance: number; + larynxTokenBalance: number; + larynxPowerBalance: number; + showChart: boolean; +} + +export class WalletPortfolio extends BaseComponent { + state: State = { + tokens: [], + loading: true, + assetBalance: 0, + allTokens: null, + converting: 0, + coingeckoData: [], + estimatedPointsValue: 0, + estimatedPointsValueLoading: false, + search: "", + showTokenList: false, + favoriteTokens: [], + selectedTokens: [], + isChecked: false, + tokenBalance: 0, + larynxTokenBalance: 0, + larynxPowerBalance: 0, + showChart: 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(); + this._isMounted && this.getSpkTokens(); + this._isMounted && this.setExistingProfileTokens(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + dataFromCoinGecko = async () => { + const data = await marketInfo(); + this.setState({ coingeckoData: data }); + }; + + engineTokensData = async () => { + const allMarketTokens = await getMetrics(); + const spkTokens = await this.getSpkTokens(); + 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, + type: "Engine" + }; + }); + + let engineTokens: 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: [...engineTokens, ...spkTokens] }); + // return engineTokens; + }; + + setExistingProfileTokens = () => { + this._isMounted && + this.setState({ + selectedTokens: JSON.parse(this.props.account?.posting_json_metadata)?.profile?.profileTokens || [] + }); + } + + 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(`wallet/${symbol.toLowerCase()}`); + } + + hideList = () => { + this.setState({ showTokenList: false }); + }; + + showList = () => { + this.setState({ showTokenList: true }); + }; + + handleChange = (e: any, token: any) => { + const { account } = this.props; + const { selectedTokens, isChecked } = this.state; + const json_data = JSON.parse(account.posting_json_metadata); + let userProfile = json_data?.profile; + let { profileTokens } = userProfile; + const index = findIndex(profileTokens, (t: any) => t?.symbol === token?.symbol); + + 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], + }; + }); + }; + + addToProfile = async () => { + const { account } = this.props; + const { selectedTokens } = this.state; + + // const spkTokens = await this.getSpkTokens(); + const json_data = JSON.parse(account.posting_json_metadata); + let userProfile = json_data?.profile; + + if (!userProfile.profileTokens) { + userProfile.profileTokens = []; + } + + const newPostMeta: any = { + ...userProfile, + profileTokens: mergeAndRemoveDuplicates([], selectedTokens), + }; + + //TODO: update UI state + + await updateProfile(account, newPostMeta); + }; + + getSpkTokens = async () => { + const wallet = await getSpkWallet(this.props.account.name); + const marketData = await getMarketInfo(); + const larynxData = await getLarynxData(); + 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; + }; + + toggleChart = (e: any) => { + 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 { + allTokens, + converting, + coingeckoData, + estimatedPointsValue, + search, + showTokenList, + isChecked, + showChart, + selectedTokens + } = this.state; + const { hivePerMVests } = dynamicProps; + + const profileTokens: any = [...selectedTokens] || []; + const w = new HiveWallet(account, dynamicProps, converting); + + 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")} {this.formatCurrency(estimatedTotal)} + +
+ {_t("wallet-portfolio.show-trend")} + +
+
+
+ + + + + {!global?.isMobile && } + + {!global?.isMobile && showChart && } + + + + + + <> + this.handleLink("points")}> + + + + {!global?.isMobile && showChart && ( + + )} + + + + + this.handleLink("hive-power")}> + + + + {!global?.isMobile && showChart && ( + + )} + + + + + this.handleLink("hive")}> + + + + {!global?.isMobile && showChart && ( + + )} + + + + + this.handleLink("hbd")}> + + + + {!global?.isMobile && showChart && ( + + )} + + + + + {!profileTokens ? ( + + ) : ( + profileTokens?.map((a: any) => { + const changeValue = parseFloat(a?.priceChangePercent); + return ( + this.handleLink(a.symbol)} + > + + {!global?.isMobile && ( + + )} + + {!global?.isMobile && showChart && ( + + )} + + + + ); + }) + )} + + +
{_t("wallet-portfolio.name")}{_t("wallet-portfolio.price")}{_t("wallet-portfolio.change")}{_t("wallet-portfolio.trend")}{_t("wallet-portfolio.balance")}{_t("wallet-portfolio.value")}
+ + {_t("wallet-portfolio.points")} + ${estimatedPointsValue}{priceUpSvg}0.00% + + {points.points}{this.formatCurrency(totalPointsValue)}
+ + {_t("wallet-portfolio.hive-power")} + ${this.pricePerHive} + + {coingeckoData[0]?.price_change_percentage_24h < 0 + ? priceDownSvg + : priceUpSvg} + + {coingeckoData[0]?.price_change_percentage_24h} + + + {totalHP}{this.formatCurrency(totalHPValue)}
+ + {_t("wallet-portfolio.hive")} + ${this.pricePerHive} + + {coingeckoData[0]?.price_change_percentage_24h < 0 + ? priceDownSvg + : priceUpSvg} + + {coingeckoData[0]?.price_change_percentage_24h} + + + {w.balance}{this.formatCurrency(totalHiveValue)}
+ + {_t("wallet-portfolio.hbd")} + ${coingeckoData[1]?.current_price} + + {coingeckoData[1]?.price_change_percentage_24h < 0 + ? priceDownSvg + : priceUpSvg} + + {coingeckoData[1]?.price_change_percentage_24h} + + + {w.hbdBalance}{this.formatCurrency(totalHbdValue)}
+
+ + +
+ {a.symbol} +
+ ${a.lastPrice} + + + {a?.symbol === a.symbol && ( + {changeValue < 0 ? priceDownSvg : priceUpSvg} + )} + {a?.symbol === a.symbol ? a?.priceChangePercent : null} + + + +
+ +
+
+
+ {a.balance} + + {this.formatCurrency(a.usd_value)} +
+
+ {activeUser?.username === account.name && ( +
+ +
+ )} + {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 ( + + ); + })} +
+ +
+
+
+
+ )} +
+ ); + } +} + +export default (p: Props) => { + const props = { + global: p.global, + dynamicProps: p.dynamicProps, + account: p.account, + activeUser: p.activeUser, + updateActiveUser: p.updateActiveUser, + updateWalletValues: p.updateWalletValues, + history: p.history, + points: p.points + }; + + return ; +}; + +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 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" } diff --git a/src/common/i18n/locales/en-US.json b/src/common/i18n/locales/en-US.json index 01bfc368910..5065b975068 100644 --- a/src/common/i18n/locales/en-US.json +++ b/src/common/i18n/locales/en-US.json @@ -170,6 +170,20 @@ "vote-power": "Vote Power:", "boost": "Boost" }, + "wallet-portfolio": { + "name": "Token Name", + "price": "Price", + "change": "% Change", + "trend": "Trend", + "balance": "Balance", + "value": "Value", + "show-trend": "Show trend", + "total-value": "Total Wallet Value:", + "hive": "Hive", + "hive-power": "Hive Power", + "points": "Points", + "hbd": "HBD" + }, "intro": { "title": "Aspire to greatness", "sub-title": "rewarding communities", @@ -1162,6 +1176,7 @@ "group-interests": "Interests", "group-stake-operations": "Stake operations", "group-rewards": "Rewards", + "group-delegate": "Delegate", "type-curation_reward": "Curation reward", "type-author_reward": "Author reward", "type-comment_benefactor_reward": "Comment benefactor reward", diff --git a/src/common/pages/profile-functional.tsx b/src/common/pages/profile-functional.tsx index f86340f05ad..5c93e1f4922 100644 --- a/src/common/pages/profile-functional.tsx +++ b/src/common/pages/profile-functional.tsx @@ -24,6 +24,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"; @@ -56,6 +58,7 @@ interface MatchParams { username: string; section?: string; search?: string; + symbol?: string; } interface Props extends PageProps { @@ -85,6 +88,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: "", @@ -102,8 +106,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); @@ -116,6 +119,7 @@ export const Profile = (props: Props) => { setAccount(account); setSection(section || ProfileFilter.blog); + setSymbol(symbol || "") setUsername(username.replace("@", "")); await initPinnedEntry(username.replace("@", ""), account); @@ -127,9 +131,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) { @@ -490,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 }); } @@ -515,6 +520,12 @@ export const Profile = (props: Props) => { if (section === "referrals") { return ProfileReferrals({ ...props, account, updateWalletValues: ensureAccount }); } + if (section === "wallet") { + if (symbol && section === "wallet") { + 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 4bf63ecfe99..5da366a6ce9 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|trail)`, + )}|wallet|communities|settings|permissions|referrals|followers|following|spk|trail)`, COMMUNITIES: `/communities`, COMMUNITIES_CREATE: `/communities/create`, COMMUNITIES_CREATE_HS: `/communities/create-hs`, @@ -38,5 +38,6 @@ export default { PROPOSALS: `/proposals`, PROPOSAL_DETAIL: `/proposals/:id(\\d+)`, PURCHASE: "/purchase", + TOKEN_DETAIL: `/:username(@[\\w\\.\\d-]+)/:section(wallet)/:symbol`, DECKS: "/decks" }; diff --git a/src/common/store/accounts/types.ts b/src/common/store/accounts/types.ts index fe6ab4b7a1b..4b8fd7fa777 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 {