Skip to content
This repository has been archived by the owner on Feb 2, 2024. It is now read-only.

Integrate the cow-sdk into the Home Page widgets #77

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/apps/explorer/components/SummaryCardsWidget/SummaryCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { formatDistanceToNowStrict } from 'date-fns'

import { Card, CardContent } from 'components/common/Card'
import { CardRow } from 'components/common/CardRow'
import { TotalSummaryResponse } from '.'
import { TotalSummaryResponse } from './useGetSummaryData'
import { abbreviateString } from 'utils'
import { useMediaBreakpoint } from 'hooks/useMediaBreakPoint'
import { calcDiff, getColorBySign } from 'components/common/Card/card.utils'
import { CopyButton } from 'components/common/CopyButton'
import { LinkWithPrefixNetwork } from 'components/common/LinkWithPrefixNetwork'
import { numberFormatter } from './utils'

const BatchInfoHeight = '19.6rem'
const DESKTOP_TEXT_SIZE = 1.8 // rem
Expand Down Expand Up @@ -96,7 +97,7 @@ export function SummaryCards({ summaryData, children }: SummaryCardsProps): JSX.
<CardContent
variant="3row"
label1="Last Batch"
value1={batchInfo && formatDistanceToNowStrict(batchInfo.lastBatchDate)}
value1={batchInfo && formatDistanceToNowStrict(batchInfo.lastBatchDate, { addSuffix: true })}
loading={isLoading}
valueSize={valueTextSize}
/>
Expand All @@ -122,7 +123,7 @@ export function SummaryCards({ summaryData, children }: SummaryCardsProps): JSX.
<CardContent
variant={rowsByCard}
label1="24h Transactions"
value1={dailyTransactions?.now}
value1={dailyTransactions?.now.toLocaleString()}
caption1={`${diffTransactions.toFixed(2)}%`}
captionColor={getColorBySign(diffTransactions)}
loading={isLoading}
Expand All @@ -133,7 +134,7 @@ export function SummaryCards({ summaryData, children }: SummaryCardsProps): JSX.
<CardContent
variant="2row"
label1="Total Tokens"
value1={totalTokens}
value1={totalTokens?.toLocaleString()}
loading={isLoading}
valueSize={valueTextSize}
/>
Expand All @@ -142,7 +143,7 @@ export function SummaryCards({ summaryData, children }: SummaryCardsProps): JSX.
<CardContent
variant={rowsByCard}
label1="24h Fees"
value1={`$${dailyFees?.now.toFixed(2)}`}
value1={`$${numberFormatter(dailyFees?.now || 0)}`}
alfetopito marked this conversation as resolved.
Show resolved Hide resolved
caption1={`${diffFees.toFixed(2)}%`}
captionColor={getColorBySign(diffFees)}
loading={isLoading}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import styled from 'styled-components'

import { GlobalStyles, ThemeToggler } from 'storybook/decorators'
import { VolumeChart, VolumeChartProps } from './VolumeChart'
import { VolumePeriod } from './VolumeChartWidget'
import volumeDataJson from './volumeData.json'
import { buildVolumeData, VolumePeriod } from 'apps/explorer/components/SummaryCardsWidget/VolumeChartWidget'
import { buildVolumeData } from './useGetVolumeData'

export default {
title: 'ExplorerApp/Chart',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ import {
ContainerTitle,
WrapperPeriodButton,
StyledShimmerBar,
} from 'apps/explorer/components/SummaryCardsWidget/VolumeChart.styled'
} from 'apps/explorer/components/SummaryCardsWidget/VolumeChart/VolumeChart.styled'
import { VolumePeriod } from './VolumeChartWidget'
import { numberFormatter } from '../utils'
import { useNetworkId } from 'state/network'

const DEFAULT_CHART_HEIGHT = 196 // px
const DEFAULT_PERIOD_ID = 'ALL'
const COLOR_POSITIVE_DIFFERENCE = 'rgba(0, 196, 110, 0.01)'
const COLOR_NEGATIVE_DIFFERENCE = 'rgba(255, 48, 91, 0.01)'
const COLOR_POSITIVE_DIFFERENCE_LINE = 'rgb(0, 196, 111)'
const COLOR_NEGATIVE_DIFFERENCE_LINE = 'rgb(255, 48, 89)'

