Apply full refactor updates plus pipeline/email UX confirmations
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m33s
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m33s
This commit is contained in:
@@ -1,133 +1,133 @@
|
||||
import type { StageType, TrackKind, RoutingMode, DecisionMode, AwardScoringMode } from '@prisma/client'
|
||||
|
||||
// ============================================================================
|
||||
// Stage Config Discriminated Unions
|
||||
// ============================================================================
|
||||
|
||||
export type IntakeConfig = {
|
||||
submissionWindowEnabled: boolean
|
||||
lateSubmissionPolicy: 'reject' | 'flag' | 'accept'
|
||||
lateGraceHours: number
|
||||
fileRequirements: FileRequirementConfig[]
|
||||
}
|
||||
|
||||
export type FileRequirementConfig = {
|
||||
name: string
|
||||
description?: string
|
||||
acceptedMimeTypes: string[]
|
||||
maxSizeMB?: number
|
||||
isRequired: boolean
|
||||
}
|
||||
|
||||
export type FilterConfig = {
|
||||
rules: FilterRuleConfig[]
|
||||
aiRubricEnabled: boolean
|
||||
aiCriteriaText: string
|
||||
aiConfidenceThresholds: {
|
||||
high: number
|
||||
medium: number
|
||||
low: number
|
||||
}
|
||||
manualQueueEnabled: boolean
|
||||
}
|
||||
|
||||
export type FilterRuleConfig = {
|
||||
field: string
|
||||
operator: string
|
||||
value: string | number | boolean
|
||||
weight: number
|
||||
}
|
||||
|
||||
export type EvaluationConfig = {
|
||||
requiredReviews: number
|
||||
maxLoadPerJuror: number
|
||||
minLoadPerJuror: number
|
||||
availabilityWeighting: boolean
|
||||
overflowPolicy: 'queue' | 'expand_pool' | 'reduce_reviews'
|
||||
}
|
||||
|
||||
export type SelectionConfig = {
|
||||
finalistCount?: number
|
||||
rankingMethod: 'score_average' | 'weighted_criteria' | 'binary_pass'
|
||||
tieBreaker: 'admin_decides' | 'highest_individual' | 'revote'
|
||||
}
|
||||
|
||||
export type LiveFinalConfig = {
|
||||
juryVotingEnabled: boolean
|
||||
audienceVotingEnabled: boolean
|
||||
audienceVoteWeight: number
|
||||
cohortSetupMode: 'auto' | 'manual'
|
||||
revealPolicy: 'immediate' | 'delayed' | 'ceremony'
|
||||
}
|
||||
|
||||
export type ResultsConfig = {
|
||||
publicationMode: 'manual' | 'auto_on_close'
|
||||
showDetailedScores: boolean
|
||||
showRankings: boolean
|
||||
}
|
||||
|
||||
export type StageConfigMap = {
|
||||
INTAKE: IntakeConfig
|
||||
FILTER: FilterConfig
|
||||
EVALUATION: EvaluationConfig
|
||||
SELECTION: SelectionConfig
|
||||
LIVE_FINAL: LiveFinalConfig
|
||||
RESULTS: ResultsConfig
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Wizard Stage / Track / State Types
|
||||
// ============================================================================
|
||||
|
||||
export type WizardStageConfig = {
|
||||
id?: string
|
||||
name: string
|
||||
slug: string
|
||||
stageType: StageType
|
||||
sortOrder: number
|
||||
configJson: Record<string, unknown>
|
||||
windowOpenAt?: Date | null
|
||||
windowCloseAt?: Date | null
|
||||
}
|
||||
|
||||
export type WizardTrackConfig = {
|
||||
id?: string
|
||||
name: string
|
||||
slug: string
|
||||
kind: TrackKind
|
||||
sortOrder: number
|
||||
routingModeDefault?: RoutingMode
|
||||
decisionMode?: DecisionMode
|
||||
stages: WizardStageConfig[]
|
||||
awardConfig?: {
|
||||
name: string
|
||||
description?: string
|
||||
scoringMode?: AwardScoringMode
|
||||
}
|
||||
}
|
||||
|
||||
export type WizardState = {
|
||||
name: string
|
||||
slug: string
|
||||
programId: string
|
||||
settingsJson: Record<string, unknown>
|
||||
tracks: WizardTrackConfig[]
|
||||
notificationConfig: Record<string, boolean>
|
||||
overridePolicy: Record<string, unknown>
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Validation Result
|
||||
// ============================================================================
|
||||
|
||||
export type ValidationResult = {
|
||||
valid: boolean
|
||||
errors: string[]
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export type SectionValidation = {
|
||||
basics: ValidationResult
|
||||
tracks: ValidationResult
|
||||
notifications: ValidationResult
|
||||
}
|
||||
import type { StageType, TrackKind, RoutingMode, DecisionMode, AwardScoringMode } from '@prisma/client'
|
||||
|
||||
// ============================================================================
|
||||
// Stage Config Discriminated Unions
|
||||
// ============================================================================
|
||||
|
||||
export type IntakeConfig = {
|
||||
submissionWindowEnabled: boolean
|
||||
lateSubmissionPolicy: 'reject' | 'flag' | 'accept'
|
||||
lateGraceHours: number
|
||||
fileRequirements: FileRequirementConfig[]
|
||||
}
|
||||
|
||||
export type FileRequirementConfig = {
|
||||
name: string
|
||||
description?: string
|
||||
acceptedMimeTypes: string[]
|
||||
maxSizeMB?: number
|
||||
isRequired: boolean
|
||||
}
|
||||
|
||||
export type FilterConfig = {
|
||||
rules: FilterRuleConfig[]
|
||||
aiRubricEnabled: boolean
|
||||
aiCriteriaText: string
|
||||
aiConfidenceThresholds: {
|
||||
high: number
|
||||
medium: number
|
||||
low: number
|
||||
}
|
||||
manualQueueEnabled: boolean
|
||||
}
|
||||
|
||||
export type FilterRuleConfig = {
|
||||
field: string
|
||||
operator: string
|
||||
value: string | number | boolean
|
||||
weight: number
|
||||
}
|
||||
|
||||
export type EvaluationConfig = {
|
||||
requiredReviews: number
|
||||
maxLoadPerJuror: number
|
||||
minLoadPerJuror: number
|
||||
availabilityWeighting: boolean
|
||||
overflowPolicy: 'queue' | 'expand_pool' | 'reduce_reviews'
|
||||
}
|
||||
|
||||
export type SelectionConfig = {
|
||||
finalistCount?: number
|
||||
rankingMethod: 'score_average' | 'weighted_criteria' | 'binary_pass'
|
||||
tieBreaker: 'admin_decides' | 'highest_individual' | 'revote'
|
||||
}
|
||||
|
||||
export type LiveFinalConfig = {
|
||||
juryVotingEnabled: boolean
|
||||
audienceVotingEnabled: boolean
|
||||
audienceVoteWeight: number
|
||||
cohortSetupMode: 'auto' | 'manual'
|
||||
revealPolicy: 'immediate' | 'delayed' | 'ceremony'
|
||||
}
|
||||
|
||||
export type ResultsConfig = {
|
||||
publicationMode: 'manual' | 'auto_on_close'
|
||||
showDetailedScores: boolean
|
||||
showRankings: boolean
|
||||
}
|
||||
|
||||
export type StageConfigMap = {
|
||||
INTAKE: IntakeConfig
|
||||
FILTER: FilterConfig
|
||||
EVALUATION: EvaluationConfig
|
||||
SELECTION: SelectionConfig
|
||||
LIVE_FINAL: LiveFinalConfig
|
||||
RESULTS: ResultsConfig
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Wizard Stage / Track / State Types
|
||||
// ============================================================================
|
||||
|
||||
export type WizardStageConfig = {
|
||||
id?: string
|
||||
name: string
|
||||
slug: string
|
||||
stageType: StageType
|
||||
sortOrder: number
|
||||
configJson: Record<string, unknown>
|
||||
windowOpenAt?: Date | null
|
||||
windowCloseAt?: Date | null
|
||||
}
|
||||
|
||||
export type WizardTrackConfig = {
|
||||
id?: string
|
||||
name: string
|
||||
slug: string
|
||||
kind: TrackKind
|
||||
sortOrder: number
|
||||
routingModeDefault?: RoutingMode
|
||||
decisionMode?: DecisionMode
|
||||
stages: WizardStageConfig[]
|
||||
awardConfig?: {
|
||||
name: string
|
||||
description?: string
|
||||
scoringMode?: AwardScoringMode
|
||||
}
|
||||
}
|
||||
|
||||
export type WizardState = {
|
||||
name: string
|
||||
slug: string
|
||||
programId: string
|
||||
settingsJson: Record<string, unknown>
|
||||
tracks: WizardTrackConfig[]
|
||||
notificationConfig: Record<string, boolean>
|
||||
overridePolicy: Record<string, unknown>
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Validation Result
|
||||
// ============================================================================
|
||||
|
||||
export type ValidationResult = {
|
||||
valid: boolean
|
||||
errors: string[]
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export type SectionValidation = {
|
||||
basics: ValidationResult
|
||||
tracks: ValidationResult
|
||||
notifications: ValidationResult
|
||||
}
|
||||
|
||||
@@ -1,155 +1,155 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
// --- Step Configuration ---
|
||||
|
||||
export const WIZARD_STEP_IDS = ['welcome', 'contact', 'project', 'team', 'additional', 'review'] as const
|
||||
export type WizardStepId = (typeof WIZARD_STEP_IDS)[number]
|
||||
|
||||
export const wizardStepSchema = z.object({
|
||||
id: z.enum(WIZARD_STEP_IDS),
|
||||
enabled: z.boolean().default(true),
|
||||
order: z.number().int().min(0),
|
||||
title: z.string().optional(),
|
||||
conditionalOn: z
|
||||
.object({
|
||||
field: z.string(),
|
||||
operator: z.enum(['equals', 'notEquals', 'in', 'notIn']),
|
||||
value: z.union([z.string(), z.array(z.string())]),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
// --- Field Configuration ---
|
||||
|
||||
export const wizardFieldValidationSchema = z.object({
|
||||
min: z.number().optional(),
|
||||
max: z.number().optional(),
|
||||
pattern: z.string().optional(),
|
||||
patternMessage: z.string().optional(),
|
||||
})
|
||||
|
||||
export const wizardFieldConfigSchema = z.object({
|
||||
required: z.boolean().optional(),
|
||||
visible: z.boolean().optional(),
|
||||
label: z.string().optional(),
|
||||
helpText: z.string().optional(),
|
||||
placeholder: z.string().optional(),
|
||||
validation: wizardFieldValidationSchema.optional(),
|
||||
})
|
||||
|
||||
// --- Dropdown Option ---
|
||||
|
||||
export const dropdownOptionSchema = z.object({
|
||||
value: z.string().min(1),
|
||||
label: z.string().min(1).max(100),
|
||||
description: z.string().max(300).optional(),
|
||||
icon: z.string().optional(),
|
||||
})
|
||||
|
||||
// --- Custom Field ---
|
||||
|
||||
export const customFieldSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
type: z.enum(['text', 'textarea', 'number', 'select', 'multiselect', 'checkbox', 'date']),
|
||||
label: z.string().min(1).max(100),
|
||||
placeholder: z.string().optional(),
|
||||
helpText: z.string().optional(),
|
||||
required: z.boolean().default(false),
|
||||
options: z.array(z.string()).optional(),
|
||||
validation: wizardFieldValidationSchema.optional(),
|
||||
stepId: z.enum(WIZARD_STEP_IDS),
|
||||
order: z.number().int().default(0),
|
||||
})
|
||||
|
||||
// --- Welcome Message ---
|
||||
|
||||
export const welcomeMessageSchema = z.object({
|
||||
title: z.string().max(200).optional(),
|
||||
description: z.string().max(1000).optional(),
|
||||
imageUrl: z.string().url().optional(),
|
||||
})
|
||||
|
||||
// --- Feature Flags ---
|
||||
|
||||
export const wizardFeaturesSchema = z.object({
|
||||
enableWhatsApp: z.boolean().optional(),
|
||||
enableMentorship: z.boolean().optional(),
|
||||
enableTeamMembers: z.boolean().optional(),
|
||||
requireInstitution: z.boolean().optional(),
|
||||
})
|
||||
|
||||
// --- Main Config Schema ---
|
||||
|
||||
export const wizardConfigSchema = z.object({
|
||||
steps: z.array(wizardStepSchema).default([]),
|
||||
fields: z.record(z.string(), wizardFieldConfigSchema).default({}),
|
||||
competitionCategories: z.array(dropdownOptionSchema).optional(),
|
||||
oceanIssues: z.array(dropdownOptionSchema).optional(),
|
||||
features: wizardFeaturesSchema.optional(),
|
||||
welcomeMessage: welcomeMessageSchema.optional(),
|
||||
customFields: z.array(customFieldSchema).optional(),
|
||||
})
|
||||
|
||||
// --- Exported Types ---
|
||||
|
||||
export type WizardStep = z.infer<typeof wizardStepSchema>
|
||||
export type WizardFieldConfig = z.infer<typeof wizardFieldConfigSchema>
|
||||
export type WizardFieldValidation = z.infer<typeof wizardFieldValidationSchema>
|
||||
export type DropdownOption = z.infer<typeof dropdownOptionSchema>
|
||||
export type CustomField = z.infer<typeof customFieldSchema>
|
||||
export type WizardFeatures = z.infer<typeof wizardFeaturesSchema>
|
||||
export type WelcomeMessage = z.infer<typeof welcomeMessageSchema>
|
||||
export type WizardConfig = z.infer<typeof wizardConfigSchema>
|
||||
|
||||
// --- Default Configuration ---
|
||||
// Must match current hardcoded behavior exactly for backward compatibility
|
||||
|
||||
export const DEFAULT_COMPETITION_CATEGORIES: DropdownOption[] = [
|
||||
{
|
||||
value: 'BUSINESS_CONCEPT',
|
||||
label: 'Business Concepts',
|
||||
description: 'For students and recent graduates with innovative ocean-focused business ideas',
|
||||
icon: 'GraduationCap',
|
||||
},
|
||||
{
|
||||
value: 'STARTUP',
|
||||
label: 'Start-ups',
|
||||
description: 'For established companies working on ocean protection solutions',
|
||||
icon: 'Rocket',
|
||||
},
|
||||
]
|
||||
|
||||
export const DEFAULT_OCEAN_ISSUES: DropdownOption[] = [
|
||||
{ value: 'POLLUTION_REDUCTION', label: 'Reduction of pollution (plastics, chemicals, noise, light,...)' },
|
||||
{ value: 'CLIMATE_MITIGATION', label: 'Mitigation of climate change and sea-level rise' },
|
||||
{ value: 'TECHNOLOGY_INNOVATION', label: 'Technology & innovations' },
|
||||
{ value: 'SUSTAINABLE_SHIPPING', label: 'Sustainable shipping & yachting' },
|
||||
{ value: 'BLUE_CARBON', label: 'Blue carbon' },
|
||||
{ value: 'HABITAT_RESTORATION', label: 'Restoration of marine habitats & ecosystems' },
|
||||
{ value: 'COMMUNITY_CAPACITY', label: 'Capacity building for coastal communities' },
|
||||
{ value: 'SUSTAINABLE_FISHING', label: 'Sustainable fishing and aquaculture & blue food' },
|
||||
{ value: 'CONSUMER_AWARENESS', label: 'Consumer awareness and education' },
|
||||
{ value: 'OCEAN_ACIDIFICATION', label: 'Mitigation of ocean acidification' },
|
||||
{ value: 'OTHER', label: 'Other' },
|
||||
]
|
||||
|
||||
export const DEFAULT_WIZARD_CONFIG: WizardConfig = {
|
||||
steps: [
|
||||
{ id: 'welcome', enabled: true, order: 0, title: 'Category' },
|
||||
{ id: 'contact', enabled: true, order: 1, title: 'Contact' },
|
||||
{ id: 'project', enabled: true, order: 2, title: 'Project' },
|
||||
{ id: 'team', enabled: true, order: 3, title: 'Team' },
|
||||
{ id: 'additional', enabled: true, order: 4, title: 'Details' },
|
||||
{ id: 'review', enabled: true, order: 5, title: 'Review' },
|
||||
],
|
||||
fields: {},
|
||||
competitionCategories: DEFAULT_COMPETITION_CATEGORIES,
|
||||
oceanIssues: DEFAULT_OCEAN_ISSUES,
|
||||
features: {
|
||||
enableWhatsApp: false,
|
||||
enableMentorship: true,
|
||||
enableTeamMembers: true,
|
||||
requireInstitution: false,
|
||||
},
|
||||
customFields: [],
|
||||
}
|
||||
import { z } from 'zod'
|
||||
|
||||
// --- Step Configuration ---
|
||||
|
||||
export const WIZARD_STEP_IDS = ['welcome', 'contact', 'project', 'team', 'additional', 'review'] as const
|
||||
export type WizardStepId = (typeof WIZARD_STEP_IDS)[number]
|
||||
|
||||
export const wizardStepSchema = z.object({
|
||||
id: z.enum(WIZARD_STEP_IDS),
|
||||
enabled: z.boolean().default(true),
|
||||
order: z.number().int().min(0),
|
||||
title: z.string().optional(),
|
||||
conditionalOn: z
|
||||
.object({
|
||||
field: z.string(),
|
||||
operator: z.enum(['equals', 'notEquals', 'in', 'notIn']),
|
||||
value: z.union([z.string(), z.array(z.string())]),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
// --- Field Configuration ---
|
||||
|
||||
export const wizardFieldValidationSchema = z.object({
|
||||
min: z.number().optional(),
|
||||
max: z.number().optional(),
|
||||
pattern: z.string().optional(),
|
||||
patternMessage: z.string().optional(),
|
||||
})
|
||||
|
||||
export const wizardFieldConfigSchema = z.object({
|
||||
required: z.boolean().optional(),
|
||||
visible: z.boolean().optional(),
|
||||
label: z.string().optional(),
|
||||
helpText: z.string().optional(),
|
||||
placeholder: z.string().optional(),
|
||||
validation: wizardFieldValidationSchema.optional(),
|
||||
})
|
||||
|
||||
// --- Dropdown Option ---
|
||||
|
||||
export const dropdownOptionSchema = z.object({
|
||||
value: z.string().min(1),
|
||||
label: z.string().min(1).max(100),
|
||||
description: z.string().max(300).optional(),
|
||||
icon: z.string().optional(),
|
||||
})
|
||||
|
||||
// --- Custom Field ---
|
||||
|
||||
export const customFieldSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
type: z.enum(['text', 'textarea', 'number', 'select', 'multiselect', 'checkbox', 'date']),
|
||||
label: z.string().min(1).max(100),
|
||||
placeholder: z.string().optional(),
|
||||
helpText: z.string().optional(),
|
||||
required: z.boolean().default(false),
|
||||
options: z.array(z.string()).optional(),
|
||||
validation: wizardFieldValidationSchema.optional(),
|
||||
stepId: z.enum(WIZARD_STEP_IDS),
|
||||
order: z.number().int().default(0),
|
||||
})
|
||||
|
||||
// --- Welcome Message ---
|
||||
|
||||
export const welcomeMessageSchema = z.object({
|
||||
title: z.string().max(200).optional(),
|
||||
description: z.string().max(1000).optional(),
|
||||
imageUrl: z.string().url().optional(),
|
||||
})
|
||||
|
||||
// --- Feature Flags ---
|
||||
|
||||
export const wizardFeaturesSchema = z.object({
|
||||
enableWhatsApp: z.boolean().optional(),
|
||||
enableMentorship: z.boolean().optional(),
|
||||
enableTeamMembers: z.boolean().optional(),
|
||||
requireInstitution: z.boolean().optional(),
|
||||
})
|
||||
|
||||
// --- Main Config Schema ---
|
||||
|
||||
export const wizardConfigSchema = z.object({
|
||||
steps: z.array(wizardStepSchema).default([]),
|
||||
fields: z.record(z.string(), wizardFieldConfigSchema).default({}),
|
||||
competitionCategories: z.array(dropdownOptionSchema).optional(),
|
||||
oceanIssues: z.array(dropdownOptionSchema).optional(),
|
||||
features: wizardFeaturesSchema.optional(),
|
||||
welcomeMessage: welcomeMessageSchema.optional(),
|
||||
customFields: z.array(customFieldSchema).optional(),
|
||||
})
|
||||
|
||||
// --- Exported Types ---
|
||||
|
||||
export type WizardStep = z.infer<typeof wizardStepSchema>
|
||||
export type WizardFieldConfig = z.infer<typeof wizardFieldConfigSchema>
|
||||
export type WizardFieldValidation = z.infer<typeof wizardFieldValidationSchema>
|
||||
export type DropdownOption = z.infer<typeof dropdownOptionSchema>
|
||||
export type CustomField = z.infer<typeof customFieldSchema>
|
||||
export type WizardFeatures = z.infer<typeof wizardFeaturesSchema>
|
||||
export type WelcomeMessage = z.infer<typeof welcomeMessageSchema>
|
||||
export type WizardConfig = z.infer<typeof wizardConfigSchema>
|
||||
|
||||
// --- Default Configuration ---
|
||||
// Must match current hardcoded behavior exactly for backward compatibility
|
||||
|
||||
export const DEFAULT_COMPETITION_CATEGORIES: DropdownOption[] = [
|
||||
{
|
||||
value: 'BUSINESS_CONCEPT',
|
||||
label: 'Business Concepts',
|
||||
description: 'For students and recent graduates with innovative ocean-focused business ideas',
|
||||
icon: 'GraduationCap',
|
||||
},
|
||||
{
|
||||
value: 'STARTUP',
|
||||
label: 'Start-ups',
|
||||
description: 'For established companies working on ocean protection solutions',
|
||||
icon: 'Rocket',
|
||||
},
|
||||
]
|
||||
|
||||
export const DEFAULT_OCEAN_ISSUES: DropdownOption[] = [
|
||||
{ value: 'POLLUTION_REDUCTION', label: 'Reduction of pollution (plastics, chemicals, noise, light,...)' },
|
||||
{ value: 'CLIMATE_MITIGATION', label: 'Mitigation of climate change and sea-level rise' },
|
||||
{ value: 'TECHNOLOGY_INNOVATION', label: 'Technology & innovations' },
|
||||
{ value: 'SUSTAINABLE_SHIPPING', label: 'Sustainable shipping & yachting' },
|
||||
{ value: 'BLUE_CARBON', label: 'Blue carbon' },
|
||||
{ value: 'HABITAT_RESTORATION', label: 'Restoration of marine habitats & ecosystems' },
|
||||
{ value: 'COMMUNITY_CAPACITY', label: 'Capacity building for coastal communities' },
|
||||
{ value: 'SUSTAINABLE_FISHING', label: 'Sustainable fishing and aquaculture & blue food' },
|
||||
{ value: 'CONSUMER_AWARENESS', label: 'Consumer awareness and education' },
|
||||
{ value: 'OCEAN_ACIDIFICATION', label: 'Mitigation of ocean acidification' },
|
||||
{ value: 'OTHER', label: 'Other' },
|
||||
]
|
||||
|
||||
export const DEFAULT_WIZARD_CONFIG: WizardConfig = {
|
||||
steps: [
|
||||
{ id: 'welcome', enabled: true, order: 0, title: 'Category' },
|
||||
{ id: 'contact', enabled: true, order: 1, title: 'Contact' },
|
||||
{ id: 'project', enabled: true, order: 2, title: 'Project' },
|
||||
{ id: 'team', enabled: true, order: 3, title: 'Team' },
|
||||
{ id: 'additional', enabled: true, order: 4, title: 'Details' },
|
||||
{ id: 'review', enabled: true, order: 5, title: 'Review' },
|
||||
],
|
||||
fields: {},
|
||||
competitionCategories: DEFAULT_COMPETITION_CATEGORIES,
|
||||
oceanIssues: DEFAULT_OCEAN_ISSUES,
|
||||
features: {
|
||||
enableWhatsApp: false,
|
||||
enableMentorship: true,
|
||||
enableTeamMembers: true,
|
||||
requireInstitution: false,
|
||||
},
|
||||
customFields: [],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user