init with dummy posts

This commit is contained in:
2025-03-18 15:07:45 -05:00
commit 5ec7b398d1
42 changed files with 9902 additions and 0 deletions
+163
View File
@@ -0,0 +1,163 @@
import React, { useState } from 'react';
interface CommentFormProps {
postId: string;
}
const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [comment, setComment] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Basic validation
if (!name.trim() || !email.trim() || !comment.trim()) {
setError('All fields are required');
return;
}
setIsSubmitting(true);
setError(null);
try {
// In a real implementation, you would send this data to your API
// For now, we'll just simulate a successful submission
await new Promise(resolve => setTimeout(resolve, 1000));
// Clear form and show success message
setName('');
setEmail('');
setComment('');
setIsSubmitted(true);
setIsSubmitting(false);
// Reset success message after 5 seconds
setTimeout(() => {
setIsSubmitted(false);
}, 5000);
} catch (err) {
setError('Something went wrong. Please try again later.');
setIsSubmitting(false);
}
};
return (
<div className="mt-12 pt-6 border-t border-primary-200">
<h3 className="text-2xl font-bold mb-6">Leave a Comment</h3>
{isSubmitted && (
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md mb-6">
Your comment has been submitted and is awaiting moderation. Thank you!
</div>
)}
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md mb-6">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label htmlFor="name" className="block text-primary-700 mb-1">
Name <span className="text-red-500">*</span>
</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-4 py-2 border border-primary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
required
/>
</div>
<div>
<label htmlFor="email" className="block text-primary-700 mb-1">
Email <span className="text-red-500">*</span>
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border border-primary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
required
/>
</div>
</div>
<div>
<label htmlFor="comment" className="block text-primary-700 mb-1">
Comment <span className="text-red-500">*</span>
</label>
<textarea
id="comment"
rows={6}
value={comment}
onChange={(e) => setComment(e.target.value)}
className="w-full px-4 py-2 border border-primary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
required
></textarea>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="save-info"
className="h-4 w-4 text-accent border-primary-300 rounded focus:ring-accent"
/>
<label htmlFor="save-info" className="ml-2 block text-sm text-primary-600">
Save my name and email for the next time I comment
</label>
</div>
<button
type="submit"
className="button"
disabled={isSubmitting}
>
{isSubmitting ? 'Submitting...' : 'Post Comment'}
</button>
</form>
{/* Sample comments - in a real implementation, these would be fetched from an API */}
<div className="mt-12">
<h3 className="text-xl font-bold mb-6">Comments (2)</h3>
<div className="space-y-6">
<div className="bg-white p-6 rounded-md shadow-sm border border-primary-200">
<div className="flex justify-between items-start mb-4">
<div>
<h4 className="font-bold">John Calvin</h4>
<p className="text-sm text-primary-500">March 10, 2025</p>
</div>
</div>
<p className="text-primary-700">
A profound meditation on the doctrine of grace. I particularly appreciate your emphasis on how this truth transforms our daily lives, not just our theological understanding.
</p>
</div>
<div className="bg-white p-6 rounded-md shadow-sm border border-primary-200">
<div className="flex justify-between items-start mb-4">
<div>
<h4 className="font-bold">Martin Luther</h4>
<p className="text-sm text-primary-500">March 5, 2025</p>
</div>
</div>
<p className="text-primary-700">
This post eloquently articulates what I've been trying to explain to my congregation. The way you connected Scripture with practical application was masterful. I'll be sharing this widely.
</p>
</div>
</div>
</div>
</div>
);
};
export default CommentSection;
+23
View File
@@ -0,0 +1,23 @@
import React from 'react';
import { format, parseISO } from 'date-fns';
interface DateFormatterProps {
dateString: string;
formatString?: string;
className?: string;
}
const DateFormatter: React.FC<DateFormatterProps> = ({
dateString,
formatString = 'MMMM d, yyyy',
className = ''
}) => {
const date = parseISO(dateString);
return (
<time dateTime={dateString} className={className}>
{format(date, formatString)}
</time>
);
};
export default DateFormatter;
+70
View File
@@ -0,0 +1,70 @@
import React from 'react';
import Link from 'next/link';
const Footer: React.FC = () => {
const currentYear = new Date().getFullYear();
return (
<footer className="bg-primary-800 text-white mt-16">
<div className="container mx-auto px-4 py-12">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h3 className="text-xl font-bold mb-4 text-white">Confessions of Grace</h3>
<p className="text-primary-300">
A blog dedicated to exploring the doctrines of grace and Reformed theology.
</p>
</div>
<div>
<h3 className="text-xl font-bold mb-4 text-white">Navigation</h3>
<ul className="space-y-2">
<li>
<Link href="/" className="text-primary-300 hover:text-white">
Home
</Link>
</li>
<li>
<Link href="/about" className="text-primary-300 hover:text-white">
About
</Link>
</li>
<li>
<Link href="/archive" className="text-primary-300 hover:text-white">
Archive
</Link>
</li>
<li>
<Link href="/resources" className="text-primary-300 hover:text-white">
Resources
</Link>
</li>
</ul>
</div>
<div>
<h3 className="text-xl font-bold mb-4 text-white">Subscribe</h3>
<p className="text-primary-300 mb-4">
Stay updated with the latest posts.
</p>
<div className="flex">
<input
type="email"
placeholder="Your email"
className="px-4 py-2 w-full rounded-l-md bg-primary-700 border border-primary-600 text-white placeholder-primary-400"
/>
<button className="bg-accent hover:bg-accent-dark px-4 py-2 rounded-r-md">
Subscribe
</button>
</div>
</div>
</div>
<div className="border-t border-primary-700 mt-8 pt-8 text-center text-primary-400">
<p>&copy; {currentYear} Confessions of Grace. All rights reserved.</p>
<p className="mt-2 text-sm">
"For by grace you have been saved through faith. And this is not your own doing; it is the gift of God." Ephesians 2:8
</p>
</div>
</div>
</footer>
);
};
export default Footer;
+35
View File
@@ -0,0 +1,35 @@
import React from 'react';
import Link from 'next/link';
const Header: React.FC = () => {
return (
<header className="bg-white shadow-sm border-b border-primary-200">
<div className="container mx-auto px-4 py-6">
<div className="flex flex-col md:flex-row justify-between items-center">
<div className="mb-4 md:mb-0">
<Link href="/" className="no-underline">
<h1 className="text-3xl font-bold text-primary-900 mb-0">Confessions of Grace</h1>
</Link>
<p className="text-primary-500 italic text-sm">Exploring Reformed Theology</p>
</div>
<nav className="flex space-x-6">
<Link href="/" className="text-primary-700 hover:text-accent-dark">
Home
</Link>
<Link href="/about" className="text-primary-700 hover:text-accent-dark">
About
</Link>
<Link href="/archive" className="text-primary-700 hover:text-accent-dark">
Archive
</Link>
<Link href="/resources" className="text-primary-700 hover:text-accent-dark">
Resources
</Link>
</nav>
</div>
</div>
</header>
);
};
export default Header;
+36
View File
@@ -0,0 +1,36 @@
import React, { ReactNode } from 'react';
import Head from 'next/head';
import Header from './Header';
import Footer from './Footer';
interface LayoutProps {
children: ReactNode;
title?: string;
description?: string;
}
const Layout: React.FC<LayoutProps> = ({
children,
title = 'Confessions of Grace',
description = 'A blog dedicated to exploring the doctrines of grace and Reformed theology.'
}) => {
return (
<>
<Head>
<title>{title}</title>
<meta name="description" content={description} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow container mx-auto px-4 py-8">
{children}
</main>
<Footer />
</div>
</>
);
};
export default Layout;
+50
View File
@@ -0,0 +1,50 @@
import React from 'react';
import Head from 'next/head';
interface MetaProps {
title?: string;
description?: string;
keywords?: string;
image?: string;
url?: string;
type?: string;
}
const Meta: React.FC<MetaProps> = ({
title = 'Confessions of Grace',
description = 'A blog dedicated to exploring the doctrines of grace and Reformed theology.',
keywords = 'reformed theology, doctrines of grace, christianity, calvinism, theology',
image = '/images/og-default.jpg',
url = 'https://confessionsofgrace.com',
type = 'website'
}) => {
const siteTitle = title === 'Confessions of Grace'
? 'Confessions of Grace | Reformed Theology Blog'
: `${title} | Confessions of Grace`;
return (
<Head>
<title>{siteTitle}</title>
<meta name="description" content={description} />
<meta name="keywords" content={keywords} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
{/* Open Graph / Facebook */}
<meta property="og:type" content={type} />
<meta property="og:url" content={url} />
<meta property="og:title" content={siteTitle} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
{/* Twitter */}
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={url} />
<meta property="twitter:title" content={siteTitle} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={image} />
</Head>
);
};
export default Meta;
+113
View File
@@ -0,0 +1,113 @@
import React from 'react';
import Link from 'next/link';
interface PaginationProps {
currentPage: number;
totalPages: number;
basePath: string;
}
const Pagination: React.FC<PaginationProps> = ({
currentPage,
totalPages,
basePath
}) => {
// Generate page numbers to display
const getPageNumbers = () => {
// Always show first and last page
// Show 2 pages before and after current page
const pageNumbers = new Set<number>();
// Add current page
pageNumbers.add(currentPage);
// Add adjacent pages
for (let i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + 2); i++) {
pageNumbers.add(i);
}
// Always add first and last page
pageNumbers.add(1);
pageNumbers.add(totalPages);
// Convert to array and sort
return Array.from(pageNumbers).sort((a, b) => a - b);
};
const pageNumbers = getPageNumbers();
// Generate link for a page number
const getPageLink = (pageNum: number) => {
if (pageNum === 1) {
return basePath;
}
return `${basePath}/page/${pageNum}`;
};
return (
<nav className="flex justify-center my-8">
<ul className="flex items-center space-x-1">
{/* Previous page button */}
{currentPage > 1 && (
<li>
<Link
href={getPageLink(currentPage - 1)}
className="flex items-center justify-center w-10 h-10 rounded-md border border-primary-200 bg-white text-primary-700 hover:bg-primary-50"
aria-label="Previous page"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Link>
</li>
)}
{/* Page numbers */}
{pageNumbers.map((pageNum, index) => {
// Add ellipsis if there's a gap
const showEllipsisBefore = index > 0 && pageNum > pageNumbers[index - 1] + 1;
return (
<React.Fragment key={pageNum}>
{showEllipsisBefore && (
<li className="flex items-center justify-center w-10 h-10 text-primary-500">
...
</li>
)}
<li>
<Link
href={getPageLink(pageNum)}
className={`flex items-center justify-center w-10 h-10 rounded-md border ${
pageNum === currentPage
? 'bg-accent text-white border-accent font-bold'
: 'bg-white text-primary-700 border-primary-200 hover:bg-primary-50'
}`}
aria-current={pageNum === currentPage ? 'page' : undefined}
>
{pageNum}
</Link>
</li>
</React.Fragment>
);
})}
{/* Next page button */}
{currentPage < totalPages && (
<li>
<Link
href={getPageLink(currentPage + 1)}
className="flex items-center justify-center w-10 h-10 rounded-md border border-primary-200 bg-white text-primary-700 hover:bg-primary-50"
aria-label="Next page"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link>
</li>
)}
</ul>
</nav>
);
};
export default Pagination;
+69
View File
@@ -0,0 +1,69 @@
import React from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { format } from 'date-fns';
import { PostMetadata } from '@/types';
interface PostCardProps {
post: PostMetadata;
}
const PostCard: React.FC<PostCardProps> = ({ post }) => {
return (
<article className="card hover:shadow-md transition-shadow duration-200">
{post.coverImage && (
<div className="mb-4 relative h-48 w-full overflow-hidden rounded-md">
<Image
src={post.coverImage}
alt={post.title}
fill
className="object-cover"
/>
</div>
)}
<h2 className="text-xl font-bold mb-2">
<Link href={`/posts/${post.id}`} className="hover:text-accent-dark">
{post.title}
</Link>
</h2>
<div className="flex items-center mb-2 text-sm text-primary-500">
<span>{post.author}</span>
<span className="mx-2"></span>
<time dateTime={post.date}>
{format(new Date(post.date), 'MMMM d, yyyy')}
</time>
</div>
<p className="text-primary-600 mb-4">{post.excerpt}</p>
<div className="flex flex-wrap gap-2 mb-4">
{post.tags.map(tag => (
<Link
key={tag}
href={`/tags/${tag}`}
className="text-xs bg-primary-100 text-primary-600 px-2 py-1 rounded-md hover:bg-primary-200"
>
{tag}
</Link>
))}
</div>
<Link href={`/posts/${post.id}`} className="inline-flex items-center text-accent-dark hover:text-accent">
Read more
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4 ml-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M14 5l7 7m0 0l-7 7m7-7H3"
/>
</svg>
</Link>
</article>
);
};
export default PostCard;
+48
View File
@@ -0,0 +1,48 @@
import React, { useState } from 'react';
import { useRouter } from 'next/router';
const SearchBar: React.FC = () => {
const [searchQuery, setSearchQuery] = useState('');
const router = useRouter();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (searchQuery.trim()) {
router.push(`/search?q=${encodeURIComponent(searchQuery.trim())}`);
}
};
return (
<form onSubmit={handleSubmit} className="relative">
<input
type="text"
placeholder="Search posts..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full px-4 py-2 pl-10 pr-4 rounded-md border border-primary-200 focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
/>
<button
type="submit"
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-primary-400"
aria-label="Search"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</button>
</form>
);
};
export default SearchBar;
+118
View File
@@ -0,0 +1,118 @@
import React from 'react';
interface ShareButtonsProps {
url: string;
title: string;
description?: string;
}
const ShareButtons: React.FC<ShareButtonsProps> = ({ url, title, description = '' }) => {
// Encode parameters for sharing URLs
const encodedUrl = encodeURIComponent(url);
const encodedTitle = encodeURIComponent(title);
const encodedDescription = encodeURIComponent(description);
// Generate sharing URLs
const facebookUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`;
const twitterUrl = `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`;
const linkedinUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`;
const emailUrl = `mailto:?subject=${encodedTitle}&body=${encodedDescription}%0A%0A${encodedUrl}`;
return (
<div className="flex items-center space-x-3">
<span className="text-primary-600 font-medium">Share:</span>
{/* Facebook */}
<a
href={facebookUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary-500 hover:text-accent-dark transition-colors duration-200"
aria-label="Share on Facebook"
title="Share on Facebook"
>
<svg
className="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z"
clipRule="evenodd"
/>
</svg>
</a>
{/* Twitter */}
<a
href={twitterUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary-500 hover:text-accent-dark transition-colors duration-200"
aria-label="Share on Twitter"
title="Share on Twitter"
>
<svg
className="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84"
/>
</svg>
</a>
{/* LinkedIn */}
<a
href={linkedinUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary-500 hover:text-accent-dark transition-colors duration-200"
aria-label="Share on LinkedIn"
title="Share on LinkedIn"
>
<svg
className="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"
clipRule="evenodd"
/>
</svg>
</a>
{/* Email */}
<a
href={emailUrl}
className="text-primary-500 hover:text-accent-dark transition-colors duration-200"
aria-label="Share via Email"
title="Share via Email"
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
</a>
</div>
);
};
export default ShareButtons;
+96
View File
@@ -0,0 +1,96 @@
import React from 'react';
import Link from 'next/link';
import TagList from './TagList';
interface SidebarProps {
recentPosts: Array<{
id: string;
title: string;
date: string;
}>;
tags?: Array<{
tag: string;
count: number;
}>;
}
const Sidebar: React.FC<SidebarProps> = ({ recentPosts, tags = [] }) => {
return (
<aside className="space-y-8">
<div className="bg-white rounded-md shadow-sm p-6 border border-primary-200">
<h2 className="text-xl font-bold mb-4 border-b border-primary-200 pb-2">About</h2>
<p className="text-primary-700 mb-4">
Confessions of Grace explores the riches of Reformed theology and the doctrines of grace.
</p>
<Link
href="/about"
className="text-accent-dark hover:text-accent inline-flex items-center"
>
Learn more
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4 ml-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</Link>
</div>
<div className="bg-white rounded-md shadow-sm p-6 border border-primary-200">
<h2 className="text-xl font-bold mb-4 border-b border-primary-200 pb-2">Recent Posts</h2>
<ul className="space-y-3">
{recentPosts.map(post => (
<li key={post.id}>
<Link
href={`/posts/${post.id}`}
className="hover:text-accent-dark block"
>
<h3 className="font-medium">{post.title}</h3>
<p className="text-sm text-primary-500">
{new Date(post.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}
</p>
</Link>
</li>
))}
</ul>
</div>
<div className="bg-white rounded-md shadow-sm p-6 border border-primary-200">
<h2 className="text-xl font-bold mb-4 border-b border-primary-200 pb-2">Popular Tags</h2>
<TagList limit={15} tags={tags} />
</div>
<div className="bg-white rounded-md shadow-sm p-6 border border-primary-200">
<h2 className="text-xl font-bold mb-4 border-b border-primary-200 pb-2">Subscribe</h2>
<p className="text-primary-700 mb-4">
Stay updated with the latest posts and resources.
</p>
<form className="space-y-3">
<div>
<input
type="email"
placeholder="Your email"
className="w-full px-3 py-2 border border-primary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
required
/>
</div>
<button
type="submit"
className="button w-full"
>
Subscribe
</button>
</form>
</div>
</aside>
);
};
export default Sidebar;
+38
View File
@@ -0,0 +1,38 @@
import React from 'react';
import Link from 'next/link';
interface TagListProps {
className?: string;
limit?: number;
tags?: Array<{
tag: string;
count: number;
}>;
}
const TagList: React.FC<TagListProps> = ({ className = '', limit, tags = [] }) => {
// Sort tags by count (descending)
const sortedTags = [...tags].sort((a, b) => b.count - a.count);
// Apply limit if provided
const tagsToShow = limit ? sortedTags.slice(0, limit) : sortedTags;
return (
<div className={`flex flex-wrap gap-2 ${className}`}>
{tagsToShow.map(({ tag, count }) => (
<Link
key={tag}
href={`/tags/${tag}`}
className="bg-primary-100 text-primary-700 px-3 py-1 rounded-md hover:bg-primary-200 text-sm transition-colors duration-200 flex items-center"
>
{tag}
<span className="ml-1 text-xs bg-primary-200 rounded-full px-2 py-0.5">
{count}
</span>
</Link>
))}
</div>
);
};
export default TagList;