@@ -308,6 +308,23 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
308308 }
309309 } ;
310310
311+ // Fetch with automatic retry on 503/5xx (HF Space cold-start / rate-limit)
312+ const fetchWithRetry = async ( url : string , options : RequestInit , retries = 3 ) : Promise < Response > => {
313+ for ( let attempt = 1 ; attempt <= retries ; attempt ++ ) {
314+ const response = await fetch ( url , options ) ;
315+ // If 5xx and not last attempt, wait and retry
316+ if ( response . status >= 500 && attempt < retries ) {
317+ const delay = attempt * 1200 ; // 1.2s, 2.4s
318+ setErrorMsg ( `Server is warming up… retrying (${ attempt } /${ retries - 1 } )` ) ;
319+ await new Promise ( res => setTimeout ( res , delay ) ) ;
320+ continue ;
321+ }
322+ return response ;
323+ }
324+ // Should never reach here, but TypeScript needs it
325+ return fetch ( url , options ) ;
326+ } ;
327+
311328 const handleTraditionalSubmit = async ( e : React . FormEvent ) => {
312329 e . preventDefault ( ) ;
313330 setErrorMsg ( null ) ;
@@ -338,7 +355,7 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
338355
339356 try {
340357 if ( authMode === 'signup' ) {
341- const response = await fetch ( `${ apiUrl } /api/v1/auth/signup` , {
358+ const response = await fetchWithRetry ( `${ apiUrl } /api/v1/auth/signup` , {
342359 method : 'POST' ,
343360 headers : { 'Content-Type' : 'application/json' } ,
344361 body : JSON . stringify ( {
@@ -349,6 +366,12 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
349366 } )
350367 } ) ;
351368
369+ // Guard against non-JSON 503 HTML responses from HF
370+ const contentType = response . headers . get ( 'content-type' ) || '' ;
371+ if ( ! contentType . includes ( 'application/json' ) ) {
372+ throw new Error ( "The server is temporarily unavailable. Please try again in a few seconds." ) ;
373+ }
374+
352375 const data = await response . json ( ) ;
353376 if ( ! response . ok ) {
354377 throw new Error ( data . detail || "Sign up failed." ) ;
@@ -370,8 +393,8 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
370393 } , 1000 ) ;
371394 }
372395 } else {
373- // Sign In
374- const response = await fetch ( `${ apiUrl } /api/v1/auth/login` , {
396+ // Sign In — with retry on 503
397+ const response = await fetchWithRetry ( `${ apiUrl } /api/v1/auth/login` , {
375398 method : 'POST' ,
376399 headers : { 'Content-Type' : 'application/json' } ,
377400 body : JSON . stringify ( {
@@ -380,6 +403,12 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
380403 } )
381404 } ) ;
382405
406+ // Guard against non-JSON 503 HTML responses from HF
407+ const contentType = response . headers . get ( 'content-type' ) || '' ;
408+ if ( ! contentType . includes ( 'application/json' ) ) {
409+ throw new Error ( "The server is temporarily unavailable. Please wait a moment and try again." ) ;
410+ }
411+
383412 const data = await response . json ( ) ;
384413 if ( ! response . ok ) {
385414 throw new Error ( data . detail || "Invalid email or password." ) ;
@@ -393,6 +422,7 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
393422 setSuccessMsg ( "Your account is not verified yet. A new verification code has been sent to your email." ) ;
394423 setPassword ( '' ) ;
395424 } else if ( data . access_token ) {
425+ setErrorMsg ( null ) ;
396426 onAuthSuccess ( data . access_token ) ;
397427 setShowAuthModal ( false ) ;
398428 }
@@ -417,7 +447,7 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
417447 setLoading ( true ) ;
418448
419449 try {
420- const response = await fetch ( `${ apiUrl } /api/v1/auth/verify` , {
450+ const response = await fetchWithRetry ( `${ apiUrl } /api/v1/auth/verify` , {
421451 method : 'POST' ,
422452 headers : { 'Content-Type' : 'application/json' } ,
423453 body : JSON . stringify ( {
@@ -426,6 +456,11 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
426456 } )
427457 } ) ;
428458
459+ const contentType = response . headers . get ( 'content-type' ) || '' ;
460+ if ( ! contentType . includes ( 'application/json' ) ) {
461+ throw new Error ( "The server is temporarily unavailable. Please try again in a few seconds." ) ;
462+ }
463+
429464 const data = await response . json ( ) ;
430465 if ( ! response . ok ) {
431466 throw new Error ( data . detail || "Verification failed." ) ;
@@ -451,14 +486,19 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
451486 setSuccessMsg ( null ) ;
452487
453488 try {
454- const response = await fetch ( `${ apiUrl } /api/v1/auth/resend-code` , {
489+ const response = await fetchWithRetry ( `${ apiUrl } /api/v1/auth/resend-code` , {
455490 method : 'POST' ,
456491 headers : { 'Content-Type' : 'application/json' } ,
457492 body : JSON . stringify ( {
458493 email : emailToVerify
459494 } )
460495 } ) ;
461496
497+ const contentType = response . headers . get ( 'content-type' ) || '' ;
498+ if ( ! contentType . includes ( 'application/json' ) ) {
499+ throw new Error ( "The server is temporarily unavailable. Please try again in a few seconds." ) ;
500+ }
501+
462502 const data = await response . json ( ) ;
463503 if ( ! response . ok ) {
464504 throw new Error ( data . detail || "Failed to resend code." ) ;
0 commit comments