Skip to content

Commit

Permalink
fix repo page
Browse files Browse the repository at this point in the history
  • Loading branch information
emilwidlund committed Feb 8, 2025
1 parent f7cb8dc commit 3ec977a
Show file tree
Hide file tree
Showing 3 changed files with 484 additions and 0 deletions.
241 changes: 241 additions & 0 deletions clients/apps/web/src/app/(main)/[organization]/[repo]/ClientPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
'use client'

import revalidate from '@/app/actions'
import IssuesLookingForFunding from '@/components/Organization/IssuesLookingForFunding'
import { CoverEditor } from '@/components/Profile/CoverEditor/CoverEditor'
import { CreatorsEditor } from '@/components/Profile/CreatorEditor/CreatorsEditor'
import { DescriptionEditor } from '@/components/Profile/DescriptionEditor/DescriptionEditor'
import {
Link as LinkItem,
LinksEditor,
} from '@/components/Profile/LinksEditor/LinksEditor'
import { useUpdateProject } from '@/hooks/queries'
import useDebouncedCallback from '@/hooks/utils'
import { organizationPageLink } from '@/utils/nav'
import { formatStarsNumber } from '@/utils/stars'
import { ArrowUpRightIcon } from '@heroicons/react/20/solid'
import {
ListResourceIssueFunding,
Organization,
Repository,
RepositoryProfileSettingsUpdate,
} from '@polar-sh/api'
import Avatar from '@polar-sh/ui/components/atoms/Avatar'
import { ShadowBoxOnMd } from '@polar-sh/ui/components/atoms/ShadowBox'
import Link from 'next/link'
import type { SuccessResult } from 'open-graph-scraper-lite'
import { useMemo } from 'react'

type OgObject = SuccessResult['result']

const ClientPage = ({
organization,
repository,
issuesFunding,
featuredOrganizations,
userOrganizations,
links,
}: {
organization: Organization
repository: Repository
issuesFunding: ListResourceIssueFunding
featuredOrganizations: Organization[]
userOrganizations: Organization[]
links: { opengraph: OgObject; url: string }[]
}) => {
const isOrgMember = useMemo(
() => userOrganizations?.some((org) => org.id === organization.id),
[organization, userOrganizations],
)

const updateProjectMutation = useUpdateProject()

const updateProfile = (setting: Partial<RepositoryProfileSettingsUpdate>) => {
return updateProjectMutation
.mutateAsync({
id: repository.id,
body: {
profile_settings: setting,
},
})
.then(() =>
revalidate(`repository:${organization.slug}/${repository.name}`),
)
}

const updateFeaturedCreators = (organizations: Organization[]) => {
updateProfile({
featured_organizations: organizations.map((c) => c.id),
})
}

const updateCoverImage = (coverImageUrl: string | undefined) => {
updateProfile({ set_cover_image_url: true, cover_image_url: coverImageUrl })
}

const updateDescription = useDebouncedCallback(
async (description: string | undefined) => {
await updateProfile({ set_description: true, description })
},
500,
[updateProfile],
)

const updateLinks = (links: LinkItem[]) => {
updateProfile({ links: links.map((l) => l.url) })
}

return (
<div className="flex w-full flex-col gap-y-12">
<div className="flex w-full flex-col gap-16">
<div className="flex flex-col gap-16 md:flex-row">
<div className="flex w-full min-w-0 flex-shrink flex-col gap-y-16">
<DescriptionEditor
description={
repository.profile_settings?.description ??
repository.description ??
''
}
onChange={updateDescription}
disabled={!isOrgMember}
loading={updateProjectMutation.isPending}
failed={updateProjectMutation.isError}
maxLength={240}
/>

<CoverEditor
organization={organization}
onChange={updateCoverImage}
coverImageUrl={
repository.profile_settings?.cover_image_url || undefined
}
disabled={!isOrgMember}
/>

<CreatorsEditor
organization={organization}
featuredOrganizations={featuredOrganizations}
onChange={updateFeaturedCreators}
disabled={!isOrgMember}
/>

{organization.feature_settings?.issue_funding_enabled &&
(issuesFunding.items.length ?? 0) > 0 && (
<ShadowBoxOnMd>
<div className="p-4">
<div className="flex flex-row items-start justify-between pb-8">
<h2 className="text-lg font-medium">
Issues looking for funding
</h2>
</div>
<IssuesLookingForFunding
organization={organization}
repository={repository}
issues={issuesFunding}
/>
</div>
</ShadowBoxOnMd>
)}
</div>

<div className="flex w-full flex-col gap-12 md:max-w-52 lg:max-w-72">
<div className="flex flex-col gap-6">
<ShadowBoxOnMd className="flex flex-col gap-6 md:p-6">
<div className="flex flex-col gap-4">
<Avatar
className="h-12 w-12"
avatar_url={organization.avatar_url}
name={organization.name}
/>
<span className="flex flex-row flex-wrap gap-2">
<Link
className="dark:text-polar-500 text-wrap text-gray-500 transition-colors hover:text-blue-500 dark:hover:text-blue-400"
href={organizationPageLink(organization)}
>
{repository.organization.name}
</Link>
<span className="dark:text-polar-600 text-gray-400">/</span>
<Link
className="text-wrap transition-colors hover:text-blue-500 dark:hover:text-blue-400"
href={organizationPageLink(organization, repository.name)}
>
{repository.name}
</Link>
</span>
</div>
<div className="flex flex-col gap-1.5 text-sm">
<div className="flex flex-row justify-between gap-x-4">
<span className="dark:text-polar-400 text-gray-600">
Creator
</span>
<Link
className="truncate text-right"
href={organizationPageLink(organization)}
>
{repository.organization.name}
</Link>
</div>
<div className="flex flex-row justify-between gap-x-4">
<span className="dark:text-polar-400 text-gray-600">
Stars
</span>
<span className="truncate text-right">
{formatStarsNumber(repository.stars ?? 0)}
</span>
</div>
<div className="flex flex-row justify-between gap-x-4">
<span className="dark:text-polar-400 text-gray-600">
License
</span>
<span className="truncate text-right">
{repository.license ?? 'Unlicensed'}
</span>
</div>
<div className="flex flex-row justify-between gap-x-4">
<span className="dark:text-polar-400 text-gray-600">
Repository
</span>
<Link
className="flex flex-row items-center gap-x-2 truncate text-right"
href={`https://github.com/${repository.organization.name}/${repository.name}`}
rel="noopener noreferrer"
target="_blank"
>
{'GitHub'}
<ArrowUpRightIcon className="h-4 w-4" />
</Link>
</div>
{repository.homepage && (
<div className="flex flex-row justify-between gap-x-4">
<span className="dark:text-polar-400 text-gray-600">
Website
</span>
<Link
className="flex flex-row items-center gap-x-2 truncate text-right"
href={repository.homepage}
rel="noopener noreferrer"
target="_blank"
>
{new URL(repository.homepage).hostname}
<ArrowUpRightIcon className="h-4 w-4" />
</Link>
</div>
)}
</div>
</ShadowBoxOnMd>
</div>

<LinksEditor
links={links}
onChange={updateLinks}
disabled={!isOrgMember}
variant="column"
/>
</div>
</div>
</div>
</div>
)
}

