9.6 KiB
Setrip — Trust & Trip Detail Roadmap
Status implementasi yang menaikkan kepercayaan calon peserta — trip detail experience yang meyakinkan + sistem reputasi organizer yang transparan.
Prinsip: trust = fungsi dari (a) kelengkapan informasi trip dan (b) reputasi organizer yang transparan. Setiap fitur dievaluasi: apakah memberi calon peserta alasan obyektif untuk percaya?
Audit state sekarang (baseline)
Trip detail (~80% sudah ada):
- ✅ Itinerary, include/exclude, meeting point — schema lengkap di
Trip, ditampilkan viaTripProgramBlock. - ✅ Participant preview (kartu confirmed peserta dengan avatar, kota, interests).
- ✅ Slot tersisa (X / Y) dengan progress bar berwarna.
- ⚠️ Urgency message ada tapi tidak mencolok saat slot menipis.
- ⚠️ Itinerary text bebas — organizer butuh hint format supaya konsisten isi lengkap.
Review/trust (~40% sudah ada):
- ✅ Model
TripReview(rating + comment per trip+user, unique). - ✅ Trust panel di trip detail (verified badge, trips created, avg rating, review count).
- ❌ Profil organizer publik
/u/[id]belum tampilkan rating/review aggregate. - ❌ Belum ada total participants served, completion rate, rating breakdown.
- ❌ Belum ada list ulasan terkumpul per organizer.
- ❌
TripStatus.COMPLETEDenum-nya ada tapi tidak pernah di-set.
File baseline: app/trips/[id]/page.tsx, server/services/trust.service.ts, server/services/review.service.ts, app/u/[id]/page.tsx.
PR-1 — Trip Detail Polish (UI only) ⏳
Cosmetic. Tidak ada migration, tidak ada perubahan service/repo.
| # | Item | Status | File |
|---|---|---|---|
| 1.1 | Urgency badge mencolok saat spotsLeft <= 3 ("⚡ Tinggal X spot!") di header progress bar |
⏳ | app/trips/[id]/page.tsx |
| 1.2 | Participant preview ringkas di blok progress ("👥 Sudah join: Andi, Rina, Budi +4") — first impression tanpa scroll | ⏳ | app/trips/[id]/page.tsx |
| 1.3 | Hint deskriptif + placeholder lebih konkret di field itinerary form create-trip | ⏳ | features/trip/components/create-trip-form.tsx |
Tindakan manual: tidak ada.
PR-2 — Organizer Trust Aggregates (service + UI, tanpa migration) ✅
Selesai. tsc --noEmit lulus. Tanpa schema baru, tanpa migration.
Keputusan asumsi yang dipakai:
tripsCompleted≠Trip.status = COMPLETED(status itu tidak pernah di-set). PakaiendDate < now()(fallbackdate < now()) ANDstatus != CLOSED.tripsCancelled=Trip.status = CLOSED(organizer batalkan trip eksplisit).completionRatebutuh sample ≥ 3 (COMPLETION_RATE_MIN_SAMPLEdi lib/trust.ts) supaya tidak menyesatkan organizer baru.- Rating breakdown di-render sebagai bar chart kecil (visual cue lebih kuat dari angka mentah).
OrganizerStatsPaneldi profil publik tidak di-render untuk user yang murni peserta — query Prisma juga di-skip kalauorganizedTrips.length === 0 && !isVerifiedOrganizer.- Trip detail: stat box "Trip dibuat" diganti jadi "Trip selesai" (lebih meaningful) + tambah "Peserta dilayani". Total 3 stat box, masih kompak.
| # | Item | Status | File |
|---|---|---|---|
| 2.1 | Extend OrganizerTrust type: tripsCompleted, tripsCancelled, totalParticipantsServed, completionRate, ratingBreakdown |
✅ | server/services/trust.service.ts |
| 2.2 | tripsCompleted di-derive dari endDate < now() (fallback date < now()) AND status != CLOSED |
✅ | server/services/trust.service.ts |
| 2.3 | totalParticipantsServed = count TripParticipant CONFIRMED di trip yang sudah lewat & tidak dibatalkan |
✅ | server/services/trust.service.ts |
| 2.4 | completionRate = tripsCompleted / (tripsCompleted + tripsCancelled). Null bila sample < 3 |
✅ | server/services/trust.service.ts, lib/trust.ts |
| 2.5 | ratingBreakdown via prisma.tripReview.groupBy({ by: ['rating'] }) |
✅ | server/services/trust.service.ts |
| 2.6 | Komponen OrganizerStatsPanel (badges + 4 stat box + bar chart breakdown) |
✅ | features/profile/components/organizer-stats-panel.tsx |
| 2.7 | Update OrganizerTrustPanel di trip detail — Trip selesai (+ subtitle "berjalan"), Peserta dilayani, Rating |
✅ | features/trip/components/organizer-trust-panel.tsx |
| 2.8 | Render OrganizerStatsPanel di /u/[id] (skip query untuk non-organizer) |
✅ | app/u/[id]/page.tsx |
Tindakan manual: tidak ada.
PR-3 — Organizer Reviews Aggregator (service + UI, tanpa migration) ✅
Selesai. tsc --noEmit lulus. Tanpa schema baru, tanpa migration.
Keputusan asumsi yang dipakai:
- Default limit 20 ulasan terbaru — cukup untuk MVP, tidak perlu pagination dulu.
- Komponen RSC (server component, no
"use client") — pure render, tidak ada interaktivitas. - Tipe
OrganizerReviewItemdi-extract dariAwaited<ReturnType<typeof reviewRepo.findByOrganizer>>[number]supaya schema repo = sumber kebenaran tanpa duplikasi tipe. - Fetch trust + reviews via single
Promise.alldi/u/[id](paralel, hemat 1 round-trip). - Komponen di-skip kalau
reviews.length === 0— biar tidak makin "kosong" di profil organizer baru. Stats panel sudah punya pesan "Belum ada ulasan". - Rating ditampilkan sebagai bintang penuh (
★★★★☆) bukan angka — visual cue lebih kuat untuk testimoni. - Header section: "X terbaru dari Y ulasan" kalau di-limit, atau cuma "Y ulasan" kalau seluruh list ditampilkan.
| # | Item | Status | File |
|---|---|---|---|
| 3.1 | reviewService.getReviewsByOrganizer(organizerId, limit?) + tipe OrganizerReviewItem |
✅ | server/services/review.service.ts |
| 3.2 | Repo helper findByOrganizer (default limit 20, urut newest, include user + trip) |
✅ | server/repositories/review.repo.ts |
| 3.3 | Komponen OrganizerReviewsList (avatar + name + bintang + trip link + tanggal + comment) |
✅ | features/review/components/organizer-reviews-list.tsx |
| 3.4 | Render di /u/[id] di bawah OrganizerStatsPanel, fetch via Promise.all paralel |
✅ | app/u/[id]/page.tsx |
Tindakan manual: tidak ada.
PR-4 — Trip Completion Mechanism (opsional, butuh diskusi) ⏳
Saat ini Trip.status = COMPLETED tidak pernah di-set oleh kode mana pun. PR ini hanya perlu kalau ingin pakai status sebagai sumber kebenaran formal (bukan computed-from-endDate).
Opsi:
- A. Manual — organizer klik "Tandai trip selesai" pasca-pulang. Pro: kontrol di organizer. Con: gampang lupa,
statustidak accurate kalau organizer pasif. - B. Cron job — daily job set
status = COMPLETEDuntuk trip denganendDate < today() AND status IN ('OPEN','FULL'). Pro: otomatis akurat. Con: butuh infra cron (belum ada di project). - C. Skip — biarkan computed-from-
endDatedi service layer. Pro: paling sederhana, sejalan dengan PR-2 yang juga compute on-the-fly. Con: fieldstatusjadi sebagian "live" (OPEN/FULL/CLOSED murni, COMPLETED computed).
Rekomendasi: C dulu sampai ada kebutuhan riil untuk transisi formal (mis. trigger payout organizer pasca-trip atau notif post-trip continuity di Phase C SOCIAL_ROADMAP).
| # | Item | Status |
|---|---|---|
| 4.1 | Pilih opsi A/B/C | ⏳ |
| 4.2 | Implementasi sesuai pilihan (atau dokumentasikan keputusan kalau C) | ⏳ |
❌ Anti-list (yang harus DITOLAK kalau muncul)
- Model
OrganizerReviewterpisah —TripReviewsudah cukup, 1 trip = 1 organizer. Bikin model baru = duplikasi data + sumber kebenaran ambigu. - Denormalisasi cache (mis.
User.cachedAvgRating) sebelum aggregate query terbukti lambat. Premature optimization → drift jadi tech debt cepat. - Auto-hapus review buruk atau organizer "respon" review (untuk MVP). Bisa nanti — fokus dulu menampilkan data jujur.
- "Trust score" gabungan satu angka — kasih breakdown agar calon peserta evaluasi sendiri. Single number gampang dimanipulasi & menyesatkan.
- Review user/peserta (no-show, kooperatif?) — itu C6 di SOCIAL_ROADMAP.md. Scope berbeda, jangan campur.
- Rating dengan setengah bintang / kustom 1-10 — tetap 1-5 integer (sudah di schema). Granularitas lebih halus tidak meningkatkan trust, hanya menambah noise.
Saran phasing
PR berurutan. Setiap PR mandiri (siap di-deploy):
- PR-1 — Trip detail polish. Cepat, low-risk, no migration. Mulai dari sini.
- PR-2 — Trust aggregates di service + UI. Read-only, no migration.
- PR-3 — Reviews list per organizer di service + UI. Read-only, no migration.
- PR-4 — Diskusi opsi completion mechanism (atau skip kalau opsi C dipilih).
Pertanyaan terbuka sebelum PR-2:
- Apakah
tripsCompletedcount termasuk trip denganstatus = CLOSEDyangendDate < now()? Saran: tidak — CLOSED = dibatalkan, dipisah ketripsCancelled. - Threshold minimum supaya
completionRateditampilkan? Saran: min 3 trip selesai supaya angka tidak menyesatkan (1 trip dibatalkan dari 1 trip = 0% looks bad untuk organizer baru). - Tampilkan rating breakdown sebagai bar chart atau hanya angka? Saran: bar chart kecil — visual cue lebih kuat untuk credibility.