1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

User pool transactions on asset details (#193)

* refactored pool transactions component, output pool transactions on asset details

* styling

* remove sticky around asset actions

* hasAddedLiquidity simplification

* owner from useMetadata

* refactor, styling, number formatting

* refactor

* toggle pattern
This commit is contained in:
Matthias Kretschmann 2020-11-02 19:18:21 +01:00 committed by GitHub
parent a5b7c66331
commit be8307f34d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 257 additions and 115 deletions

View File

@ -25,10 +25,12 @@ const confettiConfig = {
export default function SuccessConfetti({
success,
action
action,
className
}: {
success: string
action?: ReactNode
className?: string
}): ReactElement {
// Have some confetti upon success
useEffect(() => {
@ -41,11 +43,11 @@ export default function SuccessConfetti({
}, [success])
return (
<>
<div className={className || null}>
<Alert text={success} state="success" />
<span className={styles.action} data-confetti>
{action}
</span>
</>
</div>
)
}

View File

@ -6,6 +6,7 @@
.table [role='row'] {
color: var(--font-color-text);
font-size: var(--font-size-small);
min-width: 0;
}
.table [class~='rdt_TableCol'] {
@ -26,6 +27,7 @@
font-size: var(--font-size-small);
color: var(--color-secondary);
background: none;
min-height: 0;
}
.table + div [class*='rdt_Pagination'] svg {

View File

@ -17,6 +17,8 @@ export default function Table({
columns,
isLoading,
emptyMessage,
pagination,
paginationPerPage,
...props
}: TableProps): ReactElement {
return (
@ -25,8 +27,8 @@ export default function Table({
data={data}
className={styles.table}
noHeader
pagination={data?.length >= 9}
paginationPerPage={10}
pagination={pagination || data?.length >= 9}
paginationPerPage={paginationPerPage || 10}
paginationComponentOptions={{ noRowsPerPage: true }}
noDataComponent={<Empty message={emptyMessage} />}
progressPending={isLoading}

View File

@ -4,11 +4,13 @@ import { format, formatDistance } from 'date-fns'
export default function Time({
date,
relative,
isUnix
isUnix,
className
}: {
date: string
relative?: boolean
isUnix?: boolean
className?: string
}): ReactElement {
const dateNew = isUnix ? new Date(Number(date) * 1000) : new Date(date)
const dateIso = dateNew.toISOString()
@ -19,6 +21,7 @@ export default function Time({
<time
title={relative ? format(dateNew, 'MMMM d, yyyy') : undefined}
dateTime={dateIso}
className={className || undefined}
>
{relative
? formatDistance(dateNew, Date.now(), { addSuffix: true })

View File

@ -0,0 +1,3 @@
.time {
color: var(--color-secondary);
}

View File

@ -0,0 +1,127 @@
import { PoolTransaction } from '@oceanprotocol/lib/dist/node/balancer/OceanPool'
import { useOcean } from '@oceanprotocol/react'
import React, { ReactElement, useEffect, useState } from 'react'
import EtherscanLink from '../atoms/EtherscanLink'
import Time from '../atoms/Time'
import Table from '../atoms/Table'
import AssetTitle from './AssetTitle'
import styles from './PoolTransactions.module.css'
import { formatCurrency } from '@coingecko/cryptoformat'
import { useUserPreferences } from '../../providers/UserPreferences'
function formatNumber(number: number, locale: string) {
return formatCurrency(number, '', locale, false, {
significantFigures: 4
})
}
function Title({ row }: { row: PoolTransaction }) {
const { ocean, networkId } = useOcean()
const [dtSymbol, setDtSymbol] = useState<string>()
const { locale } = useUserPreferences()
const title = row.tokenAmountIn
? `Add ${formatNumber(Number(row.tokenAmountIn), locale)} ${
dtSymbol || 'OCEAN'
}`
: `Remove ${formatNumber(Number(row.tokenAmountOut), locale)} ${
dtSymbol || 'OCEAN'
}`
useEffect(() => {
if (!ocean) return
async function getSymbol() {
const symbol = await ocean.datatokens.getSymbol(
row.tokenIn || row.tokenOut
)
setDtSymbol(symbol)
}
getSymbol()
}, [ocean, row])
return (
<EtherscanLink networkId={networkId} path={`/tx/${row.transactionHash}`}>
{title}
</EtherscanLink>
)
}
function getColumns(minimal?: boolean) {
return [
{
name: 'Title',
selector: function getTitleRow(row: PoolTransaction) {
return <Title row={row} />
},
minWidth: '14rem',
grow: 1
},
{
name: 'Data Set',
selector: function getAssetRow(row: PoolTransaction) {
const did = row.dtAddress.replace('0x', 'did:op:')
return <AssetTitle did={did} />
},
omit: minimal
},
{
name: 'Time',
selector: function getTimeRow(row: PoolTransaction) {
return (
<Time
className={styles.time}
date={row.timestamp.toString()}
relative
isUnix
/>
)
}
}
]
}
export default function PoolTransactions({
poolAddress,
minimal
}: {
poolAddress?: string
minimal?: boolean
}): ReactElement {
const { ocean, accountId } = useOcean()
const [logs, setLogs] = useState<PoolTransaction[]>()
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
async function getLogs() {
if (!ocean || !accountId) return
setIsLoading(true)
const logs = poolAddress
? await ocean.pool.getPoolLogs(poolAddress, 0, accountId)
: await ocean.pool.getAllPoolLogs(accountId)
// sort logs by date, newest first
const logsSorted = logs.sort((a, b) => {
if (a.timestamp > b.timestamp) return -1
if (a.timestamp < b.timestamp) return 1
return 0
})
setLogs(logsSorted)
setIsLoading(false)
}
getLogs()
}, [ocean, accountId, poolAddress])
return (
<Table
columns={getColumns(minimal)}
data={logs}
isLoading={isLoading}
noTableHead={minimal}
dense={minimal}
pagination={minimal ? logs?.length >= 4 : logs?.length >= 9}
paginationPerPage={minimal ? 5 : 10}
emptyMessage="Your pool transactions will show up here"
/>
)
}

View File

@ -11,7 +11,7 @@
justify-content: center;
}
.actions + div {
.success {
margin-top: calc(var(--spacer) / 2);
margin-bottom: calc(var(--spacer) / 2);
}

View File

@ -41,6 +41,7 @@ export default function Actions({
</div>
{txId && (
<SuccessConfetti
className={styles.success}
success={successMessage}
action={
<EtherscanLink networkId={networkId} path={`/tx/${txId}`}>

View File

@ -33,9 +33,7 @@
}
.title {
font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 3);
color: var(--color-secondary);
composes: title from './index.module.css';
}
.totalLiquidity {

View File

@ -0,0 +1,45 @@
.transactions {
composes: container from './index.module.css';
border-top: 1px solid var(--border-color);
margin-top: calc(var(--spacer) / 1.5);
padding: calc(var(--spacer) / 1.5);
background: var(--background-highlight);
margin-bottom: -2rem;
}
.transactions [class*='rdt_Pagination'] {
margin-bottom: -1rem;
}
.title {
composes: title from './index.module.css';
margin-bottom: 0;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
.title:hover .toggle {
color: var(--color-primary);
}
.title + div {
margin-top: calc(var(--spacer) / 3);
}
.toggle {
color: var(--color-secondary);
}
.toggle svg {
display: inline-block;
width: var(--font-size-mini);
height: var(--font-size-mini);
fill: currentColor;
transition: 0.2s ease-out;
}
.open .toggle svg {
transform: rotate(180deg);
}

View File

@ -0,0 +1,37 @@
import React, { ReactElement, useState } from 'react'
import Button from '../../../atoms/Button'
import PoolTransactions from '../../../molecules/PoolTransactions'
import styles from './Transactions.module.css'
import { ReactComponent as Caret } from '../../../../images/caret.svg'
export default function Transactions({
poolAddress
}: {
poolAddress: string
}): ReactElement {
const [open, setOpen] = useState(false)
function handleClick() {
setOpen(!open)
}
return (
<div
className={`${styles.transactions} ${open === true ? styles.open : ''}`}
>
{/* TODO: onClick on h3 is nasty but we're in a hurry */}
<h3 className={styles.title} onClick={handleClick}>
Your Pool Transactions{' '}
<Button
style="text"
size="small"
onClick={handleClick}
className={styles.toggle}
>
{open ? 'Hide' : 'Show'} <Caret />
</Button>
</h3>
{open === true && <PoolTransactions poolAddress={poolAddress} minimal />}
</div>
)
}

View File

@ -1,10 +1,14 @@
.dataToken {
padding-bottom: calc(var(--spacer) / 1.5);
font-size: var(--font-size-large);
.container {
margin-left: -2rem;
margin-right: -2rem;
padding-left: calc(var(--spacer) / 1.5);
padding-right: calc(var(--spacer) / 1.5);
}
.dataToken {
composes: container;
padding-bottom: calc(var(--spacer) / 1.5);
font-size: var(--font-size-large);
text-align: center;
position: relative;
}
@ -21,13 +25,18 @@
margin-right: calc(var(--spacer) / 3);
}
.title {
font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 3);
color: var(--color-secondary);
}
.update {
composes: container;
font-size: var(--font-size-mini);
color: var(--color-secondary);
text-align: center;
border-top: 1px solid var(--border-color);
margin-left: -2rem;
margin-right: -2rem;
padding-top: calc(var(--spacer) / 4);
}

View File

@ -12,6 +12,8 @@ import EtherscanLink from '../../../atoms/EtherscanLink'
import Token from './Token'
import TokenList from './TokenList'
import { graphql, useStaticQuery } from 'gatsby'
import PoolTransactions from '../../../molecules/PoolTransactions'
import Transactions from './Transactions'
export interface Balance {
ocean: number
@ -44,7 +46,7 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
const content = data.content.edges[0].node.childContentJson.pool
const { ocean, accountId, networkId } = useOcean()
const { price, refreshPrice } = useMetadata(ddo)
const { price, refreshPrice, owner } = useMetadata(ddo)
const { dtSymbol } = usePricing(ddo)
const [poolTokens, setPoolTokens] = useState<string>()
@ -71,16 +73,12 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
const [refreshPool, setRefreshPool] = useState(false)
useEffect(() => {
const hasAddedLiquidity =
userLiquidity && (userLiquidity.ocean > 0 || userLiquidity.datatoken > 0)
setHasAddedLiquidity(hasAddedLiquidity)
const poolShare =
price?.ocean &&
price?.datatoken &&
userLiquidity &&
((Number(poolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
((Number(poolTokens) / Number(totalPoolTokens)) * 100).toFixed(5)
setPoolShare(poolShare)
setHasAddedLiquidity(Number(poolShare) > 0)
const totalUserLiquidityInOcean =
userLiquidity?.ocean + userLiquidity?.datatoken * price?.value
@ -129,15 +127,13 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
//
// Get everything the creator put into the pool
//
const creatorPoolTokens = await ocean.pool.sharesBalance(
ddo.publicKey[0].owner,
owner,
price.address
)
setCreatorPoolTokens(creatorPoolTokens)
// calculate creator's provided liquidity based on pool tokens
// Calculate creator's provided liquidity based on pool tokens
const creatorOceanBalance =
(Number(creatorPoolTokens) / Number(totalPoolTokens)) * price.ocean
@ -287,6 +283,8 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
</Button>
)}
</div>
{accountId && <Transactions poolAddress={price?.address} />}
</>
)}
</>

View File

@ -20,9 +20,7 @@
grid-template-columns: 1.5fr 1fr;
}
.sticky {
position: sticky;
top: calc(var(--spacer) / 2);
.actions {
margin-top: var(--spacer);
}
}

View File

@ -106,10 +106,8 @@ export default function AssetContent({
</div>
</div>
<div>
<div className={styles.sticky}>
<AssetActions ddo={ddo} />
</div>
<div className={styles.actions}>
<AssetActions ddo={ddo} />
</div>
</article>
)

View File

@ -1,81 +0,0 @@
import { PoolTransaction } from '@oceanprotocol/lib/dist/node/balancer/OceanPool'
import { useOcean } from '@oceanprotocol/react'
import React, { ReactElement, useEffect, useState } from 'react'
import EtherscanLink from '../../atoms/EtherscanLink'
import Time from '../../atoms/Time'
import Table from '../../atoms/Table'
import AssetTitle from '../../molecules/AssetTitle'
function Title({ row }: { row: PoolTransaction }) {
const { ocean, networkId } = useOcean()
const [dtSymbol, setDtSymbol] = useState<string>()
const title = row.tokenAmountIn
? `Add ${row.tokenAmountIn} ${dtSymbol || 'OCEAN'}`
: `Remove ${row.tokenAmountOut} ${dtSymbol || 'OCEAN'}`
useEffect(() => {
if (!ocean) return
async function getSymbol() {
const symbol = await ocean.datatokens.getSymbol(
row.tokenIn || row.tokenOut
)
setDtSymbol(symbol)
}
getSymbol()
}, [ocean, row])
return (
<EtherscanLink networkId={networkId} path={`/tx/${row.transactionHash}`}>
{title}
</EtherscanLink>
)
}
const columns = [
{
name: 'Title',
selector: function getTitleRow(row: PoolTransaction) {
return <Title row={row} />
}
},
{
name: 'Data Set',
selector: function getAssetRow(row: PoolTransaction) {
const did = row.dtAddress.replace('0x', 'did:op:')
return <AssetTitle did={did} />
}
},
{
name: 'Time',
selector: function getTimeRow(row: PoolTransaction) {
return <Time date={row.timestamp.toString()} relative isUnix />
}
}
]
export default function PoolTransactions(): ReactElement {
const { ocean, accountId } = useOcean()
const [logs, setLogs] = useState<PoolTransaction[]>()
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
async function getLogs() {
if (!ocean || !accountId) return
setIsLoading(true)
const logs = await ocean.pool.getAllPoolLogs(accountId)
// sort logs by date, newest first
const logsSorted = logs.sort((a, b) => {
if (a.timestamp > b.timestamp) return -1
if (a.timestamp < b.timestamp) return 1
return 0
})
setLogs(logsSorted)
setIsLoading(false)
}
getLogs()
}, [ocean, accountId])
return <Table columns={columns} data={logs} isLoading={isLoading} />
}

View File

@ -2,7 +2,7 @@ import React, { ReactElement, ReactNode } from 'react'
import ComputeJobs from './ComputeJobs'
import styles from './index.module.css'
import PoolShares from './PoolShares'
import PoolTransactions from './PoolTransactions'
import PoolTransactions from '../../molecules/PoolTransactions'
import PublishedList from './PublishedList'
const sections = [