-
Notifications
You must be signed in to change notification settings - Fork 202
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: show projects in custom orbit pages (#1961)
- Loading branch information
1 parent
ac26607
commit ea0ab15
Showing
8 changed files
with
285 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
packages/arb-token-bridge-ui/src/components/common/PortalProject.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import Image from 'next/image' | ||
import { ExternalLink } from './ExternalLink' | ||
import { PORTAL_API_ENDPOINT } from '../../constants' | ||
|
||
export type PortalProject = { | ||
chains: string[] | ||
description: string | ||
id: string | ||
images: { logoUrl: string; bannerUrl: string } | ||
subcategories: { id: string; title: string }[] | ||
title: string | ||
url: string | ||
} | ||
|
||
export const Project = ({ | ||
project, | ||
onClick, | ||
isTestnetMode | ||
}: { | ||
project: PortalProject | ||
onClick?: () => void | ||
isTestnetMode: boolean | ||
}) => { | ||
return ( | ||
<ExternalLink | ||
className="relative flex h-full min-h-[150px] w-full flex-col gap-2 overflow-hidden rounded-md border border-white/30 bg-dark p-4 hover:bg-dark-hover hover:opacity-100" | ||
aria-label={`${project.title}`} | ||
href={ | ||
isTestnetMode | ||
? PORTAL_API_ENDPOINT | ||
: `${PORTAL_API_ENDPOINT}?project=${project.id}` | ||
} | ||
onClick={onClick} | ||
> | ||
{/* Normal project contents */} | ||
<div className="flex w-full flex-row gap-1"> | ||
{/* Logos */} | ||
<div className="flex shrink-0 grow-0 flex-col gap-2 overflow-hidden bg-cover bg-center"> | ||
{/* Project logo */} | ||
<div className="relative flex h-[50px] w-[50px] items-center justify-center overflow-hidden rounded-md bg-white p-[1px]"> | ||
<div className="[&:hover_span]:opacity-100"> | ||
<Image | ||
alt={`${project.title} logo`} | ||
src={project.images.logoUrl} | ||
width={50} | ||
height={50} | ||
className="rounded-md" | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
{/* Content */} | ||
<div className="relative grow text-left"> | ||
<div className="flex flex-col gap-1 px-2"> | ||
<h5 className="relative flex items-center gap-2 text-left text-lg font-semibold leading-7"> | ||
{project.title} | ||
</h5> | ||
<p className="mb-2 line-clamp-3 text-sm opacity-70"> | ||
{project.description} | ||
</p> | ||
|
||
<p className="flex flex-wrap justify-start gap-2 text-center leading-6 text-gray-700"> | ||
{project.subcategories.slice(0, 2).map(subcategory => ( | ||
<span | ||
key={subcategory.id} | ||
className="inline-flex items-start justify-start gap-2 break-words rounded bg-black px-1.5 py-0.5 text-xs font-normal text-white/60" | ||
> | ||
{subcategory.title.replaceAll('/', ' / ')} | ||
</span> | ||
))} | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
</ExternalLink> | ||
) | ||
} |
170 changes: 170 additions & 0 deletions
170
packages/arb-token-bridge-ui/src/components/common/ProjectsListing.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { useMemo } from 'react' | ||
import axios from 'axios' | ||
import useSWRImmutable from 'swr/immutable' | ||
import { ChevronRightIcon } from '@heroicons/react/24/outline' | ||
import { useNetworks } from '../../hooks/useNetworks' | ||
import { getNetworkName, isNetwork } from '../../util/networks' | ||
import { PortalProject, Project } from './PortalProject' | ||
import { PORTAL_API_ENDPOINT } from '../../constants' | ||
import { ExternalLink } from './ExternalLink' | ||
import { getBridgeUiConfigForChain } from '../../util/bridgeUiConfig' | ||
import { getChainQueryParamForChain } from '../../types/ChainQueryParam' | ||
import { trackEvent } from '../../util/AnalyticsUtils' | ||
import { useIsTestnetMode } from '../../hooks/useIsTestnetMode' | ||
import { isTestingEnvironment } from '../../util/CommonUtils' | ||
|
||
const shuffleArray = (array: PortalProject[]) => { | ||
return array.sort(() => Math.random() - 0.5) | ||
} | ||
|
||
const generateTestnetProjects = ( | ||
chainId: number, | ||
count: number | ||
): PortalProject[] => { | ||
const { | ||
network: { name: chainName, logo: chainImage } | ||
} = getBridgeUiConfigForChain(chainId) | ||
|
||
return [...Array(count)].map((_, key) => ({ | ||
chains: [chainName], | ||
description: `This is a featured project deployed on ${chainName}.`, | ||
id: `project_${key}`, | ||
images: { | ||
logoUrl: chainImage, | ||
bannerUrl: chainImage | ||
}, | ||
subcategories: [ | ||
{ id: 'defi', title: 'Defi' }, | ||
{ id: 'nfts', title: 'NFTs' } | ||
], | ||
title: `Featured Project ${key + 1}`, | ||
url: PORTAL_API_ENDPOINT | ||
})) | ||
} | ||
|
||
const fetchProjects = async ( | ||
chainId: number, | ||
isTestnetMode: boolean | ||
): Promise<PortalProject[]> => { | ||
const isChainOrbit = isNetwork(chainId).isOrbitChain | ||
const chainSlug = getChainQueryParamForChain(chainId) | ||
|
||
if (!isChainOrbit || !chainSlug) { | ||
return [] | ||
} | ||
|
||
if (isTestnetMode) { | ||
return isTestingEnvironment ? generateTestnetProjects(chainId, 6) : [] // don't show any test projects in production | ||
} | ||
|
||
try { | ||
const response = await axios.get( | ||
`${PORTAL_API_ENDPOINT}/api/projects?chains=${chainSlug}` | ||
) | ||
return response.data as PortalProject[] | ||
} catch (error) { | ||
console.warn('Error fetching projects:', error) | ||
return [] | ||
} | ||
} | ||
|
||
export const ProjectsListing = () => { | ||
const [{ destinationChain }] = useNetworks() | ||
const [isTestnetMode] = useIsTestnetMode() | ||
|
||
const isDestinationChainOrbit = isNetwork(destinationChain.id).isOrbitChain | ||
const { color: destinationChainUIcolor } = getBridgeUiConfigForChain( | ||
destinationChain.id | ||
) | ||
const destinationChainSlug = getChainQueryParamForChain(destinationChain.id) | ||
|
||
const { | ||
data: projects, | ||
error, | ||
isLoading | ||
} = useSWRImmutable( | ||
isDestinationChainOrbit | ||
? [destinationChain.id, isTestnetMode, 'fetchProjects'] | ||
: null, | ||
([destinationChainId, isTestnetMode]) => | ||
fetchProjects(destinationChainId, isTestnetMode) | ||
) | ||
|
||
// Shuffle projects and limit to 4 | ||
const randomizedProjects = useMemo( | ||
() => (projects ? shuffleArray(projects).slice(0, 4) : []), | ||
[projects] | ||
) | ||
|
||
if ( | ||
isLoading || | ||
!projects || | ||
projects.length === 0 || | ||
typeof error !== 'undefined' | ||
) { | ||
return null | ||
} | ||
|
||
return ( | ||
<div | ||
className="flex flex-col gap-3 border-y bg-dark p-4 text-white sm:rounded-md sm:border" | ||
style={{ | ||
borderColor: destinationChainUIcolor | ||
}} | ||
> | ||
<h2 className="text-lg"> | ||
Explore Apps on {getNetworkName(destinationChain.id)} | ||
</h2> | ||
|
||
{isTestnetMode && ( | ||
<div className="text-xs text-white/70"> | ||
<b>Development-mode only</b>. These are placeholder projects for | ||
showing how this feature works in non-production mode. Real projects | ||
are fetched from the Portal for mainnet Orbit chains. | ||
</div> | ||
)} | ||
|
||
<div className="grid gap-3 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2"> | ||
{randomizedProjects.map(project => ( | ||
<Project | ||
key={project.id} | ||
project={project} | ||
isTestnetMode={isTestnetMode} | ||
onClick={() => { | ||
if (isTestnetMode) return | ||
|
||
trackEvent('Project Click', { | ||
network: getNetworkName(destinationChain.id), | ||
projectName: project.title | ||
}) | ||
}} | ||
/> | ||
))} | ||
</div> | ||
{projects.length > 4 && ( | ||
<ExternalLink | ||
href={ | ||
isTestnetMode | ||
? PORTAL_API_ENDPOINT | ||
: `${PORTAL_API_ENDPOINT}/projects?chains=${destinationChainSlug}` | ||
} | ||
className="flex w-min flex-nowrap items-center gap-2 self-end whitespace-nowrap rounded-md border p-2 text-sm" | ||
style={{ | ||
borderColor: destinationChainUIcolor, | ||
backgroundColor: `${destinationChainUIcolor}66` | ||
}} | ||
onClick={() => { | ||
if (isTestnetMode) return | ||
|
||
trackEvent('Show All Projects Click', { | ||
network: getNetworkName(destinationChain.id) | ||
}) | ||
}} | ||
> | ||
See all | ||
<ChevronRightIcon className="h-3 w-3" /> | ||
</ExternalLink> | ||
)} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters