Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions data/contact-submissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[
{
"id": "contact_1739124000000_a1b2c3",
"name": "Avery Lane",
"email": "avery@example.com",
"subject": "Question about premium access",
"message": "Hi team, I want to understand what is included in the premium plan for small agent teams.",
"type": "general",
"submittedAt": "2026-02-01T16:00:00.000Z"
},
{
"id": "contact_1739131200000_d4e5f6",
"name": "Morgan Chen",
"email": "morgan@opsbot.ai",
"subject": "Need help with setup",
"message": "Our CI workflow fails during bootstrap. Could someone point us to the right debugging guide?",
"type": "support",
"submittedAt": "2026-02-01T18:00:00.000Z"
},
{
"id": "contact_1739203200000_g7h8i9",
"name": "Jordan Patel",
"email": "jordan@partnerco.dev",
"subject": "Potential integration partnership",
"message": "We are building agent tooling and would like to discuss a partnership and listing opportunities.",
"type": "partnership",
"submittedAt": "2026-02-02T14:00:00.000Z"
},
{
"id": "contact_1739289600000_j1k2l3",
"name": "Sam Rivera",
"email": "sam.rivera@example.net",
"subject": "Feedback on onboarding",
"message": "The onboarding flow is smooth overall, but adding one more example for environment setup would help.",
"type": "feedback",
"submittedAt": "2026-02-03T14:00:00.000Z"
},
{
"id": "contact_1739376000000_m4n5o6",
"name": "Taylor Brooks",
"email": "taylor@agentlabs.io",
"subject": "Support response time question",
"message": "Can you share expected response times for technical support requests submitted on weekends?",
"type": "support",
"submittedAt": "2026-02-04T14:00:00.000Z"
}
]
125 changes: 125 additions & 0 deletions src/app/api/contact/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { NextRequest, NextResponse } from "next/server";
import { promises as fs } from "fs";
import path from "path";

const CONTACT_SUBMISSIONS_PATH = path.join(
process.cwd(),
"data",
"contact-submissions.json"
);

const CONTACT_TYPES = ["general", "support", "partnership", "feedback"] as const;

type ContactType = (typeof CONTACT_TYPES)[number];

type ContactSubmission = {
id: string;
name: string;
email: string;
subject: string;
message: string;
type: ContactType;
submittedAt: string;
};

type ContactPayload = {
name?: unknown;
email?: unknown;
subject?: unknown;
message?: unknown;
type?: unknown;
};

const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function toTrimmedString(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}

function validateContactPayload(payload: ContactPayload): string[] {
const errors: string[] = [];
const name = toTrimmedString(payload.name);
const email = toTrimmedString(payload.email);
const subject = toTrimmedString(payload.subject);
const message = toTrimmedString(payload.message);
const type = payload.type;

if (name.length < 2) {
errors.push("Name must be at least 2 characters long.");
}

if (!EMAIL_REGEX.test(email)) {
errors.push("Email must be a valid email address.");
}

if (subject.length < 3) {
errors.push("Subject must be at least 3 characters long.");
}

if (message.length < 10) {
errors.push("Message must be at least 10 characters long.");
}

if (!CONTACT_TYPES.includes(type as ContactType)) {
errors.push("Type must be one of: general, support, partnership, feedback.");
}

return errors;
}

async function readSubmissions(): Promise<ContactSubmission[]> {
try {
const raw = await fs.readFile(CONTACT_SUBMISSIONS_PATH, "utf-8");
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? (parsed as ContactSubmission[]) : [];
} catch {
return [];
}
}

async function writeSubmissions(submissions: ContactSubmission[]): Promise<void> {
await fs.writeFile(CONTACT_SUBMISSIONS_PATH, JSON.stringify(submissions, null, 2));
}

export async function POST(request: NextRequest) {
try {
const body = (await request.json()) as ContactPayload;
const errors = validateContactPayload(body);

if (errors.length > 0) {
return NextResponse.json(
{ success: false, error: "Validation failed", details: errors },
{ status: 400 }
);
}

const submission: ContactSubmission = {
id: `contact_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
name: toTrimmedString(body.name),
email: toTrimmedString(body.email).toLowerCase(),
subject: toTrimmedString(body.subject),
message: toTrimmedString(body.message),
type: body.type as ContactType,
submittedAt: new Date().toISOString(),
};

const submissions = await readSubmissions();
submissions.push(submission);
await writeSubmissions(submissions);

return NextResponse.json(
{
success: true,
message: "Contact form submitted successfully.",
submissionId: submission.id,
},
{ status: 201 }
);
} catch (error) {
console.error("Contact submission error:", error);
return NextResponse.json(
{ success: false, error: "Invalid request. Expected JSON body." },
{ status: 400 }
);
}
}
Loading
Loading