Commit abafa3a9 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #185 from blockscout/page-layout

page layout refactoring
parents 388081a4 7ac6e22c
import type { GetStaticPaths } from 'next';
export const getStaticPaths: GetStaticPaths = async() => {
return { paths: [], fallback: true };
};
...@@ -2,13 +2,13 @@ import Head from 'next/head'; ...@@ -2,13 +2,13 @@ import Head from 'next/head';
import React from 'react'; import React from 'react';
import Apps from 'ui/pages/Apps'; import Apps from 'ui/pages/Apps';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
const AppsPage = () => { const AppsPage = () => {
return ( return (
<Page> <Page>
<PageHeader text="Apps"/> <PageTitle text="Apps"/>
<Head><title>Apps</title></Head> <Head><title>Apps</title></Head>
<Apps/> <Apps/>
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import App from 'ui/pages/App';
const AppPage: NextPage = () => {
return (
<>
<Head><title>App Card Page</title></Head>
<App/>
</>
);
};
export default AppPage;
export { getStaticPaths } from 'lib/next/apps/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import { VStack, Textarea, Button, Alert, AlertTitle, AlertDescription, Link, Code } from '@chakra-ui/react'; import type { NextPage } from 'next';
import type { NextPage, GetStaticPaths } from 'next'; import Head from 'next/head';
import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
import * as cookies from 'lib/cookies'; import Home from 'ui/pages/Home';
import useNetwork from 'lib/hooks/useNetwork';
import useToast from 'lib/hooks/useToast';
import getAvailablePaths from 'lib/networks/getAvailablePaths';
import Page from 'ui/shared/Page';
import PageHeader from 'ui/shared/PageHeader';
const Home: NextPage = () => {
const router = useRouter();
const selectedNetwork = useNetwork();
const toast = useToast();
const [ isFormVisible, setFormVisibility ] = React.useState(false);
const [ token, setToken ] = React.useState('');
React.useEffect(() => {
const token = cookies.get(cookies.NAMES.API_TOKEN);
setFormVisibility(Boolean(!token && selectedNetwork?.isAccountSupported));
}, [ selectedNetwork?.isAccountSupported ]);
const handleTokenChange = React.useCallback((event: ChangeEvent<HTMLTextAreaElement>) => {
setToken(event.target.value);
}, []);
const handleSetTokenClick = React.useCallback(() => {
cookies.set(cookies.NAMES.API_TOKEN, token);
setToken('');
toast({
position: 'top-right',
title: 'Success 🥳',
description: 'Successfully set cookie',
status: 'success',
variant: 'subtle',
isClosable: true,
onCloseComplete: () => {
setFormVisibility(false);
},
});
}, [ toast, token ]);
const prodUrl = new URL(`/${ router.query.network_type }/${ router.query.network_sub_type }`, 'https://blockscout.com').toString();
const HomePage: NextPage = () => {
return ( return (
<Page> <>
<VStack gap={ 4 } alignItems="flex-start" maxW="800px"> <Head><title>Home Page</title></Head>
<PageHeader text={ <Home/>
`Home Page for ${ selectedNetwork?.name } network` </>
}/>
{ /* will be deleted when we move to new CI */ }
{ isFormVisible && (
<>
<Alert status="error" flexDirection="column" alignItems="flex-start">
<AlertTitle fontSize="md">
!!! Temporary solution for authentication !!!
</AlertTitle>
<AlertDescription mt={ 3 }>
To Sign in go to <Link href={ prodUrl } target="_blank">{ prodUrl }</Link> first, sign in there, copy obtained API token from cookie
<Code ml={ 1 }>{ cookies.NAMES.API_TOKEN }</Code> and paste it in the form below. After submitting the form you should be successfully
authenticated in current environment
</AlertDescription>
</Alert>
<Textarea value={ token } onChange={ handleTokenChange } placeholder="API token"/>
<Button onClick={ handleSetTokenClick }>Set cookie</Button>
</>
) }
</VStack>
</Page>
); );
}; };
export default Home; export default HomePage;
export const getStaticPaths: GetStaticPaths = async() => {
return { paths: getAvailablePaths(), fallback: false };
};
export const getStaticProps = async() => { export { getStaticPaths } from 'lib/next/account/getStaticPaths';
return { export { getStaticProps } from 'lib/next/getStaticProps';
props: {},
};
};
...@@ -12,8 +12,8 @@ import ApiKeyListItem from 'ui/apiKey/ApiKeyTable/ApiKeyListItem'; ...@@ -12,8 +12,8 @@ import ApiKeyListItem from 'ui/apiKey/ApiKeyTable/ApiKeyListItem';
import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable'; import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable';
import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal'; import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile'; import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable'; import SkeletonTable from 'ui/shared/SkeletonTable';
...@@ -128,7 +128,7 @@ const ApiKeysPage: React.FC = () => { ...@@ -128,7 +128,7 @@ const ApiKeysPage: React.FC = () => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageHeader text="API keys"/> <PageTitle text="API keys"/>
{ content } { content }
</Box> </Box>
</Page> </Page>
......
import { Center, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import Page from 'ui/shared/Page/Page';
const App = () => {
return (
<Page wrapChildren={ false }>
<Center as="main" bgColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } h="100%" paddingTop={{ base: '138px', lg: 0 }}>
3rd party app content
</Center>
</Page>
);
};
export default App;
...@@ -11,8 +11,8 @@ import CustomAbiListItem from 'ui/customAbi/CustomAbiTable/CustomAbiListItem'; ...@@ -11,8 +11,8 @@ import CustomAbiListItem from 'ui/customAbi/CustomAbiTable/CustomAbiListItem';
import CustomAbiTable from 'ui/customAbi/CustomAbiTable/CustomAbiTable'; import CustomAbiTable from 'ui/customAbi/CustomAbiTable/CustomAbiTable';
import DeleteCustomAbiModal from 'ui/customAbi/DeleteCustomAbiModal'; import DeleteCustomAbiModal from 'ui/customAbi/DeleteCustomAbiModal';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile'; import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable'; import SkeletonTable from 'ui/shared/SkeletonTable';
...@@ -116,7 +116,7 @@ const CustomAbiPage: React.FC = () => { ...@@ -116,7 +116,7 @@ const CustomAbiPage: React.FC = () => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageHeader text="Custom ABI"/> <PageTitle text="Custom ABI"/>
{ content } { content }
</Box> </Box>
</Page> </Page>
......
import { VStack, Textarea, Button, Alert, AlertTitle, AlertDescription, Link, Code } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react';
import React from 'react';
import * as cookies from 'lib/cookies';
import useNetwork from 'lib/hooks/useNetwork';
import useToast from 'lib/hooks/useToast';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
const Home = () => {
const router = useRouter();
const selectedNetwork = useNetwork();
const toast = useToast();
const [ isFormVisible, setFormVisibility ] = React.useState(false);
const [ token, setToken ] = React.useState('');
React.useEffect(() => {
const token = cookies.get(cookies.NAMES.API_TOKEN);
setFormVisibility(Boolean(!token && selectedNetwork?.isAccountSupported));
}, [ selectedNetwork?.isAccountSupported ]);
const handleTokenChange = React.useCallback((event: ChangeEvent<HTMLTextAreaElement>) => {
setToken(event.target.value);
}, []);
const handleSetTokenClick = React.useCallback(() => {
cookies.set(cookies.NAMES.API_TOKEN, token);
setToken('');
toast({
position: 'top-right',
title: 'Success 🥳',
description: 'Successfully set cookie',
status: 'success',
variant: 'subtle',
isClosable: true,
onCloseComplete: () => {
setFormVisibility(false);
},
});
}, [ toast, token ]);
const prodUrl = new URL(`/${ router.query.network_type }/${ router.query.network_sub_type }`, 'https://blockscout.com').toString();
return (
<Page>
<VStack gap={ 4 } alignItems="flex-start" maxW="800px">
<PageTitle text={
`Home Page for ${ selectedNetwork?.name } network`
}/>
{ /* will be deleted when we move to new CI */ }
{ isFormVisible && (
<>
<Alert status="error" flexDirection="column" alignItems="flex-start">
<AlertTitle fontSize="md">
!!! Temporary solution for authentication !!!
</AlertTitle>
<AlertDescription mt={ 3 }>
To Sign in go to <Link href={ prodUrl } target="_blank">{ prodUrl }</Link> first, sign in there, copy obtained API token from cookie
<Code ml={ 1 }>{ cookies.NAMES.API_TOKEN }</Code> and paste it in the form below. After submitting the form you should be successfully
authenticated in current environment
</AlertDescription>
</Alert>
<Textarea value={ token } onChange={ handleTokenChange } placeholder="API token"/>
<Button onClick={ handleSetTokenClick }>Set cookie</Button>
</>
) }
</VStack>
</Page>
);
};
export default Home;
...@@ -4,8 +4,8 @@ import React from 'react'; ...@@ -4,8 +4,8 @@ import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo'; import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
import UserAvatar from 'ui/shared/UserAvatar'; import UserAvatar from 'ui/shared/UserAvatar';
const MyProfile = () => { const MyProfile = () => {
...@@ -56,7 +56,7 @@ const MyProfile = () => { ...@@ -56,7 +56,7 @@ const MyProfile = () => {
return ( return (
<Page> <Page>
<PageHeader text="My profile"/> <PageTitle text="My profile"/>
{ content } { content }
</Page> </Page>
); );
......
import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import PrivateAddressTags from 'ui/privateTags/PrivateAddressTags'; import PrivateAddressTags from 'ui/privateTags/PrivateAddressTags';
import PrivateTransactionTags from 'ui/privateTags/PrivateTransactionTags'; import PrivateTransactionTags from 'ui/privateTags/PrivateTransactionTags';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const TABS: Array<RoutedTab> = [ const TABS: Array<RoutedTab> = [
...@@ -21,10 +20,8 @@ type Props = { ...@@ -21,10 +20,8 @@ type Props = {
const PrivateTags = ({ tab }: Props) => { const PrivateTags = ({ tab }: Props) => {
return ( return (
<Page> <Page>
<Box h="100%"> <PageTitle text="Private tags"/>
<PageHeader text="Private tags"/> <RoutedTabs tabs={ TABS } defaultActiveTab={ tab }/>
<RoutedTabs tabs={ TABS } defaultActiveTab={ tab }/>
</Box>
</Page> </Page>
); );
}; };
......
import { Box, Link, Text, Icon } from '@chakra-ui/react'; import { Link, Text, Icon } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { animateScroll } from 'react-scroll'; import { animateScroll } from 'react-scroll';
...@@ -9,8 +9,8 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -9,8 +9,8 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import useToast from 'lib/hooks/useToast'; import useToast from 'lib/hooks/useToast';
import PublicTagsData from 'ui/publicTags/PublicTagsData'; import PublicTagsData from 'ui/publicTags/PublicTagsData';
import PublicTagsForm from 'ui/publicTags/PublicTagsForm/PublicTagsForm'; import PublicTagsForm from 'ui/publicTags/PublicTagsForm/PublicTagsForm';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
type TScreen = 'data' | 'form'; type TScreen = 'data' | 'form';
...@@ -77,16 +77,14 @@ const PublicTagsComponent: React.FC = () => { ...@@ -77,16 +77,14 @@ const PublicTagsComponent: React.FC = () => {
return ( return (
<Page> <Page>
<Box h="100%"> { isMobile && screen === 'form' && (
{ isMobile && screen === 'form' && ( <Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }>
<Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }> <Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/>
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/> <Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text>
<Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text> </Link>
</Link> ) }
) } <PageTitle text={ header }/>
<PageHeader text={ header }/> { content }
{ content }
</Box>
</Page> </Page>
); );
}; };
......
...@@ -6,8 +6,8 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; ...@@ -6,8 +6,8 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
import useLink from 'lib/link/useLink'; import useLink from 'lib/link/useLink';
import ExternalLink from 'ui/shared/ExternalLink'; import ExternalLink from 'ui/shared/ExternalLink';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TxDetails from 'ui/tx/TxDetails'; import TxDetails from 'ui/tx/TxDetails';
import TxInternals from 'ui/tx/TxInternals'; import TxInternals from 'ui/tx/TxInternals';
...@@ -37,7 +37,7 @@ const TransactionPageContent = ({ tab }: Props) => { ...@@ -37,7 +37,7 @@ const TransactionPageContent = ({ tab }: Props) => {
<Icon as={ eastArrowIcon } boxSize={ 6 } mr={ 2 } transform="rotate(180deg)"/> <Icon as={ eastArrowIcon } boxSize={ 6 } mr={ 2 } transform="rotate(180deg)"/>
Transactions Transactions
</Link> </Link>
<PageHeader text="Transaction details"/> <PageTitle text="Transaction details"/>
<Flex marginLeft="auto" alignItems="center" flexWrap="wrap" columnGap={ 6 } rowGap={ 3 } mb={ 6 }> <Flex marginLeft="auto" alignItems="center" flexWrap="wrap" columnGap={ 6 } rowGap={ 3 } mb={ 6 }>
<ExternalLink title="Open in Tenderly" href="#"/> <ExternalLink title="Open in Tenderly" href="#"/>
<ExternalLink title="Open in Blockchair" href="#"/> <ExternalLink title="Open in Blockchair" href="#"/>
......
...@@ -5,8 +5,8 @@ import React from 'react'; ...@@ -5,8 +5,8 @@ import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TxsPending from 'ui/txs/TxsPending'; import TxsPending from 'ui/txs/TxsPending';
import TxsValidated from 'ui/txs/TxsValidated'; import TxsValidated from 'ui/txs/TxsValidated';
...@@ -25,7 +25,7 @@ const Transactions = ({ tab }: Props) => { ...@@ -25,7 +25,7 @@ const Transactions = ({ tab }: Props) => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageHeader text="Transactions"/> <PageTitle text="Transactions"/>
<RoutedTabs tabs={ TABS } defaultActiveTab={ tab }/> <RoutedTabs tabs={ TABS } defaultActiveTab={ tab }/>
</Box> </Box>
</Page> </Page>
......
...@@ -8,8 +8,8 @@ import useFetch from 'lib/hooks/useFetch'; ...@@ -8,8 +8,8 @@ import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageTitle from 'ui/shared/Page/PageTitle';
import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile'; import SkeletonAccountMobile from 'ui/shared/SkeletonAccountMobile';
import SkeletonTable from 'ui/shared/SkeletonTable'; import SkeletonTable from 'ui/shared/SkeletonTable';
import AddressModal from 'ui/watchlist/AddressModal/AddressModal'; import AddressModal from 'ui/watchlist/AddressModal/AddressModal';
...@@ -113,7 +113,7 @@ const WatchList: React.FC = () => { ...@@ -113,7 +113,7 @@ const WatchList: React.FC = () => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageHeader text="Watch list"/> <PageTitle text="Watch list"/>
{ content } { content }
</Box> </Box>
</Page> </Page>
......
import { Box, HStack, VStack } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import PageContent from 'ui/shared/Page/PageContent';
import Header from 'ui/snippets/header/Header'; import Header from 'ui/snippets/header/Header';
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop'; import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
wrapChildren?: boolean;
} }
const Page = ({ children }: Props) => { const Page = ({ children, wrapChildren = true }: Props) => {
const router = useRouter(); const router = useRouter();
const fetch = useFetch(); const fetch = useFetch();
...@@ -30,30 +32,18 @@ const Page = ({ children }: Props) => { ...@@ -30,30 +32,18 @@ const Page = ({ children }: Props) => {
} }
}, [ networkType, networkSubType ]); }, [ networkType, networkSubType ]);
const renderedChildren = wrapChildren ? (
<PageContent>{ children }</PageContent>
) : children;
return ( return (
<HStack <Flex w="100%" minH="100vh" alignItems="stretch">
w="100%"
minH="100vh"
alignItems="stretch"
>
<NavigationDesktop/> <NavigationDesktop/>
<VStack <Flex flexDir="column" width="100%">
width="100%"
paddingX={{ base: 4, lg: 8 }}
paddingTop={{ base: 0, lg: 9 }}
paddingBottom={ 10 }
spacing={ 0 }
>
<Header/> <Header/>
<Box { renderedChildren }
as="main" </Flex>
w="100%" </Flex>
paddingTop={{ base: '138px', lg: '52px' }}
>
{ children }
</Box>
</VStack>
</HStack>
); );
}; };
......
import { Box } from '@chakra-ui/react';
import React from 'react';
interface Props {
children: React.ReactNode;
}
const PageContent = ({ children }: Props) => {
return (
<Box
as="main"
w="100%"
paddingX={{ base: 4, lg: 12 }}
paddingBottom={ 10 }
paddingTop={{ base: '138px', lg: 0 }}
>
{ children }
</Box>
);
};
export default PageContent;
import { Heading } from '@chakra-ui/react'; import { Heading } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const PageHeader = ({ text }: {text: string}) => { const PageTitle = ({ text }: {text: string}) => {
return ( return (
<Heading as="h1" size="lg" marginBottom={{ base: 6, lg: 8 }}>{ text }</Heading> <Heading as="h1" size="lg" marginBottom={{ base: 6, lg: 8 }}>{ text }</Heading>
); );
}; };
export default PageHeader; export default PageTitle;
...@@ -40,6 +40,9 @@ const Header = () => { ...@@ -40,6 +40,9 @@ const Header = () => {
justifyContent="center" justifyContent="center"
gap={ 12 } gap={ 12 }
display={{ base: 'none', lg: 'flex' }} display={{ base: 'none', lg: 'flex' }}
paddingX={ 12 }
paddingTop={ 9 }
paddingBottom="52px"
> >
<SearchBar/> <SearchBar/>
<ColorModeToggler/> <ColorModeToggler/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment