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

Adding statistics SummaryCards component #50

Merged
merged 22 commits into from
Apr 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
167 changes: 167 additions & 0 deletions src/apps/explorer/components/SummaryCardsWidget/SummaryCards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React from 'react'
import styled, { css } from 'styled-components'
import { media } from 'theme/styles/media'
import { formatDistanceToNowStrict } from 'date-fns'

import { Card, CardContent } from 'components/common/Card'
import { CardRow } from 'components/common/CardRow'
import { TotalSummaryResponse } from '.'
import { abbreviateString } from 'utils'
import { useMediaBreakpoint } from 'hooks/useMediaBreakPoint'

const BatchInfoHeight = '19.6rem'
const DESKTOP_TEXT_SIZE = 1.8 // rem
const MOBILE_TEXT_SIZE = 1.65 // rem

const WrapperCardRow = styled(CardRow)`
max-width: 70%;

${media.mobile} {
max-width: 100%;
}
`

const DoubleContentSize = css`
min-height: ${BatchInfoHeight};
`
const WrapperColumn = styled.div`
/* Equivalent to use lg={8} MUI grid system */
flex-grow: 0;
max-width: 66.666667%;
flex-basis: 66.666667%;
padding-right: 2rem;

> div {
margin: 1rem;
max-height: ${BatchInfoHeight};
}

${media.mediumDownMd} {
flex-grow: 0;
max-width: 100%;
flex-basis: 100%;
}
`
const DoubleCardStyle = css`
${DoubleContentSize}

${media.mediumDownMd} {
min-height: 16rem;
}
`
const WrappedDoubleCard = styled(Card)`
${DoubleCardStyle}
`

const CardTransactions = styled(Card)`
${media.mediumDownMd} {
${DoubleCardStyle}
}
`

const WrapperDoubleContent = styled.div`
display: flex;
flex-direction: column;
gap: 3rem;

${media.mediumDownMd} {
gap: 2rem;
}
`

interface SummaryCardsProps {
summaryData: TotalSummaryResponse | undefined
children: React.ReactNode
}

function calcDiff(a: number, b: number): number {
return (a - b === 0 ? 0 : (100 * (a - b)) / b) || 0
}

function getColorBySign(n: number): string {
if (n > 0) {
return 'green'
} else if (n < 0) {
return 'red1'
}

return 'grey'
henrypalacios marked this conversation as resolved.
Show resolved Hide resolved
}

export function SummaryCards({ summaryData, children }: SummaryCardsProps): JSX.Element {
const { batchInfo, dailyTransactions, totalTokens, dailyFees, isLoading } = summaryData || {}
const isDesktop = useMediaBreakpoint(['xl', 'lg'])
const valueTextSize = isDesktop ? DESKTOP_TEXT_SIZE : MOBILE_TEXT_SIZE
const rowsByCard = isDesktop ? '2row' : '3row'
const diffTransactions = (dailyTransactions && calcDiff(dailyTransactions.now, dailyTransactions.before)) || 0
const diffFees = (dailyFees && calcDiff(dailyFees.now, dailyFees.before)) || 0

return (
<WrapperCardRow>
<>
<WrapperColumn>{children}</WrapperColumn>
<WrappedDoubleCard xs={6} lg={4}>
<WrapperDoubleContent>
<CardContent
variant="3row"
label1="Last Batch"
value1={batchInfo && formatDistanceToNowStrict(batchInfo.lastBatchDate)}
loading={isLoading}
valueSize={valueTextSize}
/>
<CardContent
variant="3row"
label1="Batch ID"
value1={batchInfo && abbreviateString(batchInfo?.batchId, 0, 6)}
loading={isLoading}
valueSize={valueTextSize}
/>
</WrapperDoubleContent>
</WrappedDoubleCard>
<CardTransactions xs={6} lg={4}>
<CardContent
variant={rowsByCard}
label1="24h Transactions"
value1={dailyTransactions?.now}
caption1={`${diffTransactions.toFixed(2)}%`}
captionColor={getColorBySign(diffTransactions)}
loading={isLoading}
valueSize={valueTextSize}
/>
</CardTransactions>
<Card xs={6} lg={4}>
<CardContent
variant="2row"
label1="Total Tokens"
value1={totalTokens}
loading={isLoading}
valueSize={valueTextSize}
/>
</Card>
<Card xs={6} lg={4}>
<CardContent
variant={rowsByCard}
label1="24h fees"
value1={`$${dailyFees?.now.toFixed(2)}`}
caption1={`${diffFees.toFixed(2)}%`}
captionColor={getColorBySign(diffFees)}
loading={isLoading}
valueSize={valueTextSize}
/>
</Card>
{/** Surplus is not yet available */}
{/* <Card>
<CardContent
variant="2row"
label1="30d Surplus"
value1={monthSurplus.now}
caption1={monthSurplus.before}
captionColor="green"
loading={isLoading}
valueSize={valueTextSize}
/>
</Card> */}
</>
</WrapperCardRow>
)
}
81 changes: 81 additions & 0 deletions src/apps/explorer/components/SummaryCardsWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useEffect, useState } from 'react'
import styled from 'styled-components'
import { SummaryCards } from './SummaryCards'

