1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge branch 'main' into feature/compute

This commit is contained in:
Matthias Kretschmann 2021-02-18 09:52:34 +01:00
commit 8430cb9ba2
Signed by: m
GPG Key ID: 606EEEF3C479A91F
15 changed files with 253 additions and 147 deletions

View File

@ -75,6 +75,8 @@ npm start
To use the app together with MetaMask, importing one of the accounts auto-generated by the Ganache container is the easiest way to have test ETH available. All of them have 100 ETH by default. Upon start, the `ocean_ganache_1` container will print out the private keys of multiple accounts in its logs. Pick one of them and import into MetaMask. To use the app together with MetaMask, importing one of the accounts auto-generated by the Ganache container is the easiest way to have test ETH available. All of them have 100 ETH by default. Upon start, the `ocean_ganache_1` container will print out the private keys of multiple accounts in its logs. Pick one of them and import into MetaMask.
To fully test all [The Graph](https://thegraph.com) integrations, you have to run your own local Graph node with our [`ocean-subgraph`](https://github.com/oceanprotocol/ocean-subgraph) deployed to it. Barge does not include a local subgraph so by default, the `config.subgraphUri` is hardcoded to the Rinkeby subgraph in our [`NetworkMonitor` component](https://github.com/oceanprotocol/market/blob/main/src/helpers/NetworkMonitor.tsx).
> Cleaning all Docker images so they are fetched freshly is often a good idea to make sure no issues are caused by old or stale images: `docker system prune --all --volumes` > Cleaning all Docker images so they are fetched freshly is often a good idea to make sure no issues are caused by old or stale images: `docker system prune --all --volumes`
## 🦑 Environment variables ## 🦑 Environment variables

View File

@ -1,4 +1,4 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement } from 'react'
import Footer from './organisms/Footer' import Footer from './organisms/Footer'
import Header from './organisms/Header' import Header from './organisms/Header'
import Styles from '../global/Styles' import Styles from '../global/Styles'
@ -7,15 +7,7 @@ import { useSiteMetadata } from '../hooks/useSiteMetadata'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
import Alert from './atoms/Alert' import Alert from './atoms/Alert'
import { graphql, PageProps, useStaticQuery } from 'gatsby' import { graphql, PageProps, useStaticQuery } from 'gatsby'
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
NormalizedCacheObject
} from '@apollo/client'
import fetch from 'cross-fetch'
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
const contentQuery = graphql` const contentQuery = graphql`
query AppQuery { query AppQuery {
purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) { purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) {
@ -45,24 +37,9 @@ export default function App({
const { warning } = useSiteMetadata() const { warning } = useSiteMetadata()
const { const {
isInPurgatory: isAccountInPurgatory, isInPurgatory: isAccountInPurgatory,
purgatoryData: accountPurgatory, purgatoryData: accountPurgatory
config
} = useOcean() } = useOcean()
const [client, setClient] = useState<ApolloClient<NormalizedCacheObject>>()
useEffect(() => {
const newClient = new ApolloClient({
link: new HttpLink({
uri: `${
(config as ConfigHelperConfig).subgraphUri
}/subgraphs/name/oceanprotocol/ocean-subgraph`,
fetch
}),
cache: new InMemoryCache()
})
setClient(newClient)
}, [config])
return ( return (
<Styles> <Styles>
<div className={styles.app}> <div className={styles.app}>
@ -78,13 +55,7 @@ export default function App({
state="error" state="error"
/> />
)} )}
{client ? (
<ApolloProvider client={client}>
<main className={styles.main}>{children}</main> <main className={styles.main}>{children}</main>
</ApolloProvider>
) : (
<></>
)}
<Footer /> <Footer />
</div> </div>
</Styles> </Styles>

View File

@ -23,15 +23,17 @@
color: var(--color-secondary); color: var(--color-secondary);
} }
.table [role='row']:not(:last-of-type) { .table [role='row'] {
border-color: var(--border-color); border-bottom: 1px solid var(--border-color) !important;
} }
.table + div [class*='rdt_Pagination'] { .table + div [class*='rdt_Pagination'] {
font-size: var(--font-size-small); font-size: var(--font-size-small);
font-weight: var(--font-weight-bold);
color: var(--color-secondary); color: var(--color-secondary);
background: none; background: none;
min-height: 0; min-height: 0;
padding-top: calc(var(--spacer) / 2);
} }
.table + div [class*='rdt_Pagination'] svg { .table + div [class*='rdt_Pagination'] svg {
@ -50,6 +52,11 @@
fill: var(--brand-pink); fill: var(--brand-pink);
} }
.table + div [class*='rdt_Pagination'] button[aria-label='First Page'],
.table + div [class*='rdt_Pagination'] button[aria-label='Last Page'] {
display: none;
}
.empty { .empty {
width: 100%; width: 100%;
text-align: left; text-align: left;
@ -57,3 +64,11 @@
font-size: var(--font-size-small); font-size: var(--font-size-small);
font-style: italic; font-style: italic;
} }
.arrow {
composes: arrow from '../molecules/Pagination.module.css';
}
.previous {
composes: previous from '../molecules/Pagination.module.css';
}

View File

@ -1,6 +1,7 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import DataTable, { IDataTableProps } from 'react-data-table-component' import DataTable, { IDataTableProps } from 'react-data-table-component'
import Loader from './Loader' import Loader from './Loader'
import Pagination from '../molecules/Pagination'
import styles from './Table.module.css' import styles from './Table.module.css'
interface TableProps extends IDataTableProps { interface TableProps extends IDataTableProps {
@ -35,10 +36,10 @@ export default function Table({
noHeader noHeader
pagination={pagination || data?.length >= 9} pagination={pagination || data?.length >= 9}
paginationPerPage={paginationPerPage || 10} paginationPerPage={paginationPerPage || 10}
paginationComponentOptions={{ noRowsPerPage: true }}
noDataComponent={<Empty message={emptyMessage} />} noDataComponent={<Empty message={emptyMessage} />}
progressPending={isLoading} progressPending={isLoading}
progressComponent={<Loader />} progressComponent={<Loader />}
paginationComponent={Pagination}
defaultSortField={sortField} defaultSortField={sortField}
defaultSortAsc={sortAsc} defaultSortAsc={sortAsc}
{...props} {...props}

View File

@ -28,7 +28,9 @@ export default function Tooltip({
trigger, trigger,
disabled, disabled,
className, className,
placement placement,
link,
reference
}: { }: {
content: ReactNode content: ReactNode
children?: ReactNode children?: ReactNode
@ -36,6 +38,8 @@ export default function Tooltip({
disabled?: boolean disabled?: boolean
className?: string className?: string
placement?: Placement placement?: Placement
link?: string
reference?: string
}): ReactElement { }): ReactElement {
const [props, setSpring] = useSpring(() => animation.from) const [props, setSpring] = useSpring(() => animation.from)
@ -72,6 +76,7 @@ export default function Tooltip({
<animated.div style={props}> <animated.div style={props}>
<div className={styles.content} {...attrs}> <div className={styles.content} {...attrs}>
{content} {content}
{link && <a href={link}>{reference}</a>}
<div className={styles.arrow} data-popper-arrow /> <div className={styles.arrow} data-popper-arrow />
</div> </div>
</animated.div> </animated.div>

View File

@ -12,3 +12,7 @@
color: var(--color-secondary) !important; color: var(--color-secondary) !important;
font-size: var(--font-size-small) !important; font-size: var(--font-size-small) !important;
} }
.info {
width: .85rem
}

View File

@ -1,83 +1,47 @@
import { Logger } from '@oceanprotocol/lib'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import PriceUnit from '../atoms/Price/PriceUnit'
import axios from 'axios'
import styles from './MarketStats.module.css' import styles from './MarketStats.module.css'
import { useInView } from 'react-intersection-observer' import { gql, useQuery } from '@apollo/client'
import Conversion from '../atoms/Price/Conversion'
import PriceUnit from '../atoms/Price/PriceUnit'
import Tooltip from '../atoms/Tooltip'
interface MarketStatsResponse { const getTotalPoolsValues = gql`
datasets: { query PoolsData {
pools: number poolFactories {
exchanges: number totalValueLocked
none: number totalOceanLiquidity
total: number finalizedPoolCount
} }
owners: number }
ocean: number `
datatoken: number
}
const refreshInterval = 60000 // 60 sec.
export default function MarketStats(): ReactElement { export default function MarketStats(): ReactElement {
const [ref, inView] = useInView() const [totalValueLocked, setTotalValueLocked] = useState<string>()
const [stats, setStats] = useState<MarketStatsResponse>() const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<string>()
const [poolCount, setPoolCount] = useState<number>()
const { data } = useQuery(getTotalPoolsValues)
useEffect(() => { useEffect(() => {
const source = axios.CancelToken.source() if (!data) return
async function getStats() { setTotalValueLocked(data.poolFactories[0].totalValueLocked)
try { setTotalOceanLiquidity(data.poolFactories[0].totalOceanLiquidity)
const response = await axios('https://market-stats.oceanprotocol.com', { setPoolCount(data.poolFactories[0].finalizedPoolCount)
cancelToken: source.token }, [data])
})
if (!response || response.status !== 200) return
setStats(response.data)
} catch (error) {
if (axios.isCancel(error)) {
Logger.log(error.message)
} else {
Logger.error(error.message)
}
}
}
// Update periodically when in viewport
const interval = setInterval(getStats, refreshInterval)
if (!inView) {
clearInterval(interval)
}
getStats()
return () => {
clearInterval(interval)
source.cancel()
}
}, [inView])
return ( return (
<div className={styles.stats} ref={ref}> <div className={styles.stats}>
Total of <strong>{stats?.datasets.total}</strong> data sets & unique <Conversion price={`${totalValueLocked}`} hideApproximateSymbol />{' '}
datatokens published by <strong>{stats?.owners}</strong> accounts. <abbr title="Total Value Locked">TVL</abbr> across{' '}
<br /> <strong>{poolCount}</strong> data set pools that contain{' '}
<PriceUnit <PriceUnit price={totalOceanLiquidity} small className={styles.total} />,
price={`${stats?.ocean}`} plus datatokens for each pool.
small <Tooltip
className={styles.total} className={styles.info}
conversion content="Counted on-chain from our pool factory. Does not filter out data sets in "
/>{' '} reference="list-purgatory"
and{' '} link="https://github.com/oceanprotocol/list-purgatory"
<PriceUnit />
price={`${stats?.datatoken}`}
symbol="datatokens"
small
className={styles.total}
/>{' '}
in <strong>{stats?.datasets.pools}</strong> data set pools.
<br />
<strong>{stats?.datasets.none}</strong> data sets have no price set yet.
</div> </div>
) )
} }

View File

@ -2,21 +2,25 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
margin-top: calc(var(--spacer) * 2); margin-top: var(--spacer);
margin-bottom: var(--spacer); margin-bottom: var(--spacer);
padding-left: 0; padding-left: 0;
font-size: var(--font-size-small);
} }
.number { .number {
text-align: center;
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2); padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
margin-left: -1px; margin-left: -1px;
margin-top: -1px; margin-top: -1px;
display: inline-block; display: block;
cursor: pointer; cursor: pointer;
border: 1px solid var(--border-color); min-width: 3rem;
min-width: 3.5rem; height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-secondary);
} }
li:first-child .number, li:first-child .number,
@ -39,8 +43,7 @@ li:last-child .number {
} }
.number:hover { .number:hover {
background-color: var(--color-primary); color: var(--brand-pink);
color: var(--brand-white);
} }
.current, .current,
@ -59,7 +62,7 @@ li:last-child .number {
.current:hover, .current:hover,
.current:focus, .current:focus,
.current:active { .current:active {
color: var(--brand-grey); color: var(--text-color);
} }
.next { .next {
@ -69,3 +72,13 @@ li:last-child .number {
.prevNextDisabled { .prevNextDisabled {
opacity: 0; opacity: 0;
} }
.arrow {
width: 1rem;
height: 1rem;
fill: currentColor;
}
.arrow.previous {
transform: rotate(180deg);
}

View File

@ -1,24 +1,49 @@
import React, { useState, useEffect, ReactElement } from 'react' import React, { useState, useEffect, ReactElement } from 'react'
import ReactPaginate from 'react-paginate' import ReactPaginate from 'react-paginate'
import styles from './Pagination.module.css' import styles from './Pagination.module.css'
import { ReactComponent as Arrow } from '../../images/arrow.svg'
interface PaginationProps { interface PaginationProps {
totalPages: number totalPages?: number
currentPage: number currentPage?: number
onPageChange(selected: number): void onChangePage?(selected: number): void
rowsPerPage?: number
rowCount?: number
} }
export default function Pagination({ export default function Pagination({
totalPages, totalPages,
currentPage, currentPage,
onPageChange rowsPerPage,
rowCount,
onChangePage
}: PaginationProps): ReactElement { }: PaginationProps): ReactElement {
const [smallViewport, setSmallViewport] = useState(true) const [smallViewport, setSmallViewport] = useState(true)
const [totalPageNumbers, setTotalPageNumbers] = useState<number>()
function getTotalPages() {
if (totalPages) return setTotalPageNumbers(totalPages)
const doublePageNumber = rowCount / rowsPerPage
const roundedPageNumber = Math.round(doublePageNumber)
const total =
roundedPageNumber < doublePageNumber
? roundedPageNumber + 1
: roundedPageNumber
setTotalPageNumbers(total)
}
function onPageChange(page: number) {
totalPages ? onChangePage(page) : onChangePage(page + 1)
}
function viewportChange(mq: { matches: boolean }) { function viewportChange(mq: { matches: boolean }) {
setSmallViewport(!mq.matches) setSmallViewport(!mq.matches)
} }
useEffect(() => {
getTotalPages()
}, [totalPages, rowCount])
useEffect(() => { useEffect(() => {
const mq = window.matchMedia('(min-width: 600px)') const mq = window.matchMedia('(min-width: 600px)')
viewportChange(mq) viewportChange(mq)
@ -29,18 +54,18 @@ export default function Pagination({
} }
}, []) }, [])
return totalPages && totalPages > 1 ? ( return totalPageNumbers && totalPageNumbers > 1 ? (
<ReactPaginate <ReactPaginate
pageCount={totalPages} pageCount={totalPageNumbers}
// react-pagination starts counting at 0, we start at 1 // react-pagination starts counting at 0, we start at 1
initialPage={currentPage - 1} initialPage={currentPage ? currentPage - 1 : 0}
// adapt based on media query match // adapt based on media query match
marginPagesDisplayed={smallViewport ? 0 : 1} marginPagesDisplayed={smallViewport ? 0 : 1}
pageRangeDisplayed={smallViewport ? 3 : 6} pageRangeDisplayed={smallViewport ? 3 : 6}
onPageChange={(data) => onPageChange(data.selected)} onPageChange={(data) => onPageChange(data.selected)}
disableInitialCallback disableInitialCallback
previousLabel="←" previousLabel={<Arrow className={`${styles.arrow} ${styles.previous}`} />}
nextLabel="→" nextLabel={<Arrow className={styles.arrow} />}
breakLabel="..." breakLabel="..."
containerClassName={styles.pagination} containerClassName={styles.pagination}
pageLinkClassName={styles.number} pageLinkClassName={styles.number}

View File

@ -48,7 +48,7 @@ const AssetList: React.FC<AssetListProps> = ({
<Pagination <Pagination
totalPages={totalPages} totalPages={totalPages}
currentPage={page} currentPage={page}
onPageChange={handlePageChange} onChangePage={handlePageChange}
/> />
)} )}
</> </>

View File

@ -4,20 +4,39 @@ import { useOcean } from '@oceanprotocol/react'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Loader from '../../atoms/Loader' import Loader from '../../atoms/Loader'
import AssetList from '../../organisms/AssetList' import AssetList from '../../organisms/AssetList'
import axios from 'axios'
import { queryMetadata } from '../../../utils/aquarius'
export default function PublishedList(): ReactElement { export default function PublishedList(): ReactElement {
const { ocean, status, accountId } = useOcean() const { accountId } = useOcean()
const [queryResult, setQueryResult] = useState<QueryResult>() const [queryResult, setQueryResult] = useState<QueryResult>()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const { config } = useOcean()
const source = axios.CancelToken.source()
const [page, setPage] = useState<number>(1)
useEffect(() => { useEffect(() => {
async function getPublished() { async function getPublished() {
if (!accountId || !ocean) return if (!accountId) return
const queryPublishedAssets = {
page: page,
offset: 9,
query: {
nativeSearch: 1,
query_string: {
query: `(publicKey.owner:${accountId})`
}
},
sort: { 'price.ocean': -1 }
}
try { try {
setIsLoading(true) queryResult || setIsLoading(true)
const queryResult = await ocean.assets.ownerAssets(accountId) const result = await queryMetadata(
setQueryResult(queryResult) queryPublishedAssets as any,
config.metadataCacheUri,
source.token
)
setQueryResult(result)
} catch (error) { } catch (error) {
Logger.error(error.message) Logger.error(error.message)
} finally { } finally {
@ -25,12 +44,20 @@ export default function PublishedList(): ReactElement {
} }
} }
getPublished() getPublished()
}, [ocean, status, accountId]) }, [accountId, page, config.metadataCacheUri])
return isLoading ? ( return isLoading ? (
<Loader /> <Loader />
) : accountId && ocean && queryResult ? ( ) : accountId && queryResult ? (
<AssetList assets={queryResult.results} showPagination={false} /> <AssetList
assets={queryResult.results}
showPagination
page={queryResult.page}
totalPages={queryResult.totalPages}
onPageChange={(newPage) => {
setPage(newPage)
}}
/>
) : ( ) : (
<div>Connect your wallet to see your published data sets.</div> <div>Connect your wallet to see your published data sets.</div>
) )

View File

@ -104,12 +104,20 @@ export default function HomePage(): ReactElement {
</section> </section>
<SectionQueryResult <SectionQueryResult
title="Highest Liquidity Pools" title="Highest Liquidity"
query={queryHighest} query={queryHighest}
action={
<Button
style="text"
to="/search?priceType=pool&sort=liquidity&sortOrder=desc"
>
All data sets with pool
</Button>
}
/> />
<SectionQueryResult <SectionQueryResult
title="New Data Sets" title="Recently Published"
query={queryLatest} query={queryLatest}
action={ action={
<Button style="text" to="/search?sort=created&sortOrder=desc"> <Button style="text" to="/search?sort=created&sortOrder=desc">

View File

@ -7,6 +7,18 @@ import contractAddresses from '@oceanprotocol/contracts/artifacts/address.json'
const refreshInterval = 5000 // 5 sec. const refreshInterval = 5000 // 5 sec.
export function getDevelopmentConfig(): Partial<ConfigHelperConfig> {
return {
factoryAddress: contractAddresses.development?.DTFactory,
poolFactoryAddress: contractAddresses.development?.BFactory,
fixedRateExchangeAddress: contractAddresses.development?.FixedRateExchange,
metadataContractAddress: contractAddresses.development?.Metadata,
oceanTokenAddress: contractAddresses.development?.Ocean,
// There is no subgraph in barge so we hardcode the Rinkeby one for now
subgraphUri: 'https://subgraph.rinkeby.oceanprotocol.com'
}
}
export function NetworkMonitor(): ReactElement { export function NetworkMonitor(): ReactElement {
const { const {
connect, connect,
@ -28,12 +40,7 @@ export function NetworkMonitor(): ReactElement {
// add local dev values // add local dev values
...(chainId === '8996' && { ...(chainId === '8996' && {
factoryAddress: contractAddresses.development?.DTFactory, ...getDevelopmentConfig()
poolFactoryAddress: contractAddresses.development?.BFactory,
fixedRateExchangeAddress:
contractAddresses.development?.FixedRateExchange,
metadataContractAddress: contractAddresses.development?.Metadata,
oceanTokenAddress: contractAddresses.development?.Ocean
}) })
} }

View File

@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
import { OceanProvider } from '@oceanprotocol/react' import { OceanProvider } from '@oceanprotocol/react'
import { ConfigHelper, Config } from '@oceanprotocol/lib' import { ConfigHelper, Config } from '@oceanprotocol/lib'
import { web3ModalOpts } from '../utils/wallet' import { web3ModalOpts } from '../utils/wallet'
import { NetworkMonitor } from './NetworkMonitor' import { getDevelopmentConfig, NetworkMonitor } from './NetworkMonitor'
import appConfig from '../../app.config' import appConfig from '../../app.config'
import { import {
ConfigHelperNetworkName, ConfigHelperNetworkName,
@ -10,6 +10,7 @@ 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 ApolloClientProvider from '../providers/ApolloClientProvider'
export function getOceanConfig( export function getOceanConfig(
network: ConfigHelperNetworkName | ConfigHelperNetworkId network: ConfigHelperNetworkName | ConfigHelperNetworkId
@ -26,16 +27,26 @@ export default function wrapRootElement({
element: ReactElement element: ReactElement
}): ReactElement { }): ReactElement {
const { network } = appConfig const { network } = appConfig
const oceanInitialConfig = getOceanConfig(network) const oceanInitialConfig = {
...getOceanConfig(network),
// add local dev values
...(network === 'development' && {
...getDevelopmentConfig()
})
}
return ( return (
<OceanProvider <OceanProvider
initialConfig={oceanInitialConfig} initialConfig={oceanInitialConfig}
web3ModalOpts={web3ModalOpts} web3ModalOpts={web3ModalOpts}
> >
<ApolloClientProvider>
<UserPreferencesProvider> <UserPreferencesProvider>
<NetworkMonitor /> <NetworkMonitor />
<PricesProvider>{element}</PricesProvider> <PricesProvider>{element}</PricesProvider>
</UserPreferencesProvider> </UserPreferencesProvider>
</ApolloClientProvider>
</OceanProvider> </OceanProvider>
) )
} }

View File

@ -0,0 +1,53 @@
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
NormalizedCacheObject
} from '@apollo/client'
import { Logger } from '@oceanprotocol/lib'
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
import { useOcean } from '@oceanprotocol/react'
import fetch from 'cross-fetch'
import React, { useState, useEffect, ReactNode, ReactElement } from 'react'
function createClient(subgraphUri: string) {
const client = new ApolloClient({
link: new HttpLink({
uri: `${subgraphUri}/subgraphs/name/oceanprotocol/ocean-subgraph`,
fetch
}),
cache: new InMemoryCache()
})
return client
}
export default function ApolloClientProvider({
children
}: {
children: ReactNode
}): ReactElement {
const { config } = useOcean()
const [client, setClient] = useState<ApolloClient<NormalizedCacheObject>>()
useEffect(() => {
if (!(config as ConfigHelperConfig)?.subgraphUri) {
Logger.error(
'No subgraphUri defined, preventing ApolloProvider from initialization.'
)
return
}
const newClient = createClient((config as ConfigHelperConfig).subgraphUri)
setClient(newClient)
}, [config])
return client ? (
<ApolloProvider client={client}>{children}</ApolloProvider>
) : (
<></>
)
}
export { ApolloClientProvider }