7.9 KiB
SeTrip
Aplikasi open trip pendakian yang mempertemukan organizer (pembuat trip) dengan peserta yang ingin naik gunung bareng atau mencari teman trip.
Stack: Next.js (App Router), NextAuth, Prisma (PostgreSQL), Tailwind CSS.
Alur aplikasi
1. Autentikasi
- Pengguna baru mendaftar di
/register(nama, email, password disimpan di database). - Login di
/loginmelalui NextAuth; sesi dipakai di server action dan di halaman client (navbar, form buat trip, join).
Tanpa login, pengguna tetap bisa melihat daftar trip dan detail trip, tetapi tidak bisa membuat trip atau join.
2. Organizer: membuat trip
- Setelah login, organizer membuka Buat Trip (
/create-trip) dari navbar,/trips, beranda, atau tombol (+). - Form memvalidasi sesi; jika belum login, ditampilkan ajakan login.
- Organizer mengisi judul, gunung, lokasi, deskripsi (opsional), meeting point, itinerary, termasuk / tidak termasuk (opsional), rentang tanggal berangkat–pulang, maks peserta, harga (Rupiah), dan URL gambar opsional.
- Submit →
createTripAction→ validasi Zod →tripService.createTripmenulisTripke database (status default OPEN) beserta gambar jika ada. - Pengguna diarahkan ke detail trip
/trips/[id].
Organizer tidak bisa join trip sendiri; di detail trip ditampilkan bahwa dia adalah organizer trip ini.
Verifikasi organizer (untuk trip berbayar). Trip dengan harga > 0 hanya bisa dibuat oleh user yang sudah mengirim KTP, selfie, dan data rekening di /verify lalu disetujui admin di /admin/verifications. Trip gratis tidak butuh verifikasi. Organizer yang sudah disetujui tampil dengan badge ✅ Verified Organizer di halaman detail trip.
3. Peserta: mencari trip
- Beranda (
/) dan Open Trip (/trips) menampilkan trip OPEN dengan tanggal berangkat yang masih relevan (tripService.getOpenTrips). - Filter pencarian (
TripFilter) mengirim query string (q,from,to); penyaringan dilakukan di server. - Dari TripCard, pengguna membuka detail
/trips/[id]: melihat info trip, kuota, panel kepercayaan organizer (jumlah trip, rating, badge jika ada), daftar peserta yang sudah disetujui, dan ulasan.
4. Alur lengkap: dari join hingga pembayaran sukses
Alur ini menggambarkan satu peserta dari pertama kali mendaftar sampai pembayaran dianggap selesai di aplikasi (pembayaran manual / transfer di luar gateway).
[Peserta] [Sistem] [Organizer]
| | |
|-- buka detail trip ------>| |
| | |
|-- "Join Trip" ------------>| status partisipasi: PENDING |
| | (mengisi slot kuota trip) |
|<-- menunggu persetujuan ---| |
| |<-- lihat "Permintaan join" ------|
| | |
| |<-- "Setujui" atau "Tolak" -------|
| | Setujui -> CONFIRMED |
| | Tolak -> CANCELLED |
|<-- terkonfirmasi ikut -----| (jika disetujui) |
| | |
|-- transfer uang (WA/rek) --| (di luar app) |
| | |
|-- "Saya sudah bayar" ----->| markedPaidAt = sekarang |
|<-- menunggu konfirmasi ----| |
| | |
| |<-- "Konfirmasi pembayaran" ------|
| | paymentConfirmedAt = sekarang |
|<-- pembayaran dikonfirmasi-| |
Langkah per langkah
-
Join trip
Di halaman detail, peserta login menekan Join Trip Sekarang →joinTripAction→tripService.joinTrip.
Dibuat atau diaktifkan kembali barisTripParticipantdengan statusPENDING. Kuota trip (peserta aktif: PENDING + CONFIRMED) bertambah; trip bisa menjadi FULL jika slot habis. -
Menunggu organizer
Peserta melihat status bahwa permintaan menunggu persetujuan organizer. Dia bisa Batal ikut selama tanggal berangkat belum lewat (status menjadiCANCELLED, slot bisa longgar lagi). -
Organizer menyetujui atau menolak
Di blok Permintaan join, organizer menekan Setujui → status partisipasi menjadiCONFIRMED, atau Tolak →CANCELLED.
Jika ada peserta yang sudah menandai bayar sebelum disetujui, penolakan juga membersihkan data tandai bayar pada baris itu. -
Peserta terkonfirmasi
Setelah CONFIRMED, peserta dianggap bagian dari daftar “peserta terkonfirmasi” di halaman trip. Fitur ulasan trip setelah selesai hanya relevan untuk partisipasi terkonfirmasi (sesuai aturan di service). -
Menandai sudah membayar
Peserta mentransfer sesuai instruksi organizer (di luar app). Di app dia menekan Saya sudah bayar → kolommarkedPaidAtdiisi. Tombol ini tidak muncul jika pembayaran sudah dikonfirmasi atau tanggal trip sudah lewat.
Aksi ini atomik (aman dari double klik). -
Organizer mengonfirmasi pembayaran
Di blok Konfirmasi pembayaran, organizer mengecek mutasi/bukti lalu menekan Konfirmasi pembayaran untuk peserta bersangkutan → kolompaymentConfirmedAtdiisi.
Ini idempoten (konfirmasi ganda tidak mengubah hasil akhir yang salah). -
Selesai (sukses payment di app)
Peserta melihat bahwa pembayaran sudah dikonfirmasi organizer. Di sini alur “commit” peserta + uang dalam konteks SeTrip dianggap lengkap dari sisi data aplikasi.
Catatan produk: tidak ada payment gateway; bukti transfer dan nominal final tetap komunikasi organizer–peserta (misalnya WA).
5. Ringkasan peran data
| Konsep | Penyimpanan |
|---|---|
| Trip | Trip: judul, gunung, lokasi, tanggal, kuota, harga, status trip (OPEN / FULL / …), meeting point, itinerary, termasuk/tidak termasuk, relasi ke organizer |
| Peserta | TripParticipant unik per (tripId, userId): status PENDING / CONFIRMED / CANCELLED, serta markedPaidAt & paymentConfirmedAt untuk alur bayar manual |
| Organizer (kepercayaan) | OrganizerVerification (1-1 ke User) berisi KTP, selfie, rekening, dan status (PENDING / APPROVED / REJECTED); badge Verified Organizer muncul ketika status === "APPROVED" (helper lib/trust.ts → isVerifiedOrganizer). Agregat rating & jumlah trip dihitung dari ulasan & trip. |
| Persetujuan T&C / Privasi | User.acceptedTermsAndPrivacy + User.acceptedAt, dicentang saat registrasi (link ke /terms & /privacy). |
Menjalankan secara lokal
Pastikan PostgreSQL berjalan dan variabel DATABASE_URL di .env mengarah ke database yang valid.
npm install
npx prisma migrate dev
npm run seed # opsional: data contoh
npm run dev
Buka http://localhost:3000.
Perbaikan bug (yang relevan dengan join & listing)
-
Join lagi setelah “Batal ikut”
Satu user hanya satu baris per trip (@@unique([tripId, userId])). Jika baris sudahCANCELLED, join berikutnya mengaktifkan kembali partisipasi kePENDING(bukan insert baru). -
Jumlah peserta di kartu / daftar
_count.participantshanya menghitung status bukanCANCELLED, agar slot dan label “penuh” konsisten. -
Segar halaman setelah aksi
revalidatePathdipanggil setelah join, batal, buat trip, setujui/tolak peserta, dan konfirmasi pembayaran agar daftar dan detail konsisten.