Initial Paprika Next.js implementation
This commit is contained in:
31
app/[page]/page.tsx
Normal file
31
app/[page]/page.tsx
Normal 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} />;
|
||||
}
|
||||
33
app/api/submissions/route.ts
Normal file
33
app/api/submissions/route.ts
Normal 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
29
app/ask/page.tsx
Normal 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
2263
app/globals.css
Normal file
File diff suppressed because it is too large
Load Diff
44
app/layout.tsx
Normal file
44
app/layout.tsx
Normal 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
5
app/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { LegacyPage } from "@/components/LegacyPage";
|
||||
|
||||
export default function HomePage() {
|
||||
return <LegacyPage pageKey="home" />;
|
||||
}
|
||||
13
app/robots.ts
Normal file
13
app/robots.ts
Normal 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
28
app/sitemap.ts
Normal 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
30
app/start/page.tsx
Normal 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
29
app/thanks/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user