Skip to content

Commit

Permalink
feat: show summary per day
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Nov 17, 2022
1 parent 3338db7 commit 70607ef
Show file tree
Hide file tree
Showing 11 changed files with 293 additions and 35 deletions.
31 changes: 18 additions & 13 deletions apps/web/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { ColorScheme, ColorSchemeProvider, MantineProvider } from '@mantine/core'
import { useHotkeys, useLocalStorage } from '@mantine/hooks'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Navigate, Route, Routes } from 'react-router-dom'
import { DaysIndex, DevIndex, HomeIndex, KreIndexFeature } from './feature'
import { NotFoundIndex } from './feature/not-found/not-found-index'
import { UiLayout } from './ui/layout/ui-layout'
import { UiLinks } from './ui/layout/ui-link'

const queryClient = new QueryClient()

export function App() {
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>({
key: 'kin-data',
Expand All @@ -18,23 +23,23 @@ export function App() {

const copyright = <p>Kin Foundation &copy; {new Date().getUTCFullYear()}</p>
const name = 'Kin Data'
const links: UiLinks = [
{ label: 'Home', path: '/home' },
{ label: 'KRE', path: '/KRE' },
]
const links: UiLinks = []

return (
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
<MantineProvider theme={{ colorScheme, primaryColor: 'violet' }} withGlobalStyles withNormalizeCSS>
{/*<UiLayout name={name} copyright={copyright} links={links}>*/}
<Routes>
<Route path="/" element={<Navigate to="/not-found" replace />} />
{/*<Route path="/dev/*" element={<DevIndex />} />*/}
{/*<Route path="/home" element={<HomeIndex />} />*/}
<Route path="/not-found" element={<NotFoundIndex />} />
{/*<Route path="/kre/*" element={<KreIndexFeature />} />*/}
</Routes>
{/*</UiLayout>*/}
<QueryClientProvider client={queryClient}>
<UiLayout name={name} copyright={copyright} links={links}>
<Routes>
<Route path="/" element={<Navigate to="/not-found" replace />} />
<Route path="/dates/*" element={<DaysIndex />} />
<Route path="/dev/*" element={<DevIndex />} />
<Route path="/home" element={<HomeIndex />} />
<Route path="/not-found" element={<NotFoundIndex />} />
<Route path="/kre/*" element={<KreIndexFeature />} />
</Routes>
</UiLayout>
</QueryClientProvider>
</MantineProvider>
</ColorSchemeProvider>
)
Expand Down
39 changes: 39 additions & 0 deletions apps/web/src/app/feature/days/days-api.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export interface DaySummary {
date: string
activeApps: number
activeUserBalance: number
activeCappedUserBalance: number
activeUsers: number
dailyTransactions: number
monthlyActiveEarners: number
monthlyActiveSpenders: number
monthlyActiveUsers: number
}

export class DaysApi {
static async getSummary(date?: string): Promise<DaySummary> {
return fetch(`/api/stats/summary?date=${date}`).then((r) => r.json())
}

static async getSummaryDates(): Promise<string[]> {
return fetch('/api/stats/summary-dates').then((r) => r.json())
}
}

export function formatNumber(amount: number, digits = 2) {
const lookup = [
{ value: 1, symbol: '' },
{ value: 1e3, symbol: 'Thousand' },
{ value: 1e6, symbol: 'Million' },
{ value: 1e9, symbol: 'Billion' },
{ value: 1e12, symbol: 'Trillion' },
]
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/
const item = lookup
.slice()
.reverse()
.find(function (item) {
return amount >= item.value
})
return item ? `${(amount / item.value).toFixed(digits).replace(rx, '$1')} ${item.symbol}` : '0'
}
88 changes: 88 additions & 0 deletions apps/web/src/app/feature/days/days-detail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Anchor, Card, Container, Flex, SimpleGrid, Stack, Text, Tooltip } from '@mantine/core'
import { useQuery } from '@tanstack/react-query'
import React, { useEffect } from 'react'
import { Link, useParams } from 'react-router-dom'
import { UiLoader } from '../../ui/loader/ui-loader'
import { DaysApi, formatNumber } from './days-api'

export function DaysDetail() {
const { date } = useParams()
const query = useQuery({ queryKey: ['date'], queryFn: () => DaysApi.getSummary(date) })
const queryDates = useQuery({ queryKey: ['dates'], queryFn: DaysApi.getSummaryDates })

useEffect(() => {
query.refetch()
}, [date])

return (
<Container>
<Card radius="md">
<Stack>
{date && queryDates?.data?.length ? <DaysStatHeader date={date} dates={queryDates.data} /> : null}
{query.isFetching ? (
<UiLoader />
) : (
<Stack>
<SimpleGrid cols={3}>
<DaysStatBox label={'Daily Transactions'} amount={query.data?.dailyTransactions ?? 0} />
<DaysStatBox label={'Active Apps'} amount={query.data?.activeApps ?? 0} />
<DaysStatBox label={'Active User Balance'} amount={query.data?.activeUserBalance ?? 0} />
</SimpleGrid>
<SimpleGrid cols={3}>
<DaysStatBox label={'Monthly Active Spenders'} amount={query.data?.monthlyActiveSpenders ?? 0} />
<DaysStatBox label={'Monthly Active Earners'} amount={query.data?.monthlyActiveEarners ?? 0} />
<DaysStatBox label={'Monthly Active Users'} amount={query.data?.monthlyActiveUsers ?? 0} />
</SimpleGrid>
</Stack>
)}
</Stack>
</Card>
</Container>
)
}

export function DaysStatBox({ amount, label }: { amount: number; label: string }) {
return (
<Stack>
<Text size="lg" color="dimmed">
{label}
</Text>
<Tooltip label={amount}>
<Text size="xl">{formatNumber(amount ?? 0)}</Text>
</Tooltip>
</Stack>
)
}

export function DaysStatHeader({ date, dates }: { date: string; dates: string[] }) {
const currentDate = dates.findIndex((d) => d === date) || 0
const previousDate = dates[currentDate + 1] || dates[0]
const nextDate = dates[currentDate - 1]

return (
<SimpleGrid cols={3}>
<DaysStatHeaderBox date={nextDate} />
<DaysStatHeaderBox date={date} />
<DaysStatHeaderBox date={previousDate} />
</SimpleGrid>
)
}

export function DaysStatHeaderBox({ date }: { date?: string }) {
return (
<Card radius="md" withBorder>
<Flex justify="center" align="center" mih={50}>
{date ? (
<Anchor component={Link} color={'primary'} to={`../${date}`}>
<Text align="center">{new Date(date).toLocaleDateString('en-US', { weekday: 'long' })}</Text>
<Text align="center" color="dimmed">
{new Date(date).toLocaleDateString()}
</Text>
</Anchor>
) : (
'No date selected'
)}
</Flex>
</Card>
)
}
13 changes: 13 additions & 0 deletions apps/web/src/app/feature/days/days-index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import { Route, Routes } from 'react-router-dom'
import { DaysDetail } from './days-detail'
import { DaysList } from './days-list'

export function DaysIndex() {
return (
<Routes>
<Route index element={<DaysList />} />
<Route path=":date" element={<DaysDetail />} />
</Routes>
)
}
31 changes: 31 additions & 0 deletions apps/web/src/app/feature/days/days-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Anchor, Card, Container, SimpleGrid, Text } from '@mantine/core'
import { useQuery } from '@tanstack/react-query'
import React from 'react'
import { Link } from 'react-router-dom'
import { UiLoader } from '../../ui/loader/ui-loader'
import { DaysApi } from './days-api'

export function DaysList() {
const query = useQuery({ queryKey: ['dates'], queryFn: DaysApi.getSummaryDates })

return (
<Container>
{query.isLoading ? (
<UiLoader />
) : (
<SimpleGrid cols={7} spacing="sm" verticalSpacing="sm">
{query.data?.map((date) => (
<Card radius="md" key={date}>
<Anchor component={Link} color={'primary'} to={`./${date}`}>
<Text size="sm">{new Date(date).toLocaleDateString('en-US', { weekday: 'long' })}</Text>
<Text size="xs" color="dimmed">
{new Date(date).toLocaleDateString()}
</Text>
</Anchor>
</Card>
))}
</SimpleGrid>
)}
</Container>
)
}
60 changes: 60 additions & 0 deletions apps/web/src/app/feature/days/days-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
import { DaysApi, DaySummary } from './days-api'

export interface DaysProviderContext {
dates: string[]
date: string | undefined
previous: string | undefined
next: string | undefined
loading: boolean
setDate: (date: string) => void
summary: DaySummary | undefined
}

const DaysContext = createContext<DaysProviderContext>({} as DaysProviderContext)

function DaysProvider({ children }: { children: ReactNode }) {
const [dates, setDates] = useState<string[]>([])
const [date, setDate] = useState<string>('')
const [summary, setSummary] = useState<DaySummary | undefined>()
const [loading, setLoading] = useState(false)

useEffect(() => {
if (dates?.length || loading) return
setLoading(true)
DaysApi.getSummaryDates().then((res: string[]) => {
setDates(res)
setLoading(false)
})
}, [dates, loading])

useEffect(() => {
if (!date || loading || summary) return
setLoading(true)
DaysApi.getSummary(date).then((res) => {
setSummary(res)
setLoading(false)
})
}, [date, summary, loading])

const previous = dates[dates.indexOf(date) + 1]
const next = dates[dates.indexOf(date) - 1]

const value = {
dates,
date,
previous,
next,
loading,
setDate: () => {
setSummary(undefined)
setDate(date)
},
summary,
}
return <DaysContext.Provider value={value}>{children}</DaysContext.Provider>
}

const useDays = () => useContext(DaysContext)

export { DaysProvider, useDays }
3 changes: 3 additions & 0 deletions apps/web/src/app/feature/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export * from './days/days-index'
export * from './dev/dev-index'
export * from './home/home-index'
export * from './kre/features/kre-detail-feature'
export * from './kre/features/kre-index-feature'
export * from './kre/features/kre-list-feature'
export * from './kre/features/string-to-color'
export { DaysDetail } from './days/days-detail'
export { DaysList } from './days/days-list'
2 changes: 1 addition & 1 deletion apps/web/src/app/ui/layout/ui-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function UiLayout({
background: theme.colorScheme === 'dark' ? theme.colors.dark[8] : theme.colors.gray[0],
},
}}
footer={<UiLayoutFooter copyright={copyright} />}
// footer={<UiLayoutFooter copyright={copyright} />}
header={<UiLayoutHeader name={name} links={links} />}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/ui/loader/ui-loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react'

export function UiLoader() {
return (
<Container sx={{ padding: 100 }}>
<Container sx={{ padding: 50 }}>
<Loader size="xl" m="auto" variant="bars" />
</Container>
)
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"@emotion/react": "^11.10.4",
"@google-cloud/bigquery": "^5.11.0",
"@kinecosystem/kin-sdk-v2": "^0.5.0",
"@mantine/core": "^5.5.6",
"@mantine/hooks": "^5.5.6",
"@mantine/core": "^5.8.0",
"@mantine/hooks": "^5.8.0",
"@nestjs/apollo": "^10.0.19",
"@nestjs/common": "9.0.8",
"@nestjs/config": "^2.2.0",
Expand All @@ -36,6 +36,7 @@
"@prisma/client": "^4.1.1",
"@solana/web3.js": "^1.42.0",
"@tabler/icons": "^1.106.0",
"@tanstack/react-query": "^4.16.1",
"apollo-server-core": "^3.6.3",
"apollo-server-express": "^3.6.3",
"camel-case": "^4.1.2",
Expand Down
Loading

0 comments on commit 70607ef

Please sign in to comment.