2026-01-30 13:41:32 +01:00
|
|
|
import { z } from 'zod'
|
|
|
|
|
import { TRPCError } from '@trpc/server'
|
|
|
|
|
import { router, publicProcedure } from '../trpc'
|
Implement 15 platform features: digest, availability, templates, comparison, live voting SSE, file versioning, mentorship, messaging, analytics, drafts, webhooks, peer review, audit enhancements, i18n
Features implemented:
- F1: Email digest notifications with cron endpoint and per-user frequency
- F2: Jury availability windows and workload preferences in smart assignment
- F3: Round templates with save-from-round and CRUD management
- F4: Side-by-side project comparison view for jury members
- F5: Real-time voting dashboard with Server-Sent Events (SSE)
- F6: Live voting UX: QR codes, audience voting, tie-breaking, score animations
- F7: File versioning, inline preview, bulk download with presigned URLs
- F8: Mentor dashboard: milestones, private notes, activity tracking
- F9: Communication hub with broadcasts, templates, and recipient targeting
- F10: Advanced analytics: cross-round comparison, juror consistency, diversity metrics, PDF export
- F11: Applicant draft saving with magic link resume and cron cleanup
- F12: Webhook integration layer with HMAC signing, retry, and delivery logs
- F13: Peer review discussions with anonymized scores and threaded comments
- F14: Audit log enhancements: before/after diffs, session grouping, anomaly detection, retention
- F15: i18n foundation with next-intl (EN/FR), cookie-based locale, language switcher
Schema: 12 new models, field additions to User, Project, ProjectFile, LiveVotingSession, LiveVote, MentorAssignment, AuditLog, Program
New routers: roundTemplate, message, webhook (registered in _app.ts)
New services: email-digest, webhook-dispatcher
New cron endpoints: /api/cron/digest, /api/cron/draft-cleanup, /api/cron/audit-cleanup
New API routes: /api/live-voting/stream (SSE), /api/files/bulk-download
All features are admin-configurable via SystemSettings or per-model settingsJson fields.
Docker build verified successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:31:41 +01:00
|
|
|
import { Prisma, CompetitionCategory, OceanIssue, TeamMemberRole } from '@prisma/client'
|
2026-02-04 00:10:51 +01:00
|
|
|
import {
|
|
|
|
|
createNotification,
|
|
|
|
|
notifyAdmins,
|
2026-02-12 15:06:11 +01:00
|
|
|
notifyProjectTeam,
|
2026-02-04 00:10:51 +01:00
|
|
|
NotificationTypes,
|
|
|
|
|
} from '../services/in-app-notification'
|
2026-02-12 15:06:11 +01:00
|
|
|
import { getFirstRoundForProgram } from '@/server/utils/round-helpers'
|
2026-02-05 20:31:08 +01:00
|
|
|
import { checkRateLimit } from '@/lib/rate-limit'
|
2026-02-05 21:09:06 +01:00
|
|
|
import { logAudit } from '@/server/utils/audit'
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
import { parseWizardConfig } from '@/lib/wizard-config'
|
2026-01-30 13:41:32 +01:00
|
|
|
|
|
|
|
|
// Zod schemas for the application form
|
|
|
|
|
const teamMemberSchema = z.object({
|
|
|
|
|
name: z.string().min(1, 'Name is required'),
|
|
|
|
|
email: z.string().email('Invalid email address'),
|
|
|
|
|
role: z.nativeEnum(TeamMemberRole).default('MEMBER'),
|
|
|
|
|
title: z.string().optional(),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const applicationSchema = z.object({
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
// Step 1: Category (string to support admin-configured custom values)
|
|
|
|
|
competitionCategory: z.string().min(1, 'Competition category is required'),
|
2026-01-30 13:41:32 +01:00
|
|
|
|
|
|
|
|
// Step 2: Contact Info
|
|
|
|
|
contactName: z.string().min(2, 'Full name is required'),
|
|
|
|
|
contactEmail: z.string().email('Invalid email address'),
|
|
|
|
|
contactPhone: z.string().min(5, 'Phone number is required'),
|
|
|
|
|
country: z.string().min(2, 'Country is required'),
|
|
|
|
|
city: z.string().optional(),
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
// Step 3: Project Details (string to support admin-configured custom values)
|
2026-01-30 13:41:32 +01:00
|
|
|
projectName: z.string().min(2, 'Project name is required').max(200),
|
|
|
|
|
teamName: z.string().optional(),
|
|
|
|
|
description: z.string().min(20, 'Description must be at least 20 characters'),
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
oceanIssue: z.string().min(1, 'Ocean issue is required'),
|
2026-01-30 13:41:32 +01:00
|
|
|
|
|
|
|
|
// Step 4: Team Members
|
|
|
|
|
teamMembers: z.array(teamMemberSchema).optional(),
|
|
|
|
|
|
|
|
|
|
// Step 5: Additional Info (conditional & optional)
|
|
|
|
|
institution: z.string().optional(), // Required if BUSINESS_CONCEPT
|
|
|
|
|
startupCreatedDate: z.string().optional(), // Required if STARTUP
|
|
|
|
|
wantsMentorship: z.boolean().default(false),
|
|
|
|
|
referralSource: z.string().optional(),
|
|
|
|
|
|
|
|
|
|
// Consent
|
|
|
|
|
gdprConsent: z.boolean().refine((val) => val === true, {
|
|
|
|
|
message: 'You must agree to the data processing terms',
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
// Passthrough version for tRPC input (allows custom fields to pass through)
|
|
|
|
|
const applicationInputSchema = applicationSchema.passthrough()
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
export type ApplicationFormData = z.infer<typeof applicationSchema>
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
// Known core field names that are stored in dedicated columns (not custom fields)
|
|
|
|
|
const CORE_FIELD_NAMES = new Set([
|
|
|
|
|
'competitionCategory', 'contactName', 'contactEmail', 'contactPhone',
|
|
|
|
|
'country', 'city', 'projectName', 'teamName', 'description', 'oceanIssue',
|
|
|
|
|
'teamMembers', 'institution', 'startupCreatedDate', 'wantsMentorship',
|
|
|
|
|
'referralSource', 'gdprConsent',
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extract custom field values from form data based on wizard config.
|
|
|
|
|
* Returns an object with { customFields: { fieldId: value } } if any custom fields exist.
|
|
|
|
|
*/
|
|
|
|
|
function extractCustomFieldData(
|
|
|
|
|
settingsJson: unknown,
|
|
|
|
|
formData: Record<string, unknown>
|
|
|
|
|
): Record<string, unknown> {
|
|
|
|
|
const config = parseWizardConfig(settingsJson)
|
|
|
|
|
if (!config.customFields?.length) return {}
|
|
|
|
|
|
|
|
|
|
const customFieldData: Record<string, unknown> = {}
|
|
|
|
|
for (const field of config.customFields) {
|
|
|
|
|
const value = formData[field.id as keyof typeof formData]
|
|
|
|
|
if (value !== undefined && value !== '' && value !== null) {
|
|
|
|
|
customFieldData[field.id] = value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Object.keys(customFieldData).length === 0) return {}
|
|
|
|
|
return { customFields: customFieldData }
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
export const applicationRouter = router({
|
|
|
|
|
/**
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
* Get application configuration for a round or edition
|
2026-01-30 13:41:32 +01:00
|
|
|
*/
|
|
|
|
|
getConfig: publicProcedure
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
slug: z.string(),
|
|
|
|
|
mode: z.enum(['edition', 'round']).default('round'),
|
|
|
|
|
})
|
|
|
|
|
)
|
2026-01-30 13:41:32 +01:00
|
|
|
.query(async ({ ctx, input }) => {
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
const now = new Date()
|
|
|
|
|
|
|
|
|
|
if (input.mode === 'edition') {
|
|
|
|
|
// Edition-wide application mode
|
|
|
|
|
const program = await ctx.prisma.program.findFirst({
|
|
|
|
|
where: { slug: input.slug },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!program) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'NOT_FOUND',
|
|
|
|
|
message: 'Program not found',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if program supports edition-wide applications
|
|
|
|
|
const settingsJson = (program.settingsJson || {}) as Record<string, unknown>
|
|
|
|
|
const applyMode = (settingsJson.applyMode as string) || 'round'
|
|
|
|
|
|
|
|
|
|
if (applyMode !== 'edition') {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: 'This program does not support edition-wide applications',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if applications are open (based on program dates)
|
|
|
|
|
const submissionStartDate = settingsJson.submissionStartDate
|
|
|
|
|
? new Date(settingsJson.submissionStartDate as string)
|
|
|
|
|
: null
|
|
|
|
|
const submissionEndDate = settingsJson.submissionEndDate
|
|
|
|
|
? new Date(settingsJson.submissionEndDate as string)
|
|
|
|
|
: null
|
|
|
|
|
|
|
|
|
|
let isOpen = false
|
|
|
|
|
let gracePeriodEnd: Date | null = null
|
|
|
|
|
|
|
|
|
|
if (submissionStartDate && submissionEndDate) {
|
|
|
|
|
isOpen = now >= submissionStartDate && now <= submissionEndDate
|
|
|
|
|
|
|
|
|
|
// Check grace period
|
|
|
|
|
const lateSubmissionGrace = settingsJson.lateSubmissionGrace as number | undefined
|
|
|
|
|
if (!isOpen && lateSubmissionGrace) {
|
|
|
|
|
gracePeriodEnd = new Date(submissionEndDate.getTime() + lateSubmissionGrace * 60 * 60 * 1000)
|
|
|
|
|
isOpen = now <= gracePeriodEnd
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
isOpen = program.status === 'ACTIVE'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const wizardConfig = parseWizardConfig(program.settingsJson)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
mode: 'edition' as const,
|
2026-01-30 13:41:32 +01:00
|
|
|
program: {
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
id: program.id,
|
|
|
|
|
name: program.name,
|
|
|
|
|
year: program.year,
|
|
|
|
|
description: program.description,
|
|
|
|
|
slug: program.slug,
|
|
|
|
|
submissionStartDate,
|
|
|
|
|
submissionEndDate,
|
|
|
|
|
gracePeriodEnd,
|
|
|
|
|
isOpen,
|
|
|
|
|
},
|
|
|
|
|
wizardConfig,
|
|
|
|
|
oceanIssueOptions: wizardConfig.oceanIssues ?? [],
|
|
|
|
|
competitionCategories: wizardConfig.competitionCategories ?? [],
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Round-specific application mode (backward compatible)
|
|
|
|
|
const round = await ctx.prisma.round.findFirst({
|
|
|
|
|
where: { slug: input.slug },
|
|
|
|
|
include: {
|
|
|
|
|
program: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
name: true,
|
|
|
|
|
year: true,
|
|
|
|
|
description: true,
|
|
|
|
|
settingsJson: true,
|
|
|
|
|
},
|
2026-01-30 13:41:32 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
if (!round) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'NOT_FOUND',
|
|
|
|
|
message: 'Application round not found',
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-01-30 13:41:32 +01:00
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
// Check if submissions are open
|
|
|
|
|
let isOpen = false
|
2026-01-30 13:41:32 +01:00
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
if (round.submissionStartDate && round.submissionEndDate) {
|
|
|
|
|
isOpen = now >= round.submissionStartDate && now <= round.submissionEndDate
|
|
|
|
|
} else if (round.submissionDeadline) {
|
|
|
|
|
isOpen = now <= round.submissionDeadline
|
|
|
|
|
} else {
|
|
|
|
|
isOpen = round.status === 'ACTIVE'
|
2026-01-30 13:41:32 +01:00
|
|
|
}
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
// Calculate grace period if applicable
|
|
|
|
|
let gracePeriodEnd: Date | null = null
|
|
|
|
|
if (round.lateSubmissionGrace && round.submissionEndDate) {
|
|
|
|
|
gracePeriodEnd = new Date(round.submissionEndDate.getTime() + round.lateSubmissionGrace * 60 * 60 * 1000)
|
|
|
|
|
if (now <= gracePeriodEnd) {
|
|
|
|
|
isOpen = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const roundWizardConfig = parseWizardConfig(round.program.settingsJson)
|
|
|
|
|
const { settingsJson: _s, ...programData } = round.program
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
mode: 'round' as const,
|
|
|
|
|
round: {
|
|
|
|
|
id: round.id,
|
|
|
|
|
name: round.name,
|
|
|
|
|
slug: round.slug,
|
|
|
|
|
submissionStartDate: round.submissionStartDate,
|
|
|
|
|
submissionEndDate: round.submissionEndDate,
|
|
|
|
|
submissionDeadline: round.submissionDeadline,
|
|
|
|
|
lateSubmissionGrace: round.lateSubmissionGrace,
|
|
|
|
|
gracePeriodEnd,
|
|
|
|
|
phase1Deadline: round.phase1Deadline,
|
|
|
|
|
phase2Deadline: round.phase2Deadline,
|
|
|
|
|
isOpen,
|
2026-01-30 13:41:32 +01:00
|
|
|
},
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
program: programData,
|
|
|
|
|
wizardConfig: roundWizardConfig,
|
|
|
|
|
oceanIssueOptions: roundWizardConfig.oceanIssues ?? [],
|
|
|
|
|
competitionCategories: roundWizardConfig.competitionCategories ?? [],
|
|
|
|
|
}
|
2026-01-30 13:41:32 +01:00
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
* Submit a new application (edition-wide or round-specific)
|
2026-01-30 13:41:32 +01:00
|
|
|
*/
|
|
|
|
|
submit: publicProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
mode: z.enum(['edition', 'round']).default('round'),
|
|
|
|
|
programId: z.string().optional(),
|
|
|
|
|
roundId: z.string().optional(),
|
|
|
|
|
data: applicationInputSchema,
|
2026-01-30 13:41:32 +01:00
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
2026-02-05 20:31:08 +01:00
|
|
|
// Stricter rate limit for application submissions: 5 per hour per IP
|
|
|
|
|
const ip = ctx.ip || 'unknown'
|
|
|
|
|
const submitRateLimit = checkRateLimit(`app-submit:${ip}`, 5, 60 * 60 * 1000)
|
|
|
|
|
if (!submitRateLimit.success) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'TOO_MANY_REQUESTS',
|
|
|
|
|
message: 'Too many application submissions. Please try again later.',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
const { mode, programId, roundId, data } = input
|
2026-01-30 13:41:32 +01:00
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
// Validate input based on mode
|
|
|
|
|
if (mode === 'edition' && !programId) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: 'programId is required for edition-wide applications',
|
|
|
|
|
})
|
2026-01-30 13:41:32 +01:00
|
|
|
}
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
if (mode === 'round' && !roundId) {
|
2026-01-30 13:41:32 +01:00
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
message: 'roundId is required for round-specific applications',
|
2026-01-30 13:41:32 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
const now = new Date()
|
|
|
|
|
let program: { id: string; name: string; year: number; status: string; settingsJson?: unknown }
|
|
|
|
|
let isOpen = false
|
2026-01-30 13:41:32 +01:00
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
if (mode === 'edition') {
|
|
|
|
|
// Edition-wide application
|
|
|
|
|
program = await ctx.prisma.program.findUniqueOrThrow({
|
|
|
|
|
where: { id: programId },
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
name: true,
|
|
|
|
|
year: true,
|
|
|
|
|
status: true,
|
|
|
|
|
settingsJson: true,
|
|
|
|
|
},
|
2026-01-30 13:41:32 +01:00
|
|
|
})
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
|
|
|
|
|
// Check if program supports edition-wide applications
|
|
|
|
|
const settingsJson = (program.settingsJson || {}) as Record<string, unknown>
|
|
|
|
|
const applyMode = (settingsJson.applyMode as string) || 'round'
|
|
|
|
|
|
|
|
|
|
if (applyMode !== 'edition') {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: 'This program does not support edition-wide applications',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check submission window
|
|
|
|
|
const submissionStartDate = settingsJson.submissionStartDate
|
|
|
|
|
? new Date(settingsJson.submissionStartDate as string)
|
|
|
|
|
: null
|
|
|
|
|
const submissionEndDate = settingsJson.submissionEndDate
|
|
|
|
|
? new Date(settingsJson.submissionEndDate as string)
|
|
|
|
|
: null
|
|
|
|
|
|
|
|
|
|
if (submissionStartDate && submissionEndDate) {
|
|
|
|
|
isOpen = now >= submissionStartDate && now <= submissionEndDate
|
|
|
|
|
|
|
|
|
|
// Check grace period
|
|
|
|
|
const lateSubmissionGrace = settingsJson.lateSubmissionGrace as number | undefined
|
|
|
|
|
if (!isOpen && lateSubmissionGrace) {
|
|
|
|
|
const gracePeriodEnd = new Date(submissionEndDate.getTime() + lateSubmissionGrace * 60 * 60 * 1000)
|
|
|
|
|
isOpen = now <= gracePeriodEnd
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
isOpen = program.status === 'ACTIVE'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isOpen) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: 'Applications are currently closed for this edition',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if email already submitted for this edition
|
|
|
|
|
const existingProject = await ctx.prisma.project.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
programId,
|
|
|
|
|
roundId: null,
|
|
|
|
|
submittedByEmail: data.contactEmail,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (existingProject) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'CONFLICT',
|
|
|
|
|
message: 'An application with this email already exists for this edition',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Round-specific application (backward compatible)
|
|
|
|
|
const round = await ctx.prisma.round.findUniqueOrThrow({
|
|
|
|
|
where: { id: roundId },
|
|
|
|
|
include: { program: true },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
program = round.program
|
|
|
|
|
|
|
|
|
|
// Check submission window
|
|
|
|
|
if (round.submissionStartDate && round.submissionEndDate) {
|
|
|
|
|
isOpen = now >= round.submissionStartDate && now <= round.submissionEndDate
|
|
|
|
|
|
|
|
|
|
// Check grace period
|
|
|
|
|
if (!isOpen && round.lateSubmissionGrace) {
|
|
|
|
|
const gracePeriodEnd = new Date(
|
|
|
|
|
round.submissionEndDate.getTime() + round.lateSubmissionGrace * 60 * 60 * 1000
|
|
|
|
|
)
|
|
|
|
|
isOpen = now <= gracePeriodEnd
|
|
|
|
|
}
|
|
|
|
|
} else if (round.submissionDeadline) {
|
|
|
|
|
isOpen = now <= round.submissionDeadline
|
|
|
|
|
} else {
|
|
|
|
|
isOpen = round.status === 'ACTIVE'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isOpen) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: 'Applications are currently closed for this round',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if email already submitted for this round
|
|
|
|
|
const existingProject = await ctx.prisma.project.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
roundId,
|
|
|
|
|
submittedByEmail: data.contactEmail,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (existingProject) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'CONFLICT',
|
|
|
|
|
message: 'An application with this email already exists for this round',
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-01-30 13:41:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user exists, or create a new applicant user
|
|
|
|
|
let user = await ctx.prisma.user.findUnique({
|
|
|
|
|
where: { email: data.contactEmail },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
user = await ctx.prisma.user.create({
|
|
|
|
|
data: {
|
|
|
|
|
email: data.contactEmail,
|
|
|
|
|
name: data.contactName,
|
|
|
|
|
role: 'APPLICANT',
|
|
|
|
|
status: 'ACTIVE',
|
|
|
|
|
phoneNumber: data.contactPhone,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
// Map string values to Prisma enums (safe for admin-configured custom values)
|
|
|
|
|
const validCategories = Object.values(CompetitionCategory) as string[]
|
|
|
|
|
const validOceanIssues = Object.values(OceanIssue) as string[]
|
|
|
|
|
const categoryEnum = validCategories.includes(data.competitionCategory)
|
|
|
|
|
? (data.competitionCategory as CompetitionCategory)
|
|
|
|
|
: null
|
|
|
|
|
const oceanIssueEnum = validOceanIssues.includes(data.oceanIssue)
|
|
|
|
|
? (data.oceanIssue as OceanIssue)
|
|
|
|
|
: null
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
// Create the project
|
|
|
|
|
const project = await ctx.prisma.project.create({
|
|
|
|
|
data: {
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
programId: program.id,
|
|
|
|
|
roundId: mode === 'round' ? roundId! : null,
|
2026-01-30 13:41:32 +01:00
|
|
|
title: data.projectName,
|
|
|
|
|
teamName: data.teamName,
|
|
|
|
|
description: data.description,
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
competitionCategory: categoryEnum,
|
|
|
|
|
oceanIssue: oceanIssueEnum,
|
2026-01-30 13:41:32 +01:00
|
|
|
country: data.country,
|
|
|
|
|
geographicZone: data.city ? `${data.city}, ${data.country}` : data.country,
|
|
|
|
|
institution: data.institution,
|
|
|
|
|
wantsMentorship: data.wantsMentorship,
|
|
|
|
|
referralSource: data.referralSource,
|
|
|
|
|
submissionSource: 'PUBLIC_FORM',
|
|
|
|
|
submittedByEmail: data.contactEmail,
|
|
|
|
|
submittedByUserId: user.id,
|
|
|
|
|
submittedAt: now,
|
|
|
|
|
metadataJson: {
|
|
|
|
|
contactPhone: data.contactPhone,
|
|
|
|
|
startupCreatedDate: data.startupCreatedDate,
|
|
|
|
|
gdprConsentAt: now.toISOString(),
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
applicationMode: mode,
|
|
|
|
|
// Store raw string values for custom categories/issues
|
|
|
|
|
...(categoryEnum ? {} : { competitionCategoryRaw: data.competitionCategory }),
|
|
|
|
|
...(oceanIssueEnum ? {} : { oceanIssueRaw: data.oceanIssue }),
|
|
|
|
|
// Store custom field values from wizard config
|
|
|
|
|
...extractCustomFieldData(program.settingsJson, data),
|
2026-01-30 13:41:32 +01:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-12 15:06:11 +01:00
|
|
|
// Auto-assign to first round if project has no roundId (edition-wide mode)
|
|
|
|
|
let assignedRound: { id: string; name: string; entryNotificationType: string | null } | null = null
|
|
|
|
|
if (!project.roundId) {
|
|
|
|
|
assignedRound = await getFirstRoundForProgram(ctx.prisma, program.id)
|
|
|
|
|
if (assignedRound) {
|
|
|
|
|
await ctx.prisma.project.update({
|
|
|
|
|
where: { id: project.id },
|
|
|
|
|
data: { roundId: assignedRound.id },
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
// Create team lead membership
|
|
|
|
|
await ctx.prisma.teamMember.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: project.id,
|
|
|
|
|
userId: user.id,
|
|
|
|
|
role: 'LEAD',
|
|
|
|
|
title: 'Team Lead',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Create additional team members
|
|
|
|
|
if (data.teamMembers && data.teamMembers.length > 0) {
|
|
|
|
|
for (const member of data.teamMembers) {
|
|
|
|
|
// Find or create user for team member
|
|
|
|
|
let memberUser = await ctx.prisma.user.findUnique({
|
|
|
|
|
where: { email: member.email },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!memberUser) {
|
|
|
|
|
memberUser = await ctx.prisma.user.create({
|
|
|
|
|
data: {
|
|
|
|
|
email: member.email,
|
|
|
|
|
name: member.name,
|
|
|
|
|
role: 'APPLICANT',
|
2026-02-10 23:08:00 +01:00
|
|
|
status: 'NONE',
|
2026-01-30 13:41:32 +01:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create team membership
|
|
|
|
|
await ctx.prisma.teamMember.create({
|
|
|
|
|
data: {
|
|
|
|
|
projectId: project.id,
|
|
|
|
|
userId: memberUser.id,
|
|
|
|
|
role: member.role,
|
|
|
|
|
title: member.title,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create audit log
|
2026-02-05 21:09:06 +01:00
|
|
|
await logAudit({
|
|
|
|
|
prisma: ctx.prisma,
|
|
|
|
|
userId: user.id,
|
|
|
|
|
action: 'CREATE',
|
|
|
|
|
entityType: 'Project',
|
|
|
|
|
entityId: project.id,
|
|
|
|
|
detailsJson: {
|
|
|
|
|
source: 'public_application_form',
|
|
|
|
|
title: data.projectName,
|
|
|
|
|
category: data.competitionCategory,
|
2026-02-12 15:06:11 +01:00
|
|
|
autoAssignedRound: assignedRound?.name || null,
|
2026-01-30 13:41:32 +01:00
|
|
|
},
|
2026-02-05 21:09:06 +01:00
|
|
|
ipAddress: ctx.ip,
|
|
|
|
|
userAgent: ctx.userAgent,
|
2026-01-30 13:41:32 +01:00
|
|
|
})
|
|
|
|
|
|
2026-02-04 00:10:51 +01:00
|
|
|
// Notify applicant of successful submission
|
|
|
|
|
await createNotification({
|
|
|
|
|
userId: user.id,
|
|
|
|
|
type: NotificationTypes.APPLICATION_SUBMITTED,
|
|
|
|
|
title: 'Application Received',
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
message: `Your application for "${data.projectName}" has been successfully submitted to ${program.name}.`,
|
2026-02-04 00:10:51 +01:00
|
|
|
linkUrl: `/team/projects/${project.id}`,
|
|
|
|
|
linkLabel: 'View Application',
|
|
|
|
|
metadata: {
|
|
|
|
|
projectName: data.projectName,
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
programName: program.name,
|
2026-02-04 00:10:51 +01:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Notify admins of new application
|
|
|
|
|
await notifyAdmins({
|
|
|
|
|
type: NotificationTypes.NEW_APPLICATION,
|
|
|
|
|
title: 'New Application',
|
|
|
|
|
message: `New application received: "${data.projectName}" from ${data.contactName}.`,
|
|
|
|
|
linkUrl: `/admin/projects/${project.id}`,
|
|
|
|
|
linkLabel: 'Review Application',
|
|
|
|
|
metadata: {
|
|
|
|
|
projectName: data.projectName,
|
|
|
|
|
applicantName: data.contactName,
|
|
|
|
|
applicantEmail: data.contactEmail,
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
programName: program.name,
|
2026-02-04 00:10:51 +01:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-12 15:06:11 +01:00
|
|
|
// Send SUBMISSION_RECEIVED notification if the round is configured for it
|
|
|
|
|
if (assignedRound?.entryNotificationType === 'SUBMISSION_RECEIVED') {
|
|
|
|
|
try {
|
|
|
|
|
await notifyProjectTeam(project.id, {
|
|
|
|
|
type: NotificationTypes.SUBMISSION_RECEIVED,
|
|
|
|
|
title: 'Submission Received',
|
|
|
|
|
message: `Your submission "${data.projectName}" has been received and is now under review.`,
|
|
|
|
|
linkUrl: `/team/projects/${project.id}`,
|
|
|
|
|
linkLabel: 'View Submission',
|
|
|
|
|
metadata: {
|
|
|
|
|
projectName: data.projectName,
|
|
|
|
|
roundName: assignedRound.name,
|
|
|
|
|
programName: program.name,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
} catch {
|
|
|
|
|
// Never fail on notification
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
projectId: project.id,
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
message: `Thank you for applying to ${program.name} ${program.year}! We will review your application and contact you at ${data.contactEmail}.`,
|
2026-01-30 13:41:32 +01:00
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
* Check if email is already registered for a round or edition
|
2026-01-30 13:41:32 +01:00
|
|
|
*/
|
|
|
|
|
checkEmailAvailability: publicProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
mode: z.enum(['edition', 'round']).default('round'),
|
|
|
|
|
programId: z.string().optional(),
|
|
|
|
|
roundId: z.string().optional(),
|
2026-01-30 13:41:32 +01:00
|
|
|
email: z.string().email(),
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
2026-02-05 20:31:08 +01:00
|
|
|
// Rate limit to prevent email enumeration
|
|
|
|
|
const ip = ctx.ip || 'unknown'
|
|
|
|
|
const emailCheckLimit = checkRateLimit(`email-check:${ip}`, 20, 15 * 60 * 1000)
|
|
|
|
|
if (!emailCheckLimit.success) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'TOO_MANY_REQUESTS',
|
|
|
|
|
message: 'Too many requests. Please try again later.',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
let existing
|
|
|
|
|
if (input.mode === 'edition') {
|
|
|
|
|
existing = await ctx.prisma.project.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
programId: input.programId,
|
|
|
|
|
roundId: null,
|
|
|
|
|
submittedByEmail: input.email,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
existing = await ctx.prisma.project.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
roundId: input.roundId,
|
|
|
|
|
submittedByEmail: input.email,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-01-30 13:41:32 +01:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
available: !existing,
|
|
|
|
|
message: existing
|
Add dynamic apply wizard customization with admin settings UI
- Create wizard config types, utilities, and defaults (wizard-config.ts)
- Add admin apply settings page with drag-and-drop step ordering, dropdown
option management, feature toggles, welcome message customization, and
custom field builder with select/multiselect options editor
- Build dynamic apply wizard component with animated step transitions,
mobile-first responsive design, and config-driven form validation
- Update step components to accept dynamic config (categories, ocean issues,
field visibility, feature flags)
- Replace hardcoded enum validation with string-based validation for
admin-configurable dropdown values, with safe enum casting at storage layer
- Add wizard template system (model, router, admin UI) with built-in
MOPC Classic preset
- Add program wizard config CRUD procedures to program router
- Update application router getConfig to return wizardConfig, submit handler
to store custom field data in metadataJson
- Add edition-based apply page, project pool page, and supporting routers
- Fix CSS (invalid sm:fixed-none), Enter key handler (skip textarea),
safe area insets for notched phones, buildStepsArray field visibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:18:20 +01:00
|
|
|
? `An application with this email already exists for this ${input.mode === 'edition' ? 'edition' : 'round'}`
|
2026-01-30 13:41:32 +01:00
|
|
|
: null,
|
|
|
|
|
}
|
|
|
|
|
}),
|
Implement 15 platform features: digest, availability, templates, comparison, live voting SSE, file versioning, mentorship, messaging, analytics, drafts, webhooks, peer review, audit enhancements, i18n
Features implemented:
- F1: Email digest notifications with cron endpoint and per-user frequency
- F2: Jury availability windows and workload preferences in smart assignment
- F3: Round templates with save-from-round and CRUD management
- F4: Side-by-side project comparison view for jury members
- F5: Real-time voting dashboard with Server-Sent Events (SSE)
- F6: Live voting UX: QR codes, audience voting, tie-breaking, score animations
- F7: File versioning, inline preview, bulk download with presigned URLs
- F8: Mentor dashboard: milestones, private notes, activity tracking
- F9: Communication hub with broadcasts, templates, and recipient targeting
- F10: Advanced analytics: cross-round comparison, juror consistency, diversity metrics, PDF export
- F11: Applicant draft saving with magic link resume and cron cleanup
- F12: Webhook integration layer with HMAC signing, retry, and delivery logs
- F13: Peer review discussions with anonymized scores and threaded comments
- F14: Audit log enhancements: before/after diffs, session grouping, anomaly detection, retention
- F15: i18n foundation with next-intl (EN/FR), cookie-based locale, language switcher
Schema: 12 new models, field additions to User, Project, ProjectFile, LiveVotingSession, LiveVote, MentorAssignment, AuditLog, Program
New routers: roundTemplate, message, webhook (registered in _app.ts)
New services: email-digest, webhook-dispatcher
New cron endpoints: /api/cron/digest, /api/cron/draft-cleanup, /api/cron/audit-cleanup
New API routes: /api/live-voting/stream (SSE), /api/files/bulk-download
All features are admin-configurable via SystemSettings or per-model settingsJson fields.
Docker build verified successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:31:41 +01:00
|
|
|
|
|
|
|
|
// =========================================================================
|
|
|
|
|
// Draft Saving & Resume (F11)
|
|
|
|
|
// =========================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Save application as draft with resume token
|
|
|
|
|
*/
|
|
|
|
|
saveDraft: publicProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
roundSlug: z.string(),
|
|
|
|
|
email: z.string().email(),
|
|
|
|
|
draftDataJson: z.record(z.unknown()),
|
|
|
|
|
title: z.string().optional(),
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
|
|
|
|
// Find round by slug
|
|
|
|
|
const round = await ctx.prisma.round.findFirst({
|
|
|
|
|
where: { slug: input.roundSlug },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!round) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'NOT_FOUND',
|
|
|
|
|
message: 'Round not found',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if drafts are enabled
|
|
|
|
|
const settings = (round.settingsJson as Record<string, unknown>) || {}
|
|
|
|
|
if (settings.drafts_enabled === false) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: 'Draft saving is not enabled for this round',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate draft expiry
|
|
|
|
|
const draftExpiryDays = (settings.draft_expiry_days as number) || 30
|
|
|
|
|
const draftExpiresAt = new Date()
|
|
|
|
|
draftExpiresAt.setDate(draftExpiresAt.getDate() + draftExpiryDays)
|
|
|
|
|
|
|
|
|
|
// Generate resume token
|
|
|
|
|
const draftToken = `draft_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`
|
|
|
|
|
|
|
|
|
|
// Find or create draft project for this email+round
|
|
|
|
|
const existingDraft = await ctx.prisma.project.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
roundId: round.id,
|
|
|
|
|
submittedByEmail: input.email,
|
|
|
|
|
isDraft: true,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (existingDraft) {
|
|
|
|
|
// Update existing draft
|
|
|
|
|
const updated = await ctx.prisma.project.update({
|
|
|
|
|
where: { id: existingDraft.id },
|
|
|
|
|
data: {
|
|
|
|
|
title: input.title || existingDraft.title,
|
|
|
|
|
draftDataJson: input.draftDataJson as Prisma.InputJsonValue,
|
|
|
|
|
draftExpiresAt,
|
|
|
|
|
metadataJson: {
|
|
|
|
|
...((existingDraft.metadataJson as Record<string, unknown>) || {}),
|
|
|
|
|
draftToken,
|
|
|
|
|
} as Prisma.InputJsonValue,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return { projectId: updated.id, draftToken }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create new draft project
|
|
|
|
|
const project = await ctx.prisma.project.create({
|
|
|
|
|
data: {
|
Fix build errors: add missing Prisma models/fields and resolve TypeScript type errors
Schema: Add 11 new models (RoundTemplate, MentorNote, MentorMilestone,
MentorMilestoneCompletion, EvaluationDiscussion, DiscussionComment,
Message, MessageRecipient, MessageTemplate, Webhook, WebhookDelivery,
DigestLog) and missing fields on existing models (Project.isDraft,
ProjectFile.version, LiveVotingSession.allowAudienceVotes, User.digestFrequency,
AuditLog.sessionId, MentorAssignment.completionStatus, etc).
Add AUDIT_CONFIG/LOCALIZATION/DIGEST/ANALYTICS enum values.
Code: Fix implicit any types, route type casts, enum casts, null safety,
composite key handling, and relation field names across 11 source files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 14:04:02 +01:00
|
|
|
programId: round.programId,
|
Implement 15 platform features: digest, availability, templates, comparison, live voting SSE, file versioning, mentorship, messaging, analytics, drafts, webhooks, peer review, audit enhancements, i18n
Features implemented:
- F1: Email digest notifications with cron endpoint and per-user frequency
- F2: Jury availability windows and workload preferences in smart assignment
- F3: Round templates with save-from-round and CRUD management
- F4: Side-by-side project comparison view for jury members
- F5: Real-time voting dashboard with Server-Sent Events (SSE)
- F6: Live voting UX: QR codes, audience voting, tie-breaking, score animations
- F7: File versioning, inline preview, bulk download with presigned URLs
- F8: Mentor dashboard: milestones, private notes, activity tracking
- F9: Communication hub with broadcasts, templates, and recipient targeting
- F10: Advanced analytics: cross-round comparison, juror consistency, diversity metrics, PDF export
- F11: Applicant draft saving with magic link resume and cron cleanup
- F12: Webhook integration layer with HMAC signing, retry, and delivery logs
- F13: Peer review discussions with anonymized scores and threaded comments
- F14: Audit log enhancements: before/after diffs, session grouping, anomaly detection, retention
- F15: i18n foundation with next-intl (EN/FR), cookie-based locale, language switcher
Schema: 12 new models, field additions to User, Project, ProjectFile, LiveVotingSession, LiveVote, MentorAssignment, AuditLog, Program
New routers: roundTemplate, message, webhook (registered in _app.ts)
New services: email-digest, webhook-dispatcher
New cron endpoints: /api/cron/digest, /api/cron/draft-cleanup, /api/cron/audit-cleanup
New API routes: /api/live-voting/stream (SSE), /api/files/bulk-download
All features are admin-configurable via SystemSettings or per-model settingsJson fields.
Docker build verified successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:31:41 +01:00
|
|
|
roundId: round.id,
|
|
|
|
|
title: input.title || 'Untitled Draft',
|
|
|
|
|
isDraft: true,
|
|
|
|
|
draftDataJson: input.draftDataJson as Prisma.InputJsonValue,
|
|
|
|
|
draftExpiresAt,
|
|
|
|
|
submittedByEmail: input.email,
|
|
|
|
|
metadataJson: {
|
|
|
|
|
draftToken,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return { projectId: project.id, draftToken }
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resume a draft application using a token
|
|
|
|
|
*/
|
|
|
|
|
resumeDraft: publicProcedure
|
|
|
|
|
.input(z.object({ draftToken: z.string() }))
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
|
|
|
|
const projects = await ctx.prisma.project.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
isDraft: true,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Find project with matching token in metadataJson
|
|
|
|
|
const project = projects.find((p) => {
|
|
|
|
|
const metadata = p.metadataJson as Record<string, unknown> | null
|
|
|
|
|
return metadata?.draftToken === input.draftToken
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!project) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'NOT_FOUND',
|
|
|
|
|
message: 'Draft not found or invalid token',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check expiry
|
|
|
|
|
if (project.draftExpiresAt && new Date() > project.draftExpiresAt) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: 'This draft has expired',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
projectId: project.id,
|
|
|
|
|
draftDataJson: project.draftDataJson,
|
|
|
|
|
title: project.title,
|
|
|
|
|
roundId: project.roundId,
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Submit a saved draft as a final application
|
|
|
|
|
*/
|
|
|
|
|
submitDraft: publicProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
projectId: z.string(),
|
|
|
|
|
draftToken: z.string(),
|
|
|
|
|
data: applicationSchema,
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
|
|
|
|
const project = await ctx.prisma.project.findUniqueOrThrow({
|
|
|
|
|
where: { id: input.projectId },
|
|
|
|
|
include: { round: { include: { program: true } } },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Verify token
|
|
|
|
|
const metadata = (project.metadataJson as Record<string, unknown>) || {}
|
|
|
|
|
if (metadata.draftToken !== input.draftToken) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'FORBIDDEN',
|
|
|
|
|
message: 'Invalid draft token',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!project.isDraft) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: 'This project has already been submitted',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
const { data } = input
|
|
|
|
|
|
|
|
|
|
// Find or create user
|
|
|
|
|
let user = await ctx.prisma.user.findUnique({
|
|
|
|
|
where: { email: data.contactEmail },
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
user = await ctx.prisma.user.create({
|
|
|
|
|
data: {
|
|
|
|
|
email: data.contactEmail,
|
|
|
|
|
name: data.contactName,
|
|
|
|
|
role: 'APPLICANT',
|
|
|
|
|
status: 'ACTIVE',
|
|
|
|
|
phoneNumber: data.contactPhone,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update project with final data
|
|
|
|
|
const updated = await ctx.prisma.project.update({
|
|
|
|
|
where: { id: input.projectId },
|
|
|
|
|
data: {
|
|
|
|
|
isDraft: false,
|
|
|
|
|
draftDataJson: Prisma.DbNull,
|
|
|
|
|
draftExpiresAt: null,
|
|
|
|
|
title: data.projectName,
|
|
|
|
|
teamName: data.teamName,
|
|
|
|
|
description: data.description,
|
Fix build errors: add missing Prisma models/fields and resolve TypeScript type errors
Schema: Add 11 new models (RoundTemplate, MentorNote, MentorMilestone,
MentorMilestoneCompletion, EvaluationDiscussion, DiscussionComment,
Message, MessageRecipient, MessageTemplate, Webhook, WebhookDelivery,
DigestLog) and missing fields on existing models (Project.isDraft,
ProjectFile.version, LiveVotingSession.allowAudienceVotes, User.digestFrequency,
AuditLog.sessionId, MentorAssignment.completionStatus, etc).
Add AUDIT_CONFIG/LOCALIZATION/DIGEST/ANALYTICS enum values.
Code: Fix implicit any types, route type casts, enum casts, null safety,
composite key handling, and relation field names across 11 source files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 14:04:02 +01:00
|
|
|
competitionCategory: data.competitionCategory as CompetitionCategory,
|
|
|
|
|
oceanIssue: data.oceanIssue as OceanIssue,
|
Implement 15 platform features: digest, availability, templates, comparison, live voting SSE, file versioning, mentorship, messaging, analytics, drafts, webhooks, peer review, audit enhancements, i18n
Features implemented:
- F1: Email digest notifications with cron endpoint and per-user frequency
- F2: Jury availability windows and workload preferences in smart assignment
- F3: Round templates with save-from-round and CRUD management
- F4: Side-by-side project comparison view for jury members
- F5: Real-time voting dashboard with Server-Sent Events (SSE)
- F6: Live voting UX: QR codes, audience voting, tie-breaking, score animations
- F7: File versioning, inline preview, bulk download with presigned URLs
- F8: Mentor dashboard: milestones, private notes, activity tracking
- F9: Communication hub with broadcasts, templates, and recipient targeting
- F10: Advanced analytics: cross-round comparison, juror consistency, diversity metrics, PDF export
- F11: Applicant draft saving with magic link resume and cron cleanup
- F12: Webhook integration layer with HMAC signing, retry, and delivery logs
- F13: Peer review discussions with anonymized scores and threaded comments
- F14: Audit log enhancements: before/after diffs, session grouping, anomaly detection, retention
- F15: i18n foundation with next-intl (EN/FR), cookie-based locale, language switcher
Schema: 12 new models, field additions to User, Project, ProjectFile, LiveVotingSession, LiveVote, MentorAssignment, AuditLog, Program
New routers: roundTemplate, message, webhook (registered in _app.ts)
New services: email-digest, webhook-dispatcher
New cron endpoints: /api/cron/digest, /api/cron/draft-cleanup, /api/cron/audit-cleanup
New API routes: /api/live-voting/stream (SSE), /api/files/bulk-download
All features are admin-configurable via SystemSettings or per-model settingsJson fields.
Docker build verified successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:31:41 +01:00
|
|
|
country: data.country,
|
|
|
|
|
geographicZone: data.city ? `${data.city}, ${data.country}` : data.country,
|
|
|
|
|
institution: data.institution,
|
|
|
|
|
wantsMentorship: data.wantsMentorship,
|
|
|
|
|
referralSource: data.referralSource,
|
|
|
|
|
submissionSource: 'PUBLIC_FORM',
|
|
|
|
|
submittedByEmail: data.contactEmail,
|
|
|
|
|
submittedByUserId: user.id,
|
|
|
|
|
submittedAt: now,
|
|
|
|
|
status: 'SUBMITTED',
|
|
|
|
|
metadataJson: {
|
|
|
|
|
contactPhone: data.contactPhone,
|
|
|
|
|
startupCreatedDate: data.startupCreatedDate,
|
|
|
|
|
gdprConsentAt: now.toISOString(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-12 15:06:11 +01:00
|
|
|
// Auto-assign to first round if project has no roundId
|
|
|
|
|
let draftAssignedRound: { id: string; name: string; entryNotificationType: string | null } | null = null
|
|
|
|
|
if (!updated.roundId) {
|
|
|
|
|
draftAssignedRound = await getFirstRoundForProgram(ctx.prisma, updated.programId)
|
|
|
|
|
if (draftAssignedRound) {
|
|
|
|
|
await ctx.prisma.project.update({
|
|
|
|
|
where: { id: updated.id },
|
|
|
|
|
data: { roundId: draftAssignedRound.id },
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Implement 15 platform features: digest, availability, templates, comparison, live voting SSE, file versioning, mentorship, messaging, analytics, drafts, webhooks, peer review, audit enhancements, i18n
Features implemented:
- F1: Email digest notifications with cron endpoint and per-user frequency
- F2: Jury availability windows and workload preferences in smart assignment
- F3: Round templates with save-from-round and CRUD management
- F4: Side-by-side project comparison view for jury members
- F5: Real-time voting dashboard with Server-Sent Events (SSE)
- F6: Live voting UX: QR codes, audience voting, tie-breaking, score animations
- F7: File versioning, inline preview, bulk download with presigned URLs
- F8: Mentor dashboard: milestones, private notes, activity tracking
- F9: Communication hub with broadcasts, templates, and recipient targeting
- F10: Advanced analytics: cross-round comparison, juror consistency, diversity metrics, PDF export
- F11: Applicant draft saving with magic link resume and cron cleanup
- F12: Webhook integration layer with HMAC signing, retry, and delivery logs
- F13: Peer review discussions with anonymized scores and threaded comments
- F14: Audit log enhancements: before/after diffs, session grouping, anomaly detection, retention
- F15: i18n foundation with next-intl (EN/FR), cookie-based locale, language switcher
Schema: 12 new models, field additions to User, Project, ProjectFile, LiveVotingSession, LiveVote, MentorAssignment, AuditLog, Program
New routers: roundTemplate, message, webhook (registered in _app.ts)
New services: email-digest, webhook-dispatcher
New cron endpoints: /api/cron/digest, /api/cron/draft-cleanup, /api/cron/audit-cleanup
New API routes: /api/live-voting/stream (SSE), /api/files/bulk-download
All features are admin-configurable via SystemSettings or per-model settingsJson fields.
Docker build verified successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:31:41 +01:00
|
|
|
// Audit log
|
|
|
|
|
try {
|
|
|
|
|
await logAudit({
|
|
|
|
|
prisma: ctx.prisma,
|
|
|
|
|
userId: user.id,
|
|
|
|
|
action: 'DRAFT_SUBMITTED',
|
|
|
|
|
entityType: 'Project',
|
|
|
|
|
entityId: updated.id,
|
|
|
|
|
detailsJson: {
|
|
|
|
|
source: 'draft_submission',
|
|
|
|
|
title: data.projectName,
|
|
|
|
|
category: data.competitionCategory,
|
2026-02-12 15:06:11 +01:00
|
|
|
autoAssignedRound: draftAssignedRound?.name || null,
|
Implement 15 platform features: digest, availability, templates, comparison, live voting SSE, file versioning, mentorship, messaging, analytics, drafts, webhooks, peer review, audit enhancements, i18n
Features implemented:
- F1: Email digest notifications with cron endpoint and per-user frequency
- F2: Jury availability windows and workload preferences in smart assignment
- F3: Round templates with save-from-round and CRUD management
- F4: Side-by-side project comparison view for jury members
- F5: Real-time voting dashboard with Server-Sent Events (SSE)
- F6: Live voting UX: QR codes, audience voting, tie-breaking, score animations
- F7: File versioning, inline preview, bulk download with presigned URLs
- F8: Mentor dashboard: milestones, private notes, activity tracking
- F9: Communication hub with broadcasts, templates, and recipient targeting
- F10: Advanced analytics: cross-round comparison, juror consistency, diversity metrics, PDF export
- F11: Applicant draft saving with magic link resume and cron cleanup
- F12: Webhook integration layer with HMAC signing, retry, and delivery logs
- F13: Peer review discussions with anonymized scores and threaded comments
- F14: Audit log enhancements: before/after diffs, session grouping, anomaly detection, retention
- F15: i18n foundation with next-intl (EN/FR), cookie-based locale, language switcher
Schema: 12 new models, field additions to User, Project, ProjectFile, LiveVotingSession, LiveVote, MentorAssignment, AuditLog, Program
New routers: roundTemplate, message, webhook (registered in _app.ts)
New services: email-digest, webhook-dispatcher
New cron endpoints: /api/cron/digest, /api/cron/draft-cleanup, /api/cron/audit-cleanup
New API routes: /api/live-voting/stream (SSE), /api/files/bulk-download
All features are admin-configurable via SystemSettings or per-model settingsJson fields.
Docker build verified successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:31:41 +01:00
|
|
|
},
|
|
|
|
|
ipAddress: ctx.ip,
|
|
|
|
|
userAgent: ctx.userAgent,
|
|
|
|
|
})
|
|
|
|
|
} catch {
|
|
|
|
|
// Never throw on audit failure
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:06:11 +01:00
|
|
|
// Send SUBMISSION_RECEIVED notification if the round is configured for it
|
|
|
|
|
if (draftAssignedRound?.entryNotificationType === 'SUBMISSION_RECEIVED') {
|
|
|
|
|
try {
|
|
|
|
|
await notifyProjectTeam(updated.id, {
|
|
|
|
|
type: NotificationTypes.SUBMISSION_RECEIVED,
|
|
|
|
|
title: 'Submission Received',
|
|
|
|
|
message: `Your submission "${data.projectName}" has been received and is now under review.`,
|
|
|
|
|
linkUrl: `/team/projects/${updated.id}`,
|
|
|
|
|
linkLabel: 'View Submission',
|
|
|
|
|
metadata: {
|
|
|
|
|
projectName: data.projectName,
|
|
|
|
|
roundName: draftAssignedRound.name,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
} catch {
|
|
|
|
|
// Never fail on notification
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Implement 15 platform features: digest, availability, templates, comparison, live voting SSE, file versioning, mentorship, messaging, analytics, drafts, webhooks, peer review, audit enhancements, i18n
Features implemented:
- F1: Email digest notifications with cron endpoint and per-user frequency
- F2: Jury availability windows and workload preferences in smart assignment
- F3: Round templates with save-from-round and CRUD management
- F4: Side-by-side project comparison view for jury members
- F5: Real-time voting dashboard with Server-Sent Events (SSE)
- F6: Live voting UX: QR codes, audience voting, tie-breaking, score animations
- F7: File versioning, inline preview, bulk download with presigned URLs
- F8: Mentor dashboard: milestones, private notes, activity tracking
- F9: Communication hub with broadcasts, templates, and recipient targeting
- F10: Advanced analytics: cross-round comparison, juror consistency, diversity metrics, PDF export
- F11: Applicant draft saving with magic link resume and cron cleanup
- F12: Webhook integration layer with HMAC signing, retry, and delivery logs
- F13: Peer review discussions with anonymized scores and threaded comments
- F14: Audit log enhancements: before/after diffs, session grouping, anomaly detection, retention
- F15: i18n foundation with next-intl (EN/FR), cookie-based locale, language switcher
Schema: 12 new models, field additions to User, Project, ProjectFile, LiveVotingSession, LiveVote, MentorAssignment, AuditLog, Program
New routers: roundTemplate, message, webhook (registered in _app.ts)
New services: email-digest, webhook-dispatcher
New cron endpoints: /api/cron/digest, /api/cron/draft-cleanup, /api/cron/audit-cleanup
New API routes: /api/live-voting/stream (SSE), /api/files/bulk-download
All features are admin-configurable via SystemSettings or per-model settingsJson fields.
Docker build verified successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:31:41 +01:00
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
projectId: updated.id,
|
Fix build errors: add missing Prisma models/fields and resolve TypeScript type errors
Schema: Add 11 new models (RoundTemplate, MentorNote, MentorMilestone,
MentorMilestoneCompletion, EvaluationDiscussion, DiscussionComment,
Message, MessageRecipient, MessageTemplate, Webhook, WebhookDelivery,
DigestLog) and missing fields on existing models (Project.isDraft,
ProjectFile.version, LiveVotingSession.allowAudienceVotes, User.digestFrequency,
AuditLog.sessionId, MentorAssignment.completionStatus, etc).
Add AUDIT_CONFIG/LOCALIZATION/DIGEST/ANALYTICS enum values.
Code: Fix implicit any types, route type casts, enum casts, null safety,
composite key handling, and relation field names across 11 source files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 14:04:02 +01:00
|
|
|
message: `Thank you for applying to ${project.round?.program.name ?? 'the program'}!`,
|
Implement 15 platform features: digest, availability, templates, comparison, live voting SSE, file versioning, mentorship, messaging, analytics, drafts, webhooks, peer review, audit enhancements, i18n
Features implemented:
- F1: Email digest notifications with cron endpoint and per-user frequency
- F2: Jury availability windows and workload preferences in smart assignment
- F3: Round templates with save-from-round and CRUD management
- F4: Side-by-side project comparison view for jury members
- F5: Real-time voting dashboard with Server-Sent Events (SSE)
- F6: Live voting UX: QR codes, audience voting, tie-breaking, score animations
- F7: File versioning, inline preview, bulk download with presigned URLs
- F8: Mentor dashboard: milestones, private notes, activity tracking
- F9: Communication hub with broadcasts, templates, and recipient targeting
- F10: Advanced analytics: cross-round comparison, juror consistency, diversity metrics, PDF export
- F11: Applicant draft saving with magic link resume and cron cleanup
- F12: Webhook integration layer with HMAC signing, retry, and delivery logs
- F13: Peer review discussions with anonymized scores and threaded comments
- F14: Audit log enhancements: before/after diffs, session grouping, anomaly detection, retention
- F15: i18n foundation with next-intl (EN/FR), cookie-based locale, language switcher
Schema: 12 new models, field additions to User, Project, ProjectFile, LiveVotingSession, LiveVote, MentorAssignment, AuditLog, Program
New routers: roundTemplate, message, webhook (registered in _app.ts)
New services: email-digest, webhook-dispatcher
New cron endpoints: /api/cron/digest, /api/cron/draft-cleanup, /api/cron/audit-cleanup
New API routes: /api/live-voting/stream (SSE), /api/files/bulk-download
All features are admin-configurable via SystemSettings or per-model settingsJson fields.
Docker build verified successfully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:31:41 +01:00
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a read-only preview of draft data
|
|
|
|
|
*/
|
|
|
|
|
getPreview: publicProcedure
|
|
|
|
|
.input(z.object({ draftToken: z.string() }))
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
|
|
|
|
const projects = await ctx.prisma.project.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
isDraft: true,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const project = projects.find((p) => {
|
|
|
|
|
const metadata = p.metadataJson as Record<string, unknown> | null
|
|
|
|
|
return metadata?.draftToken === input.draftToken
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!project) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'NOT_FOUND',
|
|
|
|
|
message: 'Draft not found or invalid token',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
title: project.title,
|
|
|
|
|
draftDataJson: project.draftDataJson,
|
|
|
|
|
createdAt: project.createdAt,
|
|
|
|
|
expiresAt: project.draftExpiresAt,
|
|
|
|
|
}
|
|
|
|
|
}),
|
2026-01-30 13:41:32 +01:00
|
|
|
})
|