Skip to content

Commit df89b91

Browse files
committed
feature/chain-switch
1 parent b5568b4 commit df89b91

33 files changed

+688
-275
lines changed
Lines changed: 21 additions & 0 deletions
Loading

src/components/ChainLink.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useParams } from '@tanstack/react-router';
2+
import { Link } from '@tanstack/react-router';
3+
import { ComponentProps } from 'react';
4+
5+
function buildRoutePath(path: string, chainSlug: string) {
6+
if (path === '/') return `/${chainSlug}`;
7+
return `/${chainSlug}${path.startsWith('/') ? path : `/${path}`}`;
8+
}
9+
10+
type ChainLinkProps = {
11+
to: string;
12+
} & Omit<ComponentProps<typeof Link>, 'to'>;
13+
14+
export function ChainLink({ to, ...props }: ChainLinkProps) {
15+
const { chainSlug } = useParams({ from: '/$chainSlug' });
16+
const path = buildRoutePath(to, chainSlug);
17+
return <Link to={path} {...props} />;
18+
}

src/components/DataTable.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Link } from '@tanstack/react-router';
21
import {
32
ColumnDef,
43
flexRender,
@@ -13,6 +12,7 @@ import {
1312
TableHeader,
1413
TableRow,
1514
} from '@/components/ui/table';
15+
import { ChainLink } from './ChainLink';
1616

1717
interface DataTableProps<TData, TValue> {
1818
columns: ColumnDef<TData, TValue>[];
@@ -58,12 +58,12 @@ export function DataTable<TData extends { destination: string }, TValue>({
5858
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
5959
{row.getVisibleCells().map((cell) => (
6060
<TableCell key={cell.id} className="p-0">
61-
<Link
61+
<ChainLink
6262
className="block px-5 py-6"
6363
to={cell.row.original.destination}
6464
>
6565
{flexRender(cell.column.columnDef.cell, cell.getContext())}
66-
</Link>
66+
</ChainLink>
6767
</TableCell>
6868
))}
6969
</TableRow>

src/components/Footer.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ import {
66
SiX,
77
SiYoutube,
88
} from '@icons-pack/react-simple-icons';
9-
import { Link } from '@tanstack/react-router';
109
import { Linkedin } from 'lucide-react';
10+
import useUserStore from '@/stores/useUser.store';
11+
import { getBlockExplorerUrl } from '@/utils/chain.utils';
1112
import iExecLogo from '../assets/iexec-logo.svg';
13+
import { ChainLink } from './ChainLink';
1214
import { Button } from './ui/button';
1315

1416
export function Footer({ className }: { className?: string }) {
17+
const { chainId } = useUserStore();
18+
1519
const navLinks = [
1620
{ href: 'https://www.iex.ec/', label: 'iExec Website' },
17-
{ href: 'https://blockscout-bellecour.iex.ec/', label: 'Block explorer' }, // TODO import it from protocol data list
21+
{ href: getBlockExplorerUrl(chainId), label: 'Block explorer' },
1822
{ href: 'https://www.iex.ec/contact', label: 'Contact Us' },
1923
];
2024

@@ -48,10 +52,10 @@ export function Footer({ className }: { className?: string }) {
4852
)}
4953
>
5054
<div className="grid place-items-center justify-center gap-10 xl:grid-cols-3 xl:place-items-stretch">
51-
<Link to="/" className="flex items-center gap-2 font-mono">
55+
<ChainLink to="/" className="flex items-center gap-2 font-mono">
5256
<img src={iExecLogo} width="25" height="25" alt="iExec logo" />
5357
<span className="hidden sm:block">iExec Explorer</span>
54-
</Link>
58+
</ChainLink>
5559

5660
<nav className="flex flex-col items-start gap-4 sm:flex-row sm:items-center sm:gap-4">
5761
{navLinks.map(({ href, label }, idx) => (

src/components/UnsupportedChain.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
import { SUPPORTED_CHAINS } from '@/config';
2-
import { Terminal } from 'lucide-react';
3-
import useUserStore from '@/stores/useUser.store';
2+
import { AlertOctagon } from 'lucide-react';
3+
import { useAccount } from 'wagmi';
44
import { Alert, AlertDescription, AlertTitle } from './ui/alert';
55

66
const SUPPORTED_CHAIN_IDS = SUPPORTED_CHAINS.map((chain) => chain.id);
77

88
export function UnsupportedChain() {
9-
const { isConnected, chainId } = useUserStore();
10-
9+
const { isConnected, chainId } = useAccount();
1110
const isChainSupported =
1211
chainId !== undefined && SUPPORTED_CHAIN_IDS.includes(chainId);
1312

14-
if (isChainSupported || !isConnected) {
13+
if (!isConnected || isChainSupported) {
1514
return null;
1615
}
1716

1817
return (
1918
<Alert variant="destructive" className="mx-auto mt-8 text-left">
20-
<Terminal className="h-4 w-4" />
19+
<AlertOctagon className="h-4 w-4" />
2120
<AlertTitle>Error</AlertTitle>
2221
<AlertDescription>
2322
It seems that you are using a chain that is not supported.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { SUPPORTED_CHAINS } from '@/config.ts';
2+
import { useChainSwitch } from '@/hooks/useChainSwitch.ts';
3+
import useUserStore from '@/stores/useUser.store.ts';
4+
import {
5+
Select,
6+
SelectContent,
7+
SelectItem,
8+
SelectTrigger,
9+
SelectValue,
10+
} from '../ui/select.tsx';
11+
12+
export function ChainSelector() {
13+
const { chainId } = useUserStore();
14+
const { requestChainChange } = useChainSwitch();
15+
const handleChainChange = async (value: string) => {
16+
requestChainChange(Number(value));
17+
};
18+
19+
return (
20+
<Select
21+
value={chainId?.toString()}
22+
onValueChange={handleChainChange}
23+
defaultValue="-1"
24+
>
25+
<SelectTrigger>
26+
<SelectValue placeholder="Select Chain" />
27+
</SelectTrigger>
28+
<SelectContent>
29+
{SUPPORTED_CHAINS.map((chain) => (
30+
<SelectItem key={chain.id} value={chain.id.toString()}>
31+
<img src={chain.icon} className="size-4" alt="" /> {chain.name}
32+
</SelectItem>
33+
))}
34+
</SelectContent>
35+
</Select>
36+
);
37+
}

src/components/navbar/NavBar.tsx

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,10 @@ import { useState } from 'react';
44
import { useLoginLogout } from '@/hooks/useLoginLogout';
55
import useUserStore from '@/stores/useUser.store';
66
import iExecLogo from '../../assets/iexec-logo.svg';
7+
import { ChainLink } from '../ChainLink.tsx';
78
import { Button } from '../ui/button.tsx';
8-
import {
9-
Select,
10-
SelectContent,
11-
SelectItem,
12-
SelectTrigger,
13-
SelectValue,
14-
} from '../ui/select.tsx';
159
import { AddressChip } from './AddressChip.tsx';
10+
import { ChainSelector } from './ChainSelector.tsx';
1611

1712
export function Navbar() {
1813
const { isConnected, address } = useUserStore();
@@ -24,30 +19,21 @@ export function Navbar() {
2419

2520
return (
2621
<div className="flex items-center justify-between pt-6 lg:pt-3">
27-
<Link to="/" className="-m-2 flex items-center gap-2 p-2 font-mono">
22+
<ChainLink to="/" className="-m-2 flex items-center gap-2 p-2 font-mono">
2823
<img src={iExecLogo} width="25" height="25" alt="iExec logo" />
2924
<span className="hidden sm:block">iExec Explorer</span>
30-
</Link>
25+
</ChainLink>
3126
<div className="mr-6 flex items-center gap-4 md:mr-0">
3227
{isConnected && (
3328
<div className="hidden md:flex">
3429
<Button variant="link" asChild className="text-white">
35-
<Link to="/account">iExec Account</Link>
30+
<ChainLink to="/account">iExec Account</ChainLink>
3631
</Button>
3732
<span className="border-secondary border-l" />
3833
</div>
3934
)}
4035
<div className="content hidden md:flex">
41-
<Select value="bellecour">
42-
<SelectTrigger className="">
43-
<SelectValue />
44-
</SelectTrigger>
45-
<SelectContent>
46-
<SelectItem value="bellecour">
47-
<img src={iExecLogo} className="size-4" alt="" /> Bellecour
48-
</SelectItem>
49-
</SelectContent>
50-
</Select>
36+
<ChainSelector />
5137
</div>
5238
{isConnected ? (
5339
<div className="flex max-w-[1260px] items-center gap-2">
@@ -89,9 +75,9 @@ export function Navbar() {
8975

9076
<div className="border-grey-600 bg-grey-900 pointer-events-auto fixed inset-y-0 left-0 z-10 flex w-full -translate-x-full flex-col overflow-auto rounded-r-3xl border-r px-6 pt-6 duration-300 group-has-[:checked]:translate-x-0 lg:w-[255px] lg:translate-x-0">
9177
<div className="-m-2 mr-6 flex items-center justify-between gap-2 py-2 pl-2">
92-
<Link to="/" className="font-mono" onClick={handleMenuToggle}>
78+
<ChainLink to="/" className="font-mono" onClick={handleMenuToggle}>
9379
<img src={iExecLogo} width="25" height="25" alt="iExec logo" />
94-
</Link>
80+
</ChainLink>
9581
{isConnected ? (
9682
<div className="flex max-w-[1260px] items-center gap-2">
9783
<AddressChip address={address!} />
@@ -117,18 +103,9 @@ export function Navbar() {
117103
asChild
118104
className="justify-baseline px-3 text-white"
119105
>
120-
<Link to="/account">iExec Account</Link>
106+
<ChainLink to="/account">iExec Account</ChainLink>
121107
</Button>
122-
<Select value="bellecour">
123-
<SelectTrigger className="w-full border-none">
124-
<SelectValue />
125-
</SelectTrigger>
126-
<SelectContent>
127-
<SelectItem value="bellecour">
128-
<img src={iExecLogo} className="size-4" alt="" /> Bellecour
129-
</SelectItem>
130-
</SelectContent>
131-
</Select>
108+
<ChainSelector />
132109
</div>
133110
</div>
134111
</div>

src/components/ui/alert.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { cva, type VariantProps } from 'class-variance-authority';
33
import * as React from 'react';
44

55
const alertVariants = cva(
6-
'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
6+
'relative w-full rounded border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
77
{
88
variants: {
99
variant: {
1010
default: 'bg-card text-card-foreground',
1111
destructive:
12-
'text-destructive bg-danger border-danger-border [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
12+
'text-destructive bg-danger border-danger-border [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90 border-l-4',
1313
},
1414
},
1515
defaultVariants: {

src/config.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import arbitrumSepoliaIcon from './assets/chain-icons/arbitrum-sepolia.svg';
2+
import iexecLogo from './assets/iexec-logo.svg';
3+
import { bellecour, arbitrumSepolia } from './utils/wagmiNetworks';
4+
15
export const PREVIEW_TABLE_LENGTH = 5;
26
export const TABLE_LENGTH = 16;
37
export const PREVIEW_TABLE_REFETCH_INTERVAL = 10_000;
@@ -6,8 +10,22 @@ export const SUPPORTED_CHAINS = [
610
{
711
id: 134,
812
name: 'Bellecour',
9-
icon: 'src/assets/iexec-logo.svg',
13+
slug: 'bellecour',
14+
color: '#F4942566',
15+
icon: iexecLogo,
1016
blockExplorerUrl: 'https://blockscout-bellecour.iex.ec',
1117
subgraphUrl: 'https://thegraph.iex.ec/subgraphs/name/bellecour/poco-v5',
18+
wagmiNetwork: bellecour,
19+
},
20+
{
21+
id: 421614,
22+
name: 'Arbitrum Sepolia',
23+
slug: 'arbitrum-sepolia',
24+
color: '#28A0F080',
25+
icon: arbitrumSepoliaIcon,
26+
blockExplorerUrl: 'https://sepolia.arbiscan.io/',
27+
subgraphUrl:
28+
'http://localhost:8080/subgraphs/id/2GCj8gzLCihsiEDq8cYvC5nUgK6VfwZ6hm3Wj8A3kcxz',
29+
wagmiNetwork: arbitrumSepolia,
1230
},
1331
];

src/graphql/execute.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import { getSubgraphUrl } from '@/utils/chain.utils';
12
import type { TypedDocumentString } from './graphql'
23

34
export async function execute<TResult, TVariables>(
45
query: TypedDocumentString<TResult, TVariables>,
6+
chainId?: number,
57
...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
68
) {
7-
const subgraphUrl = import.meta.env.VITE_POCO_SUBGRAPH_URL;
8-
9+
if (!chainId) {
10+
throw Error('Missing chainId')
11+
}
12+
const subgraphUrl = getSubgraphUrl(chainId);
913
const response = await fetch(subgraphUrl, {
1014
method: 'POST',
1115
headers: {

0 commit comments

Comments
 (0)