import "dotenv/config"; import { PrismaClient } from "../app/generated/prisma/client"; import type { Prisma } from "../app/generated/prisma/client"; import type { ActivityCategory, Vibe, } from "../app/generated/prisma/enums"; import { PrismaPg } from "@prisma/adapter-pg"; import bcrypt from "bcryptjs"; import { encryptString, hmacHex } from "../lib/crypto"; const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL!, }); const prisma = new PrismaClient({ adapter }); // ============================================================================ // Helpers // ============================================================================ /** Tanggal absolut UTC — dipakai untuk data yang tidak boleh bergeser (tanggal lahir). */ const utc = (y: number, m0: number, d: number, h = 12, min = 0) => new Date(Date.UTC(y, m0, d, h, min, 0, 0)); /** Waktu seed dijalankan — basis semua tanggal relatif. */ const SEED_NOW = new Date(); /** * Tanggal relatif terhadap waktu seed dijalankan — bikin seeder *evergreen*: * kapan pun di-run, trip "OPEN" selalu di masa depan dan "COMPLETED" di masa * lalu. `offsetDays` negatif = lampau, positif = mendatang. Hasil = tengah * malam UTC, sinkron dengan trip yang dibuat lewat form (`tripStoredInstantFromYmd`). */ function relDate(offsetDays: number): Date { const d = new Date(SEED_NOW); d.setUTCDate(d.getUTCDate() + offsetDays); d.setUTCHours(0, 0, 0, 0); return d; } const img = (id: string) => `https://images.unsplash.com/photo-${id}?w=1200&q=80&auto=format&fit=crop`; /** * Kumpulan foto Unsplash per kategori — di-reuse antar trip supaya tidak ada * URL gambar yang 404. Semua id sudah terverifikasi valid. */ const PHOTO = { hiking: [ "1554629947-334ff61d85dc", "1464822759023-fed622ff2c3b", "1480497490787-505ec076689f", "1502085671122-2d218cd434e6", ], camping: ["1504280390367-361c6d9f38f4", "1517824806704-9040b037703b"], beach: [ "1583212292454-1fe6229603b7", "1559825481-12a05cc00344", "1507525428034-b723cf961d3e", "1519046904884-53103b34b206", ], diving: ["1544551763-46a013bb70d5", "1566024287286-457247b70310"], city: ["1596402184320-417e7178b2cd", "1583309217394-d191d747bc66"], culinary: ["1565299624946-b28f40a0ae38"], concert: ["1470229722913-7c0e2dbbafd3"], workshop: ["1452587925148-ce544e77e70d", "1444080748397-f442aa95c3e5"], retreat: ["1545389336-cf090694435e", "1518609878373-06d740f60d8b"], } as const; const SEED_PASSWORD = "password123"; // ============================================================================ // Itineraries — array terstruktur per hari/jam. Setiap item: {day, startTime, // endTime, activity}. Disimpan ke TripItineraryItem (lihat schema.prisma). // ============================================================================ interface SeedItineraryItem { day: number; startTime: string; endTime?: string; activity: string; } const ITIN_PAPANDAYAN: SeedItineraryItem[] = [ { day: 1, startTime: "05:00", endTime: "05:30", activity: "Meeting & briefing di Alun-alun Garut" }, { day: 1, startTime: "05:30", endTime: "07:00", activity: "Perjalanan menuju basecamp Cisurupan" }, { day: 1, startTime: "07:00", endTime: "08:00", activity: "Sarapan + repacking + pemanasan" }, { day: 1, startTime: "08:00", endTime: "11:00", activity: "Trekking Camp David — Hutan Mati — Pondok Salada" }, { day: 1, startTime: "11:00", endTime: "12:30", activity: "Setup tenda di Pondok Salada" }, { day: 1, startTime: "12:30", endTime: "14:00", activity: "ISHOMA + games kenalan grup" }, { day: 1, startTime: "14:00", endTime: "17:00", activity: "Eksplor Tegal Alun & Hutan Mati (golden hour foto)" }, { day: 1, startTime: "17:00", endTime: "19:00", activity: "Masak bareng + makan malam" }, { day: 1, startTime: "19:00", endTime: "21:00", activity: "Api unggun, kopi, sharing rencana summit" }, { day: 1, startTime: "21:00", activity: "Istirahat" }, { day: 2, startTime: "03:30", endTime: "04:00", activity: "Bangun + sarapan ringan + air panas" }, { day: 2, startTime: "04:00", endTime: "05:30", activity: "Summit attack ke puncak Papandayan" }, { day: 2, startTime: "05:30", endTime: "07:00", activity: "Sunrise + foto bareng di puncak" }, { day: 2, startTime: "07:00", endTime: "09:00", activity: "Turun ke camp + sarapan utama" }, { day: 2, startTime: "09:00", endTime: "11:00", activity: "Beres-beres tenda + repacking" }, { day: 2, startTime: "11:00", endTime: "13:30", activity: "Turun ke basecamp Cisurupan" }, { day: 2, startTime: "13:30", endTime: "14:30", activity: "Bersih-bersih + makan siang" }, { day: 2, startTime: "14:30", endTime: "16:30", activity: "Perjalanan kembali ke Garut" }, { day: 2, startTime: "16:30", activity: "Sampai Garut, bubar grup" }, ]; const ITIN_CIREMAI: SeedItineraryItem[] = [ { day: 1, startTime: "04:00", endTime: "04:30", activity: "Meeting & briefing di Stasiun Kuningan" }, { day: 1, startTime: "04:30", endTime: "06:30", activity: "Perjalanan ke basecamp Apuy via Maja & Argapura" }, { day: 1, startTime: "06:30", endTime: "07:30", activity: "Sarapan + registrasi simaksi + repacking" }, { day: 1, startTime: "07:30", endTime: "10:30", activity: "Trek Pos 1 (Berod) → Pos 2 (Arban) → Pos 3 (Tegal Masawa)" }, { day: 1, startTime: "10:30", endTime: "11:30", activity: "ISHOMA di Pos 4 (Tegal Jamuju)" }, { day: 1, startTime: "11:30", endTime: "14:30", activity: "Lanjut Pos 5 (Sanghyang Rangkah) → Pos 6 (Goa Walet)" }, { day: 1, startTime: "14:30", endTime: "16:00", activity: "Setup tenda di Pos 6" }, { day: 1, startTime: "16:00", endTime: "18:00", activity: "Acara bebas + makan sore + persiapan summit" }, { day: 1, startTime: "18:00", endTime: "20:00", activity: "Briefing summit + early dinner" }, { day: 1, startTime: "20:00", activity: "Istirahat (bangun dini hari)" }, { day: 2, startTime: "02:00", endTime: "02:30", activity: "Bangun + cemilan + minuman hangat" }, { day: 2, startTime: "02:30", endTime: "05:00", activity: "Summit attack ke puncak Sunan Cirebon (3.078 mdpl)" }, { day: 2, startTime: "05:00", endTime: "06:30", activity: "Sunrise di puncak Ciremai" }, { day: 2, startTime: "06:30", endTime: "08:30", activity: "Turun ke Pos 6 + sarapan" }, { day: 2, startTime: "08:30", endTime: "10:30", activity: "Beres tenda + repacking" }, { day: 2, startTime: "10:30", endTime: "14:00", activity: "Turun ke basecamp Apuy (track curam — hati-hati lutut)" }, { day: 2, startTime: "14:00", endTime: "15:00", activity: "Bersih-bersih + makan siang di basecamp" }, { day: 2, startTime: "15:00", endTime: "17:00", activity: "Kembali ke Stasiun Kuningan" }, { day: 2, startTime: "17:00", activity: "Bubar grup" }, ]; const ITIN_CAMPING: SeedItineraryItem[] = [ { day: 1, startTime: "13:00", endTime: "13:30", activity: "Meeting di Pertigaan Pasar Lembang" }, { day: 1, startTime: "13:30", endTime: "15:00", activity: "Perjalanan ke Ranca Upas via Ciwidey" }, { day: 1, startTime: "15:00", endTime: "16:00", activity: "Check-in + setup tenda dome (sudah disiapkan tim)" }, { day: 1, startTime: "16:00", endTime: "17:30", activity: "Tour camp area + ketemu rusa-rusa" }, { day: 1, startTime: "17:30", endTime: "19:00", activity: "Persiapan BBQ + nyalakan api unggun" }, { day: 1, startTime: "19:00", endTime: "21:00", activity: "Makan malam BBQ" }, { day: 1, startTime: "21:00", endTime: "23:00", activity: "Live music akustik + games" }, { day: 1, startTime: "23:00", activity: "Istirahat" }, { day: 2, startTime: "06:00", endTime: "07:00", activity: "Sunrise + foto di hutan pinus" }, { day: 2, startTime: "07:00", endTime: "08:30", activity: "Sarapan (nasi goreng + roti bakar + kopi)" }, { day: 2, startTime: "08:30", endTime: "10:00", activity: "Memberi makan rusa + sesi foto" }, { day: 2, startTime: "10:00", endTime: "11:00", activity: "Beres tenda + bersih-bersih" }, { day: 2, startTime: "11:00", endTime: "11:30", activity: "Pulang menuju Lembang" }, { day: 2, startTime: "12:00", activity: "Sampai Lembang, bubar grup" }, ]; const ITIN_PAHAWANG: SeedItineraryItem[] = [ { day: 1, startTime: "07:00", endTime: "07:30", activity: "Meeting di Dermaga Ketapang, Lampung Selatan" }, { day: 1, startTime: "07:30", endTime: "08:30", activity: "Briefing safety + fitting alat snorkel" }, { day: 1, startTime: "08:30", endTime: "09:30", activity: "Sailing menuju Pulau Pahawang Kecil" }, { day: 1, startTime: "09:30", endTime: "11:30", activity: "Snorkeling spot Cukuh Bedil — terumbu warna-warni" }, { day: 1, startTime: "11:30", endTime: "12:30", activity: "Pindah spot ke Pulau Kelagian Kecil" }, { day: 1, startTime: "12:30", endTime: "14:00", activity: "Makan siang + istirahat di pasir putih" }, { day: 1, startTime: "14:00", endTime: "15:30", activity: "Snorkeling Tanjung Putus + sesi foto underwater" }, { day: 1, startTime: "15:30", endTime: "16:30", activity: "Sailing kembali ke dermaga" }, { day: 1, startTime: "16:30", endTime: "17:00", activity: "Bersih-bersih + bubar grup" }, ]; const ITIN_DIVING: SeedItineraryItem[] = [ { day: 1, startTime: "06:30", endTime: "07:00", activity: "Meeting di dive shop Tulamben + welcome coffee" }, { day: 1, startTime: "07:00", endTime: "08:00", activity: "Briefing dive plan + cek sertifikasi + fitting gear" }, { day: 1, startTime: "08:00", endTime: "09:00", activity: "Surface interval + pengecekan tank/regulator" }, { day: 1, startTime: "09:00", endTime: "10:30", activity: "Dive #1 — USS Liberty Wreck (5–28m, ~50 menit bottom time)" }, { day: 1, startTime: "10:30", endTime: "12:00", activity: "Surface interval + brunch + log dive" }, { day: 1, startTime: "12:00", endTime: "13:30", activity: "Dive #2 — Coral Garden / Drop Off (~50 menit)" }, { day: 1, startTime: "13:30", endTime: "15:00", activity: "Debrief + makan siang" }, { day: 1, startTime: "15:00", endTime: "17:00", activity: "Acara bebas (rest atau eksplor desa Tulamben)" }, { day: 1, startTime: "17:00", endTime: "19:00", activity: "Sunset di pantai Tulamben + dinner" }, { day: 1, startTime: "19:00", activity: "Istirahat di homestay (mandiri)" }, { day: 2, startTime: "06:00", endTime: "06:30", activity: "Bangun + kopi" }, { day: 2, startTime: "06:30", endTime: "07:30", activity: "Briefing dive #3 (early morning visibility tinggi)" }, { day: 2, startTime: "07:30", endTime: "09:00", activity: "Dive #3 — Liberty Wreck pagi" }, { day: 2, startTime: "09:00", endTime: "10:30", activity: "Surface interval + sarapan + log" }, { day: 2, startTime: "10:30", endTime: "12:00", activity: "Dive #4 (opsional, fun dive shallow reef)" }, { day: 2, startTime: "12:00", endTime: "13:00", activity: "Bersih gear + debrief akhir" }, { day: 2, startTime: "13:00", endTime: "14:00", activity: "Makan siang penutupan" }, { day: 2, startTime: "14:00", activity: "Bubar grup" }, ]; const ITIN_ISLANDHOP: SeedItineraryItem[] = [ { day: 1, startTime: "07:00", endTime: "07:30", activity: "Meeting di Pelabuhan Kartini Jepara" }, { day: 1, startTime: "07:30", endTime: "13:00", activity: "Penyeberangan kapal feri Jepara → Karimun Jawa" }, { day: 1, startTime: "13:00", endTime: "14:00", activity: "Tiba di Pelabuhan Karimun + transfer homestay" }, { day: 1, startTime: "14:00", endTime: "15:00", activity: "Check-in homestay + ISHOMA" }, { day: 1, startTime: "15:00", endTime: "17:30", activity: "Sunset di Bukit Love + foto-foto" }, { day: 1, startTime: "17:30", endTime: "19:00", activity: "Bersih-bersih + makan malam" }, { day: 1, startTime: "19:00", endTime: "21:00", activity: "Alun-alun Karimun + jajan kuliner" }, { day: 1, startTime: "21:00", activity: "Istirahat" }, { day: 2, startTime: "06:30", endTime: "07:30", activity: "Sarapan + briefing hopping" }, { day: 2, startTime: "07:30", endTime: "09:30", activity: "Boat ke Pulau Menjangan Kecil — snorkeling spot terumbu" }, { day: 2, startTime: "09:30", endTime: "11:30", activity: "Pulau Menjangan Besar — interaksi hiu (penangkaran)" }, { day: 2, startTime: "11:30", endTime: "13:00", activity: "Makan siang BBQ ikan di Pulau Cemara Besar" }, { day: 2, startTime: "13:00", endTime: "15:00", activity: "Pulau Cemara Kecil + foto pasir putih" }, { day: 2, startTime: "15:00", endTime: "17:00", activity: "Pulau Cilik — sunset + snorkel terakhir" }, { day: 2, startTime: "17:00", endTime: "18:30", activity: "Kembali ke homestay + bersih-bersih" }, { day: 2, startTime: "18:30", endTime: "20:00", activity: "Makan malam seafood" }, { day: 2, startTime: "20:00", activity: "Acara bebas" }, { day: 3, startTime: "06:00", endTime: "07:00", activity: "Sunrise di Tanjung Gelam" }, { day: 3, startTime: "07:00", endTime: "09:00", activity: "Sarapan + pack-up" }, { day: 3, startTime: "09:00", endTime: "10:00", activity: "Belanja oleh-oleh di pelabuhan" }, { day: 3, startTime: "10:00", endTime: "16:00", activity: "Penyeberangan kapal feri Karimun → Jepara" }, { day: 3, startTime: "16:00", endTime: "17:00", activity: "Tiba di Pelabuhan Kartini, bubar grup" }, ]; const ITIN_CITYTRIP: SeedItineraryItem[] = [ { day: 1, startTime: "08:00", endTime: "08:30", activity: "Meeting di Stasiun Tugu Yogyakarta" }, { day: 1, startTime: "08:30", endTime: "10:00", activity: "Sarapan Gudeg Yu Djum + briefing rute" }, { day: 1, startTime: "10:00", endTime: "12:00", activity: "Kotagede — kerajinan perak + Masjid Mataram" }, { day: 1, startTime: "12:00", endTime: "13:30", activity: "Makan siang Sate Klathak Pak Pong" }, { day: 1, startTime: "13:30", endTime: "16:00", activity: "Tamansari — pemandian Sultan + sumur Gumuling" }, { day: 1, startTime: "16:00", endTime: "17:30", activity: "Coffee break di kedai lokal Prawirotaman" }, { day: 1, startTime: "17:30", endTime: "19:30", activity: "Sunset di Bukit Bintang (Jl. Imogiri)" }, { day: 1, startTime: "19:30", endTime: "21:30", activity: "Angkringan Lik Man — kopi joss + nasi kucing" }, { day: 1, startTime: "21:30", activity: "Drop ke penginapan masing-masing" }, { day: 2, startTime: "06:00", endTime: "07:00", activity: "Pickup dari penginapan" }, { day: 2, startTime: "07:00", endTime: "09:30", activity: "Perjalanan ke Kalibiru, Kulon Progo" }, { day: 2, startTime: "09:30", endTime: "11:30", activity: "Kalibiru — spot foto rumah pohon di tebing" }, { day: 2, startTime: "11:30", endTime: "13:00", activity: "Makan siang pecel di warung lokal" }, { day: 2, startTime: "13:00", endTime: "15:00", activity: "Pinus Pengger — instalasi seni alam" }, { day: 2, startTime: "15:00", endTime: "16:30", activity: "Heha Sky View (opsional, cek cuaca)" }, { day: 2, startTime: "16:30", endTime: "18:00", activity: "Kembali ke kota — drop di Stasiun Tugu / Bandara" }, { day: 2, startTime: "18:00", activity: "Bubar grup" }, ]; const ITIN_CULINARY: SeedItineraryItem[] = [ { day: 1, startTime: "09:00", endTime: "09:30", activity: "Meeting di Stasiun Bandung pintu utara + briefing rute" }, { day: 1, startTime: "09:30", endTime: "10:15", activity: "Stop 1: Surabi Enhaii (sarapan tradisional)" }, { day: 1, startTime: "10:15", endTime: "11:00", activity: "Stop 2: Lotek Kalipah Apo" }, { day: 1, startTime: "11:00", endTime: "11:45", activity: "Stop 3: Mie Kocok Mang Dadeng (Kebon Jukut)" }, { day: 1, startTime: "11:45", endTime: "12:30", activity: "Stop 4: Bakso Akung (cabang Burangrang)" }, { day: 1, startTime: "12:30", endTime: "13:30", activity: "Istirahat + jalan santai di Cihampelas" }, { day: 1, startTime: "13:30", endTime: "14:15", activity: "Stop 5: Batagor Kingsley" }, { day: 1, startTime: "14:15", endTime: "15:00", activity: "Stop 6: Cuanki Serayu" }, { day: 1, startTime: "15:00", endTime: "15:45", activity: "Stop 7: Es Cendol Elizabeth" }, { day: 1, startTime: "15:45", endTime: "16:30", activity: "Stop 8: Roti Gempol & Kopi Anjis (penutup)" }, { day: 1, startTime: "16:30", endTime: "17:00", activity: "Closing + foto bareng di Braga" }, ]; const ITIN_CONCERT: SeedItineraryItem[] = [ { day: 1, startTime: "17:00", endTime: "17:30", activity: "Meeting di Plaza GBK, depan loket Cat 1" }, { day: 1, startTime: "17:30", endTime: "18:30", activity: "Foto bareng pre-show + obrolan singkat" }, { day: 1, startTime: "18:30", endTime: "19:00", activity: "Masuk venue bareng (kategori tetap masing-masing)" }, { day: 1, startTime: "19:00", endTime: "22:30", activity: "Konser Coldplay — Music of the Spheres" }, { day: 1, startTime: "22:30", endTime: "23:00", activity: "Berkumpul lagi di luar gerbang utama GBK" }, { day: 1, startTime: "23:00", activity: "After-party dinner di Senayan (resto TBA via grup WA)" }, ]; const ITIN_WORKSHOP: SeedItineraryItem[] = [ { day: 1, startTime: "04:00", endTime: "04:30", activity: "Meeting di Alun-alun Pangalengan" }, { day: 1, startTime: "04:30", endTime: "05:30", activity: "Briefing teknis + setup peralatan" }, { day: 1, startTime: "05:30", endTime: "07:30", activity: "Sunrise shoot di Perkebunan Teh Malabar" }, { day: 1, startTime: "07:30", endTime: "09:00", activity: "Sarapan + sesi review foto bareng mentor" }, { day: 1, startTime: "09:00", endTime: "11:00", activity: "Materi indoor: long exposure & filter ND" }, { day: 1, startTime: "11:00", endTime: "13:00", activity: "ISHOMA + transfer ke Situ Cileunca" }, { day: 1, startTime: "13:00", endTime: "16:00", activity: "Workshop on-field di Situ Cileunca (panorama, refleksi)" }, { day: 1, startTime: "16:00", endTime: "18:00", activity: "Golden hour shoot di Bukit Nini" }, { day: 1, startTime: "18:00", endTime: "19:30", activity: "Makan malam di villa" }, { day: 1, startTime: "19:30", endTime: "22:00", activity: "Sesi malam — milky way / star trail (cuaca permitting)" }, { day: 1, startTime: "22:00", activity: "Istirahat di villa" }, { day: 2, startTime: "05:00", endTime: "07:00", activity: "Sunrise shoot di Pangalengan + foto siluet" }, { day: 2, startTime: "07:00", endTime: "08:30", activity: "Sarapan + diskusi hasil" }, { day: 2, startTime: "08:30", endTime: "11:00", activity: "Sesi editing Lightroom (laptop pribadi)" }, { day: 2, startTime: "11:00", endTime: "12:00", activity: "Review akhir + sertifikat" }, { day: 2, startTime: "12:00", endTime: "13:00", activity: "Makan siang penutupan" }, { day: 2, startTime: "13:00", endTime: "15:00", activity: "Kembali ke Alun-alun Pangalengan" }, { day: 2, startTime: "15:00", activity: "Bubar grup" }, ]; const ITIN_RETREAT: SeedItineraryItem[] = [ { day: 1, startTime: "14:00", endTime: "15:00", activity: "Check-in Villa Sawah Ubud + welcome drink (jamu)" }, { day: 1, startTime: "15:00", endTime: "16:00", activity: "Tour fasilitas + pembagian welcome kit" }, { day: 1, startTime: "16:00", endTime: "17:30", activity: "Yin Yoga pembuka — release perjalanan" }, { day: 1, startTime: "17:30", endTime: "18:30", activity: "Journaling: niat & ekspektasi retreat" }, { day: 1, startTime: "18:30", endTime: "20:00", activity: "Dinner vegan (set menu)" }, { day: 1, startTime: "20:00", endTime: "21:00", activity: "Circle pengenalan + meditasi singkat" }, { day: 1, startTime: "21:00", activity: "Lights-out" }, { day: 2, startTime: "06:00", endTime: "07:30", activity: "Hatha Yoga matahari terbit" }, { day: 2, startTime: "07:30", endTime: "09:00", activity: "Sarapan vegan + tea ceremony" }, { day: 2, startTime: "09:00", endTime: "10:30", activity: "Meditasi guided: body scan" }, { day: 2, startTime: "10:30", endTime: "12:00", activity: "Pranayama (latihan napas)" }, { day: 2, startTime: "12:00", endTime: "13:30", activity: "Lunch + acara bebas (sawah walk)" }, { day: 2, startTime: "13:30", endTime: "15:00", activity: "Sound healing dengan singing bowl" }, { day: 2, startTime: "15:00", endTime: "16:30", activity: "Workshop: mindful eating + jamu making" }, { day: 2, startTime: "16:30", endTime: "18:00", activity: "Yin Yoga sore + savasana panjang" }, { day: 2, startTime: "18:00", endTime: "19:30", activity: "Dinner vegan" }, { day: 2, startTime: "19:30", endTime: "21:00", activity: "Sharing circle + meditasi malam" }, { day: 2, startTime: "21:00", activity: "Lights-out" }, { day: 3, startTime: "06:00", endTime: "07:30", activity: "Vinyasa Flow penutupan" }, { day: 3, startTime: "07:30", endTime: "09:00", activity: "Sarapan + closing journaling" }, { day: 3, startTime: "09:00", endTime: "10:30", activity: "Closing circle + tukar pesan" }, { day: 3, startTime: "10:30", endTime: "11:30", activity: "Check-out + pelukan perpisahan" }, { day: 3, startTime: "11:30", endTime: "14:00", activity: "Acara bebas (rekomendasi spa/cafe sekitar)" }, { day: 3, startTime: "14:00", activity: "Trip resmi ditutup" }, ]; const ITIN_PRAU: SeedItineraryItem[] = [ { day: 1, startTime: "13:00", endTime: "13:30", activity: "Meeting & registrasi di basecamp Patak Banteng, Dieng" }, { day: 1, startTime: "13:30", endTime: "14:00", activity: "Briefing + repacking + pemanasan" }, { day: 1, startTime: "14:00", endTime: "17:00", activity: "Trekking Pos 1 — Pos 2 — Pos 3 menuju area camp puncak" }, { day: 1, startTime: "17:00", endTime: "18:30", activity: "Setup tenda + makan sore + sunset" }, { day: 1, startTime: "18:30", endTime: "21:00", activity: "Api unggun, kopi, sharing antar peserta" }, { day: 1, startTime: "21:00", activity: "Istirahat" }, { day: 2, startTime: "04:30", endTime: "05:00", activity: "Bangun + jalan ke spot sunrise" }, { day: 2, startTime: "05:00", endTime: "06:30", activity: "Golden sunrise + Bukit Teletubbies + foto bareng" }, { day: 2, startTime: "06:30", endTime: "08:30", activity: "Kembali ke camp + sarapan" }, { day: 2, startTime: "08:30", endTime: "10:00", activity: "Beres tenda + repacking" }, { day: 2, startTime: "10:00", endTime: "12:00", activity: "Turun ke basecamp Patak Banteng" }, { day: 2, startTime: "12:00", activity: "Bersih-bersih, bubar grup" }, ]; const ITIN_BROMO: SeedItineraryItem[] = [ { day: 1, startTime: "00:00", endTime: "00:30", activity: "Meeting di Cemoro Lawang + briefing" }, { day: 1, startTime: "00:30", endTime: "03:00", activity: "Jeep menuju Penanjakan + posisi spot sunrise" }, { day: 1, startTime: "03:00", endTime: "05:30", activity: "Menunggu + sunrise di view point Penanjakan" }, { day: 1, startTime: "05:30", endTime: "07:00", activity: "Jeep ke lautan pasir + Pura Luhur Poten" }, { day: 1, startTime: "07:00", endTime: "08:30", activity: "Trek tangga ke kawah Bromo" }, { day: 1, startTime: "08:30", endTime: "10:00", activity: "Bukit Teletubbies + Pasir Berbisik (sesi foto)" }, { day: 1, startTime: "10:00", endTime: "11:00", activity: "Sarapan + bersih-bersih" }, { day: 1, startTime: "11:00", activity: "Bubar grup" }, ]; const ITIN_NUSAPENIDA: SeedItineraryItem[] = [ { day: 1, startTime: "06:00", endTime: "06:30", activity: "Meeting di Sanur, Bali + briefing" }, { day: 1, startTime: "06:30", endTime: "07:30", activity: "Fast boat Sanur menuju Nusa Penida" }, { day: 1, startTime: "07:30", endTime: "09:30", activity: "Kelingking Beach — spot ikonik tebing T-Rex" }, { day: 1, startTime: "09:30", endTime: "11:00", activity: "Angel's Billabong + Broken Beach" }, { day: 1, startTime: "11:00", endTime: "12:30", activity: "Makan siang + istirahat" }, { day: 1, startTime: "12:30", endTime: "14:30", activity: "Crystal Bay — snorkeling + santai pantai" }, { day: 1, startTime: "14:30", endTime: "15:30", activity: "Fast boat kembali ke Sanur" }, { day: 1, startTime: "15:30", activity: "Tiba di Sanur, bubar grup" }, ]; const ITIN_RINJANI: SeedItineraryItem[] = [ { day: 1, startTime: "06:00", endTime: "07:00", activity: "Meeting & registrasi di basecamp Sembalun" }, { day: 1, startTime: "07:00", endTime: "08:00", activity: "Sarapan + briefing + repacking bareng porter" }, { day: 1, startTime: "08:00", endTime: "13:00", activity: "Trek Sembalun → Pos 1 → Pos 2 (sabana luas)" }, { day: 1, startTime: "13:00", endTime: "14:00", activity: "ISHOMA di Pos 2" }, { day: 1, startTime: "14:00", endTime: "17:00", activity: "Lanjut ke Pos 3 — Plawangan Sembalun" }, { day: 1, startTime: "17:00", endTime: "19:00", activity: "Setup tenda + makan malam + sunset" }, { day: 1, startTime: "19:00", activity: "Istirahat (summit dini hari)" }, { day: 2, startTime: "02:00", endTime: "02:30", activity: "Bangun + minuman hangat + briefing summit" }, { day: 2, startTime: "02:30", endTime: "06:00", activity: "Summit attack ke puncak Rinjani (3.726 mdpl)" }, { day: 2, startTime: "06:00", endTime: "07:30", activity: "Sunrise di puncak + foto bareng" }, { day: 2, startTime: "07:30", endTime: "10:30", activity: "Turun ke Plawangan + sarapan" }, { day: 2, startTime: "10:30", endTime: "14:30", activity: "Turun ke Danau Segara Anak" }, { day: 2, startTime: "14:30", endTime: "17:00", activity: "Camp di tepi danau + berendam air panas" }, { day: 2, startTime: "17:00", endTime: "19:00", activity: "Makan malam + santai di tepi danau" }, { day: 2, startTime: "19:00", activity: "Istirahat" }, { day: 3, startTime: "06:00", endTime: "07:30", activity: "Sarapan + beres tenda" }, { day: 3, startTime: "07:30", endTime: "13:00", activity: "Trek turun via Senaru (hutan tropis)" }, { day: 3, startTime: "13:00", endTime: "14:00", activity: "Makan siang di basecamp Senaru" }, { day: 3, startTime: "14:00", activity: "Bersih-bersih, bubar grup" }, ]; // ============================================================================ // 1. Cleanup — hapus SEMUA data lama supaya tiap `npm run seed` mulai dari nol. // Urutan FK-aware: Refund/Payout → Payment → Booking → Review → Image → // Itinerary → Participant → Trip → OrgVerification → Profile → Account → // User. Tabel log operasional (email/cron/audit) juga dibersihkan. // ============================================================================ async function cleanup() { // Log operasional — tidak punya FK ke data inti, aman dihapus duluan. await prisma.emailJob.deleteMany(); await prisma.emailSent.deleteMany(); await prisma.cronRun.deleteMany(); await prisma.adminActionLog.deleteMany(); // Refund & Payout pakai onDelete: Restrict ke Booking/Payment, jadi mereka // wajib dihapus duluan sebelum Booking/Payment bisa dibersihkan. await prisma.refund.deleteMany(); await prisma.payout.deleteMany(); await prisma.payment.deleteMany(); await prisma.booking.deleteMany(); await prisma.tripReview.deleteMany(); await prisma.tripImage.deleteMany(); await prisma.tripItineraryItem.deleteMany(); await prisma.tripParticipant.deleteMany(); await prisma.trip.deleteMany(); await prisma.organizerVerification.deleteMany(); await prisma.userProfile.deleteMany(); await prisma.account.deleteMany(); await prisma.user.deleteMany(); } // ============================================================================ // 2. Users + Profile // ============================================================================ interface SeedUser { key: string; name: string; email: string; isAdmin?: boolean; profile?: { bio?: string; city?: string; interests?: string[]; instagram?: string; vibe?: Vibe; }; } const SEED_USERS: SeedUser[] = [ // Admin { key: "admin", name: "Admin Setrip", email: "admin@setrip.id", isAdmin: true, }, // Organizers (akan di-link ke OrganizerVerification) { key: "dede", name: "Dede Inoen", email: "dede.inoen@setrip.id", profile: { bio: "Pendaki sejak 2015. Suka trip santai, bawa peserta pulang dengan selamat.", city: "Garut", interests: ["hiking", "camping", "fotografi"], instagram: "dede.inoen", vibe: "BALANCED", }, }, { key: "panji", name: "Panji Petualang", email: "panji@setrip.id", profile: { bio: "Trip leader 10+ tahun. Spesialis gunung & laut.", city: "Kuningan", interests: ["hiking", "diving", "snorkeling"], instagram: "panji.adv", vibe: "HARDCORE", }, }, { key: "fiersa", name: "Fiersa Besari", email: "fiersa@setrip.id", profile: { bio: "Buku, kopi, gunung. Kadang ngajak nonton konser bareng.", city: "Bandung", interests: ["hiking", "kuliner", "musik"], instagram: "fiersabesari", vibe: "CHILL", }, }, { key: "rangga", name: "Rangga Wisesa", email: "rangga@setrip.id", profile: { bio: "Founder komunitas trip Jawa Timur. Hobi sunrise hunting & curug.", city: "Malang", interests: ["hiking", "camping", "fotografi"], instagram: "rangga.wisesa", vibe: "HARDCORE", }, }, { key: "ayu", name: "Ayu Lestari", email: "ayu@setrip.id", profile: { bio: "Open trip organizer Bali & NTT. Cinta laut, anti drama.", city: "Denpasar", interests: ["diving", "snorkeling", "island hopping"], instagram: "ayu.lestari", vibe: "BALANCED", }, }, // Peserta { key: "budi", name: "Budi Santoso", email: "budi@gmail.com", profile: { bio: "Office worker yang lagi cari teman buat jalan akhir pekan.", city: "Jakarta", interests: ["hiking", "kuliner"], vibe: "BALANCED", }, }, { key: "sari", name: "Sari Dewi", email: "sari@gmail.com", profile: { bio: "Solo traveler. Suka pantai, ikan, dan tidur siang di hammock.", city: "Surabaya", interests: ["snorkeling", "yoga", "fotografi"], instagram: "saridew_id", vibe: "CHILL", }, }, { key: "doni", name: "Doni Prasetyo", email: "doni@gmail.com", profile: { bio: "Diver. Cari teman buddy fun-dive sebulan sekali.", city: "Denpasar", interests: ["diving", "freediving"], vibe: "HARDCORE", }, }, { key: "maya", name: "Maya Putri", email: "maya@gmail.com", profile: { bio: "Yoga teacher. Lagi belajar lebih hadir di setiap perjalanan.", city: "Yogyakarta", interests: ["yoga", "retreat", "kuliner"], instagram: "maya.putri", vibe: "CHILL", }, }, { key: "raka", name: "Raka Aditya", email: "raka@gmail.com", profile: { bio: "Anak baru di dunia trip. Energy tinggi, niat banyak.", city: "Bandung", interests: ["hiking", "konser"], vibe: "HARDCORE", }, }, { key: "intan", name: "Intan Permata", email: "intan@gmail.com", profile: { bio: "Pencinta sunrise & kopi gunung. Lagi ngumpulin teman seperjalanan.", city: "Bandung", interests: ["hiking", "fotografi", "kuliner"], instagram: "intan.permata", vibe: "BALANCED", }, }, { key: "galih", name: "Galih Nugraha", email: "galih@gmail.com", profile: { bio: "Weekend hiker dari Semarang. Suka camping & ngobrol sampai pagi.", city: "Semarang", interests: ["hiking", "camping"], vibe: "BALANCED", }, }, { key: "wina", name: "Wina Oktaviani", email: "wina@gmail.com", profile: { bio: "Beach person. Kerja remote, jalan tiap ada libur panjang.", city: "Jakarta", interests: ["snorkeling", "island hopping", "fotografi"], instagram: "wina.okta", vibe: "CHILL", }, }, { key: "bayu", name: "Bayu Saputra", email: "bayu@gmail.com", profile: { bio: "Hobi naik gunung sejak kuliah. Target: 7 summit Jawa.", city: "Malang", interests: ["hiking", "diving"], vibe: "HARDCORE", }, }, ]; type UserMap = Record; async function seedUsers(): Promise { const password = await bcrypt.hash(SEED_PASSWORD, 12); const acceptedAt = new Date(); const map: UserMap = {}; for (const u of SEED_USERS) { const created = await prisma.user.create({ data: { name: u.name, email: u.email, password, emailVerified: acceptedAt, acceptedTermsAndPrivacy: true, acceptedAt, profile: u.profile ? { create: { bio: u.profile.bio, city: u.profile.city, interests: u.profile.interests ?? [], instagram: u.profile.instagram, vibe: u.profile.vibe, }, } : undefined, }, }); map[u.key] = { id: created.id, email: created.email, name: created.name }; } return map; } // ============================================================================ // 3. Organizer Verification — APPROVED, PENDING, REJECTED untuk demo admin queue // ============================================================================ interface SeedVerification { userKey: string; fullName: string; nik: string; birthDate: Date; address: string; bankName: string; bankAccountNumber: string; bankAccountName: string; status: "APPROVED" | "PENDING" | "REJECTED"; rejectionReason?: string; } const SEED_VERIFICATIONS: SeedVerification[] = [ { userKey: "dede", fullName: "Dede Inoen", nik: "3201010101010001", birthDate: utc(1990, 0, 1, 0, 0), address: "Jl. Pendaki No. 1, Garut, Jawa Barat", bankName: "BCA", bankAccountNumber: "1234567890", bankAccountName: "Dede Inoen", status: "APPROVED", }, { userKey: "panji", fullName: "Panji Petualang", nik: "3201010101010002", birthDate: utc(1985, 5, 15, 0, 0), address: "Jl. Adventure No. 7, Kuningan, Jawa Barat", bankName: "Mandiri", bankAccountNumber: "9876543210", bankAccountName: "Panji Petualang", status: "APPROVED", }, { userKey: "fiersa", fullName: "Fiersa Besari", nik: "3201010101010003", birthDate: utc(1988, 2, 3, 0, 0), address: "Jl. Cisitu No. 99, Bandung, Jawa Barat", bankName: "BNI", bankAccountNumber: "5566778899", bankAccountName: "Fiersa Besari", status: "APPROVED", }, { userKey: "rangga", fullName: "Rangga Wisesa", nik: "3573010101010004", birthDate: utc(1991, 7, 20, 0, 0), address: "Jl. Ijen No. 23, Malang, Jawa Timur", bankName: "BRI", bankAccountNumber: "3344556677", bankAccountName: "Rangga Wisesa", status: "APPROVED", }, { userKey: "ayu", fullName: "Ayu Lestari", nik: "5171010101010005", birthDate: utc(1993, 10, 8, 0, 0), address: "Jl. Danau Tamblingan No. 5, Denpasar, Bali", bankName: "BCA", bankAccountNumber: "7788990011", bankAccountName: "Ayu Lestari", status: "APPROVED", }, // Demo admin queue: PENDING + REJECTED { userKey: "doni", fullName: "Doni Prasetyo", nik: "5101010202020001", birthDate: utc(1992, 8, 10, 0, 0), address: "Jl. Diving No. 12, Denpasar, Bali", bankName: "BCA", bankAccountNumber: "1112223334", bankAccountName: "Doni Prasetyo", status: "PENDING", }, { userKey: "raka", fullName: "Raka Aditya", nik: "3201010101010099", birthDate: utc(2000, 11, 31, 0, 0), address: "Alamat tidak jelas", bankName: "Bank lain", bankAccountNumber: "0001", bankAccountName: "RAKA", status: "REJECTED", rejectionReason: "Foto KTP tidak terbaca, alamat tidak lengkap.", }, ]; async function seedVerifications(users: UserMap, adminId: string) { const now = new Date(); for (const v of SEED_VERIFICATIONS) { const owner = users[v.userKey]; if (!owner) continue; const isApproved = v.status === "APPROVED"; const isReviewed = v.status !== "PENDING"; await prisma.organizerVerification.create({ data: { userId: owner.id, fullName: v.fullName, nikEncrypted: encryptString(v.nik), nikHash: hmacHex(v.nik), birthDate: v.birthDate, address: v.address, ktpImageKey: `ktp/seed-${v.userKey}.jpg`, livenessKey: `liveness/seed-${v.userKey}.jpg`, bankName: v.bankName, bankAccountNumber: v.bankAccountNumber, bankAccountName: v.bankAccountName, status: v.status, rejectionReason: v.rejectionReason, reviewedAt: isReviewed ? now : null, reviewedById: isReviewed ? adminId : null, verifiedAt: isApproved ? now : null, }, }); } } // ============================================================================ // 4. Trips — semua tanggal RELATIF terhadap waktu seed (lihat `relDate`): // COMPLETED/CLOSED < hari ini, OPEN/FULL > hari ini. Referensi destinasi // & kisaran harga diambil dari listing open trip populer di Indonesia. // ============================================================================ interface SeedTripImage { url: string; caption: string; } interface SeedTrip { key: string; organizerKey: string; category: ActivityCategory; title: string; description: string; meetingPoint?: string; itineraryItems?: SeedItineraryItem[]; whatsIncluded?: string; whatsExcluded?: string; destination: string; location: string; date: Date; endDate: Date | null; maxParticipants: number; price: number; vibe: Vibe; status: "OPEN" | "FULL" | "CLOSED" | "COMPLETED"; images: SeedTripImage[]; } /** Bangun daftar gambar dari pool kategori. */ function pics( pool: readonly string[], captions: string[] ): SeedTripImage[] { return captions.map((caption, i) => ({ url: img(pool[i % pool.length]), caption, })); } const SEED_TRIPS: SeedTrip[] = [ // ============ MASA LALU (COMPLETED — ada review) ============ { key: "past_papandayan", organizerKey: "dede", category: "HIKING", title: "Open Trip Papandayan — Batch Lalu", description: `Pendakian santai ke Gunung Papandayan. Cocok untuk pemula.`, meetingPoint: "Alun-alun Garut, 05:00 WIB", itineraryItems: ITIN_PAPANDAYAN, destination: "Gunung Papandayan", location: "Garut, Jawa Barat", date: relDate(-67), endDate: relDate(-66), maxParticipants: 10, price: 250000, vibe: "BALANCED", status: "COMPLETED", images: pics(PHOTO.hiking, ["Kawah Papandayan", "Track menuju puncak"]), }, { key: "past_pahawang", organizerKey: "panji", category: "SNORKELING", title: "Snorkeling Pulau Pahawang — Batch Lalu", description: `Trip snorkeling batch lalu. Cuaca bersahabat, visibility 10m+.`, meetingPoint: "Dermaga Ketapang, Lampung Selatan, 07:00 WIB", itineraryItems: ITIN_PAHAWANG, destination: "Pulau Pahawang Kecil", location: "Lampung Selatan, Lampung", date: relDate(-88), endDate: null, maxParticipants: 10, price: 380000, vibe: "CHILL", status: "COMPLETED", images: pics(PHOTO.beach, ["Snorkeling di Pahawang"]), }, { key: "past_culinary", organizerKey: "dede", category: "CULINARY", title: "Kulineran Street Food Bandung — Batch Lalu", description: `Food tour batch lalu. 8 spot legend dilibas dalam satu hari.`, meetingPoint: "Stasiun Bandung pintu utara, 09:00 WIB", itineraryItems: ITIN_CULINARY, destination: "Street Food Tour Bandung", location: "Bandung, Jawa Barat", date: relDate(-38), endDate: null, maxParticipants: 8, price: 175000, vibe: "CHILL", status: "COMPLETED", images: pics(PHOTO.culinary, ["Street food Bandung"]), }, { key: "past_prau", organizerKey: "rangga", category: "HIKING", title: "Open Trip Gunung Prau Dieng — Batch Lalu", description: `Sunrise camp di Prau, batch lalu. Cuaca cerah, lautan awan sempurna.`, meetingPoint: "Basecamp Patak Banteng, Dieng, 13:00 WIB", itineraryItems: ITIN_PRAU, destination: "Gunung Prau", location: "Wonosobo, Jawa Tengah", date: relDate(-25), endDate: relDate(-24), maxParticipants: 15, price: 320000, vibe: "BALANCED", status: "COMPLETED", images: pics(PHOTO.hiking, ["Sunrise Gunung Prau", "Bukit Teletubbies"]), }, { key: "past_nusapenida", organizerKey: "ayu", category: "ISLAND_HOPPING", title: "Nusa Penida West Tour — Batch Lalu", description: `One day tour Nusa Penida sisi barat. Kelingking, Crystal Bay, dan kawan-kawan.`, meetingPoint: "Pantai Sanur, Bali, 06:00 WITA", itineraryItems: ITIN_NUSAPENIDA, destination: "Nusa Penida Barat", location: "Klungkung, Bali", date: relDate(-45), endDate: null, maxParticipants: 14, price: 600000, vibe: "BALANCED", status: "COMPLETED", images: pics(PHOTO.beach, ["Kelingking Beach", "Crystal Bay"]), }, // ============ TRIP DIBATALKAN (CLOSED) ============ { key: "past_workshop_cancelled", organizerKey: "panji", category: "WORKSHOP", title: "Workshop Astrofotografi Bromo (BATAL)", description: `Cuaca tidak mendukung. Trip dibatalkan H-3, peserta refund 100%.`, meetingPoint: "-", destination: "Astrofotografi Bromo", location: "Probolinggo, Jawa Timur", date: relDate(-110), endDate: relDate(-109), maxParticipants: 6, price: 950000, vibe: "HARDCORE", status: "CLOSED", images: [], }, // ============ MASA DEPAN (OPEN — fitur join + bayar bisa di-test) ============ { key: "open_kotatua", organizerKey: "fiersa", category: "CITY_TRIP", title: "Heritage Walk Kota Tua Jakarta", description: `Jalan kaki menyusuri Batavia lama bareng pemandu sejarah. 🚶 Santai, ramah pemula — cocok buat after-office Sabtu`, meetingPoint: "Stasiun Jakarta Kota (BEOS), Sabtu 07:00 WIB", whatsIncluded: `• Pemandu sejarah lokal • Tiket Museum Fatahillah • Snack + air mineral`, whatsExcluded: `• Transport ke Kota Tua • Makan siang`, destination: "Kota Tua Jakarta", location: "Jakarta Barat", date: relDate(6), endDate: null, maxParticipants: 15, price: 95000, vibe: "CHILL", status: "OPEN", images: pics(PHOTO.city, ["Museum Fatahillah", "Sudut Kota Tua"]), }, { key: "open_prau", organizerKey: "rangga", category: "HIKING", title: "Open Trip Gunung Prau via Patak Banteng", description: `Golden sunrise terbaik se-Jawa Tengah. 🌄 ⛰️ Track pendek 2–3 jam — paling ramah pemula 🎒 Bawa: jaket tebal, sleeping bag, headlamp`, meetingPoint: "Basecamp Patak Banteng, Dieng, Sabtu 13:00 WIB", itineraryItems: ITIN_PRAU, whatsIncluded: `• Tenda + matras tim • Guide lokal • Konsumsi 3x + air panas • Tiket masuk + simaksi`, whatsExcluded: `• Transport ke Dieng • Sleeping bag pribadi`, destination: "Gunung Prau", location: "Wonosobo, Jawa Tengah", date: relDate(7), endDate: relDate(8), maxParticipants: 15, price: 320000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.hiking, ["Sunrise Gunung Prau", "Camp area puncak"]), }, { key: "open_culinary", organizerKey: "dede", category: "CULINARY", title: "Kulineran Street Food Bandung", description: `Hopping 8 spot kuliner legend Bandung dalam satu hari. 🍜 Cocok buat foodie & first-timer`, meetingPoint: "Stasiun Bandung pintu utara, 09:00 WIB", itineraryItems: ITIN_CULINARY, whatsIncluded: `• Transport angkot/grup • Tour leader food explorer • Sample setiap spot (8 tempat)`, whatsExcluded: `• Pembelian extra di luar sample`, destination: "Street Food Tour Bandung", location: "Bandung, Jawa Barat", date: relDate(8), endDate: null, maxParticipants: 8, price: 175000, vibe: "CHILL", status: "OPEN", images: pics(PHOTO.culinary, ["Street food Bandung"]), }, { key: "open_seribu", organizerKey: "panji", category: "SNORKELING", title: "Open Trip Pulau Harapan 2D1N", description: `Snorkeling hopping ala Maladewa-nya Jakarta. 🏝️ ✅ Trip ini sudah PENUH — pantau jadwal batch berikutnya`, meetingPoint: "Pelabuhan Kaliadem Muara Angke, Sabtu 06:00 WIB", whatsIncluded: `• Kapal PP + boat hopping • Homestay 1 malam • Alat snorkel • Makan 4x + BBQ`, whatsExcluded: `• Transport ke Muara Angke • Sewa kamera underwater`, destination: "Pulau Harapan", location: "Kepulauan Seribu, DKI Jakarta", date: relDate(9), endDate: relDate(10), maxParticipants: 6, price: 475000, vibe: "CHILL", status: "FULL", images: pics(PHOTO.beach, ["Pasir putih Pulau Harapan", "Spot snorkeling"]), }, { key: "open_papandayan", organizerKey: "dede", category: "HIKING", title: "Open Trip Papandayan Weekend", description: `Pendakian santai ke Gunung Papandayan, cocok untuk pemula! ⚠️ Bawa: Sleeping bag, jaket, headlamp, air 2L`, meetingPoint: "Alun-alun Garut, Sabtu 05:00 WIB — detail grup WA.", itineraryItems: ITIN_PAPANDAYAN, whatsIncluded: `• Transport PP Garut–basecamp • Guide lokal • Tenda tim • Konsumsi 3x + snack`, whatsExcluded: `• Tiket masuk TN • Sleeping bag pribadi • Asuransi perjalanan`, destination: "Gunung Papandayan", location: "Garut, Jawa Barat", date: relDate(10), endDate: relDate(11), maxParticipants: 10, price: 250000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.hiking, ["Kawah Papandayan", "Track menuju puncak"]), }, { key: "open_bromo", organizerKey: "rangga", category: "HIKING", title: "Bromo Sunrise Midnight Trip", description: `Berburu sunrise legendaris Bromo dari Penanjakan. 🌅 🚙 Naik jeep 4x4 — gak perlu trekking berat 📷 Spot foto: kawah, lautan pasir, Bukit Teletubbies`, meetingPoint: "Cemoro Lawang, Probolinggo, 00:00 WIB", itineraryItems: ITIN_BROMO, whatsIncluded: `• Jeep wisata 4x4 • Guide lokal • Tiket masuk kawasan TNBTS • Sarapan ringan`, whatsExcluded: `• Transport ke Cemoro Lawang • Sewa kuda ke kawah`, destination: "Gunung Bromo", location: "Probolinggo, Jawa Timur", date: relDate(12), endDate: null, maxParticipants: 12, price: 475000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.hiking, ["Sunrise Bromo", "Lautan pasir"]), }, { key: "open_camping", organizerKey: "dede", category: "CAMPING", title: "Camping Santai di Ranca Upas", description: `Camping bareng di tengah hutan pinus dengan ditemani rusa-rusa! Cocok buat first-timer. 📍 Meeting Point: Lembang, Sabtu 13:00 WIB 🎒 Fasilitas: Tenda dome, sleeping bag, BBQ, api unggun 🔥 Bonus: Live music akustik malam hari`, meetingPoint: "Pertigaan Pasar Lembang, Sabtu 13:00 WIB", itineraryItems: ITIN_CAMPING, whatsIncluded: `• Tenda + sleeping bag + matras • Logistik camp • Makan malam BBQ + sarapan • Tiket masuk lokasi`, whatsExcluded: `• Transport pribadi • Snack tambahan`, destination: "Ranca Upas", location: "Bandung Selatan, Jawa Barat", date: relDate(13), endDate: relDate(14), maxParticipants: 12, price: 220000, vibe: "CHILL", status: "OPEN", images: pics(PHOTO.camping, ["Tenda di hutan pinus", "Sunset di camp"]), }, { key: "open_semarang_kuliner", organizerKey: "rangga", category: "CULINARY", title: "Kulineran Heritage Semarang", description: `Susur kuliner legendaris Semarang sambil jalan di Kota Lama. 🥟 Lumpia, tahu gimbal, nasi ayam, sampai es puter`, meetingPoint: "Stasiun Semarang Tawang, 09:00 WIB", whatsIncluded: `• Tour leader food explorer • Sample 7 spot kuliner • Air mineral`, whatsExcluded: `• Transport ke Semarang • Pembelian di luar sample`, destination: "Kuliner Kota Lama Semarang", location: "Semarang, Jawa Tengah", date: relDate(15), endDate: null, maxParticipants: 10, price: 185000, vibe: "CHILL", status: "OPEN", images: pics(PHOTO.culinary, ["Kuliner Semarang"]), }, { key: "open_nusapenida", organizerKey: "ayu", category: "ISLAND_HOPPING", title: "Nusa Penida West One Day Trip", description: `Eksplor spot ikonik Nusa Penida sisi barat dalam sehari. 🏝️ Kelingking, Angel's Billabong, Broken Beach, Crystal Bay`, meetingPoint: "Pantai Sanur, Bali, 06:00 WITA", itineraryItems: ITIN_NUSAPENIDA, whatsIncluded: `• Fast boat PP Sanur–Nusa Penida • Mobil + sopir lokal seharian • Makan siang • Tiket masuk semua spot`, whatsExcluded: `• Transport ke Sanur • Alat snorkel (sewa di lokasi)`, destination: "Nusa Penida Barat", location: "Klungkung, Bali", date: relDate(16), endDate: null, maxParticipants: 14, price: 625000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.beach, ["Kelingking Beach", "Broken Beach"]), }, { key: "open_citytrip", organizerKey: "fiersa", category: "CITY_TRIP", title: "City Trip Jogja Hidden Gems", description: `Bukan Malioboro lagi. Trip jelajah Jogja sisi 'lokal'. 🚐 Mobil grup, bukan tour bus`, meetingPoint: "Stasiun Tugu Yogyakarta, Sabtu 08:00 WIB", itineraryItems: ITIN_CITYTRIP, whatsIncluded: `• Transport mobil grup 2 hari • Tour leader lokal • Makan 3x (kuliner lokal) • Tiket masuk semua spot`, whatsExcluded: `• Transport ke Jogja • Penginapan (rekomendasi disediakan)`, destination: "Yogyakarta", location: "Yogyakarta", date: relDate(18), endDate: relDate(19), maxParticipants: 8, price: 650000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.city, ["Tamansari", "Sudut Jogja"]), }, { key: "open_ciremai", organizerKey: "panji", category: "HIKING", title: "Pendakian Ciremai via Apuy", description: `Trip ke puncak tertinggi Jawa Barat! 🏔️ 📍 Meeting Point: Stasiun Kuningan, 04:00 WIB ⚠️ Level: Menengah — perlu stamina baik`, meetingPoint: "Stasiun Kuningan, Sabtu 04:00 WIB", itineraryItems: ITIN_CIREMAI, whatsIncluded: `• Transport PP Kuningan–basecamp • Guide + porter logistik • Tenda tim + konsumsi 4x • Simaksi`, whatsExcluded: `• Sleeping bag pribadi • Asuransi perjalanan`, destination: "Gunung Ciremai", location: "Kuningan, Jawa Barat", date: relDate(20), endDate: relDate(21), maxParticipants: 8, price: 350000, vibe: "HARDCORE", status: "OPEN", images: pics(PHOTO.hiking, ["Puncak Ciremai 3.078 mdpl", "Sunrise dari puncak"]), }, { key: "open_sentul_camp", organizerKey: "rangga", category: "CAMPING", title: "Camping & Curug Hunting Sentul", description: `Camping di tepi sungai + main air di curug-curug Sentul. 💧 Leuwi Hejo, Leuwi Lieuk — air jernih kebiruan`, meetingPoint: "Pertigaan Sentul City, Bogor, Sabtu 09:00 WIB", whatsIncluded: `• Tenda + matras + sleeping bag • Guide curug lokal • Makan 3x + BBQ malam • Tiket masuk semua curug`, whatsExcluded: `• Transport ke Sentul • Sandal gunung pribadi`, destination: "Curug Leuwi Hejo", location: "Bogor, Jawa Barat", date: relDate(22), endDate: relDate(23), maxParticipants: 14, price: 265000, vibe: "CHILL", status: "OPEN", images: pics(PHOTO.camping, ["Camp tepi sungai", "Curug Leuwi Hejo"]), }, { key: "open_merbabu", organizerKey: "panji", category: "HIKING", title: "Pendakian Merbabu via Suwanting", description: `Sabana Merbabu yang ikonik — salah satu jalur terindah di Jawa. 🌾 ⚠️ Level: Menengah–lanjut, jalur panjang & menanjak`, meetingPoint: "Basecamp Suwanting, Magelang, Sabtu 05:00 WIB", whatsIncluded: `• Guide + porter logistik • Tenda tim + konsumsi 4x • Simaksi + tiket masuk`, whatsExcluded: `• Transport ke Magelang • Sleeping bag & jaket pribadi`, destination: "Gunung Merbabu", location: "Magelang, Jawa Tengah", date: relDate(24), endDate: relDate(25), maxParticipants: 10, price: 375000, vibe: "HARDCORE", status: "OPEN", images: pics(PHOTO.hiking, ["Sabana Merbabu", "Jalur Suwanting"]), }, { key: "open_snorkel", organizerKey: "panji", category: "SNORKELING", title: "Snorkeling Pulau Pahawang", description: `Air kristal-jernih, ikan warna-warni, dan pasir putih lembut. 🤿 Pemula friendly — guide profesional 📷 Underwater photo session included`, meetingPoint: "Dermaga Ketapang, Lampung Selatan, 07:00 WIB", itineraryItems: ITIN_PAHAWANG, whatsIncluded: `• Boat PP • Alat snorkel (masker, fin, life vest) • Guide & pemandu underwater • Konsumsi 2x`, whatsExcluded: `• Transport ke Lampung • Penginapan Sabtu malam`, destination: "Pulau Pahawang Kecil", location: "Lampung Selatan, Lampung", date: relDate(27), endDate: null, maxParticipants: 10, price: 380000, vibe: "CHILL", status: "OPEN", images: pics(PHOTO.beach, ["Snorkeling di Pahawang", "Pasir putih Pahawang"]), }, { key: "open_lembongan_dive", organizerKey: "ayu", category: "DIVING", title: "Fun Dive Nusa Lembongan — Manta Point", description: `Diving bareng manta ray raksasa di Manta Bay! 🐠 ⚠️ Sertifikasi minimal: Open Water (PADI/SSI) 🌊 Arus sedang — bukan untuk dive pertama`, meetingPoint: "Dive center Sanur, Bali, 07:00 WITA", whatsIncluded: `• 2x dive guided (Manta Point + Crystal Bay) • Full gear + tank & weight • Fast boat PP • Makan siang`, whatsExcluded: `• Transport ke Sanur • Sertifikasi (cek validitas)`, destination: "Manta Point Nusa Lembongan", location: "Klungkung, Bali", date: relDate(29), endDate: relDate(30), maxParticipants: 6, price: 1550000, vibe: "HARDCORE", status: "OPEN", images: pics(PHOTO.diving, ["Manta ray", "Reef Nusa Lembongan"]), }, { key: "open_workshop", organizerKey: "panji", category: "WORKSHOP", title: "Workshop Fotografi Lanskap — Pangalengan", description: `Belajar fotografi lanskap langsung di lapangan. 📷 Bawa kamera DSLR/mirrorless + tripod 👨‍🏫 Mentor: fotografer pro (10+ tahun pengalaman)`, meetingPoint: "Alun-alun Pangalengan, Sabtu 04:00 WIB", itineraryItems: ITIN_WORKSHOP, whatsIncluded: `• Materi workshop (briefing + on-field) • Tour leader & mentor • Penginapan villa 1 malam • Konsumsi 3x`, whatsExcluded: `• Kamera & tripod (bawa sendiri) • Transport ke Pangalengan`, destination: "Fotografi Lanskap", location: "Pangalengan, Bandung Selatan", date: relDate(30), endDate: relDate(31), maxParticipants: 6, price: 850000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.workshop, ["Sunrise kebun teh", "Workshop on-field"]), }, { key: "open_diving", organizerKey: "fiersa", category: "DIVING", title: "Fun Dive Tulamben — USS Liberty Wreck", description: `Dive trip ke USS Liberty Wreck. ⚠️ Sertifikasi minimal: Open Water (PADI/SSI)`, meetingPoint: "Dive shop Tulamben, 06:30 WITA", itineraryItems: ITIN_DIVING, whatsIncluded: `• 2x dive guided • Full gear rental • Tank & weight • Konsumsi siang`, whatsExcluded: `• Transport ke Bali • Penginapan • Sertifikasi (cek validitas)`, destination: "USS Liberty Wreck", location: "Tulamben, Karangasem, Bali", date: relDate(33), endDate: relDate(34), maxParticipants: 6, price: 1850000, vibe: "HARDCORE", status: "OPEN", images: pics(PHOTO.diving, ["Wreck dive Tulamben", "Reef Tulamben"]), }, { key: "open_festival", organizerKey: "fiersa", category: "CONCERT", title: "Nonton Festival Musik Bareng — Pestapora", description: `Cari rombongan buat nonton festival musik biar gak sendirian? 🎫 Tiket BUKAN termasuk — peserta bawa masing-masing 🤝 Grup untuk koordinasi meet-up, line-up favorit & pulang bareng`, meetingPoint: "Gate utama venue, 15:00 WIB", whatsIncluded: `• Koordinasi grup + grup WA • Foto bareng • Atur jadwal nonton line-up favorit`, whatsExcluded: `• Tiket festival (bawa sendiri!) • Transport ke venue`, destination: "Festival Musik Pestapora", location: "Jakarta", date: relDate(33), endDate: null, maxParticipants: 8, price: 0, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.concert, ["Festival musik"]), }, { key: "open_rinjani", organizerKey: "ayu", category: "HIKING", title: "Pendakian Rinjani Summit 3D2N via Sembalun", description: `Summit Rinjani 3.726 mdpl + camp di tepi Danau Segara Anak. 🏔️ ⚠️ Level: LANJUT — wajib fisik prima & pengalaman gunung 🔥 Salah satu pendakian terbaik di Indonesia`, meetingPoint: "Basecamp Sembalun, Lombok Timur, 06:00 WITA", itineraryItems: ITIN_RINJANI, whatsIncluded: `• Guide + porter + tenda tim • Konsumsi 8x selama trek • Simaksi TN Gunung Rinjani • Transport basecamp PP Mataram`, whatsExcluded: `• Tiket pesawat ke Lombok • Sleeping bag & carrier pribadi • Asuransi perjalanan`, destination: "Gunung Rinjani", location: "Lombok Timur, NTB", date: relDate(35), endDate: relDate(37), maxParticipants: 10, price: 2750000, vibe: "HARDCORE", status: "OPEN", images: pics(PHOTO.hiking, ["Puncak Rinjani", "Danau Segara Anak"]), }, { key: "open_belitung", organizerKey: "ayu", category: "ISLAND_HOPPING", title: "Belitung Island Hopping 3D2N", description: `Pulau-pulau batu granit raksasa & pantai pasir sehalus tepung. 🪨 Pulau Lengkuas (mercusuar), Pulau Batu Berlayar, Tanjung Tinggi`, meetingPoint: "Bandara H.A.S. Hanandjoeddin, Tanjung Pandan", whatsIncluded: `• Penginapan 2 malam • Boat hopping + alat snorkel • Mobil + sopir selama trip • Makan 6x`, whatsExcluded: `• Tiket pesawat ke Belitung • Pengeluaran pribadi`, destination: "Kepulauan Belitung", location: "Belitung, Bangka Belitung", date: relDate(38), endDate: relDate(39), maxParticipants: 12, price: 1350000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.beach, ["Pulau Lengkuas", "Pantai batu granit"]), }, { key: "open_islandhop", organizerKey: "panji", category: "ISLAND_HOPPING", title: "Karimun Jawa Island Hopping 3D2N", description: `Hopping 5 pulau favorit di Karimun Jawa. 🏝️ Cocok untuk solo traveler & couple`, meetingPoint: "Pelabuhan Kartini Jepara, Jumat 07:00 WIB", itineraryItems: ITIN_ISLANDHOP, whatsIncluded: `• Tiket kapal feri PP Jepara–Karimun • Homestay 2 malam (twin sharing) • Boat hopping 2 hari • Alat snorkel • Makan 6x`, whatsExcluded: `• Transport ke Jepara • Tiket pesawat`, destination: "Kepulauan Karimun Jawa", location: "Jepara, Jawa Tengah", date: relDate(40), endDate: relDate(42), maxParticipants: 12, price: 1450000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.beach, ["Pantai Karimun", "Boat hopping"]), }, { key: "open_ijen", organizerKey: "rangga", category: "HIKING", title: "Kawah Ijen Blue Fire Tour", description: `Fenomena api biru langka — hanya ada 2 di dunia. 🔥💙 ⚠️ Trek malam, wajib masker gas (disediakan) 🌅 Lanjut sunrise di kawah belerang toska`, meetingPoint: "Pos Paltuding, Banyuwangi, 00:30 WIB", whatsIncluded: `• Guide lokal + masker gas • Tiket masuk kawasan • Sarapan + kopi`, whatsExcluded: `• Transport ke Banyuwangi • Sewa headlamp`, destination: "Kawah Ijen", location: "Banyuwangi, Jawa Timur", date: relDate(41), endDate: relDate(42), maxParticipants: 12, price: 525000, vibe: "HARDCORE", status: "OPEN", images: pics(PHOTO.hiking, ["Kawah Ijen", "Sunrise di kawah"]), }, { key: "open_concert", organizerKey: "fiersa", category: "CONCERT", title: "Nonton Coldplay Bareng — Music of the Spheres Jakarta", description: `Cari teman buat nonton Coldplay tapi gak mau nonton sendirian? Gabung grup ini. 🎤 Tiket BUKAN termasuk — peserta bawa tiket masing-masing 🤝 Grup hanya untuk koordinasi meet-up & after-party`, meetingPoint: "Plaza GBK, depan loket Cat 1, 17:00 WIB", itineraryItems: ITIN_CONCERT, whatsIncluded: `• Koordinasi grup • Foto bareng pre-show • After-party dinner di Senayan`, whatsExcluded: `• Tiket konser (bawa sendiri!) • Transport ke GBK`, destination: "Coldplay — Music of the Spheres", location: "Stadion Utama GBK, Jakarta", date: relDate(45), endDate: null, maxParticipants: 6, price: 0, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.concert, ["Konser malam"]), }, { key: "open_komodo", organizerKey: "ayu", category: "ISLAND_HOPPING", title: "Sailing Komodo 3D2N — Open Trip Phinisi", description: `Live-on-board kapal phinisi keliling Taman Nasional Komodo. ⛵ 🦎 Pulau Padar, Pink Beach, ketemu komodo, Manta Point`, meetingPoint: "Pelabuhan Labuan Bajo, Flores, 08:00 WITA", whatsIncluded: `• Kapal phinisi (kabin sharing) 2 malam • Makan 8x + snack • Alat snorkel • Tiket masuk TN Komodo + ranger`, whatsExcluded: `• Tiket pesawat ke Labuan Bajo • Tipping kru kapal`, destination: "Taman Nasional Komodo", location: "Labuan Bajo, NTT", date: relDate(50), endDate: relDate(52), maxParticipants: 16, price: 3650000, vibe: "BALANCED", status: "OPEN", images: pics(PHOTO.beach, ["Pulau Padar", "Pink Beach Komodo"]), }, { key: "open_retreat", organizerKey: "fiersa", category: "RETREAT", title: "Mindfulness Retreat 3D — Ubud", description: `Retreat 3 hari di tengah sawah Ubud. 🧘 Untuk yang lagi burnout & butuh reset 👥 Grup kecil (max 8) — pengalaman akrab`, meetingPoint: "Villa Sawah Ubud (alamat dikirim H-3 via WA)", itineraryItems: ITIN_RETREAT, whatsIncluded: `• Penginapan villa 2 malam • Yoga 4 sesi + meditasi 6 sesi • Sound healing (1 sesi) • Konsumsi vegan 6x • Welcome kit (jurnal, herbal tea)`, whatsExcluded: `• Transport ke Ubud • Treatment spa opsional`, destination: "Mindfulness Retreat Ubud", location: "Ubud, Gianyar, Bali", date: relDate(52), endDate: relDate(54), maxParticipants: 8, price: 2400000, vibe: "CHILL", status: "OPEN", images: pics(PHOTO.retreat, ["Yoga di sawah Ubud", "Villa retreat"]), }, { key: "open_yoga_retreat", organizerKey: "fiersa", category: "RETREAT", title: "Weekend Yoga & Digital Detox — Selabintana", description: `Lepas dari notifikasi 2 hari penuh di kaki Gunung Gede. 📵 Digital detox — HP dititip saat sesi 🧘 Yoga, hiking ringan, journaling`, meetingPoint: "Resort Selabintana, Sukabumi, Sabtu 13:00 WIB", whatsIncluded: `• Penginapan resort 1 malam • Yoga 3 sesi + meditasi • Hiking ringan berpemandu • Konsumsi sehat 4x`, whatsExcluded: `• Transport ke Sukabumi • Treatment tambahan`, destination: "Yoga Retreat Selabintana", location: "Sukabumi, Jawa Barat", date: relDate(58), endDate: relDate(60), maxParticipants: 10, price: 1950000, vibe: "CHILL", status: "OPEN", images: pics(PHOTO.retreat, ["Sesi yoga pagi", "Resort di kaki gunung"]), }, ]; type TripMap = Record; async function seedTrips(users: UserMap): Promise { const map: TripMap = {}; for (const t of SEED_TRIPS) { const organizer = users[t.organizerKey]; if (!organizer) { throw new Error(`Organizer ${t.organizerKey} tidak ditemukan`); } const created = await prisma.trip.create({ data: { category: t.category, title: t.title, description: t.description, meetingPoint: t.meetingPoint, whatsIncluded: t.whatsIncluded, whatsExcluded: t.whatsExcluded, destination: t.destination, location: t.location, date: t.date, endDate: t.endDate, maxParticipants: t.maxParticipants, price: t.price, vibe: t.vibe, status: t.status, organizerId: organizer.id, images: t.images.length ? { create: t.images.map((im, order) => ({ url: im.url, caption: im.caption, order, })), } : undefined, itineraryItems: t.itineraryItems?.length ? { create: t.itineraryItems.map((item, order) => ({ day: item.day, startTime: item.startTime, endTime: item.endTime ?? null, activity: item.activity, order, })), } : undefined, }, }); map[t.key] = { id: created.id, price: created.price, status: t.status }; } return map; } // ============================================================================ // 5. Participants + Booking + Payment // Setiap baris = (tripKey, userKey, intent). `intent` mendeskripsikan // state akhir yang kita simulasikan, supaya UI menampilkan beragam kondisi. // // Mapping intent → (Participant, Booking, Payment): // - "PENDING_REQUEST" : Participant PENDING, Booking PENDING, no Payment // - "AWAITING_PAY" : Participant CONFIRMED, Booking AWAITING_PAY, no Payment // - "MARKED_PAID" : Participant CONFIRMED, Booking AWAITING_PAY, Payment AWAITING (peserta klik "sudah transfer") // - "PAID" : Participant CONFIRMED, Booking PAID, Payment PAID (organizer sudah konfirmasi) // - "FREE_CONFIRMED" : Participant CONFIRMED, Booking PAID, no Payment (trip gratis) // - "CANCELLED" : Participant CANCELLED, Booking CANCELLED, no Payment // ============================================================================ type Intent = | "PENDING_REQUEST" | "AWAITING_PAY" | "MARKED_PAID" | "PAID" | "FREE_CONFIRMED" | "CANCELLED"; interface SeedParticipant { tripKey: string; userKey: string; intent: Intent; } const SEED_PARTICIPANTS: SeedParticipant[] = [ // ---------- TRIP MASA LALU (COMPLETED) — semua PAID, jadi pondasi review ---------- { tripKey: "past_papandayan", userKey: "budi", intent: "PAID" }, { tripKey: "past_papandayan", userKey: "sari", intent: "PAID" }, { tripKey: "past_papandayan", userKey: "doni", intent: "PAID" }, { tripKey: "past_papandayan", userKey: "maya", intent: "PAID" }, { tripKey: "past_pahawang", userKey: "budi", intent: "PAID" }, { tripKey: "past_pahawang", userKey: "maya", intent: "PAID" }, { tripKey: "past_pahawang", userKey: "raka", intent: "PAID" }, { tripKey: "past_culinary", userKey: "sari", intent: "PAID" }, { tripKey: "past_culinary", userKey: "doni", intent: "PAID" }, { tripKey: "past_culinary", userKey: "raka", intent: "CANCELLED" }, // batal H-1 { tripKey: "past_prau", userKey: "budi", intent: "PAID" }, { tripKey: "past_prau", userKey: "intan", intent: "PAID" }, { tripKey: "past_prau", userKey: "galih", intent: "PAID" }, { tripKey: "past_nusapenida", userKey: "sari", intent: "PAID" }, { tripKey: "past_nusapenida", userKey: "wina", intent: "PAID" }, { tripKey: "past_nusapenida", userKey: "bayu", intent: "PAID" }, // CLOSED trip — peserta yang ke-refund (di-mark CANCELLED) { tripKey: "past_workshop_cancelled", userKey: "budi", intent: "CANCELLED" }, { tripKey: "past_workshop_cancelled", userKey: "doni", intent: "CANCELLED" }, // ---------- TRIP MASA DEPAN — mix state ---------- // Kota Tua { tripKey: "open_kotatua", userKey: "galih", intent: "PAID" }, { tripKey: "open_kotatua", userKey: "wina", intent: "PAID" }, { tripKey: "open_kotatua", userKey: "intan", intent: "PAID" }, { tripKey: "open_kotatua", userKey: "raka", intent: "PENDING_REQUEST" }, // Prau { tripKey: "open_prau", userKey: "intan", intent: "PAID" }, { tripKey: "open_prau", userKey: "galih", intent: "AWAITING_PAY" }, { tripKey: "open_prau", userKey: "bayu", intent: "PENDING_REQUEST" }, // Culinary Bandung — penuh joiner { tripKey: "open_culinary", userKey: "budi", intent: "PAID" }, { tripKey: "open_culinary", userKey: "sari", intent: "PAID" }, { tripKey: "open_culinary", userKey: "doni", intent: "PAID" }, { tripKey: "open_culinary", userKey: "maya", intent: "PAID" }, { tripKey: "open_culinary", userKey: "raka", intent: "PAID" }, // Pulau Harapan — FULL (6 peserta = maxParticipants) { tripKey: "open_seribu", userKey: "budi", intent: "PAID" }, { tripKey: "open_seribu", userKey: "sari", intent: "PAID" }, { tripKey: "open_seribu", userKey: "maya", intent: "PAID" }, { tripKey: "open_seribu", userKey: "raka", intent: "PAID" }, { tripKey: "open_seribu", userKey: "intan", intent: "PAID" }, { tripKey: "open_seribu", userKey: "galih", intent: "PAID" }, // Papandayan — campur semua state biar dashboard organizer kaya { tripKey: "open_papandayan", userKey: "budi", intent: "PAID" }, { tripKey: "open_papandayan", userKey: "sari", intent: "MARKED_PAID" }, { tripKey: "open_papandayan", userKey: "doni", intent: "AWAITING_PAY" }, { tripKey: "open_papandayan", userKey: "raka", intent: "PENDING_REQUEST" }, // Bromo { tripKey: "open_bromo", userKey: "budi", intent: "PAID" }, { tripKey: "open_bromo", userKey: "wina", intent: "PAID" }, { tripKey: "open_bromo", userKey: "raka", intent: "MARKED_PAID" }, { tripKey: "open_bromo", userKey: "intan", intent: "PENDING_REQUEST" }, // Camping Ranca Upas { tripKey: "open_camping", userKey: "budi", intent: "PAID" }, { tripKey: "open_camping", userKey: "sari", intent: "PAID" }, { tripKey: "open_camping", userKey: "doni", intent: "MARKED_PAID" }, { tripKey: "open_camping", userKey: "maya", intent: "AWAITING_PAY" }, { tripKey: "open_camping", userKey: "raka", intent: "PENDING_REQUEST" }, // Kuliner Semarang { tripKey: "open_semarang_kuliner", userKey: "budi", intent: "PAID" }, { tripKey: "open_semarang_kuliner", userKey: "galih", intent: "PAID" }, { tripKey: "open_semarang_kuliner", userKey: "bayu", intent: "MARKED_PAID" }, // Nusa Penida { tripKey: "open_nusapenida", userKey: "sari", intent: "PAID" }, { tripKey: "open_nusapenida", userKey: "maya", intent: "PAID" }, { tripKey: "open_nusapenida", userKey: "wina", intent: "MARKED_PAID" }, // City trip Jogja { tripKey: "open_citytrip", userKey: "budi", intent: "PAID" }, { tripKey: "open_citytrip", userKey: "maya", intent: "PAID" }, { tripKey: "open_citytrip", userKey: "sari", intent: "PENDING_REQUEST" }, // Ciremai { tripKey: "open_ciremai", userKey: "budi", intent: "PAID" }, { tripKey: "open_ciremai", userKey: "maya", intent: "AWAITING_PAY" }, // Camping & curug Sentul { tripKey: "open_sentul_camp", userKey: "intan", intent: "PAID" }, { tripKey: "open_sentul_camp", userKey: "wina", intent: "AWAITING_PAY" }, { tripKey: "open_sentul_camp", userKey: "raka", intent: "PENDING_REQUEST" }, // Merbabu { tripKey: "open_merbabu", userKey: "galih", intent: "PAID" }, { tripKey: "open_merbabu", userKey: "bayu", intent: "AWAITING_PAY" }, // Snorkeling Pahawang { tripKey: "open_snorkel", userKey: "sari", intent: "PAID" }, { tripKey: "open_snorkel", userKey: "maya", intent: "MARKED_PAID" }, { tripKey: "open_snorkel", userKey: "raka", intent: "PENDING_REQUEST" }, // Diving Lembongan { tripKey: "open_lembongan_dive", userKey: "doni", intent: "PAID" }, { tripKey: "open_lembongan_dive", userKey: "bayu", intent: "AWAITING_PAY" }, // Diving Tulamben { tripKey: "open_diving", userKey: "doni", intent: "AWAITING_PAY" }, // Festival (FREE) — Booking langsung PAID { tripKey: "open_festival", userKey: "raka", intent: "FREE_CONFIRMED" }, { tripKey: "open_festival", userKey: "bayu", intent: "FREE_CONFIRMED" }, { tripKey: "open_festival", userKey: "galih", intent: "PENDING_REQUEST" }, // Rinjani { tripKey: "open_rinjani", userKey: "doni", intent: "AWAITING_PAY" }, { tripKey: "open_rinjani", userKey: "bayu", intent: "PENDING_REQUEST" }, // Belitung { tripKey: "open_belitung", userKey: "wina", intent: "PAID" }, { tripKey: "open_belitung", userKey: "bayu", intent: "PAID" }, { tripKey: "open_belitung", userKey: "doni", intent: "PENDING_REQUEST" }, // Island hop Karimun { tripKey: "open_islandhop", userKey: "budi", intent: "PAID" }, { tripKey: "open_islandhop", userKey: "sari", intent: "PAID" }, { tripKey: "open_islandhop", userKey: "maya", intent: "MARKED_PAID" }, { tripKey: "open_islandhop", userKey: "raka", intent: "PENDING_REQUEST" }, // Kawah Ijen { tripKey: "open_ijen", userKey: "doni", intent: "PENDING_REQUEST" }, { tripKey: "open_ijen", userKey: "raka", intent: "PENDING_REQUEST" }, // Concert Coldplay (FREE) { tripKey: "open_concert", userKey: "maya", intent: "FREE_CONFIRMED" }, { tripKey: "open_concert", userKey: "raka", intent: "FREE_CONFIRMED" }, { tripKey: "open_concert", userKey: "sari", intent: "PENDING_REQUEST" }, // Komodo { tripKey: "open_komodo", userKey: "budi", intent: "AWAITING_PAY" }, { tripKey: "open_komodo", userKey: "intan", intent: "PENDING_REQUEST" }, // Workshop fotografi { tripKey: "open_workshop", userKey: "doni", intent: "PAID" }, { tripKey: "open_workshop", userKey: "sari", intent: "AWAITING_PAY" }, // Retreat Ubud { tripKey: "open_retreat", userKey: "maya", intent: "PENDING_REQUEST" }, // Yoga retreat Selabintana { tripKey: "open_yoga_retreat", userKey: "maya", intent: "PENDING_REQUEST" }, { tripKey: "open_yoga_retreat", userKey: "sari", intent: "PENDING_REQUEST" }, ]; interface BookingPlan { participantStatus: "PENDING" | "CONFIRMED" | "CANCELLED"; bookingStatus: "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED"; payment: null | { status: "AWAITING" | "PAID" }; markedPaidAt: Date | null; paymentConfirmedAt: Date | null; } function planFromIntent(intent: Intent, now: Date): BookingPlan { switch (intent) { case "PENDING_REQUEST": return { participantStatus: "PENDING", bookingStatus: "PENDING", payment: null, markedPaidAt: null, paymentConfirmedAt: null, }; case "AWAITING_PAY": return { participantStatus: "CONFIRMED", bookingStatus: "AWAITING_PAY", payment: null, markedPaidAt: null, paymentConfirmedAt: null, }; case "MARKED_PAID": return { participantStatus: "CONFIRMED", bookingStatus: "AWAITING_PAY", payment: { status: "AWAITING" }, markedPaidAt: now, paymentConfirmedAt: null, }; case "PAID": return { participantStatus: "CONFIRMED", bookingStatus: "PAID", payment: { status: "PAID" }, markedPaidAt: now, paymentConfirmedAt: now, }; case "FREE_CONFIRMED": return { participantStatus: "CONFIRMED", bookingStatus: "PAID", payment: null, markedPaidAt: null, paymentConfirmedAt: null, }; case "CANCELLED": return { participantStatus: "CANCELLED", bookingStatus: "CANCELLED", payment: null, markedPaidAt: null, paymentConfirmedAt: null, }; } } async function seedParticipants(trips: TripMap, users: UserMap) { const now = new Date(); for (const sp of SEED_PARTICIPANTS) { const trip = trips[sp.tripKey]; const user = users[sp.userKey]; if (!trip || !user) continue; const plan = planFromIntent(sp.intent, now); const participant = await prisma.tripParticipant.create({ data: { tripId: trip.id, userId: user.id, status: plan.participantStatus, markedPaidAt: plan.markedPaidAt, paymentConfirmedAt: plan.paymentConfirmedAt, }, }); const booking = await prisma.booking.create({ data: { tripId: trip.id, userId: user.id, participantId: participant.id, amount: trip.price, status: plan.bookingStatus, }, }); if (plan.payment) { const data: Prisma.PaymentUncheckedCreateInput = { bookingId: booking.id, provider: "MANUAL", externalOrderId: `manual-${booking.id}`, amount: trip.price, status: plan.payment.status, method: "manual_transfer", paidAt: plan.payment.status === "PAID" ? now : null, }; await prisma.payment.create({ data }); } } } // ============================================================================ // 6. Reviews — hanya untuk trip COMPLETED, dari peserta CONFIRMED non-organizer. // ============================================================================ interface SeedReview { tripKey: string; userKey: string; rating: number; comment: string; } const SEED_REVIEWS: SeedReview[] = [ { tripKey: "past_papandayan", userKey: "budi", rating: 5, comment: "Trip-nya santai tapi tetep seru. Guide-nya sabar banget sama yang baru pertama mendaki.", }, { tripKey: "past_papandayan", userKey: "sari", rating: 5, comment: "Worth it banget! Sunrise di Tegal Alun cakep, makanan juga oke.", }, { tripKey: "past_papandayan", userKey: "doni", rating: 4, comment: "Trip aman dan terorganisir. Kurangin lama briefing pagi aja.", }, { tripKey: "past_papandayan", userKey: "maya", rating: 5, comment: "Bang Dede bener-bener jaga energi grup. Highly recommended.", }, { tripKey: "past_pahawang", userKey: "budi", rating: 4, comment: "Spot snorkeling-nya bagus, ikan rame. Kapal agak lama nunggu di dermaga.", }, { tripKey: "past_pahawang", userKey: "maya", rating: 5, comment: "Pertama kali snorkel & gak takut karena guide-nya stand-by. Mantap!", }, { tripKey: "past_pahawang", userKey: "raka", rating: 5, comment: "Sunset Pahawang Kecil top. Bakal ikut lagi.", }, { tripKey: "past_culinary", userKey: "sari", rating: 4, comment: "8 spot dalam 1 hari, perut sampe penuh. Mie kocoknya juara.", }, { tripKey: "past_culinary", userKey: "doni", rating: 3, comment: "Seru tapi rute pagi-siang kepadetan, jadwal molor 1 jam.", }, { tripKey: "past_prau", userKey: "budi", rating: 5, comment: "Sunrise Prau emang gak boong. Track-nya pendek, cocok bawa pemula.", }, { tripKey: "past_prau", userKey: "intan", rating: 4, comment: "Pemandangan juara. Camp area agak rame karena weekend, tapi tim handle dengan baik.", }, { tripKey: "past_prau", userKey: "galih", rating: 5, comment: "Bang Rangga ramah, logistik lengkap. Kopi pagi di puncak gak terlupakan.", }, { tripKey: "past_nusapenida", userKey: "sari", rating: 5, comment: "Kelingking Beach se-keren itu aslinya. Sopir + guide-nya enak diajak ngobrol.", }, { tripKey: "past_nusapenida", userKey: "wina", rating: 4, comment: "Spotnya bagus semua, cuma antrian foto di Kelingking lumayan panjang. Overall puas.", }, { tripKey: "past_nusapenida", userKey: "bayu", rating: 5, comment: "Crystal Bay jernih banget buat snorkeling. Mbak Ayu organizer yang rapi.", }, ]; async function seedReviews(trips: TripMap, users: UserMap) { for (const r of SEED_REVIEWS) { const trip = trips[r.tripKey]; const user = users[r.userKey]; if (!trip || !user) continue; await prisma.tripReview.create({ data: { tripId: trip.id, userId: user.id, rating: r.rating, comment: r.comment, }, }); } } // ============================================================================ // Main // ============================================================================ async function main() { console.log("🌱 Seeding database...\n"); console.log("⏳ Cleanup — hapus semua data lama..."); await cleanup(); console.log("⏳ Seed users + profile..."); const users = await seedUsers(); const admin = users["admin"]; if (!admin) throw new Error("Admin user gagal dibuat"); console.log("⏳ Seed organizer verifications..."); await seedVerifications(users, admin.id); console.log("⏳ Seed trips..."); const trips = await seedTrips(users); console.log("⏳ Seed participants + bookings + payments..."); await seedParticipants(trips, users); console.log("⏳ Seed reviews..."); await seedReviews(trips, users); const countBy = (s: SeedTrip["status"]) => SEED_TRIPS.filter((t) => t.status === s).length; const organizerCount = SEED_VERIFICATIONS.filter( (v) => v.status === "APPROVED" ).length; console.log(` ✅ Selesai. Database fresh. Ringkasan: ${SEED_USERS.length} users (1 admin, ${organizerCount} organizer terverifikasi, sisanya peserta) ${SEED_VERIFICATIONS.length} organizer verifications (${organizerCount} APPROVED, 1 PENDING, 1 REJECTED) ${SEED_TRIPS.length} trips (${countBy("COMPLETED")} COMPLETED, ${countBy("CLOSED")} CLOSED, ${countBy("OPEN")} OPEN, ${countBy("FULL")} FULL) ${SEED_PARTICIPANTS.length} participants (mix PENDING/CONFIRMED/CANCELLED) ${SEED_REVIEWS.length} reviews 📅 Semua tanggal relatif terhadap hari seed dijalankan — trip OPEN selalu di masa depan. 🔐 Login info: Admin : admin@setrip.id Organizer : dede.inoen@setrip.id, panji@setrip.id, fiersa@setrip.id, rangga@setrip.id, ayu@setrip.id Peserta : budi/sari/doni/maya/raka/intan/galih/wina/bayu @gmail.com Password : ${SEED_PASSWORD} `); } main() .catch((e) => { console.error("❌ Seed failed:", e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });