diff --git a/src/assets/chain-icons/arbitrum-sepolia.svg b/src/assets/chain-icons/arbitrum-sepolia.svg new file mode 100644 index 00000000..29208a6a --- /dev/null +++ b/src/assets/chain-icons/arbitrum-sepolia.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/ChainLink.tsx b/src/components/ChainLink.tsx new file mode 100644 index 00000000..d6f5d68e --- /dev/null +++ b/src/components/ChainLink.tsx @@ -0,0 +1,18 @@ +import { useParams } from '@tanstack/react-router'; +import { Link } from '@tanstack/react-router'; +import { ComponentProps } from 'react'; + +function buildRoutePath(path: string, chainSlug: string) { + if (path === '/') return `/${chainSlug}`; + return `/${chainSlug}${path.startsWith('/') ? path : `/${path}`}`; +} + +type ChainLinkProps = { + to: string; +} & Omit, 'to'>; + +export function ChainLink({ to, ...props }: ChainLinkProps) { + const { chainSlug } = useParams({ from: '/$chainSlug' }); + const path = buildRoutePath(to, chainSlug); + return ; +} diff --git a/src/components/DataTable.tsx b/src/components/DataTable.tsx index 70ec76f5..70a2e9bd 100644 --- a/src/components/DataTable.tsx +++ b/src/components/DataTable.tsx @@ -1,4 +1,3 @@ -import { Link } from '@tanstack/react-router'; import { ColumnDef, flexRender, @@ -13,6 +12,7 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; +import { ChainLink } from './ChainLink'; interface DataTableProps { columns: ColumnDef[]; @@ -58,12 +58,12 @@ export function DataTable({ {row.getVisibleCells().map((cell) => ( - {flexRender(cell.column.columnDef.cell, cell.getContext())} - + ))} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index a21617fc..17bc83ec 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -6,15 +6,19 @@ import { SiX, SiYoutube, } from '@icons-pack/react-simple-icons'; -import { Link } from '@tanstack/react-router'; import { Linkedin } from 'lucide-react'; +import useUserStore from '@/stores/useUser.store'; +import { getBlockExplorerUrl } from '@/utils/chain.utils'; import iExecLogo from '../assets/iexec-logo.svg'; +import { ChainLink } from './ChainLink'; import { Button } from './ui/button'; export function Footer({ className }: { className?: string }) { + const { chainId } = useUserStore(); + const navLinks = [ { href: 'https://www.iex.ec/', label: 'iExec Website' }, - { href: 'https://blockscout-bellecour.iex.ec/', label: 'Block explorer' }, // TODO import it from protocol data list + { href: getBlockExplorerUrl(chainId), label: 'Block explorer' }, { href: 'https://www.iex.ec/contact', label: 'Contact Us' }, ]; @@ -48,10 +52,10 @@ export function Footer({ className }: { className?: string }) { )} >
- + iExec logo iExec Explorer - +
{(tasks.isError || tasks.errorUpdateCount > 0) && !tasks.data ? ( diff --git a/src/modules/workerpools/WorkerpoolsPreviewTable.tsx b/src/modules/workerpools/WorkerpoolsPreviewTable.tsx index eac85811..ab6eda30 100644 --- a/src/modules/workerpools/WorkerpoolsPreviewTable.tsx +++ b/src/modules/workerpools/WorkerpoolsPreviewTable.tsx @@ -2,21 +2,26 @@ import { PREVIEW_TABLE_LENGTH, PREVIEW_TABLE_REFETCH_INTERVAL } from '@/config'; import { execute } from '@/graphql/execute'; import { cn } from '@/lib/utils'; import { useQuery } from '@tanstack/react-query'; -import { Link } from '@tanstack/react-router'; import { Box, LoaderCircle, Terminal } from 'lucide-react'; -import { CircularLoader } from '@/components/CircularLoader'; +import { ChainLink } from '@/components/ChainLink'; import { DataTable } from '@/components/DataTable'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; +import useUserStore from '@/stores/useUser.store'; import { workerpoolsQuery } from './workerpoolsQuery'; import { columns } from './workerpoolsTable/columns'; export function WorkerpoolsPreviewTable({ className }: { className?: string }) { + const { chainId } = useUserStore(); const workerpools = useQuery({ - queryKey: ['workerpools_preview'], + queryKey: [chainId, 'workerpools_preview'], queryFn: () => - execute(workerpoolsQuery, { length: PREVIEW_TABLE_LENGTH, skip: 0 }), + execute(workerpoolsQuery, chainId, { + length: PREVIEW_TABLE_LENGTH, + skip: 0, + }), refetchInterval: PREVIEW_TABLE_REFETCH_INTERVAL, + enabled: !!chainId, }); const formattedData = @@ -39,7 +44,7 @@ export function WorkerpoolsPreviewTable({ className }: { className?: string }) { {workerpools.isFetching && } {(workerpools.isError || workerpools.errorUpdateCount > 0) && diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 82452268..acf5847d 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -8,52 +8,79 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. +import { createFileRoute } from '@tanstack/react-router' + // Import Routes import { Route as rootRoute } from './routes/__root' -import { Route as WorkerpoolsImport } from './routes/workerpools' -import { Route as TasksImport } from './routes/tasks' -import { Route as DealsImport } from './routes/deals' -import { Route as DatasetsImport } from './routes/datasets' -import { Route as AppsImport } from './routes/apps' import { Route as IndexImport } from './routes/index' +import { Route as ChainSlugLayoutImport } from './routes/$chainSlug/_layout' +import { Route as ChainSlugLayoutIndexImport } from './routes/$chainSlug/_layout/index' +import { Route as ChainSlugLayoutWorkerpoolsImport } from './routes/$chainSlug/_layout/workerpools' +import { Route as ChainSlugLayoutTasksImport } from './routes/$chainSlug/_layout/tasks' +import { Route as ChainSlugLayoutDealsImport } from './routes/$chainSlug/_layout/deals' +import { Route as ChainSlugLayoutDatasetsImport } from './routes/$chainSlug/_layout/datasets' +import { Route as ChainSlugLayoutAppsImport } from './routes/$chainSlug/_layout/apps' + +// Create Virtual Routes + +const ChainSlugImport = createFileRoute('/$chainSlug')() // Create/Update Routes -const WorkerpoolsRoute = WorkerpoolsImport.update({ - id: '/workerpools', - path: '/workerpools', +const ChainSlugRoute = ChainSlugImport.update({ + id: '/$chainSlug', + path: '/$chainSlug', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRoute, } as any) -const TasksRoute = TasksImport.update({ +const ChainSlugLayoutRoute = ChainSlugLayoutImport.update({ + id: '/_layout', + getParentRoute: () => ChainSlugRoute, +} as any) + +const ChainSlugLayoutIndexRoute = ChainSlugLayoutIndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => ChainSlugLayoutRoute, +} as any) + +const ChainSlugLayoutWorkerpoolsRoute = ChainSlugLayoutWorkerpoolsImport.update( + { + id: '/workerpools', + path: '/workerpools', + getParentRoute: () => ChainSlugLayoutRoute, + } as any, +) + +const ChainSlugLayoutTasksRoute = ChainSlugLayoutTasksImport.update({ id: '/tasks', path: '/tasks', - getParentRoute: () => rootRoute, + getParentRoute: () => ChainSlugLayoutRoute, } as any) -const DealsRoute = DealsImport.update({ +const ChainSlugLayoutDealsRoute = ChainSlugLayoutDealsImport.update({ id: '/deals', path: '/deals', - getParentRoute: () => rootRoute, + getParentRoute: () => ChainSlugLayoutRoute, } as any) -const DatasetsRoute = DatasetsImport.update({ +const ChainSlugLayoutDatasetsRoute = ChainSlugLayoutDatasetsImport.update({ id: '/datasets', path: '/datasets', - getParentRoute: () => rootRoute, + getParentRoute: () => ChainSlugLayoutRoute, } as any) -const AppsRoute = AppsImport.update({ +const ChainSlugLayoutAppsRoute = ChainSlugLayoutAppsImport.update({ id: '/apps', path: '/apps', - getParentRoute: () => rootRoute, -} as any) - -const IndexRoute = IndexImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRoute, + getParentRoute: () => ChainSlugLayoutRoute, } as any) // Populate the FileRoutesByPath interface @@ -67,106 +94,177 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } - '/apps': { - id: '/apps' - path: '/apps' - fullPath: '/apps' - preLoaderRoute: typeof AppsImport + '/$chainSlug': { + id: '/$chainSlug' + path: '/$chainSlug' + fullPath: '/$chainSlug' + preLoaderRoute: typeof ChainSlugImport parentRoute: typeof rootRoute } - '/datasets': { - id: '/datasets' + '/$chainSlug/_layout': { + id: '/$chainSlug/_layout' + path: '/$chainSlug' + fullPath: '/$chainSlug' + preLoaderRoute: typeof ChainSlugLayoutImport + parentRoute: typeof ChainSlugRoute + } + '/$chainSlug/_layout/apps': { + id: '/$chainSlug/_layout/apps' + path: '/apps' + fullPath: '/$chainSlug/apps' + preLoaderRoute: typeof ChainSlugLayoutAppsImport + parentRoute: typeof ChainSlugLayoutImport + } + '/$chainSlug/_layout/datasets': { + id: '/$chainSlug/_layout/datasets' path: '/datasets' - fullPath: '/datasets' - preLoaderRoute: typeof DatasetsImport - parentRoute: typeof rootRoute + fullPath: '/$chainSlug/datasets' + preLoaderRoute: typeof ChainSlugLayoutDatasetsImport + parentRoute: typeof ChainSlugLayoutImport } - '/deals': { - id: '/deals' + '/$chainSlug/_layout/deals': { + id: '/$chainSlug/_layout/deals' path: '/deals' - fullPath: '/deals' - preLoaderRoute: typeof DealsImport - parentRoute: typeof rootRoute + fullPath: '/$chainSlug/deals' + preLoaderRoute: typeof ChainSlugLayoutDealsImport + parentRoute: typeof ChainSlugLayoutImport } - '/tasks': { - id: '/tasks' + '/$chainSlug/_layout/tasks': { + id: '/$chainSlug/_layout/tasks' path: '/tasks' - fullPath: '/tasks' - preLoaderRoute: typeof TasksImport - parentRoute: typeof rootRoute + fullPath: '/$chainSlug/tasks' + preLoaderRoute: typeof ChainSlugLayoutTasksImport + parentRoute: typeof ChainSlugLayoutImport } - '/workerpools': { - id: '/workerpools' + '/$chainSlug/_layout/workerpools': { + id: '/$chainSlug/_layout/workerpools' path: '/workerpools' - fullPath: '/workerpools' - preLoaderRoute: typeof WorkerpoolsImport - parentRoute: typeof rootRoute + fullPath: '/$chainSlug/workerpools' + preLoaderRoute: typeof ChainSlugLayoutWorkerpoolsImport + parentRoute: typeof ChainSlugLayoutImport + } + '/$chainSlug/_layout/': { + id: '/$chainSlug/_layout/' + path: '/' + fullPath: '/$chainSlug/' + preLoaderRoute: typeof ChainSlugLayoutIndexImport + parentRoute: typeof ChainSlugLayoutImport } } } // Create and export the route tree +interface ChainSlugLayoutRouteChildren { + ChainSlugLayoutAppsRoute: typeof ChainSlugLayoutAppsRoute + ChainSlugLayoutDatasetsRoute: typeof ChainSlugLayoutDatasetsRoute + ChainSlugLayoutDealsRoute: typeof ChainSlugLayoutDealsRoute + ChainSlugLayoutTasksRoute: typeof ChainSlugLayoutTasksRoute + ChainSlugLayoutWorkerpoolsRoute: typeof ChainSlugLayoutWorkerpoolsRoute + ChainSlugLayoutIndexRoute: typeof ChainSlugLayoutIndexRoute +} + +const ChainSlugLayoutRouteChildren: ChainSlugLayoutRouteChildren = { + ChainSlugLayoutAppsRoute: ChainSlugLayoutAppsRoute, + ChainSlugLayoutDatasetsRoute: ChainSlugLayoutDatasetsRoute, + ChainSlugLayoutDealsRoute: ChainSlugLayoutDealsRoute, + ChainSlugLayoutTasksRoute: ChainSlugLayoutTasksRoute, + ChainSlugLayoutWorkerpoolsRoute: ChainSlugLayoutWorkerpoolsRoute, + ChainSlugLayoutIndexRoute: ChainSlugLayoutIndexRoute, +} + +const ChainSlugLayoutRouteWithChildren = ChainSlugLayoutRoute._addFileChildren( + ChainSlugLayoutRouteChildren, +) + +interface ChainSlugRouteChildren { + ChainSlugLayoutRoute: typeof ChainSlugLayoutRouteWithChildren +} + +const ChainSlugRouteChildren: ChainSlugRouteChildren = { + ChainSlugLayoutRoute: ChainSlugLayoutRouteWithChildren, +} + +const ChainSlugRouteWithChildren = ChainSlugRoute._addFileChildren( + ChainSlugRouteChildren, +) + export interface FileRoutesByFullPath { '/': typeof IndexRoute - '/apps': typeof AppsRoute - '/datasets': typeof DatasetsRoute - '/deals': typeof DealsRoute - '/tasks': typeof TasksRoute - '/workerpools': typeof WorkerpoolsRoute + '/$chainSlug': typeof ChainSlugLayoutRouteWithChildren + '/$chainSlug/apps': typeof ChainSlugLayoutAppsRoute + '/$chainSlug/datasets': typeof ChainSlugLayoutDatasetsRoute + '/$chainSlug/deals': typeof ChainSlugLayoutDealsRoute + '/$chainSlug/tasks': typeof ChainSlugLayoutTasksRoute + '/$chainSlug/workerpools': typeof ChainSlugLayoutWorkerpoolsRoute + '/$chainSlug/': typeof ChainSlugLayoutIndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute - '/apps': typeof AppsRoute - '/datasets': typeof DatasetsRoute - '/deals': typeof DealsRoute - '/tasks': typeof TasksRoute - '/workerpools': typeof WorkerpoolsRoute + '/$chainSlug': typeof ChainSlugLayoutIndexRoute + '/$chainSlug/apps': typeof ChainSlugLayoutAppsRoute + '/$chainSlug/datasets': typeof ChainSlugLayoutDatasetsRoute + '/$chainSlug/deals': typeof ChainSlugLayoutDealsRoute + '/$chainSlug/tasks': typeof ChainSlugLayoutTasksRoute + '/$chainSlug/workerpools': typeof ChainSlugLayoutWorkerpoolsRoute } export interface FileRoutesById { __root__: typeof rootRoute '/': typeof IndexRoute - '/apps': typeof AppsRoute - '/datasets': typeof DatasetsRoute - '/deals': typeof DealsRoute - '/tasks': typeof TasksRoute - '/workerpools': typeof WorkerpoolsRoute + '/$chainSlug': typeof ChainSlugRouteWithChildren + '/$chainSlug/_layout': typeof ChainSlugLayoutRouteWithChildren + '/$chainSlug/_layout/apps': typeof ChainSlugLayoutAppsRoute + '/$chainSlug/_layout/datasets': typeof ChainSlugLayoutDatasetsRoute + '/$chainSlug/_layout/deals': typeof ChainSlugLayoutDealsRoute + '/$chainSlug/_layout/tasks': typeof ChainSlugLayoutTasksRoute + '/$chainSlug/_layout/workerpools': typeof ChainSlugLayoutWorkerpoolsRoute + '/$chainSlug/_layout/': typeof ChainSlugLayoutIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/apps' | '/datasets' | '/deals' | '/tasks' | '/workerpools' + fullPaths: + | '/' + | '/$chainSlug' + | '/$chainSlug/apps' + | '/$chainSlug/datasets' + | '/$chainSlug/deals' + | '/$chainSlug/tasks' + | '/$chainSlug/workerpools' + | '/$chainSlug/' fileRoutesByTo: FileRoutesByTo - to: '/' | '/apps' | '/datasets' | '/deals' | '/tasks' | '/workerpools' + to: + | '/' + | '/$chainSlug' + | '/$chainSlug/apps' + | '/$chainSlug/datasets' + | '/$chainSlug/deals' + | '/$chainSlug/tasks' + | '/$chainSlug/workerpools' id: | '__root__' | '/' - | '/apps' - | '/datasets' - | '/deals' - | '/tasks' - | '/workerpools' + | '/$chainSlug' + | '/$chainSlug/_layout' + | '/$chainSlug/_layout/apps' + | '/$chainSlug/_layout/datasets' + | '/$chainSlug/_layout/deals' + | '/$chainSlug/_layout/tasks' + | '/$chainSlug/_layout/workerpools' + | '/$chainSlug/_layout/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute - AppsRoute: typeof AppsRoute - DatasetsRoute: typeof DatasetsRoute - DealsRoute: typeof DealsRoute - TasksRoute: typeof TasksRoute - WorkerpoolsRoute: typeof WorkerpoolsRoute + ChainSlugRoute: typeof ChainSlugRouteWithChildren } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, - AppsRoute: AppsRoute, - DatasetsRoute: DatasetsRoute, - DealsRoute: DealsRoute, - TasksRoute: TasksRoute, - WorkerpoolsRoute: WorkerpoolsRoute, + ChainSlugRoute: ChainSlugRouteWithChildren, } export const routeTree = rootRoute @@ -180,30 +278,53 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", - "/apps", - "/datasets", - "/deals", - "/tasks", - "/workerpools" + "/$chainSlug" ] }, "/": { "filePath": "index.tsx" }, - "/apps": { - "filePath": "apps.tsx" + "/$chainSlug": { + "filePath": "$chainSlug", + "children": [ + "/$chainSlug/_layout" + ] + }, + "/$chainSlug/_layout": { + "filePath": "$chainSlug/_layout.tsx", + "parent": "/$chainSlug", + "children": [ + "/$chainSlug/_layout/apps", + "/$chainSlug/_layout/datasets", + "/$chainSlug/_layout/deals", + "/$chainSlug/_layout/tasks", + "/$chainSlug/_layout/workerpools", + "/$chainSlug/_layout/" + ] + }, + "/$chainSlug/_layout/apps": { + "filePath": "$chainSlug/_layout/apps.tsx", + "parent": "/$chainSlug/_layout" + }, + "/$chainSlug/_layout/datasets": { + "filePath": "$chainSlug/_layout/datasets.tsx", + "parent": "/$chainSlug/_layout" }, - "/datasets": { - "filePath": "datasets.tsx" + "/$chainSlug/_layout/deals": { + "filePath": "$chainSlug/_layout/deals.tsx", + "parent": "/$chainSlug/_layout" }, - "/deals": { - "filePath": "deals.tsx" + "/$chainSlug/_layout/tasks": { + "filePath": "$chainSlug/_layout/tasks.tsx", + "parent": "/$chainSlug/_layout" }, - "/tasks": { - "filePath": "tasks.tsx" + "/$chainSlug/_layout/workerpools": { + "filePath": "$chainSlug/_layout/workerpools.tsx", + "parent": "/$chainSlug/_layout" }, - "/workerpools": { - "filePath": "workerpools.tsx" + "/$chainSlug/_layout/": { + "filePath": "$chainSlug/_layout/index.tsx", + "parent": "/$chainSlug/_layout" } } } diff --git a/src/routes/$chainSlug/_layout.tsx b/src/routes/$chainSlug/_layout.tsx new file mode 100644 index 00000000..ed02a23d --- /dev/null +++ b/src/routes/$chainSlug/_layout.tsx @@ -0,0 +1,9 @@ +import { createFileRoute, Outlet } from '@tanstack/react-router'; + +export const Route = createFileRoute('/$chainSlug/_layout')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ; +} diff --git a/src/routes/apps.tsx b/src/routes/$chainSlug/_layout/apps.tsx similarity index 85% rename from src/routes/apps.tsx rename to src/routes/$chainSlug/_layout/apps.tsx index 8e3948f9..230fee00 100644 --- a/src/routes/apps.tsx +++ b/src/routes/$chainSlug/_layout/apps.tsx @@ -2,7 +2,7 @@ import { TABLE_LENGTH, TABLE_REFETCH_INTERVAL } from '@/config'; import { execute } from '@/graphql/execute'; import { useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; -import { Box, LoaderCircle, Terminal } from 'lucide-react'; +import { Box, LoaderCircle } from 'lucide-react'; import { useState } from 'react'; import { DataTable } from '@/components/DataTable'; import { PaginatedNavigation } from '@/components/PaginatedNavigation'; @@ -11,30 +11,35 @@ import { SearcherBar } from '@/modules/SearcherBar'; import { appsQuery } from '@/modules/apps/appsQuery'; import { columns } from '@/modules/apps/appsTable/columns'; import { nextAppsQuery } from '@/modules/apps/nextAppsQuery'; +import useUserStore from '@/stores/useUser.store'; -export const Route = createFileRoute('/apps')({ +export const Route = createFileRoute('/$chainSlug/_layout/apps')({ component: AppsRoute, }); function useAppsData(currentPage: number) { + const { chainId } = useUserStore(); const skip = currentPage * TABLE_LENGTH; const { data, isLoading, isRefetching, isError, errorUpdateCount } = useQuery( { - queryKey: ['apps', currentPage], - queryFn: () => execute(appsQuery, { length: TABLE_LENGTH, skip }), + queryKey: [chainId, 'apps', currentPage], + queryFn: () => + execute(appsQuery, chainId, { length: TABLE_LENGTH, skip }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, } ); const { data: nextData } = useQuery({ - queryKey: ['apps-next', currentPage], + queryKey: [chainId, 'apps-next', currentPage], queryFn: () => - execute(nextAppsQuery, { + execute(nextAppsQuery, chainId, { length: TABLE_LENGTH * 2, skip: (currentPage + 1) * TABLE_LENGTH, }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, }); const nextApps = nextData?.apps ?? []; diff --git a/src/routes/datasets.tsx b/src/routes/$chainSlug/_layout/datasets.tsx similarity index 87% rename from src/routes/datasets.tsx rename to src/routes/$chainSlug/_layout/datasets.tsx index db4beb7d..89b23d48 100644 --- a/src/routes/datasets.tsx +++ b/src/routes/$chainSlug/_layout/datasets.tsx @@ -11,30 +11,35 @@ import { SearcherBar } from '@/modules/SearcherBar'; import { datasetsQuery } from '@/modules/datasets/datasetsQuery'; import { columns } from '@/modules/datasets/datasetsTable/columns'; import { nextDatasetsQuery } from '@/modules/datasets/nextDatasetsQuery'; +import useUserStore from '@/stores/useUser.store'; -export const Route = createFileRoute('/datasets')({ +export const Route = createFileRoute('/$chainSlug/_layout/datasets')({ component: DatasetsRoute, }); function useDatasetsData(currentPage: number) { + const { chainId } = useUserStore(); const skip = currentPage * TABLE_LENGTH; const { data, isLoading, isRefetching, isError, errorUpdateCount } = useQuery( { - queryKey: ['datasets', currentPage], - queryFn: () => execute(datasetsQuery, { length: TABLE_LENGTH, skip }), + queryKey: [chainId, 'datasets', currentPage], + queryFn: () => + execute(datasetsQuery, chainId, { length: TABLE_LENGTH, skip }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, } ); const { data: nextData } = useQuery({ - queryKey: ['datasets-next', currentPage], + queryKey: [chainId, 'datasets-next', currentPage], queryFn: () => - execute(nextDatasetsQuery, { + execute(nextDatasetsQuery, chainId, { length: TABLE_LENGTH * 2, skip: (currentPage + 1) * TABLE_LENGTH, }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, }); const nextDatasets = nextData?.datasets ?? []; diff --git a/src/routes/deals.tsx b/src/routes/$chainSlug/_layout/deals.tsx similarity index 87% rename from src/routes/deals.tsx rename to src/routes/$chainSlug/_layout/deals.tsx index e22240a9..418a8f8d 100644 --- a/src/routes/deals.tsx +++ b/src/routes/$chainSlug/_layout/deals.tsx @@ -11,30 +11,35 @@ import { SearcherBar } from '@/modules/SearcherBar'; import { dealsQuery } from '@/modules/deals/dealsQuery'; import { columns } from '@/modules/deals/dealsTable/columns'; import { nextDealsQuery } from '@/modules/deals/nextDealsQuery'; +import useUserStore from '@/stores/useUser.store'; -export const Route = createFileRoute('/deals')({ +export const Route = createFileRoute('/$chainSlug/_layout/deals')({ component: DealsRoute, }); function useDealsData(currentPage: number) { + const { chainId } = useUserStore(); const skip = currentPage * TABLE_LENGTH; const { data, isLoading, isRefetching, isError, errorUpdateCount } = useQuery( { - queryKey: ['deals', currentPage], - queryFn: () => execute(dealsQuery, { length: TABLE_LENGTH, skip }), + queryKey: [chainId, 'deals', currentPage], + queryFn: () => + execute(dealsQuery, chainId, { length: TABLE_LENGTH, skip }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, } ); const { data: nextData } = useQuery({ - queryKey: ['deals-next', currentPage], + queryKey: [chainId, 'deals-next', currentPage], queryFn: () => - execute(nextDealsQuery, { + execute(nextDealsQuery, chainId, { length: TABLE_LENGTH * 2, skip: (currentPage + 1) * TABLE_LENGTH, }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, }); const nextDeals = nextData?.deals ?? []; diff --git a/src/routes/$chainSlug/_layout/index.tsx b/src/routes/$chainSlug/_layout/index.tsx new file mode 100644 index 00000000..83f64b2c --- /dev/null +++ b/src/routes/$chainSlug/_layout/index.tsx @@ -0,0 +1,43 @@ +import { SUPPORTED_CHAINS } from '@/config'; +import { createFileRoute } from '@tanstack/react-router'; +import { SearcherBar } from '@/modules/SearcherBar'; +import { AppsPreviewTable } from '@/modules/apps/AppsPreviewTable'; +import { DatasetsPreviewTable } from '@/modules/datasets/DatasetsPreviewTable'; +import { DealsPreviewTable } from '@/modules/deals/DealsPreviewTable'; +import { TasksPreviewTable } from '@/modules/tasks/TasksPreviewTable'; +import { WorkerpoolsPreviewTable } from '@/modules/workerpools/WorkerpoolsPreviewTable'; +import useUserStore from '@/stores/useUser.store'; + +export const Route = createFileRoute('/$chainSlug/_layout/')({ + component: Index, +}); + +function Index() { + const { chainId } = useUserStore(); + const currentChain = SUPPORTED_CHAINS.find((chain) => chain.id === chainId); + + return ( +
+
+ +
+
+
+
+
+
+ + + + + {' '} +
+
+ ); +} diff --git a/src/routes/tasks.tsx b/src/routes/$chainSlug/_layout/tasks.tsx similarity index 87% rename from src/routes/tasks.tsx rename to src/routes/$chainSlug/_layout/tasks.tsx index 36b15858..cb997137 100644 --- a/src/routes/tasks.tsx +++ b/src/routes/$chainSlug/_layout/tasks.tsx @@ -11,30 +11,35 @@ import { SearcherBar } from '@/modules/SearcherBar'; import { nextTasksQuery } from '@/modules/tasks/nextTasksQuery'; import { tasksQuery } from '@/modules/tasks/tasksQuery'; import { columns } from '@/modules/tasks/tasksTable/columns'; +import useUserStore from '@/stores/useUser.store'; -export const Route = createFileRoute('/tasks')({ +export const Route = createFileRoute('/$chainSlug/_layout/tasks')({ component: TasksRoute, }); function useTasksData(currentPage: number) { + const { chainId } = useUserStore(); const skip = currentPage * TABLE_LENGTH; const { data, isLoading, isRefetching, isError, errorUpdateCount } = useQuery( { - queryKey: ['tasks', currentPage], - queryFn: () => execute(tasksQuery, { length: TABLE_LENGTH, skip }), + queryKey: [chainId, 'tasks', currentPage], + queryFn: () => + execute(tasksQuery, chainId, { length: TABLE_LENGTH, skip }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, } ); const { data: nextData } = useQuery({ - queryKey: ['tasks-next', currentPage], + queryKey: [chainId, 'tasks-next', currentPage], queryFn: () => - execute(nextTasksQuery, { + execute(nextTasksQuery, chainId, { length: TABLE_LENGTH * 2, skip: (currentPage + 1) * TABLE_LENGTH, }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, }); const nextTasks = nextData?.tasks ?? []; diff --git a/src/routes/workerpools.tsx b/src/routes/$chainSlug/_layout/workerpools.tsx similarity index 87% rename from src/routes/workerpools.tsx rename to src/routes/$chainSlug/_layout/workerpools.tsx index bb9d0676..d2f8e578 100644 --- a/src/routes/workerpools.tsx +++ b/src/routes/$chainSlug/_layout/workerpools.tsx @@ -11,30 +11,35 @@ import { SearcherBar } from '@/modules/SearcherBar'; import { nextWorkerpoolsQuery } from '@/modules/workerpools/nextWorkerpoolsQuery'; import { workerpoolsQuery } from '@/modules/workerpools/workerpoolsQuery'; import { columns } from '@/modules/workerpools/workerpoolsTable/columns'; +import useUserStore from '@/stores/useUser.store'; -export const Route = createFileRoute('/workerpools')({ +export const Route = createFileRoute('/$chainSlug/_layout/workerpools')({ component: WorkerpoolsRoute, }); function useWorkerpoolsData(currentPage: number) { + const { chainId } = useUserStore(); const skip = currentPage * TABLE_LENGTH; const { data, isLoading, isRefetching, isError, errorUpdateCount } = useQuery( { - queryKey: ['workerpools', currentPage], - queryFn: () => execute(workerpoolsQuery, { length: TABLE_LENGTH, skip }), + queryKey: [chainId, 'workerpools', currentPage], + queryFn: () => + execute(workerpoolsQuery, chainId, { length: TABLE_LENGTH, skip }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, } ); const { data: nextData } = useQuery({ - queryKey: ['workerpools-next', currentPage], + queryKey: [chainId, 'workerpools-next', currentPage], queryFn: () => - execute(nextWorkerpoolsQuery, { + execute(nextWorkerpoolsQuery, chainId, { length: TABLE_LENGTH * 2, skip: (currentPage + 1) * TABLE_LENGTH, }), refetchInterval: TABLE_REFETCH_INTERVAL, + enabled: !!chainId, }); const nextWorkerpools = nextData?.workerpools ?? []; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 7546ad92..4cdc6574 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -3,17 +3,16 @@ import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'; import { Footer } from '@/components/Footer'; import { UnsupportedChain } from '@/components/UnsupportedChain'; import { Navbar } from '@/components/navbar/NavBar'; -import { useSyncAccountWithUserStore } from '@/hooks/useSyncAccountWithUserStore'; +import { ChainSyncManager } from '@/hooks/ChainSyncManger'; export const Route = createRootRoute({ component: Root, }); function Root() { - useSyncAccountWithUserStore(); - return (
+ diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 38efbc68..ad2b494f 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,32 +1,8 @@ -import { createFileRoute } from '@tanstack/react-router'; -import { SearcherBar } from '@/modules/SearcherBar'; -import { AppsPreviewTable } from '@/modules/apps/AppsPreviewTable'; -import { DatasetsPreviewTable } from '@/modules/datasets/DatasetsPreviewTable'; -import { DealsPreviewTable } from '@/modules/deals/DealsPreviewTable'; -import { TasksPreviewTable } from '@/modules/tasks/TasksPreviewTable'; -import { WorkerpoolsPreviewTable } from '@/modules/workerpools/WorkerpoolsPreviewTable'; +import { createFileRoute, redirect } from '@tanstack/react-router'; +import { INITIAL_CHAIN } from '@/utils/chain.utils'; export const Route = createFileRoute('/')({ - component: Index, + loader: () => { + return redirect({ to: INITIAL_CHAIN.slug }); + }, }); - -function Index() { - return ( -
-
- -
-
-
-
-
-
- - - - - {' '} -
-
- ); -} diff --git a/src/stores/useUser.store.ts b/src/stores/useUser.store.ts index 135623d4..ee84b606 100644 --- a/src/stores/useUser.store.ts +++ b/src/stores/useUser.store.ts @@ -1,10 +1,7 @@ import { Address } from '@/types'; -import type { Connector } from 'wagmi'; import { create } from 'zustand'; type UserState = { - connector: Connector | undefined; - setConnector: (param: Connector | undefined) => void; isConnected: boolean; setIsConnected: (param: boolean) => void; isInitialized: boolean; @@ -12,12 +9,10 @@ type UserState = { address: Address | undefined; setAddress: (param: Address | undefined) => void; chainId: number | undefined; - setChainId: (param: number | undefined) => void; + setChainId: (param: number) => void; }; const useUserStore = create((set) => ({ - connector: undefined, - setConnector: (connector: Connector | undefined) => set({ connector }), isConnected: false, setIsConnected: (isConnected: boolean) => set({ isConnected }), isInitialized: false, @@ -27,7 +22,7 @@ const useUserStore = create((set) => ({ set({ address: address?.toLowerCase() as Address }); }, chainId: undefined, - setChainId: (chainId: number | undefined) => { + setChainId: (chainId: number) => { set({ chainId: chainId }); }, })); diff --git a/src/utils/chain.utils.ts b/src/utils/chain.utils.ts new file mode 100644 index 00000000..2c0fbb6c --- /dev/null +++ b/src/utils/chain.utils.ts @@ -0,0 +1,31 @@ +import { SUPPORTED_CHAINS } from '@/config'; + +export function getSubgraphUrl(chainId: number) { + const subgraphUrl = SUPPORTED_CHAINS.find( + (chain) => chain.id === chainId + )?.subgraphUrl; + if (!subgraphUrl) { + throw new Error(`Subgraph URL not found for chain ID: ${chainId}`); + } + return subgraphUrl; +} + +export function getChainFromSlug(slug: string | undefined) { + return SUPPORTED_CHAINS.find((c) => c.slug === slug); +} + +export function getChainFromId(id: number | undefined) { + return SUPPORTED_CHAINS.find((c) => c.id === id); +} + +export function getBlockExplorerUrl(chainId: number) { + const chain = getChainFromId(chainId); + return chain?.blockExplorerUrl ?? 'https://blockscout.com/'; +} + +/** + * initial chain evaluated once against the current location when the app loads + */ +export const INITIAL_CHAIN = + getChainFromSlug(new URL(window.location.href).pathname.split('/')[1]) || + SUPPORTED_CHAINS[0]; diff --git a/src/utils/wagmiConfig.ts b/src/utils/wagmiConfig.ts index 8a05749b..ba50be07 100644 --- a/src/utils/wagmiConfig.ts +++ b/src/utils/wagmiConfig.ts @@ -1,10 +1,11 @@ import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; +import { AppKitNetwork } from '@reown/appkit/networks'; import { createAppKit } from '@reown/appkit/react'; import { http, CreateConnectorFn } from 'wagmi'; import { injected, walletConnect } from 'wagmi/connectors'; -import { bellecour } from './bellecourChainConfig.ts'; import { InjectedWalletProvider } from './injected-wallet-provider/injected-wallet-provider.ts'; import { EIP6963ProviderDetail } from './injected-wallet-provider/types.ts'; +import wagmiNetworks from './wagmiNetworks.ts'; // Wagmi Client initialization if (!import.meta.env.VITE_REOWN_PROJECT_ID) { @@ -68,12 +69,17 @@ preservedAvailableProviderDetails.forEach((providerDetails) => { ); }); +const networks = Object.values(wagmiNetworks) as [ + AppKitNetwork, + ...AppKitNetwork[], +]; + export const wagmiAdapter = new WagmiAdapter({ - networks: [bellecour], + networks: networks, multiInjectedProviderDiscovery: false, - transports: { - [bellecour.id]: http(), - }, + transports: Object.fromEntries( + Object.values(wagmiNetworks).map((network) => [network.id, http()]) + ), projectId, connectors, }); @@ -87,12 +93,10 @@ const featuredWalletIds = [ 'ecc4036f814562b41a5268adc86270fba1365471402006302e70169465b7ac18', // Zerion ]; -// Create modal createAppKit({ adapters: [wagmiAdapter], - networks: [bellecour], + networks: networks, projectId, - defaultNetwork: bellecour, featuredWalletIds, features: { email: false, diff --git a/src/utils/bellecourChainConfig.ts b/src/utils/wagmiNetworks.ts similarity index 62% rename from src/utils/bellecourChainConfig.ts rename to src/utils/wagmiNetworks.ts index 99a221bb..2be3b395 100644 --- a/src/utils/bellecourChainConfig.ts +++ b/src/utils/wagmiNetworks.ts @@ -1,7 +1,10 @@ -export const bellecour = { +import { AppKitNetwork, arbitrumSepolia } from '@reown/appkit/networks'; + +export { arbitrumSepolia } from '@reown/appkit/networks'; + +export const bellecour: AppKitNetwork = { id: 0x86, name: 'iExec Sidechain', - network: 'bellecour', nativeCurrency: { decimals: 18, name: 'xRLC', @@ -19,3 +22,10 @@ export const bellecour = { default: { name: 'Blockscout', url: 'https://blockscout-bellecour.iex.ec' }, }, }; + +const wagmiNetworks = { + bellecour, + arbitrumSepolia, +}; + +export default wagmiNetworks;