Skip to content

Commit

Permalink
Merge pull request #72
Browse files Browse the repository at this point in the history
refactor: setup grantee list component
  • Loading branch information
johnshift authored Aug 5, 2024
2 parents 78d87a4 + 5e18784 commit 3416a09
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 108 deletions.
9 changes: 3 additions & 6 deletions src/app/grants/[grantId]/@list/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import GranteeList from '@/grants/components/grantee-list';

import { fakeGrantee } from '@/grants/testutils/fake-grantee';
import { GranteeList } from '@/grants/components/grantee-list';

interface Props {
params: { grantId: string };
}

const ParallelGranteeList = ({}: Props) => {
// TODO: fetch grantees using grantId
const grantees = Array.from({ length: 12 }).map(() => fakeGrantee);
// TODO: React-Query SSR grantee list

return <GranteeList grantees={grantees} />;
return <GranteeList />;
};

export default ParallelGranteeList;
1 change: 0 additions & 1 deletion src/grants/components/grantee-item/index.ts

This file was deleted.

20 changes: 0 additions & 20 deletions src/grants/components/grantee-list.tsx

This file was deleted.

67 changes: 67 additions & 0 deletions src/grants/components/grantee-list/grantee-list.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Meta, StoryObj } from '@storybook/react';

import { MockInfiniteQueryResult } from '@/shared/testutils/misc';

import { mockGranteeListQuery } from '@/grants/testutils/mock-grantee-list-query';

import { GranteeList } from './grantee-list';

const meta: Meta<typeof GranteeList> = {
title: 'grants/components/grantee-list',
component: GranteeList,
args: {},
parameters: {
msw: {
handlers: [mockGranteeListQuery(MockInfiniteQueryResult.SUCCESS)],
},
},
};

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

export const Success: Story = {};

export const Loading: Story = {
parameters: {
msw: {
handlers: [
mockGranteeListQuery(MockInfiniteQueryResult.SUCCESS, {
networkDelay: 'infinite',
}),
],
},
},
};

export const Empty: Story = {
parameters: {
msw: {
handlers: [mockGranteeListQuery(MockInfiniteQueryResult.EMPTY)],
},
},
};

export const EndOfResults: Story = {
parameters: {
msw: {
handlers: [mockGranteeListQuery(MockInfiniteQueryResult.END_OF_RESULTS)],
},
},
};

export const NetworkError: Story = {
parameters: {
msw: {
handlers: [mockGranteeListQuery(MockInfiniteQueryResult.NETWORK_ERROR)],
},
},
};

export const FetchError: Story = {
parameters: {
msw: {
handlers: [mockGranteeListQuery(MockInfiniteQueryResult.FETCH_ERROR)],
},
},
};
47 changes: 47 additions & 0 deletions src/grants/components/grantee-list/grantee-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use client';

import { useParams } from 'next/navigation';
import { useMemo } from 'react';

import { VirtualWrapper } from '@/shared/components/virtual-wrapper';

import { GranteeListItem } from './item';
import { useGranteeList } from './use-grantee-list';

export const GranteeList = () => {
// TODO: JOB-681

const params = useParams();

const { grantees, error, inViewRef, hasNextPage, isPending } = useGranteeList(
params.grantId as string,
);

const lastItem = useMemo(() => {
if (error) return <p>Error: {error.message}</p>;

if (!hasNextPage) return <p>No more grantees available.</p>;

return <div ref={inViewRef}>Loading more...</div>;
}, [error, hasNextPage, inViewRef]);

if (isPending) return <p>Loading Grants ...</p>;

if (!grantees.length) {
return error ? <p>Error: {error.message}</p> : <p>No grantees found.</p>;
}

return (
<div className="flex flex-col gap-4">
<VirtualWrapper count={grantees.length}>
{(index) => (
<div className="pt-8">
<GranteeListItem grantee={grantees[index]} />
</div>
)}
</VirtualWrapper>

{lastItem}
</div>
);
};
1 change: 1 addition & 0 deletions src/grants/components/grantee-list/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './grantee-list';
1 change: 1 addition & 0 deletions src/grants/components/grantee-list/item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './item';
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { Meta, StoryObj } from '@storybook/react';

import { fakeGrantee } from '@/grants/testutils/fake-grantee';

import { GranteeItem } from './grantee-item';
import { GranteeListItem } from './item';

