2102 lines
80 KiB
TypeScript
2102 lines
80 KiB
TypeScript
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<string, { id: string; email: string; name: string }>;
|
||
|
||
async function seedUsers(): Promise<UserMap> {
|
||
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<string, { id: string; price: number; status: string }>;
|
||
|
||
async function seedTrips(users: UserMap): Promise<TripMap> {
|
||
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();
|
||
});
|