-
Notifications
You must be signed in to change notification settings - Fork 203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: show projects in custom orbit pages #1961
Merged
Merged
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
41bbee5
Dev: show portal projects in bridge
dewanshparashar ecc59c3
Merge branch 'master' of github.com:OffchainLabs/arbitrum-token-bridg…
dewanshparashar 9af546b
Dev: added analytics
dewanshparashar fabba35
Dev: randomize list and hide projects on network switch
dewanshparashar b89c14f
Dev: updated endpoint
dewanshparashar 5c5c221
Merge branch 'master' of github.com:OffchainLabs/arbitrum-token-bridg…
dewanshparashar 3b25131
Dev: review comments
dewanshparashar 7da4b4c
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar 41b76ca
Dev: handle test mode
dewanshparashar 9e82f7d
Merge branch 'fs-609/portal-in-bridge' of github.com:OffchainLabs/arb…
dewanshparashar bf03da2
dev: added doscaimer
dewanshparashar 4a925b2
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar 2729e8e
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar 38d855b
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar 6b6146c
Dev: add cors headers
dewanshparashar 4d9bc84
Merge branch 'master' into fs-609/portal-in-bridge
fionnachan b722128
conflicts
dewanshparashar 4dbefbe
Merge branch 'fs-609/portal-in-bridge' of github.com:OffchainLabs/arb…
dewanshparashar 44e1af4
Dev: remove access control header
dewanshparashar 4300b09
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple implementation of Portal Project Cards