Initial Paprika Next.js implementation
This commit is contained in:
8
components/LegacyPage.tsx
Normal file
8
components/LegacyPage.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { LegacyPageKey } from "@/lib/legacy-pages";
|
||||
import { legacyPages } from "@/lib/legacy-pages";
|
||||
|
||||
export function LegacyPage({ pageKey }: { pageKey: LegacyPageKey }) {
|
||||
const page = legacyPages[pageKey];
|
||||
|
||||
return <div dangerouslySetInnerHTML={{ __html: page.html }} />;
|
||||
}
|
||||
56
components/SiteHeader.tsx
Normal file
56
components/SiteHeader.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { navItems, resourceItems } from "@/lib/navigation";
|
||||
|
||||
export function SiteHeader() {
|
||||
const pathname = usePathname();
|
||||
const current = pathname === "/" ? "home" : pathname.split("/")[1];
|
||||
const resourcesActive = current === "faq" || current === "resources";
|
||||
|
||||
return (
|
||||
<header className="site-header">
|
||||
<Link className="brand" href="/">
|
||||
<div className="brand-name">Paprika</div>
|
||||
<div className="brand-tag">Attorney Curated Evidence Building for O-1 Talent</div>
|
||||
</Link>
|
||||
<nav className="nav" id="nav">
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.key}
|
||||
href={item.href}
|
||||
data-page={item.key}
|
||||
className={current === item.key ? "active" : undefined}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<div className="nav-dropdown">
|
||||
<Link
|
||||
href="/resources"
|
||||
className={`nav-trigger${resourcesActive ? " active" : ""}`}
|
||||
data-page="resources"
|
||||
>
|
||||
Resources <span className="nav-chevron">⌄</span>
|
||||
</Link>
|
||||
<div className="resources-menu" aria-label="Resources menu">
|
||||
{resourceItems.map((item) => (
|
||||
<Link key={item.key} href={item.href} data-page={item.key}>
|
||||
<strong>{item.label}</strong>
|
||||
<span>{item.description}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Link
|
||||
href="/start"
|
||||
className={`start${current === "start" ? " active" : ""}`}
|
||||
data-page="start"
|
||||
>
|
||||
Start Here <span className="button-arrow">→</span>
|
||||
</Link>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
150
components/forms/SubmissionForm.tsx
Normal file
150
components/forms/SubmissionForm.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState, type FormEvent } from "react";
|
||||
|
||||
type FormType = "start" | "ask";
|
||||
|
||||
type FieldErrors = Record<string, string[] | undefined>;
|
||||
|
||||
export function SubmissionForm({ type }: { type: FormType }) {
|
||||
const router = useRouter();
|
||||
const [errors, setErrors] = useState<FieldErrors>({});
|
||||
const [status, setStatus] = useState<"idle" | "submitting" | "error">("idle");
|
||||
|
||||
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
setErrors({});
|
||||
setStatus("submitting");
|
||||
|
||||
const form = new FormData(event.currentTarget);
|
||||
const payload = Object.fromEntries(form.entries());
|
||||
|
||||
const response = await fetch("/api/submissions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
...payload,
|
||||
type,
|
||||
consentAccepted: form.get("consentAccepted") === "on",
|
||||
sourcePath: type === "start" ? "/start" : "/ask"
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json().catch(() => ({}));
|
||||
|
||||
if (!response.ok) {
|
||||
setErrors(result.issues || { form: [result.error || "Submission failed"] });
|
||||
setStatus("error");
|
||||
return;
|
||||
}
|
||||
|
||||
router.push(`/thanks?submission_id=${encodeURIComponent(result.submission_id)}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="form-wrap" onSubmit={handleSubmit}>
|
||||
<label className="field">
|
||||
Name <span className="req">*</span>
|
||||
<input className="field-control" name="name" autoComplete="name" required />
|
||||
<FieldError errors={errors} name="name" />
|
||||
</label>
|
||||
<label className="field">
|
||||
Email <span className="req">*</span>
|
||||
<input className="field-control" name="email" type="email" autoComplete="email" required />
|
||||
<FieldError errors={errors} name="email" />
|
||||
</label>
|
||||
<label className="field">
|
||||
LinkedIn {type === "ask" ? <span>(optional)</span> : null}
|
||||
<input className="field-control" name="linkedin" type="url" placeholder="https://linkedin.com/in/..." />
|
||||
<FieldError errors={errors} name="linkedin" />
|
||||
</label>
|
||||
|
||||
{type === "start" ? <StartFields errors={errors} /> : <AskFields errors={errors} />}
|
||||
|
||||
<label className="field large checkbox-field">
|
||||
<input name="consentAccepted" type="checkbox" required />
|
||||
<span>
|
||||
I understand that submitting this form does not create an attorney-client relationship or
|
||||
constitute legal advice. Paprika is not a law firm and does not provide legal services.
|
||||
</span>
|
||||
<FieldError errors={errors} name="consentAccepted" />
|
||||
</label>
|
||||
|
||||
{errors.form?.length ? <div className="form-error">{errors.form[0]}</div> : null}
|
||||
|
||||
<button className="button primary" type="submit" disabled={status === "submitting"}>
|
||||
{status === "submitting" ? "Submitting..." : type === "start" ? "Submit Profile" : "Send Question"}
|
||||
</button>
|
||||
<div className="form-note">
|
||||
File upload is intentionally disabled until storage, size limits, access control, and privacy
|
||||
handling are approved.
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function StartFields({ errors }: { errors: FieldErrors }) {
|
||||
return (
|
||||
<>
|
||||
<label className="field">
|
||||
Company Website
|
||||
<input className="field-control" name="companyWebsite" type="url" placeholder="https://..." />
|
||||
<FieldError errors={errors} name="companyWebsite" />
|
||||
</label>
|
||||
<label className="field">
|
||||
Current Role / Company
|
||||
<input className="field-control" name="currentRoleCompany" />
|
||||
</label>
|
||||
<label className="field">
|
||||
Field or Industry
|
||||
<input className="field-control" name="fieldOrIndustry" />
|
||||
</label>
|
||||
<label className="field large start-question-field">
|
||||
<strong>
|
||||
What brings you to Paprika? <span className="req">*</span>
|
||||
</strong>
|
||||
<span>
|
||||
Tell us where you are in your O-1 journey. Whether you have a specific goal, are building
|
||||
evidence early, or are just exploring, a quick summary is enough.
|
||||
</span>
|
||||
<textarea className="field-control" name="reason" required />
|
||||
<FieldError errors={errors} name="reason" />
|
||||
</label>
|
||||
<label className="field large start-question-field">
|
||||
<strong>What recognition have you already built, if any?</strong>
|
||||
<span>
|
||||
Press, interviews, judging roles, awards, speaking, scholarly articles, patents, funding,
|
||||
major projects, or other recognition.
|
||||
</span>
|
||||
<textarea className="field-control" name="existingRecognition" />
|
||||
</label>
|
||||
<label className="field large start-question-field">
|
||||
<strong>Is there a timeline on your mind?</strong>
|
||||
<span>
|
||||
Whether it is urgent, months away, or just a future possibility, an approximate timeline
|
||||
helps us recommend the right pace.
|
||||
</span>
|
||||
<textarea className="field-control" name="timeline" />
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function AskFields({ errors }: { errors: FieldErrors }) {
|
||||
return (
|
||||
<label className="field large question-field">
|
||||
<strong>
|
||||
Your question <span className="req">*</span>
|
||||
</strong>
|
||||
<span>Ask us anything about our services, your situation, or where to start.</span>
|
||||
<textarea className="field-control" name="question" required />
|
||||
<FieldError errors={errors} name="question" />
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function FieldError({ errors, name }: { errors: FieldErrors; name: string }) {
|
||||
const message = errors[name]?.[0];
|
||||
return message ? <span className="form-error">{message}</span> : null;
|
||||
}
|
||||
Reference in New Issue
Block a user