From 60162e05750fc7b6390e64839f77664a5c18aa3a Mon Sep 17 00:00:00 2001 From: Musa Date: Mon, 22 Dec 2025 15:02:45 -0800 Subject: [PATCH] include contact page and restructuring (#640) * include contact and navbar changes * removereact references * tweak contacts APi route * change font --- apps/www/package.json | 1 + apps/www/src/app/api/contact/route.ts | 102 +++++++++++ apps/www/src/app/contact/page.tsx | 234 ++++++++++++++++++++++++++ apps/www/src/app/docs/page.tsx | 2 - apps/www/src/app/page.tsx | 1 - apps/www/src/app/research/page.tsx | 1 - apps/www/tsconfig.json | 1 + package-lock.json | 125 +++++++++++++- package.json | 6 +- packages/shared-styles/package.json | 2 +- packages/ui/src/components/Footer.tsx | 2 +- packages/ui/src/components/Navbar.tsx | 1 + 12 files changed, 468 insertions(+), 10 deletions(-) create mode 100644 apps/www/src/app/api/contact/route.ts create mode 100644 apps/www/src/app/contact/page.tsx diff --git a/apps/www/package.json b/apps/www/package.json index 130aa1b1..8492b36f 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -31,6 +31,7 @@ "papaparse": "^5.5.3", "react": "19.2.0", "react-dom": "19.2.0", + "resend": "^6.6.0", "sanity": "^4.18.0", "styled-components": "^6.1.19" }, diff --git a/apps/www/src/app/api/contact/route.ts b/apps/www/src/app/api/contact/route.ts new file mode 100644 index 00000000..6c80bf6f --- /dev/null +++ b/apps/www/src/app/api/contact/route.ts @@ -0,0 +1,102 @@ +import { Resend } from 'resend'; +import { NextResponse } from 'next/server'; + +function getResendClient() { + const apiKey = process.env.RESEND_API_KEY; + if (!apiKey) { + throw new Error('RESEND_API_KEY environment variable is not set'); + } + return new Resend(apiKey); +} + +interface ContactPayload { + email: string; + firstName: string; + lastName: string; + company?: string; + lookingFor: string; +} + +function buildProperties(company?: string, lookingFor?: string): Record | undefined { + const properties: Record = {}; + if (company) properties.company_name = company; + if (lookingFor) properties.looking_for = lookingFor; + return Object.keys(properties).length > 0 ? properties : undefined; +} + +function isDuplicateError(error: { message?: string; statusCode?: number | null }): boolean { + const errorMessage = error.message?.toLowerCase() || ''; + return ( + errorMessage.includes('already exists') || + errorMessage.includes('duplicate') || + error.statusCode === 409 + ); +} + +function createContactPayload( + email: string, + firstName: string, + lastName: string, + company?: string, + lookingFor?: string +) { + const properties = buildProperties(company, lookingFor); + return { + email, + firstName, + lastName, + unsubscribed: false, + ...(properties && { properties }), + }; +} + +export async function POST(req: Request) { + try { + const body = await req.json(); + const { firstName, lastName, email, company, lookingFor }: ContactPayload = body; + + if (!email || !firstName || !lastName || !lookingFor) { + return NextResponse.json( + { error: 'Missing required fields' }, + { status: 400 } + ); + } + + const contactPayload = createContactPayload(email, firstName, lastName, company, lookingFor); + const resend = getResendClient(); + + const { data, error } = await resend.contacts.create(contactPayload); + + if (error) { + if (isDuplicateError(error)) { + const { data: updateData, error: updateError } = await resend.contacts.update( + contactPayload + ); + + if (updateError) { + console.error('Resend update error:', updateError); + return NextResponse.json( + { error: updateError.message || 'Failed to update contact' }, + { status: 500 } + ); + } + + return NextResponse.json({ success: true, data: updateData }); + } + + console.error('Resend create error:', error); + return NextResponse.json( + { error: error.message || 'Failed to create contact' }, + { status: error.statusCode || 500 } + ); + } + + return NextResponse.json({ success: true, data }); + } catch (error) { + console.error('Unexpected error:', error); + return NextResponse.json( + { error: error instanceof Error ? error.message : 'Unknown error' }, + { status: 500 } + ); + } +} diff --git a/apps/www/src/app/contact/page.tsx b/apps/www/src/app/contact/page.tsx new file mode 100644 index 00000000..84dcbdd8 --- /dev/null +++ b/apps/www/src/app/contact/page.tsx @@ -0,0 +1,234 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@katanemo/ui"; +import Link from "next/link"; +import { ArrowRight, MessageSquare, Building2, MessagesSquare } from "lucide-react"; + +export default function ContactPage() { + const [formData, setFormData] = useState({ + firstName: "", + lastName: "", + email: "", + company: "", + lookingFor: "", + message: "", + }); + const [status, setStatus] = useState<"idle" | "submitting" | "success" | "error">("idle"); + const [errorMessage, setErrorMessage] = useState(""); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setStatus("submitting"); + setErrorMessage(""); + + try { + const res = await fetch("/api/contact", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(formData), + }); + + const data = await res.json(); + + if (!res.ok) { + throw new Error(data.error || "Something went wrong"); + } + + setStatus("success"); + setFormData({ + firstName: "", + lastName: "", + email: "", + company: "", + lookingFor: "", + message: "", + }); + } catch (error) { + setStatus("error"); + setErrorMessage(error instanceof Error ? error.message : "Failed to submit form"); + } + }; + + return ( +
+ {/* Hero / Header Section */} +
+
+

+ Let's start a + + conversation + +

+

+ Whether you're an enterprise looking for a custom solution or a developer building cool agents, we'd love to hear from you. +

+
+
+ + {/* Main Content - Split Layout */} +
+
+
+ + {/* Left Side: Community (Discord) */} +
+ {/* Background icon */} +
+ +
+ +
+
+
+ + Community +
+

Join Our Discord

+
+

+ Connect with other developers, ask questions, share what you're building, and stay updated on the latest features by joining our Discord server. +

+
+ + +
+ + {/* Right Side: Enterprise Contact */} +
+ {/* Subtle background pattern */} +
+ + {/* Background icon */} +
+ +
+ +
+
+ + Enterprise +
+

Contact Us

+
+ +
+ {status === "success" ? ( +
+
+ + + +
+
Message Sent!
+

Thank you for reaching out. We'll be in touch shortly.

+ +
+ ) : ( +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ +