diff --git a/app/blog/[slug]/layout.tsx b/app/blog/[slug]/layout.tsx new file mode 100644 index 0000000..5958de2 --- /dev/null +++ b/app/blog/[slug]/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from "next"; +import { ProjectProvider } from "@/app/context/ProjectContext"; +import { getBlogDetails, BLOG_POSTS } from "@/data/content"; + +export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise { + const { slug } = await params; + const post = BLOG_POSTS.find((p) => p.slug === slug); + if (!post) return { title: "Post not found" }; + return { title: `${post.title} | Blog`, description: post.description }; +} + +/** + * Layout for all paths caught under blog/[slug]. + * Mirrors app/project/[slug]/layout.tsx: the rendered markdown is resolved + * here on the server and handed to the client page via the shared context. + * (ProjectContext is a generic `any` context despite its name; its value + * contract is `string | null` = rendered HTML.) + */ +export default async function BlogSlugLayout({ children, params }: { + children: React.ReactNode; + params: Promise<{ slug: string }>; +}) { + + const { slug } = await params; + const postData = await getBlogDetails(slug); + return ( + +
+ {children} +
+
+ ); +} diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..e402afb --- /dev/null +++ b/app/blog/[slug]/page.tsx @@ -0,0 +1,76 @@ +'use client' +import { use } from 'react' + +import { notFound } from 'next/navigation' +import { useSharedData } from '@/app/context/ProjectContext'; +import { useMountTransition } from '@/hooks/useAnimations'; +import "../../../styles/markdown-container.css"; +import Link from 'next/link'; +import { BLOG_POSTS } from '@/data/content'; +import SimpleGallery from '@/components/SimpleGallery'; + +export default function BlogPostPage({params}: { params: Promise<{ slug: string }> }) +{ + + const mounted = useMountTransition(50) + const { slug } = use(params); + + const hasValidPost = BLOG_POSTS.find((v)=>v.slug == slug); + if (!slug || !hasValidPost) notFound(); + + const hasValidDetails = hasValidPost.details + const postDetails = useSharedData(); + + return ( +
+ + { + hasValidDetails ? + ( +
+
+ ) : ( +

No post details available.

+ ) + } + + {hasValidPost.images && hasValidPost.images.length > 0 && ( +
+

Gallery

+ +
+ )} + + +
+ { + window.scrollTo({ + top: 0, + left: 0, + behavior: 'smooth' + }); + }} + + className="expand-on-hover-button" + href={"#"}>Back To Top + + All Posts +
+
+ ); +} diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx new file mode 100644 index 0000000..cc13e85 --- /dev/null +++ b/app/blog/layout.tsx @@ -0,0 +1,57 @@ +import type { Metadata } from "next"; +import Nav from "@/components/Nav"; +import Footer from "@/components/Footer"; + +export async function generateMetadata(): Promise { + return { + title: "Blog", + description: "Notes, write-ups and behind-the-scenes from Hunter", + }; +} + +export default async function BlogLayout({children}: { + children: React.ReactNode; +}) { + + return ( +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ ); +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx new file mode 100644 index 0000000..44ae0ae --- /dev/null +++ b/app/blog/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import BlogList from "@/components/BlogList"; + +export default function Blog() { + return ( +
+ +
+ ); +}