Skip to content

Commit

Permalink
clients/issues: fix 404 on issue pages
Browse files Browse the repository at this point in the history
  • Loading branch information
emilwidlund committed Feb 7, 2025
1 parent 33cc68b commit f7cb8dc
Show file tree
Hide file tree
Showing 3 changed files with 289 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use client'

import WhiteCard from '@/components/Cards/WhiteCard'
import FAQ from '@/components/Pledge/FAQ'
import HowItWorks from '@/components/Pledge/HowItWorks'
import IssueCard from '@/components/Pledge/IssueCard'
import PledgeCheckoutPanel from '@/components/Pledge/PledgeCheckoutPanel'
import { usePostHog } from '@/hooks/posthog'
import { Issue, Organization, Pledger, RewardsSummary } from '@polar-sh/api'
import Banner from '@polar-sh/ui/components/molecules/Banner'
import { useEffect, useState } from 'react'

const ClientPage = ({
issue,
organization,
htmlBody,
pledgers,
gotoURL,
rewards,
}: {
issue: Issue
organization: Organization
htmlBody?: string
pledgers: Pledger[]
gotoURL?: string
rewards?: RewardsSummary
}) => {
const posthog = usePostHog()

const [amount, setAmount] = useState(0)
const onAmountChange = (amount: number) => {
setAmount(amount)
}

useEffect(() => {
if (issue) {
posthog.capture('storefront:issues:page:view', {
organization_id: organization.id,
organization_name: organization.slug,
repository_id: issue.repository.id,
repository_name: issue.repository.name,
issue_id: issue.id,
issue_number: issue.number,
})
}
}, [issue, organization])

return (
<>
{issue.repository.is_private && (
<div className="w-full">
<Banner color="muted">
This is an issue in a private repository. Only logged in users that
are members of {issue.repository.organization.name} can see it.
</Banner>
</div>
)}

<div className="grid w-full grid-cols-1 gap-12 lg:grid-cols-2">
{/* Left side */}
<div className="mt-12">
<IssueCard
issue={issue}
organization={organization}
htmlBody={htmlBody}
pledgers={pledgers}
currentPledgeAmount={amount}
rewards={rewards}
/>
</div>

{/* Right side */}
<div>
<WhiteCard padding>
<PledgeCheckoutPanel
issue={issue}
organization={organization}
gotoURL={gotoURL}
onAmountChange={onAmountChange}
/>
</WhiteCard>
</div>
</div>

<HowItWorks />
<FAQ />
</>
)
}

export default ClientPage
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { getServerSideAPI } from '@/utils/api/serverside'
import { resolveIssuePath } from '@/utils/issue'
import { organizationPageLink } from '@/utils/nav'
import { Pledger, ResponseError, RewardsSummary } from '@polar-sh/api'
import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
import ClientPage from './ClientPage'

const cacheConfig = {
cache: 'no-store',
} as const

export async function generateMetadata({
params,
}: {
params: { organization: string; repo: string; number: string }
}): Promise<Metadata> {
const api = getServerSideAPI()
const resolvedIssueOrganization = await resolveIssuePath(
api,
params.organization,
params.repo,
params.number,
cacheConfig,
)

if (!resolvedIssueOrganization) {
notFound()
}

const [issue, organization] = resolvedIssueOrganization

// Redirect to the actual Polar organization if resolved from external organization
if (organization.slug !== params.organization) {
redirect(
organizationPageLink(
organization,
`${issue.repository.name}/issues/${issue.number}`,
),
)
}

return {
title: `Fund: ${issue.title}`, // " | Polar is added by the template"
openGraph: {
title: `Fund: ${issue.title}`,
description: `${issue.repository.organization.name} seeks funding for ${issue.title} Polar`,
type: 'website',
images: [
{
url: `https://polar.sh/og?org=${issue.repository.organization.name}&repo=${issue.repository.name}&number=${issue.number}`,
width: 1200,
height: 630,
},
],
},
twitter: {
images: [
{
url: `https://polar.sh/og?org=${issue.repository.organization.name}&repo=${issue.repository.name}&number=${issue.number}`,
width: 1200,
height: 630,
alt: `${issue.repository.organization.name} seeks funding for ${issue.title} on Polar`,
},
],
card: 'summary_large_image',
title: `${issue.repository.organization.name} seeks funding for ${issue.title}`,
description: `${issue.repository.organization.name} seeks funding for ${issue.title} on Polar`,
},
}
}

export default async function Page({
params,
}: {
params: { organization: string; repo: string; number: string }
}) {
const api = getServerSideAPI()
const resolvedIssueOrganization = await resolveIssuePath(
api,
params.organization,
params.repo,
params.number,
cacheConfig,
)

if (!resolvedIssueOrganization) {
notFound()
}

const [issue, organization] = resolvedIssueOrganization

// Redirect to the actual Polar organization if resolved from external organization
if (organization.slug !== params.organization) {
redirect(
organizationPageLink(
organization,
`${issue.repository.name}/issues/${issue.number}`,
),
)
}

let issueHTMLBody: string | undefined
let pledgers: Pledger[] = []
let rewards: RewardsSummary | undefined

try {
const [bodyResponse, pledgeSummary, rewardsSummary] = await Promise.all([
api.issues.getBody({ id: issue.id }, { next: { revalidate: 60 } }), // Cache for 60s
api.pledges.summary({ issueId: issue.id }, cacheConfig),
api.rewards.summary({ issueId: issue.id }, cacheConfig),
])

issueHTMLBody = bodyResponse
pledgers = pledgeSummary.pledges
.map(({ pledger }) => pledger)
.filter((p): p is Pledger => !!p)
rewards = rewardsSummary
} catch (e) {
if (e instanceof ResponseError && e.response.status === 404) {
notFound()
}
}

// Closed issue, redirect to donation instead if linked organization
if (issue.issue_closed_at) {
redirect(organizationPageLink(organization, `donate?issue_id=${issue.id}`))
}

return (
<ClientPage
issue={issue}
organization={organization}
htmlBody={issueHTMLBody}
pledgers={pledgers}
rewards={rewards}
gotoURL={undefined}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Status from '@/components/Pledge/Status'
import { api } from '@/utils/api'
import { resolveRepositoryPath } from '@/utils/repository'
import { Pledge, ResponseError } from '@polar-sh/api'
import { notFound } from 'next/navigation'

const cacheConfig = {
cache: 'no-store',
} as const

export default async function Page({
params,
searchParams,
}: {
params: { organization: string; repo: string; number: string }
searchParams: { [key: string]: string | string[] | undefined }
}) {
const resolvedRepositoryOrganization = await resolveRepositoryPath(
api,
params.organization,
params.repo,
cacheConfig,
)

if (!resolvedRepositoryOrganization) {
notFound()
}

const [, organization] = resolvedRepositoryOrganization

const paymentIntentId = searchParams['payment_intent_id']
if (typeof paymentIntentId !== 'string') {
notFound()
}

let pledge: Pledge

try {
pledge = await api.pledges.create({
body: {
payment_intent_id: paymentIntentId,
},
})
} catch (e) {
if (e instanceof ResponseError) {
if (e.response.status === 404) {
notFound()
}
}
throw e
}

const email = searchParams['email'] as string | undefined

// TODO: Handle different statuses than success... #happy-path-alpha-programming

return <Status pledge={pledge} organization={organization} email={email} />
}

0 comments on commit f7cb8dc

Please sign in to comment.