Initial Paprika Next.js implementation

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

15
lib/db.ts Normal file
View 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

File diff suppressed because one or more lines are too long

22
lib/navigation.ts Normal file
View 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
View 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
View 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
}
});
}