Remove dynamic form builder and complete RoundProject→roundId migration
Major cleanup and schema migration: - Remove unused dynamic form builder system (ApplicationForm, ApplicationFormField, etc.) - Complete migration from RoundProject junction table to direct Project.roundId - Add sortOrder and entryNotificationType fields to Round model - Add country field to User model for mentor matching - Enhance onboarding with profile photo and country selection steps - Fix all TypeScript errors related to roundProjects references - Remove unused libraries (@radix-ui/react-toast, embla-carousel-react, vaul) Files removed: - admin/forms/* pages and related components - admin/onboarding/* pages - applicationForm.ts and onboarding.ts routers - Dynamic form builder Prisma models and enums Schema changes: - Removed ApplicationForm, ApplicationFormField, OnboardingStep, ApplicationFormSubmission, SubmissionFile models - Removed FormFieldType and SpecialFieldType enums - Added Round.sortOrder, Round.entryNotificationType - Added User.country Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,28 +3,28 @@ import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
async function check() {
|
||||
const projects = await prisma.project.count()
|
||||
console.log('Total projects:', projects)
|
||||
const projectCount = await prisma.project.count()
|
||||
console.log('Total projects:', projectCount)
|
||||
|
||||
const rounds = await prisma.round.findMany({
|
||||
include: {
|
||||
_count: { select: { roundProjects: true } }
|
||||
_count: { select: { projects: true } }
|
||||
}
|
||||
})
|
||||
|
||||
for (const r of rounds) {
|
||||
console.log(`Round: ${r.name} (id: ${r.id})`)
|
||||
console.log(` Projects: ${r._count.roundProjects}`)
|
||||
console.log(` Projects: ${r._count.projects}`)
|
||||
}
|
||||
|
||||
// Check if projects have programId set
|
||||
// Check sample projects with their round
|
||||
const sampleProjects = await prisma.project.findMany({
|
||||
select: { id: true, title: true, programId: true },
|
||||
select: { id: true, title: true, roundId: true },
|
||||
take: 5
|
||||
})
|
||||
console.log('\nSample projects:')
|
||||
for (const p of sampleProjects) {
|
||||
console.log(` ${p.title}: programId=${p.programId}`)
|
||||
console.log(` ${p.title}: roundId=${p.roundId}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,18 @@ async function cleanup() {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
roundProjects: { select: { id: true, projectId: true, project: { select: { id: true, title: true } } } },
|
||||
_count: { select: { roundProjects: true } }
|
||||
projects: { select: { id: true, title: true } },
|
||||
_count: { select: { projects: true } }
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`Found ${rounds.length} rounds:`)
|
||||
for (const round of rounds) {
|
||||
console.log(`- ${round.name} (slug: ${round.slug}): ${round._count.roundProjects} projects`)
|
||||
console.log(`- ${round.name} (slug: ${round.slug}): ${round._count.projects} projects`)
|
||||
}
|
||||
|
||||
// Find rounds with 9 or fewer projects (dummy data)
|
||||
const dummyRounds = rounds.filter(r => r._count.roundProjects <= 9)
|
||||
const dummyRounds = rounds.filter(r => r._count.projects <= 9)
|
||||
|
||||
if (dummyRounds.length > 0) {
|
||||
console.log(`\nDeleting ${dummyRounds.length} dummy round(s)...`)
|
||||
@@ -29,15 +29,9 @@ async function cleanup() {
|
||||
for (const round of dummyRounds) {
|
||||
console.log(`\nProcessing: ${round.name}`)
|
||||
|
||||
const projectIds = round.roundProjects.map(rp => rp.projectId)
|
||||
const projectIds = round.projects.map(p => p.id)
|
||||
|
||||
if (projectIds.length > 0) {
|
||||
// Delete round-project associations first
|
||||
const rpDeleted = await prisma.roundProject.deleteMany({
|
||||
where: { roundId: round.id }
|
||||
})
|
||||
console.log(` Deleted ${rpDeleted.count} round-project associations`)
|
||||
|
||||
// Delete team members
|
||||
const teamDeleted = await prisma.teamMember.deleteMany({
|
||||
where: { projectId: { in: projectIds } }
|
||||
|
||||
@@ -8,15 +8,15 @@ async function cleanup() {
|
||||
// Find and delete the dummy round
|
||||
const dummyRound = await prisma.round.findFirst({
|
||||
where: { slug: 'round-1-2026' },
|
||||
include: { roundProjects: { include: { project: true } } }
|
||||
include: { projects: true }
|
||||
})
|
||||
|
||||
if (dummyRound) {
|
||||
console.log(`Found dummy round: ${dummyRound.name}`)
|
||||
console.log(`Projects in round: ${dummyRound.roundProjects.length}`)
|
||||
console.log(`Projects in round: ${dummyRound.projects.length}`)
|
||||
|
||||
// Get project IDs to delete
|
||||
const projectIds = dummyRound.roundProjects.map(rp => rp.projectId)
|
||||
const projectIds = dummyRound.projects.map(p => p.id)
|
||||
|
||||
// Delete team members for these projects
|
||||
if (projectIds.length > 0) {
|
||||
@@ -25,12 +25,6 @@ async function cleanup() {
|
||||
})
|
||||
console.log(`Deleted ${teamDeleted.count} team members`)
|
||||
|
||||
// Delete round-project associations
|
||||
await prisma.roundProject.deleteMany({
|
||||
where: { roundId: dummyRound.id }
|
||||
})
|
||||
console.log(`Deleted round-project associations`)
|
||||
|
||||
// Delete the projects
|
||||
const projDeleted = await prisma.project.deleteMany({
|
||||
where: { id: { in: projectIds } }
|
||||
|
||||
@@ -147,35 +147,6 @@ enum PartnerType {
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum FormFieldType {
|
||||
TEXT
|
||||
TEXTAREA
|
||||
NUMBER
|
||||
EMAIL
|
||||
PHONE
|
||||
URL
|
||||
DATE
|
||||
DATETIME
|
||||
SELECT
|
||||
MULTI_SELECT
|
||||
RADIO
|
||||
CHECKBOX
|
||||
CHECKBOX_GROUP
|
||||
FILE
|
||||
FILE_MULTIPLE
|
||||
SECTION
|
||||
INSTRUCTIONS
|
||||
}
|
||||
|
||||
enum SpecialFieldType {
|
||||
TEAM_MEMBERS // Team member repeater
|
||||
COMPETITION_CATEGORY // Business Concept vs Startup
|
||||
OCEAN_ISSUE // Ocean issue dropdown
|
||||
FILE_UPLOAD // File upload
|
||||
GDPR_CONSENT // GDPR consent checkbox
|
||||
COUNTRY_SELECT // Country dropdown
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// APPLICANT SYSTEM ENUMS
|
||||
// =============================================================================
|
||||
@@ -225,6 +196,7 @@ model User {
|
||||
status UserStatus @default(INVITED)
|
||||
expertiseTags String[] @default([])
|
||||
maxAssignments Int? // Per-round limit
|
||||
country String? // User's home country (for mentor matching)
|
||||
metadataJson Json? @db.JsonB
|
||||
|
||||
// Profile image
|
||||
@@ -348,10 +320,8 @@ model Program {
|
||||
|
||||
// Relations
|
||||
rounds Round[]
|
||||
projects Project[]
|
||||
learningResources LearningResource[]
|
||||
partners Partner[]
|
||||
applicationForms ApplicationForm[]
|
||||
specialAwards SpecialAward[]
|
||||
|
||||
@@unique([name, year])
|
||||
@@ -365,7 +335,10 @@ model Round {
|
||||
slug String? @unique // URL-friendly identifier for public submissions
|
||||
status RoundStatus @default(DRAFT)
|
||||
roundType RoundType @default(EVALUATION)
|
||||
sortOrder Int @default(0) // Progression order within program
|
||||
sortOrder Int @default(0) // Display order within program
|
||||
|
||||
// Entry notification settings
|
||||
entryNotificationType String? // Type of notification to send when project enters round
|
||||
|
||||
// Submission window (for applicant portal)
|
||||
submissionDeadline DateTime? // Deadline for project submissions
|
||||
@@ -385,15 +358,12 @@ model Round {
|
||||
requiredReviews Int @default(3) // Min evaluations per project
|
||||
settingsJson Json? @db.JsonB // Grace periods, visibility rules, etc.
|
||||
|
||||
// Notification sent to project team when they enter this round
|
||||
entryNotificationType String? // e.g., "ADVANCED_SEMIFINAL", "ADVANCED_FINAL"
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
||||
roundProjects RoundProject[]
|
||||
projects Project[]
|
||||
assignments Assignment[]
|
||||
evaluationForms EvaluationForm[]
|
||||
gracePeriods GracePeriod[]
|
||||
@@ -401,7 +371,6 @@ model Round {
|
||||
filteringRules FilteringRule[]
|
||||
filteringResults FilteringResult[]
|
||||
filteringJobs FilteringJob[]
|
||||
applicationForm ApplicationForm?
|
||||
|
||||
@@index([programId])
|
||||
@@index([status])
|
||||
@@ -439,7 +408,8 @@ model EvaluationForm {
|
||||
|
||||
model Project {
|
||||
id String @id @default(cuid())
|
||||
programId String
|
||||
roundId String
|
||||
status ProjectStatus @default(SUBMITTED)
|
||||
|
||||
// Core fields
|
||||
title String
|
||||
@@ -493,8 +463,7 @@ model Project {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
||||
roundProjects RoundProject[]
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
files ProjectFile[]
|
||||
assignments Assignment[]
|
||||
submittedBy User? @relation("ProjectSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
|
||||
@@ -504,8 +473,10 @@ model Project {
|
||||
awardEligibilities AwardEligibility[]
|
||||
awardVotes AwardVote[]
|
||||
wonAwards SpecialAward[] @relation("AwardWinner")
|
||||
projectTags ProjectTag[]
|
||||
|
||||
@@index([programId])
|
||||
@@index([roundId])
|
||||
@@index([status])
|
||||
@@index([tags])
|
||||
@@index([submissionSource])
|
||||
@@index([submittedByUserId])
|
||||
@@ -514,23 +485,6 @@ model Project {
|
||||
@@index([country])
|
||||
}
|
||||
|
||||
model RoundProject {
|
||||
id String @id @default(cuid())
|
||||
roundId String
|
||||
projectId String
|
||||
status ProjectStatus @default(SUBMITTED)
|
||||
addedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([roundId, projectId])
|
||||
@@index([roundId])
|
||||
@@index([projectId])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
model ProjectFile {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
@@ -906,149 +860,6 @@ model Partner {
|
||||
@@index([sortOrder])
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// APPLICATION FORMS (Phase 2)
|
||||
// =============================================================================
|
||||
|
||||
model ApplicationForm {
|
||||
id String @id @default(cuid())
|
||||
programId String? // null = global form
|
||||
name String
|
||||
description String? @db.Text
|
||||
status String @default("DRAFT") // DRAFT, PUBLISHED, CLOSED
|
||||
|
||||
isPublic Boolean @default(false)
|
||||
publicSlug String? @unique // /apply/ocean-challenge-2026
|
||||
submissionLimit Int?
|
||||
opensAt DateTime?
|
||||
closesAt DateTime?
|
||||
|
||||
confirmationMessage String? @db.Text
|
||||
|
||||
// Round linking (for onboarding forms that create projects)
|
||||
roundId String? @unique
|
||||
|
||||
// Email settings
|
||||
sendConfirmationEmail Boolean @default(true)
|
||||
sendTeamInviteEmails Boolean @default(true)
|
||||
confirmationEmailSubject String?
|
||||
confirmationEmailBody String? @db.Text
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
program Program? @relation(fields: [programId], references: [id], onDelete: SetNull)
|
||||
round Round? @relation(fields: [roundId], references: [id], onDelete: SetNull)
|
||||
fields ApplicationFormField[]
|
||||
steps OnboardingStep[]
|
||||
submissions ApplicationFormSubmission[]
|
||||
|
||||
@@index([programId])
|
||||
@@index([status])
|
||||
@@index([isPublic])
|
||||
@@index([roundId])
|
||||
}
|
||||
|
||||
model ApplicationFormField {
|
||||
id String @id @default(cuid())
|
||||
formId String
|
||||
stepId String? // Which step this field belongs to (for onboarding)
|
||||
fieldType FormFieldType
|
||||
name String // Internal name (e.g., "project_title")
|
||||
label String // Display label (e.g., "Project Title")
|
||||
description String? @db.Text
|
||||
placeholder String?
|
||||
|
||||
required Boolean @default(false)
|
||||
minLength Int?
|
||||
maxLength Int?
|
||||
minValue Float? // For NUMBER type
|
||||
maxValue Float? // For NUMBER type
|
||||
|
||||
optionsJson Json? @db.JsonB // For select/radio: [{ value, label }]
|
||||
conditionJson Json? @db.JsonB // Conditional logic: { fieldId, operator, value }
|
||||
|
||||
// Onboarding-specific fields
|
||||
projectMapping String? // Maps to Project column: "title", "description", etc.
|
||||
specialType SpecialFieldType? // Special handling for complex fields
|
||||
|
||||
sortOrder Int @default(0)
|
||||
width String @default("full") // full, half
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
form ApplicationForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
step OnboardingStep? @relation(fields: [stepId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([formId])
|
||||
@@index([stepId])
|
||||
@@index([sortOrder])
|
||||
}
|
||||
|
||||
model OnboardingStep {
|
||||
id String @id @default(cuid())
|
||||
formId String
|
||||
name String // Internal identifier (e.g., "category", "contact")
|
||||
title String // Display title (e.g., "Category", "Contact Information")
|
||||
description String? @db.Text
|
||||
sortOrder Int @default(0)
|
||||
isOptional Boolean @default(false)
|
||||
conditionJson Json? @db.JsonB // Conditional visibility: { fieldId, operator, value }
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
form ApplicationForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
fields ApplicationFormField[]
|
||||
|
||||
@@index([formId])
|
||||
@@index([sortOrder])
|
||||
}
|
||||
|
||||
model ApplicationFormSubmission {
|
||||
id String @id @default(cuid())
|
||||
formId String
|
||||
email String?
|
||||
name String?
|
||||
dataJson Json @db.JsonB // Field values: { fieldName: value, ... }
|
||||
status String @default("SUBMITTED") // SUBMITTED, REVIEWED, APPROVED, REJECTED
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
form ApplicationForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
files SubmissionFile[]
|
||||
|
||||
@@index([formId])
|
||||
@@index([status])
|
||||
@@index([email])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model SubmissionFile {
|
||||
id String @id @default(cuid())
|
||||
submissionId String
|
||||
fieldName String
|
||||
fileName String
|
||||
mimeType String?
|
||||
size Int?
|
||||
bucket String
|
||||
objectKey String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
submission ApplicationFormSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([submissionId])
|
||||
@@unique([bucket, objectKey])
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EXPERTISE TAGS (Phase 2B)
|
||||
// =============================================================================
|
||||
@@ -1065,11 +876,32 @@ model ExpertiseTag {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
projectTags ProjectTag[]
|
||||
|
||||
@@index([category])
|
||||
@@index([isActive])
|
||||
@@index([sortOrder])
|
||||
}
|
||||
|
||||
// Project-Tag relationship for AI tagging
|
||||
model ProjectTag {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
tagId String
|
||||
confidence Float @default(1.0) // AI confidence score 0-1
|
||||
source String @default("AI") // "AI" or "MANUAL"
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
tag ExpertiseTag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([projectId, tagId])
|
||||
@@index([projectId])
|
||||
@@index([tagId])
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LIVE VOTING (Phase 2B)
|
||||
// =============================================================================
|
||||
|
||||
@@ -321,7 +321,7 @@ async function main() {
|
||||
// Check if project already exists
|
||||
const existingProject = await prisma.project.findFirst({
|
||||
where: {
|
||||
programId: program.id,
|
||||
roundId: round.id,
|
||||
OR: [
|
||||
{ title: projectName },
|
||||
{ submittedByEmail: email },
|
||||
@@ -365,7 +365,7 @@ async function main() {
|
||||
// Create project
|
||||
const project = await prisma.project.create({
|
||||
data: {
|
||||
programId: program.id,
|
||||
roundId: round.id,
|
||||
title: projectName,
|
||||
description: row['Comment ']?.trim() || null,
|
||||
competitionCategory: mapCategory(row['Category']),
|
||||
@@ -391,15 +391,6 @@ async function main() {
|
||||
},
|
||||
})
|
||||
|
||||
// Create round-project association
|
||||
await prisma.roundProject.create({
|
||||
data: {
|
||||
roundId: round.id,
|
||||
projectId: project.id,
|
||||
status: 'SUBMITTED',
|
||||
},
|
||||
})
|
||||
|
||||
// Create team lead membership
|
||||
await prisma.teamMember.create({
|
||||
data: {
|
||||
@@ -474,7 +465,7 @@ async function main() {
|
||||
console.log('\nBackfilling missing country codes...\n')
|
||||
let backfilled = 0
|
||||
const nullCountryProjects = await prisma.project.findMany({
|
||||
where: { programId: program.id, country: null },
|
||||
where: { roundId: round.id, country: null },
|
||||
select: { id: true, submittedByEmail: true, title: true },
|
||||
})
|
||||
|
||||
|
||||
@@ -64,14 +64,13 @@ async function main() {
|
||||
|
||||
console.log(`Voting window: ${votingStart.toISOString()} → ${votingEnd.toISOString()}\n`)
|
||||
|
||||
// Get some projects to assign (via RoundProject)
|
||||
const roundProjects = await prisma.roundProject.findMany({
|
||||
// Get some projects to assign
|
||||
const projects = await prisma.project.findMany({
|
||||
where: { roundId: round.id },
|
||||
take: 8,
|
||||
orderBy: { addedAt: 'desc' },
|
||||
select: { project: { select: { id: true, title: true } } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: { id: true, title: true },
|
||||
})
|
||||
const projects = roundProjects.map(rp => rp.project)
|
||||
|
||||
if (projects.length === 0) {
|
||||
console.error('No projects found! Run seed-candidatures first.')
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
/**
|
||||
* Seed script for MOPC Onboarding Form (ESM version for production)
|
||||
* Run with: node prisma/seed-mopc-onboarding.mjs
|
||||
*/
|
||||
|
||||
import { PrismaClient } 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: 'RADIO',
|
||||
specialType: '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: 'TEXT', required: true, sortOrder: 0, width: 'half', placeholder: 'Enter your full name' },
|
||||
{ name: 'contactEmail', label: 'Email Address', fieldType: '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: 'PHONE', required: true, sortOrder: 2, width: 'half', placeholder: '+1 (555) 123-4567' },
|
||||
{ name: 'country', label: 'Country', fieldType: 'SELECT', specialType: 'COUNTRY_SELECT', required: true, sortOrder: 3, width: 'half', projectMapping: 'country' },
|
||||
{ name: 'city', label: 'City', fieldType: '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: '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: 'TEXT', required: false, sortOrder: 1, width: 'half', projectMapping: 'teamName', placeholder: 'Your team or company name' },
|
||||
{ name: 'oceanIssue', label: 'Primary Ocean Issue', fieldType: 'SELECT', specialType: '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: '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: '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: 'TEXT', specialType: '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: '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: '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: '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: '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: '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: 'CHECKBOX', specialType: '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: '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 field of stepData.fields) {
|
||||
await prisma.applicationFormField.create({
|
||||
data: {
|
||||
formId: existingForm.id,
|
||||
stepId: step.id,
|
||||
name: field.name,
|
||||
label: field.label,
|
||||
fieldType: field.fieldType,
|
||||
specialType: field.specialType || null,
|
||||
required: field.required,
|
||||
sortOrder: field.sortOrder,
|
||||
width: field.width,
|
||||
description: field.description || null,
|
||||
placeholder: field.placeholder || null,
|
||||
projectMapping: field.projectMapping || null,
|
||||
minLength: field.minLength || null,
|
||||
maxLength: field.maxLength || null,
|
||||
optionsJson: field.optionsJson || undefined,
|
||||
conditionJson: field.conditionJson || 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 field of stepData.fields) {
|
||||
await prisma.applicationFormField.create({
|
||||
data: {
|
||||
formId: form.id,
|
||||
stepId: step.id,
|
||||
name: field.name,
|
||||
label: field.label,
|
||||
fieldType: field.fieldType,
|
||||
specialType: field.specialType || null,
|
||||
required: field.required,
|
||||
sortOrder: field.sortOrder,
|
||||
width: field.width,
|
||||
description: field.description || null,
|
||||
placeholder: field.placeholder || null,
|
||||
projectMapping: field.projectMapping || null,
|
||||
minLength: field.minLength || null,
|
||||
maxLength: field.maxLength || null,
|
||||
optionsJson: field.optionsJson || undefined,
|
||||
conditionJson: field.conditionJson || 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()
|
||||
})
|
||||
@@ -1,456 +0,0 @@
|
||||
/**
|
||||
* 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<string, unknown>
|
||||
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<string, unknown>
|
||||
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()
|
||||
})
|
||||
Reference in New Issue
Block a user