trust roadmap
This commit is contained in:
+33
-4
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user