add user profile, profile vibe and trip vibe and social signal
This commit is contained in:
@@ -3,12 +3,21 @@ 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";
|
||||
import { vibeMeta } from "@/lib/vibe";
|
||||
import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums";
|
||||
|
||||
interface TripCardParticipant {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string | null;
|
||||
interests: string[];
|
||||
}
|
||||
|
||||
interface TripCardProps {
|
||||
id: string;
|
||||
title: string;
|
||||
category: ActivityCategory;
|
||||
vibe?: Vibe | null;
|
||||
destination: string;
|
||||
location: string;
|
||||
date: Date | string;
|
||||
@@ -21,12 +30,17 @@ interface TripCardProps {
|
||||
coverImage?: string | null;
|
||||
priority?: boolean;
|
||||
isVerifiedOrganizer?: boolean;
|
||||
/** Daftar peserta CONFIRMED (subset, untuk preview avatar). Optional. */
|
||||
participants?: TripCardParticipant[];
|
||||
/** Interests user yang sedang melihat — untuk hitung overlap. Optional. */
|
||||
viewerInterests?: string[];
|
||||
}
|
||||
|
||||
export function TripCard({
|
||||
id,
|
||||
title,
|
||||
category,
|
||||
vibe,
|
||||
destination,
|
||||
location,
|
||||
date,
|
||||
@@ -39,10 +53,25 @@ export function TripCard({
|
||||
coverImage,
|
||||
priority,
|
||||
isVerifiedOrganizer,
|
||||
participants,
|
||||
viewerInterests,
|
||||
}: TripCardProps) {
|
||||
const spotsLeft = maxParticipants - participantCount;
|
||||
const isSmallGroup = maxParticipants <= 10;
|
||||
const meta = categoryMeta(category);
|
||||
const vMeta = vibe ? vibeMeta(vibe) : null;
|
||||
|
||||
const previewParticipants = participants?.slice(0, 3) ?? [];
|
||||
const moreCount =
|
||||
participants && participants.length > 3 ? participants.length - 3 : 0;
|
||||
|
||||
let overlapCount = 0;
|
||||
if (viewerInterests && viewerInterests.length > 0 && participants) {
|
||||
const viewerSet = new Set(viewerInterests.map((i) => i.toLowerCase()));
|
||||
overlapCount = participants.filter((p) =>
|
||||
p.interests.some((tag) => viewerSet.has(tag.toLowerCase()))
|
||||
).length;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={`/trips/${id}`} className="group block">
|
||||
@@ -63,13 +92,24 @@ export function TripCard({
|
||||
<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>
|
||||
<div className="absolute left-3 top-3 flex flex-wrap gap-1">
|
||||
<span
|
||||
className="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>
|
||||
{vMeta && (
|
||||
<span
|
||||
className="inline-flex items-center gap-1 rounded-full bg-secondary-600/90 px-2 py-0.5 text-[11px] font-semibold text-white shadow-sm backdrop-blur-sm"
|
||||
title={`Vibe: ${vMeta.label} — ${vMeta.description}`}
|
||||
>
|
||||
<span aria-hidden>{vMeta.icon}</span>
|
||||
<span>{vMeta.label}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className={`absolute right-3 top-3 rounded-full px-2.5 py-0.5 text-xs font-bold backdrop-blur-sm ${
|
||||
status === "OPEN"
|
||||
@@ -120,6 +160,48 @@ export function TripCard({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(previewParticipants.length > 0 || overlapCount > 0) && (
|
||||
<div className="mt-3 flex items-center gap-2 border-t border-neutral-100 pt-3">
|
||||
{previewParticipants.length > 0 && (
|
||||
<div className="flex -space-x-2">
|
||||
{previewParticipants.map((p) =>
|
||||
p.image ? (
|
||||
<Image
|
||||
key={p.id}
|
||||
src={p.image}
|
||||
alt=""
|
||||
width={24}
|
||||
height={24}
|
||||
className="h-6 w-6 rounded-full border-2 border-white object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
key={p.id}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full border-2 border-white bg-primary-600 text-[10px] font-bold text-white"
|
||||
title={p.name}
|
||||
>
|
||||
{p.name.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{moreCount > 0 && (
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-full border-2 border-white bg-neutral-200 text-[10px] font-bold text-neutral-600">
|
||||
+{moreCount}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{overlapCount > 0 && (
|
||||
<span
|
||||
className="rounded-full bg-secondary-50 px-2 py-0.5 text-[11px] font-semibold text-secondary-700"
|
||||
title="Peserta dengan minimal 1 minat sama dengan kamu"
|
||||
>
|
||||
✨ {overlapCount} peserta sama minat
|
||||
</span>
|
||||
)}
|
||||
</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)}
|
||||
|
||||
Reference in New Issue
Block a user