2026-05-27 21:34:47 +03:30
|
|
|
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);
|
2026-06-02 01:51:50 +03:30
|
|
|
const priceTier = cafe.discoverProfile?.priceTier ?? null;
|
|
|
|
|
const themes = cafe.discoverProfile?.themes ?? [];
|
2026-05-27 21:34:47 +03:30
|
|
|
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 */}
|
2026-06-02 01:51:50 +03:30
|
|
|
{themes.length > 0 && (
|
2026-05-27 21:34:47 +03:30
|
|
|
<div className="mt-2 flex flex-wrap gap-1">
|
2026-06-02 01:51:50 +03:30
|
|
|
{themes.slice(0, 3).map((tag) => (
|
2026-05-27 21:34:47 +03:30
|
|
|
<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>
|
|
|
|
|
);
|
|
|
|
|
}
|