add user profile, profile vibe and trip vibe and social signal

This commit is contained in:
2026-05-08 19:20:27 +07:00
parent 3228ef712f
commit 7f419638b5
39 changed files with 1361 additions and 192 deletions
+79 -19
View File
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getServerSession } from "next-auth";
import Link from "next/link";
import Image from "next/image";
import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service";
import { trustService } from "@/server/services/trust.service";
@@ -15,6 +16,8 @@ import { TripProgramBlock } from "@/features/trip/components/trip-program-block"
import { OrganizerPaymentQueue } from "@/features/booking/components/organizer-payment-queue";
import { ImageGallery } from "@/features/trip/components/image-gallery";
import { TripReviewSection } from "@/features/review/components/trip-review-section";
import { categoryMeta } from "@/lib/activity-category";
import { vibeMeta } from "@/lib/vibe";
import {
isPastTripLastDayForReview,
isTripDepartureDayPast,
@@ -127,6 +130,8 @@ export default async function TripDetailPage({
(p) => p.markedPaidAt && !p.paymentConfirmedAt
);
const catMeta = categoryMeta(trip.category);
const tripUrl = absoluteUrl(`/trips/${trip.id}`);
const eventStatus =
trip.status === "OPEN"
@@ -240,8 +245,21 @@ export default async function TripDetailPage({
<h1 className="text-lg font-bold text-neutral-800 sm:text-xl">
{trip.title}
</h1>
<p className="mt-0.5 flex items-center gap-1.5 text-sm text-neutral-500">
🏔 {trip.destination}
<p className="mt-0.5 flex flex-wrap items-center gap-1.5 text-sm text-neutral-500">
<span aria-hidden>{catMeta.icon}</span>
<span className="rounded-full bg-neutral-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-neutral-600">
{catMeta.label}
</span>
{trip.vibe && (
<span
className="inline-flex items-center gap-0.5 rounded-full bg-secondary-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-secondary-700"
title={vibeMeta(trip.vibe).description}
>
<span aria-hidden>{vibeMeta(trip.vibe).icon}</span>
<span>{vibeMeta(trip.vibe).label}</span>
</span>
)}
<span className="truncate">{trip.destination}</span>
</p>
</div>
<span
@@ -448,9 +466,12 @@ export default async function TripDetailPage({
{/* Peserta yang sudah disetujui organizer (publik) */}
<div>
<h2 className="mb-3 text-xs font-bold text-neutral-700 sm:text-sm">
<h2 className="mb-1 text-xs font-bold text-neutral-700 sm:text-sm">
Peserta terkonfirmasi ({confirmedCount})
</h2>
<p className="mb-3 text-[11px] text-neutral-500 sm:text-xs">
Kenalan dulu sebelum berangkat klik kartu untuk lihat profil.
</p>
{confirmedCount === 0 ? (
<p className="text-xs text-neutral-400 sm:text-sm">
Belum ada peserta yang dikonfirmasi.{" "}
@@ -459,22 +480,61 @@ export default async function TripDetailPage({
: "Jadilah yang pertama mendaftar! 🎒"}
</p>
) : (
<div className="flex flex-wrap gap-1.5 sm:gap-2">
{confirmedParticipants.map((p) => (
<Link
key={p.id}
href={`/u/${p.user.id}`}
className="flex items-center gap-1.5 rounded-full bg-neutral-100 px-2.5 py-1 transition-colors hover:bg-primary-100 sm:gap-2 sm:px-3 sm:py-1.5"
>
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-primary-600 text-[9px] font-bold text-white sm:h-6 sm:w-6 sm:text-[10px]">
{p.user.name.charAt(0).toUpperCase()}
</div>
<span className="text-xs font-medium text-neutral-700 sm:text-sm">
{p.user.name}
</span>
</Link>
))}
</div>
<ul className="grid gap-2 sm:grid-cols-2">
{confirmedParticipants.map((p) => {
const interests = p.user.profile?.interests ?? [];
const city = p.user.profile?.city;
return (
<li key={p.id}>
<Link
href={`/u/${p.user.id}`}
className="flex items-start gap-3 rounded-xl border border-neutral-200 bg-white px-3 py-2.5 transition-colors hover:border-primary-300 hover:bg-primary-50/30"
>
{p.user.image ? (
<Image
src={p.user.image}
alt=""
width={40}
height={40}
className="h-10 w-10 shrink-0 rounded-full object-cover"
/>
) : (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary-600 text-sm font-bold text-white">
{p.user.name.charAt(0).toUpperCase()}
</div>
)}
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-neutral-800">
{p.user.name}
</p>
{city && (
<p className="truncate text-[11px] text-neutral-500 sm:text-xs">
📍 {city}
</p>
)}
{interests.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
{interests.slice(0, 3).map((tag) => (
<span
key={tag}
className="rounded-full bg-secondary-50 px-1.5 py-0.5 text-[10px] font-medium text-secondary-700"
>
#{tag}
</span>
))}
{interests.length > 3 && (
<span className="text-[10px] text-neutral-400">
+{interests.length - 3}
</span>
)}
</div>
)}
</div>
</Link>
</li>
);
})}
</ul>
)}
</div>
</div>