2026-05-20 13:34:03 +07:00
2026-05-18 20:54:59 +07:00
2026-05-20 13:16:25 +07:00
-
2026-05-18 18:31:16 +07:00
2026-04-16 14:51:54 +07:00
2026-05-11 13:04:20 +07:00
2026-04-21 16:26:17 +07:00
2026-04-16 16:26:29 +07:00
2026-05-20 13:34:03 +07:00
2026-05-20 13:34:03 +07:00
2026-04-16 14:51:54 +07:00

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 /login melalui 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

  1. Setelah login, organizer membuka Buat Trip (/create-trip) dari navbar, /trips, beranda, atau tombol (+).
  2. Form memvalidasi sesi; jika belum login, ditampilkan ajakan login.
  3. Organizer mengisi judul, gunung, lokasi, deskripsi (opsional), meeting point, itinerary, termasuk / tidak termasuk (opsional), rentang tanggal berangkatpulang, maks peserta, harga (Rupiah), dan URL gambar opsional.
  4. Submit → createTripAction → validasi Zod → tripService.createTrip menulis Trip ke database (status default OPEN) beserta gambar jika ada.
  5. 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, foto liveness (memegang kertas tulisan "SETRIP"), 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

  1. Beranda (/) dan Open Trip (/trips) menampilkan trip OPEN dengan tanggal berangkat yang masih relevan (tripService.getOpenTrips).
  2. Filter pencarian (TripFilter) mengirim query string (q, from, to); penyaringan dilakukan di server.
  3. 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

  1. Join trip
    Di halaman detail, peserta login menekan Join Trip SekarangjoinTripActiontripService.joinTrip.
    Dibuat atau diaktifkan kembali baris TripParticipant dengan status PENDING. Kuota trip (peserta aktif: PENDING + CONFIRMED) bertambah; trip bisa menjadi FULL jika slot habis.

  2. Menunggu organizer
    Peserta melihat status bahwa permintaan menunggu persetujuan organizer. Dia bisa Batal ikut selama tanggal berangkat belum lewat (status menjadi CANCELLED, slot bisa longgar lagi).

  3. Organizer menyetujui atau menolak
    Di blok Permintaan join, organizer menekan Setujui → status partisipasi menjadi CONFIRMED, atau TolakCANCELLED.
    Jika ada peserta yang sudah menandai bayar sebelum disetujui, penolakan juga membersihkan data tandai bayar pada baris itu.

  4. 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).

  5. Menandai sudah membayar
    Peserta mentransfer sesuai instruksi organizer (di luar app). Di app dia menekan Saya sudah bayar → kolom markedPaidAt diisi. Tombol ini tidak muncul jika pembayaran sudah dikonfirmasi atau tanggal trip sudah lewat.
    Aksi ini atomik (aman dari double klik).

  6. Organizer mengonfirmasi pembayaran
    Di blok Konfirmasi pembayaran, organizer mengecek mutasi/bukti lalu menekan Konfirmasi pembayaran untuk peserta bersangkutan → kolom paymentConfirmedAt diisi.
    Ini idempoten (konfirmasi ganda tidak mengubah hasil akhir yang salah).

  7. 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 organizerpeserta (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, foto liveness (memegang kertas "SETRIP"), 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)

  1. Join lagi setelah “Batal ikut”
    Satu user hanya satu baris per trip (@@unique([tripId, userId])). Jika baris sudah CANCELLED, join berikutnya mengaktifkan kembali partisipasi ke PENDING (bukan insert baru).

  2. Jumlah peserta di kartu / daftar
    _count.participants hanya menghitung status bukan CANCELLED, agar slot dan label “penuh” konsisten.

  3. Segar halaman setelah aksi
    revalidatePath dipanggil setelah join, batal, buat trip, setujui/tolak peserta, dan konfirmasi pembayaran agar daftar dan detail konsisten.

Learn More

S
Description
No description provided
Readme 5.9 MiB
Languages
TypeScript 99.3%
CSS 0.5%
JavaScript 0.2%