From 742f3dce8f7c38290a629840a1c12ba4e1aa184d Mon Sep 17 00:00:00 2001 From: Nick Lionis Date: Fri, 17 Jan 2025 16:19:06 +0200 Subject: [PATCH 1/2] feat: addition of pool-program lists --- src/client.ts | 2 + .../components/PoolCard/PoolCard.stories.tsx | 7 +- .../pool/components/PoolCard/PoolDataCard.tsx | 9 +- .../PoolCardGroup/PoolCardGroup.stories.tsx | 8 +- .../PoolCardGroup/PoolCardGroup.tsx | 4 +- .../components/PoolList/PoolList.stories.tsx | 67 +++++++++++++++ .../pool/components/PoolList/PoolList.tsx | 76 +++++++++++++++++ .../pool/components/PoolList/hooks/index.ts | 1 + .../hooks/useFilteredAndOrderedPools.ts | 70 ++++++++++++++++ .../pool/components/PoolList/index.ts | 3 + .../pool/components/PoolList/utils.ts | 76 +++++++++++++++++ src/features/pool/index.ts | 1 + .../ProgramCard/ProgramCard.stories.tsx | 3 +- .../components/ProgramCard/ProgramCard.tsx | 7 +- .../ProgramCardGroup.stories.tsx | 9 +- .../ProgramList/ProgramList.stories.tsx | 51 ++++++++++++ .../components/ProgramList/ProgramList.tsx | 82 +++++++++++++++++++ .../hooks/useFilteredAndOrderedPrograms.ts | 48 +++++++++++ .../program/components/ProgramList/index.ts | 1 + .../program/components/ProgramList/utils.ts | 53 ++++++++++++ src/features/program/index.ts | 1 + src/types/pool.ts | 3 +- 22 files changed, 566 insertions(+), 16 deletions(-) create mode 100644 src/features/pool/components/PoolList/PoolList.stories.tsx create mode 100644 src/features/pool/components/PoolList/PoolList.tsx create mode 100644 src/features/pool/components/PoolList/hooks/index.ts create mode 100644 src/features/pool/components/PoolList/hooks/useFilteredAndOrderedPools.ts create mode 100644 src/features/pool/components/PoolList/index.ts create mode 100644 src/features/pool/components/PoolList/utils.ts create mode 100644 src/features/program/components/ProgramList/ProgramList.stories.tsx create mode 100644 src/features/program/components/ProgramList/ProgramList.tsx create mode 100644 src/features/program/components/ProgramList/hooks/useFilteredAndOrderedPrograms.ts create mode 100644 src/features/program/components/ProgramList/index.ts create mode 100644 src/features/program/components/ProgramList/utils.ts diff --git a/src/client.ts b/src/client.ts index 8fb3e10..b127d9a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4,3 +4,5 @@ export * from "./primitives/MarkdownEditor"; export * from "./components/Form"; export * from "./components/GenericProgressForm"; export * from "./components/MultipleSelect"; +export * from "./features/pool/components/PoolList"; +export * from "./features/program/components/ProgramList"; diff --git a/src/features/pool/components/PoolCard/PoolCard.stories.tsx b/src/features/pool/components/PoolCard/PoolCard.stories.tsx index 62d65d3..563fbe9 100644 --- a/src/features/pool/components/PoolCard/PoolCard.stories.tsx +++ b/src/features/pool/components/PoolCard/PoolCard.stories.tsx @@ -6,7 +6,7 @@ import { PoolStatus, PoolType } from "@/types"; import { PoolCard, PoolCardProps, PoolCardQueryProps } from "./PoolCard"; -const onProgramClick = action("Pool Clicked!"); +const onPoolClick = action("Pool Clicked!"); const simpleRound = { roundName: "Grants Round Defi", @@ -21,7 +21,8 @@ const simpleRound = { operatorsCount: 10, logoImg: "https://cdn.prod.website-files.com/6433c5d029c6bb75f3f00bd5/66f47dd26d8ec8d0e48a22d0_gitcoin-profile.png", - onClick: () => onProgramClick, + onClick: (pool?: { chainId: number; roundId: string }) => onPoolClick(pool), + createdAtBlock: 123456, }; export default { @@ -39,6 +40,8 @@ export default { votingEndDate: { control: "date" }, operatorsCount: { control: "number" }, queryResult: { table: { disable: true } }, // Hide queryResult from controls + createdAtBlock: { control: "number" }, + onClick: { action: "onClick" }, }, } as Meta; diff --git a/src/features/pool/components/PoolCard/PoolDataCard.tsx b/src/features/pool/components/PoolCard/PoolDataCard.tsx index fabba0e..1c0c5a5 100644 --- a/src/features/pool/components/PoolCard/PoolDataCard.tsx +++ b/src/features/pool/components/PoolCard/PoolDataCard.tsx @@ -15,8 +15,13 @@ export function PoolDataCard({ data }: PoolDataCardProps) { const { name, icon } = getChainInfo(data.chainId); return (
+ data.onClick?.({ + chainId: data.chainId, + roundId: data.roundId, + }) + } + className="inline-flex h-60 w-full cursor-pointer items-center justify-between rounded-2xl border border-grey-100 p-6" >
diff --git a/src/features/pool/components/PoolCardGroup/PoolCardGroup.stories.tsx b/src/features/pool/components/PoolCardGroup/PoolCardGroup.stories.tsx index dda5e12..669e0e6 100644 --- a/src/features/pool/components/PoolCardGroup/PoolCardGroup.stories.tsx +++ b/src/features/pool/components/PoolCardGroup/PoolCardGroup.stories.tsx @@ -5,7 +5,7 @@ import { PoolStatus, PoolType } from "@/types"; import { PoolCardGroup } from "./PoolCardGroup"; -const onProgramClick = action("Pool Clicked!"); +const onPoolClick = action("Pool Clicked!"); const pools = [ { @@ -19,9 +19,10 @@ const pools = [ votingStartDate: new Date("2024-12-09T19:22:56.413Z"), votingEndDate: new Date("2024-12-10T19:23:30.678Z"), operatorsCount: 10, + createdAtBlock: 1234567890, logoImg: "https://cdn.prod.website-files.com/6433c5d029c6bb75f3f00bd5/66f47dd26d8ec8d0e48a22d0_gitcoin-profile.png", - onClick: () => onProgramClick, + onClick: (pool?: { chainId: number; roundId: string }) => onPoolClick(pool), }, { roundName: "Uniswap", @@ -35,7 +36,8 @@ const pools = [ votingEndDate: new Date("2024-12-10T19:23:30.678Z"), operatorsCount: 5, logoImg: "https://thegivingblock.com/wp-content/uploads/2021/07/Uniswap-Logo.png", - onClick: () => onProgramClick, + createdAtBlock: 1234567890, + onClick: (pool?: { chainId: number; roundId: string }) => onPoolClick(pool), }, ]; diff --git a/src/features/pool/components/PoolCardGroup/PoolCardGroup.tsx b/src/features/pool/components/PoolCardGroup/PoolCardGroup.tsx index 57451a4..99e56b1 100644 --- a/src/features/pool/components/PoolCardGroup/PoolCardGroup.tsx +++ b/src/features/pool/components/PoolCardGroup/PoolCardGroup.tsx @@ -39,8 +39,8 @@ const justifyVariants = tv({ export const PoolCardGroup = ({ pools, justify }: PoolCardGroupProps) => { return (
- {pools.map((stat, index) => ( - + {pools.map((pool, index) => ( + ))}
); diff --git a/src/features/pool/components/PoolList/PoolList.stories.tsx b/src/features/pool/components/PoolList/PoolList.stories.tsx new file mode 100644 index 0000000..9d86d61 --- /dev/null +++ b/src/features/pool/components/PoolList/PoolList.stories.tsx @@ -0,0 +1,67 @@ +import { action } from "@storybook/addon-actions"; +import type { Meta, StoryObj } from "@storybook/react"; + +import { PoolType } from "@/types"; +import { PoolStatus } from "@/types/pool"; + +import { PoolList } from "./PoolList"; + +const onPoolClick = action("Pool clicked!"); + +const meta: Meta = { + title: "Features/Pool/PoolList", + component: PoolList, +}; + +export default meta; +type Story = StoryObj; + +const mockPools = [ + { + roundName: "Grants Round Defi", + roundId: "90", + chainId: 10, + poolType: PoolType.QuadraticFunding, + poolStatus: PoolStatus.ApplicationsInProgress, + applicationStartDate: new Date("2024-12-08T19:22:56.413Z"), + applicationEndDate: new Date("2024-12-09T19:23:30.678Z"), + votingStartDate: new Date("2024-12-09T19:22:56.413Z"), + votingEndDate: new Date("2024-12-10T19:23:30.678Z"), + operatorsCount: 10, + createdAtBlock: 100000, + logoImg: + "https://cdn.prod.website-files.com/6433c5d029c6bb75f3f00bd5/66f47dd26d8ec8d0e48a22d0_gitcoin-profile.png", + }, + { + roundName: "Uniswap", + roundId: "91", + chainId: 8453, + poolType: PoolType.DirectGrants, + poolStatus: PoolStatus.FundingPending, + applicationStartDate: new Date("2024-12-08T19:22:56.413Z"), + applicationEndDate: new Date("2024-12-09T19:23:30.678Z"), + votingStartDate: new Date("2024-12-09T19:22:56.413Z"), + votingEndDate: new Date("2024-12-10T19:23:30.678Z"), + operatorsCount: 5, + createdAtBlock: 1000, + logoImg: "https://thegivingblock.com/wp-content/uploads/2021/07/Uniswap-Logo.png", + }, +]; + +export const Default: Story = { + args: { + pools: mockPools.map((pool) => ({ + ...pool, + onClick: (pool?: { chainId: number; roundId: string }) => onPoolClick(pool), + })), + title: "Available Pools", + noPoolsPlaceholder: "No pools found", + }, +}; + +export const Empty: Story = { + args: { + ...Default.args, + pools: [], + }, +}; diff --git a/src/features/pool/components/PoolList/PoolList.tsx b/src/features/pool/components/PoolList/PoolList.tsx new file mode 100644 index 0000000..be4a0e4 --- /dev/null +++ b/src/features/pool/components/PoolList/PoolList.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { useState } from "react"; + +import { MultipleSelect } from "@/components/MultipleSelect"; + +import { PoolCardProps } from "../PoolCard"; +import { PoolCardGroup } from "../PoolCardGroup"; +import { useFilteredAndOrderedPools } from "./hooks/useFilteredAndOrderedPools"; +import { getSortFilterOptions } from "./utils"; + +export interface PoolListProps { + pools: PoolCardProps[]; + title?: string; + noPoolsPlaceholder?: string; +} + +export const PoolList = ({ + pools, + title = "Pools", + noPoolsPlaceholder = "No pools found", +}: PoolListProps) => { + const [order, setOrder] = useState>({}); + const [selectedFilters, setSelectedFilters] = useState>({}); + + const { orderOptions, filterOptions } = getSortFilterOptions(pools); + + const handleFilterChange = (values: Record) => { + setSelectedFilters(values); + }; + + const handleOrderChange = (values: Record) => { + setOrder(values); + }; + + const filteredAndOrderedPools = useFilteredAndOrderedPools({ pools, order, selectedFilters }); + + return ( +
+
+
+ {`${title} (${pools.length})`} +
+
+
+
Order by
+ +
+
+
Filter by
+ +
+
+
+
+ {filteredAndOrderedPools.length > 0 ? ( + + ) : ( +
{noPoolsPlaceholder}
+ )} +
+
+ ); +}; diff --git a/src/features/pool/components/PoolList/hooks/index.ts b/src/features/pool/components/PoolList/hooks/index.ts new file mode 100644 index 0000000..5a7e0c1 --- /dev/null +++ b/src/features/pool/components/PoolList/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useFilteredAndOrderedPools"; diff --git a/src/features/pool/components/PoolList/hooks/useFilteredAndOrderedPools.ts b/src/features/pool/components/PoolList/hooks/useFilteredAndOrderedPools.ts new file mode 100644 index 0000000..e0380a5 --- /dev/null +++ b/src/features/pool/components/PoolList/hooks/useFilteredAndOrderedPools.ts @@ -0,0 +1,70 @@ +import { useMemo } from "react"; + +import { PoolData, PoolStatus } from "@/types/pool"; + +interface UseFilteredAndOrderedPoolsProps { + pools: PoolData[]; + selectedFilters: Record; + order: Record; +} + +export const useFilteredAndOrderedPools = ({ + pools, + selectedFilters, + order, +}: UseFilteredAndOrderedPoolsProps) => { + return useMemo(() => { + let result = [...pools]; + + // 1) Check for "All" filter + const ungrouped = selectedFilters["ungrouped"]; + const hasAllFilter = ungrouped?.includes("All"); + + // 2) Filter by network + const selectedNetworks = selectedFilters["Network"] || []; + if (!hasAllFilter && selectedNetworks.length > 0) { + result = result.filter((pool) => selectedNetworks.includes(pool.chainId.toString())); + } + + // 3) Filter by status + const selectedStatuses = selectedFilters["Status"] || []; + if (!hasAllFilter && selectedStatuses.length > 0) { + result = result.filter((pool) => { + return selectedStatuses.some((status) => { + switch (status) { + case "active": + return ( + pool.poolStatus === PoolStatus.RoundInProgress || + pool.poolStatus === PoolStatus.ApplicationsInProgress + ); + case "applications": + return pool.poolStatus === PoolStatus.ApplicationsInProgress; + case "finished": + return pool.poolStatus === PoolStatus.FundingPending; + default: + return false; + } + }); + }); + } + + // 4) Apply ordering + const orderValue = order["ORDER BY TIME"]?.[0] || order["ORDER BY NAME"]?.[0] || "Recent"; + switch (orderValue) { + case "Recent": + result.sort((a, b) => b.createdAtBlock - a.createdAtBlock); + break; + case "Oldest": + result.sort((a, b) => a.createdAtBlock - b.createdAtBlock); + break; + case "A-Z": + result.sort((a, b) => a.roundName.localeCompare(b.roundName)); + break; + case "Z-A": + result.sort((a, b) => b.roundName.localeCompare(a.roundName)); + break; + } + + return result; + }, [pools, order, selectedFilters]); +}; diff --git a/src/features/pool/components/PoolList/index.ts b/src/features/pool/components/PoolList/index.ts new file mode 100644 index 0000000..c8dbaa9 --- /dev/null +++ b/src/features/pool/components/PoolList/index.ts @@ -0,0 +1,3 @@ +export * from "./PoolList"; +export * from "./utils"; +export * from "./hooks"; diff --git a/src/features/pool/components/PoolList/utils.ts b/src/features/pool/components/PoolList/utils.ts new file mode 100644 index 0000000..e344da3 --- /dev/null +++ b/src/features/pool/components/PoolList/utils.ts @@ -0,0 +1,76 @@ +import { MultipleSelectGroup } from "@/components/MultipleSelect/types"; +import { getChainInfo } from "@/lib"; +import { PoolData } from "@/types/pool"; + +export const getSortFilterOptions = (pools: PoolData[]) => { + const orderOptions = [ + { + groupLabel: "ORDER BY TIME", + multiple: false, + items: ["Recent", "Oldest"].map((value) => ({ + label: value, + value, + exclusive: true, + exclusiveScope: "global", + })), + }, + { + groupLabel: "ORDER BY NAME", + multiple: false, + items: ["A-Z", "Z-A"].map((value) => ({ + label: value, + value, + exclusive: true, + exclusiveScope: "global", + })), + }, + ] satisfies MultipleSelectGroup[]; + + // Example "All" ungrouped + networks + statuses + const filterOptions = [ + { + multiple: false, + items: [ + { + label: "All", + value: "All", + exclusive: true, + exclusiveScope: "global", + }, + ], + }, + { + groupLabel: "Network", + multiple: true, + collapsible: true, + items: [...new Set(pools.map((pool) => pool.chainId))].map((chainId) => { + const chainInfo = getChainInfo(chainId); + return { + label: `Rounds on ${chainInfo.name}`, + value: chainId.toString(), + }; + }), + }, + { + groupLabel: "Status", + multiple: true, + collapsible: true, + items: [ + { + label: "Active", + value: "active", + }, + { + label: "Taking Applications", + value: "applications", + }, + { + label: "Finished", + value: "finished", + }, + ], + }, + ] satisfies MultipleSelectGroup[]; + + return { orderOptions, filterOptions }; +}; diff --git a/src/features/pool/index.ts b/src/features/pool/index.ts index 0eedc81..84fdd0d 100644 --- a/src/features/pool/index.ts +++ b/src/features/pool/index.ts @@ -1,6 +1,7 @@ export * from "./components/PoolBadge"; export * from "./components/PoolCard"; export * from "./components/PoolCardGroup"; +export * from "./components/PoolList"; export * from "./components/PoolStatusBadge"; export * from "./components/PoolSummary"; export * from "./components/PoolTypeBadge"; diff --git a/src/features/program/components/ProgramCard/ProgramCard.stories.tsx b/src/features/program/components/ProgramCard/ProgramCard.stories.tsx index d5345c6..64da8cf 100644 --- a/src/features/program/components/ProgramCard/ProgramCard.stories.tsx +++ b/src/features/program/components/ProgramCard/ProgramCard.stories.tsx @@ -13,7 +13,8 @@ const program: ProgramCardProps = { title: "Gitcoin Grants Stack", operatorsCount: 2, roundsCount: 10, - onClick: () => onProgramClick(), + createdAtBlock: 1000000, + onClick: (program?: { chainId: number; programId: string }) => onProgramClick(program), }; export default { diff --git a/src/features/program/components/ProgramCard/ProgramCard.tsx b/src/features/program/components/ProgramCard/ProgramCard.tsx index b2d5613..af19f1d 100644 --- a/src/features/program/components/ProgramCard/ProgramCard.tsx +++ b/src/features/program/components/ProgramCard/ProgramCard.tsx @@ -16,7 +16,8 @@ export interface ProgramCardProps { title: string; operatorsCount: number; roundsCount: number; - onClick?: () => void; + createdAtBlock: number; + onClick?: (program?: { chainId: number; programId: string }) => void; } export interface ProgramCardQueryProps { queryResult: UseQueryResult; @@ -42,8 +43,8 @@ export function ProgramDataCard({ data }: ProgramDataCardProps) { const { name, icon } = getChainInfo(data.chainId); return ( data.onClick?.({ chainId: data.chainId, programId: data.id })} >

{data.title}

diff --git a/src/features/program/components/ProgramCardGroup/ProgramCardGroup.stories.tsx b/src/features/program/components/ProgramCardGroup/ProgramCardGroup.stories.tsx index dd283d7..4c8273a 100644 --- a/src/features/program/components/ProgramCardGroup/ProgramCardGroup.stories.tsx +++ b/src/features/program/components/ProgramCardGroup/ProgramCardGroup.stories.tsx @@ -12,7 +12,8 @@ const programs = [ title: "Gitcoin Grants Stack", operatorsCount: 2, roundsCount: 10, - onClick: () => onProgramClick(), + createdAtBlock: 1000, + onClick: (program?: { chainId: number; programId: string }) => onProgramClick(program), }, { id: "0x3456", @@ -20,7 +21,8 @@ const programs = [ title: "Allo Protocol", operatorsCount: 4, roundsCount: 2, - onClick: () => onProgramClick(), + createdAtBlock: 1000000, + onClick: (program?: { chainId: number; programId: string }) => onProgramClick(program), }, ]; @@ -96,6 +98,7 @@ export const withFourCard: Story = { title: "Pump Fun", operatorsCount: 4, roundsCount: 2, + createdAtBlock: 1000000, onClick: () => onProgramClick(), }, ], @@ -112,6 +115,7 @@ export const withFiveCard: Story = { title: "Pump Fun", operatorsCount: 4, roundsCount: 2, + createdAtBlock: 1000000, onClick: () => onProgramClick(), }, { @@ -120,6 +124,7 @@ export const withFiveCard: Story = { title: "Eigen Protocol", operatorsCount: 4, roundsCount: 2, + createdAtBlock: 1000000, onClick: () => onProgramClick(), }, ], diff --git a/src/features/program/components/ProgramList/ProgramList.stories.tsx b/src/features/program/components/ProgramList/ProgramList.stories.tsx new file mode 100644 index 0000000..cf7701d --- /dev/null +++ b/src/features/program/components/ProgramList/ProgramList.stories.tsx @@ -0,0 +1,51 @@ +import { action } from "@storybook/addon-actions"; +import type { Meta, StoryObj } from "@storybook/react"; + +import { ProgramList } from "./ProgramList"; + +const onProgramClick = action("Program clicked!"); + +const meta: Meta = { + title: "Features/Program/ProgramList", + component: ProgramList, +}; + +export default meta; +type Story = StoryObj; + +const mockPrograms = [ + { + id: "0x123456789", + chainId: 1, + title: "Gitcoin Grants Stack", + operatorsCount: 2, + roundsCount: 10, + createdAtBlock: 100000000, + }, + { + id: "0x3456", + chainId: 10, + title: "Allo Protocol", + operatorsCount: 4, + roundsCount: 2, + createdAtBlock: 1000000, + }, +]; + +export const Default: Story = { + args: { + programs: mockPrograms.map((program) => ({ + ...program, + onClick: (program?: { programId: string; chainId: number }) => onProgramClick(program), + })), + title: "Available Programs", + noProgramsPlaceholder: "No programs found", + }, +}; + +export const Empty: Story = { + args: { + ...Default.args, + programs: [], + }, +}; diff --git a/src/features/program/components/ProgramList/ProgramList.tsx b/src/features/program/components/ProgramList/ProgramList.tsx new file mode 100644 index 0000000..d741bf1 --- /dev/null +++ b/src/features/program/components/ProgramList/ProgramList.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useState } from "react"; + +import { MultipleSelect } from "@/components/MultipleSelect"; + +import { ProgramCardProps } from "../ProgramCard"; +import { ProgramCardGroup } from "../ProgramCardGroup"; +import { useFilteredAndOrderedPrograms } from "./hooks/useFilteredAndOrderedPrograms"; +import { getOrderAndFilterOptions } from "./utils"; + +export interface ProgramListProps { + programs: ProgramCardProps[]; + title?: string; + noProgramsPlaceholder: string; +} + +export const ProgramList = ({ + programs, + noProgramsPlaceholder = "No programs found", + title = "Programs", +}: ProgramListProps) => { + const [order, setOrder] = useState>({ "ORDER BY TIME": ["Recent"] }); + const [selectedFilters, setSelectedFilters] = useState>({ + ungrouped: ["All"], + }); + + const { orderOptions, filterOptions } = getOrderAndFilterOptions(programs); + + const handleOrderChange = (values: Record) => { + setOrder(values); + }; + + const handleFilterChange = (values: Record) => { + setSelectedFilters(values); + }; + + const filteredAndOrderedPrograms = useFilteredAndOrderedPrograms({ + programs, + selectedFilters, + order, + }); + + return ( +
+
+
{`${title} (${programs.length})`}
+
+
+ Order by + +
+
+
Filter by
+ +
+
+
+
+ {filteredAndOrderedPrograms.length > 0 ? ( + + ) : ( +
{noProgramsPlaceholder}
+ )} +
+
+ ); +}; diff --git a/src/features/program/components/ProgramList/hooks/useFilteredAndOrderedPrograms.ts b/src/features/program/components/ProgramList/hooks/useFilteredAndOrderedPrograms.ts new file mode 100644 index 0000000..e329575 --- /dev/null +++ b/src/features/program/components/ProgramList/hooks/useFilteredAndOrderedPrograms.ts @@ -0,0 +1,48 @@ +import { useMemo } from "react"; + +import { ProgramCardProps } from "@/features/program"; + +interface UseFilteredAndOrderedProgramsProps { + programs: ProgramCardProps[]; + selectedFilters: Record; + order: Record; +} + +export const useFilteredAndOrderedPrograms = ({ + programs, + selectedFilters, + order, +}: UseFilteredAndOrderedProgramsProps) => { + return useMemo(() => { + let result = [...programs]; + + // Apply filters + const ungrouped = selectedFilters["ungrouped"]; + const hasAllFilter = ungrouped?.includes("All"); + + // Apply network filters if not "All" + const selectedNetworks = selectedFilters["Network"] || []; + if (!hasAllFilter && selectedNetworks.length > 0) { + result = result.filter((program) => selectedNetworks.includes(program.chainId.toString())); + } + + // Apply ordering + const orderValue = order["ORDER BY TIME"]?.[0] || order["ORDER BY NAME"]?.[0] || "Recent"; + switch (orderValue) { + case "Recent": + result.sort((a, b) => b.createdAtBlock - a.createdAtBlock); + break; + case "Oldest": + result.sort((a, b) => a.createdAtBlock - b.createdAtBlock); + break; + case "A-Z": + result.sort((a, b) => a.title.localeCompare(b.title)); + break; + case "Z-A": + result.sort((a, b) => b.title.localeCompare(a.title)); + break; + } + + return result; + }, [programs, order, selectedFilters]); +}; diff --git a/src/features/program/components/ProgramList/index.ts b/src/features/program/components/ProgramList/index.ts new file mode 100644 index 0000000..57aa0dd --- /dev/null +++ b/src/features/program/components/ProgramList/index.ts @@ -0,0 +1 @@ +export * from "./ProgramList"; diff --git a/src/features/program/components/ProgramList/utils.ts b/src/features/program/components/ProgramList/utils.ts new file mode 100644 index 0000000..51442b2 --- /dev/null +++ b/src/features/program/components/ProgramList/utils.ts @@ -0,0 +1,53 @@ +import { MultipleSelectGroup } from "@/components/MultipleSelect"; +import { ProgramCardProps } from "@/features/program"; +import { getChainInfo } from "@/lib"; + +export const getOrderAndFilterOptions = (programs: ProgramCardProps[]) => { + const orderOptions = [ + { + groupLabel: "ORDER BY TIME", + multiple: false, + items: ["Recent", "Oldest"].map((value) => ({ + label: value, + value, + exclusive: true, + exclusiveScope: "global", + })), + }, + { + groupLabel: "ORDER BY NAME", + multiple: false, + items: ["A-Z", "Z-A"].map((value) => ({ + label: value, + value, + exclusive: true, + exclusiveScope: "global", + })), + }, + ] satisfies MultipleSelectGroup[]; + + const filterOptions = [ + { + multiple: false, + items: [ + { + label: "All", + value: "All", + exclusive: true, + exclusiveScope: "global", + }, + ], + }, + { + groupLabel: "Network", + multiple: true, + collapsible: true, + items: [...new Set(programs.map((program) => program.chainId))].map((chainId) => ({ + label: `Rounds on ${getChainInfo(chainId).name}`, + value: chainId.toString(), + })), + }, + ] satisfies MultipleSelectGroup[]; + + return { orderOptions, filterOptions }; +}; diff --git a/src/features/program/index.ts b/src/features/program/index.ts index 4720f4f..f5c9a4d 100644 --- a/src/features/program/index.ts +++ b/src/features/program/index.ts @@ -1,2 +1,3 @@ export * from "./components/ProgramCard"; export * from "./components/ProgramCardGroup"; +export * from "./components/ProgramList"; diff --git a/src/types/pool.ts b/src/types/pool.ts index e8d3a7e..593a048 100644 --- a/src/types/pool.ts +++ b/src/types/pool.ts @@ -30,8 +30,9 @@ export interface PoolData { votingEndDate: Date; poolStatus: PoolStatus; operatorsCount: number; + createdAtBlock: number; logoImg?: string; - onClick?: () => void; + onClick?: (pool?: { chainId: number; roundId: string }) => void; } // Type guard for PoolData From 40587db672b4ab8b17b191848f5c462aaebfc752 Mon Sep 17 00:00:00 2001 From: Nick Lionis Date: Fri, 17 Jan 2025 16:21:10 +0200 Subject: [PATCH 2/2] chore: added storybook action instead of console.log in genericprogressform story --- .../GenericProgressForm/GenericProgressForm.stories.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/GenericProgressForm/GenericProgressForm.stories.tsx b/src/components/GenericProgressForm/GenericProgressForm.stories.tsx index 6592754..2afd9c4 100644 --- a/src/components/GenericProgressForm/GenericProgressForm.stories.tsx +++ b/src/components/GenericProgressForm/GenericProgressForm.stories.tsx @@ -1,8 +1,11 @@ +import { action } from "@storybook/addon-actions"; import type { Meta, StoryObj } from "@storybook/react"; import { GenericProgressForm } from "./GenericProgressForm"; import { roundSetupSteps } from "./mocks/RoundSetup"; +const onSubmit = action("onSubmit"); + const meta: Meta = { title: "Components/GenericProgressForm", component: GenericProgressForm, @@ -16,9 +19,7 @@ export const Default: Story = { args: { name: "Round setup", steps: roundSetupSteps, - onSubmit: async (values: any) => { - console.log("Submitted final values:", values); - }, + onSubmit: async (values: any) => onSubmit(values), dbName: "formDB", storeName: "formDrafts", },