Skip to content

Commit

Permalink
Add project-card component (#145)
Browse files Browse the repository at this point in the history
* refactor: add project-card component

* fix: typo
  • Loading branch information
johnshift authored Apr 7, 2024
1 parent a360f77 commit b28dd3d
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/projects/atoms/active-project-id-atom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { atom } from 'jotai';

export const activeProjectIdAtom = atom<string | null>(null);
38 changes: 38 additions & 0 deletions src/projects/components/init-project-details-syncer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useEffect } from 'react';

import { useAtom } from 'jotai';

import { useIsDesktop } from '~/shared/hooks/use-media-query';

import { activeProjectIdAtom } from '~/projects/atoms/active-project-id-atom';
import { initProjectAtom } from '~/projects/atoms/init-project-atom';
import { useProjectDetails } from '~/projects/hooks/use-project-details';

interface Props {
id: string;
}

export const InitProjectDetailsSyncer = ({ id }: Props) => {
const [activeId, setActiveId] = useAtom(activeProjectIdAtom);
const [initProject, setInitProject] = useAtom(initProjectAtom);

const isDesktop = useIsDesktop();

const { data } = useProjectDetails(id);

// Initialize project details
useEffect(() => {
if (!initProject && data) {
setInitProject(data);
}
}, [data, initProject, setInitProject]);

// Set active project ID on desktop
useEffect(() => {
if (isDesktop && !activeId && data) {
setActiveId(data.id);
}
}, [activeId, data, isDesktop, setActiveId]);

return null;
};
52 changes: 52 additions & 0 deletions src/projects/components/project-card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Link from 'next/link';

import { HREFS } from '~/shared/core/constants';
import { ProjectInfo } from '~/shared/core/schemas';
import { getLogoUrl } from '~/shared/utils/get-logo-url';
import { CardWrapper } from '~/shared/components/card-wrapper';
import { ChainsInfoTag } from '~/shared/components/chains-info-tag';
import { Divider } from '~/shared/components/divider';
import { InfoTags } from '~/shared/components/info-tags';
import { LogoTitle } from '~/shared/components/logo-title';

import { PROJECT_TEST_IDS } from '~/projects/core/constants';
import { createProjectTags } from '~/projects/utils/create-project-tags';
import { activeProjectIdAtom } from '~/projects/atoms/active-project-id-atom';

interface Props {
project: ProjectInfo;
isInit?: boolean;
filterParamsString?: string;
}

export const ProjectCard = ({ project, isInit, filterParamsString }: Props) => {
const { id, website, logo, name, chains } = project;

const src = getLogoUrl(website, logo);
const { upperTags, midTags } = createProjectTags(project);

return (
<CardWrapper id={id} idAtom={activeProjectIdAtom}>
<Link
href={`${HREFS.PROJECTS_PAGE}/${id}/details${filterParamsString}`}
scroll={false}
data-testid={PROJECT_TEST_IDS.PROJECT_CARD}
data-uuid={id}
data-is-init={isInit ?? undefined}
prefetch={true}
className="flex flex-col gap-3 p-6"
>
<LogoTitle src={src} name={name} />

{upperTags.length > 0 && <Divider />}
<InfoTags compact tags={upperTags} />

{midTags.length > 0 && <Divider />}
<InfoTags compact tags={midTags} />

{chains.length > 0 && <Divider />}
<ChainsInfoTag chains={chains} />
</Link>
</CardWrapper>
);
};
9 changes: 6 additions & 3 deletions src/projects/components/project-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { VirtualWrapper } from '~/shared/components/virtual-wrapper';
import { useProjectList } from '~/projects/hooks/use-project-list';
import { useFiltersContext } from '~/filters/providers/filters-provider/context';

import { ProjectCard } from './project-card';

