-
Notifications
You must be signed in to change notification settings - Fork 481
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
888 additions
and
296 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import type { GetServerSideProps, NextPage } from 'next'; | ||
import dynamic from 'next/dynamic'; | ||
import React from 'react'; | ||
|
||
import type { Route } from 'nextjs-routes'; | ||
import * as gSSP from 'nextjs/getServerSideProps'; | ||
import type { Props } from 'nextjs/getServerSideProps'; | ||
import PageNextJs from 'nextjs/PageNextJs'; | ||
import detectBotRequest from 'nextjs/utils/detectBotRequest'; | ||
import fetchApi from 'nextjs/utils/fetchApi'; | ||
|
||
import config from 'configs/app'; | ||
import dayjs from 'lib/date/dayjs'; | ||
import getQueryParamString from 'lib/router/getQueryParamString'; | ||
|
||
const Chart = dynamic(() => import('ui/pages/Chart'), { ssr: false }); | ||
|
||
const pathname: Route['pathname'] = '/stats/[id]'; | ||
|
||
const Page: NextPage<Props<typeof pathname>> = (props: Props<typeof pathname>) => { | ||
return ( | ||
<PageNextJs pathname="/stats/[id]" query={ props.query } apiData={ props.apiData }> | ||
<Chart/> | ||
</PageNextJs> | ||
); | ||
}; | ||
|
||
export default Page; | ||
|
||
export const getServerSideProps: GetServerSideProps<Props<typeof pathname>> = async(ctx) => { | ||
const baseResponse = await gSSP.base<typeof pathname>(ctx); | ||
|
||
if ('props' in baseResponse) { | ||
if ( | ||
config.meta.seo.enhancedDataEnabled || | ||
(config.meta.og.enhancedDataEnabled && detectBotRequest(ctx.req)?.type === 'social_preview') | ||
) { | ||
const chartData = await fetchApi({ | ||
resource: 'stats_line', | ||
pathParams: { id: getQueryParamString(ctx.query.id) }, | ||
queryParams: { from: dayjs().format('YYYY-MM-DD'), to: dayjs().format('YYYY-MM-DD') }, | ||
timeout: 1000, | ||
}); | ||
|
||
(await baseResponse.props).apiData = chartData?.info ?? null; | ||
} | ||
} | ||
|
||
return baseResponse; | ||
}; | ||
|
||
// export { base as getServerSideProps } from 'nextjs/getServerSideProps'; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
import { Button, Flex, IconButton, Text } from '@chakra-ui/react'; | ||
import { useRouter } from 'next/router'; | ||
import React from 'react'; | ||
|
||
import { Resolution } from '@blockscout/stats-types'; | ||
import type { StatsIntervalIds } from 'types/client/stats'; | ||
|
||
import config from 'configs/app'; | ||
import { useAppContext } from 'lib/contexts/app'; | ||
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; | ||
import useIsMobile from 'lib/hooks/useIsMobile'; | ||
import isBrowser from 'lib/isBrowser'; | ||
import * as metadata from 'lib/metadata'; | ||
import * as mixpanel from 'lib/mixpanel/index'; | ||
import getQueryParamString from 'lib/router/getQueryParamString'; | ||
import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; | ||
import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect'; | ||
import ChartMenu from 'ui/shared/chart/ChartMenu'; | ||
import ChartResolutionSelect from 'ui/shared/chart/ChartResolutionSelect'; | ||
import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent'; | ||
import useChartQuery from 'ui/shared/chart/useChartQuery'; | ||
import useZoomReset from 'ui/shared/chart/useZoomReset'; | ||
import CopyToClipboard from 'ui/shared/CopyToClipboard'; | ||
import IconSvg from 'ui/shared/IconSvg'; | ||
import PageTitle from 'ui/shared/Page/PageTitle'; | ||
|
||
const DEFAULT_RESOLUTION = Resolution.DAY; | ||
|
||
const getIntervalByResolution = (resolution: Resolution): StatsIntervalIds => { | ||
switch (resolution) { | ||
case 'DAY': | ||
return 'oneMonth'; | ||
case 'WEEK': | ||
return 'oneMonth'; | ||
case 'MONTH': | ||
return 'oneYear'; | ||
case 'YEAR': | ||
return 'all'; | ||
default: | ||
return 'oneMonth'; | ||
} | ||
}; | ||
|
||
const Chart = () => { | ||
const router = useRouter(); | ||
const id = getQueryParamString(router.query.id); | ||
const [ intervalState, setIntervalState ] = React.useState<StatsIntervalIds | undefined>(); | ||
const [ resolution, setResolution ] = React.useState<Resolution>(DEFAULT_RESOLUTION); | ||
const { isZoomResetInitial, handleZoom, handleZoomReset } = useZoomReset(); | ||
|
||
const interval = intervalState || getIntervalByResolution(resolution); | ||
|
||
const ref = React.useRef(null); | ||
|
||
const isMobile = useIsMobile(); | ||
const isInBrowser = isBrowser(); | ||
|
||
const appProps = useAppContext(); | ||
const backLink = React.useMemo(() => { | ||
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/stats'); | ||
|
||
if (!hasGoBackLink) { | ||
return; | ||
} | ||
|
||
return { | ||
label: 'Back to charts list', | ||
url: appProps.referrer, | ||
}; | ||
}, [ appProps.referrer ]); | ||
|
||
const handleReset = React.useCallback(() => { | ||
handleZoomReset(); | ||
setResolution(DEFAULT_RESOLUTION); | ||
}, [ handleZoomReset ]); | ||
|
||
const { items, info, lineQuery } = useChartQuery(id, resolution, interval); | ||
|
||
React.useEffect(() => { | ||
if (info && !config.meta.seo.enhancedDataEnabled) { | ||
metadata.update({ pathname: '/stats/[id]', query: { id } }, info); | ||
} | ||
}, [ info, id ]); | ||
|
||
const onShare = React.useCallback(async() => { | ||
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Share chart', Info: id }); | ||
try { | ||
await window.navigator.share({ | ||
title: info?.title, | ||
text: info?.description, | ||
url: window.location.href, | ||
}); | ||
} catch (error) {} | ||
}, [ info, id ]); | ||
|
||
if (lineQuery.isError) { | ||
if (isCustomAppError(lineQuery.error)) { | ||
throwOnResourceLoadError({ resource: 'stats_line', error: lineQuery.error, isError: true }); | ||
} | ||
} | ||
|
||
const hasItems = (items && items.length > 2) || lineQuery.isPending; | ||
|
||
const isInfoLoading = !info && lineQuery.isPlaceholderData; | ||
|
||
const shareButton = isMobile ? ( | ||
<IconButton | ||
aria-label="share" | ||
variant="outline" | ||
boxSize={ 8 } | ||
size="sm" | ||
icon={ <IconSvg name="share" boxSize={ 5 }/> } | ||
onClick={ onShare } | ||
/> | ||
) : ( | ||
<Button | ||
leftIcon={ <IconSvg name="share" w={ 4 } h={ 4 }/> } | ||
colorScheme="blue" | ||
gridColumn={ 2 } | ||
justifySelf="end" | ||
alignSelf="top" | ||
gridRow="1/3" | ||
size="sm" | ||
variant="outline" | ||
onClick={ onShare } | ||
ml={ 6 } | ||
> | ||
Share | ||
</Button> | ||
); | ||
|
||
const shareAndMenu = ( | ||
<Flex alignItems="center" ml="auto" gap={ 3 }> | ||
{ /* TS thinks window.navigator.share can't be undefined, but it can */ } | ||
{ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ } | ||
{ (isInBrowser && ((window.navigator.share as any) ? | ||
shareButton : | ||
( | ||
<CopyToClipboard | ||
text={ config.app.baseUrl + router.asPath } | ||
size={ 5 } | ||
type="link" | ||
variant="outline" | ||
colorScheme="gray" | ||
display="flex" | ||
borderRadius="8px" | ||
width={ 8 } | ||
height={ 8 } | ||
/> | ||
) | ||
)) } | ||
{ (hasItems || lineQuery.isPlaceholderData) && ( | ||
<ChartMenu | ||
items={ items } | ||
title={ info?.title || '' } | ||
isLoading={ lineQuery.isPlaceholderData } | ||
chartRef={ ref } | ||
/> | ||
) } | ||
</Flex> | ||
); | ||
|
||
return ( | ||
<> | ||
<PageTitle | ||
title={ info?.title || lineQuery.data?.info?.title || '' } | ||
mb={ 3 } | ||
isLoading={ isInfoLoading } | ||
backLink={ backLink } | ||
contentAfter={ isMobile ? shareAndMenu : undefined } | ||
secondRow={ info?.description || lineQuery.data?.info?.description } | ||
// withTextAd | ||
/> | ||
<Flex alignItems="center" justifyContent="space-between"> | ||
<Flex alignItems="center" gap={ 3 } maxW="100%" overflow="hidden"> | ||
<Text>Period</Text> | ||
<ChartIntervalSelect interval={ interval } onIntervalChange={ setIntervalState }/> | ||
<Text>{ isMobile ? 'Res.' : 'Resolution' }</Text> | ||
<ChartResolutionSelect | ||
resolution={ resolution } | ||
onResolutionChange={ setResolution } | ||
resolutions={ lineQuery.data?.info?.resolutions || [] } | ||
/> | ||
{ (!isZoomResetInitial || resolution !== 'DAY') && ( | ||
isMobile ? ( | ||
<IconButton | ||
aria-label="Reset" | ||
variant="ghost" | ||
size="sm" | ||
icon={ <IconSvg name="repeat" boxSize={ 5 }/> } | ||
onClick={ handleReset } | ||
/> | ||
) : ( | ||
<Button | ||
leftIcon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> } | ||
colorScheme="blue" | ||
gridColumn={ 2 } | ||
justifySelf="end" | ||
alignSelf="top" | ||
gridRow="1/3" | ||
size="sm" | ||
variant="outline" | ||
onClick={ handleReset } | ||
ml={ 6 } | ||
> | ||
Reset | ||
</Button> | ||
) | ||
) } | ||
</Flex> | ||
{ !isMobile && shareAndMenu } | ||
</Flex> | ||
<Flex | ||
ref={ ref } | ||
flexGrow={ 1 } | ||
h="50vh" | ||
mt={ 3 } | ||
position="relative" | ||
> | ||
<ChartWidgetContent | ||
isError={ lineQuery.isError } | ||
items={ items } | ||
title={ info?.title || '' } | ||
units={ info?.units || undefined } | ||
isEnlarged | ||
isLoading={ lineQuery.isPlaceholderData } | ||
isZoomResetInitial={ isZoomResetInitial } | ||
handleZoom={ handleZoom } | ||
emptyText="No data for the selected resolution & interval." | ||
/> | ||
</Flex> | ||
</> | ||
); | ||
}; | ||
|
||
export default Chart; |
Oops, something went wrong.