140 lines
4.1 KiB
TypeScript
140 lines
4.1 KiB
TypeScript
import { EyeIcon, HeartIcon, PencilIcon, Trash2Icon } from "lucide-react"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card } from "@/components/ui/card"
|
|
import { StatusBadge } from "@/components/hexa/status-badge"
|
|
|
|
interface PostCardAuthor {
|
|
name: string
|
|
role?: string
|
|
avatarUrl?: string
|
|
}
|
|
|
|
interface PostCardProps {
|
|
imageUrl?: string
|
|
title: string
|
|
description: string
|
|
tag?: string
|
|
author: PostCardAuthor
|
|
published: boolean
|
|
likes: number
|
|
createdAt: string
|
|
liked?: boolean
|
|
onToggleLike?: () => void
|
|
onView?: () => void
|
|
onEdit?: () => void
|
|
onDelete?: () => void
|
|
className?: string
|
|
}
|
|
|
|
function PostCard({
|
|
imageUrl,
|
|
title,
|
|
description,
|
|
tag,
|
|
author,
|
|
published,
|
|
likes,
|
|
createdAt,
|
|
liked,
|
|
onToggleLike,
|
|
onView,
|
|
onEdit,
|
|
onDelete,
|
|
className,
|
|
}: PostCardProps) {
|
|
return (
|
|
<Card className={cn("gap-0 overflow-hidden p-0", className)}>
|
|
{imageUrl && (
|
|
<div className="aspect-video w-full overflow-hidden bg-muted">
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img src={imageUrl} alt="" className="size-full object-cover" />
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex flex-col gap-3 p-4">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<h3 className="font-semibold">{title}</h3>
|
|
<StatusBadge status={published ? "success" : "neutral"}>
|
|
{published ? "Published" : "Draft"}
|
|
</StatusBadge>
|
|
</div>
|
|
|
|
<p className="line-clamp-2 text-sm text-muted-foreground">{description}</p>
|
|
|
|
{tag && (
|
|
<Badge variant="secondary" className="w-fit">
|
|
{tag}
|
|
</Badge>
|
|
)}
|
|
|
|
<div className="flex items-center justify-between gap-2 border-t pt-3">
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
<Avatar size="sm">
|
|
<AvatarImage src={author.avatarUrl} alt={author.name} />
|
|
<AvatarFallback>{author.name.slice(0, 1)}</AvatarFallback>
|
|
</Avatar>
|
|
<div className="min-w-0 leading-tight">
|
|
<p className="truncate text-sm font-medium">{author.name}</p>
|
|
{author.role && (
|
|
<p className="truncate text-xs text-muted-foreground">{author.role}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex shrink-0 items-center gap-3 text-xs text-muted-foreground">
|
|
{onToggleLike ? (
|
|
<button
|
|
type="button"
|
|
aria-label={liked ? "Unlike post" : "Like post"}
|
|
className="flex items-center gap-1"
|
|
onClick={onToggleLike}
|
|
>
|
|
<HeartIcon
|
|
className={cn(
|
|
"size-3.5",
|
|
liked ? "fill-rose-500 text-rose-500" : "text-muted-foreground"
|
|
)}
|
|
/>
|
|
{likes}
|
|
</button>
|
|
) : (
|
|
<span className="flex items-center gap-1">
|
|
<HeartIcon className="size-3.5 fill-rose-500 text-rose-500" />
|
|
{likes}
|
|
</span>
|
|
)}
|
|
<span>{createdAt}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{(onView || onEdit || onDelete) && (
|
|
<div className="flex items-center justify-end gap-1.5">
|
|
{onView && (
|
|
<Button variant="outline" size="icon-sm" aria-label="View" onClick={onView}>
|
|
<EyeIcon />
|
|
</Button>
|
|
)}
|
|
{onEdit && (
|
|
<Button variant="outline" size="icon-sm" aria-label="Edit" onClick={onEdit}>
|
|
<PencilIcon />
|
|
</Button>
|
|
)}
|
|
{onDelete && (
|
|
<Button variant="destructive" size="icon-sm" aria-label="Delete" onClick={onDelete}>
|
|
<Trash2Icon />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
export { PostCard }
|
|
export type { PostCardAuthor }
|