78 lines
4.6 KiB
Markdown
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)
|