Seed: editorial template (15 files)

This commit is contained in:
2026-07-23 22:14:22 +00:00
parent f09b602f26
commit 057def056d
+171
View File
@@ -0,0 +1,171 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Clock, Calendar, Heart, Share2, Bookmark, MessageCircle } from "lucide-react";
import { toast } from "sonner";
import { useSaasMutation } from "@/hooks/use-tygarun";
const relatedStories = [
{ category: "Culture", title: "The Renaissance of Public Libraries", author: "Kai Mendez", img: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=600&h=400&fit=crop" },
{ category: "Style", title: "Minimalism Beyond the Aesthetic", author: "Ines Moreau", img: "https://images.unsplash.com/photo-1494438639946-1ebd1d20bf85?w=600&h=400&fit=crop" },
{ category: "Technology", title: "Building Cities That Breathe", author: "Leo Chang", img: "https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=600&h=400&fit=crop" },
];
const comments = [
{ name: "Sarah T.", initials: "ST", date: "April 9, 2026", text: "Beautifully written. This captures something I have been feeling but could not articulate." },
{ name: "Marcus L.", initials: "ML", date: "April 9, 2026", text: "The section on adaptive reuse was especially compelling. Would love a follow-up piece." },
{ name: "Priya K.", initials: "PK", date: "April 8, 2026", text: "Shared this with my entire studio. Essential reading for anyone in design." },
];
const tags = ["Urban Design", "Architecture", "Sustainability", "Culture", "Future Cities"];
export default function Article() {
const [commentList, setCommentList] = useState(comments);
const [newComment, setNewComment] = useState("");
const { mutate: postComment, loading: posting } = useSaasMutation("community", "/comments");
const handlePostComment = async () => {
if (!newComment.trim()) return;
try {
await postComment({ body: newComment });
setCommentList([{ name: "You", initials: "YO", date: "Just now", text: newComment }, ...commentList]);
setNewComment("");
toast.success("Comment posted!");
} catch { toast.error("Could not post comment. Please try again."); }
};
return (
<div className="min-h-screen bg-stone-50 dark:bg-stone-950">
<Header />
<main className="pt-24">
{/* Article Header */}
<section className="mx-auto max-w-3xl px-6 mb-8">
<Badge className="bg-stone-200 dark:bg-stone-800 text-stone-600 dark:text-stone-300 border-0 mb-4">Culture</Badge>
<h1 className="font-serif text-3xl md:text-5xl font-bold text-stone-800 dark:text-stone-100 leading-tight animate-fade-up">The Quiet Revolution Reshaping How We Live</h1>
<p className="mt-4 font-serif text-lg md:text-xl text-stone-500 dark:text-stone-400 leading-relaxed">From adaptive reuse to biophilic design, a new generation is reimagining the spaces we inhabit.</p>
<div className="mt-8 flex items-center gap-4 pb-8 border-b border-stone-200 dark:border-stone-800">
<Avatar className="h-12 w-12"><AvatarFallback className="bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300 font-serif">EV</AvatarFallback></Avatar>
<div>
<p className="font-medium text-stone-800 dark:text-stone-100">Elena Vasquez</p>
<p className="text-sm text-stone-500 dark:text-stone-400">Senior Correspondent</p>
</div>
<div className="ml-auto flex items-center gap-4 text-sm text-stone-400 dark:text-stone-500">
<span className="flex items-center gap-1"><Calendar className="h-3.5 w-3.5" /> April 8, 2026</span>
<span className="flex items-center gap-1"><Clock className="h-3.5 w-3.5" /> 12 min read</span>
</div>
</div>
</section>
{/* Hero Image */}
<section className="mx-auto max-w-4xl px-6 mb-12">
<div className="aspect-[16/9] rounded-xl overflow-hidden">
<img src="https://images.unsplash.com/photo-1486325212027-8081e485255e?w=1200&h=675&fit=crop" alt="Article hero" className="w-full h-full object-cover" />
</div>
<p className="mt-2 text-xs text-stone-400 dark:text-stone-500 text-center italic">Photo by Unsplash</p>
</section>
{/* Article Body */}
<article className="mx-auto max-w-3xl px-6 mb-12">
<div className="prose prose-stone dark:prose-invert prose-lg max-w-none">
<p className="first-letter:text-5xl first-letter:font-serif first-letter:font-bold first-letter:float-left first-letter:mr-3 first-letter:mt-1 text-stone-700 dark:text-stone-300 leading-relaxed">There is a movement underway that does not announce itself with manifestos or grand gestures. It arrives quietly, in the form of a converted warehouse that now houses a community library, a rooftop garden where concrete once baked in the sun, a neighborhood that chose bicycles over parking lots.</p>
<p className="text-stone-700 dark:text-stone-300 leading-relaxed mt-6">Across dozens of cities, architects, urban planners, and community organizers are collaborating on projects that prioritize human experience over pure efficiency. The results are spaces that feel alive: places where people linger, connect, and create.</p>
<blockquote className="border-l-4 border-stone-300 dark:border-stone-700 pl-6 my-8 italic text-stone-600 dark:text-stone-400 font-serif text-xl leading-relaxed">The best design does not impose itself. It listens to the rhythms of a place and amplifies what was already there.</blockquote>
<h2 className="font-serif text-2xl font-bold text-stone-800 dark:text-stone-100 mt-10 mb-4 animate-fade-up">The Art of Adaptive Reuse</h2>
<p className="text-stone-700 dark:text-stone-300 leading-relaxed">Rather than demolishing and rebuilding, a growing number of architects are finding beauty in transformation. Old factories become cultural centers. Abandoned churches host maker spaces. The past is not erased but reinterpreted.</p>
<div className="my-10 bg-stone-100 dark:bg-stone-900 rounded-xl p-8 border-l-4 border-stone-400 dark:border-stone-600">
<p className="font-serif text-lg italic text-stone-700 dark:text-stone-300 leading-relaxed">We are not building for eternity. We are building for the next conversation between a space and its people.</p>
</div>
<h2 className="font-serif text-2xl font-bold text-stone-800 dark:text-stone-100 mt-10 mb-4 animate-fade-up">Looking Forward</h2>
<p className="text-stone-700 dark:text-stone-300 leading-relaxed">The implications extend beyond architecture. When we redesign our physical environments, we reshape social patterns, economic opportunities, and even our sense of identity.</p>
<p className="text-stone-700 dark:text-stone-300 leading-relaxed mt-4">What emerges is not utopia but something more useful: a framework for cities that can adapt, communities that can breathe, and lives that have room for the unexpected.</p>
</div>
{/* Actions */}
<div className="mt-8 flex items-center gap-3 py-6 border-y border-stone-200 dark:border-stone-800">
<Button variant="ghost" size="sm" className="text-stone-500 hover:text-rose-500" onClick={() => toast.success("Liked!")}><Heart className="h-4 w-4 mr-1" /> 124</Button>
<Button variant="ghost" size="sm" className="text-stone-500" onClick={() => toast.success("Link copied!")}><Share2 className="h-4 w-4 mr-1" /> Share</Button>
<Button variant="ghost" size="sm" className="text-stone-500" onClick={() => toast.success("Saved!")}><Bookmark className="h-4 w-4 mr-1" /> Save</Button>
</div>
{/* Tags */}
<div className="mt-6 flex flex-wrap gap-2">
{tags.map((t) => (
<Badge key={t} variant="outline" className="rounded-full border-stone-300 dark:border-stone-700 text-stone-500 dark:text-stone-400 text-xs">{t}</Badge>
))}
</div>
{/* Author Bio */}
<Card className="mt-10 bg-stone-100 dark:bg-stone-900 border-stone-200 dark:border-stone-800">
<CardContent className="p-6 flex gap-5">
<Avatar className="h-16 w-16"><AvatarFallback className="bg-stone-300 dark:bg-stone-700 text-stone-600 dark:text-stone-300 font-serif text-lg">EV</AvatarFallback></Avatar>
<div>
<p className="font-serif font-bold text-stone-800 dark:text-stone-100">Elena Vasquez</p>
<p className="text-sm text-stone-500 dark:text-stone-400 mt-1">Elena Vasquez covers culture, design, and the built environment. She has written for The Atlantic, Dwell, and Metropolis. Based in Barcelona.</p>
<Button variant="outline" size="sm" className="mt-3 border-stone-300 dark:border-stone-700 text-stone-600 dark:text-stone-300 text-xs">Follow</Button>
</div>
</CardContent>
</Card>
</article>
{/* Related Stories */}
<section className="mx-auto max-w-6xl px-6 mb-16">
<h2 className="font-serif text-2xl font-bold text-stone-800 dark:text-stone-100 mb-8 animate-fade-up">Related Stories</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 stagger">
{relatedStories.map((s, i) => (
<Link key={i} to="/article" className="group">
<Card className="overflow-hidden border-stone-200 dark:border-stone-800 bg-white dark:bg-stone-900 hover:-translate-y-1 hover:shadow-lg transition-all duration-300">
<div className="aspect-[3/2] overflow-hidden">
<img src={s.img} alt={s.title} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" />
</div>
<CardContent className="p-5">
<p className="text-xs uppercase tracking-[0.15em] text-stone-400 dark:text-stone-500 mb-2">{s.category}</p>
<h3 className="font-serif text-lg font-semibold text-stone-800 dark:text-stone-100 leading-snug">{s.title}</h3>
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">{s.author}</p>
</CardContent>
</Card>
</Link>
))}
</div>
</section>
{/* Comments */}
<section className="mx-auto max-w-3xl px-6 mb-20">
<h2 className="font-serif text-2xl font-bold text-stone-800 dark:text-stone-100 mb-6 flex items-center gap-2 animate-fade-up">
<MessageCircle className="h-5 w-5" /> Comments ({commentList.length})
</h2>
<div className="mb-6">
<Textarea value={newComment} onChange={(e) => setNewComment(e.target.value)} placeholder="Share your thoughts..." className="bg-white dark:bg-stone-900 border-stone-200 dark:border-stone-800 mb-3" />
<Button disabled={posting} className="bg-stone-800 hover:bg-stone-700 text-stone-50 dark:bg-stone-200 dark:text-stone-900 dark:hover:bg-stone-300" onClick={handlePostComment}>Post Comment</Button>
</div>
<div className="space-y-6 stagger">
{commentList.map((c, i) => (
<div key={i} className="flex gap-4">
<Avatar className="h-9 w-9"><AvatarFallback className="text-xs bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-300">{c.initials}</AvatarFallback></Avatar>
<div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-stone-800 dark:text-stone-100">{c.name}</span>
<span className="text-xs text-stone-400 dark:text-stone-500">{c.date}</span>
</div>
<p className="mt-1 text-sm text-stone-600 dark:text-stone-400 leading-relaxed">{c.text}</p>
</div>
</div>
))}
</div>
</section>
</main>
<Footer />
</div>
);
}