Skip to content
This repository has been archived by the owner on Feb 10, 2025. It is now read-only.

Commit

Permalink
feat: add poollist and programlist (#118)
Browse files Browse the repository at this point in the history
* feat: addition of pool-program lists

* chore: added storybook action instead of console.log in genericprogressform story
  • Loading branch information
nijoe1 authored Jan 21, 2025
1 parent 312a3d9 commit 9b95216
Show file tree
Hide file tree
Showing 23 changed files with 570 additions and 19 deletions.
2 changes: 2 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Original file line number Diff line number Diff line change
@@ -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<typeof GenericProgressForm> = {
title: "Components/GenericProgressForm",
component: GenericProgressForm,
Expand All @@ -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",
},
Expand Down
7 changes: 5 additions & 2 deletions src/features/pool/components/PoolCard/PoolCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 {
Expand All @@ -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<typeof PoolCard>;

Expand Down
9 changes: 7 additions & 2 deletions src/features/pool/components/PoolCard/PoolDataCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ export function PoolDataCard({ data }: PoolDataCardProps) {
const { name, icon } = getChainInfo(data.chainId);
return (
<div
onClick={data.onClick}
className="inline-flex h-60 w-full items-center justify-between rounded-2xl border border-grey-100 p-6"
onClick={() =>
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"
>
<div className="flex items-center justify-start gap-6">
<img className="relative size-48 rounded-2xl" src={data.logoImg} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand All @@ -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",
Expand All @@ -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),
},
];

Expand Down
4 changes: 2 additions & 2 deletions src/features/pool/components/PoolCardGroup/PoolCardGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const justifyVariants = tv({
export const PoolCardGroup = ({ pools, justify }: PoolCardGroupProps) => {
return (
<div className={justifyVariants({ justify })}>
{pools.map((stat, index) => (
<PoolCard key={index} {...stat} />
{pools.map((pool, index) => (
<PoolCard key={index} {...pool} />
))}
</div>
);
Expand Down
67 changes: 67 additions & 0 deletions src/features/pool/components/PoolList/PoolList.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof PoolList> = {
title: "Features/Pool/PoolList",
component: PoolList,
};

export default meta;
type Story = StoryObj<typeof PoolList>;

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: [],
},
};
76 changes: 76 additions & 0 deletions src/features/pool/components/PoolList/PoolList.tsx
Original file line number Diff line number Diff line change
@@ -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<Record<string, string[]>>({});
const [selectedFilters, setSelectedFilters] = useState<Record<string, string[]>>({});

const { orderOptions, filterOptions } = getSortFilterOptions(pools);

const handleFilterChange = (values: Record<string, string[]>) => {
setSelectedFilters(values);
};

const handleOrderChange = (values: Record<string, string[]>) => {
setOrder(values);
};

const filteredAndOrderedPools = useFilteredAndOrderedPools({ pools, order, selectedFilters });

return (
<div className="flex w-full flex-col gap-4">
<div className="flex w-full items-center justify-between">
<div className="flex-1 text-start font-ui-sans text-2xl font-medium">
{`${title} (${pools.length})`}
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2">
<div className="text-nowrap font-ui-sans text-body font-medium">Order by</div>
<MultipleSelect
options={orderOptions}
onChange={handleOrderChange}
defaultValue={{ "ORDER BY TIME": ["Recent"] }}
className="w-40"
variants={{ triggerTextColor: "green", itemsPosition: "end", headerPosition: "end" }}
/>
</div>
<div className="flex items-center gap-2">
<div className="text-nowrap font-ui-sans text-body font-medium">Filter by</div>
<MultipleSelect
options={filterOptions}
onChange={handleFilterChange}
defaultValue={{ ungrouped: ["All"] }} // so it starts with 'All' selected
className="w-64"
variants={{ triggerTextColor: "red" }}
/>
</div>
</div>
</div>
<div className="w-full">
{filteredAndOrderedPools.length > 0 ? (
<PoolCardGroup pools={filteredAndOrderedPools} />
) : (
<div className="font-ui-sans text-lg">{noPoolsPlaceholder}</div>
)}
</div>
</div>
);
};
1 change: 1 addition & 0 deletions src/features/pool/components/PoolList/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useFilteredAndOrderedPools";
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useMemo } from "react";

import { PoolData, PoolStatus } from "@/types/pool";

interface UseFilteredAndOrderedPoolsProps {
pools: PoolData[];
selectedFilters: Record<string, string[]>;
order: Record<string, string[]>;
}

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]);
};
3 changes: 3 additions & 0 deletions src/features/pool/components/PoolList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./PoolList";
export * from "./utils";
export * from "./hooks";
Loading

0 comments on commit 9b95216

Please sign in to comment.