mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
refactor root components (#263)
* refactor layout * refactor * root components reordering * type-check fix
This commit is contained in:
parent
2437b72ea3
commit
ed57702ff3
src
tests/unit/components
@ -1,7 +1,7 @@
|
|||||||
.app {
|
.app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: url('../../node_modules/@oceanprotocol/art/waves/waves.svg')
|
background: url('../../node_modules/@oceanprotocol/art/waves/waves.svg')
|
||||||
no-repeat center 11rem;
|
no-repeat center 13.5rem;
|
||||||
|
|
||||||
/* sticky footer technique */
|
/* sticky footer technique */
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -20,8 +20,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
padding: calc(var(--spacer) * 2) 0;
|
padding: calc(var(--spacer) * 1.5) 0 calc(var(--spacer) * 2) 0;
|
||||||
|
|
||||||
/* sticky footer technique */
|
/* sticky footer technique */
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main > div[class*='Alert']:first-child {
|
||||||
|
margin-top: -2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main > div[class*='Alert'] {
|
||||||
|
margin-bottom: calc(var(--spacer) / 1.5);
|
||||||
|
}
|
46
src/components/App.tsx
Normal file
46
src/components/App.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import Footer from './organisms/Footer'
|
||||||
|
import Header from './organisms/Header'
|
||||||
|
import Styles from '../global/Styles'
|
||||||
|
import styles from './App.module.css'
|
||||||
|
import { useSiteMetadata } from '../hooks/useSiteMetadata'
|
||||||
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
|
import Alert from './atoms/Alert'
|
||||||
|
import { PageProps } from 'gatsby'
|
||||||
|
|
||||||
|
export default function App({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
children: ReactElement
|
||||||
|
}): ReactElement {
|
||||||
|
const { warning } = useSiteMetadata()
|
||||||
|
const {
|
||||||
|
isInPurgatory: isAccountInPurgatory,
|
||||||
|
purgatoryData: accountPurgatory
|
||||||
|
} = useOcean()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styles>
|
||||||
|
<div className={styles.app}>
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
{(props as PageProps).uri === '/' && (
|
||||||
|
<Alert text={warning} state="info" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isAccountInPurgatory && accountPurgatory && (
|
||||||
|
<Alert
|
||||||
|
title="Account In Purgatory"
|
||||||
|
badge={`Reason: ${accountPurgatory.reason}`}
|
||||||
|
text="No further actions are permitted by this account. For more details go to [list-purgatory](https://github.com/oceanprotocol/list-purgatory)."
|
||||||
|
state="error"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<main className={styles.main}>{children}</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</Styles>
|
||||||
|
)
|
||||||
|
}
|
@ -1,84 +0,0 @@
|
|||||||
import React, { ReactNode, ReactElement, useEffect } from 'react'
|
|
||||||
import Header from './organisms/Header'
|
|
||||||
import Footer from './organisms/Footer'
|
|
||||||
import PageHeader from './molecules/PageHeader'
|
|
||||||
import styles from './Layout.module.css'
|
|
||||||
import Seo from './atoms/Seo'
|
|
||||||
import Container from './atoms/Container'
|
|
||||||
import Alert from './atoms/Alert'
|
|
||||||
import { useSiteMetadata } from '../hooks/useSiteMetadata'
|
|
||||||
import { useAsset, useOcean } from '@oceanprotocol/react'
|
|
||||||
import { Logger } from '@oceanprotocol/lib'
|
|
||||||
|
|
||||||
export interface LayoutProps {
|
|
||||||
children: ReactNode
|
|
||||||
title: string
|
|
||||||
uri: string
|
|
||||||
description?: string
|
|
||||||
noPageHeader?: boolean
|
|
||||||
headerCenter?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Layout({
|
|
||||||
children,
|
|
||||||
title,
|
|
||||||
uri,
|
|
||||||
description,
|
|
||||||
noPageHeader,
|
|
||||||
headerCenter
|
|
||||||
}: LayoutProps): ReactElement {
|
|
||||||
const { warning } = useSiteMetadata()
|
|
||||||
const { isInPurgatory, purgatoryData } = useAsset()
|
|
||||||
const {
|
|
||||||
isInPurgatory: isAccountInPurgatory,
|
|
||||||
purgatoryData: accountPurgatory
|
|
||||||
} = useOcean()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
Logger.log('isInPurgatory', isInPurgatory, purgatoryData)
|
|
||||||
}, [isInPurgatory, purgatoryData])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.app}>
|
|
||||||
<Seo title={title} description={description} uri={uri} />
|
|
||||||
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
{uri === '/' && (
|
|
||||||
<Alert text={warning} state="info" className={styles.banner} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isAccountInPurgatory && accountPurgatory && (
|
|
||||||
<Alert
|
|
||||||
title="Account In Purgatory"
|
|
||||||
badge={`Reason: ${accountPurgatory.reason}`}
|
|
||||||
text="No further actions are permitted by this account. For more details go to [list-purgatory](https://github.com/oceanprotocol/list-purgatory)."
|
|
||||||
state="error"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isInPurgatory && purgatoryData && (
|
|
||||||
<Alert
|
|
||||||
title="Data Set In Purgatory"
|
|
||||||
badge={`Reason: ${purgatoryData.reason}`}
|
|
||||||
text="Except for removing liquidity, no further actions are permitted on this data set and it will not be returned in any search. For more details go to [list-purgatory](https://github.com/oceanprotocol/list-purgatory)."
|
|
||||||
state="error"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<main className={styles.main}>
|
|
||||||
<Container>
|
|
||||||
{title && !noPageHeader && (
|
|
||||||
<PageHeader
|
|
||||||
title={title}
|
|
||||||
description={description}
|
|
||||||
center={headerCenter}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
</Container>
|
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -13,7 +13,6 @@ import { useMetadata, useOcean, usePricing } from '@oceanprotocol/react'
|
|||||||
import EtherscanLink from '../../atoms/EtherscanLink'
|
import EtherscanLink from '../../atoms/EtherscanLink'
|
||||||
import Bookmark from './Bookmark'
|
import Bookmark from './Bookmark'
|
||||||
import Byline from './Byline'
|
import Byline from './Byline'
|
||||||
import Alert from '../../atoms/Alert'
|
|
||||||
|
|
||||||
export interface AssetContentProps {
|
export interface AssetContentProps {
|
||||||
metadata: MetadataMarket
|
metadata: MetadataMarket
|
||||||
|
39
src/components/templates/Page.tsx
Normal file
39
src/components/templates/Page.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React, { ReactNode, ReactElement } from 'react'
|
||||||
|
import PageHeader from '../molecules/PageHeader'
|
||||||
|
import Seo from '../atoms/Seo'
|
||||||
|
import Container from '../atoms/Container'
|
||||||
|
|
||||||
|
export interface PageProps {
|
||||||
|
children: ReactNode
|
||||||
|
title: string
|
||||||
|
uri: string
|
||||||
|
description?: string
|
||||||
|
noPageHeader?: boolean
|
||||||
|
headerCenter?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Page({
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
uri,
|
||||||
|
description,
|
||||||
|
noPageHeader,
|
||||||
|
headerCenter
|
||||||
|
}: PageProps): ReactElement {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Seo title={title} description={description} uri={uri} />
|
||||||
|
|
||||||
|
<Container>
|
||||||
|
{title && !noPageHeader && (
|
||||||
|
<PageHeader
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
center={headerCenter}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import React, { useState, useEffect, ReactElement } from 'react'
|
import React, { useState, useEffect, ReactElement } from 'react'
|
||||||
import { Router } from '@reach/router'
|
import { Router } from '@reach/router'
|
||||||
import AssetContent from '../../components/organisms/AssetContent'
|
import AssetContent from '../organisms/AssetContent'
|
||||||
import Layout from '../../components/Layout'
|
import Page from './Page'
|
||||||
import { MetadataMarket } from '../../@types/MetaData'
|
import { MetadataMarket } from '../../@types/MetaData'
|
||||||
import { MetadataCache, Logger, DDO } from '@oceanprotocol/lib'
|
import { MetadataCache, Logger, DDO } from '@oceanprotocol/lib'
|
||||||
import Alert from '../../components/atoms/Alert'
|
import Alert from '../atoms/Alert'
|
||||||
import Loader from '../../components/atoms/Loader'
|
import Loader from '../atoms/Loader'
|
||||||
import { useOcean } from '@oceanprotocol/react'
|
import { useAsset, useOcean } from '@oceanprotocol/react'
|
||||||
|
|
||||||
export default function PageTemplateAssetDetails({
|
export default function PageTemplateAssetDetails({
|
||||||
did,
|
did,
|
||||||
@ -16,6 +16,7 @@ export default function PageTemplateAssetDetails({
|
|||||||
uri: string
|
uri: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { config } = useOcean()
|
const { config } = useOcean()
|
||||||
|
const { isInPurgatory, purgatoryData } = useAsset()
|
||||||
const [metadata, setMetadata] = useState<MetadataMarket>()
|
const [metadata, setMetadata] = useState<MetadataMarket>()
|
||||||
const [title, setTitle] = useState<string>()
|
const [title, setTitle] = useState<string>()
|
||||||
const [error, setError] = useState<string>()
|
const [error, setError] = useState<string>()
|
||||||
@ -57,7 +58,16 @@ export default function PageTemplateAssetDetails({
|
|||||||
}, [ddo, did, config.metadataCacheUri])
|
}, [ddo, did, config.metadataCacheUri])
|
||||||
|
|
||||||
return did && metadata ? (
|
return did && metadata ? (
|
||||||
<Layout title={title} uri={uri}>
|
<>
|
||||||
|
{isInPurgatory && purgatoryData && (
|
||||||
|
<Alert
|
||||||
|
title="Data Set In Purgatory"
|
||||||
|
badge={`Reason: ${purgatoryData.reason}`}
|
||||||
|
text="Except for removing liquidity, no further actions are permitted on this data set and it will not be returned in any search. For more details go to [list-purgatory](https://github.com/oceanprotocol/list-purgatory)."
|
||||||
|
state="error"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Page title={title} uri={uri}>
|
||||||
<Router basepath="/asset">
|
<Router basepath="/asset">
|
||||||
<AssetContent
|
<AssetContent
|
||||||
ddo={ddo}
|
ddo={ddo}
|
||||||
@ -65,14 +75,15 @@ export default function PageTemplateAssetDetails({
|
|||||||
path=":did"
|
path=":did"
|
||||||
/>
|
/>
|
||||||
</Router>
|
</Router>
|
||||||
</Layout>
|
</Page>
|
||||||
|
</>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<Layout title={title} noPageHeader uri={uri}>
|
<Page title={title} noPageHeader uri={uri}>
|
||||||
<Alert title={title} text={error} state="error" />
|
<Alert title={title} text={error} state="error" />
|
||||||
</Layout>
|
</Page>
|
||||||
) : (
|
) : (
|
||||||
<Layout title={undefined} uri={uri}>
|
<Page title={undefined} uri={uri}>
|
||||||
<Loader />
|
<Loader />
|
||||||
</Layout>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { graphql, PageProps } from 'gatsby'
|
import { graphql, PageProps } from 'gatsby'
|
||||||
import Layout from '../Layout'
|
import Page from './Page'
|
||||||
import styles from './PageMarkdown.module.css'
|
import styles from './PageMarkdown.module.css'
|
||||||
import Container from '../atoms/Container'
|
import Container from '../atoms/Container'
|
||||||
|
|
||||||
@ -9,19 +9,14 @@ export default function PageTemplateMarkdown(props: PageProps): ReactElement {
|
|||||||
const { title, description } = frontmatter
|
const { title, description } = frontmatter
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Page title={title} description={description} uri={props.uri} headerCenter>
|
||||||
title={title}
|
|
||||||
description={description}
|
|
||||||
uri={props.uri}
|
|
||||||
headerCenter
|
|
||||||
>
|
|
||||||
<Container narrow>
|
<Container narrow>
|
||||||
<div
|
<div
|
||||||
className={styles.content}
|
className={styles.content}
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</Layout>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import { useOcean } from '@oceanprotocol/react'
|
|||||||
import { getOceanConfig } from './wrapRootElement'
|
import { getOceanConfig } from './wrapRootElement'
|
||||||
import { Logger } from '@oceanprotocol/lib'
|
import { Logger } from '@oceanprotocol/lib'
|
||||||
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
||||||
import { LogLevel } from '@oceanprotocol/lib/dist/node/utils'
|
|
||||||
|
|
||||||
export function NetworkMonitor(): ReactElement {
|
export function NetworkMonitor(): ReactElement {
|
||||||
const { connect, web3Provider, web3, networkId, config } = useOcean()
|
const { connect, web3Provider, web3, networkId, config } = useOcean()
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
import { PageProps } from 'gatsby'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Styles from '../global/Styles'
|
import App from '../components/App'
|
||||||
|
|
||||||
const wrapPageElement = ({
|
const wrapPageElement = ({
|
||||||
element
|
element,
|
||||||
|
props
|
||||||
}: {
|
}: {
|
||||||
element: ReactElement
|
element: ReactElement
|
||||||
}): ReactElement => <Styles>{element}</Styles>
|
props: PageProps
|
||||||
|
}): ReactElement => <App {...props}>{element}</App>
|
||||||
|
|
||||||
export default wrapPageElement
|
export default wrapPageElement
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
} from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
} from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
||||||
import { UserPreferencesProvider } from '../providers/UserPreferences'
|
import { UserPreferencesProvider } from '../providers/UserPreferences'
|
||||||
import PricesProvider from '../providers/Prices'
|
import PricesProvider from '../providers/Prices'
|
||||||
import { LogLevel } from '@oceanprotocol/lib/dist/node/utils'
|
|
||||||
|
|
||||||
export function getOceanConfig(
|
export function getOceanConfig(
|
||||||
network: ConfigHelperNetworkName | ConfigHelperNetworkId
|
network: ConfigHelperNetworkName | ConfigHelperNetworkId
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { PageProps } from 'gatsby'
|
import { PageProps } from 'gatsby'
|
||||||
import PageTemplateAssetDetails from '../../components/templates/AssetDetails'
|
import PageTemplateAssetDetails from '../../components/templates/PageAssetDetails'
|
||||||
import { AssetProvider } from '@oceanprotocol/react'
|
import { AssetProvider } from '@oceanprotocol/react'
|
||||||
|
|
||||||
export default function PageGatsbyAssetDetails(props: PageProps): ReactElement {
|
export default function PageGatsbyAssetDetails(props: PageProps): ReactElement {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import PageHistory from '../components/pages/History'
|
import PageHistory from '../components/pages/History'
|
||||||
import Layout from '../components/Layout'
|
import Page from '../components/templates/Page'
|
||||||
import { graphql, PageProps } from 'gatsby'
|
import { graphql, PageProps } from 'gatsby'
|
||||||
|
|
||||||
export default function PageGatsbyHistory(props: PageProps): ReactElement {
|
export default function PageGatsbyHistory(props: PageProps): ReactElement {
|
||||||
@ -8,9 +8,9 @@ export default function PageGatsbyHistory(props: PageProps): ReactElement {
|
|||||||
const { title, description } = content
|
const { title, description } = content
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout title={title} description={description} uri={props.uri}>
|
<Page title={title} description={description} uri={props.uri}>
|
||||||
<PageHistory />
|
<PageHistory />
|
||||||
</Layout>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,19 +2,19 @@ import React, { ReactElement } from 'react'
|
|||||||
import { PageProps } from 'gatsby'
|
import { PageProps } from 'gatsby'
|
||||||
import PageHome from '../components/pages/Home'
|
import PageHome from '../components/pages/Home'
|
||||||
import { useSiteMetadata } from '../hooks/useSiteMetadata'
|
import { useSiteMetadata } from '../hooks/useSiteMetadata'
|
||||||
import Layout from '../components/Layout'
|
import Page from '../components/templates/Page'
|
||||||
|
|
||||||
export default function PageGatsbyHome(props: PageProps): ReactElement {
|
export default function PageGatsbyHome(props: PageProps): ReactElement {
|
||||||
const { siteTitle, siteTagline } = useSiteMetadata()
|
const { siteTitle, siteTagline } = useSiteMetadata()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Page
|
||||||
title={siteTitle}
|
title={siteTitle}
|
||||||
description={siteTagline}
|
description={siteTagline}
|
||||||
uri={props.uri}
|
uri={props.uri}
|
||||||
headerCenter
|
headerCenter
|
||||||
>
|
>
|
||||||
<PageHome />
|
<PageHome />
|
||||||
</Layout>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import PagePublish from '../components/pages/Publish'
|
import PagePublish from '../components/pages/Publish'
|
||||||
import Layout from '../components/Layout'
|
import Page from '../components/templates/Page'
|
||||||
import { graphql, PageProps } from 'gatsby'
|
import { graphql, PageProps } from 'gatsby'
|
||||||
|
|
||||||
export default function PageGatsbyPublish(props: PageProps): ReactElement {
|
export default function PageGatsbyPublish(props: PageProps): ReactElement {
|
||||||
@ -8,9 +8,9 @@ export default function PageGatsbyPublish(props: PageProps): ReactElement {
|
|||||||
const { title, description } = content
|
const { title, description } = content
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout title={title} description={description} uri={props.uri}>
|
<Page title={title} description={description} uri={props.uri}>
|
||||||
<PagePublish content={content} />
|
<PagePublish content={content} />
|
||||||
</Layout>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import PageSearch from '../components/templates/Search'
|
import PageSearch from '../components/templates/Search'
|
||||||
import { PageProps } from 'gatsby'
|
import { PageProps } from 'gatsby'
|
||||||
import Layout from '../components/Layout'
|
import Page from '../components/templates/Page'
|
||||||
import queryString from 'query-string'
|
import queryString from 'query-string'
|
||||||
import { accountTruncate } from '../utils/wallet'
|
import { accountTruncate } from '../utils/wallet'
|
||||||
import ethereumAddress from 'ethereum-address'
|
import ethereumAddress from 'ethereum-address'
|
||||||
@ -20,8 +20,8 @@ export default function PageGatsbySearch(props: PageProps): ReactElement {
|
|||||||
: `Search for ${searchValue || 'all data sets'}`
|
: `Search for ${searchValue || 'all data sets'}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout title={title} uri={props.uri}>
|
<Page title={title} uri={props.uri}>
|
||||||
<PageSearch location={props.location} />
|
<PageSearch location={props.location} />
|
||||||
</Layout>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import testRender from '../testRender'
|
import testRender from '../testRender'
|
||||||
import Layout from '../../../src/components/Layout'
|
import Page from '../../../src/components/templates/Page'
|
||||||
import {
|
import {
|
||||||
createHistory,
|
createHistory,
|
||||||
createMemorySource,
|
createMemorySource,
|
||||||
LocationProvider
|
LocationProvider
|
||||||
} from '@reach/router'
|
} from '@reach/router'
|
||||||
|
|
||||||
describe('Layout', () => {
|
describe('Page', () => {
|
||||||
const history = createHistory(createMemorySource('/'))
|
const history = createHistory(createMemorySource('/'))
|
||||||
|
|
||||||
testRender(
|
testRender(
|
||||||
<LocationProvider history={history}>
|
<LocationProvider history={history}>
|
||||||
<Layout title="Hello" uri={history.location.href}>
|
<Page title="Hello" uri={history.location.href}>
|
||||||
Hello
|
Hello
|
||||||
</Layout>
|
</Page>
|
||||||
</LocationProvider>
|
</LocationProvider>
|
||||||
)
|
)
|
||||||
})
|
})
|
Loading…
Reference in New Issue
Block a user