From 76f9b3b4df07c281210ba994004e34911d80b245 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 15 Mar 2025 13:27:38 +0000 Subject: [PATCH] Update error messages Update error messages to provide more clarity. --- src/contexts/auth/auth.utils.ts | 37 +++++- src/contexts/auth/signIn.ts | 161 ++++++++++++++++--------- src/contexts/auth/signUp.ts | 205 ++++++++++++++++++++++++++------ src/pages/Register.tsx | 87 +++++++++++++- 4 files changed, 393 insertions(+), 97 deletions(-) diff --git a/src/contexts/auth/auth.utils.ts b/src/contexts/auth/auth.utils.ts index 1abb225..a1eeccb 100644 --- a/src/contexts/auth/auth.utils.ts +++ b/src/contexts/auth/auth.utils.ts @@ -17,6 +17,10 @@ export const handleNetworkError = (error: any): string => { return '네트워크 오류가 발생했습니다. 인터넷 연결을 확인하세요.'; } else if (error.message && error.message.includes('json')) { return '서버 응답 형식 오류: 서버가 올바른 JSON 응답을 반환하지 않았습니다.'; + } else if (error.message && error.message.includes('timeout')) { + return '서버 응답 시간 초과: 서버가 응답하지 않습니다.'; + } else if (error.message && error.message.includes('aborted')) { + return '요청이 중단되었습니다.'; } return error.message || '예상치 못한 오류가 발생했습니다.'; @@ -51,6 +55,12 @@ export const parseResponse = async (response: Response) => { error: '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.', status: 404 }; + } else if (response.status >= 200 && response.status < 300) { + return { + success: true, + status: response.status, + message: '서버가 성공 상태를 반환했지만 응답 내용이 없습니다.' + }; } else { return { error: `서버가 빈 응답을 반환했습니다 (상태 코드: ${response.status})`, @@ -80,6 +90,12 @@ export const parseResponse = async (response: Response) => { error: '서버 내부 오류가 발생했습니다.', status: response.status }; + } else if (response.status >= 200 && response.status < 300) { + return { + success: true, + status: response.status, + message: '서버가 성공 상태를 반환했지만 JSON 형식이 아닙니다.' + }; } else { return { error: `응답 형식 오류 (상태 코드: ${response.status}): 서버가 올바른 JSON 응답을 반환하지 않았습니다.`, @@ -134,23 +150,26 @@ export const checkSupabaseConnection = async (): Promise => { } }; -// 서버 연결 상태 검사 +// 서버 연결 상태 검사 (개선 버전) export const verifyServerConnection = async (): Promise<{ connected: boolean; message: string; + statusCode?: number; }> => { try { const start = Date.now(); // Supabase URL 가져오기 - const supabaseUrl = localStorage.getItem('supabase_url') || 'http://a11.ism.kr:8000'; + const supabaseUrl = supabase.auth.url; // 단순 헬스 체크 요청 - const response = await fetch(`${supabaseUrl}/rest/v1/`, { + const response = await fetch(`${supabaseUrl}/`, { method: 'GET', headers: { 'Content-Type': 'application/json' - } + }, + // 연결 확인만을 위한 요청이므로 짧은 타임아웃 설정 + signal: AbortSignal.timeout(3000) }); const elapsed = Date.now() - start; @@ -159,12 +178,14 @@ export const verifyServerConnection = async (): Promise<{ // 401도 서버가 응답했다는 의미이므로 연결 성공으로 간주 return { connected: true, - message: `서버 연결 성공 (응답 시간: ${elapsed}ms)` + message: `서버 연결 성공 (응답 시간: ${elapsed}ms)`, + statusCode: response.status }; } else { return { connected: false, - message: `서버 응답 오류: ${response.status} ${response.statusText}` + message: `서버 응답 오류: ${response.status} ${response.statusText}`, + statusCode: response.status }; } } catch (error: any) { @@ -180,6 +201,10 @@ export const verifyServerConnection = async (): Promise<{ errorMessage = '네트워크 연결 실패'; } else if (error.message.includes('TypeError')) { errorMessage = '네트워크 요청 형식 오류'; + } else if (error.message.includes('timeout')) { + errorMessage = '서버 응답 시간 초과'; + } else if (error.message.includes('aborted')) { + errorMessage = '요청이 중단됨'; } else { errorMessage = error.message; } diff --git a/src/contexts/auth/signIn.ts b/src/contexts/auth/signIn.ts index 027575e..362f132 100644 --- a/src/contexts/auth/signIn.ts +++ b/src/contexts/auth/signIn.ts @@ -27,12 +27,10 @@ export const signIn = async (email: string, password: string) => { showAuthToast('로그인 성공', '환영합니다!'); return { error: null, user: data.user }; } else if (error) { - // 기본 방식 실패 시 커스텀 로그인 시도 - console.warn('기본 로그인 실패, 커스텀 방식 시도:', error.message); - - // JSON 파싱 오류라면 직접 API 호출 시도 + // JSON 파싱 오류인 경우 직접 API 호출 시도 if (error.message.includes('json') || error.message.includes('Unexpected end')) { - // 다음 단계에서 직접 API 호출 + console.warn('기본 로그인 실패, 직접 API 호출 시도:', error.message); + return await signInWithDirectApi(email, password); } else { // 다른 종류의 오류는 그대로 반환 let errorMessage = error.message; @@ -49,38 +47,98 @@ export const signIn = async (email: string, password: string) => { } } catch (basicAuthError) { console.warn('기본 인증 방식 예외 발생:', basicAuthError); - // 기본 로그인 실패 시 아래 커스텀 방식 계속 진행 + // 기본 로그인 실패 시 아래 직접 API 호출 방식 계속 진행 + return await signInWithDirectApi(email, password); } - // 기본 방식 실패 시 직접 API 호출 - // 응답 예외 처리를 위한 래핑 - try { - const response = await fetch(`${supabase.auth.url}/token?grant_type=password`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'apikey': supabase.supabaseKey, - 'Authorization': `Bearer ${supabase.supabaseKey}`, - 'X-Client-Info': 'supabase-js/2.x' - }, - body: JSON.stringify({ email, password }) - }); - - // 응답 상태 확인 및 로깅 - console.log('로그인 응답 상태:', response.status); - - // 응답 처리 - const responseData = await parseResponse(response); - - // 오류 응답 확인 - if (responseData?.error) { - const errorMessage = responseData.error_description || responseData.error; - showAuthToast('로그인 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null }; + // 기본 방식이 성공적으로 완료되지 않았을 경우 직접 API 호출 + return await signInWithDirectApi(email, password); + } catch (error: any) { + console.error('로그인 중 예외 발생:', error); + + // 네트워크 오류 확인 + const errorMessage = handleNetworkError(error); + showAuthToast('로그인 오류', errorMessage, 'destructive'); + + return { error: { message: errorMessage }, user: null }; + } +}; + +// 직접 API 호출을 통한 로그인 시도 (대체 방법) +const signInWithDirectApi = async (email: string, password: string) => { + console.log('직접 API 호출로 로그인 시도'); + + try { + // 로그인 API 엔드포인트 URL과 헤더 준비 + const tokenUrl = `${supabase.auth.url}/token?grant_type=password`; + const headers = { + 'Content-Type': 'application/json', + 'apikey': supabase.supabaseKey, + 'Authorization': `Bearer ${supabase.supabaseKey}`, + 'X-Client-Info': 'supabase-js/2.x' + }; + + console.log('로그인 API 요청 URL:', tokenUrl); + + // 로그인 요청 보내기 + const response = await fetch(tokenUrl, { + method: 'POST', + headers, + body: JSON.stringify({ email, password }) + }); + + // 응답 상태 확인 및 로깅 + console.log('로그인 응답 상태:', response.status); + + // HTTP 상태 코드 확인 + if (response.status === 401) { + showAuthToast('로그인 실패', '이메일 또는 비밀번호가 올바르지 않습니다.', 'destructive'); + return { + error: { message: '인증 실패: 이메일 또는 비밀번호가 올바르지 않습니다.' }, + user: null + }; + } + + if (response.status === 404) { + showAuthToast('로그인 실패', '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.', 'destructive'); + return { + error: { message: '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.' }, + user: null + }; + } + + // 응답 처리 + const responseData = await parseResponse(response); + + // 빈 응답이나 파싱 실패 시 + if (responseData.status && !responseData.success && !responseData.error) { + if (response.status >= 200 && response.status < 300) { + // 성공 상태 코드이지만 응답 내용 없음 + showAuthToast('로그인 상태 확인 필요', '서버가 성공 응답을 보냈지만 내용이 없습니다. 세션 상태를 확인하세요.'); + + // 사용자 세션 확인 시도 + try { + const { data: userData } = await supabase.auth.getUser(); + if (userData.user) { + showAuthToast('로그인 성공', '환영합니다!'); + return { error: null, user: userData.user }; + } + } catch (sessionCheckError) { + console.error('세션 확인 오류:', sessionCheckError); + } } - - // 응답 처리 - if (response.ok && responseData?.access_token) { + } + + // 오류 응답 확인 + if (responseData?.error) { + const errorMessage = responseData.error_description || responseData.error; + showAuthToast('로그인 실패', errorMessage, 'destructive'); + return { error: { message: errorMessage }, user: null }; + } + + // 로그인 성공 응답 처리 + if (response.ok && responseData?.access_token) { + try { // 로그인 성공 시 Supabase 세션 설정 await supabase.auth.setSession({ access_token: responseData.access_token, @@ -95,31 +153,28 @@ export const signIn = async (email: string, password: string) => { showAuthToast('로그인 성공', '환영합니다!'); return { error: null, user: userData.user }; - } else { - // 오류 응답 처리 - console.error('로그인 오류 응답:', responseData); - - const errorMessage = responseData?.error_description || - responseData?.error || - '로그인에 실패했습니다. 이메일과 비밀번호를 확인하세요.'; - - showAuthToast('로그인 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null }; + } catch (sessionError) { + console.error('세션 설정 오류:', sessionError); + showAuthToast('로그인 후처리 오류', '로그인에 성공했지만 세션 설정에 실패했습니다.', 'destructive'); + return { error: { message: '세션 설정 오류' }, user: null }; } - } catch (fetchError) { - console.error('로그인 요청 중 fetch 오류:', fetchError); + } else { + // 오류 응답이나 예상치 못한 응답 형식 처리 + console.error('로그인 오류 응답:', responseData); - const errorMessage = handleNetworkError(fetchError); - showAuthToast('로그인 요청 실패', errorMessage, 'destructive'); + const errorMessage = responseData?.error_description || + responseData?.error || + responseData?.message || + '로그인에 실패했습니다. 이메일과 비밀번호를 확인하세요.'; + showAuthToast('로그인 실패', errorMessage, 'destructive'); return { error: { message: errorMessage }, user: null }; } - } catch (error: any) { - console.error('로그인 중 예외 발생:', error); + } catch (fetchError) { + console.error('로그인 요청 중 fetch 오류:', fetchError); - // 네트워크 오류 확인 - const errorMessage = handleNetworkError(error); - showAuthToast('로그인 오류', errorMessage, 'destructive'); + const errorMessage = handleNetworkError(fetchError); + showAuthToast('로그인 요청 실패', errorMessage, 'destructive'); return { error: { message: errorMessage }, user: null }; } diff --git a/src/contexts/auth/signUp.ts b/src/contexts/auth/signUp.ts index 523fe87..195772a 100644 --- a/src/contexts/auth/signUp.ts +++ b/src/contexts/auth/signUp.ts @@ -6,7 +6,30 @@ export const signUp = async (email: string, password: string, username: string) try { console.log('회원가입 시도:', { email, username }); - // 회원가입 시도 + // 서버 연결 상태 확인 + try { + const response = await fetch(`${supabase.auth.url}/`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + // 연결 확인만을 위한 요청이므로 짧은 타임아웃 설정 + signal: AbortSignal.timeout(5000) + }); + + if (!response.ok && response.status !== 401) { + // 401은 인증 필요 오류로, API 엔드포인트가 존재한다는 의미이므로 정상으로 간주 + console.warn('서버 연결 확인 실패:', response.status, response.statusText); + showAuthToast('서버 연결 오류', `서버가 응답하지 않거나 오류 상태입니다 (${response.status}). 관리자에게 문의하세요.`, 'destructive'); + return { error: { message: `서버 연결 오류 (${response.status})` }, user: null }; + } + } catch (connectionError: any) { + console.error('서버 연결 확인 중 오류:', connectionError); + showAuthToast('서버 연결 실패', '서버에 연결할 수 없습니다. 네트워크 상태 또는 서버 주소를 확인하세요.', 'destructive'); + return { error: connectionError, user: null }; + } + + // 회원가입 시도 (기본 방식) try { const { data, error } = await supabase.auth.signUp({ email, @@ -33,7 +56,10 @@ export const signUp = async (email: string, password: string, username: string) } else if (error.message.includes('Unable to validate')) { errorMessage = '서버 연결 문제: 이메일 검증에 실패했습니다.'; } else if (error.message.includes('json')) { - errorMessage = '서버 응답 형식 오류: 올바른 JSON 응답이 없습니다.'; + errorMessage = '서버 응답 형식 오류: 올바른 JSON 응답이 없습니다. 직접 API 호출을 시도합니다.'; + + // JSON 파싱 오류 발생 시 직접 API 호출 시도 + return await signUpWithDirectApi(email, password, username); } else if (error.message.includes('fetch failed')) { errorMessage = '서버 연결에 실패했습니다. 네트워크 연결 또는 서버 상태를 확인하세요.'; } else if (error.message.includes('Failed to fetch')) { @@ -57,44 +83,12 @@ export const signUp = async (email: string, password: string, username: string) } catch (signUpError: any) { console.error('기본 회원가입 방식 실패:', signUpError); - // 특정 오류 패턴을 확인하여 대체 방법 시도 + // JSON 파싱 오류 또는 기타 예외 시 직접 API 호출 시도 if (signUpError.message && ( signUpError.message.includes('json') || signUpError.message.includes('Unexpected end') )) { - console.log('JSON 파싱 오류 발생, 직접 API 호출 시도'); - - // 직접 API 호출 시도 - const response = await fetch(`${supabase.auth.url}/signup`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'apikey': supabase.supabaseKey, - 'Authorization': `Bearer ${supabase.supabaseKey}`, - }, - body: JSON.stringify({ - email, - password, - data: { username }, - }) - }); - - // 응답 처리 - const result = await parseResponse(response); - - if (result.error) { - showAuthToast('회원가입 실패', result.error, 'destructive'); - return { error: { message: result.error }, user: null }; - } - - if (result.id) { - showAuthToast('회원가입 성공', '이메일 확인 후 로그인해주세요.'); - return { error: null, user: result }; - } - - // 알 수 없는 응답 처리 - showAuthToast('회원가입 결과 불명확', '로그인을 시도해보세요.', 'destructive'); - return { error: { message: '서버 응답을 해석할 수 없습니다.' }, user: null }; + return await signUpWithDirectApi(email, password, username); } // 그 외 오류는 그대로 던짐 @@ -110,3 +104,142 @@ export const signUp = async (email: string, password: string, username: string) return { error, user: null }; } }; + +// 직접 API 호출을 통한 회원가입 시도 (대체 방법) +const signUpWithDirectApi = async (email: string, password: string, username: string) => { + console.log('직접 API 호출로 회원가입 시도'); + + try { + // 요청 URL과 헤더 준비 + const signUpUrl = `${supabase.auth.url}/signup`; + const headers = { + 'Content-Type': 'application/json', + 'apikey': supabase.supabaseKey, + 'Authorization': `Bearer ${supabase.supabaseKey}`, + }; + + console.log('회원가입 API 요청 URL:', signUpUrl); + + // 회원가입 요청 보내기 + const response = await fetch(signUpUrl, { + method: 'POST', + headers, + body: JSON.stringify({ + email, + password, + data: { username } + }) + }); + + console.log('회원가입 API 응답 상태:', response.status); + + // HTTP 상태 코드 확인 + if (response.status === 401) { + showAuthToast('회원가입 실패', '인증 오류: API 키가 올바르지 않거나 서버 설정 문제가 있습니다.', 'destructive'); + return { + error: { message: '인증 오류: API 키가 올바르지 않거나 서버 설정 문제가 있습니다.' }, + user: null + }; + } + + if (response.status === 404) { + showAuthToast('회원가입 실패', '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.', 'destructive'); + return { + error: { message: '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.' }, + user: null + }; + } + + if (response.status === 500) { + showAuthToast('회원가입 실패', '서버 내부 오류가 발생했습니다.', 'destructive'); + return { + error: { message: '서버 내부 오류가 발생했습니다.' }, + user: null + }; + } + + // 응답 본문이 비어 있는지 확인 + const responseText = await response.text(); + if (!responseText || responseText.trim() === '') { + if (response.ok) { + // 응답이 비어 있지만 상태 코드가 성공인 경우 + showAuthToast('회원가입 가능성 있음', '서버가 빈 응답을 반환했지만 상태 코드는 성공입니다. 로그인을 시도해보세요.'); + return { + error: null, + user: { email, username } + }; + } else { + // 응답이 비어 있고 상태 코드가 실패인 경우 + showAuthToast('회원가입 실패', `서버가 빈 응답을 반환했습니다 (${response.status}). 서버 설정을 확인하세요.`, 'destructive'); + return { + error: { + message: `서버가 빈 응답을 반환했습니다 (${response.status}). 서버 설정을 확인하세요.` + }, + user: null + }; + } + } + + // 응답 JSON 파싱 시도 + let result; + try { + result = JSON.parse(responseText); + } catch (jsonError) { + console.error('JSON 파싱 오류:', jsonError, '원본 텍스트:', responseText); + + if (response.ok) { + // 파싱 오류지만 상태 코드가 성공인 경우 + showAuthToast('회원가입 가능성 있음', '서버 응답을 해석할 수 없지만 상태 코드는 성공입니다. 로그인을 시도해보세요.'); + return { + error: null, + user: { email, username } + }; + } else { + // 파싱 오류이고 상태 코드도 실패인 경우 + showAuthToast('회원가입 실패', `서버 응답을 해석할 수 없습니다 (${response.status}). 서버 설정을 확인하세요.`, 'destructive'); + return { + error: { + message: `서버 응답을 해석할 수 없습니다 (${response.status}). 서버 설정을 확인하세요.` + }, + user: null + }; + } + } + + // 응답 분석 및 결과 반환 + if (result.error) { + showAuthToast('회원가입 실패', result.error_description || result.error || '회원가입에 실패했습니다.', 'destructive'); + return { + error: { message: result.error_description || result.error }, + user: null + }; + } + + if (result.id || result.user || result.data?.user) { + showAuthToast('회원가입 성공', '이메일 확인 후 로그인해주세요.'); + return { + error: null, + user: result.user || result.data?.user || { id: result.id, email, username } + }; + } + + // 알 수 없는 응답 형식 + console.warn('알 수 없는 회원가입 API 응답 형식:', result); + showAuthToast('회원가입 결과 불명확', '로그인을 시도해보세요.', 'destructive'); + return { + error: { message: '서버 응답을 해석할 수 없습니다.' }, + user: null + }; + } catch (error: any) { + console.error('직접 API 호출 회원가입 중 오류:', error); + + // 네트워크 오류 확인 + const errorMessage = handleNetworkError(error); + + showAuthToast('회원가입 API 호출 실패', errorMessage, 'destructive'); + return { + error: { message: errorMessage }, + user: null + }; + } +}; diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 7261f4f..a6873a9 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -4,11 +4,13 @@ import { Link, useNavigate } from "react-router-dom"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { ArrowRight, Mail, KeyRound, User, Eye, EyeOff } from "lucide-react"; +import { ArrowRight, Mail, KeyRound, User, Eye, EyeOff, RefreshCcw } from "lucide-react"; import { useToast } from "@/hooks/useToast.wrapper"; import { useAuth } from "@/contexts/auth"; +import { verifyServerConnection } from "@/contexts/auth/auth.utils"; import TestConnectionSection from "@/components/auth/TestConnectionSection"; import SupabaseConnectionStatus from "@/components/auth/SupabaseConnectionStatus"; +import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; const Register = () => { const [username, setUsername] = useState(""); @@ -19,6 +21,15 @@ const Register = () => { const [isLoading, setIsLoading] = useState(false); const [registerError, setRegisterError] = useState(null); const [testResults, setTestResults] = useState(null); + const [serverStatus, setServerStatus] = useState<{ + checked: boolean; + connected: boolean; + message: string; + }>({ + checked: false, + connected: false, + message: "서버 연결 상태를 확인 중입니다..." + }); const { toast } = useToast(); const navigate = useNavigate(); const { signUp, user } = useAuth(); @@ -30,10 +41,61 @@ const Register = () => { } }, [user, navigate]); + // 서버 연결 상태 확인 + useEffect(() => { + checkServerConnection(); + }, []); + + // 서버 연결 상태 확인 함수 + const checkServerConnection = async () => { + try { + const status = await verifyServerConnection(); + setServerStatus({ + checked: true, + connected: status.connected, + message: status.message + }); + + // 연결 실패 시 토스트 표시 + if (!status.connected) { + toast({ + title: "서버 연결 문제", + description: status.message, + variant: "destructive" + }); + } + } catch (error: any) { + setServerStatus({ + checked: true, + connected: false, + message: `연결 확인 중 오류: ${error.message || '알 수 없는 오류'}` + }); + } + }; + const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); setRegisterError(null); + // 서버 연결 상태 재확인 + if (!serverStatus.connected) { + const currentStatus = await verifyServerConnection(); + setServerStatus({ + checked: true, + connected: currentStatus.connected, + message: currentStatus.message + }); + + if (!currentStatus.connected) { + toast({ + title: "서버 연결 실패", + description: "서버에 연결할 수 없습니다. 네트워크 또는 서버 상태를 확인하세요.", + variant: "destructive" + }); + return; + } + } + if (!username || !email || !password || !confirmPassword) { toast({ title: "입력 오류", @@ -79,6 +141,27 @@ const Register = () => {

새 계정을 만들고 재정 관리를 시작하세요

+ {/* 서버 연결 상태 알림 */} + {serverStatus.checked && !serverStatus.connected && ( + + + 서버 연결 문제 + + + + {serverStatus.message} +

회원가입을 시도하기 전에 서버 연결 문제를 해결해주세요.

+
+
+ )} +
@@ -158,7 +241,7 @@ const Register = () => {