fix(#411): remediate frontend review findings — wire fetchWithRetry, fix error handling
- Wire fetchWithRetry into login page config fetch (was dead code) - Remove duplicate ERROR_CODE_MESSAGES, use parseAuthError from auth-errors.ts - Fix OAuth sign-in fire-and-forget: add .catch() with PDA error + loading reset - Fix credential login catch: use parseAuthError for better error messages - Add user feedback when auth config fetch fails (was silent degradation) - Fix sign-out failure: use logAuthError and set authError state - Enable fetchWithRetry production logging for retry visibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,8 @@ import { Loader2 } from "lucide-react";
|
||||
import type { AuthConfigResponse, AuthProviderConfig } from "@mosaic/shared";
|
||||
import { API_BASE_URL } from "@/lib/config";
|
||||
import { signIn } from "@/lib/auth-client";
|
||||
import { fetchWithRetry } from "@/lib/auth/fetch-with-retry";
|
||||
import { parseAuthError } from "@/lib/auth/auth-errors";
|
||||
import { OAuthButton } from "@/components/auth/OAuthButton";
|
||||
import { LoginForm } from "@/components/auth/LoginForm";
|
||||
import { AuthDivider } from "@/components/auth/AuthDivider";
|
||||
@@ -17,22 +19,6 @@ const EMAIL_ONLY_CONFIG: AuthConfigResponse = {
|
||||
providers: [{ id: "email", name: "Email", type: "credentials" }],
|
||||
};
|
||||
|
||||
/** Maps URL error codes to PDA-friendly messages (no alarming language). */
|
||||
const ERROR_CODE_MESSAGES: Record<string, string> = {
|
||||
access_denied: "Authentication paused. Please try again when ready.",
|
||||
invalid_credentials: "The email and password combination wasn't recognized.",
|
||||
server_error: "The service is taking a break. Please try again in a moment.",
|
||||
network_error: "Unable to connect. Check your network and try again.",
|
||||
rate_limited: "You've tried a few times. Take a moment and try again shortly.",
|
||||
session_expired: "Your session ended. Please sign in again when ready.",
|
||||
};
|
||||
|
||||
const DEFAULT_ERROR_MESSAGE = "Authentication didn't complete. Please try again when ready.";
|
||||
|
||||
function mapErrorCodeToMessage(code: string): string {
|
||||
return ERROR_CODE_MESSAGES[code] ?? DEFAULT_ERROR_MESSAGE;
|
||||
}
|
||||
|
||||
export default function LoginPage(): ReactElement {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -47,7 +33,8 @@ export default function LoginPage(): ReactElement {
|
||||
useEffect(() => {
|
||||
const errorCode = searchParams.get("error");
|
||||
if (errorCode) {
|
||||
setUrlError(mapErrorCodeToMessage(errorCode));
|
||||
const parsed = parseAuthError(errorCode);
|
||||
setUrlError(parsed.message);
|
||||
// Clean up the URL by removing the error param without triggering navigation
|
||||
const nextParams = new URLSearchParams(searchParams.toString());
|
||||
nextParams.delete("error");
|
||||
@@ -61,7 +48,7 @@ export default function LoginPage(): ReactElement {
|
||||
|
||||
async function fetchConfig(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/config`);
|
||||
const response = await fetchWithRetry(`${API_BASE_URL}/auth/config`);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch auth config");
|
||||
}
|
||||
@@ -69,9 +56,13 @@ export default function LoginPage(): ReactElement {
|
||||
if (!cancelled) {
|
||||
setConfig(data);
|
||||
}
|
||||
} catch {
|
||||
} catch (err: unknown) {
|
||||
if (!cancelled) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error("[Auth] Failed to load auth config:", err);
|
||||
}
|
||||
setConfig(EMAIL_ONLY_CONFIG);
|
||||
setUrlError("Some sign-in options may be temporarily unavailable.");
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
@@ -98,7 +89,14 @@ export default function LoginPage(): ReactElement {
|
||||
const handleOAuthLogin = useCallback((providerId: string): void => {
|
||||
setOauthLoading(providerId);
|
||||
setError(null);
|
||||
void signIn.oauth2({ providerId, callbackURL: "/" });
|
||||
signIn.oauth2({ providerId, callbackURL: "/" }).catch((err: unknown) => {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error(`[Auth] OAuth sign-in initiation failed for ${providerId}:`, message);
|
||||
}
|
||||
setError("Unable to connect to the sign-in provider. Please try again in a moment.");
|
||||
setOauthLoading(null);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleCredentialsLogin = useCallback(
|
||||
@@ -118,8 +116,12 @@ export default function LoginPage(): ReactElement {
|
||||
} else {
|
||||
router.push("/tasks");
|
||||
}
|
||||
} catch {
|
||||
setError("Something went wrong. Please try again in a moment.");
|
||||
} catch (err: unknown) {
|
||||
const parsed = parseAuthError(err);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error("[Auth] Credentials sign-in failed:", err);
|
||||
}
|
||||
setError(parsed.message);
|
||||
} finally {
|
||||
setCredentialsLoading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user