- Added image quality settings and device sizes in next.config.ts for better image optimization. - Updated package dependencies, including cheerio and baseline-browser-mapping. - Improved global styles for blog content images to ensure responsiveness. - Refactored button components for consistent sizing and styling across the application. - Enhanced blog post card layout with new variants and improved author name formatting. - Adjusted hero section and content layout for better visual appeal and user experience.
173 lines
6.7 KiB
TypeScript
173 lines
6.7 KiB
TypeScript
import Link from "next/link";
|
|
import Image from "next/image";
|
|
import { ImageIcon, User } from "lucide-react";
|
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
|
import { getFeaturedImageUrl, type WpPost } from "@/lib/wp-api";
|
|
import { formatDateShamsi } from "@/lib/format-date";
|
|
import { formatAuthorName } from "@/lib/blog-utils";
|
|
|
|
function stripHtml(html: string): string {
|
|
return html.replace(/<[^>]*>/g, "").trim();
|
|
}
|
|
|
|
type BlogPostCardVariant = "default" | "tiny" | "related";
|
|
|
|
export function BlogPostCard({
|
|
post,
|
|
variant = "default",
|
|
}: {
|
|
post: WpPost;
|
|
variant?: BlogPostCardVariant;
|
|
}) {
|
|
const excerpt = stripHtml(post.excerpt.rendered) || stripHtml(post.content.rendered).slice(0, 160);
|
|
const modifiedDate = post.modified ? formatDateShamsi(post.modified) : null;
|
|
const authorName = formatAuthorName(post._embedded?.author?.[0]?.name);
|
|
const thumbnailUrl = getFeaturedImageUrl(post, "full");
|
|
|
|
if (variant === "related") {
|
|
return (
|
|
<Link href={`/blog/${encodeURIComponent(post.slug)}`} className="block h-full min-w-0">
|
|
<Card className="h-full overflow-hidden transition-all rounded-md duration-300 hover:shadow-md flex flex-col p-0 gap-0">
|
|
<CardContent className="p-3 sm:p-4 grid grid-cols-3 gap-3 sm:gap-4 min-w-0">
|
|
{thumbnailUrl ? (
|
|
<div className="relative rounded-md aspect-[4/3] w-full overflow-hidden bg-muted shrink-0 col-span-1 min-w-0">
|
|
<Image
|
|
src={thumbnailUrl}
|
|
alt=""
|
|
fill
|
|
className="object-cover"
|
|
quality={100}
|
|
sizes="(max-width: 640px) 33vw, (max-width: 1024px) 20vw, 200px"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div
|
|
className="relative rounded-md flex aspect-[4/3] w-full items-center justify-center overflow-hidden bg-muted shrink-0 col-span-1"
|
|
aria-hidden
|
|
>
|
|
<ImageIcon className="size-10 text-muted-foreground/50" />
|
|
</div>
|
|
)}
|
|
<div className="col-span-2 min-w-0 flex flex-col justify-center">
|
|
<h4 className="text-base font-semibold leading-snug line-clamp-3">
|
|
{post.title.rendered}
|
|
</h4>
|
|
<div className="flex flex-wrap w-full items-center gap-x-4 gap-y-1 text-sm text-muted-foreground bg-muted inline-flex p-1.5 mt-3 rounded-sm">
|
|
{authorName && (
|
|
<span className="inline-flex flex-1 items-center gap-1.5 min-w-0">
|
|
<User className="size-4 shrink-0" aria-hidden />
|
|
<span className="truncate">{authorName}</span>
|
|
</span>
|
|
)}
|
|
{modifiedDate && (
|
|
<time dateTime={post.modified} className="shrink-0">
|
|
{modifiedDate}
|
|
</time>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
if (variant === "tiny") {
|
|
return (
|
|
<Link href={`/blog/${encodeURIComponent(post.slug)}`} className="block h-full">
|
|
<Card className="h-full overflow-hidden transition-all rounded-md duration-300 hover:shadow-md flex flex-col p-2">
|
|
<CardContent className="p-2 grid grid-cols-3 gap-4">
|
|
{thumbnailUrl ? (
|
|
<div className="relative rounded-md aspect-[4/3] w-full overflow-hidden bg-muted shrink-0 col-span-1">
|
|
<Image
|
|
src={thumbnailUrl}
|
|
alt=""
|
|
fill
|
|
className="object-cover"
|
|
quality={100}
|
|
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div
|
|
className="relative rounded-md flex aspect-[4/3] w-full items-center justify-center overflow-hidden bg-muted shrink-0"
|
|
aria-hidden
|
|
>
|
|
<ImageIcon className="size-8 text-muted-foreground/50" />
|
|
</div>
|
|
)}
|
|
<div className="col-span-2">
|
|
<h4 className="text-sm font-semibold leading-snug line-clamp-2">
|
|
{post.title.rendered}
|
|
</h4>
|
|
<div className="flex flex-wrap w-full items-center gap-x-4 gap-y-1 text-xs text-muted-foreground bg-muted inline-flex p-1 mt-2 rounded-sm">
|
|
{authorName && (
|
|
<span className="inline-flex flex-1 items-center gap-1.5">
|
|
<User className="size-3.5 shrink-0" aria-hidden />
|
|
{authorName}
|
|
</span>
|
|
)}
|
|
{modifiedDate && (
|
|
<time dateTime={post.modified}>
|
|
{modifiedDate}
|
|
</time>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Link href={`/blog/${encodeURIComponent(post.slug)}`} className="block h-full">
|
|
<Card className="h-full overflow-hidden transition-all duration-300 hover:-translate-y-1 hover:shadow-lg pt-0 gap-1">
|
|
{thumbnailUrl ? (
|
|
<div className="relative aspect-video w-full overflow-hidden bg-muted">
|
|
<Image
|
|
src={thumbnailUrl}
|
|
alt=""
|
|
fill
|
|
className="object-cover"
|
|
quality={100}
|
|
priority
|
|
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div
|
|
className="relative flex aspect-video w-full items-center justify-center overflow-hidden bg-muted"
|
|
aria-hidden
|
|
>
|
|
<ImageIcon className="size-12 text-muted-foreground/50" />
|
|
</div>
|
|
)}
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-base font-bold leading-tight line-clamp-2 my-2">
|
|
{post.title.rendered}
|
|
</CardTitle>
|
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground bg-muted inline-flex p-1 rounded-sm">
|
|
{authorName && (
|
|
<span className="inline-flex flex-1 items-center gap-1.5">
|
|
<User className="size-3.5 shrink-0" aria-hidden />
|
|
{authorName}
|
|
</span>
|
|
)}
|
|
{modifiedDate && (
|
|
<time dateTime={post.modified}>
|
|
{modifiedDate}
|
|
</time>
|
|
)}
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="pt-0">
|
|
<p className="text-muted-foreground text-sm leading-relaxed line-clamp-3">
|
|
{excerpt}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
}
|