Skip to content

Commit e6e7e01

Browse files
feat: add (datasets, apps, workerpools) access table and enhance pagination (#88)
* feat: add tab navigation for app details and deals in AppsRoute component * feat: add AppAccessTable component and integrate into AppsRoute * feat: enhance AppAccessTable and AppDealsTable with loading and outdated state management * feat: rename columns to appColumns and remove app address and price column * feat: add tab navigation for dataset details, deals, and access in DatasetsRoute * feat: integrate loading and outdated state management in DatasetDealsTable * feat: add DatasetAccessTable component and integrate it into DatasetsRoute * feat: add tab navigation for workerpool details, deals, and access * feat: enhance WorkerpoolDealsTable with loading and outdated state management * feat: add WorkerpoolAccessTable component and integrate it into WorkerpoolsRoute * feat: refactor loading and outdated state management in access and deals tables using useEffect * feat: add access tables for apps, datasets, and workerpools on address page * fix: correct import path for WorkerpoolAccessTable component * refactor: remove console logs from access data fetching functions * feat: enhance pagination logic to display all pages for 7 or fewer total pages * feat: update pagination logic to support mobile-first design and dynamic page visibility * feat: update access data fetching logic to support pagination and total count for apps, datasets, and workerpools * fix: prevent rendering of pagination controls for invalid states * refactor: prettier * fix: stabilize pagination rendering during lading state * fix: normalize case for order restrictions in CopyButton components * fix: useTabParam() name in dataset Co-authored-by: Copilot <[email protected]> * fix: correct error message wording in loading alerts across multiple tables * feat: add price column with token symbol for app, dataset, and workerpool orders * fix: conditionally apply cursor pointer class based on destination * feat: format access data to include destination paths for apps, datasets, and workerpools * fix: adjust indicator position calculation to include scroll offset * fix: update initIExecSDKs to work without wallet connection * fix: convert price values from NRLC to RLC across app, dataset, and workerpool access columns * fix: update orderbook fetch calls to include 'any' for dataset, app, and workerpool parameters * fix: improve pagination stability by updating totalPages reference condition * fix: stabilize pagination by resetting totalPages reference on chainId change * fix: remove debug log for provider in initIExecSDKs function * fix: include 'requester' parameter as 'any' in access data queries for app, dataset, and workerpool * fix: reorder AddressAppsAccessTable in AddressRoute component --------- Co-authored-by: Copilot <[email protected]>
1 parent 82c4f7e commit e6e7e01

37 files changed

+3803
-433
lines changed

package-lock.json

Lines changed: 2300 additions & 328 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"class-variance-authority": "^0.7.1",
4747
"clsx": "^2.1.1",
4848
"graphql": "^16.11.0",
49-
"iexec": "^8.18.0",
49+
"iexec": "^8.20.0",
5050
"lucide-react": "^0.536.0",
5151
"prettier-plugin-tailwindcss": "^0.6.14",
5252
"react": "^19.2.0",

src/components/DataTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export function DataTable<TData, TValue>({
9696
<TableRow
9797
key={row.id}
9898
data-state={row.getIsSelected() && 'selected'}
99-
className="cursor-pointer"
99+
className={destination ? 'cursor-pointer' : ''}
100100
tabIndex={0}
101101
onClick={(e) => handleRowClick(destination, e)}
102102
onKeyDown={(e) => handleRowKeyDown(destination, e)}

src/components/PaginatedNavigation.tsx

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useRef } from 'react';
12
import {
23
Pagination,
34
PaginationContent,
@@ -7,6 +8,7 @@ import {
78
PaginationNext,
89
PaginationEllipsis,
910
} from '@/components/ui/pagination';
11+
import useUserStore from '@/stores/useUser.store';
1012

1113
type PaginationControlsProps = {
1214
currentPage: number;
@@ -19,24 +21,87 @@ export const PaginatedNavigation = ({
1921
totalPages,
2022
onPageChange,
2123
}: PaginationControlsProps) => {
24+
const { chainId } = useUserStore();
25+
26+
const lastValidTotalPagesRef = useRef(1);
27+
const lastChainIdRef = useRef<number | null>(null);
28+
const chainChangeFrameRef = useRef(0);
29+
30+
const chainHasChanged = chainId !== lastChainIdRef.current;
31+
32+
if (chainHasChanged) {
33+
lastChainIdRef.current = chainId ?? null;
34+
chainChangeFrameRef.current = 0;
35+
} else {
36+
chainChangeFrameRef.current++;
37+
}
38+
39+
let stableTotalPages = lastValidTotalPagesRef.current;
40+
41+
const isRecentChainChange = chainChangeFrameRef.current <= 5;
42+
43+
if (chainHasChanged || isRecentChainChange) {
44+
stableTotalPages = Math.max(totalPages, 1);
45+
} else if (totalPages > 0 && totalPages >= lastValidTotalPagesRef.current) {
46+
stableTotalPages = totalPages;
47+
}
48+
49+
lastValidTotalPagesRef.current = stableTotalPages;
50+
51+
// Don't render pagination if no pages or invalid state
52+
if (!stableTotalPages || stableTotalPages <= 0 || currentPage <= 0) {
53+
return null;
54+
}
55+
2256
const generatePages = () => {
2357
const pages: (number | 'ellipsis')[] = [];
2458

25-
if (totalPages <= 5) {
26-
for (let i = 1; i <= totalPages; i++) {
59+
// Mobile-first approach: show fewer pages on small screens
60+
const isMobile = typeof window !== 'undefined' && window.innerWidth < 640;
61+
const maxVisiblePages = isMobile ? 3 : 7;
62+
63+
if (stableTotalPages <= maxVisiblePages) {
64+
// Show all pages if within limit
65+
for (let i = 1; i <= stableTotalPages; i++) {
2766
pages.push(i);
2867
}
29-
} else if (currentPage <= 3) {
30-
for (let i = 1; i <= Math.min(currentPage + 2, totalPages); i++) {
31-
pages.push(i);
68+
} else if (isMobile) {
69+
// Mobile: simplified pagination - only show current and neighbors
70+
if (currentPage === 1) {
71+
// At start: 1 2 ... last
72+
pages.push(1, 2, 'ellipsis', stableTotalPages);
73+
} else if (currentPage === stableTotalPages) {
74+
// At end: 1 ... (last-1) last
75+
pages.push(1, 'ellipsis', stableTotalPages - 1, stableTotalPages);
76+
} else {
77+
// Middle: 1 ... current ... last
78+
pages.push(1, 'ellipsis', currentPage, 'ellipsis', stableTotalPages);
3279
}
3380
} else {
81+
// Desktop: full pagination logic
3482
pages.push(1);
35-
pages.push('ellipsis');
3683

37-
const maxPage = Math.min(currentPage + 2, totalPages);
38-
for (let i = currentPage; i <= maxPage; i++) {
39-
pages.push(i);
84+
if (currentPage <= 3) {
85+
// Near beginning: 1 2 3 4 ... last
86+
for (let i = 2; i <= 4; i++) {
87+
pages.push(i);
88+
}
89+
pages.push('ellipsis');
90+
pages.push(stableTotalPages);
91+
} else if (currentPage >= stableTotalPages - 2) {
92+
// Near end: 1 ... (last-3) (last-2) (last-1) last
93+
pages.push('ellipsis');
94+
for (let i = stableTotalPages - 3; i <= stableTotalPages; i++) {
95+
pages.push(i);
96+
}
97+
} else {
98+
// In middle: 1 ... (current-1) current (current+1) ... last
99+
pages.push('ellipsis');
100+
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
101+
pages.push(i);
102+
}
103+
pages.push('ellipsis');
104+
pages.push(stableTotalPages);
40105
}
41106
}
42107

@@ -50,7 +115,7 @@ export const PaginatedNavigation = ({
50115
};
51116

52117
const handleNext = () => {
53-
if (currentPage < totalPages) onPageChange(currentPage + 1);
118+
if (currentPage < stableTotalPages) onPageChange(currentPage + 1);
54119
};
55120

56121
return (
@@ -86,7 +151,9 @@ export const PaginatedNavigation = ({
86151
<PaginationNext
87152
onClick={handleNext}
88153
className={
89-
currentPage === totalPages ? 'pointer-events-none opacity-50' : ''
154+
currentPage === stableTotalPages
155+
? 'pointer-events-none opacity-50'
156+
: ''
90157
}
91158
/>
92159
</PaginationItem>

src/externals/iexecSdkClient.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,24 @@ export function cleanIExecSDKs() {
1414
readonlyIExec = null;
1515
}
1616

17-
export async function initIExecSDKs({ connector }: { connector?: Connector }) {
17+
export async function initIExecSDKs({
18+
connector,
19+
chainId,
20+
}: {
21+
connector?: Connector;
22+
chainId?: number;
23+
}) {
24+
let provider = chainId as unknown as Eip1193Provider;
1825
if (!connector || !connector.getProvider) {
1926
cleanIExecSDKs();
20-
return;
27+
} else {
28+
provider = (await connector.getProvider()) as Eip1193Provider;
2129
}
22-
23-
const provider = (await connector.getProvider()) as Eip1193Provider;
2430
if (!provider) {
2531
cleanIExecSDKs();
2632
return;
2733
}
34+
2835
// Initialize
2936
const config = new IExecConfig(
3037
{ ethProvider: provider },

src/hooks/ChainSyncManger.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,8 @@ export function ChainSyncManager() {
4242
if (accountChain?.id && chainId !== accountChain?.id) {
4343
setChainId(accountChain?.id);
4444
}
45-
if (accountStatus === 'connected') {
46-
initIExecSDKs({ connector: accountConnector });
47-
return;
48-
}
4945
cleanIExecSDKs();
46+
initIExecSDKs({ connector: accountConnector, chainId });
5047
}, [
5148
accountAddress,
5249
accountIsConnected,

src/modules/Tabs.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export function Tabs({
3737
const containerRect = containerRef.current.getBoundingClientRect();
3838
const buttonRect = activeButton.getBoundingClientRect();
3939

40-
const left = buttonRect.left - containerRect.left;
40+
const left =
41+
buttonRect.left - containerRect.left + containerRef.current.scrollLeft;
4142
const width = buttonRect.width;
4243

4344
setIndicatorStyle({ left, width });

src/modules/access/appColumns.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { ColumnDef } from '@tanstack/react-table';
2+
import { PublishedApporder } from 'iexec/IExecOrderbookModule';
3+
import CopyButton from '@/components/CopyButton';
4+
import useUserStore from '@/stores/useUser.store';
5+
import { getTokenSymbol } from '@/utils/chain.utils';
6+
import { nrlcToRlc } from '@/utils/nrlcToRlc';
7+
import { truncateAddress } from '@/utils/truncateAddress';
8+
9+
export const columns: ColumnDef<PublishedApporder>[] = [
10+
{
11+
accessorKey: 'order.appprice',
12+
header: 'Price',
13+
cell: ({ row }) => (
14+
<span>
15+
{nrlcToRlc(row.original.order.appprice)}{' '}
16+
{getTokenSymbol(useUserStore.getState().chainId)}
17+
</span>
18+
),
19+
},
20+
{
21+
accessorKey: 'order.datasetrestrict',
22+
header: 'Dataset Restriction',
23+
cell: ({ row }) => (
24+
<CopyButton
25+
displayText={truncateAddress(
26+
row.original.order.datasetrestrict.toLowerCase(),
27+
{
28+
startLen: 8,
29+
}
30+
)}
31+
textToCopy={row.original.order.datasetrestrict.toLowerCase()}
32+
/>
33+
),
34+
},
35+
{
36+
accessorKey: 'order.requesterrestrict',
37+
header: 'Requester Restriction',
38+
cell: ({ row }) => (
39+
<CopyButton
40+
displayText={truncateAddress(
41+
row.original.order.requesterrestrict.toLowerCase(),
42+
{
43+
startLen: 8,
44+
}
45+
)}
46+
textToCopy={row.original.order.requesterrestrict.toLowerCase()}
47+
/>
48+
),
49+
},
50+
{
51+
accessorKey: 'order.workerpoolrestrict',
52+
header: 'Workerpool Restriction',
53+
cell: ({ row }) => (
54+
<CopyButton
55+
displayText={truncateAddress(
56+
row.original.order.workerpoolrestrict.toLowerCase(),
57+
{
58+
startLen: 8,
59+
}
60+
)}
61+
textToCopy={row.original.order.workerpoolrestrict.toLowerCase()}
62+
/>
63+
),
64+
},
65+
{
66+
accessorKey: 'order.volume',
67+
header: 'Volume',
68+
cell: ({ row }) => <span>{row.original.order.volume}</span>,
69+
},
70+
{
71+
accessorKey: 'remaining',
72+
header: 'Remaining',
73+
cell: ({ row }) => <span>{row.original.remaining}</span>,
74+
},
75+
];
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { ColumnDef } from '@tanstack/react-table';
2+
import { PublishedDatasetorder } from 'iexec/IExecOrderbookModule';
3+
import CopyButton from '@/components/CopyButton';
4+
import useUserStore from '@/stores/useUser.store';
5+
import { getTokenSymbol } from '@/utils/chain.utils';
6+
import { nrlcToRlc } from '@/utils/nrlcToRlc';
7+
import { truncateAddress } from '@/utils/truncateAddress';
8+
9+
export const columns: ColumnDef<PublishedDatasetorder>[] = [
10+
{
11+
accessorKey: 'order.datasetprice',
12+
header: 'Price',
13+
cell: ({ row }) => (
14+
<span>
15+
{nrlcToRlc(row.original.order.datasetprice)}{' '}
16+
{getTokenSymbol(useUserStore.getState().chainId)}
17+
</span>
18+
),
19+
},
20+
{
21+
accessorKey: 'order.apprestrict',
22+
header: 'App Restriction',
23+
cell: ({ row }) => (
24+
<CopyButton
25+
displayText={truncateAddress(
26+
row.original.order.apprestrict.toLowerCase(),
27+
{
28+
startLen: 8,
29+
}
30+
)}
31+
textToCopy={row.original.order.apprestrict.toLowerCase()}
32+
/>
33+
),
34+
},
35+
{
36+
accessorKey: 'order.requesterrestrict',
37+
header: 'Requester Restriction',
38+
cell: ({ row }) => (
39+
<CopyButton
40+
displayText={truncateAddress(
41+
row.original.order.requesterrestrict.toLowerCase(),
42+
{
43+
startLen: 8,
44+
}
45+
)}
46+
textToCopy={row.original.order.requesterrestrict.toLowerCase()}
47+
/>
48+
),
49+
},
50+
{
51+
accessorKey: 'order.workerpoolrestrict',
52+
header: 'Workerpool Restriction',
53+
cell: ({ row }) => (
54+
<CopyButton
55+
displayText={truncateAddress(
56+
row.original.order.workerpoolrestrict.toLowerCase(),
57+
{
58+
startLen: 8,
59+
}
60+
)}
61+
textToCopy={row.original.order.workerpoolrestrict.toLowerCase()}
62+
/>
63+
),
64+
},
65+
{
66+
accessorKey: 'order.volume',
67+
header: 'Volume',
68+
cell: ({ row }) => <span>{row.original.order.volume}</span>,
69+
},
70+
{
71+
accessorKey: 'remaining',
72+
header: 'Remaining',
73+
cell: ({ row }) => <span>{row.original.remaining}</span>,
74+
},
75+
];

0 commit comments

Comments
 (0)