Files
meezi/web/koja/src/components/cafe/cafe-card.tsx
T

112 lines
3.8 KiB
TypeScript
Raw Normal View History

import { Star, MapPin, Clock, BadgeCheck } from "lucide-react";
import { cn, formatRating, PRICE_TIER_LABELS } from "@/lib/utils";
import type { CafeDiscoverDto } from "@/lib/types";
interface Props {
cafe: CafeDiscoverDto;
locale: string;
href: string;
}
export function CafeCard({ cafe, locale, href }: Props) {
const isFa = locale === "fa";
const name = isFa ? cafe.name : (cafe.name);
const priceTier = cafe.discoverProfile.priceTier;
const priceLabel = priceTier ? (PRICE_TIER_LABELS[priceTier]?.[isFa ? "fa" : "en"] ?? priceTier) : null;
return (
<a
href={href}
className="group flex flex-col overflow-hidden rounded-2xl border border-gray-100 bg-white shadow-sm transition-all hover:shadow-md hover:-translate-y-0.5"
>
{/* Cover image */}
<div className="relative h-44 overflow-hidden bg-gray-100">
{cafe.coverImageUrl ? (
<img
src={cafe.coverImageUrl}
alt={name}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
loading="lazy"
/>
) : (
<div className="flex h-full items-center justify-center bg-gradient-to-br from-brand-50 to-brand-100">
<span className="text-4xl font-bold text-brand-200">{name.charAt(0)}</span>
</div>
)}
{/* Open/Closed badge */}
<div className={cn(
"absolute top-3 end-3 rounded-full px-2.5 py-0.5 text-[10px] font-semibold",
cafe.isOpenNow
? "bg-emerald-500 text-white"
: "bg-gray-800/70 text-white"
)}>
{cafe.isOpenNow ? (isFa ? "باز" : "Open") : (isFa ? "بسته" : "Closed")}
</div>
{/* Logo overlay */}
{cafe.logoUrl && (
<div className="absolute bottom-3 start-3 h-10 w-10 overflow-hidden rounded-xl border-2 border-white bg-white shadow-sm">
<img src={cafe.logoUrl} alt="" className="h-full w-full object-cover" />
</div>
)}
</div>
{/* Content */}
<div className="flex flex-1 flex-col p-4">
<div className="flex items-start gap-2">
<h3 className="flex-1 text-sm font-semibold leading-snug text-gray-900 line-clamp-2">
{name}
{cafe.isVerified && (
<BadgeCheck className="inline ms-1 h-3.5 w-3.5 text-brand-600" />
)}
</h3>
</div>
{/* City */}
{cafe.city && (
<p className="mt-1 flex items-center gap-1 text-xs text-gray-400">
<MapPin className="h-3 w-3 shrink-0" />
{cafe.city}
</p>
)}
{/* Tags */}
{cafe.discoverProfile.themes.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
{cafe.discoverProfile.themes.slice(0, 3).map((tag) => (
<span
key={tag}
className="rounded-full bg-brand-50 px-2 py-0.5 text-[10px] font-medium text-brand-700"
>
{tag}
</span>
))}
</div>
)}
{/* Footer row */}
<div className="mt-auto flex items-center justify-between pt-3">
{/* Rating */}
<div className="flex items-center gap-1">
<Star className="h-3.5 w-3.5 fill-amber-400 text-amber-400" />
<span className="text-sm font-semibold text-gray-900">
{formatRating(cafe.averageRating)}
</span>
{cafe.reviewCount > 0 && (
<span className="text-xs text-gray-400">
({cafe.reviewCount})
</span>
)}
</div>
{/* Price tier */}
{priceLabel && (
<span className="text-xs text-gray-400">{priceLabel}</span>
)}
</div>
</div>
</a>
);
}