Files
setrip/README.md
T
2026-04-20 00:25:05 +07:00

78 lines
4.6 KiB
Markdown

# SeTrip
Aplikasi open trip pendakian yang mempertemukan **organizer** (pembuat trip) dengan **peserta** yang ingin naik gunung bareng atau mencari teman trip.
Stack: [Next.js](https://nextjs.org) (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 (misalnya navbar, form buat trip).
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, halaman `/trips`, beranda, atau tombol mengambang (+).
2. Halaman form (`app/create-trip/page.tsx`) memvalidasi sesi di client; jika belum login, ditampilkan ajakan login.
3. Organizer mengisi judul, gunung, lokasi, deskripsi (opsional), rentang tanggal (DatePicker), maks peserta, harga (format Rupiah), dan URL gambar opsional (`ImageUrlInput`).
4. Submit memanggil server action `createTripAction` (`features/trip/actions.ts`):
- Memastikan ada sesi.
- Mem-parse dan memvalidasi input dengan Zod (`features/trip/schemas.ts`).
- `tripService.createTrip` menulis trip baru ke database lewat `tripRepo.create`, menghubungkan `organizerId` ke user yang login, dan menyimpan gambar jika ada.
5. Trip baru berstatus **OPEN** (default schema), lalu pengguna diarahkan ke detail trip `/trips/[id]`.
Organizer **tidak** bisa join trip sendiri; di detail trip tombol join diganti pesan bahwa user adalah organizer.
### 3. Peserta: mencari trip dan join
1. **Beranda** (`/`) dan **Open Trip** (`/trips`) menampilkan trip dengan status **OPEN** dan tanggal berangkat tidak di masa lalu (`tripService.getOpenTrips` + filter di repository).
2. Filter pencarian (`TripFilter`) mengirim query string; daftar trip disaring di server.
3. Dari kartu trip (`TripCard`), pengguna membuka **detail** `/trips/[id]` (`app/trips/[id]/page.tsx`):
- Peserta aktif = baris `TripParticipant` yang statusnya bukan `CANCELLED`.
- Slot tersisa dan progress bar memakai jumlah peserta aktif tersebut.
4. **Join** (`JoinTripButton` + `joinTripAction`):
- Jika belum login: tautan ke `/login`.
- Jika trip bukan `OPEN` dan user belum join: pendaftaran ditutup (kecuali user sudah terdaftar dan ingin membatalkan, mengikuti logika UI).
- `tripService.joinTrip` memeriksa: trip ada, status `OPEN`, bukan organizer, belum terdaftar aktif, kapasitas belum penuh; lalu menambah atau mengaktifkan kembali partisipasi (lihat bagian perbaikan bug di bawah).
5. Jika jumlah peserta aktif mencapai `maxParticipants`, status trip diperbarui menjadi **FULL**.
6. **Batal ikut** memanggil `cancelJoinAction``tripService.cancelJoin`: partisipasi ditandai `CANCELLED`; jika trip sebelumnya `FULL` dan setelah batal slot kosong lagi, status dikembalikan ke **OPEN**.
### 4. Ringkasan peran data
| Konsep | Penyimpanan |
|--------|-------------|
| Trip | Model `Trip` (judul, gunung, lokasi, tanggal, kuota, harga, status, relasi ke organizer) |
| Peserta | `TripParticipant` unik per `(tripId, userId)` dengan status `CONFIRMED` / `CANCELLED` (default schema juga mengenal `PENDING`; alur UI saat ini memakai `CONFIRMED` saat join) |
## Menjalankan secara lokal
Pastikan PostgreSQL berjalan dan variabel `DATABASE_URL` di `.env` mengarah ke database yang valid.
```bash
npm install
npx prisma migrate dev
npm run seed # opsional: data contoh
npm run dev
```
Buka [http://localhost:3000](http://localhost:3000).
## Perbaikan bug (yang relevan dengan join & listing)
1. **Join lagi setelah “Batal ikut”**
Satu user hanya boleh satu baris partisipasi per trip (`@@unique([tripId, userId])`). Kode lama mencoba `create` lagi setelah status `CANCELLED`, sehingga bisa gagal dengan pelanggaran unik. Sekarang jika sudah ada baris `CANCELLED`, partisipasi **diaktifkan kembali** (`CONFIRMED`) lewat `participantRepo.reactivate`, bukan insert baru.
2. **Jumlah peserta di kartu / daftar**
`_count.participants` di query listing sebelumnya menghitung semua baris termasuk yang `CANCELLED`, sehingga “slot tersisa” di `TripCard` bisa salah. Count sekarang hanya menghitung peserta dengan status **bukan** `CANCELLED`.
3. **Segar halaman setelah join/batal/buat trip**
Setelah aksi trip, cache halaman `/trips` dan `/` ikut di-`revalidatePath` agar jumlah slot dan daftar di beranda konsisten tanpa harus refresh manual.
## Learn More
- [Next.js Documentation](https://nextjs.org/docs)
- [Prisma Documentation](https://www.prisma.io/docs)