trust roadmap

This commit is contained in:
arifal
2026-05-09 00:55:40 +07:00
parent 5e0232d909
commit 54cd984a7e
14 changed files with 628 additions and 281 deletions
+33 -4
View File
@@ -344,10 +344,22 @@ export default async function TripDetailPage({
{/* Participant Progress */}
<div className="rounded-xl border border-neutral-200 p-3 sm:p-4">
<div className="mb-2 flex items-center justify-between">
<span className="text-xs font-semibold text-neutral-700 sm:text-sm">
Peserta
</span>
<div className="mb-2 flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<span className="text-xs font-semibold text-neutral-700 sm:text-sm">
Peserta
</span>
{spotsLeft > 0 && spotsLeft <= 3 && (
<span className="rounded-full bg-amber-100 px-2 py-0.5 text-[10px] font-bold text-amber-800 sm:text-[11px]">
Tinggal {spotsLeft} spot!
</span>
)}
{spotsLeft <= 0 && (
<span className="rounded-full bg-neutral-200 px-2 py-0.5 text-[10px] font-bold text-neutral-700 sm:text-[11px]">
Penuh
</span>
)}
</div>
<span className="text-xs font-bold text-neutral-800 sm:text-sm">
{participantCount}{" "}
<span className="font-normal text-neutral-400">
@@ -383,6 +395,23 @@ export default async function TripDetailPage({
</>
)}
</p>
{confirmedCount > 0 && (
<p className="mt-2 text-[11px] text-neutral-600 sm:text-xs">
<span aria-hidden>👥</span> Sudah join:{" "}
<span className="font-medium text-neutral-800">
{confirmedParticipants
.slice(0, 3)
.map((p) => p.user.name.split(" ")[0])
.join(", ")}
</span>
{confirmedCount > 3 && (
<span className="text-neutral-500">
{" "}
+{confirmedCount - 3} lainnya
</span>
)}
</p>
)}
</div>
<TripProgramBlock
+23
View File
@@ -3,8 +3,12 @@ import Link from "next/link";
import Image from "next/image";
import { notFound } from "next/navigation";
import { profileService } from "@/server/services/profile.service";
import { trustService } from "@/server/services/trust.service";
import { reviewService } from "@/server/services/review.service";
import { TripCard } from "@/features/trip/components/trip-card";
import { ProfileTripRow } from "@/features/profile/components/profile-trip-row";
import { OrganizerStatsPanel } from "@/features/profile/components/organizer-stats-panel";
import { OrganizerReviewsList } from "@/features/review/components/organizer-reviews-list";
import { siteConfig } from "@/lib/site";
import { vibeMeta } from "@/lib/vibe";
@@ -45,6 +49,16 @@ export default async function PublicProfilePage({ params }: PageProps) {
year: "numeric",
});
// Trust panel hanya relevan untuk user yang berperan organizer.
// Hindari query Prisma yang nggak perlu untuk user yang murni peserta.
const isOrganizerProfile = organizedTrips.length > 0 || isVerifiedOrganizer;
const [organizerTrust, organizerReviews] = isOrganizerProfile
? await Promise.all([
trustService.getOrganizerTrust(user.id),
reviewService.getReviewsByOrganizer(user.id),
])
: [null, []];
return (
<div className="mx-auto max-w-4xl px-4 py-6 sm:py-8">
{/* Header */}
@@ -156,6 +170,15 @@ export default async function PublicProfilePage({ params }: PageProps) {
</div>
</section>
{organizerTrust && <OrganizerStatsPanel trust={organizerTrust} />}
{organizerTrust && organizerReviews.length > 0 && (
<OrganizerReviewsList
reviews={organizerReviews}
totalCount={organizerTrust.reviewCount}
/>
)}
{/* Empty profile hint */}
{!profile && (
<p className="mt-5 rounded-xl border border-dashed border-neutral-200 bg-neutral-50 px-4 py-3 text-center text-xs text-neutral-500">