Build Your Kit page and full Micromelon website

Complete website build including:
- Build Your Kit store page with cart system, sectioned layout
  (Hardware, Software, Attachments, Spare Parts), inline quote
  request form, and sticky sidebar summary
- 16+ pages: Education, Platform, Resources, News, About Us,
  Download, Contact, Rover, Code Editor, Robot Simulator, etc.
- 89+ MDX resource articles and 18 news posts
- Store product images scraped from micromelon.com.au
- Quote request API route with Airtable integration
- Dynamic back links and cover photos on resource pages
- Redesigned downloads page
- Fixed corrupted MDX code blocks
This commit is contained in:
Tim Hadwen
2026-02-28 19:00:42 +10:00
parent 5233233662
commit 707c49dd3f
320 changed files with 22333 additions and 107 deletions

View File

@@ -0,0 +1,102 @@
import { notFound } from "next/navigation";
import Image from "next/image";
import { Metadata } from "next";
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";
const NEWS_CATEGORIES = ["News & Updates", "Customer Stories"];
interface ResourcePageProps {
params: Promise<{ slug: string }>;
}
export function generateStaticParams() {
const resources = getAllResources();
return resources.map((resource) => ({
slug: resource.slug,
}));
}
export async function generateMetadata({
params,
}: ResourcePageProps): Promise<Metadata> {
const { slug } = await params;
const resource = getResourceBySlug(slug);
if (!resource) return {};
return {
title: resource.title,
description: resource.excerpt,
};
}
export default async function ResourcePage({ params }: ResourcePageProps) {
const { slug } = await params;
const resource = getResourceBySlug(slug);
if (!resource) {
notFound();
}
const isNews = resource.categories.some((cat) =>
NEWS_CATEGORIES.includes(cat)
);
return (
<section className="bg-white py-16 sm:py-20">
<Container className="max-w-3xl">
<Button
href={isNews ? "/news" : "/resources"}
variant="outline"
size="sm"
className="mb-8"
>
&larr; {isNews ? "Back to News" : "Back to Resources"}
</Button>
{/* Header */}
<div className="mb-8">
<div className="mb-3 flex flex-wrap gap-1.5">
{resource.categories.map((cat) => (
<span
key={cat}
className="rounded-full bg-muted px-2.5 py-0.5 text-xs font-medium text-muted-foreground"
>
{cat}
</span>
))}
</div>
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
{resource.title}
</h1>
<p className="mt-3 text-muted-foreground">
{new Date(resource.date).toLocaleDateString("en-AU", {
year: "numeric",
month: "long",
day: "numeric",
})}
</p>
</div>
{/* Cover Image */}
{resource.featuredImage && (
<div className="relative mb-10 aspect-[16/9] overflow-hidden rounded-xl">
<Image
src={resource.featuredImage}
alt={resource.title}
fill
className="object-cover"
priority
/>
</div>
)}
{/* MDX Content */}
<article className="mdx-content">
<MDXRemote source={resource.content} />
</article>
</Container>
</section>
);
}