kyc user and upload partial update encrypt nik and picture

This commit is contained in:
2026-04-27 21:48:24 +07:00
parent b31fe675ae
commit a92b4a8fd9
51 changed files with 5180 additions and 452 deletions
+8 -1
View File
@@ -230,10 +230,17 @@ Alur data mengikuti pola yang sama: **UI (`app/`) → server actions (`features/
### Trust & organizer (`server/services/trust.service.ts`) ### Trust & organizer (`server/services/trust.service.ts`)
- **Verified:** kolom `User.isVerified` (default false; set manual / seed / admin ke depan). - **Verified Organizer:** dihitung dari `OrganizerVerification.status === "APPROVED"` (lihat `lib/trust.ts → isVerifiedOrganizer()`). Tidak lagi pakai `User.isVerified`.
- **Trip leader:** heuristik `jumlah trip dibuat ≥ TRIP_LEADER_MIN_TRIPS` (`lib/trust.ts`). - **Trip leader:** heuristik `jumlah trip dibuat ≥ TRIP_LEADER_MIN_TRIPS` (`lib/trust.ts`).
- **Jumlah trip dibuat & rating organizer:** dihitung agregat dari DB (rating = rata-rata `TripReview` pada semua trip sang organizer). - **Jumlah trip dibuat & rating organizer:** dihitung agregat dari DB (rating = rata-rata `TripReview` pada semua trip sang organizer).
### 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.
- 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`).
- **Akses admin:** `lib/admin.ts → isAdminEmail()` membaca `ADMIN_EMAILS` (env, comma-separated).
--- ---
# 🧠 Final Principle # 🧠 Final Principle
+175
View File
@@ -0,0 +1,175 @@
# 🔒 Privacy Policy (Kebijakan Privasi) SeTrip
**Terakhir diperbarui: 2026-04-27**
SeTrip menghargai privasi Anda. Kebijakan Privasi ini menjelaskan bagaimana kami mengumpulkan, menggunakan, dan melindungi informasi Anda saat menggunakan platform SeTrip.
Dengan menggunakan SeTrip, Anda menyetujui praktik yang dijelaskan dalam Kebijakan Privasi ini.
---
# 1. Informasi yang Kami Kumpulkan
Kami dapat mengumpulkan informasi berikut:
## a. Informasi Akun
- Nama
- Email
- Nomor telepon
- Password (disimpan dalam bentuk terenkripsi)
## b. Informasi Profil
- Foto profil
- Deskripsi diri
- Riwayat trip
## c. Informasi Transaksi
- Data booking trip
- Status pembayaran
- Riwayat aktivitas
## d. Informasi Teknis
- Alamat IP
- Browser
- Perangkat yang digunakan
- Log aktivitas
---
# 2. Cara Kami Menggunakan Informasi
Kami menggunakan informasi Anda untuk:
- Membuat dan mengelola akun
- Menghubungkan pengguna dengan organizer
- Memproses booking dan aktivitas trip
- Meningkatkan layanan dan pengalaman pengguna
- Mengirim notifikasi terkait aktivitas
- Mencegah penipuan dan penyalahgunaan
---
# 3. Pembagian Informasi
Kami tidak menjual data pribadi Anda.
Namun, kami dapat membagikan informasi dalam kondisi berikut:
## a. Dengan Organizer
- Informasi dasar seperti nama dan kontak dapat dibagikan kepada organizer untuk keperluan trip
## b. Dengan Penyedia Layanan
- Untuk kebutuhan teknis (hosting, analytics, dll)
## c. Kewajiban Hukum
- Jika diminta oleh hukum atau otoritas berwenang
---
# 4. Keamanan Data
Kami berusaha melindungi data Anda dengan:
- Enkripsi password
- Pembatasan akses data
- Sistem keamanan standar industri
Namun, tidak ada sistem yang 100% aman.
---
# 5. Penyimpanan Data
Kami menyimpan data Anda selama:
- Akun Anda aktif
- Dibutuhkan untuk keperluan layanan
Data dapat dihapus atas permintaan pengguna, kecuali diwajibkan oleh hukum untuk disimpan.
---
# 6. Hak Pengguna
Anda memiliki hak untuk:
- Mengakses data pribadi Anda
- Memperbarui informasi
- Menghapus akun
- Menarik persetujuan
---
# 7. Cookie & Tracking
SeTrip dapat menggunakan:
- Cookie
- Teknologi pelacakan sederhana
Untuk:
- Menyimpan sesi login
- Meningkatkan pengalaman pengguna
---
# 8. Layanan Pihak Ketiga
SeTrip dapat menggunakan layanan pihak ketiga seperti:
- Hosting
- Analytics
- Payment gateway (di masa depan)
Kami tidak bertanggung jawab atas kebijakan privasi pihak ketiga tersebut.
---
# 9. Perlindungan terhadap Penipuan
Kami dapat menggunakan data untuk:
- Mendeteksi aktivitas mencurigakan
- Mencegah penipuan
- Melindungi pengguna lain
---
# 10. Perubahan Kebijakan Privasi
SeTrip dapat memperbarui Kebijakan Privasi ini sewaktu-waktu.
Pengguna disarankan untuk:
- Membaca secara berkala
- Memahami perubahan yang berlaku
---
# 11. Kontak
Jika Anda memiliki pertanyaan mengenai Kebijakan Privasi ini, silakan hubungi:
Email: [support@setrip.com](mailto:support@setrip.com)
---
# ✅ Persetujuan
Dengan menggunakan SeTrip, Anda menyatakan bahwa:
- Telah membaca
- Memahami
- Menyetujui Kebijakan Privasi ini
---
**End of Document**
+4 -1
View File
@@ -23,6 +23,8 @@ 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.
### 3. Peserta: mencari trip ### 3. Peserta: mencari trip
1. **Beranda** (`/`) dan **Open Trip** (`/trips`) menampilkan trip **OPEN** dengan tanggal berangkat yang masih relevan (`tripService.getOpenTrips`). 1. **Beranda** (`/`) dan **Open Trip** (`/trips`) menampilkan trip **OPEN** dengan tanggal berangkat yang masih relevan (`tripService.getOpenTrips`).
@@ -95,7 +97,8 @@ 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) | `User.isVerified`; agregat rating & jumlah trip dibuat dihitung dari data ulasan & trip | | 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. |
| Persetujuan T&C / Privasi | `User.acceptedTermsAndPrivacy` + `User.acceptedAt`, dicentang saat registrasi (link ke `/terms` & `/privacy`). |
## Menjalankan secara lokal ## Menjalankan secara lokal
+197
View File
@@ -0,0 +1,197 @@
# 📜 Terms & Conditions (Syarat & Ketentuan) SeTrip
**Terakhir diperbarui: 2026-04-27**
Selamat datang di SeTrip. Dengan mengakses atau menggunakan platform SeTrip, Anda menyetujui untuk terikat oleh Syarat & Ketentuan berikut.
---
# 1. Definisi
Dalam dokumen ini:
- **SeTrip**: Platform yang menghubungkan pengguna dengan penyelenggara trip.
- **Pengguna (User)**: Individu yang menggunakan aplikasi SeTrip.
- **Organizer (Penyelenggara)**: Pengguna yang membuat dan mengelola trip.
- **Trip**: Kegiatan perjalanan yang dibuat oleh organizer.
- **Platform**: Website atau aplikasi SeTrip.
---
# 2. Peran SeTrip
SeTrip bertindak sebagai **platform perantara** yang menghubungkan pengguna dan organizer.
SeTrip:
- **Bukan penyelenggara trip**
- **Tidak terlibat langsung dalam pelaksanaan perjalanan**
- **Tidak bertanggung jawab atas kegiatan selama trip berlangsung**
---
# 3. Penggunaan Platform
Dengan menggunakan SeTrip, Anda menyatakan bahwa:
- Berusia minimal 18 tahun atau memiliki izin dari wali
- Memberikan informasi yang benar dan akurat
- Tidak menggunakan platform untuk:
- Penipuan
- Aktivitas ilegal
- Penyebaran informasi palsu
---
# 4. Akun Pengguna
- Pengguna bertanggung jawab atas keamanan akun masing-masing
- Dilarang membagikan akun kepada pihak lain
- SeTrip berhak menangguhkan atau menghapus akun jika terjadi pelanggaran
---
# 5. Trip & Booking
- Organizer bertanggung jawab atas seluruh informasi trip
- Pengguna wajib membaca detail trip sebelum melakukan join
- Dengan melakukan join trip, pengguna menyetujui seluruh ketentuan trip yang dibuat oleh organizer
---
# 6. Pembayaran
- Pembayaran dilakukan sesuai metode yang tersedia di platform
- Dalam fase awal, pembayaran dapat dilakukan langsung kepada organizer
- SeTrip tidak menjamin keamanan transaksi yang dilakukan di luar platform
---
# 7. Pembatalan & Refund
- Kebijakan pembatalan ditentukan oleh organizer
- SeTrip tidak bertanggung jawab atas refund yang tidak diberikan oleh organizer
- Pengguna disarankan untuk memahami kebijakan sebelum melakukan pembayaran
---
# 8. Tanggung Jawab Organizer
Organizer wajib:
- Memberikan informasi trip yang jelas dan akurat
- Menjalankan trip sesuai deskripsi
- Bertanggung jawab atas keselamatan peserta selama trip
---
# 9. Risiko Perjalanan
Pengguna memahami bahwa aktivitas perjalanan, terutama kegiatan outdoor, memiliki risiko termasuk namun tidak terbatas pada:
- Cedera
- Kecelakaan
- Cuaca ekstrem
- Kondisi tak terduga lainnya
Dengan mengikuti trip, pengguna menyatakan:
> Mengikuti kegiatan secara sadar dan bertanggung jawab atas risiko pribadi
---
# 10. Batasan Tanggung Jawab
SeTrip tidak bertanggung jawab atas:
- Kerugian finansial
- Cedera atau kecelakaan
- Kegagalan pelaksanaan trip
- Tindakan organizer atau pengguna lain
---
# 11. Larangan Transaksi di Luar Platform
Pengguna disarankan untuk tidak melakukan transaksi di luar platform.
SeTrip tidak bertanggung jawab atas:
- Penipuan
- Kerugian
- Masalah lain yang terjadi akibat transaksi di luar sistem SeTrip
---
# 12. Sistem Review
- Pengguna dapat memberikan review setelah trip
- Review harus jujur dan tidak mengandung unsur fitnah
- SeTrip berhak menghapus review yang melanggar
---
# 13. Penangguhan & Penghentian Akun
SeTrip berhak untuk:
- Menangguhkan akun
- Menghapus akun
- Membatasi akses
Jika pengguna:
- Melanggar ketentuan
- Terindikasi melakukan penipuan
- Menyalahgunakan platform
---
# 14. Perubahan Layanan
SeTrip dapat:
- Mengubah fitur
- Menghentikan layanan
- Menambahkan kebijakan baru
Tanpa pemberitahuan sebelumnya
---
# 15. Perubahan Syarat & Ketentuan
SeTrip dapat memperbarui Syarat & Ketentuan ini kapan saja.
Pengguna disarankan untuk:
- Membaca secara berkala
- Memahami perubahan yang berlaku
---
# 16. Hukum yang Berlaku
Syarat & Ketentuan ini diatur oleh hukum yang berlaku di Republik Indonesia.
---
# 17. Kontak
Jika Anda memiliki pertanyaan, silakan hubungi:
Email: [support@setrip.com](mailto:support@setrip.com)
---
# ✅ Persetujuan
Dengan menggunakan SeTrip, Anda menyatakan bahwa:
- Telah membaca
- Memahami
- Menyetujui seluruh isi Syarat & Ketentuan ini
---
**End of Document**
+16
View File
@@ -0,0 +1,16 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Admin · Review Verifikasi Organizer",
description: "Halaman admin untuk meninjau pengajuan verifikasi organizer.",
alternates: { canonical: "/admin/verifications" },
robots: { index: false, follow: false },
};
export default function AdminVerificationsLayout({
children,
}: {
children: React.ReactNode;
}) {
return children;
}
+79
View File
@@ -0,0 +1,79 @@
import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { isAdminEmail } from "@/lib/admin";
import { organizerRepo } from "@/server/repositories/organizer.repo";
import { ReviewCard } from "@/features/organizer/components/review-card";
type Tab = "PENDING" | "APPROVED" | "REJECTED";
interface PageProps {
searchParams: Promise<{ tab?: string }>;
}
export default async function AdminVerificationsPage({ searchParams }: PageProps) {
const session = await getServerSession(authOptions);
if (!session?.user) redirect("/login?callbackUrl=/admin/verifications");
if (!isAdminEmail(session.user.email)) {
return (
<div className="mx-auto max-w-2xl px-4 py-12 text-center">
<p className="text-sm text-neutral-600">
Halaman ini hanya untuk admin SeTrip.
</p>
</div>
);
}
const params = await searchParams;
const tab: Tab =
params.tab === "APPROVED" || params.tab === "REJECTED" ? params.tab : "PENDING";
const items = await organizerRepo.listByStatus(tab);
const tabs: { key: Tab; label: string }[] = [
{ key: "PENDING", label: "Pending" },
{ key: "APPROVED", label: "Disetujui" },
{ key: "REJECTED", label: "Ditolak" },
];
return (
<div className="mx-auto max-w-4xl px-4 py-8 sm:py-12">
<header className="mb-6">
<h1 className="text-2xl font-bold text-neutral-900 sm:text-3xl">
Review Verifikasi Organizer
</h1>
<p className="mt-1 text-sm text-neutral-500">
Periksa data KTP, selfie, dan rekening sebelum menyetujui.
</p>
</header>
<div className="mb-6 flex gap-2">
{tabs.map((t) => (
<a
key={t.key}
href={`/admin/verifications?tab=${t.key}`}
className={`rounded-full px-4 py-1.5 text-sm font-semibold transition-colors ${
tab === t.key
? "bg-primary-600 text-white"
: "bg-neutral-100 text-neutral-600 hover:bg-neutral-200"
}`}
>
{t.label}
</a>
))}
</div>
{items.length === 0 ? (
<div className="rounded-2xl border border-dashed border-neutral-300 bg-white p-10 text-center">
<p className="text-sm text-neutral-500">Tidak ada data.</p>
</div>
) : (
<div className="space-y-4">
{items.map((v) => (
<ReviewCard key={v.id} verification={v} />
))}
</div>
)}
</div>
);
}
+54 -337
View File
@@ -1,44 +1,11 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import Link from "next/link"; import Link from "next/link";
import DatePicker from "react-datepicker"; import { getServerSession } from "next-auth";
import "react-datepicker/dist/react-datepicker.css"; import { authOptions } from "@/lib/auth";
import { createTripAction } from "@/features/trip/actions"; import { organizerService } from "@/server/services/organizer.service";
import { ImageUrlInput } from "@/features/trip/components/image-url-input"; import { CreateTripForm } from "@/features/trip/components/create-trip-form";
import { formatLocalCalendarYmd } from "@/lib/trip-dates";
const SAMPLE_MOUNTAINS = [ export default async function CreateTripPage() {
{ name: "Gunung Papandayan", location: "Garut, Jawa Barat" }, const session = await getServerSession(authOptions);
{ name: "Gunung Ciremai", location: "Kuningan, Jawa Barat" },
{ name: "Gunung Pangrango", location: "Bogor/Cianjur, Jawa Barat" },
{ name: "Gunung Gede", location: "Bogor/Cianjur, Jawa Barat" },
{ name: "Gunung Tangkuban Parahu", location: "Bandung, Jawa Barat" },
{ name: "Gunung Bukit Tunggul", location: "Bandung, Jawa Barat" },
{ name: "Gunung Malabar", location: "Bandung, Jawa Barat" },
{ name: "Gunung Guntur", location: "Garut, Jawa Barat" },
];
function formatRupiahInput(value: string): string {
const num = value.replace(/\D/g, "");
return num.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
function parseRupiahInput(value: string): string {
return value.replace(/\./g, "");
}
export default function CreateTripPage() {
const { data: session } = useSession();
const router = useRouter();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [priceDisplay, setPriceDisplay] = useState("");
if (!session?.user) { if (!session?.user) {
return ( return (
@@ -51,7 +18,7 @@ export default function CreateTripPage() {
Kamu harus login untuk membuat trip. Kamu harus login untuk membuat trip.
</p> </p>
<Link <Link
href="/login" href="/login?callbackUrl=/create-trip"
className="inline-block rounded-xl bg-primary-600 px-6 py-2.5 text-sm font-semibold text-white hover:bg-primary-700" className="inline-block rounded-xl bg-primary-600 px-6 py-2.5 text-sm font-semibold text-white hover:bg-primary-700"
> >
Login Login
@@ -61,69 +28,8 @@ export default function CreateTripPage() {
); );
} }
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { const verification = await organizerService.getStatusForUser(session.user.id);
e.preventDefault(); const isVerifiedOrganizer = verification?.status === "APPROVED";
setError("");
if (!startDate) {
setError("Tanggal berangkat harus diisi");
return;
}
setLoading(true);
const formData = new FormData(e.currentTarget);
// Tanggal dari picker → string tanggal untuk server action
formData.set("date", formatLocalCalendarYmd(startDate));
if (endDate) {
const startYmd = formatLocalCalendarYmd(startDate);
const endYmd = formatLocalCalendarYmd(endDate);
// Satu hari: tanggal pulang sama dengan berangkat → jangan kirim endDate (trip 1 hari)
if (endYmd !== startYmd) {
formData.set("endDate", endYmd);
}
}
// Set raw price number
formData.set("price", parseRupiahInput(priceDisplay));
const result = await createTripAction(formData);
setLoading(false);
if (result.error) {
setError(result.error);
} else if (result.tripId) {
router.push(`/trips/${result.tripId}`);
}
}
function handleMountainSelect(e: React.ChangeEvent<HTMLSelectElement>) {
const selected = SAMPLE_MOUNTAINS.find((m) => m.name === e.target.value);
if (selected) {
const form = e.target.form;
if (form) {
const mountainInput = form.elements.namedItem(
"mountain"
) as HTMLInputElement;
const locationInput = form.elements.namedItem(
"location"
) as HTMLInputElement;
mountainInput.value = selected.name;
locationInput.value = selected.location;
}
}
}
function handleDateChange(dates: [Date | null, Date | null]) {
const [start, end] = dates;
setStartDate(start);
setEndDate(end);
}
function handlePriceChange(e: React.ChangeEvent<HTMLInputElement>) {
const raw = e.target.value.replace(/\D/g, "");
setPriceDisplay(raw ? formatRupiahInput(raw) : "");
}
return ( return (
<div className="mx-auto max-w-2xl px-4 py-6 sm:py-8"> <div className="mx-auto max-w-2xl px-4 py-6 sm:py-8">
@@ -134,243 +40,54 @@ export default function CreateTripPage() {
</p> </p>
</div> </div>
<div className="rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm"> {!isVerifiedOrganizer && (
{error && ( <VerificationBanner status={verification?.status ?? null} />
<div className="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm font-medium text-red-600">
{error}
</div>
)} )}
<form onSubmit={handleSubmit} className="space-y-5"> <CreateTripForm isVerifiedOrganizer={isVerifiedOrganizer} />
{/* Mountain Quick Picker */} </div>
<div className="rounded-xl bg-primary-50 p-4"> );
<label className="mb-2 flex items-center gap-1.5 text-sm font-bold text-primary-800"> }
<span>🏔</span> Pilih Gunung Jawa Barat
</label> function VerificationBanner({
<select status,
onChange={handleMountainSelect} }: {
className="w-full rounded-lg border border-primary-200 bg-white px-4 py-2.5 text-sm text-neutral-800" status: "PENDING" | "APPROVED" | "REJECTED" | null;
defaultValue="" }) {
if (status === "PENDING") {
return (
<div className="mb-5 rounded-2xl border border-amber-200 bg-amber-50 p-4 sm:p-5">
<p className="text-sm font-bold text-amber-800">
Verifikasi sedang diproses
</p>
<p className="mt-1 text-sm text-neutral-700">
Pengajuan verifikasi-mu masih ditinjau admin. Sementara menunggu, kamu
masih bisa membuat <strong>trip gratis</strong> (harga 0).
</p>
</div>
);
}
const isRejected = status === "REJECTED";
return (
<div className="mb-5 rounded-2xl border border-amber-200 bg-amber-50 p-4 sm:p-5">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="flex-1">
<p className="text-sm font-bold text-amber-800">
{isRejected ? "Verifikasi ditolak" : "Belum terverifikasi"}
</p>
<p className="mt-1 text-sm text-neutral-700">
{isRejected
? "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."}
</p>
</div>
<Link
href="/verify"
className="inline-flex shrink-0 items-center justify-center rounded-xl bg-amber-600 px-4 py-2 text-sm font-bold text-white shadow-sm transition-colors hover:bg-amber-700 sm:px-5"
> >
<option value="" disabled> {isRejected ? "Ajukan Ulang" : "Verifikasi Sekarang"}
Pilih gunung... </Link>
</option>
{SAMPLE_MOUNTAINS.map((m) => (
<option key={m.name} value={m.name}>
{m.name} {m.location}
</option>
))}
</select>
</div>
<div>
<label htmlFor="title" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Judul Trip
</label>
<input
id="title"
name="title"
type="text"
required
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="contoh: Open Trip Papandayan Weekend"
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label htmlFor="mountain" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Nama Gunung
</label>
<input
id="mountain"
name="mountain"
type="text"
required
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Gunung Papandayan"
/>
</div>
<div>
<label htmlFor="location" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Lokasi
</label>
<input
id="location"
name="location"
type="text"
required
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Garut, Jawa Barat"
/>
</div>
</div>
<div>
<label htmlFor="description" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Deskripsi
</label>
<textarea
id="description"
name="description"
rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Ringkasan trip, vibe, level kesulitan..."
/>
</div>
<div>
<label htmlFor="meetingPoint" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Meeting point
</label>
<input
id="meetingPoint"
name="meetingPoint"
type="text"
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="contoh: Alfamart Cicaheum, 05:00 WIB"
/>
</div>
<div>
<label htmlFor="itinerary" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Itinerary
</label>
<textarea
id="itinerary"
name="itinerary"
rows={5}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder={"Hari 1: …\nHari 2: …"}
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label htmlFor="whatsIncluded" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Termasuk
</label>
<textarea
id="whatsIncluded"
name="whatsIncluded"
rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Transport, konsumsi, tenda, …"
/>
</div>
<div>
<label htmlFor="whatsExcluded" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Tidak termasuk
</label>
<textarea
id="whatsExcluded"
name="whatsExcluded"
rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Tiket masuk TN, sleeping bag, …"
/>
</div>
</div>
<ImageUrlInput />
{/* Date Range & Participants & Price */}
<div className="grid gap-4 sm:grid-cols-2">
{/* Date Range Picker */}
<div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Tanggal berangkat pulang
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 z-10 -translate-y-1/2 text-neutral-400">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-4 w-4"
>
<path
fillRule="evenodd"
d="M5.75 2a.75.75 0 01.75.75V4h7V2.75a.75.75 0 011.5 0V4h.25A2.75 2.75 0 0118 6.75v8.5A2.75 2.75 0 0115.25 18H4.75A2.75 2.75 0 012 15.25v-8.5A2.75 2.75 0 014.75 4H5V2.75A.75.75 0 015.75 2zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75z"
clipRule="evenodd"
/>
</svg>
</span>
<DatePicker
selectsRange
startDate={startDate}
endDate={endDate}
onChange={handleDateChange}
minDate={new Date()}
placeholderText="Pilih tanggal..."
dateFormat="dd MMM yyyy"
isClearable
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-9 pr-3 text-sm text-neutral-800 placeholder:text-neutral-400 focus:border-primary-500 focus:bg-white"
/>
</div>
</div>
{/* Max Participants */}
<div>
<label htmlFor="maxParticipants" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Maks Peserta
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-4 w-4"
>
<path d="M7 8a3 3 0 100-6 3 3 0 000 6zM14.5 9a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM1.615 16.428a1.224 1.224 0 01-.569-1.175 6.002 6.002 0 0111.908 0c.058.467-.172.92-.57 1.174A9.953 9.953 0 017 18a9.953 9.953 0 01-5.385-1.572zM14.5 16h-.106c.07-.297.088-.611.048-.933a7.47 7.47 0 00-1.588-3.755 4.502 4.502 0 015.874 2.636.818.818 0 01-.36.98A7.465 7.465 0 0114.5 16z" />
</svg>
</span>
<input
id="maxParticipants"
name="maxParticipants"
type="number"
required
min={1}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-9 pr-4 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="10"
/>
</div>
</div>
</div>
{/* Price with Rp format */}
<div>
<label htmlFor="priceDisplay" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Harga per Orang
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-sm font-semibold text-neutral-500">
Rp
</span>
<input
id="priceDisplay"
type="text"
inputMode="numeric"
required
value={priceDisplay}
onChange={handlePriceChange}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-10 pr-4 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="150.000"
/>
{/* Hidden input for form submission */}
<input type="hidden" name="price" value={parseRupiahInput(priceDisplay)} />
</div>
</div>
<button
type="submit"
disabled={loading}
className="w-full rounded-xl bg-primary-600 py-3 text-sm font-bold text-white shadow-lg shadow-primary-600/20 transition-colors hover:bg-primary-700 disabled:opacity-50"
>
{loading ? "Membuat Trip..." : "Buat Trip"}
</button>
</form>
</div> </div>
</div> </div>
); );
+5
View File
@@ -22,6 +22,11 @@ export * from './enums';
* *
*/ */
export type User = Prisma.UserModel export type User = Prisma.UserModel
/**
* Model OrganizerVerification
*
*/
export type OrganizerVerification = Prisma.OrganizerVerificationModel
/** /**
* Model Trip * Model Trip
* *
+5
View File
@@ -46,6 +46,11 @@ export { Prisma }
* *
*/ */
export type User = Prisma.UserModel export type User = Prisma.UserModel
/**
* Model OrganizerVerification
*
*/
export type OrganizerVerification = Prisma.OrganizerVerificationModel
/** /**
* Model Trip * Model Trip
* *
+80 -46
View File
@@ -49,6 +49,17 @@ export type BoolFilter<$PrismaModel = never> = {
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
} }
export type DateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
}
export type DateTimeFilter<$PrismaModel = never> = { export type DateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
@@ -109,6 +120,20 @@ export type BoolWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedBoolFilter<$PrismaModel> _max?: Prisma.NestedBoolFilter<$PrismaModel>
} }
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
}
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = { export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
@@ -123,15 +148,21 @@ export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedDateTimeFilter<$PrismaModel> _max?: Prisma.NestedDateTimeFilter<$PrismaModel>
} }
export type DateTimeNullableFilter<$PrismaModel = never> = { export type EnumVerificationStatusFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null equals?: $Enums.VerificationStatus | Prisma.EnumVerificationStatusFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null in?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null notIn?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> not?: Prisma.NestedEnumVerificationStatusFilter<$PrismaModel> | $Enums.VerificationStatus
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> }
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> export type EnumVerificationStatusWithAggregatesFilter<$PrismaModel = never> = {
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null equals?: $Enums.VerificationStatus | Prisma.EnumVerificationStatusFieldRefInput<$PrismaModel>
in?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumVerificationStatusWithAggregatesFilter<$PrismaModel> | $Enums.VerificationStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumVerificationStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumVerificationStatusFilter<$PrismaModel>
} }
export type IntFilter<$PrismaModel = never> = { export type IntFilter<$PrismaModel = never> = {
@@ -152,20 +183,6 @@ export type EnumTripStatusFilter<$PrismaModel = never> = {
not?: Prisma.NestedEnumTripStatusFilter<$PrismaModel> | $Enums.TripStatus not?: Prisma.NestedEnumTripStatusFilter<$PrismaModel> | $Enums.TripStatus
} }
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
}
export type IntWithAggregatesFilter<$PrismaModel = never> = { export type IntWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
@@ -242,6 +259,17 @@ export type NestedBoolFilter<$PrismaModel = never> = {
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
} }
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
}
export type NestedDateTimeFilter<$PrismaModel = never> = { export type NestedDateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
@@ -317,6 +345,20 @@ export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedBoolFilter<$PrismaModel> _max?: Prisma.NestedBoolFilter<$PrismaModel>
} }
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
}
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = { export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
@@ -331,15 +373,21 @@ export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedDateTimeFilter<$PrismaModel> _max?: Prisma.NestedDateTimeFilter<$PrismaModel>
} }
export type NestedDateTimeNullableFilter<$PrismaModel = never> = { export type NestedEnumVerificationStatusFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null equals?: $Enums.VerificationStatus | Prisma.EnumVerificationStatusFieldRefInput<$PrismaModel>
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null in?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null notIn?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> not?: Prisma.NestedEnumVerificationStatusFilter<$PrismaModel> | $Enums.VerificationStatus
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> }
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> export type NestedEnumVerificationStatusWithAggregatesFilter<$PrismaModel = never> = {
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null equals?: $Enums.VerificationStatus | Prisma.EnumVerificationStatusFieldRefInput<$PrismaModel>
in?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumVerificationStatusWithAggregatesFilter<$PrismaModel> | $Enums.VerificationStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumVerificationStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumVerificationStatusFilter<$PrismaModel>
} }
export type NestedEnumTripStatusFilter<$PrismaModel = never> = { export type NestedEnumTripStatusFilter<$PrismaModel = never> = {
@@ -349,20 +397,6 @@ export type NestedEnumTripStatusFilter<$PrismaModel = never> = {
not?: Prisma.NestedEnumTripStatusFilter<$PrismaModel> | $Enums.TripStatus not?: Prisma.NestedEnumTripStatusFilter<$PrismaModel> | $Enums.TripStatus
} }
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
}
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = { export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
+9
View File
@@ -9,6 +9,15 @@
* 🟢 You can import this file directly. * 🟢 You can import this file directly.
*/ */
export const VerificationStatus = {
PENDING: 'PENDING',
APPROVED: 'APPROVED',
REJECTED: 'REJECTED'
} as const
export type VerificationStatus = (typeof VerificationStatus)[keyof typeof VerificationStatus]
export const TripStatus = { export const TripStatus = {
OPEN: 'OPEN', OPEN: 'OPEN',
FULL: 'FULL', FULL: 'FULL',
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',
OrganizerVerification: 'OrganizerVerification',
Trip: 'Trip', Trip: 'Trip',
TripReview: 'TripReview', TripReview: 'TripReview',
TripImage: 'TripImage', TripImage: 'TripImage',
@@ -404,7 +405,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
omit: GlobalOmitOptions omit: GlobalOmitOptions
} }
meta: { meta: {
modelProps: "user" | "trip" | "tripReview" | "tripImage" | "tripParticipant" modelProps: "user" | "organizerVerification" | "trip" | "tripReview" | "tripImage" | "tripParticipant"
txIsolationLevel: TransactionIsolationLevel txIsolationLevel: TransactionIsolationLevel
} }
model: { model: {
@@ -482,6 +483,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
} }
} }
} }
OrganizerVerification: {
payload: Prisma.$OrganizerVerificationPayload<ExtArgs>
fields: Prisma.OrganizerVerificationFieldRefs
operations: {
findUnique: {
args: Prisma.OrganizerVerificationFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload> | null
}
findUniqueOrThrow: {
args: Prisma.OrganizerVerificationFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>
}
findFirst: {
args: Prisma.OrganizerVerificationFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload> | null
}
findFirstOrThrow: {
args: Prisma.OrganizerVerificationFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>
}
findMany: {
args: Prisma.OrganizerVerificationFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>[]
}
create: {
args: Prisma.OrganizerVerificationCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>
}
createMany: {
args: Prisma.OrganizerVerificationCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.OrganizerVerificationCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>[]
}
delete: {
args: Prisma.OrganizerVerificationDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>
}
update: {
args: Prisma.OrganizerVerificationUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>
}
deleteMany: {
args: Prisma.OrganizerVerificationDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.OrganizerVerificationUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.OrganizerVerificationUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>[]
}
upsert: {
args: Prisma.OrganizerVerificationUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$OrganizerVerificationPayload>
}
aggregate: {
args: Prisma.OrganizerVerificationAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateOrganizerVerification>
}
groupBy: {
args: Prisma.OrganizerVerificationGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.OrganizerVerificationGroupByOutputType>[]
}
count: {
args: Prisma.OrganizerVerificationCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.OrganizerVerificationCountAggregateOutputType> | number
}
}
}
Trip: { Trip: {
payload: Prisma.$TripPayload<ExtArgs> payload: Prisma.$TripPayload<ExtArgs>
fields: Prisma.TripFieldRefs fields: Prisma.TripFieldRefs
@@ -823,7 +898,8 @@ export const UserScalarFieldEnum = {
email: 'email', email: 'email',
password: 'password', password: 'password',
image: 'image', image: 'image',
isVerified: 'isVerified', acceptedTermsAndPrivacy: 'acceptedTermsAndPrivacy',
acceptedAt: 'acceptedAt',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -831,6 +907,30 @@ export const UserScalarFieldEnum = {
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum] export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
export const OrganizerVerificationScalarFieldEnum = {
id: 'id',
userId: 'userId',
fullName: 'fullName',
nik: 'nik',
birthDate: 'birthDate',
address: 'address',
ktpImageUrl: 'ktpImageUrl',
selfieUrl: 'selfieUrl',
bankName: 'bankName',
bankAccountNumber: 'bankAccountNumber',
bankAccountName: 'bankAccountName',
status: 'status',
rejectionReason: 'rejectionReason',
reviewedAt: 'reviewedAt',
reviewedById: 'reviewedById',
verifiedAt: 'verifiedAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type OrganizerVerificationScalarFieldEnum = (typeof OrganizerVerificationScalarFieldEnum)[keyof typeof OrganizerVerificationScalarFieldEnum]
export const TripScalarFieldEnum = { export const TripScalarFieldEnum = {
id: 'id', id: 'id',
title: 'title', title: 'title',
@@ -956,6 +1056,20 @@ export type ListDateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaM
/**
* Reference to a field of type 'VerificationStatus'
*/
export type EnumVerificationStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'VerificationStatus'>
/**
* Reference to a field of type 'VerificationStatus[]'
*/
export type ListEnumVerificationStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'VerificationStatus[]'>
/** /**
* Reference to a field of type 'Int' * Reference to a field of type 'Int'
*/ */
@@ -1107,6 +1221,7 @@ export type PrismaClientOptions = ({
} }
export type GlobalOmitConfig = { export type GlobalOmitConfig = {
user?: Prisma.UserOmit user?: Prisma.UserOmit
organizerVerification?: Prisma.OrganizerVerificationOmit
trip?: Prisma.TripOmit trip?: Prisma.TripOmit
tripReview?: Prisma.TripReviewOmit tripReview?: Prisma.TripReviewOmit
tripImage?: Prisma.TripImageOmit tripImage?: Prisma.TripImageOmit
@@ -52,6 +52,7 @@ export const AnyNull = runtime.AnyNull
export const ModelName = { export const ModelName = {
User: 'User', User: 'User',
OrganizerVerification: 'OrganizerVerification',
Trip: 'Trip', Trip: 'Trip',
TripReview: 'TripReview', TripReview: 'TripReview',
TripImage: 'TripImage', TripImage: 'TripImage',
@@ -80,7 +81,8 @@ export const UserScalarFieldEnum = {
email: 'email', email: 'email',
password: 'password', password: 'password',
image: 'image', image: 'image',
isVerified: 'isVerified', acceptedTermsAndPrivacy: 'acceptedTermsAndPrivacy',
acceptedAt: 'acceptedAt',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -88,6 +90,30 @@ export const UserScalarFieldEnum = {
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum] export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
export const OrganizerVerificationScalarFieldEnum = {
id: 'id',
userId: 'userId',
fullName: 'fullName',
nik: 'nik',
birthDate: 'birthDate',
address: 'address',
ktpImageUrl: 'ktpImageUrl',
selfieUrl: 'selfieUrl',
bankName: 'bankName',
bankAccountNumber: 'bankAccountNumber',
bankAccountName: 'bankAccountName',
status: 'status',
rejectionReason: 'rejectionReason',
reviewedAt: 'reviewedAt',
reviewedById: 'reviewedById',
verifiedAt: 'verifiedAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type OrganizerVerificationScalarFieldEnum = (typeof OrganizerVerificationScalarFieldEnum)[keyof typeof OrganizerVerificationScalarFieldEnum]
export const TripScalarFieldEnum = { export const TripScalarFieldEnum = {
id: 'id', id: 'id',
title: 'title', title: 'title',
+1
View File
@@ -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/OrganizerVerification'
export type * from './models/Trip' export type * from './models/Trip'
export type * from './models/TripReview' export type * from './models/TripReview'
export type * from './models/TripImage' export type * from './models/TripImage'
File diff suppressed because it is too large Load Diff
-4
View File
@@ -699,10 +699,6 @@ export type TripUncheckedUpdateManyWithoutOrganizerNestedInput = {
deleteMany?: Prisma.TripScalarWhereInput | Prisma.TripScalarWhereInput[] deleteMany?: Prisma.TripScalarWhereInput | Prisma.TripScalarWhereInput[]
} }
export type NullableDateTimeFieldUpdateOperationsInput = {
set?: Date | string | null
}
export type IntFieldUpdateOperationsInput = { export type IntFieldUpdateOperationsInput = {
set?: number set?: number
increment?: number increment?: number
+382 -42
View File
@@ -30,7 +30,8 @@ export type UserMinAggregateOutputType = {
email: string | null email: string | null
password: string | null password: string | null
image: string | null image: string | null
isVerified: boolean | null acceptedTermsAndPrivacy: boolean | null
acceptedAt: Date | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -41,7 +42,8 @@ export type UserMaxAggregateOutputType = {
email: string | null email: string | null
password: string | null password: string | null
image: string | null image: string | null
isVerified: boolean | null acceptedTermsAndPrivacy: boolean | null
acceptedAt: Date | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -52,7 +54,8 @@ export type UserCountAggregateOutputType = {
email: number email: number
password: number password: number
image: number image: number
isVerified: number acceptedTermsAndPrivacy: number
acceptedAt: number
createdAt: number createdAt: number
updatedAt: number updatedAt: number
_all: number _all: number
@@ -65,7 +68,8 @@ export type UserMinAggregateInputType = {
email?: true email?: true
password?: true password?: true
image?: true image?: true
isVerified?: true acceptedTermsAndPrivacy?: true
acceptedAt?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -76,7 +80,8 @@ export type UserMaxAggregateInputType = {
email?: true email?: true
password?: true password?: true
image?: true image?: true
isVerified?: true acceptedTermsAndPrivacy?: true
acceptedAt?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -87,7 +92,8 @@ export type UserCountAggregateInputType = {
email?: true email?: true
password?: true password?: true
image?: true image?: true
isVerified?: true acceptedTermsAndPrivacy?: true
acceptedAt?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
_all?: true _all?: true
@@ -171,7 +177,8 @@ export type UserGroupByOutputType = {
email: string email: string
password: string password: string
image: string | null image: string | null
isVerified: boolean acceptedTermsAndPrivacy: boolean
acceptedAt: Date | null
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
_count: UserCountAggregateOutputType | null _count: UserCountAggregateOutputType | null
@@ -203,12 +210,15 @@ export type UserWhereInput = {
email?: Prisma.StringFilter<"User"> | string email?: Prisma.StringFilter<"User"> | string
password?: Prisma.StringFilter<"User"> | string password?: Prisma.StringFilter<"User"> | string
image?: Prisma.StringNullableFilter<"User"> | string | null image?: Prisma.StringNullableFilter<"User"> | string | null
isVerified?: Prisma.BoolFilter<"User"> | boolean acceptedTermsAndPrivacy?: Prisma.BoolFilter<"User"> | boolean
acceptedAt?: Prisma.DateTimeNullableFilter<"User"> | Date | string | null
createdAt?: Prisma.DateTimeFilter<"User"> | Date | string createdAt?: Prisma.DateTimeFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
trips?: Prisma.TripListRelationFilter trips?: Prisma.TripListRelationFilter
participations?: Prisma.TripParticipantListRelationFilter participations?: Prisma.TripParticipantListRelationFilter
tripReviews?: Prisma.TripReviewListRelationFilter tripReviews?: Prisma.TripReviewListRelationFilter
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
} }
export type UserOrderByWithRelationInput = { export type UserOrderByWithRelationInput = {
@@ -217,12 +227,15 @@ export type UserOrderByWithRelationInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrderInput | Prisma.SortOrder image?: Prisma.SortOrderInput | Prisma.SortOrder
isVerified?: Prisma.SortOrder acceptedTermsAndPrivacy?: Prisma.SortOrder
acceptedAt?: Prisma.SortOrderInput | Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
trips?: Prisma.TripOrderByRelationAggregateInput trips?: Prisma.TripOrderByRelationAggregateInput
participations?: Prisma.TripParticipantOrderByRelationAggregateInput participations?: Prisma.TripParticipantOrderByRelationAggregateInput
tripReviews?: Prisma.TripReviewOrderByRelationAggregateInput tripReviews?: Prisma.TripReviewOrderByRelationAggregateInput
organizerVerification?: Prisma.OrganizerVerificationOrderByWithRelationInput
reviewedVerifications?: Prisma.OrganizerVerificationOrderByRelationAggregateInput
} }
export type UserWhereUniqueInput = Prisma.AtLeast<{ export type UserWhereUniqueInput = Prisma.AtLeast<{
@@ -234,12 +247,15 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
name?: Prisma.StringFilter<"User"> | string name?: Prisma.StringFilter<"User"> | string
password?: Prisma.StringFilter<"User"> | string password?: Prisma.StringFilter<"User"> | string
image?: Prisma.StringNullableFilter<"User"> | string | null image?: Prisma.StringNullableFilter<"User"> | string | null
isVerified?: Prisma.BoolFilter<"User"> | boolean acceptedTermsAndPrivacy?: Prisma.BoolFilter<"User"> | boolean
acceptedAt?: Prisma.DateTimeNullableFilter<"User"> | Date | string | null
createdAt?: Prisma.DateTimeFilter<"User"> | Date | string createdAt?: Prisma.DateTimeFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
trips?: Prisma.TripListRelationFilter trips?: Prisma.TripListRelationFilter
participations?: Prisma.TripParticipantListRelationFilter participations?: Prisma.TripParticipantListRelationFilter
tripReviews?: Prisma.TripReviewListRelationFilter tripReviews?: Prisma.TripReviewListRelationFilter
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
}, "id" | "email"> }, "id" | "email">
export type UserOrderByWithAggregationInput = { export type UserOrderByWithAggregationInput = {
@@ -248,7 +264,8 @@ export type UserOrderByWithAggregationInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrderInput | Prisma.SortOrder image?: Prisma.SortOrderInput | Prisma.SortOrder
isVerified?: Prisma.SortOrder acceptedTermsAndPrivacy?: Prisma.SortOrder
acceptedAt?: Prisma.SortOrderInput | Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
_count?: Prisma.UserCountOrderByAggregateInput _count?: Prisma.UserCountOrderByAggregateInput
@@ -265,7 +282,8 @@ export type UserScalarWhereWithAggregatesInput = {
email?: Prisma.StringWithAggregatesFilter<"User"> | string email?: Prisma.StringWithAggregatesFilter<"User"> | string
password?: Prisma.StringWithAggregatesFilter<"User"> | string password?: Prisma.StringWithAggregatesFilter<"User"> | string
image?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null image?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null
isVerified?: Prisma.BoolWithAggregatesFilter<"User"> | boolean acceptedTermsAndPrivacy?: Prisma.BoolWithAggregatesFilter<"User"> | boolean
acceptedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"User"> | Date | string | null
createdAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string createdAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
} }
@@ -276,12 +294,15 @@ export type UserCreateInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
} }
export type UserUncheckedCreateInput = { export type UserUncheckedCreateInput = {
@@ -290,12 +311,15 @@ export type UserUncheckedCreateInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
} }
export type UserUpdateInput = { export type UserUpdateInput = {
@@ -304,12 +328,15 @@ export type UserUpdateInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
} }
export type UserUncheckedUpdateInput = { export type UserUncheckedUpdateInput = {
@@ -318,12 +345,15 @@ export type UserUncheckedUpdateInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
} }
export type UserCreateManyInput = { export type UserCreateManyInput = {
@@ -332,7 +362,8 @@ export type UserCreateManyInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -343,7 +374,8 @@ export type UserUpdateManyMutationInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -354,7 +386,8 @@ export type UserUncheckedUpdateManyInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -365,7 +398,8 @@ export type UserCountOrderByAggregateInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrder image?: Prisma.SortOrder
isVerified?: Prisma.SortOrder acceptedTermsAndPrivacy?: Prisma.SortOrder
acceptedAt?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -376,7 +410,8 @@ export type UserMaxOrderByAggregateInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrder image?: Prisma.SortOrder
isVerified?: Prisma.SortOrder acceptedTermsAndPrivacy?: Prisma.SortOrder
acceptedAt?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -387,7 +422,8 @@ export type UserMinOrderByAggregateInput = {
email?: Prisma.SortOrder email?: Prisma.SortOrder
password?: Prisma.SortOrder password?: Prisma.SortOrder
image?: Prisma.SortOrder image?: Prisma.SortOrder
isVerified?: Prisma.SortOrder acceptedTermsAndPrivacy?: Prisma.SortOrder
acceptedAt?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -397,6 +433,11 @@ export type UserScalarRelationFilter = {
isNot?: Prisma.UserWhereInput isNot?: Prisma.UserWhereInput
} }
export type UserNullableScalarRelationFilter = {
is?: Prisma.UserWhereInput | null
isNot?: Prisma.UserWhereInput | null
}
export type StringFieldUpdateOperationsInput = { export type StringFieldUpdateOperationsInput = {
set?: string set?: string
} }
@@ -409,10 +450,44 @@ export type BoolFieldUpdateOperationsInput = {
set?: boolean set?: boolean
} }
export type NullableDateTimeFieldUpdateOperationsInput = {
set?: Date | string | null
}
export type DateTimeFieldUpdateOperationsInput = { export type DateTimeFieldUpdateOperationsInput = {
set?: Date | string set?: Date | string
} }
export type UserCreateNestedOneWithoutOrganizerVerificationInput = {
create?: Prisma.XOR<Prisma.UserCreateWithoutOrganizerVerificationInput, Prisma.UserUncheckedCreateWithoutOrganizerVerificationInput>
connectOrCreate?: Prisma.UserCreateOrConnectWithoutOrganizerVerificationInput
connect?: Prisma.UserWhereUniqueInput
}
export type UserCreateNestedOneWithoutReviewedVerificationsInput = {
create?: Prisma.XOR<Prisma.UserCreateWithoutReviewedVerificationsInput, Prisma.UserUncheckedCreateWithoutReviewedVerificationsInput>
connectOrCreate?: Prisma.UserCreateOrConnectWithoutReviewedVerificationsInput
connect?: Prisma.UserWhereUniqueInput
}
export type UserUpdateOneRequiredWithoutOrganizerVerificationNestedInput = {
create?: Prisma.XOR<Prisma.UserCreateWithoutOrganizerVerificationInput, Prisma.UserUncheckedCreateWithoutOrganizerVerificationInput>
connectOrCreate?: Prisma.UserCreateOrConnectWithoutOrganizerVerificationInput
upsert?: Prisma.UserUpsertWithoutOrganizerVerificationInput
connect?: Prisma.UserWhereUniqueInput
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutOrganizerVerificationInput, Prisma.UserUpdateWithoutOrganizerVerificationInput>, Prisma.UserUncheckedUpdateWithoutOrganizerVerificationInput>
}
export type UserUpdateOneWithoutReviewedVerificationsNestedInput = {
create?: Prisma.XOR<Prisma.UserCreateWithoutReviewedVerificationsInput, Prisma.UserUncheckedCreateWithoutReviewedVerificationsInput>
connectOrCreate?: Prisma.UserCreateOrConnectWithoutReviewedVerificationsInput
upsert?: Prisma.UserUpsertWithoutReviewedVerificationsInput
disconnect?: Prisma.UserWhereInput | boolean
delete?: Prisma.UserWhereInput | boolean
connect?: Prisma.UserWhereUniqueInput
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutReviewedVerificationsInput, Prisma.UserUpdateWithoutReviewedVerificationsInput>, Prisma.UserUncheckedUpdateWithoutReviewedVerificationsInput>
}
export type UserCreateNestedOneWithoutTripsInput = { export type UserCreateNestedOneWithoutTripsInput = {
create?: Prisma.XOR<Prisma.UserCreateWithoutTripsInput, Prisma.UserUncheckedCreateWithoutTripsInput> create?: Prisma.XOR<Prisma.UserCreateWithoutTripsInput, Prisma.UserUncheckedCreateWithoutTripsInput>
connectOrCreate?: Prisma.UserCreateOrConnectWithoutTripsInput connectOrCreate?: Prisma.UserCreateOrConnectWithoutTripsInput
@@ -455,17 +530,180 @@ 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 UserCreateWithoutOrganizerVerificationInput = {
id?: string
name: string
email: string
password: string
image?: string | null
acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
}
export type UserUncheckedCreateWithoutOrganizerVerificationInput = {
id?: string
name: string
email: string
password: string
image?: string | null
acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
}
export type UserCreateOrConnectWithoutOrganizerVerificationInput = {
where: Prisma.UserWhereUniqueInput
create: Prisma.XOR<Prisma.UserCreateWithoutOrganizerVerificationInput, Prisma.UserUncheckedCreateWithoutOrganizerVerificationInput>
}
export type UserCreateWithoutReviewedVerificationsInput = {
id?: string
name: string
email: string
password: string
image?: string | null
acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
}
export type UserUncheckedCreateWithoutReviewedVerificationsInput = {
id?: string
name: string
email: string
password: string
image?: string | null
acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
}
export type UserCreateOrConnectWithoutReviewedVerificationsInput = {
where: Prisma.UserWhereUniqueInput
create: Prisma.XOR<Prisma.UserCreateWithoutReviewedVerificationsInput, Prisma.UserUncheckedCreateWithoutReviewedVerificationsInput>
}
export type UserUpsertWithoutOrganizerVerificationInput = {
update: Prisma.XOR<Prisma.UserUpdateWithoutOrganizerVerificationInput, Prisma.UserUncheckedUpdateWithoutOrganizerVerificationInput>
create: Prisma.XOR<Prisma.UserCreateWithoutOrganizerVerificationInput, Prisma.UserUncheckedCreateWithoutOrganizerVerificationInput>
where?: Prisma.UserWhereInput
}
export type UserUpdateToOneWithWhereWithoutOrganizerVerificationInput = {
where?: Prisma.UserWhereInput
data: Prisma.XOR<Prisma.UserUpdateWithoutOrganizerVerificationInput, Prisma.UserUncheckedUpdateWithoutOrganizerVerificationInput>
}
export type UserUpdateWithoutOrganizerVerificationInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string
email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
}
export type UserUncheckedUpdateWithoutOrganizerVerificationInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string
email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
}
export type UserUpsertWithoutReviewedVerificationsInput = {
update: Prisma.XOR<Prisma.UserUpdateWithoutReviewedVerificationsInput, Prisma.UserUncheckedUpdateWithoutReviewedVerificationsInput>
create: Prisma.XOR<Prisma.UserCreateWithoutReviewedVerificationsInput, Prisma.UserUncheckedCreateWithoutReviewedVerificationsInput>
where?: Prisma.UserWhereInput
}
export type UserUpdateToOneWithWhereWithoutReviewedVerificationsInput = {
where?: Prisma.UserWhereInput
data: Prisma.XOR<Prisma.UserUpdateWithoutReviewedVerificationsInput, Prisma.UserUncheckedUpdateWithoutReviewedVerificationsInput>
}
export type UserUpdateWithoutReviewedVerificationsInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string
email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
}
export type UserUncheckedUpdateWithoutReviewedVerificationsInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string
email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
}
export type UserCreateWithoutTripsInput = { export type UserCreateWithoutTripsInput = {
id?: string id?: string
name: string name: string
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
} }
export type UserUncheckedCreateWithoutTripsInput = { export type UserUncheckedCreateWithoutTripsInput = {
@@ -474,11 +712,14 @@ export type UserUncheckedCreateWithoutTripsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
} }
export type UserCreateOrConnectWithoutTripsInput = { export type UserCreateOrConnectWithoutTripsInput = {
@@ -503,11 +744,14 @@ export type UserUpdateWithoutTripsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
} }
export type UserUncheckedUpdateWithoutTripsInput = { export type UserUncheckedUpdateWithoutTripsInput = {
@@ -516,11 +760,14 @@ export type UserUncheckedUpdateWithoutTripsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
} }
export type UserCreateWithoutTripReviewsInput = { export type UserCreateWithoutTripReviewsInput = {
@@ -529,11 +776,14 @@ export type UserCreateWithoutTripReviewsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
} }
export type UserUncheckedCreateWithoutTripReviewsInput = { export type UserUncheckedCreateWithoutTripReviewsInput = {
@@ -542,11 +792,14 @@ export type UserUncheckedCreateWithoutTripReviewsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
} }
export type UserCreateOrConnectWithoutTripReviewsInput = { export type UserCreateOrConnectWithoutTripReviewsInput = {
@@ -571,11 +824,14 @@ export type UserUpdateWithoutTripReviewsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
} }
export type UserUncheckedUpdateWithoutTripReviewsInput = { export type UserUncheckedUpdateWithoutTripReviewsInput = {
@@ -584,11 +840,14 @@ export type UserUncheckedUpdateWithoutTripReviewsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
} }
export type UserCreateWithoutParticipationsInput = { export type UserCreateWithoutParticipationsInput = {
@@ -597,11 +856,14 @@ export type UserCreateWithoutParticipationsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
} }
export type UserUncheckedCreateWithoutParticipationsInput = { export type UserUncheckedCreateWithoutParticipationsInput = {
@@ -610,11 +872,14 @@ export type UserUncheckedCreateWithoutParticipationsInput = {
email: string email: string
password: string password: string
image?: string | null image?: string | null
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
} }
export type UserCreateOrConnectWithoutParticipationsInput = { export type UserCreateOrConnectWithoutParticipationsInput = {
@@ -639,11 +904,14 @@ export type UserUpdateWithoutParticipationsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
} }
export type UserUncheckedUpdateWithoutParticipationsInput = { export type UserUncheckedUpdateWithoutParticipationsInput = {
@@ -652,11 +920,14 @@ export type UserUncheckedUpdateWithoutParticipationsInput = {
email?: Prisma.StringFieldUpdateOperationsInput | string email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.StringFieldUpdateOperationsInput | string password?: Prisma.StringFieldUpdateOperationsInput | string
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
} }
@@ -668,12 +939,14 @@ export type UserCountOutputType = {
trips: number trips: number
participations: number participations: number
tripReviews: number tripReviews: number
reviewedVerifications: number
} }
export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
trips?: boolean | UserCountOutputTypeCountTripsArgs trips?: boolean | UserCountOutputTypeCountTripsArgs
participations?: boolean | UserCountOutputTypeCountParticipationsArgs participations?: boolean | UserCountOutputTypeCountParticipationsArgs
tripReviews?: boolean | UserCountOutputTypeCountTripReviewsArgs tripReviews?: boolean | UserCountOutputTypeCountTripReviewsArgs
reviewedVerifications?: boolean | UserCountOutputTypeCountReviewedVerificationsArgs
} }
/** /**
@@ -707,6 +980,13 @@ export type UserCountOutputTypeCountTripReviewsArgs<ExtArgs extends runtime.Type
where?: Prisma.TripReviewWhereInput where?: Prisma.TripReviewWhereInput
} }
/**
* UserCountOutputType without action
*/
export type UserCountOutputTypeCountReviewedVerificationsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
where?: Prisma.OrganizerVerificationWhereInput
}
export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
id?: boolean id?: boolean
@@ -714,12 +994,15 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
email?: boolean email?: boolean
password?: boolean password?: boolean
image?: boolean image?: boolean
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
trips?: boolean | Prisma.User$tripsArgs<ExtArgs> trips?: boolean | Prisma.User$tripsArgs<ExtArgs>
participations?: boolean | Prisma.User$participationsArgs<ExtArgs> participations?: boolean | Prisma.User$participationsArgs<ExtArgs>
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs> tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs> _count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
}, ExtArgs["result"]["user"]> }, ExtArgs["result"]["user"]>
@@ -729,7 +1012,8 @@ export type UserSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensio
email?: boolean email?: boolean
password?: boolean password?: boolean
image?: boolean image?: boolean
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["user"]> }, ExtArgs["result"]["user"]>
@@ -740,7 +1024,8 @@ export type UserSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensio
email?: boolean email?: boolean
password?: boolean password?: boolean
image?: boolean image?: boolean
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["user"]> }, ExtArgs["result"]["user"]>
@@ -751,16 +1036,19 @@ export type UserSelectScalar = {
email?: boolean email?: boolean
password?: boolean password?: boolean
image?: boolean image?: boolean
isVerified?: boolean acceptedTermsAndPrivacy?: boolean
acceptedAt?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
} }
export type UserOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "password" | "image" | "isVerified" | "createdAt" | "updatedAt", ExtArgs["result"]["user"]> export type UserOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "password" | "image" | "acceptedTermsAndPrivacy" | "acceptedAt" | "createdAt" | "updatedAt", ExtArgs["result"]["user"]>
export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
trips?: boolean | Prisma.User$tripsArgs<ExtArgs> trips?: boolean | Prisma.User$tripsArgs<ExtArgs>
participations?: boolean | Prisma.User$participationsArgs<ExtArgs> participations?: boolean | Prisma.User$participationsArgs<ExtArgs>
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs> tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<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> = {}
@@ -772,6 +1060,8 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
trips: Prisma.$TripPayload<ExtArgs>[] trips: Prisma.$TripPayload<ExtArgs>[]
participations: Prisma.$TripParticipantPayload<ExtArgs>[] participations: Prisma.$TripParticipantPayload<ExtArgs>[]
tripReviews: Prisma.$TripReviewPayload<ExtArgs>[] tripReviews: Prisma.$TripReviewPayload<ExtArgs>[]
organizerVerification: Prisma.$OrganizerVerificationPayload<ExtArgs> | null
reviewedVerifications: Prisma.$OrganizerVerificationPayload<ExtArgs>[]
} }
scalars: runtime.Types.Extensions.GetPayloadResult<{ scalars: runtime.Types.Extensions.GetPayloadResult<{
id: string id: string
@@ -780,9 +1070,13 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
password: string password: string
image: string | null image: string | null
/** /**
* Akun diverifikasi tim SeTrip (manual / admin) — tampil sebagai badge kepercayaan * Apakah user telah menyetujui Syarat & Ketentuan dan Kebijakan Privasi
*/ */
isVerified: boolean acceptedTermsAndPrivacy: boolean
/**
* Waktu user menyetujui Syarat & Ketentuan dan Kebijakan Privasi
*/
acceptedAt: Date | null
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
}, ExtArgs["result"]["user"]> }, ExtArgs["result"]["user"]>
@@ -1182,6 +1476,8 @@ export interface Prisma__UserClient<T, Null = never, ExtArgs extends runtime.Typ
trips<T extends Prisma.User$tripsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$tripsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null> trips<T extends Prisma.User$tripsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$tripsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
participations<T extends Prisma.User$participationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$participationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripParticipantPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null> participations<T extends Prisma.User$participationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$participationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripParticipantPayload<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> 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>
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>
/** /**
* 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.
@@ -1216,7 +1512,8 @@ export interface UserFieldRefs {
readonly email: Prisma.FieldRef<"User", 'String'> readonly email: Prisma.FieldRef<"User", 'String'>
readonly password: Prisma.FieldRef<"User", 'String'> readonly password: Prisma.FieldRef<"User", 'String'>
readonly image: Prisma.FieldRef<"User", 'String'> readonly image: Prisma.FieldRef<"User", 'String'>
readonly isVerified: Prisma.FieldRef<"User", 'Boolean'> readonly acceptedTermsAndPrivacy: Prisma.FieldRef<"User", 'Boolean'>
readonly acceptedAt: Prisma.FieldRef<"User", 'DateTime'>
readonly createdAt: Prisma.FieldRef<"User", 'DateTime'> readonly createdAt: Prisma.FieldRef<"User", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"User", 'DateTime'> readonly updatedAt: Prisma.FieldRef<"User", 'DateTime'>
} }
@@ -1683,6 +1980,49 @@ export type User$tripReviewsArgs<ExtArgs extends runtime.Types.Extensions.Intern
distinct?: Prisma.TripReviewScalarFieldEnum | Prisma.TripReviewScalarFieldEnum[] distinct?: Prisma.TripReviewScalarFieldEnum | Prisma.TripReviewScalarFieldEnum[]
} }
/**
* User.organizerVerification
*/
export type User$organizerVerificationArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
/**
* Select specific fields to fetch from the OrganizerVerification
*/
select?: Prisma.OrganizerVerificationSelect<ExtArgs> | null
/**
* Omit specific fields from the OrganizerVerification
*/
omit?: Prisma.OrganizerVerificationOmit<ExtArgs> | null
/**
* Choose, which related nodes to fetch as well
*/
include?: Prisma.OrganizerVerificationInclude<ExtArgs> | null
where?: Prisma.OrganizerVerificationWhereInput
}
/**
* User.reviewedVerifications
*/
export type User$reviewedVerificationsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
/**
* Select specific fields to fetch from the OrganizerVerification
*/
select?: Prisma.OrganizerVerificationSelect<ExtArgs> | null
/**
* Omit specific fields from the OrganizerVerification
*/
omit?: Prisma.OrganizerVerificationOmit<ExtArgs> | null
/**
* Choose, which related nodes to fetch as well
*/
include?: Prisma.OrganizerVerificationInclude<ExtArgs> | null
where?: Prisma.OrganizerVerificationWhereInput
orderBy?: Prisma.OrganizerVerificationOrderByWithRelationInput | Prisma.OrganizerVerificationOrderByWithRelationInput[]
cursor?: Prisma.OrganizerVerificationWhereUniqueInput
take?: number
skip?: number
distinct?: Prisma.OrganizerVerificationScalarFieldEnum | Prisma.OrganizerVerificationScalarFieldEnum[]
}
/** /**
* User without action * User without action
*/ */
+12
View File
@@ -0,0 +1,12 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Kebijakan Privasi",
description:
"Kebijakan Privasi SeTrip. Pelajari bagaimana kami mengumpulkan, menggunakan, dan melindungi data Anda.",
alternates: { canonical: "/privacy" },
};
export default function PrivacyLayout({ children }: { children: React.ReactNode }) {
return children;
}
+235
View File
@@ -0,0 +1,235 @@
import Link from "next/link";
export default function PrivacyPage() {
return (
<div className="mx-auto max-w-3xl px-4 py-8 sm:py-12">
<article className="rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm sm:p-10">
<header className="mb-8 border-b border-neutral-200 pb-6">
<h1 className="text-2xl font-bold text-neutral-900 sm:text-3xl">
🔒 Kebijakan Privasi SeTrip
</h1>
<p className="mt-2 text-sm text-neutral-500">
Terakhir diperbarui: 2026-04-27
</p>
<p className="mt-4 text-sm leading-relaxed text-neutral-700">
SeTrip menghargai privasi Anda. Kebijakan Privasi ini menjelaskan
bagaimana kami mengumpulkan, menggunakan, dan melindungi informasi
Anda saat menggunakan platform SeTrip. Dengan menggunakan SeTrip,
Anda menyetujui praktik yang dijelaskan dalam Kebijakan Privasi ini.
</p>
</header>
<div className="space-y-8 text-sm leading-relaxed text-neutral-700">
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
1. Informasi yang Kami Kumpulkan
</h2>
<h3 className="mb-2 mt-4 font-semibold text-neutral-800">a. Informasi Akun</h3>
<ul className="ml-5 list-disc space-y-1.5">
<li>Nama</li>
<li>Email</li>
<li>Nomor telepon</li>
<li>Password (disimpan dalam bentuk terenkripsi)</li>
</ul>
<h3 className="mb-2 mt-4 font-semibold text-neutral-800">b. Informasi Profil</h3>
<ul className="ml-5 list-disc space-y-1.5">
<li>Foto profil</li>
<li>Deskripsi diri</li>
<li>Riwayat trip</li>
</ul>
<h3 className="mb-2 mt-4 font-semibold text-neutral-800">c. Informasi Transaksi</h3>
<ul className="ml-5 list-disc space-y-1.5">
<li>Data booking trip</li>
<li>Status pembayaran</li>
<li>Riwayat aktivitas</li>
</ul>
<h3 className="mb-2 mt-4 font-semibold text-neutral-800">d. Informasi Teknis</h3>
<ul className="ml-5 list-disc space-y-1.5">
<li>Alamat IP</li>
<li>Browser</li>
<li>Perangkat yang digunakan</li>
<li>Log aktivitas</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
2. Cara Kami Menggunakan Informasi
</h2>
<p className="mb-3">Kami menggunakan informasi Anda untuk:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Membuat dan mengelola akun</li>
<li>Menghubungkan pengguna dengan organizer</li>
<li>Memproses booking dan aktivitas trip</li>
<li>Meningkatkan layanan dan pengalaman pengguna</li>
<li>Mengirim notifikasi terkait aktivitas</li>
<li>Mencegah penipuan dan penyalahgunaan</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">3. Pembagian Informasi</h2>
<p className="mb-3">
Kami tidak menjual data pribadi Anda. Namun, kami dapat membagikan
informasi dalam kondisi berikut:
</p>
<h3 className="mb-2 mt-4 font-semibold text-neutral-800">a. Dengan Organizer</h3>
<ul className="ml-5 list-disc space-y-1.5">
<li>
Informasi dasar seperti nama dan kontak dapat dibagikan kepada
organizer untuk keperluan trip
</li>
</ul>
<h3 className="mb-2 mt-4 font-semibold text-neutral-800">
b. Dengan Penyedia Layanan
</h3>
<ul className="ml-5 list-disc space-y-1.5">
<li>Untuk kebutuhan teknis (hosting, analytics, dll)</li>
</ul>
<h3 className="mb-2 mt-4 font-semibold text-neutral-800">c. Kewajiban Hukum</h3>
<ul className="ml-5 list-disc space-y-1.5">
<li>Jika diminta oleh hukum atau otoritas berwenang</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">4. Keamanan Data</h2>
<p className="mb-3">Kami berusaha melindungi data Anda dengan:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Enkripsi password</li>
<li>Pembatasan akses data</li>
<li>Sistem keamanan standar industri</li>
</ul>
<p className="mt-3">Namun, tidak ada sistem yang 100% aman.</p>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">5. Penyimpanan Data</h2>
<p className="mb-3">Kami menyimpan data Anda selama:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Akun Anda aktif</li>
<li>Dibutuhkan untuk keperluan layanan</li>
</ul>
<p className="mt-3">
Data dapat dihapus atas permintaan pengguna, kecuali diwajibkan
oleh hukum untuk disimpan.
</p>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">6. Hak Pengguna</h2>
<p className="mb-3">Anda memiliki hak untuk:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Mengakses data pribadi Anda</li>
<li>Memperbarui informasi</li>
<li>Menghapus akun</li>
<li>Menarik persetujuan</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">7. Cookie &amp; Tracking</h2>
<p className="mb-3">SeTrip dapat menggunakan:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Cookie</li>
<li>Teknologi pelacakan sederhana</li>
</ul>
<p className="mt-3 mb-2">Untuk:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Menyimpan sesi login</li>
<li>Meningkatkan pengalaman pengguna</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
8. Layanan Pihak Ketiga
</h2>
<p className="mb-3">SeTrip dapat menggunakan layanan pihak ketiga seperti:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Hosting</li>
<li>Analytics</li>
<li>Payment gateway (di masa depan)</li>
</ul>
<p className="mt-3">
Kami tidak bertanggung jawab atas kebijakan privasi pihak ketiga
tersebut.
</p>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
9. Perlindungan terhadap Penipuan
</h2>
<p className="mb-3">Kami dapat menggunakan data untuk:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Mendeteksi aktivitas mencurigakan</li>
<li>Mencegah penipuan</li>
<li>Melindungi pengguna lain</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
10. Perubahan Kebijakan Privasi
</h2>
<p className="mb-3">
SeTrip dapat memperbarui Kebijakan Privasi ini sewaktu-waktu.
Pengguna disarankan untuk:
</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Membaca secara berkala</li>
<li>Memahami perubahan yang berlaku</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">11. Kontak</h2>
<p>
Jika Anda memiliki pertanyaan mengenai Kebijakan Privasi ini,
silakan hubungi:{" "}
<a
href="mailto:support@setrip.com"
className="font-semibold text-primary-600 hover:text-primary-700"
>
support@setrip.com
</a>
</p>
</section>
<section className="rounded-xl bg-neutral-50 p-5">
<h2 className="mb-2 text-lg font-bold text-neutral-900"> Persetujuan</h2>
<p className="mb-2">
Dengan menggunakan SeTrip, Anda menyatakan bahwa:
</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Telah membaca</li>
<li>Memahami</li>
<li>Menyetujui Kebijakan Privasi ini</li>
</ul>
</section>
</div>
<footer className="mt-10 border-t border-neutral-200 pt-6 text-sm">
<p className="text-neutral-500">
Lihat juga{" "}
<Link
href="/terms"
className="font-semibold text-primary-600 hover:text-primary-700"
>
Syarat &amp; Ketentuan
</Link>
.
</p>
</footer>
</article>
</div>
);
}
+31
View File
@@ -140,6 +140,37 @@ export default function RegisterPage() {
/> />
</div> </div>
<label className="flex items-start gap-2.5 text-sm text-neutral-700">
<input
id="acceptedTermsAndPrivacy"
name="acceptedTermsAndPrivacy"
type="checkbox"
required
className="mt-0.5 h-4 w-4 shrink-0 rounded border-neutral-300 text-primary-600 focus:ring-primary-500"
/>
<span>
Saya menyetujui{" "}
<Link
href="/terms"
target="_blank"
rel="noopener noreferrer"
className="font-semibold text-primary-600 hover:text-primary-700"
>
Syarat &amp; Ketentuan
</Link>{" "}
dan{" "}
<Link
href="/privacy"
target="_blank"
rel="noopener noreferrer"
className="font-semibold text-primary-600 hover:text-primary-700"
>
Kebijakan Privasi
</Link>
.
</span>
</label>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
+12
View File
@@ -30,6 +30,18 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
changeFrequency: "yearly", changeFrequency: "yearly",
priority: 0.3, priority: 0.3,
}, },
{
url: absoluteUrl("/terms"),
lastModified: now,
changeFrequency: "yearly",
priority: 0.2,
},
{
url: absoluteUrl("/privacy"),
lastModified: now,
changeFrequency: "yearly",
priority: 0.2,
},
]; ];
const tripEntries: MetadataRoute.Sitemap = trips.map((t) => ({ const tripEntries: MetadataRoute.Sitemap = trips.map((t) => ({
+12
View File
@@ -0,0 +1,12 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Syarat & Ketentuan",
description:
"Syarat & Ketentuan penggunaan platform SeTrip. Baca sebelum menggunakan layanan SeTrip.",
alternates: { canonical: "/terms" },
};
export default function TermsLayout({ children }: { children: React.ReactNode }) {
return children;
}
+292
View File
@@ -0,0 +1,292 @@
import Link from "next/link";
export default function TermsPage() {
return (
<div className="mx-auto max-w-3xl px-4 py-8 sm:py-12">
<article className="rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm sm:p-10">
<header className="mb-8 border-b border-neutral-200 pb-6">
<h1 className="text-2xl font-bold text-neutral-900 sm:text-3xl">
📜 Syarat &amp; Ketentuan SeTrip
</h1>
<p className="mt-2 text-sm text-neutral-500">
Terakhir diperbarui: 2026-04-27
</p>
<p className="mt-4 text-sm leading-relaxed text-neutral-700">
Selamat datang di SeTrip. Dengan mengakses atau menggunakan platform
SeTrip, Anda menyetujui untuk terikat oleh Syarat &amp; Ketentuan
berikut.
</p>
</header>
<div className="space-y-8 text-sm leading-relaxed text-neutral-700">
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">1. Definisi</h2>
<ul className="ml-5 list-disc space-y-1.5">
<li>
<strong>SeTrip</strong>: Platform yang menghubungkan pengguna
dengan penyelenggara trip.
</li>
<li>
<strong>Pengguna (User)</strong>: Individu yang menggunakan
aplikasi SeTrip.
</li>
<li>
<strong>Organizer (Penyelenggara)</strong>: Pengguna yang membuat
dan mengelola trip.
</li>
<li>
<strong>Trip</strong>: Kegiatan perjalanan yang dibuat oleh
organizer.
</li>
<li>
<strong>Platform</strong>: Website atau aplikasi SeTrip.
</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">2. Peran SeTrip</h2>
<p className="mb-3">
SeTrip bertindak sebagai <strong>platform perantara</strong> yang
menghubungkan pengguna dan organizer. SeTrip:
</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Bukan penyelenggara trip</li>
<li>Tidak terlibat langsung dalam pelaksanaan perjalanan</li>
<li>Tidak bertanggung jawab atas kegiatan selama trip berlangsung</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
3. Penggunaan Platform
</h2>
<p className="mb-3">
Dengan menggunakan SeTrip, Anda menyatakan bahwa:
</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Berusia minimal 18 tahun atau memiliki izin dari wali</li>
<li>Memberikan informasi yang benar dan akurat</li>
<li>
Tidak menggunakan platform untuk: penipuan, aktivitas ilegal,
atau penyebaran informasi palsu
</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">4. Akun Pengguna</h2>
<ul className="ml-5 list-disc space-y-1.5">
<li>Pengguna bertanggung jawab atas keamanan akun masing-masing</li>
<li>Dilarang membagikan akun kepada pihak lain</li>
<li>
SeTrip berhak menangguhkan atau menghapus akun jika terjadi
pelanggaran
</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">5. Trip &amp; Booking</h2>
<ul className="ml-5 list-disc space-y-1.5">
<li>Organizer bertanggung jawab atas seluruh informasi trip</li>
<li>Pengguna wajib membaca detail trip sebelum melakukan join</li>
<li>
Dengan melakukan join trip, pengguna menyetujui seluruh ketentuan
trip yang dibuat oleh organizer
</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">6. Pembayaran</h2>
<ul className="ml-5 list-disc space-y-1.5">
<li>Pembayaran dilakukan sesuai metode yang tersedia di platform</li>
<li>
Dalam fase awal, pembayaran dapat dilakukan langsung kepada
organizer
</li>
<li>
SeTrip tidak menjamin keamanan transaksi yang dilakukan di luar
platform
</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
7. Pembatalan &amp; Refund
</h2>
<ul className="ml-5 list-disc space-y-1.5">
<li>Kebijakan pembatalan ditentukan oleh organizer</li>
<li>
SeTrip tidak bertanggung jawab atas refund yang tidak diberikan
oleh organizer
</li>
<li>
Pengguna disarankan untuk memahami kebijakan sebelum melakukan
pembayaran
</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
8. Tanggung Jawab Organizer
</h2>
<p className="mb-3">Organizer wajib:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Memberikan informasi trip yang jelas dan akurat</li>
<li>Menjalankan trip sesuai deskripsi</li>
<li>Bertanggung jawab atas keselamatan peserta selama trip</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">9. Risiko Perjalanan</h2>
<p className="mb-3">
Pengguna memahami bahwa aktivitas perjalanan, terutama kegiatan
outdoor, memiliki risiko termasuk namun tidak terbatas pada:
</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Cedera</li>
<li>Kecelakaan</li>
<li>Cuaca ekstrem</li>
<li>Kondisi tak terduga lainnya</li>
</ul>
<blockquote className="mt-3 border-l-4 border-primary-500 bg-primary-50 px-4 py-3 italic text-neutral-700">
Mengikuti kegiatan secara sadar dan bertanggung jawab atas risiko
pribadi
</blockquote>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
10. Batasan Tanggung Jawab
</h2>
<p className="mb-3">SeTrip tidak bertanggung jawab atas:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Kerugian finansial</li>
<li>Cedera atau kecelakaan</li>
<li>Kegagalan pelaksanaan trip</li>
<li>Tindakan organizer atau pengguna lain</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
11. Larangan Transaksi di Luar Platform
</h2>
<p className="mb-3">
Pengguna disarankan untuk tidak melakukan transaksi di luar
platform. SeTrip tidak bertanggung jawab atas:
</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Penipuan</li>
<li>Kerugian</li>
<li>Masalah lain yang terjadi akibat transaksi di luar sistem SeTrip</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">12. Sistem Review</h2>
<ul className="ml-5 list-disc space-y-1.5">
<li>Pengguna dapat memberikan review setelah trip</li>
<li>Review harus jujur dan tidak mengandung unsur fitnah</li>
<li>SeTrip berhak menghapus review yang melanggar</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
13. Penangguhan &amp; Penghentian Akun
</h2>
<p className="mb-3">SeTrip berhak untuk:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Menangguhkan akun</li>
<li>Menghapus akun</li>
<li>Membatasi akses</li>
</ul>
<p className="mt-3 mb-3">Jika pengguna:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Melanggar ketentuan</li>
<li>Terindikasi melakukan penipuan</li>
<li>Menyalahgunakan platform</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">14. Perubahan Layanan</h2>
<p className="mb-3">SeTrip dapat:</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Mengubah fitur</li>
<li>Menghentikan layanan</li>
<li>Menambahkan kebijakan baru</li>
</ul>
<p className="mt-3">Tanpa pemberitahuan sebelumnya.</p>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">
15. Perubahan Syarat &amp; Ketentuan
</h2>
<p className="mb-3">
SeTrip dapat memperbarui Syarat &amp; Ketentuan ini kapan saja.
Pengguna disarankan untuk:
</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Membaca secara berkala</li>
<li>Memahami perubahan yang berlaku</li>
</ul>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">16. Hukum yang Berlaku</h2>
<p>
Syarat &amp; Ketentuan ini diatur oleh hukum yang berlaku di
Republik Indonesia.
</p>
</section>
<section>
<h2 className="mb-3 text-lg font-bold text-neutral-900">17. Kontak</h2>
<p>
Jika Anda memiliki pertanyaan, silakan hubungi:{" "}
<a
href="mailto:support@setrip.com"
className="font-semibold text-primary-600 hover:text-primary-700"
>
support@setrip.com
</a>
</p>
</section>
<section className="rounded-xl bg-neutral-50 p-5">
<h2 className="mb-2 text-lg font-bold text-neutral-900"> Persetujuan</h2>
<p className="mb-2">
Dengan menggunakan SeTrip, Anda menyatakan bahwa:
</p>
<ul className="ml-5 list-disc space-y-1.5">
<li>Telah membaca</li>
<li>Memahami</li>
<li>Menyetujui seluruh isi Syarat &amp; Ketentuan ini</li>
</ul>
</section>
</div>
<footer className="mt-10 border-t border-neutral-200 pt-6 text-sm">
<p className="text-neutral-500">
Lihat juga{" "}
<Link
href="/privacy"
className="font-semibold text-primary-600 hover:text-primary-700"
>
Kebijakan Privasi
</Link>
.
</p>
</footer>
</article>
</div>
);
}
+13
View File
@@ -0,0 +1,13 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Verifikasi Organizer",
description:
"Ajukan verifikasi sebagai organizer SeTrip dengan KTP & data rekening untuk membuat trip berbayar.",
alternates: { canonical: "/verify" },
robots: { index: false, follow: false },
};
export default function VerifyLayout({ children }: { children: React.ReactNode }) {
return children;
}
+79
View File
@@ -0,0 +1,79 @@
import { redirect } from "next/navigation";
import Link from "next/link";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { organizerService } from "@/server/services/organizer.service";
import { VerifyForm } from "@/features/organizer/components/verify-form";
import { VerifiedBadge } from "@/components/shared/verified-badge";
export default async function VerifyPage() {
const session = await getServerSession(authOptions);
if (!session?.user) {
redirect("/login?callbackUrl=/verify");
}
const verification = await organizerService.getStatusForUser(session.user.id);
return (
<div className="mx-auto max-w-2xl px-4 py-8 sm:py-12">
<div className="mb-6">
<h1 className="text-2xl font-bold text-neutral-900 sm:text-3xl">
Verifikasi Organizer
</h1>
<p className="mt-2 text-sm text-neutral-600">
Lengkapi data berikut untuk mengaktifkan kemampuan membuat trip berbayar.
</p>
</div>
{verification?.status === "APPROVED" && (
<div className="mb-6 rounded-2xl border border-primary-200 bg-primary-50 p-5">
<div className="mb-2 flex items-center gap-2">
<VerifiedBadge size="md" />
<span className="text-sm font-semibold text-primary-800">
Akun terverifikasi
</span>
</div>
<p className="text-sm text-neutral-700">
Selamat! Kamu sudah bisa membuat trip berbayar.
</p>
</div>
)}
{verification?.status === "PENDING" && (
<div className="mb-6 rounded-2xl border border-amber-200 bg-amber-50 p-5">
<p className="mb-1 text-sm font-bold text-amber-800">
Menunggu review admin
</p>
<p className="text-sm text-neutral-700">
Pengajuanmu sedang diproses. Kami akan memberitahu via email setelah selesai.
</p>
</div>
)}
{verification?.status === "REJECTED" && (
<div className="mb-6 rounded-2xl border border-red-200 bg-red-50 p-5">
<p className="mb-1 text-sm font-bold text-red-800"> Pengajuan ditolak</p>
{verification.rejectionReason && (
<p className="text-sm text-neutral-700">
<span className="font-semibold">Alasan:</span>{" "}
{verification.rejectionReason}
</p>
)}
<p className="mt-2 text-sm text-neutral-700">
Kamu bisa memperbaiki data dan mengajukan ulang di bawah.
</p>
</div>
)}
{verification?.status !== "APPROVED" && verification?.status !== "PENDING" && (
<VerifyForm initial={verification ?? null} />
)}
<p className="mt-6 text-center text-sm text-neutral-500">
<Link href="/profile" className="hover:text-primary-600">
Kembali ke profil
</Link>
</p>
</div>
);
}
+13
View File
@@ -50,6 +50,12 @@ export function Navbar() {
> >
Profil Profil
</Link> </Link>
<Link
href="/verify"
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"
>
Verifikasi
</Link>
<div className="ml-2 flex items-center gap-2"> <div className="ml-2 flex items-center gap-2">
<Link <Link
href="/profile" href="/profile"
@@ -136,6 +142,13 @@ export function Navbar() {
> >
Profil Profil
</Link> </Link>
<Link
href="/verify"
onClick={() => setMenuOpen(false)}
className="rounded-lg px-3 py-2.5 text-sm font-medium text-neutral-700 transition-colors hover:bg-neutral-50"
>
Verifikasi
</Link>
<div className="mt-2 flex items-center justify-between rounded-lg bg-neutral-50 px-3 py-2.5"> <div className="mt-2 flex items-center justify-between rounded-lg bg-neutral-50 px-3 py-2.5">
<Link <Link
href="/profile" href="/profile"
+24
View File
@@ -0,0 +1,24 @@
type Size = "sm" | "md";
export function VerifiedBadge({ size = "sm" }: { size?: Size }) {
const cls =
size === "md"
? "px-2.5 py-1 text-xs"
: "px-2 py-0.5 text-[10px]";
return (
<span
className={`inline-flex items-center gap-1 rounded-full bg-primary-50 font-semibold text-primary-700 ring-1 ring-primary-200 ${cls}`}
title="Organizer terverifikasi SeTrip"
>
<svg
viewBox="0 0 16 16"
fill="currentColor"
className={size === "md" ? "h-3.5 w-3.5" : "h-3 w-3"}
aria-hidden="true"
>
<path d="M8 0l2.09 1.74L12.86 1.5l.64 2.78 2.5 1.5-1.5 2.5.5 2.86-2.78.64-1.5 2.5-2.72-.59L5.5 14.5 4 12 1.5 11.36 2 8.5.5 6 3 4.5l.64-2.78 2.77.24L8 0zm-1.07 9.4l4.6-4.6-1.06-1.06-3.54 3.54-1.41-1.42-1.06 1.06 2.47 2.48z" />
</svg>
Verified
</span>
);
}
+5
View File
@@ -0,0 +1,5 @@
DATABASE_URL="postgresql://setrip_user:setrip_password@localhost:5432/setrip_db"
NEXTAUTH_SECRET="3GaP/mqi1IYbafyLfyI54ouPRDE0IUK5vFqpKJQM5hg="
NEXTAUTH_URL="http://localhost:3000"
NEXT_PUBLIC_SITE_URL="https://arifal.imola.ai"
ADMIN_EMAILS=admin@setrip.id
+2
View File
@@ -9,6 +9,7 @@ export async function registerAction(formData: FormData) {
email: formData.get("email") as string, email: formData.get("email") as string,
password: formData.get("password") as string, password: formData.get("password") as string,
confirmPassword: formData.get("confirmPassword") as string, confirmPassword: formData.get("confirmPassword") as string,
acceptedTermsAndPrivacy: formData.get("acceptedTermsAndPrivacy") === "on",
}; };
const result = registerSchema.safeParse(raw); const result = registerSchema.safeParse(raw);
@@ -21,6 +22,7 @@ export async function registerAction(formData: FormData) {
name: result.data.name, name: result.data.name,
email: result.data.email, email: result.data.email,
password: result.data.password, password: result.data.password,
acceptedTermsAndPrivacy: result.data.acceptedTermsAndPrivacy,
}); });
return { success: true }; return { success: true };
} catch (err) { } catch (err) {
+3
View File
@@ -29,6 +29,9 @@ export const registerSchema = z.object({
.min(6, "Password minimal 6 karakter") .min(6, "Password minimal 6 karakter")
.max(LIMITS.MAX_PASSWORD_LENGTH, "Password terlalu panjang (maks. 72 karakter)"), .max(LIMITS.MAX_PASSWORD_LENGTH, "Password terlalu panjang (maks. 72 karakter)"),
confirmPassword: z.string(), confirmPassword: z.string(),
acceptedTermsAndPrivacy: z.literal(true, {
error: "Kamu harus menyetujui Syarat & Ketentuan dan Kebijakan Privasi",
}),
}).refine((data) => data.password === data.confirmPassword, { }).refine((data) => data.password === data.confirmPassword, {
message: "Password tidak cocok", message: "Password tidak cocok",
path: ["confirmPassword"], path: ["confirmPassword"],
+78
View File
@@ -0,0 +1,78 @@
"use server";
import { getServerSession } from "next-auth";
import { revalidatePath } from "next/cache";
import { authOptions } from "@/lib/auth";
import { isAdminEmail } from "@/lib/admin";
import { organizerService } from "@/server/services/organizer.service";
import { submitVerificationSchema, reviewVerificationSchema } from "./schemas";
export async function submitVerificationAction(formData: FormData) {
const session = await getServerSession(authOptions);
if (!session?.user) {
return { error: "Kamu harus login terlebih dahulu" };
}
const raw = {
fullName: formData.get("fullName") as string,
nik: formData.get("nik") as string,
birthDate: formData.get("birthDate") as string,
address: formData.get("address") as string,
ktpImageUrl: formData.get("ktpImageUrl") as string,
selfieUrl: formData.get("selfieUrl") as string,
bankName: formData.get("bankName") as string,
bankAccountNumber: formData.get("bankAccountNumber") as string,
bankAccountName: formData.get("bankAccountName") as string,
};
const result = submitVerificationSchema.safeParse(raw);
if (!result.success) {
return { error: result.error.issues[0].message };
}
try {
await organizerService.submitVerification(session.user.id, {
...result.data,
birthDate: new Date(result.data.birthDate),
});
revalidatePath("/verify");
revalidatePath("/profile");
revalidatePath("/admin/verifications");
return { success: true };
} catch (err) {
return { error: (err as Error).message };
}
}
export async function reviewVerificationAction(formData: FormData) {
const session = await getServerSession(authOptions);
if (!session?.user || !isAdminEmail(session.user.email)) {
return { error: "Tidak memiliki akses admin" };
}
const raw = {
verificationId: formData.get("verificationId") as string,
decision: formData.get("decision") as string,
rejectionReason: (formData.get("rejectionReason") as string) || undefined,
};
const result = reviewVerificationSchema.safeParse(raw);
if (!result.success) {
return { error: result.error.issues[0].message };
}
try {
await organizerService.reviewVerification({
verificationId: result.data.verificationId,
decision: result.data.decision,
rejectionReason: result.data.rejectionReason,
reviewerId: session.user.id,
});
revalidatePath("/admin/verifications");
revalidatePath("/verify");
revalidatePath("/profile");
return { success: true };
} catch (err) {
return { error: (err as Error).message };
}
}
@@ -0,0 +1,239 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import { reviewVerificationAction } from "@/features/organizer/actions";
type Verification = {
id: string;
fullName: string;
nik: string;
birthDate: Date;
address: string;
ktpImageUrl: string;
selfieUrl: string;
bankName: string;
bankAccountNumber: string;
bankAccountName: string;
status: "PENDING" | "APPROVED" | "REJECTED";
rejectionReason: string | null;
reviewedAt: Date | null;
createdAt: Date;
user: { id: string; name: string; email: string };
reviewedBy: { id: string; name: string; email: string } | null;
};
function formatDate(d: Date): string {
return new Date(d).toLocaleString("id-ID", {
day: "2-digit",
month: "short",
year: "numeric",
});
}
export function ReviewCard({ verification }: { verification: Verification }) {
const router = useRouter();
const [showReject, setShowReject] = useState(false);
const [rejectionReason, setRejectionReason] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
async function decide(decision: "APPROVED" | "REJECTED") {
setError("");
setLoading(true);
const fd = new FormData();
fd.set("verificationId", verification.id);
fd.set("decision", decision);
if (decision === "REJECTED") fd.set("rejectionReason", rejectionReason);
const result = await reviewVerificationAction(fd);
setLoading(false);
if (result.error) {
setError(result.error);
return;
}
setShowReject(false);
setRejectionReason("");
router.refresh();
}
return (
<article className="rounded-2xl border border-neutral-200 bg-white p-5 shadow-sm sm:p-6">
<header className="mb-4 flex flex-wrap items-start justify-between gap-3 border-b border-neutral-100 pb-4">
<div>
<h3 className="text-base font-bold text-neutral-900">
{verification.user.name}
</h3>
<p className="text-xs text-neutral-500">
{verification.user.email} · diajukan {formatDate(verification.createdAt)}
</p>
</div>
<StatusPill status={verification.status} />
</header>
<div className="grid gap-4 sm:grid-cols-2">
<Field label="Nama Lengkap" value={verification.fullName} />
<Field label="NIK" value={verification.nik} mono />
<Field
label="Tanggal Lahir"
value={formatDate(verification.birthDate)}
/>
<Field
label="Bank"
value={`${verification.bankName} · ${verification.bankAccountNumber}`}
/>
<Field
label="Pemilik Rekening"
value={verification.bankAccountName}
/>
<Field label="Alamat" value={verification.address} fullWidth />
</div>
<div className="mt-5 grid gap-4 sm:grid-cols-2">
<ImagePreview label="Foto KTP" url={verification.ktpImageUrl} />
<ImagePreview label="Selfie + KTP" url={verification.selfieUrl} />
</div>
{verification.status === "REJECTED" && verification.rejectionReason && (
<div className="mt-4 rounded-xl bg-red-50 p-3 text-sm text-red-700">
<span className="font-semibold">Alasan ditolak:</span>{" "}
{verification.rejectionReason}
</div>
)}
{verification.reviewedBy && verification.reviewedAt && (
<p className="mt-4 text-xs text-neutral-500">
Diproses oleh {verification.reviewedBy.name} pada{" "}
{formatDate(verification.reviewedAt)}
</p>
)}
{verification.status === "PENDING" && (
<div className="mt-5 border-t border-neutral-100 pt-4">
{error && (
<div className="mb-3 rounded-lg bg-red-50 px-3 py-2 text-xs text-red-600">
{error}
</div>
)}
{!showReject ? (
<div className="flex flex-wrap gap-2">
<button
type="button"
onClick={() => decide("APPROVED")}
disabled={loading}
className="rounded-xl bg-primary-600 px-4 py-2 text-sm font-bold text-white hover:bg-primary-700 disabled:opacity-50"
>
Setujui
</button>
<button
type="button"
onClick={() => setShowReject(true)}
disabled={loading}
className="rounded-xl border border-red-200 bg-white px-4 py-2 text-sm font-bold text-red-600 hover:bg-red-50 disabled:opacity-50"
>
Tolak
</button>
</div>
) : (
<div className="space-y-2">
<textarea
value={rejectionReason}
onChange={(e) => setRejectionReason(e.target.value)}
rows={2}
placeholder="Alasan penolakan (akan dilihat user)"
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2 text-sm focus:bg-white"
/>
<div className="flex gap-2">
<button
type="button"
onClick={() => decide("REJECTED")}
disabled={loading || !rejectionReason.trim()}
className="rounded-xl bg-red-600 px-4 py-2 text-sm font-bold text-white hover:bg-red-700 disabled:opacity-50"
>
Konfirmasi Tolak
</button>
<button
type="button"
onClick={() => {
setShowReject(false);
setRejectionReason("");
}}
className="rounded-xl border border-neutral-200 bg-white px-4 py-2 text-sm font-medium text-neutral-600 hover:bg-neutral-50"
>
Batal
</button>
</div>
</div>
)}
</div>
)}
</article>
);
}
function Field({
label,
value,
mono,
fullWidth,
}: {
label: string;
value: string;
mono?: boolean;
fullWidth?: boolean;
}) {
return (
<div className={fullWidth ? "sm:col-span-2" : ""}>
<p className="text-xs font-semibold uppercase tracking-wide text-neutral-500">
{label}
</p>
<p
className={`mt-0.5 text-sm text-neutral-800 ${mono ? "font-mono" : ""}`}
>
{value}
</p>
</div>
);
}
function ImagePreview({ label, url }: { label: string; url: string }) {
return (
<div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-neutral-500">
{label}
</p>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="block overflow-hidden rounded-xl border border-neutral-200 bg-neutral-100"
>
<div className="relative aspect-[4/3] w-full">
<Image
src={url}
alt={label}
fill
unoptimized
className="object-cover"
sizes="(min-width: 640px) 50vw, 100vw"
/>
</div>
</a>
</div>
);
}
function StatusPill({ status }: { status: "PENDING" | "APPROVED" | "REJECTED" }) {
const cfg = {
PENDING: { label: "Pending", cls: "bg-amber-50 text-amber-700 ring-amber-200" },
APPROVED: { label: "Disetujui", cls: "bg-primary-50 text-primary-700 ring-primary-200" },
REJECTED: { label: "Ditolak", cls: "bg-red-50 text-red-700 ring-red-200" },
}[status];
return (
<span
className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ring-1 ${cfg.cls}`}
>
{cfg.label}
</span>
);
}
@@ -0,0 +1,215 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { submitVerificationAction } from "@/features/organizer/actions";
type Initial = {
fullName: string;
nik: string;
birthDate: Date;
address: string;
ktpImageUrl: string;
selfieUrl: string;
bankName: string;
bankAccountNumber: string;
bankAccountName: string;
} | null;
function toYmd(d: Date): string {
const y = d.getUTCFullYear();
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
const day = String(d.getUTCDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
}
export function VerifyForm({ initial }: { initial: Initial }) {
const router = useRouter();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setError("");
setLoading(true);
const formData = new FormData(e.currentTarget);
const result = await submitVerificationAction(formData);
setLoading(false);
if (result.error) {
setError(result.error);
return;
}
router.refresh();
}
const inputCls =
"w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 transition-colors placeholder:text-neutral-400 focus:bg-white";
return (
<form
onSubmit={handleSubmit}
className="space-y-5 rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm sm:p-8"
>
{error && (
<div className="rounded-xl bg-red-50 px-4 py-3 text-sm font-medium text-red-600">
{error}
</div>
)}
<section>
<h2 className="mb-3 text-base font-bold text-neutral-900">📇 Data KTP</h2>
<div className="grid gap-4 sm:grid-cols-2">
<div className="sm:col-span-2">
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Nama Lengkap (sesuai KTP)
</label>
<input
name="fullName"
type="text"
required
defaultValue={initial?.fullName ?? ""}
className={inputCls}
placeholder="Mis. Budi Santoso"
/>
</div>
<div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
NIK (16 digit)
</label>
<input
name="nik"
type="text"
inputMode="numeric"
pattern="\d{16}"
maxLength={16}
required
defaultValue={initial?.nik ?? ""}
className={inputCls}
placeholder="3201xxxxxxxxxxxx"
/>
</div>
<div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Tanggal Lahir
</label>
<input
name="birthDate"
type="date"
required
defaultValue={initial ? toYmd(new Date(initial.birthDate)) : ""}
className={inputCls}
/>
</div>
<div className="sm:col-span-2">
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Alamat (sesuai KTP)
</label>
<textarea
name="address"
required
rows={3}
defaultValue={initial?.address ?? ""}
className={inputCls}
placeholder="Jalan, RT/RW, kelurahan, kota, provinsi"
/>
</div>
</div>
</section>
<section>
<h2 className="mb-3 text-base font-bold text-neutral-900">🖼 Foto</h2>
<p className="mb-3 text-xs text-neutral-500">
Upload foto ke hosting (imgur, imgbb, dll) lalu paste URL-nya. Foto akan
dilihat tim admin saat review.
</p>
<div className="space-y-4">
<div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
URL Foto KTP
</label>
<input
name="ktpImageUrl"
type="url"
required
defaultValue={initial?.ktpImageUrl ?? ""}
className={inputCls}
placeholder="https://i.imgur.com/xxxx.jpg"
/>
</div>
<div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
URL Selfie dengan KTP
</label>
<input
name="selfieUrl"
type="url"
required
defaultValue={initial?.selfieUrl ?? ""}
className={inputCls}
placeholder="https://i.imgur.com/yyyy.jpg"
/>
</div>
</div>
</section>
<section>
<h2 className="mb-3 text-base font-bold text-neutral-900">🏦 Rekening Bank</h2>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Nama Bank
</label>
<input
name="bankName"
type="text"
required
defaultValue={initial?.bankName ?? ""}
className={inputCls}
placeholder="Mis. BCA, Mandiri, BRI"
/>
</div>
<div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Nomor Rekening
</label>
<input
name="bankAccountNumber"
type="text"
inputMode="numeric"
required
defaultValue={initial?.bankAccountNumber ?? ""}
className={inputCls}
placeholder="1234567890"
/>
</div>
<div className="sm:col-span-2">
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Nama Pemilik Rekening
</label>
<input
name="bankAccountName"
type="text"
required
defaultValue={initial?.bankAccountName ?? ""}
className={inputCls}
placeholder="Sesuai buku tabungan"
/>
</div>
</div>
</section>
<button
type="submit"
disabled={loading}
className="w-full rounded-xl bg-primary-600 py-2.5 text-sm font-bold text-white shadow-lg shadow-primary-600/20 transition-colors hover:bg-primary-700 disabled:opacity-50"
>
{loading ? "Mengirim..." : "Ajukan Verifikasi"}
</button>
<p className="text-center text-xs text-neutral-500">
Data KTP & rekening hanya digunakan untuk verifikasi dan tidak akan
ditampilkan ke pengguna lain.
</p>
</form>
);
}
+71
View File
@@ -0,0 +1,71 @@
import { z } from "zod/v4";
import { LIMITS } from "@/lib/limits";
const ymdRegex = /^\d{4}-\d{2}-\d{2}$/;
export const submitVerificationSchema = z.object({
fullName: z
.string()
.trim()
.min(2, "Nama lengkap minimal 2 karakter")
.max(LIMITS.MAX_NAME_LENGTH, `Nama maksimal ${LIMITS.MAX_NAME_LENGTH} karakter`),
nik: z
.string()
.trim()
.regex(/^\d{16}$/, "NIK harus 16 digit angka"),
birthDate: z
.string()
.trim()
.regex(ymdRegex, "Format tanggal lahir harus YYYY-MM-DD"),
address: z
.string()
.trim()
.min(5, "Alamat minimal 5 karakter")
.max(LIMITS.MAX_ADDRESS_LENGTH, `Alamat maksimal ${LIMITS.MAX_ADDRESS_LENGTH} karakter`),
ktpImageUrl: z
.string()
.trim()
.min(1, "Foto KTP wajib diisi")
.max(LIMITS.MAX_URL_LENGTH, "URL foto KTP terlalu panjang")
.pipe(z.url("URL foto KTP tidak valid")),
selfieUrl: z
.string()
.trim()
.min(1, "Foto selfie dengan KTP wajib diisi")
.max(LIMITS.MAX_URL_LENGTH, "URL foto selfie terlalu panjang")
.pipe(z.url("URL foto selfie tidak valid")),
bankName: z
.string()
.trim()
.min(2, "Nama bank minimal 2 karakter")
.max(LIMITS.MAX_BANK_NAME_LENGTH, `Nama bank maksimal ${LIMITS.MAX_BANK_NAME_LENGTH} karakter`),
bankAccountNumber: z
.string()
.trim()
.regex(/^[0-9-]+$/, "Nomor rekening hanya boleh angka")
.min(5, "Nomor rekening minimal 5 digit")
.max(LIMITS.MAX_BANK_ACCOUNT_NUMBER_LENGTH, "Nomor rekening terlalu panjang"),
bankAccountName: z
.string()
.trim()
.min(2, "Nama pemilik rekening minimal 2 karakter")
.max(LIMITS.MAX_NAME_LENGTH, `Nama pemilik rekening maksimal ${LIMITS.MAX_NAME_LENGTH} karakter`),
});
export const reviewVerificationSchema = z.object({
verificationId: z.string().min(1, "Verification ID wajib"),
decision: z.enum(["APPROVED", "REJECTED"], {
error: "Keputusan tidak valid",
}),
rejectionReason: z
.string()
.trim()
.max(LIMITS.MAX_REJECTION_REASON_LENGTH, "Alasan penolakan terlalu panjang")
.optional(),
}).refine(
(data) => data.decision !== "REJECTED" || (data.rejectionReason && data.rejectionReason.length > 0),
{ message: "Alasan penolakan wajib diisi jika menolak", path: ["rejectionReason"] }
);
export type SubmitVerificationInput = z.infer<typeof submitVerificationSchema>;
export type ReviewVerificationInput = z.infer<typeof reviewVerificationSchema>;
+11
View File
@@ -4,6 +4,7 @@ import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth"; import { authOptions } from "@/lib/auth";
import { createTripSchema, tripImageUrlsSchema } from "./schemas"; import { createTripSchema, tripImageUrlsSchema } from "./schemas";
import { tripService } from "@/server/services/trip.service"; import { tripService } from "@/server/services/trip.service";
import { organizerService } from "@/server/services/organizer.service";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { tripStoredInstantFromYmd } from "@/lib/trip-dates"; import { tripStoredInstantFromYmd } from "@/lib/trip-dates";
@@ -33,6 +34,16 @@ export async function createTripAction(formData: FormData) {
return { error: result.error.issues[0].message }; return { error: result.error.issues[0].message };
} }
if (result.data.price > 0) {
const approved = await organizerService.isApproved(session.user.id);
if (!approved) {
return {
error:
"Untuk membuat trip berbayar, akun kamu perlu diverifikasi. Silakan lengkapi verifikasi di /verify.",
};
}
}
const imageUrlsRaw = formData const imageUrlsRaw = formData
.getAll("imageUrls") .getAll("imageUrls")
.map((v) => (v as string).trim()) .map((v) => (v as string).trim())
@@ -0,0 +1,362 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { createTripAction } from "@/features/trip/actions";
import { ImageUrlInput } from "@/features/trip/components/image-url-input";
import { formatLocalCalendarYmd } from "@/lib/trip-dates";
const SAMPLE_MOUNTAINS = [
{ name: "Gunung Papandayan", location: "Garut, Jawa Barat" },
{ name: "Gunung Ciremai", location: "Kuningan, Jawa Barat" },
{ name: "Gunung Pangrango", location: "Bogor/Cianjur, Jawa Barat" },
{ name: "Gunung Gede", location: "Bogor/Cianjur, Jawa Barat" },
{ name: "Gunung Tangkuban Parahu", location: "Bandung, Jawa Barat" },
{ name: "Gunung Bukit Tunggul", location: "Bandung, Jawa Barat" },
{ name: "Gunung Malabar", location: "Bandung, Jawa Barat" },
{ name: "Gunung Guntur", location: "Garut, Jawa Barat" },
];
function formatRupiahInput(value: string): string {
const num = value.replace(/\D/g, "");
return num.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
function parseRupiahInput(value: string): string {
return value.replace(/\./g, "");
}
interface CreateTripFormProps {
isVerifiedOrganizer: boolean;
}
export function CreateTripForm({ isVerifiedOrganizer }: CreateTripFormProps) {
const router = useRouter();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);
const [priceDisplay, setPriceDisplay] = useState("");
const priceNumber = Number(parseRupiahInput(priceDisplay) || "0");
const isPaidTrip = priceNumber > 0;
const blockedByVerification = isPaidTrip && !isVerifiedOrganizer;
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setError("");
if (!startDate) {
setError("Tanggal berangkat harus diisi");
return;
}
setLoading(true);
const formData = new FormData(e.currentTarget);
formData.set("date", formatLocalCalendarYmd(startDate));
if (endDate) {
const startYmd = formatLocalCalendarYmd(startDate);
const endYmd = formatLocalCalendarYmd(endDate);
if (endYmd !== startYmd) {
formData.set("endDate", endYmd);
}
}
formData.set("price", parseRupiahInput(priceDisplay));
const result = await createTripAction(formData);
setLoading(false);
if (result.error) {
setError(result.error);
} else if (result.tripId) {
router.push(`/trips/${result.tripId}`);
}
}
function handleMountainSelect(e: React.ChangeEvent<HTMLSelectElement>) {
const selected = SAMPLE_MOUNTAINS.find((m) => m.name === e.target.value);
if (selected) {
const form = e.target.form;
if (form) {
const mountainInput = form.elements.namedItem(
"mountain"
) as HTMLInputElement;
const locationInput = form.elements.namedItem(
"location"
) as HTMLInputElement;
mountainInput.value = selected.name;
locationInput.value = selected.location;
}
}
}
function handleDateChange(dates: [Date | null, Date | null]) {
const [start, end] = dates;
setStartDate(start);
setEndDate(end);
}
function handlePriceChange(e: React.ChangeEvent<HTMLInputElement>) {
const raw = e.target.value.replace(/\D/g, "");
setPriceDisplay(raw ? formatRupiahInput(raw) : "");
}
return (
<div className="rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm">
{error && (
<div className="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm font-medium text-red-600">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-5">
{/* Mountain Quick Picker */}
<div className="rounded-xl bg-primary-50 p-4">
<label className="mb-2 flex items-center gap-1.5 text-sm font-bold text-primary-800">
<span>🏔</span> Pilih Gunung Jawa Barat
</label>
<select
onChange={handleMountainSelect}
className="w-full rounded-lg border border-primary-200 bg-white px-4 py-2.5 text-sm text-neutral-800"
defaultValue=""
>
<option value="" disabled>
Pilih gunung...
</option>
{SAMPLE_MOUNTAINS.map((m) => (
<option key={m.name} value={m.name}>
{m.name} {m.location}
</option>
))}
</select>
</div>
<div>
<label htmlFor="title" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Judul Trip
</label>
<input
id="title"
name="title"
type="text"
required
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="contoh: Open Trip Papandayan Weekend"
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label htmlFor="mountain" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Nama Gunung
</label>
<input
id="mountain"
name="mountain"
type="text"
required
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Gunung Papandayan"
/>
</div>
<div>
<label htmlFor="location" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Lokasi
</label>
<input
id="location"
name="location"
type="text"
required
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Garut, Jawa Barat"
/>
</div>
</div>
<div>
<label htmlFor="description" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Deskripsi
</label>
<textarea
id="description"
name="description"
rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Ringkasan trip, vibe, level kesulitan..."
/>
</div>
<div>
<label htmlFor="meetingPoint" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Meeting point
</label>
<input
id="meetingPoint"
name="meetingPoint"
type="text"
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="contoh: Alfamart Cicaheum, 05:00 WIB"
/>
</div>
<div>
<label htmlFor="itinerary" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Itinerary
</label>
<textarea
id="itinerary"
name="itinerary"
rows={5}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder={"Hari 1: …\nHari 2: …"}
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label htmlFor="whatsIncluded" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Termasuk
</label>
<textarea
id="whatsIncluded"
name="whatsIncluded"
rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Transport, konsumsi, tenda, …"
/>
</div>
<div>
<label htmlFor="whatsExcluded" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Tidak termasuk
</label>
<textarea
id="whatsExcluded"
name="whatsExcluded"
rows={4}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="Tiket masuk TN, sleeping bag, …"
/>
</div>
</div>
<ImageUrlInput />
{/* Date Range & Participants & Price */}
<div className="grid gap-4 sm:grid-cols-2">
{/* Date Range Picker */}
<div>
<label className="mb-1.5 block text-sm font-semibold text-neutral-700">
Tanggal berangkat pulang
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 z-10 -translate-y-1/2 text-neutral-400">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-4 w-4"
>
<path
fillRule="evenodd"
d="M5.75 2a.75.75 0 01.75.75V4h7V2.75a.75.75 0 011.5 0V4h.25A2.75 2.75 0 0118 6.75v8.5A2.75 2.75 0 0115.25 18H4.75A2.75 2.75 0 012 15.25v-8.5A2.75 2.75 0 014.75 4H5V2.75A.75.75 0 015.75 2zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75z"
clipRule="evenodd"
/>
</svg>
</span>
<DatePicker
selectsRange
startDate={startDate}
endDate={endDate}
onChange={handleDateChange}
minDate={new Date()}
placeholderText="Pilih tanggal..."
dateFormat="dd MMM yyyy"
isClearable
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-9 pr-3 text-sm text-neutral-800 placeholder:text-neutral-400 focus:border-primary-500 focus:bg-white"
/>
</div>
</div>
{/* Max Participants */}
<div>
<label htmlFor="maxParticipants" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Maks Peserta
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-4 w-4"
>
<path d="M7 8a3 3 0 100-6 3 3 0 000 6zM14.5 9a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM1.615 16.428a1.224 1.224 0 01-.569-1.175 6.002 6.002 0 0111.908 0c.058.467-.172.92-.57 1.174A9.953 9.953 0 017 18a9.953 9.953 0 01-5.385-1.572zM14.5 16h-.106c.07-.297.088-.611.048-.933a7.47 7.47 0 00-1.588-3.755 4.502 4.502 0 015.874 2.636.818.818 0 01-.36.98A7.465 7.465 0 0114.5 16z" />
</svg>
</span>
<input
id="maxParticipants"
name="maxParticipants"
type="number"
required
min={1}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-9 pr-4 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="10"
/>
</div>
</div>
</div>
{/* Price with Rp format */}
<div>
<label htmlFor="priceDisplay" className="mb-1.5 block text-sm font-semibold text-neutral-700">
Harga per Orang
{!isVerifiedOrganizer && (
<span className="ml-2 text-xs font-normal text-neutral-500">
(isi 0 untuk trip gratis)
</span>
)}
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-sm font-semibold text-neutral-500">
Rp
</span>
<input
id="priceDisplay"
type="text"
inputMode="numeric"
required
value={priceDisplay}
onChange={handlePriceChange}
className="w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-10 pr-4 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
placeholder="150.000"
/>
<input type="hidden" name="price" value={parseRupiahInput(priceDisplay)} />
</div>
{blockedByVerification && (
<p className="mt-2 text-xs font-medium text-amber-700">
Trip berbayar butuh verifikasi organizer terlebih dahulu.
</p>
)}
</div>
<button
type="submit"
disabled={loading || blockedByVerification}
className="w-full rounded-xl bg-primary-600 py-3 text-sm font-bold text-white shadow-lg shadow-primary-600/20 transition-colors hover:bg-primary-700 disabled:cursor-not-allowed disabled:opacity-50"
>
{loading
? "Membuat Trip..."
: blockedByVerification
? "Verifikasi diperlukan untuk trip berbayar"
: "Buat Trip"}
</button>
</form>
</div>
);
}
@@ -38,8 +38,11 @@ export function OrganizerTrustPanel({
</p> </p>
<div className="mt-1.5 flex flex-wrap gap-1.5"> <div className="mt-1.5 flex flex-wrap gap-1.5">
{trust.isVerified && ( {trust.isVerified && (
<span className="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-blue-800 sm:text-xs"> <span
Verified className="inline-flex items-center gap-1 rounded-full bg-primary-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary-800 sm:text-xs"
title="Identitas organizer telah diverifikasi (KTP & rekening)"
>
Verified Organizer
</span> </span>
)} )}
{trust.isTripLeader && ( {trust.isTripLeader && (
+12
View File
@@ -0,0 +1,12 @@
/** Daftar email admin (comma-separated, lowercase). */
function adminEmails(): string[] {
return (process.env.ADMIN_EMAILS ?? "")
.split(",")
.map((e) => e.trim().toLowerCase())
.filter(Boolean);
}
export function isAdminEmail(email: string | null | undefined): boolean {
if (!email) return false;
return adminEmails().includes(email.toLowerCase());
}
+6
View File
@@ -18,4 +18,10 @@ 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,
/** Verifikasi organizer (KTP + bank) */
MAX_ADDRESS_LENGTH: 500,
MAX_BANK_NAME_LENGTH: 60,
MAX_BANK_ACCOUNT_NUMBER_LENGTH: 32,
MAX_REJECTION_REASON_LENGTH: 500,
NIK_LENGTH: 16,
} as const; } as const;
+12
View File
@@ -1,2 +1,14 @@
/** Minimal trip sebagai organizer untuk badge "Trip leader" (heuristik MVP). */ /** Minimal trip sebagai organizer untuk badge "Trip leader" (heuristik MVP). */
export const TRIP_LEADER_MIN_TRIPS = 2; export const TRIP_LEADER_MIN_TRIPS = 2;
/** Bentuk data minimal untuk cek status verifikasi organizer. */
type WithOrganizerVerification = {
organizerVerification?: { status: "PENDING" | "APPROVED" | "REJECTED" } | null;
};
/** True kalau user punya OrganizerVerification berstatus APPROVED. */
export function isVerifiedOrganizer(
user: WithOrganizerVerification | null | undefined
): boolean {
return user?.organizerVerification?.status === "APPROVED";
}
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "acceptedAt" TIMESTAMP(3),
ADD COLUMN "acceptedTermsAndPrivacy" BOOLEAN NOT NULL DEFAULT false;
@@ -0,0 +1,38 @@
-- CreateEnum
CREATE TYPE "VerificationStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED');
-- CreateTable
CREATE TABLE "OrganizerVerification" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"nik" TEXT NOT NULL,
"birthDate" TIMESTAMP(3) NOT NULL,
"address" TEXT NOT NULL,
"ktpImageUrl" TEXT NOT NULL,
"selfieUrl" TEXT NOT NULL,
"bankName" TEXT NOT NULL,
"bankAccountNumber" TEXT NOT NULL,
"bankAccountName" TEXT NOT NULL,
"status" "VerificationStatus" NOT NULL DEFAULT 'PENDING',
"rejectionReason" TEXT,
"reviewedAt" TIMESTAMP(3),
"reviewedById" TEXT,
"verifiedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "OrganizerVerification_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "OrganizerVerification_userId_key" ON "OrganizerVerification"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "OrganizerVerification_nik_key" ON "OrganizerVerification"("nik");
-- AddForeignKey
ALTER TABLE "OrganizerVerification" ADD CONSTRAINT "OrganizerVerification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "OrganizerVerification" ADD CONSTRAINT "OrganizerVerification_reviewedById_fkey" FOREIGN KEY ("reviewedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `isVerified` on the `User` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "User" DROP COLUMN "isVerified";
+45 -2
View File
@@ -13,14 +13,57 @@ model User {
email String @unique email String @unique
password String password String
image String? image String?
/// Akun diverifikasi tim SeTrip (manual / admin) — tampil sebagai badge kepercayaan /// Apakah user telah menyetujui Syarat & Ketentuan dan Kebijakan Privasi
isVerified Boolean @default(false) acceptedTermsAndPrivacy Boolean @default(false)
/// Waktu user menyetujui Syarat & Ketentuan dan Kebijakan Privasi
acceptedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
trips Trip[] trips Trip[]
participations TripParticipant[] participations TripParticipant[]
tripReviews TripReview[] tripReviews TripReview[]
organizerVerification OrganizerVerification? @relation("OrganizerVerificationOwner")
reviewedVerifications OrganizerVerification[] @relation("OrganizerVerificationReviewer")
}
model OrganizerVerification {
id String @id @default(cuid())
userId String @unique
user User @relation("OrganizerVerificationOwner", fields: [userId], references: [id], onDelete: Cascade)
/// Nama lengkap sesuai KTP
fullName String
/// Nomor Induk Kependudukan (PII — perlakukan sensitif)
nik String @unique
birthDate DateTime
address String
/// URL foto KTP (untuk MVP pakai hosting; pindah ke storage privat untuk produksi)
ktpImageUrl String
/// URL selfie memegang KTP
selfieUrl String
bankName String
bankAccountNumber String
bankAccountName String
status VerificationStatus @default(PENDING)
rejectionReason String?
reviewedAt DateTime?
reviewedById String?
reviewedBy User? @relation("OrganizerVerificationReviewer", fields: [reviewedById], references: [id])
verifiedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum VerificationStatus {
PENDING
APPROVED
REJECTED
} }
model Trip { model Trip {
+40 -2
View File
@@ -16,6 +16,7 @@ async function main() {
await prisma.tripParticipant.deleteMany(); await prisma.tripParticipant.deleteMany();
await prisma.tripImage.deleteMany(); await prisma.tripImage.deleteMany();
await prisma.trip.deleteMany(); await prisma.trip.deleteMany();
await prisma.organizerVerification.deleteMany();
await prisma.user.deleteMany(); await prisma.user.deleteMany();
// ==================== USERS ==================== // ==================== USERS ====================
@@ -28,7 +29,6 @@ async function main() {
name: "Dede Inoen", name: "Dede Inoen",
email: "dede.inoen@setrip.id", email: "dede.inoen@setrip.id",
password, password,
isVerified: true,
}, },
}); });
@@ -37,7 +37,6 @@ async function main() {
name: "Panji Petualang", name: "Panji Petualang",
email: "panji@setrip.id", email: "panji@setrip.id",
password, password,
isVerified: true,
}, },
}); });
@@ -95,6 +94,45 @@ async function main() {
console.log(" Peserta: budi, sari, doni, maya, raka @gmail.com"); console.log(" Peserta: budi, sari, doni, maya, raka @gmail.com");
console.log(" Password semua: password123\n"); console.log(" Password semua: password123\n");
// ==================== ORGANIZER VERIFICATIONS ====================
const verifiedAt = new Date();
await prisma.organizerVerification.createMany({
data: [
{
userId: dede.id,
fullName: "Dede Inoen",
nik: "3201010101010001",
birthDate: new Date(Date.UTC(1990, 0, 1)),
address: "Jl. Pendaki No. 1, Garut, Jawa Barat",
ktpImageUrl: "https://placehold.co/600x400/png?text=KTP+Dede",
selfieUrl: "https://placehold.co/600x400/png?text=Selfie+Dede",
bankName: "BCA",
bankAccountNumber: "1234567890",
bankAccountName: "Dede Inoen",
status: "APPROVED",
reviewedAt: verifiedAt,
verifiedAt,
},
{
userId: panji.id,
fullName: "Panji Petualang",
nik: "3201010101010002",
birthDate: new Date(Date.UTC(1985, 5, 15)),
address: "Jl. Adventure No. 7, Kuningan, Jawa Barat",
ktpImageUrl: "https://placehold.co/600x400/png?text=KTP+Panji",
selfieUrl: "https://placehold.co/600x400/png?text=Selfie+Panji",
bankName: "Mandiri",
bankAccountNumber: "9876543210",
bankAccountName: "Panji Petualang",
status: "APPROVED",
reviewedAt: verifiedAt,
verifiedAt,
},
],
});
console.log("✅ OrganizerVerification (APPROVED) untuk Dede & Panji\n");
// ==================== TRIPS + IMAGES ==================== // ==================== TRIPS + IMAGES ====================
/** /**
* Tanggal disimpan eksplisit di UTC agar filter `from`/`to` (YYYY-MM-DD UTC) * Tanggal disimpan eksplisit di UTC agar filter `from`/`to` (YYYY-MM-DD UTC)
+59
View File
@@ -0,0 +1,59 @@
import { prisma } from "@/lib/prisma";
import { Prisma } from "@/app/generated/prisma/client";
export const organizerRepo = {
async findByUserId(userId: string) {
return prisma.organizerVerification.findUnique({ where: { userId } });
},
async findById(id: string) {
return prisma.organizerVerification.findUnique({ where: { id } });
},
async findByNik(nik: string) {
return prisma.organizerVerification.findUnique({ where: { nik } });
},
async upsertForUser(
userId: string,
data: Omit<Prisma.OrganizerVerificationUncheckedCreateInput, "id" | "userId" | "createdAt" | "updatedAt">
) {
return prisma.organizerVerification.upsert({
where: { userId },
create: { userId, ...data },
update: data,
});
},
async listByStatus(status?: "PENDING" | "APPROVED" | "REJECTED") {
return prisma.organizerVerification.findMany({
where: status ? { status } : undefined,
orderBy: { createdAt: "desc" },
include: {
user: { select: { id: true, name: true, email: true } },
reviewedBy: { select: { id: true, name: true, email: true } },
},
});
},
async updateReview(
id: string,
data: {
status: "APPROVED" | "REJECTED";
rejectionReason?: string | null;
reviewedById: string;
}
) {
const now = new Date();
return prisma.organizerVerification.update({
where: { id },
data: {
status: data.status,
rejectionReason: data.rejectionReason ?? null,
reviewedById: data.reviewedById,
reviewedAt: now,
verifiedAt: data.status === "APPROVED" ? now : null,
},
});
},
};
+1 -1
View File
@@ -98,7 +98,7 @@ export const tripRepo = {
name: true, name: true,
email: true, email: true,
image: true, image: true,
isVerified: true, organizerVerification: { select: { status: true } },
}, },
}, },
images: { orderBy: { order: "asc" } }, images: { orderBy: { order: "asc" } },
+8 -1
View File
@@ -2,7 +2,12 @@ import bcrypt from "bcryptjs";
import { userRepo } from "@/server/repositories/user.repo"; import { userRepo } from "@/server/repositories/user.repo";
export const authService = { export const authService = {
async register(data: { name: string; email: string; password: string }) { async register(data: {
name: string;
email: string;
password: string;
acceptedTermsAndPrivacy: boolean;
}) {
const existing = await userRepo.findByEmail(data.email); const existing = await userRepo.findByEmail(data.email);
if (existing) { if (existing) {
throw new Error("Email sudah terdaftar"); throw new Error("Email sudah terdaftar");
@@ -14,6 +19,8 @@ export const authService = {
name: data.name, name: data.name,
email: data.email, email: data.email,
password: hashedPassword, password: hashedPassword,
acceptedTermsAndPrivacy: data.acceptedTermsAndPrivacy,
acceptedAt: data.acceptedTermsAndPrivacy ? new Date() : null,
}); });
return { id: user.id, name: user.name, email: user.email }; return { id: user.id, name: user.name, email: user.email };
+77
View File
@@ -0,0 +1,77 @@
import { organizerRepo } from "@/server/repositories/organizer.repo";
type SubmitInput = {
fullName: string;
nik: string;
birthDate: Date;
address: string;
ktpImageUrl: string;
selfieUrl: string;
bankName: string;
bankAccountNumber: string;
bankAccountName: string;
};
export const organizerService = {
async submitVerification(userId: string, data: SubmitInput) {
const existing = await organizerRepo.findByUserId(userId);
if (existing && existing.status === "APPROVED") {
throw new Error("Akun kamu sudah terverifikasi");
}
if (existing && existing.status === "PENDING") {
throw new Error("Pengajuan kamu masih dalam proses review");
}
const dupNik = await organizerRepo.findByNik(data.nik);
if (dupNik && dupNik.userId !== userId) {
throw new Error("NIK ini sudah dipakai akun lain");
}
return organizerRepo.upsertForUser(userId, {
fullName: data.fullName,
nik: data.nik,
birthDate: data.birthDate,
address: data.address,
ktpImageUrl: data.ktpImageUrl,
selfieUrl: data.selfieUrl,
bankName: data.bankName,
bankAccountNumber: data.bankAccountNumber,
bankAccountName: data.bankAccountName,
status: "PENDING",
rejectionReason: null,
reviewedAt: null,
reviewedById: null,
verifiedAt: null,
});
},
async reviewVerification(input: {
verificationId: string;
decision: "APPROVED" | "REJECTED";
rejectionReason?: string;
reviewerId: string;
}) {
const verification = await organizerRepo.findById(input.verificationId);
if (!verification) {
throw new Error("Pengajuan tidak ditemukan");
}
if (verification.status !== "PENDING") {
throw new Error("Pengajuan ini sudah diproses");
}
return organizerRepo.updateReview(input.verificationId, {
status: input.decision,
rejectionReason: input.decision === "REJECTED" ? input.rejectionReason ?? null : null,
reviewedById: input.reviewerId,
});
},
async getStatusForUser(userId: string) {
return organizerRepo.findByUserId(userId);
},
async isApproved(userId: string) {
const v = await organizerRepo.findByUserId(userId);
return v?.status === "APPROVED";
},
};
+6 -6
View File
@@ -11,11 +11,7 @@ export type OrganizerTrust = {
export const trustService = { export const trustService = {
async getOrganizerTrust(organizerId: string): Promise<OrganizerTrust> { async getOrganizerTrust(organizerId: string): Promise<OrganizerTrust> {
const [user, tripsCreated, reviewAgg] = await Promise.all([ const [tripsCreated, reviewAgg, organizerVerification] = await Promise.all([
prisma.user.findUnique({
where: { id: organizerId },
select: { isVerified: true },
}),
prisma.trip.count({ where: { organizerId } }), prisma.trip.count({ where: { organizerId } }),
prisma.tripReview.aggregate({ prisma.tripReview.aggregate({
where: { where: {
@@ -24,11 +20,15 @@ export const trustService = {
_avg: { rating: true }, _avg: { rating: true },
_count: { _all: true }, _count: { _all: true },
}), }),
prisma.organizerVerification.findUnique({
where: { userId: organizerId },
select: { status: true },
}),
]); ]);
const avg = reviewAgg._avg.rating; const avg = reviewAgg._avg.rating;
return { return {
isVerified: user?.isVerified ?? false, isVerified: organizerVerification?.status === "APPROVED",
tripsCreated, tripsCreated,
avgRating: avgRating:
avg != null ? Math.round(Number(avg) * 10) / 10 : null, avg != null ? Math.round(Number(avg) * 10) / 10 : null,