add payment, trust badge, handle race condition, fix booking schema

This commit is contained in:
arifal
2026-04-20 23:57:31 +07:00
parent ba5f64ae0e
commit fcdca34460
33 changed files with 1781 additions and 138 deletions
+23
View File
@@ -213,6 +213,29 @@ Database
--- ---
## Phase 3+ (SeTrip saat ini — booking, detail, trust)
Alur data mengikuti pola yang sama: **UI (`app/`) → server actions (`features/*/actions.ts`) → service (`server/services`) → repository (`server/repositories`)**.
### Booking & pembayaran manual (`features/booking/`)
- **Peserta:** tombol *Saya sudah bayar* menulis `TripParticipant.markedPaidAt` (komitmen transfer manual).
- **Organizer:** panel *Konfirmasi pembayaran* memanggil service yang mengisi `paymentConfirmedAt`.
- Tetap **tanpa payment gateway**; bukti transfer bisa di luar app (WA) sesuai kebutuhan.
### Detail trip kuat (`Trip` + halaman detail)
- Field terstruktur: `meetingPoint`, `itinerary`, `whatsIncluded`, `whatsExcluded` (teks bebas / bullet).
- Form buat trip: `features/trip/schemas.ts` + `app/create-trip/page.tsx`.
### Trust & organizer (`server/services/trust.service.ts`)
- **Verified:** kolom `User.isVerified` (default false; set manual / seed / admin ke depan).
- **Trip leader:** heuristik `jumlah trip dibuat ≥ TRIP_LEADER_MIN_TRIPS` (`lib/trust.ts`).
- **Jumlah trip dibuat & rating organizer:** dihitung agregat dari DB (rating = rata-rata `TripReview` pada semua trip sang organizer).
---
# 🧠 Final Principle # 🧠 Final Principle
> Build fast → validate → refactor later > Build fast → validate → refactor later
+79 -29
View File
@@ -9,43 +9,93 @@ Stack: [Next.js](https://nextjs.org) (App Router), NextAuth, Prisma (PostgreSQL)
### 1. Autentikasi ### 1. Autentikasi
- Pengguna baru mendaftar di `/register` (nama, email, password disimpan di database). - 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). - 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. Tanpa login, pengguna tetap bisa melihat daftar trip dan detail trip, tetapi tidak bisa membuat trip atau join.
### 2. Organizer: membuat trip ### 2. Organizer: membuat trip
1. Setelah login, organizer membuka **Buat Trip** (`/create-trip`) dari navbar, halaman `/trips`, beranda, atau tombol mengambang (+). 1. Setelah login, organizer membuka **Buat Trip** (`/create-trip`) dari navbar, `/trips`, beranda, atau tombol (+).
2. Halaman form (`app/create-trip/page.tsx`) memvalidasi sesi di client; jika belum login, ditampilkan ajakan login. 2. Form memvalidasi sesi; 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`). 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 memanggil server action `createTripAction` (`features/trip/actions.ts`): 4. Submit `createTripAction` → validasi Zod → `tripService.createTrip` menulis `Trip` ke database (status default **OPEN**) beserta gambar jika ada.
- Memastikan ada sesi. 5. Pengguna diarahkan ke detail trip `/trips/[id]`.
- 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. Organizer **tidak** bisa join trip sendiri; di detail trip ditampilkan bahwa dia adalah organizer trip ini.
### 3. Peserta: mencari trip dan join ### 3. Peserta: mencari trip
1. **Beranda** (`/`) dan **Open Trip** (`/trips`) menampilkan trip dengan status **OPEN** dan tanggal berangkat tidak di masa lalu (`tripService.getOpenTrips` + filter di repository). 1. **Beranda** (`/`) dan **Open Trip** (`/trips`) menampilkan trip **OPEN** dengan tanggal berangkat yang masih relevan (`tripService.getOpenTrips`).
2. Filter pencarian (`TripFilter`) mengirim query string; daftar trip disaring di server. 2. Filter pencarian (`TripFilter`) mengirim query string (`q`, `from`, `to`); penyaringan dilakukan di server.
3. Dari kartu trip (`TripCard`), pengguna membuka **detail** `/trips/[id]` (`app/trips/[id]/page.tsx`): 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.
- 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 ---
### 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).
```text
[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 Sekarang**`joinTripAction``tripService.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 **Tolak****`CANCELLED`**.
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 | | Konsep | Penyimpanan |
|--------|-------------| |--------|-------------|
| Trip | Model `Trip` (judul, gunung, lokasi, tanggal, kuota, harga, status, relasi ke organizer) | | 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)` dengan status `CONFIRMED` / `CANCELLED` (default schema juga mengenal `PENDING`; alur UI saat ini memakai `CONFIRMED` saat join) | | Peserta | `TripParticipant` unik per `(tripId, userId)`: status **`PENDING`** / **`CONFIRMED`** / **`CANCELLED`**, serta **`markedPaidAt`** & **`paymentConfirmedAt`** untuk alur bayar manual |
| Organizer (kepercayaan) | `User.isVerified`; agregat rating & jumlah trip dibuat dihitung dari data ulasan & trip |
## Menjalankan secara lokal ## Menjalankan secara lokal
@@ -63,13 +113,13 @@ Buka [http://localhost:3000](http://localhost:3000).
## Perbaikan bug (yang relevan dengan join & listing) ## Perbaikan bug (yang relevan dengan join & listing)
1. **Join lagi setelah “Batal ikut”** 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. 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** 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`. `_count.participants` hanya menghitung status **bukan** `CANCELLED`, agar slot dan label “penuh” konsisten.
3. **Segar halaman setelah join/batal/buat trip** 3. **Segar halaman setelah aksi**
Setelah aksi trip, cache halaman `/trips` dan `/` ikut di-`revalidatePath` agar jumlah slot dan daftar di beranda konsisten tanpa harus refresh manual. `revalidatePath` dipanggil setelah join, batal, buat trip, setujui/tolak peserta, dan konfirmasi pembayaran agar daftar dan detail konsisten.
## Learn More ## Learn More
+56 -9
View File
@@ -73,7 +73,7 @@ export default function CreateTripPage() {
setLoading(true); setLoading(true);
const formData = new FormData(e.currentTarget); const formData = new FormData(e.currentTarget);
// Hari kalender lokal → YYYY-MM-DD (bukan toISOString, supaya tidak geser ke UTC) // Tanggal dari picker → string tanggal untuk server action
formData.set("date", formatLocalCalendarYmd(startDate)); formData.set("date", formatLocalCalendarYmd(startDate));
if (endDate) { if (endDate) {
const startYmd = formatLocalCalendarYmd(startDate); const startYmd = formatLocalCalendarYmd(startDate);
@@ -215,10 +215,63 @@ export default function CreateTripPage() {
name="description" name="description"
rows={4} rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white" className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Detail trip, itinerary, meeting point, fasilitas..." placeholder="Ringkasan trip, vibe, level kesulitan..."
/> />
</div> </div>
<div>
<label htmlFor="meetingPoint" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Meeting point
</label>
<input
id="meetingPoint"
name="meetingPoint"
type="text"
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="contoh: Alfamart Cicaheum, 05:00 WIB"
/>
</div>
<div>
<label htmlFor="itinerary" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Itinerary
</label>
<textarea
id="itinerary"
name="itinerary"
rows={5}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder={"Hari 1: …\nHari 2: …"}
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label htmlFor="whatsIncluded" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Termasuk
</label>
<textarea
id="whatsIncluded"
name="whatsIncluded"
rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Transport, konsumsi, tenda, …"
/>
</div>
<div>
<label htmlFor="whatsExcluded" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Tidak termasuk
</label>
<textarea
id="whatsExcluded"
name="whatsExcluded"
rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Tiket masuk TN, sleeping bag, …"
/>
</div>
</div>
<ImageUrlInput /> <ImageUrlInput />
{/* Date Range & Participants & Price */} {/* Date Range & Participants & Price */}
@@ -226,14 +279,8 @@ export default function CreateTripPage() {
{/* Date Range Picker */} {/* Date Range Picker */}
<div> <div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700"> <label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Tanggal Berangkat Pulang Tanggal berangkat pulang
</label> </label>
<p className="mb-1.5 text-[11px] leading-snug text-neutral-500 sm:text-xs">
Pilih satu tanggal untuk trip <span className="font-medium">satu hari</span>
. Pilih rentang untuk trip <span className="font-medium">lebih dari satu hari</span>
. Tanggal disimpan sebagai hari kalender yang kamu klik; filter Open Trip memakai{" "}
<span className="font-medium">UTC</span> yang sama.
</p>
<div className="relative"> <div className="relative">
<span className="absolute left-3 top-1/2 z-10 -translate-y-1/2 text-neutral-400"> <span className="absolute left-3 top-1/2 z-10 -translate-y-1/2 text-neutral-400">
<svg <svg
+26
View File
@@ -44,6 +44,11 @@ export type StringNullableFilter<$PrismaModel = never> = {
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
} }
export type BoolFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
}
export type DateTimeFilter<$PrismaModel = never> = { export type DateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
@@ -96,6 +101,14 @@ export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedStringNullableFilter<$PrismaModel> _max?: Prisma.NestedStringNullableFilter<$PrismaModel>
} }
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedBoolFilter<$PrismaModel>
_max?: Prisma.NestedBoolFilter<$PrismaModel>
}
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = { export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
@@ -224,6 +237,11 @@ export type NestedStringNullableFilter<$PrismaModel = never> = {
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
} }
export type NestedBoolFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
}
export type NestedDateTimeFilter<$PrismaModel = never> = { export type NestedDateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
@@ -291,6 +309,14 @@ export type NestedIntNullableFilter<$PrismaModel = never> = {
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
} }
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedBoolFilter<$PrismaModel>
_max?: Prisma.NestedBoolFilter<$PrismaModel>
}
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = { export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
File diff suppressed because one or more lines are too long
@@ -823,6 +823,7 @@ export const UserScalarFieldEnum = {
email: 'email', email: 'email',
password: 'password', password: 'password',
image: 'image', image: 'image',
isVerified: 'isVerified',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -836,6 +837,10 @@ export const TripScalarFieldEnum = {
description: 'description', description: 'description',
mountain: 'mountain', mountain: 'mountain',
location: 'location', location: 'location',
meetingPoint: 'meetingPoint',
itinerary: 'itinerary',
whatsIncluded: 'whatsIncluded',
whatsExcluded: 'whatsExcluded',
date: 'date', date: 'date',
endDate: 'endDate', endDate: 'endDate',
maxParticipants: 'maxParticipants', maxParticipants: 'maxParticipants',
@@ -877,6 +882,8 @@ export const TripParticipantScalarFieldEnum = {
id: 'id', id: 'id',
status: 'status', status: 'status',
createdAt: 'createdAt', createdAt: 'createdAt',
markedPaidAt: 'markedPaidAt',
paymentConfirmedAt: 'paymentConfirmedAt',
tripId: 'tripId', tripId: 'tripId',
userId: 'userId' userId: 'userId'
} as const } as const
@@ -928,6 +935,13 @@ export type ListStringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaMod
/**
* Reference to a field of type 'Boolean'
*/
export type BooleanFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Boolean'>
/** /**
* Reference to a field of type 'DateTime' * Reference to a field of type 'DateTime'
*/ */
@@ -80,6 +80,7 @@ export const UserScalarFieldEnum = {
email: 'email', email: 'email',
password: 'password', password: 'password',
image: 'image', image: 'image',
isVerified: 'isVerified',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -93,6 +94,10 @@ export const TripScalarFieldEnum = {
description: 'description', description: 'description',
mountain: 'mountain', mountain: 'mountain',
location: 'location', location: 'location',
meetingPoint: 'meetingPoint',
itinerary: 'itinerary',
whatsIncluded: 'whatsIncluded',
whatsExcluded: 'whatsExcluded',
date: 'date', date: 'date',
endDate: 'endDate', endDate: 'endDate',
maxParticipants: 'maxParticipants', maxParticipants: 'maxParticipants',
@@ -134,6 +139,8 @@ export const TripParticipantScalarFieldEnum = {
id: 'id', id: 'id',
status: 'status', status: 'status',
createdAt: 'createdAt', createdAt: 'createdAt',
markedPaidAt: 'markedPaidAt',
paymentConfirmedAt: 'paymentConfirmedAt',
tripId: 'tripId', tripId: 'tripId',
userId: 'userId' userId: 'userId'
} as const } as const
+201 -1
View File
@@ -42,6 +42,10 @@ export type TripMinAggregateOutputType = {
description: string | null description: string | null
mountain: string | null mountain: string | null
location: string | null location: string | null
meetingPoint: string | null
itinerary: string | null
whatsIncluded: string | null
whatsExcluded: string | null
date: Date | null date: Date | null
endDate: Date | null endDate: Date | null
maxParticipants: number | null maxParticipants: number | null
@@ -58,6 +62,10 @@ export type TripMaxAggregateOutputType = {
description: string | null description: string | null
mountain: string | null mountain: string | null
location: string | null location: string | null
meetingPoint: string | null
itinerary: string | null
whatsIncluded: string | null
whatsExcluded: string | null
date: Date | null date: Date | null
endDate: Date | null endDate: Date | null
maxParticipants: number | null maxParticipants: number | null
@@ -74,6 +82,10 @@ export type TripCountAggregateOutputType = {
description: number description: number
mountain: number mountain: number
location: number location: number
meetingPoint: number
itinerary: number
whatsIncluded: number
whatsExcluded: number
date: number date: number
endDate: number endDate: number
maxParticipants: number maxParticipants: number
@@ -102,6 +114,10 @@ export type TripMinAggregateInputType = {
description?: true description?: true
mountain?: true mountain?: true
location?: true location?: true
meetingPoint?: true
itinerary?: true
whatsIncluded?: true
whatsExcluded?: true
date?: true date?: true
endDate?: true endDate?: true
maxParticipants?: true maxParticipants?: true
@@ -118,6 +134,10 @@ export type TripMaxAggregateInputType = {
description?: true description?: true
mountain?: true mountain?: true
location?: true location?: true
meetingPoint?: true
itinerary?: true
whatsIncluded?: true
whatsExcluded?: true
date?: true date?: true
endDate?: true endDate?: true
maxParticipants?: true maxParticipants?: true
@@ -134,6 +154,10 @@ export type TripCountAggregateInputType = {
description?: true description?: true
mountain?: true mountain?: true
location?: true location?: true
meetingPoint?: true
itinerary?: true
whatsIncluded?: true
whatsExcluded?: true
date?: true date?: true
endDate?: true endDate?: true
maxParticipants?: true maxParticipants?: true
@@ -237,6 +261,10 @@ export type TripGroupByOutputType = {
description: string | null description: string | null
mountain: string mountain: string
location: string location: string
meetingPoint: string | null
itinerary: string | null
whatsIncluded: string | null
whatsExcluded: string | null
date: Date date: Date
endDate: Date | null endDate: Date | null
maxParticipants: number maxParticipants: number
@@ -276,6 +304,10 @@ export type TripWhereInput = {
description?: Prisma.StringNullableFilter<"Trip"> | string | null description?: Prisma.StringNullableFilter<"Trip"> | string | null
mountain?: Prisma.StringFilter<"Trip"> | string mountain?: Prisma.StringFilter<"Trip"> | string
location?: Prisma.StringFilter<"Trip"> | string location?: Prisma.StringFilter<"Trip"> | string
meetingPoint?: Prisma.StringNullableFilter<"Trip"> | string | null
itinerary?: Prisma.StringNullableFilter<"Trip"> | string | null
whatsIncluded?: Prisma.StringNullableFilter<"Trip"> | string | null
whatsExcluded?: Prisma.StringNullableFilter<"Trip"> | string | null
date?: Prisma.DateTimeFilter<"Trip"> | Date | string date?: Prisma.DateTimeFilter<"Trip"> | Date | string
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
maxParticipants?: Prisma.IntFilter<"Trip"> | number maxParticipants?: Prisma.IntFilter<"Trip"> | number
@@ -296,6 +328,10 @@ export type TripOrderByWithRelationInput = {
description?: Prisma.SortOrderInput | Prisma.SortOrder description?: Prisma.SortOrderInput | Prisma.SortOrder
mountain?: Prisma.SortOrder mountain?: Prisma.SortOrder
location?: Prisma.SortOrder location?: Prisma.SortOrder
meetingPoint?: Prisma.SortOrderInput | Prisma.SortOrder
itinerary?: Prisma.SortOrderInput | Prisma.SortOrder
whatsIncluded?: Prisma.SortOrderInput | Prisma.SortOrder
whatsExcluded?: Prisma.SortOrderInput | Prisma.SortOrder
date?: Prisma.SortOrder date?: Prisma.SortOrder
endDate?: Prisma.SortOrderInput | Prisma.SortOrder endDate?: Prisma.SortOrderInput | Prisma.SortOrder
maxParticipants?: Prisma.SortOrder maxParticipants?: Prisma.SortOrder
@@ -319,6 +355,10 @@ export type TripWhereUniqueInput = Prisma.AtLeast<{
description?: Prisma.StringNullableFilter<"Trip"> | string | null description?: Prisma.StringNullableFilter<"Trip"> | string | null
mountain?: Prisma.StringFilter<"Trip"> | string mountain?: Prisma.StringFilter<"Trip"> | string
location?: Prisma.StringFilter<"Trip"> | string location?: Prisma.StringFilter<"Trip"> | string
meetingPoint?: Prisma.StringNullableFilter<"Trip"> | string | null
itinerary?: Prisma.StringNullableFilter<"Trip"> | string | null
whatsIncluded?: Prisma.StringNullableFilter<"Trip"> | string | null
whatsExcluded?: Prisma.StringNullableFilter<"Trip"> | string | null
date?: Prisma.DateTimeFilter<"Trip"> | Date | string date?: Prisma.DateTimeFilter<"Trip"> | Date | string
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
maxParticipants?: Prisma.IntFilter<"Trip"> | number maxParticipants?: Prisma.IntFilter<"Trip"> | number
@@ -339,6 +379,10 @@ export type TripOrderByWithAggregationInput = {
description?: Prisma.SortOrderInput | Prisma.SortOrder description?: Prisma.SortOrderInput | Prisma.SortOrder
mountain?: Prisma.SortOrder mountain?: Prisma.SortOrder
location?: Prisma.SortOrder location?: Prisma.SortOrder
meetingPoint?: Prisma.SortOrderInput | Prisma.SortOrder
itinerary?: Prisma.SortOrderInput | Prisma.SortOrder
whatsIncluded?: Prisma.SortOrderInput | Prisma.SortOrder
whatsExcluded?: Prisma.SortOrderInput | Prisma.SortOrder
date?: Prisma.SortOrder date?: Prisma.SortOrder
endDate?: Prisma.SortOrderInput | Prisma.SortOrder endDate?: Prisma.SortOrderInput | Prisma.SortOrder
maxParticipants?: Prisma.SortOrder maxParticipants?: Prisma.SortOrder
@@ -363,6 +407,10 @@ export type TripScalarWhereWithAggregatesInput = {
description?: Prisma.StringNullableWithAggregatesFilter<"Trip"> | string | null description?: Prisma.StringNullableWithAggregatesFilter<"Trip"> | string | null
mountain?: Prisma.StringWithAggregatesFilter<"Trip"> | string mountain?: Prisma.StringWithAggregatesFilter<"Trip"> | string
location?: Prisma.StringWithAggregatesFilter<"Trip"> | string location?: Prisma.StringWithAggregatesFilter<"Trip"> | string
meetingPoint?: Prisma.StringNullableWithAggregatesFilter<"Trip"> | string | null
itinerary?: Prisma.StringNullableWithAggregatesFilter<"Trip"> | string | null
whatsIncluded?: Prisma.StringNullableWithAggregatesFilter<"Trip"> | string | null
whatsExcluded?: Prisma.StringNullableWithAggregatesFilter<"Trip"> | string | null
date?: Prisma.DateTimeWithAggregatesFilter<"Trip"> | Date | string date?: Prisma.DateTimeWithAggregatesFilter<"Trip"> | Date | string
endDate?: Prisma.DateTimeNullableWithAggregatesFilter<"Trip"> | Date | string | null endDate?: Prisma.DateTimeNullableWithAggregatesFilter<"Trip"> | Date | string | null
maxParticipants?: Prisma.IntWithAggregatesFilter<"Trip"> | number maxParticipants?: Prisma.IntWithAggregatesFilter<"Trip"> | number
@@ -379,6 +427,10 @@ export type TripCreateInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -398,6 +450,10 @@ export type TripUncheckedCreateInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -417,6 +473,10 @@ export type TripUpdateInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -436,6 +496,10 @@ export type TripUncheckedUpdateInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -455,6 +519,10 @@ export type TripCreateManyInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -471,6 +539,10 @@ export type TripUpdateManyMutationInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -486,6 +558,10 @@ export type TripUncheckedUpdateManyInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -512,6 +588,10 @@ export type TripCountOrderByAggregateInput = {
description?: Prisma.SortOrder description?: Prisma.SortOrder
mountain?: Prisma.SortOrder mountain?: Prisma.SortOrder
location?: Prisma.SortOrder location?: Prisma.SortOrder
meetingPoint?: Prisma.SortOrder
itinerary?: Prisma.SortOrder
whatsIncluded?: Prisma.SortOrder
whatsExcluded?: Prisma.SortOrder
date?: Prisma.SortOrder date?: Prisma.SortOrder
endDate?: Prisma.SortOrder endDate?: Prisma.SortOrder
maxParticipants?: Prisma.SortOrder maxParticipants?: Prisma.SortOrder
@@ -533,6 +613,10 @@ export type TripMaxOrderByAggregateInput = {
description?: Prisma.SortOrder description?: Prisma.SortOrder
mountain?: Prisma.SortOrder mountain?: Prisma.SortOrder
location?: Prisma.SortOrder location?: Prisma.SortOrder
meetingPoint?: Prisma.SortOrder
itinerary?: Prisma.SortOrder
whatsIncluded?: Prisma.SortOrder
whatsExcluded?: Prisma.SortOrder
date?: Prisma.SortOrder date?: Prisma.SortOrder
endDate?: Prisma.SortOrder endDate?: Prisma.SortOrder
maxParticipants?: Prisma.SortOrder maxParticipants?: Prisma.SortOrder
@@ -549,6 +633,10 @@ export type TripMinOrderByAggregateInput = {
description?: Prisma.SortOrder description?: Prisma.SortOrder
mountain?: Prisma.SortOrder mountain?: Prisma.SortOrder
location?: Prisma.SortOrder location?: Prisma.SortOrder
meetingPoint?: Prisma.SortOrder
itinerary?: Prisma.SortOrder
whatsIncluded?: Prisma.SortOrder
whatsExcluded?: Prisma.SortOrder
date?: Prisma.SortOrder date?: Prisma.SortOrder
endDate?: Prisma.SortOrder endDate?: Prisma.SortOrder
maxParticipants?: Prisma.SortOrder maxParticipants?: Prisma.SortOrder
@@ -675,6 +763,10 @@ export type TripCreateWithoutOrganizerInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -693,6 +785,10 @@ export type TripUncheckedCreateWithoutOrganizerInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -740,6 +836,10 @@ export type TripScalarWhereInput = {
description?: Prisma.StringNullableFilter<"Trip"> | string | null description?: Prisma.StringNullableFilter<"Trip"> | string | null
mountain?: Prisma.StringFilter<"Trip"> | string mountain?: Prisma.StringFilter<"Trip"> | string
location?: Prisma.StringFilter<"Trip"> | string location?: Prisma.StringFilter<"Trip"> | string
meetingPoint?: Prisma.StringNullableFilter<"Trip"> | string | null
itinerary?: Prisma.StringNullableFilter<"Trip"> | string | null
whatsIncluded?: Prisma.StringNullableFilter<"Trip"> | string | null
whatsExcluded?: Prisma.StringNullableFilter<"Trip"> | string | null
date?: Prisma.DateTimeFilter<"Trip"> | Date | string date?: Prisma.DateTimeFilter<"Trip"> | Date | string
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
maxParticipants?: Prisma.IntFilter<"Trip"> | number maxParticipants?: Prisma.IntFilter<"Trip"> | number
@@ -756,6 +856,10 @@ export type TripCreateWithoutReviewsInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -774,6 +878,10 @@ export type TripUncheckedCreateWithoutReviewsInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -808,6 +916,10 @@ export type TripUpdateWithoutReviewsInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -826,6 +938,10 @@ export type TripUncheckedUpdateWithoutReviewsInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -844,6 +960,10 @@ export type TripCreateWithoutImagesInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -862,6 +982,10 @@ export type TripUncheckedCreateWithoutImagesInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -896,6 +1020,10 @@ export type TripUpdateWithoutImagesInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -914,6 +1042,10 @@ export type TripUncheckedUpdateWithoutImagesInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -932,6 +1064,10 @@ export type TripCreateWithoutParticipantsInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -950,6 +1086,10 @@ export type TripUncheckedCreateWithoutParticipantsInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -984,6 +1124,10 @@ export type TripUpdateWithoutParticipantsInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -1002,6 +1146,10 @@ export type TripUncheckedUpdateWithoutParticipantsInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -1020,6 +1168,10 @@ export type TripCreateManyOrganizerInput = {
description?: string | null description?: string | null
mountain: string mountain: string
location: string location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string date: Date | string
endDate?: Date | string | null endDate?: Date | string | null
maxParticipants: number maxParticipants: number
@@ -1035,6 +1187,10 @@ export type TripUpdateWithoutOrganizerInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -1053,6 +1209,10 @@ export type TripUncheckedUpdateWithoutOrganizerInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -1071,6 +1231,10 @@ export type TripUncheckedUpdateManyWithoutOrganizerInput = {
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
mountain?: Prisma.StringFieldUpdateOperationsInput | string mountain?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
@@ -1135,6 +1299,10 @@ export type TripSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
description?: boolean description?: boolean
mountain?: boolean mountain?: boolean
location?: boolean location?: boolean
meetingPoint?: boolean
itinerary?: boolean
whatsIncluded?: boolean
whatsExcluded?: boolean
date?: boolean date?: boolean
endDate?: boolean endDate?: boolean
maxParticipants?: boolean maxParticipants?: boolean
@@ -1156,6 +1324,10 @@ export type TripSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensio
description?: boolean description?: boolean
mountain?: boolean mountain?: boolean
location?: boolean location?: boolean
meetingPoint?: boolean
itinerary?: boolean
whatsIncluded?: boolean
whatsExcluded?: boolean
date?: boolean date?: boolean
endDate?: boolean endDate?: boolean
maxParticipants?: boolean maxParticipants?: boolean
@@ -1173,6 +1345,10 @@ export type TripSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensio
description?: boolean description?: boolean
mountain?: boolean mountain?: boolean
location?: boolean location?: boolean
meetingPoint?: boolean
itinerary?: boolean
whatsIncluded?: boolean
whatsExcluded?: boolean
date?: boolean date?: boolean
endDate?: boolean endDate?: boolean
maxParticipants?: boolean maxParticipants?: boolean
@@ -1190,6 +1366,10 @@ export type TripSelectScalar = {
description?: boolean description?: boolean
mountain?: boolean mountain?: boolean
location?: boolean location?: boolean
meetingPoint?: boolean
itinerary?: boolean
whatsIncluded?: boolean
whatsExcluded?: boolean
date?: boolean date?: boolean
endDate?: boolean endDate?: boolean
maxParticipants?: boolean maxParticipants?: boolean
@@ -1200,7 +1380,7 @@ export type TripSelectScalar = {
organizerId?: boolean organizerId?: boolean
} }
export type TripOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "title" | "description" | "mountain" | "location" | "date" | "endDate" | "maxParticipants" | "price" | "status" | "createdAt" | "updatedAt" | "organizerId", ExtArgs["result"]["trip"]> export type TripOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "title" | "description" | "mountain" | "location" | "meetingPoint" | "itinerary" | "whatsIncluded" | "whatsExcluded" | "date" | "endDate" | "maxParticipants" | "price" | "status" | "createdAt" | "updatedAt" | "organizerId", ExtArgs["result"]["trip"]>
export type TripInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type TripInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
organizer?: boolean | Prisma.UserDefaultArgs<ExtArgs> organizer?: boolean | Prisma.UserDefaultArgs<ExtArgs>
participants?: boolean | Prisma.Trip$participantsArgs<ExtArgs> participants?: boolean | Prisma.Trip$participantsArgs<ExtArgs>
@@ -1229,6 +1409,22 @@ export type $TripPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
description: string | null description: string | null
mountain: string mountain: string
location: string location: string
/**
* Titik kumpul / meeting point (teks bebas)
*/
meetingPoint: string | null
/**
* Itinerary hari per hari (teks bebas, bullet OK)
*/
itinerary: string | null
/**
* Yang termasuk harga (teks bebas)
*/
whatsIncluded: string | null
/**
* Yang tidak termasuk (teks bebas)
*/
whatsExcluded: string | null
date: Date date: Date
endDate: Date | null endDate: Date | null
maxParticipants: number maxParticipants: number
@@ -1669,6 +1865,10 @@ export interface TripFieldRefs {
readonly description: Prisma.FieldRef<"Trip", 'String'> readonly description: Prisma.FieldRef<"Trip", 'String'>
readonly mountain: Prisma.FieldRef<"Trip", 'String'> readonly mountain: Prisma.FieldRef<"Trip", 'String'>
readonly location: Prisma.FieldRef<"Trip", 'String'> readonly location: Prisma.FieldRef<"Trip", 'String'>
readonly meetingPoint: Prisma.FieldRef<"Trip", 'String'>
readonly itinerary: Prisma.FieldRef<"Trip", 'String'>
readonly whatsIncluded: Prisma.FieldRef<"Trip", 'String'>
readonly whatsExcluded: Prisma.FieldRef<"Trip", 'String'>
readonly date: Prisma.FieldRef<"Trip", 'DateTime'> readonly date: Prisma.FieldRef<"Trip", 'DateTime'>
readonly endDate: Prisma.FieldRef<"Trip", 'DateTime'> readonly endDate: Prisma.FieldRef<"Trip", 'DateTime'>
readonly maxParticipants: Prisma.FieldRef<"Trip", 'Int'> readonly maxParticipants: Prisma.FieldRef<"Trip", 'Int'>
+89 -1
View File
@@ -28,6 +28,8 @@ export type TripParticipantMinAggregateOutputType = {
id: string | null id: string | null
status: $Enums.ParticipantStatus | null status: $Enums.ParticipantStatus | null
createdAt: Date | null createdAt: Date | null
markedPaidAt: Date | null
paymentConfirmedAt: Date | null
tripId: string | null tripId: string | null
userId: string | null userId: string | null
} }
@@ -36,6 +38,8 @@ export type TripParticipantMaxAggregateOutputType = {
id: string | null id: string | null
status: $Enums.ParticipantStatus | null status: $Enums.ParticipantStatus | null
createdAt: Date | null createdAt: Date | null
markedPaidAt: Date | null
paymentConfirmedAt: Date | null
tripId: string | null tripId: string | null
userId: string | null userId: string | null
} }
@@ -44,6 +48,8 @@ export type TripParticipantCountAggregateOutputType = {
id: number id: number
status: number status: number
createdAt: number createdAt: number
markedPaidAt: number
paymentConfirmedAt: number
tripId: number tripId: number
userId: number userId: number
_all: number _all: number
@@ -54,6 +60,8 @@ export type TripParticipantMinAggregateInputType = {
id?: true id?: true
status?: true status?: true
createdAt?: true createdAt?: true
markedPaidAt?: true
paymentConfirmedAt?: true
tripId?: true tripId?: true
userId?: true userId?: true
} }
@@ -62,6 +70,8 @@ export type TripParticipantMaxAggregateInputType = {
id?: true id?: true
status?: true status?: true
createdAt?: true createdAt?: true
markedPaidAt?: true
paymentConfirmedAt?: true
tripId?: true tripId?: true
userId?: true userId?: true
} }
@@ -70,6 +80,8 @@ export type TripParticipantCountAggregateInputType = {
id?: true id?: true
status?: true status?: true
createdAt?: true createdAt?: true
markedPaidAt?: true
paymentConfirmedAt?: true
tripId?: true tripId?: true
userId?: true userId?: true
_all?: true _all?: true
@@ -151,6 +163,8 @@ export type TripParticipantGroupByOutputType = {
id: string id: string
status: $Enums.ParticipantStatus status: $Enums.ParticipantStatus
createdAt: Date createdAt: Date
markedPaidAt: Date | null
paymentConfirmedAt: Date | null
tripId: string tripId: string
userId: string userId: string
_count: TripParticipantCountAggregateOutputType | null _count: TripParticipantCountAggregateOutputType | null
@@ -180,6 +194,8 @@ export type TripParticipantWhereInput = {
id?: Prisma.StringFilter<"TripParticipant"> | string id?: Prisma.StringFilter<"TripParticipant"> | string
status?: Prisma.EnumParticipantStatusFilter<"TripParticipant"> | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFilter<"TripParticipant"> | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFilter<"TripParticipant"> | Date | string createdAt?: Prisma.DateTimeFilter<"TripParticipant"> | Date | string
markedPaidAt?: Prisma.DateTimeNullableFilter<"TripParticipant"> | Date | string | null
paymentConfirmedAt?: Prisma.DateTimeNullableFilter<"TripParticipant"> | Date | string | null
tripId?: Prisma.StringFilter<"TripParticipant"> | string tripId?: Prisma.StringFilter<"TripParticipant"> | string
userId?: Prisma.StringFilter<"TripParticipant"> | string userId?: Prisma.StringFilter<"TripParticipant"> | string
trip?: Prisma.XOR<Prisma.TripScalarRelationFilter, Prisma.TripWhereInput> trip?: Prisma.XOR<Prisma.TripScalarRelationFilter, Prisma.TripWhereInput>
@@ -190,6 +206,8 @@ export type TripParticipantOrderByWithRelationInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
status?: Prisma.SortOrder status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
markedPaidAt?: Prisma.SortOrderInput | Prisma.SortOrder
paymentConfirmedAt?: Prisma.SortOrderInput | Prisma.SortOrder
tripId?: Prisma.SortOrder tripId?: Prisma.SortOrder
userId?: Prisma.SortOrder userId?: Prisma.SortOrder
trip?: Prisma.TripOrderByWithRelationInput trip?: Prisma.TripOrderByWithRelationInput
@@ -204,6 +222,8 @@ export type TripParticipantWhereUniqueInput = Prisma.AtLeast<{
NOT?: Prisma.TripParticipantWhereInput | Prisma.TripParticipantWhereInput[] NOT?: Prisma.TripParticipantWhereInput | Prisma.TripParticipantWhereInput[]
status?: Prisma.EnumParticipantStatusFilter<"TripParticipant"> | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFilter<"TripParticipant"> | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFilter<"TripParticipant"> | Date | string createdAt?: Prisma.DateTimeFilter<"TripParticipant"> | Date | string
markedPaidAt?: Prisma.DateTimeNullableFilter<"TripParticipant"> | Date | string | null
paymentConfirmedAt?: Prisma.DateTimeNullableFilter<"TripParticipant"> | Date | string | null
tripId?: Prisma.StringFilter<"TripParticipant"> | string tripId?: Prisma.StringFilter<"TripParticipant"> | string
userId?: Prisma.StringFilter<"TripParticipant"> | string userId?: Prisma.StringFilter<"TripParticipant"> | string
trip?: Prisma.XOR<Prisma.TripScalarRelationFilter, Prisma.TripWhereInput> trip?: Prisma.XOR<Prisma.TripScalarRelationFilter, Prisma.TripWhereInput>
@@ -214,6 +234,8 @@ export type TripParticipantOrderByWithAggregationInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
status?: Prisma.SortOrder status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
markedPaidAt?: Prisma.SortOrderInput | Prisma.SortOrder
paymentConfirmedAt?: Prisma.SortOrderInput | Prisma.SortOrder
tripId?: Prisma.SortOrder tripId?: Prisma.SortOrder
userId?: Prisma.SortOrder userId?: Prisma.SortOrder
_count?: Prisma.TripParticipantCountOrderByAggregateInput _count?: Prisma.TripParticipantCountOrderByAggregateInput
@@ -228,6 +250,8 @@ export type TripParticipantScalarWhereWithAggregatesInput = {
id?: Prisma.StringWithAggregatesFilter<"TripParticipant"> | string id?: Prisma.StringWithAggregatesFilter<"TripParticipant"> | string
status?: Prisma.EnumParticipantStatusWithAggregatesFilter<"TripParticipant"> | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusWithAggregatesFilter<"TripParticipant"> | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeWithAggregatesFilter<"TripParticipant"> | Date | string createdAt?: Prisma.DateTimeWithAggregatesFilter<"TripParticipant"> | Date | string
markedPaidAt?: Prisma.DateTimeNullableWithAggregatesFilter<"TripParticipant"> | Date | string | null
paymentConfirmedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"TripParticipant"> | Date | string | null
tripId?: Prisma.StringWithAggregatesFilter<"TripParticipant"> | string tripId?: Prisma.StringWithAggregatesFilter<"TripParticipant"> | string
userId?: Prisma.StringWithAggregatesFilter<"TripParticipant"> | string userId?: Prisma.StringWithAggregatesFilter<"TripParticipant"> | string
} }
@@ -236,6 +260,8 @@ export type TripParticipantCreateInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
trip: Prisma.TripCreateNestedOneWithoutParticipantsInput trip: Prisma.TripCreateNestedOneWithoutParticipantsInput
user: Prisma.UserCreateNestedOneWithoutParticipationsInput user: Prisma.UserCreateNestedOneWithoutParticipationsInput
} }
@@ -244,6 +270,8 @@ export type TripParticipantUncheckedCreateInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
tripId: string tripId: string
userId: string userId: string
} }
@@ -252,6 +280,8 @@ export type TripParticipantUpdateInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
trip?: Prisma.TripUpdateOneRequiredWithoutParticipantsNestedInput trip?: Prisma.TripUpdateOneRequiredWithoutParticipantsNestedInput
user?: Prisma.UserUpdateOneRequiredWithoutParticipationsNestedInput user?: Prisma.UserUpdateOneRequiredWithoutParticipationsNestedInput
} }
@@ -260,6 +290,8 @@ export type TripParticipantUncheckedUpdateInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
tripId?: Prisma.StringFieldUpdateOperationsInput | string tripId?: Prisma.StringFieldUpdateOperationsInput | string
userId?: Prisma.StringFieldUpdateOperationsInput | string userId?: Prisma.StringFieldUpdateOperationsInput | string
} }
@@ -268,6 +300,8 @@ export type TripParticipantCreateManyInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
tripId: string tripId: string
userId: string userId: string
} }
@@ -276,12 +310,16 @@ export type TripParticipantUpdateManyMutationInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
} }
export type TripParticipantUncheckedUpdateManyInput = { export type TripParticipantUncheckedUpdateManyInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
tripId?: Prisma.StringFieldUpdateOperationsInput | string tripId?: Prisma.StringFieldUpdateOperationsInput | string
userId?: Prisma.StringFieldUpdateOperationsInput | string userId?: Prisma.StringFieldUpdateOperationsInput | string
} }
@@ -305,6 +343,8 @@ export type TripParticipantCountOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
status?: Prisma.SortOrder status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
markedPaidAt?: Prisma.SortOrder
paymentConfirmedAt?: Prisma.SortOrder
tripId?: Prisma.SortOrder tripId?: Prisma.SortOrder
userId?: Prisma.SortOrder userId?: Prisma.SortOrder
} }
@@ -313,6 +353,8 @@ export type TripParticipantMaxOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
status?: Prisma.SortOrder status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
markedPaidAt?: Prisma.SortOrder
paymentConfirmedAt?: Prisma.SortOrder
tripId?: Prisma.SortOrder tripId?: Prisma.SortOrder
userId?: Prisma.SortOrder userId?: Prisma.SortOrder
} }
@@ -321,6 +363,8 @@ export type TripParticipantMinOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
status?: Prisma.SortOrder status?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
markedPaidAt?: Prisma.SortOrder
paymentConfirmedAt?: Prisma.SortOrder
tripId?: Prisma.SortOrder tripId?: Prisma.SortOrder
userId?: Prisma.SortOrder userId?: Prisma.SortOrder
} }
@@ -417,6 +461,8 @@ export type TripParticipantCreateWithoutUserInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
trip: Prisma.TripCreateNestedOneWithoutParticipantsInput trip: Prisma.TripCreateNestedOneWithoutParticipantsInput
} }
@@ -424,6 +470,8 @@ export type TripParticipantUncheckedCreateWithoutUserInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
tripId: string tripId: string
} }
@@ -460,6 +508,8 @@ export type TripParticipantScalarWhereInput = {
id?: Prisma.StringFilter<"TripParticipant"> | string id?: Prisma.StringFilter<"TripParticipant"> | string
status?: Prisma.EnumParticipantStatusFilter<"TripParticipant"> | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFilter<"TripParticipant"> | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFilter<"TripParticipant"> | Date | string createdAt?: Prisma.DateTimeFilter<"TripParticipant"> | Date | string
markedPaidAt?: Prisma.DateTimeNullableFilter<"TripParticipant"> | Date | string | null
paymentConfirmedAt?: Prisma.DateTimeNullableFilter<"TripParticipant"> | Date | string | null
tripId?: Prisma.StringFilter<"TripParticipant"> | string tripId?: Prisma.StringFilter<"TripParticipant"> | string
userId?: Prisma.StringFilter<"TripParticipant"> | string userId?: Prisma.StringFilter<"TripParticipant"> | string
} }
@@ -468,6 +518,8 @@ export type TripParticipantCreateWithoutTripInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
user: Prisma.UserCreateNestedOneWithoutParticipationsInput user: Prisma.UserCreateNestedOneWithoutParticipationsInput
} }
@@ -475,6 +527,8 @@ export type TripParticipantUncheckedCreateWithoutTripInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
userId: string userId: string
} }
@@ -508,6 +562,8 @@ export type TripParticipantCreateManyUserInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
tripId: string tripId: string
} }
@@ -515,6 +571,8 @@ export type TripParticipantUpdateWithoutUserInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
trip?: Prisma.TripUpdateOneRequiredWithoutParticipantsNestedInput trip?: Prisma.TripUpdateOneRequiredWithoutParticipantsNestedInput
} }
@@ -522,6 +580,8 @@ export type TripParticipantUncheckedUpdateWithoutUserInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
tripId?: Prisma.StringFieldUpdateOperationsInput | string tripId?: Prisma.StringFieldUpdateOperationsInput | string
} }
@@ -529,6 +589,8 @@ export type TripParticipantUncheckedUpdateManyWithoutUserInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
tripId?: Prisma.StringFieldUpdateOperationsInput | string tripId?: Prisma.StringFieldUpdateOperationsInput | string
} }
@@ -536,6 +598,8 @@ export type TripParticipantCreateManyTripInput = {
id?: string id?: string
status?: $Enums.ParticipantStatus status?: $Enums.ParticipantStatus
createdAt?: Date | string createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
userId: string userId: string
} }
@@ -543,6 +607,8 @@ export type TripParticipantUpdateWithoutTripInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
user?: Prisma.UserUpdateOneRequiredWithoutParticipationsNestedInput user?: Prisma.UserUpdateOneRequiredWithoutParticipationsNestedInput
} }
@@ -550,6 +616,8 @@ export type TripParticipantUncheckedUpdateWithoutTripInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
userId?: Prisma.StringFieldUpdateOperationsInput | string userId?: Prisma.StringFieldUpdateOperationsInput | string
} }
@@ -557,6 +625,8 @@ export type TripParticipantUncheckedUpdateManyWithoutTripInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
userId?: Prisma.StringFieldUpdateOperationsInput | string userId?: Prisma.StringFieldUpdateOperationsInput | string
} }
@@ -566,6 +636,8 @@ export type TripParticipantSelect<ExtArgs extends runtime.Types.Extensions.Inter
id?: boolean id?: boolean
status?: boolean status?: boolean
createdAt?: boolean createdAt?: boolean
markedPaidAt?: boolean
paymentConfirmedAt?: boolean
tripId?: boolean tripId?: boolean
userId?: boolean userId?: boolean
trip?: boolean | Prisma.TripDefaultArgs<ExtArgs> trip?: boolean | Prisma.TripDefaultArgs<ExtArgs>
@@ -576,6 +648,8 @@ export type TripParticipantSelectCreateManyAndReturn<ExtArgs extends runtime.Typ
id?: boolean id?: boolean
status?: boolean status?: boolean
createdAt?: boolean createdAt?: boolean
markedPaidAt?: boolean
paymentConfirmedAt?: boolean
tripId?: boolean tripId?: boolean
userId?: boolean userId?: boolean
trip?: boolean | Prisma.TripDefaultArgs<ExtArgs> trip?: boolean | Prisma.TripDefaultArgs<ExtArgs>
@@ -586,6 +660,8 @@ export type TripParticipantSelectUpdateManyAndReturn<ExtArgs extends runtime.Typ
id?: boolean id?: boolean
status?: boolean status?: boolean
createdAt?: boolean createdAt?: boolean
markedPaidAt?: boolean
paymentConfirmedAt?: boolean
tripId?: boolean tripId?: boolean
userId?: boolean userId?: boolean
trip?: boolean | Prisma.TripDefaultArgs<ExtArgs> trip?: boolean | Prisma.TripDefaultArgs<ExtArgs>
@@ -596,11 +672,13 @@ export type TripParticipantSelectScalar = {
id?: boolean id?: boolean
status?: boolean status?: boolean
createdAt?: boolean createdAt?: boolean
markedPaidAt?: boolean
paymentConfirmedAt?: boolean
tripId?: boolean tripId?: boolean
userId?: boolean userId?: boolean
} }
export type TripParticipantOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "status" | "createdAt" | "tripId" | "userId", ExtArgs["result"]["tripParticipant"]> export type TripParticipantOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "status" | "createdAt" | "markedPaidAt" | "paymentConfirmedAt" | "tripId" | "userId", ExtArgs["result"]["tripParticipant"]>
export type TripParticipantInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type TripParticipantInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
trip?: boolean | Prisma.TripDefaultArgs<ExtArgs> trip?: boolean | Prisma.TripDefaultArgs<ExtArgs>
user?: boolean | Prisma.UserDefaultArgs<ExtArgs> user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
@@ -624,6 +702,14 @@ export type $TripParticipantPayload<ExtArgs extends runtime.Types.Extensions.Int
id: string id: string
status: $Enums.ParticipantStatus status: $Enums.ParticipantStatus
createdAt: Date createdAt: Date
/**
* Peserta menekan "Saya sudah bayar" (pembayaran manual)
*/
markedPaidAt: Date | null
/**
* Organizer mengonfirmasi uang sudah masuk
*/
paymentConfirmedAt: Date | null
tripId: string tripId: string
userId: string userId: string
}, ExtArgs["result"]["tripParticipant"]> }, ExtArgs["result"]["tripParticipant"]>
@@ -1054,6 +1140,8 @@ export interface TripParticipantFieldRefs {
readonly id: Prisma.FieldRef<"TripParticipant", 'String'> readonly id: Prisma.FieldRef<"TripParticipant", 'String'>
readonly status: Prisma.FieldRef<"TripParticipant", 'ParticipantStatus'> readonly status: Prisma.FieldRef<"TripParticipant", 'ParticipantStatus'>
readonly createdAt: Prisma.FieldRef<"TripParticipant", 'DateTime'> readonly createdAt: Prisma.FieldRef<"TripParticipant", 'DateTime'>
readonly markedPaidAt: Prisma.FieldRef<"TripParticipant", 'DateTime'>
readonly paymentConfirmedAt: Prisma.FieldRef<"TripParticipant", 'DateTime'>
readonly tripId: Prisma.FieldRef<"TripParticipant", 'String'> readonly tripId: Prisma.FieldRef<"TripParticipant", 'String'>
readonly userId: Prisma.FieldRef<"TripParticipant", 'String'> readonly userId: Prisma.FieldRef<"TripParticipant", 'String'>
} }
+48 -1
View File
@@ -30,6 +30,7 @@ export type UserMinAggregateOutputType = {
email: string | null email: string | null
password: string | null password: string | null
image: string | null image: string | null
isVerified: boolean | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -40,6 +41,7 @@ export type UserMaxAggregateOutputType = {
email: string | null email: string | null
password: string | null password: string | null
image: string | null image: string | null
isVerified: boolean | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -50,6 +52,7 @@ export type UserCountAggregateOutputType = {
email: number email: number
password: number password: number
image: number image: number
isVerified: number
createdAt: number createdAt: number
updatedAt: number updatedAt: number
_all: number _all: number
@@ -62,6 +65,7 @@ export type UserMinAggregateInputType = {
email?: true email?: true
password?: true password?: true
image?: true image?: true
isVerified?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -72,6 +76,7 @@ export type UserMaxAggregateInputType = {
email?: true email?: true
password?: true password?: true
image?: true image?: true
isVerified?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -82,6 +87,7 @@ export type UserCountAggregateInputType = {
email?: true email?: true
password?: true password?: true
image?: true image?: true
isVerified?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
_all?: true _all?: true
@@ -165,6 +171,7 @@ export type UserGroupByOutputType = {
email: string email: string
password: string password: string
image: string | null image: string | null
isVerified: boolean
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
_count: UserCountAggregateOutputType | null _count: UserCountAggregateOutputType | null
@@ -196,6 +203,7 @@ export type UserWhereInput = {
email?: Prisma.StringFilter<"User"> | string email?: Prisma.StringFilter<"User"> | string
password?: Prisma.StringFilter<"User"> | string password?: Prisma.StringFilter<"User"> | string
image?: Prisma.StringNullableFilter<"User"> | string | null image?: Prisma.StringNullableFilter<"User"> | string | null
isVerified?: Prisma.BoolFilter<"User"> | boolean
createdAt?: Prisma.DateTimeFilter<"User"> | Date | string createdAt?: Prisma.DateTimeFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
trips?: Prisma.TripListRelationFilter trips?: Prisma.TripListRelationFilter
@@ -209,6 +217,7 @@ export type UserOrderByWithRelationInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrderInput | Prisma.SortOrder image?: Prisma.SortOrderInput | Prisma.SortOrder
isVerified?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
trips?: Prisma.TripOrderByRelationAggregateInput trips?: Prisma.TripOrderByRelationAggregateInput
@@ -225,6 +234,7 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
name?: Prisma.StringFilter<"User"> | string name?: Prisma.StringFilter<"User"> | string
password?: Prisma.StringFilter<"User"> | string password?: Prisma.StringFilter<"User"> | string
image?: Prisma.StringNullableFilter<"User"> | string | null image?: Prisma.StringNullableFilter<"User"> | string | null
isVerified?: Prisma.BoolFilter<"User"> | boolean
createdAt?: Prisma.DateTimeFilter<"User"> | Date | string createdAt?: Prisma.DateTimeFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
trips?: Prisma.TripListRelationFilter trips?: Prisma.TripListRelationFilter
@@ -238,6 +248,7 @@ export type UserOrderByWithAggregationInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrderInput | Prisma.SortOrder image?: Prisma.SortOrderInput | Prisma.SortOrder
isVerified?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
_count?: Prisma.UserCountOrderByAggregateInput _count?: Prisma.UserCountOrderByAggregateInput
@@ -254,6 +265,7 @@ export type UserScalarWhereWithAggregatesInput = {
email?: Prisma.StringWithAggregatesFilter<"User"> | string email?: Prisma.StringWithAggregatesFilter<"User"> | string
password?: Prisma.StringWithAggregatesFilter<"User"> | string password?: Prisma.StringWithAggregatesFilter<"User"> | string
image?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null image?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null
isVerified?: Prisma.BoolWithAggregatesFilter<"User"> | boolean
createdAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string createdAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
} }
@@ -264,6 +276,7 @@ export type UserCreateInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
@@ -277,6 +290,7 @@ export type UserUncheckedCreateInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
@@ -290,6 +304,7 @@ export type UserUpdateInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
@@ -303,6 +318,7 @@ export type UserUncheckedUpdateInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
@@ -316,6 +332,7 @@ export type UserCreateManyInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -326,6 +343,7 @@ export type UserUpdateManyMutationInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -336,6 +354,7 @@ export type UserUncheckedUpdateManyInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -346,6 +365,7 @@ export type UserCountOrderByAggregateInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrder image?: Prisma.SortOrder
isVerified?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -356,6 +376,7 @@ export type UserMaxOrderByAggregateInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrder image?: Prisma.SortOrder
isVerified?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -366,6 +387,7 @@ export type UserMinOrderByAggregateInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrder image?: Prisma.SortOrder
isVerified?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -383,6 +405,10 @@ export type NullableStringFieldUpdateOperationsInput = {
set?: string | null set?: string | null
} }
export type BoolFieldUpdateOperationsInput = {
set?: boolean
}
export type DateTimeFieldUpdateOperationsInput = { export type DateTimeFieldUpdateOperationsInput = {
set?: Date | string set?: Date | string
} }
@@ -435,6 +461,7 @@ export type UserCreateWithoutTripsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
@@ -447,6 +474,7 @@ export type UserUncheckedCreateWithoutTripsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
@@ -475,6 +503,7 @@ export type UserUpdateWithoutTripsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
@@ -487,6 +516,7 @@ export type UserUncheckedUpdateWithoutTripsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
@@ -499,6 +529,7 @@ export type UserCreateWithoutTripReviewsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
@@ -511,6 +542,7 @@ export type UserUncheckedCreateWithoutTripReviewsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
@@ -539,6 +571,7 @@ export type UserUpdateWithoutTripReviewsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
@@ -551,6 +584,7 @@ export type UserUncheckedUpdateWithoutTripReviewsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
@@ -563,6 +597,7 @@ export type UserCreateWithoutParticipationsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
@@ -575,6 +610,7 @@ export type UserUncheckedCreateWithoutParticipationsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
@@ -603,6 +639,7 @@ export type UserUpdateWithoutParticipationsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
@@ -615,6 +652,7 @@ export type UserUncheckedUpdateWithoutParticipationsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
@@ -676,6 +714,7 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
email?: boolean email?: boolean
password?: boolean password?: boolean
image?: boolean image?: boolean
isVerified?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
trips?: boolean | Prisma.User$tripsArgs<ExtArgs> trips?: boolean | Prisma.User$tripsArgs<ExtArgs>
@@ -690,6 +729,7 @@ export type UserSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensio
email?: boolean email?: boolean
password?: boolean password?: boolean
image?: boolean image?: boolean
isVerified?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["user"]> }, ExtArgs["result"]["user"]>
@@ -700,6 +740,7 @@ export type UserSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensio
email?: boolean email?: boolean
password?: boolean password?: boolean
image?: boolean image?: boolean
isVerified?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["user"]> }, ExtArgs["result"]["user"]>
@@ -710,11 +751,12 @@ export type UserSelectScalar = {
email?: boolean email?: boolean
password?: boolean password?: boolean
image?: boolean image?: boolean
isVerified?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
} }
export type UserOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "password" | "image" | "createdAt" | "updatedAt", ExtArgs["result"]["user"]> export type UserOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "password" | "image" | "isVerified" | "createdAt" | "updatedAt", ExtArgs["result"]["user"]>
export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
trips?: boolean | Prisma.User$tripsArgs<ExtArgs> trips?: boolean | Prisma.User$tripsArgs<ExtArgs>
participations?: boolean | Prisma.User$participationsArgs<ExtArgs> participations?: boolean | Prisma.User$participationsArgs<ExtArgs>
@@ -737,6 +779,10 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
email: string email: string
password: string password: string
image: string | null image: string | null
/**
* Akun diverifikasi tim SeTrip (manual / admin) — tampil sebagai badge kepercayaan
*/
isVerified: boolean
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
}, ExtArgs["result"]["user"]> }, ExtArgs["result"]["user"]>
@@ -1170,6 +1216,7 @@ export interface UserFieldRefs {
readonly email: Prisma.FieldRef<"User", 'String'> readonly email: Prisma.FieldRef<"User", 'String'>
readonly password: Prisma.FieldRef<"User", 'String'> readonly password: Prisma.FieldRef<"User", 'String'>
readonly image: Prisma.FieldRef<"User", 'String'> readonly image: Prisma.FieldRef<"User", 'String'>
readonly isVerified: Prisma.FieldRef<"User", 'Boolean'>
readonly createdAt: Prisma.FieldRef<"User", 'DateTime'> readonly createdAt: Prisma.FieldRef<"User", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"User", 'DateTime'> readonly updatedAt: Prisma.FieldRef<"User", 'DateTime'>
} }
+12 -2
View File
@@ -173,8 +173,18 @@ export default async function ProfilePage() {
date={t.date} date={t.date}
endDate={t.endDate} endDate={t.endDate}
rightSlot={ rightSlot={
<span className="text-neutral-400"> <span
{p.status === "CONFIRMED" ? "Terdaftar" : p.status} className={
p.status === "PENDING"
? "font-medium text-amber-700"
: "text-neutral-400"
}
>
{p.status === "CONFIRMED"
? "Terkonfirmasi"
: p.status === "PENDING"
? "Menunggu organizer"
: p.status}
</span> </span>
} }
/> />
+94 -8
View File
@@ -3,8 +3,14 @@ import { getServerSession } from "next-auth";
import Link from "next/link"; import Link from "next/link";
import { authOptions } from "@/lib/auth"; import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service"; import { tripService } from "@/server/services/trip.service";
import { formatRupiah, formatDateRange } from "@/lib/utils"; import { trustService } from "@/server/services/trust.service";
import { formatRupiah } from "@/lib/utils";
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
import { JoinTripButton } from "@/features/trip/components/join-trip-button"; import { JoinTripButton } from "@/features/trip/components/join-trip-button";
import { OrganizerJoinRequests } from "@/features/trip/components/organizer-join-requests";
import { OrganizerTrustPanel } from "@/features/trip/components/organizer-trust-panel";
import { TripProgramBlock } from "@/features/trip/components/trip-program-block";
import { OrganizerPaymentQueue } from "@/features/booking/components/organizer-payment-queue";
import { ImageGallery } from "@/features/trip/components/image-gallery"; import { ImageGallery } from "@/features/trip/components/image-gallery";
import { TripReviewSection } from "@/features/review/components/trip-review-section"; import { TripReviewSection } from "@/features/review/components/trip-review-section";
import { import {
@@ -27,10 +33,21 @@ export default async function TripDetailPage({
notFound(); notFound();
} }
const organizerTrust = await trustService.getOrganizerTrust(
trip.organizerId
);
const activeParticipants = trip.participants.filter( const activeParticipants = trip.participants.filter(
(p) => p.status !== "CANCELLED" (p) => p.status !== "CANCELLED"
); );
const confirmedParticipants = activeParticipants.filter(
(p) => p.status === "CONFIRMED"
);
const pendingParticipants = activeParticipants.filter(
(p) => p.status === "PENDING"
);
const participantCount = activeParticipants.length; const participantCount = activeParticipants.length;
const confirmedCount = confirmedParticipants.length;
const spotsLeft = trip.maxParticipants - participantCount; const spotsLeft = trip.maxParticipants - participantCount;
const fillPercent = Math.min( const fillPercent = Math.min(
(participantCount / trip.maxParticipants) * 100, (participantCount / trip.maxParticipants) * 100,
@@ -63,6 +80,10 @@ export default async function TripDetailPage({
) / 10 ) / 10
: null; : null;
const paymentPendingParticipants = activeParticipants.filter(
(p) => p.markedPaidAt && !p.paymentConfirmedAt
);
return ( return (
<div className="mx-auto max-w-3xl px-4 py-4 sm:py-8"> <div className="mx-auto max-w-3xl px-4 py-4 sm:py-8">
{/* Breadcrumb */} {/* Breadcrumb */}
@@ -123,7 +144,7 @@ export default async function TripDetailPage({
<div className="min-w-0"> <div className="min-w-0">
<p className="text-[10px] font-medium text-neutral-400 sm:text-xs">Tanggal</p> <p className="text-[10px] font-medium text-neutral-400 sm:text-xs">Tanggal</p>
<p className="truncate text-xs font-semibold text-neutral-800 sm:text-sm"> <p className="truncate text-xs font-semibold text-neutral-800 sm:text-sm">
{formatDateRange(trip.date, trip.endDate)} {formatTripCalendarDateRangeLong(trip.date, trip.endDate)}
</p> </p>
</div> </div>
</div> </div>
@@ -153,6 +174,12 @@ export default async function TripDetailPage({
</div> </div>
</div> </div>
<OrganizerTrustPanel
name={trip.organizer.name}
image={trip.organizer.image}
trust={organizerTrust}
/>
{/* Participant Progress */} {/* Participant Progress */}
<div className="rounded-xl border border-neutral-200 p-3 sm:p-4"> <div className="rounded-xl border border-neutral-200 p-3 sm:p-4">
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
@@ -179,12 +206,30 @@ export default async function TripDetailPage({
/> />
</div> </div>
<p className="mt-1.5 text-[11px] text-neutral-500 sm:text-xs"> <p className="mt-1.5 text-[11px] text-neutral-500 sm:text-xs">
Maksimal {trip.maxParticipants} orang. Saat ini {participantCount}{" "}
mendaftar, {confirmedCount} sudah disetujui organizer.
</p>
<p className="mt-1 text-[11px] text-neutral-500 sm:text-xs">
{spotsLeft > 0 {spotsLeft > 0
? `${spotsLeft} slot tersisa — yuk gabung!` ? `Masih ada ${spotsLeft} tempat — yuk gabung!`
: "Trip sudah penuh"} : "Trip sudah penuh"}
{confirmedCount < participantCount && (
<>
{" "}
· {participantCount - confirmedCount} menunggu persetujuan
organizer
</>
)}
</p> </p>
</div> </div>
<TripProgramBlock
meetingPoint={trip.meetingPoint}
itinerary={trip.itinerary}
whatsIncluded={trip.whatsIncluded}
whatsExcluded={trip.whatsExcluded}
/>
{/* Description */} {/* Description */}
{trip.description && ( {trip.description && (
<div> <div>
@@ -197,12 +242,50 @@ export default async function TripDetailPage({
</div> </div>
)} )}
{isOrganizer && pendingParticipants.length > 0 && (
<OrganizerJoinRequests
tripId={trip.id}
pending={pendingParticipants.map((p) => ({
id: p.id,
user: p.user,
markedPaidAt: p.markedPaidAt,
}))}
/>
)}
{isOrganizer && paymentPendingParticipants.length > 0 && (
<OrganizerPaymentQueue
tripId={trip.id}
items={paymentPendingParticipants.map((p) => ({
id: p.id,
user: p.user,
joinStatus:
p.status === "PENDING" ? ("PENDING" as const) : ("CONFIRMED" as const),
}))}
/>
)}
{/* Action */} {/* Action */}
<JoinTripButton <JoinTripButton
tripId={trip.id} tripId={trip.id}
isLoggedIn={!!session?.user} isLoggedIn={!!session?.user}
isOrganizer={isOrganizer} isOrganizer={isOrganizer}
isJoined={!!currentParticipation} isJoined={!!currentParticipation}
participationStatus={
currentParticipation?.status === "PENDING" ||
currentParticipation?.status === "CONFIRMED"
? currentParticipation.status
: null
}
participantPayment={
currentParticipation
? {
markedPaidAt: currentParticipation.markedPaidAt,
paymentConfirmedAt:
currentParticipation.paymentConfirmedAt,
}
: null
}
isFull={spotsLeft <= 0} isFull={spotsLeft <= 0}
tripStatus={trip.status} tripStatus={trip.status}
isDeparturePast={isDeparturePast} isDeparturePast={isDeparturePast}
@@ -226,18 +309,21 @@ export default async function TripDetailPage({
} }
/> />
{/* Participants List */} {/* Peserta yang sudah disetujui organizer (publik) */}
<div> <div>
<h2 className="mb-3 text-xs font-bold text-neutral-700 sm:text-sm"> <h2 className="mb-3 text-xs font-bold text-neutral-700 sm:text-sm">
Peserta ({participantCount}) Peserta terkonfirmasi ({confirmedCount})
</h2> </h2>
{participantCount === 0 ? ( {confirmedCount === 0 ? (
<p className="text-xs text-neutral-400 sm:text-sm"> <p className="text-xs text-neutral-400 sm:text-sm">
Belum ada peserta. Jadilah yang pertama! 🎒 Belum ada peserta yang dikonfirmasi.{" "}
{pendingParticipants.length > 0
? "Cek permintaan join di atas untuk menyetujui peserta."
: "Jadilah yang pertama mendaftar! 🎒"}
</p> </p>
) : ( ) : (
<div className="flex flex-wrap gap-1.5 sm:gap-2"> <div className="flex flex-wrap gap-1.5 sm:gap-2">
{activeParticipants.map((p) => ( {confirmedParticipants.map((p) => (
<div <div
key={p.id} key={p.id}
className="flex items-center gap-1.5 rounded-full bg-neutral-100 px-2.5 py-1 sm:gap-2 sm:px-3 sm:py-1.5" className="flex items-center gap-1.5 rounded-full bg-neutral-100 px-2.5 py-1 sm:gap-2 sm:px-3 sm:py-1.5"
+49
View File
@@ -0,0 +1,49 @@
"use server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service";
import { revalidatePath } from "next/cache";
export async function markParticipantPaidAction(tripId: string) {
const session = await getServerSession(authOptions);
if (!session?.user) {
return { error: "Kamu harus login terlebih dahulu" };
}
try {
await tripService.markParticipantPayment(tripId, session.user.id);
revalidatePath(`/trips/${tripId}`);
revalidatePath("/trips");
revalidatePath("/");
revalidatePath("/profile");
return { success: true };
} catch (err) {
return { error: (err as Error).message };
}
}
export async function confirmParticipantPaymentAction(
tripId: string,
participantId: string
) {
const session = await getServerSession(authOptions);
if (!session?.user) {
return { error: "Kamu harus login terlebih dahulu" };
}
try {
await tripService.confirmParticipantPayment(
tripId,
participantId,
session.user.id
);
revalidatePath(`/trips/${tripId}`);
revalidatePath("/trips");
revalidatePath("/");
revalidatePath("/profile");
return { success: true };
} catch (err) {
return { error: (err as Error).message };
}
}
@@ -0,0 +1,102 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import { confirmParticipantPaymentAction } from "@/features/booking/actions";
export interface PaymentPendingParticipant {
id: string;
user: { name: string; image: string | null };
/** PENDING atau CONFIRMED (join) — keduanya bisa sudah tandai bayar */
joinStatus: "PENDING" | "CONFIRMED";
}
interface OrganizerPaymentQueueProps {
tripId: string;
items: PaymentPendingParticipant[];
}
export function OrganizerPaymentQueue({
tripId,
items,
}: OrganizerPaymentQueueProps) {
const router = useRouter();
const [loadingId, setLoadingId] = useState<string | null>(null);
const [error, setError] = useState("");
async function confirm(participantId: string) {
setLoadingId(participantId);
setError("");
const result = await confirmParticipantPaymentAction(tripId, participantId);
setLoadingId(null);
if (result.error) {
setError(result.error);
return;
}
router.refresh();
}
return (
<div className="rounded-xl border border-primary-200 bg-primary-50/60 p-4 sm:p-5">
<h2 className="text-sm font-bold text-primary-950 sm:text-base">
Konfirmasi pembayaran ({items.length})
</h2>
<p className="mt-1 text-xs text-primary-900/85 sm:text-sm">
Peserta sudah menandai pembayaran. Cek rekening atau bukti transfer,
lalu konfirmasi.
</p>
{error && (
<p className="mt-3 rounded-lg bg-red-50 px-3 py-2 text-xs font-medium text-red-700 sm:text-sm">
{error}
</p>
)}
<ul className="mt-4 space-y-3">
{items.map((p) => (
<li
key={p.id}
className="flex flex-col gap-3 rounded-xl border border-primary-100 bg-white/95 p-3 sm:flex-row sm:items-center sm:justify-between"
>
<div className="flex min-w-0 items-center gap-3">
{p.user.image ? (
<Image
src={p.user.image}
alt=""
width={40}
height={40}
className="h-10 w-10 shrink-0 rounded-full object-cover"
/>
) : (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary-600 text-sm font-bold text-white">
{p.user.name.charAt(0).toUpperCase()}
</div>
)}
<div className="min-w-0">
<p className="truncate text-sm font-semibold text-neutral-800">
{p.user.name}
</p>
<p className="text-xs text-primary-800/90">
Menunggu konfirmasi pembayaran
{p.joinStatus === "PENDING" && (
<span className="text-neutral-500">
{" "}
· belum disetujui ikut trip
</span>
)}
</p>
</div>
</div>
<button
type="button"
disabled={loadingId !== null}
onClick={() => confirm(p.id)}
className="shrink-0 rounded-lg bg-primary-600 px-4 py-2 text-xs font-semibold text-white shadow-sm hover:bg-primary-700 disabled:opacity-50 sm:text-sm"
>
{loadingId === p.id ? "Memproses…" : "Konfirmasi pembayaran"}
</button>
</li>
))}
</ul>
</div>
);
}
@@ -1,6 +1,6 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import Link from "next/link"; import Link from "next/link";
import { formatDateRange } from "@/lib/utils"; import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
interface ProfileTripRowProps { interface ProfileTripRowProps {
href: string; href: string;
@@ -28,7 +28,7 @@ export function ProfileTripRow({
<p className="truncate text-sm font-semibold text-neutral-800">{title}</p> <p className="truncate text-sm font-semibold text-neutral-800">{title}</p>
<p className="truncate text-xs text-neutral-500">{mountain}</p> <p className="truncate text-xs text-neutral-500">{mountain}</p>
<p className="mt-0.5 text-[11px] text-neutral-400 sm:text-xs"> <p className="mt-0.5 text-[11px] text-neutral-400 sm:text-xs">
{formatDateRange(date, endDate)} {formatTripCalendarDateRangeLong(date, endDate)}
</p> </p>
</div> </div>
{rightSlot && ( {rightSlot && (
+67 -1
View File
@@ -18,6 +18,10 @@ export async function createTripAction(formData: FormData) {
description: formData.get("description") as string, description: formData.get("description") as string,
mountain: formData.get("mountain") as string, mountain: formData.get("mountain") as string,
location: formData.get("location") as string, location: formData.get("location") as string,
meetingPoint: formData.get("meetingPoint") as string,
itinerary: formData.get("itinerary") as string,
whatsIncluded: formData.get("whatsIncluded") as string,
whatsExcluded: formData.get("whatsExcluded") as string,
date: formData.get("date") as string, date: formData.get("date") as string,
endDate: (formData.get("endDate") as string) || undefined, endDate: (formData.get("endDate") as string) || undefined,
maxParticipants: formData.get("maxParticipants") as string, maxParticipants: formData.get("maxParticipants") as string,
@@ -50,8 +54,20 @@ export async function createTripAction(formData: FormData) {
} }
try { try {
const {
meetingPoint,
itinerary,
whatsIncluded,
whatsExcluded,
...tripCore
} = result.data;
const trip = await tripService.createTrip({ const trip = await tripService.createTrip({
...result.data, ...tripCore,
meetingPoint,
itinerary,
whatsIncluded,
whatsExcluded,
date, date,
endDate, endDate,
organizerId: session.user.id, organizerId: session.user.id,
@@ -101,3 +117,53 @@ export async function cancelJoinAction(tripId: string) {
return { error: (err as Error).message }; return { error: (err as Error).message };
} }
} }
export async function confirmParticipantAction(
tripId: string,
participantId: string
) {
const session = await getServerSession(authOptions);
if (!session?.user) {
return { error: "Kamu harus login terlebih dahulu" };
}
try {
await tripService.confirmParticipant(
tripId,
participantId,
session.user.id
);
revalidatePath(`/trips/${tripId}`);
revalidatePath("/trips");
revalidatePath("/");
revalidatePath("/profile");
return { success: true };
} catch (err) {
return { error: (err as Error).message };
}
}
export async function rejectParticipantAction(
tripId: string,
participantId: string
) {
const session = await getServerSession(authOptions);
if (!session?.user) {
return { error: "Kamu harus login terlebih dahulu" };
}
try {
await tripService.rejectParticipant(
tripId,
participantId,
session.user.id
);
revalidatePath(`/trips/${tripId}`);
revalidatePath("/trips");
revalidatePath("/");
revalidatePath("/profile");
return { success: true };
} catch (err) {
return { error: (err as Error).message };
}
}
+70 -1
View File
@@ -4,15 +4,23 @@ import { useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import { joinTripAction, cancelJoinAction } from "@/features/trip/actions"; import { joinTripAction, cancelJoinAction } from "@/features/trip/actions";
import { markParticipantPaidAction } from "@/features/booking/actions";
interface JoinTripButtonProps { interface JoinTripButtonProps {
tripId: string; tripId: string;
isLoggedIn: boolean; isLoggedIn: boolean;
isOrganizer: boolean; isOrganizer: boolean;
isJoined: boolean; isJoined: boolean;
/** Status partisipasi user saat isJoined (bukan organizer) */
participationStatus?: "PENDING" | "CONFIRMED" | null;
/** Status pembayaran manual (peserta) */
participantPayment?: {
markedPaidAt: string | Date | null;
paymentConfirmedAt: string | Date | null;
} | null;
isFull: boolean; isFull: boolean;
tripStatus: string; tripStatus: string;
/** Tanggal berangkat sudah lewat (hari kalender UTC) */ /** Tanggal berangkat trip sudah lewat */
isDeparturePast?: boolean; isDeparturePast?: boolean;
} }
@@ -21,6 +29,8 @@ export function JoinTripButton({
isLoggedIn, isLoggedIn,
isOrganizer, isOrganizer,
isJoined, isJoined,
participationStatus,
participantPayment,
isFull, isFull,
tripStatus, tripStatus,
isDeparturePast, isDeparturePast,
@@ -98,6 +108,29 @@ export function JoinTripButton({
} }
} }
async function handleMarkPaid() {
setLoading(true);
setError("");
const result = await markParticipantPaidAction(tripId);
setLoading(false);
if (result.error) {
setError(result.error);
} else {
router.refresh();
}
}
const pay = participantPayment;
const showMarkPaid =
isJoined &&
pay &&
!pay.paymentConfirmedAt &&
!pay.markedPaidAt &&
!isDeparturePast;
const waitingPaymentConfirm =
isJoined && pay && pay.markedPaidAt && !pay.paymentConfirmedAt;
const paymentDone = isJoined && pay && pay.paymentConfirmedAt;
return ( return (
<div> <div>
{error && ( {error && (
@@ -105,6 +138,42 @@ export function JoinTripButton({
{error} {error}
</div> </div>
)} )}
{isJoined && participationStatus === "PENDING" && (
<div className="mb-3 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm font-medium leading-relaxed text-amber-900">
Permintaan ikut trip kamu{" "}
<span className="font-semibold">menunggu persetujuan organizer</span>.
Kamu bisa membatalkan kapan saja sebelum disetujui.
</div>
)}
{isJoined && participationStatus === "CONFIRMED" && (
<div className="mb-3 rounded-xl border border-secondary-200 bg-secondary-50 px-4 py-3 text-sm font-medium text-secondary-900">
Kamu sudah{" "}
<span className="font-semibold">terkonfirmasi</span> sebagai peserta
trip ini.
</div>
)}
{waitingPaymentConfirm && (
<div className="mb-3 rounded-xl border border-primary-200 bg-primary-50 px-4 py-3 text-sm font-medium leading-relaxed text-primary-950">
Kamu sudah menandai <span className="font-semibold">sudah bayar</span>.
Tunggu organizer mengonfirmasi pembayaran.
</div>
)}
{paymentDone && (
<div className="mb-3 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-900">
Pembayaran kamu sudah{" "}
<span className="font-semibold">dikonfirmasi organizer</span>.
</div>
)}
{showMarkPaid && (
<button
type="button"
onClick={handleMarkPaid}
disabled={loading}
className="mb-3 w-full rounded-xl border-2 border-primary-500 bg-white py-3 text-sm font-bold text-primary-700 transition-colors hover:bg-primary-50 disabled:opacity-50"
>
{loading ? "Memproses..." : "Saya sudah bayar"}
</button>
)}
{isJoined ? ( {isJoined ? (
<button <button
onClick={handleCancel} onClick={handleCancel}
@@ -0,0 +1,119 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import {
confirmParticipantAction,
rejectParticipantAction,
} from "@/features/trip/actions";
export interface PendingJoinRequest {
id: string;
user: { name: string; image: string | null };
/** Peserta sudah menekan &quot;Saya sudah bayar&quot; */
markedPaidAt?: string | Date | null;
}
interface OrganizerJoinRequestsProps {
tripId: string;
pending: PendingJoinRequest[];
}
export function OrganizerJoinRequests({
tripId,
pending,
}: OrganizerJoinRequestsProps) {
const router = useRouter();
const [loadingId, setLoadingId] = useState<string | null>(null);
const [error, setError] = useState("");
async function run(
participantId: string,
action: "confirm" | "reject"
) {
setLoadingId(participantId);
setError("");
const result =
action === "confirm"
? await confirmParticipantAction(tripId, participantId)
: await rejectParticipantAction(tripId, participantId);
setLoadingId(null);
if (result.error) {
setError(result.error);
return;
}
router.refresh();
}
return (
<div className="rounded-xl border border-amber-200 bg-amber-50/70 p-4 sm:p-5">
<h2 className="text-sm font-bold text-amber-950 sm:text-base">
Permintaan join ({pending.length})
</h2>
<p className="mt-1 text-xs text-amber-900/80 sm:text-sm">
Setujui atau tolak siapa yang boleh ikut trip ini.
</p>
{error && (
<p className="mt-3 rounded-lg bg-red-50 px-3 py-2 text-xs font-medium text-red-700 sm:text-sm">
{error}
</p>
)}
<ul className="mt-4 space-y-3">
{pending.map((p) => (
<li
key={p.id}
className="flex flex-col gap-3 rounded-xl border border-amber-100 bg-white/90 p-3 sm:flex-row sm:items-center sm:justify-between"
>
<div className="flex min-w-0 items-center gap-3">
{p.user.image ? (
<Image
src={p.user.image}
alt=""
width={40}
height={40}
className="h-10 w-10 shrink-0 rounded-full object-cover"
/>
) : (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary-600 text-sm font-bold text-white">
{p.user.name.charAt(0).toUpperCase()}
</div>
)}
<div className="min-w-0">
<p className="truncate text-sm font-semibold text-neutral-800">
{p.user.name}
</p>
<div className="flex flex-wrap items-center gap-1.5">
<p className="text-xs text-amber-800/80">Menunggu persetujuan</p>
{p.markedPaidAt ? (
<span className="rounded-full bg-primary-100 px-2 py-0.5 text-[10px] font-bold text-primary-800">
Sudah tandai bayar
</span>
) : null}
</div>
</div>
</div>
<div className="flex shrink-0 gap-2">
<button
type="button"
disabled={loadingId !== null}
onClick={() => run(p.id, "reject")}
className="rounded-lg border border-neutral-200 bg-white px-3 py-2 text-xs font-semibold text-neutral-600 hover:bg-neutral-50 disabled:opacity-50 sm:text-sm"
>
{loadingId === p.id ? "…" : "Tolak"}
</button>
<button
type="button"
disabled={loadingId !== null}
onClick={() => run(p.id, "confirm")}
className="rounded-lg bg-primary-600 px-3 py-2 text-xs font-semibold text-white shadow-sm hover:bg-primary-700 disabled:opacity-50 sm:text-sm"
>
{loadingId === p.id ? "Memproses…" : "Setujui"}
</button>
</div>
</li>
))}
</ul>
</div>
);
}
@@ -0,0 +1,79 @@
import Image from "next/image";
import type { OrganizerTrust } from "@/server/services/trust.service";
interface OrganizerTrustPanelProps {
name: string;
image: string | null;
trust: OrganizerTrust;
}
export function OrganizerTrustPanel({
name,
image,
trust,
}: OrganizerTrustPanelProps) {
return (
<div className="rounded-xl border border-neutral-200 bg-linear-to-br from-white to-neutral-50 p-4 sm:p-5">
<h2 className="mb-3 text-xs font-bold text-neutral-700 sm:text-sm">
Organizer & kepercayaan
</h2>
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:gap-4">
<div className="flex items-center gap-3">
{image ? (
<Image
src={image}
alt=""
width={48}
height={48}
className="h-12 w-12 shrink-0 rounded-full object-cover ring-2 ring-white shadow"
/>
) : (
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-primary-600 text-lg font-bold text-white shadow">
{name.charAt(0).toUpperCase()}
</div>
)}
<div className="min-w-0">
<p className="truncate text-sm font-bold text-neutral-900 sm:text-base">
{name}
</p>
<div className="mt-1.5 flex flex-wrap gap-1.5">
{trust.isVerified && (
<span className="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-blue-800 sm:text-xs">
Verified
</span>
)}
{trust.isTripLeader && (
<span className="inline-flex items-center rounded-full bg-secondary-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-secondary-900 sm:text-xs">
Trip leader
</span>
)}
</div>
</div>
</div>
<div className="flex flex-1 flex-wrap gap-3 border-t border-neutral-100 pt-3 sm:border-t-0 sm:border-l sm:pl-4 sm:pt-0">
<div className="min-w-[100px] rounded-lg bg-white/80 px-3 py-2 shadow-sm ring-1 ring-neutral-100">
<p className="text-[10px] font-medium text-neutral-500 sm:text-xs">
Trip dibuat
</p>
<p className="text-lg font-bold text-neutral-800">
{trust.tripsCreated}
</p>
</div>
<div className="min-w-[100px] rounded-lg bg-white/80 px-3 py-2 shadow-sm ring-1 ring-neutral-100">
<p className="text-[10px] font-medium text-neutral-500 sm:text-xs">
Rating organizer
</p>
<p className="text-lg font-bold text-amber-700">
{trust.avgRating != null ? `${trust.avgRating}` : "—"}
</p>
{trust.reviewCount > 0 && (
<p className="text-[10px] text-neutral-400">
dari {trust.reviewCount} ulasan trip
</p>
)}
</div>
</div>
</div>
</div>
);
}
+3 -2
View File
@@ -1,6 +1,7 @@
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { formatRupiah, formatDateRange } from "@/lib/utils"; import { formatRupiah } from "@/lib/utils";
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
interface TripCardProps { interface TripCardProps {
id: string; id: string;
@@ -80,7 +81,7 @@ export function TripCard({
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-xs text-secondary-500">📅</span>{" "} <span className="text-xs text-secondary-500">📅</span>{" "}
{formatDateRange(date, endDate)} {formatTripCalendarDateRangeLong(date, endDate)}
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-xs text-secondary-500">👤</span>{" "} <span className="text-xs text-secondary-500">👤</span>{" "}
+4 -7
View File
@@ -4,6 +4,7 @@ import { useRouter, useSearchParams } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
import { formatLocalCalendarYmd } from "@/lib/trip-dates";
export function TripFilter() { export function TripFilter() {
const router = useRouter(); const router = useRouter();
@@ -35,8 +36,8 @@ export function TripFilter() {
e.preventDefault(); e.preventDefault();
const params = new URLSearchParams(); const params = new URLSearchParams();
if (query.trim()) params.set("q", query.trim()); if (query.trim()) params.set("q", query.trim());
if (startDate) params.set("from", startDate.toISOString().split("T")[0]); if (startDate) params.set("from", formatLocalCalendarYmd(startDate));
if (endDate) params.set("to", endDate.toISOString().split("T")[0]); if (endDate) params.set("to", formatLocalCalendarYmd(endDate));
const qs = params.toString(); const qs = params.toString();
router.push(`/trips${qs ? `?${qs}` : ""}`); router.push(`/trips${qs ? `?${qs}` : ""}`);
@@ -90,12 +91,8 @@ export function TripFilter() {
{/* Date range */} {/* Date range */}
<div className="sm:w-64"> <div className="sm:w-64">
<label className="mb-1.5 block text-xs font-medium text-neutral-500"> <label className="mb-1.5 block text-xs font-medium text-neutral-500">
Rentang tanggal (UTC) Tanggal
</label> </label>
<p className="mb-1 text-[10px] leading-snug text-neutral-400 sm:text-xs">
Menampilkan trip yang jadwalnya overlap rentang ini: multi hari pakai
tanggal pulang; satu hari pakai tanggal berangkat saja.
</p>
<div className="relative"> <div className="relative">
<span className="absolute left-3 top-1/2 z-10 -translate-y-1/2 text-neutral-400"> <span className="absolute left-3 top-1/2 z-10 -translate-y-1/2 text-neutral-400">
<svg <svg
@@ -0,0 +1,72 @@
interface TripProgramBlockProps {
meetingPoint: string | null;
itinerary: string | null;
whatsIncluded: string | null;
whatsExcluded: string | null;
}
export function TripProgramBlock({
meetingPoint,
itinerary,
whatsIncluded,
whatsExcluded,
}: TripProgramBlockProps) {
const hasAny =
meetingPoint || itinerary || whatsIncluded || whatsExcluded;
if (!hasAny) return null;
return (
<div className="space-y-4 rounded-xl border border-neutral-200 bg-neutral-50/50 p-4 sm:p-5">
<h2 className="text-xs font-bold text-neutral-800 sm:text-sm">
Detail perjalanan
</h2>
{meetingPoint && (
<div>
<h3 className="mb-1 text-[11px] font-bold uppercase tracking-wide text-primary-700 sm:text-xs">
Meeting point
</h3>
<p className="whitespace-pre-wrap text-xs leading-relaxed text-neutral-700 sm:text-sm">
{meetingPoint}
</p>
</div>
)}
{itinerary && (
<div>
<h3 className="mb-1 text-[11px] font-bold uppercase tracking-wide text-primary-700 sm:text-xs">
Itinerary
</h3>
<p className="whitespace-pre-wrap text-xs leading-relaxed text-neutral-700 sm:text-sm">
{itinerary}
</p>
</div>
)}
{(whatsIncluded || whatsExcluded) && (
<div className="grid gap-4 sm:grid-cols-2">
{whatsIncluded && (
<div className="rounded-lg border border-secondary-200 bg-white p-3">
<h3 className="mb-2 text-[11px] font-bold uppercase tracking-wide text-secondary-800 sm:text-xs">
Termasuk
</h3>
<p className="whitespace-pre-wrap text-xs leading-relaxed text-neutral-700 sm:text-sm">
{whatsIncluded}
</p>
</div>
)}
{whatsExcluded && (
<div className="rounded-lg border border-neutral-200 bg-white p-3">
<h3 className="mb-2 text-[11px] font-bold uppercase tracking-wide text-neutral-600 sm:text-xs">
Tidak termasuk
</h3>
<p className="whitespace-pre-wrap text-xs leading-relaxed text-neutral-700 sm:text-sm">
{whatsExcluded}
</p>
</div>
)}
</div>
)}
</div>
);
}
+56
View File
@@ -84,6 +84,62 @@ export const createTripSchema = z
LIMITS.MAX_PRICE_IDR, LIMITS.MAX_PRICE_IDR,
`Harga maksimal Rp ${LIMITS.MAX_PRICE_IDR.toLocaleString("id-ID")}` `Harga maksimal Rp ${LIMITS.MAX_PRICE_IDR.toLocaleString("id-ID")}`
), ),
meetingPoint: z.preprocess(
(val) => {
if (val == null) return undefined;
const s = String(val).trim();
return s === "" ? undefined : s;
},
z
.string()
.max(
LIMITS.MAX_MEETING_POINT_LENGTH,
`Meeting point maksimal ${LIMITS.MAX_MEETING_POINT_LENGTH} karakter`
)
.optional()
),
itinerary: z.preprocess(
(val) => {
if (val == null) return undefined;
const s = String(val).trim();
return s === "" ? undefined : s;
},
z
.string()
.max(
LIMITS.MAX_TRIP_ITINERARY_LENGTH,
`Itinerary maksimal ${LIMITS.MAX_TRIP_ITINERARY_LENGTH} karakter`
)
.optional()
),
whatsIncluded: z.preprocess(
(val) => {
if (val == null) return undefined;
const s = String(val).trim();
return s === "" ? undefined : s;
},
z
.string()
.max(
LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH,
`Bagian 'Termasuk' maksimal ${LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH} karakter`
)
.optional()
),
whatsExcluded: z.preprocess(
(val) => {
if (val == null) return undefined;
const s = String(val).trim();
return s === "" ? undefined : s;
},
z
.string()
.max(
LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH,
`Bagian 'Tidak termasuk' maksimal ${LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH} karakter`
)
.optional()
),
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
const dep = tripStoredInstantFromYmd(data.date); const dep = tripStoredInstantFromYmd(data.date);
+4
View File
@@ -9,6 +9,10 @@ export const LIMITS = {
MAX_MOUNTAIN_LENGTH: 100, MAX_MOUNTAIN_LENGTH: 100,
MAX_LOCATION_LENGTH: 120, MAX_LOCATION_LENGTH: 120,
MAX_DESCRIPTION_LENGTH: 5000, MAX_DESCRIPTION_LENGTH: 5000,
/** Meeting point & tiap blok include/exclude */
MAX_MEETING_POINT_LENGTH: 500,
MAX_TRIP_ITINERARY_LENGTH: 8000,
MAX_TRIP_BULLET_SECTION_LENGTH: 4000,
MAX_REVIEW_COMMENT: 500, MAX_REVIEW_COMMENT: 500,
MAX_IMAGE_URLS: 5, MAX_IMAGE_URLS: 5,
MAX_URL_LENGTH: 2048, MAX_URL_LENGTH: 2048,
+27
View File
@@ -81,3 +81,30 @@ export function isPastTripLastDayForReview(
); );
return Date.now() > endMs; return Date.now() > endMs;
} }
/**
* Tampilkan tanggal trip untuk UI: pakai **kalender UTC** (sama dengan
* `tripStoredInstantFromYmd` & filter Open Trip), supaya tidak bergeser
* karena timezone runtime (server vs browser).
*/
export function formatTripCalendarDateLong(
d: Date | string,
locale = "id-ID"
): string {
const date = typeof d === "string" ? new Date(d) : d;
return new Intl.DateTimeFormat(locale, {
dateStyle: "long",
timeZone: "UTC",
}).format(date);
}
export function formatTripCalendarDateRangeLong(
start: Date | string,
end?: Date | string | null,
locale = "id-ID"
): string {
const startStr = formatTripCalendarDateLong(start, locale);
if (!end) return startStr;
const endStr = formatTripCalendarDateLong(end, locale);
return `${startStr}${endStr}`;
}
+2
View File
@@ -0,0 +1,2 @@
/** Minimal trip sebagai organizer untuk badge "Trip leader" (heuristik MVP). */
export const TRIP_LEADER_MIN_TRIPS = 2;
@@ -0,0 +1,12 @@
-- AlterTable
ALTER TABLE "Trip" ADD COLUMN "itinerary" TEXT,
ADD COLUMN "meetingPoint" TEXT,
ADD COLUMN "whatsExcluded" TEXT,
ADD COLUMN "whatsIncluded" TEXT;
-- AlterTable
ALTER TABLE "TripParticipant" ADD COLUMN "markedPaidAt" TIMESTAMP(3),
ADD COLUMN "paymentConfirmedAt" TIMESTAMP(3);
-- AlterTable
ALTER TABLE "User" ADD COLUMN "isVerified" BOOLEAN NOT NULL DEFAULT false;
+14
View File
@@ -13,6 +13,8 @@ model User {
email String @unique email String @unique
password String password String
image String? image String?
/// Akun diverifikasi tim SeTrip (manual / admin) — tampil sebagai badge kepercayaan
isVerified Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -27,6 +29,14 @@ model Trip {
description String? description String?
mountain String mountain String
location String location String
/// Titik kumpul / meeting point (teks bebas)
meetingPoint String?
/// Itinerary hari per hari (teks bebas, bullet OK)
itinerary String?
/// Yang termasuk harga (teks bebas)
whatsIncluded String?
/// Yang tidak termasuk (teks bebas)
whatsExcluded String?
date DateTime date DateTime
endDate DateTime? endDate DateTime?
maxParticipants Int maxParticipants Int
@@ -73,6 +83,10 @@ model TripParticipant {
id String @id @default(cuid()) id String @id @default(cuid())
status ParticipantStatus @default(PENDING) status ParticipantStatus @default(PENDING)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
/// Peserta menekan "Saya sudah bayar" (pembayaran manual)
markedPaidAt DateTime?
/// Organizer mengonfirmasi uang sudah masuk
paymentConfirmedAt DateTime?
tripId String tripId String
trip Trip @relation(fields: [tripId], references: [id]) trip Trip @relation(fields: [tripId], references: [id])
+22 -6
View File
@@ -28,6 +28,7 @@ async function main() {
name: "Dede Inoen", name: "Dede Inoen",
email: "dede.inoen@setrip.id", email: "dede.inoen@setrip.id",
password, password,
isVerified: true,
}, },
}); });
@@ -36,6 +37,7 @@ async function main() {
name: "Panji Petualang", name: "Panji Petualang",
email: "panji@setrip.id", email: "panji@setrip.id",
password, password,
isVerified: true,
}, },
}); });
@@ -111,13 +113,27 @@ async function main() {
title: "Open Trip Papandayan Weekend", title: "Open Trip Papandayan Weekend",
description: `Pendakian santai ke Gunung Papandayan, cocok untuk pemula! description: `Pendakian santai ke Gunung Papandayan, cocok untuk pemula!
📍 Meeting Point: Alun-alun Garut, 05:00 WIB ⚠️ Bawa: Sleeping bag, jaket, headlamp, air 2L`,
🎒 Fasilitas: Transport PP, guide, tenda, makan 3x meetingPoint:
⚠️ Bawa: Sleeping bag, jaket, headlamp, air 2L "Alun-alun Garut (depan pendopo), Sabtu 05:00 WIB — detail grup WA.",
itinerary: `Sabtu
• 05:00 Meeting & briefing
• 07:00 Berangkat menuju basecamp
• 12:00 Makan siang trail
• 15:00 Camp area Pondok Salada
Itinerary: Minggu
- Sabtu: Berangkat → Basecamp → Summit → Camp • 04:00 Summit attack
- Minggu: Sunrise → Turun → Pulang`, • 08:00 Sarap & packing
• 11:00 Turun
• 16:00 Estimasi kembali ke Garut`,
whatsIncluded: `• Transport PP Garutbasecamp
• Guide lokal
• Tenda tim (kapasitas sesuai muatan)
• Konsumsi: makan 3x + snack`,
whatsExcluded: `• Tiket masuk TNGGP
• Sleeping bag & matras pribadi
• Asuransi perjalanan`,
mountain: "Gunung Papandayan", mountain: "Gunung Papandayan",
location: "Garut, Jawa Barat", location: "Garut, Jawa Barat",
date: utc(2026, 3, 23, 8, 0), date: utc(2026, 3, 23, 8, 0),
+77 -3
View File
@@ -1,4 +1,5 @@
import { prisma } from "@/lib/prisma"; import { prisma } from "@/lib/prisma";
import type { ParticipantStatus } from "@/app/generated/prisma/client";
export const participantRepo = { export const participantRepo = {
async findByTripAndUser(tripId: string, userId: string) { async findByTripAndUser(tripId: string, userId: string) {
@@ -7,9 +8,31 @@ export const participantRepo = {
}); });
}, },
async findById(id: string) {
return prisma.tripParticipant.findUnique({ where: { id } });
},
async create(tripId: string, userId: string) { async create(tripId: string, userId: string) {
return prisma.tripParticipant.create({ return prisma.tripParticipant.create({
data: { tripId, userId, status: "CONFIRMED" }, data: { tripId, userId, status: "PENDING" },
});
},
async setStatus(id: string, status: ParticipantStatus) {
return prisma.tripParticipant.update({
where: { id },
data: { status },
});
},
async setStatusAndClearPayment(id: string, status: ParticipantStatus) {
return prisma.tripParticipant.update({
where: { id },
data: {
status,
markedPaidAt: null,
paymentConfirmedAt: null,
},
}); });
}, },
@@ -22,14 +45,65 @@ export const participantRepo = {
async cancel(tripId: string, userId: string) { async cancel(tripId: string, userId: string) {
return prisma.tripParticipant.update({ return prisma.tripParticipant.update({
where: { tripId_userId: { tripId, userId } }, where: { tripId_userId: { tripId, userId } },
data: { status: "CANCELLED" }, data: {
status: "CANCELLED",
markedPaidAt: null,
paymentConfirmedAt: null,
},
}); });
}, },
async reactivate(tripId: string, userId: string) { async reactivate(tripId: string, userId: string) {
return prisma.tripParticipant.update({ return prisma.tripParticipant.update({
where: { tripId_userId: { tripId, userId } }, where: { tripId_userId: { tripId, userId } },
data: { status: "CONFIRMED" }, data: {
status: "PENDING",
markedPaidAt: null,
paymentConfirmedAt: null,
},
});
},
async markPaidByUser(tripId: string, userId: string) {
return prisma.tripParticipant.update({
where: { tripId_userId: { tripId, userId } },
data: { markedPaidAt: new Date() },
});
},
/**
* Satu baris update atomik — aman dari double-submit / race saat tandai bayar.
*/
async tryMarkPaidByUser(tripId: string, userId: string) {
return prisma.tripParticipant.updateMany({
where: {
tripId,
userId,
status: { not: "CANCELLED" },
markedPaidAt: null,
paymentConfirmedAt: null,
},
data: { markedPaidAt: new Date() },
});
},
async confirmPaymentByOrganizer(participantId: string) {
return prisma.tripParticipant.update({
where: { id: participantId },
data: { paymentConfirmedAt: new Date() },
});
},
/** Konfirmasi pembayaran hanya jika masih eligible — idempotent terhadap double klik. */
async tryConfirmPaymentByOrganizer(participantId: string) {
return prisma.tripParticipant.updateMany({
where: {
id: participantId,
markedPaidAt: { not: null },
paymentConfirmedAt: null,
status: { not: "CANCELLED" },
},
data: { paymentConfirmedAt: new Date() },
}); });
}, },
+9 -1
View File
@@ -92,7 +92,15 @@ export const tripRepo = {
return prisma.trip.findUnique({ return prisma.trip.findUnique({
where: { id }, where: { id },
include: { include: {
organizer: { select: { id: true, name: true, email: true, image: true } }, organizer: {
select: {
id: true,
name: true,
email: true,
image: true,
isVerified: true,
},
},
images: { orderBy: { order: "asc" } }, images: { orderBy: { order: "asc" } },
participants: { participants: {
include: { user: { select: { id: true, name: true, image: true } } }, include: { user: { select: { id: true, name: true, image: true } } },
+262 -23
View File
@@ -1,13 +1,30 @@
import { Prisma } from "@/app/generated/prisma/client";
import { prisma } from "@/lib/prisma";
import { tripRepo } from "@/server/repositories/trip.repo"; import { tripRepo } from "@/server/repositories/trip.repo";
import { participantRepo } from "@/server/repositories/participant.repo"; import { participantRepo } from "@/server/repositories/participant.repo";
import { LIMITS } from "@/lib/limits"; import { LIMITS } from "@/lib/limits";
import { utcStartOfDay, isTripDepartureDayPast } from "@/lib/trip-dates"; import { utcStartOfDay, isTripDepartureDayPast } from "@/lib/trip-dates";
const SERIAL_TX_ATTEMPTS = 6;
function isSerializationConflict(err: unknown): boolean {
return (
typeof err === "object" &&
err !== null &&
"code" in err &&
(err as { code: string }).code === "P2034"
);
}
interface CreateTripInput { interface CreateTripInput {
title: string; title: string;
description?: string; description?: string;
mountain: string; mountain: string;
location: string; location: string;
meetingPoint?: string;
itinerary?: string;
whatsIncluded?: string;
whatsExcluded?: string;
date: Date; date: Date;
endDate?: Date; endDate?: Date;
maxParticipants: number; maxParticipants: number;
@@ -34,17 +51,6 @@ export const tripService = {
}, },
async createTrip(input: CreateTripInput) { async createTrip(input: CreateTripInput) {
const since = utcStartOfDay(new Date());
const todayCount = await tripRepo.countByOrganizerSince(
input.organizerId,
since
);
if (todayCount >= LIMITS.MAX_TRIPS_PER_ORGANIZER_PER_DAY) {
throw new Error(
`Batas harian: maksimal ${LIMITS.MAX_TRIPS_PER_ORGANIZER_PER_DAY} trip per hari (UTC). Coba lagi besok.`
);
}
if (isTripDepartureDayPast(input.date)) { if (isTripDepartureDayPast(input.date)) {
throw new Error("Tanggal berangkat tidak boleh di masa lalu"); throw new Error("Tanggal berangkat tidak boleh di masa lalu");
} }
@@ -53,69 +59,157 @@ export const tripService = {
throw new Error("Tanggal pulang tidak boleh sebelum tanggal berangkat"); throw new Error("Tanggal pulang tidak boleh sebelum tanggal berangkat");
} }
const since = utcStartOfDay(new Date());
const images = input.imageUrls?.length const images = input.imageUrls?.length
? { ? {
create: input.imageUrls.map((url, i) => ({ url, order: i })), create: input.imageUrls.map((url, i) => ({ url, order: i })),
} }
: undefined; : undefined;
return tripRepo.create({ const tripData = {
title: input.title, title: input.title,
description: input.description, description: input.description,
mountain: input.mountain, mountain: input.mountain,
location: input.location, location: input.location,
meetingPoint: input.meetingPoint,
itinerary: input.itinerary,
whatsIncluded: input.whatsIncluded,
whatsExcluded: input.whatsExcluded,
date: input.date, date: input.date,
endDate: input.endDate, endDate: input.endDate,
maxParticipants: input.maxParticipants, maxParticipants: input.maxParticipants,
price: input.price, price: input.price,
organizer: { connect: { id: input.organizerId } }, organizer: { connect: { id: input.organizerId } },
images, images,
} satisfies Prisma.TripCreateInput;
let lastErr: unknown;
for (let attempt = 0; attempt < SERIAL_TX_ATTEMPTS; attempt++) {
try {
return await prisma.$transaction(
async (tx) => {
const todayCount = await tx.trip.count({
where: {
organizerId: input.organizerId,
createdAt: { gte: since },
},
}); });
if (todayCount >= LIMITS.MAX_TRIPS_PER_ORGANIZER_PER_DAY) {
throw new Error(
`Batas harian: maksimal ${LIMITS.MAX_TRIPS_PER_ORGANIZER_PER_DAY} trip per hari. Coba lagi besok.`
);
}
return tx.trip.create({ data: tripData });
},
{
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
maxWait: 5000,
timeout: 15000,
}
);
} catch (e) {
lastErr = e;
if (isSerializationConflict(e) && attempt < SERIAL_TX_ATTEMPTS - 1) {
continue;
}
throw e;
}
}
throw lastErr instanceof Error
? lastErr
: new Error("Gagal membuat trip. Coba lagi sebentar.");
}, },
async joinTrip(tripId: string, userId: string) { async joinTrip(tripId: string, userId: string) {
const trip = await tripRepo.findById(tripId); let lastErr: unknown;
for (let attempt = 0; attempt < SERIAL_TX_ATTEMPTS; attempt++) {
try {
return await prisma.$transaction(
async (tx) => {
const trip = await tx.trip.findUnique({
where: { id: tripId },
select: {
id: true,
status: true,
date: true,
organizerId: true,
maxParticipants: true,
},
});
if (!trip) { if (!trip) {
throw new Error("Trip tidak ditemukan"); throw new Error("Trip tidak ditemukan");
} }
if (trip.status !== "OPEN") { if (trip.status !== "OPEN") {
throw new Error("Trip tidak tersedia untuk pendaftaran"); throw new Error("Trip tidak tersedia untuk pendaftaran");
} }
if (isTripDepartureDayPast(trip.date)) { if (isTripDepartureDayPast(trip.date)) {
throw new Error( throw new Error(
"Trip sudah melewati tanggal berangkat, tidak bisa mendaftar" "Trip sudah melewati tanggal berangkat, tidak bisa mendaftar"
); );
} }
if (trip.organizerId === userId) { if (trip.organizerId === userId) {
throw new Error("Organizer tidak bisa join trip sendiri"); throw new Error("Organizer tidak bisa join trip sendiri");
} }
const existing = await participantRepo.findByTripAndUser(tripId, userId); const existing = await tx.tripParticipant.findUnique({
where: { tripId_userId: { tripId, userId } },
});
if (existing && existing.status !== "CANCELLED") { if (existing && existing.status !== "CANCELLED") {
throw new Error("Kamu sudah terdaftar di trip ini"); throw new Error("Kamu sudah terdaftar di trip ini");
} }
const participantCount = await participantRepo.countByTrip(tripId); const participantCount = await tx.tripParticipant.count({
where: { tripId, status: { not: "CANCELLED" } },
});
if (participantCount >= trip.maxParticipants) { if (participantCount >= trip.maxParticipants) {
await tripRepo.updateStatus(tripId, "FULL");
throw new Error("Trip sudah penuh"); throw new Error("Trip sudah penuh");
} }
const participant = const participant =
existing?.status === "CANCELLED" existing?.status === "CANCELLED"
? await participantRepo.reactivate(tripId, userId) ? await tx.tripParticipant.update({
: await participantRepo.create(tripId, userId); where: { tripId_userId: { tripId, userId } },
data: {
status: "PENDING",
markedPaidAt: null,
paymentConfirmedAt: null,
},
})
: await tx.tripParticipant.create({
data: { tripId, userId, status: "PENDING" },
});
const newCount = await participantRepo.countByTrip(tripId); const newCount = await tx.tripParticipant.count({
where: { tripId, status: { not: "CANCELLED" } },
});
if (newCount >= trip.maxParticipants) { if (newCount >= trip.maxParticipants) {
await tripRepo.updateStatus(tripId, "FULL"); await tx.trip.update({
where: { id: tripId },
data: { status: "FULL" },
});
} }
return participant; return participant;
}, },
{
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
maxWait: 5000,
timeout: 15000,
}
);
} catch (e) {
lastErr = e;
if (isSerializationConflict(e) && attempt < SERIAL_TX_ATTEMPTS - 1) {
continue;
}
throw e;
}
}
throw lastErr instanceof Error
? lastErr
: new Error("Pendaftaran sedang ramai. Coba lagi sebentar.");
},
async cancelJoin(tripId: string, userId: string) { async cancelJoin(tripId: string, userId: string) {
const trip = await tripRepo.findById(tripId); const trip = await tripRepo.findById(tripId);
@@ -145,4 +239,149 @@ export const tripService = {
return result; return result;
}, },
async confirmParticipant(
tripId: string,
participantId: string,
organizerId: string
) {
const trip = await tripRepo.findById(tripId);
if (!trip) {
throw new Error("Trip tidak ditemukan");
}
if (trip.organizerId !== organizerId) {
throw new Error("Hanya organizer trip ini yang bisa mengonfirmasi peserta");
}
const participant = await participantRepo.findById(participantId);
if (!participant || participant.tripId !== tripId) {
throw new Error("Peserta tidak ditemukan");
}
if (participant.status !== "PENDING") {
throw new Error("Peserta ini tidak dalam status menunggu persetujuan");
}
return participantRepo.setStatus(participantId, "CONFIRMED");
},
async rejectParticipant(
tripId: string,
participantId: string,
organizerId: string
) {
const trip = await tripRepo.findById(tripId);
if (!trip) {
throw new Error("Trip tidak ditemukan");
}
if (trip.organizerId !== organizerId) {
throw new Error("Hanya organizer trip ini yang bisa menolak permintaan bergabung");
}
const participant = await participantRepo.findById(participantId);
if (!participant || participant.tripId !== tripId) {
throw new Error("Peserta tidak ditemukan");
}
if (participant.status !== "PENDING") {
throw new Error("Hanya permintaan yang masih menunggu yang bisa ditolak");
}
await participantRepo.setStatusAndClearPayment(
participantId,
"CANCELLED"
);
if (trip.status === "FULL") {
const count = await participantRepo.countByTrip(tripId);
if (count < trip.maxParticipants) {
await tripRepo.updateStatus(tripId, "OPEN");
}
}
return { ok: true as const };
},
async markParticipantPayment(tripId: string, userId: string) {
const trip = await tripRepo.findById(tripId);
if (!trip) {
throw new Error("Trip tidak ditemukan");
}
if (isTripDepartureDayPast(trip.date)) {
throw new Error("Trip sudah lewat — pembayaran tidak perlu ditandai");
}
const p = await participantRepo.findByTripAndUser(tripId, userId);
if (!p || p.status === "CANCELLED") {
throw new Error("Kamu tidak terdaftar di trip ini");
}
if (p.paymentConfirmedAt) {
throw new Error("Pembayaran kamu sudah dikonfirmasi organizer");
}
if (p.markedPaidAt) {
throw new Error("Kamu sudah menandai sudah bayar — tunggu konfirmasi organizer");
}
const updated = await participantRepo.tryMarkPaidByUser(tripId, userId);
if (updated.count === 0) {
const again = await participantRepo.findByTripAndUser(tripId, userId);
if (!again || again.status === "CANCELLED") {
throw new Error("Kamu tidak terdaftar di trip ini");
}
if (again.paymentConfirmedAt) {
throw new Error("Pembayaran kamu sudah dikonfirmasi organizer");
}
if (again.markedPaidAt) {
throw new Error(
"Kamu sudah menandai sudah bayar — tunggu konfirmasi organizer"
);
}
throw new Error("Tidak bisa menandai pembayaran. Coba lagi sebentar.");
}
const row = await participantRepo.findByTripAndUser(tripId, userId);
if (!row) {
throw new Error("Data peserta tidak ditemukan setelah update");
}
return row;
},
async confirmParticipantPayment(
tripId: string,
participantId: string,
organizerId: string
) {
const trip = await tripRepo.findById(tripId);
if (!trip) {
throw new Error("Trip tidak ditemukan");
}
if (trip.organizerId !== organizerId) {
throw new Error("Hanya organizer yang bisa mengonfirmasi pembayaran");
}
const participant = await participantRepo.findById(participantId);
if (!participant || participant.tripId !== tripId) {
throw new Error("Peserta tidak ditemukan");
}
if (participant.status === "CANCELLED") {
throw new Error("Peserta sudah tidak aktif");
}
if (!participant.markedPaidAt) {
throw new Error("Peserta belum menandai sudah membayar");
}
if (participant.paymentConfirmedAt) {
throw new Error("Pembayaran peserta ini sudah dikonfirmasi");
}
const updated = await participantRepo.tryConfirmPaymentByOrganizer(
participantId
);
if (updated.count === 0) {
throw new Error(
"Konfirmasi tidak diproses — mungkin sudah dikonfirmasi atau pembayaran belum ditandai peserta."
);
}
return participantRepo.findById(participantId);
},
}; };
+39
View File
@@ -0,0 +1,39 @@
import { prisma } from "@/lib/prisma";
import { TRIP_LEADER_MIN_TRIPS } from "@/lib/trust";
export type OrganizerTrust = {
isVerified: boolean;
tripsCreated: number;
avgRating: number | null;
reviewCount: number;
isTripLeader: boolean;
};
export const trustService = {
async getOrganizerTrust(organizerId: string): Promise<OrganizerTrust> {
const [user, tripsCreated, reviewAgg] = await Promise.all([
prisma.user.findUnique({
where: { id: organizerId },
select: { isVerified: true },
}),
prisma.trip.count({ where: { organizerId } }),
prisma.tripReview.aggregate({
where: {
trip: { organizerId },
},
_avg: { rating: true },
_count: { _all: true },
}),
]);
const avg = reviewAgg._avg.rating;
return {
isVerified: user?.isVerified ?? false,
tripsCreated,
avgRating:
avg != null ? Math.round(Number(avg) * 10) / 10 : null,
reviewCount: reviewAgg._count._all,
isTripLeader: tripsCreated >= TRIP_LEADER_MIN_TRIPS,
};
},
};