Skip to content

Commit

Permalink
V4: Add SEO support
Browse files Browse the repository at this point in the history
  • Loading branch information
wraeth-eth committed Dec 6, 2024
1 parent 99604b9 commit 1fbd1d2
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 279 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@
"tiny-invariant": "^1.2.0",
"use-deep-compare-effect": "^1.6.1",
"uuid": "^8.3.2",
"viem": "2.12.0",
"wagmi": "^2.12.5",
"viem": "^2.12.0",
"wagmi": "^2.10.2",
"yarn": "^1.22.22",
"yup": "^1.4.0"
},
Expand Down
52 changes: 52 additions & 0 deletions src/components/ProjectPageSEO.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { SiteBaseUrl } from 'constants/url'
import { ProjectMetadata } from 'models/projectMetadata'
import { cidFromUrl, ipfsPublicGatewayUrl } from 'utils/ipfs'
import { stripHtmlTags } from 'utils/string'
import { SEO } from './common/SEO/SEO'

const ProjectPageSEO: React.FC<{
metadata?: ProjectMetadata
url: string
}> = ({ metadata, url }) => (
<SEO
// Set known values, leave others undefined to be overridden
title={metadata?.name}
url={url}
description={
metadata?.projectTagline
? metadata.projectTagline
: metadata?.description
? stripHtmlTags(metadata.description)
: undefined
}
twitter={{
card: 'summary',
creator: metadata?.twitter,
handle: metadata?.twitter,
// Swap out all gateways with ipfs.io public gateway until we can resolve our meta tag issue.
image: metadata?.logoUri
? ipfsPublicGatewayUrl(cidFromUrl(metadata.logoUri))
: undefined,
}}
/>
)

export const V2V3ProjectSEO: React.FC<{
metadata?: ProjectMetadata
projectId: number
}> = ({ metadata, projectId }) => (
<ProjectPageSEO metadata={metadata} url={`${SiteBaseUrl}v2/p/${projectId}`} />
)

