diff --git a/apps/commune-validator/src/app/(pages)/modules/page.tsx b/apps/commune-validator/src/app/(pages)/modules/page.tsx index 631a834..3f8b6e4 100644 --- a/apps/commune-validator/src/app/(pages)/modules/page.tsx +++ b/apps/commune-validator/src/app/(pages)/modules/page.tsx @@ -1,11 +1,12 @@ import { Suspense } from "react"; +import type { Module } from "~/utils/types"; import { ModuleCard } from "~/app/components/module-card"; import { PaginationControls } from "~/app/components/pagination-controls"; import { ViewControls } from "~/app/components/view-controls"; import { api } from "~/trpc/server"; -export default async function Page({ +export default async function ModulesPage({ searchParams, }: { searchParams: { page?: string; sortBy?: string; order?: string }; @@ -17,7 +18,6 @@ export default async function Page({ const { modules, metadata } = await api.module.paginatedAll({ page: currentPage, limit: 24, - // @ts-expect-error - TS doesn't know about sortBy for some reason sortBy: sortBy, order: order, }); @@ -29,7 +29,7 @@ export default async function Page({
{modules.length ? ( - modules.map((module) => ( + modules.map((module: Module) => ( - {data.map((subnet) => ( - - ))} -
+ <> + Loading view controls...}> + + +
+ {subnets.length ? ( + subnets.map((subnet: Subnet) => ( + + )) + ) : ( +

No subnets found

+ )} +
+ Loading...}> + + + ); } diff --git a/apps/commune-validator/src/app/components/subnet-view-controls.tsx b/apps/commune-validator/src/app/components/subnet-view-controls.tsx new file mode 100644 index 0000000..eb3a232 --- /dev/null +++ b/apps/commune-validator/src/app/components/subnet-view-controls.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; + +type SortField = + | "id" + | "founderShare" + | "incentiveRatio" + | "proposalRewardTreasuryAllocation" + | "minValidatorStake" + | "createdAt"; + +type SortOrder = "asc" | "desc"; + +const sortFieldLabels: Record = { + id: "ID", + founderShare: "Founder Share", + incentiveRatio: "Incentive Ratio", + proposalRewardTreasuryAllocation: "Proposal Reward Alloc.", + minValidatorStake: "Min. Vali. Stake", + createdAt: "Creation Date", +}; + +export function SubnetViewControls() { + const router = useRouter(); + const searchParams = useSearchParams(); + const [sortField, setSortField] = useState( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + (searchParams.get("sortBy") as SortField) ?? "id", + ); + const [sortOrder, setSortOrder] = useState( + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + (searchParams.get("order") as SortOrder) ?? "asc", + ); + + useEffect(() => { + const newSearchParams = new URLSearchParams(searchParams); + newSearchParams.set("sortBy", sortField); + newSearchParams.set("order", sortOrder); + router.push(`?${newSearchParams.toString()}`, { scroll: false }); + }, [sortField, sortOrder, router, searchParams]); + + const handleSortChange = (field: SortField) => { + const newOrder = + field === sortField && sortOrder === "asc" ? "desc" : "asc"; + setSortField(field); + setSortOrder(newOrder); + }; + + return ( +
+ Sort by: + {(Object.keys(sortFieldLabels) as SortField[]).map((field) => ( + + ))} +
+ ); +} diff --git a/packages/api/src/router/subnet.ts b/packages/api/src/router/subnet.ts index ba7845c..1015430 100644 --- a/packages/api/src/router/subnet.ts +++ b/packages/api/src/router/subnet.ts @@ -23,6 +23,51 @@ export const subnetRouter = { where: eq(subnetDataSchema.id, input.id), }); }), + paginatedAll: publicProcedure + .input( + z.object({ + page: z.number().int().positive().default(1), + limit: z.number().int().positive().max(100).default(50), + sortBy: z + .enum([ + "id", + "founderShare", + "incentiveRatio", + "proposalRewardTreasuryAllocation", + "minValidatorStake", + "createdAt", + ]) + .default("id"), + order: z.enum(["asc", "desc"]).default("asc"), + }), + ) + .query(async ({ ctx, input }) => { + const { page, limit, sortBy, order } = input; + const offset = (page - 1) * limit; + + const subnets = await ctx.db.query.subnetDataSchema.findMany({ + limit: limit, + offset: offset, + orderBy: (subnetData, { asc, desc }) => [ + order === "asc" ? asc(subnetData[sortBy]) : desc(subnetData[sortBy]), + ], + }); + + const totalCount = await ctx.db + .select({ count: sql`count(*)` }) + .from(subnetDataSchema) + .then((result) => Number(result[0]?.count)); + + return { + subnets, + metadata: { + currentPage: page, + pageSize: limit, + totalCount, + totalPages: Math.ceil(totalCount / limit), + }, + }; + }), byUserSubnetData: publicProcedure .input(z.object({ userKey: z.string() })) .query(async ({ ctx, input }) => { diff --git a/packages/subspace/queries/index.ts b/packages/subspace/queries/index.ts index 0ea6182..b740342 100644 --- a/packages/subspace/queries/index.ts +++ b/packages/subspace/queries/index.ts @@ -21,12 +21,12 @@ import type { } from "@commune-ts/utils"; import { checkSS58, - STAKE_OUT_DATA_SCHEMA, GOVERNANCE_CONFIG_SCHEMA, isSS58, - STAKE_FROM_SCHEMA, MODULE_BURN_CONFIG_SCHEMA, NetworkSubnetConfigSchema, + STAKE_FROM_SCHEMA, + STAKE_OUT_DATA_SCHEMA, SUBSPACE_MODULE_SCHEMA, } from "@commune-ts/types"; import { @@ -36,7 +36,6 @@ import { standardizeUidToSS58address, } from "@commune-ts/utils"; - export { ApiPromise }; // == chain == @@ -348,7 +347,7 @@ export async function queryStakeOutCORRECT( if (!response.ok) { throw new Error("Failed to fetch data"); } - const stakeOutData = STAKE_OUT_DATA_SCHEMA.parse(await response.json()) + const stakeOutData = STAKE_OUT_DATA_SCHEMA.parse(await response.json()); return stakeOutData; } @@ -521,8 +520,9 @@ export async function queryUserTotalStaked( * @param netuidWhitelist if empty, modules from all subnets are returned */ - -export async function querySubnetParams(api: Api): Promise { +export async function querySubnetParams( + api: Api, +): Promise { const subnetProps: SubspaceStorageName[] = [ "subnetNames", "immunityPeriod", @@ -550,7 +550,6 @@ export async function querySubnetParams(api: Api): Promise> + stakeFromStorage: Map>, ) { const stakerMap = stakeFromStorage.get(targetKey); let totalStake = 0n; @@ -618,13 +622,17 @@ export async function queryRegisteredModulesInfo( "dividends", "delegationFee", "stakeFrom", - ] + ]; - const extraPropsQuery: { subspaceModule: SubspaceStorageName[] } = { subspaceModule: moduleProps } + const extraPropsQuery: { subspaceModule: SubspaceStorageName[] } = { + subspaceModule: moduleProps, + }; const modulesInfo = await queryChain(api, extraPropsQuery, netuid); const processedModules = standardizeUidToSS58address(modulesInfo, uidToSS58); const moduleMap: SubspaceModule[] = []; - const parsedStakeFromStorage = STAKE_FROM_SCHEMA.parse({ stakeFromStorage: processedModules.stakeFrom }); + const parsedStakeFromStorage = STAKE_FROM_SCHEMA.parse({ + stakeFromStorage: processedModules.stakeFrom, + }); for (const uid of Object.keys(uidToSS58)) { const moduleKey = uidToSS58[uid]; @@ -646,10 +654,13 @@ export async function queryRegisteredModulesInfo( incentive: processedModules.incentive[moduleKey], dividends: processedModules.dividends[moduleKey], delegationFee: processedModules.delegationFee[moduleKey], - totalStaked: keyStakeFrom(moduleKey, parsedStakeFromStorage.stakeFromStorage), - totalStakers: parsedStakeFromStorage.stakeFromStorage.get(moduleKey)?.size ?? 0, - - }) + totalStaked: keyStakeFrom( + moduleKey, + parsedStakeFromStorage.stakeFromStorage, + ), + totalStakers: + parsedStakeFromStorage.stakeFromStorage.get(moduleKey)?.size ?? 0, + }); moduleMap.push(module); } return moduleMap;