Initial Paprika Next.js implementation
This commit is contained in:
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DATABASE_URL="postgresql://paprika_app:REPLACE_WITH_SECURE_PASSWORD@127.0.0.1:5432/paprika"
|
||||||
|
NEXT_PUBLIC_SITE_URL="https://paprikalaw.com"
|
||||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.next/
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
coverage/
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
*.log
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
git.txt
|
||||||
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
27
doc/README.md
Normal file
27
doc/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Paprika Frontend Mockup
|
||||||
|
|
||||||
|
This repository contains the current Paprika website mockup.
|
||||||
|
|
||||||
|
## Open the mockup
|
||||||
|
|
||||||
|
Open `index.html` in a browser.
|
||||||
|
|
||||||
|
The mockup is a single self-contained HTML file with all page routes handled through query parameters, including:
|
||||||
|
|
||||||
|
- `?page=home`
|
||||||
|
- `?page=press`
|
||||||
|
- `?page=judging`
|
||||||
|
- `?page=scholarly`
|
||||||
|
- `?page=services`
|
||||||
|
- `?page=why`
|
||||||
|
- `?page=resources`
|
||||||
|
- `?page=faq`
|
||||||
|
- `?page=terms`
|
||||||
|
- `?page=privacy`
|
||||||
|
- `?page=disclaimer`
|
||||||
|
- `?page=start`
|
||||||
|
- `?page=ask`
|
||||||
|
|
||||||
|
## Upload rule
|
||||||
|
|
||||||
|
After each approved refinement is completed, commit the updated mockup and upload it to the cloud repository immediately.
|
||||||
3423
doc/index.html
Normal file
3423
doc/index.html
Normal file
File diff suppressed because it is too large
Load Diff
10
eslint.config.mjs
Normal file
10
eslint.config.mjs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||||
|
|
||||||
|
const config = [
|
||||||
|
...nextVitals,
|
||||||
|
{
|
||||||
|
ignores: [".next/**", "node_modules/**", "lib/legacy-pages.ts"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default config;
|
||||||
15
lib/db.ts
Normal file
15
lib/db.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
const globalForPrisma = globalThis as unknown as {
|
||||||
|
prisma?: PrismaClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prisma =
|
||||||
|
globalForPrisma.prisma ??
|
||||||
|
new PrismaClient({
|
||||||
|
log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
globalForPrisma.prisma = prisma;
|
||||||
|
}
|
||||||
83
lib/legacy-pages.ts
Normal file
83
lib/legacy-pages.ts
Normal file
File diff suppressed because one or more lines are too long
22
lib/navigation.ts
Normal file
22
lib/navigation.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const navItems = [
|
||||||
|
{ href: "/press", label: "Press", key: "press" },
|
||||||
|
{ href: "/judging", label: "Judging", key: "judging" },
|
||||||
|
{ href: "/scholarly", label: "Scholarly Articles", key: "scholarly" },
|
||||||
|
{ href: "/services", label: "Services", key: "services" },
|
||||||
|
{ href: "/why", label: "Why Paprika", key: "why" }
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const resourceItems = [
|
||||||
|
{
|
||||||
|
href: "/faq",
|
||||||
|
key: "faq",
|
||||||
|
label: "Frequently Asked Questions",
|
||||||
|
description: "Common questions before you start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/resources",
|
||||||
|
key: "resources",
|
||||||
|
label: "O-1A Criteria Overview",
|
||||||
|
description: "All 8 O-1A criteria, explained"
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
46
lib/submission-schema.ts
Normal file
46
lib/submission-schema.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const emptyToUndefined = (value: unknown) => {
|
||||||
|
if (typeof value !== "string") return value;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed.length ? trimmed : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const optionalString = z.preprocess(emptyToUndefined, z.string().max(4000).optional());
|
||||||
|
const optionalUrl = z.preprocess(
|
||||||
|
emptyToUndefined,
|
||||||
|
z.string().url("Enter a valid URL, including https://").max(500).optional()
|
||||||
|
);
|
||||||
|
|
||||||
|
const baseSubmissionSchema = z.object({
|
||||||
|
type: z.enum(["start", "ask"]),
|
||||||
|
name: z.string().trim().min(1, "Name is required").max(160),
|
||||||
|
email: z.string().trim().email("Enter a valid email").max(255),
|
||||||
|
linkedin: optionalUrl,
|
||||||
|
consentAccepted: z.literal(true, {
|
||||||
|
errorMap: () => ({ message: "You must acknowledge the disclaimer before submitting" })
|
||||||
|
}),
|
||||||
|
sourcePath: z.string().max(200).optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const startSubmissionSchema = baseSubmissionSchema.extend({
|
||||||
|
type: z.literal("start"),
|
||||||
|
companyWebsite: optionalUrl,
|
||||||
|
currentRoleCompany: optionalString,
|
||||||
|
fieldOrIndustry: optionalString,
|
||||||
|
reason: z.string().trim().min(1, "Tell us what brings you to Paprika").max(4000),
|
||||||
|
existingRecognition: optionalString,
|
||||||
|
timeline: optionalString
|
||||||
|
});
|
||||||
|
|
||||||
|
export const askSubmissionSchema = baseSubmissionSchema.extend({
|
||||||
|
type: z.literal("ask"),
|
||||||
|
question: z.string().trim().min(1, "Question is required").max(4000)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const submissionSchema = z.discriminatedUnion("type", [
|
||||||
|
startSubmissionSchema,
|
||||||
|
askSubmissionSchema
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type SubmissionInput = z.infer<typeof submissionSchema>;
|
||||||
26
lib/submissions.ts
Normal file
26
lib/submissions.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { prisma } from "@/lib/db";
|
||||||
|
import type { SubmissionInput } from "@/lib/submission-schema";
|
||||||
|
|
||||||
|
export async function createSubmission(input: SubmissionInput) {
|
||||||
|
return prisma.submission.create({
|
||||||
|
data: {
|
||||||
|
type: input.type,
|
||||||
|
name: input.name,
|
||||||
|
email: input.email,
|
||||||
|
linkedin: input.linkedin,
|
||||||
|
companyWebsite: input.type === "start" ? input.companyWebsite : undefined,
|
||||||
|
currentRoleCompany: input.type === "start" ? input.currentRoleCompany : undefined,
|
||||||
|
fieldOrIndustry: input.type === "start" ? input.fieldOrIndustry : undefined,
|
||||||
|
reason: input.type === "start" ? input.reason : undefined,
|
||||||
|
existingRecognition: input.type === "start" ? input.existingRecognition : undefined,
|
||||||
|
timeline: input.type === "start" ? input.timeline : undefined,
|
||||||
|
question: input.type === "ask" ? input.question : undefined,
|
||||||
|
consentAccepted: input.consentAccepted,
|
||||||
|
sourcePath: input.sourcePath
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
createdAt: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
6
next-env.d.ts
vendored
Normal file
6
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
4
next.config.mjs
Normal file
4
next.config.mjs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
5783
package-lock.json
generated
Normal file
5783
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "meetpaprika",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"generate:legacy": "node scripts/extract-legacy-pages.mjs",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:migrate": "prisma migrate dev"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client": "^5.22.0",
|
||||||
|
"next": "^16.2.10",
|
||||||
|
"prisma": "^5.22.0",
|
||||||
|
"react": "^19.2.7",
|
||||||
|
"react-dom": "^19.2.7",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.14.10",
|
||||||
|
"@types/react": "^19.2.17",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"eslint": "^9.39.4",
|
||||||
|
"eslint-config-next": "^16.2.10",
|
||||||
|
"typescript": "^5.5.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
39
prisma/migrations/20260706120000_init/migration.sql
Normal file
39
prisma/migrations/20260706120000_init/migration.sql
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Submission" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"linkedin" TEXT,
|
||||||
|
"companyWebsite" TEXT,
|
||||||
|
"currentRoleCompany" TEXT,
|
||||||
|
"fieldOrIndustry" TEXT,
|
||||||
|
"reason" TEXT,
|
||||||
|
"existingRecognition" TEXT,
|
||||||
|
"timeline" TEXT,
|
||||||
|
"question" TEXT,
|
||||||
|
"consentAccepted" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"sourcePath" TEXT,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'new',
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Submission_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SubmissionFile" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"submissionId" TEXT NOT NULL,
|
||||||
|
"fileName" TEXT NOT NULL,
|
||||||
|
"mimeType" TEXT NOT NULL,
|
||||||
|
"byteSize" INTEGER NOT NULL,
|
||||||
|
"storageKey" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "SubmissionFile_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "SubmissionFile" ADD CONSTRAINT "SubmissionFile_submissionId_fkey" FOREIGN KEY ("submissionId") REFERENCES "Submission"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
40
prisma/schema.prisma
Normal file
40
prisma/schema.prisma
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Submission {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
type String
|
||||||
|
name String
|
||||||
|
email String
|
||||||
|
linkedin String?
|
||||||
|
companyWebsite String?
|
||||||
|
currentRoleCompany String?
|
||||||
|
fieldOrIndustry String?
|
||||||
|
reason String?
|
||||||
|
existingRecognition String?
|
||||||
|
timeline String?
|
||||||
|
question String?
|
||||||
|
consentAccepted Boolean @default(false)
|
||||||
|
sourcePath String?
|
||||||
|
status String @default("new")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
files SubmissionFile[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model SubmissionFile {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
submissionId String
|
||||||
|
fileName String
|
||||||
|
mimeType String
|
||||||
|
byteSize Int
|
||||||
|
storageKey String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
submission Submission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
101
scripts/extract-legacy-pages.mjs
Normal file
101
scripts/extract-legacy-pages.mjs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import vm from "node:vm";
|
||||||
|
|
||||||
|
const sourcePath = "doc/index.html";
|
||||||
|
const html = fs.readFileSync(sourcePath, "utf8");
|
||||||
|
|
||||||
|
const styleMatch = html.match(/<style>([\s\S]*?)<\/style>/);
|
||||||
|
const scriptMatch = html.match(/<script>([\s\S]*?)<\/script>/);
|
||||||
|
|
||||||
|
if (!styleMatch || !scriptMatch) {
|
||||||
|
throw new Error("Could not find inline style or script in doc/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
const css = styleMatch[1]
|
||||||
|
.replaceAll("href=\"?page=", "href=\"/")
|
||||||
|
.replace(/href="\/home"/g, "href=\"/\"");
|
||||||
|
|
||||||
|
const legacyScript = scriptMatch[1];
|
||||||
|
const renderScript = legacyScript.replace(
|
||||||
|
/const params = new URLSearchParams\(window\.location\.search\);[\s\S]*$/,
|
||||||
|
"globalThis.__pageData = pageData;"
|
||||||
|
);
|
||||||
|
|
||||||
|
const sandbox = {
|
||||||
|
console,
|
||||||
|
URLSearchParams,
|
||||||
|
window: { location: { search: "" } },
|
||||||
|
document: {
|
||||||
|
title: "",
|
||||||
|
getElementById() {
|
||||||
|
return { innerHTML: "" };
|
||||||
|
},
|
||||||
|
querySelectorAll() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
querySelector() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.createContext(sandbox);
|
||||||
|
vm.runInContext(renderScript, sandbox, { filename: sourcePath });
|
||||||
|
|
||||||
|
const pageData = sandbox.__pageData;
|
||||||
|
const routeMap = {
|
||||||
|
home: "/",
|
||||||
|
press: "/press",
|
||||||
|
judging: "/judging",
|
||||||
|
scholarly: "/scholarly",
|
||||||
|
services: "/services",
|
||||||
|
why: "/why",
|
||||||
|
resources: "/resources",
|
||||||
|
faq: "/faq",
|
||||||
|
thanks: "/thanks",
|
||||||
|
terms: "/terms",
|
||||||
|
privacy: "/privacy",
|
||||||
|
disclaimer: "/disclaimer"
|
||||||
|
};
|
||||||
|
|
||||||
|
const pages = Object.fromEntries(
|
||||||
|
Object.entries(routeMap).map(([key, path]) => {
|
||||||
|
const page = pageData[key];
|
||||||
|
if (!page?.body) {
|
||||||
|
throw new Error(`Missing page body for ${key}`);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
path,
|
||||||
|
title: key === "home" ? "Paprika" : `Paprika - ${key}`,
|
||||||
|
html: normalizeLinks(page.body)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
function normalizeLinks(value) {
|
||||||
|
return value
|
||||||
|
.replaceAll("href=\"?page=home\"", "href=\"/\"")
|
||||||
|
.replace(/href="\?page=([a-z-]+)"/g, (_match, page) => {
|
||||||
|
if (page === "home") return "href=\"/\"";
|
||||||
|
return `href="/${page}"`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync("app", { recursive: true });
|
||||||
|
fs.mkdirSync("lib", { recursive: true });
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
"app/globals.css",
|
||||||
|
`${css}\n\n.form-error { color: var(--deep-red); font-size: 14px; font-weight: 750; line-height: 1.4; }\n.form-success { color: var(--green); font-size: 14px; font-weight: 750; line-height: 1.4; }\n.field-control { width: 100%; border: 0; outline: 0; background: transparent; color: var(--ink); font: inherit; font-weight: 650; }\n.field-control::placeholder { color: var(--muted); opacity: .78; }\ntextarea.field-control { min-height: 126px; resize: vertical; line-height: 1.45; }\n.checkbox-field { align-items: flex-start; gap: 12px; text-align: left; color: var(--text); font-size: 14px; line-height: 1.45; }\n.checkbox-field input { margin-top: 3px; accent-color: var(--red); }\n.button[disabled] { cursor: not-allowed; opacity: .62; }\n`
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
"lib/legacy-pages.ts",
|
||||||
|
`export type LegacyPageKey = ${Object.keys(pages).map((key) => JSON.stringify(key)).join(" | ")};\n\nexport type LegacyPage = {\n key: LegacyPageKey;\n path: string;\n title: string;\n html: string;\n};\n\nexport const legacyPages = ${JSON.stringify(pages, null, 2)} as const satisfies Record<LegacyPageKey, LegacyPage>;\n`
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Generated ${Object.keys(pages).length} pages from ${sourcePath}`);
|
||||||
41
tsconfig.json
Normal file
41
tsconfig.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2017",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
771
working/01-项目功能内容.md
Normal file
771
working/01-项目功能内容.md
Normal file
@@ -0,0 +1,771 @@
|
|||||||
|
# 01-项目功能内容
|
||||||
|
|
||||||
|
## 1. 项目基线
|
||||||
|
|
||||||
|
- 项目名称:MeetPaprika / Paprika
|
||||||
|
- 当前对标文件:`/home/mes123456/MeetPaprika/doc/index.html`
|
||||||
|
- 原始对标形态:单文件静态网站 mockup,HTML、CSS、页面数据、页面渲染脚本全部内联在 `doc/index.html`
|
||||||
|
- 品牌主张:Attorney Curated Evidence Building for O-1 Talent
|
||||||
|
- 业务定位:面向 O-1 人才的证据建设服务,帮助 founders、engineers、researchers、executives、creators 等提前建设媒体报道、评审机会、学术文章和完整证据包
|
||||||
|
- 页面运行方式:读取 URL 查询参数 `?page=...`,将对应页面 HTML 字符串写入 `<main id="app"></main>`
|
||||||
|
|
||||||
|
## 2. 技术路线基准
|
||||||
|
|
||||||
|
### 当前静态实现
|
||||||
|
|
||||||
|
- 入口文件:`doc/index.html`
|
||||||
|
- 样式:内联 `<style>`
|
||||||
|
- 脚本:内联 `<script>`
|
||||||
|
- 状态:作为旧版静态 mockup 和视觉对标基线保留;正式工程已在根目录新增
|
||||||
|
- 路由:查询参数路由,不依赖 History API
|
||||||
|
|
||||||
|
### 已落地的正式开发技术架构
|
||||||
|
|
||||||
|
已明确采用:
|
||||||
|
|
||||||
|
- 前端框架:Next.js + React
|
||||||
|
- 数据库:PostgreSQL
|
||||||
|
- 数据访问层:Prisma ORM
|
||||||
|
- 表单处理:Next.js Route Handler `POST /api/submissions` 承接 Start Here 和 Ask a Question
|
||||||
|
- 部署端口:Next.js 服务或反向代理后的应用服务监听 `8082`
|
||||||
|
- HTTPS:由 Nginx/Caddy/云服务反向代理终止 TLS,转发到本机 `8082`
|
||||||
|
|
||||||
|
架构目标:
|
||||||
|
|
||||||
|
- 将当前 `doc/index.html` 的视觉和内容完整迁移到 Next.js 页面/组件。
|
||||||
|
- 将 `start` 和 `ask` 从视觉 mockup 改为真实表单。
|
||||||
|
- 将提交数据保存到 PostgreSQL。
|
||||||
|
- 预留 CV/resume 文件上传能力,但必须先明确存储方案、文件大小、访问权限和隐私规则。
|
||||||
|
- 保留所有 legal disclaimer,防止用户误以为提交表单会创建 attorney-client relationship。
|
||||||
|
|
||||||
|
当前代码状态:
|
||||||
|
|
||||||
|
- 根目录已存在 Next.js App Router 工程。
|
||||||
|
- `app/[page]/page.tsx` 渲染从 `doc/index.html` 抽取的静态页面内容。
|
||||||
|
- `app/start/page.tsx` 和 `app/ask/page.tsx` 已替换为真实 React 表单。
|
||||||
|
- `app/api/submissions/route.ts` 已实现 JSON 提交、Zod 校验和 Prisma 写入。
|
||||||
|
- `prisma/schema.prisma` 和 `prisma/migrations/20260706120000_init/migration.sql` 已建立 `Submission`、`SubmissionFile` 表。
|
||||||
|
- `.env.example` 已提供 `DATABASE_URL` 和 `NEXT_PUBLIC_SITE_URL` 模板,不包含真实密码。
|
||||||
|
- 成功入库尚需生产或本地 PostgreSQL `DATABASE_URL`。
|
||||||
|
|
||||||
|
建议工程结构:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/
|
||||||
|
page.tsx
|
||||||
|
press/page.tsx
|
||||||
|
judging/page.tsx
|
||||||
|
scholarly/page.tsx
|
||||||
|
services/page.tsx
|
||||||
|
why/page.tsx
|
||||||
|
resources/page.tsx
|
||||||
|
faq/page.tsx
|
||||||
|
start/page.tsx
|
||||||
|
ask/page.tsx
|
||||||
|
thanks/page.tsx
|
||||||
|
terms/page.tsx
|
||||||
|
privacy/page.tsx
|
||||||
|
disclaimer/page.tsx
|
||||||
|
api/
|
||||||
|
submissions/route.ts
|
||||||
|
components/
|
||||||
|
SiteHeader.tsx
|
||||||
|
SiteFooter.tsx
|
||||||
|
Hero.tsx
|
||||||
|
ServiceCard.tsx
|
||||||
|
ServicesCta.tsx
|
||||||
|
LegalPage.tsx
|
||||||
|
forms/
|
||||||
|
StartForm.tsx
|
||||||
|
AskForm.tsx
|
||||||
|
lib/
|
||||||
|
db.ts
|
||||||
|
validators.ts
|
||||||
|
submissions.ts
|
||||||
|
prisma/
|
||||||
|
schema.prisma
|
||||||
|
```
|
||||||
|
|
||||||
|
### 目标发布环境
|
||||||
|
|
||||||
|
- 目标域名:`https://paprikalaw.com/`
|
||||||
|
- 云服务器:`170.106.192.152`
|
||||||
|
- 登录用户:`ubuntu`
|
||||||
|
- 目标端口:`8082`
|
||||||
|
- 可用端口段:`8080-8099`
|
||||||
|
- PostgreSQL:用户确认已在服务器安装完成,待验证服务状态、版本、应用数据库和应用用户
|
||||||
|
- SSH 凭据:用户已在交接信息中提供;长期文档不保存明文密码,后续应改为 SSH key、部署密钥或受控密钥库
|
||||||
|
- 目标发布形态:HTTPS 对外访问,服务内部监听 `8082` 或由反向代理转发至 `8082`
|
||||||
|
|
||||||
|
### 邮箱系统需求
|
||||||
|
|
||||||
|
- 普通用户网页登录:`https://mail.paprikalaw.com/webmail/`
|
||||||
|
- 管理员网页登录:`https://mail.paprikalaw.com/admin/`
|
||||||
|
- 管理员邮箱:`admin@paprikalaw.com`
|
||||||
|
- 普通公司邮箱:
|
||||||
|
- `info@paprikalaw.com`
|
||||||
|
- `contact@paprikalaw.com`
|
||||||
|
- `support@paprikalaw.com`
|
||||||
|
- `sales@paprikalaw.com`
|
||||||
|
- `marketing@paprikalaw.com`
|
||||||
|
- `hr@paprikalaw.com`
|
||||||
|
- `finance@paprikalaw.com`
|
||||||
|
- `billing@paprikalaw.com`
|
||||||
|
- `legal@paprikalaw.com`
|
||||||
|
- `service@paprikalaw.com`
|
||||||
|
- 凭据规则:用户已提供统一初始密码;长期文档不保存明文密码。创建后应要求首次登录修改,或为每个邮箱生成独立强密码。
|
||||||
|
|
||||||
|
邮件系统与网站的关系:
|
||||||
|
|
||||||
|
- 表单通知邮箱建议使用 `support@paprikalaw.com` 或 `contact@paprikalaw.com`。
|
||||||
|
- 法务和隐私页面中的联系邮箱目前是 `support@meetpaprika.com`,上线前需统一为 `paprikalaw.com` 域名邮箱或保留现状并记录理由。
|
||||||
|
- 若 Next.js 表单提交后发送邮件通知,需要配置 SMTP 凭据、发件地址、收件地址和退信策略。
|
||||||
|
|
||||||
|
## 3. 当前页面路由清单
|
||||||
|
|
||||||
|
| 路由 | 页面 | 导航入口 | 当前用途 | 状态 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `?page=home` | Home | 品牌 logo 默认入口 | 品牌主张、三大核心服务、提前建设证据、工作流程、价格起点 | 已实现 mockup |
|
||||||
|
| `?page=press` | Press | 顶部导航 Press | 媒体报道服务、证据标准、应避免的 press 类型、代表性 outlet 网络 | 已实现 mockup |
|
||||||
|
| `?page=judging` | Judging | 顶部导航 Judging | 评审机会服务、哪些 judging counts、judging network、证据包内容 | 已实现 mockup |
|
||||||
|
| `?page=scholarly` | Scholarly Articles | 顶部导航 Scholarly Articles | 学术文章服务、发表策略、证据包价值 | 已实现 mockup |
|
||||||
|
| `?page=services` | Services | 顶部导航 Services | 6 类服务卡片和服务转化 CTA | 已实现 mockup |
|
||||||
|
| `?page=why` | Why Paprika | 顶部导航 Why Paprika、footer About | 差异化说明、证据质量、时间节奏、完整证据记录 | 已实现 mockup |
|
||||||
|
| `?page=resources` | Resources / O-1A Criteria | Resources 下拉 | 8 项 O-1A criteria 的 plain-English overview | 已实现 mockup |
|
||||||
|
| `?page=faq` | FAQ | Resources 下拉、footer FAQ | Paprika、O-1、timeline、pricing/refund FAQ | 已实现 mockup |
|
||||||
|
| `?page=start` | Start Here | 顶部 CTA、各页面 CTA | 背景资料提交视觉 mockup | 已实现视觉,未实现提交 |
|
||||||
|
| `?page=ask` | Ask a Question | 各页面 secondary CTA | 简短问题提交视觉 mockup | 已实现视觉,未实现提交 |
|
||||||
|
| `?page=thanks` | Thank You | 提交按钮跳转 | 感谢页 | 已实现 mockup |
|
||||||
|
| `?page=terms` | Terms | footer Terms | 服务条款草稿 | 已实现草稿,需法务确认 |
|
||||||
|
| `?page=privacy` | Privacy | footer Privacy | 隐私政策草稿 | 已实现草稿,需法务确认 |
|
||||||
|
| `?page=disclaimer` | Disclaimer | footer Disclaimer | 法律免责声明草稿 | 已实现草稿,需法务确认 |
|
||||||
|
| `?page=blog` | Blog | 无公开导航 | Field notes 文章卡片占位 | 代码存在,未公开 |
|
||||||
|
| `?page=about` | About | 无公开导航;footer About 实际指向 `why` | 公司说明占位 | 代码存在,未公开 |
|
||||||
|
|
||||||
|
未识别路由会回退到 `home`,页面标题为 `Paprika - ${selected}`。
|
||||||
|
|
||||||
|
## 4. 全站公共模块
|
||||||
|
|
||||||
|
### Header
|
||||||
|
|
||||||
|
- 品牌区:
|
||||||
|
- `Paprika`
|
||||||
|
- `Attorney Curated Evidence Building for O-1 Talent`
|
||||||
|
- 顶部导航:
|
||||||
|
- Press
|
||||||
|
- Judging
|
||||||
|
- Scholarly Articles
|
||||||
|
- Services
|
||||||
|
- Why Paprika
|
||||||
|
- Resources 下拉
|
||||||
|
- Start Here CTA
|
||||||
|
- Resources 下拉:
|
||||||
|
- Frequently Asked Questions -> `?page=faq`
|
||||||
|
- O-1A Criteria Overview -> `?page=resources`
|
||||||
|
- 当前页高亮:
|
||||||
|
- 根据 `data-page` 和当前 `selected` 页面添加 `active`
|
||||||
|
- 当 `selected` 为 `faq` 或 `resources` 时,Resources 触发器高亮
|
||||||
|
|
||||||
|
### Footer
|
||||||
|
|
||||||
|
- 链接:
|
||||||
|
- About -> `?page=why`
|
||||||
|
- FAQ -> `?page=faq`
|
||||||
|
- Terms -> `?page=terms`
|
||||||
|
- Privacy -> `?page=privacy`
|
||||||
|
- Disclaimer -> `?page=disclaimer`
|
||||||
|
- 固定声明:
|
||||||
|
- Paprika is not a law firm and does not provide legal advice or immigration representation.
|
||||||
|
- Use of our services does not create an attorney-client relationship.
|
||||||
|
|
||||||
|
## 5. 首页内容
|
||||||
|
|
||||||
|
### Hero
|
||||||
|
|
||||||
|
- H1:O-1 evidence should not be built in a panic.
|
||||||
|
- Subhead:帮助高成就人才提前建设 credible public recognition
|
||||||
|
- Primary CTA:Send Us Your Profile -> `?page=start`
|
||||||
|
- Secondary CTA:Ask a Question -> `?page=ask`
|
||||||
|
- Microcopy:提交 LinkedIn 或 CV 后 review background,并推荐 evidence path
|
||||||
|
- 时间主张:The only thing you cannot buy later is time.
|
||||||
|
|
||||||
|
### Core services
|
||||||
|
|
||||||
|
三张服务卡片:
|
||||||
|
|
||||||
|
1. Press:Editorial feature in a recognized publication in your field.
|
||||||
|
2. Judging:Opportunities to judge work that matters.
|
||||||
|
3. Scholarly Articles:Scholarly articles in credible, peer-reviewed publications.
|
||||||
|
|
||||||
|
### Start Early
|
||||||
|
|
||||||
|
- 强调证据不能临近 filing 时集中出现
|
||||||
|
- 目标是构建 natural publication and judging timeline
|
||||||
|
- 输出给用户或 immigration attorney 使用的 stronger materials
|
||||||
|
|
||||||
|
### Our standard
|
||||||
|
|
||||||
|
提出推荐前要判断的 8 个问题:
|
||||||
|
|
||||||
|
1. 文章是否关于个人而不只是公司或产品
|
||||||
|
2. 是否连接特定专业领域和 O-1 criteria
|
||||||
|
3. outlet 是否 credible、independent、documentable
|
||||||
|
4. timing 是否自然
|
||||||
|
5. judging 是否真实 evaluative
|
||||||
|
6. authorship 是否体现 expertise
|
||||||
|
7. 是否能清晰整理进 evidence package
|
||||||
|
8. 是否补强其他 existing criteria
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
四步流程:
|
||||||
|
|
||||||
|
1. Send us your profile
|
||||||
|
2. Assess background and recommend evidence path
|
||||||
|
3. Build credible public record
|
||||||
|
4. Receive documented evidence package
|
||||||
|
|
||||||
|
价格带:
|
||||||
|
|
||||||
|
- Packages start at `$1,699+`
|
||||||
|
- final pricing depends on outlet level, timeline, field, evidence goals
|
||||||
|
|
||||||
|
## 6. Press 页面内容
|
||||||
|
|
||||||
|
### 核心主张
|
||||||
|
|
||||||
|
- Attorney-Curated Press for O-1 Talent
|
||||||
|
- Press that works as evidence, not just visibility
|
||||||
|
- 关注 outlet、format、field relevance、documentation
|
||||||
|
|
||||||
|
### Cadence note
|
||||||
|
|
||||||
|
- 避免 filing 前突然集中出现的 uncurated evidence
|
||||||
|
- 构建 natural cadence of press over time
|
||||||
|
|
||||||
|
### What Paprika looks for
|
||||||
|
|
||||||
|
Press 推荐前检查:
|
||||||
|
|
||||||
|
1. 文章是否关于本人
|
||||||
|
2. headline 是否出现姓名并连接领域
|
||||||
|
3. 是否 genuine editorial coverage
|
||||||
|
4. outlet 是否有真实 readership、editorial standards、clean track record
|
||||||
|
5. 是否存在 contributor、sponsored、editorial-staff-not-involved 等 disclosure
|
||||||
|
6. timing 是否自然
|
||||||
|
7. 是否 complement other O-1 criteria
|
||||||
|
|
||||||
|
结论句:The outlet name is not the evidence. The editorial independence is.
|
||||||
|
|
||||||
|
### What to avoid
|
||||||
|
|
||||||
|
当前展示三类不推荐 press:
|
||||||
|
|
||||||
|
- Contributor content
|
||||||
|
- Editorial staff not involved
|
||||||
|
- Sponsored or promotional placement
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- outlet 没有 editorially choose to cover this person
|
||||||
|
- third party produced or paid for content
|
||||||
|
- 不体现 independent recognition
|
||||||
|
|
||||||
|
### Representative outlets
|
||||||
|
|
||||||
|
页面列出代表性 outlet 网络,包括 Forbes、Bloomberg、Vogue、Business Insider、USA Today、TechCrunch、VentureBeat、Los Angeles Times、Newsweek、Fast Company Mexico、Time 等。正式上线前需要确认是否拥有真实合作网络、是否允许展示、是否需要增加免责声明。
|
||||||
|
|
||||||
|
## 7. Judging 页面内容
|
||||||
|
|
||||||
|
### 核心主张
|
||||||
|
|
||||||
|
- Judging roles should show that your field trusts your expertise.
|
||||||
|
- 服务目标:source and vet selective evaluation opportunities
|
||||||
|
|
||||||
|
### What counts as judging
|
||||||
|
|
||||||
|
当前列出 6 类:
|
||||||
|
|
||||||
|
1. Startup pitch competitions
|
||||||
|
2. Hackathon & innovation judging
|
||||||
|
3. Industry award committees
|
||||||
|
4. Academic & conference peer review
|
||||||
|
5. Grant review panels
|
||||||
|
6. Research competition judging
|
||||||
|
|
||||||
|
### Judging network examples
|
||||||
|
|
||||||
|
当前展示示例 chips:Y Combinator Demo Day Adjacent、TechCrunch Disrupt、SXSW Pitch、Web Summit、Slush、Collision、Startup Grind Global、MassChallenge、Plug and Play、500 Global、Berkeley SkyDeck、Techstars、Founder Institute、academic journal review、conference TPCs、design competition juries、arts award panels、research grant committees 等。
|
||||||
|
|
||||||
|
正式上线前需要确认名称使用风险、真实性、合作关系或改为“示例类型”。
|
||||||
|
|
||||||
|
### Evidence package
|
||||||
|
|
||||||
|
Judging package 当前包含:
|
||||||
|
|
||||||
|
- Signed organizer recommendation letter
|
||||||
|
- Judge selection documentation
|
||||||
|
- Organizer correspondence
|
||||||
|
- Agenda & event page
|
||||||
|
- Panel agenda & bio listing
|
||||||
|
- Ready-to-use packet
|
||||||
|
|
||||||
|
## 8. Scholarly Articles 页面内容
|
||||||
|
|
||||||
|
核心内容:
|
||||||
|
|
||||||
|
- Scholarly articles in credible, peer-reviewed publications
|
||||||
|
- 服务覆盖 peer-reviewed、Scopus-indexed、Google-verified journals
|
||||||
|
- 两个 panel:
|
||||||
|
- Attorney-curated strategy
|
||||||
|
- Complete evidence packet
|
||||||
|
|
||||||
|
正式上线前需确认:
|
||||||
|
|
||||||
|
- 是否能承诺具体 indexing 或 journal 类型
|
||||||
|
- 是否需要补充 publication ethics、authorship、conflict disclosure
|
||||||
|
- 是否避免暗示 pay-to-publish 或不当 authorship arrangement
|
||||||
|
|
||||||
|
## 9. Services 页面内容
|
||||||
|
|
||||||
|
六张服务卡:
|
||||||
|
|
||||||
|
| 编号 | 服务 | 当前标题 | CTA |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 01 | Press | Editorial feature in a recognized publication in your field. | Explore Press |
|
||||||
|
| 02 | Judging | Opportunities to judge work that matters. | Explore Judging |
|
||||||
|
| 03 | Scholarly Articles | Scholarly articles in credible, peer-reviewed publications. | Explore Articles |
|
||||||
|
| 04 | Expert consultation | Know where you stand before you commit. | Start Here |
|
||||||
|
| 05 | Agency representation | Useful when you have multiple engagements or no single employer. | Start Here |
|
||||||
|
| 06 | Attorney Referral | The right attorney, when you need one. | Start Here |
|
||||||
|
|
||||||
|
注意:服务 04、05、06 曾经传入不存在的 `contact` 路由;R007 已改为 `start`。
|
||||||
|
|
||||||
|
## 10. Why Paprika 页面内容
|
||||||
|
|
||||||
|
五个差异化理由:
|
||||||
|
|
||||||
|
1. Attorney-curated. Built to matter.
|
||||||
|
2. Built before you need it.
|
||||||
|
3. A coherent profile, tailored to you.
|
||||||
|
4. A complete evidence record.
|
||||||
|
5. Everything in one place.
|
||||||
|
|
||||||
|
结尾 peace of mind:
|
||||||
|
|
||||||
|
- You do not have to second-guess what you are building.
|
||||||
|
- You just build.
|
||||||
|
|
||||||
|
## 11. Resources / O-1A Criteria 页面内容
|
||||||
|
|
||||||
|
页面解释 O-1A 8 项 criteria:
|
||||||
|
|
||||||
|
1. Awards
|
||||||
|
2. Memberships
|
||||||
|
3. Published Material About You
|
||||||
|
4. Judging the Work of Others
|
||||||
|
5. Original Contributions of Major Significance
|
||||||
|
6. Scholarly Articles
|
||||||
|
7. Critical or Essential Role
|
||||||
|
8. High Salary or Remuneration
|
||||||
|
|
||||||
|
每项都包含:
|
||||||
|
|
||||||
|
- subtitle
|
||||||
|
- plain-English body
|
||||||
|
- stronger examples
|
||||||
|
- watch out for
|
||||||
|
- bottom line
|
||||||
|
|
||||||
|
页面有 disclaimer:此 overview 是 general information,不是 legal advice,Paprika 不是 law firm。
|
||||||
|
|
||||||
|
## 12. FAQ 页面内容
|
||||||
|
|
||||||
|
FAQ 分组:
|
||||||
|
|
||||||
|
- About Paprika
|
||||||
|
- O-1 Basics
|
||||||
|
- Timeline
|
||||||
|
- Pricing, scope, and refunds
|
||||||
|
|
||||||
|
关键问答覆盖:
|
||||||
|
|
||||||
|
- Paprika 是否 law firm
|
||||||
|
- Paprika 做什么
|
||||||
|
- 适合谁
|
||||||
|
- process 如何工作
|
||||||
|
- 何时开始
|
||||||
|
- 是否 guarantee visa approval
|
||||||
|
- 是否需要 immigration attorney
|
||||||
|
- evidence-building 是否 legitimate
|
||||||
|
- 信息安全
|
||||||
|
- O-1A 基础
|
||||||
|
- 更多证据是否总是更好
|
||||||
|
- top-tier outlet 是否必须
|
||||||
|
- hackathon judging 是否有帮助
|
||||||
|
- timeline 和 rush concerns
|
||||||
|
- pricing、refund、scope
|
||||||
|
|
||||||
|
## 13. Start Here 表单现状
|
||||||
|
|
||||||
|
当前不是实际 `<form>`,只是视觉字段。
|
||||||
|
|
||||||
|
字段视觉:
|
||||||
|
|
||||||
|
- Name required
|
||||||
|
- Email required
|
||||||
|
- LinkedIn
|
||||||
|
- Company Website
|
||||||
|
- Current Role / Company
|
||||||
|
- Field or Industry
|
||||||
|
- Attach Resume or CV optional
|
||||||
|
- What brings you to Paprika? required
|
||||||
|
- What recognition have you already built, if any?
|
||||||
|
- Is there a timeline on your mind?
|
||||||
|
|
||||||
|
提交按钮:
|
||||||
|
|
||||||
|
- `<a class="button primary" href="?page=thanks">Submit Profile</a>`
|
||||||
|
|
||||||
|
边界:
|
||||||
|
|
||||||
|
- 无输入控件
|
||||||
|
- 无校验
|
||||||
|
- 无上传
|
||||||
|
- 无提交接口
|
||||||
|
- 无邮件通知
|
||||||
|
- 无 CRM/数据库记录
|
||||||
|
|
||||||
|
## 14. Ask a Question 表单现状
|
||||||
|
|
||||||
|
当前不是实际 `<form>`,只是视觉字段。
|
||||||
|
|
||||||
|
字段视觉:
|
||||||
|
|
||||||
|
- Name required
|
||||||
|
- Email required
|
||||||
|
- LinkedIn optional
|
||||||
|
- Attach Resume or CV optional
|
||||||
|
- Your question required
|
||||||
|
|
||||||
|
提交按钮:
|
||||||
|
|
||||||
|
- `<a class="button primary" href="?page=thanks">Send Question</a>`
|
||||||
|
|
||||||
|
边界同 Start Here。
|
||||||
|
|
||||||
|
## 15. Legal 页面内容
|
||||||
|
|
||||||
|
已有页面:
|
||||||
|
|
||||||
|
- Terms of Service
|
||||||
|
- Privacy Policy
|
||||||
|
- Disclaimer
|
||||||
|
|
||||||
|
当前固定日期:
|
||||||
|
|
||||||
|
- `Last updated: July 6, 2026`
|
||||||
|
|
||||||
|
风险:
|
||||||
|
|
||||||
|
- 日期已修正为当前工作日期;正式发布前仍需确认是否应改为实际发布日期。
|
||||||
|
- 法务文本涉及 immigration、attorney referral、not a law firm、privacy、refund、payment、third-party services 等高风险内容,必须由负责人或法律顾问确认。
|
||||||
|
|
||||||
|
## 16. 当前已知缺陷和风险
|
||||||
|
|
||||||
|
| 编号 | 问题 | 影响 | 建议 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| RISK-001 | Services 中 04/05/06 CTA 指向不存在的 `contact` 页面 | 点击后回退首页,转化断点 | 改为 `start` 或新增 `contact` |
|
||||||
|
| RISK-002 | Start/Ask 不是表单 | 无法收集线索 | 改成真实 `<form>` 并接后端或第三方表单服务 |
|
||||||
|
| RISK-003 | 法务页面日期晚于当前日期 | 发布可信度和合规风险 | 发布前确认日期 |
|
||||||
|
| RISK-004 | outlet/network 名称可能暗示合作关系 | 法律和商业陈述风险 | 确认授权或改为“representative examples/types” |
|
||||||
|
| RISK-005 | 无 SEO/OG/sitemap/robots | 正式站收录和分享效果弱 | 上线前补齐 |
|
||||||
|
| RISK-006 | 无截图回归和移动端验收 | 可能存在响应式问题 | 引入 Playwright 或人工截图台账 |
|
||||||
|
| RISK-007 | 无部署配置 | 不能可重复发布 | 建立服务、反向代理、HTTPS 和部署脚本 |
|
||||||
|
| RISK-008 | PostgreSQL schema 尚未设计 | 表单无法稳定入库和追踪 | 建立 `submissions`、`submission_files` 等数据表 |
|
||||||
|
| RISK-009 | 文件上传存储未定 | CV/resume 涉及隐私和访问控制 | 先确定本地磁盘、对象存储或禁用上传 |
|
||||||
|
| RISK-010 | 邮箱统一初始密码 | 多账号共用弱初始密码有安全风险 | 首次登录强制修改或生成独立强密码 |
|
||||||
|
| RISK-011 | 网站联系邮箱域名不一致 | `support@meetpaprika.com` 与 `paprikalaw.com` 品牌不统一 | 上线前统一 contact/support/legal 邮箱 |
|
||||||
|
|
||||||
|
## 17. 视觉设计对标
|
||||||
|
|
||||||
|
`doc/index.html` 的视觉风格不是通用模板,而是一套完整的 Paprika 品牌 mockup。后续重构或拆分必须保持以下基线,除非在 `06-决策记录.md` 新增变更决策。
|
||||||
|
|
||||||
|
### 17.1 CSS 变量
|
||||||
|
|
||||||
|
| 变量 | 值 | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `--red` | `#d9432f` | 主品牌红、按钮、kicker、编号 |
|
||||||
|
| `--deep-red` | `#a9372d` | 深红强调、active、结论句 |
|
||||||
|
| `--ink` | `#241b14` | 主文字色 |
|
||||||
|
| `--text` | `#5c5249` | 正文灰褐色 |
|
||||||
|
| `--muted` | `#8d847a` | 次级文字 |
|
||||||
|
| `--line` | `#e9e1d8` | 边线 |
|
||||||
|
| `--paper` | `#fffdfa` | 页面底色 |
|
||||||
|
| `--panel` | `rgba(255,255,255,.86)` | 卡片背景 |
|
||||||
|
| `--green` | `#4f7d61` | 少量绿色标签 |
|
||||||
|
| `--header-text` | `#41505a` | 导航文字 |
|
||||||
|
|
||||||
|
### 17.2 字体和排版
|
||||||
|
|
||||||
|
- 全站主字体:`Avenir`, `"Avenir Next"`, system sans-serif。
|
||||||
|
- 强调语句和 italic accent 使用 `Georgia`, `"Times New Roman"`, serif。
|
||||||
|
- 页面不使用图片资源,视觉主要由排版、红色强调、浅纸色背景、网格纹理、卡片阴影组成。
|
||||||
|
- `h1` 首页字号范围为 `clamp(54px, 6.3vw, 74px)`。
|
||||||
|
- 子页 `h1` 字号范围为 `clamp(44px, 5.2vw, 66px)`。
|
||||||
|
- 主要 section title 使用 `clamp(34px, 4vw, 46px)` 或更大的 `clamp(48px, 6vw, 74px)`。
|
||||||
|
|
||||||
|
### 17.3 背景和布局
|
||||||
|
|
||||||
|
- `body` 背景由两层浅网格线、中心 radial glow 和 `--paper` 组成。
|
||||||
|
- Header 高度 `92px`,sticky top,半透明纸色背景,`backdrop-filter: blur(14px)`。
|
||||||
|
- 主内容容器常用:
|
||||||
|
- `.hero`:`width: min(1040px, calc(100% - 56px))`
|
||||||
|
- `.cards/.section/.form-wrap`:`width: min(1160px, calc(100% - 72px))`
|
||||||
|
- `.legal-doc`:`width: min(920px, calc(100% - 72px))`
|
||||||
|
- 卡片圆角主要是 `4px` 或 `8px`,按钮和 chip 使用 `999px`。
|
||||||
|
|
||||||
|
### 17.4 响应式规则
|
||||||
|
|
||||||
|
唯一显式断点:`@media (max-width: 1080px)`。
|
||||||
|
|
||||||
|
断点内行为:
|
||||||
|
|
||||||
|
- Header 允许换行,`.nav` 占满宽度并 flex-wrap。
|
||||||
|
- `.cards`、`.two-col`、`.grid`、`.split-feature`、`.avoid-grid`、`.why-v2-grid`、`.criteria-grid`、`.faq-section`、`.form-wrap`、`.questions`、`.steps` 等多列布局统一变为单列。
|
||||||
|
- `.field.large`、`.form-note` 从跨两列改为单列。
|
||||||
|
- `.pricing-band` 改为单列并居中。
|
||||||
|
- `.press-check-item` 从 `46px 1fr` 调整为 `38px 1fr`。
|
||||||
|
|
||||||
|
注意:当前只有一个 1080px 断点,未针对窄屏 header 高度、FAQ hero nowrap、长 outlet chip、法务长段落做更细处理,移动端必须截图验收。
|
||||||
|
|
||||||
|
## 18. 组件和 CSS 类对标
|
||||||
|
|
||||||
|
| 模块 | 关键类 | 当前用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Header | `.site-header`, `.brand`, `.brand-name`, `.brand-tag`, `.nav`, `.nav-dropdown`, `.resources-menu`, `.start` | 顶部品牌、导航、下拉和 CTA |
|
||||||
|
| Hero | `.hero`, `.subpage`, `.hero-kicker`, `.subhead`, `.actions`, `.button`, `.microcopy`, `.time-line` | 首页和子页首屏 |
|
||||||
|
| 服务卡 | `.cards`, `.card`, `.meta`, `.num`, `.label`, `.explore` | 首页和 Services 入口 |
|
||||||
|
| 通用内容 | `.section`, `.section-head`, `.section-title`, `.section-copy`, `.two-col`, `.grid`, `.panel`, `.mini`, `.article` | 双栏、四栏、文章卡片 |
|
||||||
|
| CTA | `.services-cta`, `.services-cta-inner`, `.button-arrow` | 服务转化横幅 |
|
||||||
|
| Why | `.why-v2`, `.why-v2-grid`, `.why-card`, `.why-v2-peace`, `.why-card-line` | Why Paprika 页面 |
|
||||||
|
| Resources | `.criteria-page`, `.criteria-intro`, `.criteria-grid`, `.criterion-card`, `.criterion-block`, `.criteria-disclaimer` | O-1A criteria 页面 |
|
||||||
|
| FAQ | `.faq-page`, `.faq-section`, `.faq-section-head`, `.faq-list`, `.faq-item`, `.faq-num` | FAQ 页面 |
|
||||||
|
| Press | `.press-cadence`, `.press-standard`, `.press-check-card`, `.outlet-network`, `.outlet-grid`, `.avoid-section`, `.avoid-card`, `.avoid-explain` | Press 页面 |
|
||||||
|
| Judging | `.judging-counts`, `.judging-counts-grid`, `.judging-network`, `.judging-network-chip`, `.judging-package`, `.check-dot` | Judging 页面 |
|
||||||
|
| Process | `.process-section`, `.steps`, `.step`, `.step-num`, `.pricing-band` | 首页四步流程和价格 |
|
||||||
|
| Form mockup | `.form-wrap`, `.field`, `.upload`, `.upload-inline`, `.large`, `.question-field`, `.start-question-field`, `.form-note`, `.req` | Start/Ask 视觉表单 |
|
||||||
|
| Legal | `.legal-doc`, `.legal-doc-head`, `.legal-date`, `.legal-sections`, `.legal-section` | Terms/Privacy/Disclaimer |
|
||||||
|
| Footer | `footer`, `.footer-links` | 底部链接和固定声明 |
|
||||||
|
|
||||||
|
已存在但当前页面未使用或未公开使用的样式/函数相关模块:
|
||||||
|
|
||||||
|
- `.resource-hub`, `.resource-tabs`, `.resource-group`, `.resource-card`:对应 `resourcesHub()`,当前 `pageData.resources` 使用的是 `o1CriteriaPage()`,未使用 hub。
|
||||||
|
- `.why-proof`, `.why-reason`, `.why-closing`:疑似旧版 Why 结构,当前 `whyPaprikaProof()` 使用 `.why-v2`。
|
||||||
|
- `pressQuality()`:旧版 press 对比区块,当前 `pressPage()` 未调用。
|
||||||
|
- `standardGrid()`:标准 mini grid,当前未调用。
|
||||||
|
|
||||||
|
## 19. JavaScript 函数对标
|
||||||
|
|
||||||
|
| 函数/常量 | 当前状态 | 作用 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `serviceCards` | 使用中 | 首页三张服务卡 HTML 片段 |
|
||||||
|
| `pageData` | 使用中 | 所有路由页面数据表 |
|
||||||
|
| `hero()` | 使用中 | 通用子页 hero,含 Start/Ask CTA |
|
||||||
|
| `heroWithKicker()` | 使用中 | 带 kicker 的 hero,可关闭 actions |
|
||||||
|
| `servicesCta()` | 使用中 | 红色服务 CTA 横幅 |
|
||||||
|
| `card()` | 使用中 | 服务卡片 |
|
||||||
|
| `panel()` | 使用中 | 双栏 panel |
|
||||||
|
| `productPage()` | 当前未调用 | 通用产品页工厂 |
|
||||||
|
| `judgingPage()` | 使用中 | Judging 页面组合 |
|
||||||
|
| `scholarlyPage()` | 使用中 | Scholarly 页面组合 |
|
||||||
|
| `productPageWithKicker()` | 当前未调用 | 带 kicker 的产品页工厂 |
|
||||||
|
| `pressPage()` | 使用中 | Press 页面组合 |
|
||||||
|
| `pressHero()` | 使用中 | Press 专属 hero |
|
||||||
|
| `judgingCounts()` | 使用中 | Judging counts 区块 |
|
||||||
|
| `judgingCountCard()` | 使用中 | Judging count card |
|
||||||
|
| `judgingNetwork()` | 使用中 | Judging network chips |
|
||||||
|
| `judgingNetworkGroup()` | 当前未调用 | 分组 network chips |
|
||||||
|
| `judgingPackage()` | 使用中 | Judging evidence package |
|
||||||
|
| `judgingPackageCard()` | 使用中 | Package card |
|
||||||
|
| `pressLookFor()` | 使用中 | Press standards checklist |
|
||||||
|
| `outletNetwork()` | 使用中 | Representative outlet chips |
|
||||||
|
| `pressCheck()` | 使用中 | Press checklist item |
|
||||||
|
| `pressCadenceNote()` | 使用中 | Press cadence note |
|
||||||
|
| `pressAvoidSection()` | 使用中 | Press avoid section |
|
||||||
|
| `avoidCard()` | 使用中 | Avoid card |
|
||||||
|
| `miniGrid()` | 使用中 | mini card grid |
|
||||||
|
| `articleGrid()` | 使用中 | blog article grid |
|
||||||
|
| `o1CriteriaPage()` | 使用中 | O-1A 8 criteria 页面 |
|
||||||
|
| `criterionCard()` | 使用中 | Criteria card |
|
||||||
|
| `faqPage()` | 使用中 | FAQ 页面 |
|
||||||
|
| `faqGroup()` | 使用中 | FAQ 分组 |
|
||||||
|
| `faqItem()` | 使用中 | FAQ 单项 |
|
||||||
|
| `resourcesHub()` | 当前未调用 | Resource hub 草稿 |
|
||||||
|
| `resourceGroup()` | 当前未调用 | Resource group 草稿 |
|
||||||
|
| `legalPage()` | 当前未调用 | 旧版 legal page 工厂 |
|
||||||
|
| `legalTermsPage()` | 使用中 | Terms 页面 |
|
||||||
|
| `legalPrivacyPage()` | 使用中 | Privacy 页面 |
|
||||||
|
| `legalDisclaimerPage()` | 使用中 | Disclaimer 页面 |
|
||||||
|
| `disclaimerSection()` | 使用中 | Disclaimer section |
|
||||||
|
| `disclaimerPart()` | 使用中 | Disclaimer p/ul 渲染 |
|
||||||
|
| `whyEarlySection()` | 使用中 | 首页 Start Early |
|
||||||
|
| `servicesIntro()` | 使用中 | 首页 Core services intro |
|
||||||
|
| `standardPreview()` | 使用中 | 首页标准问题 |
|
||||||
|
| `question()` | 使用中 | 标准问题 item |
|
||||||
|
| `howItWorks()` | 使用中 | 首页四步流程和价格 |
|
||||||
|
| `step()` | 使用中 | 流程 step |
|
||||||
|
| `standardGrid()` | 当前未调用 | 标准 grid 草稿 |
|
||||||
|
| `whyPaprikaProof()` | 使用中 | Why 页面 v2 内容 |
|
||||||
|
| `whyV2Card()` | 使用中 | Why card |
|
||||||
|
| `pressQuality()` | 当前未调用 | 旧版 Press quality 对比 |
|
||||||
|
|
||||||
|
最终渲染逻辑:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const page = params.get("page") || "home";
|
||||||
|
const selected = pageData[page] ? page : "home";
|
||||||
|
document.title = `Paprika - ${selected}`;
|
||||||
|
document.getElementById("app").innerHTML = pageData[selected].body;
|
||||||
|
```
|
||||||
|
|
||||||
|
active 逻辑:
|
||||||
|
|
||||||
|
- `[data-page]` 与 `selected` 相同时添加 `.active`。
|
||||||
|
- `selected` 为 `faq` 或 `resources` 时,额外给 `.nav-trigger` 添加 `.active`。
|
||||||
|
|
||||||
|
## 20. CTA 和链接全量映射
|
||||||
|
|
||||||
|
| 位置 | 文案 | 目标 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Header brand | Paprika | `?page=home` |
|
||||||
|
| Header nav | Press | `?page=press` |
|
||||||
|
| Header nav | Judging | `?page=judging` |
|
||||||
|
| Header nav | Scholarly Articles | `?page=scholarly` |
|
||||||
|
| Header nav | Services | `?page=services` |
|
||||||
|
| Header nav | Why Paprika | `?page=why` |
|
||||||
|
| Header Resources | Resources | `?page=resources` |
|
||||||
|
| Resources menu | Frequently Asked Questions | `?page=faq` |
|
||||||
|
| Resources menu | O-1A Criteria Overview | `?page=resources` |
|
||||||
|
| Header CTA | Start Here | `?page=start` |
|
||||||
|
| Footer | About | `?page=why` |
|
||||||
|
| Footer | FAQ | `?page=faq` |
|
||||||
|
| Footer | Terms | `?page=terms` |
|
||||||
|
| Footer | Privacy | `?page=privacy` |
|
||||||
|
| Footer | Disclaimer | `?page=disclaimer` |
|
||||||
|
| Home hero | Send Us Your Profile | `?page=start` |
|
||||||
|
| Home hero | Ask a Question | `?page=ask` |
|
||||||
|
| 通用 hero | Start Here | `?page=start` |
|
||||||
|
| 通用 hero | Ask a Question | `?page=ask` |
|
||||||
|
| Services CTA | Send Us Your Profile | `?page=start` |
|
||||||
|
| Home pricing band | See Services | `?page=services` |
|
||||||
|
| Start mockup | Submit Profile | `?page=thanks` |
|
||||||
|
| Ask mockup | Send Question | `?page=thanks` |
|
||||||
|
| Thanks | Back to Homepage | `?page=home` |
|
||||||
|
| Service card 01 | Explore Press | `?page=press` |
|
||||||
|
| Service card 02 | Explore Judging | `?page=judging` |
|
||||||
|
| Service card 03 | Explore Articles | `?page=scholarly` |
|
||||||
|
| Service card 04 | Start Here | 原为 `?page=contact`,已修复为 `?page=start` |
|
||||||
|
| Service card 05 | Start Here | 原为 `?page=contact`,已修复为 `?page=start` |
|
||||||
|
| Service card 06 | Start Here | 原为 `?page=contact`,已修复为 `?page=start` |
|
||||||
|
|
||||||
|
## 21. FAQ 完整编号对标
|
||||||
|
|
||||||
|
当前 FAQ 共 4 组、20 个问题。
|
||||||
|
|
||||||
|
| 编号范围 | 分组 kicker | 分组标题 | 问题数量 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 01-09 | About Paprika | How Paprika works. | 9 |
|
||||||
|
| 10-14 | O1 Visa | O-1 Basics | 5 |
|
||||||
|
| 15-18 | Timeline | How long things take. | 4 |
|
||||||
|
| 19-20 | Pricing Structure | Pricing, scope, and refunds | 2 |
|
||||||
|
|
||||||
|
需要注意的原文问题:
|
||||||
|
|
||||||
|
- `O1 Visa` kicker 少了 hyphen,是否改为 `O-1 Visa` 需产品确认。
|
||||||
|
- FAQ 15 中原文 `Coverage spaced out over time demonstrate...` 主谓可能不一致。
|
||||||
|
- FAQ 20 写了 “If we are unable to secure the agreed-upon media placements...” 可能只覆盖 media placements,不覆盖 judging/scholarly,应由法务或产品确认。
|
||||||
|
|
||||||
|
## 22. Legal 页面逐项结构对标
|
||||||
|
|
||||||
|
### Terms of Service
|
||||||
|
|
||||||
|
当前 Terms 共 18 个 section,但编号为 1-16、18、19,缺少 17。
|
||||||
|
|
||||||
|
| 编号 | 标题 |
|
||||||
|
| --- | --- |
|
||||||
|
| 1 | Agreement to These Terms |
|
||||||
|
| 2 | What Paprika Provides |
|
||||||
|
| 3 | Not a Law Firm — No Legal Advice |
|
||||||
|
| 4 | No Attorney-Client Relationship |
|
||||||
|
| 5 | No Guarantee of Immigration Outcomes |
|
||||||
|
| 6 | Attorney Referrals |
|
||||||
|
| 7 | Fees, Payment, and Refunds |
|
||||||
|
| 8 | Client Responsibilities and Accuracy of Information |
|
||||||
|
| 9 | Evidence Integrity |
|
||||||
|
| 10 | Third-Party Decisions |
|
||||||
|
| 11 | Your Materials |
|
||||||
|
| 12 | Work Product |
|
||||||
|
| 13 | Prohibited Uses |
|
||||||
|
| 14 | Disclaimer of Warranties |
|
||||||
|
| 15 | Limitation of Liability |
|
||||||
|
| 16 | Changes to These Terms |
|
||||||
|
| 18 | Governing Law |
|
||||||
|
| 19 | Contact |
|
||||||
|
|
||||||
|
### Privacy Policy
|
||||||
|
|
||||||
|
当前 Privacy 共 12 个 section:
|
||||||
|
|
||||||
|
1. Introduction
|
||||||
|
2. Information We Collect
|
||||||
|
3. How We Use Your Information
|
||||||
|
4. How We Share Your Information
|
||||||
|
5. Data Storage and Security
|
||||||
|
6. Data Retention
|
||||||
|
7. Your Rights
|
||||||
|
8. Cookies
|
||||||
|
9. Children's Privacy
|
||||||
|
10. International Users
|
||||||
|
11. Changes to This Policy
|
||||||
|
12. Contact
|
||||||
|
|
||||||
|
### Disclaimer
|
||||||
|
|
||||||
|
当前 Disclaimer 共 9 个 section:
|
||||||
|
|
||||||
|
1. Not a Law Firm
|
||||||
|
2. What "Attorney-Curated" Means
|
||||||
|
3. No Attorney-Client Relationship
|
||||||
|
4. Independent Counsel
|
||||||
|
5. Attorney Referral Disclosure
|
||||||
|
6. No Outcome Guarantee
|
||||||
|
7. User Responsibility
|
||||||
|
8. General Information Only
|
||||||
|
9. Questions
|
||||||
|
|
||||||
|
需要注意的原文问题:
|
||||||
|
|
||||||
|
- Disclaimer 中 `Paprika's evidence-building services is shaped...` 主谓不一致。
|
||||||
|
- Disclaimer 中 `Use of Paprika's does not create...` 语句不完整。
|
||||||
|
- 三个 legal 页面日期已由 `Last updated: July 10, 2026` 修正为 `Last updated: July 6, 2026`。
|
||||||
|
- 当前日期为 `2026-07-06`,发布前仍需法务确认文本内容和最终发布日期。
|
||||||
|
|
||||||
|
## 23. 源码级对标结论
|
||||||
|
|
||||||
|
- `doc/index.html` 共 3423 行。
|
||||||
|
- 前 2261 行主要是 CSS 和 HTML shell。
|
||||||
|
- 2262 行后进入 body、header、main、footer、script。
|
||||||
|
- 2300 行后定义 `serviceCards` 和 `pageData`。
|
||||||
|
- 2428 行后定义组件/页面函数。
|
||||||
|
- 3410 行后执行路由选择、渲染和 active 状态设置。
|
||||||
|
- 当前没有外部依赖、图片、字体文件、构建脚本、后端接口或测试。
|
||||||
|
|
||||||
|
## 24. 后续推进入口
|
||||||
|
|
||||||
|
- 任务领取:`04-任务矩阵.md`
|
||||||
|
- 每轮记录:`03-推进台账.md`
|
||||||
|
- 验收证据:`05-验收证据.md`
|
||||||
|
- 决策依据:`06-决策记录.md`
|
||||||
|
- 开发步骤:`02-项目程序开发详细步骤.md`
|
||||||
670
working/02-项目程序开发详细步骤.md
Normal file
670
working/02-项目程序开发详细步骤.md
Normal file
@@ -0,0 +1,670 @@
|
|||||||
|
# 02-项目程序开发详细步骤
|
||||||
|
|
||||||
|
## 1. 开发原则
|
||||||
|
|
||||||
|
1. 先完整对标 `doc/index.html`,再做工程化拆分。
|
||||||
|
2. 每一轮开发只领取 `04-任务矩阵.md` 中明确编号的任务,避免重复做。
|
||||||
|
3. 每一轮必须同步更新:
|
||||||
|
- `03-推进台账.md`
|
||||||
|
- `04-任务矩阵.md`
|
||||||
|
- `05-验收证据.md`
|
||||||
|
- 必要时更新 `06-决策记录.md`
|
||||||
|
4. 涉及移民、法律、隐私、退款、attorney referral、表单收集的内容,必须留有验收证据和确认记录。
|
||||||
|
5. 不把明文服务器密码写入长期文档;部署凭据用 SSH key、环境变量或受控密钥库。
|
||||||
|
|
||||||
|
## 2. 当前基线复现
|
||||||
|
|
||||||
|
目标:确认 `doc/index.html` 当前 mockup 可回溯、可打开、可作为工程化基准。
|
||||||
|
|
||||||
|
步骤:
|
||||||
|
|
||||||
|
1. 进入项目目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/mes123456/MeetPaprika
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 确认文件存在:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
test -f doc/index.html && wc -l doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 检查当前页面路由和脚本入口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n "pageData|URLSearchParams|data-page|page=start|page=ask|page=terms|page=privacy|page=disclaimer" doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 浏览器打开以下页面并记录截图:
|
||||||
|
|
||||||
|
- `doc/index.html?page=home`
|
||||||
|
- `doc/index.html?page=press`
|
||||||
|
- `doc/index.html?page=judging`
|
||||||
|
- `doc/index.html?page=scholarly`
|
||||||
|
- `doc/index.html?page=services`
|
||||||
|
- `doc/index.html?page=why`
|
||||||
|
- `doc/index.html?page=resources`
|
||||||
|
- `doc/index.html?page=faq`
|
||||||
|
- `doc/index.html?page=start`
|
||||||
|
- `doc/index.html?page=ask`
|
||||||
|
- `doc/index.html?page=terms`
|
||||||
|
- `doc/index.html?page=privacy`
|
||||||
|
- `doc/index.html?page=disclaimer`
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 所有公开路由能渲染。
|
||||||
|
- 未识别 `?page=unknown` 回退 `home`。
|
||||||
|
- 顶部导航 active 状态正确。
|
||||||
|
- Resources 下拉可见且链接正确。
|
||||||
|
- 证据记录写入 `05-验收证据.md`。
|
||||||
|
|
||||||
|
## 3. 阶段 A:静态 mockup 修复
|
||||||
|
|
||||||
|
目标:不引入构建系统,先修复当前单文件中影响体验和转化的明确问题。
|
||||||
|
|
||||||
|
任务:
|
||||||
|
|
||||||
|
1. 修复 Services 页面 04/05/06 卡片 CTA。
|
||||||
|
- 已由 `contact` 改为 `start`。
|
||||||
|
2. 明确 `blog` 和 `about` 是否保留。
|
||||||
|
- 若保留,加入导航或 footer。
|
||||||
|
- 若不保留,记录为隐藏路由。
|
||||||
|
3. 检查 legal 页面日期。
|
||||||
|
- 已由 `July 10, 2026` 改为 `July 6, 2026`。
|
||||||
|
- 当前工作日期为 `2026-07-06`。
|
||||||
|
4. 检查首页、Press、Judging、Services、FAQ、Legal 移动端布局。
|
||||||
|
5. 补充基础 SEO:
|
||||||
|
- description
|
||||||
|
- canonical
|
||||||
|
- Open Graph
|
||||||
|
- favicon 占位或正式资源
|
||||||
|
6. 将 mockup 表单明确标注为占位,或进入阶段 B 改为真实表单。
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 无明显死链。
|
||||||
|
- 所有 CTA 指向存在页面。
|
||||||
|
- 法务日期有明确确认。
|
||||||
|
- 桌面和移动端核心页面无明显重叠。
|
||||||
|
- 变更写入台账和证据。
|
||||||
|
|
||||||
|
## 4. 阶段 B:真实表单与线索收集
|
||||||
|
|
||||||
|
目标:Start Here 和 Ask a Question 从视觉 mockup 变成真实可提交入口。
|
||||||
|
|
||||||
|
### 推荐字段
|
||||||
|
|
||||||
|
Start Here:
|
||||||
|
|
||||||
|
- name required
|
||||||
|
- email required
|
||||||
|
- linkedin
|
||||||
|
- company_website
|
||||||
|
- current_role_company
|
||||||
|
- field_or_industry
|
||||||
|
- resume_cv optional upload
|
||||||
|
- reason required
|
||||||
|
- existing_recognition
|
||||||
|
- timeline
|
||||||
|
- consent/disclaimer acknowledgement
|
||||||
|
|
||||||
|
Ask a Question:
|
||||||
|
|
||||||
|
- name required
|
||||||
|
- email required
|
||||||
|
- linkedin optional
|
||||||
|
- resume_cv optional upload
|
||||||
|
- question required
|
||||||
|
- consent/disclaimer acknowledgement
|
||||||
|
|
||||||
|
### 实现路径选项
|
||||||
|
|
||||||
|
| 方案 | 适用 | 优点 | 风险 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 第三方表单服务 | 快速上线 | 开发少,有后台记录 | 文件上传和隐私策略受限 |
|
||||||
|
| 自建轻量 API | 需要控制数据 | 可控、可扩展 | 需要安全、日志、备份 |
|
||||||
|
| CRM 直连 | 销售流程明确 | 直接进入业务系统 | 依赖第三方 API 和权限 |
|
||||||
|
|
||||||
|
建议优先级:
|
||||||
|
|
||||||
|
1. 若目标是快速 HTTPS 上线:先接第三方表单或 webhook。
|
||||||
|
2. 若会收 CV/敏感信息:优先自建 API,加上传限制和访问控制。
|
||||||
|
3. 若已有 CRM:按 CRM 字段建映射并保留 submission id。
|
||||||
|
|
||||||
|
### 表单验收标准
|
||||||
|
|
||||||
|
- 必填字段缺失时不能提交。
|
||||||
|
- email 格式错误时提示。
|
||||||
|
- 文件上传明确限制类型和大小。
|
||||||
|
- 成功后有 submission id 或后台记录 id。
|
||||||
|
- 失败时显示错误状态,不静默跳转。
|
||||||
|
- disclaimer 可见:提交不创建 attorney-client relationship。
|
||||||
|
- 验收证据包含页面截图、提交记录、job_id/report_id/submission id。
|
||||||
|
|
||||||
|
## 5. 阶段 C:Next.js + React + PostgreSQL 工程化
|
||||||
|
|
||||||
|
目标:把单文件 mockup 迁移为 Next.js + React 应用,并接入 PostgreSQL,形成可维护、可部署、可验收的正式工程。
|
||||||
|
|
||||||
|
### 5.1 技术架构
|
||||||
|
|
||||||
|
- 前端和服务端框架:Next.js + React
|
||||||
|
- 语言:TypeScript
|
||||||
|
- 样式:先使用全局 CSS / CSS Modules 迁移 `doc/index.html` 视觉;不强行引入 UI 框架
|
||||||
|
- 数据库:PostgreSQL
|
||||||
|
- ORM:Prisma
|
||||||
|
- 表单校验:Zod 或等价 schema 校验
|
||||||
|
- 部署:Node.js 进程监听 `8082`,Nginx/Caddy 提供 HTTPS 反向代理
|
||||||
|
- 验收:Playwright 页面截图 + API/数据库写入验证
|
||||||
|
|
||||||
|
### 5.2 初始化工程
|
||||||
|
|
||||||
|
当前已完成初始化。保留建议命令供复建参考:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx create-next-app@latest paprika-site --ts --eslint --app --src-dir=false
|
||||||
|
cd paprika-site
|
||||||
|
npm install prisma @prisma/client zod
|
||||||
|
npx prisma init
|
||||||
|
```
|
||||||
|
|
||||||
|
本项目已直接在当前根目录工程化,并保留 `doc/index.html` 为对标基线。
|
||||||
|
|
||||||
|
### 5.3 页面迁移
|
||||||
|
|
||||||
|
从 `doc/index.html` 迁移为真实路由:
|
||||||
|
|
||||||
|
| 旧路由 | Next.js 路由 |
|
||||||
|
| --- | --- |
|
||||||
|
| `?page=home` | `/` |
|
||||||
|
| `?page=press` | `/press` |
|
||||||
|
| `?page=judging` | `/judging` |
|
||||||
|
| `?page=scholarly` | `/scholarly` |
|
||||||
|
| `?page=services` | `/services` |
|
||||||
|
| `?page=why` | `/why` |
|
||||||
|
| `?page=resources` | `/resources` |
|
||||||
|
| `?page=faq` | `/faq` |
|
||||||
|
| `?page=start` | `/start` |
|
||||||
|
| `?page=ask` | `/ask` |
|
||||||
|
| `?page=thanks` | `/thanks` |
|
||||||
|
| `?page=terms` | `/terms` |
|
||||||
|
| `?page=privacy` | `/privacy` |
|
||||||
|
| `?page=disclaimer` | `/disclaimer` |
|
||||||
|
|
||||||
|
隐藏路由 `blog`、`about` 先不要公开,除非产品确认。
|
||||||
|
|
||||||
|
### 5.4 PostgreSQL 数据模型初稿
|
||||||
|
|
||||||
|
建议最小表:
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
model Submission {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
type String
|
||||||
|
name String
|
||||||
|
email String
|
||||||
|
linkedin String?
|
||||||
|
companyWebsite String?
|
||||||
|
currentRoleCompany String?
|
||||||
|
fieldOrIndustry String?
|
||||||
|
reason String?
|
||||||
|
existingRecognition String?
|
||||||
|
timeline String?
|
||||||
|
question String?
|
||||||
|
consentAccepted Boolean @default(false)
|
||||||
|
sourcePath String?
|
||||||
|
status String @default("new")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
files SubmissionFile[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model SubmissionFile {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
submissionId String
|
||||||
|
fileName String
|
||||||
|
mimeType String
|
||||||
|
byteSize Int
|
||||||
|
storageKey String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
submission Submission @relation(fields: [submissionId], references: [id])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
- `type`:`start` 或 `ask`
|
||||||
|
- `sourcePath`:提交来源,如 `/start`、`/ask`
|
||||||
|
- `status`:`new`、`reviewed`、`spam`、`archived`
|
||||||
|
- 文件上传未落地前,可以先不创建 `SubmissionFile` 或禁用上传。
|
||||||
|
|
||||||
|
### 5.5 表单 API
|
||||||
|
|
||||||
|
建议 API:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/submissions
|
||||||
|
```
|
||||||
|
|
||||||
|
请求类型:
|
||||||
|
|
||||||
|
- `type=start`
|
||||||
|
- `type=ask`
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 必填字段缺失返回 400。
|
||||||
|
- 邮箱格式错误返回 400。
|
||||||
|
- 成功写入 PostgreSQL 并返回 `submission_id`。
|
||||||
|
- 前端成功后跳转 `/thanks?submission_id=...` 或展示成功状态。
|
||||||
|
- 失败时显示明确错误。
|
||||||
|
|
||||||
|
### 5.6 环境变量
|
||||||
|
|
||||||
|
`.env` 至少包含:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/paprika"
|
||||||
|
NEXT_PUBLIC_SITE_URL="https://paprikalaw.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
规则:
|
||||||
|
|
||||||
|
- `.env` 不提交。
|
||||||
|
- 服务器凭据和数据库密码不写入 `working` 文档。
|
||||||
|
- 生产数据库用户只授予应用所需权限。
|
||||||
|
|
||||||
|
### 5.7 服务器 PostgreSQL 验证
|
||||||
|
|
||||||
|
用户已确认 PostgreSQL 在服务器 `170.106.192.152` 安装完成。正式接入前需要在服务器上验证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh ubuntu@170.106.192.152
|
||||||
|
systemctl status postgresql --no-pager
|
||||||
|
psql --version
|
||||||
|
sudo -u postgres psql -c '\l'
|
||||||
|
```
|
||||||
|
|
||||||
|
建议创建应用数据库和受限应用用户:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE paprika;
|
||||||
|
CREATE USER paprika_app WITH ENCRYPTED PASSWORD 'REPLACE_WITH_SECURE_PASSWORD';
|
||||||
|
GRANT CONNECT ON DATABASE paprika TO paprika_app;
|
||||||
|
```
|
||||||
|
|
||||||
|
进入数据库后授予 schema 权限:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
\c paprika
|
||||||
|
GRANT USAGE, CREATE ON SCHEMA public TO paprika_app;
|
||||||
|
```
|
||||||
|
|
||||||
|
生产 `DATABASE_URL` 示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DATABASE_URL="postgresql://paprika_app:REPLACE_WITH_SECURE_PASSWORD@127.0.0.1:5432/paprika"
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- 不使用 SSH 登录密码作为数据库密码。
|
||||||
|
- 不把数据库密码写入仓库、`working` 文档或聊天后的可复制文档。
|
||||||
|
- 如果 PostgreSQL 仅供本机 Next.js 使用,优先监听 `127.0.0.1`,不要对公网开放 5432。
|
||||||
|
|
||||||
|
### 5.8 构建和本地验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run generate:legacy
|
||||||
|
npm run prisma:generate
|
||||||
|
npm run lint
|
||||||
|
npx tsc --noEmit
|
||||||
|
npm run build
|
||||||
|
npm run start -- -p 8082
|
||||||
|
```
|
||||||
|
|
||||||
|
本地生产服务冒烟:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for p in / /press /judging /scholarly /services /why /resources /faq /start /ask /thanks /terms /privacy /disclaimer /robots.txt /sitemap.xml; do
|
||||||
|
code=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8082$p)
|
||||||
|
printf '%s %s\n' "$code" "$p"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
生产数据库迁移:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx prisma migrate deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- `npm run dev` 可本地启动。
|
||||||
|
- `npm run build` 通过。
|
||||||
|
- `npm run start -- -p 8082` 可在 8082 启动。
|
||||||
|
- `npx prisma migrate deploy` 可在生产环境执行。
|
||||||
|
- 核心页面内容、视觉和 CTA 与 `doc/index.html` 对标。
|
||||||
|
- Start/Ask 提交能写入 PostgreSQL。当前本地未配置 `DATABASE_URL` 时只能验证 400 校验路径和 500 失败路径,不能关闭入库验收。
|
||||||
|
|
||||||
|
## 6. 阶段 D:部署到云服务器
|
||||||
|
|
||||||
|
目标:让 `https://paprikalaw.com/` 访问目标站点,内部服务端口使用 `8082`。
|
||||||
|
|
||||||
|
### 服务器信息
|
||||||
|
|
||||||
|
- Host:`170.106.192.152`
|
||||||
|
- User:`ubuntu`
|
||||||
|
- 端口范围:`8080-8099` 已开放
|
||||||
|
- 本项目端口:`8082`
|
||||||
|
- 密码:不写入文档;使用用户交接的临时凭据登录后立即配置 SSH key
|
||||||
|
|
||||||
|
### 建议部署结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
/opt/paprika/
|
||||||
|
current/
|
||||||
|
releases/
|
||||||
|
shared/
|
||||||
|
.env
|
||||||
|
logs/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 基础命令模板
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh ubuntu@170.106.192.152
|
||||||
|
sudo mkdir -p /opt/paprika/releases /opt/paprika/shared/logs
|
||||||
|
sudo chown -R ubuntu:ubuntu /opt/paprika
|
||||||
|
```
|
||||||
|
|
||||||
|
### Next.js 服务示例
|
||||||
|
|
||||||
|
构建并启动:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
npm run start -- -p 8082
|
||||||
|
```
|
||||||
|
|
||||||
|
生产建议用 systemd:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Paprika static site
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=ubuntu
|
||||||
|
WorkingDirectory=/opt/paprika/current
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
EnvironmentFile=/opt/paprika/shared/.env
|
||||||
|
ExecStart=/usr/bin/npm run start -- -p 8082
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx 反向代理示例
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name paprikalaw.com www.paprikalaw.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8082;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
HTTPS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo certbot --nginx -d paprikalaw.com -d www.paprikalaw.com
|
||||||
|
```
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- `curl -I http://127.0.0.1:8082` 在服务器上返回 200。
|
||||||
|
- `curl -I https://paprikalaw.com/` 返回 200。
|
||||||
|
- `https://paprikalaw.com/`、`/start`、`/faq` 可访问。
|
||||||
|
- `POST /api/submissions` 能写入 PostgreSQL 并返回 `submission_id`。
|
||||||
|
- systemd 服务 `active (running)`。
|
||||||
|
- Nginx 配置通过 `sudo nginx -t`。
|
||||||
|
- HTTPS 证书有效。
|
||||||
|
|
||||||
|
## 7. 阶段 E:回归验收
|
||||||
|
|
||||||
|
目标:上线前后能证明页面、表单、合规、部署都可用。
|
||||||
|
|
||||||
|
### 页面回归
|
||||||
|
|
||||||
|
必测页面:
|
||||||
|
|
||||||
|
- home
|
||||||
|
- press
|
||||||
|
- judging
|
||||||
|
- scholarly
|
||||||
|
- services
|
||||||
|
- why
|
||||||
|
- resources
|
||||||
|
- faq
|
||||||
|
- start
|
||||||
|
- ask
|
||||||
|
- thanks
|
||||||
|
- terms
|
||||||
|
- privacy
|
||||||
|
- disclaimer
|
||||||
|
|
||||||
|
### 设备尺寸
|
||||||
|
|
||||||
|
- Desktop:1440x900
|
||||||
|
- Tablet:768x1024
|
||||||
|
- Mobile:390x844
|
||||||
|
|
||||||
|
### 验收内容
|
||||||
|
|
||||||
|
- 页面可访问。
|
||||||
|
- 首屏品牌和 CTA 可见。
|
||||||
|
- Header 不遮挡内容。
|
||||||
|
- 文本无明显重叠。
|
||||||
|
- CTA 链接可达。
|
||||||
|
- 表单可提交或明确是 mockup。
|
||||||
|
- Footer legal 链接可达。
|
||||||
|
- Legal disclaimer 可见。
|
||||||
|
|
||||||
|
### 证据格式
|
||||||
|
|
||||||
|
每次验收写入 `05-验收证据.md`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
EV-YYYYMMDD-序号
|
||||||
|
时间:
|
||||||
|
任务:
|
||||||
|
命令/页面:
|
||||||
|
结果:
|
||||||
|
证据文件:
|
||||||
|
job_id/report_id/submission_id:
|
||||||
|
结论:
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 阶段 F:后续产品增强
|
||||||
|
|
||||||
|
可选任务:
|
||||||
|
|
||||||
|
- 增加真实 Contact 页面。
|
||||||
|
- 增加 Blog/Resources 内容系统。
|
||||||
|
- 增加案例或证据包示例,但必须避免误导和隐私泄露。
|
||||||
|
- 增加 analytics,并同步 Privacy Policy。
|
||||||
|
- 增加 sitemap、robots、structured data。
|
||||||
|
- 增加 Playwright screenshot regression。
|
||||||
|
- 增加后台 leads 管理或 CRM 同步。
|
||||||
|
|
||||||
|
## 9. 阶段 G:邮箱系统配置与验证
|
||||||
|
|
||||||
|
目标:确认 `mail.paprikalaw.com` 邮件系统可用,管理员和普通公司邮箱能登录、收发和承接网站通知。
|
||||||
|
|
||||||
|
### 9.1 邮箱入口
|
||||||
|
|
||||||
|
- 普通用户网页登录:`https://mail.paprikalaw.com/webmail/`
|
||||||
|
- 管理员网页登录:`https://mail.paprikalaw.com/admin/`
|
||||||
|
- 管理员邮箱:`admin@paprikalaw.com`
|
||||||
|
|
||||||
|
### 9.2 需要创建或确认的普通邮箱
|
||||||
|
|
||||||
|
```text
|
||||||
|
info@paprikalaw.com
|
||||||
|
contact@paprikalaw.com
|
||||||
|
support@paprikalaw.com
|
||||||
|
sales@paprikalaw.com
|
||||||
|
marketing@paprikalaw.com
|
||||||
|
hr@paprikalaw.com
|
||||||
|
finance@paprikalaw.com
|
||||||
|
billing@paprikalaw.com
|
||||||
|
legal@paprikalaw.com
|
||||||
|
service@paprikalaw.com
|
||||||
|
```
|
||||||
|
|
||||||
|
凭据规则:
|
||||||
|
|
||||||
|
- 用户已提供统一初始密码,但不写入长期文档。
|
||||||
|
- 建议创建后立即为每个邮箱设置独立强密码。
|
||||||
|
- 如果邮件系统支持,启用首次登录改密、2FA、登录审计和反垃圾策略。
|
||||||
|
|
||||||
|
### 9.3 DNS 和投递验证
|
||||||
|
|
||||||
|
需要确认:
|
||||||
|
|
||||||
|
- `mail.paprikalaw.com` A/AAAA 记录正确。
|
||||||
|
- `paprikalaw.com` MX 记录指向邮件服务器。
|
||||||
|
- SPF 记录包含授权发件源。
|
||||||
|
- DKIM 已生成并加入 DNS。
|
||||||
|
- DMARC 已配置,至少先从 `p=none` 观察。
|
||||||
|
- 反向 DNS/PTR 与邮件服务器发信域名匹配。
|
||||||
|
|
||||||
|
建议验证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dig MX paprikalaw.com
|
||||||
|
dig TXT paprikalaw.com
|
||||||
|
dig TXT default._domainkey.paprikalaw.com
|
||||||
|
dig TXT _dmarc.paprikalaw.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.4 网站表单通知接入
|
||||||
|
|
||||||
|
Next.js 表单写入 PostgreSQL 后,可选邮件通知:
|
||||||
|
|
||||||
|
- 发件邮箱:建议 `support@paprikalaw.com` 或专用 `no-reply@paprikalaw.com`
|
||||||
|
- 收件邮箱:建议 `support@paprikalaw.com`、`contact@paprikalaw.com`
|
||||||
|
- 表单类型:
|
||||||
|
- `/start` 提交通知
|
||||||
|
- `/ask` 提交通知
|
||||||
|
- 邮件内容不得包含完整敏感 CV 文件,只发送 submission id 和后台查看提示。
|
||||||
|
|
||||||
|
环境变量示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SMTP_HOST="mail.paprikalaw.com"
|
||||||
|
SMTP_PORT="587"
|
||||||
|
SMTP_USER="support@paprikalaw.com"
|
||||||
|
SMTP_PASSWORD="REPLACE_WITH_SECURE_PASSWORD"
|
||||||
|
LEADS_NOTIFY_TO="support@paprikalaw.com,contact@paprikalaw.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
验收标准:
|
||||||
|
|
||||||
|
- 管理员能登录 admin。
|
||||||
|
- 10 个普通邮箱能登录 webmail。
|
||||||
|
- 至少完成一封内部互发、一封外部收信、一封外部发信测试。
|
||||||
|
- SPF/DKIM/DMARC 检查通过或有明确待处理项。
|
||||||
|
- 表单邮件通知如启用,必须记录 message id 或测试截图。
|
||||||
|
|
||||||
|
## 10. 每轮工作固定流程
|
||||||
|
|
||||||
|
1. 查看 `04-任务矩阵.md`,领取一个或一组任务。
|
||||||
|
2. 查看相关决策:`06-决策记录.md`。
|
||||||
|
3. 修改代码或文档。
|
||||||
|
4. 执行对应验证。
|
||||||
|
5. 将证据写入 `05-验收证据.md`。
|
||||||
|
6. 更新任务状态。
|
||||||
|
7. 在 `03-推进台账.md` 记录:
|
||||||
|
- 本轮做了什么
|
||||||
|
- 改了哪些文件
|
||||||
|
- 验证了什么
|
||||||
|
- 下一步是什么
|
||||||
|
|
||||||
|
## 11. 完全对标核验清单
|
||||||
|
|
||||||
|
每次声称“已完全对标 `doc/index.html`”时,至少执行以下核验。
|
||||||
|
|
||||||
|
### 10.1 源文件结构核验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wc -l doc/index.html
|
||||||
|
rg -n 'function [A-Za-z0-9_]+\(|const [A-Za-z0-9_]+ =|href="\?page=|Last updated|Packages start|@media|--[a-z-]+:' doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 记录 `doc/index.html` 行数。
|
||||||
|
- 记录 CSS token、断点、路由、CTA、函数、法务日期、价格文案。
|
||||||
|
|
||||||
|
### 10.2 页面和函数映射核验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n 'pageData|home:|press:|judging:|scholarly:|services:|why:|resources:|faq:|blog:|start:|ask:|thanks:|about:|terms:|privacy:|disclaimer:' doc/index.html
|
||||||
|
rg -n 'function (hero|heroWithKicker|servicesCta|card|pressPage|judgingPage|scholarlyPage|o1CriteriaPage|faqPage|legalTermsPage|legalPrivacyPage|legalDisclaimerPage)' doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- `01-项目功能内容.md` 中的路由清单与 `pageData` 保持一致。
|
||||||
|
- 使用中函数、未调用函数都有记录。
|
||||||
|
- 隐藏路由 `blog`、`about` 不被误删。
|
||||||
|
|
||||||
|
### 10.3 链接和 CTA 核验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n 'href="\?page=|card\("' doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- Header、footer、hero、services CTA、form mockup、thanks、service card 链接全部登记。
|
||||||
|
- 不存在的 `contact` 路由历史缺陷必须保留在风险和任务矩阵中;修复后记录验收证据。
|
||||||
|
|
||||||
|
### 10.4 Legal 和 FAQ 核验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sed -n '2870,3035p' doc/index.html
|
||||||
|
sed -n '3070,3228p' doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- FAQ 4 组 20 个问题记录完整。
|
||||||
|
- Terms、Privacy、Disclaimer section 数量和标题记录完整。
|
||||||
|
- Terms 缺少 17、legal 日期晚于当前日期、原文语法风险均进入任务矩阵。
|
||||||
|
|
||||||
|
### 10.5 视觉样式核验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n '^\\s*\\.[A-Za-z0-9_-]+|@media|grid-template-columns|width: min|border-radius|background:' doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- CSS token、布局宽度、卡片、表单、Press、Judging、FAQ、Legal、响应式断点在文档中有对应说明。
|
||||||
|
- 后续工程化拆分不能只迁移内容,必须迁移视觉规则。
|
||||||
253
working/03-推进台账.md
Normal file
253
working/03-推进台账.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# 03-推进台账
|
||||||
|
|
||||||
|
本文件按轮次记录工作。每轮结束必须写清楚:做了什么、改了哪些文件、验证了什么、下一步是什么。
|
||||||
|
|
||||||
|
## R001 - 建立 working 文档体系
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 任务编号:T001、T002
|
||||||
|
- 做了什么:
|
||||||
|
- 阅读 `doc/index.html` 的页面结构、导航、路由、表单 mockup、服务模块、FAQ、O-1A criteria、法务页面和渲染脚本。
|
||||||
|
- 建立 `working` 目录下 6 份编号文档和 README 索引。
|
||||||
|
- 初步记录当前项目是自包含静态 HTML mockup。
|
||||||
|
- 改了哪些文件:
|
||||||
|
- `working/README.md`
|
||||||
|
- `working/01-项目功能内容.md`
|
||||||
|
- `working/02-项目程序开发详细步骤.md`
|
||||||
|
- `working/03-推进台账.md`
|
||||||
|
- `working/04-任务矩阵.md`
|
||||||
|
- `working/05-验收证据.md`
|
||||||
|
- `working/06-决策记录.md`
|
||||||
|
- 验证了什么:
|
||||||
|
- 确认 `doc/index.html` 是自包含静态 HTML。
|
||||||
|
- 确认页面通过 `?page=...` 查询参数切换。
|
||||||
|
- 确认 Start Here 和 Ask a Question 当前是视觉 mockup,不是真实表单。
|
||||||
|
- 证据链接或编号:E001、E002、E003
|
||||||
|
- 遗留问题:
|
||||||
|
- 未做浏览器截图验收。
|
||||||
|
- 未做移动端回归。
|
||||||
|
- 未实现表单和部署。
|
||||||
|
- 下一步:
|
||||||
|
- 按 `04-任务矩阵.md` 从 `VERIFY` 与 `TODO` 任务继续推进。
|
||||||
|
|
||||||
|
## R002 - 完全对标 doc/index.html 补强 working 文档
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 任务编号:T028
|
||||||
|
- 做了什么:
|
||||||
|
- 复读 `doc/index.html` 中 header、footer、`pageData`、页面组合函数、Press/Judging/Resources/FAQ/Legal/Form 模块。
|
||||||
|
- 补充 `01-项目功能内容.md`,把页面路由、内容模块、表单字段、服务卡片、O-1A criteria、法律页面、已知缺陷和发布环境写完整。
|
||||||
|
- 补充 `02-项目程序开发详细步骤.md`,形成阶段化路线:基线复现、静态修复、真实表单、工程化拆分、8082 端口部署、HTTPS、回归验收。
|
||||||
|
- 补强任务矩阵,新增服务器部署、凭据处理、contact 死链、outlet/network 陈述风险、README 索引维护等任务。
|
||||||
|
- 补强验收证据,记录本轮命令、源码发现和后续证据清单。
|
||||||
|
- 补强决策记录,明确不在长期文档保存明文密码、服务默认走 8082、Services 的 `contact` 路由是缺陷。
|
||||||
|
- 改了哪些文件:
|
||||||
|
- `working/README.md`
|
||||||
|
- `working/01-项目功能内容.md`
|
||||||
|
- `working/02-项目程序开发详细步骤.md`
|
||||||
|
- `working/03-推进台账.md`
|
||||||
|
- `working/04-任务矩阵.md`
|
||||||
|
- `working/05-验收证据.md`
|
||||||
|
- `working/06-决策记录.md`
|
||||||
|
- 验证了什么:
|
||||||
|
- `rg --files` 确认项目当前主要文件为 `doc/index.html`、`doc/README.md` 和 `working/*.md`。
|
||||||
|
- `rg -n` 确认公开路由、隐藏路由、表单 mockup、legal 页面、动态路由逻辑均在 `doc/index.html`。
|
||||||
|
- `sed` 分段读取源文件,确认 Services 04/05/06 卡片指向不存在的 `contact` 页面。
|
||||||
|
- `git status --short` 失败,确认当前目录不是 Git 仓库或未在 Git 工作树内。
|
||||||
|
- 证据链接或编号:E004、E005、E006、E007
|
||||||
|
- 遗留问题:
|
||||||
|
- 尚未改 `doc/index.html`。
|
||||||
|
- 尚未启动本地服务器或做截图。
|
||||||
|
- 尚未登录远端服务器。
|
||||||
|
- 用户提供了密码,但长期文档未写入明文密码。
|
||||||
|
- 下一步:
|
||||||
|
- 优先处理 T029:修复 Services 页面不存在的 `contact` 路由。
|
||||||
|
- 继续 T003-T011:对核心页面做浏览器截图验收。
|
||||||
|
- 之后按产品选择推进 T012/T013 表单落地或 T022 工程化拆分。
|
||||||
|
|
||||||
|
## R003 - 源码级对标附录完善
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 任务编号:T034
|
||||||
|
- 做了什么:
|
||||||
|
- 重新扫描 `doc/index.html` 的 CSS token、响应式断点、路由链接、函数列表、FAQ、Legal sections 和渲染逻辑。
|
||||||
|
- 在 `01-项目功能内容.md` 增加视觉设计、组件 CSS 类、JavaScript 函数、CTA 全量映射、FAQ 完整编号、Legal 逐项结构和源码级结论。
|
||||||
|
- 在 `02-项目程序开发详细步骤.md` 增加“完全对标核验清单”,规定以后如何证明文档与源 HTML 对齐。
|
||||||
|
- 更新任务矩阵、验收证据和决策记录。
|
||||||
|
- 改了哪些文件:
|
||||||
|
- `working/01-项目功能内容.md`
|
||||||
|
- `working/02-项目程序开发详细步骤.md`
|
||||||
|
- `working/03-推进台账.md`
|
||||||
|
- `working/04-任务矩阵.md`
|
||||||
|
- `working/05-验收证据.md`
|
||||||
|
- `working/06-决策记录.md`
|
||||||
|
- 验证了什么:
|
||||||
|
- `doc/index.html` 共 3423 行。
|
||||||
|
- CSS 只有一个显式响应式断点:`@media (max-width: 1080px)`。
|
||||||
|
- `pageData` 包含公开路由、隐藏路由和 legal 页面。
|
||||||
|
- FAQ 共 4 组 20 个问题。
|
||||||
|
- Terms 编号缺少 17,Legal 日期均为 `July 10, 2026`。
|
||||||
|
- 多个函数和 CSS 模块存在但当前未调用。
|
||||||
|
- 证据链接或编号:E016
|
||||||
|
- 遗留问题:
|
||||||
|
- 仍未修改 `doc/index.html` 源码。
|
||||||
|
- 仍未做浏览器截图和真实部署验收。
|
||||||
|
- 下一步:
|
||||||
|
- 按 T029 修复 `contact` 缺陷,或按 T003-T011 做页面截图验收。
|
||||||
|
|
||||||
|
## R004 - 明确正式开发技术架构
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 任务编号:T035
|
||||||
|
- 做了什么:
|
||||||
|
- 根据用户确认,将正式开发路线从候选方案收敛为 Next.js + React + PostgreSQL。
|
||||||
|
- 将默认数据访问层定为 Prisma ORM。
|
||||||
|
- 补充 Next.js 路由迁移表、PostgreSQL 数据模型初稿、表单 API、环境变量、8082 启动方式和部署验收标准。
|
||||||
|
- 更新任务矩阵和决策记录。
|
||||||
|
- 改了哪些文件:
|
||||||
|
- `working/01-项目功能内容.md`
|
||||||
|
- `working/02-项目程序开发详细步骤.md`
|
||||||
|
- `working/03-推进台账.md`
|
||||||
|
- `working/04-任务矩阵.md`
|
||||||
|
- `working/05-验收证据.md`
|
||||||
|
- `working/06-决策记录.md`
|
||||||
|
- `working/README.md`
|
||||||
|
- 验证了什么:
|
||||||
|
- 文档已不再把 Vite/静态拆分作为推荐路线。
|
||||||
|
- 部署命令已调整为 Next.js `npm run start -- -p 8082`。
|
||||||
|
- 数据库任务已进入矩阵。
|
||||||
|
- 证据链接或编号:E017
|
||||||
|
- 遗留问题:
|
||||||
|
- 尚未实际创建 Next.js 工程。
|
||||||
|
- 尚未创建 PostgreSQL 数据库或 Prisma migration。
|
||||||
|
- 文件上传存储方案仍需单独确认。
|
||||||
|
- 下一步:
|
||||||
|
- 执行 T036 初始化 Next.js 工程。
|
||||||
|
- 执行 T037 设计并迁移 PostgreSQL schema。
|
||||||
|
|
||||||
|
## R005 - 记录服务器 PostgreSQL 安装状态
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 任务编号:T040
|
||||||
|
- 做了什么:
|
||||||
|
- 根据用户反馈,记录 PostgreSQL 已在服务器 `170.106.192.152` 安装完成。
|
||||||
|
- 补充服务器 PostgreSQL 验证命令、应用数据库和受限用户创建建议。
|
||||||
|
- 保持凭据安全规则:不把明文 SSH 密码写入长期文档。
|
||||||
|
- 改了哪些文件:
|
||||||
|
- `working/01-项目功能内容.md`
|
||||||
|
- `working/02-项目程序开发详细步骤.md`
|
||||||
|
- `working/03-推进台账.md`
|
||||||
|
- `working/04-任务矩阵.md`
|
||||||
|
- `working/05-验收证据.md`
|
||||||
|
- `working/06-决策记录.md`
|
||||||
|
- 验证了什么:
|
||||||
|
- 本轮未登录服务器验证,仅记录用户确认。
|
||||||
|
- 证据链接或编号:E018
|
||||||
|
- 遗留问题:
|
||||||
|
- 需要远程执行 `systemctl status postgresql`、`psql --version`、数据库列表检查。
|
||||||
|
- 需要创建或确认 `paprika` 数据库和 `paprika_app` 应用用户。
|
||||||
|
- 下一步:
|
||||||
|
- 执行 T040 远程验证 PostgreSQL。
|
||||||
|
- 执行 T037 接入 Prisma schema 和 migration。
|
||||||
|
|
||||||
|
## R006 - 记录邮箱系统账号需求
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 任务编号:T041
|
||||||
|
- 做了什么:
|
||||||
|
- 记录普通用户 webmail 入口、管理员入口和 `admin@paprikalaw.com` 管理员账号。
|
||||||
|
- 记录 10 个普通公司邮箱账号清单。
|
||||||
|
- 补充邮件 DNS、收发验证、表单通知接入和凭据安全要求。
|
||||||
|
- 明确用户提供的统一初始密码不写入长期文档。
|
||||||
|
- 改了哪些文件:
|
||||||
|
- `working/01-项目功能内容.md`
|
||||||
|
- `working/02-项目程序开发详细步骤.md`
|
||||||
|
- `working/03-推进台账.md`
|
||||||
|
- `working/04-任务矩阵.md`
|
||||||
|
- `working/05-验收证据.md`
|
||||||
|
- `working/06-决策记录.md`
|
||||||
|
- `working/README.md`
|
||||||
|
- 验证了什么:
|
||||||
|
- 本轮只记录需求,未登录邮件后台,未验证 DNS/MX/SPF/DKIM/DMARC。
|
||||||
|
- 证据链接或编号:E019
|
||||||
|
- 遗留问题:
|
||||||
|
- 需要登录 `https://mail.paprikalaw.com/admin/` 确认账号是否已创建。
|
||||||
|
- 需要测试 webmail 登录、内外部收发和 DNS 记录。
|
||||||
|
- 需要决定网站表单通知使用哪个邮箱。
|
||||||
|
- 下一步:
|
||||||
|
- 执行 T041 邮箱账号创建/确认。
|
||||||
|
- 执行 T042 邮件 DNS 和收发测试。
|
||||||
|
- 执行 T043 Next.js 表单邮件通知配置。
|
||||||
|
|
||||||
|
## R007 - 落地 Next.js 工程、Prisma 和真实表单
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 任务编号:T012、T013、T017、T020、T022、T029、T036、T037、T038、T039、T044
|
||||||
|
- 做了什么:
|
||||||
|
- 修复 `doc/index.html` 中 Services 04/05/06 CTA 指向不存在 `contact` 的缺陷,统一改为 `start`。
|
||||||
|
- 将 legal 页面日期从未来日期 `July 10, 2026` 改为当前工作日期 `July 6, 2026`。
|
||||||
|
- 在项目根目录初始化 Next.js + React + TypeScript App Router 工程。
|
||||||
|
- 编写 `scripts/extract-legacy-pages.mjs`,从 `doc/index.html` 抽取 CSS 和静态页面 HTML,生成 `app/globals.css` 与 `lib/legacy-pages.ts`。
|
||||||
|
- 建立 `app/[page]/page.tsx` 静态路由,迁移公开页面到 `/press`、`/judging`、`/services` 等路径路由。
|
||||||
|
- 建立 `/start`、`/ask` 真实 React 表单,提交到 `POST /api/submissions`。
|
||||||
|
- 建立 Prisma schema、初始化 migration、`.env.example`、数据库访问层和 Zod 校验。
|
||||||
|
- 增加基础 SEO metadata、`robots.txt` 和 `sitemap.xml`。
|
||||||
|
- 升级到 Next `16.2.10`、React `19.2.7`,并配置 ESLint 9 flat config。
|
||||||
|
- 改了哪些文件:
|
||||||
|
- `doc/index.html`
|
||||||
|
- `.env.example`
|
||||||
|
- `.gitignore`
|
||||||
|
- `package.json`
|
||||||
|
- `package-lock.json`
|
||||||
|
- `tsconfig.json`
|
||||||
|
- `next-env.d.ts`
|
||||||
|
- `next.config.mjs`
|
||||||
|
- `eslint.config.mjs`
|
||||||
|
- `scripts/extract-legacy-pages.mjs`
|
||||||
|
- `app/*`
|
||||||
|
- `components/*`
|
||||||
|
- `lib/*`
|
||||||
|
- `prisma/schema.prisma`
|
||||||
|
- `prisma/migrations/20260706120000_init/migration.sql`
|
||||||
|
- `working/README.md`
|
||||||
|
- `working/01-项目功能内容.md`
|
||||||
|
- `working/02-项目程序开发详细步骤.md`
|
||||||
|
- `working/03-推进台账.md`
|
||||||
|
- `working/04-任务矩阵.md`
|
||||||
|
- `working/05-验收证据.md`
|
||||||
|
- `working/06-决策记录.md`
|
||||||
|
- 验证了什么:
|
||||||
|
- `npm run generate:legacy` 成功生成 12 个旧页面数据。
|
||||||
|
- `npm run prisma:generate` 成功生成 Prisma Client。
|
||||||
|
- `npm run lint` 通过。
|
||||||
|
- `npx tsc --noEmit` 通过。
|
||||||
|
- `npm run build` 通过,生成 19 个 app route。
|
||||||
|
- `npm run start -- -p 8082` 本地生产服务可启动。
|
||||||
|
- `/`、`/press`、`/judging`、`/scholarly`、`/services`、`/why`、`/resources`、`/faq`、`/start`、`/ask`、`/thanks`、`/terms`、`/privacy`、`/disclaimer`、`/robots.txt`、`/sitemap.xml` 本地生产服务均返回 200。
|
||||||
|
- `/api/submissions` 对缺失必填字段和错误 email 返回 400 校验错误。
|
||||||
|
- 有效提交在未配置 `DATABASE_URL` 时返回 500,确认入库成功仍需真实 PostgreSQL。
|
||||||
|
- 证据链接或编号:E022、E023、E024、E025、E026
|
||||||
|
- 遗留问题:
|
||||||
|
- 本地未配置 PostgreSQL `DATABASE_URL`,尚不能完成真实入库验收。
|
||||||
|
- 远程 PostgreSQL、应用数据库、应用用户、migration deploy 尚未验证。
|
||||||
|
- 邮箱后台、DNS、SMTP 通知未验证。
|
||||||
|
- 法务内容审核、outlet/network 名称确认、文件上传策略仍需外部确认。
|
||||||
|
- 尚未做浏览器截图/PDF 归档。
|
||||||
|
- 下一步:
|
||||||
|
- 执行 T040/T044:远程验证 PostgreSQL,配置生产 `DATABASE_URL`,执行 migration,并提交 Start/Ask 获取 `submission_id`。
|
||||||
|
- 执行 T039/T024:部署 Next.js 到服务器 `8082` 并配置 systemd。
|
||||||
|
- 执行 T041-T043:验证邮箱和 SMTP 通知。
|
||||||
|
- 执行 T018/T026:补桌面和移动端截图证据。
|
||||||
|
|
||||||
|
## 后续轮次模板
|
||||||
|
|
||||||
|
### RXXX - 标题
|
||||||
|
|
||||||
|
- 日期:
|
||||||
|
- 任务编号:
|
||||||
|
- 做了什么:
|
||||||
|
- 改了哪些文件:
|
||||||
|
- 验证了什么:
|
||||||
|
- 证据链接或编号:
|
||||||
|
- 遗留问题:
|
||||||
|
- 下一步:
|
||||||
81
working/04-任务矩阵.md
Normal file
81
working/04-任务矩阵.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# 04-任务矩阵
|
||||||
|
|
||||||
|
状态定义:
|
||||||
|
|
||||||
|
- `DONE`:已完成并有证据。
|
||||||
|
- `VERIFY`:源码看起来具备,但缺浏览器、截图、端到端或人工确认。
|
||||||
|
- `TODO`:尚未实现。
|
||||||
|
- `BLOCKED`:被外部输入、账号、法律审核、DNS 或产品决策阻塞。
|
||||||
|
- `DEFER`:暂缓,不影响当前阶段交付。
|
||||||
|
|
||||||
|
## 任务总表
|
||||||
|
|
||||||
|
| 编号 | 任务 | 状态 | 验收标准 | 依赖 | 备注 |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| T001 | 建立 working 文档体系 | DONE | 6 份编号文档和 README 索引存在 | 无 | R001 |
|
||||||
|
| T002 | 盘点当前页面路由 | DONE | 路由清单覆盖公开路由、隐藏路由和回退逻辑 | T001 | 见 01 |
|
||||||
|
| T003 | 顶部导航可用 | VERIFY | 点击 Press/Judging/Scholarly/Services/Why/Start 能进入对应页面,active 状态正确 | T002 | 需截图 |
|
||||||
|
| T004 | Resources 下拉可用 | VERIFY | hover/focus 显示 FAQ 和 O-1A Criteria Overview,链接正确 | T002 | 需桌面验证 |
|
||||||
|
| T005 | 首页内容完整 | VERIFY | hero、三大服务、Start Early、标准问题、四步流程、价格 band 渲染正常 | T002 | 需截图 |
|
||||||
|
| T006 | Services 页面完整 | VERIFY | 6 张服务卡片和说明渲染正常 | T002 | 存在 T029 缺陷 |
|
||||||
|
| T007 | Press 页面完整 | VERIFY | press hero、cadence、standards、avoid、outlet network、CTA 渲染正常 | T002 | 需截图 |
|
||||||
|
| T008 | Judging 页面完整 | VERIFY | judging counts、network、evidence package、CTA 渲染正常 | T002 | 需截图 |
|
||||||
|
| T009 | Scholarly 页面完整 | VERIFY | hero、两栏说明、CTA 渲染正常 | T002 | 需截图 |
|
||||||
|
| T010 | Resources/O-1A criteria 页面完整 | VERIFY | 8 项 criteria 全部展示,免责声明展示 | T002 | 需内容抽查 |
|
||||||
|
| T011 | FAQ 页面完整 | VERIFY | FAQ 分组和问题渲染正常 | T002 | 需内容抽查 |
|
||||||
|
| T012 | Start Here 表单真实可用 | VERIFY | 真实 `<form>` 或等价组件;必填校验;提交后产生可追踪记录 | T037/T044 | React 表单和 API 已实现,待真实 PostgreSQL 入库证据 |
|
||||||
|
| T013 | Ask a Question 表单真实可用 | VERIFY | 真实 `<form>` 或等价组件;必填校验;提交后产生可追踪记录 | T037/T044 | React 表单和 API 已实现,待真实 PostgreSQL 入库证据 |
|
||||||
|
| T014 | 文件上传策略 | DEFER | 明确支持或不支持上传;若支持,限制类型、大小、存储、访问权限 | T012/T013 | 当前代码禁用上传,待隐私/存储方案确认 |
|
||||||
|
| T015 | Thanks 页面流程验收 | VERIFY | 成功提交后进入 thanks;直接访问 thanks 不造成误导 | T012/T013 | 当前仅链接跳转 |
|
||||||
|
| T016 | 法务页面内容审核 | BLOCKED | Terms、Privacy、Disclaimer 经负责人或律师确认 | 法务输入 | 当前为草稿 |
|
||||||
|
| T017 | 法务日期修正 | DONE | 页面日期不晚于实际发布日,或明确未来生效逻辑 | T016 | 已改为 `July 6, 2026`,内容审核仍见 T016 |
|
||||||
|
| T018 | 响应式布局验证 | TODO | 1440、768、390 宽度核心页面无重叠、溢出、不可读 | T003-T011 | 需截图 |
|
||||||
|
| T019 | 无障碍基础检查 | TODO | heading 层级合理;交互元素可聚焦;颜色对比和表单标签达标 | T012/T013 | 当前表单不是 input |
|
||||||
|
| T020 | SEO 基础 | VERIFY | 每页 title/description、OG、favicon、robots/sitemap 有明确策略 | 内容确认 | 已有 metadata、robots、sitemap;favicon/逐页文案待确认 |
|
||||||
|
| T021 | 分析和隐私策略 | DEFER | 若添加 analytics,Privacy 同步说明并记录供应商 | T016 | 当前未添加 analytics |
|
||||||
|
| T022 | 工程化拆分 | DONE | 从单文件拆为可维护结构,或明确保持单文件的理由 | T002-T011 | 已新增 Next.js App Router 工程,保留 doc 基线 |
|
||||||
|
| T023 | 自动化测试 | TODO | 至少覆盖核心路由冒烟、表单提交、未知路由回退 | T022 | 当前无测试框架 |
|
||||||
|
| T024 | 部署到云服务器 8082 | TODO | 服务器 `127.0.0.1:8082` 返回 200,systemd 或等价进程可自恢复 | T022 或静态服务 | 目标主机 170.106.192.152 |
|
||||||
|
| T025 | HTTPS 域名发布 | BLOCKED | `https://paprikalaw.com/` 返回 200,证书有效 | DNS/服务器权限/T024 | 需确认 DNS 指向 |
|
||||||
|
| T026 | 截图/PDF 验收资产 | TODO | 核心页面截图或 PDF 存档,路径写入 `05-验收证据.md` | T018 | 用于回归比较 |
|
||||||
|
| T027 | `blog` 和 `about` 去留确认 | TODO | 明确保留、隐藏或删除;导航和 footer 一致 | 产品输入 | 当前代码存在但未公开 |
|
||||||
|
| T028 | 完全对标补强 working 文档 | DONE | 01-06 和 README 覆盖页面、开发步骤、台账、任务矩阵、证据、决策 | T001/T002 | R002 |
|
||||||
|
| T029 | 修复 Services 中不存在的 `contact` 路由 | DONE | Services 04/05/06 CTA 指向存在页面,点击不回退首页 | T006 | 已改 `start`,Next `/services` 产物确认 href `/start` |
|
||||||
|
| T030 | 服务器凭据安全处理 | TODO | 不在仓库保存明文密码;配置 SSH key 或受控密钥交接 | T024 | 用户已提供临时凭据 |
|
||||||
|
| T031 | Outlet/network 陈述风险确认 | BLOCKED | 负责人确认可展示 outlet/network 名称或改成示例类型 | 产品/法务输入 | Press/Judging 涉及名称 |
|
||||||
|
| T032 | README 索引维护 | DONE | README 能指向 01-06,并说明后续入口 | T028 | 本轮更新 |
|
||||||
|
| T033 | Git 仓库状态确认 | TODO | 明确项目是否应纳入 Git;若是,初始化或进入正确仓库后记录 commit hash | 用户/仓库输入 | 当前 `git status` 失败 |
|
||||||
|
| T034 | 源码级对标附录完善 | DONE | 文档覆盖 CSS token、组件类、函数清单、CTA 映射、FAQ 编号、Legal 结构、核验命令 | T028 | R003 |
|
||||||
|
| T035 | 明确正式技术架构 | DONE | 文档明确采用 Next.js + React + PostgreSQL,并记录 ORM、路由、API、部署方式 | 用户确认 | R004 |
|
||||||
|
| T036 | 初始化 Next.js + React 工程 | DONE | `npm run dev`、`npm run build` 可用,基础页面路由建立 | T035 | TypeScript + App Router,Next 16 + React 19 |
|
||||||
|
| T037 | PostgreSQL + Prisma 接入 | VERIFY | `schema.prisma`、`.env.example`、migration 存在,能连接数据库 | T035/T040 | 本地代码完成;真实连接待生产 `DATABASE_URL` |
|
||||||
|
| T038 | 表单入库实现 | VERIFY | `/start`、`/ask` 提交写入 PostgreSQL,并返回 `submission_id` | T036/T037/T044 | API 写入代码完成;缺真实 DB 成功提交证据 |
|
||||||
|
| T039 | Next.js 部署到 8082 | VERIFY | `npm run start -- -p 8082` 生产可运行,systemd 管理 | T036/T037 | 本地生产 8082 通过;远程 systemd 未做 |
|
||||||
|
| T040 | 远程验证 PostgreSQL 安装 | TODO | 服务器上 `systemctl status postgresql` 正常,`psql --version` 可用,应用数据库/用户确认 | 用户已确认安装 | 不记录明文密码 |
|
||||||
|
| T041 | 邮箱账号创建/确认 | TODO | admin 入口可登录,10 个普通邮箱可通过 webmail 登录 | 邮件后台权限 | 不记录统一初始密码 |
|
||||||
|
| T042 | 邮件 DNS 与收发验证 | TODO | MX/SPF/DKIM/DMARC 检查完成,内外部收发测试通过 | T041/DNS 权限 | 记录 dig 输出和测试截图 |
|
||||||
|
| T043 | 表单邮件通知配置 | TODO | `/start`、`/ask` 入库后向指定邮箱发送通知,记录 message id | T038/T041 | SMTP 密码放环境变量 |
|
||||||
|
| T044 | 真实 PostgreSQL 入库验收 | TODO | 配置 `DATABASE_URL` 后,Start/Ask 成功返回 `submission_id`,数据库可查记录 | T037/T040 | 本地未配置数据库,远程待执行 migration |
|
||||||
|
|
||||||
|
## 优先级建议
|
||||||
|
|
||||||
|
| 优先级 | 任务 | 原因 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| P0 | T029 | 明确死链,改动小,影响转化 |
|
||||||
|
| P0 | T017/T016 | 法务日期和文本影响正式发布可信度 |
|
||||||
|
| P0 | T012/T013/T014 | 当前无法真实收集线索 |
|
||||||
|
| P0 | T035/T036/T037/T038 | 正式架构已明确,需落地工程和数据库 |
|
||||||
|
| P0 | T040 | PostgreSQL 已安装,需验证后才能接 Prisma |
|
||||||
|
| P0 | T041/T042 | 公司邮箱是对外联系和表单通知基础设施 |
|
||||||
|
| P1 | T003-T011/T018/T026 | 页面上线前必须有截图证据 |
|
||||||
|
| P1 | T024/T025/T030 | 服务器发布和凭据安全 |
|
||||||
|
| P2 | T020/T021/T023 | SEO、analytics、自动化测试 |
|
||||||
|
| P2 | T022 | 工程化拆分取决于上线节奏 |
|
||||||
|
|
||||||
|
## 防重复规则
|
||||||
|
|
||||||
|
1. 开始任务前先查本表编号。
|
||||||
|
2. 已是 `DONE` 的任务不得重复做,除非新建变更任务并说明原因。
|
||||||
|
3. `VERIFY` 任务完成验证后改为 `DONE`,并在 `05-验收证据.md` 填证据。
|
||||||
|
4. 涉及法务、隐私、表单数据、部署凭据的任务必须有明确验收证据,不能只凭页面看起来正常关闭。
|
||||||
|
5. 每次新增任务必须使用下一个编号,并补齐状态、验收标准和依赖。
|
||||||
374
working/05-验收证据.md
Normal file
374
working/05-验收证据.md
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
# 05-验收证据
|
||||||
|
|
||||||
|
本文件记录可复查证据:命令、页面、job_id、report_id、PDF、截图、部署链接、提交记录等。
|
||||||
|
|
||||||
|
## E001 - 文档基线盘点
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T001、T002
|
||||||
|
- 类型:命令输出摘要
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pwd
|
||||||
|
rg --files -g '!*node_modules*' -g '!*.png' -g '!*.jpg' -g '!*.jpeg' -g '!*.gif' -g '!*.pdf' | sed -n '1,160p'
|
||||||
|
sed -n '1,260p' doc/index.html
|
||||||
|
find working -maxdepth 2 -type f -print | sort
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 当前工作目录为 `/home/mes123456/MeetPaprika`。
|
||||||
|
- 参考文件存在:`doc/index.html`。
|
||||||
|
- `working` 目录存在。
|
||||||
|
- `doc/index.html` 包含 `<main id="app"></main>`、内联 CSS、内联 JS。
|
||||||
|
- 页面通过 `?page=...` 查询参数选择内容。
|
||||||
|
|
||||||
|
## E002 - 当前路由证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T002
|
||||||
|
- 类型:源码证据
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n "const pages|function|data-page|render|page:|title:|href=|main.innerHTML|case|pages\\[|\\?page=|<body|<script|</html>|Services|Resources|FAQ|Privacy|Terms|Get Started|Application|Contact" doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 导航显式链接:`press`、`judging`、`scholarly`、`services`、`why`、`resources`、`faq`、`start`。
|
||||||
|
- Footer 链接:`why`、`faq`、`terms`、`privacy`、`disclaimer`。
|
||||||
|
- `pageData` 中定义:`home`、`press`、`judging`、`scholarly`、`services`、`why`、`resources`、`faq`、`blog`、`start`、`ask`、`thanks`、`about`、`terms`、`privacy`、`disclaimer`。
|
||||||
|
- 路由选择逻辑:未识别页面回退为 `home`。
|
||||||
|
|
||||||
|
## E003 - 表单 mockup 证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T012、T013、T014、T015
|
||||||
|
- 类型:源码证据
|
||||||
|
- 文件:`doc/index.html`
|
||||||
|
- 结果摘要:
|
||||||
|
- `start` 页面字段使用 `<div class="field">`,提交入口是 `<a class="button primary" href="?page=thanks">Submit Profile</a>`。
|
||||||
|
- `ask` 页面字段使用 `<div class="field">`,提交入口是 `<a class="button primary" href="?page=thanks">Send Question</a>`。
|
||||||
|
- 当前没有 `<form>`、`input`、`textarea`、`file`、`method`、`action` 或提交接口。
|
||||||
|
- 因此当前只能证明视觉流程存在,不能证明真实提交能力。
|
||||||
|
|
||||||
|
## E004 - R002 源码复核证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T028
|
||||||
|
- 类型:命令输出摘要
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sed -n '2262,2468p' doc/index.html
|
||||||
|
sed -n '2469,2765p' doc/index.html
|
||||||
|
sed -n '2766,3428p' doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 复核 header、footer、`serviceCards`、`pageData`、`hero`、`heroWithKicker`、`pressPage`、`judgingPage`、`o1CriteriaPage`、`faqPage`、legal 页面和最终渲染逻辑。
|
||||||
|
- `document.getElementById("app").innerHTML = pageData[selected].body;`
|
||||||
|
- `if (["faq", "resources"].includes(selected))` 会高亮 Resources trigger。
|
||||||
|
- legal 页面当时日期写为 `Last updated: July 10, 2026`;后续已在 E024 修复。
|
||||||
|
|
||||||
|
## E005 - Services contact 路由缺陷证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T029
|
||||||
|
- 类型:源码证据
|
||||||
|
- 文件:`doc/index.html`
|
||||||
|
- 结果摘要:
|
||||||
|
- Services 页面卡片 04、05、06 调用:
|
||||||
|
- `card("04", ..., "Start Here", "contact")`
|
||||||
|
- `card("05", ..., "Start Here", "contact")`
|
||||||
|
- `card("06", ..., "Start Here", "contact")`
|
||||||
|
- `pageData` 没有 `contact`。
|
||||||
|
- 当前路由逻辑会把未知 `contact` 回退到 `home`。
|
||||||
|
- 结论:这是明确死链/误跳转缺陷;后续已在 E024 修复。
|
||||||
|
|
||||||
|
## E006 - Git 状态证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T033
|
||||||
|
- 类型:命令输出摘要
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 输出:`fatal: not a git repository (or any of the parent directories): .git`
|
||||||
|
- 结论:当前路径不是 Git 仓库,暂时无法记录 commit hash。
|
||||||
|
|
||||||
|
## E007 - Working 文档更新证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T028、T032
|
||||||
|
- 类型:文件证据
|
||||||
|
- 文件:
|
||||||
|
- `working/README.md`
|
||||||
|
- `working/01-项目功能内容.md`
|
||||||
|
- `working/02-项目程序开发详细步骤.md`
|
||||||
|
- `working/03-推进台账.md`
|
||||||
|
- `working/04-任务矩阵.md`
|
||||||
|
- `working/05-验收证据.md`
|
||||||
|
- `working/06-决策记录.md`
|
||||||
|
- 结果摘要:
|
||||||
|
- 已形成 README 索引。
|
||||||
|
- 已形成从项目功能、开发步骤、推进台账、任务矩阵、验收证据到决策记录的闭环。
|
||||||
|
|
||||||
|
## E016 - 源码级对标扫描证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T034
|
||||||
|
- 类型:命令输出摘要
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wc -l doc/index.html working/*.md
|
||||||
|
rg -n 'function [A-Za-z0-9_]+\(|const [A-Za-z0-9_]+ =|^[[:space:]]+[A-Za-z0-9_]+: \{|href="\?page=|Last updated|Packages start|@media|--[a-z-]+:' doc/index.html
|
||||||
|
rg -n '^\s*\.[A-Za-z0-9_-]+|^\s*#[A-Za-z0-9_-]+|^\s*[a-z]+\s*\{|@media|grid-template-columns|width: min|border-radius|background:' doc/index.html
|
||||||
|
sed -n '2870,3035p' doc/index.html
|
||||||
|
sed -n '3070,3228p' doc/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- `doc/index.html` 共 3423 行。
|
||||||
|
- CSS token 包含 `--red`、`--deep-red`、`--ink`、`--text`、`--muted`、`--line`、`--paper`、`--panel`、`--green`、`--header-text`。
|
||||||
|
- 唯一显式断点为 `@media (max-width: 1080px)`。
|
||||||
|
- `pageData` 包含 `home`、`press`、`judging`、`scholarly`、`services`、`why`、`resources`、`faq`、`blog`、`start`、`ask`、`thanks`、`about`、`terms`、`privacy`、`disclaimer`。
|
||||||
|
- FAQ 共 4 组 20 个问题。
|
||||||
|
- Terms 共 18 个 section,但编号缺少 17。
|
||||||
|
- Privacy 共 12 个 section。
|
||||||
|
- Disclaimer 共 9 个 section。
|
||||||
|
- Legal 页面当时日期均为 `Last updated: July 10, 2026`;后续已在 E024 修复。
|
||||||
|
- 发现未调用函数/模块:`productPage`、`productPageWithKicker`、`judgingNetworkGroup`、`resourcesHub`、`resourceGroup`、`legalPage`、`standardGrid`、`pressQuality`。
|
||||||
|
|
||||||
|
## E017 - 技术架构确认记录
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T035
|
||||||
|
- 类型:用户确认 + 文档证据
|
||||||
|
- 用户确认:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Next+ React ,数据库使用 PostGreSQL
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 正式开发技术架构已收敛为 Next.js + React + PostgreSQL。
|
||||||
|
- 文档默认 ORM 为 Prisma。
|
||||||
|
- 表单提交目标改为 Next.js API/Server Actions 写入 PostgreSQL。
|
||||||
|
- 生产启动方式改为 `npm run start -- -p 8082`。
|
||||||
|
- 新增任务 T036-T039,覆盖工程初始化、数据库接入、表单入库、Next.js 部署。
|
||||||
|
|
||||||
|
## E018 - 服务器 PostgreSQL 安装用户确认
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T040
|
||||||
|
- 类型:用户确认
|
||||||
|
- 用户确认摘要:
|
||||||
|
- PostgreSQL 已在云服务器安装完成。
|
||||||
|
- 服务器:`170.106.192.152`
|
||||||
|
- SSH 用户:`ubuntu`
|
||||||
|
- 可用端口:`8080-8099`
|
||||||
|
- 应用目标端口:`8082`
|
||||||
|
- 凭据处理:
|
||||||
|
- 用户在对话中提供了 SSH 密码。
|
||||||
|
- 长期文档不保存明文密码。
|
||||||
|
- 待验证命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh ubuntu@170.106.192.152
|
||||||
|
systemctl status postgresql --no-pager
|
||||||
|
psql --version
|
||||||
|
sudo -u postgres psql -c '\l'
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结论:
|
||||||
|
- 当前状态为“用户确认已安装,待远程验证”。
|
||||||
|
|
||||||
|
## E019 - 邮箱系统账号需求用户确认
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T041、T042、T043
|
||||||
|
- 类型:用户确认
|
||||||
|
- 用户确认摘要:
|
||||||
|
- 普通用户入口:`https://mail.paprikalaw.com/webmail/`
|
||||||
|
- 管理员入口:`https://mail.paprikalaw.com/admin/`
|
||||||
|
- 管理员账号:`admin@paprikalaw.com`
|
||||||
|
- 普通邮箱账号:
|
||||||
|
- `info@paprikalaw.com`
|
||||||
|
- `contact@paprikalaw.com`
|
||||||
|
- `support@paprikalaw.com`
|
||||||
|
- `sales@paprikalaw.com`
|
||||||
|
- `marketing@paprikalaw.com`
|
||||||
|
- `hr@paprikalaw.com`
|
||||||
|
- `finance@paprikalaw.com`
|
||||||
|
- `billing@paprikalaw.com`
|
||||||
|
- `legal@paprikalaw.com`
|
||||||
|
- `service@paprikalaw.com`
|
||||||
|
- 凭据处理:
|
||||||
|
- 用户在对话中提供了统一初始密码。
|
||||||
|
- 长期文档不保存明文密码。
|
||||||
|
- 待验证:
|
||||||
|
- 管理员登录。
|
||||||
|
- 普通邮箱 webmail 登录。
|
||||||
|
- MX/SPF/DKIM/DMARC。
|
||||||
|
- 内部互发、外部收信、外部发信。
|
||||||
|
- Next.js 表单通知 SMTP 配置。
|
||||||
|
|
||||||
|
## E022 - Next.js 工程初始化和构建证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T036、T022
|
||||||
|
- 类型:命令输出摘要
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run generate:legacy
|
||||||
|
npm run lint
|
||||||
|
npx tsc --noEmit
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 依赖安装完成。
|
||||||
|
- `generate:legacy` 从 `doc/index.html` 生成 12 个页面数据。
|
||||||
|
- `lint` 通过。
|
||||||
|
- TypeScript `--noEmit` 通过。
|
||||||
|
- `next build` 通过,生成 `/`、`/[page]`、`/api/submissions`、`/ask`、`/start`、`/thanks`、`/robots.txt`、`/sitemap.xml`。
|
||||||
|
|
||||||
|
## E023 - Prisma schema 和 migration 证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T037
|
||||||
|
- 类型:文件和命令证据
|
||||||
|
- 文件:
|
||||||
|
- `prisma/schema.prisma`
|
||||||
|
- `prisma/migrations/20260706120000_init/migration.sql`
|
||||||
|
- `.env.example`
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run prisma:generate
|
||||||
|
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- Prisma Client 生成成功。
|
||||||
|
- schema 包含 `Submission` 和 `SubmissionFile`。
|
||||||
|
- `.env.example` 只保留占位 `DATABASE_URL`,未写入真实密码。
|
||||||
|
- 待补:
|
||||||
|
- 生产 PostgreSQL 执行 `npx prisma migrate deploy`。
|
||||||
|
- 成功入库后记录 `submission_id`。
|
||||||
|
|
||||||
|
## E024 - Services CTA 和 legal 日期修复证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T017、T029
|
||||||
|
- 类型:源码和 HTTP 产物证据
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n "contact|Last updated" doc/index.html
|
||||||
|
curl -s http://localhost:8082/services | rg -n 'href="/start"|Expert consultation|Attorney Referral'
|
||||||
|
curl -s http://localhost:8082/terms | rg -n "Last updated: July 6, 2026"
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- `doc/index.html` 中 Services 04/05/06 已不再指向不存在的 `contact`。
|
||||||
|
- `/services` 产物中 Expert consultation、Agency representation、Attorney Referral 的 CTA 均为 `href="/start"`。
|
||||||
|
- legal 页面显示 `Last updated: July 6, 2026`。
|
||||||
|
|
||||||
|
## E025 - 本地生产 8082 路由冒烟证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T003-T011、T015、T020、T039
|
||||||
|
- 类型:HTTP 状态码证据
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start -- -p 8082
|
||||||
|
for p in / /press /judging /scholarly /services /why /resources /faq /start /ask /thanks /terms /privacy /disclaimer /robots.txt /sitemap.xml; do
|
||||||
|
code=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8082$p)
|
||||||
|
printf '%s %s\n' "$code" "$p"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 所有列出的路径均返回 `200`。
|
||||||
|
- `robots.txt` 输出 `Sitemap: https://paprikalaw.com/sitemap.xml`。
|
||||||
|
- `sitemap.xml` 包含核心公开路径。
|
||||||
|
- 备注:
|
||||||
|
- 这是本地生产服务验收,不等同于远程服务器和 HTTPS 验收。
|
||||||
|
|
||||||
|
## E026 - 表单 API 校验证据
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T012、T013、T038、T044
|
||||||
|
- 类型:API 响应证据
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://localhost:8082/api/submissions \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"type":"ask","name":"","email":"bad","question":"","consentAccepted":false}'
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 返回 `Validation failed`。
|
||||||
|
- 字段错误包含 `name`、`email`、`consentAccepted`、`question`。
|
||||||
|
- 待补:
|
||||||
|
- 配置 `DATABASE_URL` 后提交有效 `/start` 和 `/ask` 表单,记录 `submission_id` 和数据库查询结果。
|
||||||
|
- 未配置 `DATABASE_URL` 时,有效提交会返回 500,不能作为入库成功证据。
|
||||||
|
|
||||||
|
## E027 - npm audit 结果
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 关联任务:T023、T036
|
||||||
|
- 类型:安全扫描摘要
|
||||||
|
- 命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm audit --audit-level=moderate --json
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果摘要:
|
||||||
|
- 剩余 2 个 `moderate` vulnerability。
|
||||||
|
- 来源为 `next` 依赖链中的 `postcss <8.5.10` 公告。
|
||||||
|
- 当前项目已使用 registry 当前稳定版本 Next `16.2.10`。
|
||||||
|
- npm 给出的 `fixAvailable` 会降级到 `next@9.3.3`,属于不合理破坏性修复,未执行。
|
||||||
|
- 后续:
|
||||||
|
- 等待 Next/PostCSS 依赖链发布可用修复版本后升级。
|
||||||
|
|
||||||
|
## 待补证据清单
|
||||||
|
|
||||||
|
| 编号 | 关联任务 | 需要补充的证据 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| E008 | T003-T011 | 桌面浏览器截图,覆盖 home、press、judging、scholarly、services、why、resources、faq |
|
||||||
|
| E009 | T018 | 移动端截图,至少 390px 宽度覆盖 home、services、faq、start |
|
||||||
|
| E010 | T012/T013 | 表单提交成功记录、submission id、后台记录截图或 webhook job_id |
|
||||||
|
| E011 | T016/T017 | 法务审核记录、确认人、确认日期 |
|
||||||
|
| E012 | T023 | 自动化测试命令和结果 |
|
||||||
|
| E013 | T024/T025 | 部署 URL、systemd 状态、Nginx 测试、证书、job_id、commit hash |
|
||||||
|
| E014 | T026 | PDF 或截图归档路径 |
|
||||||
|
| E015 | T030 | SSH key 或密钥管理方式确认,不能包含明文密码 |
|
||||||
|
| E020 | T041/T042 | 邮箱后台截图、DNS 查询输出、收发测试截图 |
|
||||||
|
| E021 | T043 | 表单通知邮件 message id 或测试截图 |
|
||||||
|
| E028 | T044 | PostgreSQL `DATABASE_URL` 配置后的真实表单提交、`submission_id` 和数据库查询结果 |
|
||||||
|
|
||||||
|
## 证据模板
|
||||||
|
|
||||||
|
### EXXX - 标题
|
||||||
|
|
||||||
|
- 日期:
|
||||||
|
- 关联任务:
|
||||||
|
- 类型:
|
||||||
|
- 命令或页面:
|
||||||
|
- 结果:
|
||||||
|
- 文件或链接:
|
||||||
|
- job_id/report_id/submission_id:
|
||||||
|
- 备注:
|
||||||
195
working/06-决策记录.md
Normal file
195
working/06-决策记录.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# 06-决策记录
|
||||||
|
|
||||||
|
本文件记录已经做出的工程、产品和流程决策。后续若要改变决策,需要新增记录说明原因,不直接覆盖旧记录。
|
||||||
|
|
||||||
|
## D001 - 当前基线按静态单文件 mockup 记录
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:当前以 `doc/index.html` 作为项目基线,而不是假设已有完整前端工程。
|
||||||
|
- 原因:
|
||||||
|
- 当前目录未发现 `package.json`、构建配置或后端代码。
|
||||||
|
- `doc/index.html` 已包含页面、样式和路由脚本。
|
||||||
|
- 用户本轮要求是在 `working` 中编写推进文档。
|
||||||
|
- 影响:
|
||||||
|
- 当前验收以静态页面可打开、路由可渲染、视觉可检查为主。
|
||||||
|
- 表单、部署、测试、工程化都作为后续任务单独推进。
|
||||||
|
|
||||||
|
## D002 - 表单不按真实功能验收
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:`start` 和 `ask` 当前只能记为表单视觉 mockup,不能记为真实线索收集功能。
|
||||||
|
- 原因:
|
||||||
|
- 字段是 `<div>`,不是输入控件。
|
||||||
|
- 提交按钮是跳转链接,不会提交数据。
|
||||||
|
- 没有后端、第三方表单服务、文件上传或校验逻辑。
|
||||||
|
- 影响:
|
||||||
|
- `T012`、`T013`、`T014` 保持 `TODO`。
|
||||||
|
- 上线前必须实现真实提交,或在页面明确标注不可提交。
|
||||||
|
|
||||||
|
## D003 - 任务推进以任务矩阵为唯一入口
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:后续开发先查 `04-任务矩阵.md`,每个任务必须有编号、状态、验收标准和依赖。
|
||||||
|
- 原因:
|
||||||
|
- 用户明确要求防止重复做。
|
||||||
|
- 当前项目范围包含内容、前端、表单、法务、部署和证据留存,容易混线。
|
||||||
|
- 影响:
|
||||||
|
- 新工作不得只写在聊天记录里。
|
||||||
|
- 完成任务必须同步推进台账和验收证据。
|
||||||
|
|
||||||
|
## D004 - 法务内容按草稿处理
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:Terms、Privacy、Disclaimer 当前按草稿记录,正式上线前需要法务或负责人确认。
|
||||||
|
- 原因:
|
||||||
|
- 页面涉及 non-law-firm statement、attorney-client relationship、refund、privacy、file upload、attorney referral、immigration outcome guarantee 等高风险内容。
|
||||||
|
- 页面曾使用 `Last updated: July 10, 2026`,晚于当前日期 `2026-07-06`;R007 已修正日期,但内容仍需审核。
|
||||||
|
- 影响:
|
||||||
|
- `T016` 标为 `BLOCKED`,等待法务/负责人输入。
|
||||||
|
- `T017` 后续已完成日期修正;法务内容审核仍不关闭。
|
||||||
|
|
||||||
|
## D005 - 本轮不改 doc/index.html
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:R002 只补强 `working` 文档,不修改 `doc/index.html`。
|
||||||
|
- 原因:
|
||||||
|
- 用户要求是在 `working` 中编写文档体系。
|
||||||
|
- 当前还没有要求开始代码修复或工程化迁移。
|
||||||
|
- 影响:
|
||||||
|
- `T029` 虽已识别,但仍为 `TODO`。
|
||||||
|
- 后续修复死链时应单独开一轮并记录证据。
|
||||||
|
|
||||||
|
## D006 - 长期文档不保存明文服务器密码
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:用户提供的 SSH 密码只视为临时交接信息,不写入 `working` 长期文档。
|
||||||
|
- 原因:
|
||||||
|
- `working` 文档会作为后续推进资料,可能被复制、上传或纳入版本控制。
|
||||||
|
- 明文密码进入项目文件会扩大泄露面。
|
||||||
|
- 影响:
|
||||||
|
- 服务器信息保留 host、user、port、domain。
|
||||||
|
- `T030` 要求改用 SSH key、部署密钥或受控密钥库。
|
||||||
|
|
||||||
|
## D007 - 目标部署端口采用 8082
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:部署方案默认内部服务监听 `8082`,HTTPS 由域名和反向代理对外提供。
|
||||||
|
- 原因:
|
||||||
|
- 用户明确给出端口号 `8082`。
|
||||||
|
- 用户说明 `8080-8099` 已开放。
|
||||||
|
- HTTPS 通常由 Nginx/Caddy/负载均衡终止,再转发到本地应用端口。
|
||||||
|
- 影响:
|
||||||
|
- `T024` 验收服务器本地 `127.0.0.1:8082`。
|
||||||
|
- `T025` 验收 `https://paprikalaw.com/`。
|
||||||
|
|
||||||
|
## D008 - Services 的 contact 路由判定为缺陷
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:Services 页面 04/05/06 CTA 指向 `contact` 是缺陷,不按设计接受。
|
||||||
|
- 原因:
|
||||||
|
- `pageData` 没有 `contact` 页面。
|
||||||
|
- 当前路由逻辑会把未知页面回退到 `home`。
|
||||||
|
- CTA 文案是 Start Here,合理目标应为 `start`,除非新增真实 Contact 页面。
|
||||||
|
- 影响:
|
||||||
|
- 新增 `T029`。
|
||||||
|
- 修复前 `T006` 只能保持 `VERIFY`,不能标为 `DONE`。
|
||||||
|
|
||||||
|
## D009 - Outlet/network 名称正式发布前需确认
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:Press 和 Judging 页面中的 outlet/network 名称上线前必须由负责人确认,或改成示例类型表达。
|
||||||
|
- 原因:
|
||||||
|
- 页面列出大量媒体和 judging/network 名称,可能被理解为合作关系、可保证 placement 或背书。
|
||||||
|
- 这类陈述涉及商业真实性和法律风险。
|
||||||
|
- 影响:
|
||||||
|
- 新增 `T031`,状态 `BLOCKED`。
|
||||||
|
- 未确认前不建议直接正式上线。
|
||||||
|
|
||||||
|
## D010 - 完全对标必须包含视觉和源码结构
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:后续提到“完全对标 `doc/index.html`”时,不能只记录页面文案,还必须覆盖 CSS token、组件类、函数清单、CTA 映射、FAQ 编号、Legal section、未调用模块和源码缺陷。
|
||||||
|
- 原因:
|
||||||
|
- `doc/index.html` 是 3423 行单文件 mockup,页面内容、视觉规范和路由逻辑耦合在一起。
|
||||||
|
- 如果只记录页面功能,工程化拆分时容易丢失视觉细节或保留隐藏缺陷。
|
||||||
|
- 影响:
|
||||||
|
- `01-项目功能内容.md` 增加源码级对标附录。
|
||||||
|
- `02-项目程序开发详细步骤.md` 增加完全对标核验清单。
|
||||||
|
- 新增 `T034` 和 `E016`。
|
||||||
|
|
||||||
|
## D011 - 正式开发架构采用 Next.js + React + PostgreSQL
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:正式开发技术架构采用 Next.js + React,数据库使用 PostgreSQL。
|
||||||
|
- 默认补充决策:
|
||||||
|
- 使用 TypeScript。
|
||||||
|
- 使用 Next.js App Router。
|
||||||
|
- 使用 Prisma 作为 PostgreSQL ORM。
|
||||||
|
- 使用 Next.js Route Handlers 或 Server Actions 处理表单提交。
|
||||||
|
- 生产服务监听 `8082`,HTTPS 由反向代理处理。
|
||||||
|
- 原因:
|
||||||
|
- 用户明确指定 `Next+ React` 和 `PostGreSQL`。
|
||||||
|
- 项目需要真实表单、数据持久化、后续可能的文件上传和后台线索管理,单纯静态站不够。
|
||||||
|
- Next.js 可同时承接页面、API、SEO、构建和生产服务。
|
||||||
|
- 影响:
|
||||||
|
- `02-项目程序开发详细步骤.md` 中工程化路线改为 Next.js + PostgreSQL。
|
||||||
|
- 新增 `T035-T039`。
|
||||||
|
- 后续不再优先推进 Vite/纯静态拆分,除非另有新决策。
|
||||||
|
|
||||||
|
## D012 - PostgreSQL 已安装但仍需远程验证
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:将服务器 PostgreSQL 状态记录为“用户确认已安装,待远程验证”,不直接视为已完成数据库接入。
|
||||||
|
- 原因:
|
||||||
|
- 用户已明确说明 PostgreSQL 在服务器安装完成。
|
||||||
|
- 但尚未执行服务状态、版本、数据库列表、应用数据库、应用用户和连接字符串验证。
|
||||||
|
- 影响:
|
||||||
|
- 新增 `T040` 远程验证 PostgreSQL 安装。
|
||||||
|
- `T037` PostgreSQL + Prisma 接入仍保持 `TODO`。
|
||||||
|
- 继续执行不保存明文密码的凭据规则。
|
||||||
|
|
||||||
|
## D013 - 邮箱账号统一记录但不保存初始密码
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:记录 `paprikalaw.com` 邮箱入口、管理员账号和 10 个普通邮箱账号,但不在长期文档保存用户提供的统一初始密码。
|
||||||
|
- 原因:
|
||||||
|
- 邮箱账号属于项目基础设施,网站联系、表单通知和法务联系需要引用。
|
||||||
|
- 多账号共用初始密码存在安全风险,进入文档会扩大泄露面。
|
||||||
|
- 影响:
|
||||||
|
- 新增 `T041-T043`。
|
||||||
|
- 创建或确认邮箱后,应尽快改为独立强密码并记录验证证据。
|
||||||
|
- Next.js SMTP 凭据只能放服务器环境变量或受控密钥库。
|
||||||
|
|
||||||
|
## D014 - 保留 doc/index.html 作为对标基线,同时以 Next.js 为正式工程
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:不删除 `doc/index.html`,正式代码在根目录 Next.js 工程中维护。
|
||||||
|
- 原因:
|
||||||
|
- `doc/index.html` 是既有视觉、文案和路由逻辑的完整基线。
|
||||||
|
- 新工程需要持续对标旧 mockup,避免内容迁移漂移。
|
||||||
|
- `scripts/extract-legacy-pages.mjs` 可从旧基线重新生成静态页面 CSS 和 HTML 数据。
|
||||||
|
- 影响:
|
||||||
|
- 静态内容页面暂由 `lib/legacy-pages.ts` 和 `LegacyPage` 渲染。
|
||||||
|
- `/start`、`/ask`、`/thanks` 已由 React 页面接管,不再使用旧 mockup 表单。
|
||||||
|
|
||||||
|
## D015 - 文件上传暂不启用
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:Start/Ask 表单当前不启用 CV/resume 上传。
|
||||||
|
- 原因:
|
||||||
|
- 文件上传涉及存储位置、大小限制、访问权限、隐私保留期限和删除策略。
|
||||||
|
- 未确认前收集 CV 会扩大敏感数据风险。
|
||||||
|
- 影响:
|
||||||
|
- `SubmissionFile` 表作为未来能力预留。
|
||||||
|
- 前端表单明确说明 file upload 暂停,直到存储和隐私方案获批。
|
||||||
|
|
||||||
|
## D016 - 表单入库任务不在缺少 DATABASE_URL 时关闭
|
||||||
|
|
||||||
|
- 日期:2026-07-06
|
||||||
|
- 决策:虽然 `/api/submissions` 已实现 Prisma 写入代码,但在没有真实 PostgreSQL `DATABASE_URL` 和成功 `submission_id` 证据前,T038/T044 不标为 `DONE`。
|
||||||
|
- 原因:
|
||||||
|
- API 代码通过构建不等于数据已经成功持久化。
|
||||||
|
- 生产数据库连接、migration、权限和网络策略都可能导致运行时失败。
|
||||||
|
- 影响:
|
||||||
|
- T012/T013/T038 保持 `VERIFY`。
|
||||||
|
- 新增 T044,专门记录真实 PostgreSQL 入库验收。
|
||||||
54
working/README.md
Normal file
54
working/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# MeetPaprika Working Index
|
||||||
|
|
||||||
|
本目录用于承接 Paprika 网站后续开发推进。所有后续工作优先从 `04-任务矩阵.md` 领取任务,并在完成后同步更新 `03-推进台账.md`、`05-验收证据.md` 和必要的 `06-决策记录.md`。
|
||||||
|
|
||||||
|
## 文档索引
|
||||||
|
|
||||||
|
1. [01-项目功能内容.md](01-项目功能内容.md)
|
||||||
|
对标 `/home/mes123456/MeetPaprika/doc/index.html`,说明当前 Paprika 静态网站 mockup 的页面、功能、内容模块、交互方式、视觉规范、CSS 类、JS 函数、CTA 映射、表单边界、服务器目标和已知风险。
|
||||||
|
|
||||||
|
2. [02-项目程序开发详细步骤.md](02-项目程序开发详细步骤.md)
|
||||||
|
说明从当前单文件 mockup 到可维护、可上线版本的开发步骤,包括静态修复、真实表单、工程化拆分、8082 部署、HTTPS、回归验收和完全对标核验命令。
|
||||||
|
|
||||||
|
3. [03-推进台账.md](03-推进台账.md)
|
||||||
|
记录每轮做了什么、改了哪些文件、验证了什么、下一步是什么。
|
||||||
|
|
||||||
|
4. [04-任务矩阵.md](04-任务矩阵.md)
|
||||||
|
统一维护任务编号、状态、验收标准和依赖,防止重复做同一件事。
|
||||||
|
|
||||||
|
5. [05-验收证据.md](05-验收证据.md)
|
||||||
|
记录命令、页面、截图、PDF、job_id、report_id、submission_id、部署链接等证据。
|
||||||
|
|
||||||
|
6. [06-决策记录.md](06-决策记录.md)
|
||||||
|
记录关键设计、工程、部署和安全决策,后续变更时先查这里,避免反复争论同一个问题。
|
||||||
|
|
||||||
|
## 当前基线
|
||||||
|
|
||||||
|
- 参考文件:`/home/mes123456/MeetPaprika/doc/index.html`
|
||||||
|
- 当前形态:已新增 Next.js + React 工程;`doc/index.html` 保留为静态 mockup 对标基线。
|
||||||
|
- 静态基线:`doc/index.html` 仍通过查询参数 `?page=...` 渲染不同页面。
|
||||||
|
- Next 路由:`/`、`/press`、`/judging`、`/scholarly`、`/services`、`/why`、`/resources`、`/faq`、`/start`、`/ask`、`/thanks`、`/terms`、`/privacy`、`/disclaimer`。
|
||||||
|
- 当前表单:`/start` 和 `/ask` 已改为真实 React 表单,提交到 `POST /api/submissions`;成功入库依赖生产 `DATABASE_URL`。
|
||||||
|
- 数据访问层:Prisma ORM;已存在 `prisma/schema.prisma` 和初始化 migration。
|
||||||
|
- 目标域名:`https://paprikalaw.com/`
|
||||||
|
- 目标服务器:`ubuntu@170.106.192.152`
|
||||||
|
- 目标端口:`8082`
|
||||||
|
- 邮箱普通入口:`https://mail.paprikalaw.com/webmail/`
|
||||||
|
- 邮箱管理员入口:`https://mail.paprikalaw.com/admin/`
|
||||||
|
- 凭据规则:长期文档不保存明文密码,后续部署改用 SSH key 或受控密钥交接。
|
||||||
|
|
||||||
|
## 下一步建议
|
||||||
|
|
||||||
|
1. 配置生产 `.env` 的 `DATABASE_URL`,在服务器 PostgreSQL 上执行 Prisma migration。
|
||||||
|
2. 远程验证 `170.106.192.152` 的 PostgreSQL、应用数据库和应用用户。
|
||||||
|
3. 将 Next.js 应用部署到服务器 `8082`,再配置 HTTPS 反向代理到 `https://paprikalaw.com/`。
|
||||||
|
4. 登录邮件后台确认邮箱、DNS 和收发,再接入表单通知。
|
||||||
|
5. 完成法务文本、outlet/network 名称、文件上传策略和截图验收。
|
||||||
|
|
||||||
|
## 工作规则
|
||||||
|
|
||||||
|
1. 开始前查 `04-任务矩阵.md`。
|
||||||
|
2. 完成后写 `03-推进台账.md`。
|
||||||
|
3. 有命令、截图、部署、表单提交、PDF、job_id、report_id 时写 `05-验收证据.md`。
|
||||||
|
4. 有路线、合规、安全、部署、架构取舍时写 `06-决策记录.md`。
|
||||||
|
5. 不把 SSH 密码、API key、表单 webhook secret、CRM token 写入本目录。
|
||||||
Reference in New Issue
Block a user