export default ClientPage
94 changes: 94 additions & 0 deletions clients/apps/web/src/app/(main)/[organization]/[repo]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import LogoIcon from '@/components/Brand/LogoIcon'
import PolarMenu from '@/components/Layout/PolarMenu'
import { BrandingMenu } from '@/components/Layout/Public/BrandingMenu'
import { getServerSideAPI } from '@/utils/api/serverside'
import { organizationPageLink } from '@/utils/nav'
import { resolveRepositoryPath } from '@/utils/repository'
import { getAuthenticatedUser, getUserOrganizations } from '@/utils/user'
import Avatar from '@polar-sh/ui/components/atoms/Avatar'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import React from 'react'

const cacheConfig = {
next: {
revalidate: 30, // 30 seconds
},
}

export default async function Layout({
params,
children,
}: {
params: { organization: string; repo: string }
children: React.ReactNode
}) {
const api = getServerSideAPI()
const resolvedRepositoryOrganization = await resolveRepositoryPath(
api,
params.organization,
params.repo,
cacheConfig,
)

if (!resolvedRepositoryOrganization) {
notFound()
}

const [repository, organization] = resolvedRepositoryOrganization

const authenticatedUser = await getAuthenticatedUser()
const userOrganizations = await getUserOrganizations(api)

return (
<div className="flex flex-col">
<div className="mx-auto flex w-full max-w-[1440px] flex-col items-start gap-y-8 px-4 pb-12 md:h-full md:space-y-8 md:px-24">
<div className="dark:bg-polar-950 sticky top-0 z-20 flex w-full flex-row items-center justify-between bg-gray-50 py-4 md:relative md:hidden">
<a href="/">
<LogoIcon className="text-blue-500 dark:text-blue-400" size={40} />
</a>
<PolarMenu
authenticatedUser={authenticatedUser}
userOrganizations={userOrganizations}
organization={organization}
/>
</div>
<div className="jusitfy-between flex w-full flex-row items-center gap-x-10">
<div className="flex w-full flex-row items-center gap-x-8">
<BrandingMenu />
<Link className="-mr-4" href={organizationPageLink(organization)}>
<Avatar
className="h-8 w-8"
avatar_url={organization.avatar_url}
name={organization.name}
/>
</Link>
<h1 className="flex flex-row items-baseline gap-x-4 text-2xl !font-normal">
<Link
className="dark:text-polar-600 text-gray-400 transition-colors hover:text-blue-500 dark:hover:text-blue-400"
href={organizationPageLink(organization)}
>
{organization.name}
</Link>
<span className="dark:text-polar-600 text-gray-400">/</span>
<Link
className="transition-colors hover:text-blue-500 dark:hover:text-blue-400"
href={organizationPageLink(organization, repository.name)}
>
<span>{repository.name}</span>
</Link>
</h1>
</div>
<div className="hidden flex-row items-center md:flex">
<PolarMenu
authenticatedUser={authenticatedUser}
userOrganizations={userOrganizations}
organization={organization}
/>
</div>
</div>
{children}
</div>
</div>
)
}
Loading

0 comments on commit 3ec977a

Please sign in to comment.