Skip to content
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 20 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
41bbee5
Dev: show portal projects in bridge
dewanshparashar Oct 3, 2024
ecc59c3
Merge branch 'master' of github.com:OffchainLabs/arbitrum-token-bridg…
dewanshparashar Oct 7, 2024
9af546b
Dev: added analytics
dewanshparashar Oct 7, 2024
fabba35
Dev: randomize list and hide projects on network switch
dewanshparashar Oct 7, 2024
b89c14f
Dev: updated endpoint
dewanshparashar Oct 7, 2024
5c5c221
Merge branch 'master' of github.com:OffchainLabs/arbitrum-token-bridg…
dewanshparashar Oct 8, 2024
3b25131
Dev: review comments
dewanshparashar Oct 8, 2024
7da4b4c
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar Oct 8, 2024
41b76ca
Dev: handle test mode
dewanshparashar Oct 8, 2024
9e82f7d
Merge branch 'fs-609/portal-in-bridge' of github.com:OffchainLabs/arb…
dewanshparashar Oct 8, 2024
bf03da2
dev: added doscaimer
dewanshparashar Oct 8, 2024
4a925b2
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar Oct 8, 2024
2729e8e
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar Oct 9, 2024
38d855b
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar Oct 9, 2024
6b6146c
Dev: add cors headers
dewanshparashar Oct 9, 2024
4d9bc84
Merge branch 'master' into fs-609/portal-in-bridge
fionnachan Oct 9, 2024
b722128
conflicts
dewanshparashar Oct 10, 2024
4dbefbe
Merge branch 'fs-609/portal-in-bridge' of github.com:OffchainLabs/arb…
dewanshparashar Oct 10, 2024
44e1af4
Dev: remove access control header
dewanshparashar Oct 10, 2024
4300b09
Merge branch 'master' into fs-609/portal-in-bridge
dewanshparashar Oct 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/arb-token-bridge-ui/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ module.exports = {
distDir: 'build',
productionBrowserSourceMaps: true,
reactStrictMode: true,
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'portal.arbitrum.io'
}
]
},
async headers() {
return [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dayjs from 'dayjs'
import { useState, useMemo, useCallback } from 'react'
import { useState, useMemo, useCallback, useEffect } from 'react'
import Tippy from '@tippyjs/react'
import { constants, utils } from 'ethers'
import { useLatest } from 'react-use'
Expand All @@ -8,7 +8,7 @@ import { TransactionResponse } from '@ethersproject/providers'
import { twMerge } from 'tailwind-merge'

import { useAppState } from '../../state'
import { getNetworkName } from '../../util/networks'
import { getNetworkName, isNetwork } from '../../util/networks'
import {
TokenDepositCheckDialog,
TokenDepositCheckDialogType
Expand Down Expand Up @@ -75,6 +75,7 @@ import { ExternalLink } from '../common/ExternalLink'
import { isExperimentalFeatureEnabled } from '../../util'
import { useIsTransferAllowed } from './hooks/useIsTransferAllowed'
import { MoveFundsButton } from './MoveFundsButton'
import { ProjectsListing } from '../common/ProjectsListing'

const signerUndefinedError = 'Signer is undefined'
const transferNotAllowedError = 'Transfer not allowed'
Expand Down Expand Up @@ -180,8 +181,15 @@ export function TransferPanel() {

const { destinationAddressError } = useDestinationAddressError()

const [showProjectsListing, setShowProjectsListing] = useState(false)

const isBatchTransfer = isBatchTransferSupported && Number(amount2) > 0

useEffect(() => {
// hide Project listing when networks are changed
setShowProjectsListing(false)
}, [childChain.id, parentChain.id])

function closeWithResetTokenImportDialog() {
setTokenQueryParam(undefined)
setImportTokenModalStatus(ImportTokenModalStatus.CLOSED)
Expand Down Expand Up @@ -865,6 +873,11 @@ export function TransferPanel() {
setTransferring(false)
clearAmountInput()

// for custom orbit pages, show Projects' listing after transfer
if (isDepositMode && isNetwork(childChain.id).isOrbitChain) {
setShowProjectsListing(true)
}

await (sourceChainTransaction as TransactionResponse).wait()

// tx confirmed, update balances
Expand Down Expand Up @@ -1044,6 +1057,8 @@ export function TransferPanel() {
</Tippy>
)}
</div>

{showProjectsListing && <ProjectsListing />}
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Chain, useAccount } from 'wagmi'
import { useMedia } from 'react-use'

import { useAppState } from '../../state'
import { getExplorerUrl, isNetwork } from '../../util/networks'
import { getExplorerUrl } from '../../util/networks'
import { useDestinationAddressStore } from './AdvancedSettings'
import { ExternalLink } from '../common/ExternalLink'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Image from 'next/image'
Copy link
Contributor Author

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

import { ExternalLink } from './ExternalLink'

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
}: {
project: PortalProject
onClick?: () => void
}) => {
return (
<div className="h-full min-h-[150px] w-full overflow-hidden rounded-md border border-white/30 bg-dark hover:bg-dark-hover">
dewanshparashar marked this conversation as resolved.
Show resolved Hide resolved
<ExternalLink
className="relative flex h-full w-full flex-col gap-2 p-4 hover:opacity-100"
aria-label={`${project.title}`}
href={`https://portal.arbitrum.io?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 inline-block max-w-[70px] 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>
</div>
)
}
108 changes: 108 additions & 0 deletions packages/arb-token-bridge-ui/src/components/common/ProjectsListing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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'

const shuffleArray = (array: PortalProject[]) => {
return array.sort(() => Math.random() - 0.5)
}

const fetchProjects = async (chainId: number) => {
const isChainOrbit = isNetwork(chainId).isOrbitChain
const chainSlug = getChainQueryParamForChain(chainId)

if (!isChainOrbit || !chainSlug) {
return []
}

const response = await axios.get(
`${PORTAL_API_ENDPOINT}/api/projects?chains=${chainSlug}`
)

return response.data as PortalProject[]
}

export const ProjectsListing = () => {
const [{ destinationChain }] = useNetworks()

const isDestinationChainOrbit = isNetwork(destinationChain.id).isOrbitChain

const { color: destinationChainUIcolor } = getBridgeUiConfigForChain(
destinationChain.id
)

const destinationChainSlug = getChainQueryParamForChain(destinationChain.id)

const {
data: projects,
error,
isLoading
} = useSWRImmutable(['fetchProjects', destinationChain.id], () =>
fetchProjects(destinationChain.id)
)
fionnachan marked this conversation as resolved.
Show resolved Hide resolved

if (
isLoading ||
!isDestinationChainOrbit ||
!projects ||
projects.length === 0 ||
typeof error !== 'undefined'
) {
return null
}
fionnachan marked this conversation as resolved.
Show resolved Hide resolved

// Shuffle projects and limit to 4
const randomizedProjects = shuffleArray(projects).slice(0, 4)

return (
<div
className="flex flex-col gap-3 rounded-md border bg-dark p-4 text-white"
style={{
borderColor: destinationChainUIcolor
}}
>
<h2 className="text-lg">
Explore Apps on {getNetworkName(destinationChain.id)}
</h2>
<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}
onClick={() => {
trackEvent('Project Click', {
network: getNetworkName(destinationChain.id),
projectName: project.title
})
}}
/>
))}
</div>
{projects.length > 4 && (
<ExternalLink
href={`${PORTAL_API_ENDPOINT}/projects?chains=${destinationChainSlug}`}
className="flex w-min flex-nowrap items-center gap-2 self-end whitespace-nowrap rounded-sm border p-2 text-sm"
style={{
borderColor: destinationChainUIcolor,
backgroundColor: `${destinationChainUIcolor}66`
}}
onClick={() => {
trackEvent('Show All Projects Click', {
network: getNetworkName(destinationChain.id)
})
}}
>
See all
<ChevronRightIcon className="h-3 w-3" />
</ExternalLink>
)}
</div>
)
}
2 changes: 2 additions & 0 deletions packages/arb-token-bridge-ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ export const MULTICALL_TESTNET_ADDRESS =
export const ETHER_TOKEN_LOGO = '/images/EthereumLogoRound.svg'

export const ether = { name: 'Ether', symbol: 'ETH', decimals: 18 } as const

export const PORTAL_API_ENDPOINT = 'https://portal.arbitrum.io'
7 changes: 7 additions & 0 deletions packages/arb-token-bridge-ui/src/util/AnalyticsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ type AnalyticsEventMap = {
complete: boolean
version: number
}
'Project Click': {
network: string
projectName: string
}
'Show All Projects Click': {
network: string
}
}

type AnalyticsEvent = keyof AnalyticsEventMap
Expand Down
1 change: 1 addition & 0 deletions packages/arb-token-bridge-ui/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ module.exports = {
'gray-dark': '#6D6D6D',
'line-gray': '#F4F4F4',
dark: '#1A1C1D', // (or default-black)
'dark-hover': '#2b2e30', // (or default-black-hover)

'bg-gray-1': '#191919',

Expand Down
Loading