feat: marketplace public project detail page looking better now

pull/717/head
Travis Fischer 2025-07-01 05:59:53 -05:00
rodzic d14f214c08
commit d85098b8b9
7 zmienionych plików z 105 dodań i 13 usunięć

Wyświetl plik

@ -51,7 +51,7 @@ export default function RootLayout({
<div className='relative w-full min-h-[100vh] flex flex-col items-center'> <div className='relative w-full min-h-[100vh] flex flex-col items-center'>
<Header /> <Header />
<main className='relative w-full flex-1 flex flex-col items-center gap-16 pt-16 pb-24 px-2 overflow-hidden'> <main className='relative w-full flex-1 flex flex-col items-center gap-16 py-16 px-2 overflow-hidden'>
{children} {children}
</main> </main>

Wyświetl plik

@ -0,0 +1,51 @@
import { parseProjectIdentifier } from '@agentic/platform-validators'
import { notFound } from 'next/navigation'
import { toastError } from '@/lib/notifications'
import { MarketplacePublicProjectDetail } from '../marketplace-public-project-detail'
import {
type MarketplacePublicProjectDetailTab,
marketplacePublicProjectDetailTabsSet
} from '../utils'
export default async function PublicProjectDetailPageTabPage({
params
}: {
params: Promise<{
namespace: string
'project-slug': string
tab: string
}>
}) {
const {
namespace: rawNamespace,
'project-slug': rawProjectSlug,
tab
} = await params
if (!marketplacePublicProjectDetailTabsSet.has(tab)) {
return notFound()
}
try {
const namespace = decodeURIComponent(rawNamespace)
const projectSlug = decodeURIComponent(rawProjectSlug)
const { projectIdentifier } = parseProjectIdentifier(
`${namespace}/${projectSlug}`,
{ strict: true }
)
return (
<MarketplacePublicProjectDetail
projectIdentifier={projectIdentifier}
tab={tab as MarketplacePublicProjectDetailTab}
/>
)
} catch (err: any) {
void toastError(err, { label: 'Invalid project identifier' })
return notFound()
}
}

Wyświetl plik

@ -10,6 +10,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useAgentic } from '@/components/agentic-provider' import { useAgentic } from '@/components/agentic-provider'
import { ExampleUsage } from '@/components/example-usage' import { ExampleUsage } from '@/components/example-usage'
import { HeroButton } from '@/components/hero-button'
import { LoadingIndicator } from '@/components/loading-indicator' import { LoadingIndicator } from '@/components/loading-indicator'
import { PageContainer } from '@/components/page-container' import { PageContainer } from '@/components/page-container'
import { ProjectPricingPlans } from '@/components/project-pricing-plans' import { ProjectPricingPlans } from '@/components/project-pricing-plans'
@ -24,12 +25,17 @@ import { GitHubIcon } from '@/icons/github'
import { toast, toastError } from '@/lib/notifications' import { toast, toastError } from '@/lib/notifications'
import { useQuery } from '@/lib/query-client' import { useQuery } from '@/lib/query-client'
const MAX_TOOLS_TO_SHOW = 5 import {
type MarketplacePublicProjectDetailTab,
MAX_TOOLS_TO_SHOW
} from './utils'
export function MarketplaceProjectIndex({ export function MarketplacePublicProjectDetail({
projectIdentifier projectIdentifier,
tab = 'overview'
}: { }: {
projectIdentifier: string projectIdentifier: string
tab?: MarketplacePublicProjectDetailTab
}) { }) {
const ctx = useAgentic() const ctx = useAgentic()
const searchParams = useSearchParams() const searchParams = useSearchParams()
@ -184,7 +190,18 @@ export function MarketplaceProjectIndex({
<div className='flex flex-col gap-4 w-full'> <div className='flex flex-col gap-4 w-full'>
<ProjectHeader project={project} /> <ProjectHeader project={project} />
<Tabs defaultValue='overview'> <Tabs
value={tab}
onValueChange={(value) => {
if (value === 'overview') {
router.push(`/marketplace/projects/${projectIdentifier}`)
} else {
router.push(
`/marketplace/projects/${projectIdentifier}/${value}`
)
}
}}
>
<TabsList> <TabsList>
<TabsTrigger value='overview' className='cursor-pointer'> <TabsTrigger value='overview' className='cursor-pointer'>
Overview Overview
@ -393,7 +410,7 @@ export function MarketplaceProjectIndex({
function ProjectHeader({ project }: { project: Project }) { function ProjectHeader({ project }: { project: Project }) {
return ( return (
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
<div className='flex flex-row gap-2.5 items-center'> <div className='w-full flex flex-row gap-2.5 items-center'>
<img <img
src={ src={
project.lastPublishedDeployment?.iconUrl || project.lastPublishedDeployment?.iconUrl ||
@ -404,13 +421,19 @@ function ProjectHeader({ project }: { project: Project }) {
className='aspect-square w-10 h-10' className='aspect-square w-10 h-10'
/> />
<h1 className='font-semibold text-balance text-3xl leading-tight'> <h1 className='flex-1 font-semibold text-balance text-3xl leading-tight'>
{project.name} {project.name}
</h1> </h1>
<HeroButton heroVariant='orange' className='justify-self-end'>
<Link href={`/marketplace/projects/${project.identifier}/pricing`}>
Subscribe to {project.identifier}
</Link>
</HeroButton>
</div> </div>
<div className='flex flex-row items-center'> <div className='flex flex-row items-center'>
<div className='text-sm text-gray-500 flex flex-row gap-0.5 items-center hover:no-underline! no-underline!'> <div className='text-sm text-muted-foreground flex flex-row gap-0.5 items-center hover:no-underline! no-underline!'>
<span>{project.identifier}</span> <span>{project.identifier}</span>
{/* TODO: <CopyIcon className='w-4 h-4' /> */} {/* TODO: <CopyIcon className='w-4 h-4' /> */}
@ -420,7 +443,9 @@ function ProjectHeader({ project }: { project: Project }) {
<Button asChild variant='link'> <Button asChild variant='link'>
<Link <Link
href={project.lastPublishedDeployment.websiteUrl} href={project.lastPublishedDeployment.websiteUrl}
className='text-sm flex flex-row gap-1.5! items-center text-gray-500! py-1! px-2!' className='text-sm flex flex-row gap-1.5! items-center text-muted-foreground! py-1! px-2!'
target='_blank'
rel='noopener noreferrer'
> >
<ExternalLinkIcon className='w-4 h-4' /> <ExternalLinkIcon className='w-4 h-4' />
@ -433,7 +458,9 @@ function ProjectHeader({ project }: { project: Project }) {
<Button asChild variant='link'> <Button asChild variant='link'>
<Link <Link
href={project.lastPublishedDeployment.sourceUrl} href={project.lastPublishedDeployment.sourceUrl}
className='text-sm flex flex-row gap-1.5! items-center text-gray-500! py-1! px-2!' className='text-sm flex flex-row gap-1.5! items-center text-muted-foreground! py-1! px-2!'
target='_blank'
rel='noopener noreferrer'
> >
<GitHubIcon className='w-4 h-4' /> <GitHubIcon className='w-4 h-4' />

Wyświetl plik

@ -3,9 +3,9 @@ import { notFound } from 'next/navigation'
import { toastError } from '@/lib/notifications' import { toastError } from '@/lib/notifications'
import { MarketplaceProjectIndex } from './marketplace-project-index' import { MarketplacePublicProjectDetail } from './marketplace-public-project-detail'
export default async function MarketplaceProjectIndexPage({ export default async function MarketplacePublicProjectDetailPage({
params params
}: { }: {
params: Promise<{ params: Promise<{
@ -25,7 +25,9 @@ export default async function MarketplaceProjectIndexPage({
{ strict: true } { strict: true }
) )
return <MarketplaceProjectIndex projectIdentifier={projectIdentifier} /> return (
<MarketplacePublicProjectDetail projectIdentifier={projectIdentifier} />
)
} catch (err: any) { } catch (err: any) {
void toastError(err, { label: 'Invalid project identifier' }) void toastError(err, { label: 'Invalid project identifier' })

Wyświetl plik

@ -0,0 +1,12 @@
export const MAX_TOOLS_TO_SHOW = 5
export const marketplacePublicProjectDetailTabs = [
'overview',
'tools',
'pricing',
'debug'
] as const
export const marketplacePublicProjectDetailTabsSet = new Set(
marketplacePublicProjectDetailTabs
)
export type MarketplacePublicProjectDetailTab =
(typeof marketplacePublicProjectDetailTabs)[number]

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 90 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 80 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 90 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 80 KiB