Seed: blog template (17 files)

This commit is contained in:
2026-07-23 22:10:01 +00:00
parent 8558117f79
commit c29087196d
+174
View File
@@ -0,0 +1,174 @@
import { Link, useParams } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
ArrowRight, ChevronLeft, ChevronRight, Clock
} from "lucide-react";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
const categoryColors: Record<string, string> = {
Technology: "bg-emerald-100 text-emerald-700",
Design: "bg-amber-100 text-amber-700",
Business: "bg-stone-200 text-stone-700",
Lifestyle: "bg-teal-100 text-teal-700",
};
const categoryInfo: Record<string, { description: string; count: number }> = {
technology: { description: "Deep dives into software development, web technologies, AI, and the tools shaping the future of tech.", count: 42 },
design: { description: "Exploring visual design, UX principles, design systems, and the art of crafting beautiful interfaces.", count: 28 },
business: { description: "Insights on startups, product strategy, growth, and the business side of technology.", count: 19 },
lifestyle: { description: "Productivity tips, remote work culture, career advice, and life as a tech professional.", count: 15 },
};
const articles = [
{ id: 1, title: "Understanding React Server Components in Production", excerpt: "A practical guide to adopting RSC in existing React applications.", category: "Technology", author: "Sarah Chen", initials: "SC", date: "Mar 12, 2025", gradient: "from-amber-200 to-orange-300", slug: "react-server-components" },
{ id: 2, title: "TypeScript Best Practices for Large Codebases", excerpt: "Type-safe patterns and architectural decisions that pay off at scale.", category: "Technology", author: "James Liu", initials: "JL", date: "Mar 6, 2025", gradient: "from-emerald-200 to-teal-300", slug: "typescript-best-practices" },
{ id: 3, title: "Edge Functions: A Practical Introduction", excerpt: "How edge computing changes the way we deploy and run web applications.", category: "Technology", author: "Sarah Chen", initials: "SC", date: "Mar 1, 2025", gradient: "from-stone-200 to-stone-400", slug: "edge-functions-intro" },
{ id: 4, title: "The State of WebAssembly in 2025", excerpt: "Where Wasm stands today and where it is heading for web developers.", category: "Technology", author: "Aisha Patel", initials: "AP", date: "Feb 25, 2025", gradient: "from-amber-100 to-amber-300", slug: "webassembly-2025" },
{ id: 5, title: "Building Real-Time Apps with Server-Sent Events", excerpt: "A simpler alternative to WebSockets for many real-time use cases.", category: "Technology", author: "Marcus Rivera", initials: "MR", date: "Feb 20, 2025", gradient: "from-emerald-100 to-emerald-300", slug: "server-sent-events" },
{ id: 6, title: "Modern CSS Layout Patterns You Should Know", excerpt: "Container queries, subgrid, and other CSS features transforming layout.", category: "Technology", author: "David Kim", initials: "DK", date: "Feb 15, 2025", gradient: "from-stone-100 to-amber-200", slug: "modern-css-layout" },
];
const allCategories = ["Technology", "Design", "Business", "Lifestyle"];
export default function CategoryPage() {
const { name } = useParams();
const categoryName = name ? name.charAt(0).toUpperCase() + name.slice(1) : "Technology";
const info = categoryInfo[name || "technology"] || categoryInfo.technology;
return (
<div className="min-h-screen bg-stone-50 text-stone-900 antialiased">
<Header />
{/* Category Hero */}
<section className="py-24 pb-0">
<div className="mx-auto max-w-7xl px-6 pt-8 pb-12 border-b border-stone-200">
<nav className="flex items-center gap-2 text-sm text-stone-400 mb-6">
<Link to="/" className="hover:text-emerald-600 transition-colors">Home</Link>
<ChevronRight className="h-3 w-3" />
<span className="text-stone-600">Categories</span>
<ChevronRight className="h-3 w-3" />
<span className="text-stone-600">{categoryName}</span>
</nav>
<div className="animate-fade-up">
<Badge className={`mb-4 border-0 hover:bg-primary hover:text-primary-foreground transition-colors \${categoryColors[categoryName] || "bg-emerald-100 text-emerald-700"}`}>
{categoryName}
</Badge>
<div className="flex items-start justify-between flex-wrap gap-4">
<div>
<h1 className="text-3xl sm:text-4xl font-bold tracking-tight">{categoryName}</h1>
<p className="mt-3 text-stone-500 max-w-2xl">{info.description}</p>
<div className="mt-3 flex items-center gap-3">
<Badge variant="secondary" className="bg-emerald-100 text-emerald-700 text-xs">{info.count} articles</Badge>
</div>
</div>
<div className="flex items-center gap-1 text-sm text-stone-400 mt-2">
<span className="font-medium text-stone-900 cursor-pointer hover:text-emerald-600 transition-colors">Latest</span>
<span className="mx-1">|</span>
<span className="cursor-pointer hover:text-emerald-600 transition-colors">Popular</span>
<span className="mx-1">|</span>
<span className="cursor-pointer hover:text-emerald-600 transition-colors">Oldest</span>
</div>
</div>
</div>
</div>
</section>
{/* Articles + Sidebar */}
<section className="py-24">
<div className="mx-auto max-w-7xl px-6">
<div className="grid lg:grid-cols-4 gap-10">
{/* Articles Grid */}
<div className="lg:col-span-3">
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 stagger">
{articles.map((article) => (
<Link to={`/article/\${article.slug}`} key={article.id} className="group">
<Card className="overflow-hidden border-0 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300 bg-white">
<div className={`relative aspect-video bg-gradient-to-br \${article.gradient}`}>
<div className="absolute inset-0" style={{ backgroundImage: "radial-gradient(circle at 25% 25%, rgba(255,255,255,0.15) 1px, transparent 1px)", backgroundSize: "20px 20px" }} />
<div className="absolute top-1/3 left-1/4 w-24 h-24 rounded-full bg-white/10 blur-2xl" />
</div>
<CardContent className="p-5">
<h3 className="font-semibold text-stone-900 group-hover:text-emerald-600 transition-colors leading-snug line-clamp-2">
{article.title}
</h3>
<p className="mt-2 text-sm text-stone-500 line-clamp-2">{article.excerpt}</p>
<div className="mt-3 flex items-center text-xs text-stone-400">
<span>{article.date}</span>
<span className="mx-1.5">&middot;</span>
<span className="flex items-center gap-1"><Clock className="h-3 w-3" /> 5 min read</span>
</div>
<div className="mt-3 flex items-center gap-2">
<div className="h-5 w-5 rounded-full bg-gradient-to-br from-emerald-400 to-teal-600 flex items-center justify-center">
<span className="text-[8px] text-white font-bold">{article.initials}</span>
</div>
<span className="text-xs text-stone-500">{article.author}</span>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
{/* Pagination */}
<div className="mt-12 flex items-center justify-center gap-2">
<Button variant="outline" size="icon" className="h-9 w-9 rounded-full">
<ChevronLeft className="h-4 w-4" />
</Button>
{[1, 2, 3, 4, 5].map((page) => (
<Button
key={page}
variant={page === 1 ? "default" : "outline"}
size="icon"
className={`h-9 w-9 rounded-full \${page === 1 ? "bg-emerald-600 hover:bg-emerald-700" : ""}`}
>
{page}
</Button>
))}
<Button variant="outline" size="icon" className="h-9 w-9 rounded-full">
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
{/* Sidebar */}
<div className="lg:col-span-1">
<Card className="border-0 shadow-sm bg-white sticky top-28">
<CardContent className="p-6">
<h3 className="font-semibold text-sm uppercase tracking-wider text-stone-500 mb-4">All Categories</h3>
<ul className="space-y-2">
{allCategories.map((cat) => (
<li key={cat}>
<Link
to={`/category/\${cat.toLowerCase()}`}
className={`flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-colors \${
cat === categoryName
? "bg-emerald-50 text-emerald-700 font-medium"
: "text-stone-600 hover:bg-stone-50"
}`}
>
<span className={`h-2 w-2 rounded-full \${
cat === categoryName ? "bg-emerald-500" : "bg-stone-300"
}`} />
{cat}
<Badge variant="secondary" className="ml-auto bg-stone-100 text-stone-500 text-xs">
{categoryInfo[cat.toLowerCase()]?.count || 0}
</Badge>
</Link>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
</div>
</div>
</section>
<Footer />
</div>
);
}