From b28dd3df7006ab1c9e8ba652329a36a8d4b5272e Mon Sep 17 00:00:00 2001 From: John Ballesteros <89079900+johnshift@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:25:05 +0800 Subject: [PATCH] Add project-card component (#145) * refactor: add project-card component * fix: typo --- src/projects/atoms/active-project-id-atom.ts | 3 + .../init-project-details-syncer.tsx | 38 ++++++ .../components/project-card/index.tsx | 52 ++++++++ src/projects/components/project-list.tsx | 9 +- src/projects/core/constants.ts | 3 + src/projects/utils/create-project-tags.tsx | 111 ++++++++++++++++++ 6 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 src/projects/atoms/active-project-id-atom.ts create mode 100644 src/projects/components/init-project-details-syncer.tsx create mode 100644 src/projects/components/project-card/index.tsx create mode 100644 src/projects/core/constants.ts create mode 100644 src/projects/utils/create-project-tags.tsx diff --git a/src/projects/atoms/active-project-id-atom.ts b/src/projects/atoms/active-project-id-atom.ts new file mode 100644 index 00000000..296e5285 --- /dev/null +++ b/src/projects/atoms/active-project-id-atom.ts @@ -0,0 +1,3 @@ +import { atom } from 'jotai'; + +export const activeProjectIdAtom = atom(null); diff --git a/src/projects/components/init-project-details-syncer.tsx b/src/projects/components/init-project-details-syncer.tsx new file mode 100644 index 00000000..f5ba58e0 --- /dev/null +++ b/src/projects/components/init-project-details-syncer.tsx @@ -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; +}; diff --git a/src/projects/components/project-card/index.tsx b/src/projects/components/project-card/index.tsx new file mode 100644 index 00000000..aec1ee32 --- /dev/null +++ b/src/projects/components/project-card/index.tsx @@ -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 ( + + + + + {upperTags.length > 0 && } + + + {midTags.length > 0 && } + + + {chains.length > 0 && } + + + + ); +}; diff --git a/src/projects/components/project-list.tsx b/src/projects/components/project-list.tsx index 39bf48c5..c468cc3c 100644 --- a/src/projects/components/project-list.tsx +++ b/src/projects/components/project-list.tsx @@ -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, @@ -42,9 +44,10 @@ export const ProjectList = () => { {(index) => (
0 })}> -
-
{JSON.stringify(projects[index], undefined, '\t')}
-
+
)}
diff --git a/src/projects/core/constants.ts b/src/projects/core/constants.ts new file mode 100644 index 00000000..85f5966a --- /dev/null +++ b/src/projects/core/constants.ts @@ -0,0 +1,3 @@ +export const PROJECT_TEST_IDS = { + PROJECT_CARD: 'project-card', +} as const; diff --git a/src/projects/utils/create-project-tags.tsx b/src/projects/utils/create-project-tags.tsx new file mode 100644 index 00000000..248d9ef0 --- /dev/null +++ b/src/projects/utils/create-project-tags.tsx @@ -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: , + }); + } + + if (tvl) { + upperTags.push({ + text: `TVL: $${formatNumber(tvl)}`, + icon: , + }); + } + + if (monthlyVolume) { + upperTags.push({ + text: `Monthly Volume: $${formatNumber(monthlyVolume)}`, + icon: , + }); + } + + if (monthlyActiveUsers) { + upperTags.push({ + text: `Monthly Active Users: $${formatNumber(monthlyActiveUsers)}`, + icon: , + }); + } + + if (monthlyFees) { + upperTags.push({ + text: `Monthly Fees: $${formatNumber(monthlyFees)}`, + icon: , + }); + } + + if (monthlyRevenue) { + upperTags.push({ + text: `Monthly Revenue: $${formatNumber(monthlyRevenue)}`, + icon: , + }); + } + + if (isMainnet) { + upperTags.push({ + text: 'Mainnet', + icon: , + }); + } + + 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: , + 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: , + }); + } + } + + return { upperTags, midTags }; +};