Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
339 changes: 335 additions & 4 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@
"postinstall": "prisma generate"
},
"dependencies": {
"@clerk/nextjs": "^6.12.0",
"@faker-js/faker": "^9.5.0",
"@paralleldrive/cuid2": "^2.2.2",
"@prisma/client": "^6.3.1",
"clsx": "^2.1.1",
"framer-motion": "^12.4.7",
"next": "15.1.7",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
Expand Down
28 changes: 19 additions & 9 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

import { ClerkProvider, SignedOut, SignInButton, SignedIn, UserButton } from '@clerk/nextjs';

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
Expand All @@ -11,16 +13,24 @@ export const metadata: Metadata = {

export default function RootLayout({
children,
}: Readonly<{
}: {
children: React.ReactNode;
}>) {
}) {
return (
<html lang="en" className="dark">
<body
className={`${inter.className} bg-gray-900 text-gray-100 min-h-screen`}
>
{children}
</body>
</html>
<ClerkProvider>
<html lang="en" className="dark">
<body className={`${inter.className} bg-gray-900 text-gray-100 min-h-screen`}>
<div className="absolute top-4 right-4">
<SignedOut>
<SignInButton />
</SignedOut>
<SignedIn>
<UserButton afterSignOutUrl="/"/>
</SignedIn>
</div>
{children}
</body>
</html>
</ClerkProvider>
);
}
140 changes: 92 additions & 48 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,67 +7,111 @@ const ROOT_URL = process.env.ROOT_SITE;

export default async function Home() {
const projects = (await getProjects()) ?? [];
console.log(projects);
const baseUrl = ROOT_URL;

return (
<div className="min-h-screen bg-gray-900">
<div className="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h1 className="text-4xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400 sm:text-5xl md:text-6xl">
Create Your Mock API
<div className="min-h-screen bg-gradient-to-b from-gray-900 to-gray-800">
<div className="max-w-7xl mx-auto py-16 px-4 sm:px-6 lg:px-8">
{/* Hero Section with Nextra-style */}
<div className="text-center mb-16">
<h1 className="text-6xl font-extrabold tracking-tight text-white mb-4">
Mock<span className="text-blue-500">API</span>
</h1>
<p className="mt-3 max-w-md mx-auto text-base text-gray-400 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
Design, create and manage your mock APIs with ease. Generate
realistic data for your frontend development.
<p className="mt-4 text-xl text-gray-400 max-w-2xl mx-auto">
Build and test your frontend with realistic mock APIs. Generate code for Next.js, Express, and FastAPI.
</p>
<div className="mt-5 max-w-md mx-auto sm:flex sm:justify-center md:mt-8">
<div className="rounded-md shadow">
<Link
href="/projects/new"
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors duration-200 md:py-4 md:text-lg md:px-10"
>
Create New Project
</Link>
<div className="mt-8 flex justify-center gap-4">
<Link
href="/projects/new"
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 transition-all duration-200 shadow-lg hover:shadow-blue-500/20"
>
Create New Project
<svg className="ml-2 -mr-1 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</Link>
<Link
href="/docs"
className="inline-flex items-center px-6 py-3 border border-gray-700 text-base font-medium rounded-lg text-gray-300 hover:bg-gray-800 transition-all duration-200"
>
Documentation
<svg className="ml-2 -mr-1 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link>
</div>
</div>

{/* Features Grid with Nextra-style cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-16">
<div className="p-6 rounded-xl border border-gray-700/50 bg-gray-800/30 hover:border-blue-500/30 hover:bg-gray-800/50 transition-all duration-200">
<div className="text-blue-400 mb-4">
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-white mb-2">Instant API Generation</h3>
<p className="text-gray-400">Generate RESTful APIs with realistic mock data in seconds.</p>
</div>

<div className="p-6 rounded-xl border border-gray-700/50 bg-gray-800/30 hover:border-purple-500/30 hover:bg-gray-800/50 transition-all duration-200">
<div className="text-purple-400 mb-4">
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
</div>
<h3 className="text-lg font-semibold text-white mb-2">Realistic Data</h3>
<p className="text-gray-400">Powered by Faker.js for realistic and customizable mock data.</p>
</div>

<div className="p-6 rounded-xl border border-gray-700/50 bg-gray-800/30 hover:border-green-500/30 hover:bg-gray-800/50 transition-all duration-200">
<div className="text-green-400 mb-4">
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-white mb-2">Code Generation</h3>
<p className="text-gray-400">Get ready-to-use code for your favorite frameworks.</p>
</div>
</div>

<div className="bg-gray-800 shadow-xl overflow-hidden sm:rounded-lg mt-8 border border-gray-700">
<ul className="divide-y divide-gray-700">
{/* Projects List with Nextra-style */}
<div className="rounded-xl border border-gray-700/50 bg-gray-800/30 overflow-hidden">
<div className="px-6 py-4 border-b border-gray-700/50">
<h2 className="text-xl font-semibold text-white">Your Projects</h2>
</div>
<div className="divide-y divide-gray-700/50">
{projects.map((project) => (
<li key={project.id}>
<Link href={`/projects/${project.id}`}>
<div className="px-4 py-4 flex items-center hover:bg-gray-700/50 transition-colors duration-200">
<div className="min-w-0 flex-1">
<h3 className="text-lg font-medium text-gray-100">
{project.name}
</h3>
<p className="mt-1 text-sm text-gray-400">
{project.resources.length} resources
</p>
<p className="mt-1 text-xs text-gray-500">
Base URL: {baseUrl}/{project.id}
</p>
</div>
<Link
key={project.id}
href={`/projects/${project.id}`}
className="block hover:bg-gray-700/30 transition-colors duration-200"
>
<div className="px-6 py-4">
<div className="flex items-center justify-between">
<div>
<svg
className="h-5 w-5 text-gray-500"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clipRule="evenodd"
/>
</svg>
<h3 className="text-lg font-medium text-white">{project.name}</h3>
<div className="mt-1 flex items-center gap-4">
<p className="text-sm text-gray-400">
{project.resources.length} resources
</p>
<code className="text-xs px-2 py-1 rounded-md bg-gray-700/50 text-gray-300">
{ROOT_URL}/{project.id}
</code>
</div>
</div>
<svg className="w-5 h-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div>
</Link>
</li>
</div>
</Link>
))}
</ul>
{projects.length === 0 && (
<div className="px-6 py-8 text-center">
<p className="text-gray-400">No projects yet. Create your first project to get started.</p>
</div>
)}
</div>
</div>
</div>
</div>
Expand Down
138 changes: 87 additions & 51 deletions src/app/projects/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { notFound } from "next/navigation";
import { getProject } from "@/server/actions/projects";
import { DeleteResourceButton } from "@/app/resources/DeleteResourceButton";
import { DeleteProjectButton } from "@/app/projects/DeleteProjectButton";
import { CopyButton } from "@/components/common/CopyButton";
import type { Resource as PrismaResource } from "@prisma/client";

const ROOT_URL = process.env.ROOT_SITE;

interface Resource {
id: string;
name: string;
endpoint: string;
interface Resource extends PrismaResource {
_count?: {
data: number;
};
}

interface PageProps {
Expand All @@ -33,60 +35,94 @@ export default async function ProjectPage({ params }: PageProps) {
};

return (
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-semibold text-gray-100">
{project.name}
</h1>
<p className="mt-1 text-sm text-gray-400">Base URL: {apiBaseUrl}</p>
</div>
<div className="flex items-center gap-4">
<DeleteProjectButton id={project.id} />
<Link
href={`/projects/${project.id}/resources/new`}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-lg text-white bg-blue-600 hover:bg-blue-700 transition-colors duration-200"
>
Create Resource
<div className="min-h-screen bg-gradient-to-b from-gray-900 to-gray-800">
<div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8">
{/* Project Header */}
<div className="mb-8">
<div className="flex items-center gap-2 text-sm text-gray-400 mb-2">
<Link href="/" className="hover:text-gray-300 transition-colors">
Projects
</Link>
<span>/</span>
<span className="text-gray-300">{project.name}</span>
</div>
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-white mb-2">{project.name}</h1>
<div className="flex items-center gap-2 text-sm">
<code className="px-2 py-1 bg-gray-800 rounded-md text-gray-300 font-mono">
{apiBaseUrl}
</code>
<CopyButton text={apiBaseUrl} />
</div>
</div>
<div className="flex items-center gap-3">
<DeleteProjectButton id={project.id} />
<Link
href={`/projects/${project.id}/resources/new`}
className="inline-flex items-center px-4 py-2 rounded-lg text-white bg-blue-600 hover:bg-blue-700 transition-all duration-200 shadow-lg hover:shadow-blue-500/20"
>
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
New Resource
</Link>
</div>
</div>
</div>

<div className="bg-gray-800 shadow-xl overflow-hidden sm:rounded-lg border border-gray-700">
<ul className="divide-y divide-gray-700">
{project.resources.map((resource) => (
<li key={resource.id}>
<div className="px-4 py-4 flex items-center justify-between sm:px-6 hover:bg-gray-700/50 transition-colors duration-200">
<div className="flex-1 min-w-0">
<h3 className="text-lg font-medium text-gray-100">
{resource.name}
</h3>
<p className="mt-1 text-sm text-gray-400">
Endpoint: {apiBaseUrl}/{getResourceEndpoint(resource)}
</p>
</div>
<div className="flex gap-2">
<Link
href={`/projects/${project.id}/resources/${resource.id}`}
className="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
View
</Link>
<DeleteResourceButton resourceId={resource.id} />
{/* Resources List */}
<div className="space-y-4">
{project.resources.map((resource) => (
<div
key={resource.id}
className="p-6 rounded-xl border border-gray-700/50 bg-gray-800/30 hover:border-blue-500/30 hover:bg-gray-800/50 transition-all duration-200"
>
<div className="flex items-center justify-between">
<div>
<h3 className="text-xl font-semibold text-white mb-2">
{resource.name}
</h3>
<div className="flex items-center gap-4">
<code className="text-sm px-2 py-1 bg-gray-800 rounded-md text-gray-300 font-mono">
{apiBaseUrl}/{getResourceEndpoint(resource)}
</code>
<div className="flex items-center gap-2 text-sm text-gray-400">
<span className="w-2 h-2 rounded-full bg-green-500"></span>
{resource._count?.data || 0} records
</div>
</div>
</div>
</li>
))}

{project.resources.length === 0 && (
<li>
<div className="px-4 py-8 text-center text-gray-500">
No resources yet. Create your first resource to get started.
<div className="flex items-center gap-2">
<Link
href={`/projects/${project.id}/resources/${resource.id}`}
className="px-3 py-2 rounded-lg text-gray-300 hover:text-white hover:bg-gray-700/50 transition-all duration-200"
>
<span className="sr-only">View Resource</span>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link>
<DeleteResourceButton resourceId={resource.id} />
</div>
</li>
)}
</ul>
</div>
</div>
))}

{project.resources.length === 0 && (
<div className="text-center py-12">
<svg className="w-12 h-12 text-gray-600 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
<p className="text-gray-400 mb-4">No resources yet</p>
<Link
href={`/projects/${project.id}/resources/new`}
className="inline-flex items-center px-4 py-2 rounded-lg text-white bg-blue-600 hover:bg-blue-700 transition-all duration-200"
>
Create your first resource
</Link>
</div>
)}
</div>
</div>
</div>
Expand Down
Loading