kyc user and upload partial update encrypt nik and picture
This commit is contained in:
+8
-1
@@ -230,10 +230,17 @@ Alur data mengikuti pola yang sama: **UI (`app/`) → server actions (`features/
|
||||
|
||||
### 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`).
|
||||
- **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
|
||||
|
||||
+175
@@ -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**
|
||||
@@ -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.
|
||||
|
||||
**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
|
||||
|
||||
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 |
|
||||
| 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
|
||||
|
||||
|
||||
@@ -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**
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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 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";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { organizerService } from "@/server/services/organizer.service";
|
||||
import { CreateTripForm } from "@/features/trip/components/create-trip-form";
|
||||
|
||||
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, "");
|
||||
}
|
||||
|
||||
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("");
|
||||
export default async function CreateTripPage() {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user) {
|
||||
return (
|
||||
@@ -51,7 +18,7 @@ export default function CreateTripPage() {
|
||||
Kamu harus login untuk membuat trip.
|
||||
</p>
|
||||
<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"
|
||||
>
|
||||
Login
|
||||
@@ -61,69 +28,8 @@ export default function CreateTripPage() {
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
// 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) : "");
|
||||
}
|
||||
const verification = await organizerService.getStatusForUser(session.user.id);
|
||||
const isVerifiedOrganizer = verification?.status === "APPROVED";
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl px-4 py-6 sm:py-8">
|
||||
@@ -134,243 +40,54 @@ export default function CreateTripPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{!isVerifiedOrganizer && (
|
||||
<VerificationBanner status={verification?.status ?? null} />
|
||||
)}
|
||||
|
||||
<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=""
|
||||
<CreateTripForm isVerifiedOrganizer={isVerifiedOrganizer} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VerificationBanner({
|
||||
status,
|
||||
}: {
|
||||
status: "PENDING" | "APPROVED" | "REJECTED" | null;
|
||||
}) {
|
||||
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>
|
||||
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
|
||||
</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>
|
||||
{isRejected ? "Ajukan Ulang" : "Verifikasi Sekarang"}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -22,6 +22,11 @@ export * from './enums';
|
||||
*
|
||||
*/
|
||||
export type User = Prisma.UserModel
|
||||
/**
|
||||
* Model OrganizerVerification
|
||||
*
|
||||
*/
|
||||
export type OrganizerVerification = Prisma.OrganizerVerificationModel
|
||||
/**
|
||||
* Model Trip
|
||||
*
|
||||
|
||||
@@ -46,6 +46,11 @@ export { Prisma }
|
||||
*
|
||||
*/
|
||||
export type User = Prisma.UserModel
|
||||
/**
|
||||
* Model OrganizerVerification
|
||||
*
|
||||
*/
|
||||
export type OrganizerVerification = Prisma.OrganizerVerificationModel
|
||||
/**
|
||||
* Model Trip
|
||||
*
|
||||
|
||||
@@ -49,6 +49,17 @@ export type BoolFilter<$PrismaModel = never> = {
|
||||
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> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
@@ -109,6 +120,20 @@ export type BoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_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> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
@@ -123,15 +148,21 @@ export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
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 EnumVerificationStatusFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.VerificationStatus | Prisma.EnumVerificationStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumVerificationStatusFilter<$PrismaModel> | $Enums.VerificationStatus
|
||||
}
|
||||
|
||||
export type EnumVerificationStatusWithAggregatesFilter<$PrismaModel = never> = {
|
||||
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> = {
|
||||
@@ -152,20 +183,6 @@ export type EnumTripStatusFilter<$PrismaModel = never> = {
|
||||
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> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
@@ -242,6 +259,17 @@ export type NestedBoolFilter<$PrismaModel = never> = {
|
||||
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> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
@@ -317,6 +345,20 @@ export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_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> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||
@@ -331,15 +373,21 @@ export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
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 NestedEnumVerificationStatusFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.VerificationStatus | Prisma.EnumVerificationStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
|
||||
notIn?: $Enums.VerificationStatus[] | Prisma.ListEnumVerificationStatusFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedEnumVerificationStatusFilter<$PrismaModel> | $Enums.VerificationStatus
|
||||
}
|
||||
|
||||
export type NestedEnumVerificationStatusWithAggregatesFilter<$PrismaModel = never> = {
|
||||
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> = {
|
||||
@@ -349,20 +397,6 @@ export type NestedEnumTripStatusFilter<$PrismaModel = never> = {
|
||||
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> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
* 🟢 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 = {
|
||||
OPEN: 'OPEN',
|
||||
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 = {
|
||||
User: 'User',
|
||||
OrganizerVerification: 'OrganizerVerification',
|
||||
Trip: 'Trip',
|
||||
TripReview: 'TripReview',
|
||||
TripImage: 'TripImage',
|
||||
@@ -404,7 +405,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
omit: GlobalOmitOptions
|
||||
}
|
||||
meta: {
|
||||
modelProps: "user" | "trip" | "tripReview" | "tripImage" | "tripParticipant"
|
||||
modelProps: "user" | "organizerVerification" | "trip" | "tripReview" | "tripImage" | "tripParticipant"
|
||||
txIsolationLevel: TransactionIsolationLevel
|
||||
}
|
||||
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: {
|
||||
payload: Prisma.$TripPayload<ExtArgs>
|
||||
fields: Prisma.TripFieldRefs
|
||||
@@ -823,7 +898,8 @@ export const UserScalarFieldEnum = {
|
||||
email: 'email',
|
||||
password: 'password',
|
||||
image: 'image',
|
||||
isVerified: 'isVerified',
|
||||
acceptedTermsAndPrivacy: 'acceptedTermsAndPrivacy',
|
||||
acceptedAt: 'acceptedAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
@@ -831,6 +907,30 @@ export const 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 = {
|
||||
id: 'id',
|
||||
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'
|
||||
*/
|
||||
@@ -1107,6 +1221,7 @@ export type PrismaClientOptions = ({
|
||||
}
|
||||
export type GlobalOmitConfig = {
|
||||
user?: Prisma.UserOmit
|
||||
organizerVerification?: Prisma.OrganizerVerificationOmit
|
||||
trip?: Prisma.TripOmit
|
||||
tripReview?: Prisma.TripReviewOmit
|
||||
tripImage?: Prisma.TripImageOmit
|
||||
|
||||
@@ -52,6 +52,7 @@ export const AnyNull = runtime.AnyNull
|
||||
|
||||
export const ModelName = {
|
||||
User: 'User',
|
||||
OrganizerVerification: 'OrganizerVerification',
|
||||
Trip: 'Trip',
|
||||
TripReview: 'TripReview',
|
||||
TripImage: 'TripImage',
|
||||
@@ -80,7 +81,8 @@ export const UserScalarFieldEnum = {
|
||||
email: 'email',
|
||||
password: 'password',
|
||||
image: 'image',
|
||||
isVerified: 'isVerified',
|
||||
acceptedTermsAndPrivacy: 'acceptedTermsAndPrivacy',
|
||||
acceptedAt: 'acceptedAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
} as const
|
||||
@@ -88,6 +90,30 @@ export const 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 = {
|
||||
id: 'id',
|
||||
title: 'title',
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
export type * from './models/User'
|
||||
export type * from './models/OrganizerVerification'
|
||||
export type * from './models/Trip'
|
||||
export type * from './models/TripReview'
|
||||
export type * from './models/TripImage'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -699,10 +699,6 @@ export type TripUncheckedUpdateManyWithoutOrganizerNestedInput = {
|
||||
deleteMany?: Prisma.TripScalarWhereInput | Prisma.TripScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type NullableDateTimeFieldUpdateOperationsInput = {
|
||||
set?: Date | string | null
|
||||
}
|
||||
|
||||
export type IntFieldUpdateOperationsInput = {
|
||||
set?: number
|
||||
increment?: number
|
||||
|
||||
@@ -30,7 +30,8 @@ export type UserMinAggregateOutputType = {
|
||||
email: string | null
|
||||
password: string | null
|
||||
image: string | null
|
||||
isVerified: boolean | null
|
||||
acceptedTermsAndPrivacy: boolean | null
|
||||
acceptedAt: Date | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
}
|
||||
@@ -41,7 +42,8 @@ export type UserMaxAggregateOutputType = {
|
||||
email: string | null
|
||||
password: string | null
|
||||
image: string | null
|
||||
isVerified: boolean | null
|
||||
acceptedTermsAndPrivacy: boolean | null
|
||||
acceptedAt: Date | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
}
|
||||
@@ -52,7 +54,8 @@ export type UserCountAggregateOutputType = {
|
||||
email: number
|
||||
password: number
|
||||
image: number
|
||||
isVerified: number
|
||||
acceptedTermsAndPrivacy: number
|
||||
acceptedAt: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
_all: number
|
||||
@@ -65,7 +68,8 @@ export type UserMinAggregateInputType = {
|
||||
email?: true
|
||||
password?: true
|
||||
image?: true
|
||||
isVerified?: true
|
||||
acceptedTermsAndPrivacy?: true
|
||||
acceptedAt?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
}
|
||||
@@ -76,7 +80,8 @@ export type UserMaxAggregateInputType = {
|
||||
email?: true
|
||||
password?: true
|
||||
image?: true
|
||||
isVerified?: true
|
||||
acceptedTermsAndPrivacy?: true
|
||||
acceptedAt?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
}
|
||||
@@ -87,7 +92,8 @@ export type UserCountAggregateInputType = {
|
||||
email?: true
|
||||
password?: true
|
||||
image?: true
|
||||
isVerified?: true
|
||||
acceptedTermsAndPrivacy?: true
|
||||
acceptedAt?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
_all?: true
|
||||
@@ -171,7 +177,8 @@ export type UserGroupByOutputType = {
|
||||
email: string
|
||||
password: string
|
||||
image: string | null
|
||||
isVerified: boolean
|
||||
acceptedTermsAndPrivacy: boolean
|
||||
acceptedAt: Date | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
_count: UserCountAggregateOutputType | null
|
||||
@@ -203,12 +210,15 @@ export type UserWhereInput = {
|
||||
email?: Prisma.StringFilter<"User"> | string
|
||||
password?: Prisma.StringFilter<"User"> | string
|
||||
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
|
||||
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
trips?: Prisma.TripListRelationFilter
|
||||
participations?: Prisma.TripParticipantListRelationFilter
|
||||
tripReviews?: Prisma.TripReviewListRelationFilter
|
||||
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
|
||||
}
|
||||
|
||||
export type UserOrderByWithRelationInput = {
|
||||
@@ -217,12 +227,15 @@ export type UserOrderByWithRelationInput = {
|
||||
email?: Prisma.SortOrder
|
||||
password?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
isVerified?: Prisma.SortOrder
|
||||
acceptedTermsAndPrivacy?: Prisma.SortOrder
|
||||
acceptedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
trips?: Prisma.TripOrderByRelationAggregateInput
|
||||
participations?: Prisma.TripParticipantOrderByRelationAggregateInput
|
||||
tripReviews?: Prisma.TripReviewOrderByRelationAggregateInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationOrderByWithRelationInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationOrderByRelationAggregateInput
|
||||
}
|
||||
|
||||
export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||
@@ -234,12 +247,15 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||
name?: Prisma.StringFilter<"User"> | string
|
||||
password?: Prisma.StringFilter<"User"> | string
|
||||
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
|
||||
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
trips?: Prisma.TripListRelationFilter
|
||||
participations?: Prisma.TripParticipantListRelationFilter
|
||||
tripReviews?: Prisma.TripReviewListRelationFilter
|
||||
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
|
||||
}, "id" | "email">
|
||||
|
||||
export type UserOrderByWithAggregationInput = {
|
||||
@@ -248,7 +264,8 @@ export type UserOrderByWithAggregationInput = {
|
||||
email?: Prisma.SortOrder
|
||||
password?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
isVerified?: Prisma.SortOrder
|
||||
acceptedTermsAndPrivacy?: Prisma.SortOrder
|
||||
acceptedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
_count?: Prisma.UserCountOrderByAggregateInput
|
||||
@@ -265,7 +282,8 @@ export type UserScalarWhereWithAggregatesInput = {
|
||||
email?: Prisma.StringWithAggregatesFilter<"User"> | string
|
||||
password?: Prisma.StringWithAggregatesFilter<"User"> | string
|
||||
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
|
||||
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string
|
||||
}
|
||||
@@ -276,12 +294,15 @@ export type UserCreateInput = {
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
|
||||
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateInput = {
|
||||
@@ -290,12 +311,15 @@ export type UserUncheckedCreateInput = {
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
|
||||
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||
}
|
||||
|
||||
export type UserUpdateInput = {
|
||||
@@ -304,12 +328,15 @@ export type UserUpdateInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
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
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateInput = {
|
||||
@@ -318,12 +345,15 @@ export type UserUncheckedUpdateInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
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
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateManyInput = {
|
||||
@@ -332,7 +362,8 @@ export type UserCreateManyInput = {
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
@@ -343,7 +374,8 @@ export type UserUpdateManyMutationInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -354,7 +386,8 @@ export type UserUncheckedUpdateManyInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
@@ -365,7 +398,8 @@ export type UserCountOrderByAggregateInput = {
|
||||
email?: Prisma.SortOrder
|
||||
password?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrder
|
||||
isVerified?: Prisma.SortOrder
|
||||
acceptedTermsAndPrivacy?: Prisma.SortOrder
|
||||
acceptedAt?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
@@ -376,7 +410,8 @@ export type UserMaxOrderByAggregateInput = {
|
||||
email?: Prisma.SortOrder
|
||||
password?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrder
|
||||
isVerified?: Prisma.SortOrder
|
||||
acceptedTermsAndPrivacy?: Prisma.SortOrder
|
||||
acceptedAt?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
@@ -387,7 +422,8 @@ export type UserMinOrderByAggregateInput = {
|
||||
email?: Prisma.SortOrder
|
||||
password?: Prisma.SortOrder
|
||||
image?: Prisma.SortOrder
|
||||
isVerified?: Prisma.SortOrder
|
||||
acceptedTermsAndPrivacy?: Prisma.SortOrder
|
||||
acceptedAt?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
}
|
||||
@@ -397,6 +433,11 @@ export type UserScalarRelationFilter = {
|
||||
isNot?: Prisma.UserWhereInput
|
||||
}
|
||||
|
||||
export type UserNullableScalarRelationFilter = {
|
||||
is?: Prisma.UserWhereInput | null
|
||||
isNot?: Prisma.UserWhereInput | null
|
||||
}
|
||||
|
||||
export type StringFieldUpdateOperationsInput = {
|
||||
set?: string
|
||||
}
|
||||
@@ -409,10 +450,44 @@ export type BoolFieldUpdateOperationsInput = {
|
||||
set?: boolean
|
||||
}
|
||||
|
||||
export type NullableDateTimeFieldUpdateOperationsInput = {
|
||||
set?: Date | string | null
|
||||
}
|
||||
|
||||
export type DateTimeFieldUpdateOperationsInput = {
|
||||
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 = {
|
||||
create?: Prisma.XOR<Prisma.UserCreateWithoutTripsInput, Prisma.UserUncheckedCreateWithoutTripsInput>
|
||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutTripsInput
|
||||
@@ -455,17 +530,180 @@ export type UserUpdateOneRequiredWithoutParticipationsNestedInput = {
|
||||
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 = {
|
||||
id?: string
|
||||
name: string
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutTripsInput = {
|
||||
@@ -474,11 +712,14 @@ export type UserUncheckedCreateWithoutTripsInput = {
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutTripsInput = {
|
||||
@@ -503,11 +744,14 @@ export type UserUpdateWithoutTripsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
|
||||
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutTripsInput = {
|
||||
@@ -516,11 +760,14 @@ export type UserUncheckedUpdateWithoutTripsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
|
||||
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateWithoutTripReviewsInput = {
|
||||
@@ -529,11 +776,14 @@ export type UserCreateWithoutTripReviewsInput = {
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
|
||||
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutTripReviewsInput = {
|
||||
@@ -542,11 +792,14 @@ export type UserUncheckedCreateWithoutTripReviewsInput = {
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
|
||||
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutTripReviewsInput = {
|
||||
@@ -571,11 +824,14 @@ export type UserUpdateWithoutTripReviewsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
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
|
||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutTripReviewsInput = {
|
||||
@@ -584,11 +840,14 @@ export type UserUncheckedUpdateWithoutTripReviewsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
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
|
||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateWithoutParticipationsInput = {
|
||||
@@ -597,11 +856,14 @@ export type UserCreateWithoutParticipationsInput = {
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
|
||||
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutParticipationsInput = {
|
||||
@@ -610,11 +872,14 @@ export type UserUncheckedCreateWithoutParticipationsInput = {
|
||||
email: string
|
||||
password: string
|
||||
image?: string | null
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: Date | string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
|
||||
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutParticipationsInput = {
|
||||
@@ -639,11 +904,14 @@ export type UserUpdateWithoutParticipationsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
|
||||
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutParticipationsInput = {
|
||||
@@ -652,11 +920,14 @@ export type UserUncheckedUpdateWithoutParticipationsInput = {
|
||||
email?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
isVerified?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
|
||||
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
|
||||
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
|
||||
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
|
||||
}
|
||||
|
||||
|
||||
@@ -668,12 +939,14 @@ export type UserCountOutputType = {
|
||||
trips: number
|
||||
participations: number
|
||||
tripReviews: number
|
||||
reviewedVerifications: number
|
||||
}
|
||||
|
||||
export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
trips?: boolean | UserCountOutputTypeCountTripsArgs
|
||||
participations?: boolean | UserCountOutputTypeCountParticipationsArgs
|
||||
tripReviews?: boolean | UserCountOutputTypeCountTripReviewsArgs
|
||||
reviewedVerifications?: boolean | UserCountOutputTypeCountReviewedVerificationsArgs
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -707,6 +980,13 @@ export type UserCountOutputTypeCountTripReviewsArgs<ExtArgs extends runtime.Type
|
||||
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<{
|
||||
id?: boolean
|
||||
@@ -714,12 +994,15 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
||||
email?: boolean
|
||||
password?: boolean
|
||||
image?: boolean
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
trips?: boolean | Prisma.User$tripsArgs<ExtArgs>
|
||||
participations?: boolean | Prisma.User$participationsArgs<ExtArgs>
|
||||
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
|
||||
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
|
||||
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["user"]>
|
||||
|
||||
@@ -729,7 +1012,8 @@ export type UserSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensio
|
||||
email?: boolean
|
||||
password?: boolean
|
||||
image?: boolean
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
}, ExtArgs["result"]["user"]>
|
||||
@@ -740,7 +1024,8 @@ export type UserSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensio
|
||||
email?: boolean
|
||||
password?: boolean
|
||||
image?: boolean
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
}, ExtArgs["result"]["user"]>
|
||||
@@ -751,16 +1036,19 @@ export type UserSelectScalar = {
|
||||
email?: boolean
|
||||
password?: boolean
|
||||
image?: boolean
|
||||
isVerified?: boolean
|
||||
acceptedTermsAndPrivacy?: boolean
|
||||
acceptedAt?: boolean
|
||||
createdAt?: 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> = {
|
||||
trips?: boolean | Prisma.User$tripsArgs<ExtArgs>
|
||||
participations?: boolean | Prisma.User$participationsArgs<ExtArgs>
|
||||
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
|
||||
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
|
||||
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}
|
||||
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>[]
|
||||
participations: Prisma.$TripParticipantPayload<ExtArgs>[]
|
||||
tripReviews: Prisma.$TripReviewPayload<ExtArgs>[]
|
||||
organizerVerification: Prisma.$OrganizerVerificationPayload<ExtArgs> | null
|
||||
reviewedVerifications: Prisma.$OrganizerVerificationPayload<ExtArgs>[]
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
@@ -780,9 +1070,13 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||
password: string
|
||||
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
|
||||
updatedAt: Date
|
||||
}, 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>
|
||||
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>
|
||||
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.
|
||||
* @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 password: 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 updatedAt: Prisma.FieldRef<"User", 'DateTime'>
|
||||
}
|
||||
@@ -1683,6 +1980,49 @@ export type User$tripReviewsArgs<ExtArgs extends runtime.Types.Extensions.Intern
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 & 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 & Ketentuan
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -140,6 +140,37 @@ export default function RegisterPage() {
|
||||
/>
|
||||
</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 & 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
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
|
||||
@@ -30,6 +30,18 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
changeFrequency: "yearly",
|
||||
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) => ({
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 & 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 & 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 & 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 & 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 & 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 & Ketentuan
|
||||
</h2>
|
||||
<p className="mb-3">
|
||||
SeTrip dapat memperbarui Syarat & 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 & 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 & 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -50,6 +50,12 @@ export function Navbar() {
|
||||
>
|
||||
Profil
|
||||
</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">
|
||||
<Link
|
||||
href="/profile"
|
||||
@@ -136,6 +142,13 @@ export function Navbar() {
|
||||
>
|
||||
Profil
|
||||
</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">
|
||||
<Link
|
||||
href="/profile"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
@@ -9,6 +9,7 @@ export async function registerAction(formData: FormData) {
|
||||
email: formData.get("email") as string,
|
||||
password: formData.get("password") as string,
|
||||
confirmPassword: formData.get("confirmPassword") as string,
|
||||
acceptedTermsAndPrivacy: formData.get("acceptedTermsAndPrivacy") === "on",
|
||||
};
|
||||
|
||||
const result = registerSchema.safeParse(raw);
|
||||
@@ -21,6 +22,7 @@ export async function registerAction(formData: FormData) {
|
||||
name: result.data.name,
|
||||
email: result.data.email,
|
||||
password: result.data.password,
|
||||
acceptedTermsAndPrivacy: result.data.acceptedTermsAndPrivacy,
|
||||
});
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
|
||||
@@ -29,6 +29,9 @@ export const registerSchema = z.object({
|
||||
.min(6, "Password minimal 6 karakter")
|
||||
.max(LIMITS.MAX_PASSWORD_LENGTH, "Password terlalu panjang (maks. 72 karakter)"),
|
||||
confirmPassword: z.string(),
|
||||
acceptedTermsAndPrivacy: z.literal(true, {
|
||||
error: "Kamu harus menyetujui Syarat & Ketentuan dan Kebijakan Privasi",
|
||||
}),
|
||||
}).refine((data) => data.password === data.confirmPassword, {
|
||||
message: "Password tidak cocok",
|
||||
path: ["confirmPassword"],
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -4,6 +4,7 @@ import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { createTripSchema, tripImageUrlsSchema } from "./schemas";
|
||||
import { tripService } from "@/server/services/trip.service";
|
||||
import { organizerService } from "@/server/services/organizer.service";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { tripStoredInstantFromYmd } from "@/lib/trip-dates";
|
||||
|
||||
@@ -33,6 +34,16 @@ export async function createTripAction(formData: FormData) {
|
||||
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
|
||||
.getAll("imageUrls")
|
||||
.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>
|
||||
<div className="mt-1.5 flex flex-wrap gap-1.5">
|
||||
{trust.isVerified && (
|
||||
<span className="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-blue-800 sm:text-xs">
|
||||
Verified
|
||||
<span
|
||||
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>
|
||||
)}
|
||||
{trust.isTripLeader && (
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -18,4 +18,10 @@ export const LIMITS = {
|
||||
MAX_URL_LENGTH: 2048,
|
||||
MAX_NAME_LENGTH: 80,
|
||||
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;
|
||||
|
||||
@@ -1,2 +1,14 @@
|
||||
/** Minimal trip sebagai organizer untuk badge "Trip leader" (heuristik MVP). */
|
||||
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;
|
||||
+8
@@ -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
@@ -13,14 +13,57 @@ model User {
|
||||
email String @unique
|
||||
password String
|
||||
image String?
|
||||
/// Akun diverifikasi tim SeTrip (manual / admin) — tampil sebagai badge kepercayaan
|
||||
isVerified Boolean @default(false)
|
||||
/// Apakah user telah menyetujui Syarat & Ketentuan dan Kebijakan Privasi
|
||||
acceptedTermsAndPrivacy Boolean @default(false)
|
||||
/// Waktu user menyetujui Syarat & Ketentuan dan Kebijakan Privasi
|
||||
acceptedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
trips Trip[]
|
||||
participations TripParticipant[]
|
||||
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 {
|
||||
|
||||
+40
-2
@@ -16,6 +16,7 @@ async function main() {
|
||||
await prisma.tripParticipant.deleteMany();
|
||||
await prisma.tripImage.deleteMany();
|
||||
await prisma.trip.deleteMany();
|
||||
await prisma.organizerVerification.deleteMany();
|
||||
await prisma.user.deleteMany();
|
||||
|
||||
// ==================== USERS ====================
|
||||
@@ -28,7 +29,6 @@ async function main() {
|
||||
name: "Dede Inoen",
|
||||
email: "dede.inoen@setrip.id",
|
||||
password,
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,7 +37,6 @@ async function main() {
|
||||
name: "Panji Petualang",
|
||||
email: "panji@setrip.id",
|
||||
password,
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -95,6 +94,45 @@ async function main() {
|
||||
console.log(" Peserta: budi, sari, doni, maya, raka @gmail.com");
|
||||
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 ====================
|
||||
/**
|
||||
* Tanggal disimpan eksplisit di UTC agar filter `from`/`to` (YYYY-MM-DD UTC)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -98,7 +98,7 @@ export const tripRepo = {
|
||||
name: true,
|
||||
email: true,
|
||||
image: true,
|
||||
isVerified: true,
|
||||
organizerVerification: { select: { status: true } },
|
||||
},
|
||||
},
|
||||
images: { orderBy: { order: "asc" } },
|
||||
|
||||
@@ -2,7 +2,12 @@ import bcrypt from "bcryptjs";
|
||||
import { userRepo } from "@/server/repositories/user.repo";
|
||||
|
||||
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);
|
||||
if (existing) {
|
||||
throw new Error("Email sudah terdaftar");
|
||||
@@ -14,6 +19,8 @@ export const authService = {
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
password: hashedPassword,
|
||||
acceptedTermsAndPrivacy: data.acceptedTermsAndPrivacy,
|
||||
acceptedAt: data.acceptedTermsAndPrivacy ? new Date() : null,
|
||||
});
|
||||
|
||||
return { id: user.id, name: user.name, email: user.email };
|
||||
|
||||
@@ -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";
|
||||
},
|
||||
};
|
||||
@@ -11,11 +11,7 @@ export type OrganizerTrust = {
|
||||
|
||||
export const trustService = {
|
||||
async getOrganizerTrust(organizerId: string): Promise<OrganizerTrust> {
|
||||
const [user, tripsCreated, reviewAgg] = await Promise.all([
|
||||
prisma.user.findUnique({
|
||||
where: { id: organizerId },
|
||||
select: { isVerified: true },
|
||||
}),
|
||||
const [tripsCreated, reviewAgg, organizerVerification] = await Promise.all([
|
||||
prisma.trip.count({ where: { organizerId } }),
|
||||
prisma.tripReview.aggregate({
|
||||
where: {
|
||||
@@ -24,11 +20,15 @@ export const trustService = {
|
||||
_avg: { rating: true },
|
||||
_count: { _all: true },
|
||||
}),
|
||||
prisma.organizerVerification.findUnique({
|
||||
where: { userId: organizerId },
|
||||
select: { status: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
const avg = reviewAgg._avg.rating;
|
||||
return {
|
||||
isVerified: user?.isVerified ?? false,
|
||||
isVerified: organizerVerification?.status === "APPROVED",
|
||||
tripsCreated,
|
||||
avgRating:
|
||||
avg != null ? Math.round(Number(avg) * 10) / 10 : null,
|
||||
|
||||
Reference in New Issue
Block a user