diff --git a/frontend/apps/artcraft-webapp/src/components/auth/GoogleLoginButton.tsx b/frontend/apps/artcraft-webapp/src/components/auth/GoogleLoginButton.tsx index d54b1fbe25..a5c976c5ea 100644 --- a/frontend/apps/artcraft-webapp/src/components/auth/GoogleLoginButton.tsx +++ b/frontend/apps/artcraft-webapp/src/components/auth/GoogleLoginButton.tsx @@ -8,7 +8,7 @@ import { } from "@storyteller/common"; import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useState } from "react"; +import { useEffect, useState } from "react"; interface GoogleLoginButtonProps { onSuccess: (isNewUser: boolean) => void; @@ -25,6 +25,17 @@ export function GoogleLoginButton({ }: GoogleLoginButtonProps) { const [isLoading, setIsLoading] = useState(false); + // Defer mounting Google's GSI widget until after first paint. Its cross-site + // init stalls for several seconds on mobile Safari (ITP throttles + // accounts.google.com), so mounting it inline blocks the surrounding UI (e.g. + // the signup modal) from appearing. The custom visual button below shows + // instantly; the real Google button hydrates a frame later. + const [mountGsi, setMountGsi] = useState(false); + useEffect(() => { + const raf = requestAnimationFrame(() => setMountGsi(true)); + return () => cancelAnimationFrame(raf); + }, []); + const handleGoogleSuccess = async ( credentialResponse: CredentialResponse, ) => { @@ -75,19 +86,23 @@ export function GoogleLoginButton({ )} - {/* Google's actual button - invisible overlay that receives clicks */} -
- onError("Google login failed")} - size="large" - width={400} - text="continue_with" - /> -
+ {/* Google's actual button - invisible overlay that receives clicks. + Mounted after first paint (see mountGsi) so its slow cross-site init + never blocks the surrounding UI from showing. */} + {mountGsi && ( +
+ onError("Google login failed")} + size="large" + width={400} + text="continue_with" + /> +
+ )} ); }