120 lines
4.1 KiB
TypeScript
120 lines
4.1 KiB
TypeScript
import { postsQuery, urlFor } from '@/lib/sanity'
|
|
import { sanityFetch } from '@/lib/sanityFetch'
|
|
import Link from 'next/link'
|
|
import Image from 'next/image'
|
|
import type { Metadata } from 'next'
|
|
|
|
export const metadata: Metadata = {
|
|
title: 'Build Guides',
|
|
description: 'AR-15 build guides, part breakdowns, and buying advice from Battl Builders.',
|
|
}
|
|
|
|
export const revalidate = 60 // ISR — regenerate every 60s
|
|
|
|
type Post = {
|
|
_id: string
|
|
title: string
|
|
slug: { current: string }
|
|
publishedAt: string
|
|
excerpt: string | null
|
|
mainImage: { asset: object; alt?: string } | null
|
|
author: { name: string; image?: object } | null
|
|
}
|
|
|
|
function formatDate(iso: string) {
|
|
return new Date(iso).toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
})
|
|
}
|
|
|
|
export default async function GuidesPage() {
|
|
let posts: Post[] = []
|
|
try {
|
|
posts = await sanityFetch<Post[]>(postsQuery)
|
|
} catch (err) {
|
|
console.error('[Guides] Sanity fetch failed:', err)
|
|
}
|
|
|
|
return (
|
|
<main className="mx-auto max-w-4xl px-4 py-10">
|
|
<header className="mb-10">
|
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-zinc-500">
|
|
Battl Builders
|
|
</p>
|
|
<h1 className="mt-2 text-3xl font-semibold tracking-tight">
|
|
Build Guides
|
|
</h1>
|
|
<p className="mt-3 text-sm text-zinc-400 max-w-xl">
|
|
Part breakdowns, buying advice, and build walkthroughs to help you
|
|
make smarter decisions before you spend a dollar.
|
|
</p>
|
|
</header>
|
|
|
|
{posts.length === 0 ? (
|
|
<p className="text-sm text-zinc-500">No guides published yet — check back soon.</p>
|
|
) : (
|
|
<div className="space-y-8">
|
|
{posts.map((post) => (
|
|
<article
|
|
key={post._id}
|
|
className="group flex flex-col gap-4 rounded-xl border border-zinc-800 bg-zinc-950/60 p-5 transition-colors hover:border-zinc-700 sm:flex-row"
|
|
>
|
|
{/* Thumbnail */}
|
|
{post.mainImage?.asset && (
|
|
<div className="relative h-44 w-full shrink-0 overflow-hidden rounded-lg sm:h-32 sm:w-48">
|
|
<Image
|
|
src={urlFor(post.mainImage).width(400).height(256).url()}
|
|
alt={post.mainImage.alt ?? post.title}
|
|
fill
|
|
className="object-cover transition-transform duration-300 group-hover:scale-105"
|
|
sizes="(max-width: 640px) 100vw, 192px"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Text */}
|
|
<div className="flex flex-col justify-center gap-2">
|
|
<div className="flex items-center gap-2 text-[0.7rem] text-zinc-500">
|
|
{post.author?.name && <span>{post.author.name}</span>}
|
|
{post.author?.name && <span>·</span>}
|
|
<time dateTime={post.publishedAt}>{formatDate(post.publishedAt)}</time>
|
|
</div>
|
|
|
|
<h2 className="text-lg font-semibold leading-snug text-zinc-100 group-hover:text-amber-300 transition-colors">
|
|
<Link href={`/guides/${post.slug.current}`} className="stretched-link">
|
|
{post.title}
|
|
</Link>
|
|
</h2>
|
|
|
|
{post.excerpt && (
|
|
<p className="text-sm text-zinc-400 leading-relaxed line-clamp-2">
|
|
{post.excerpt}
|
|
</p>
|
|
)}
|
|
|
|
<Link
|
|
href={`/guides/${post.slug.current}`}
|
|
className="mt-1 self-start text-xs font-medium text-amber-400 hover:text-amber-300"
|
|
>
|
|
Read guide →
|
|
</Link>
|
|
</div>
|
|
</article>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-12 rounded-lg border border-zinc-800 bg-zinc-950/60 p-5 text-center">
|
|
<p className="text-sm text-zinc-400">
|
|
Ready to start building?{' '}
|
|
<Link href="/builder" className="font-semibold text-amber-400 hover:text-amber-300">
|
|
Open the Builder →
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|