diff --git a/src/components/app/dashboard/AppDashboardActivity.tsx b/src/components/app/dashboard/AppDashboardActivity.tsx index 44067cf6..01f70f0c 100644 --- a/src/components/app/dashboard/AppDashboardActivity.tsx +++ b/src/components/app/dashboard/AppDashboardActivity.tsx @@ -1,26 +1,11 @@ -"use client"; +import React, { FC, useEffect, useState } from "react"; import { - Amount, - Button, Card, DateTime, - Tooltip, - TooltipContent, - TooltipTrigger, - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, + Button, Spinner, - TxButton, + Amount, } from "@/components/ui"; -import React, { FC, useCallback, useEffect, useMemo, useState } from "react"; -import { twMerge } from "tailwind-merge"; import { SortingState, createColumnHelper, @@ -30,177 +15,165 @@ import { getSortedRowModel, useReactTable, } from "@tanstack/react-table"; +import { useAccount } from "wagmi"; import clsx from "clsx"; -import { Activity, LToken, execute } from "../../../../.graphclient"; -import { useContractAddress } from "@/hooks/useContractAddress"; -import { - useReadLTokenDecimals, - useReadLTokenWithdrawalQueue, - useSimulateLTokenCancelWithdrawalRequest, -} from "@/generated"; -import { UseSimulateContractReturnType, useAccount, useBlockNumber } from "wagmi"; -import { useQueryClient } from "@tanstack/react-query"; -type SafePreparation = UseSimulateContractReturnType & { - __brand: 'safe_preparation' -}; - -function castPreparation(prep: any): SafePreparation { - return { - ...prep, - __brand: 'safe_preparation' - } as SafePreparation; +interface Activity { + id: string; + requestId: string; + timestamp: string; + action: string; + amount: string; + amountAfterFees: string; + status: string; + ltoken: { + symbol: string; + decimals: number; + }; + chainId?: number; } -const CancelButton: FC<{ lTokenSymbol: string; requestId: bigint; amount: bigint }> = ({ - lTokenSymbol, - requestId, -}) => { - const ltokenAddress = useContractAddress(lTokenSymbol); - const { data: decimals } = useReadLTokenDecimals({ - address: ltokenAddress, - }); - - const { data: requestData, queryKey } = useReadLTokenWithdrawalQueue({ - address: ltokenAddress, - args: [requestId], - }); +const SUPPORTED_NETWORKS = { + 42161: { + name: 'Arbitrum', + endpoint: process.env.NEXT_PUBLIC_ARBITRUM_SUBGRAPH_URL || '', + prefix: '' + }, + 59144: { + name: 'Linea', + endpoint: process.env.NEXT_PUBLIC_LINEA_SUBGRAPH_URL || '', + prefix: '' + }, + 195: { + name: 'OKX X1 Testnet', + endpoint: process.env.NEXT_PUBLIC_OKX_SUBGRAPH_URL || '', + prefix: '' + }, +}; - const simulation = useSimulateLTokenCancelWithdrawalRequest({ - address: ltokenAddress, - args: [requestId], - }); +export const AppDashboardActivity: FC> = ({ className }) => { + const { address, chainId } = useAccount(); + const [sorting, setSorting] = useState([{ id: "timestamp", desc: true }]); + const [activityData, setActivityData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); - const preparation = useMemo(() => castPreparation(simulation), [simulation]); + const columnHelper = createColumnHelper(); - // Refresh simulation when request data changes - useEffect(() => { - simulation.refetch(); - }, [simulation, requestData]); + const getNetworkConfig = (chainId?: number) => { + if (!chainId) return null; + return SUPPORTED_NETWORKS[chainId as keyof typeof SUPPORTED_NETWORKS]; + }; - // Refresh data every 5 blocks - const queryKeys = useMemo(() => [queryKey], [queryKey]); - const { data: blockNumber } = useBlockNumber({ watch: true }); - const queryClient = useQueryClient(); - - useEffect(() => { - if (!blockNumber || blockNumber % 5n !== 0n) return; - queryKeys.forEach((k) => { - if (k) queryClient.invalidateQueries({ queryKey: k }); + const querySubgraph = async (endpoint: string, query: string) => { + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ query }), }); - }, [blockNumber, queryClient, queryKeys]); - return ( - - - - - - - - Cancel withdrawal request - - - - Are you sure? - - This action cannot be undone and{" "} - you will loose your current position in the - withdrawal queue. -
-
- By cancelling this request{" "} - - you will receive your{" "} - {" "} - {lTokenSymbol}{" "} - - tokens back to your wallet. -
-
- - - - Cancel this request - - - - -
-
- ); -}; + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } -export const AppDashboardActivity: React.PropsWithoutRef = ({ className }) => { - const account = useAccount(); - const [sorting, setSorting] = useState([ - { - id: "timestamp", - desc: true, - }, - ]); - const columnHelper = createColumnHelper(); - const [activityData, setActivityData] = useState([]); - const [isLoading, setIsLoading] = useState(false); + return response.json(); + }; + + const fetchActivityData = async () => { + if (!address) { + setError("Please connect your wallet"); + setIsLoading(false); + return; + } + + const network = getNetworkConfig(chainId); + if (!network) { + setError("Please connect to a supported network"); + setIsLoading(false); + return; + } + + if (!network.endpoint) { + setError(`Subgraph endpoint not configured for ${network.name}`); + setIsLoading(false); + return; + } - const computeActivityData = useCallback(async () => { - if (isLoading) return; - - setIsLoading(true); try { - if (account && account.address) { - const result = await execute( - ` - { - c${account.chainId}_activities(where: { account: "${account.address}" }) { - id - requestId - ltoken { - symbol - decimals - } - timestamp - action - amount - amountAfterFees - status + const healthQuery = ` + { + _meta { + block { + number } } - `, - {}, - ); - - // @ts-ignore - setActivityData(result.data[`c${account.chainId}_activities`]); + } + `; + + await querySubgraph(network.endpoint, healthQuery); + + const query = ` + { + activities( + where: { account: "${address.toLowerCase()}" } + orderBy: timestamp + orderDirection: desc + first: 1000 + ) { + id + requestId + ltoken { + symbol + decimals + } + timestamp + action + amount + amountAfterFees + status + } + } + `; + + const result = await querySubgraph(network.endpoint, query); + + if (result.errors) { + throw new Error(result.errors[0].message); + } + + const activities = result.data?.activities; + + if (Array.isArray(activities) && activities.length > 0) { + const enrichedActivities = activities.map(activity => ({ + ...activity, + chainId, + })); + setActivityData(enrichedActivities); + setError(null); + } else { + setActivityData([]); + setError(`No activity found for your account on ${network.name}`); } - } catch (e) { - console.error('Failed to fetch activity data:', e); + } catch (err) { + setError(`Failed to load activity data: ${err instanceof Error ? err.message : 'Unknown error'}`); setActivityData([]); } finally { setIsLoading(false); } - }, [account?.address, account?.chainId, isLoading]); + }; useEffect(() => { - computeActivityData(); - }, [computeActivityData]); + fetchActivityData(); + }, [address, chainId]); - const activityColumns = useMemo(() => [ + const columns = [ columnHelper.accessor("timestamp", { header: "Date", cell: (info) => ( @@ -210,31 +183,29 @@ export const AppDashboardActivity: React.PropsWithoutRef = ({ class header: "Action", cell: (info) => info.getValue(), }), - columnHelper.accessor("ltoken", { + columnHelper.accessor("ltoken.symbol", { header: "L-Token", - cell: (info) => info.getValue().symbol, + cell: (info) => info.getValue(), }), columnHelper.accessor("amount", { header: "Amount", cell: (info) => { - const amount = info.getValue() as string; - const amountAfterFees = activityData[info.row.index].amountAfterFees as string; - const ltoken = info.row.getValue("ltoken") as LToken; + const row = info.row.original; return ( - Received after fees: + After fees: ) @@ -247,162 +218,153 @@ export const AppDashboardActivity: React.PropsWithoutRef = ({ class header: "Status", cell: (info) => { const status = info.getValue(); - const ltoken = info.row.getValue("ltoken") as LToken; - const requestId = activityData[info.row.index].requestId; - const amount = info.row.getValue("amount") as string; return ( -
+
-
-

{status}

-
- {status === "Queued" && ( - - )} + {status} +
); }, }), - ], [activityData, columnHelper]); - - const sortableColumns = ["timestamp", "action", "amount", "ltoken", "status"]; + ]; const table = useReactTable({ data: activityData, - columns: activityColumns, - state: { - sorting, - }, + columns, + state: { sorting }, onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), }); - // Set page size useEffect(() => { - if (table) { - table.setPageSize(10); - } - }, [table]); + table.setPageSize(10); + }, []); - const headerGroup = table.getHeaderGroups()[0]; + if (isLoading) { + return ( +
+ + Loading activities... +
+ ); + } - return ( -
-
- {headerGroup.headers.map((header, index) => { - const content = flexRender(header.column.columnDef.header, header.getContext()); - return ( -
- {(sortableColumns.includes(header.column.id) && ( - - )) || - content} -
- ); - })} - {(() => { - const tableRows = table.getRowModel().rows; + const network = getNetworkConfig(chainId); - if (isLoading) - return ( -
- -
- ); - else if (tableRows.length === 0) - return ( -

- No activity yet. -

- ); - else { - return tableRows.map((row, rowIndex) => - row.getVisibleCells().map((cell, cellIndex) => ( -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- )), - ); - } - })()} -
-
- - + return ( +
+
+
+ {network ? `Connected to ${network.name}` : 'Unsupported Network'} +
+
+ {address && `${address.slice(0, 6)}...${address.slice(-4)}`} +
+ + {error ? ( +
+
+ {error} +
+
+ Supported networks: {Object.values(SUPPORTED_NETWORKS).map(n => n.name).join(', ')} +
+
+ ) : ( + <> +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + +
+ {header.column.getCanSort() ? ( + + ) : ( + flexRender(header.column.columnDef.header, header.getContext()) + )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+
+ + +
+ + )}
); -}; \ No newline at end of file +}; + +export default AppDashboardActivity; \ No newline at end of file