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

V4: Add SEO support #4556

Merged
merged 1 commit into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading