kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: marketing page work
rodzic
33e943a441
commit
a42ea77ff2
|
@ -1,7 +1,6 @@
|
||||||
import './globals.css'
|
import './globals.css'
|
||||||
|
|
||||||
import type { Metadata } from 'next'
|
import type { Metadata } from 'next'
|
||||||
import cs from 'clsx'
|
|
||||||
import { Geist } from 'next/font/google'
|
import { Geist } from 'next/font/google'
|
||||||
import { Toaster } from 'sonner'
|
import { Toaster } from 'sonner'
|
||||||
|
|
||||||
|
@ -11,7 +10,6 @@ import { Header } from '@/components/header'
|
||||||
import * as config from '@/lib/config'
|
import * as config from '@/lib/config'
|
||||||
|
|
||||||
import Providers from './providers'
|
import Providers from './providers'
|
||||||
import styles from './styles.module.css'
|
|
||||||
|
|
||||||
const geist = Geist({
|
const geist = Geist({
|
||||||
variable: '--font-geist',
|
variable: '--font-geist',
|
||||||
|
@ -49,10 +47,10 @@ export default function RootLayout({
|
||||||
<html lang='en' suppressHydrationWarning>
|
<html lang='en' suppressHydrationWarning>
|
||||||
<body className={`${geist.variable} antialiased`}>
|
<body className={`${geist.variable} antialiased`}>
|
||||||
<Providers>
|
<Providers>
|
||||||
<div className={styles.root}>
|
<div className='w-full min-h-[100vh] relative flex flex-col items-center'>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<main className={cs(styles.main, 'pt-8 pb-16 px-4 md:px-0')}>
|
<main className='flex-1 w-full flex flex-col items-center max-w-[1200px] gap-16 pt-8 pb-16 px-4 md:px-0'>
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
import { OpenSourceSection } from '@/components/open-source-section'
|
import Link from 'next/link'
|
||||||
import { SupplySideCTA } from '@/components/supply-side-cta'
|
|
||||||
|
|
||||||
export default function IndexPage() {
|
import { GitHubStarCounter } from '@/components/github-star-counter'
|
||||||
|
import { SupplySideCTA } from '@/components/supply-side-cta'
|
||||||
|
import {
|
||||||
|
calendarBookingUrl,
|
||||||
|
discordUrl,
|
||||||
|
docsUrl,
|
||||||
|
githubUrl,
|
||||||
|
twitterUrl
|
||||||
|
} from '@/lib/config'
|
||||||
|
|
||||||
|
export default function TheBestDamnLandingPageEver() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* Hero section */}
|
||||||
<section className='gap-8'>
|
<section className='gap-8'>
|
||||||
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-extrabold'>
|
<h1 className='text-center text-balance leading-snug md:leading-none text-4xl font-extrabold'>
|
||||||
Your API → Paid MCP, Instantly
|
Your API → Paid MCP, Instantly
|
||||||
|
@ -18,13 +28,208 @@ export default function IndexPage() {
|
||||||
<SupplySideCTA />
|
<SupplySideCTA />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className='flex-1'>
|
{/* How it works section */}
|
||||||
<h2 className='text-center text-balance text-lg'>How it works</h2>
|
<section className='flex flex-col gap-8'>
|
||||||
|
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-heading'>
|
||||||
|
How It Works
|
||||||
|
</h2>
|
||||||
|
|
||||||
<div>TODO</div>
|
<div>TODO</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<OpenSourceSection />
|
{/* Features section */}
|
||||||
|
<section className='flex flex-col gap-8'>
|
||||||
|
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-heading'>
|
||||||
|
Production-Ready and Extremely Flexible
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className='grid gap-6'>
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<h4 className='text-center text-balance text-lg font-heading'>
|
||||||
|
Auth
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
Ship to production fast with Agentic's free, built-in
|
||||||
|
authentication. Email & password, OAuth, GitHub, Google, Twitter,
|
||||||
|
etc – if your origin API requires OAuth credentials, Agentic
|
||||||
|
likely already supports it, and if not,{' '}
|
||||||
|
<Link
|
||||||
|
href={calendarBookingUrl}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener'
|
||||||
|
className='link'
|
||||||
|
>
|
||||||
|
let me know
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<h4 className='text-center text-balance text-lg font-heading'>
|
||||||
|
Monetization
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
Charge for your MCP products with a flexible, declarative pricing
|
||||||
|
model built on top of Stripe. Agentic supports almost any
|
||||||
|
combination of fixed and usage-based billing models, both at the
|
||||||
|
MCP level, at the tool-call level, and at the custom metric level
|
||||||
|
(e.g., tokens, image transformations, etc).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<h4 className='text-center text-balance text-lg font-heading'>
|
||||||
|
Support both MCP <em>and</em> HTTP
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
All agentic products support being used both as a standard MCP
|
||||||
|
server <em>and</em> as an extremely simple HTTP API. MCP is
|
||||||
|
important for interop, discoverability, and future-proofing,
|
||||||
|
whereas being able to call your agentic tools via simple{' '}
|
||||||
|
<em>HTTP POST</em> requests makes tool use easy to debug and makes
|
||||||
|
integration with existing LLM tool calling patterns a breeze. With
|
||||||
|
Agentic, you get the best of both worlds, including future support
|
||||||
|
for unreleased MCP features and related specs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<h4 className='text-center text-balance text-lg font-heading'>
|
||||||
|
Rate-limiting
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
Customize your MCP product with durable rate-limiting built on top
|
||||||
|
of Cloudflare's global infrastructure. Create default rate-limits,
|
||||||
|
change them based on a customer's subscribed pricing plan, and
|
||||||
|
create custom tool-specific overrides, all with a simple,
|
||||||
|
strongly-typed JSON config. REST assured that your origin API will
|
||||||
|
be safe behind Agentic's robust API gateway.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<h4 className='text-center text-balance text-lg font-heading'>
|
||||||
|
Caching
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
Opt-in to caching with familiar <em>cache-control</em> and{' '}
|
||||||
|
<em>stale-while-revalidate</em> features. MCP tool calls include
|
||||||
|
caching information in their <em>_meta</em> fields with symmetry
|
||||||
|
to standard HTTP headers. All caching takes place in Cloudflare's
|
||||||
|
global edge cache, and will only be enabled if you choose to
|
||||||
|
enable it for your product or specific tools.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<h4 className='text-center text-balance text-lg font-heading'>
|
||||||
|
Analytics
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
Agentic tracks usage-based billing and analytics at a fine-grained
|
||||||
|
level, so you can understand how your customers are using your
|
||||||
|
product.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<h4 className='text-center text-balance text-lg font-heading'>
|
||||||
|
Versioning
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
Just like Vercel, Agentic uses immutable deployments, so every
|
||||||
|
time you make a change to your product's config, pricing, or docs,
|
||||||
|
a unique preview deployment is created for that change. This
|
||||||
|
enables instant rollbacks if there are problems with a deployment.
|
||||||
|
Publish deployment publicly uses semantic versioning (
|
||||||
|
<em>semver</em>), so your customers can choose how to handle
|
||||||
|
breaking changes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<h4 className='text-center text-balance text-lg font-heading'>
|
||||||
|
And more!
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
Checkout our <Link href={docsUrl}>docs</Link> for more details on
|
||||||
|
Agentic's MCP API gateway.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Marketplace section */}
|
||||||
|
<section className='flex flex-col gap-8'>
|
||||||
|
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-heading'>
|
||||||
|
MCP Marketplace
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>Coming soon...</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Open source section */}
|
||||||
|
<section className='flex flex-col items-center gap-8 max-w-2xl text-center'>
|
||||||
|
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-heading italic'>
|
||||||
|
Agentic is 100% Open Source!
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className='text-sm'>
|
||||||
|
Join the tens of thousands of TypeScript AI engineers who've{' '}
|
||||||
|
<Link
|
||||||
|
href={githubUrl}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener'
|
||||||
|
className='link'
|
||||||
|
>
|
||||||
|
starred the project on GitHub
|
||||||
|
</Link>
|
||||||
|
.{' '}
|
||||||
|
<Link
|
||||||
|
href={githubUrl}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener'
|
||||||
|
className='link'
|
||||||
|
>
|
||||||
|
Check out the source on GitHub
|
||||||
|
</Link>
|
||||||
|
,{' '}
|
||||||
|
<Link
|
||||||
|
href={discordUrl}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener'
|
||||||
|
className='link'
|
||||||
|
>
|
||||||
|
join our community on Discord
|
||||||
|
</Link>
|
||||||
|
, or{' '}
|
||||||
|
<Link
|
||||||
|
href={twitterUrl}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener'
|
||||||
|
className='link'
|
||||||
|
>
|
||||||
|
ping me on Twitter
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<GitHubStarCounter />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CTA section */}
|
||||||
|
<section className='flex flex-col gap-8'>
|
||||||
|
<SupplySideCTA />
|
||||||
|
</section>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
.root {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 1200px;
|
|
||||||
}
|
|
|
@ -4,6 +4,8 @@ import type { Simplify } from 'type-fest'
|
||||||
import confetti, { type Options as ConfettiBaseOptions } from 'canvas-confetti'
|
import confetti, { type Options as ConfettiBaseOptions } from 'canvas-confetti'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { randomInRange } from '@/lib/utils'
|
||||||
|
|
||||||
export type ConfettiOptions = Simplify<
|
export type ConfettiOptions = Simplify<
|
||||||
ConfettiBaseOptions & {
|
ConfettiBaseOptions & {
|
||||||
duration?: number
|
duration?: number
|
||||||
|
@ -55,7 +57,3 @@ export function useConfettiFireworks() {
|
||||||
fireConfetti
|
fireConfetti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomInRange(min: number, max: number) {
|
|
||||||
return Math.random() * (max - min) + min
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export function Footer() {
|
||||||
<div className='flex flex-col md:items-center'>
|
<div className='flex flex-col md:items-center'>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<h3 className='text-lg font-semibold'>Site</h3>
|
<h3 className='text-lg font-semibold'>Site</h3>
|
||||||
|
|
||||||
<nav className='flex flex-col space-y-2'>
|
<nav className='flex flex-col space-y-2'>
|
||||||
<span>
|
<span>
|
||||||
<ActiveLink href='/' className='link'>
|
<ActiveLink href='/' className='link'>
|
||||||
|
@ -38,16 +39,18 @@ export function Footer() {
|
||||||
<div className='flex flex-col order-last md:order-none col-span-2'>
|
<div className='flex flex-col order-last md:order-none col-span-2'>
|
||||||
<div className='space-y-4 flex flex-col w-full'>
|
<div className='space-y-4 flex flex-col w-full'>
|
||||||
<h3 className='text-lg font-semibold'>TODO</h3>
|
<h3 className='text-lg font-semibold'>TODO</h3>
|
||||||
|
|
||||||
<div className='grid grid-cols-[repeat(auto-fill,_minmax(10em,_1fr))] gap-y-4 gap-x-8 w-full flex-auto'>
|
<div className='grid grid-cols-[repeat(auto-fill,_minmax(10em,_1fr))] gap-y-4 gap-x-8 w-full flex-auto'>
|
||||||
<div className='link'>TODO</div>
|
<div className='link'>TODO</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex flex-col md:items-center'>
|
<div className='flex flex-col md:items-center gap-4'>
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<h3 className='text-lg font-semibold'>Social</h3>
|
<h3 className='text-lg font-semibold'>Social</h3>
|
||||||
<nav className='flex flex-col space-y-2'>
|
|
||||||
|
<nav className='flex flex-col gap-4'>
|
||||||
<Link
|
<Link
|
||||||
href={twitterUrl}
|
href={twitterUrl}
|
||||||
className='flex items-center space-x-2'
|
className='flex items-center space-x-2'
|
||||||
|
|
|
@ -6,22 +6,28 @@ import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { GitHubIcon } from '@/icons/github'
|
import { GitHubIcon } from '@/icons/github'
|
||||||
import { githubUrl } from '@/lib/config'
|
import { githubUrl } from '@/lib/config'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
import { Button } from './ui/button'
|
import { Button } from './ui/button'
|
||||||
|
|
||||||
// TODO: fetch this dynamically
|
// TODO: fetch this dynamically
|
||||||
const numGitHubStars = 17_600
|
const numGitHubStars = 17_600
|
||||||
|
|
||||||
export function GitHubStarCounter() {
|
export function GitHubStarCounter({ className }: { className?: string }) {
|
||||||
const [numStars, setNumStars] = useState(0)
|
const [numStars, setNumStars] = useState(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNumStars(numGitHubStars)
|
setNumStars(numGitHubStars)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant='outline' className='flex items-center gap-2' asChild>
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
className={cn('flex items-center gap-2', className)}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
<Link href={githubUrl} target='_blank' rel='noopener'>
|
<Link href={githubUrl} target='_blank' rel='noopener'>
|
||||||
<GitHubIcon className='' />
|
<GitHubIcon />
|
||||||
|
|
||||||
<NumberFlow
|
<NumberFlow
|
||||||
value={numStars}
|
value={numStars}
|
||||||
|
@ -30,6 +36,7 @@ export function GitHubStarCounter() {
|
||||||
roundingPriority: 'morePrecision'
|
roundingPriority: 'morePrecision'
|
||||||
}}
|
}}
|
||||||
suffix=' stars'
|
suffix=' stars'
|
||||||
|
willChange
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { GitHubStarCounter } from './github-star-counter'
|
|
||||||
|
|
||||||
export function OpenSourceSection() {
|
|
||||||
return (
|
|
||||||
<section className='flex flex-col items-center gap-8'>
|
|
||||||
<h2 className='text-center text-balance leading-snug md:leading-none text-2xl font-heading italic'>
|
|
||||||
Agentic is 100% open source
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<GitHubStarCounter />
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,14 +1,29 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { sanitizeSearchParams } from '@agentic/platform-core'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
import { HeroButton } from '@/components/hero-button'
|
import { HeroButton } from '@/components/hero-button'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { calendarBookingUrl, docsQuickStartUrl } from '@/lib/config'
|
import { calendarBookingUrl, docsQuickStartUrl } from '@/lib/config'
|
||||||
|
|
||||||
|
import { useAgentic } from './agentic-provider'
|
||||||
|
|
||||||
export function SupplySideCTA() {
|
export function SupplySideCTA() {
|
||||||
|
const ctx = useAgentic()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-center items-center gap-8'>
|
<div className='flex justify-center items-center gap-8'>
|
||||||
<HeroButton asChild className='h-full'>
|
<HeroButton asChild className='h-full'>
|
||||||
<Link href={docsQuickStartUrl}>Get Started</Link>
|
<Link
|
||||||
|
href={
|
||||||
|
ctx?.isAuthenticated
|
||||||
|
? docsQuickStartUrl
|
||||||
|
: `/signup?${sanitizeSearchParams({ next: docsQuickStartUrl })}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</Link>
|
||||||
</HeroButton>
|
</HeroButton>
|
||||||
|
|
||||||
<Button variant='outline' asChild className='h-full'>
|
<Button variant='outline' asChild className='h-full'>
|
||||||
|
|
|
@ -21,6 +21,7 @@ export const calendarBookingUrl =
|
||||||
export const docsUrl = 'https://docs.agentic.so'
|
export const docsUrl = 'https://docs.agentic.so'
|
||||||
export const docsQuickStartUrl = `${docsUrl}/quick-start`
|
export const docsQuickStartUrl = `${docsUrl}/quick-start`
|
||||||
export const docsMarketplaceUrl = `${docsUrl}/marketplace`
|
export const docsMarketplaceUrl = `${docsUrl}/marketplace`
|
||||||
|
export const discordUrl = 'https://discord.agentic.so'
|
||||||
|
|
||||||
export const keywords = [
|
export const keywords = [
|
||||||
'agentic',
|
'agentic',
|
||||||
|
|
|
@ -4,3 +4,7 @@ import { twMerge } from 'tailwind-merge'
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function randomInRange(min: number, max: number) {
|
||||||
|
return Math.random() * (max - min) + min
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
- stripe checkout for changing plans? (need to at least be able to upgrade)
|
- stripe checkout for changing plans? (need to at least be able to upgrade)
|
||||||
- stripe billing portal
|
- stripe billing portal
|
||||||
- should we bypass stripe for `free` plans to increase conversions?
|
- should we bypass stripe for `free` plans to increase conversions?
|
||||||
|
- handle browser back/forward with `?next=`
|
||||||
- **API gateway**
|
- **API gateway**
|
||||||
- oauth flow
|
- oauth flow
|
||||||
- https://docs.scalekit.com/guides/mcp/oauth
|
- https://docs.scalekit.com/guides/mcp/oauth
|
||||||
|
|
Ładowanie…
Reference in New Issue