140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { formatRupiah } from "@/lib/utils";
|
|
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
|
|
import { categoryMeta } from "@/lib/activity-category";
|
|
import type { ActivityCategory } from "@/app/generated/prisma/enums";
|
|
|
|
interface TripCardProps {
|
|
id: string;
|
|
title: string;
|
|
category: ActivityCategory;
|
|
destination: string;
|
|
location: string;
|
|
date: Date | string;
|
|
endDate?: Date | string | null;
|
|
price: number;
|
|
maxParticipants: number;
|
|
participantCount: number;
|
|
organizerName: string;
|
|
status: string;
|
|
coverImage?: string | null;
|
|
priority?: boolean;
|
|
isVerifiedOrganizer?: boolean;
|
|
}
|
|
|
|
export function TripCard({
|
|
id,
|
|
title,
|
|
category,
|
|
destination,
|
|
location,
|
|
date,
|
|
endDate,
|
|
price,
|
|
maxParticipants,
|
|
participantCount,
|
|
organizerName,
|
|
status,
|
|
coverImage,
|
|
priority,
|
|
isVerifiedOrganizer,
|
|
}: TripCardProps) {
|
|
const spotsLeft = maxParticipants - participantCount;
|
|
const isSmallGroup = maxParticipants <= 10;
|
|
const meta = categoryMeta(category);
|
|
|
|
return (
|
|
<Link href={`/trips/${id}`} className="group block">
|
|
<div className="overflow-hidden rounded-2xl border border-neutral-200 bg-white transition-all group-hover:-translate-y-0.5 group-hover:shadow-lg group-hover:shadow-neutral-200/60">
|
|
{/* Cover Image */}
|
|
<div className="relative h-40 bg-neutral-800">
|
|
{coverImage ? (
|
|
<Image
|
|
src={coverImage}
|
|
alt={title}
|
|
fill
|
|
className="object-cover transition-transform group-hover:scale-105"
|
|
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
|
priority={priority}
|
|
/>
|
|
) : (
|
|
<div className="flex h-full items-center justify-center bg-linear-to-br from-primary-800 to-secondary-900">
|
|
<span className="text-4xl">{meta.icon}</span>
|
|
</div>
|
|
)}
|
|
<span
|
|
className="absolute left-3 top-3 inline-flex items-center gap-1 rounded-full bg-white/90 px-2 py-0.5 text-[11px] font-semibold text-neutral-700 shadow-sm backdrop-blur-sm"
|
|
title={`Kategori: ${meta.label}`}
|
|
>
|
|
<span>{meta.icon}</span>
|
|
<span>{meta.label}</span>
|
|
</span>
|
|
<span
|
|
className={`absolute right-3 top-3 rounded-full px-2.5 py-0.5 text-xs font-bold backdrop-blur-sm ${
|
|
status === "OPEN"
|
|
? "bg-primary-600/80 text-white"
|
|
: status === "FULL"
|
|
? "bg-amber-500/80 text-white"
|
|
: "bg-neutral-600/80 text-neutral-200"
|
|
}`}
|
|
>
|
|
{status}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="p-4">
|
|
<h3 className="font-bold text-neutral-800 group-hover:text-primary-700">
|
|
{title}
|
|
</h3>
|
|
<p className="mt-0.5 text-sm text-neutral-500">{destination}</p>
|
|
|
|
<div className="mt-3 space-y-1 text-sm text-neutral-600">
|
|
<div className="flex items-center gap-1.5">
|
|
<span className="text-xs text-secondary-500">📍</span> {location}
|
|
</div>
|
|
<div className="flex items-center gap-1.5">
|
|
<span className="text-xs text-secondary-500">📅</span>{" "}
|
|
{formatTripCalendarDateRangeLong(date, endDate)}
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
<span className="text-xs text-secondary-500">👤</span>{" "}
|
|
<span className="truncate">{organizerName}</span>
|
|
{isVerifiedOrganizer && (
|
|
<span
|
|
className="inline-flex items-center gap-0.5 rounded-full bg-primary-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary-800"
|
|
title="Identitas organizer telah diverifikasi (KTP & rekening)"
|
|
>
|
|
✅ Verified
|
|
</span>
|
|
)}
|
|
{isSmallGroup && (
|
|
<span
|
|
className="inline-flex items-center rounded-full bg-secondary-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-secondary-800"
|
|
title="Grup kecil — pengalaman lebih akrab"
|
|
>
|
|
Small group
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-3 flex items-center justify-between border-t border-neutral-100 pt-3">
|
|
<span className="text-lg font-bold text-primary-600">
|
|
{formatRupiah(price)}
|
|
</span>
|
|
<span
|
|
className={`text-xs font-semibold ${
|
|
spotsLeft > 0 ? "text-secondary-600" : "text-amber-600"
|
|
}`}
|
|
>
|
|
{spotsLeft > 0 ? `${spotsLeft} slot tersisa` : "Penuh"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|