Major site overhaul: resources hub, content migration, new blog posts, forms
- Redesign /resources as sectioned hub with category pages - Migrate 645 Squarespace CDN images to local /images/content/ - Create 9 new news/blog posts with event photos - Fix blog post slugs (rename gibberish filenames) - Rename Design Blog to Design Blogs across site - Remove education page, replace with Platform in nav - Redesign rover repair request form with dynamic rover entries - Add school search combobox to contact, store, and repair forms - Extract shared KNOWN_SCHOOLS data - Make /rover-expansion-3d-printing dynamically pull from MDX - Add related resources sections to product pages - Fix homepage broken /quote links to /store - Store page: sample kit cards, inline quote builder, mailing list opt-in
This commit is contained in:
@@ -5,24 +5,94 @@ import { MDXRemote } from "next-mdx-remote/rsc";
|
||||
import { Container } from "@/components/layout/Container";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { getAllResources, getResourceBySlug } from "@/lib/resources";
|
||||
import { ResourcesGrid } from "../resources-client";
|
||||
|
||||
function MdxLink(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
|
||||
const { href, children, ...rest } = props;
|
||||
const isExternal = href?.startsWith("http");
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
{...(isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const mdxComponents = {
|
||||
a: MdxLink,
|
||||
};
|
||||
|
||||
const NEWS_CATEGORIES = ["News & Updates", "Customer Stories"];
|
||||
|
||||
const CATEGORY_SLUGS: Record<string, {
|
||||
name: string;
|
||||
description: string;
|
||||
matchCategories?: string[];
|
||||
filterCategories?: string[];
|
||||
}> = {
|
||||
"getting-started": {
|
||||
name: "Getting Started",
|
||||
description: "Everything you need to get up and running with the Micromelon platform.",
|
||||
},
|
||||
activities: {
|
||||
name: "Activities",
|
||||
description: "Hands-on activities for the Micromelon Rover and Robot Simulator.",
|
||||
matchCategories: ["Activities", "Simulator Activities"],
|
||||
filterCategories: ["Activities", "Simulator Activities"],
|
||||
},
|
||||
"sensor-guides": {
|
||||
name: "Sensor Guides",
|
||||
description: "Learn how each sensor on the Micromelon Rover works and how to code it.",
|
||||
},
|
||||
"3d-printing-guides": {
|
||||
name: "3D Printing Guides",
|
||||
description: "Guides for designing, printing, and troubleshooting rover attachments.",
|
||||
},
|
||||
"advanced-guides": {
|
||||
name: "Advanced Guides",
|
||||
description: "I2C, UART, OpenMV, and other advanced topics for experienced users.",
|
||||
},
|
||||
"build-guides": {
|
||||
name: "Build Guides",
|
||||
description: "Step-by-step instructions for building rover attachments.",
|
||||
},
|
||||
"design-blogs": {
|
||||
name: "Design Blogs",
|
||||
description: "Read about the design process behind rover attachments.",
|
||||
},
|
||||
};
|
||||
|
||||
interface ResourcePageProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
const resources = getAllResources();
|
||||
return resources.map((resource) => ({
|
||||
const resourceParams = resources.map((resource) => ({
|
||||
slug: resource.slug,
|
||||
}));
|
||||
const categoryParams = Object.keys(CATEGORY_SLUGS).map((slug) => ({
|
||||
slug,
|
||||
}));
|
||||
return [...resourceParams, ...categoryParams];
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: ResourcePageProps): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
|
||||
const category = CATEGORY_SLUGS[slug];
|
||||
if (category) {
|
||||
return {
|
||||
title: `${category.name} - Resources`,
|
||||
description: category.description,
|
||||
};
|
||||
}
|
||||
|
||||
const resource = getResourceBySlug(slug);
|
||||
if (!resource) return {};
|
||||
return {
|
||||
@@ -33,6 +103,35 @@ export async function generateMetadata({
|
||||
|
||||
export default async function ResourcePage({ params }: ResourcePageProps) {
|
||||
const { slug } = await params;
|
||||
|
||||
/* Category page */
|
||||
const category = CATEGORY_SLUGS[slug];
|
||||
if (category) {
|
||||
const cats = category.matchCategories || [category.name];
|
||||
const resources = getAllResources()
|
||||
.filter((r) => r.categories.some((c) => cats.includes(c)))
|
||||
.map(({ content, ...meta }) => meta);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="bg-white pt-16 sm:pt-20">
|
||||
<Container>
|
||||
<Button href="/resources" variant="outline" size="sm" className="mb-8">
|
||||
← All Resources
|
||||
</Button>
|
||||
</Container>
|
||||
</section>
|
||||
<ResourcesGrid
|
||||
resources={resources}
|
||||
title={category.name}
|
||||
subtitle={category.description}
|
||||
filterCategories={category.filterCategories}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* Individual resource page */
|
||||
const resource = getResourceBySlug(slug);
|
||||
|
||||
if (!resource) {
|
||||
@@ -94,8 +193,19 @@ export default async function ResourcePage({ params }: ResourcePageProps) {
|
||||
|
||||
{/* MDX Content */}
|
||||
<article className="mdx-content">
|
||||
<MDXRemote source={resource.content} />
|
||||
<MDXRemote source={resource.content} components={mdxComponents} />
|
||||
</article>
|
||||
|
||||
{/* Return button */}
|
||||
<div className="mt-12 border-t border-border pt-8">
|
||||
<Button
|
||||
href={isNews ? "/news" : "/resources"}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
← {isNews ? "Return to News" : "Return to Resources"}
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user