- 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.
181 lines
5.0 KiB
TypeScript
181 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useCallback, useEffect } from "react";
|
|
import {
|
|
Link2,
|
|
Clock,
|
|
Calendar,
|
|
User,
|
|
Share2,
|
|
Check,
|
|
} from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export interface BlogPostMetaProps {
|
|
/** Path (e.g. /blog/slug). Full URL is built on client for share/copy. */
|
|
url: string;
|
|
title: string;
|
|
readingTimeMinutes: number;
|
|
/** Shamsi publish date (e.g. "۱۴۰۳/۰۸/۱۵") */
|
|
date?: string | null;
|
|
/** ISO date for <time dateTime> */
|
|
dateTime?: string | null;
|
|
modifiedDate?: string | null;
|
|
authorName?: string | null;
|
|
className?: string;
|
|
}
|
|
|
|
export function BlogPostMeta({
|
|
url,
|
|
title,
|
|
readingTimeMinutes,
|
|
date,
|
|
dateTime,
|
|
modifiedDate,
|
|
authorName,
|
|
className,
|
|
}: BlogPostMetaProps) {
|
|
const [copied, setCopied] = useState(false);
|
|
const [shared, setShared] = useState(false);
|
|
const [fullUrl, setFullUrl] = useState(url);
|
|
const [canNativeShare, setCanNativeShare] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (url.startsWith("/")) {
|
|
setFullUrl(window.location.origin + url);
|
|
} else {
|
|
setFullUrl(url);
|
|
}
|
|
setCanNativeShare(Boolean(typeof navigator !== "undefined" && navigator.share));
|
|
}, [url]);
|
|
|
|
const copyLink = useCallback(async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(fullUrl);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch {
|
|
setCopied(false);
|
|
}
|
|
}, [fullUrl]);
|
|
|
|
const handleShare = useCallback(async () => {
|
|
if (typeof navigator !== "undefined" && navigator.share) {
|
|
try {
|
|
await navigator.share({
|
|
title,
|
|
url: fullUrl,
|
|
text: title,
|
|
});
|
|
setShared(true);
|
|
setTimeout(() => setShared(false), 2000);
|
|
} catch (err) {
|
|
if ((err as Error).name !== "AbortError") copyLink();
|
|
}
|
|
} else {
|
|
copyLink();
|
|
}
|
|
}, [title, fullUrl, copyLink]);
|
|
|
|
return (
|
|
<TooltipProvider delayDuration={300}>
|
|
<div
|
|
className={cn(
|
|
"flex flex-wrap items-center gap-x-5 gap-y-2 text-sm text-muted-foreground",
|
|
className
|
|
)}
|
|
>
|
|
{/* Reading time */}
|
|
<span className="inline-flex items-center gap-1.5">
|
|
<Clock className="size-4 shrink-0" aria-hidden />
|
|
<span>
|
|
{readingTimeMinutes === 1
|
|
? "۱ دقیقه مطالعه"
|
|
: `${readingTimeMinutes.toLocaleString("fa-IR")} دقیقه مطالعه`}
|
|
</span>
|
|
</span>
|
|
|
|
{/* Publish date */}
|
|
{date && (
|
|
<time
|
|
className="inline-flex items-center gap-1.5"
|
|
dateTime={dateTime ?? undefined}
|
|
>
|
|
<Calendar className="size-4 shrink-0" aria-hidden />
|
|
<span>انتشار: {date}</span>
|
|
</time>
|
|
)}
|
|
|
|
{/* Last updated */}
|
|
{modifiedDate && (
|
|
<span className="inline-flex items-center gap-1.5">
|
|
<Calendar className="size-4 shrink-0" aria-hidden />
|
|
<span>آخرین بروزرسانی: {modifiedDate}</span>
|
|
</span>
|
|
)}
|
|
|
|
{/* Author */}
|
|
{authorName && (
|
|
<span className="inline-flex items-center gap-1.5">
|
|
<User className="size-4 shrink-0" aria-hidden />
|
|
<span>{authorName}</span>
|
|
</span>
|
|
)}
|
|
|
|
{/* Divider + actions */}
|
|
<span className="mr-auto inline-flex items-center gap-1">
|
|
<span
|
|
className="hidden sm:inline h-4 w-px bg-border ml-2"
|
|
aria-hidden
|
|
/>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={copyLink}
|
|
aria-label="کپی لینک"
|
|
>
|
|
{copied ? (
|
|
<Check className="size-4 text-green-600" />
|
|
) : (
|
|
<Link2 className="size-4" />
|
|
)}
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">
|
|
{copied ? "کپی شد" : "کپی لینک"}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={handleShare}
|
|
aria-label="اشتراکگذاری"
|
|
>
|
|
{shared ? (
|
|
<Check className="size-4 text-green-600" />
|
|
) : (
|
|
<Share2 className="size-4" />
|
|
)}
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom">
|
|
{shared ? "اشتراک گذاشته شد" : canNativeShare ? "اشتراکگذاری" : "کپی لینک"}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</span>
|
|
</div>
|
|
</TooltipProvider>
|
|
);
|
|
}
|