Add: PostCard component

This commit is contained in:
Aroy-Art 2025-03-22 21:08:51 +01:00
parent 98e9389537
commit 1bcffe619c
Signed by: Aroy
GPG key ID: 583642324A1D2070

View file

@ -0,0 +1,174 @@
import { Link } from "react-router-dom"
import { Card } from "@/components/ui/card"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { File, FileText, Play, ImagePlay } from "lucide-react"
import { relativeTime, getFirstGrapheme } from "@/lib/utils"
import { getFileUrl } from "@/services/api"
interface PostCardProps {
post_id: string
title: string
description: string
creator: {
[key: string]: string
}
date: {
[key: string]: string
}
media: Array<{
[key: string]: string
}>
media_count: number
source_site: {
[key: string]: string
}
}
export function PostCard({ post_id, title, description, creator, date, media, media_count, source_site }: PostCardProps) {
const renderMedia = () => {
const mediaCount = media.length;
const baseImageClass = "object-cover w-full h-full rounded-md";
if (mediaCount === 0) {
return (
<div className="relative h-40 md:h-48 xl:h-[14rem] w-full overflow-hidden rounded-md flex items-center justify-center">
<p className="text-center">No attached files</p>
</div>
);
}
if (mediaCount === 1) {
return (
<div className="relative h-40 md:h-48 xl:h-[14rem] w-full overflow-hidden rounded-md">
<img src={getFileUrl(media[0].hash, { thumbnail: "sm" }) || "/placeholder.svg"} alt={`${title} - media 1`} className={baseImageClass} />
{renderMediaBadge(media[0])}
</div>
);
}
if (mediaCount === 2) {
return (
<div className="grid grid-cols-2 gap-0.5 h-40 md:h-48 xl:h-[14rem]">
{media.map((item, index) => (
<div key={index} className="relative overflow-hidden rounded-md">
<img src={getFileUrl(item.hash, { thumbnail: "sm" }) || "/placeholder.svg"} alt={`${title} - media ${index + 1}`} className={baseImageClass} />
{renderMediaBadge(item)}
</div>
))}
</div>
);
}
if (mediaCount === 3) {
return (
<div className="grid grid-cols-2 gap-0.5 h-40 md:h-48 xl:h-[14rem]">
<div className="relative row-span-2 overflow-hidden rounded-md">
<img src={getFileUrl(media[0].hash, { thumbnail: "sm" }) || "/placeholder.svg"} alt={`${title} - media 1`} className={baseImageClass} />
{renderMediaBadge(media[0])}
</div>
<div className="relative overflow-hidden rounded-md">
<img src={getFileUrl(media[1].hash, { thumbnail: "sm" }) || "/placeholder.svg"} alt={`${title} - media 2`} className={baseImageClass} />
{renderMediaBadge(media[1])}
</div>
<div className="relative overflow-hidden rounded-md">
<img src={getFileUrl(media[2].hash, { thumbnail: "sm" }) || "/placeholder.svg"} alt={`${title} - media 3`} className={baseImageClass} />
{renderMediaBadge(media[2])}
</div>
</div>
);
}
return (
<div className="grid grid-cols-2 gap-0.5 h-40 md:h-48 xl:h-[14rem]">
{media.slice(0, 4).map((item, index) => (
<div key={index} className="relative overflow-hidden rounded-md">
<img src={getFileUrl(item.hash, { thumbnail: "sm" }) || "/placeholder.svg"} alt={`${title} - media ${index + 1}`} className={baseImageClass} />
{renderMediaBadge(item)}
{index === 3 && mediaCount > 4 && (
<div className="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<span className="text-white text-sm font-bold">+{mediaCount - 4}</span>
</div>
)}
</div>
))}
</div>
);
};
const renderMediaBadge = (item: { type: 'image' | 'video' | 'gif' | 'pdf', src: string }) => {
if (item.type === 'video') {
return (
<div className="absolute top-1 left-1 bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded-md flex items-center">
<Play className="w-3 h-3 mr-1" />
Video
</div>
);
} else if (item.type === 'gif') {
return (
<div className="absolute top-1 left-1 bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded-md flex items-center">
<ImagePlay className="w-3 h-3 mr-1" />
GIF
</div>
);
} else if (item.type === 'pdf') {
return (
<div className="absolute top-1 left-1 bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded-md flex items-center">
<FileText className="w-3 h-3 mr-1" />
Doc
</div>
);
};
return null;
};
const renderSourceSiteIcon = () => {
if (source_site.slug === 'twitter') {
return (
<div className="absolute top-1 right-1 bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded-xl flex items-center">
<img src="/images/sites/twitter_logo-128.png" alt="Twitter Icon" className="w-3 h-3 md:w-4 md:h-4 m-0.5" />
</div>
)
} else if (source_site.slug === 'furaffinity') {
return (
<div className="absolute top-1 right-1 bg-black bg-opacity-50 text-white text-xs px-1 py-0.5 rounded-xl flex items-center">
<img src="/images/sites/fa_logo-128.png" alt="Furaffinity Icon" className="w-3 h-3 md:w-4 md:h-4 m-0.5" />
</div>
)
}
}
return (
<Link to={`/post/${source_site.slug}/${creator.slug}/${post_id}`}>
<Card className="w-44 md:w-56 lg:w-64 xl:w-80 h-full overflow-hidden cursor-pointer">
<div className="relative">
{renderMedia()}
{renderSourceSiteIcon()}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black to-transparent p-2">
<h2 className="text-sm text-white line-clamp-1">
{title || (description.length > 28 ? `${description.substring(0, 28)}...` : description)}
</h2>
<div className="flex mt-1 flex-col">
<div className="flex items-center space-x-1">
<Avatar className="w-6 h-6">
{creator.avatar && <AvatarImage src={getFileUrl(creator.avatar, { thumbnail: "sx" })} />}
<AvatarFallback>{getFirstGrapheme(creator.name)}</AvatarFallback>
</Avatar>
<div className=" text-sm font-semibold text-gray-300 overflow-hidden text-nowrap">{creator.name}</div>
</div>
<div className="pt-1 flex items-center justify-between text-xs text-gray-300">
<div className="">{relativeTime(date.created)}</div>
<div className="flex items-center">
<File className="w-3 h-3 mr-1" />
<div>{media_count}</div>
</div>
</div>
</div>
</div>
</div>
</Card>
</Link>
)
}