@@ -50,6 +50,7 @@ import {
5050 type GalleryPhoto ,
5151} from '@/lib/supabase' ;
5252import { REGISTRATIONS_OPEN , REGISTRATIONS_SOON_PATH , MERCH_STORE_OPEN , MERCH_SOON_PATH } from '@/config/launch' ;
53+ import { REGISTRATION_FEE , UPI_ID , UPI_PAY_LINK , UPI_QR_SRC } from '@/config/payment' ;
5354import { SKIP_INTRO_KEY } from '@/components/coming-soon/SkipIntroOnReturn' ;
5455import { isIterSoaCollege , isIterSoaEmail , ITER_SOA_ERROR_MESSAGE , isValidGoogleDriveUrl , GENDER_OPTIONS as GENDERS } from '@/lib/validation' ;
5556import { httpErrorMessage , isTransientFailure } from '@/lib/api-errors' ;
@@ -363,12 +364,17 @@ export class Innovision extends Component<Props, State> {
363364 if ( ! REGISTRATIONS_OPEN && ( isOpenRegister || search . get ( 'required' ) === 'register' ) ) {
364365 this . showRegistrationsSoon ( true ) ;
365366 } else if ( isAuthRequired ) {
367+ // Remembered across the Google sign-in round trip, so the student lands on the form once signed in.
368+ if ( search . get ( 'required' ) === 'register' ) sessionStorage . setItem ( 'inv_pending_action' , 'register' ) ;
366369 setTimeout ( ( ) => {
367370 this . openAuth ( 'login' ) ;
368371 this . toast ( 'Please log in to register.' ) ;
369372 } , 1200 ) ;
370373 } else if ( isOpenRegister ) {
371- setTimeout ( ( ) => {
374+ // Waits for the session check, so a signed-in student isn't sent to log in first.
375+ setTimeout ( async ( ) => {
376+ if ( ! this . _authReady . done ) await this . _authReady . p ;
377+ if ( ! this . alive ) return ;
372378 if ( ! this . state . user ) {
373379 sessionStorage . setItem ( 'inv_pending_action' , 'register' ) ;
374380 this . openAuth ( 'login' ) ;
@@ -1843,9 +1849,9 @@ export class Innovision extends Component<Props, State> {
18431849 // @ts -ignore
18441850 newId ( ) { return 'IV26-' + ( Math . floor ( Math . random ( ) * 9000 ) + 1000 ) ; }
18451851 drop ( err , k ) { const e = { ...err } ; delete e [ k ] ; return e ; }
1846- fee ( ) { const n = Math . round ( Number ( this . props . regFee ?? 499 ) ) ; return n > 0 ? n : 499 ; }
1852+ fee ( ) { return REGISTRATION_FEE ; }
18471853 // @ts -ignore
1848- upi ( ) { return String ( this . props . upiId || 'innovision@sbi' ) . trim ( ) ; }
1854+ upi ( ) { return UPI_ID ; }
18491855 speed ( ) { const n = Number ( this . props . riftSpeed ?? 1 ) ; return n > 0 ? n : 1 ; }
18501856 // @ts -ignore
18511857 fmtSize ( n ) { return n < 1048576 ? Math . max ( 1 , Math . round ( n / 1024 ) ) + ' KB' : ( n / 1048576 ) . toFixed ( 1 ) + ' MB' ; }
@@ -2131,7 +2137,7 @@ export class Innovision extends Component<Props, State> {
21312137 const isImg = / ^ i m a g e \/ / . test ( f . type ) ;
21322138
21332139 if ( ! isImg ) {
2134- this . fail ( { [ ek ] : 'Upload the screenshot as an image (JPG or PNG ).' } ) ;
2140+ this . fail ( { [ ek ] : 'Upload the screenshot as an image (JPG, PNG or WebP ).' } ) ;
21352141 return ;
21362142 }
21372143
@@ -2307,14 +2313,20 @@ export class Innovision extends Component<Props, State> {
23072313 const gender = v ( 'gender' ) ;
23082314
23092315 if ( name . length < 2 ) err . name = 'Tell us your full name.' ;
2316+ else if ( name . length > 100 ) err . name = 'Keep your name under 100 characters.' ;
23102317 if ( ! GENDERS . some ( ( g ) => g . value === gender ) ) err . gender = 'Select your gender.' ;
23112318 if ( ! isInternal && college . length < 3 ) {
23122319 err . college = 'Which college are you from?' ;
2320+ } else if ( ! isInternal && college . length > 200 ) {
2321+ err . college = 'Keep the college name under 200 characters.' ;
23132322 } else if ( ! isInternal && ( isIterSoaCollege ( college ) || isIterSoaEmail ( email ) ) ) {
23142323 err . college = ITER_SOA_ERROR_MESSAGE ;
23152324 }
23162325 if ( ! / ^ [ 6 - 9 ] \d { 9 } $ / . test ( phone ) ) err . phone = 'Use a 10-digit Indian mobile number.' ;
23172326 if ( isInternal && ! enrollment_no ) err . enrollment_no = 'Enter your NIT Rourkela Roll / Enrollment number.' ;
2327+ else if ( enrollment_no . length > 50 || ( enrollment_no && ! / ^ [ A - Z a - z 0 - 9 / _ - ] + $ / . test ( enrollment_no ) ) ) {
2328+ err . enrollment_no = 'Use only letters, numbers, spaces and / _ - (up to 50 characters).' ;
2329+ }
23182330
23192331 if ( this . fail ( err ) ) return ;
23202332 this . reg = { name, college, email, phone, enrollment_no, gender } ;
@@ -2360,18 +2372,31 @@ export class Innovision extends Component<Props, State> {
23602372 const saveRes = await createRegistration ( regPayload ) ;
23612373 const savedReg = saveRes . registration || ( saveRes . success ? regPayload : null ) ;
23622374 if ( ! savedReg ) {
2363- this . toast ( saveRes . error || 'Registration failed' ) ;
2364- if ( isTransientFailure ( saveRes . status ) ) {
2365- // Rate limited, server error or offline: stay on this step with the UTR and screenshot intact, so the
2366- // student can submit again by hand once they have waited. Nothing is resubmitted automatically.
2367- this . setState ( { busyLbl : '' , err : { form : saveRes . error || 'Registration failed' } } ) ;
2375+ const msg = saveRes . error || 'Registration failed' ;
2376+ this . toast ( msg ) ;
2377+ const field = saveRes . field || '' ;
2378+ const detailsField = [ 'name' , 'college' , 'phone' , 'gender' , 'enrollment_no' ] . includes ( field ) ;
2379+ if ( isTransientFailure ( saveRes . status ) || ! ( detailsField || field === 'payfile' || field === 'utr' ) ) {
2380+ // Rate limited, server error, offline, closed or signed out: stay on this step with the UTR and screenshot
2381+ // intact, so the student can submit again by hand. Nothing is resubmitted automatically.
2382+ this . setState ( { busyLbl : '' , err : { form : msg } } ) ;
23682383 return ;
23692384 }
2370- this . setState ( {
2371- busyLbl : '' ,
2372- step : 0 ,
2373- err : { college : saveRes . error || 'Registration failed' } ,
2374- } ) ;
2385+ if ( field === 'payfile' ) {
2386+ // The stored screenshot can't be used: pick it again (a fresh upload gets a fresh storage path).
2387+ this . removeFile ( 'pay' ) ;
2388+ this . setState ( { busyLbl : '' , err : { payfile : msg } } ) ;
2389+ return ;
2390+ }
2391+ if ( field === 'utr' ) {
2392+ this . setState ( { busyLbl : '' } ) ;
2393+ this . fail ( { utr : msg } ) ;
2394+ return ;
2395+ }
2396+ // Details from the first step (name, college, phone, gender, enrollment number): go back to fix them.
2397+ this . setState ( { busyLbl : '' } ) ;
2398+ this . toStep ( 0 ) ;
2399+ this . setState ( { err : { [ field ] : msg } } ) ;
23752400 return ;
23762401 }
23772402
@@ -2471,7 +2496,7 @@ export class Innovision extends Component<Props, State> {
24712496 err : E , bc, inv, genderOptions : GENDERS ,
24722497 upPay : up ( 'pay' , 'payfile' , 'Drop the screenshot here or browse (Max 1MB)' ) ,
24732498 fee, upi, copyUpi : this . copyUpi , copyLbl : s . copied ? 'COPIED' : 'COPY' ,
2474- upiLink : 'upi://pay?pa=' + encodeURIComponent ( upi ) + '&pn=' + encodeURIComponent ( 'Innovision NIT Rourkela' ) + '&am=' + fee + '&cu=INR&tn=' + encodeURIComponent ( 'Innovision 2026 registration' ) ,
2499+ upiLink : UPI_PAY_LINK , upiQr : UPI_QR_SRC ,
24752500 upiAppD : s . narrow ? 'inline-flex' : 'none' ,
24762501 authSubmit : this . authSubmit , clearErr : this . clearErr , switchMode : this . switchMode , closeAuthH : ( ) => this . closeAuth ( ) ,
24772502 showSwitch : am !== 'pass' && ! reg && ! ! s . user ,
0 commit comments