import summaryData from './summaryGraphResp.json'

const DELAY_SECONDS = 3 // Emulating API request

export interface BatchInfo {
lastBatchDate: Date
batchId: string
}

interface PastAndPresentValue {
now: number
before: number
}

interface TotalSummary {
batchInfo?: BatchInfo
dailyTransactions?: PastAndPresentValue
totalTokens?: number
dailyFees?: PastAndPresentValue
}

type RawTotalSummary = Omit<TotalSummary, 'batchInfo'> & {
batchInfo: { lastBatchDate: number; batchId: string }
}

function buildSummary(data: RawTotalSummary): TotalSummary {
return {
...data,
batchInfo: {
...data.batchInfo,
lastBatchDate: new Date(data.batchInfo.lastBatchDate * 1000),
},
}
}
henrypalacios marked this conversation as resolved.
Show resolved Hide resolved

export type TotalSummaryResponse = TotalSummary & {
isLoading: boolean
}

function useGetTotalSummary(): TotalSummaryResponse | undefined {
const [summary, setSummary] = useState<TotalSummaryResponse | undefined>()

useEffect(() => {
setSummary((prevState) => {
return { ...prevState, isLoading: true }
})
const timer = setTimeout(() => setSummary({ ...buildSummary(summaryData), isLoading: false }), DELAY_SECONDS * 1000)

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

return summary
}

const Wrapper = styled.div`
display: flex;
flex: 1;
justify-content: center;
`
const VolumeChart = styled.div`
background: #28f3282c;
border-radius: 0.4rem;
height: 19.6rem;
width: 100%;
`

export function StatsSummaryCardsWidget(): JSX.Element {
const summary = useGetTotalSummary()

return (
<Wrapper>
<SummaryCards summaryData={summary}>
<VolumeChart />
</SummaryCards>
</Wrapper>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"batchInfo": { "lastBatchDate": 1649881035, "batchId": "0x0003723b9eb1598e12d15c69206bf13c971be0310fa5744cf9601ed79f89e29c" },
"dailyTransactions": {"now": 612, "before": 912},
"totalTokens": 193,
"dailyFees": {"now": 55225.61205047748511254485049406426, "before": 40361.20651840192742698089787142249}
Copy link
Collaborator

Choose a reason for hiding this comment

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

One more thing about the shape of the data.

Usually it's not a good idea to work with floats in JS and also if working with big numbers JS number type won't be able to handle it.

Having said that, it might not be a problem for this use case as:

  1. We are simply displaying data without any further calculations, so loss of precision won't be really a problem
  2. I expect fees to not be large enough to go over JS number limit
  3. It mostly depends on how the graph response will look like, which might not be yet known

So, keep that in mind for the future, but does not need necessarily to change anything at this point

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I have it in mind! I would think they come as a string and may need to be formatted, but we can define it in #40 when integrating the SDK.

}
30 changes: 30 additions & 0 deletions src/apps/explorer/components/common/ShimmerBar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import styled, { keyframes } from 'styled-components'

const ShimmerKeyframe = keyframes`
0% {
background-position: 0px top;
}
90% {
background-position: 300px top;
}
100% {
background-position: 300px top;
}
`

const ShimmerBar = styled.div`
width: 100%;
height: 12px;
border-radius: 2px;
color: white;
background: ${({ theme }): string =>
`${theme.greyOpacity} -webkit-gradient(linear, 100% 0, 0 0, from(${theme.greyOpacity}), color-stop(0.5, ${theme.borderPrimary}), to(${theme.gradient1}))`};
background-position: -5rem top;
background-repeat: no-repeat;
-webkit-animation-name: ${ShimmerKeyframe};
-webkit-animation-duration: 1.3s;
-webkit-animation-iteration-count: infinite;
-webkit-background-size: 5rem 100%;
`

export default ShimmerBar
16 changes: 15 additions & 1 deletion src/apps/explorer/pages/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { Search } from 'apps/explorer/components/common/Search'
import { Wrapper as WrapperMod } from 'apps/explorer/pages/styled'
import styled from 'styled-components'
import { media } from 'theme/styles/media'
import { StatsSummaryCardsWidget } from 'apps/explorer/components/SummaryCardsWidget'

const Wrapper = styled(WrapperMod)`
max-width: 140rem;
flex-flow: column wrap;
justify-content: center;
justify-content: flex-start;
display: flex;
padding-top: 10rem;

> h1 {
justify-content: center;
Expand All @@ -23,11 +25,23 @@ const Wrapper = styled(WrapperMod)`
}
`

const SummaryWrapper = styled.section`
display: flex;
padding-top: 10rem;

${media.mobile} {
padding-top: 4rem;
}
`

export const Home: React.FC = () => {
return (
<Wrapper>
<h1>Search on CoW Protocol Explorer</h1>
<Search className="home" />
<SummaryWrapper>
<StatsSummaryCardsWidget />
</SummaryWrapper>
</Wrapper>
)
}
Expand Down
Loading