Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ Create a `.env.local` file in the root directory:

```env
# Required
NEXT_PUBLIC_SITE_URL=http://localhost:3100
NEXT_PUBLIC_GRAPHQL_URL=https://gql.res.fm/graphql
VITE_SITE_URL=http://localhost:3100

# Optional
ANALYZE=false
Expand Down
3 changes: 1 addition & 2 deletions codegen.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
schema: 'https://gql.res.fm/graphql',
// this assumes that all your source files are in a top-level `src/` directory - you might need to adjust this to your file structure
schema: 'https://quantu.se/graphql',
documents: ['src/**/*.{ts,tsx}'],
generates: {
'./src/__generated__/': {
Expand Down
100 changes: 68 additions & 32 deletions src/__generated__/gql.ts

Large diffs are not rendered by default.

1,395 changes: 1,250 additions & 145 deletions src/__generated__/graphql.ts

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { accounts } from './accounts';
import { blocks } from './blocks';
import { chainStatus } from './chain-status';
import { minerLeaderboard } from './miner-leaderboard';
import { minerRewards } from './miner-rewards';
import { reversibleTransactions } from './reversible-transactions';
import { search } from './search';
Expand All @@ -13,7 +14,8 @@ const api = {
reversibleTransactions,
search,
blocks,
minerRewards
minerRewards,
minerLeaderboard
};

export default api;
40 changes: 40 additions & 0 deletions src/api/miner-leaderboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { QueryHookOptions } from '@apollo/client';
import { gql, useQuery } from '@apollo/client';

import { QUERY_DEFAULT_LIMIT } from '@/constants/query-default-limit';
import type { MinerLeaderboardResponse } from '@/schemas';
import type { PaginatedQueryVariables } from '@/types/query';

export const minerLeaderboard = {
useGetAll: (
config?: QueryHookOptions<MinerLeaderboardResponse, PaginatedQueryVariables>
) => {
const GET_MINER_LEADERBOARD = gql`
query GetMinerLeaderboard($limit: Int, $offset: Int) {
leaderboardEntries: minerStats(
limit: $limit
offset: $offset
orderBy: totalMinedBlocks_DESC
) {
id
totalMinedBlocks
totalRewards
}
meta: minerStatsConnection(orderBy: id_ASC) {
totalCount
}
}
`;

return useQuery<MinerLeaderboardResponse, PaginatedQueryVariables>(
GET_MINER_LEADERBOARD,
{
...config,
variables: {
limit: config?.variables?.limit ?? QUERY_DEFAULT_LIMIT,
offset: config?.variables?.offset ?? 0
}
}
);
}
};
34 changes: 34 additions & 0 deletions src/components/common/table-columns/MINER_LEADERBOARD_COLUMNS.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createColumnHelper } from '@tanstack/react-table';

import { LinkWithCopy } from '@/components/ui/composites/link-with-copy/LinkWithCopy';
import { RESOURCES } from '@/constants/resources';
import type { MinerStats } from '@/schemas';
import { formatMonetaryValue } from '@/utils/formatter';

const columnHelper = createColumnHelper<MinerStats>();

export const MINER_LEADERBOARD_COLUMNS = [
columnHelper.accessor('id', {
id: 'id',
header: 'Miner',
cell: (props) => (
<LinkWithCopy
href={`${RESOURCES.accounts}/${props.getValue()}`}
text={props.getValue() ?? '-'}
/>
),
enableSorting: false
}),
columnHelper.accessor('totalMinedBlocks', {
id: 'total_mined_blocks',
header: 'Mined Blocks',
cell: (props) => props.getValue(),
enableSorting: true
}),
columnHelper.accessor('totalRewards', {
id: 'total_rewards',
header: 'Total Rewards',
cell: (props) => formatMonetaryValue(props.getValue(), 5),
enableSorting: true
})
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

import { DataTable } from '@/components/ui/composites/data-table/DataTable';

import { useMinerLeaderboardTable } from './hook';

export const MinerLeaderboardTable = () => {
const { getStatus, table, error } = useMinerLeaderboardTable();

return (
<DataTable
table={table}
fetch={{
status: getStatus(),
errorFallback: <p>Error: {error && error.message}</p>
}}
withControls
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { OnChangeFn, PaginationState } from '@tanstack/react-table';
import { getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { parseAsInteger, useQueryState } from 'nuqs';
import { useEffect, useMemo, useState } from 'react';

import api from '@/api';
import { MINER_LEADERBOARD_COLUMNS } from '@/components/common/table-columns/MINER_LEADERBOARD_COLUMNS';
import { DATA_POOL_INTERVAL } from '@/constants/data-pool-interval';
import { QUERY_DEFAULT_LIMIT } from '@/constants/query-default-limit';
import type { MinerStats } from '@/schemas';

export const useMinerLeaderboardTable = () => {
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
const [limit, setLimit] = useQueryState(
'limit',
parseAsInteger.withDefault(QUERY_DEFAULT_LIMIT)
);

const currentPageIndex = page - 1;

const paginationValue: PaginationState = {
pageSize: limit,
pageIndex: currentPageIndex
};

const handleChangePagination: OnChangeFn<PaginationState> = (pagination) => {
if (typeof pagination === 'function') {
const newPagination = pagination(paginationValue);

setPage(newPagination.pageIndex + 1);
setLimit(newPagination.pageSize);
} else {
setPage(pagination.pageIndex + 1);
setLimit(pagination.pageSize);
}
};

const {
loading,
data,
error: fetchError
} = api.minerLeaderboard.useGetAll({
pollInterval: DATA_POOL_INTERVAL,
variables: {
limit,
offset: currentPageIndex * limit
}
});

const minerLeaderboardColumns = useMemo(() => MINER_LEADERBOARD_COLUMNS, []);
const [rowCount, setRowCount] = useState<number>(data?.meta.totalCount ?? 0);

const table = useReactTable<MinerStats>({
data: data?.leaderboardEntries ?? [],
columns: minerLeaderboardColumns,
getCoreRowModel: getCoreRowModel(),
state: {
pagination: paginationValue
},
rowCount,
onPaginationChange: handleChangePagination,
manualPagination: true
});

const success = !loading && !fetchError;
const error = !loading && fetchError;

const getStatus = () => {
switch (true) {
case success:
return 'success';
case !!error:
return 'error';
case !!loading:
return 'loading';
default:
return 'idle';
}
};

useEffect(() => {
if (!loading && data?.meta.totalCount) setRowCount(data.meta.totalCount);
}, [loading, data?.meta.totalCount]);

return {
table,
getStatus,
error
};
};
6 changes: 3 additions & 3 deletions src/components/layout/header/DesktopMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ export const DesktopMenu: React.FC<DesktopMenuProps> = () => {
{nav.label}
</NavigationMenuTrigger>

<NavigationMenuContent className="flex flex-col gap-2 whitespace-nowrap p-2">
<NavigationMenuContent className="top-10 flex flex-col gap-2 whitespace-nowrap rounded border bg-popover p-2">
{nav.children.map((subNav) => (
<NavigationMenuLink key={subNav.path} asChild>
<Link
to={subNav.path}
className="rounded p-1 no-underline hover:bg-accent data-[active=true]:font-semibold data-[active=true]:text-foreground-active"
className="rounded p-1 !text-base no-underline hover:bg-accent data-[active=true]:font-semibold data-[active=true]:text-foreground-active"
data-active={rootPath === subNav.path.split('/')[1]}
>
{subNav.label}
Expand All @@ -58,7 +58,7 @@ export const DesktopMenu: React.FC<DesktopMenuProps> = () => {
<NavigationMenuLink asChild>
<Link
to={nav.path}
className="no-underline data-[active=true]:font-semibold data-[active=true]:text-foreground-active"
className="!text-base no-underline focus:bg-transparent data-[active=true]:bg-transparent data-[active=true]:font-semibold data-[active=true]:text-foreground-active data-[active=true]:focus:bg-transparent"
data-active={rootPath === nav.path.split('/')[1]}
>
{nav.label}
Expand Down
6 changes: 3 additions & 3 deletions src/components/layout/header/MobileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({
return (
<NavigationMenuItem key={nav.label} className="!ml-0">
<NavigationMenuTrigger
className="bg-transparent p-0 text-base font-normal no-underline hover:!bg-transparent focus:!bg-transparent data-[active=true]:font-semibold"
className="bg-transparent p-2 text-base font-normal no-underline hover:!bg-transparent focus:!bg-transparent data-[active=true]:font-semibold"
data-active={
!!nav.children.find(
(subNav) => subNav.path.split('/')[1] === rootPath
Expand All @@ -58,7 +58,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({
<NavigationMenuLink key={subNav.path} asChild>
<Link
to={subNav.path}
className="no-underline data-[active=true]:font-semibold"
className="!text-base no-underline data-[active=true]:font-semibold"
data-active={rootPath === subNav.path.split('/')[1]}
onClick={toggleMenu}
>
Expand All @@ -75,7 +75,7 @@ export const MobileMenu: React.FC<MobileMenuProps> = ({
<NavigationMenuLink asChild>
<Link
to={nav.path}
className="no-underline data-[active=true]:font-semibold"
className="!text-base no-underline data-[active=true]:font-semibold"
data-active={rootPath === nav.path.split('/')[1]}
onClick={toggleMenu}
>
Expand Down
Loading