const meta: Meta<typeof GranteeItem> = {
title: 'grants/components/grantee-item',
component: GranteeItem,
const meta: Meta<typeof GranteeListItem> = {
title: 'grants/components/grantee-list-item',
component: GranteeListItem,
args: {
grantee: fakeGrantee,
},
};

export default meta;
type Story = StoryObj<typeof GranteeItem>;
type Story = StoryObj<typeof GranteeListItem>;

export const Default: Story = {};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface Props {
grantee: Grantee;
}

export const GranteeItem = ({ grantee }: Props) => {
export const GranteeListItem = ({ grantee }: Props) => {
// TODO: JOB-680

const { id, name, logo, category } = grantee;
Expand Down
24 changes: 24 additions & 0 deletions src/grants/components/grantee-list/use-grantee-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useInView } from 'react-intersection-observer';

import { useGranteeListQuery } from '@/grants/hooks/use-grantee-list-query';

export const useGranteeList = (grantId: string) => {
const { data, error, fetchNextPage, hasNextPage, isPending, isFetching } =
useGranteeListQuery(grantId);

// Next page fetch on scroll
const { ref: inViewRef } = useInView({
threshold: 1,
onChange: (inView) => {
if (inView && !error && !isFetching) fetchNextPage();
},
});

return {
grantees: data?.pages.flatMap((d) => d.data) ?? [],
error,
inViewRef,
hasNextPage,
isPending,
};
};
7 changes: 7 additions & 0 deletions src/grants/core/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { MW_URL } from '@/shared/core/envs';

export const GRANT_TEST_IDS = {
GRANT_ITEM: 'grant-item',
GRANT_CARD: 'grant-card',
GRANTEE_ITEM: 'grantee-item',
} as const;

export const GRANT_QUERY_URLS = {
GRANT_LIST: `${MW_URL}/grants/list`,
GRANTEE_LIST: `${MW_URL}/grantees/list`,
} as const;
10 changes: 9 additions & 1 deletion src/grants/core/query-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export const grantQueryKeys = {

return [...grantQueryKeys.all, 'list', searchParams] as const;
},
};
grantees: (grantId: string, params: string | Record<string, string>) => {
const searchParams =
typeof params === 'string'
? params
: new URLSearchParams(params).toString();

return [...grantQueryKeys.all, 'grantees', grantId, searchParams] as const;
},
} as const;

export type GrantQueryKeys = typeof grantQueryKeys;
6 changes: 6 additions & 0 deletions src/grants/core/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ export const grantListQueryPageSchema = z.object({
data: z.array(grantSchema),
});
export type GrantListQueryPage = z.infer<typeof grantListQueryPageSchema>;

export const granteeListQueryPageSchema = z.object({
page: z.number().optional(),
data: z.array(granteeSchema),
});
export type GranteeListQueryPage = z.infer<typeof granteeListQueryPageSchema>;
5 changes: 3 additions & 2 deletions src/grants/data/get-grant-list.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { MW_URL, PAGE_SIZE } from '@/shared/core/envs';
import { PAGE_SIZE } from '@/shared/core/envs';
import { createUrlWithSearchParams } from '@/shared/utils/create-url-with-search-params';
import { mwGET } from '@/shared/utils/mw-get';

import { GRANT_QUERY_URLS } from '@/grants/core/constants';
import { grantListQueryPageSchema } from '@/grants/core/schemas';

export const getGrantList = async (page: number, searchParams = '') => {
const url = createUrlWithSearchParams(
`${MW_URL}/grants/list?page=${page}&limit=${PAGE_SIZE}`,
`${GRANT_QUERY_URLS.GRANT_LIST}?page=${page}&limit=${PAGE_SIZE}`,
searchParams,
);

Expand Down
30 changes: 30 additions & 0 deletions src/grants/data/get-grantees-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { PAGE_SIZE } from '@/shared/core/envs';
import { createUrlWithSearchParams } from '@/shared/utils/create-url-with-search-params';
import { mwGET } from '@/shared/utils/mw-get';

import { GRANT_QUERY_URLS } from '@/grants/core/constants';
import { granteeListQueryPageSchema } from '@/grants/core/schemas';

interface Props {
page: number;
searchParams?: string;
grantId: string;
}

export const getGranteesList = async ({
page,
grantId,
searchParams = '',
}: Props) => {
const url = createUrlWithSearchParams(
`${GRANT_QUERY_URLS.GRANTEE_LIST}?page=${page}&limit=${PAGE_SIZE}&grantId=${grantId}`,
searchParams,
);

return mwGET({
url,
label: 'getGrantList',
responseSchema: granteeListQueryPageSchema,
options: { next: { revalidate: 60 * 60 } },
});
};
30 changes: 30 additions & 0 deletions src/grants/hooks/use-grantee-list-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query';

import { QUERY_STALETIME } from '@/shared/core/constants';

import { GrantQueryKeys, grantQueryKeys } from '@/grants/core/query-keys';
import { GranteeListQueryPage } from '@/grants/core/schemas';
import { getGranteesList } from '@/grants/data/get-grantees-list';

export const useGranteeListQuery = (grantId: string) => {
// TODO: filter search params string
const searchParams = '';

return useInfiniteQuery<
GranteeListQueryPage,
Error,
InfiniteData<GranteeListQueryPage, number>,
ReturnType<GrantQueryKeys['grantees']>,
number
>({
queryKey: grantQueryKeys.grantees(grantId, searchParams),
queryFn: async ({ pageParam }) =>
getGranteesList({ page: pageParam, grantId, searchParams }),
initialPageParam: 1,
getNextPageParam: ({ page, data }) =>
typeof page === 'number' && page > 0 && data.length > 0
? page + 1
: undefined,
staleTime: QUERY_STALETIME.DEFAULT,
});
};
2 changes: 2 additions & 0 deletions src/grants/pages/grant-list-page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { GrantList } from '@/grants/components/grant-list/grant-list';

export const GrantListPage = () => {
// TODO: React-Query SSR grant list

return (
<div className="p-8">
<GrantList />
Expand Down
Loading

0 comments on commit 3416a09

Please sign in to comment.