@@ -92,7 +92,10 @@ const baseSubmissionSchema = z.object({
9292 videoUrl : z
9393 . union ( [ z . string ( ) . url ( 'Please enter a valid URL' ) , z . literal ( '' ) ] )
9494 . optional ( ) ,
95- introduction : z . string ( ) . optional ( ) ,
95+ introduction : z
96+ . string ( )
97+ . max ( 500 , 'Introduction cannot exceed 500 characters' )
98+ . optional ( ) ,
9699 links : z . array (
97100 z . object ( {
98101 type : z . string ( ) ,
@@ -155,12 +158,18 @@ const INITIAL_STEPS: Step[] = [
155158const LINK_TYPES = [
156159 { value : 'github' , label : 'GitHub' } ,
157160 { value : 'demo' , label : 'Demo' } ,
158- { value : 'website' , label : 'Website' } ,
159- { value : 'documentation' , label : 'Documentation' } ,
161+ { value : 'video' , label : 'Video' } ,
162+ { value : 'document' , label : 'Document' } ,
163+ { value : 'presentation' , label : 'Presentation' } ,
160164 { value : 'other' , label : 'Other' } ,
161165] ;
162166
163- const OTHER_LINK_TYPES = [ 'demo' , 'website' , 'documentation' , 'other' ] ;
167+ const OTHER_LINK_TYPES = [ 'demo' , 'video' , 'document' , 'presentation' , 'other' ] ;
168+
169+ const MAX_OTHER_LINKS = 5 ;
170+ const FIXED_LINK_TYPES = LINK_TYPES . map ( t => t . value ) . filter (
171+ v => v !== 'other'
172+ ) ;
164173
165174const isValidUrl = ( url : string | undefined ) : boolean => {
166175 if ( ! url || String ( url ) . trim ( ) === '' ) return false ;
@@ -517,6 +526,9 @@ const SubmissionFormContent: React.FC<SubmissionFormContentProps> = ({
517526 } ;
518527
519528 const handleFillMockData = ( ) => {
529+ const participationType : 'INDIVIDUAL' | 'TEAM' = myTeam
530+ ? 'TEAM'
531+ : 'INDIVIDUAL' ;
520532 const mockData = {
521533 projectName : 'AI-Powered Task Manager' ,
522534 category : categoryOptions [ 0 ] ,
@@ -529,7 +541,7 @@ const SubmissionFormContent: React.FC<SubmissionFormContentProps> = ({
529541 { type : 'github' , url : 'https://github.com/example/ai-task-manager' } ,
530542 { type : 'demo' , url : 'https://demo.example.com/ai-task-manager' } ,
531543 ] ,
532- participationType : 'INDIVIDUAL' as const ,
544+ participationType,
533545 } ;
534546
535547 form . reset ( mockData ) ;
@@ -539,7 +551,27 @@ const SubmissionFormContent: React.FC<SubmissionFormContentProps> = ({
539551
540552 const handleAddLink = ( ) => {
541553 const currentLinks = form . getValues ( 'links' ) || [ ] ;
542- form . setValue ( 'links' , [ ...currentLinks , { type : 'github' , url : '' } ] , {
554+ const usedFixedTypes = new Set (
555+ currentLinks . map ( l => l . type ) . filter ( t => t !== 'other' )
556+ ) ;
557+ const otherCount = currentLinks . filter ( l => l . type === 'other' ) . length ;
558+
559+ const firstUnusedFixed = FIXED_LINK_TYPES . find ( t => ! usedFixedTypes . has ( t ) ) ;
560+
561+ let nextType : string ;
562+ if ( firstUnusedFixed ) {
563+ nextType = firstUnusedFixed ;
564+ } else if ( otherCount < MAX_OTHER_LINKS ) {
565+ nextType = 'other' ;
566+ } else {
567+ toast . error ( 'Cannot add another link' , {
568+ description : `All fixed link types are used and you have reached the limit of ${ MAX_OTHER_LINKS } "Other" links.` ,
569+ duration : 6000 ,
570+ } ) ;
571+ return ;
572+ }
573+
574+ form . setValue ( 'links' , [ ...currentLinks , { type : nextType , url : '' } ] , {
543575 shouldValidate : false ,
544576 } ) ;
545577 } ;
@@ -582,6 +614,33 @@ const SubmissionFormContent: React.FC<SubmissionFormContentProps> = ({
582614 value : string
583615 ) => {
584616 const currentLinks = form . getValues ( 'links' ) || [ ] ;
617+
618+ if ( field === 'type' ) {
619+ if ( value !== 'other' ) {
620+ const isDuplicate = currentLinks . some (
621+ ( l , i ) => i !== index && l . type === value
622+ ) ;
623+ if ( isDuplicate ) {
624+ toast . error ( 'Duplicate link type' , {
625+ description : `"${ value } " is already used. Each link type can be used at most once. Choose "Other" for additional links.` ,
626+ duration : 6000 ,
627+ } ) ;
628+ return ;
629+ }
630+ } else {
631+ const otherCount = currentLinks . filter (
632+ ( l , i ) => i !== index && l . type === 'other'
633+ ) . length ;
634+ if ( otherCount >= MAX_OTHER_LINKS ) {
635+ toast . error ( 'Too many "Other" links' , {
636+ description : `You can include at most ${ MAX_OTHER_LINKS } "Other" links per submission.` ,
637+ duration : 6000 ,
638+ } ) ;
639+ return ;
640+ }
641+ }
642+ }
643+
585644 const updatedLinks = [ ...currentLinks ] ;
586645 updatedLinks [ index ] = { ...updatedLinks [ index ] , [ field ] : value } ;
587646 form . setValue ( 'links' , updatedLinks , { shouldValidate : true } ) ;
@@ -643,7 +702,7 @@ const SubmissionFormContent: React.FC<SubmissionFormContentProps> = ({
643702 if ( requireOtherLinks && ! hasValidOtherLink ) {
644703 form . setError ( 'links' , {
645704 message :
646- 'At least one additional link (Demo, Website, Documentation , or Other) is required for this hackathon.' ,
705+ 'At least one additional link (Demo, Video, Document, Presentation , or Other) is required for this hackathon.' ,
647706 } ) ;
648707 return ;
649708 }
@@ -792,7 +851,7 @@ const SubmissionFormContent: React.FC<SubmissionFormContentProps> = ({
792851 if ( requireOtherLinks && ! hasValidOtherLink ) {
793852 form . setError ( 'links' , {
794853 message :
795- 'At least one additional link (Demo, Website, Documentation , or Other) is required for this hackathon.' ,
854+ 'At least one additional link (Demo, Video, Document, Presentation , or Other) is required for this hackathon.' ,
796855 } ) ;
797856 setCurrentStep ( 2 ) ;
798857 updateStepState ( 2 , 'active' ) ;
@@ -1346,12 +1405,13 @@ const SubmissionFormContent: React.FC<SubmissionFormContentProps> = ({
13461405 < FormControl >
13471406 < Textarea
13481407 placeholder = 'Tell us more about your project...'
1408+ maxLength = { 500 }
13491409 className = 'min-h-[100px] border-gray-700 bg-gray-800/50 text-white placeholder:text-gray-500'
13501410 { ...field }
13511411 />
13521412 </ FormControl >
13531413 < FormDescription className = 'text-gray-400' >
1354- Optional: Additional information about your project
1414+ Optional. { field . value ?. length || 0 } / 500 characters max
13551415 </ FormDescription >
13561416 < FormMessage />
13571417 </ FormItem >
@@ -1386,12 +1446,17 @@ const SubmissionFormContent: React.FC<SubmissionFormContentProps> = ({
13861446 { ( requireGithub || requireOtherLinks ) && (
13871447 < FormDescription className = 'text-gray-400' >
13881448 { requireGithub && requireOtherLinks
1389- ? 'GitHub repository link and at least one additional link (Demo, Website, Documentation , or Other) are required for this hackathon.'
1449+ ? 'GitHub repository link and at least one additional link (Demo, Video, Document, Presentation , or Other) are required for this hackathon.'
13901450 : requireGithub
13911451 ? 'GitHub repository link is required for this hackathon.'
1392- : 'At least one additional link (Demo, Website, Documentation , or Other) is required for this hackathon.' }
1452+ : 'At least one additional link (Demo, Video, Document, Presentation , or Other) is required for this hackathon.' }
13931453 </ FormDescription >
13941454 ) }
1455+ < FormDescription className = 'text-gray-400' >
1456+ Each link type can be used at most once. For additional
1457+ links, choose "Other" (up to { MAX_OTHER_LINKS } { ' ' }
1458+ allowed).
1459+ </ FormDescription >
13951460 { formLinks . length === 0 ? (
13961461 < p className = 'text-sm text-gray-400' >
13971462 No links added. Click "Add Link" to add project links.
0 commit comments