export const ProjectList = () => {
const {
projects,
Expand Down Expand Up @@ -42,9 +44,10 @@ export const ProjectList = () => {
<VirtualWrapper count={projects.length}>
{(index) => (
<div className={cn({ 'pt-8': index > 0 })}>
<div className="flex flex-col bg-darkest-gray p-4">
<pre>{JSON.stringify(projects[index], undefined, '\t')}</pre>
</div>
<ProjectCard
project={projects[index]}
filterParamsString={filterParamsString}
/>
</div>
)}
</VirtualWrapper>
Expand Down
3 changes: 3 additions & 0 deletions src/projects/core/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const PROJECT_TEST_IDS = {
PROJECT_CARD: 'project-card',
} as const;
111 changes: 111 additions & 0 deletions src/projects/utils/create-project-tags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { ProjectInfo } from '~/shared/core/schemas';
import { InfoTagProps } from '~/shared/core/types';
import { formatNumber } from '~/shared/utils/format-number';
import { getPluralText } from '~/shared/utils/get-plural-text';
import { ActiveUsersIcon } from '~/shared/components/icons/active-users-icon';
import { AuditsIcon } from '~/shared/components/icons/audits-icon';
import { CategoryIcon } from '~/shared/components/icons/category-icon';
import { HacksIcon } from '~/shared/components/icons/hacks-icon';
import { MainnetIcon } from '~/shared/components/icons/mainnet-icon';
import { MonthlyVolumeIcon } from '~/shared/components/icons/monthly-volume-icon';
import { RevenueIcon } from '~/shared/components/icons/revenue-icon';
import { TvlIcon } from '~/shared/components/icons/tvl-icon';

export const createProjectTags = (project: ProjectInfo) => {
const upperTags: InfoTagProps[] = [];
const midTags: InfoTagProps[] = [];

const {
tvl,
monthlyVolume,
monthlyActiveUsers,
monthlyFees,
monthlyRevenue,
category,
isMainnet,
audits,
hacks,
} = project;

if (category) {
upperTags.push({
text: `Category: ${category}`,
icon: <CategoryIcon />,
});
}

if (tvl) {
upperTags.push({
text: `TVL: $${formatNumber(tvl)}`,
icon: <TvlIcon />,
});
}

if (monthlyVolume) {
upperTags.push({
text: `Monthly Volume: $${formatNumber(monthlyVolume)}`,
icon: <MonthlyVolumeIcon />,
});
}

if (monthlyActiveUsers) {
upperTags.push({
text: `Monthly Active Users: $${formatNumber(monthlyActiveUsers)}`,
icon: <ActiveUsersIcon />,
});
}

if (monthlyFees) {
upperTags.push({
text: `Monthly Fees: $${formatNumber(monthlyFees)}`,
icon: <MonthlyVolumeIcon />,
});
}

if (monthlyRevenue) {
upperTags.push({
text: `Monthly Revenue: $${formatNumber(monthlyRevenue)}`,
icon: <RevenueIcon />,
});
}

if (isMainnet) {
upperTags.push({
text: 'Mainnet',
icon: <MainnetIcon />,
});
}

if (audits.length > 0) {
for (const audit of audits) {
const issueCount = audit.techIssues ?? 0;
const title = audit.name;
midTags.push({
text: `${getPluralText('Audit', issueCount)}: ${title}${
issueCount
? ' (' + issueCount + ` ${getPluralText('issue', issueCount)})`
: ''
}`,
icon: <AuditsIcon />,
link: audit.link ?? undefined,
});
}
}

if (hacks.length > 0) {
for (const hack of hacks) {
const title =
hack.category && hack.category !== 'Other' ? hack.category : '';
const issueType = hack.issueType ?? 'Other';
const fundsLost = hack.fundsLost
? `($${formatNumber(hack.fundsLost)})`
: '';
midTags.push({
text: `Hack: ${title} ${issueType} ${fundsLost}`,
icon: <HacksIcon />,
});
}
}

return { upperTags, midTags };
};

0 comments on commit b28dd3d

Please sign in to comment.