feat: webapp work

pull/715/head
Travis Fischer 2025-06-28 04:43:23 -05:00
rodzic db4d24a452
commit 8d328b69fc
15 zmienionych plików z 187 dodań i 112 usunięć

Plik binarny nie jest wyświetlany.

Po

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

Wyświetl plik

@ -1,5 +1,6 @@
import Link from 'next/link'
import { PageContainer } from '@/components/page-container'
import { SupplySideCTA } from '@/components/supply-side-cta'
import { githubUrl, twitterUrl } from '@/lib/config'
import { cn } from '@/lib/utils'
@ -8,7 +9,7 @@ import styles from './styles.module.css'
export default function AboutPage() {
return (
<>
<PageContainer>
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
About
</h1>
@ -185,6 +186,6 @@ export default function AboutPage() {
<SupplySideCTA variant='github-2' />
</section>
</>
</PageContainer>
)
}

Wyświetl plik

@ -1,9 +1,10 @@
import { AppConsumersList } from '@/components/app-consumers-list'
import { AppProjectsList } from '@/components/app-projects-list'
import { PageContainer } from '@/components/page-container'
export function AppDashboard() {
return (
<>
<PageContainer>
<section>
<h1
className='text-center text-balance leading-snug md:leading-none
@ -12,12 +13,12 @@ export function AppDashboard() {
Dashboard
</h1>
<div className='flex gap-8 space-around'>
<div className='flex flex-col lg:flex-row gap-8 space-around'>
<AppConsumersList />
<AppProjectsList />
</div>
</section>
</>
</PageContainer>
)
}

Wyświetl plik

@ -47,10 +47,10 @@ export default function RootLayout({
<html lang='en' suppressHydrationWarning>
<body className={`${geist.variable} antialiased`}>
<Providers>
<div className='w-full min-h-[100vh] relative flex flex-col items-center'>
<div className='relative w-full min-h-[100vh] flex flex-col items-center'>
<Header />
<main className='flex-1 w-full flex flex-col items-center max-w-[1200px] gap-16 pt-16 pb-24 px-2 overflow-x-hidden'>
<main className='relative w-full flex-1 flex flex-col items-center gap-16 pt-16 pb-24 px-2 overflow-x-hidden'>
{children}
</main>

Wyświetl plik

@ -1,11 +1,15 @@
import { Suspense } from 'react'
import { PageContainer } from '@/components/page-container'
import { LoginForm } from './login-form'
export default function Page() {
return (
<Suspense>
<LoginForm />
<PageContainer>
<LoginForm />
</PageContainer>
</Suspense>
)
}

Wyświetl plik

@ -4,6 +4,7 @@ import useInfiniteScroll from 'react-infinite-scroll-hook'
import { useAgentic } from '@/components/agentic-provider'
import { LoadingIndicator } from '@/components/loading-indicator'
import { PageContainer } from '@/components/page-container'
import { PublicProject } from '@/components/public-project'
import { useInfiniteQuery } from '@/lib/query-client'
@ -51,7 +52,7 @@ export function MarketplaceIndex() {
const projects = data ? data.pages.flatMap((p) => p.projects) : []
return (
<>
<PageContainer>
<section>
<h1
className='text-center text-balance leading-snug md:leading-none
@ -74,7 +75,7 @@ export function MarketplaceIndex() {
Please refresh or contact support.
</p>
) : (
<div className='grid gap-4'>
<div className='grid grid-cols grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3'>
{projects.map((project) => (
<PublicProject key={project.id} project={project} />
))}
@ -89,6 +90,6 @@ export function MarketplaceIndex() {
</div>
)}
</section>
</>
</PageContainer>
)
}

Wyświetl plik

@ -1,13 +1,13 @@
'use client'
import { assert, omit, sanitizeSearchParams } from '@agentic/platform-core'
import { Loader2Icon } from 'lucide-react'
import { useRouter, useSearchParams } from 'next/navigation'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useAgentic } from '@/components/agentic-provider'
import { LoadingIndicator } from '@/components/loading-indicator'
import { Button } from '@/components/ui/button'
import { PageContainer } from '@/components/page-container'
import { ProjectPricingPlans } from '@/components/project-pricing-plans'
import { toast, toastError } from '@/lib/notifications'
import { useQuery } from '@/lib/query-client'
@ -139,72 +139,42 @@ export function MarketplaceProjectIndex({
])
return (
<section>
{!ctx || isLoading ? (
<LoadingIndicator />
) : isError ? (
<p>Error fetching project</p>
) : !project ? (
<p>Project "{projectIdentifier}" not found</p>
) : (
<>
<h1
className='text-center text-balance leading-snug md:leading-none
<PageContainer>
<section>
{!ctx || isLoading ? (
<LoadingIndicator />
) : isError ? (
<p>Error fetching project</p>
) : !project ? (
<p>Project "{projectIdentifier}" not found</p>
) : (
<>
<h1
className='text-center text-balance leading-snug md:leading-none
text-4xl font-extrabold'
>
Project {project.name}
</h1>
>
Project {project.name}
</h1>
<div className='mt-8'>
<pre className='max-w-lg'>
{JSON.stringify(
omit(project, 'lastPublishedDeployment', 'lastDeployment'),
null,
2
)}
</pre>
</div>
<div className='mt-8'>
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-extrabold mb-4'>
Pricing Plans
</h2>
<div className='flex gap-8'>
{project.lastPublishedDeployment!.pricingPlans.map((plan) => (
<div key={plan.slug} className='flex flex-col gap-4'>
<h3 className='text-center text-balance leading-snug md:leading-none text-xl font-bold'>
{plan.name}
</h3>
<pre className='max-w-lg'>
{JSON.stringify(plan, null, 2)}
</pre>
<Button
onClick={() => onSubscribe(plan.slug)}
// TODO: handle free plans correctly
disabled={
consumer?.plan === plan.slug ||
!!isLoadingStripeCheckoutForPlan
}
>
{isLoadingStripeCheckoutForPlan === plan.slug && (
<Loader2Icon className='animate-spin mr-2' />
)}
{consumer?.plan === plan.slug ? (
<span>Currently subscribed to "{plan.name}"</span>
) : (
<span>Subscribe to "{plan.name}"</span>
)}
</Button>
</div>
))}
<div className='mt-8'>
<pre className='max-w-lg'>
{JSON.stringify(
omit(project, 'lastPublishedDeployment', 'lastDeployment'),
null,
2
)}
</pre>
</div>
</div>
</>
)}
</section>
<ProjectPricingPlans
project={project}
consumer={consumer}
isLoadingStripeCheckoutForPlan={isLoadingStripeCheckoutForPlan}
onSubscribe={onSubscribe}
/>
</>
)}
</section>
</PageContainer>
)
}

Wyświetl plik

@ -7,6 +7,7 @@ import { ExampleUsage } from '@/components/example-usage'
import { GitHubStarCounter } from '@/components/github-star-counter'
import { HeroSimulation2 } from '@/components/hero-simulation-2'
import { MCPMarketplaceFeatures } from '@/components/mcp-marketplace-features'
import { PageContainer } from '@/components/page-container'
import { githubUrl, twitterUrl } from '@/lib/config'
import {
defaultConfig,
@ -35,22 +36,20 @@ export default async function TheBestDamnLandingPageEver() {
const initialCodeBlock = await highlight(initialCodeSnippet)
return (
<>
<PageContainer>
{/* Hero section */}
<DotsSection className='mb-16'>
<div className='flex flex-col gap-10 relative z-10'>
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
The App Store for LLM Tools
</h1>
<section className='flex flex-col gap-10 mb-16'>
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
The App Store for LLM Tools
</h1>
<h5 className='text-center text-lg max-w-2xl'>
Agentic is a curated marketplace of production-grade LLM tools. All
tools are exposed as both MCP servers as well as simple HTTP APIs.
</h5>
<h5 className='text-center text-lg max-w-2xl'>
Agentic is a curated marketplace of production-grade LLM tools. All
tools are exposed as both MCP servers as well as simple HTTP APIs.
</h5>
<DemandSideCTA />
</div>
</DotsSection>
<DemandSideCTA />
</section>
{/* How it works section */}
<section className='flex flex-col gap-8 mb-16'>
@ -163,6 +162,6 @@ export default async function TheBestDamnLandingPageEver() {
<DemandSideCTA />
</div>
</DotsSection>
</>
</PageContainer>
)
}

Wyświetl plik

@ -4,28 +4,27 @@ import { DotsSection } from '@/components/dots-section'
import { ExampleAgenticConfigs } from '@/components/example-agentic-configs'
import { GitHubStarCounter } from '@/components/github-star-counter'
import { MCPGatewayFeatures } from '@/components/mcp-gateway-features'
import { PageContainer } from '@/components/page-container'
import { SupplySideCTA } from '@/components/supply-side-cta'
import { githubUrl, twitterUrl } from '@/lib/config'
export default function MCPAuthorsPage() {
export default function PublishingMCPsPage() {
return (
<>
<PageContainer>
{/* Hero section */}
<DotsSection className='mb-16'>
<div className='flex flex-col gap-8 relative z-10'>
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
Your API Paid MCP, Instantly
</h1>
<section className='flex flex-col gap-8 mb-16'>
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-semibold'>
Your API Paid MCP, Instantly
</h1>
<h5 className='text-center text-lg max-w-2xl'>
Run one command to turn any MCP server or OpenAPI service into a
paid MCP product,{' '}
<em>with built-in distribution to over 20k AI engineers</em>.
</h5>
<h5 className='text-center text-lg max-w-2xl'>
Run one command to turn any MCP server or OpenAPI service into a paid
MCP product,{' '}
<em>with built-in distribution to over 20k AI engineers</em>.
</h5>
<SupplySideCTA />
</div>
</DotsSection>
<SupplySideCTA />
</section>
{/* How it works section */}
<section className='flex flex-col gap-8 mb-16'>
@ -109,6 +108,6 @@ export default function MCPAuthorsPage() {
<SupplySideCTA variant='github-2' />
</div>
</DotsSection>
</>
</PageContainer>
)
}

Wyświetl plik

@ -1,11 +1,15 @@
import { Suspense } from 'react'
import { PageContainer } from '@/components/page-container'
import { SignupForm } from './signup-form'
export default function Page() {
return (
<Suspense>
<SignupForm />
<PageContainer>
<SignupForm />
</PageContainer>
</Suspense>
)
}

Wyświetl plik

@ -68,7 +68,7 @@ export function AppConsumersList() {
started.
</p>
) : (
<div className='grid gap-4'>
<div className='grid grid-cols grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3'>
{consumers.map((consumer) => (
<Link
key={consumer.id}

Wyświetl plik

@ -63,7 +63,7 @@ export function AppProjectsList() {
) : !projects.length ? (
<p>No projects found. Create your first project to get started.</p>
) : (
<div className='grid gap-4'>
<div className='grid grid-cols grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3'>
{projects.map((project) => (
<Link
key={project.id}

Wyświetl plik

@ -28,8 +28,8 @@ export function Footer() {
</span>
<span>
<ActiveLink href='/mcp-authors' className='link'>
For MCP Authors
<ActiveLink href='/publishing' className='link'>
Publishing MCPs
</ActiveLink>
</span>

Wyświetl plik

@ -0,0 +1,28 @@
import { cn } from '@/lib/utils'
export function PageContainer({
background = true,
className,
children
}: {
background?: boolean
className?: string
children: React.ReactNode
}) {
return (
<>
{background && (
<div className='absolute top-0 left-0 w-[100vw] h-[100vh] bg-[url(/bg.png)] bg-top-right bg-no-repeat bg-size-[100vw_50vh] md:bg-auto z-0' />
)}
<div
className={cn(
'relative w-full flex-1 flex flex-col items-center max-w-[1200px] gap-16 z-10',
className
)}
>
{children}
</div>
</>
)
}

Wyświetl plik

@ -0,0 +1,68 @@
import type { Consumer, Project } from '@agentic/platform-types'
import { Loader2Icon } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
export function ProjectPricingPlans({
project,
consumer,
isLoadingStripeCheckoutForPlan,
onSubscribe,
className
}: {
project: Project
consumer?: Consumer
isLoadingStripeCheckoutForPlan: string | null
onSubscribe: (planSlug: string) => void
className?: string
}) {
const numPricingPlans =
project.lastPublishedDeployment?.pricingPlans.length || 1
return (
<div className={cn('mt-8 flex flex-col gap-8', className)}>
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-extrabold'>
Pricing Plans
</h2>
<div
className={`grid grid-cols grid-cols-1 gap-8 sm:grid-cols-${Math.min(
2,
numPricingPlans
)} xl:grid-cols-${Math.min(3, numPricingPlans)}`}
>
{project.lastPublishedDeployment!.pricingPlans.map((plan) => (
<div
key={plan.slug}
className='flex flex-col gap-4 p-4 border rounded-lg shadow-sm'
>
<h3 className='text-center text-balance leading-snug md:leading-none text-xl font-bold'>
{plan.name}
</h3>
<pre className='max-w-lg'>{JSON.stringify(plan, null, 2)}</pre>
<Button
onClick={() => onSubscribe(plan.slug)}
// TODO: handle free plans correctly
disabled={
consumer?.plan === plan.slug || !!isLoadingStripeCheckoutForPlan
}
>
{isLoadingStripeCheckoutForPlan === plan.slug && (
<Loader2Icon className='animate-spin mr-2' />
)}
{consumer?.plan === plan.slug ? (
<span>Currently subscribed to "{plan.name}"</span>
) : (
<span>Subscribe to "{plan.name}"</span>
)}
</Button>
</div>
))}
</div>
</div>
)
}