Files
techzaa-frontend/src/pages/BlogArticle.tsx
T

413 lines
16 KiB
TypeScript

import PageTransition from "@/components/home/PageTransition";
import { Button } from "@/components/ui/button";
import { getRelatedPosts } from "@/data/blogData";
import { useBlogById } from "@/hooks/queires/useBlogs";
import { motion } from "framer-motion";
import {
ArrowLeft,
Calendar,
Clock,
Facebook,
Link2,
Linkedin,
Twitter,
} from "lucide-react";
import ReactMarkdown from "react-markdown";
import { Link, useNavigate, useParams } from "react-router-dom";
import remarkGfm from "remark-gfm";
import { toast } from "sonner";
export default function BlogArticle() {
const { id } = useParams<{ id: string }>();
const { data } = useBlogById(id);
const post = data?.data.data;
const navigate = useNavigate();
// const post = getPostBySlug(id || '');
if (!post) {
return (
<PageTransition>
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4">Article Not Found</h1>
<p className="text-muted-foreground mb-8">
The article you're looking for doesn't exist.
</p>
<Button onClick={() => navigate("/blog")}>Back to Blog</Button>
</div>
</div>
</PageTransition>
);
}
const relatedPosts = getRelatedPosts(post);
const shareUrl = window.location.href;
const handleShare = (platform: string) => {
const urls: Record<string, string> = {
twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(post.title)}`,
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`,
facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,
};
if (platform === "copy") {
navigator.clipboard.writeText(shareUrl);
toast.success("Link copied to clipboard!");
} else {
window.open(urls[platform], "_blank", "width=600,height=400");
}
};
return (
<PageTransition>
<div className="min-h-screen bg-background overflow-x-hidden">
{/* Hero */}
<section className="pt-32 pb-16 relative overflow-hidden">
<div className="absolute inset-0">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-primary/20 rounded-full blur-3xl" />
</div>
<div className="container mx-auto px-4 relative z-10 max-w-4xl">
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
>
<Link to="/blog">
<Button variant="ghost" className="mb-8 group">
<ArrowLeft className="w-4 h-4 mr-2 transition-transform group-hover:-translate-x-1" />
Back to Blog
</Button>
</Link>
</motion.div>
{/* Category */}
<motion.span
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="inline-block px-4 py-1.5 rounded-full bg-primary/20 text-primary text-sm font-medium mb-6"
>
{post.category}
</motion.span>
{/* Title */}
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-4xl md:text-5xl font-bold mb-6 leading-tight"
>
{post.title}
</motion.h1>
{/* Meta */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="flex flex-wrap items-center gap-6 text-muted-foreground mb-8"
>
<div className="flex items-center gap-3">
<img
src={post.author.avatar}
alt={post.author.name}
className="w-10 h-10 rounded-full object-cover border-2 border-primary/50"
/>
<div>
<p className="font-medium text-foreground">
{post.author.name}
</p>
<p className="text-sm">{post.author.role}</p>
</div>
</div>
<span className="flex items-center gap-2">
<Calendar className="w-4 h-4" />
{new Date(post.date).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})}
</span>
<span className="flex items-center gap-2">
<Clock className="w-4 h-4" />
{post.readTime}
</span>
</motion.div>
{/* Featured Image */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="rounded-2xl overflow-hidden mb-12"
>
<img
src={post.image}
alt={post.title}
className="w-full h-[400px] object-cover"
/>
</motion.div>
</div>
</section>
{/* Content */}
<section className="pb-16">
<div className="container mx-auto px-4 max-w-4xl">
<div className="grid lg:grid-cols-[1fr_200px] gap-12">
{/* Article Content */}
<motion.article
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="max-w-none prose-content"
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({ children }) => (
<h1 className="text-3xl font-bold text-foreground mt-8 mb-4">
{children}
</h1>
),
h2: ({ children }) => (
<h2 className="text-2xl font-bold text-foreground mt-8 mb-4">
{children}
</h2>
),
h3: ({ children }) => (
<h3 className="text-xl font-bold text-foreground mt-6 mb-3">
{children}
</h3>
),
h4: ({ children }) => (
<h4 className="text-lg font-bold text-foreground mt-4 mb-2">
{children}
</h4>
),
p: ({ children }) => (
<p className="text-muted-foreground leading-relaxed mb-4">
{children}
</p>
),
ul: ({ children }) => (
<ul className="list-disc list-inside text-muted-foreground space-y-2 mb-4 ml-4">
{children}
</ul>
),
ol: ({ children }) => (
<ol className="list-decimal list-inside text-muted-foreground space-y-2 mb-4 ml-4">
{children}
</ol>
),
li: ({ children }) => (
<li className="text-muted-foreground">{children}</li>
),
a: ({ href, children }) => (
<Link to={href} className="text-primary hover:underline">
{children}
</Link>
),
strong: ({ children }) => (
<strong className="font-bold text-foreground">
{children}
</strong>
),
em: ({ children }) => (
<em className="italic">{children}</em>
),
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-primary pl-4 italic text-muted-foreground my-4">
{children}
</blockquote>
),
code: ({ className, children }) => {
const isInline = !className;
if (isInline) {
return (
<code className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">
{children}
</code>
);
}
return (
<code className="block bg-muted border border-border rounded-lg p-4 overflow-x-auto text-sm font-mono my-4">
{children}
</code>
);
},
pre: ({ children }) => (
<pre className="bg-muted border border-border rounded-lg p-4 overflow-x-auto my-4">
{children}
</pre>
),
hr: () => <hr className="border-border my-8" />,
}}
>
{post.content}
</ReactMarkdown>
</motion.article>
{/* Sidebar */}
<aside className="hidden lg:block">
<div className="sticky top-32 space-y-8">
{/* Share */}
<div className="glass rounded-2xl p-6">
<h4 className="font-bold mb-4 text-sm uppercase tracking-wide text-muted-foreground">
Share
</h4>
<div className="flex flex-col gap-3">
<Button
variant="outline"
size="sm"
onClick={() => handleShare("twitter")}
className="justify-start gap-2 glass border-primary/30"
>
<Twitter className="w-4 h-4" /> Twitter
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleShare("linkedin")}
className="justify-start gap-2 glass border-primary/30"
>
<Linkedin className="w-4 h-4" /> LinkedIn
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleShare("facebook")}
className="justify-start gap-2 glass border-primary/30"
>
<Facebook className="w-4 h-4" /> Facebook
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleShare("copy")}
className="justify-start gap-2 glass border-primary/30"
>
<Link2 className="w-4 h-4" /> Copy Link
</Button>
</div>
</div>
{/* Tags */}
<div className="glass rounded-2xl p-6">
<h4 className="font-bold mb-4 text-sm uppercase tracking-wide text-muted-foreground">
Tags
</h4>
<div className="flex flex-wrap gap-2">
{post.tags.map((tag) => (
<span
key={tag}
className="px-3 py-1 rounded-full bg-muted text-muted-foreground text-xs"
>
{tag}
</span>
))}
</div>
</div>
</div>
</aside>
</div>
</div>
</section>
{/* Author Bio */}
<section className="py-16 bg-secondary/30">
<div className="container mx-auto px-4 max-w-4xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="glass rounded-3xl p-8 flex flex-col md:flex-row gap-6 items-center md:items-start"
>
<img
src={post.author.avatar}
alt={post.author.name}
className="w-24 h-24 rounded-full object-cover border-4 border-primary/50"
/>
<div className="text-center md:text-left">
<h3 className="text-2xl font-bold mb-2">{post.author.name}</h3>
<p className="text-primary font-medium mb-4">
{post.author.role}
</p>
<p className="text-muted-foreground mb-4">{post.author.bio}</p>
<div className="flex gap-3 justify-center md:justify-start">
{post.author.twitter && (
<a
href={`https://twitter.com/${post.author.twitter}`}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-full glass hover:neon-glow transition-all"
>
<Twitter className="w-5 h-5" />
</a>
)}
{post.author.linkedin && (
<a
href={`https://linkedin.com/in/${post.author.linkedin}`}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-full glass hover:neon-glow transition-all"
>
<Linkedin className="w-5 h-5" />
</a>
)}
</div>
</div>
</motion.div>
</div>
</section>
{/* Related Articles */}
{relatedPosts.length > 0 && (
<section className="py-24">
<div className="container mx-auto px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-12"
>
<h2 className="text-3xl font-bold">Related Articles</h2>
</motion.div>
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
{relatedPosts.map((relatedPost, index) => (
<motion.article
key={relatedPost.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="group glass rounded-2xl overflow-hidden hover:neon-glow transition-all duration-500"
>
<Link to={`/blog/${relatedPost.slug}`}>
<div className="relative h-40 overflow-hidden">
<img
src={relatedPost.image}
alt={relatedPost.title}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
/>
</div>
<div className="p-5">
<h3 className="font-bold mb-2 group-hover:text-primary transition-colors line-clamp-2">
{relatedPost.title}
</h3>
<p className="text-sm text-muted-foreground flex items-center gap-2">
<Clock className="w-3 h-3" /> {relatedPost.readTime}
</p>
</div>
</Link>
</motion.article>
))}
</div>
</div>
</section>
)}
</div>
</PageTransition>
);
}