Skip to content
This repository was archived by the owner on Apr 4, 2022. It is now read-only.

1046 load from api orderstable #611

Merged
merged 11 commits into from
Sep 20, 2021
12 changes: 10 additions & 2 deletions src/api/operator/operatorApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,16 +193,24 @@ export async function getOrder(params: GetOrderParams): Promise<RawOrder | null>
* - owner: address
* - sellToken: address
* - buyToken: address
* - minValidTo: number
*/
export async function getOrders(params: GetOrdersParams): Promise<RawOrder[]> {
const { networkId, ...searchParams } = params
const { owner, sellToken, buyToken } = searchParams
const { owner, sellToken, buyToken, minValidTo } = searchParams
const defaultValues = {
includeFullyExecuted: 'true',
includeInvalidated: 'true',
includeInsufficientBalance: 'true',
includePresignaturePending: 'true',
includeUnsupportedTokens: 'true',
}

console.log(
`[getOrders] Fetching orders on network ${networkId} with filters: owner=${owner} sellToken=${sellToken} buyToken=${buyToken}`,
)

const searchString = buildSearchString({ ...searchParams })
const searchString = buildSearchString({ ...searchParams, ...defaultValues, minValidTo: String(minValidTo) })

const queryString = '/orders/' + searchString

Expand Down
3 changes: 2 additions & 1 deletion src/api/operator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ export type GetOrderParams = WithNetworkId & {
}

export type GetOrdersParams = WithNetworkId & {
owner?: string
owner: string
minValidTo: number
sellToken?: string
buyToken?: string
}
Expand Down
2 changes: 1 addition & 1 deletion src/apps/explorer/ExplorerApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const AppContent = (): JSX.Element => {
}

const Wrapper = styled.div`
max-width: 140rem;
max-width: 118rem;
margin: 0 auto;

${media.mediumDown} {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { useContext } from 'react'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'

import OrdersTable from 'components/orders/OrdersUserDetailsTable'
import { EmptyItemWrapper } from 'components/common/StyledUserDetailsTable'
import { OrdersTableContext } from './context/OrdersTableContext'

export const OrdersTableWithData: React.FC = () => {
const { orders, isFirstLoading } = useContext(OrdersTableContext)

return isFirstLoading ? (
<EmptyItemWrapper>
<FontAwesomeIcon icon={faSpinner} spin size="3x" />
</EmptyItemWrapper>
) : (
<OrdersTable orders={orders} />
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'

import { Order } from 'api/operator'

interface CommonState {
orders: Order[]
error: string
isFirstLoading: boolean
}

export const OrdersTableContext = React.createContext({} as CommonState)
51 changes: 51 additions & 0 deletions src/apps/explorer/components/OrdersTableWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useMemo } from 'react'
import styled from 'styled-components'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'

import ExplorerTabs from 'apps/explorer/components/common/ExplorerTabs/ExplorerTab'
import { OrdersTableWithData } from './OrdersTableWithData'
import { OrdersTableContext } from './context/OrdersTableContext'
import { useGetOrders } from './useGetOrders'
import { TabItemInterface } from 'components/common/Tabs/Tabs'

const StyledTabLoader = styled.span`
padding-left: 4px;
`

const tabItems = (isLoadingOrders: boolean): TabItemInterface[] => {
return [
{
id: 1,
tab: (
<>
Orders
<StyledTabLoader>{isLoadingOrders && <FontAwesomeIcon icon={faSpinner} spin size="1x" />}</StyledTabLoader>
</>
),
content: <OrdersTableWithData />,
},
]
}

interface Props {
ownerAddress: string
}

const OrdersTableWidget: React.FC<Props> = ({ ownerAddress }) => {
const { orders, isLoading, error } = useGetOrders(ownerAddress)

const isFirstLoading = useMemo(() => {
if (isLoading && orders.length === 0) return true

return false
}, [isLoading, orders.length])

return (
<OrdersTableContext.Provider value={{ orders, error, isFirstLoading }}>
<ExplorerTabs tabItems={tabItems(isLoading)} />
</OrdersTableContext.Provider>
)
}

export default OrdersTableWidget
128 changes: 128 additions & 0 deletions src/apps/explorer/components/OrdersTableWidget/useGetOrders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useState, useCallback, useEffect } from 'react'
import { subMinutes, getTime } from 'date-fns'

import { Network } from 'types'
import { useMultipleErc20 } from 'hooks/useErc20'
import { getOrders, Order, RawOrder } from 'api/operator'
import { useNetworkId } from 'state/network'
import { transformOrder } from 'utils'
import { ORDERS_HISTORY_MINUTES_AGO, ORDERS_QUERY_INTERVAL } from 'apps/explorer/const'

/**
*
* Merge new RawOrders consulted, that may have changed status
*
* @param previousOrders List of orders
* @param newOrdersFetched List of fetched block order that could have changed
*/
export function mergeNewOrders(previousOrders: Order[], newOrdersFetched: RawOrder[]): Order[] {
if (newOrdersFetched.length === 0) return previousOrders

// find the order up to which it is to be replaced
const lastOrder = newOrdersFetched[newOrdersFetched.length - 1]
const positionLastOrder = previousOrders.findIndex((o) => o.uid === lastOrder.uid)
if (positionLastOrder === -1) {
return newOrdersFetched.map((order) => transformOrder(order)).concat(previousOrders)
}

const slicedOrders: Order[] = previousOrders.slice(positionLastOrder + 1)
return newOrdersFetched.map((order) => transformOrder(order)).concat(slicedOrders)
}

function isObjectEmpty(object: Record<string, unknown>): boolean {
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
for (const key in object) {
if (key) return false
}

return true
}

type Result = {
orders: Order[]
error: string
isLoading: boolean
}

export function useGetOrders(ownerAddress: string): Result {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
const [orders, setOrders] = useState<Order[]>([])
const networkId = useNetworkId() || undefined
const [erc20Addresses, setErc20Addresses] = useState<string[]>([])
const { value: valueErc20s, isLoading: areErc20Loading } = useMultipleErc20({ networkId, addresses: erc20Addresses })
const [mountNewOrders, setMountNewOrders] = useState(false)

const fetchOrders = useCallback(
async (network: Network, owner: string, minTimeHistoryTimeStamp = 0): Promise<void> => {
setIsLoading(true)
setError('')

try {
const ordersFetched = await getOrders({ networkId: network, owner, minValidTo: minTimeHistoryTimeStamp })
const newErc20Addresses = ordersFetched.reduce((accumulator: string[], element) => {
const updateAccumulator = (tokenAddress: string): void => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using a Set (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) for this?

const newErc20Address = new Set<string>()
ordersFetched.forEach(order => {
  newErc20Address.add(order.buyToken)
  newErc20Address.add(order.sellToken)
})

Copy link
Contributor Author

@henrypalacios henrypalacios Sep 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the Set, it would be much more readable however but I went for the reduce for performance.
Do you think it makes up for it or should I go for the Set? https://jsben.ch/ognIg

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, no idea it had such a performance impact. Guess they make mose sense in languages where Sets are first class citizens.

I prefer sets for readability but maybe we should stick with reducer due to performance as you suggest.

@anxolin @W3stside FYI

if (accumulator.indexOf(tokenAddress) === -1) {
accumulator.push(tokenAddress)
}
}
updateAccumulator(element.buyToken)
updateAccumulator(element.sellToken)

return accumulator
}, [])

setErc20Addresses(newErc20Addresses)
// TODO -> For the moment it is neccesary to sort by date
ordersFetched.sort((a, b) => +new Date(b.creationDate) - +new Date(a.creationDate))

setOrders((previousOrders) => mergeNewOrders(previousOrders, ordersFetched))
setMountNewOrders(true)
} catch (e) {
const msg = `Failed to fetch orders`
console.error(msg, e)
setError(msg)
} finally {
setIsLoading(false)
}
},
[],
)

useEffect(() => {
if (!networkId) {
return
}
const getOrUpdateOrders = (minHistoryTime?: number): Promise<void> =>
fetchOrders(networkId, ownerAddress, minHistoryTime)

getOrUpdateOrders()

const intervalId: NodeJS.Timeout = setInterval(() => {
const minutesAgoTimestamp = getTime(subMinutes(new Date(), ORDERS_HISTORY_MINUTES_AGO))
getOrUpdateOrders(Math.floor(minutesAgoTimestamp / 1000))
}, ORDERS_QUERY_INTERVAL)

return (): void => {
clearInterval(intervalId)
}
}, [fetchOrders, networkId, ownerAddress])

useEffect(() => {
if (areErc20Loading || isObjectEmpty(valueErc20s) || !mountNewOrders) {
return
}

const newOrders = orders.map((order) => {
order.buyToken = valueErc20s[order.buyTokenAddress] || order.buyToken
order.sellToken = valueErc20s[order.sellTokenAddress] || order.sellToken

return order
})

setOrders(newOrders)
setMountNewOrders(false)
}, [valueErc20s, networkId, areErc20Loading, mountNewOrders, orders])

return { orders, error, isLoading }
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const StyledTabs = styled.div`
padding: 0;
border: ${({ theme }): string => `1px solid ${theme.borderPrimary}`};
border-radius: 4px;
min-height: 33rem;

> div > div.tablist {
justify-content: flex-start;
Expand Down
2 changes: 2 additions & 0 deletions src/apps/explorer/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { AnalyticsDimension } from 'types'

/** Explorer app constants */
export const ORDER_QUERY_INTERVAL = 10000 // in ms
export const ORDERS_QUERY_INTERVAL = 30000 // in ms
export const ORDERS_HISTORY_MINUTES_AGO = 10 // in minutes

export const DISPLAY_TEXT_COPIED_CHECK = 1000 // in ms

Expand Down
59 changes: 58 additions & 1 deletion src/apps/explorer/pages/UserDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,62 @@
import React from 'react'
import { useParams } from 'react-router'
import styled from 'styled-components'

const UserDetails: React.FC = () => <h3>Placeholder Page</h3>
import { isAddress } from 'web3-utils'

import { media } from 'theme/styles/media'
import NotFound from './NotFound'
import OrdersTableWidget from '../components/OrdersTableWidget'
import { useNetworkId } from 'state/network'
import { BlockExplorerLink } from 'components/common/BlockExplorerLink'
import { RowWithCopyButton } from 'components/common/RowWithCopyButton'

const Wrapper = styled.div`
padding: 1.6rem;
margin: 0 auto;
width: 100%;

${media.mediumDown} {
max-width: 94rem;
}

${media.mobile} {
max-width: 100%;
}
> h1 {
display: flex;
padding: 2.4rem 0 2.35rem;
align-items: center;
font-weight: ${({ theme }): string => theme.fontBold};
}
`
const TitleAddress = styled(RowWithCopyButton)`
font-size: ${({ theme }): string => theme.fontSizeDefault};
font-weight: ${({ theme }): string => theme.fontNormal};
margin: 0 0 0 1.5rem;
display: flex;
align-items: center;
`
const UserDetails: React.FC = () => {
const { address } = useParams<{ address: string }>()
const networkId = useNetworkId() || undefined

if (!isAddress(address)) {
return <NotFound />
} else {
return (
<Wrapper>
<h1>
User details
<TitleAddress
textToCopy={address}
contentsToDisplay={<BlockExplorerLink type="address" networkId={networkId} identifier={address} />}
/>
</h1>
<OrdersTableWidget ownerAddress={address} />
</Wrapper>
)
}
}

export default UserDetails
Loading