Skip to content

Commit

Permalink
Feat: As a user, I want to be able to see the list of modules on Expl…
Browse files Browse the repository at this point in the history
…orer #412 (#455)
  • Loading branch information
voenkomatiwe authored Dec 7, 2023
1 parent 2b9ac68 commit 0b8ad3f
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 38 deletions.
13 changes: 9 additions & 4 deletions explorer/src/components/Back/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { ChevronLeft } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useLocation, useNavigate } from "react-router-dom";

import { EMPTY_STRING } from "@/constants";

export interface BackProps {
interface BackProps {
className?: string;
}

export const Back: React.FC<BackProps> = ({ className }) => {
const navigate = useNavigate();
const { state, pathname } = useLocation();
const backPath = pathname.split("/").slice(0, -1);
const { title, handler } = state?.from
? { handler: () => navigate(-1), title: "Back" }
: { handler: () => navigate(backPath.join("/")), title: `Back to ${backPath.slice(-1)}` };
return (
<button
onClick={() => navigate(-1)}
onClick={() => handler()}
className={`w-fit flex gap-2 text-text-tertiary hover:opacity-60 ${className || EMPTY_STRING}`}
>
<ChevronLeft width={24} height={24} />
Back
{title}
</button>
);
};
7 changes: 2 additions & 5 deletions explorer/src/components/DataTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData
return (
<TableHead
key={header.id}
className="text-xs not-italic font-normal text-text-quaternary uppercase whitespace-nowrap shadow-column lg:shadow-none transition-shadow"
className="text-xs not-italic font-normal text-text-quaternary uppercase whitespace-nowrap"
>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
Expand All @@ -35,10 +35,7 @@ export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className="whitespace-nowrap shadow-column lg:shadow-none transition-shadow text-text-secondary"
>
<TableCell key={cell.id} className="whitespace-nowrap text-text-secondary">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
Expand Down
2 changes: 2 additions & 0 deletions explorer/src/components/HelperIndicator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const getIndicatorColorClass = (type: Page): string => {
return "bg-indicator-blue";
case "portal":
return "bg-indicator-green";
case "module":
return "bg-indicator-orange";
default:
return "bg-transparent";
}
Expand Down
5 changes: 3 additions & 2 deletions explorer/src/components/Link/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PropsWithChildren } from "react";
import { LinkProps, Link as RouterLink } from "react-router-dom";
import { LinkProps, Link as RouterLink, useLocation } from "react-router-dom";

import { useNetworkContext } from "@/providers/network-provider/context";
import { CHAIN_ID_ROUTE } from "@/routes/constants";
Expand All @@ -8,8 +8,9 @@ export const Link: React.FC<PropsWithChildren & LinkProps> = ({ children, ...pro
const {
network: { network },
} = useNetworkContext();
const { pathname } = useLocation();
return (
<RouterLink {...props} to={props.to.toString().replace(CHAIN_ID_ROUTE, network)}>
<RouterLink {...props} to={props.to.toString().replace(CHAIN_ID_ROUTE, network)} state={{ from: pathname }}>
{children}
</RouterLink>
);
Expand Down
5 changes: 3 additions & 2 deletions explorer/src/components/NavLink/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PropsWithChildren } from "react";
import { NavLinkProps, NavLink as RouterNavLink } from "react-router-dom";
import { NavLinkProps, NavLink as RouterNavLink, useLocation } from "react-router-dom";

import { useNetworkContext } from "@/providers/network-provider/context";
import { CHAIN_ID_ROUTE } from "@/routes/constants";
Expand All @@ -8,8 +8,9 @@ export const NavLink: React.FC<PropsWithChildren & NavLinkProps> = ({ children,
const {
network: { network },
} = useNetworkContext();
const { pathname } = useLocation();
return (
<RouterNavLink {...props} to={props.to.toString().replace(CHAIN_ID_ROUTE, network)}>
<RouterNavLink {...props} to={props.to.toString().replace(CHAIN_ID_ROUTE, network)} state={{ from: pathname }}>
{children}
</RouterNavLink>
);
Expand Down
2 changes: 1 addition & 1 deletion explorer/src/components/NotFoundPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const NotFoundPage: React.FC<NotFoundPageProps> = ({ id, page }) => {
className="flex gap-2 border border-solid rounded-md px-4 py-3 border-button-secondary-border hover:border-button-secondary-hover"
>
<ChevronLeft width={24} height={24} />
Go Back
{`Go Back to ${page}s`}
</Link>
</div>
</section>
Expand Down
24 changes: 11 additions & 13 deletions explorer/src/components/Pagination/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";
import { useSearchParams } from "react-router-dom";

import { CURRENT_PAGE_DEFAULT, ITEMS_PER_PAGE_DEFAULT, ZERO } from "@/constants";
import { ITEMS_PER_PAGE_DEFAULT } from "@/constants";
import { EQueryParams } from "@/enums/queryParams";
import { displayAmountWithComma } from "@/utils/amountUtils";
import { pageBySearchParams } from "@/utils/paginationUtils";

import { IPaginationProps } from "./interface";

export const Pagination = ({ itemsCount, handleSkip }: IPaginationProps) => {
export const Pagination = ({ itemsCount, handlePage }: IPaginationProps) => {
const [searchParams, setSearchParams] = useSearchParams();
const page = searchParams.get(EQueryParams.PAGE);
const currentPage = pageBySearchParams(searchParams, itemsCount);

useEffect(() => {
handlePage(currentPage);
}, [currentPage, handlePage, searchParams]);

const [currentPage, setCurrentPage] = useState<number>(Number(page) || 1);
const totalPages = Math.ceil(itemsCount / ITEMS_PER_PAGE_DEFAULT);

const disablePrev = currentPage === 1;
const disableNext = currentPage === totalPages;

useEffect(() => {
const currentSearchParams = new URLSearchParams(searchParams);
currentSearchParams.set(EQueryParams.PAGE, currentPage.toString());
setSearchParams(currentSearchParams);
}, [currentPage, searchParams, setSearchParams]);

const inputRef = useRef<HTMLInputElement>(null);

const handlePageChange = (newPage: number) => {
if (newPage >= 1 && newPage <= totalPages && inputRef && inputRef.current) {
setCurrentPage(newPage);
handleSkip(newPage === CURRENT_PAGE_DEFAULT ? ZERO : (newPage - 1) * ITEMS_PER_PAGE_DEFAULT);
inputRef.current.value = newPage.toString();
searchParams.set(EQueryParams.PAGE, newPage.toString());
setSearchParams(searchParams);
}
};

Expand Down
2 changes: 1 addition & 1 deletion explorer/src/components/Pagination/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface IPaginationProps {
itemsCount: number;
handleSkip: React.Dispatch<React.SetStateAction<number>>;
handlePage: (page: number) => void;
}
2 changes: 1 addition & 1 deletion explorer/src/components/ui/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-lime-100 data-[state=selected]:bg-muted whitespace-nowrap",
"border-b table-row-transition hover:bg-hover-lime20 data-[state=selected]:bg-muted whitespace-nowrap",
className,
)}
{...props}
Expand Down
61 changes: 61 additions & 0 deletions explorer/src/constants/columns/module.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { ColumnDef } from "@tanstack/react-table";
import { Module } from "@verax-attestation-registry/verax-sdk";

import { HelperIndicator } from "@/components/HelperIndicator";
import { Link } from "@/components/Link";
import { toModuleById } from "@/routes/constants";
import { cropString } from "@/utils/stringUtils";

import { links } from "../index";

export const columns = (): ColumnDef<Module>[] => [
{
accessorKey: "id",
header: () => "ID",
cell: ({ row }) => {
const id = row.getValue("id") as string;
return (
<Link to={toModuleById(id)} className="hover:underline hover:text-text-quaternary">
{cropString(id)}
</Link>
);
},
},
{
accessorKey: "name",
header: () => (
<div className="flex items-center gap-2.5">
<HelperIndicator type="module" />
Module Name
</div>
),
cell: ({ row }) => {
const name = row.getValue("name") as string;
return name;
},
},
{
accessorKey: "description",
header: () => "Module Description",
cell: ({ row }) => {
const description = row.getValue("description");
return <p className="max-w-[400px] overflow-hidden text-ellipsis">{description as string}</p>;
},
},
{
accessorKey: "moduleAddress",
header: () => <p className="text-right">Contract Address</p>,
cell: ({ row }) => {
const address = row.getValue("moduleAddress") as string;
return (
<a
href={`${links.lineascan.address}/${address}`}
target="_blank"
className="hover:underline hover:text-text-quaternary"
>
{cropString(address)}
</a>
);
},
},
];
30 changes: 30 additions & 0 deletions explorer/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
--indicator-blue: #2D4EC3;
--indicator-magenta: #D6247A;
--indicator-green: #41AC00;
--indicator-orange: #B93800;

--text-primary: #0D0D12;
--text-secondary: #3D3D51;
Expand Down Expand Up @@ -98,5 +99,34 @@
stroke: #9096B2;
}

.table-row-transition {
transition: all cubic-bezier(0, 0.52, 1, 1) .7s;

td:last-child {
display: flex;
justify-content: flex-end;
position: relative;
transition: transform 0.5s;

&:after {
content: '\203A';
position: absolute;
opacity: 0;
top: 15px;
right: 0;
transition: 0.5s;
scale: 2;
}
}
}

.table-row-transition:hover {
td:last-child {
transform: translateX(-20px);

&:after {
opacity: 1;
}
}
}
}
2 changes: 1 addition & 1 deletion explorer/src/interfaces/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export interface NavigationProps {
submenu?: JSX.Element;
}

export type Page = "schema" | "portal" | "attestation";
export type Page = "schema" | "portal" | "attestation" | "module";
2 changes: 2 additions & 0 deletions explorer/src/interfaces/swr/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export enum SWRKeys {
GET_ATTESTATION_LIST = "getAttestationList",
GET_ATTESTATION_COUNT = "getAttestationCount",
GET_RECENT_ATTESTATION = "getRecentAttestations",
GET_MODULE_LIST = "getModuleList",
GET_MODULE_COUNT = "getModuleCount",
}
23 changes: 15 additions & 8 deletions explorer/src/pages/Attestations/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { OrderDirection } from "@verax-attestation-registry/verax-sdk/lib/types/.graphclient";
import { useState } from "react";
import { useSearchParams } from "react-router-dom";
import useSWR from "swr";

import { DataTable } from "@/components/DataTable";
Expand All @@ -10,6 +9,7 @@ import { columns } from "@/constants/columns/attestation";
import { EQueryParams } from "@/enums/queryParams";
import { SWRKeys } from "@/interfaces/swr/enum";
import { useNetworkContext } from "@/providers/network-provider/context";
import { getItemsByPage, pageBySearchParams } from "@/utils/paginationUtils";

import { ListSwitcher } from "./components/ListSwitcher";

Expand All @@ -19,8 +19,16 @@ export const Attestations: React.FC = () => {
network: { chain },
} = useNetworkContext();

const [searchParams] = useSearchParams();
const [skip, setSkip] = useState<number>(ZERO);
const { data: attestationsCount } = useSWR(
`${SWRKeys.GET_ATTESTATION_COUNT}/${chain.id}`,
() => sdk.attestation.getAttestationIdCounter() as Promise<number>,
);

const totalItems = attestationsCount ?? ZERO;
const searchParams = new URLSearchParams(window.location.search);
const page = pageBySearchParams(searchParams, totalItems);

const [skip, setSkip] = useState<number>(getItemsByPage(page));

const sortByDateDirection = searchParams.get(EQueryParams.SORT_BY_DATE);
const attester = searchParams.get(EQueryParams.ATTESTER);
Expand All @@ -37,10 +45,9 @@ export const Attestations: React.FC = () => {
),
);

const { data: attestationsCount } = useSWR(
`${SWRKeys.GET_ATTESTATION_COUNT}/${chain.id}`,
() => sdk.attestation.getAttestationIdCounter() as Promise<number>,
);
const handlePage = (retrievedPage: number) => {
setSkip(getItemsByPage(retrievedPage));
};

return (
<div className="container mt-5 md:mt-8">
Expand All @@ -51,7 +58,7 @@ export const Attestations: React.FC = () => {
<ListSwitcher />
{/* TODO: add skeleton for table */}
{attestationsList && <DataTable columns={columns()} data={attestationsList} />}
{attestationsCount && <Pagination itemsCount={attestationsCount} handleSkip={setSkip} />}
{attestationsCount && <Pagination itemsCount={attestationsCount} handlePage={handlePage} />}
</div>
</div>
);
Expand Down
48 changes: 48 additions & 0 deletions explorer/src/pages/Modules/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState } from "react";
import useSWR from "swr";

import { DataTable } from "@/components/DataTable";
import { Pagination } from "@/components/Pagination";
import { ITEMS_PER_PAGE_DEFAULT, ZERO } from "@/constants";
import { columns } from "@/constants/columns/module";
import { SWRKeys } from "@/interfaces/swr/enum";
import { useNetworkContext } from "@/providers/network-provider/context";
import { getItemsByPage, pageBySearchParams } from "@/utils/paginationUtils";

export const Modules: React.FC = () => {
const {
sdk,
network: { chain },
} = useNetworkContext();
const { data: modulesCount } = useSWR(
`${SWRKeys.GET_MODULE_COUNT}/${chain.id}`,
() => sdk.module.getModulesNumber() as Promise<bigint>,
);

const totalItems = modulesCount ? Number(modulesCount) : ZERO;
const searchParams = new URLSearchParams(window.location.search);
const page = pageBySearchParams(searchParams, totalItems);

const [skip, setSkip] = useState<number>(getItemsByPage(page));

const { data: modulesList } = useSWR(`${SWRKeys.GET_MODULE_LIST}/${skip}/${chain.id}`, () =>
sdk.module.findBy(ITEMS_PER_PAGE_DEFAULT, skip),
);

const handlePage = (retrievedPage: number) => {
setSkip(getItemsByPage(retrievedPage));
};

return (
<div className="container mt-5 md:mt-8">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between mb-6 md:mb-8 gap-6 md:gap-0">
<h1 className="text-2xl md:text-[2rem]/[2rem] font-semibold tracking-tighter zinc-950">Explore Modules</h1>
</div>
<div>
{/* TODO: add skeleton for table */}
{modulesList && <DataTable columns={columns()} data={modulesList} />}
{Boolean(modulesCount) && <Pagination itemsCount={totalItems} handlePage={handlePage} />}
</div>
</div>
);
};
Loading

0 comments on commit 0b8ad3f

Please sign in to comment.