Skip to content

Commit

Permalink
refactor: sync impact grantee interface
Browse files Browse the repository at this point in the history
  • Loading branch information
johnshift committed Feb 21, 2025
1 parent 7011a8f commit 1db6355
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 43 deletions.
6 changes: 3 additions & 3 deletions src/grants/components/grantee-card/grantee-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ export const GranteeCard = () => {
return <p>{`TODO: <GranteeCardErrorUI /> "${errorMessage}"`}</p>;
}

if (!granteeData?.data) {
if (!granteeData) {
return <p>Grantee not found.</p>;
}

const { logoUrl, name, website, description } = granteeData.data;
const { logoUrl, name, website, description } = granteeData;

return (
<div className={WRAPPER_CLASSNAME}>
Expand All @@ -85,7 +85,7 @@ export const GranteeCard = () => {

<div className="flex flex-col gap-2 md:border-t md:border-divider/25 md:pt-4">
<span className="text-13 text-white">Funding Details</span>
<GranteeFundingItems granteeItem={granteeData.data} />
<GranteeFundingItems granteeItem={granteeData} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const ProjectSelections = () => {
granteeId,
);

if (granteeData?.data?.projects.length === 0) return null;
if (granteeData?.projects.length === 0) return null;

if (isLoading) {
return (
Expand All @@ -32,14 +32,13 @@ export const ProjectSelections = () => {
}

// This component is stacked with others. Top most component renders the error.
if (errorMessage) return null;
if (!granteeData?.data) return null;
if (errorMessage || !granteeData) return null;

const baseHref = `/${ROUTE_SECTIONS.IMPACT}/${grantId}/grantees/${granteeData.data.slug}/projects`;
const baseHref = `/${ROUTE_SECTIONS.IMPACT}/${grantId}/grantees/${granteeData.slug}/projects`;

return (
<div className={WRAPPER_CLASSNAME}>
{granteeData.data.projects.map(({ id, name }, index) => (
{granteeData.projects.map(({ id, name }, index) => (
<ProjectSelection
key={id}
projectId={id}
Expand Down
8 changes: 4 additions & 4 deletions src/grants/components/project-stats/project-stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ export const GranteeProjectStats = () => {
);

// This component is stacked with others. Top most component renders the error.
if (granteeData?.data?.projects.length === 0) return null;
if (errorMessage || !granteeData?.data) return null;
if (granteeData?.projects.length === 0) return null;
if (errorMessage || !granteeData) return null;

const currentProject = projectId
? granteeData.data.projects.find((project) => project.id === projectId)
: granteeData.data.projects[0];
? granteeData.projects.find((project) => project.id === projectId)
: granteeData.projects[0];

if (!currentProject || currentProject.tabs.length === 0) return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ export const ProjectTabSelection = () => {
}

// This component is stacked with others. Top most component renders the error.
if (errorMessage || !granteeData?.data) return null;
if (errorMessage || !granteeData) return null;

const currentProject = projectId
? granteeData.data.projects.find((p) => p.id === projectId)
: granteeData.data.projects[0];
? granteeData.projects.find((p) => p.id === projectId)
: granteeData.projects[0];

if (!currentProject?.tabs?.length) return null;

const baseHref = `/${ROUTE_SECTIONS.IMPACT}/${grantId}/grantees/${granteeData.data.slug}/projects`;
const baseHref = `/${ROUTE_SECTIONS.IMPACT}/${grantId}/grantees/${granteeData.slug}/projects`;

const activeTab = tab || currentProject.tabs[0].tab;

Expand Down
18 changes: 15 additions & 3 deletions src/grants/components/ui/grantee-funding-items.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CoinsIcon } from 'lucide-react';

import { conditionalItem } from '@/shared/utils/conditional-item';
import { formatNumber } from '@/shared/utils/format-number';
import { shortTimestamp } from '@/shared/utils/short-timestamp';
Expand All @@ -11,21 +13,31 @@ import { DetailValueText } from './base/detail-value-text';

const createFundingItems = ({
lastFundingAmount,
lastFundingUnit,
lastFundingTokenAmount,
lastFundingTokenUnit,
lastFundingDate,
}: GranteeItem): DetailItemProps[] => [
...conditionalItem(!!lastFundingAmount, {
icon: <PaperbillIcon />,
label: 'Last Funding:',
value: (
<DetailValueText>{`${formatNumber(lastFundingAmount!)} ${lastFundingUnit}`}</DetailValueText>
<DetailValueText>{`$${formatNumber(lastFundingAmount!)}`}</DetailValueText>
),
}),
...conditionalItem(!!lastFundingTokenAmount && !!lastFundingTokenUnit, {
icon: <CoinsIcon size={18} />,
label: 'Token Amount:',
value: (
<DetailValueText>{`${formatNumber(lastFundingTokenAmount!)} ${lastFundingTokenUnit}`}</DetailValueText>
),
}),
...conditionalItem(!!lastFundingDate, {
icon: <BankIcon />,
label: 'Funding Date:',
value: (
<DetailValueText>{shortTimestamp(lastFundingDate!)}</DetailValueText>
<DetailValueText>
{shortTimestamp(lastFundingDate! / 1000)}
</DetailValueText>
),
}),
];
Expand Down
4 changes: 2 additions & 2 deletions src/grants/components/ui/grantee-logo-title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { ClassValue } from 'clsx';

import { cn } from '@/shared/utils/cn';

import { Grantee } from '@/grants/core/schemas';
import { GranteeDetails } from '@/grants/core/schemas';

const CLASSNAMES = {
WRAPPER: 'flex items-center',
INNER: 'md:pr-4',
AVATAR: 'mr-3 h-8 w-8 rounded-xl lg:h-16 lg:w-16 lg:rounded-[24px]',
};

interface Props extends Pick<Grantee, 'name' | 'logoUrl'> {
interface Props extends Pick<GranteeDetails, 'name' | 'logoUrl'> {
classNames?: {
root?: ClassValue;
logo?: ClassValue;
Expand Down
71 changes: 62 additions & 9 deletions src/grants/core/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,45 @@ export type GrantDtoInfiniteListPage = z.infer<
typeof grantDtoInfiniteListPageSchema
>;

// Grantee item matches what's returned from api. No need to differentiate as dto
export const fundingEventDtoSchema = z.object({
id: z.string(),
timestamp: z.number(),
amountInUsd: z.number().nullable(),
tokenAmount: z.number().nullable(),
tokenUnit: z.string().nullable(),
roundName: z.string().nullable(),
sourceLink: z.string().nullable(),
eventType: z.union([z.literal('funding'), z.literal('grant')]),
});
export type FundingEventDto = z.infer<typeof fundingEventDtoSchema>;

export const granteeItemDtoSchema = z.object({
id: z.string(),
name: z.string(),
website: z.string().nullable(),
slug: z.string(),
logoUrl: z.string().nullable(),
fundingEvents: z.array(fundingEventDtoSchema),
});
export type GranteeItemDto = z.infer<typeof granteeItemDtoSchema>;

export const granteeInfiniteListPageDto = z.object({
page: z.number().optional(),
data: z.array(granteeItemDtoSchema),
});
export type GranteeInfiniteListPageDto = z.infer<
typeof granteeInfiniteListPageDto
>;

export const granteeItemSchema = z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
logoUrl: z.string().nullable(),
lastFundingDate: z.number().nullable().optional(),
lastFundingAmount: z.number().optional(),
lastFundingUnit: z.string().optional(),
lastFundingDate: z.number().nullable(),
lastFundingAmount: z.number().nullable(),
lastFundingTokenAmount: z.number().nullable(),
lastFundingTokenUnit: z.string().nullable(),
});
export type GranteeItem = z.infer<typeof granteeItemSchema>;

Expand Down Expand Up @@ -148,16 +178,39 @@ export const granteeProjectSchema = z.object({
});
export type GranteeProject = z.infer<typeof granteeProjectSchema>;

export const granteeSchema = granteeItemSchema.extend({
export const granteeDtoProject = z.object({
id: z.string(),
name: z.string(),
tags: z.array(z.string()),
tabs: z.array(granteeTabItemSchema),
});
export type GranteeDtoProject = z.infer<typeof granteeDtoProject>;

export const granteeDto = granteeItemDtoSchema.extend({
status: z.union([
z.literal('PENDING'),
z.literal('APPROVED'),
z.literal('REJECTED'),
z.literal('CANCELLED'),
z.literal('IN_REVIEW'),
]),
description: z.string(),
projects: z.array(granteeDtoProject),
});
export type GranteeDto = z.infer<typeof granteeDto>;

export const granteeDetailsSchema = granteeItemSchema.extend({
tags: z.array(z.string()).optional(),
website: z.string().nullable(),
status: z.string(), // TODO: convert to literals
description: z.string(),
projects: z.array(granteeProjectSchema),
});
export type Grantee = z.infer<typeof granteeSchema>;
export type GranteeDetails = z.infer<typeof granteeDetailsSchema>;

export const granteeDtoSchema = genericResponseSchema.extend({
data: granteeSchema.optional(),
export const granteeDetailsResponseSchema = genericResponseSchema.extend({
data: granteeDto.optional(),
});
export type GranteeDto = z.infer<typeof granteeDtoSchema>;
export type GranteeDetailsResponse = z.infer<
typeof granteeDetailsResponseSchema
>;
21 changes: 17 additions & 4 deletions src/grants/data/get-grantee-details.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import { mwGET } from '@/shared/utils/mw-get';

import { grantQueryUrls } from '@/grants/core/query-urls';
import { granteeDtoSchema } from '@/grants/core/schemas';
import {
GranteeDetails,
granteeDetailsResponseSchema,
} from '@/grants/core/schemas';
import { dtoToGranteeDetails } from '@/grants/utils/dto-to-grantee-details';

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

export const getGranteeDetails = async (grantId: string, granteeId: string) => {
export const getGranteeDetails = async (
grantId: string,
granteeId: string,
): Promise<GranteeDetails> => {
// return {
// success: true,
// message: 'Grantee details fetched successfully',
// data: fakeGrantee(),
// };

return mwGET({
const response = await mwGET({
url: `${grantQueryUrls.grantee(grantId, granteeId)}`,
label: 'getGranteeDetails',
responseSchema: granteeDtoSchema,
responseSchema: granteeDetailsResponseSchema,
});

if (!response.success || !response.data) {
throw new Error(response.message);
}

return dtoToGranteeDetails(response.data);
};
12 changes: 9 additions & 3 deletions src/grants/data/get-grantee-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { mwGET } from '@/shared/utils/mw-get';
import { grantQueryUrls } from '@/grants/core/query-urls';
import {
GranteeInfiniteListPage,
granteeInfiniteListPageSchema,
granteeInfiniteListPageDto,
} from '@/grants/core/schemas';
import { dtoToGranteeItem } from '@/grants/utils/dto-to-grantee-item';

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

Expand Down Expand Up @@ -35,9 +36,14 @@ export const getGranteeList = async ({
searchParams,
);

return mwGET({
const response = await mwGET({
url,
label: 'getGranteeList',
responseSchema: granteeInfiniteListPageSchema,
responseSchema: granteeInfiniteListPageDto,
});

return {
...response,
data: response.data.map(dtoToGranteeItem),
};
};
12 changes: 8 additions & 4 deletions src/grants/testutils/fake-grantee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { faker } from '@faker-js/faker';

import { fakeNullable } from '@/shared/testutils/fake-nullable';

import { Grantee, GranteeItem } from '@/grants/core/schemas';
import { GranteeDetails, GranteeItem } from '@/grants/core/schemas';

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

faker.seed(69);

export const fakeGrantee = (partial: Partial<Grantee> = {}): Grantee => ({
export const fakeGrantee = (
partial: Partial<GranteeDetails> = {},
): GranteeDetails => ({
id: faker.string.uuid(),
slug: faker.internet.domainName(),
status: '',
Expand All @@ -18,7 +20,8 @@ export const fakeGrantee = (partial: Partial<Grantee> = {}): Grantee => ({
description: faker.lorem.paragraph({ min: 3, max: 8 }),
website: faker.internet.url(),
lastFundingAmount: faker.number.int({ min: 500_000, max: 200_000_000 }),
lastFundingUnit: 'USD',
lastFundingTokenAmount: faker.number.int({ min: 1000, max: 10_000_000 }),
lastFundingTokenUnit: 'ETH',
lastFundingDate: faker.date
.past({ years: faker.number.int({ min: 2, max: 4 }) })
.getTime(),
Expand All @@ -39,7 +42,8 @@ export const fakeGranteeItem = (
.past({ years: faker.number.int({ min: 2, max: 4 }) })
.getTime(),
lastFundingAmount: faker.number.int({ min: 500_000, max: 200_000_000 }),
lastFundingUnit: 'USD',
lastFundingTokenAmount: faker.number.int({ min: 1000, max: 10_000_000 }),
lastFundingTokenUnit: 'ETH',
...partial,
});

Expand Down
4 changes: 2 additions & 2 deletions src/grants/testutils/mock-grantee-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
} from '@/shared/testutils/misc';

import { grantQueryUrls } from '@/grants/core/query-urls';
import { Grantee } from '@/grants/core/schemas';
import { GranteeDetails } from '@/grants/core/schemas';

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

export const mockGranteeQuery = (
result: MockQueryResult,
options: MswOptions & { grantId: string; data?: Grantee },
options: MswOptions & { grantId: string; data?: GranteeDetails },
) =>
http.get(
`${grantQueryUrls.grantees(options.grantId)}/:granteeId`,
Expand Down
13 changes: 13 additions & 0 deletions src/grants/utils/dto-to-grantee-details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { GranteeDetails, GranteeDto } from '@/grants/core/schemas';
import { dtoToGranteeItem } from '@/grants/utils/dto-to-grantee-item';

export const dtoToGranteeDetails = (dto: GranteeDto): GranteeDetails => {
return {
...dtoToGranteeItem(dto),
tags: dto.projects.flatMap((project) => project.tags),
website: dto.website,
status: dto.status,
description: dto.description,
projects: dto.projects,
};
};
Loading

0 comments on commit 1db6355

Please sign in to comment.