export interface VolumeDataResponse {
data?: VolumeItem[]
data?: HistogramData[]
currentVolume?: number
changedVolume?: number
isLoading: boolean
Expand All @@ -30,11 +34,7 @@ export interface VolumeChartProps {
volumeData: VolumeDataResponse | undefined
height?: number
width?: number
periodId?: string
}

export interface VolumeItem extends HistogramData {
id?: string
period?: VolumePeriod
}

export function PeriodButton({
Expand All @@ -58,8 +58,8 @@ function _formatAmount(amount: string): string {
* requires the graph to be rendered.
* example: <lastRecordId>-<volumePeriodSelected>
* */
function usePreviousLastValueData(value: string): string | undefined {
const ref = useRef<string>()
function usePreviousLastValueData<T>(value: T): T | undefined {
const ref = useRef<T>()

useEffect(() => {
ref.current = value
Expand Down Expand Up @@ -124,7 +124,7 @@ export function VolumeChart({
volumeData,
height = DEFAULT_CHART_HEIGHT,
width = undefined,
periodId = DEFAULT_PERIOD_ID,
period,
children,
}: React.PropsWithChildren<VolumeChartProps>): JSX.Element {
const { data: items, currentVolume, changedVolume, isLoading } = volumeData || {}
Expand All @@ -134,23 +134,25 @@ export function VolumeChart({
const diffPercentageVolume = currentVolume && changedVolume && calcDiff(currentVolume, changedVolume)
const captionNameColor = getColorBySign(diffPercentageVolume || 0)
const [crossHairData, setCrossHairData] = useState<HistogramData | null>(null)
const previousPeriod = usePreviousLastValueData(periodId)
const network = useNetworkId()
const previousPeriod = usePreviousLastValueData(period)
const previousNetwork = usePreviousLastValueData(network)

// reset the chart when the volume period is changed
useEffect(() => {
if (periodId !== previousPeriod && chartCreated) {
if ((period !== previousPeriod || network !== previousNetwork) && chartCreated) {
chartCreated.resize(0, 0)
setChartCreated(null)
}
}, [chartCreated, periodId, previousPeriod])
}, [chartCreated, period, previousPeriod, network])

useEffect(() => {
if (chartCreated || !chartContainerRef.current || !items || isLoading) return

const chart = _buildChart(chartContainerRef.current, width, height, theme)
const series = chart.addAreaSeries({
lineWidth: 1,
lineColor: theme[captionNameColor],
lineColor: captionNameColor === 'red1' ? COLOR_NEGATIVE_DIFFERENCE_LINE : COLOR_POSITIVE_DIFFERENCE_LINE,
topColor: theme[captionNameColor],
bottomColor: captionNameColor === 'red1' ? COLOR_NEGATIVE_DIFFERENCE : COLOR_POSITIVE_DIFFERENCE,
})
Expand Down Expand Up @@ -180,6 +182,16 @@ export function VolumeChart({
chartCreated.timeScale().scrollToPosition(0, false)
}, [chartCreated, height, width])

const formattedDate = React.useMemo(() => {
if (!crossHairData) return ''

if (period === VolumePeriod.DAILY) {
return format(fromUnixTime(crossHairData.time as UTCTimestamp), 'MMM d HH:mm, yyyy')
}

return format(fromUnixTime(crossHairData.time as UTCTimestamp), 'MMM d, yyyy')
Comment on lines +189 to +192
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these date formats locale aware?

}, [crossHairData, period])

if (isLoading && chartCreated === undefined)
return (
<ChartSkeleton>
Expand All @@ -198,12 +210,19 @@ export function VolumeChart({
) : crossHairData ? (
<>
<p>${_formatAmount(crossHairData.value.toString())}</p>
<p className="date">{format(fromUnixTime(crossHairData.time as UTCTimestamp), 'MMM d, yyyy')}</p>
<p className="date">{formattedDate}</p>
</>
) : (
<>
<p>${currentVolume && _formatAmount(currentVolume.toString())}</p>
<p className="caption">{diffPercentageVolume && _formatAmount(diffPercentageVolume.toString())}%</p>
<p>${currentVolume && numberFormatter(currentVolume)}</p>
{Number.isNaN(diffPercentageVolume) ? (
''
) : (
<p className="caption">
{(diffPercentageVolume ?? 0) > 0 ? '+' : ''}
{diffPercentageVolume && _formatAmount(diffPercentageVolume.toString())}%
</p>
)}
</>
)}
</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { UTCTimestamp } from 'lightweight-charts'
import React, { useRef, useState, useEffect } from 'react'
import React, { useRef, useState } from 'react'
import styled from 'styled-components'
import { useGetVolumeData } from './useGetVolumeData'

import { PeriodButton, VolumeChart, VolumeItem, VolumeDataResponse } from './VolumeChart'
import volumeDataJson from './volumeData.json'
import { PeriodButton, VolumeChart } from './VolumeChart'

const WrapperVolumeChart = styled.div`
height: 19.6rem;
Expand All @@ -15,57 +14,6 @@ export enum VolumePeriod {
YEARLY = '1Y',
}

type RawVolumeItem = Pick<VolumeItem, 'id'> & {
timestamp: string
volumeUsd: string
}

// TODO move builds to a file where The graph API is called
export function buildVolumeData(
_data: RawVolumeItem[],
volumePeriod: VolumePeriod,
): {
data: VolumeItem[]
currentVolume: number
changedVolume: number
} {
const periods = {
[VolumePeriod.DAILY]: 7,
[VolumePeriod.WEEKLY]: 14,
[VolumePeriod.MONTHLY]: 30,
[VolumePeriod.YEARLY]: 365,
}
const slicedData = _data.slice(0, periods[volumePeriod])
return {
data: slicedData.map((item) => ({
id: item.id,
time: parseInt(item.timestamp) as UTCTimestamp,
value: parseFloat(item.volumeUsd),
})),
currentVolume: parseFloat(slicedData[slicedData.length - 1].volumeUsd),
changedVolume: parseFloat(slicedData[0].volumeUsd),
}
}

function useGetVolumeData(volumeTimePeriod = VolumePeriod.DAILY): VolumeDataResponse | undefined {
const [volumeData, setVolumeDataJson] = useState<VolumeDataResponse | undefined>()
const SECONDS = 2 // Emulating API Request delay

useEffect(() => {
setVolumeDataJson((prevState) => {
return { ...prevState, isLoading: true }
})
const timer = setTimeout(
() => setVolumeDataJson({ ...buildVolumeData(volumeDataJson, volumeTimePeriod), isLoading: false }),
SECONDS * 1000,
)

return (): void => clearTimeout(timer)
}, [volumeTimePeriod])

return volumeData
}

export function VolumeChartWidget(): JSX.Element {
const [periodSelected, setVolumeTimePeriod] = useState(VolumePeriod.DAILY)
const volumeData = useGetVolumeData(periodSelected)
Expand All @@ -88,7 +36,7 @@ export function VolumeChartWidget(): JSX.Element {

return (
<WrapperVolumeChart ref={containerRef}>
<VolumeChart volumeData={volumeData} width={width} periodId={periodSelected}>
<VolumeChart volumeData={volumeData} width={width} period={periodSelected}>
<PeriodButton
isLoading={volumeData?.isLoading}
active={periodSelected === VolumePeriod.DAILY}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useEffect, useState } from 'react'
import { HistogramData, UTCTimestamp } from 'lightweight-charts'
import { COW_SDK } from 'const'
import { useNetworkId } from 'state/network'
import { Network } from 'types'
import { VolumePeriod } from './VolumeChartWidget'
import { VolumeDataResponse } from './VolumeChart'

type RawVolumeItem = {
timestamp: string
volumeUsd: string
}

export function useGetVolumeData(volumeTimePeriod = VolumePeriod.DAILY): VolumeDataResponse | undefined {
const [volumeData, setVolumeDataJson] = useState<VolumeDataResponse | undefined>()
const network = useNetworkId() ?? Network.MAINNET

useEffect(() => {
setVolumeDataJson((prevState) => {
return { ...prevState, isLoading: true }
})

let rawData: Promise<RawVolumeItem[]>
if (volumeTimePeriod === VolumePeriod.DAILY) {
rawData = getLastHoursData(network)
} else {
rawData = getLastDaysData(volumeTimePeriod, network)
}

rawData.then((data: RawVolumeItem[]) => {
const volumeData = buildVolumeData(data, volumeTimePeriod)
volumeData.data.sort((a, b) => (a.time < b.time ? -1 : 1))
setVolumeDataJson({ ...volumeData, isLoading: false })
})
}, [network, volumeTimePeriod])

return volumeData
}

async function getLastHoursData(network: Network): Promise<RawVolumeItem[]> {
const data = await COW_SDK[network]?.cowSubgraphApi.getLastHoursVolume(48)

return (data?.hourlyTotals as RawVolumeItem[]) || []
}

async function getLastDaysData(
period: VolumePeriod.WEEKLY | VolumePeriod.MONTHLY | VolumePeriod.YEARLY,
network: Network,
): Promise<RawVolumeItem[]> {
const days = {
[VolumePeriod.WEEKLY]: 7 * 2,
[VolumePeriod.MONTHLY]: 30 * 2,
[VolumePeriod.YEARLY]: 365 * 2,
alfetopito marked this conversation as resolved.
Show resolved Hide resolved
}
const data = await COW_SDK[network]?.cowSubgraphApi.getLastDaysVolume(days[period])

return (data?.dailyTotals as RawVolumeItem[]) || []
}

export function buildVolumeData(
_data: RawVolumeItem[],
volumePeriod: VolumePeriod,
): {
data: HistogramData[]
currentVolume: number
changedVolume: number
} {
const periods = {
[VolumePeriod.DAILY]: 24,
[VolumePeriod.WEEKLY]: 7,
[VolumePeriod.MONTHLY]: 30,
[VolumePeriod.YEARLY]: 365,
}
const currentPeriodData = _data.slice(0, periods[volumePeriod])
const previousPeriodData = _data.slice(periods[volumePeriod], periods[volumePeriod] * 2)

return {
data: currentPeriodData.map((item) => ({
time: Number(item.timestamp) as UTCTimestamp,
value: Number(item.volumeUsd),
})),
currentVolume: getVolumeAverage(currentPeriodData),
changedVolume: getVolumeAverage(previousPeriodData),
}
}

function getVolumeAverage(data: RawVolumeItem[]): number {
return data.reduce((acc, item) => acc + Number(item.volumeUsd), 0) / data.length
}
Loading