Initial Paprika Next.js implementation

This commit is contained in:
2026-07-06 12:12:36 -04:00
commit 7d585484a6
38 changed files with 14824 additions and 0 deletions

31
app/[page]/page.tsx Normal file
View File

@@ -0,0 +1,31 @@
import { notFound } from "next/navigation";
import { LegacyPage } from "@/components/LegacyPage";
import { legacyPages, type LegacyPageKey } from "@/lib/legacy-pages";
const routablePages = new Set(Object.keys(legacyPages));
const replacedPages = new Set(["home", "start", "ask", "thanks"]);
export function generateStaticParams() {
return Object.keys(legacyPages)
.filter((page) => !replacedPages.has(page))
.map((page) => ({ page }));
}
export async function generateMetadata({ params }: { params: Promise<{ page: string }> }) {
const { page: pageParam } = await params;
if (!routablePages.has(pageParam)) return {};
const page = legacyPages[pageParam as LegacyPageKey];
return {
title: page.title.replace("Paprika - ", "")
};
}
export default async function StaticPage({ params }: { params: Promise<{ page: string }> }) {
const { page } = await params;
if (!routablePages.has(page) || replacedPages.has(page)) {
notFound();
}
return <LegacyPage pageKey={page as LegacyPageKey} />;
}

View File

@@ -0,0 +1,33 @@
import { NextResponse } from "next/server";
import { ZodError } from "zod";
import { createSubmission } from "@/lib/submissions";
import { submissionSchema } from "@/lib/submission-schema";
export async function POST(request: Request) {
try {
const payload = await request.json();
const input = submissionSchema.parse(payload);
const submission = await createSubmission(input);
return NextResponse.json({
submission_id: submission.id,
created_at: submission.createdAt.toISOString()
});
} catch (error) {
if (error instanceof ZodError) {
return NextResponse.json(
{
error: "Validation failed",
issues: error.flatten().fieldErrors
},
{ status: 400 }
);
}
console.error("Submission failed", error);
return NextResponse.json(
{ error: "Submission could not be saved. Please try again." },
{ status: 500 }
);
}
}

29
app/ask/page.tsx Normal file
View File

@@ -0,0 +1,29 @@
import Link from "next/link";
import { SubmissionForm } from "@/components/forms/SubmissionForm";
export const metadata = {
title: "Ask a Question"
};
export default function AskPage() {
return (
<>
<section className="hero subpage press-hero">
<h1>Ask a question.</h1>
<p className="subhead">
Not ready to submit your full profile yet? Send us a quick question. We will help you
understand whether Paprika may be a fit.
</p>
<div className="actions">
<Link className="button primary" href="/start">
Start Here
</Link>
<Link className="button secondary" href="/ask">
Ask a Question
</Link>
</div>
</section>
<SubmissionForm type="ask" />
</>
);
}

2263
app/globals.css Normal file

File diff suppressed because it is too large Load Diff

44
app/layout.tsx Normal file
View File

@@ -0,0 +1,44 @@
import type { Metadata } from "next";
import Link from "next/link";
import { SiteHeader } from "@/components/SiteHeader";
import "./globals.css";
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || "https://paprikalaw.com"),
title: {
default: "Paprika",
template: "Paprika - %s"
},
description:
"Attorney-curated evidence building for O-1 talent through press, judging opportunities, scholarly articles, and documented profile strategy.",
openGraph: {
title: "Paprika",
description:
"Attorney-curated evidence building for O-1 talent through press, judging opportunities, scholarly articles, and documented profile strategy.",
url: "/",
siteName: "Paprika",
type: "website"
}
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<SiteHeader />
<main>{children}</main>
<footer>
<div className="footer-links">
<Link href="/why">About</Link>
<Link href="/faq">FAQ</Link>
<Link href="/terms">Terms</Link>
<Link href="/privacy">Privacy</Link>
<Link href="/disclaimer">Disclaimer</Link>
</div>
<div>Paprika is not a law firm and does not provide legal advice or immigration representation.</div>
<div>Use of our services does not create an attorney-client relationship.</div>
</footer>
</body>
</html>
);
}

5
app/page.tsx Normal file
View File

@@ -0,0 +1,5 @@
import { LegacyPage } from "@/components/LegacyPage";
export default function HomePage() {
return <LegacyPage pageKey="home" />;
}

13
app/robots.ts Normal file
View File

@@ -0,0 +1,13 @@
import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://paprikalaw.com";
return {
rules: {
userAgent: "*",
allow: "/"
},
sitemap: `${siteUrl}/sitemap.xml`
};
}

28
app/sitemap.ts Normal file
View File

@@ -0,0 +1,28 @@
import type { MetadataRoute } from "next";
const routeOrder = [
"home",
"press",
"judging",
"scholarly",
"services",
"why",
"resources",
"faq",
"start",
"ask",
"terms",
"privacy",
"disclaimer"
] as const;
export default function sitemap(): MetadataRoute.Sitemap {
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://paprikalaw.com";
return routeOrder.map((key) => ({
url: `${siteUrl}${key === "home" ? "" : `/${key}`}`,
lastModified: new Date("2026-07-06"),
changeFrequency: key === "home" ? "weekly" : "monthly",
priority: key === "home" ? 1 : key === "start" ? 0.9 : 0.7
}));
}

30
app/start/page.tsx Normal file
View File

@@ -0,0 +1,30 @@
import Link from "next/link";
import { SubmissionForm } from "@/components/forms/SubmissionForm";
export const metadata = {
title: "Start Here"
};
export default function StartPage() {
return (
<>
<section className="hero subpage press-hero">
<h1>Start here.</h1>
<p className="subhead">
Tell us a little about your background, goals, and timeline, whether you are actively
preparing or just starting to explore. No need to have everything figured out. A quick
summary is enough to start.
</p>
<div className="actions">
<Link className="button primary" href="/start">
Start Here
</Link>
<Link className="button secondary" href="/ask">
Ask a Question
</Link>
</div>
</section>
<SubmissionForm type="start" />
</>
);
}

29
app/thanks/page.tsx Normal file
View File

@@ -0,0 +1,29 @@
import Link from "next/link";
export const metadata = {
title: "Thank You"
};
export default function ThanksPage({
searchParams
}: {
searchParams: { submission_id?: string };
}) {
return (
<section className="notice">
<h2>Thank you. We received your background.</h2>
<p>
The Paprika team will review your profile and follow up with next steps. If your timeline is
urgent, mention that in your reply.
</p>
{searchParams.submission_id ? (
<p className="form-success">Submission ID: {searchParams.submission_id}</p>
) : null}
<div className="actions">
<Link className="button secondary" href="/">
Back to Homepage
</Link>
</div>
</section>
);
}