mirror of
https://github.com/paperclipai/clipmart.git
synced 2026-11-07 06:15:58 -10:00
Add ClipMart scaffolding: deps, schema, auth, layout, and pages
Sets up Drizzle ORM with Postgres, better-auth for authentication, database schema (listings, creators, purchases, reviews, team_blueprints), and basic layout shell with browse/about/pricing pages. Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
1 parent
fa22ba8200
commit
7d51f20a4b
17 files changed
+2522
-88
No files matched your search
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
export default defineConfig({
|
||||
schema: "./src/db/schema.ts",
|
||||
out: "./drizzle",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
Generated
+1565
-7
File diff suppressed because it is too large.
Load diff
@@ -9,7 +9,13 @@
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@better-auth/drizzle-adapter": "^1.5.3",
|
||||
"@supabase/supabase-js": "^2.98.0",
|
||||
"better-auth": "^1.5.3",
|
||||
"dotenv": "^17.3.1",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"next": "16.1.6",
|
||||
"postgres": "^3.4.8",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
},
|
||||
@@ -18,6 +24,7 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"drizzle-kit": "^0.31.9",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"tailwindcss": "^4",
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import Link from "next/link";
|
||||
|
||||
const pillars = [
|
||||
{
|
||||
title: "System-Level Products",
|
||||
text: "ClipMart listings package reporting chains, approvals, and operating defaults so teams launch as systems, not isolated prompts.",
|
||||
},
|
||||
{
|
||||
title: "Open-Source Foundation",
|
||||
text: "Everything is designed for transparent operations and auditable behavior within Paperclip's governance model.",
|
||||
},
|
||||
{
|
||||
title: "Outcome-Oriented Teams",
|
||||
text: "Each blueprint is evaluated on practical output: shipping software, running growth loops, and resolving customer work reliably.",
|
||||
},
|
||||
];
|
||||
|
||||
const principles = [
|
||||
"Role clarity before model tuning",
|
||||
"Governance built-in, never bolted on",
|
||||
"Reusable skills over one-off prompts",
|
||||
"Install paths that keep local environments clean",
|
||||
];
|
||||
|
||||
export const metadata = {
|
||||
title: "About",
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col gap-14 px-6 py-12 md:gap-18 md:py-16">
|
||||
<section className="grid gap-8 md:grid-cols-[1.2fr_0.8fr] md:items-end">
|
||||
<div className="fade-up space-y-5">
|
||||
<p className="section-label">About ClipMart</p>
|
||||
<h1 className="section-heading text-5xl leading-[1.03] md:text-7xl">
|
||||
The marketplace for autonomous company architecture.
|
||||
</h1>
|
||||
<p className="max-w-2xl text-lg text-[var(--ink-700)] md:text-xl">
|
||||
We built ClipMart to let teams skip months of org design and operational trial-and-error.
|
||||
Every listing is a deployable operating model for Paperclip companies.
|
||||
</p>
|
||||
</div>
|
||||
<div className="panel fade-up stagger-2 rounded-3xl p-5">
|
||||
<p className="section-label">What We Ship</p>
|
||||
<ul className="mt-4 space-y-2 text-sm text-[var(--ink-800)]">
|
||||
<li>Org charts with explicit manager chains</li>
|
||||
<li>Agent prompts and reusable skill packs</li>
|
||||
<li>Governance defaults for approvals and budgets</li>
|
||||
<li>Project templates with workspace configuration</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-6">
|
||||
<p className="section-label fade-up">Core Pillars</p>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{pillars.map((pillar, index) => (
|
||||
<article
|
||||
key={pillar.title}
|
||||
className={`panel fade-up rounded-3xl p-5 ${index === 0 ? "stagger-1" : index === 1 ? "stagger-2" : "stagger-3"}`}
|
||||
>
|
||||
<h2 className="font-[family-name:var(--font-display)] text-3xl text-[var(--ink-950)]">
|
||||
{pillar.title}
|
||||
</h2>
|
||||
<p className="mt-3 text-sm text-[var(--ink-700)]">{pillar.text}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-6 rounded-3xl bg-[var(--ink-950)] p-6 text-[var(--sand-50)] md:grid-cols-[1fr_1.1fr] md:p-8">
|
||||
<div className="fade-up">
|
||||
<p className="font-[family-name:var(--font-mono)] text-xs tracking-[0.14em] uppercase text-[var(--sand-200)]">
|
||||
Product Principles
|
||||
</p>
|
||||
<h2 className="mt-2 font-[family-name:var(--font-display)] text-4xl md:text-5xl">
|
||||
How we evaluate every listing
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="space-y-3">
|
||||
{principles.map((item, index) => (
|
||||
<li
|
||||
key={item}
|
||||
className={`fade-up rounded-2xl border border-white/20 bg-white/6 px-4 py-3 text-sm ${
|
||||
index < 2 ? "stagger-2" : "stagger-3"
|
||||
}`}
|
||||
>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="panel fade-up stagger-4 rounded-3xl p-6 md:p-8">
|
||||
<p className="section-label">Build With Us</p>
|
||||
<h2 className="section-heading mt-2 text-4xl md:text-5xl">
|
||||
Publishing proven operating models is the new leverage.
|
||||
</h2>
|
||||
<p className="mt-3 max-w-2xl text-[var(--ink-700)]">
|
||||
If you run a high-performing Paperclip company, package your setup and publish it to
|
||||
ClipMart. We handle discovery, delivery, and creator monetization rails.
|
||||
</p>
|
||||
<div className="mt-5 flex flex-wrap gap-3">
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="rounded-full bg-[var(--ink-950)] px-6 py-3 text-sm font-bold text-[var(--sand-50)] transition hover:bg-[var(--accent-700)]"
|
||||
>
|
||||
View Pricing
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="rounded-full border border-[var(--ink-300)] bg-white/80 px-6 py-3 text-sm font-bold text-[var(--ink-900)] transition hover:border-[var(--ink-900)]"
|
||||
>
|
||||
Back To Homepage
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export default function BrowsePage() {
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">Browse Marketplace</h1>
|
||||
<p className="mt-2 text-gray-600">
|
||||
Discover team blueprints, agent configs, skills, and governance
|
||||
templates.
|
||||
</p>
|
||||
<div className="mt-8 text-sm text-gray-400">
|
||||
Listings will appear here once the database is connected.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+79
-14
@@ -1,26 +1,91 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--sand-50: #f8f2e8;
|
||||
--sand-100: #f3e7d5;
|
||||
--sand-200: #ebdcc7;
|
||||
|
||||
--ink-50: #e6eaef;
|
||||
--ink-200: #b9c2cf;
|
||||
--ink-700: #3a4958;
|
||||
--ink-800: #2d3b4a;
|
||||
--ink-900: #1f2a36;
|
||||
--ink-950: #101922;
|
||||
|
||||
--accent-500: #2b7a6b;
|
||||
--accent-700: #1f5d52;
|
||||
--ember-500: #f18e49;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
color: var(--ink-900);
|
||||
font-family: var(--font-sans), sans-serif;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.grain {
|
||||
opacity: 0.2;
|
||||
background-image: radial-gradient(circle at 1px 1px, rgba(16, 25, 34, 0.12) 1px, transparent 0);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border: 1px solid color-mix(in oklab, var(--ink-200) 75%, white 25%);
|
||||
background: color-mix(in oklab, white 72%, var(--sand-100) 28%);
|
||||
box-shadow: 0 14px 32px rgba(16, 25, 34, 0.08);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
color: var(--accent-700);
|
||||
font-family: var(--font-mono), monospace;
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
color: var(--ink-950);
|
||||
font-family: var(--font-display), serif;
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
|
||||
@keyframes rise-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(18px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-up {
|
||||
animation: rise-in 640ms cubic-bezier(0.22, 0.8, 0.26, 1) both;
|
||||
}
|
||||
|
||||
.stagger-1 {
|
||||
animation-delay: 70ms;
|
||||
}
|
||||
|
||||
.stagger-2 {
|
||||
animation-delay: 140ms;
|
||||
}
|
||||
|
||||
.stagger-3 {
|
||||
animation-delay: 210ms;
|
||||
}
|
||||
|
||||
.stagger-4 {
|
||||
animation-delay: 280ms;
|
||||
}
|
||||
+69
-11
@@ -1,22 +1,39 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import Link from "next/link";
|
||||
import { Fraunces, Manrope, Space_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
const display = Fraunces({
|
||||
variable: "--font-display",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
const sans = Manrope({
|
||||
variable: "--font-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const mono = Space_Mono({
|
||||
variable: "--font-mono",
|
||||
weight: ["400", "700"],
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: {
|
||||
default: "ClipMart",
|
||||
template: "%s | ClipMart",
|
||||
},
|
||||
description:
|
||||
"Marketplace for full Paperclip company blueprints, agent packs, and operational skills.",
|
||||
};
|
||||
|
||||
const navigation = [
|
||||
{ href: "/", label: "Home" },
|
||||
{ href: "/about", label: "About" },
|
||||
{ href: "/pricing", label: "Pricing" },
|
||||
];
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
@@ -24,10 +41,51 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
<body className={`${display.variable} ${sans.variable} ${mono.variable}`}>
|
||||
<div className="pointer-events-none fixed inset-0 -z-10 bg-[radial-gradient(circle_at_15%_15%,rgba(43,122,107,0.14),transparent_38%),radial-gradient(circle_at_82%_22%,rgba(241,142,73,0.2),transparent_35%),linear-gradient(170deg,#f7f0e6_0%,#f2e8d9_48%,#f7f2e8_100%)]" />
|
||||
<div className="grain fixed inset-0 -z-10" />
|
||||
|
||||
<header className="sticky top-0 z-20 border-b border-[var(--ink-200)]/70 bg-[var(--sand-50)]/85 backdrop-blur">
|
||||
<div className="mx-auto flex w-full max-w-6xl items-center justify-between px-6 py-4">
|
||||
<Link href="/" className="font-[family-name:var(--font-display)] text-2xl font-semibold tracking-tight text-[var(--ink-950)]">
|
||||
ClipMart
|
||||
</Link>
|
||||
<nav className="flex items-center gap-1 rounded-full border border-[var(--ink-300)]/80 bg-white/50 p-1 text-sm">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="rounded-full px-4 py-1.5 font-semibold text-[var(--ink-700)] transition hover:bg-[var(--ink-900)] hover:text-[var(--sand-50)]"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>{children}</main>
|
||||
|
||||
<footer className="border-t border-[var(--ink-200)] bg-[var(--sand-100)]/80">
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-6 py-10 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<p className="font-[family-name:var(--font-display)] text-3xl text-[var(--ink-950)]">
|
||||
Open-source orchestration for zero-human companies.
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-[var(--ink-700)]">
|
||||
Build faster with complete operating teams, not disconnected prompts.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-4 text-sm font-semibold text-[var(--ink-800)]">
|
||||
<Link href="/about" className="hover:text-[var(--accent-700)]">
|
||||
About
|
||||
</Link>
|
||||
<Link href="/pricing" className="hover:text-[var(--accent-700)]">
|
||||
Pricing
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
+215
-56
@@ -1,65 +1,224 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Home() {
|
||||
const featuredBlueprints = [
|
||||
{
|
||||
name: "SaaS Launch Crew",
|
||||
tagline: "CEO + CTO + 3 specialists pre-wired to ship a funded MVP in days.",
|
||||
installs: 324,
|
||||
agents: 5,
|
||||
price: "$199",
|
||||
},
|
||||
{
|
||||
name: "Support Ops Grid",
|
||||
tagline: "Ticket triage, escalation, and customer comms with built-in governance rules.",
|
||||
installs: 188,
|
||||
agents: 7,
|
||||
price: "$149",
|
||||
},
|
||||
{
|
||||
name: "Growth Studio Team",
|
||||
tagline: "End-to-end content and demand generation with analytics accountability.",
|
||||
installs: 271,
|
||||
agents: 6,
|
||||
price: "$179",
|
||||
},
|
||||
];
|
||||
|
||||
const popularSkills = [
|
||||
"PR Quality Gate",
|
||||
"Incident Escalation",
|
||||
"Stripe Revenue Digest",
|
||||
"Customer Voice Classifier",
|
||||
"Spec-to-Story Mapper",
|
||||
"Release Readiness Report",
|
||||
];
|
||||
|
||||
const categories = [
|
||||
"Engineering",
|
||||
"Marketing",
|
||||
"Customer Success",
|
||||
"Finance",
|
||||
"Ops",
|
||||
"Product",
|
||||
"Compliance",
|
||||
"Founders",
|
||||
];
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: "Browse",
|
||||
copy: "Find complete company templates built for your target stage and team shape.",
|
||||
},
|
||||
{
|
||||
title: "Install",
|
||||
copy: "Deploy the org chart, governance rules, and project defaults into your Paperclip company.",
|
||||
},
|
||||
{
|
||||
title: "Run",
|
||||
copy: "Launch with clear reporting lines, approvals, and production-ready execution loops.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col gap-16 px-6 py-12 md:gap-24 md:py-18">
|
||||
<section className="grid gap-8 md:grid-cols-[1.15fr_0.85fr] md:items-end">
|
||||
<div className="fade-up space-y-6">
|
||||
<p className="section-label">Marketplace For Autonomous Teams</p>
|
||||
<h1 className="section-heading max-w-2xl text-5xl leading-[1.02] md:text-7xl">
|
||||
Buy a company that is ready to ship.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
<p className="max-w-xl text-lg text-[var(--ink-700)] md:text-xl">
|
||||
ClipMart is where founders install full Paperclip operating teams: org charts,
|
||||
skills, approvals, and project templates bundled as one deployable blueprint.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3 pt-2">
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="rounded-full bg-[var(--ink-950)] px-6 py-3 text-sm font-bold text-[var(--sand-50)] transition hover:bg-[var(--accent-700)]"
|
||||
>
|
||||
Explore Pricing
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
className="rounded-full border border-[var(--ink-300)] bg-white/80 px-6 py-3 text-sm font-bold text-[var(--ink-900)] transition hover:border-[var(--ink-900)]"
|
||||
>
|
||||
How ClipMart Works
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
|
||||
<aside className="panel fade-up stagger-2 rounded-3xl p-5 md:p-6">
|
||||
<p className="section-label">Live Blueprint Snapshot</p>
|
||||
<h2 className="mt-3 font-[family-name:var(--font-display)] text-3xl text-[var(--ink-950)]">
|
||||
Product Team Accelerator
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-[var(--ink-700)]">
|
||||
6-agent structure with shipping workflows and decision checkpoints pre-configured.
|
||||
</p>
|
||||
<div className="mt-5 rounded-2xl border border-[var(--ink-200)] bg-[var(--sand-50)] p-4">
|
||||
<p className="text-xs font-bold tracking-[0.15em] text-[var(--accent-700)] uppercase">
|
||||
Org Shape
|
||||
</p>
|
||||
<ul className="mt-3 space-y-2 text-sm text-[var(--ink-900)]">
|
||||
<li>CEO {'->'} CTO {'->'} Engineering Lead</li>
|
||||
<li>CMO {'->'} Content Strategist</li>
|
||||
<li>COO {'->'} Ops Analyst</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-between rounded-2xl bg-[var(--ink-950)] px-4 py-3 text-sm text-[var(--sand-50)]">
|
||||
<span>Install-ready package</span>
|
||||
<strong>$189</strong>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section className="space-y-6">
|
||||
<div className="fade-up">
|
||||
<p className="section-label">Featured Blueprints</p>
|
||||
<h2 className="section-heading mt-2 text-4xl md:text-5xl">Teams people install first</h2>
|
||||
</div>
|
||||
</main>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{featuredBlueprints.map((item, index) => (
|
||||
<article
|
||||
key={item.name}
|
||||
className={`panel fade-up rounded-3xl p-5 ${index === 0 ? "stagger-1" : index === 1 ? "stagger-2" : "stagger-3"}`}
|
||||
>
|
||||
<h3 className="font-[family-name:var(--font-display)] text-2xl text-[var(--ink-950)]">
|
||||
{item.name}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-[var(--ink-700)]">{item.tagline}</p>
|
||||
<div className="mt-5 flex items-center justify-between text-sm text-[var(--ink-800)]">
|
||||
<span>{item.agents} agents</span>
|
||||
<span>{item.installs} installs</span>
|
||||
<strong className="font-[family-name:var(--font-mono)] text-[var(--ink-950)]">
|
||||
{item.price}
|
||||
</strong>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 rounded-3xl bg-[var(--ink-950)] p-6 text-[var(--sand-50)] md:grid-cols-[1fr_1.3fr] md:p-8">
|
||||
<div className="fade-up">
|
||||
<p className="font-[family-name:var(--font-mono)] text-xs tracking-[0.14em] uppercase text-[var(--sand-200)]">
|
||||
Popular Skills
|
||||
</p>
|
||||
<h2 className="mt-2 font-[family-name:var(--font-display)] text-4xl">Operational modules you can plug in</h2>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{popularSkills.map((skill, index) => (
|
||||
<div
|
||||
key={skill}
|
||||
className={`fade-up rounded-2xl border border-white/18 bg-white/6 px-4 py-3 text-sm ${index % 2 === 0 ? "stagger-2" : "stagger-3"}`}
|
||||
>
|
||||
{skill}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-6">
|
||||
<div className="fade-up">
|
||||
<p className="section-label">How It Works</p>
|
||||
<h2 className="section-heading mt-2 text-4xl md:text-5xl">Browse, install, run.</h2>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{steps.map((step, index) => (
|
||||
<article
|
||||
key={step.title}
|
||||
className={`panel fade-up rounded-3xl p-5 ${index === 0 ? "stagger-1" : index === 1 ? "stagger-2" : "stagger-3"}`}
|
||||
>
|
||||
<p className="font-[family-name:var(--font-mono)] text-xs tracking-[0.14em] uppercase text-[var(--accent-700)]">
|
||||
0{index + 1}
|
||||
</p>
|
||||
<h3 className="mt-3 font-[family-name:var(--font-display)] text-3xl text-[var(--ink-950)]">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-[var(--ink-700)]">{step.copy}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-5">
|
||||
<div className="fade-up">
|
||||
<p className="section-label">Categories</p>
|
||||
<h2 className="section-heading mt-2 text-4xl md:text-5xl">Blueprints for every function</h2>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{categories.map((category, index) => (
|
||||
<span
|
||||
key={category}
|
||||
className={`fade-up rounded-full border border-[var(--ink-200)] bg-white/70 px-4 py-2 text-sm font-semibold text-[var(--ink-800)] ${index < 4 ? "stagger-2" : "stagger-3"}`}
|
||||
>
|
||||
{category}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="panel fade-up stagger-4 rounded-3xl p-6 md:p-8">
|
||||
<p className="section-label">Creator Program</p>
|
||||
<div className="mt-2 grid gap-6 md:grid-cols-[1fr_auto] md:items-end">
|
||||
<div>
|
||||
<h2 className="section-heading text-4xl md:text-5xl">
|
||||
Publish your team architecture. Get paid when companies install it.
|
||||
</h2>
|
||||
<p className="mt-3 max-w-2xl text-[var(--ink-700)]">
|
||||
Upload your blueprint bundle, set pricing in minutes, and earn each time another
|
||||
company launches with your playbook.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/about"
|
||||
className="inline-flex w-fit rounded-full bg-[var(--ember-500)] px-6 py-3 text-sm font-bold text-[var(--ink-950)] transition hover:bg-[var(--accent-500)] hover:text-[var(--sand-50)]"
|
||||
>
|
||||
Become A Creator
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import Link from "next/link";
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: "Explorer",
|
||||
price: "$0",
|
||||
cadence: "always free",
|
||||
details: [
|
||||
"Browse and install free blueprints",
|
||||
"Save favorites and compare packages",
|
||||
"Public creator profile support",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Creator",
|
||||
price: "10%",
|
||||
cadence: "platform fee per paid sale",
|
||||
details: [
|
||||
"You keep 90% of paid listing revenue",
|
||||
"Stripe payout rails and reporting",
|
||||
"Priority review for featured placements",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Team Launch",
|
||||
price: "$49",
|
||||
cadence: "per company / month",
|
||||
details: [
|
||||
"Private listing collections",
|
||||
"Install history and compliance audit view",
|
||||
"Cross-workspace deployment controls",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const feeRows = [
|
||||
{ label: "Marketplace fee", value: "10% on paid listings" },
|
||||
{ label: "Creator payout", value: "90% to creator account" },
|
||||
{ label: "Free listings", value: "No platform fee" },
|
||||
{ label: "Buyer transaction", value: "Price shown on listing" },
|
||||
];
|
||||
|
||||
export const metadata = {
|
||||
title: "Pricing",
|
||||
};
|
||||
|
||||
export default function PricingPage() {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col gap-14 px-6 py-12 md:gap-18 md:py-16">
|
||||
<section className="fade-up space-y-5">
|
||||
<p className="section-label">Pricing</p>
|
||||
<h1 className="section-heading max-w-3xl text-5xl leading-[1.03] md:text-7xl">
|
||||
Straightforward economics for buyers and creators.
|
||||
</h1>
|
||||
<p className="max-w-2xl text-lg text-[var(--ink-700)] md:text-xl">
|
||||
Buy only what you need, or monetize proven company setups with a transparent revenue split.
|
||||
No hidden infrastructure tax.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
{plans.map((plan, index) => (
|
||||
<article
|
||||
key={plan.name}
|
||||
className={`panel fade-up rounded-3xl p-5 ${index === 0 ? "stagger-1" : index === 1 ? "stagger-2" : "stagger-3"}`}
|
||||
>
|
||||
<p className="section-label">{plan.name}</p>
|
||||
<p className="mt-3 font-[family-name:var(--font-display)] text-5xl text-[var(--ink-950)]">
|
||||
{plan.price}
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-[var(--ink-700)]">{plan.cadence}</p>
|
||||
<ul className="mt-5 space-y-2 text-sm text-[var(--ink-800)]">
|
||||
{plan.details.map((detail) => (
|
||||
<li key={detail}>• {detail}</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="grid gap-6 rounded-3xl bg-[var(--ink-950)] p-6 text-[var(--sand-50)] md:grid-cols-[0.95fr_1.05fr] md:p-8">
|
||||
<div className="fade-up">
|
||||
<p className="font-[family-name:var(--font-mono)] text-xs tracking-[0.14em] uppercase text-[var(--sand-200)]">
|
||||
Revenue Model
|
||||
</p>
|
||||
<h2 className="mt-2 font-[family-name:var(--font-display)] text-4xl md:text-5xl">
|
||||
Creator-first split, platform-level reliability.
|
||||
</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{feeRows.map((row, index) => (
|
||||
<div
|
||||
key={row.label}
|
||||
className={`fade-up flex items-center justify-between rounded-2xl border border-white/20 bg-white/6 px-4 py-3 text-sm ${
|
||||
index < 2 ? "stagger-2" : "stagger-3"
|
||||
}`}
|
||||
>
|
||||
<span>{row.label}</span>
|
||||
<strong>{row.value}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="panel fade-up stagger-4 rounded-3xl p-6 md:p-8">
|
||||
<p className="section-label">Ready To Launch</p>
|
||||
<h2 className="section-heading mt-2 text-4xl md:text-5xl">
|
||||
Start with free blueprints, then scale into paid operating systems.
|
||||
</h2>
|
||||
<p className="mt-3 max-w-2xl text-[var(--ink-700)]">
|
||||
Every purchase includes install-ready structures for Paperclip companies, so you can deploy
|
||||
and adapt immediately.
|
||||
</p>
|
||||
<div className="mt-5 flex flex-wrap gap-3">
|
||||
<Link
|
||||
href="/"
|
||||
className="rounded-full bg-[var(--ink-950)] px-6 py-3 text-sm font-bold text-[var(--sand-50)] transition hover:bg-[var(--accent-700)]"
|
||||
>
|
||||
Browse Blueprints
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
className="rounded-full border border-[var(--ink-300)] bg-white/80 px-6 py-3 text-sm font-bold text-[var(--ink-900)] transition hover:border-[var(--ink-900)]"
|
||||
>
|
||||
Learn More
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-gray-200 bg-white">
|
||||
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col items-center justify-between gap-4 sm:flex-row">
|
||||
<p className="text-sm text-gray-500">
|
||||
ClipMart — The marketplace for Paperclip team blueprints
|
||||
</p>
|
||||
<div className="flex gap-6">
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-sm text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
About
|
||||
</Link>
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="text-sm text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
Pricing
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header className="border-b border-gray-200 bg-white">
|
||||
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center gap-8">
|
||||
<Link href="/" className="text-xl font-bold text-gray-900">
|
||||
ClipMart
|
||||
</Link>
|
||||
<nav className="hidden items-center gap-6 md:flex">
|
||||
<Link
|
||||
href="/browse"
|
||||
className="text-sm font-medium text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
Browse
|
||||
</Link>
|
||||
<Link
|
||||
href="/browse?type=team_blueprint"
|
||||
className="text-sm font-medium text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
Teams
|
||||
</Link>
|
||||
<Link
|
||||
href="/browse?type=skill"
|
||||
className="text-sm font-medium text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
Skills
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Link
|
||||
href="/sign-in"
|
||||
className="text-sm font-medium text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
Sign in
|
||||
</Link>
|
||||
<Link
|
||||
href="/sign-up"
|
||||
className="rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800"
|
||||
>
|
||||
Get started
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "./schema";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
|
||||
const client = postgres(connectionString);
|
||||
export const db = drizzle(client, { schema });
|
||||
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
uuid,
|
||||
varchar,
|
||||
numeric,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
// ── better-auth tables ──────────────────────────────────────────────
|
||||
|
||||
export const user = pgTable("user", {
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
email: text("email").notNull().unique(),
|
||||
emailVerified: boolean("email_verified").notNull(),
|
||||
image: text("image"),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
updatedAt: timestamp("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const session = pgTable("session", {
|
||||
id: text("id").primaryKey(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
token: text("token").notNull().unique(),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
updatedAt: timestamp("updated_at").notNull(),
|
||||
ipAddress: text("ip_address"),
|
||||
userAgent: text("user_agent"),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
});
|
||||
|
||||
export const account = pgTable("account", {
|
||||
id: text("id").primaryKey(),
|
||||
accountId: text("account_id").notNull(),
|
||||
providerId: text("provider_id").notNull(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
idToken: text("id_token"),
|
||||
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
||||
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
||||
scope: text("scope"),
|
||||
password: text("password"),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
updatedAt: timestamp("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const verification = pgTable("verification", {
|
||||
id: text("id").primaryKey(),
|
||||
identifier: text("identifier").notNull(),
|
||||
value: text("value").notNull(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
createdAt: timestamp("created_at"),
|
||||
updatedAt: timestamp("updated_at"),
|
||||
});
|
||||
|
||||
// ── ClipMart domain tables ──────────────────────────────────────────
|
||||
|
||||
export const listings = pgTable("listings", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||
type: varchar("type", { length: 50 }).notNull(), // team_blueprint, agent_blueprint, skill, governance_template
|
||||
title: varchar("title", { length: 255 }).notNull(),
|
||||
tagline: varchar("tagline", { length: 120 }),
|
||||
description: text("description"),
|
||||
price: integer("price").notNull().default(0), // cents
|
||||
creatorId: uuid("creator_id")
|
||||
.notNull()
|
||||
.references(() => creators.id),
|
||||
categories: jsonb("categories").$type<string[]>().default([]),
|
||||
tags: jsonb("tags").$type<string[]>().default([]),
|
||||
agentCount: integer("agent_count"),
|
||||
previewImages: jsonb("preview_images").$type<string[]>().default([]),
|
||||
readmeMarkdown: text("readme_markdown"),
|
||||
includedFiles: jsonb("included_files").$type<string[]>().default([]),
|
||||
compatibleAdapters: jsonb("compatible_adapters").$type<string[]>().default([]),
|
||||
requiredModels: jsonb("required_models").$type<string[]>().default([]),
|
||||
installCount: integer("install_count").notNull().default(0),
|
||||
rating: numeric("rating", { precision: 2, scale: 1 }),
|
||||
reviewCount: integer("review_count").notNull().default(0),
|
||||
version: varchar("version", { length: 20 }).default("1.0.0"),
|
||||
status: varchar("status", { length: 20 }).notNull().default("draft"), // draft, published, archived
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const creators = pgTable("creators", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
displayName: varchar("display_name", { length: 100 }).notNull(),
|
||||
bio: text("bio"),
|
||||
avatarUrl: text("avatar_url"),
|
||||
website: text("website"),
|
||||
totalInstalls: integer("total_installs").notNull().default(0),
|
||||
totalRevenue: integer("total_revenue").notNull().default(0), // cents
|
||||
verified: boolean("verified").notNull().default(false),
|
||||
stripeAccountId: text("stripe_account_id"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const purchases = pgTable("purchases", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
listingId: uuid("listing_id")
|
||||
.notNull()
|
||||
.references(() => listings.id),
|
||||
buyerUserId: text("buyer_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
buyerCompanyId: text("buyer_company_id"),
|
||||
pricePaidCents: integer("price_paid_cents").notNull().default(0),
|
||||
status: varchar("status", { length: 20 }).notNull().default("pending"), // pending, completed, refunded
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const reviews = pgTable("reviews", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
listingId: uuid("listing_id")
|
||||
.notNull()
|
||||
.references(() => listings.id),
|
||||
authorUserId: text("author_user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
rating: integer("rating").notNull(), // 1-5
|
||||
title: varchar("title", { length: 255 }),
|
||||
body: text("body"),
|
||||
verifiedPurchase: boolean("verified_purchase").notNull().default(false),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const teamBlueprints = pgTable("team_blueprints", {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
listingId: uuid("listing_id")
|
||||
.notNull()
|
||||
.references(() => listings.id),
|
||||
agents: jsonb("agents").notNull(),
|
||||
reportingChain: jsonb("reporting_chain").notNull(),
|
||||
governance: jsonb("governance"),
|
||||
projects: jsonb("projects"),
|
||||
companyDefaults: jsonb("company_defaults"),
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { auth } from "@/lib/auth";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export async function getSession() {
|
||||
return auth.api.getSession({
|
||||
headers: await headers(),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSessionUserId(): Promise<string | null> {
|
||||
const session = await getSession();
|
||||
return session?.user?.id ?? null;
|
||||
}
|
||||
|
||||
export async function requireSession() {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
throw new Error("Unauthorized");
|
||||
}
|
||||
return session;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL ?? "http://localhost:3000",
|
||||
});
|
||||
|
||||
export const { signIn, signUp, signOut, useSession } = authClient;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { db } from "@/db";
|
||||
import * as schema from "@/db/schema";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "pg",
|
||||
schema,
|
||||
}),
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
export function slugify(input: string): string {
|
||||
const normalized = input
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9\s-]/g, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
|
||||
return normalized || "untitled";
|
||||
}
|
||||
|
||||
export function parseTagInput(input: unknown): string[] {
|
||||
if (Array.isArray(input)) {
|
||||
return input
|
||||
.map((value) => String(value).trim())
|
||||
.filter(Boolean)
|
||||
.slice(0, 20);
|
||||
}
|
||||
|
||||
if (typeof input === "string") {
|
||||
return input
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean)
|
||||
.slice(0, 20);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
Reference in new issue
Block a user