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:
102
src/app/resources/[slug]/page.tsx
Normal file
102
src/app/resources/[slug]/page.tsx
Normal 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"
|
||||
>
|
||||
← {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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user