Files
setrip/docs/archive/TRUST_ROADMAP.md
T
2026-05-10 22:27:21 +07:00

11 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 via TripProgramBlock.
  • 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.COMPLETED enum-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)

Selesai. 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:

  • tripsCompletedTrip.status = COMPLETED (status itu tidak pernah di-set). Pakai endDate < now() (fallback date < now()) AND status != CLOSED.
  • tripsCancelled = Trip.status = CLOSED (organizer batalkan trip eksplisit).
  • completionRate butuh sample ≥ 3 (COMPLETION_RATE_MIN_SAMPLE di lib/trust.ts) supaya tidak menyesatkan organizer baru.
  • Rating breakdown di-render sebagai bar chart kecil (visual cue lebih kuat dari angka mentah).
  • OrganizerStatsPanel di profil publik tidak di-render untuk user yang murni peserta — query Prisma juga di-skip kalau organizedTrips.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 OrganizerReviewItem di-extract dari Awaited<ReturnType<typeof reviewRepo.findByOrganizer>>[number] supaya schema repo = sumber kebenaran tanpa duplikasi tipe.
  • Fetch trust + reviews via single Promise.all di /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 (cron daily)

Selesai. Pilihan B (cron daily) — Trip.status = COMPLETED di-set otomatis untuk trip yang endDate (atau date kalau endDate null) sudah lewat hari ini UTC.

Keputusan asumsi yang dipakai:

  • Cutoff = utcStartOfDay(new Date()) (start of today UTC). Trip dengan endDate < cutoff di-flip; trip yang berakhir hari ini belum.
  • Hanya trip dengan status OPEN atau FULL yang di-flip. CLOSED tidak disentuh (organizer eksplisit membatalkan; tetap dibedakan dari COMPLETED untuk perhitungan tripsCancelled di trust panel).
  • Idempotent: dua kali run di hari yang sama, run kedua match 0 row.
  • Endpoint diproteksi via Authorization: Bearer ${CRON_SECRET}. Kalau CRON_SECRET tidak di-set, endpoint hard-fail 500 (mencegah accidentally jalan tanpa proteksi).
  • Schedule cron: 0 18 * * * (jam 18:00 UTC = 01:00 WIB hari berikutnya) — buffer ~7 jam pasca-akhir hari WIB sebelum flip.
  • trustService tetap pakai computed-from-endDate (tidak diganti ke status = COMPLETED). Alasan: trust calc tetap correct walau cron telat / down sehari, dan backward-compat untuk trip lama yang dibuat sebelum cron aktif.
  • Vercel Cron via vercel.json — host lain tinggal panggil endpoint yang sama dari cron eksternal apa saja (GitHub Actions, cron-job.org, dst) dengan header yang sama.
# Item Status File
4.1 Repo helper bulkCompletePastTrips(cutoff) (idempotent, batch update) server/repositories/trip.repo.ts
4.2 Service tripService.autoCompletePastTrips() server/services/trip.service.ts
4.3 API route /api/cron/auto-complete-trips (GET, proteksi CRON_SECRET) app/api/cron/auto-complete-trips/route.ts
4.4 Schedule 0 18 * * * di Vercel Cron vercel.json

Tindakan manual:

  1. Set env CRON_SECRET di hosting (random ≥32 char). Generate cepat: openssl rand -hex 32.
  2. Kalau host bukan Vercel: panggil endpoint dari cron eksternal apa saja (GitHub Actions schedule, cron-job.org, EasyCron, dst) dengan header Authorization: Bearer ${CRON_SECRET}. vercel.json bisa dihapus.

Anti-list (yang harus DITOLAK kalau muncul)

  • Model OrganizerReview terpisahTripReview sudah 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):

  1. PR-1 — Trip detail polish. Cepat, low-risk, no migration. Mulai dari sini.
  2. PR-2 — Trust aggregates di service + UI. Read-only, no migration.
  3. PR-3 — Reviews list per organizer di service + UI. Read-only, no migration.
  4. PR-4 — Diskusi opsi completion mechanism (atau skip kalau opsi C dipilih).

Pertanyaan terbuka sebelum PR-2:

  1. Apakah tripsCompleted count termasuk trip dengan status = CLOSED yang endDate < now()? Saran: tidak — CLOSED = dibatalkan, dipisah ke tripsCancelled.
  2. Threshold minimum supaya completionRate ditampilkan? Saran: min 3 trip selesai supaya angka tidak menyesatkan (1 trip dibatalkan dari 1 trip = 0% looks bad untuk organizer baru).
  3. Tampilkan rating breakdown sebagai bar chart atau hanya angka? Saran: bar chart kecil — visual cue lebih kuat untuk credibility.