From d67f9f205c4f776bc70d3a94faa70e21c5bbda77 Mon Sep 17 00:00:00 2001 From: Musa Date: Wed, 28 Jan 2026 10:41:42 -0800 Subject: [PATCH] introduce SEO optimizations for marketing reach --- .gitignore | 1 + apps/www/src/app/blog/[slug]/page.tsx | 26 ++ apps/www/src/app/blog/page.tsx | 7 +- .../www/src/app/contact/ContactPageClient.tsx | 233 ++++++++++++++ apps/www/src/app/contact/page.tsx | 235 +-------------- apps/www/src/app/docs/page.tsx | 14 - apps/www/src/app/layout.tsx | 12 +- .../src/app/research/ResearchPageClient.tsx | 26 ++ apps/www/src/app/research/page.tsx | 28 +- apps/www/src/app/robots.ts | 32 ++ apps/www/src/app/sitemap.ts | 77 +++++ apps/www/src/lib/metadata.ts | 285 ++++++++++++++++++ 12 files changed, 701 insertions(+), 275 deletions(-) create mode 100644 apps/www/src/app/contact/ContactPageClient.tsx delete mode 100644 apps/www/src/app/docs/page.tsx create mode 100644 apps/www/src/app/research/ResearchPageClient.tsx create mode 100644 apps/www/src/app/robots.ts create mode 100644 apps/www/src/app/sitemap.ts create mode 100644 apps/www/src/lib/metadata.ts diff --git a/.gitignore b/.gitignore index 00735b6c..c17af8c4 100644 --- a/.gitignore +++ b/.gitignore @@ -149,3 +149,4 @@ apps/*/dist/ *.logs .cursor/ +.agents diff --git a/apps/www/src/app/blog/[slug]/page.tsx b/apps/www/src/app/blog/[slug]/page.tsx index d2a53dc5..49c55955 100644 --- a/apps/www/src/app/blog/[slug]/page.tsx +++ b/apps/www/src/app/blog/[slug]/page.tsx @@ -1,9 +1,11 @@ import { client, urlFor } from "@/lib/sanity"; +import type { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; import { PortableText } from "@/components/PortableText"; import { notFound } from "next/navigation"; import { UnlockPotentialSection } from "@/components/UnlockPotentialSection"; +import { createBlogPostMetadata } from "@/lib/metadata"; interface BlogPost { _id: string; @@ -67,6 +69,30 @@ export async function generateStaticParams() { return slugs.map((slug) => ({ slug })); } +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}): Promise { + const { slug } = await params; + const post = await getBlogPost(slug); + + if (!post) { + return { + title: "Post Not Found | Plano Blog", + description: "The requested blog post could not be found.", + }; + } + + return createBlogPostMetadata({ + title: post.title, + description: post.summary, + slug: post.slug.current, + publishedAt: post.publishedAt, + author: post.author?.name, + }); +} + export default async function BlogPostPage({ params, }: { diff --git a/apps/www/src/app/blog/page.tsx b/apps/www/src/app/blog/page.tsx index 63d586bc..9b13c5e5 100644 --- a/apps/www/src/app/blog/page.tsx +++ b/apps/www/src/app/blog/page.tsx @@ -5,10 +5,9 @@ import { BlogHeader } from "@/components/BlogHeader"; import { FeaturedBlogCard } from "@/components/FeaturedBlogCard"; import { BlogCard } from "@/components/BlogCard"; import { BlogSectionHeader } from "@/components/BlogSectionHeader"; -export const metadata: Metadata = { - title: "Blog - Plano", - description: "Latest insights, updates, and stories from Plano", -}; +import { pageMetadata } from "@/lib/metadata"; + +export const metadata: Metadata = pageMetadata.blog; interface BlogPost { _id: string; diff --git a/apps/www/src/app/contact/ContactPageClient.tsx b/apps/www/src/app/contact/ContactPageClient.tsx new file mode 100644 index 00000000..4b22a410 --- /dev/null +++ b/apps/www/src/app/contact/ContactPageClient.tsx @@ -0,0 +1,233 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@katanemo/ui"; +import { MessageSquare, Building2, MessagesSquare } from "lucide-react"; + +export default function ContactPageClient() { + 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.

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