Compare commits
5 Commits
2de8ac4086
...
ccb3437e82
| Author | SHA1 | Date | |
|---|---|---|---|
| ccb3437e82 | |||
| f5d86d2414 | |||
| 7f419638b5 | |||
| 3228ef712f | |||
| 63349a144d |
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"WebFetch(domain:unsplash.com)"
|
"WebFetch(domain:unsplash.com)",
|
||||||
|
"Bash(npx prisma *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ yarn-error.log*
|
|||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env*
|
||||||
|
|
||||||
# private uploads (KYC: KTP / selfie). Never serve directly.
|
# private uploads (KYC: KTP / liveness). Never serve directly.
|
||||||
/uploads/
|
/uploads/
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
|
|||||||
+1
-1
@@ -236,7 +236,7 @@ Alur data mengikuti pola yang sama: **UI (`app/`) → server actions (`features/
|
|||||||
|
|
||||||
### Verifikasi organizer (KYC ringan)
|
### Verifikasi organizer (KYC ringan)
|
||||||
|
|
||||||
- Model `OrganizerVerification` (1-1 ke `User`) menyimpan KTP (nama, NIK unik, tanggal lahir, alamat), URL foto KTP & selfie, data rekening bank, dan status `PENDING` / `APPROVED` / `REJECTED` + audit reviewer.
|
- Model `OrganizerVerification` (1-1 ke `User`) menyimpan KTP (nama, NIK unik, tanggal lahir, alamat), storage key foto KTP & foto liveness (user memegang kertas tulisan "SETRIP" sebagai bukti pengajuan), data rekening bank, dan status `PENDING` / `APPROVED` / `REJECTED` + audit reviewer.
|
||||||
- Alur: user submit di `/verify` (`features/organizer/`) → admin review di `/admin/verifications` → setujui/tolak.
|
- Alur: user submit di `/verify` (`features/organizer/`) → admin review di `/admin/verifications` → setujui/tolak.
|
||||||
- **Gate trip berbayar:** `createTripAction` menolak `price > 0` jika user belum `APPROVED` (`organizerService.isApproved`).
|
- **Gate trip berbayar:** `createTripAction` menolak `price > 0` jika user belum `APPROVED` (`organizerService.isApproved`).
|
||||||
- **Akses admin:** `lib/admin.ts → isAdminEmail()` membaca `ADMIN_EMAILS` (env, comma-separated).
|
- **Akses admin:** `lib/admin.ts → isAdminEmail()` membaca `ADMIN_EMAILS` (env, comma-separated).
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Tanpa login, pengguna tetap bisa melihat daftar trip dan detail trip, tetapi tid
|
|||||||
|
|
||||||
Organizer **tidak** bisa join trip sendiri; di detail trip ditampilkan bahwa dia adalah organizer trip ini.
|
Organizer **tidak** bisa join trip sendiri; di detail trip ditampilkan bahwa dia adalah organizer trip ini.
|
||||||
|
|
||||||
**Verifikasi organizer (untuk trip berbayar).** Trip dengan harga > 0 hanya bisa dibuat oleh user yang sudah mengirim KTP, selfie, dan data rekening di `/verify` lalu disetujui admin di `/admin/verifications`. Trip gratis tidak butuh verifikasi. Organizer yang sudah disetujui tampil dengan badge **✅ Verified Organizer** di halaman detail trip.
|
**Verifikasi organizer (untuk trip berbayar).** Trip dengan harga > 0 hanya bisa dibuat oleh user yang sudah mengirim KTP, foto liveness (memegang kertas tulisan "SETRIP"), dan data rekening di `/verify` lalu disetujui admin di `/admin/verifications`. Trip gratis tidak butuh verifikasi. Organizer yang sudah disetujui tampil dengan badge **✅ Verified Organizer** di halaman detail trip.
|
||||||
|
|
||||||
### 3. Peserta: mencari trip
|
### 3. Peserta: mencari trip
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ Alur ini menggambarkan satu peserta dari pertama kali mendaftar sampai pembayara
|
|||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| Trip | `Trip`: judul, gunung, lokasi, tanggal, kuota, harga, status trip (`OPEN` / `FULL` / …), meeting point, itinerary, termasuk/tidak termasuk, 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)`: status **`PENDING`** / **`CONFIRMED`** / **`CANCELLED`**, serta **`markedPaidAt`** & **`paymentConfirmedAt`** untuk alur bayar manual |
|
| Peserta | `TripParticipant` unik per `(tripId, userId)`: status **`PENDING`** / **`CONFIRMED`** / **`CANCELLED`**, serta **`markedPaidAt`** & **`paymentConfirmedAt`** untuk alur bayar manual |
|
||||||
| Organizer (kepercayaan) | `OrganizerVerification` (1-1 ke `User`) berisi KTP, selfie, rekening, dan status (`PENDING` / `APPROVED` / `REJECTED`); badge **Verified Organizer** muncul ketika `status === "APPROVED"` (helper `lib/trust.ts → isVerifiedOrganizer`). Agregat rating & jumlah trip dihitung dari ulasan & trip. |
|
| Organizer (kepercayaan) | `OrganizerVerification` (1-1 ke `User`) berisi KTP, foto liveness (memegang kertas "SETRIP"), rekening, dan status (`PENDING` / `APPROVED` / `REJECTED`); badge **Verified Organizer** muncul ketika `status === "APPROVED"` (helper `lib/trust.ts → isVerifiedOrganizer`). Agregat rating & jumlah trip dihitung dari ulasan & trip. |
|
||||||
| Persetujuan T&C / Privasi | `User.acceptedTermsAndPrivacy` + `User.acceptedAt`, dicentang saat registrasi (link ke `/terms` & `/privacy`). |
|
| Persetujuan T&C / Privasi | `User.acceptedTermsAndPrivacy` + `User.acceptedAt`, dicentang saat registrasi (link ke `/terms` & `/privacy`). |
|
||||||
|
|
||||||
## Menjalankan secara lokal
|
## Menjalankan secara lokal
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# Setrip — Social Repositioning Roadmap
|
||||||
|
|
||||||
|
Status implementasi reposisi dari "open trip pendakian" → "platform untuk menemukan teman aktivitas & trip bareng".
|
||||||
|
|
||||||
|
> **Prinsip pembeda:** Setrip bukan trip-marketplace. Trip adalah kendaraan untuk koneksi sosial (stranger → teman → circle). Setiap fitur dievaluasi: apakah memperkuat **find a companion**, atau hanya **book a trip**? Kalau cuma yang kedua → tolak.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase A — Quick wins (sinyal sosial dari data yang sudah ada) ✅
|
||||||
|
|
||||||
|
Selesai. `tsc --noEmit` lulus. Migration `20260508120000_add_profile_vibe` belum di-apply ke DB.
|
||||||
|
|
||||||
|
| # | Item | Status | File |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A1 | Hapus ikon `🏔️` hardcoded di trip detail → ikon + label kategori dinamis | ✅ | [app/trips/[id]/page.tsx](app/trips/[id]/page.tsx) |
|
||||||
|
| A2 | Banner onboarding profil di layout (muncul kalau `UserProfile` kosong) | ✅ | [components/shared/profile-nudge-banner.tsx](components/shared/profile-nudge-banner.tsx), [app/layout.tsx](app/layout.tsx) |
|
||||||
|
| A3 | Confirmed-peserta dirombak: chip nama → kartu (avatar + kota + 3 tag minat) | ✅ | [server/repositories/trip.repo.ts](server/repositories/trip.repo.ts), [app/trips/[id]/page.tsx](app/trips/[id]/page.tsx) |
|
||||||
|
| A4 | Field `vibe` (CHILL/BALANCED/HARDCORE) di `UserProfile` + UI editor + badge di profil publik | ✅ | [prisma/schema.prisma](prisma/schema.prisma), [prisma/migrations/20260508120000_add_profile_vibe/migration.sql](prisma/migrations/20260508120000_add_profile_vibe/migration.sql), [lib/vibe.ts](lib/vibe.ts), [features/profile/schemas.ts](features/profile/schemas.ts), [features/profile/actions.ts](features/profile/actions.ts), [server/repositories/profile.repo.ts](server/repositories/profile.repo.ts), [server/repositories/user.repo.ts](server/repositories/user.repo.ts), [server/services/profile.service.ts](server/services/profile.service.ts), [features/profile/components/profile-editor.tsx](features/profile/components/profile-editor.tsx), [app/profile/page.tsx](app/profile/page.tsx), [app/u/[id]/page.tsx](app/u/[id]/page.tsx) |
|
||||||
|
|
||||||
|
**Tindakan manual:** jalankan `npx prisma migrate deploy` (atau `dev`) untuk apply migration `20260507185257_add_user_profile` + `20260508120000_add_profile_vibe`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase B — Discovery (people-first, bukan price-first) ✅
|
||||||
|
|
||||||
|
Selesai. `tsc --noEmit` lulus. Migration `20260508130000_add_trip_vibe` belum di-apply ke DB.
|
||||||
|
|
||||||
|
**Keputusan desain:** `Trip` reuse enum `Vibe` yang sama dengan `UserProfile` (alih-alih bikin `pace`/`level` baru) supaya matching peserta↔trip langsung selaras tanpa mapping.
|
||||||
|
|
||||||
|
| # | Item | Status | File |
|
||||||
|
|---|---|---|---|
|
||||||
|
| B1 | Halaman `/people` — daftar user dengan profil terisi | ✅ | [app/people/page.tsx](app/people/page.tsx), [server/repositories/user.repo.ts](server/repositories/user.repo.ts) (`findPeople`), [server/services/profile.service.ts](server/services/profile.service.ts) |
|
||||||
|
| B2 | Filter kota, interests, vibe di `/people` | ✅ | [features/profile/components/people-filter.tsx](features/profile/components/people-filter.tsx), [features/profile/components/user-card.tsx](features/profile/components/user-card.tsx) |
|
||||||
|
| B3 | Field `vibe` di `Trip` + tampil di trip detail & TripCard + filter di `/trips` | ✅ | [prisma/schema.prisma](prisma/schema.prisma), [prisma/migrations/20260508130000_add_trip_vibe/migration.sql](prisma/migrations/20260508130000_add_trip_vibe/migration.sql), [features/trip/schemas.ts](features/trip/schemas.ts), [features/trip/actions.ts](features/trip/actions.ts), [server/services/trip.service.ts](server/services/trip.service.ts), [features/trip/components/create-trip-form.tsx](features/trip/components/create-trip-form.tsx), [features/trip/components/trip-card.tsx](features/trip/components/trip-card.tsx), [app/trips/[id]/page.tsx](app/trips/[id]/page.tsx) |
|
||||||
|
| B4 | TripCard: 3 avatar peserta confirmed + counter `+N` | ✅ | [server/repositories/trip.repo.ts](server/repositories/trip.repo.ts) (include participants di `findOpen`), [features/trip/components/trip-card.tsx](features/trip/components/trip-card.tsx), [app/page.tsx](app/page.tsx), [app/trips/page.tsx](app/trips/page.tsx) |
|
||||||
|
| B5 | TripCard: badge "✨ X peserta sama minat" untuk user login | ✅ | [features/trip/components/trip-card.tsx](features/trip/components/trip-card.tsx) (compute overlap), homepage & `/trips` (fetch viewer interests) |
|
||||||
|
| B6 | Filter ukuran grup (Small ≤10 / Medium 11–20 / Large 21+) | ✅ | [server/repositories/trip.repo.ts](server/repositories/trip.repo.ts) (`GroupSize` filter), [features/trip/components/trip-filter.tsx](features/trip/components/trip-filter.tsx), [app/trips/page.tsx](app/trips/page.tsx) |
|
||||||
|
| B7 | Section "Budget Friendly" → "Lagi Ramai" (social proof) | ✅ | [app/page.tsx](app/page.tsx) — sort by `participantCount desc`, framing "kamu nggak bakal jalan sendirian" |
|
||||||
|
| B+ | Link `/people` di navbar (desktop + mobile) | ✅ | [components/shared/navbar.tsx](components/shared/navbar.tsx) |
|
||||||
|
|
||||||
|
**Tindakan manual:** jalankan `npx prisma migrate deploy` untuk apply migration `20260508130000_add_trip_vibe` (selain 2 migration Phase A yang masih pending).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Patch — KYC liveness photo rename (di luar fase social repositioning)
|
||||||
|
|
||||||
|
Mengubah foto liveness dari "selfie memegang KTP" (pola KYC standar) menjadi "memegang kertas tulisan SETRIP".
|
||||||
|
|
||||||
|
| Item | Status | File |
|
||||||
|
|---|---|---|
|
||||||
|
| Field `selfieKey` → `livenessKey` di `OrganizerVerification` | ✅ | [prisma/schema.prisma](prisma/schema.prisma), [prisma/migrations/20260508140000_rename_selfie_to_liveness/migration.sql](prisma/migrations/20260508140000_rename_selfie_to_liveness/migration.sql) |
|
||||||
|
| Storage kind `selfie` → `liveness` (path `liveness/<id>.<ext>`) | ✅ | [lib/secure-storage.ts](lib/secure-storage.ts) |
|
||||||
|
| Validasi + action + service + verify-form + review-card | ✅ | [features/organizer/schemas.ts](features/organizer/schemas.ts), [features/organizer/actions.ts](features/organizer/actions.ts), [server/services/organizer.service.ts](server/services/organizer.service.ts), [features/organizer/components/verify-form.tsx](features/organizer/components/verify-form.tsx), [features/organizer/components/review-card.tsx](features/organizer/components/review-card.tsx) |
|
||||||
|
| API routes `/api/upload/kyc` & `/api/files/kyc/[id]/[kind]` | ✅ | [app/api/upload/kyc/route.ts](app/api/upload/kyc/route.ts), [app/api/files/kyc/[id]/[kind]/route.ts](app/api/files/kyc/%5Bid%5D/%5Bkind%5D/route.ts) |
|
||||||
|
| Halaman verify, admin, seed, README, ARCHITECTURE | ✅ | [app/verify/page.tsx](app/verify/page.tsx), [app/admin/verifications/page.tsx](app/admin/verifications/page.tsx), [app/create-trip/page.tsx](app/create-trip/page.tsx), [prisma/seed.ts](prisma/seed.ts), [README.md](README.md), [ARCHITECTURE.md](ARCHITECTURE.md) |
|
||||||
|
|
||||||
|
**Trade-off keamanan yang sudah dikomunikasikan:** pola "selfie + KTP" lebih kuat (membuktikan KTP fisik di tangan pemilik). Pola "selfie + kertas SETRIP" lebih lemah dari sisi binding KTP↔orang, tapi mengurangi paparan KTP user dan masih mencegah replay dari platform lain. Risiko fraud naik sedikit — tetap dipilih atas request user.
|
||||||
|
|
||||||
|
**Catatan migrasi data lama:** kolom DB di-rename, tapi nilai-nilai key lama masih punya prefix `selfie/` (mis. `selfie/abc.jpg`). Setelah migration di-apply, validasi schema menolak prefix lama → user dengan pengajuan PENDING perlu re-upload foto liveness baru. Folder fisik `uploads/private/selfie/` tidak dipakai lagi, bisa dihapus manual setelah konfirmasi tidak ada data aktif yang merujuk.
|
||||||
|
|
||||||
|
**Tindakan manual:** jalankan `npx prisma migrate deploy` untuk apply `20260508140000_rename_selfie_to_liveness` (sekarang total 4 migration pending kalau belum pernah deploy).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase C — Interaksi & continuity (separate, lebih besar) ⏳
|
||||||
|
|
||||||
|
Belum mulai. Setiap item bisa jadi PR terpisah karena perlu schema baru + UI substansial.
|
||||||
|
|
||||||
|
| # | Item | Status | Catatan |
|
||||||
|
|---|---|---|---|
|
||||||
|
| C1 | Model `TripMessage` — Q&A publik per trip (sebelum berangkat) | ⏳ pending | Schema + actions + UI di trip detail. Calon peserta tanya organizer tanpa keluar app. |
|
||||||
|
| C2 | Group chat untuk peserta CONFIRMED (post-confirmation) | ⏳ pending | Bisa pakai tabel `TripMessage` yang sama dengan flag `audience` (PUBLIC/CONFIRMED_ONLY). |
|
||||||
|
| C3 | Model `Connection` (follow / circle) antar user | ⏳ pending | Foundation untuk "from strangers → circle". Halaman "Circle saya". |
|
||||||
|
| C4 | Notifikasi: organizer punya pending join, peserta dapat balasan Q&A, dst | ⏳ pending | Bisa email dulu, in-app belakangan. |
|
||||||
|
| C5 | Post-trip continuity: tombol "follow buddies dari trip ini" + album foto bareng | ⏳ pending | Momen konversi stranger → circle terbesar saat ini terbuang. |
|
||||||
|
| C6 | Review user (bukan cuma trip) — reputasi peserta (no-show? kooperatif?) | ⏳ pending | Lengkapi trust layer. Anti-scam. |
|
||||||
|
| C7 | Onboarding flow wajib post-register (bukan banner) — minta minimal 3 interests + city + vibe sebelum bisa join trip | ⏳ pending | Banner Phase A2 cuma soft nudge. Hard-gate saat user pertama kali pencet "Join". |
|
||||||
|
| C8 | Referral / invite-with-link | ⏳ pending | Loop pertumbuhan komunitas. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❌ Anti-list (yang harus DITOLAK kalau muncul)
|
||||||
|
|
||||||
|
Fitur-fitur ini akan menarik Setrip ke arena OTA (Traveloka/Klook) yang tidak bisa dimenangkan:
|
||||||
|
|
||||||
|
- Booking hotel / tiket pesawat
|
||||||
|
- Tour massal tanpa interaksi (>30 orang, bus pariwisata)
|
||||||
|
- Mass listing dari travel agent (B2B aggregator)
|
||||||
|
- Filter & sort harga yang lebih agresif (price-low-to-high, dll.) — perkuat framing harga-dulu
|
||||||
|
- Affiliate/komisi dari pihak ketiga yang bukan organizer terverifikasi
|
||||||
|
- SEO-driven mass content untuk destinasi (artikel "10 Gunung Terbaik di Jawa") tanpa angle social
|
||||||
|
- Integrasi pembayaran kompleks (split-bill, escrow rumit) sebelum chat dasar (C1) ada — prioritas terbalik
|
||||||
|
|
||||||
|
Kalau muncul request ke arah ini, tanya: "ini meningkatkan kemungkinan dua orang asing kenalan, atau cuma memudahkan transaksi?" Kalau jawabannya yang kedua → tolak / tunda.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Konteks positioning (referensi cepat)
|
||||||
|
|
||||||
|
**Untuk siapa:** orang yang ingin pergi tapi tidak punya teman, ingin kenalan baru lewat aktivitas bareng.
|
||||||
|
|
||||||
|
**Bukan untuk:** orang yang sudah punya grup dan tinggal cari paket trip termurah.
|
||||||
|
|
||||||
|
**Categories yang valid** (semua harus punya: organizer, group kecil, interaksi sosial):
|
||||||
|
- Core: hiking, camping
|
||||||
|
- Natural expansion: snorkeling, diving, island hopping
|
||||||
|
- Social activity: city trip, kulineran, konser bareng
|
||||||
|
- Semi-professional: workshop, kelas outdoor, retreat
|
||||||
|
|
||||||
|
**Tagline:** "Pergi bareng, bukan sendiri" / "From strangers to travel buddies".
|
||||||
@@ -60,7 +60,8 @@ export default async function AdminVerificationsPage({ searchParams }: PageProps
|
|||||||
Review Verifikasi Organizer
|
Review Verifikasi Organizer
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-1 text-sm text-neutral-500">
|
<p className="mt-1 text-sm text-neutral-500">
|
||||||
Periksa data KTP, selfie, dan rekening sebelum menyetujui.
|
Periksa data KTP, foto liveness (memegang kertas SETRIP), dan rekening
|
||||||
|
sebelum menyetujui.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export async function GET(_req: NextRequest, ctx: RouteCtx) {
|
|||||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = kind === "ktp" ? verification.ktpImageKey : verification.selfieKey;
|
const key = kind === "ktp" ? verification.ktpImageKey : verification.livenessKey;
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return NextResponse.json({ error: "File belum diunggah" }, { status: 404 });
|
return NextResponse.json({ error: "File belum diunggah" }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ export async function POST(req: NextRequest) {
|
|||||||
const file = form.get("file");
|
const file = form.get("file");
|
||||||
|
|
||||||
if (!isKycKind(kind)) {
|
if (!isKycKind(kind)) {
|
||||||
return NextResponse.json({ error: "kind harus 'ktp' atau 'selfie'" }, { status: 400 });
|
return NextResponse.json(
|
||||||
|
{ error: "kind harus 'ktp' atau 'liveness'" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!(file instanceof File)) {
|
if (!(file instanceof File)) {
|
||||||
return NextResponse.json({ error: "File wajib diisi" }, { status: 400 });
|
return NextResponse.json({ error: "File wajib diisi" }, { status: 400 });
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ function VerificationBanner({
|
|||||||
<p className="mt-1 text-sm text-neutral-700">
|
<p className="mt-1 text-sm text-neutral-700">
|
||||||
{isRejected
|
{isRejected
|
||||||
? "Pengajuan sebelumnya ditolak. Untuk membuat trip berbayar, perbaiki data dan ajukan ulang."
|
? "Pengajuan sebelumnya ditolak. Untuk membuat trip berbayar, perbaiki data dan ajukan ulang."
|
||||||
: "Untuk membuat trip berbayar, akun kamu perlu diverifikasi (KTP, selfie, & rekening). Trip gratis tidak butuh verifikasi."}
|
: "Untuk membuat trip berbayar, akun kamu perlu diverifikasi (KTP, foto memegang kertas SETRIP, & rekening). Trip gratis tidak butuh verifikasi."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ export * from './enums';
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type User = Prisma.UserModel
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model UserProfile
|
||||||
|
* Profil sosial publik. Berisi info yang user pilih untuk dibagikan ke peserta lain
|
||||||
|
* (bio, kota, minat, vibe). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
|
||||||
|
*/
|
||||||
|
export type UserProfile = Prisma.UserProfileModel
|
||||||
/**
|
/**
|
||||||
* Model Account
|
* Model Account
|
||||||
* Tabel link akun OAuth pihak ketiga (Google, dst). Diisi oleh PrismaAdapter NextAuth.
|
* Tabel link akun OAuth pihak ketiga (Google, dst). Diisi oleh PrismaAdapter NextAuth.
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ export { Prisma }
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type User = Prisma.UserModel
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model UserProfile
|
||||||
|
* Profil sosial publik. Berisi info yang user pilih untuk dibagikan ke peserta lain
|
||||||
|
* (bio, kota, minat, vibe). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
|
||||||
|
*/
|
||||||
|
export type UserProfile = Prisma.UserProfileModel
|
||||||
/**
|
/**
|
||||||
* Model Account
|
* Model Account
|
||||||
* Tabel link akun OAuth pihak ketiga (Google, dst). Diisi oleh PrismaAdapter NextAuth.
|
* Tabel link akun OAuth pihak ketiga (Google, dst). Diisi oleh PrismaAdapter NextAuth.
|
||||||
|
|||||||
@@ -148,6 +148,23 @@ export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
|||||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EnumVibeNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.Vibe | Prisma.EnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel> | $Enums.Vibe | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnumVibeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.Vibe | Prisma.EnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: Prisma.NestedEnumVibeNullableWithAggregatesFilter<$PrismaModel> | $Enums.Vibe | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
export type IntNullableFilter<$PrismaModel = never> = {
|
export type IntNullableFilter<$PrismaModel = never> = {
|
||||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
@@ -417,6 +434,23 @@ export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
|||||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NestedEnumVibeNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.Vibe | Prisma.EnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel> | $Enums.Vibe | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedEnumVibeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.Vibe | Prisma.EnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: $Enums.Vibe[] | Prisma.ListEnumVibeFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: Prisma.NestedEnumVibeNullableWithAggregatesFilter<$PrismaModel> | $Enums.Vibe | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedEnumVibeNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
|||||||
@@ -9,6 +9,15 @@
|
|||||||
* 🟢 You can import this file directly.
|
* 🟢 You can import this file directly.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export const Vibe = {
|
||||||
|
CHILL: 'CHILL',
|
||||||
|
BALANCED: 'BALANCED',
|
||||||
|
HARDCORE: 'HARDCORE'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type Vibe = (typeof Vibe)[keyof typeof Vibe]
|
||||||
|
|
||||||
|
|
||||||
export const VerificationStatus = {
|
export const VerificationStatus = {
|
||||||
PENDING: 'PENDING',
|
PENDING: 'PENDING',
|
||||||
APPROVED: 'APPROVED',
|
APPROVED: 'APPROVED',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -385,6 +385,7 @@ type FieldRefInputType<Model, FieldType> = Model extends never ? never : FieldRe
|
|||||||
|
|
||||||
export const ModelName = {
|
export const ModelName = {
|
||||||
User: 'User',
|
User: 'User',
|
||||||
|
UserProfile: 'UserProfile',
|
||||||
Account: 'Account',
|
Account: 'Account',
|
||||||
OrganizerVerification: 'OrganizerVerification',
|
OrganizerVerification: 'OrganizerVerification',
|
||||||
Trip: 'Trip',
|
Trip: 'Trip',
|
||||||
@@ -406,7 +407,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
omit: GlobalOmitOptions
|
omit: GlobalOmitOptions
|
||||||
}
|
}
|
||||||
meta: {
|
meta: {
|
||||||
modelProps: "user" | "account" | "organizerVerification" | "trip" | "tripReview" | "tripImage" | "tripParticipant"
|
modelProps: "user" | "userProfile" | "account" | "organizerVerification" | "trip" | "tripReview" | "tripImage" | "tripParticipant"
|
||||||
txIsolationLevel: TransactionIsolationLevel
|
txIsolationLevel: TransactionIsolationLevel
|
||||||
}
|
}
|
||||||
model: {
|
model: {
|
||||||
@@ -484,6 +485,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
UserProfile: {
|
||||||
|
payload: Prisma.$UserProfilePayload<ExtArgs>
|
||||||
|
fields: Prisma.UserProfileFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.UserProfileFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.UserProfileFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.UserProfileFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.UserProfileFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.UserProfileFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.UserProfileCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.UserProfileCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.UserProfileCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.UserProfileDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.UserProfileUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.UserProfileDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.UserProfileUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.UserProfileUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.UserProfileUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$UserProfilePayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.UserProfileAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateUserProfile>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.UserProfileGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.UserProfileGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.UserProfileCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.UserProfileCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Account: {
|
Account: {
|
||||||
payload: Prisma.$AccountPayload<ExtArgs>
|
payload: Prisma.$AccountPayload<ExtArgs>
|
||||||
fields: Prisma.AccountFieldRefs
|
fields: Prisma.AccountFieldRefs
|
||||||
@@ -983,6 +1058,21 @@ export const UserScalarFieldEnum = {
|
|||||||
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const UserProfileScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
bio: 'bio',
|
||||||
|
city: 'city',
|
||||||
|
interests: 'interests',
|
||||||
|
instagram: 'instagram',
|
||||||
|
vibe: 'vibe',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UserProfileScalarFieldEnum = (typeof UserProfileScalarFieldEnum)[keyof typeof UserProfileScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const AccountScalarFieldEnum = {
|
export const AccountScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
@@ -1010,7 +1100,7 @@ export const OrganizerVerificationScalarFieldEnum = {
|
|||||||
birthDate: 'birthDate',
|
birthDate: 'birthDate',
|
||||||
address: 'address',
|
address: 'address',
|
||||||
ktpImageKey: 'ktpImageKey',
|
ktpImageKey: 'ktpImageKey',
|
||||||
selfieKey: 'selfieKey',
|
livenessKey: 'livenessKey',
|
||||||
bankName: 'bankName',
|
bankName: 'bankName',
|
||||||
bankAccountNumber: 'bankAccountNumber',
|
bankAccountNumber: 'bankAccountNumber',
|
||||||
bankAccountName: 'bankAccountName',
|
bankAccountName: 'bankAccountName',
|
||||||
@@ -1041,6 +1131,7 @@ export const TripScalarFieldEnum = {
|
|||||||
endDate: 'endDate',
|
endDate: 'endDate',
|
||||||
maxParticipants: 'maxParticipants',
|
maxParticipants: 'maxParticipants',
|
||||||
price: 'price',
|
price: 'price',
|
||||||
|
vibe: 'vibe',
|
||||||
status: 'status',
|
status: 'status',
|
||||||
createdAt: 'createdAt',
|
createdAt: 'createdAt',
|
||||||
updatedAt: 'updatedAt',
|
updatedAt: 'updatedAt',
|
||||||
@@ -1152,6 +1243,20 @@ export type BooleanFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to a field of type 'Vibe'
|
||||||
|
*/
|
||||||
|
export type EnumVibeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Vibe'>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to a field of type 'Vibe[]'
|
||||||
|
*/
|
||||||
|
export type ListEnumVibeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Vibe[]'>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to a field of type 'Int'
|
* Reference to a field of type 'Int'
|
||||||
*/
|
*/
|
||||||
@@ -1346,6 +1451,7 @@ export type PrismaClientOptions = ({
|
|||||||
}
|
}
|
||||||
export type GlobalOmitConfig = {
|
export type GlobalOmitConfig = {
|
||||||
user?: Prisma.UserOmit
|
user?: Prisma.UserOmit
|
||||||
|
userProfile?: Prisma.UserProfileOmit
|
||||||
account?: Prisma.AccountOmit
|
account?: Prisma.AccountOmit
|
||||||
organizerVerification?: Prisma.OrganizerVerificationOmit
|
organizerVerification?: Prisma.OrganizerVerificationOmit
|
||||||
trip?: Prisma.TripOmit
|
trip?: Prisma.TripOmit
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const AnyNull = runtime.AnyNull
|
|||||||
|
|
||||||
export const ModelName = {
|
export const ModelName = {
|
||||||
User: 'User',
|
User: 'User',
|
||||||
|
UserProfile: 'UserProfile',
|
||||||
Account: 'Account',
|
Account: 'Account',
|
||||||
OrganizerVerification: 'OrganizerVerification',
|
OrganizerVerification: 'OrganizerVerification',
|
||||||
Trip: 'Trip',
|
Trip: 'Trip',
|
||||||
@@ -92,6 +93,21 @@ export const UserScalarFieldEnum = {
|
|||||||
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const UserProfileScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
bio: 'bio',
|
||||||
|
city: 'city',
|
||||||
|
interests: 'interests',
|
||||||
|
instagram: 'instagram',
|
||||||
|
vibe: 'vibe',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UserProfileScalarFieldEnum = (typeof UserProfileScalarFieldEnum)[keyof typeof UserProfileScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const AccountScalarFieldEnum = {
|
export const AccountScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
@@ -119,7 +135,7 @@ export const OrganizerVerificationScalarFieldEnum = {
|
|||||||
birthDate: 'birthDate',
|
birthDate: 'birthDate',
|
||||||
address: 'address',
|
address: 'address',
|
||||||
ktpImageKey: 'ktpImageKey',
|
ktpImageKey: 'ktpImageKey',
|
||||||
selfieKey: 'selfieKey',
|
livenessKey: 'livenessKey',
|
||||||
bankName: 'bankName',
|
bankName: 'bankName',
|
||||||
bankAccountNumber: 'bankAccountNumber',
|
bankAccountNumber: 'bankAccountNumber',
|
||||||
bankAccountName: 'bankAccountName',
|
bankAccountName: 'bankAccountName',
|
||||||
@@ -150,6 +166,7 @@ export const TripScalarFieldEnum = {
|
|||||||
endDate: 'endDate',
|
endDate: 'endDate',
|
||||||
maxParticipants: 'maxParticipants',
|
maxParticipants: 'maxParticipants',
|
||||||
price: 'price',
|
price: 'price',
|
||||||
|
vibe: 'vibe',
|
||||||
status: 'status',
|
status: 'status',
|
||||||
createdAt: 'createdAt',
|
createdAt: 'createdAt',
|
||||||
updatedAt: 'updatedAt',
|
updatedAt: 'updatedAt',
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
* 🟢 You can import this file directly.
|
* 🟢 You can import this file directly.
|
||||||
*/
|
*/
|
||||||
export type * from './models/User'
|
export type * from './models/User'
|
||||||
|
export type * from './models/UserProfile'
|
||||||
export type * from './models/Account'
|
export type * from './models/Account'
|
||||||
export type * from './models/OrganizerVerification'
|
export type * from './models/OrganizerVerification'
|
||||||
export type * from './models/Trip'
|
export type * from './models/Trip'
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export type OrganizerVerificationMinAggregateOutputType = {
|
|||||||
birthDate: Date | null
|
birthDate: Date | null
|
||||||
address: string | null
|
address: string | null
|
||||||
ktpImageKey: string | null
|
ktpImageKey: string | null
|
||||||
selfieKey: string | null
|
livenessKey: string | null
|
||||||
bankName: string | null
|
bankName: string | null
|
||||||
bankAccountNumber: string | null
|
bankAccountNumber: string | null
|
||||||
bankAccountName: string | null
|
bankAccountName: string | null
|
||||||
@@ -55,7 +55,7 @@ export type OrganizerVerificationMaxAggregateOutputType = {
|
|||||||
birthDate: Date | null
|
birthDate: Date | null
|
||||||
address: string | null
|
address: string | null
|
||||||
ktpImageKey: string | null
|
ktpImageKey: string | null
|
||||||
selfieKey: string | null
|
livenessKey: string | null
|
||||||
bankName: string | null
|
bankName: string | null
|
||||||
bankAccountNumber: string | null
|
bankAccountNumber: string | null
|
||||||
bankAccountName: string | null
|
bankAccountName: string | null
|
||||||
@@ -77,7 +77,7 @@ export type OrganizerVerificationCountAggregateOutputType = {
|
|||||||
birthDate: number
|
birthDate: number
|
||||||
address: number
|
address: number
|
||||||
ktpImageKey: number
|
ktpImageKey: number
|
||||||
selfieKey: number
|
livenessKey: number
|
||||||
bankName: number
|
bankName: number
|
||||||
bankAccountNumber: number
|
bankAccountNumber: number
|
||||||
bankAccountName: number
|
bankAccountName: number
|
||||||
@@ -101,7 +101,7 @@ export type OrganizerVerificationMinAggregateInputType = {
|
|||||||
birthDate?: true
|
birthDate?: true
|
||||||
address?: true
|
address?: true
|
||||||
ktpImageKey?: true
|
ktpImageKey?: true
|
||||||
selfieKey?: true
|
livenessKey?: true
|
||||||
bankName?: true
|
bankName?: true
|
||||||
bankAccountNumber?: true
|
bankAccountNumber?: true
|
||||||
bankAccountName?: true
|
bankAccountName?: true
|
||||||
@@ -123,7 +123,7 @@ export type OrganizerVerificationMaxAggregateInputType = {
|
|||||||
birthDate?: true
|
birthDate?: true
|
||||||
address?: true
|
address?: true
|
||||||
ktpImageKey?: true
|
ktpImageKey?: true
|
||||||
selfieKey?: true
|
livenessKey?: true
|
||||||
bankName?: true
|
bankName?: true
|
||||||
bankAccountNumber?: true
|
bankAccountNumber?: true
|
||||||
bankAccountName?: true
|
bankAccountName?: true
|
||||||
@@ -145,7 +145,7 @@ export type OrganizerVerificationCountAggregateInputType = {
|
|||||||
birthDate?: true
|
birthDate?: true
|
||||||
address?: true
|
address?: true
|
||||||
ktpImageKey?: true
|
ktpImageKey?: true
|
||||||
selfieKey?: true
|
livenessKey?: true
|
||||||
bankName?: true
|
bankName?: true
|
||||||
bankAccountNumber?: true
|
bankAccountNumber?: true
|
||||||
bankAccountName?: true
|
bankAccountName?: true
|
||||||
@@ -240,7 +240,7 @@ export type OrganizerVerificationGroupByOutputType = {
|
|||||||
birthDate: Date
|
birthDate: Date
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -283,7 +283,7 @@ export type OrganizerVerificationWhereInput = {
|
|||||||
birthDate?: Prisma.DateTimeFilter<"OrganizerVerification"> | Date | string
|
birthDate?: Prisma.DateTimeFilter<"OrganizerVerification"> | Date | string
|
||||||
address?: Prisma.StringFilter<"OrganizerVerification"> | string
|
address?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
ktpImageKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
ktpImageKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
selfieKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
livenessKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankAccountNumber?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankAccountNumber?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankAccountName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankAccountName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
@@ -307,7 +307,7 @@ export type OrganizerVerificationOrderByWithRelationInput = {
|
|||||||
birthDate?: Prisma.SortOrder
|
birthDate?: Prisma.SortOrder
|
||||||
address?: Prisma.SortOrder
|
address?: Prisma.SortOrder
|
||||||
ktpImageKey?: Prisma.SortOrder
|
ktpImageKey?: Prisma.SortOrder
|
||||||
selfieKey?: Prisma.SortOrder
|
livenessKey?: Prisma.SortOrder
|
||||||
bankName?: Prisma.SortOrder
|
bankName?: Prisma.SortOrder
|
||||||
bankAccountNumber?: Prisma.SortOrder
|
bankAccountNumber?: Prisma.SortOrder
|
||||||
bankAccountName?: Prisma.SortOrder
|
bankAccountName?: Prisma.SortOrder
|
||||||
@@ -334,7 +334,7 @@ export type OrganizerVerificationWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
birthDate?: Prisma.DateTimeFilter<"OrganizerVerification"> | Date | string
|
birthDate?: Prisma.DateTimeFilter<"OrganizerVerification"> | Date | string
|
||||||
address?: Prisma.StringFilter<"OrganizerVerification"> | string
|
address?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
ktpImageKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
ktpImageKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
selfieKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
livenessKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankAccountNumber?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankAccountNumber?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankAccountName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankAccountName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
@@ -358,7 +358,7 @@ export type OrganizerVerificationOrderByWithAggregationInput = {
|
|||||||
birthDate?: Prisma.SortOrder
|
birthDate?: Prisma.SortOrder
|
||||||
address?: Prisma.SortOrder
|
address?: Prisma.SortOrder
|
||||||
ktpImageKey?: Prisma.SortOrder
|
ktpImageKey?: Prisma.SortOrder
|
||||||
selfieKey?: Prisma.SortOrder
|
livenessKey?: Prisma.SortOrder
|
||||||
bankName?: Prisma.SortOrder
|
bankName?: Prisma.SortOrder
|
||||||
bankAccountNumber?: Prisma.SortOrder
|
bankAccountNumber?: Prisma.SortOrder
|
||||||
bankAccountName?: Prisma.SortOrder
|
bankAccountName?: Prisma.SortOrder
|
||||||
@@ -386,7 +386,7 @@ export type OrganizerVerificationScalarWhereWithAggregatesInput = {
|
|||||||
birthDate?: Prisma.DateTimeWithAggregatesFilter<"OrganizerVerification"> | Date | string
|
birthDate?: Prisma.DateTimeWithAggregatesFilter<"OrganizerVerification"> | Date | string
|
||||||
address?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
address?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
||||||
ktpImageKey?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
ktpImageKey?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
||||||
selfieKey?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
livenessKey?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
||||||
bankName?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
bankName?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
||||||
bankAccountNumber?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
bankAccountNumber?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
||||||
bankAccountName?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
bankAccountName?: Prisma.StringWithAggregatesFilter<"OrganizerVerification"> | string
|
||||||
@@ -407,7 +407,7 @@ export type OrganizerVerificationCreateInput = {
|
|||||||
birthDate: Date | string
|
birthDate: Date | string
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -430,7 +430,7 @@ export type OrganizerVerificationUncheckedCreateInput = {
|
|||||||
birthDate: Date | string
|
birthDate: Date | string
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -451,7 +451,7 @@ export type OrganizerVerificationUpdateInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -474,7 +474,7 @@ export type OrganizerVerificationUncheckedUpdateInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -496,7 +496,7 @@ export type OrganizerVerificationCreateManyInput = {
|
|||||||
birthDate: Date | string
|
birthDate: Date | string
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -517,7 +517,7 @@ export type OrganizerVerificationUpdateManyMutationInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -538,7 +538,7 @@ export type OrganizerVerificationUncheckedUpdateManyInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -575,7 +575,7 @@ export type OrganizerVerificationCountOrderByAggregateInput = {
|
|||||||
birthDate?: Prisma.SortOrder
|
birthDate?: Prisma.SortOrder
|
||||||
address?: Prisma.SortOrder
|
address?: Prisma.SortOrder
|
||||||
ktpImageKey?: Prisma.SortOrder
|
ktpImageKey?: Prisma.SortOrder
|
||||||
selfieKey?: Prisma.SortOrder
|
livenessKey?: Prisma.SortOrder
|
||||||
bankName?: Prisma.SortOrder
|
bankName?: Prisma.SortOrder
|
||||||
bankAccountNumber?: Prisma.SortOrder
|
bankAccountNumber?: Prisma.SortOrder
|
||||||
bankAccountName?: Prisma.SortOrder
|
bankAccountName?: Prisma.SortOrder
|
||||||
@@ -597,7 +597,7 @@ export type OrganizerVerificationMaxOrderByAggregateInput = {
|
|||||||
birthDate?: Prisma.SortOrder
|
birthDate?: Prisma.SortOrder
|
||||||
address?: Prisma.SortOrder
|
address?: Prisma.SortOrder
|
||||||
ktpImageKey?: Prisma.SortOrder
|
ktpImageKey?: Prisma.SortOrder
|
||||||
selfieKey?: Prisma.SortOrder
|
livenessKey?: Prisma.SortOrder
|
||||||
bankName?: Prisma.SortOrder
|
bankName?: Prisma.SortOrder
|
||||||
bankAccountNumber?: Prisma.SortOrder
|
bankAccountNumber?: Prisma.SortOrder
|
||||||
bankAccountName?: Prisma.SortOrder
|
bankAccountName?: Prisma.SortOrder
|
||||||
@@ -619,7 +619,7 @@ export type OrganizerVerificationMinOrderByAggregateInput = {
|
|||||||
birthDate?: Prisma.SortOrder
|
birthDate?: Prisma.SortOrder
|
||||||
address?: Prisma.SortOrder
|
address?: Prisma.SortOrder
|
||||||
ktpImageKey?: Prisma.SortOrder
|
ktpImageKey?: Prisma.SortOrder
|
||||||
selfieKey?: Prisma.SortOrder
|
livenessKey?: Prisma.SortOrder
|
||||||
bankName?: Prisma.SortOrder
|
bankName?: Prisma.SortOrder
|
||||||
bankAccountNumber?: Prisma.SortOrder
|
bankAccountNumber?: Prisma.SortOrder
|
||||||
bankAccountName?: Prisma.SortOrder
|
bankAccountName?: Prisma.SortOrder
|
||||||
@@ -718,7 +718,7 @@ export type OrganizerVerificationCreateWithoutUserInput = {
|
|||||||
birthDate: Date | string
|
birthDate: Date | string
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -739,7 +739,7 @@ export type OrganizerVerificationUncheckedCreateWithoutUserInput = {
|
|||||||
birthDate: Date | string
|
birthDate: Date | string
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -765,7 +765,7 @@ export type OrganizerVerificationCreateWithoutReviewedByInput = {
|
|||||||
birthDate: Date | string
|
birthDate: Date | string
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -787,7 +787,7 @@ export type OrganizerVerificationUncheckedCreateWithoutReviewedByInput = {
|
|||||||
birthDate: Date | string
|
birthDate: Date | string
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -828,7 +828,7 @@ export type OrganizerVerificationUpdateWithoutUserInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -849,7 +849,7 @@ export type OrganizerVerificationUncheckedUpdateWithoutUserInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -890,7 +890,7 @@ export type OrganizerVerificationScalarWhereInput = {
|
|||||||
birthDate?: Prisma.DateTimeFilter<"OrganizerVerification"> | Date | string
|
birthDate?: Prisma.DateTimeFilter<"OrganizerVerification"> | Date | string
|
||||||
address?: Prisma.StringFilter<"OrganizerVerification"> | string
|
address?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
ktpImageKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
ktpImageKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
selfieKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
livenessKey?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankAccountNumber?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankAccountNumber?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
bankAccountName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
bankAccountName?: Prisma.StringFilter<"OrganizerVerification"> | string
|
||||||
@@ -912,7 +912,7 @@ export type OrganizerVerificationCreateManyReviewedByInput = {
|
|||||||
birthDate: Date | string
|
birthDate: Date | string
|
||||||
address: string
|
address: string
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -932,7 +932,7 @@ export type OrganizerVerificationUpdateWithoutReviewedByInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -954,7 +954,7 @@ export type OrganizerVerificationUncheckedUpdateWithoutReviewedByInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -975,7 +975,7 @@ export type OrganizerVerificationUncheckedUpdateManyWithoutReviewedByInput = {
|
|||||||
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
birthDate?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
address?: Prisma.StringFieldUpdateOperationsInput | string
|
address?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
ktpImageKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
selfieKey?: Prisma.StringFieldUpdateOperationsInput | string
|
livenessKey?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
bankAccountName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
@@ -998,7 +998,7 @@ export type OrganizerVerificationSelect<ExtArgs extends runtime.Types.Extensions
|
|||||||
birthDate?: boolean
|
birthDate?: boolean
|
||||||
address?: boolean
|
address?: boolean
|
||||||
ktpImageKey?: boolean
|
ktpImageKey?: boolean
|
||||||
selfieKey?: boolean
|
livenessKey?: boolean
|
||||||
bankName?: boolean
|
bankName?: boolean
|
||||||
bankAccountNumber?: boolean
|
bankAccountNumber?: boolean
|
||||||
bankAccountName?: boolean
|
bankAccountName?: boolean
|
||||||
@@ -1022,7 +1022,7 @@ export type OrganizerVerificationSelectCreateManyAndReturn<ExtArgs extends runti
|
|||||||
birthDate?: boolean
|
birthDate?: boolean
|
||||||
address?: boolean
|
address?: boolean
|
||||||
ktpImageKey?: boolean
|
ktpImageKey?: boolean
|
||||||
selfieKey?: boolean
|
livenessKey?: boolean
|
||||||
bankName?: boolean
|
bankName?: boolean
|
||||||
bankAccountNumber?: boolean
|
bankAccountNumber?: boolean
|
||||||
bankAccountName?: boolean
|
bankAccountName?: boolean
|
||||||
@@ -1046,7 +1046,7 @@ export type OrganizerVerificationSelectUpdateManyAndReturn<ExtArgs extends runti
|
|||||||
birthDate?: boolean
|
birthDate?: boolean
|
||||||
address?: boolean
|
address?: boolean
|
||||||
ktpImageKey?: boolean
|
ktpImageKey?: boolean
|
||||||
selfieKey?: boolean
|
livenessKey?: boolean
|
||||||
bankName?: boolean
|
bankName?: boolean
|
||||||
bankAccountNumber?: boolean
|
bankAccountNumber?: boolean
|
||||||
bankAccountName?: boolean
|
bankAccountName?: boolean
|
||||||
@@ -1070,7 +1070,7 @@ export type OrganizerVerificationSelectScalar = {
|
|||||||
birthDate?: boolean
|
birthDate?: boolean
|
||||||
address?: boolean
|
address?: boolean
|
||||||
ktpImageKey?: boolean
|
ktpImageKey?: boolean
|
||||||
selfieKey?: boolean
|
livenessKey?: boolean
|
||||||
bankName?: boolean
|
bankName?: boolean
|
||||||
bankAccountNumber?: boolean
|
bankAccountNumber?: boolean
|
||||||
bankAccountName?: boolean
|
bankAccountName?: boolean
|
||||||
@@ -1083,7 +1083,7 @@ export type OrganizerVerificationSelectScalar = {
|
|||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OrganizerVerificationOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "userId" | "fullName" | "nikEncrypted" | "nikHash" | "birthDate" | "address" | "ktpImageKey" | "selfieKey" | "bankName" | "bankAccountNumber" | "bankAccountName" | "status" | "rejectionReason" | "reviewedAt" | "reviewedById" | "verifiedAt" | "createdAt" | "updatedAt", ExtArgs["result"]["organizerVerification"]>
|
export type OrganizerVerificationOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "userId" | "fullName" | "nikEncrypted" | "nikHash" | "birthDate" | "address" | "ktpImageKey" | "livenessKey" | "bankName" | "bankAccountNumber" | "bankAccountName" | "status" | "rejectionReason" | "reviewedAt" | "reviewedById" | "verifiedAt" | "createdAt" | "updatedAt", ExtArgs["result"]["organizerVerification"]>
|
||||||
export type OrganizerVerificationInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type OrganizerVerificationInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
|
||||||
reviewedBy?: boolean | Prisma.OrganizerVerification$reviewedByArgs<ExtArgs>
|
reviewedBy?: boolean | Prisma.OrganizerVerification$reviewedByArgs<ExtArgs>
|
||||||
@@ -1125,9 +1125,10 @@ export type $OrganizerVerificationPayload<ExtArgs extends runtime.Types.Extensio
|
|||||||
*/
|
*/
|
||||||
ktpImageKey: string
|
ktpImageKey: string
|
||||||
/**
|
/**
|
||||||
* Storage key selfie memegang KTP.
|
* Storage key foto liveness — user memegang kertas bertuliskan "SETRIP".
|
||||||
|
* (Sebelumnya: selfie memegang KTP. Diganti supaya user tidak perlu memajang KTP dua kali.)
|
||||||
*/
|
*/
|
||||||
selfieKey: string
|
livenessKey: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAccountNumber: string
|
bankAccountNumber: string
|
||||||
bankAccountName: string
|
bankAccountName: string
|
||||||
@@ -1571,7 +1572,7 @@ export interface OrganizerVerificationFieldRefs {
|
|||||||
readonly birthDate: Prisma.FieldRef<"OrganizerVerification", 'DateTime'>
|
readonly birthDate: Prisma.FieldRef<"OrganizerVerification", 'DateTime'>
|
||||||
readonly address: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
readonly address: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
||||||
readonly ktpImageKey: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
readonly ktpImageKey: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
||||||
readonly selfieKey: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
readonly livenessKey: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
||||||
readonly bankName: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
readonly bankName: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
||||||
readonly bankAccountNumber: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
readonly bankAccountNumber: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
||||||
readonly bankAccountName: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
readonly bankAccountName: Prisma.FieldRef<"OrganizerVerification", 'String'>
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export type TripMinAggregateOutputType = {
|
|||||||
endDate: Date | null
|
endDate: Date | null
|
||||||
maxParticipants: number | null
|
maxParticipants: number | null
|
||||||
price: number | null
|
price: number | null
|
||||||
|
vibe: $Enums.Vibe | null
|
||||||
status: $Enums.TripStatus | null
|
status: $Enums.TripStatus | null
|
||||||
createdAt: Date | null
|
createdAt: Date | null
|
||||||
updatedAt: Date | null
|
updatedAt: Date | null
|
||||||
@@ -72,6 +73,7 @@ export type TripMaxAggregateOutputType = {
|
|||||||
endDate: Date | null
|
endDate: Date | null
|
||||||
maxParticipants: number | null
|
maxParticipants: number | null
|
||||||
price: number | null
|
price: number | null
|
||||||
|
vibe: $Enums.Vibe | null
|
||||||
status: $Enums.TripStatus | null
|
status: $Enums.TripStatus | null
|
||||||
createdAt: Date | null
|
createdAt: Date | null
|
||||||
updatedAt: Date | null
|
updatedAt: Date | null
|
||||||
@@ -93,6 +95,7 @@ export type TripCountAggregateOutputType = {
|
|||||||
endDate: number
|
endDate: number
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe: number
|
||||||
status: number
|
status: number
|
||||||
createdAt: number
|
createdAt: number
|
||||||
updatedAt: number
|
updatedAt: number
|
||||||
@@ -126,6 +129,7 @@ export type TripMinAggregateInputType = {
|
|||||||
endDate?: true
|
endDate?: true
|
||||||
maxParticipants?: true
|
maxParticipants?: true
|
||||||
price?: true
|
price?: true
|
||||||
|
vibe?: true
|
||||||
status?: true
|
status?: true
|
||||||
createdAt?: true
|
createdAt?: true
|
||||||
updatedAt?: true
|
updatedAt?: true
|
||||||
@@ -147,6 +151,7 @@ export type TripMaxAggregateInputType = {
|
|||||||
endDate?: true
|
endDate?: true
|
||||||
maxParticipants?: true
|
maxParticipants?: true
|
||||||
price?: true
|
price?: true
|
||||||
|
vibe?: true
|
||||||
status?: true
|
status?: true
|
||||||
createdAt?: true
|
createdAt?: true
|
||||||
updatedAt?: true
|
updatedAt?: true
|
||||||
@@ -168,6 +173,7 @@ export type TripCountAggregateInputType = {
|
|||||||
endDate?: true
|
endDate?: true
|
||||||
maxParticipants?: true
|
maxParticipants?: true
|
||||||
price?: true
|
price?: true
|
||||||
|
vibe?: true
|
||||||
status?: true
|
status?: true
|
||||||
createdAt?: true
|
createdAt?: true
|
||||||
updatedAt?: true
|
updatedAt?: true
|
||||||
@@ -276,6 +282,7 @@ export type TripGroupByOutputType = {
|
|||||||
endDate: Date | null
|
endDate: Date | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe: $Enums.Vibe | null
|
||||||
status: $Enums.TripStatus
|
status: $Enums.TripStatus
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
@@ -320,6 +327,7 @@ export type TripWhereInput = {
|
|||||||
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
|
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFilter<"Trip"> | number
|
maxParticipants?: Prisma.IntFilter<"Trip"> | number
|
||||||
price?: Prisma.IntFilter<"Trip"> | number
|
price?: Prisma.IntFilter<"Trip"> | number
|
||||||
|
vibe?: Prisma.EnumVibeNullableFilter<"Trip"> | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
||||||
@@ -345,6 +353,7 @@ export type TripOrderByWithRelationInput = {
|
|||||||
endDate?: Prisma.SortOrderInput | Prisma.SortOrder
|
endDate?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
maxParticipants?: Prisma.SortOrder
|
maxParticipants?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
price?: Prisma.SortOrder
|
||||||
|
vibe?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
status?: Prisma.SortOrder
|
status?: Prisma.SortOrder
|
||||||
createdAt?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
updatedAt?: Prisma.SortOrder
|
updatedAt?: Prisma.SortOrder
|
||||||
@@ -373,6 +382,7 @@ export type TripWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
|
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFilter<"Trip"> | number
|
maxParticipants?: Prisma.IntFilter<"Trip"> | number
|
||||||
price?: Prisma.IntFilter<"Trip"> | number
|
price?: Prisma.IntFilter<"Trip"> | number
|
||||||
|
vibe?: Prisma.EnumVibeNullableFilter<"Trip"> | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
||||||
@@ -398,6 +408,7 @@ export type TripOrderByWithAggregationInput = {
|
|||||||
endDate?: Prisma.SortOrderInput | Prisma.SortOrder
|
endDate?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
maxParticipants?: Prisma.SortOrder
|
maxParticipants?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
price?: Prisma.SortOrder
|
||||||
|
vibe?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
status?: Prisma.SortOrder
|
status?: Prisma.SortOrder
|
||||||
createdAt?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
updatedAt?: Prisma.SortOrder
|
updatedAt?: Prisma.SortOrder
|
||||||
@@ -427,6 +438,7 @@ export type TripScalarWhereWithAggregatesInput = {
|
|||||||
endDate?: Prisma.DateTimeNullableWithAggregatesFilter<"Trip"> | Date | string | null
|
endDate?: Prisma.DateTimeNullableWithAggregatesFilter<"Trip"> | Date | string | null
|
||||||
maxParticipants?: Prisma.IntWithAggregatesFilter<"Trip"> | number
|
maxParticipants?: Prisma.IntWithAggregatesFilter<"Trip"> | number
|
||||||
price?: Prisma.IntWithAggregatesFilter<"Trip"> | number
|
price?: Prisma.IntWithAggregatesFilter<"Trip"> | number
|
||||||
|
vibe?: Prisma.EnumVibeNullableWithAggregatesFilter<"Trip"> | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusWithAggregatesFilter<"Trip"> | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusWithAggregatesFilter<"Trip"> | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeWithAggregatesFilter<"Trip"> | Date | string
|
createdAt?: Prisma.DateTimeWithAggregatesFilter<"Trip"> | Date | string
|
||||||
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Trip"> | Date | string
|
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Trip"> | Date | string
|
||||||
@@ -448,6 +460,7 @@ export type TripCreateInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -472,6 +485,7 @@ export type TripUncheckedCreateInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -496,6 +510,7 @@ export type TripUpdateInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -520,6 +535,7 @@ export type TripUncheckedUpdateInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -544,6 +560,7 @@ export type TripCreateManyInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -565,6 +582,7 @@ export type TripUpdateManyMutationInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -585,6 +603,7 @@ export type TripUncheckedUpdateManyInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -616,6 +635,7 @@ export type TripCountOrderByAggregateInput = {
|
|||||||
endDate?: Prisma.SortOrder
|
endDate?: Prisma.SortOrder
|
||||||
maxParticipants?: Prisma.SortOrder
|
maxParticipants?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
price?: Prisma.SortOrder
|
||||||
|
vibe?: Prisma.SortOrder
|
||||||
status?: Prisma.SortOrder
|
status?: Prisma.SortOrder
|
||||||
createdAt?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
updatedAt?: Prisma.SortOrder
|
updatedAt?: Prisma.SortOrder
|
||||||
@@ -642,6 +662,7 @@ export type TripMaxOrderByAggregateInput = {
|
|||||||
endDate?: Prisma.SortOrder
|
endDate?: Prisma.SortOrder
|
||||||
maxParticipants?: Prisma.SortOrder
|
maxParticipants?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
price?: Prisma.SortOrder
|
||||||
|
vibe?: Prisma.SortOrder
|
||||||
status?: Prisma.SortOrder
|
status?: Prisma.SortOrder
|
||||||
createdAt?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
updatedAt?: Prisma.SortOrder
|
updatedAt?: Prisma.SortOrder
|
||||||
@@ -663,6 +684,7 @@ export type TripMinOrderByAggregateInput = {
|
|||||||
endDate?: Prisma.SortOrder
|
endDate?: Prisma.SortOrder
|
||||||
maxParticipants?: Prisma.SortOrder
|
maxParticipants?: Prisma.SortOrder
|
||||||
price?: Prisma.SortOrder
|
price?: Prisma.SortOrder
|
||||||
|
vibe?: Prisma.SortOrder
|
||||||
status?: Prisma.SortOrder
|
status?: Prisma.SortOrder
|
||||||
createdAt?: Prisma.SortOrder
|
createdAt?: Prisma.SortOrder
|
||||||
updatedAt?: Prisma.SortOrder
|
updatedAt?: Prisma.SortOrder
|
||||||
@@ -794,6 +816,7 @@ export type TripCreateWithoutOrganizerInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -817,6 +840,7 @@ export type TripUncheckedCreateWithoutOrganizerInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -869,6 +893,7 @@ export type TripScalarWhereInput = {
|
|||||||
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
|
endDate?: Prisma.DateTimeNullableFilter<"Trip"> | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFilter<"Trip"> | number
|
maxParticipants?: Prisma.IntFilter<"Trip"> | number
|
||||||
price?: Prisma.IntFilter<"Trip"> | number
|
price?: Prisma.IntFilter<"Trip"> | number
|
||||||
|
vibe?: Prisma.EnumVibeNullableFilter<"Trip"> | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFilter<"Trip"> | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
createdAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
updatedAt?: Prisma.DateTimeFilter<"Trip"> | Date | string
|
||||||
@@ -890,6 +915,7 @@ export type TripCreateWithoutReviewsInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -913,6 +939,7 @@ export type TripUncheckedCreateWithoutReviewsInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -952,6 +979,7 @@ export type TripUpdateWithoutReviewsInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -975,6 +1003,7 @@ export type TripUncheckedUpdateWithoutReviewsInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -998,6 +1027,7 @@ export type TripCreateWithoutImagesInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -1021,6 +1051,7 @@ export type TripUncheckedCreateWithoutImagesInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -1060,6 +1091,7 @@ export type TripUpdateWithoutImagesInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -1083,6 +1115,7 @@ export type TripUncheckedUpdateWithoutImagesInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -1106,6 +1139,7 @@ export type TripCreateWithoutParticipantsInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -1129,6 +1163,7 @@ export type TripUncheckedCreateWithoutParticipantsInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -1168,6 +1203,7 @@ export type TripUpdateWithoutParticipantsInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -1191,6 +1227,7 @@ export type TripUncheckedUpdateWithoutParticipantsInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -1214,6 +1251,7 @@ export type TripCreateManyOrganizerInput = {
|
|||||||
endDate?: Date | string | null
|
endDate?: Date | string | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
vibe?: $Enums.Vibe | null
|
||||||
status?: $Enums.TripStatus
|
status?: $Enums.TripStatus
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
@@ -1234,6 +1272,7 @@ export type TripUpdateWithoutOrganizerInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -1257,6 +1296,7 @@ export type TripUncheckedUpdateWithoutOrganizerInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -1280,6 +1320,7 @@ export type TripUncheckedUpdateManyWithoutOrganizerInput = {
|
|||||||
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
price?: Prisma.IntFieldUpdateOperationsInput | number
|
price?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
|
||||||
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
@@ -1349,6 +1390,7 @@ export type TripSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
|||||||
endDate?: boolean
|
endDate?: boolean
|
||||||
maxParticipants?: boolean
|
maxParticipants?: boolean
|
||||||
price?: boolean
|
price?: boolean
|
||||||
|
vibe?: boolean
|
||||||
status?: boolean
|
status?: boolean
|
||||||
createdAt?: boolean
|
createdAt?: boolean
|
||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
@@ -1375,6 +1417,7 @@ export type TripSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensio
|
|||||||
endDate?: boolean
|
endDate?: boolean
|
||||||
maxParticipants?: boolean
|
maxParticipants?: boolean
|
||||||
price?: boolean
|
price?: boolean
|
||||||
|
vibe?: boolean
|
||||||
status?: boolean
|
status?: boolean
|
||||||
createdAt?: boolean
|
createdAt?: boolean
|
||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
@@ -1397,6 +1440,7 @@ export type TripSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensio
|
|||||||
endDate?: boolean
|
endDate?: boolean
|
||||||
maxParticipants?: boolean
|
maxParticipants?: boolean
|
||||||
price?: boolean
|
price?: boolean
|
||||||
|
vibe?: boolean
|
||||||
status?: boolean
|
status?: boolean
|
||||||
createdAt?: boolean
|
createdAt?: boolean
|
||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
@@ -1419,13 +1463,14 @@ export type TripSelectScalar = {
|
|||||||
endDate?: boolean
|
endDate?: boolean
|
||||||
maxParticipants?: boolean
|
maxParticipants?: boolean
|
||||||
price?: boolean
|
price?: boolean
|
||||||
|
vibe?: boolean
|
||||||
status?: boolean
|
status?: boolean
|
||||||
createdAt?: boolean
|
createdAt?: boolean
|
||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
organizerId?: boolean
|
organizerId?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TripOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "title" | "description" | "category" | "destination" | "location" | "meetingPoint" | "itinerary" | "whatsIncluded" | "whatsExcluded" | "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" | "category" | "destination" | "location" | "meetingPoint" | "itinerary" | "whatsIncluded" | "whatsExcluded" | "date" | "endDate" | "maxParticipants" | "price" | "vibe" | "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>
|
||||||
@@ -1481,6 +1526,10 @@ export type $TripPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||||||
endDate: Date | null
|
endDate: Date | null
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
price: number
|
price: number
|
||||||
|
/**
|
||||||
|
* Ritme/energi trip — dipakai untuk matching dengan vibe user.
|
||||||
|
*/
|
||||||
|
vibe: $Enums.Vibe | null
|
||||||
status: $Enums.TripStatus
|
status: $Enums.TripStatus
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
@@ -1926,6 +1975,7 @@ export interface TripFieldRefs {
|
|||||||
readonly endDate: Prisma.FieldRef<"Trip", 'DateTime'>
|
readonly endDate: Prisma.FieldRef<"Trip", 'DateTime'>
|
||||||
readonly maxParticipants: Prisma.FieldRef<"Trip", 'Int'>
|
readonly maxParticipants: Prisma.FieldRef<"Trip", 'Int'>
|
||||||
readonly price: Prisma.FieldRef<"Trip", 'Int'>
|
readonly price: Prisma.FieldRef<"Trip", 'Int'>
|
||||||
|
readonly vibe: Prisma.FieldRef<"Trip", 'Vibe'>
|
||||||
readonly status: Prisma.FieldRef<"Trip", 'TripStatus'>
|
readonly status: Prisma.FieldRef<"Trip", 'TripStatus'>
|
||||||
readonly createdAt: Prisma.FieldRef<"Trip", 'DateTime'>
|
readonly createdAt: Prisma.FieldRef<"Trip", 'DateTime'>
|
||||||
readonly updatedAt: Prisma.FieldRef<"Trip", 'DateTime'>
|
readonly updatedAt: Prisma.FieldRef<"Trip", 'DateTime'>
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ export type UserWhereInput = {
|
|||||||
tripReviews?: Prisma.TripReviewListRelationFilter
|
tripReviews?: Prisma.TripReviewListRelationFilter
|
||||||
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
|
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
|
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
|
||||||
|
profile?: Prisma.XOR<Prisma.UserProfileNullableScalarRelationFilter, Prisma.UserProfileWhereInput> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserOrderByWithRelationInput = {
|
export type UserOrderByWithRelationInput = {
|
||||||
@@ -247,6 +248,7 @@ export type UserOrderByWithRelationInput = {
|
|||||||
tripReviews?: Prisma.TripReviewOrderByRelationAggregateInput
|
tripReviews?: Prisma.TripReviewOrderByRelationAggregateInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationOrderByWithRelationInput
|
organizerVerification?: Prisma.OrganizerVerificationOrderByWithRelationInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationOrderByRelationAggregateInput
|
reviewedVerifications?: Prisma.OrganizerVerificationOrderByRelationAggregateInput
|
||||||
|
profile?: Prisma.UserProfileOrderByWithRelationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserWhereUniqueInput = Prisma.AtLeast<{
|
export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||||
@@ -269,6 +271,7 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
tripReviews?: Prisma.TripReviewListRelationFilter
|
tripReviews?: Prisma.TripReviewListRelationFilter
|
||||||
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
|
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
|
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
|
||||||
|
profile?: Prisma.XOR<Prisma.UserProfileNullableScalarRelationFilter, Prisma.UserProfileWhereInput> | null
|
||||||
}, "id" | "email">
|
}, "id" | "email">
|
||||||
|
|
||||||
export type UserOrderByWithAggregationInput = {
|
export type UserOrderByWithAggregationInput = {
|
||||||
@@ -320,6 +323,7 @@ export type UserCreateInput = {
|
|||||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateInput = {
|
export type UserUncheckedCreateInput = {
|
||||||
@@ -339,6 +343,7 @@ export type UserUncheckedCreateInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUpdateInput = {
|
export type UserUpdateInput = {
|
||||||
@@ -358,6 +363,7 @@ export type UserUpdateInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateInput = {
|
export type UserUncheckedUpdateInput = {
|
||||||
@@ -377,6 +383,7 @@ export type UserUncheckedUpdateInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateManyInput = {
|
export type UserCreateManyInput = {
|
||||||
@@ -487,6 +494,20 @@ export type DateTimeFieldUpdateOperationsInput = {
|
|||||||
set?: Date | string
|
set?: Date | string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UserCreateNestedOneWithoutProfileInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.UserCreateWithoutProfileInput, Prisma.UserUncheckedCreateWithoutProfileInput>
|
||||||
|
connectOrCreate?: Prisma.UserCreateOrConnectWithoutProfileInput
|
||||||
|
connect?: Prisma.UserWhereUniqueInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUpdateOneRequiredWithoutProfileNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.UserCreateWithoutProfileInput, Prisma.UserUncheckedCreateWithoutProfileInput>
|
||||||
|
connectOrCreate?: Prisma.UserCreateOrConnectWithoutProfileInput
|
||||||
|
upsert?: Prisma.UserUpsertWithoutProfileInput
|
||||||
|
connect?: Prisma.UserWhereUniqueInput
|
||||||
|
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutProfileInput, Prisma.UserUpdateWithoutProfileInput>, Prisma.UserUncheckedUpdateWithoutProfileInput>
|
||||||
|
}
|
||||||
|
|
||||||
export type UserCreateNestedOneWithoutAccountsInput = {
|
export type UserCreateNestedOneWithoutAccountsInput = {
|
||||||
create?: Prisma.XOR<Prisma.UserCreateWithoutAccountsInput, Prisma.UserUncheckedCreateWithoutAccountsInput>
|
create?: Prisma.XOR<Prisma.UserCreateWithoutAccountsInput, Prisma.UserUncheckedCreateWithoutAccountsInput>
|
||||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutAccountsInput
|
connectOrCreate?: Prisma.UserCreateOrConnectWithoutAccountsInput
|
||||||
@@ -573,6 +594,98 @@ export type UserUpdateOneRequiredWithoutParticipationsNestedInput = {
|
|||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutParticipationsInput, Prisma.UserUpdateWithoutParticipationsInput>, Prisma.UserUncheckedUpdateWithoutParticipationsInput>
|
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutParticipationsInput, Prisma.UserUpdateWithoutParticipationsInput>, Prisma.UserUncheckedUpdateWithoutParticipationsInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UserCreateWithoutProfileInput = {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
password?: string | null
|
||||||
|
image?: string | null
|
||||||
|
emailVerified?: Date | string | null
|
||||||
|
acceptedTermsAndPrivacy?: boolean
|
||||||
|
acceptedAt?: Date | string | null
|
||||||
|
createdAt?: Date | string
|
||||||
|
updatedAt?: Date | string
|
||||||
|
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
|
||||||
|
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
|
||||||
|
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
||||||
|
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||||
|
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||||
|
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUncheckedCreateWithoutProfileInput = {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
password?: string | null
|
||||||
|
image?: string | null
|
||||||
|
emailVerified?: Date | string | null
|
||||||
|
acceptedTermsAndPrivacy?: boolean
|
||||||
|
acceptedAt?: Date | string | null
|
||||||
|
createdAt?: Date | string
|
||||||
|
updatedAt?: Date | string
|
||||||
|
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
|
||||||
|
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||||
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||||
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserCreateOrConnectWithoutProfileInput = {
|
||||||
|
where: Prisma.UserWhereUniqueInput
|
||||||
|
create: Prisma.XOR<Prisma.UserCreateWithoutProfileInput, Prisma.UserUncheckedCreateWithoutProfileInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUpsertWithoutProfileInput = {
|
||||||
|
update: Prisma.XOR<Prisma.UserUpdateWithoutProfileInput, Prisma.UserUncheckedUpdateWithoutProfileInput>
|
||||||
|
create: Prisma.XOR<Prisma.UserCreateWithoutProfileInput, Prisma.UserUncheckedCreateWithoutProfileInput>
|
||||||
|
where?: Prisma.UserWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUpdateToOneWithWhereWithoutProfileInput = {
|
||||||
|
where?: Prisma.UserWhereInput
|
||||||
|
data: Prisma.XOR<Prisma.UserUpdateWithoutProfileInput, Prisma.UserUncheckedUpdateWithoutProfileInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUpdateWithoutProfileInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
password?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
|
||||||
|
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
|
||||||
|
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
|
||||||
|
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||||
|
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||||
|
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserUncheckedUpdateWithoutProfileInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
password?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||||
|
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
|
||||||
|
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||||
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||||
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutAccountsInput = {
|
export type UserCreateWithoutAccountsInput = {
|
||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
@@ -589,6 +702,7 @@ export type UserCreateWithoutAccountsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutAccountsInput = {
|
export type UserUncheckedCreateWithoutAccountsInput = {
|
||||||
@@ -607,6 +721,7 @@ export type UserUncheckedCreateWithoutAccountsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutAccountsInput = {
|
export type UserCreateOrConnectWithoutAccountsInput = {
|
||||||
@@ -641,6 +756,7 @@ export type UserUpdateWithoutAccountsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutAccountsInput = {
|
export type UserUncheckedUpdateWithoutAccountsInput = {
|
||||||
@@ -659,6 +775,7 @@ export type UserUncheckedUpdateWithoutAccountsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutOrganizerVerificationInput = {
|
export type UserCreateWithoutOrganizerVerificationInput = {
|
||||||
@@ -677,6 +794,7 @@ export type UserCreateWithoutOrganizerVerificationInput = {
|
|||||||
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
||||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutOrganizerVerificationInput = {
|
export type UserUncheckedCreateWithoutOrganizerVerificationInput = {
|
||||||
@@ -695,6 +813,7 @@ export type UserUncheckedCreateWithoutOrganizerVerificationInput = {
|
|||||||
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
||||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutOrganizerVerificationInput = {
|
export type UserCreateOrConnectWithoutOrganizerVerificationInput = {
|
||||||
@@ -718,6 +837,7 @@ export type UserCreateWithoutReviewedVerificationsInput = {
|
|||||||
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
||||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||||
|
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutReviewedVerificationsInput = {
|
export type UserUncheckedCreateWithoutReviewedVerificationsInput = {
|
||||||
@@ -736,6 +856,7 @@ export type UserUncheckedCreateWithoutReviewedVerificationsInput = {
|
|||||||
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
||||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutReviewedVerificationsInput = {
|
export type UserCreateOrConnectWithoutReviewedVerificationsInput = {
|
||||||
@@ -770,6 +891,7 @@ export type UserUpdateWithoutOrganizerVerificationInput = {
|
|||||||
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
|
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
|
||||||
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutOrganizerVerificationInput = {
|
export type UserUncheckedUpdateWithoutOrganizerVerificationInput = {
|
||||||
@@ -788,6 +910,7 @@ export type UserUncheckedUpdateWithoutOrganizerVerificationInput = {
|
|||||||
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
|
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
|
||||||
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUpsertWithoutReviewedVerificationsInput = {
|
export type UserUpsertWithoutReviewedVerificationsInput = {
|
||||||
@@ -817,6 +940,7 @@ export type UserUpdateWithoutReviewedVerificationsInput = {
|
|||||||
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
|
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
|
||||||
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||||
|
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutReviewedVerificationsInput = {
|
export type UserUncheckedUpdateWithoutReviewedVerificationsInput = {
|
||||||
@@ -835,6 +959,7 @@ export type UserUncheckedUpdateWithoutReviewedVerificationsInput = {
|
|||||||
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
|
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
|
||||||
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutTripsInput = {
|
export type UserCreateWithoutTripsInput = {
|
||||||
@@ -853,6 +978,7 @@ export type UserCreateWithoutTripsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutTripsInput = {
|
export type UserUncheckedCreateWithoutTripsInput = {
|
||||||
@@ -871,6 +997,7 @@ export type UserUncheckedCreateWithoutTripsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutTripsInput = {
|
export type UserCreateOrConnectWithoutTripsInput = {
|
||||||
@@ -905,6 +1032,7 @@ export type UserUpdateWithoutTripsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutTripsInput = {
|
export type UserUncheckedUpdateWithoutTripsInput = {
|
||||||
@@ -923,6 +1051,7 @@ export type UserUncheckedUpdateWithoutTripsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutTripReviewsInput = {
|
export type UserCreateWithoutTripReviewsInput = {
|
||||||
@@ -941,6 +1070,7 @@ export type UserCreateWithoutTripReviewsInput = {
|
|||||||
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutTripReviewsInput = {
|
export type UserUncheckedCreateWithoutTripReviewsInput = {
|
||||||
@@ -959,6 +1089,7 @@ export type UserUncheckedCreateWithoutTripReviewsInput = {
|
|||||||
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutTripReviewsInput = {
|
export type UserCreateOrConnectWithoutTripReviewsInput = {
|
||||||
@@ -993,6 +1124,7 @@ export type UserUpdateWithoutTripReviewsInput = {
|
|||||||
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
|
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutTripReviewsInput = {
|
export type UserUncheckedUpdateWithoutTripReviewsInput = {
|
||||||
@@ -1011,6 +1143,7 @@ export type UserUncheckedUpdateWithoutTripReviewsInput = {
|
|||||||
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
|
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateWithoutParticipationsInput = {
|
export type UserCreateWithoutParticipationsInput = {
|
||||||
@@ -1029,6 +1162,7 @@ export type UserCreateWithoutParticipationsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedCreateWithoutParticipationsInput = {
|
export type UserUncheckedCreateWithoutParticipationsInput = {
|
||||||
@@ -1047,6 +1181,7 @@ export type UserUncheckedCreateWithoutParticipationsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserCreateOrConnectWithoutParticipationsInput = {
|
export type UserCreateOrConnectWithoutParticipationsInput = {
|
||||||
@@ -1081,6 +1216,7 @@ export type UserUpdateWithoutParticipationsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserUncheckedUpdateWithoutParticipationsInput = {
|
export type UserUncheckedUpdateWithoutParticipationsInput = {
|
||||||
@@ -1099,6 +1235,7 @@ export type UserUncheckedUpdateWithoutParticipationsInput = {
|
|||||||
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||||
|
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1185,6 +1322,7 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
|||||||
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
|
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
|
||||||
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
|
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
|
||||||
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
|
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
|
||||||
|
profile?: boolean | Prisma.User$profileArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["user"]>
|
}, ExtArgs["result"]["user"]>
|
||||||
|
|
||||||
@@ -1235,6 +1373,7 @@ export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||||||
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
|
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
|
||||||
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
|
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
|
||||||
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
|
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
|
||||||
|
profile?: boolean | Prisma.User$profileArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type UserIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
export type UserIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
||||||
@@ -1249,6 +1388,7 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||||||
tripReviews: Prisma.$TripReviewPayload<ExtArgs>[]
|
tripReviews: Prisma.$TripReviewPayload<ExtArgs>[]
|
||||||
organizerVerification: Prisma.$OrganizerVerificationPayload<ExtArgs> | null
|
organizerVerification: Prisma.$OrganizerVerificationPayload<ExtArgs> | null
|
||||||
reviewedVerifications: Prisma.$OrganizerVerificationPayload<ExtArgs>[]
|
reviewedVerifications: Prisma.$OrganizerVerificationPayload<ExtArgs>[]
|
||||||
|
profile: Prisma.$UserProfilePayload<ExtArgs> | null
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
@@ -1673,6 +1813,7 @@ export interface Prisma__UserClient<T, Null = never, ExtArgs extends runtime.Typ
|
|||||||
tripReviews<T extends Prisma.User$tripReviewsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$tripReviewsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripReviewPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
tripReviews<T extends Prisma.User$tripReviewsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$tripReviewsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripReviewPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
organizerVerification<T extends Prisma.User$organizerVerificationArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$organizerVerificationArgs<ExtArgs>>): Prisma.Prisma__OrganizerVerificationClient<runtime.Types.Result.GetResult<Prisma.$OrganizerVerificationPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
organizerVerification<T extends Prisma.User$organizerVerificationArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$organizerVerificationArgs<ExtArgs>>): Prisma.Prisma__OrganizerVerificationClient<runtime.Types.Result.GetResult<Prisma.$OrganizerVerificationPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||||
reviewedVerifications<T extends Prisma.User$reviewedVerificationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$reviewedVerificationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$OrganizerVerificationPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
reviewedVerifications<T extends Prisma.User$reviewedVerificationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$reviewedVerificationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$OrganizerVerificationPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
|
profile<T extends Prisma.User$profileArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$profileArgs<ExtArgs>>): Prisma.Prisma__UserProfileClient<runtime.Types.Result.GetResult<Prisma.$UserProfilePayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
@@ -2243,6 +2384,25 @@ export type User$reviewedVerificationsArgs<ExtArgs extends runtime.Types.Extensi
|
|||||||
distinct?: Prisma.OrganizerVerificationScalarFieldEnum | Prisma.OrganizerVerificationScalarFieldEnum[]
|
distinct?: Prisma.OrganizerVerificationScalarFieldEnum | Prisma.OrganizerVerificationScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User.profile
|
||||||
|
*/
|
||||||
|
export type User$profileArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the UserProfile
|
||||||
|
*/
|
||||||
|
select?: Prisma.UserProfileSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the UserProfile
|
||||||
|
*/
|
||||||
|
omit?: Prisma.UserProfileOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.UserProfileInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.UserProfileWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User without action
|
* User without action
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import { SessionProvider } from "@/components/providers/session-provider";
|
import { SessionProvider } from "@/components/providers/session-provider";
|
||||||
import { Navbar } from "@/components/shared/navbar";
|
import { Navbar } from "@/components/shared/navbar";
|
||||||
|
import { ProfileNudgeBanner } from "@/components/shared/profile-nudge-banner";
|
||||||
import { siteConfig, siteUrl } from "@/lib/site";
|
import { siteConfig, siteUrl } from "@/lib/site";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ export default function RootLayout({
|
|||||||
<body className="flex min-h-full flex-col bg-neutral-50">
|
<body className="flex min-h-full flex-col bg-neutral-50">
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
<ProfileNudgeBanner />
|
||||||
<main className="flex-1">{children}</main>
|
<main className="flex-1">{children}</main>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+41
-9
@@ -1,11 +1,25 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/lib/auth";
|
||||||
import { tripService } from "@/server/services/trip.service";
|
import { tripService } from "@/server/services/trip.service";
|
||||||
|
import { profileRepo } from "@/server/repositories/profile.repo";
|
||||||
import { TripCard } from "@/features/trip/components/trip-card";
|
import { TripCard } from "@/features/trip/components/trip-card";
|
||||||
import { siteConfig, siteUrl, absoluteUrl } from "@/lib/site";
|
import { siteConfig, siteUrl, absoluteUrl } from "@/lib/site";
|
||||||
import { ACTIVITY_CATEGORIES, categoryMeta } from "@/lib/activity-category";
|
import { ACTIVITY_CATEGORIES, categoryMeta } from "@/lib/activity-category";
|
||||||
|
|
||||||
|
type OpenTrip = Awaited<ReturnType<typeof tripService.getOpenTrips>>[number];
|
||||||
|
|
||||||
|
function mapParticipants(trip: OpenTrip) {
|
||||||
|
return trip.participants.map((p) => ({
|
||||||
|
id: p.id,
|
||||||
|
name: p.user.name,
|
||||||
|
image: p.user.image,
|
||||||
|
interests: p.user.profile?.interests ?? [],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Cari Teman Trip & Aktivitas — Pergi Bareng, Bukan Sendiri",
|
title: "Cari Teman Trip & Aktivitas — Pergi Bareng, Bukan Sendiri",
|
||||||
description: `${siteConfig.slogan} ${siteConfig.description}`,
|
description: `${siteConfig.slogan} ${siteConfig.description}`,
|
||||||
@@ -18,7 +32,14 @@ export const metadata: Metadata = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
const trips = await tripService.getOpenTrips();
|
const session = await getServerSession(authOptions);
|
||||||
|
const [trips, viewerProfile] = await Promise.all([
|
||||||
|
tripService.getOpenTrips(),
|
||||||
|
session?.user?.id
|
||||||
|
? profileRepo.findByUserId(session.user.id)
|
||||||
|
: Promise.resolve(null),
|
||||||
|
]);
|
||||||
|
const viewerInterests = viewerProfile?.interests ?? [];
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
|
const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
|
||||||
@@ -35,8 +56,10 @@ export default async function HomePage() {
|
|||||||
|
|
||||||
const shownIds = new Set([...upcomingIds, ...latestTrips.map((t) => t.id)]);
|
const shownIds = new Set([...upcomingIds, ...latestTrips.map((t) => t.id)]);
|
||||||
|
|
||||||
const budgetTrips = trips
|
// Section sosial: trip yang paling ramai joiner-nya (social proof, bukan price proof).
|
||||||
.filter((t) => !shownIds.has(t.id) && t.price <= 300000)
|
const buzzingTrips = trips
|
||||||
|
.filter((t) => !shownIds.has(t.id) && t._count.participants > 0)
|
||||||
|
.sort((a, b) => b._count.participants - a._count.participants)
|
||||||
.slice(0, 3);
|
.slice(0, 3);
|
||||||
|
|
||||||
const orgJsonLd = {
|
const orgJsonLd = {
|
||||||
@@ -191,6 +214,7 @@ export default async function HomePage() {
|
|||||||
id={trip.id}
|
id={trip.id}
|
||||||
title={trip.title}
|
title={trip.title}
|
||||||
category={trip.category}
|
category={trip.category}
|
||||||
|
vibe={trip.vibe}
|
||||||
destination={trip.destination}
|
destination={trip.destination}
|
||||||
location={trip.location}
|
location={trip.location}
|
||||||
date={trip.date}
|
date={trip.date}
|
||||||
@@ -204,6 +228,8 @@ export default async function HomePage() {
|
|||||||
isVerifiedOrganizer={
|
isVerifiedOrganizer={
|
||||||
trip.organizer.organizerVerification?.status === "APPROVED"
|
trip.organizer.organizerVerification?.status === "APPROVED"
|
||||||
}
|
}
|
||||||
|
participants={mapParticipants(trip)}
|
||||||
|
viewerInterests={viewerInterests}
|
||||||
priority={i === 0}
|
priority={i === 0}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -261,6 +287,7 @@ export default async function HomePage() {
|
|||||||
id={trip.id}
|
id={trip.id}
|
||||||
title={trip.title}
|
title={trip.title}
|
||||||
category={trip.category}
|
category={trip.category}
|
||||||
|
vibe={trip.vibe}
|
||||||
destination={trip.destination}
|
destination={trip.destination}
|
||||||
location={trip.location}
|
location={trip.location}
|
||||||
date={trip.date}
|
date={trip.date}
|
||||||
@@ -274,35 +301,38 @@ export default async function HomePage() {
|
|||||||
isVerifiedOrganizer={
|
isVerifiedOrganizer={
|
||||||
trip.organizer.organizerVerification?.status === "APPROVED"
|
trip.organizer.organizerVerification?.status === "APPROVED"
|
||||||
}
|
}
|
||||||
|
participants={mapParticipants(trip)}
|
||||||
|
viewerInterests={viewerInterests}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Budget Friendly */}
|
{/* Lagi Ramai — social proof, bukan price proof */}
|
||||||
{budgetTrips.length > 0 && (
|
{buzzingTrips.length > 0 && (
|
||||||
<section>
|
<section>
|
||||||
<div className="mb-4 flex items-center gap-3 sm:mb-5">
|
<div className="mb-4 flex items-center gap-3 sm:mb-5">
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary-100 text-base sm:h-9 sm:w-9 sm:text-lg">
|
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary-100 text-base sm:h-9 sm:w-9 sm:text-lg">
|
||||||
💸
|
🤝
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-bold text-neutral-800 sm:text-lg">
|
<h2 className="text-base font-bold text-neutral-800 sm:text-lg">
|
||||||
Budget Friendly
|
Lagi Ramai
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-[11px] text-neutral-500 sm:text-xs">
|
<p className="text-[11px] text-neutral-500 sm:text-xs">
|
||||||
Trip di bawah Rp 300.000
|
Banyak yang sudah gabung — kamu nggak bakal jalan sendirian
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{budgetTrips.map((trip) => (
|
{buzzingTrips.map((trip) => (
|
||||||
<TripCard
|
<TripCard
|
||||||
key={trip.id}
|
key={trip.id}
|
||||||
id={trip.id}
|
id={trip.id}
|
||||||
title={trip.title}
|
title={trip.title}
|
||||||
category={trip.category}
|
category={trip.category}
|
||||||
|
vibe={trip.vibe}
|
||||||
destination={trip.destination}
|
destination={trip.destination}
|
||||||
location={trip.location}
|
location={trip.location}
|
||||||
date={trip.date}
|
date={trip.date}
|
||||||
@@ -316,6 +346,8 @@ export default async function HomePage() {
|
|||||||
isVerifiedOrganizer={
|
isVerifiedOrganizer={
|
||||||
trip.organizer.organizerVerification?.status === "APPROVED"
|
trip.organizer.organizerVerification?.status === "APPROVED"
|
||||||
}
|
}
|
||||||
|
participants={mapParticipants(trip)}
|
||||||
|
viewerInterests={viewerInterests}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { profileService } from "@/server/services/profile.service";
|
||||||
|
import { UserCard } from "@/features/profile/components/user-card";
|
||||||
|
import { PeopleFilter } from "@/features/profile/components/people-filter";
|
||||||
|
import { isVibe, vibeLabel } from "@/lib/vibe";
|
||||||
|
import { siteConfig } from "@/lib/site";
|
||||||
|
|
||||||
|
interface PeoplePageProps {
|
||||||
|
searchParams: Promise<{
|
||||||
|
city?: string;
|
||||||
|
interest?: string;
|
||||||
|
vibe?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
searchParams,
|
||||||
|
}: PeoplePageProps): Promise<Metadata> {
|
||||||
|
const { city, interest, vibe: vibeParam } = await searchParams;
|
||||||
|
const vibe = isVibe(vibeParam) ? vibeParam : undefined;
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (vibe) parts.push(`Vibe ${vibeLabel(vibe).toLowerCase()}`);
|
||||||
|
if (city) parts.push(`di ${city}`);
|
||||||
|
if (interest) parts.push(`#${interest.toLowerCase()}`);
|
||||||
|
const title = parts.length
|
||||||
|
? `Cari Teman ${parts.join(" ")}`
|
||||||
|
: "Cari Teman Aktivitas — Profil Anggota";
|
||||||
|
const description = `Telusuri profil anggota ${siteConfig.name} berdasarkan minat, kota, dan vibe. Temukan calon teman trip dengan ritme yang cocok sebelum gabung bareng.`;
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
alternates: { canonical: "/people" },
|
||||||
|
openGraph: { title, description, url: "/people" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function PeoplePage({ searchParams }: PeoplePageProps) {
|
||||||
|
const params = await searchParams;
|
||||||
|
const vibe = isVibe(params.vibe) ? params.vibe : undefined;
|
||||||
|
const filters = {
|
||||||
|
city: params.city?.trim() || undefined,
|
||||||
|
interest: params.interest?.trim().toLowerCase() || undefined,
|
||||||
|
vibe,
|
||||||
|
};
|
||||||
|
const hasFilters = Boolean(filters.city || filters.interest || filters.vibe);
|
||||||
|
|
||||||
|
const people = await profileService.findPeople(filters);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-6xl px-4 py-6 sm:py-8">
|
||||||
|
<div className="mb-5 flex flex-col gap-2 sm:mb-6">
|
||||||
|
<h1 className="text-xl font-bold text-neutral-800 sm:text-2xl">
|
||||||
|
Cari Teman Aktivitas
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-neutral-500">
|
||||||
|
{hasFilters
|
||||||
|
? `${people.length} orang ditemukan dengan filter di atas`
|
||||||
|
: `${people.length} anggota dengan profil sosial — kenali dulu sebelum gabung trip`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<PeopleFilter />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{people.length === 0 ? (
|
||||||
|
<div className="rounded-2xl border-2 border-dashed border-neutral-200 bg-white p-8 text-center sm:p-14">
|
||||||
|
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-primary-50 text-2xl sm:h-16 sm:w-16 sm:text-3xl">
|
||||||
|
🔍
|
||||||
|
</div>
|
||||||
|
<p className="mb-1 text-base font-bold text-neutral-800 sm:text-lg">
|
||||||
|
{hasFilters
|
||||||
|
? "Belum ada anggota yang cocok"
|
||||||
|
: "Belum ada anggota dengan profil terisi"}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-neutral-500">
|
||||||
|
{hasFilters
|
||||||
|
? "Coba longgarkan filter — kota, minat, atau vibe."
|
||||||
|
: "Setelah anggota lain mengisi profil, mereka akan muncul di sini."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ul className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{people.map((u) => (
|
||||||
|
<li key={u.id}>
|
||||||
|
<UserCard
|
||||||
|
id={u.id}
|
||||||
|
name={u.name}
|
||||||
|
image={u.image}
|
||||||
|
isVerifiedOrganizer={
|
||||||
|
u.organizerVerification?.status === "APPROVED"
|
||||||
|
}
|
||||||
|
profile={u.profile}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+24
-1
@@ -7,6 +7,7 @@ import { authOptions } from "@/lib/auth";
|
|||||||
import { profileService } from "@/server/services/profile.service";
|
import { profileService } from "@/server/services/profile.service";
|
||||||
import { TripCard } from "@/features/trip/components/trip-card";
|
import { TripCard } from "@/features/trip/components/trip-card";
|
||||||
import { ProfileTripRow } from "@/features/profile/components/profile-trip-row";
|
import { ProfileTripRow } from "@/features/profile/components/profile-trip-row";
|
||||||
|
import { ProfileEditor } from "@/features/profile/components/profile-editor";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Profil Saya",
|
title: "Profil Saya",
|
||||||
@@ -19,7 +20,10 @@ export default async function ProfilePage() {
|
|||||||
redirect("/login?callbackUrl=/profile");
|
redirect("/login?callbackUrl=/profile");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await profileService.getProfileDashboard(session.user.id);
|
const [data, ownProfile] = await Promise.all([
|
||||||
|
profileService.getProfileDashboard(session.user.id),
|
||||||
|
profileService.getOwnProfile(session.user.id),
|
||||||
|
]);
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
isVerifiedOrganizer,
|
isVerifiedOrganizer,
|
||||||
@@ -80,6 +84,24 @@ export default async function ProfilePage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Profil sosial publik */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<ProfileEditor
|
||||||
|
userId={user.id}
|
||||||
|
initial={
|
||||||
|
ownProfile
|
||||||
|
? {
|
||||||
|
bio: ownProfile.bio,
|
||||||
|
city: ownProfile.city,
|
||||||
|
interests: ownProfile.interests,
|
||||||
|
instagram: ownProfile.instagram,
|
||||||
|
vibe: ownProfile.vibe,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Trip selesai — akses ulasan (trip ini tidak muncul di Open Trip) */}
|
{/* Trip selesai — akses ulasan (trip ini tidak muncul di Open Trip) */}
|
||||||
{reviewable.length > 0 && (
|
{reviewable.length > 0 && (
|
||||||
<section className="mb-8 rounded-2xl border border-amber-200 bg-amber-50/60 p-4 sm:p-5">
|
<section className="mb-8 rounded-2xl border border-amber-200 bg-amber-50/60 p-4 sm:p-5">
|
||||||
@@ -148,6 +170,7 @@ export default async function ProfilePage() {
|
|||||||
id={trip.id}
|
id={trip.id}
|
||||||
title={trip.title}
|
title={trip.title}
|
||||||
category={trip.category}
|
category={trip.category}
|
||||||
|
vibe={trip.vibe}
|
||||||
destination={trip.destination}
|
destination={trip.destination}
|
||||||
location={trip.location}
|
location={trip.location}
|
||||||
date={trip.date}
|
date={trip.date}
|
||||||
|
|||||||
+84
-20
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
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 { trustService } from "@/server/services/trust.service";
|
import { trustService } from "@/server/services/trust.service";
|
||||||
@@ -15,6 +16,8 @@ import { TripProgramBlock } from "@/features/trip/components/trip-program-block"
|
|||||||
import { OrganizerPaymentQueue } from "@/features/booking/components/organizer-payment-queue";
|
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 { categoryMeta } from "@/lib/activity-category";
|
||||||
|
import { vibeMeta } from "@/lib/vibe";
|
||||||
import {
|
import {
|
||||||
isPastTripLastDayForReview,
|
isPastTripLastDayForReview,
|
||||||
isTripDepartureDayPast,
|
isTripDepartureDayPast,
|
||||||
@@ -127,6 +130,8 @@ export default async function TripDetailPage({
|
|||||||
(p) => p.markedPaidAt && !p.paymentConfirmedAt
|
(p) => p.markedPaidAt && !p.paymentConfirmedAt
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const catMeta = categoryMeta(trip.category);
|
||||||
|
|
||||||
const tripUrl = absoluteUrl(`/trips/${trip.id}`);
|
const tripUrl = absoluteUrl(`/trips/${trip.id}`);
|
||||||
const eventStatus =
|
const eventStatus =
|
||||||
trip.status === "OPEN"
|
trip.status === "OPEN"
|
||||||
@@ -240,8 +245,21 @@ export default async function TripDetailPage({
|
|||||||
<h1 className="text-lg font-bold text-neutral-800 sm:text-xl">
|
<h1 className="text-lg font-bold text-neutral-800 sm:text-xl">
|
||||||
{trip.title}
|
{trip.title}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-0.5 flex items-center gap-1.5 text-sm text-neutral-500">
|
<p className="mt-0.5 flex flex-wrap items-center gap-1.5 text-sm text-neutral-500">
|
||||||
🏔️ {trip.destination}
|
<span aria-hidden>{catMeta.icon}</span>
|
||||||
|
<span className="rounded-full bg-neutral-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-neutral-600">
|
||||||
|
{catMeta.label}
|
||||||
|
</span>
|
||||||
|
{trip.vibe && (
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-0.5 rounded-full bg-secondary-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-secondary-700"
|
||||||
|
title={vibeMeta(trip.vibe).description}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{vibeMeta(trip.vibe).icon}</span>
|
||||||
|
<span>{vibeMeta(trip.vibe).label}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="truncate">{trip.destination}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
@@ -301,9 +319,12 @@ export default async function TripDetailPage({
|
|||||||
</span>
|
</span>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-[10px] font-medium text-neutral-400 sm:text-xs">Organizer</p>
|
<p className="text-[10px] font-medium text-neutral-400 sm:text-xs">Organizer</p>
|
||||||
<p className="truncate text-xs font-semibold text-neutral-800 sm:text-sm">
|
<Link
|
||||||
|
href={`/u/${trip.organizer.id}`}
|
||||||
|
className="truncate text-xs font-semibold text-neutral-800 hover:text-primary-700 sm:text-sm"
|
||||||
|
>
|
||||||
{trip.organizer.name}
|
{trip.organizer.name}
|
||||||
</p>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -445,9 +466,12 @@ export default async function TripDetailPage({
|
|||||||
|
|
||||||
{/* Peserta yang sudah disetujui organizer (publik) */}
|
{/* 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-1 text-xs font-bold text-neutral-700 sm:text-sm">
|
||||||
Peserta terkonfirmasi ({confirmedCount})
|
Peserta terkonfirmasi ({confirmedCount})
|
||||||
</h2>
|
</h2>
|
||||||
|
<p className="mb-3 text-[11px] text-neutral-500 sm:text-xs">
|
||||||
|
Kenalan dulu sebelum berangkat — klik kartu untuk lihat profil.
|
||||||
|
</p>
|
||||||
{confirmedCount === 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 yang dikonfirmasi.{" "}
|
Belum ada peserta yang dikonfirmasi.{" "}
|
||||||
@@ -456,21 +480,61 @@ export default async function TripDetailPage({
|
|||||||
: "Jadilah yang pertama mendaftar! 🎒"}
|
: "Jadilah yang pertama mendaftar! 🎒"}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-wrap gap-1.5 sm:gap-2">
|
<ul className="grid gap-2 sm:grid-cols-2">
|
||||||
{confirmedParticipants.map((p) => (
|
{confirmedParticipants.map((p) => {
|
||||||
<div
|
const interests = p.user.profile?.interests ?? [];
|
||||||
key={p.id}
|
const city = p.user.profile?.city;
|
||||||
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"
|
return (
|
||||||
>
|
<li key={p.id}>
|
||||||
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-primary-600 text-[9px] font-bold text-white sm:h-6 sm:w-6 sm:text-[10px]">
|
<Link
|
||||||
{p.user.name.charAt(0).toUpperCase()}
|
href={`/u/${p.user.id}`}
|
||||||
</div>
|
className="flex items-start gap-3 rounded-xl border border-neutral-200 bg-white px-3 py-2.5 transition-colors hover:border-primary-300 hover:bg-primary-50/30"
|
||||||
<span className="text-xs font-medium text-neutral-700 sm:text-sm">
|
>
|
||||||
{p.user.name}
|
{p.user.image ? (
|
||||||
</span>
|
<Image
|
||||||
</div>
|
src={p.user.image}
|
||||||
))}
|
alt=""
|
||||||
</div>
|
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 flex-1">
|
||||||
|
<p className="truncate text-sm font-semibold text-neutral-800">
|
||||||
|
{p.user.name}
|
||||||
|
</p>
|
||||||
|
{city && (
|
||||||
|
<p className="truncate text-[11px] text-neutral-500 sm:text-xs">
|
||||||
|
📍 {city}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{interests.length > 0 && (
|
||||||
|
<div className="mt-1 flex flex-wrap gap-1">
|
||||||
|
{interests.slice(0, 3).map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
className="rounded-full bg-secondary-50 px-1.5 py-0.5 text-[10px] font-medium text-secondary-700"
|
||||||
|
>
|
||||||
|
#{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{interests.length > 3 && (
|
||||||
|
<span className="text-[10px] text-neutral-400">
|
||||||
|
+{interests.length - 3}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+33
-2
@@ -1,11 +1,21 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/lib/auth";
|
||||||
import { tripService } from "@/server/services/trip.service";
|
import { tripService } from "@/server/services/trip.service";
|
||||||
|
import { profileRepo } from "@/server/repositories/profile.repo";
|
||||||
import { TripCard } from "@/features/trip/components/trip-card";
|
import { TripCard } from "@/features/trip/components/trip-card";
|
||||||
import { TripFilter } from "@/features/trip/components/trip-filter";
|
import { TripFilter } from "@/features/trip/components/trip-filter";
|
||||||
import { siteConfig } from "@/lib/site";
|
import { siteConfig } from "@/lib/site";
|
||||||
import { categoryLabel, isActivityCategory } from "@/lib/activity-category";
|
import { categoryLabel, isActivityCategory } from "@/lib/activity-category";
|
||||||
|
import { isVibe } from "@/lib/vibe";
|
||||||
|
import type { GroupSize } from "@/server/repositories/trip.repo";
|
||||||
|
|
||||||
|
const GROUP_SIZES: GroupSize[] = ["SMALL", "MEDIUM", "LARGE"];
|
||||||
|
function isGroupSize(value: unknown): value is GroupSize {
|
||||||
|
return typeof value === "string" && (GROUP_SIZES as string[]).includes(value);
|
||||||
|
}
|
||||||
|
|
||||||
interface TripsPageProps {
|
interface TripsPageProps {
|
||||||
searchParams: Promise<{
|
searchParams: Promise<{
|
||||||
@@ -13,6 +23,8 @@ interface TripsPageProps {
|
|||||||
from?: string;
|
from?: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
|
vibe?: string;
|
||||||
|
groupSize?: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,19 +56,30 @@ export async function generateMetadata({
|
|||||||
export default async function TripsPage({ searchParams }: TripsPageProps) {
|
export default async function TripsPage({ searchParams }: TripsPageProps) {
|
||||||
const params = await searchParams;
|
const params = await searchParams;
|
||||||
const category = isActivityCategory(params.category) ? params.category : undefined;
|
const category = isActivityCategory(params.category) ? params.category : undefined;
|
||||||
const hasFilters = Boolean(params.q || params.from || params.to || category);
|
const vibe = isVibe(params.vibe) ? params.vibe : undefined;
|
||||||
|
const groupSize = isGroupSize(params.groupSize) ? params.groupSize : undefined;
|
||||||
|
const hasFilters = Boolean(
|
||||||
|
params.q || params.from || params.to || category || vibe || groupSize
|
||||||
|
);
|
||||||
const filters = {
|
const filters = {
|
||||||
q: params.q,
|
q: params.q,
|
||||||
from: params.from,
|
from: params.from,
|
||||||
to: params.to,
|
to: params.to,
|
||||||
category,
|
category,
|
||||||
|
vibe,
|
||||||
|
groupSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [trips, allTrips] = await Promise.all([
|
const session = await getServerSession(authOptions);
|
||||||
|
const [trips, allTrips, viewerProfile] = await Promise.all([
|
||||||
tripService.getOpenTrips(filters),
|
tripService.getOpenTrips(filters),
|
||||||
hasFilters ? tripService.getOpenTrips() : null,
|
hasFilters ? tripService.getOpenTrips() : null,
|
||||||
|
session?.user?.id
|
||||||
|
? profileRepo.findByUserId(session.user.id)
|
||||||
|
: Promise.resolve(null),
|
||||||
]);
|
]);
|
||||||
const totalCount = hasFilters ? allTrips!.length : trips.length;
|
const totalCount = hasFilters ? allTrips!.length : trips.length;
|
||||||
|
const viewerInterests = viewerProfile?.interests ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-6xl px-4 py-6 sm:py-8">
|
<div className="mx-auto max-w-6xl px-4 py-6 sm:py-8">
|
||||||
@@ -120,6 +143,7 @@ export default async function TripsPage({ searchParams }: TripsPageProps) {
|
|||||||
id={trip.id}
|
id={trip.id}
|
||||||
title={trip.title}
|
title={trip.title}
|
||||||
category={trip.category}
|
category={trip.category}
|
||||||
|
vibe={trip.vibe}
|
||||||
destination={trip.destination}
|
destination={trip.destination}
|
||||||
location={trip.location}
|
location={trip.location}
|
||||||
date={trip.date}
|
date={trip.date}
|
||||||
@@ -133,6 +157,13 @@ export default async function TripsPage({ searchParams }: TripsPageProps) {
|
|||||||
isVerifiedOrganizer={
|
isVerifiedOrganizer={
|
||||||
trip.organizer.organizerVerification?.status === "APPROVED"
|
trip.organizer.organizerVerification?.status === "APPROVED"
|
||||||
}
|
}
|
||||||
|
participants={trip.participants.map((p) => ({
|
||||||
|
id: p.id,
|
||||||
|
name: p.user.name,
|
||||||
|
image: p.user.image,
|
||||||
|
interests: p.user.profile?.interests ?? [],
|
||||||
|
}))}
|
||||||
|
viewerInterests={viewerInterests}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { profileService } from "@/server/services/profile.service";
|
||||||
|
import { TripCard } from "@/features/trip/components/trip-card";
|
||||||
|
import { ProfileTripRow } from "@/features/profile/components/profile-trip-row";
|
||||||
|
import { siteConfig } from "@/lib/site";
|
||||||
|
import { vibeMeta } from "@/lib/vibe";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: PageProps): Promise<Metadata> {
|
||||||
|
const { id } = await params;
|
||||||
|
const data = await profileService.getPublicProfile(id);
|
||||||
|
if (!data) {
|
||||||
|
return { title: "Profil tidak ditemukan", robots: { index: false } };
|
||||||
|
}
|
||||||
|
const { user } = data;
|
||||||
|
const title = `${user.name} — Profil`;
|
||||||
|
const desc =
|
||||||
|
user.profile?.bio?.slice(0, 160) ||
|
||||||
|
`Lihat profil ${user.name} di ${siteConfig.name}: trip yang dibuat, trip yang diikuti, dan minat aktivitas.`;
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description: desc,
|
||||||
|
alternates: { canonical: `/u/${id}` },
|
||||||
|
openGraph: { title, description: desc, url: `/u/${id}` },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function PublicProfilePage({ params }: PageProps) {
|
||||||
|
const { id } = await params;
|
||||||
|
const data = await profileService.getPublicProfile(id);
|
||||||
|
if (!data) notFound();
|
||||||
|
|
||||||
|
const { user, isVerifiedOrganizer, organizedTrips, joinedTrips } = data;
|
||||||
|
const profile = user.profile;
|
||||||
|
const memberSince = new Date(user.createdAt).toLocaleDateString("id-ID", {
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-4xl px-4 py-6 sm:py-8">
|
||||||
|
{/* Header */}
|
||||||
|
<section className="rounded-2xl border border-neutral-200 bg-white p-5 shadow-sm sm:p-6">
|
||||||
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:gap-5">
|
||||||
|
<div className="relative h-20 w-20 shrink-0 overflow-hidden rounded-full bg-neutral-200 sm:h-24 sm:w-24">
|
||||||
|
{user.image ? (
|
||||||
|
<Image
|
||||||
|
src={user.image}
|
||||||
|
alt={user.name}
|
||||||
|
fill
|
||||||
|
sizes="96px"
|
||||||
|
className="object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full items-center justify-center text-2xl font-bold text-neutral-500">
|
||||||
|
{user.name.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<h1 className="text-xl font-bold text-neutral-800 sm:text-2xl">
|
||||||
|
{user.name}
|
||||||
|
</h1>
|
||||||
|
{isVerifiedOrganizer && (
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-0.5 rounded-full bg-primary-100 px-2 py-0.5 text-[11px] font-bold uppercase tracking-wide text-primary-800"
|
||||||
|
title="Identitas organizer telah diverifikasi (KTP & rekening)"
|
||||||
|
>
|
||||||
|
✅ Verified Organizer
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-neutral-500">
|
||||||
|
{profile?.city && (
|
||||||
|
<span className="inline-flex items-center gap-1">
|
||||||
|
📍 {profile.city}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="text-xs">Bergabung sejak {memberSince}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{profile?.bio && (
|
||||||
|
<p className="mt-3 whitespace-pre-line text-sm text-neutral-700">
|
||||||
|
{profile.bio}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{profile?.vibe && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-1.5 rounded-full bg-primary-50 px-2.5 py-1 text-xs font-semibold text-primary-700"
|
||||||
|
title={vibeMeta(profile.vibe).description}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{vibeMeta(profile.vibe).icon}</span>
|
||||||
|
<span>Vibe: {vibeMeta(profile.vibe).label}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{profile?.interests && profile.interests.length > 0 && (
|
||||||
|
<div className="mt-3 flex flex-wrap gap-1.5">
|
||||||
|
{profile.interests.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
className="rounded-full bg-secondary-50 px-2.5 py-0.5 text-xs font-medium text-secondary-700"
|
||||||
|
>
|
||||||
|
#{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{profile?.instagram && (
|
||||||
|
<a
|
||||||
|
href={`https://instagram.com/${profile.instagram}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer nofollow"
|
||||||
|
className="mt-3 inline-flex items-center gap-1.5 text-sm font-medium text-primary-600 hover:text-primary-700"
|
||||||
|
>
|
||||||
|
<span>📸</span>
|
||||||
|
<span>@{profile.instagram}</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-5 grid grid-cols-2 gap-3 border-t border-neutral-100 pt-4 text-center sm:grid-cols-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-lg font-bold text-primary-600">
|
||||||
|
{organizedTrips.length}
|
||||||
|
</p>
|
||||||
|
<p className="text-[11px] text-neutral-500">Trip dibuat</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-lg font-bold text-secondary-600">
|
||||||
|
{joinedTrips.length}
|
||||||
|
</p>
|
||||||
|
<p className="text-[11px] text-neutral-500">Trip diikuti</p>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 sm:col-span-1">
|
||||||
|
<p className="text-lg font-bold text-neutral-700">
|
||||||
|
{organizedTrips.length + joinedTrips.length}
|
||||||
|
</p>
|
||||||
|
<p className="text-[11px] text-neutral-500">Total perjalanan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Empty profile hint */}
|
||||||
|
{!profile && (
|
||||||
|
<p className="mt-5 rounded-xl border border-dashed border-neutral-200 bg-neutral-50 px-4 py-3 text-center text-xs text-neutral-500">
|
||||||
|
{user.name} belum melengkapi profil sosial — bio, kota, & minat akan
|
||||||
|
muncul di sini setelah diisi.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Trip dibuat */}
|
||||||
|
{organizedTrips.length > 0 && (
|
||||||
|
<section className="mt-8">
|
||||||
|
<h2 className="mb-3 text-base font-bold text-neutral-800 sm:text-lg">
|
||||||
|
Trip yang dibuat ({organizedTrips.length})
|
||||||
|
</h2>
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
{organizedTrips.map((trip) => (
|
||||||
|
<TripCard
|
||||||
|
key={trip.id}
|
||||||
|
id={trip.id}
|
||||||
|
title={trip.title}
|
||||||
|
category={trip.category}
|
||||||
|
vibe={trip.vibe}
|
||||||
|
destination={trip.destination}
|
||||||
|
location={trip.location}
|
||||||
|
date={trip.date}
|
||||||
|
endDate={trip.endDate}
|
||||||
|
price={trip.price}
|
||||||
|
maxParticipants={trip.maxParticipants}
|
||||||
|
participantCount={trip._count.participants}
|
||||||
|
organizerName={user.name}
|
||||||
|
status={trip.status}
|
||||||
|
coverImage={trip.images[0]?.url}
|
||||||
|
isVerifiedOrganizer={isVerifiedOrganizer}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Trip diikuti */}
|
||||||
|
{joinedTrips.length > 0 && (
|
||||||
|
<section className="mt-8">
|
||||||
|
<h2 className="mb-3 text-base font-bold text-neutral-800 sm:text-lg">
|
||||||
|
Trip yang diikuti ({joinedTrips.length})
|
||||||
|
</h2>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{joinedTrips.map((trip) => (
|
||||||
|
<li key={trip.id}>
|
||||||
|
<ProfileTripRow
|
||||||
|
href={`/trips/${trip.id}`}
|
||||||
|
title={trip.title}
|
||||||
|
destination={trip.destination}
|
||||||
|
date={trip.date}
|
||||||
|
endDate={trip.endDate}
|
||||||
|
rightSlot={
|
||||||
|
<span className="text-neutral-500">
|
||||||
|
bareng{" "}
|
||||||
|
<Link
|
||||||
|
href={`/u/${trip.organizer.id}`}
|
||||||
|
className="font-medium text-neutral-700 hover:text-primary-600"
|
||||||
|
>
|
||||||
|
{trip.organizer.name}
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Empty state */}
|
||||||
|
{organizedTrips.length === 0 && joinedTrips.length === 0 && (
|
||||||
|
<p className="mt-8 rounded-xl border border-dashed border-neutral-200 bg-white px-4 py-10 text-center text-sm text-neutral-500">
|
||||||
|
Belum ada trip yang dibuat atau diikuti.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+1
-1
@@ -21,7 +21,7 @@ export default async function VerifyPage() {
|
|||||||
birthDate: verification.birthDate,
|
birthDate: verification.birthDate,
|
||||||
address: verification.address,
|
address: verification.address,
|
||||||
ktpImageKey: verification.ktpImageKey,
|
ktpImageKey: verification.ktpImageKey,
|
||||||
selfieKey: verification.selfieKey,
|
livenessKey: verification.livenessKey,
|
||||||
bankName: verification.bankName,
|
bankName: verification.bankName,
|
||||||
bankAccountNumber: verification.bankAccountNumber,
|
bankAccountNumber: verification.bankAccountNumber,
|
||||||
bankAccountName: verification.bankAccountName,
|
bankAccountName: verification.bankAccountName,
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ export function Navbar() {
|
|||||||
>
|
>
|
||||||
Open Trip
|
Open Trip
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/people"
|
||||||
|
className="rounded-lg px-3 py-1.5 text-sm font-medium text-neutral-600 transition-colors hover:bg-neutral-100 hover:text-neutral-800"
|
||||||
|
>
|
||||||
|
Cari Teman
|
||||||
|
</Link>
|
||||||
|
|
||||||
{session?.user ? (
|
{session?.user ? (
|
||||||
<>
|
<>
|
||||||
@@ -125,6 +131,13 @@ export function Navbar() {
|
|||||||
>
|
>
|
||||||
Open Trip
|
Open Trip
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/people"
|
||||||
|
onClick={() => setMenuOpen(false)}
|
||||||
|
className="rounded-lg px-3 py-2.5 text-sm font-medium text-neutral-700 transition-colors hover:bg-neutral-50"
|
||||||
|
>
|
||||||
|
Cari Teman
|
||||||
|
</Link>
|
||||||
|
|
||||||
{session?.user ? (
|
{session?.user ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "@/lib/auth";
|
||||||
|
import { profileRepo } from "@/server/repositories/profile.repo";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server component banner: muncul di atas semua halaman ketika user sudah login
|
||||||
|
* tapi profil sosialnya kosong. Menjaga janji "kenalan dulu, gabung kemudian"
|
||||||
|
* dengan mendorong user mengisi minat/kota sebelum join trip.
|
||||||
|
*/
|
||||||
|
export async function ProfileNudgeBanner() {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) return null;
|
||||||
|
|
||||||
|
const profile = await profileRepo.findByUserId(session.user.id);
|
||||||
|
const hasMeaningfulProfile =
|
||||||
|
!!profile &&
|
||||||
|
(!!profile.bio?.trim() ||
|
||||||
|
!!profile.city?.trim() ||
|
||||||
|
profile.interests.length > 0);
|
||||||
|
|
||||||
|
if (hasMeaningfulProfile) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border-b border-amber-200 bg-amber-50">
|
||||||
|
<div className="mx-auto flex max-w-6xl flex-col items-start gap-2 px-4 py-2.5 text-xs sm:flex-row sm:items-center sm:justify-between sm:text-sm">
|
||||||
|
<p className="text-amber-900">
|
||||||
|
<span className="font-semibold">Lengkapi profil sosial kamu</span> —
|
||||||
|
bio, kota, dan minat. Calon teman trip akan lebih mudah kenal kamu
|
||||||
|
sebelum gabung bareng.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/profile"
|
||||||
|
className="shrink-0 rounded-lg bg-amber-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-amber-700"
|
||||||
|
>
|
||||||
|
Isi profil sekarang
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
-136
@@ -1,136 +0,0 @@
|
|||||||
# Deploy Setrip dengan PM2
|
|
||||||
|
|
||||||
Panduan ini untuk menjalankan aplikasi **Next.js** (setrip) di server Linux/VPS menggunakan **PM2**. Pastikan **PostgreSQL** sudah tersedia dan URL-nya sesuai dengan variabel lingkungan aplikasi.
|
|
||||||
|
|
||||||
## Prasyarat
|
|
||||||
|
|
||||||
- Node.js **20.x** (disarankan, selaras dengan `@types/node` di proyek)
|
|
||||||
- npm atau pnpm/yarn (contoh di bawah memakai **npm**)
|
|
||||||
- PM2 terpasang global: `npm install -g pm2`
|
|
||||||
- Basis data PostgreSQL dan file `.env` di server (lihat bagian Lingkungan)
|
|
||||||
|
|
||||||
## File PM2
|
|
||||||
|
|
||||||
Konfigurasi PM2 ada di root repositori: **`ecosystem.config.js`** (nama ini disengaja).
|
|
||||||
|
|
||||||
### Jangan `pm2 start ecosystem.js` kecuali itu skrip Node
|
|
||||||
|
|
||||||
Jika Anda menjalankan `pm2 start ecosystem.js` pada file yang isinya hanya `module.exports = { apps: [...] }`, PM2 menganggapnya **skrip aplikasi biasa** dan menjalankannya dengan `node ecosystem.js`. Akibatnya:
|
|
||||||
|
|
||||||
- Nama proses di daftar PM2 jadi **`ecosystem`** (bukan `setrip`).
|
|
||||||
- Next.js **tidak** dijalankan lewat entri `apps` Anda.
|
|
||||||
|
|
||||||
Gunakan selalu:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 start ecosystem.config.js --env production
|
|
||||||
```
|
|
||||||
|
|
||||||
Isi file menjalankan biner Next (`next start`) setelah build, mode **fork**, satu proses, **PORT** **3090**. Ubah `PORT` di file tersebut jika kebijakan port berubah.
|
|
||||||
|
|
||||||
### Berapa port yang dibutuhkan?
|
|
||||||
|
|
||||||
Untuk **trafik HTTP/HTTPS ke aplikasi Next.js**, cukup **satu port** yang didengarkan oleh `next start` — di setup ini **3090** (atau satu port lain yang Anda set).
|
|
||||||
|
|
||||||
**PostgreSQL** memakai port tersendiri (biasanya **5432**) di mesin tempat database berjalan. Itu bukan “port kedua untuk publik” dari aplikasi web: koneksi DB terjadi dari server aplikasi ke database (localhost atau jaringan internal). Di firewall publik Anda biasanya hanya membuka **80/443** (reverse proxy) atau **3090** jika diakses langsung tanpa proxy.
|
|
||||||
|
|
||||||
## Langkah deploy (pertama kali)
|
|
||||||
|
|
||||||
1. **Clone** repositori ke server (misalnya `/var/www/setrip`).
|
|
||||||
|
|
||||||
2. **Masuk** ke folder proyek dan pasang dependensi produksi:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /var/www/setrip
|
|
||||||
npm ci
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Lingkungan** — salin atau buat `.env` / `.env.production` di server (jangan commit rahasia ke git). Minimal sesuai kebutuhan aplikasi Anda, contoh:
|
|
||||||
|
|
||||||
- `DATABASE_URL` — koneksi PostgreSQL
|
|
||||||
- `NEXTAUTH_SECRET` — string acak yang kuat
|
|
||||||
- `NEXTAUTH_URL` — URL publik aplikasi (harus cocok dengan yang dibuka browser), misalnya `https://domain-anda.com` atau `http://host:3090` jika tanpa HTTPS dan akses langsung ke port tersebut
|
|
||||||
|
|
||||||
4. **Prisma** — generate client dan terapkan migrasi (jika memakai migrasi):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx prisma generate
|
|
||||||
npx prisma migrate deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Build** Next.js:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **Mulai** dengan PM2:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 start ecosystem.config.js --env production
|
|
||||||
```
|
|
||||||
|
|
||||||
Tanpa `--env production` tetap jalan; variabel default memakai blok `env` di file.
|
|
||||||
|
|
||||||
7. **Simpan** daftar proses agar bangkit lagi setelah reboot:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 save
|
|
||||||
pm2 startup
|
|
||||||
```
|
|
||||||
|
|
||||||
Ikuti perintah yang dikeluarkan PM2 (biasanya menyalin satu baris `sudo env ...`).
|
|
||||||
|
|
||||||
## Perintah PM2 yang sering dipakai
|
|
||||||
|
|
||||||
| Perintah | Keterangan |
|
|
||||||
|----------|------------|
|
|
||||||
| `pm2 status` | Status semua aplikasi |
|
|
||||||
| `pm2 logs setrip` | Log aplikasi bernama `setrip` |
|
|
||||||
| `pm2 reload setrip` | Reload tanpa downtime (berguna setelah deploy baru) |
|
|
||||||
| `pm2 restart setrip` | Restart proses |
|
|
||||||
| `pm2 stop setrip` | Menghentikan aplikasi |
|
|
||||||
| `pm2 delete setrip` | Menghapus aplikasi dari daftar PM2 |
|
|
||||||
|
|
||||||
## Deploy ulang (update kode)
|
|
||||||
|
|
||||||
Di server, setelah `git pull` (atau salin artefak baru):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /var/www/setrip
|
|
||||||
npm ci
|
|
||||||
npx prisma generate
|
|
||||||
npx prisma migrate deploy
|
|
||||||
npm run build
|
|
||||||
pm2 reload setrip
|
|
||||||
```
|
|
||||||
|
|
||||||
Jika nama aplikasi di PM2 berbeda, ganti `setrip` dengan nama di `ecosystem.config.js` (`name`).
|
|
||||||
|
|
||||||
### Hapus proses PM2 yang salah (nama `ecosystem`)
|
|
||||||
|
|
||||||
Jika Anda pernah menjalankan `pm2 start ecosystem.js` dan muncul proses bernama `ecosystem`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 stop ecosystem
|
|
||||||
pm2 delete ecosystem
|
|
||||||
```
|
|
||||||
|
|
||||||
Atau pakai id dari `pm2 status` (contoh id `9`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pm2 stop 9
|
|
||||||
pm2 delete 9
|
|
||||||
```
|
|
||||||
|
|
||||||
Lalu mulai lagi dengan `pm2 start ecosystem.config.js --env production` dan `pm2 save`.
|
|
||||||
|
|
||||||
## Reverse proxy (opsional)
|
|
||||||
|
|
||||||
Agar bisa HTTPS dan port 80/443, letakkan **Nginx** (atau Caddy) di depan aplikasi yang mendengarkan di `127.0.0.1:3090`. Pastikan `NEXTAUTH_URL` memakai skema dan host yang sama dengan yang diakses pengguna.
|
|
||||||
|
|
||||||
## Pemecahan masalah
|
|
||||||
|
|
||||||
- **502 / tidak terhubung** — cek `pm2 logs setrip`, pastikan PostgreSQL dapat dijangkau dari server, dan `PORT` tidak bentrok dengan layanan lain.
|
|
||||||
- **Error Prisma** — pastikan `npx prisma generate` dijalankan setelah `npm ci` di setiap deploy, dan `DATABASE_URL` benar.
|
|
||||||
- **NextAuth** — `NEXTAUTH_URL` harus persis URL publik (termasuk `https://`).
|
|
||||||
+1
-1
@@ -4,7 +4,7 @@ NEXTAUTH_URL="http://localhost:3000"
|
|||||||
NEXT_PUBLIC_SITE_URL="https://arifal.imola.ai"
|
NEXT_PUBLIC_SITE_URL="https://arifal.imola.ai"
|
||||||
ADMIN_EMAILS=admin@setrip.id
|
ADMIN_EMAILS=admin@setrip.id
|
||||||
|
|
||||||
# 32-byte key (hex) for AES-256-GCM encryption of KYC data (NIK + KTP/selfie files)
|
# 32-byte key (hex) for AES-256-GCM encryption of KYC data (NIK + KTP/liveness files)
|
||||||
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||||
KYC_ENCRYPTION_KEY=
|
KYC_ENCRYPTION_KEY=
|
||||||
# 32-byte hex secret used as HMAC pepper for NIK uniqueness lookup
|
# 32-byte hex secret used as HMAC pepper for NIK uniqueness lookup
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export async function submitVerificationAction(formData: FormData) {
|
|||||||
birthDate: formData.get("birthDate") as string,
|
birthDate: formData.get("birthDate") as string,
|
||||||
address: formData.get("address") as string,
|
address: formData.get("address") as string,
|
||||||
ktpImageKey: formData.get("ktpImageKey") as string,
|
ktpImageKey: formData.get("ktpImageKey") as string,
|
||||||
selfieKey: formData.get("selfieKey") as string,
|
livenessKey: formData.get("livenessKey") as string,
|
||||||
bankName: formData.get("bankName") as string,
|
bankName: formData.get("bankName") as string,
|
||||||
bankAccountNumber: formData.get("bankAccountNumber") as string,
|
bankAccountNumber: formData.get("bankAccountNumber") as string,
|
||||||
bankAccountName: formData.get("bankAccountName") as string,
|
bankAccountName: formData.get("bankAccountName") as string,
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ export function ReviewCard({ verification }: { verification: Verification }) {
|
|||||||
src={`/api/files/kyc/${verification.id}/ktp`}
|
src={`/api/files/kyc/${verification.id}/ktp`}
|
||||||
/>
|
/>
|
||||||
<ImagePreview
|
<ImagePreview
|
||||||
label="Selfie + KTP"
|
label="Foto memegang kertas SETRIP"
|
||||||
src={`/api/files/kyc/${verification.id}/selfie`}
|
src={`/api/files/kyc/${verification.id}/liveness`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ type Initial = {
|
|||||||
birthDate: Date;
|
birthDate: Date;
|
||||||
address: string;
|
address: string;
|
||||||
ktpImageKey: string;
|
ktpImageKey: string;
|
||||||
selfieKey: string;
|
livenessKey: string;
|
||||||
bankName: string;
|
bankName: string;
|
||||||
bankAccountNumber: string;
|
bankAccountNumber: string;
|
||||||
bankAccountName: string;
|
bankAccountName: string;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
type UploadKind = "ktp" | "selfie";
|
type UploadKind = "ktp" | "liveness";
|
||||||
|
|
||||||
const ACCEPT_MIME = "image/jpeg,image/png,image/webp";
|
const ACCEPT_MIME = "image/jpeg,image/png,image/webp";
|
||||||
const MAX_BYTES = 5 * 1024 * 1024;
|
const MAX_BYTES = 5 * 1024 * 1024;
|
||||||
@@ -33,19 +33,19 @@ export function VerifyForm({ initial }: { initial: Initial }) {
|
|||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [ktpKey, setKtpKey] = useState(initial?.ktpImageKey ?? "");
|
const [ktpKey, setKtpKey] = useState(initial?.ktpImageKey ?? "");
|
||||||
const [selfieKey, setSelfieKey] = useState(initial?.selfieKey ?? "");
|
const [livenessKey, setLivenessKey] = useState(initial?.livenessKey ?? "");
|
||||||
|
|
||||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError("");
|
setError("");
|
||||||
if (!ktpKey || !selfieKey) {
|
if (!ktpKey || !livenessKey) {
|
||||||
setError("Foto KTP dan selfie wajib diunggah");
|
setError("Foto KTP dan foto memegang kertas SETRIP wajib diunggah");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const formData = new FormData(e.currentTarget);
|
const formData = new FormData(e.currentTarget);
|
||||||
formData.set("ktpImageKey", ktpKey);
|
formData.set("ktpImageKey", ktpKey);
|
||||||
formData.set("selfieKey", selfieKey);
|
formData.set("livenessKey", livenessKey);
|
||||||
const result = await submitVerificationAction(formData);
|
const result = await submitVerificationAction(formData);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
@@ -143,13 +143,22 @@ export function VerifyForm({ initial }: { initial: Initial }) {
|
|||||||
onChange={setKtpKey}
|
onChange={setKtpKey}
|
||||||
onError={setError}
|
onError={setError}
|
||||||
/>
|
/>
|
||||||
<FileUpload
|
<div>
|
||||||
label="Selfie dengan KTP"
|
<FileUpload
|
||||||
kind="selfie"
|
label="Foto kamu memegang kertas tulisan SETRIP"
|
||||||
value={selfieKey}
|
kind="liveness"
|
||||||
onChange={setSelfieKey}
|
value={livenessKey}
|
||||||
onError={setError}
|
onChange={setLivenessKey}
|
||||||
/>
|
onError={setError}
|
||||||
|
/>
|
||||||
|
<p className="mt-1.5 text-[11px] leading-relaxed text-neutral-500">
|
||||||
|
Tulis kata <span className="font-semibold">SETRIP</span> dengan
|
||||||
|
tangan di selembar kertas, lalu foto diri kamu sambil memegang
|
||||||
|
kertas itu — pastikan wajah & tulisan terlihat jelas dalam satu
|
||||||
|
foto. Foto ini bukti bahwa pengajuan benar dilakukan oleh kamu
|
||||||
|
sendiri.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,13 @@ export const submitVerificationSchema = z.object({
|
|||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.regex(/^ktp\/[A-Za-z0-9_-]+\.(jpg|png|webp)$/, "Foto KTP wajib diunggah"),
|
.regex(/^ktp\/[A-Za-z0-9_-]+\.(jpg|png|webp)$/, "Foto KTP wajib diunggah"),
|
||||||
selfieKey: z
|
livenessKey: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.regex(/^selfie\/[A-Za-z0-9_-]+\.(jpg|png|webp)$/, "Foto selfie wajib diunggah"),
|
.regex(
|
||||||
|
/^liveness\/[A-Za-z0-9_-]+\.(jpg|png|webp)$/,
|
||||||
|
"Foto memegang kertas SETRIP wajib diunggah"
|
||||||
|
),
|
||||||
bankName: z
|
bankName: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { authOptions } from "@/lib/auth";
|
||||||
|
import { profileService } from "@/server/services/profile.service";
|
||||||
|
import { updateProfileSchema } from "./schemas";
|
||||||
|
|
||||||
|
export async function updateProfileAction(formData: FormData) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user) {
|
||||||
|
return { error: "Kamu harus login terlebih dahulu" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const interests = formData
|
||||||
|
.getAll("interests")
|
||||||
|
.map((v) => (v as string).trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const raw = {
|
||||||
|
bio: formData.get("bio"),
|
||||||
|
city: formData.get("city"),
|
||||||
|
instagram: formData.get("instagram"),
|
||||||
|
interests,
|
||||||
|
vibe: formData.get("vibe"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsed = updateProfileSchema.safeParse(raw);
|
||||||
|
if (!parsed.success) {
|
||||||
|
return { error: parsed.error.issues[0].message };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await profileService.updateProfile(session.user.id, parsed.data);
|
||||||
|
revalidatePath("/profile");
|
||||||
|
revalidatePath(`/u/${session.user.id}`);
|
||||||
|
return { success: true };
|
||||||
|
} catch (err) {
|
||||||
|
return { error: (err as Error).message };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { VIBES, vibeMeta, isVibe } from "@/lib/vibe";
|
||||||
|
import type { Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
|
export function PeopleFilter() {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const initialVibe = searchParams.get("vibe");
|
||||||
|
const [vibe, setVibe] = useState<Vibe | null>(
|
||||||
|
isVibe(initialVibe) ? initialVibe : null
|
||||||
|
);
|
||||||
|
const [city, setCity] = useState(searchParams.get("city") ?? "");
|
||||||
|
const [interest, setInterest] = useState(searchParams.get("interest") ?? "");
|
||||||
|
|
||||||
|
function buildParams(overrides?: { vibe?: Vibe | null }) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
const nextVibe = overrides && "vibe" in overrides ? overrides.vibe : vibe;
|
||||||
|
if (nextVibe) params.set("vibe", nextVibe);
|
||||||
|
if (city.trim()) params.set("city", city.trim());
|
||||||
|
if (interest.trim()) params.set("interest", interest.trim().toLowerCase());
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushFilters(params: URLSearchParams) {
|
||||||
|
const qs = params.toString();
|
||||||
|
router.push(`/people${qs ? `?${qs}` : ""}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectVibe(next: Vibe | null) {
|
||||||
|
setVibe(next);
|
||||||
|
pushFilters(buildParams({ vibe: next }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearch(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
pushFilters(buildParams());
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
setVibe(null);
|
||||||
|
setCity("");
|
||||||
|
setInterest("");
|
||||||
|
router.push("/people");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasFilters = vibe || city.trim() || interest.trim();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={handleSearch}
|
||||||
|
className="rounded-2xl border border-neutral-200 bg-white p-4 shadow-sm sm:p-5"
|
||||||
|
>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="mb-1.5 block text-xs font-medium text-neutral-500">
|
||||||
|
Vibe
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleSelectVibe(null)}
|
||||||
|
aria-pressed={vibe === null}
|
||||||
|
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
vibe === null
|
||||||
|
? "border-primary-600 bg-primary-600 text-white"
|
||||||
|
: "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Semua
|
||||||
|
</button>
|
||||||
|
{VIBES.map((v) => {
|
||||||
|
const m = vibeMeta(v);
|
||||||
|
const active = vibe === v;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={v}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleSelectVibe(v)}
|
||||||
|
aria-pressed={active}
|
||||||
|
title={m.description}
|
||||||
|
className={`inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
active
|
||||||
|
? "border-primary-600 bg-primary-600 text-white"
|
||||||
|
: "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{m.icon}</span>
|
||||||
|
<span>{m.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-end">
|
||||||
|
<div className="flex-1">
|
||||||
|
<label
|
||||||
|
htmlFor="people-city"
|
||||||
|
className="mb-1.5 block text-xs font-medium text-neutral-500"
|
||||||
|
>
|
||||||
|
Kota
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="people-city"
|
||||||
|
type="text"
|
||||||
|
value={city}
|
||||||
|
onChange={(e) => setCity(e.target.value)}
|
||||||
|
placeholder="Bandung, Jakarta, ..."
|
||||||
|
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:border-primary-500 focus:bg-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<label
|
||||||
|
htmlFor="people-interest"
|
||||||
|
className="mb-1.5 block text-xs font-medium text-neutral-500"
|
||||||
|
>
|
||||||
|
Minat
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="people-interest"
|
||||||
|
type="text"
|
||||||
|
value={interest}
|
||||||
|
onChange={(e) => setInterest(e.target.value)}
|
||||||
|
placeholder="hiking, fotografi, yoga..."
|
||||||
|
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:border-primary-500 focus:bg-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 sm:shrink-0">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="flex-1 rounded-xl bg-primary-600 px-5 py-2.5 text-sm font-semibold text-white shadow-md shadow-primary-600/20 transition-colors hover:bg-primary-700 sm:flex-none"
|
||||||
|
>
|
||||||
|
Cari
|
||||||
|
</button>
|
||||||
|
{hasFilters && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleReset}
|
||||||
|
className="rounded-xl border border-neutral-200 px-3 py-2.5 text-sm font-medium text-neutral-500 transition-colors hover:bg-neutral-50 hover:text-neutral-700"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { updateProfileAction } from "@/features/profile/actions";
|
||||||
|
import { LIMITS } from "@/lib/limits";
|
||||||
|
import { VIBES, vibeMeta } from "@/lib/vibe";
|
||||||
|
import type { Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
|
interface ProfileEditorProps {
|
||||||
|
userId: string;
|
||||||
|
initial: {
|
||||||
|
bio: string | null;
|
||||||
|
city: string | null;
|
||||||
|
interests: string[];
|
||||||
|
instagram: string | null;
|
||||||
|
vibe: Vibe | null;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProfileEditor({ userId, initial }: ProfileEditorProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [open, setOpen] = useState(initial === null);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [success, setSuccess] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const [bio, setBio] = useState(initial?.bio ?? "");
|
||||||
|
const [city, setCity] = useState(initial?.city ?? "");
|
||||||
|
const [instagram, setInstagram] = useState(initial?.instagram ?? "");
|
||||||
|
const [interests, setInterests] = useState<string[]>(initial?.interests ?? []);
|
||||||
|
const [interestDraft, setInterestDraft] = useState("");
|
||||||
|
const [vibe, setVibe] = useState<Vibe | null>(initial?.vibe ?? null);
|
||||||
|
|
||||||
|
function addInterest() {
|
||||||
|
const v = interestDraft.trim().toLowerCase();
|
||||||
|
if (!v) return;
|
||||||
|
if (interests.includes(v)) {
|
||||||
|
setInterestDraft("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (interests.length >= LIMITS.MAX_PROFILE_INTERESTS_COUNT) {
|
||||||
|
setError(`Maksimal ${LIMITS.MAX_PROFILE_INTERESTS_COUNT} minat`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInterests([...interests, v]);
|
||||||
|
setInterestDraft("");
|
||||||
|
setError("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeInterest(tag: string) {
|
||||||
|
setInterests(interests.filter((t) => t !== tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInterestKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
|
if (e.key === "Enter" || e.key === ",") {
|
||||||
|
e.preventDefault();
|
||||||
|
addInterest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
setError("");
|
||||||
|
setSuccess("");
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
if (bio.trim()) formData.set("bio", bio.trim());
|
||||||
|
if (city.trim()) formData.set("city", city.trim());
|
||||||
|
if (instagram.trim()) formData.set("instagram", instagram.trim());
|
||||||
|
if (vibe) formData.set("vibe", vibe);
|
||||||
|
interests.forEach((t) => formData.append("interests", t));
|
||||||
|
|
||||||
|
const result = await updateProfileAction(formData);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
setError(result.error);
|
||||||
|
} else {
|
||||||
|
setSuccess("Profil berhasil disimpan");
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
return (
|
||||||
|
<section className="rounded-2xl border border-neutral-200 bg-white p-4 shadow-sm sm:p-5">
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<h2 className="text-sm font-bold text-neutral-800 sm:text-base">
|
||||||
|
Profil sosial
|
||||||
|
</h2>
|
||||||
|
<p className="mt-0.5 truncate text-xs text-neutral-500">
|
||||||
|
{initial?.city || initial?.bio
|
||||||
|
? "Profil terisi — klik untuk edit"
|
||||||
|
: "Lengkapi profil supaya orang lain mengenalmu"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 gap-2">
|
||||||
|
<a
|
||||||
|
href={`/u/${userId}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="rounded-lg border border-neutral-200 px-3 py-1.5 text-xs font-medium text-neutral-600 hover:bg-neutral-50"
|
||||||
|
>
|
||||||
|
Lihat publik ↗
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="rounded-lg bg-primary-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-primary-700"
|
||||||
|
>
|
||||||
|
Edit profil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="rounded-2xl border border-neutral-200 bg-white p-5 shadow-sm sm:p-6">
|
||||||
|
<div className="mb-4 flex items-center justify-between gap-3">
|
||||||
|
<h2 className="text-base font-bold text-neutral-800 sm:text-lg">
|
||||||
|
Edit profil sosial
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
className="text-xs font-medium text-neutral-500 hover:text-neutral-700"
|
||||||
|
>
|
||||||
|
Tutup
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm font-medium text-red-600">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{success && (
|
||||||
|
<div className="mb-4 rounded-xl bg-secondary-50 px-4 py-3 text-sm font-medium text-secondary-700">
|
||||||
|
{success}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="bio"
|
||||||
|
className="mb-1.5 block text-sm font-semibold text-neutral-700"
|
||||||
|
>
|
||||||
|
Bio singkat
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="bio"
|
||||||
|
value={bio}
|
||||||
|
onChange={(e) => setBio(e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
maxLength={LIMITS.MAX_PROFILE_BIO_LENGTH}
|
||||||
|
placeholder="Cerita singkat tentang kamu — vibe, gaya jalan, hal yang kamu cari di trip bareng..."
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-right text-[11px] text-neutral-400">
|
||||||
|
{bio.length}/{LIMITS.MAX_PROFILE_BIO_LENGTH}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="city"
|
||||||
|
className="mb-1.5 block text-sm font-semibold text-neutral-700"
|
||||||
|
>
|
||||||
|
Kota
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="city"
|
||||||
|
type="text"
|
||||||
|
value={city}
|
||||||
|
onChange={(e) => setCity(e.target.value)}
|
||||||
|
maxLength={LIMITS.MAX_PROFILE_CITY_LENGTH}
|
||||||
|
placeholder="Bandung"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="instagram"
|
||||||
|
className="mb-1.5 block text-sm font-semibold text-neutral-700"
|
||||||
|
>
|
||||||
|
Instagram <span className="text-xs font-normal text-neutral-400">(opsional)</span>
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center rounded-xl border border-neutral-200 bg-neutral-50 px-3 focus-within:bg-white">
|
||||||
|
<span className="text-sm text-neutral-400">@</span>
|
||||||
|
<input
|
||||||
|
id="instagram"
|
||||||
|
type="text"
|
||||||
|
value={instagram}
|
||||||
|
onChange={(e) =>
|
||||||
|
setInstagram(e.target.value.replace(/^@/, ""))
|
||||||
|
}
|
||||||
|
maxLength={LIMITS.MAX_PROFILE_INSTAGRAM_LENGTH}
|
||||||
|
placeholder="username"
|
||||||
|
className="w-full bg-transparent py-2.5 pl-1 text-sm text-neutral-800 placeholder:text-neutral-400 outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="interest-input"
|
||||||
|
className="mb-1.5 block text-sm font-semibold text-neutral-700"
|
||||||
|
>
|
||||||
|
Minat aktivitas{" "}
|
||||||
|
<span className="text-xs font-normal text-neutral-400">
|
||||||
|
({interests.length}/{LIMITS.MAX_PROFILE_INTERESTS_COUNT})
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div className="mb-2 flex flex-wrap gap-1.5">
|
||||||
|
{interests.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
className="inline-flex items-center gap-1 rounded-full bg-secondary-50 px-2.5 py-0.5 text-xs font-medium text-secondary-700"
|
||||||
|
>
|
||||||
|
#{tag}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeInterest(tag)}
|
||||||
|
className="text-secondary-500 hover:text-red-600"
|
||||||
|
aria-label={`Hapus ${tag}`}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
id="interest-input"
|
||||||
|
type="text"
|
||||||
|
value={interestDraft}
|
||||||
|
onChange={(e) => setInterestDraft(e.target.value)}
|
||||||
|
onKeyDown={handleInterestKeyDown}
|
||||||
|
maxLength={LIMITS.MAX_PROFILE_INTEREST_LENGTH}
|
||||||
|
placeholder="hiking, fotografi, yoga... (Enter untuk tambah)"
|
||||||
|
className="flex-1 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"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={addInterest}
|
||||||
|
disabled={
|
||||||
|
interests.length >= LIMITS.MAX_PROFILE_INTERESTS_COUNT
|
||||||
|
}
|
||||||
|
className="rounded-xl border border-neutral-200 px-3 py-2.5 text-sm font-medium text-neutral-700 hover:bg-neutral-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
+ Tambah
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
|
||||||
|
Vibe jalanmu{" "}
|
||||||
|
<span className="text-xs font-normal text-neutral-400">(opsional)</span>
|
||||||
|
</label>
|
||||||
|
<p className="mb-2 text-[11px] text-neutral-500">
|
||||||
|
Bantu calon teman trip nyambung dengan ritme kamu.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setVibe(null)}
|
||||||
|
aria-pressed={vibe === null}
|
||||||
|
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
vibe === null
|
||||||
|
? "border-neutral-700 bg-neutral-800 text-white"
|
||||||
|
: "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Belum diisi
|
||||||
|
</button>
|
||||||
|
{VIBES.map((v) => {
|
||||||
|
const m = vibeMeta(v);
|
||||||
|
const active = vibe === v;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={v}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setVibe(v)}
|
||||||
|
aria-pressed={active}
|
||||||
|
title={m.description}
|
||||||
|
className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
active
|
||||||
|
? "border-primary-600 bg-primary-600 text-white"
|
||||||
|
: "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{m.icon}</span>
|
||||||
|
<span>{m.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{vibe && (
|
||||||
|
<p className="mt-2 text-[11px] italic text-neutral-500">
|
||||||
|
{vibeMeta(vibe).description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 pt-2">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="rounded-xl bg-primary-600 px-5 py-2.5 text-sm font-bold text-white shadow-md shadow-primary-600/20 transition-colors hover:bg-primary-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? "Menyimpan..." : "Simpan profil"}
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
href={`/u/${userId}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="rounded-xl border border-neutral-200 px-4 py-2.5 text-sm font-medium text-neutral-700 hover:bg-neutral-50"
|
||||||
|
>
|
||||||
|
Lihat publik ↗
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { vibeMeta } from "@/lib/vibe";
|
||||||
|
import type { Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
|
interface UserCardProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
image: string | null;
|
||||||
|
isVerifiedOrganizer: boolean;
|
||||||
|
profile: {
|
||||||
|
bio: string | null;
|
||||||
|
city: string | null;
|
||||||
|
interests: string[];
|
||||||
|
vibe: Vibe | null;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UserCard({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
image,
|
||||||
|
isVerifiedOrganizer,
|
||||||
|
profile,
|
||||||
|
}: UserCardProps) {
|
||||||
|
const interests = profile?.interests ?? [];
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/u/${id}`}
|
||||||
|
className="group flex h-full flex-col rounded-2xl border border-neutral-200 bg-white p-4 shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary-300 hover:shadow-md"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
{image ? (
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
alt=""
|
||||||
|
width={56}
|
||||||
|
height={56}
|
||||||
|
className="h-14 w-14 shrink-0 rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-full bg-primary-600 text-lg font-bold text-white">
|
||||||
|
{name.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="truncate text-sm font-bold text-neutral-800 group-hover:text-primary-700">
|
||||||
|
{name}
|
||||||
|
</p>
|
||||||
|
{profile?.city && (
|
||||||
|
<p className="truncate text-[11px] text-neutral-500 sm:text-xs">
|
||||||
|
📍 {profile.city}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="mt-1 flex flex-wrap gap-1">
|
||||||
|
{isVerifiedOrganizer && (
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-0.5 rounded-full bg-primary-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary-800"
|
||||||
|
title="Organizer terverifikasi"
|
||||||
|
>
|
||||||
|
✅ Organizer
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{profile?.vibe && (
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-0.5 rounded-full bg-secondary-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-secondary-800"
|
||||||
|
title={vibeMeta(profile.vibe).description}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{vibeMeta(profile.vibe).icon}</span>
|
||||||
|
<span>{vibeMeta(profile.vibe).label}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{profile?.bio && (
|
||||||
|
<p className="mt-3 line-clamp-2 text-xs leading-relaxed text-neutral-600">
|
||||||
|
{profile.bio}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{interests.length > 0 && (
|
||||||
|
<div className="mt-3 flex flex-wrap gap-1">
|
||||||
|
{interests.slice(0, 5).map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
className="rounded-full bg-secondary-50 px-2 py-0.5 text-[10px] font-medium text-secondary-700"
|
||||||
|
>
|
||||||
|
#{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{interests.length > 5 && (
|
||||||
|
<span className="text-[10px] text-neutral-400">
|
||||||
|
+{interests.length - 5}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
import { LIMITS } from "@/lib/limits";
|
||||||
|
import { VIBES } from "@/lib/vibe";
|
||||||
|
|
||||||
|
const optionalTrimmed = (max: number, label: string) =>
|
||||||
|
z.preprocess(
|
||||||
|
(val) => {
|
||||||
|
if (val == null) return undefined;
|
||||||
|
const s = String(val).trim();
|
||||||
|
return s === "" ? undefined : s;
|
||||||
|
},
|
||||||
|
z.string().max(max, `${label} maksimal ${max} karakter`).optional()
|
||||||
|
);
|
||||||
|
|
||||||
|
const interestSchema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(2, "Minat minimal 2 karakter")
|
||||||
|
.max(
|
||||||
|
LIMITS.MAX_PROFILE_INTEREST_LENGTH,
|
||||||
|
`Setiap minat maksimal ${LIMITS.MAX_PROFILE_INTEREST_LENGTH} karakter`
|
||||||
|
)
|
||||||
|
.regex(
|
||||||
|
/^[a-zA-Z0-9 \-]+$/,
|
||||||
|
"Minat hanya boleh huruf, angka, spasi, atau strip"
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateProfileSchema = z.object({
|
||||||
|
bio: optionalTrimmed(LIMITS.MAX_PROFILE_BIO_LENGTH, "Bio"),
|
||||||
|
city: optionalTrimmed(LIMITS.MAX_PROFILE_CITY_LENGTH, "Kota"),
|
||||||
|
instagram: z.preprocess(
|
||||||
|
(val) => {
|
||||||
|
if (val == null) return undefined;
|
||||||
|
const s = String(val).trim().replace(/^@/, "");
|
||||||
|
return s === "" ? undefined : s;
|
||||||
|
},
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.max(
|
||||||
|
LIMITS.MAX_PROFILE_INSTAGRAM_LENGTH,
|
||||||
|
`Instagram maksimal ${LIMITS.MAX_PROFILE_INSTAGRAM_LENGTH} karakter`
|
||||||
|
)
|
||||||
|
.regex(
|
||||||
|
/^[a-zA-Z0-9._]+$/,
|
||||||
|
"Username Instagram hanya boleh huruf, angka, titik, atau underscore"
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
),
|
||||||
|
interests: z
|
||||||
|
.array(interestSchema)
|
||||||
|
.max(
|
||||||
|
LIMITS.MAX_PROFILE_INTERESTS_COUNT,
|
||||||
|
`Maksimal ${LIMITS.MAX_PROFILE_INTERESTS_COUNT} minat`
|
||||||
|
)
|
||||||
|
.default([]),
|
||||||
|
vibe: z.preprocess(
|
||||||
|
(val) => {
|
||||||
|
if (val == null) return undefined;
|
||||||
|
const s = String(val).trim();
|
||||||
|
return s === "" ? undefined : s;
|
||||||
|
},
|
||||||
|
z.enum([...VIBES]).optional()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UpdateProfileInput = z.infer<typeof updateProfileSchema>;
|
||||||
@@ -28,6 +28,7 @@ export async function createTripAction(formData: FormData) {
|
|||||||
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,
|
||||||
price: formData.get("price") as string,
|
price: formData.get("price") as string,
|
||||||
|
vibe: formData.get("vibe"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = createTripSchema.safeParse(raw);
|
const result = createTripSchema.safeParse(raw);
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import {
|
|||||||
ACTIVITY_CATEGORIES,
|
ACTIVITY_CATEGORIES,
|
||||||
categoryMeta,
|
categoryMeta,
|
||||||
} from "@/lib/activity-category";
|
} from "@/lib/activity-category";
|
||||||
import type { ActivityCategory } from "@/app/generated/prisma/enums";
|
import { VIBES, vibeMeta } from "@/lib/vibe";
|
||||||
|
import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
function formatRupiahInput(value: string): string {
|
function formatRupiahInput(value: string): string {
|
||||||
const num = value.replace(/\D/g, "");
|
const num = value.replace(/\D/g, "");
|
||||||
@@ -32,6 +33,7 @@ export function CreateTripForm({ isVerifiedOrganizer }: CreateTripFormProps) {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const [category, setCategory] = useState<ActivityCategory>("HIKING");
|
const [category, setCategory] = useState<ActivityCategory>("HIKING");
|
||||||
|
const [vibe, setVibe] = useState<Vibe | null>(null);
|
||||||
const [startDate, setStartDate] = useState<Date | null>(null);
|
const [startDate, setStartDate] = useState<Date | null>(null);
|
||||||
const [endDate, setEndDate] = useState<Date | null>(null);
|
const [endDate, setEndDate] = useState<Date | null>(null);
|
||||||
const [priceDisplay, setPriceDisplay] = useState("");
|
const [priceDisplay, setPriceDisplay] = useState("");
|
||||||
@@ -62,6 +64,7 @@ export function CreateTripForm({ isVerifiedOrganizer }: CreateTripFormProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
formData.set("price", parseRupiahInput(priceDisplay));
|
formData.set("price", parseRupiahInput(priceDisplay));
|
||||||
|
if (vibe) formData.set("vibe", vibe);
|
||||||
|
|
||||||
const result = await createTripAction(formData);
|
const result = await createTripAction(formData);
|
||||||
|
|
||||||
@@ -124,6 +127,57 @@ export function CreateTripForm({ isVerifiedOrganizer }: CreateTripFormProps) {
|
|||||||
<input type="hidden" name="category" value={category} />
|
<input type="hidden" name="category" value={category} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Vibe Chips */}
|
||||||
|
<div className="rounded-xl bg-secondary-50 p-4">
|
||||||
|
<label className="mb-1 block text-sm font-bold text-secondary-900">
|
||||||
|
Vibe Trip{" "}
|
||||||
|
<span className="text-xs font-normal text-secondary-700">(opsional)</span>
|
||||||
|
</label>
|
||||||
|
<p className="mb-2 text-[11px] text-secondary-700/80">
|
||||||
|
Bantu calon peserta menilai apakah ritmenya cocok dengan mereka.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setVibe(null)}
|
||||||
|
aria-pressed={vibe === null}
|
||||||
|
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
vibe === null
|
||||||
|
? "border-neutral-700 bg-neutral-800 text-white"
|
||||||
|
: "border-secondary-200 bg-white text-secondary-800 hover:bg-secondary-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Belum diisi
|
||||||
|
</button>
|
||||||
|
{VIBES.map((v) => {
|
||||||
|
const m = vibeMeta(v);
|
||||||
|
const active = v === vibe;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={v}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setVibe(v)}
|
||||||
|
aria-pressed={active}
|
||||||
|
title={m.description}
|
||||||
|
className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
active
|
||||||
|
? "border-secondary-600 bg-secondary-600 text-white"
|
||||||
|
: "border-secondary-200 bg-white text-secondary-800 hover:bg-secondary-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{m.icon}</span>
|
||||||
|
<span>{m.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{vibe && (
|
||||||
|
<p className="mt-2 text-[11px] italic text-secondary-700/80">
|
||||||
|
{vibeMeta(vibe).description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="title" className="mb-1.5 block text-sm font-semibold text-neutral-700">
|
<label htmlFor="title" className="mb-1.5 block text-sm font-semibold text-neutral-700">
|
||||||
Judul Trip
|
Judul Trip
|
||||||
|
|||||||
@@ -3,12 +3,21 @@ import Link from "next/link";
|
|||||||
import { formatRupiah } from "@/lib/utils";
|
import { formatRupiah } from "@/lib/utils";
|
||||||
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
|
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
|
||||||
import { categoryMeta } from "@/lib/activity-category";
|
import { categoryMeta } from "@/lib/activity-category";
|
||||||
import type { ActivityCategory } from "@/app/generated/prisma/enums";
|
import { vibeMeta } from "@/lib/vibe";
|
||||||
|
import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
|
interface TripCardParticipant {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
image: string | null;
|
||||||
|
interests: string[];
|
||||||
|
}
|
||||||
|
|
||||||
interface TripCardProps {
|
interface TripCardProps {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
category: ActivityCategory;
|
category: ActivityCategory;
|
||||||
|
vibe?: Vibe | null;
|
||||||
destination: string;
|
destination: string;
|
||||||
location: string;
|
location: string;
|
||||||
date: Date | string;
|
date: Date | string;
|
||||||
@@ -21,12 +30,17 @@ interface TripCardProps {
|
|||||||
coverImage?: string | null;
|
coverImage?: string | null;
|
||||||
priority?: boolean;
|
priority?: boolean;
|
||||||
isVerifiedOrganizer?: boolean;
|
isVerifiedOrganizer?: boolean;
|
||||||
|
/** Daftar peserta CONFIRMED (subset, untuk preview avatar). Optional. */
|
||||||
|
participants?: TripCardParticipant[];
|
||||||
|
/** Interests user yang sedang melihat — untuk hitung overlap. Optional. */
|
||||||
|
viewerInterests?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TripCard({
|
export function TripCard({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
category,
|
category,
|
||||||
|
vibe,
|
||||||
destination,
|
destination,
|
||||||
location,
|
location,
|
||||||
date,
|
date,
|
||||||
@@ -39,10 +53,25 @@ export function TripCard({
|
|||||||
coverImage,
|
coverImage,
|
||||||
priority,
|
priority,
|
||||||
isVerifiedOrganizer,
|
isVerifiedOrganizer,
|
||||||
|
participants,
|
||||||
|
viewerInterests,
|
||||||
}: TripCardProps) {
|
}: TripCardProps) {
|
||||||
const spotsLeft = maxParticipants - participantCount;
|
const spotsLeft = maxParticipants - participantCount;
|
||||||
const isSmallGroup = maxParticipants <= 10;
|
const isSmallGroup = maxParticipants <= 10;
|
||||||
const meta = categoryMeta(category);
|
const meta = categoryMeta(category);
|
||||||
|
const vMeta = vibe ? vibeMeta(vibe) : null;
|
||||||
|
|
||||||
|
const previewParticipants = participants?.slice(0, 3) ?? [];
|
||||||
|
const moreCount =
|
||||||
|
participants && participants.length > 3 ? participants.length - 3 : 0;
|
||||||
|
|
||||||
|
let overlapCount = 0;
|
||||||
|
if (viewerInterests && viewerInterests.length > 0 && participants) {
|
||||||
|
const viewerSet = new Set(viewerInterests.map((i) => i.toLowerCase()));
|
||||||
|
overlapCount = participants.filter((p) =>
|
||||||
|
p.interests.some((tag) => viewerSet.has(tag.toLowerCase()))
|
||||||
|
).length;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={`/trips/${id}`} className="group block">
|
<Link href={`/trips/${id}`} className="group block">
|
||||||
@@ -63,13 +92,24 @@ export function TripCard({
|
|||||||
<span className="text-4xl">{meta.icon}</span>
|
<span className="text-4xl">{meta.icon}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span
|
<div className="absolute left-3 top-3 flex flex-wrap gap-1">
|
||||||
className="absolute left-3 top-3 inline-flex items-center gap-1 rounded-full bg-white/90 px-2 py-0.5 text-[11px] font-semibold text-neutral-700 shadow-sm backdrop-blur-sm"
|
<span
|
||||||
title={`Kategori: ${meta.label}`}
|
className="inline-flex items-center gap-1 rounded-full bg-white/90 px-2 py-0.5 text-[11px] font-semibold text-neutral-700 shadow-sm backdrop-blur-sm"
|
||||||
>
|
title={`Kategori: ${meta.label}`}
|
||||||
<span>{meta.icon}</span>
|
>
|
||||||
<span>{meta.label}</span>
|
<span>{meta.icon}</span>
|
||||||
</span>
|
<span>{meta.label}</span>
|
||||||
|
</span>
|
||||||
|
{vMeta && (
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-1 rounded-full bg-secondary-600/90 px-2 py-0.5 text-[11px] font-semibold text-white shadow-sm backdrop-blur-sm"
|
||||||
|
title={`Vibe: ${vMeta.label} — ${vMeta.description}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{vMeta.icon}</span>
|
||||||
|
<span>{vMeta.label}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<span
|
<span
|
||||||
className={`absolute right-3 top-3 rounded-full px-2.5 py-0.5 text-xs font-bold backdrop-blur-sm ${
|
className={`absolute right-3 top-3 rounded-full px-2.5 py-0.5 text-xs font-bold backdrop-blur-sm ${
|
||||||
status === "OPEN"
|
status === "OPEN"
|
||||||
@@ -120,6 +160,48 @@ export function TripCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{(previewParticipants.length > 0 || overlapCount > 0) && (
|
||||||
|
<div className="mt-3 flex items-center gap-2 border-t border-neutral-100 pt-3">
|
||||||
|
{previewParticipants.length > 0 && (
|
||||||
|
<div className="flex -space-x-2">
|
||||||
|
{previewParticipants.map((p) =>
|
||||||
|
p.image ? (
|
||||||
|
<Image
|
||||||
|
key={p.id}
|
||||||
|
src={p.image}
|
||||||
|
alt=""
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className="h-6 w-6 rounded-full border-2 border-white object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
key={p.id}
|
||||||
|
className="flex h-6 w-6 items-center justify-center rounded-full border-2 border-white bg-primary-600 text-[10px] font-bold text-white"
|
||||||
|
title={p.name}
|
||||||
|
>
|
||||||
|
{p.name.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
{moreCount > 0 && (
|
||||||
|
<div className="flex h-6 w-6 items-center justify-center rounded-full border-2 border-white bg-neutral-200 text-[10px] font-bold text-neutral-600">
|
||||||
|
+{moreCount}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{overlapCount > 0 && (
|
||||||
|
<span
|
||||||
|
className="rounded-full bg-secondary-50 px-2 py-0.5 text-[11px] font-semibold text-secondary-700"
|
||||||
|
title="Peserta dengan minimal 1 minat sama dengan kamu"
|
||||||
|
>
|
||||||
|
✨ {overlapCount} peserta sama minat
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-3 flex items-center justify-between border-t border-neutral-100 pt-3">
|
<div className="mt-3 flex items-center justify-between border-t border-neutral-100 pt-3">
|
||||||
<span className="text-lg font-bold text-primary-600">
|
<span className="text-lg font-bold text-primary-600">
|
||||||
{formatRupiah(price)}
|
{formatRupiah(price)}
|
||||||
|
|||||||
@@ -10,16 +10,38 @@ import {
|
|||||||
categoryMeta,
|
categoryMeta,
|
||||||
isActivityCategory,
|
isActivityCategory,
|
||||||
} from "@/lib/activity-category";
|
} from "@/lib/activity-category";
|
||||||
import type { ActivityCategory } from "@/app/generated/prisma/enums";
|
import { VIBES, vibeMeta, isVibe } from "@/lib/vibe";
|
||||||
|
import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
|
type GroupSize = "SMALL" | "MEDIUM" | "LARGE";
|
||||||
|
const GROUP_SIZES: { value: GroupSize; label: string; hint: string }[] = [
|
||||||
|
{ value: "SMALL", label: "Small", hint: "≤10 — paling akrab" },
|
||||||
|
{ value: "MEDIUM", label: "Medium", hint: "11–20" },
|
||||||
|
{ value: "LARGE", label: "Large", hint: "21+" },
|
||||||
|
];
|
||||||
|
function isGroupSize(value: unknown): value is GroupSize {
|
||||||
|
return (
|
||||||
|
typeof value === "string" &&
|
||||||
|
GROUP_SIZES.some((g) => g.value === value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function TripFilter() {
|
export function TripFilter() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const initialCategory = searchParams.get("category");
|
const initialCategory = searchParams.get("category");
|
||||||
|
const initialVibe = searchParams.get("vibe");
|
||||||
|
const initialGroup = searchParams.get("groupSize");
|
||||||
const [category, setCategory] = useState<ActivityCategory | null>(
|
const [category, setCategory] = useState<ActivityCategory | null>(
|
||||||
isActivityCategory(initialCategory) ? initialCategory : null
|
isActivityCategory(initialCategory) ? initialCategory : null
|
||||||
);
|
);
|
||||||
|
const [vibe, setVibe] = useState<Vibe | null>(
|
||||||
|
isVibe(initialVibe) ? initialVibe : null
|
||||||
|
);
|
||||||
|
const [groupSize, setGroupSize] = useState<GroupSize | null>(
|
||||||
|
isGroupSize(initialGroup) ? initialGroup : null
|
||||||
|
);
|
||||||
const [query, setQuery] = useState(searchParams.get("q") ?? "");
|
const [query, setQuery] = useState(searchParams.get("q") ?? "");
|
||||||
const [startDate, setStartDate] = useState<Date | null>(
|
const [startDate, setStartDate] = useState<Date | null>(
|
||||||
searchParams.get("from") ? new Date(searchParams.get("from")!) : null
|
searchParams.get("from") ? new Date(searchParams.get("from")!) : null
|
||||||
@@ -28,11 +50,20 @@ export function TripFilter() {
|
|||||||
searchParams.get("to") ? new Date(searchParams.get("to")!) : null
|
searchParams.get("to") ? new Date(searchParams.get("to")!) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
function buildParams(overrides?: { category?: ActivityCategory | null }) {
|
function buildParams(overrides?: {
|
||||||
|
category?: ActivityCategory | null;
|
||||||
|
vibe?: Vibe | null;
|
||||||
|
groupSize?: GroupSize | null;
|
||||||
|
}) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
const nextCategory =
|
const nextCategory =
|
||||||
overrides && "category" in overrides ? overrides.category : category;
|
overrides && "category" in overrides ? overrides.category : category;
|
||||||
|
const nextVibe = overrides && "vibe" in overrides ? overrides.vibe : vibe;
|
||||||
|
const nextGroup =
|
||||||
|
overrides && "groupSize" in overrides ? overrides.groupSize : groupSize;
|
||||||
if (nextCategory) params.set("category", nextCategory);
|
if (nextCategory) params.set("category", nextCategory);
|
||||||
|
if (nextVibe) params.set("vibe", nextVibe);
|
||||||
|
if (nextGroup) params.set("groupSize", nextGroup);
|
||||||
if (query.trim()) params.set("q", query.trim());
|
if (query.trim()) params.set("q", query.trim());
|
||||||
if (startDate) params.set("from", formatLocalCalendarYmd(startDate));
|
if (startDate) params.set("from", formatLocalCalendarYmd(startDate));
|
||||||
if (endDate) params.set("to", formatLocalCalendarYmd(endDate));
|
if (endDate) params.set("to", formatLocalCalendarYmd(endDate));
|
||||||
@@ -49,6 +80,16 @@ export function TripFilter() {
|
|||||||
pushFilters(buildParams({ category: next }));
|
pushFilters(buildParams({ category: next }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSelectVibe(next: Vibe | null) {
|
||||||
|
setVibe(next);
|
||||||
|
pushFilters(buildParams({ vibe: next }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectGroupSize(next: GroupSize | null) {
|
||||||
|
setGroupSize(next);
|
||||||
|
pushFilters(buildParams({ groupSize: next }));
|
||||||
|
}
|
||||||
|
|
||||||
function handleDateChange(dates: [Date | null, Date | null]) {
|
function handleDateChange(dates: [Date | null, Date | null]) {
|
||||||
const [start, end] = dates;
|
const [start, end] = dates;
|
||||||
setStartDate(start);
|
setStartDate(start);
|
||||||
@@ -57,6 +98,8 @@ export function TripFilter() {
|
|||||||
if (!start && !end) {
|
if (!start && !end) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (category) params.set("category", category);
|
if (category) params.set("category", category);
|
||||||
|
if (vibe) params.set("vibe", vibe);
|
||||||
|
if (groupSize) params.set("groupSize", groupSize);
|
||||||
if (query.trim()) params.set("q", query.trim());
|
if (query.trim()) params.set("q", query.trim());
|
||||||
pushFilters(params);
|
pushFilters(params);
|
||||||
}
|
}
|
||||||
@@ -69,13 +112,15 @@ export function TripFilter() {
|
|||||||
|
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
setCategory(null);
|
setCategory(null);
|
||||||
|
setVibe(null);
|
||||||
|
setGroupSize(null);
|
||||||
setQuery("");
|
setQuery("");
|
||||||
setStartDate(null);
|
setStartDate(null);
|
||||||
setEndDate(null);
|
setEndDate(null);
|
||||||
router.push("/trips");
|
router.push("/trips");
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasFilters = category || query || startDate || endDate;
|
const hasFilters = category || vibe || groupSize || query || startDate || endDate;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
@@ -123,6 +168,89 @@ export function TripFilter() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Vibe & Ukuran grup */}
|
||||||
|
<div className="mb-4 grid gap-3 sm:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label className="mb-1.5 block text-xs font-medium text-neutral-500">
|
||||||
|
Vibe
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleSelectVibe(null)}
|
||||||
|
aria-pressed={vibe === null}
|
||||||
|
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
vibe === null
|
||||||
|
? "border-secondary-600 bg-secondary-600 text-white"
|
||||||
|
: "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Semua
|
||||||
|
</button>
|
||||||
|
{VIBES.map((v) => {
|
||||||
|
const m = vibeMeta(v);
|
||||||
|
const active = vibe === v;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={v}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleSelectVibe(v)}
|
||||||
|
aria-pressed={active}
|
||||||
|
title={m.description}
|
||||||
|
className={`inline-flex items-center gap-1 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
active
|
||||||
|
? "border-secondary-600 bg-secondary-600 text-white"
|
||||||
|
: "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>{m.icon}</span>
|
||||||
|
<span>{m.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-1.5 block text-xs font-medium text-neutral-500">
|
||||||
|
Ukuran grup
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleSelectGroupSize(null)}
|
||||||
|
aria-pressed={groupSize === null}
|
||||||
|
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
groupSize === null
|
||||||
|
? "border-primary-600 bg-primary-600 text-white"
|
||||||
|
: "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Semua
|
||||||
|
</button>
|
||||||
|
{GROUP_SIZES.map((g) => {
|
||||||
|
const active = groupSize === g.value;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={g.value}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleSelectGroupSize(g.value)}
|
||||||
|
aria-pressed={active}
|
||||||
|
title={g.hint}
|
||||||
|
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
|
active
|
||||||
|
? "border-primary-600 bg-primary-600 text-white"
|
||||||
|
: "border-neutral-200 bg-white text-neutral-600 hover:bg-neutral-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{g.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:gap-3">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:gap-3">
|
||||||
{/* Search input */}
|
{/* Search input */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { LIMITS } from "@/lib/limits";
|
import { LIMITS } from "@/lib/limits";
|
||||||
import { ACTIVITY_CATEGORIES } from "@/lib/activity-category";
|
import { ACTIVITY_CATEGORIES } from "@/lib/activity-category";
|
||||||
|
import { VIBES } from "@/lib/vibe";
|
||||||
import type { ActivityCategory } from "@/app/generated/prisma/enums";
|
import type { ActivityCategory } from "@/app/generated/prisma/enums";
|
||||||
import {
|
import {
|
||||||
isTripDepartureDayPast,
|
isTripDepartureDayPast,
|
||||||
@@ -146,6 +147,14 @@ export const createTripSchema = z
|
|||||||
)
|
)
|
||||||
.optional()
|
.optional()
|
||||||
),
|
),
|
||||||
|
vibe: z.preprocess(
|
||||||
|
(val) => {
|
||||||
|
if (val == null) return undefined;
|
||||||
|
const s = String(val).trim();
|
||||||
|
return s === "" ? undefined : s;
|
||||||
|
},
|
||||||
|
z.enum([...VIBES]).optional()
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
const dep = tripStoredInstantFromYmd(data.date);
|
const dep = tripStoredInstantFromYmd(data.date);
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ export const LIMITS = {
|
|||||||
MAX_URL_LENGTH: 2048,
|
MAX_URL_LENGTH: 2048,
|
||||||
MAX_NAME_LENGTH: 80,
|
MAX_NAME_LENGTH: 80,
|
||||||
MAX_PASSWORD_LENGTH: 72,
|
MAX_PASSWORD_LENGTH: 72,
|
||||||
|
/** Profil sosial publik */
|
||||||
|
MAX_PROFILE_BIO_LENGTH: 500,
|
||||||
|
MAX_PROFILE_CITY_LENGTH: 60,
|
||||||
|
MAX_PROFILE_INTEREST_LENGTH: 30,
|
||||||
|
MAX_PROFILE_INTERESTS_COUNT: 10,
|
||||||
|
MAX_PROFILE_INSTAGRAM_LENGTH: 30,
|
||||||
/** Verifikasi organizer (KTP + bank) */
|
/** Verifikasi organizer (KTP + bank) */
|
||||||
MAX_ADDRESS_LENGTH: 500,
|
MAX_ADDRESS_LENGTH: 500,
|
||||||
MAX_BANK_NAME_LENGTH: 60,
|
MAX_BANK_NAME_LENGTH: 60,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import path from "node:path";
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import { encryptBuffer, decryptBuffer } from "@/lib/crypto";
|
import { encryptBuffer, decryptBuffer } from "@/lib/crypto";
|
||||||
|
|
||||||
export type KycKind = "ktp" | "selfie";
|
export type KycKind = "ktp" | "liveness";
|
||||||
|
|
||||||
const KIND_DIRS: Record<KycKind, string> = {
|
const KIND_DIRS: Record<KycKind, string> = {
|
||||||
ktp: "ktp",
|
ktp: "ktp",
|
||||||
selfie: "selfie",
|
liveness: "liveness",
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Bytes. ~5MB matches the form limit; raise here if you change the upload route. */
|
/** Bytes. ~5MB matches the form limit; raise here if you change the upload route. */
|
||||||
@@ -43,7 +43,7 @@ export type StoredFileMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function isKycKind(value: string): value is KycKind {
|
export function isKycKind(value: string): value is KycKind {
|
||||||
return value === "ktp" || value === "selfie";
|
return value === "ktp" || value === "liveness";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolve a storage key (`ktp/abc.jpg`) to an absolute path inside the upload dir. Throws on traversal. */
|
/** Resolve a storage key (`ktp/abc.jpg`) to an absolute path inside the upload dir. Throws on traversal. */
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
import type { Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
|
export const VIBES = ["CHILL", "BALANCED", "HARDCORE"] as const satisfies readonly Vibe[];
|
||||||
|
|
||||||
|
interface VibeMeta {
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const META: Record<Vibe, VibeMeta> = {
|
||||||
|
CHILL: {
|
||||||
|
label: "Chill",
|
||||||
|
icon: "😌",
|
||||||
|
description: "Santai, nikmati prosesnya, no rush.",
|
||||||
|
},
|
||||||
|
BALANCED: {
|
||||||
|
label: "Balanced",
|
||||||
|
icon: "🙂",
|
||||||
|
description: "Seimbang — ada effort, ada healing.",
|
||||||
|
},
|
||||||
|
HARDCORE: {
|
||||||
|
label: "Hardcore",
|
||||||
|
icon: "🔥",
|
||||||
|
description: "Push limit, target tercapai, full energy.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function vibeMeta(vibe: Vibe): VibeMeta {
|
||||||
|
return META[vibe];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function vibeLabel(vibe: Vibe): string {
|
||||||
|
return META[vibe].label;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isVibe(value: unknown): value is Vibe {
|
||||||
|
return (
|
||||||
|
typeof value === "string" && (VIBES as readonly string[]).includes(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
Generated
+47
-47
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "setrip",
|
"name": "setrip",
|
||||||
"version": "0.5.0",
|
"version": "0.7.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "setrip",
|
"name": "setrip",
|
||||||
"version": "0.5.0",
|
"version": "0.7.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next-auth/prisma-adapter": "^1.0.7",
|
"@next-auth/prisma-adapter": "^1.0.7",
|
||||||
"@prisma/adapter-pg": "^7.7.0",
|
"@prisma/adapter-pg": "^7.7.0",
|
||||||
@@ -1657,9 +1657,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.5.tgz",
|
||||||
"integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==",
|
"integrity": "sha512-Lb9ElHD2klcyeVD25vW+siPFqz9QMzDUSgvFZNO+dZEKoMHex4viJhVuzBhrXKqb+UKnih7mVYbt50/7KLsSCA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
@@ -1673,9 +1673,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.5.tgz",
|
||||||
"integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==",
|
"integrity": "sha512-BW+8PGVmsruomXHsitD8JG6gny9lEdobctjBwvtPF8AKtxGDR7nR35FOl/oK9UAPXBOBm+vx0k8qtpeHOXQMGQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1689,9 +1689,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.5.tgz",
|
||||||
"integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==",
|
"integrity": "sha512-ZoCGnCl9LlQJWmqXrZAUlNxvuNmclvE+7zUif+nDydkkehl9FKxHJ+wxSQMj+C37BYFerKiEdX9s9o02ir975Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1705,9 +1705,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.5.tgz",
|
||||||
"integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==",
|
"integrity": "sha512-AwcZzMChaWkOTZt3vu+2ZMIj8g4dYQY+B8VUVhlFSQ2JtvyZpefyYHTe00D6b6L7BysYw7vl3zsvs9jix8tl5Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1724,9 +1724,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.5.tgz",
|
||||||
"integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==",
|
"integrity": "sha512-QqMgqWbCBFsfiQ7BF3dUlW8HJy1LWhpcqbTpoHMWA9IV+TnWwDKozQJA5NdIAHjQ00yX2Q7AUkLr/XK4n77q8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1743,9 +1743,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.5.tgz",
|
||||||
"integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==",
|
"integrity": "sha512-3hzeiFGZtyATVx9pCeuzTshXmh50vHZitqaeZiyJZaUmjQyrfjsVUgS8apOj1vEJCIpKJM/55F45yPAV2kpjsA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1762,9 +1762,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.5.tgz",
|
||||||
"integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==",
|
"integrity": "sha512-0mzZV/mAt7Qj2tYNdTB6AqrS8dwng/AQLSYC5Z1YLpZdi2wxqKDPK7RY2RvjB1fXyJfOfdA3l/yTF5yLi+WfuQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1781,9 +1781,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.5.tgz",
|
||||||
"integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==",
|
"integrity": "sha512-f/H4nZ2zJBvA8/+HpsB9mNonF9zfQoAU6D0WxJrfzhJDvJLfngVN85oqxUyrDVK99DIFfFYhLpGa5K+c5uotSw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1797,9 +1797,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.5.tgz",
|
||||||
"integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==",
|
"integrity": "sha512-nuP7DHs4koAojsIxVPkihNgKiRUKtCU65j5X6DAbSy8VBrfT/o90bCLLHPf51JEdOZwZMFzM6e0NiGWfIWjVAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -5376,9 +5376,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hono": {
|
"node_modules/hono": {
|
||||||
"version": "4.12.14",
|
"version": "4.12.18",
|
||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
|
||||||
"integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
|
"integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
@@ -6563,12 +6563,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "16.2.4",
|
"version": "16.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.2.5.tgz",
|
||||||
"integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==",
|
"integrity": "sha512-TkVTm9F2WEulkgGljm4wPwNgvCCWCVw6StUHsZb8WZpHFRjepoUWg3d7L4IMg7IyjcJ4Co9eVhpro8e8O+KarQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "16.2.4",
|
"@next/env": "16.2.5",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"baseline-browser-mapping": "^2.9.19",
|
"baseline-browser-mapping": "^2.9.19",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
@@ -6582,14 +6582,14 @@
|
|||||||
"node": ">=20.9.0"
|
"node": ">=20.9.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "16.2.4",
|
"@next/swc-darwin-arm64": "16.2.5",
|
||||||
"@next/swc-darwin-x64": "16.2.4",
|
"@next/swc-darwin-x64": "16.2.5",
|
||||||
"@next/swc-linux-arm64-gnu": "16.2.4",
|
"@next/swc-linux-arm64-gnu": "16.2.5",
|
||||||
"@next/swc-linux-arm64-musl": "16.2.4",
|
"@next/swc-linux-arm64-musl": "16.2.5",
|
||||||
"@next/swc-linux-x64-gnu": "16.2.4",
|
"@next/swc-linux-x64-gnu": "16.2.5",
|
||||||
"@next/swc-linux-x64-musl": "16.2.4",
|
"@next/swc-linux-x64-musl": "16.2.5",
|
||||||
"@next/swc-win32-arm64-msvc": "16.2.4",
|
"@next/swc-win32-arm64-msvc": "16.2.5",
|
||||||
"@next/swc-win32-x64-msvc": "16.2.4",
|
"@next/swc-win32-x64-msvc": "16.2.5",
|
||||||
"sharp": "^0.34.5"
|
"sharp": "^0.34.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -7145,9 +7145,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.9",
|
"version": "8.5.14",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||||
"integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
|
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "setrip",
|
"name": "setrip",
|
||||||
"version": "0.5.0",
|
"version": "0.7.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserProfile" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"bio" TEXT,
|
||||||
|
"city" TEXT,
|
||||||
|
"interests" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"instagram" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "UserProfile_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "UserProfile_userId_key" ON "UserProfile"("userId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserProfile" ADD CONSTRAINT "UserProfile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "Vibe" AS ENUM ('CHILL', 'BALANCED', 'HARDCORE');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UserProfile" ADD COLUMN "vibe" "Vibe";
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Trip" ADD COLUMN "vibe" "Vibe";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Trip_vibe_status_date_idx" ON "Trip"("vibe", "status", "date");
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- AlterTable: rename selfieKey -> livenessKey.
|
||||||
|
-- Sebelumnya: storage key untuk selfie memegang KTP.
|
||||||
|
-- Sekarang: storage key foto liveness (user memegang kertas tulisan "SETRIP").
|
||||||
|
ALTER TABLE "OrganizerVerification" RENAME COLUMN "selfieKey" TO "livenessKey";
|
||||||
+36
-2
@@ -30,6 +30,36 @@ model User {
|
|||||||
|
|
||||||
organizerVerification OrganizerVerification? @relation("OrganizerVerificationOwner")
|
organizerVerification OrganizerVerification? @relation("OrganizerVerificationOwner")
|
||||||
reviewedVerifications OrganizerVerification[] @relation("OrganizerVerificationReviewer")
|
reviewedVerifications OrganizerVerification[] @relation("OrganizerVerificationReviewer")
|
||||||
|
|
||||||
|
profile UserProfile?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Profil sosial publik. Berisi info yang user pilih untuk dibagikan ke peserta lain
|
||||||
|
/// (bio, kota, minat, vibe). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
|
||||||
|
model UserProfile {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
userId String @unique
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
/// Bio singkat, teks bebas
|
||||||
|
bio String?
|
||||||
|
/// Kota domisili (teks bebas, mis. "Bandung", "Jakarta Selatan")
|
||||||
|
city String?
|
||||||
|
/// Tag minat aktivitas (mis. ["hiking", "fotografi", "yoga"])
|
||||||
|
interests String[] @default([])
|
||||||
|
/// Username Instagram (tanpa @, opsional)
|
||||||
|
instagram String?
|
||||||
|
/// Gaya jalan / energi user — dipakai untuk matching teman dengan ritme serupa.
|
||||||
|
vibe Vibe?
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Vibe {
|
||||||
|
CHILL
|
||||||
|
BALANCED
|
||||||
|
HARDCORE
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tabel link akun OAuth pihak ketiga (Google, dst). Diisi oleh PrismaAdapter NextAuth.
|
/// Tabel link akun OAuth pihak ketiga (Google, dst). Diisi oleh PrismaAdapter NextAuth.
|
||||||
@@ -69,8 +99,9 @@ model OrganizerVerification {
|
|||||||
|
|
||||||
/// Storage key foto KTP (mis. `ktp/<id>.jpg`). File disimpan terenkripsi di luar /public.
|
/// Storage key foto KTP (mis. `ktp/<id>.jpg`). File disimpan terenkripsi di luar /public.
|
||||||
ktpImageKey String
|
ktpImageKey String
|
||||||
/// Storage key selfie memegang KTP.
|
/// Storage key foto liveness — user memegang kertas bertuliskan "SETRIP".
|
||||||
selfieKey String
|
/// (Sebelumnya: selfie memegang KTP. Diganti supaya user tidak perlu memajang KTP dua kali.)
|
||||||
|
livenessKey String
|
||||||
|
|
||||||
bankName String
|
bankName String
|
||||||
bankAccountNumber String
|
bankAccountNumber String
|
||||||
@@ -114,6 +145,8 @@ model Trip {
|
|||||||
endDate DateTime?
|
endDate DateTime?
|
||||||
maxParticipants Int
|
maxParticipants Int
|
||||||
price Int
|
price Int
|
||||||
|
/// Ritme/energi trip — dipakai untuk matching dengan vibe user.
|
||||||
|
vibe Vibe?
|
||||||
status TripStatus @default(OPEN)
|
status TripStatus @default(OPEN)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -126,6 +159,7 @@ model Trip {
|
|||||||
reviews TripReview[]
|
reviews TripReview[]
|
||||||
|
|
||||||
@@index([category, status, date])
|
@@index([category, status, date])
|
||||||
|
@@index([vibe, status, date])
|
||||||
}
|
}
|
||||||
|
|
||||||
model TripReview {
|
model TripReview {
|
||||||
|
|||||||
+2
-2
@@ -69,7 +69,7 @@ async function main() {
|
|||||||
birthDate: new Date(Date.UTC(1990, 0, 1)),
|
birthDate: new Date(Date.UTC(1990, 0, 1)),
|
||||||
address: "Jl. Pendaki No. 1, Garut, Jawa Barat",
|
address: "Jl. Pendaki No. 1, Garut, Jawa Barat",
|
||||||
ktpImageKey: "ktp/seed-dede.jpg",
|
ktpImageKey: "ktp/seed-dede.jpg",
|
||||||
selfieKey: "selfie/seed-dede.jpg",
|
livenessKey: "liveness/seed-dede.jpg",
|
||||||
bankName: "BCA",
|
bankName: "BCA",
|
||||||
bankAccountNumber: "1234567890",
|
bankAccountNumber: "1234567890",
|
||||||
bankAccountName: "Dede Inoen",
|
bankAccountName: "Dede Inoen",
|
||||||
@@ -85,7 +85,7 @@ async function main() {
|
|||||||
birthDate: new Date(Date.UTC(1985, 5, 15)),
|
birthDate: new Date(Date.UTC(1985, 5, 15)),
|
||||||
address: "Jl. Adventure No. 7, Kuningan, Jawa Barat",
|
address: "Jl. Adventure No. 7, Kuningan, Jawa Barat",
|
||||||
ktpImageKey: "ktp/seed-panji.jpg",
|
ktpImageKey: "ktp/seed-panji.jpg",
|
||||||
selfieKey: "selfie/seed-panji.jpg",
|
livenessKey: "liveness/seed-panji.jpg",
|
||||||
bankName: "Mandiri",
|
bankName: "Mandiri",
|
||||||
bankAccountNumber: "9876543210",
|
bankAccountNumber: "9876543210",
|
||||||
bankAccountName: "Panji Petualang",
|
bankAccountName: "Panji Petualang",
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import type { Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
|
interface UpsertProfileInput {
|
||||||
|
bio?: string;
|
||||||
|
city?: string;
|
||||||
|
instagram?: string;
|
||||||
|
interests: string[];
|
||||||
|
vibe?: Vibe;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const profileRepo = {
|
||||||
|
async findByUserId(userId: string) {
|
||||||
|
return prisma.userProfile.findUnique({ where: { userId } });
|
||||||
|
},
|
||||||
|
|
||||||
|
async upsertByUserId(userId: string, data: UpsertProfileInput) {
|
||||||
|
return prisma.userProfile.upsert({
|
||||||
|
where: { userId },
|
||||||
|
create: {
|
||||||
|
userId,
|
||||||
|
bio: data.bio,
|
||||||
|
city: data.city,
|
||||||
|
instagram: data.instagram,
|
||||||
|
interests: data.interests,
|
||||||
|
vibe: data.vibe,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
bio: data.bio,
|
||||||
|
city: data.city,
|
||||||
|
instagram: data.instagram,
|
||||||
|
interests: data.interests,
|
||||||
|
vibe: data.vibe,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { Prisma } from "@/app/generated/prisma/client";
|
import { Prisma } from "@/app/generated/prisma/client";
|
||||||
import type { ActivityCategory } from "@/app/generated/prisma/enums";
|
import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums";
|
||||||
import {
|
import {
|
||||||
utcStartOfDay,
|
utcStartOfDay,
|
||||||
utcDayStartFromYmd,
|
utcDayStartFromYmd,
|
||||||
@@ -8,11 +8,15 @@ import {
|
|||||||
maxUtcDate,
|
maxUtcDate,
|
||||||
} from "@/lib/trip-dates";
|
} from "@/lib/trip-dates";
|
||||||
|
|
||||||
|
export type GroupSize = "SMALL" | "MEDIUM" | "LARGE";
|
||||||
|
|
||||||
export interface TripFilters {
|
export interface TripFilters {
|
||||||
q?: string;
|
q?: string;
|
||||||
from?: string;
|
from?: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
category?: ActivityCategory;
|
category?: ActivityCategory;
|
||||||
|
vibe?: Vibe;
|
||||||
|
groupSize?: GroupSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tripRepo = {
|
export const tripRepo = {
|
||||||
@@ -47,6 +51,18 @@ export const tripRepo = {
|
|||||||
andParts.push({ category: filters.category });
|
andParts.push({ category: filters.category });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filters?.vibe) {
|
||||||
|
andParts.push({ vibe: filters.vibe });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.groupSize === "SMALL") {
|
||||||
|
andParts.push({ maxParticipants: { lte: 10 } });
|
||||||
|
} else if (filters?.groupSize === "MEDIUM") {
|
||||||
|
andParts.push({ maxParticipants: { gte: 11, lte: 20 } });
|
||||||
|
} else if (filters?.groupSize === "LARGE") {
|
||||||
|
andParts.push({ maxParticipants: { gte: 21 } });
|
||||||
|
}
|
||||||
|
|
||||||
if (!filters?.from && !filters?.to) {
|
if (!filters?.from && !filters?.to) {
|
||||||
andParts.push({ date: { gte: todayStart } });
|
andParts.push({ date: { gte: todayStart } });
|
||||||
} else {
|
} else {
|
||||||
@@ -104,6 +120,22 @@ export const tripRepo = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
images: { orderBy: { order: "asc" }, take: 1 },
|
images: { orderBy: { order: "asc" }, take: 1 },
|
||||||
|
participants: {
|
||||||
|
where: { status: "CONFIRMED" },
|
||||||
|
take: 10,
|
||||||
|
orderBy: { createdAt: "asc" },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
image: true,
|
||||||
|
profile: { select: { interests: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
_count: {
|
_count: {
|
||||||
select: {
|
select: {
|
||||||
participants: { where: { status: { not: "CANCELLED" } } },
|
participants: { where: { status: { not: "CANCELLED" } } },
|
||||||
@@ -129,7 +161,16 @@ export const tripRepo = {
|
|||||||
},
|
},
|
||||||
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,
|
||||||
|
profile: { select: { city: true, interests: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
reviews: {
|
reviews: {
|
||||||
orderBy: { createdAt: "desc" },
|
orderBy: { createdAt: "desc" },
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { Prisma } from "@/app/generated/prisma/client";
|
import { Prisma } from "@/app/generated/prisma/client";
|
||||||
|
import type { Vibe } from "@/app/generated/prisma/enums";
|
||||||
|
|
||||||
|
export interface PeopleFilters {
|
||||||
|
city?: string;
|
||||||
|
interest?: string;
|
||||||
|
vibe?: Vibe;
|
||||||
|
}
|
||||||
|
|
||||||
export const userRepo = {
|
export const userRepo = {
|
||||||
async findByEmail(email: string) {
|
async findByEmail(email: string) {
|
||||||
@@ -24,6 +31,82 @@ export const userRepo = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profil sosial publik untuk halaman /u/[id]. JANGAN sertakan field sensitif
|
||||||
|
* (email, password, KYC). Hanya yang user pilih untuk dibagikan.
|
||||||
|
*/
|
||||||
|
async findSocialProfileById(id: string) {
|
||||||
|
return prisma.user.findUnique({
|
||||||
|
where: { id },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
image: true,
|
||||||
|
createdAt: true,
|
||||||
|
profile: {
|
||||||
|
select: {
|
||||||
|
bio: true,
|
||||||
|
city: true,
|
||||||
|
interests: true,
|
||||||
|
instagram: true,
|
||||||
|
vibe: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
organizerVerification: { select: { status: true } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discovery /people: ambil user yang punya profil sosial terisi (minimal salah
|
||||||
|
* satu dari bio/city/interests/vibe). Filter optional by city/interest/vibe.
|
||||||
|
* Tidak ekspos email/KYC.
|
||||||
|
*/
|
||||||
|
async findPeople(filters?: PeopleFilters, limit = 60) {
|
||||||
|
const profileWhere: Prisma.UserProfileWhereInput = {
|
||||||
|
OR: [
|
||||||
|
{ bio: { not: null } },
|
||||||
|
{ city: { not: null } },
|
||||||
|
{ vibe: { not: null } },
|
||||||
|
{ interests: { isEmpty: false } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (filters?.city) {
|
||||||
|
profileWhere.city = {
|
||||||
|
contains: filters.city,
|
||||||
|
mode: "insensitive",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (filters?.interest) {
|
||||||
|
profileWhere.interests = { has: filters.interest.toLowerCase() };
|
||||||
|
}
|
||||||
|
if (filters?.vibe) {
|
||||||
|
profileWhere.vibe = filters.vibe;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prisma.user.findMany({
|
||||||
|
where: { profile: { is: profileWhere } },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
image: true,
|
||||||
|
createdAt: true,
|
||||||
|
profile: {
|
||||||
|
select: {
|
||||||
|
bio: true,
|
||||||
|
city: true,
|
||||||
|
interests: true,
|
||||||
|
vibe: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
organizerVerification: { select: { status: true } },
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
take: limit,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async create(data: Prisma.UserCreateInput) {
|
async create(data: Prisma.UserCreateInput) {
|
||||||
return prisma.user.create({ data });
|
return prisma.user.create({ data });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ type SubmitInput = {
|
|||||||
birthDate: Date;
|
birthDate: Date;
|
||||||
address: string;
|
address: string;
|
||||||
ktpImageKey: string;
|
ktpImageKey: string;
|
||||||
selfieKey: string;
|
livenessKey: string;
|
||||||
bankName: string;
|
bankName: string;
|
||||||
bankAccountNumber: string;
|
bankAccountNumber: string;
|
||||||
bankAccountName: string;
|
bankAccountName: string;
|
||||||
@@ -36,7 +36,7 @@ export const organizerService = {
|
|||||||
birthDate: data.birthDate,
|
birthDate: data.birthDate,
|
||||||
address: data.address,
|
address: data.address,
|
||||||
ktpImageKey: data.ktpImageKey,
|
ktpImageKey: data.ktpImageKey,
|
||||||
selfieKey: data.selfieKey,
|
livenessKey: data.livenessKey,
|
||||||
bankName: data.bankName,
|
bankName: data.bankName,
|
||||||
bankAccountNumber: data.bankAccountNumber,
|
bankAccountNumber: data.bankAccountNumber,
|
||||||
bankAccountName: data.bankAccountName,
|
bankAccountName: data.bankAccountName,
|
||||||
|
|||||||
@@ -1,10 +1,59 @@
|
|||||||
import { userRepo } from "@/server/repositories/user.repo";
|
import { userRepo, type PeopleFilters } from "@/server/repositories/user.repo";
|
||||||
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 { organizerRepo } from "@/server/repositories/organizer.repo";
|
import { organizerRepo } from "@/server/repositories/organizer.repo";
|
||||||
|
import { profileRepo } from "@/server/repositories/profile.repo";
|
||||||
import { isPastTripLastDayForReview } from "@/lib/trip-dates";
|
import { isPastTripLastDayForReview } from "@/lib/trip-dates";
|
||||||
|
import type { UpdateProfileInput } from "@/features/profile/schemas";
|
||||||
|
|
||||||
export const profileService = {
|
export const profileService = {
|
||||||
|
async getOwnProfile(userId: string) {
|
||||||
|
return profileRepo.findByUserId(userId);
|
||||||
|
},
|
||||||
|
|
||||||
|
async findPeople(filters?: PeopleFilters) {
|
||||||
|
return userRepo.findPeople(filters);
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateProfile(userId: string, input: UpdateProfileInput) {
|
||||||
|
return profileRepo.upsertByUserId(userId, {
|
||||||
|
bio: input.bio,
|
||||||
|
city: input.city,
|
||||||
|
instagram: input.instagram,
|
||||||
|
interests: input.interests,
|
||||||
|
vibe: input.vibe,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Halaman profil publik /u/[id]. Membaca user + UserProfile + counts trip.
|
||||||
|
* Tidak ekspos email/KYC.
|
||||||
|
*/
|
||||||
|
async getPublicProfile(userId: string) {
|
||||||
|
const user = await userRepo.findSocialProfileById(userId);
|
||||||
|
if (!user) return null;
|
||||||
|
|
||||||
|
const [organizedTrips, participations] = await Promise.all([
|
||||||
|
tripRepo.findByOrganizerId(userId),
|
||||||
|
participantRepo.findWithTripForProfile(userId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const joinedTrips = participations
|
||||||
|
.filter((p) => p.status !== "CANCELLED")
|
||||||
|
.map((p) => p.trip)
|
||||||
|
.sort(
|
||||||
|
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
isVerifiedOrganizer:
|
||||||
|
user.organizerVerification?.status === "APPROVED",
|
||||||
|
organizedTrips,
|
||||||
|
joinedTrips,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
async getProfileDashboard(userId: string) {
|
async getProfileDashboard(userId: string) {
|
||||||
const user = await userRepo.findPublicProfileById(userId);
|
const user = await userRepo.findPublicProfileById(userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Prisma } from "@/app/generated/prisma/client";
|
import { Prisma } from "@/app/generated/prisma/client";
|
||||||
import type { ActivityCategory } from "@/app/generated/prisma/enums";
|
import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { tripRepo, type TripFilters } from "@/server/repositories/trip.repo";
|
import { tripRepo, type TripFilters } from "@/server/repositories/trip.repo";
|
||||||
import { participantRepo } from "@/server/repositories/participant.repo";
|
import { participantRepo } from "@/server/repositories/participant.repo";
|
||||||
@@ -31,6 +31,7 @@ interface CreateTripInput {
|
|||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
maxParticipants: number;
|
maxParticipants: number;
|
||||||
price: number;
|
price: number;
|
||||||
|
vibe?: Vibe;
|
||||||
organizerId: string;
|
organizerId: string;
|
||||||
imageUrls?: string[];
|
imageUrls?: string[];
|
||||||
}
|
}
|
||||||
@@ -82,6 +83,7 @@ export const tripService = {
|
|||||||
endDate: input.endDate,
|
endDate: input.endDate,
|
||||||
maxParticipants: input.maxParticipants,
|
maxParticipants: input.maxParticipants,
|
||||||
price: input.price,
|
price: input.price,
|
||||||
|
vibe: input.vibe,
|
||||||
organizer: { connect: { id: input.organizerId } },
|
organizer: { connect: { id: input.organizerId } },
|
||||||
images,
|
images,
|
||||||
} satisfies Prisma.TripCreateInput;
|
} satisfies Prisma.TripCreateInput;
|
||||||
|
|||||||
Reference in New Issue
Block a user