export const V4ProjectSEO: React.FC<{
metadata?: ProjectMetadata
chainName: string
projectId: number
}> = ({ metadata, chainName, projectId }) => {
return (
<ProjectPageSEO
metadata={metadata}
url={`${SiteBaseUrl}v4/${chainName}/p/${projectId}`}
/>
)
}
38 changes: 2 additions & 36 deletions src/pages/v2/p/[projectId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import { AppWrapper } from 'components/common/CoreAppWrapper/CoreAppWrapper'
import { SEO } from 'components/common/SEO/SEO'
import { V2V3ProjectSEO } from 'components/ProjectPageSEO'
import { PV_V2 } from 'constants/pv'
import { SiteBaseUrl } from 'constants/url'
import { AnnouncementsProvider } from 'contexts/Announcements/AnnouncementsProvider'
import { paginateDepleteProjectsQueryCall } from 'lib/apollo/paginateDepleteProjectsQuery'
import { loadCatalog } from 'locales/utils'
import { ProjectMetadata } from 'models/projectMetadata'
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next'
import { ProjectDashboard } from 'packages/v2v3/components/V2V3Project/ProjectDashboard/ProjectDashboard'
import { V2V3ProjectPageProvider } from 'packages/v2v3/contexts/V2V3ProjectPageProvider'
import React, { PropsWithChildren } from 'react'
import { cidFromUrl, ipfsPublicGatewayUrl } from 'utils/ipfs'
import {
ProjectPageProps,
getProjectStaticProps,
} from 'utils/server/pages/props'
import { stripHtmlTags } from 'utils/string'

export const getStaticPaths: GetStaticPaths = async () => {
if (process.env.BUILD_CACHE_V2_PROJECTS === 'true') {
Expand Down Expand Up @@ -58,43 +54,13 @@ export const getStaticProps: GetStaticProps<
}
}

const ProjectPageSEO = ({
metadata,
projectId,
}: {
metadata?: ProjectMetadata
projectId: number
}) => (
<SEO
// Set known values, leave others undefined to be overridden
title={metadata?.name}
url={`${SiteBaseUrl}v2/p/${projectId}`}
description={
metadata?.projectTagline
? metadata.projectTagline
: metadata?.description
? stripHtmlTags(metadata.description)
: undefined
}
twitter={{
card: 'summary',
creator: metadata?.twitter,
handle: metadata?.twitter,
// Swap out all gateways with ipfs.io public gateway until we can resolve our meta tag issue.
image: metadata?.logoUri
? ipfsPublicGatewayUrl(cidFromUrl(metadata.logoUri))
: undefined,
}}
/>
)

export default function V2V3ProjectPage({
metadata,
projectId,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<>
<ProjectPageSEO metadata={metadata} projectId={projectId} />
<V2V3ProjectSEO metadata={metadata} projectId={projectId} />

<_Wrapper>
<AppWrapper>
Expand Down
88 changes: 74 additions & 14 deletions src/pages/v4/[chainName]/p/[projectId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,75 @@
import { V4ProjectSEO } from 'components/ProjectPageSEO'
import { AppWrapper } from 'components/common/CoreAppWrapper/CoreAppWrapper'
import { FEATURE_FLAGS } from 'constants/featureFlags'
import { OPEN_IPFS_GATEWAY_HOSTNAME } from 'constants/ipfs'
import { PV_V4 } from 'constants/pv'
import { JBChainId, JBProjectProvider } from 'juice-sdk-react'
import { useRouter } from 'next/router'
import { loadCatalog } from 'locales/utils'
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next'
import { ReduxProjectCartProvider } from 'packages/v4/components/ProjectDashboard/ReduxProjectCartProvider'
import store from 'packages/v4/components/ProjectDashboard/redux/store'
import { V4NftRewardsProvider } from 'packages/v4/contexts/V4NftRewards/V4NftRewardsProvider'
import V4ProjectMetadataProvider from 'packages/v4/contexts/V4ProjectMetadataProvider'
import { V4UserNftCreditsProvider } from 'packages/v4/contexts/V4UserNftCreditsProvider'
import { V4UserTotalTokensBalanceProvider } from 'packages/v4/contexts/V4UserTotalTokensBalanceProvider'
import { useCurrentRouteChainId } from 'packages/v4/hooks/useCurrentRouteChainId'
import { chainNameMap } from 'packages/v4/utils/networks'
import { V4ProjectDashboard } from 'packages/v4/views/V4ProjectDashboard/V4ProjectDashboard'
import { wagmiConfig } from 'packages/v4/wagmiConfig'
import React, { PropsWithChildren } from 'react'
import { Provider } from 'react-redux'
import { featureFlagEnabled } from 'utils/featureFlags'
import globalGetServerSideProps from 'utils/next-server/globalGetServerSideProps'
import {
getProjectStaticProps,
ProjectPageProps,
} from 'utils/server/pages/props'
import { WagmiProvider } from 'wagmi'

export const getServerSideProps = globalGetServerSideProps
export const getStaticPaths: GetStaticPaths = async () => {
// TODO: static paths is convoluted with chains, needs some thought
// if (process.env.BUILD_CACHE_V4_PROJECTS === 'true') {
// const projects = await paginateDepleteProjectsQueryCall({
// variables: { where: { pv: PV_V4 } },
// })
// const paths = projects.map(({ projectId }) => ({
// params: { projectId: String(projectId) },
// }))
// return { paths, fallback: true }
// }

// TODO: We are switching to blocking as blocking fallback as its just not
// working. Need to investigate further
return {
paths: [],
fallback: 'blocking',
}
}

export const getStaticProps: GetStaticProps<
ProjectPageProps & { i18n: unknown }
> = async context => {
const locale = context.locale as string
const messages = await loadCatalog(locale)
const i18n = { locale, messages }

if (!context.params) throw new Error('params not supplied')

const projectId = parseInt(context.params.projectId as string)
const chainName = context.params.chainName as string
const props = (await getProjectStaticProps(
projectId,
PV_V4,
chainName,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
)) as any
if (props?.props) {
props.props.i18n = i18n
}

return {
...props,
revalidate: 10, // 10 seconds https://nextjs.org/docs/api-reference/data-fetching/get-static-props#revalidate
}
}

// This is a hack to avoid SSR for now. At the moment when this is not applied to this page, you will see a rehydration error.
const _Wrapper: React.FC<PropsWithChildren> = ({ children }) => {
Expand All @@ -42,20 +93,29 @@ const _Wrapper: React.FC<PropsWithChildren> = ({ children }) => {
return <>{children}</>
}

export default function V4ProjectPage() {
const router = useRouter()
const { projectId } = router.query
const chainId = useCurrentRouteChainId()
if (!chainId || !projectId) {
export default function V4ProjectPage({
metadata,
projectId,
chainName,
}: InferGetStaticPropsType<typeof getStaticProps>) {
if (!chainName || !projectId) {
return <div>Invalid URL</div>
}
const chainId = chainNameMap[chainName]

return (
<_Wrapper>
<Providers chainId={chainId} projectId={BigInt(projectId as string)}>
<V4ProjectDashboard />
</Providers>
</_Wrapper>
<>
<V4ProjectSEO
metadata={metadata}
chainName={chainName}
projectId={projectId}
/>
<_Wrapper>
<Providers chainId={chainId} projectId={BigInt(projectId)}>
<V4ProjectDashboard />
</Providers>
</_Wrapper>
</>
)
}

Expand Down
59 changes: 48 additions & 11 deletions src/utils/server/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { getPublicClient } from '@wagmi/core'
import { CV_V3 } from 'constants/cv'
import { JUICEBOX_MONEY_PROJECT_METADATA_DOMAIN } from 'constants/metadataDomain'
import { readNetwork } from 'constants/networks'
import { PV_V1, PV_V2, PV_V4 } from 'constants/pv'
import { readProvider } from 'constants/readProvider'
import {
readJbDirectoryControllerOf,
getProjectMetadata as sdkGetProjectMetadata,
} from 'juice-sdk-core'
import { PV } from 'models/pv'
import { V2V3ContractName } from 'packages/v2v3/models/contracts'
import { loadV2V3Contract } from 'packages/v2v3/utils/loadV2V3Contract'
import { chainNameMap } from 'packages/v4/utils/networks'
import { wagmiConfig } from 'packages/v4/wagmiConfig'
import { PublicClient } from 'viem'
import { findProjectMetadata } from './ipfs'

export const getProjectMetadata = async (projectId: string | number) => {
export const getProjectMetadata = async (
projectId: string | number,
pv: PV = PV_V2,
chain?: string | undefined,
) => {
if (typeof projectId === 'string') {
projectId = Number(projectId)
}
if (isNaN(projectId)) return undefined

const metadataCid = await getMetadataCidFromContract(projectId)
return await findProjectMetadata({ metadataCid })
switch (pv) {
case PV_V1:
throw new Error('V1 projects are not supported')
case PV_V2:
const metadataCid = await V2V3GetMetadataCidFromContract(projectId)
return findProjectMetadata({ metadataCid })
case PV_V4:
if (!chain) throw new Error('Chain is required for V4 projects')
return await V4GetMetadataCidFromContract(projectId, chain)
}
}

async function loadJBProjects() {
const contract = await loadV2V3Contract(
const V2V3GetMetadataCidFromContract = async (projectId: number) => {
const JBProjects = await loadV2V3Contract(
V2V3ContractName.JBProjects,
readNetwork.name,
readProvider,
CV_V3, // Note: v2 and v3 use the same JBProjects, so the CV doesn't matter.
)

return contract
}

const getMetadataCidFromContract = async (projectId: number) => {
const JBProjects = await loadJBProjects()
if (!JBProjects) {
throw new Error(`contract not found ${V2V3ContractName.JBProjects}`)
}
Expand All @@ -39,3 +56,23 @@ const getMetadataCidFromContract = async (projectId: number) => {

return metadataCid
}

const V4GetMetadataCidFromContract = async (
projectId: number,
chainName: string,
) => {
const chainId = chainNameMap[chainName]
const jbControllerAddress = await readJbDirectoryControllerOf(wagmiConfig, {
chainId,
args: [BigInt(projectId)],
})
const client = getPublicClient(wagmiConfig, {
chainId,
}) as PublicClient
const metadata = await sdkGetProjectMetadata(client, {
jbControllerAddress,
projectId: BigInt(projectId),
})

return metadata
}
8 changes: 7 additions & 1 deletion src/utils/server/pages/props.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { PV_V2 } from 'constants/pv'
import { ProjectMetadata } from 'models/projectMetadata'
import { PV } from 'models/pv'
import { GetStaticPropsResult } from 'next'
import { getProjectMetadata } from '../metadata'

export interface ProjectPageProps {
metadata?: ProjectMetadata
projectId: number
chainName?: string
}

export async function getProjectStaticProps(
projectId: number,
pv: PV = PV_V2,
chainName?: string | undefined,
): Promise<GetStaticPropsResult<ProjectPageProps>> {
try {
const metadata = await getProjectMetadata(projectId)
const metadata = await getProjectMetadata(projectId, pv, chainName)
if (!metadata) {
return { notFound: true }
}
Expand All @@ -20,6 +25,7 @@ export async function getProjectStaticProps(
props: {
metadata,
projectId,
chainName,
},
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
Loading

0 comments on commit 1fbd1d2

Please sign in to comment.