feat(auth): enhance Google sign-in flow with loading state and error handling
This commit is contained in:
parent
008f8a3cca
commit
0f3ee5d537
@ -27,9 +27,6 @@ ADDR=:8082
|
||||
AUTH_ENABLED=false
|
||||
FIREBASE_PROJECT_ID=
|
||||
FIREBASE_CREDENTIALS_FILE=/secrets/firebase_credentials.json
|
||||
# Use one of the following to supply Firebase service account:
|
||||
# FIREBASE_CREDENTIALS_JSON=
|
||||
# FIREBASE_CREDENTIALS_FILE=/path/to/firebase.json
|
||||
|
||||
# Frontend Firebase (for Google sign-in)
|
||||
VITE_AUTH_ENABLED=true
|
||||
|
||||
@ -14,6 +14,7 @@ type AuthContextShape = {
|
||||
idToken: string | null;
|
||||
backendClaims: FirebaseAuthResponse | null;
|
||||
verifying: boolean;
|
||||
signingIn: boolean;
|
||||
error: string | null;
|
||||
signInWithGoogle: () => Promise<void>;
|
||||
signOut: () => Promise<void>;
|
||||
@ -29,6 +30,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [idToken, setIdToken] = React.useState<string | null>(null);
|
||||
const [backendClaims, setBackendClaims] = React.useState<FirebaseAuthResponse | null>(null);
|
||||
const [verifying, setVerifying] = React.useState(false);
|
||||
const [signingIn, setSigningIn] = React.useState(false);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
// Subscribe to Firebase auth state
|
||||
@ -67,10 +69,28 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
setError("Auth disabled");
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
setSigningIn(true);
|
||||
try {
|
||||
const app = getFirebaseApp();
|
||||
const auth = getAuth(app);
|
||||
const provider = new GoogleAuthProvider();
|
||||
try {
|
||||
await signInWithPopup(auth, provider);
|
||||
} catch (err: unknown) {
|
||||
// Fallback to redirect when popups are blocked.
|
||||
const code = (err as { code?: string }).code || "";
|
||||
if (code === "auth/popup-blocked" || code === "auth/operation-not-supported-in-this-environment") {
|
||||
await import("firebase/auth").then(({ signInWithRedirect }) => signInWithRedirect(auth, provider));
|
||||
} else {
|
||||
const msg = err instanceof Error ? err.message : "Sign-in failed";
|
||||
setError(msg);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setSigningIn(false);
|
||||
}
|
||||
}, [enabled]);
|
||||
|
||||
const signOut = React.useCallback(async () => {
|
||||
@ -106,11 +126,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
idToken,
|
||||
backendClaims,
|
||||
verifying,
|
||||
signingIn,
|
||||
error,
|
||||
signInWithGoogle,
|
||||
signOut,
|
||||
refreshToken,
|
||||
}), [enabled, status, user, idToken, backendClaims, verifying, error, signInWithGoogle, signOut, refreshToken]);
|
||||
}), [enabled, status, user, idToken, backendClaims, verifying, signingIn, error, signInWithGoogle, signOut, refreshToken]);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useAuth } from "../auth/AuthProvider";
|
||||
|
||||
export default function AuthStatus() {
|
||||
const { enabled, status, user, backendClaims, verifying, signInWithGoogle, signOut, error } = useAuth();
|
||||
const { enabled, status, user, backendClaims, verifying, signingIn, signInWithGoogle, signOut, error } = useAuth();
|
||||
|
||||
if (!enabled) {
|
||||
return <div className="auth-chip muted">Auth off</div>;
|
||||
@ -11,9 +11,9 @@ export default function AuthStatus() {
|
||||
}
|
||||
if (!user) {
|
||||
return (
|
||||
<button className="auth-btn" onClick={() => signInWithGoogle().catch(() => {})}>
|
||||
<button className="auth-btn" onClick={() => signInWithGoogle().catch(() => {})} disabled={signingIn}>
|
||||
<span className="auth-icon">G</span>
|
||||
<span>Google でサインイン</span>
|
||||
<span>{signingIn ? "開いています…" : "Google でサインイン"}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user