/** * Seed script for MOPC Onboarding Form * * This creates the application form configuration for the Monaco Ocean Protection Challenge. * The form is accessible at /apply/mopc-2026 * * Run with: npx tsx prisma/seed-mopc-onboarding.ts */ import { PrismaClient, FormFieldType, SpecialFieldType } from '@prisma/client' const prisma = new PrismaClient() const MOPC_FORM_CONFIG = { name: 'MOPC Application 2026', description: 'Monaco Ocean Protection Challenge application form', publicSlug: 'mopc-2026', status: 'PUBLISHED', isPublic: true, sendConfirmationEmail: true, sendTeamInviteEmails: true, confirmationEmailSubject: 'Application Received - Monaco Ocean Protection Challenge', confirmationEmailBody: `Thank you for applying to the Monaco Ocean Protection Challenge 2026! We have received your application and our team will review it carefully. If you have any questions, please don't hesitate to reach out. Good luck! The MOPC Team`, confirmationMessage: 'Thank you for your application! We have sent a confirmation email to the address you provided. Our team will review your submission and get back to you soon.', } const STEPS = [ { name: 'category', title: 'Competition Category', description: 'Select your competition track', sortOrder: 0, isOptional: false, fields: [ { name: 'competitionCategory', label: 'Which category best describes your project?', fieldType: FormFieldType.RADIO, specialType: SpecialFieldType.COMPETITION_CATEGORY, required: true, sortOrder: 0, width: 'full', projectMapping: 'competitionCategory', description: 'Choose the category that best fits your stage of development', optionsJson: [ { value: 'STARTUP', label: 'Startup', description: 'You have an existing company or registered business entity', }, { value: 'BUSINESS_CONCEPT', label: 'Business Concept', description: 'You are a student, graduate, or have an idea not yet incorporated', }, ], }, ], }, { name: 'contact', title: 'Contact Information', description: 'Tell us how to reach you', sortOrder: 1, isOptional: false, fields: [ { name: 'contactName', label: 'Full Name', fieldType: FormFieldType.TEXT, required: true, sortOrder: 0, width: 'half', placeholder: 'Enter your full name', }, { name: 'contactEmail', label: 'Email Address', fieldType: FormFieldType.EMAIL, required: true, sortOrder: 1, width: 'half', placeholder: 'your.email@example.com', description: 'We will use this email for all communications', }, { name: 'contactPhone', label: 'Phone Number', fieldType: FormFieldType.PHONE, required: true, sortOrder: 2, width: 'half', placeholder: '+1 (555) 123-4567', }, { name: 'country', label: 'Country', fieldType: FormFieldType.SELECT, specialType: SpecialFieldType.COUNTRY_SELECT, required: true, sortOrder: 3, width: 'half', projectMapping: 'country', }, { name: 'city', label: 'City', fieldType: FormFieldType.TEXT, required: false, sortOrder: 4, width: 'half', placeholder: 'City name', }, ], }, { name: 'project', title: 'Project Details', description: 'Tell us about your ocean protection project', sortOrder: 2, isOptional: false, fields: [ { name: 'projectName', label: 'Project Name', fieldType: FormFieldType.TEXT, required: true, sortOrder: 0, width: 'full', projectMapping: 'title', maxLength: 200, placeholder: 'Give your project a memorable name', }, { name: 'teamName', label: 'Team / Company Name', fieldType: FormFieldType.TEXT, required: false, sortOrder: 1, width: 'half', projectMapping: 'teamName', placeholder: 'Your team or company name', }, { name: 'oceanIssue', label: 'Primary Ocean Issue', fieldType: FormFieldType.SELECT, specialType: SpecialFieldType.OCEAN_ISSUE, required: true, sortOrder: 2, width: 'half', projectMapping: 'oceanIssue', description: 'Select the primary ocean issue your project addresses', }, { name: 'description', label: 'Project Description', fieldType: FormFieldType.TEXTAREA, required: true, sortOrder: 3, width: 'full', projectMapping: 'description', minLength: 50, maxLength: 2000, placeholder: 'Describe your project, its goals, and how it will help protect the ocean...', description: 'Provide a clear description of your project (50-2000 characters)', }, { name: 'websiteUrl', label: 'Website URL', fieldType: FormFieldType.URL, required: false, sortOrder: 4, width: 'half', projectMapping: 'websiteUrl', placeholder: 'https://yourproject.com', }, ], }, { name: 'team', title: 'Team Members', description: 'Add your team members (they will receive email invitations)', sortOrder: 3, isOptional: true, fields: [ { name: 'teamMembers', label: 'Team Members', fieldType: FormFieldType.TEXT, // Will use specialType for rendering specialType: SpecialFieldType.TEAM_MEMBERS, required: false, sortOrder: 0, width: 'full', description: 'Add up to 5 team members. They will receive an invitation email to join your application.', }, ], }, { name: 'additional', title: 'Additional Details', description: 'A few more questions about your project', sortOrder: 4, isOptional: false, fields: [ { name: 'institution', label: 'University / School', fieldType: FormFieldType.TEXT, required: false, sortOrder: 0, width: 'half', projectMapping: 'institution', placeholder: 'Name of your institution', conditionJson: { field: 'competitionCategory', operator: 'equals', value: 'BUSINESS_CONCEPT', }, }, { name: 'startupCreatedDate', label: 'Startup Founded Date', fieldType: FormFieldType.DATE, required: false, sortOrder: 1, width: 'half', description: 'When was your company founded?', conditionJson: { field: 'competitionCategory', operator: 'equals', value: 'STARTUP', }, }, { name: 'wantsMentorship', label: 'I am interested in receiving mentorship', fieldType: FormFieldType.CHECKBOX, required: false, sortOrder: 2, width: 'full', projectMapping: 'wantsMentorship', description: 'Check this box if you would like to be paired with an expert mentor', }, { name: 'referralSource', label: 'How did you hear about MOPC?', fieldType: FormFieldType.SELECT, required: false, sortOrder: 3, width: 'half', optionsJson: [ { value: 'social_media', label: 'Social Media' }, { value: 'search_engine', label: 'Search Engine' }, { value: 'word_of_mouth', label: 'Word of Mouth' }, { value: 'university', label: 'University / School' }, { value: 'partner', label: 'Partner Organization' }, { value: 'media', label: 'News / Media' }, { value: 'event', label: 'Event / Conference' }, { value: 'other', label: 'Other' }, ], }, ], }, { name: 'review', title: 'Review & Submit', description: 'Review your application and accept the terms', sortOrder: 5, isOptional: false, fields: [ { name: 'instructions', label: 'Review Instructions', fieldType: FormFieldType.INSTRUCTIONS, required: false, sortOrder: 0, width: 'full', description: 'Please review all the information you have provided. Once submitted, you will not be able to make changes.', }, { name: 'gdprConsent', label: 'I consent to the processing of my personal data in accordance with the GDPR and the MOPC Privacy Policy', fieldType: FormFieldType.CHECKBOX, specialType: SpecialFieldType.GDPR_CONSENT, required: true, sortOrder: 1, width: 'full', }, { name: 'termsAccepted', label: 'I have read and accept the Terms and Conditions of the Monaco Ocean Protection Challenge', fieldType: FormFieldType.CHECKBOX, required: true, sortOrder: 2, width: 'full', }, ], }, ] async function main() { console.log('Seeding MOPC onboarding form...') // Check if form already exists const existingForm = await prisma.applicationForm.findUnique({ where: { publicSlug: MOPC_FORM_CONFIG.publicSlug }, }) if (existingForm) { console.log('Form with slug "mopc-2026" already exists. Updating...') // Delete existing steps and fields to recreate them await prisma.applicationFormField.deleteMany({ where: { formId: existingForm.id }, }) await prisma.onboardingStep.deleteMany({ where: { formId: existingForm.id }, }) // Update the form await prisma.applicationForm.update({ where: { id: existingForm.id }, data: { name: MOPC_FORM_CONFIG.name, description: MOPC_FORM_CONFIG.description, status: MOPC_FORM_CONFIG.status, isPublic: MOPC_FORM_CONFIG.isPublic, sendConfirmationEmail: MOPC_FORM_CONFIG.sendConfirmationEmail, sendTeamInviteEmails: MOPC_FORM_CONFIG.sendTeamInviteEmails, confirmationEmailSubject: MOPC_FORM_CONFIG.confirmationEmailSubject, confirmationEmailBody: MOPC_FORM_CONFIG.confirmationEmailBody, confirmationMessage: MOPC_FORM_CONFIG.confirmationMessage, }, }) // Create steps and fields for (const stepData of STEPS) { const step = await prisma.onboardingStep.create({ data: { formId: existingForm.id, name: stepData.name, title: stepData.title, description: stepData.description, sortOrder: stepData.sortOrder, isOptional: stepData.isOptional, }, }) for (const fieldData of stepData.fields) { const field = fieldData as Record await prisma.applicationFormField.create({ data: { formId: existingForm.id, stepId: step.id, name: field.name as string, label: field.label as string, fieldType: field.fieldType as FormFieldType, specialType: (field.specialType as SpecialFieldType) || null, required: field.required as boolean, sortOrder: field.sortOrder as number, width: field.width as string, description: (field.description as string) || null, placeholder: (field.placeholder as string) || null, projectMapping: (field.projectMapping as string) || null, minLength: (field.minLength as number) || null, maxLength: (field.maxLength as number) || null, optionsJson: field.optionsJson as object | undefined, conditionJson: field.conditionJson as object | undefined, }, }) } console.log(` - Created step: ${stepData.title} (${stepData.fields.length} fields)`) } console.log(`\nForm updated: ${existingForm.id}`) return } // Create new form const form = await prisma.applicationForm.create({ data: { name: MOPC_FORM_CONFIG.name, description: MOPC_FORM_CONFIG.description, publicSlug: MOPC_FORM_CONFIG.publicSlug, status: MOPC_FORM_CONFIG.status, isPublic: MOPC_FORM_CONFIG.isPublic, sendConfirmationEmail: MOPC_FORM_CONFIG.sendConfirmationEmail, sendTeamInviteEmails: MOPC_FORM_CONFIG.sendTeamInviteEmails, confirmationEmailSubject: MOPC_FORM_CONFIG.confirmationEmailSubject, confirmationEmailBody: MOPC_FORM_CONFIG.confirmationEmailBody, confirmationMessage: MOPC_FORM_CONFIG.confirmationMessage, }, }) console.log(`Created form: ${form.id}`) // Create steps and fields for (const stepData of STEPS) { const step = await prisma.onboardingStep.create({ data: { formId: form.id, name: stepData.name, title: stepData.title, description: stepData.description, sortOrder: stepData.sortOrder, isOptional: stepData.isOptional, }, }) for (const fieldData of stepData.fields) { const field = fieldData as Record await prisma.applicationFormField.create({ data: { formId: form.id, stepId: step.id, name: field.name as string, label: field.label as string, fieldType: field.fieldType as FormFieldType, specialType: (field.specialType as SpecialFieldType) || null, required: field.required as boolean, sortOrder: field.sortOrder as number, width: field.width as string, description: (field.description as string) || null, placeholder: (field.placeholder as string) || null, projectMapping: (field.projectMapping as string) || null, minLength: (field.minLength as number) || null, maxLength: (field.maxLength as number) || null, optionsJson: field.optionsJson as object | undefined, conditionJson: field.conditionJson as object | undefined, }, }) } console.log(` - Created step: ${stepData.title} (${stepData.fields.length} fields)`) } console.log(`\nMOPC form seeded successfully!`) console.log(`Form ID: ${form.id}`) console.log(`Public URL: /apply/${form.publicSlug}`) } main() .catch((e) => { console.error(e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() })