diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx new file mode 100644 index 0000000..30a86fd --- /dev/null +++ b/src/components/auth/LoginForm.tsx @@ -0,0 +1,99 @@ + +import React from "react"; +import { Link } 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, Eye, EyeOff } from "lucide-react"; + +interface LoginFormProps { + email: string; + setEmail: (email: string) => void; + password: string; + setPassword: (password: string) => void; + showPassword: boolean; + setShowPassword: (show: boolean) => void; + isLoading: boolean; + loginError: string | null; + handleLogin: (e: React.FormEvent) => Promise; +} + +const LoginForm: React.FC = ({ + email, + setEmail, + password, + setPassword, + showPassword, + setShowPassword, + isLoading, + loginError, + handleLogin +}) => { + return ( +
+
+
+
+ +
+ + setEmail(e.target.value)} + className="pl-10 neuro-pressed" + /> +
+
+ +
+ +
+ + setPassword(e.target.value)} + className="pl-10 neuro-pressed" + /> + +
+
+ + {loginError && ( +
+

{loginError}

+
+ )} + +
+ + 비밀번호를 잊으셨나요? + +
+ + +
+
+
+ ); +}; + +export default LoginForm; diff --git a/src/components/auth/SupabaseConnectionStatus.tsx b/src/components/auth/SupabaseConnectionStatus.tsx new file mode 100644 index 0000000..6921395 --- /dev/null +++ b/src/components/auth/SupabaseConnectionStatus.tsx @@ -0,0 +1,74 @@ + +import React from "react"; +import { Link } from "react-router-dom"; +import { ArrowRight, Shield } from "lucide-react"; + +interface TestResultProps { + testResults: { + url?: string; + usingProxy?: boolean; + proxyUrl?: string; + client?: boolean; + restApi?: boolean; + auth?: boolean; + database?: boolean; + errors: string[]; + } | null; +} + +const SupabaseConnectionStatus: React.FC = ({ testResults }) => { + if (!testResults) { + return null; + } + + return ( +
+ 고급 진단 정보 +
+
+

Supabase URL:

+

{testResults?.url || '알 수 없음'}

+
+ {testResults?.usingProxy && ( +
+

CORS 프록시:

+
+ +

활성화됨 - {testResults.proxyUrl}

+
+
+ )} +
+

클라이언트 초기화:

+

{testResults?.client ? '성공' : '실패'}

+
+
+

브라우저 정보:

+

{navigator.userAgent}

+
+
+

앱 버전:

+

1.0.0

+
+ {testResults && ( +
+

REST API: {testResults.restApi ? '✅' : '❌'}

+

인증: {testResults.auth ? '✅' : '❌'}

+

데이터베이스: {testResults.database ? '✅' : '❌'}

+
+ )} +
+ + Supabase 설정 변경 + + +
+
+
+ ); +}; + +export default SupabaseConnectionStatus; diff --git a/src/components/auth/TestConnectionSection.tsx b/src/components/auth/TestConnectionSection.tsx new file mode 100644 index 0000000..f34735f --- /dev/null +++ b/src/components/auth/TestConnectionSection.tsx @@ -0,0 +1,78 @@ + +import React, { useState } from "react"; +import { RefreshCw } from "lucide-react"; +import { testSupabaseConnection } from "@/lib/supabase"; +import { useToast } from "@/hooks/useToast.wrapper"; + +interface TestConnectionSectionProps { + setLoginError: (error: string | null) => void; + setTestResults: (results: any) => void; +} + +const TestConnectionSection: React.FC = ({ + setLoginError, + setTestResults +}) => { + const [testLoading, setTestLoading] = useState(false); + const { toast } = useToast(); + + const runConnectionTest = async () => { + setTestLoading(true); + setLoginError(null); + try { + const results = await testSupabaseConnection(); + setTestResults(results); + + if (results.errors.length === 0) { + toast({ + title: "연결 테스트 성공", + description: "Supabase 서버 연결이 정상입니다.", + }); + } else { + toast({ + title: "연결 테스트 실패", + description: `오류 발생: ${results.errors[0]}`, + variant: "destructive" + }); + } + } catch (error: any) { + console.error("연결 테스트 중 오류:", error); + setLoginError(error.message || "테스트 실행 중 예상치 못한 오류가 발생했습니다."); + + toast({ + title: "연결 테스트 오류", + description: "테스트 실행 중 예상치 못한 오류가 발생했습니다.", + variant: "destructive" + }); + } finally { + setTestLoading(false); + } + }; + + return ( +
+

Supabase 연결 진단

+
+ +
+
+ ); +}; + +export default TestConnectionSection; diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 219517c..28c4403 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,4 +1,2 @@ -import { AuthProvider, useAuth } from './auth'; - -export { AuthProvider, useAuth }; +export { AuthProvider, useAuth } from '@/contexts/auth'; diff --git a/src/hooks/useLogin.ts b/src/hooks/useLogin.ts new file mode 100644 index 0000000..69249b3 --- /dev/null +++ b/src/hooks/useLogin.ts @@ -0,0 +1,87 @@ + +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useToast } from "@/hooks/useToast.wrapper"; +import { useAuth } from "@/contexts/auth"; + +export function useLogin() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [loginError, setLoginError] = useState(null); + const navigate = useNavigate(); + const { toast } = useToast(); + const { signIn } = useAuth(); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setLoginError(null); + + if (!email || !password) { + toast({ + title: "입력 오류", + description: "이메일과 비밀번호를 모두 입력해주세요.", + variant: "destructive" + }); + return; + } + + setIsLoading(true); + + try { + const { error } = await signIn(email, password); + + if (error) { + console.error("로그인 실패:", error); + let errorMessage = "로그인에 실패했습니다."; + + // Supabase 오류 메시지 처리 + if (error.message) { + if (error.message.includes("Invalid login credentials")) { + errorMessage = "이메일 또는 비밀번호가 올바르지 않습니다."; + } else if (error.message.includes("Email not confirmed")) { + errorMessage = "이메일 인증이 완료되지 않았습니다. 이메일을 확인해주세요."; + } else { + errorMessage = `오류: ${error.message}`; + } + } + + setLoginError(errorMessage); + + toast({ + title: "로그인 실패", + description: errorMessage, + variant: "destructive" + }); + } else { + navigate("/"); + } + } catch (err: any) { + console.error("로그인 과정에서 예외 발생:", err); + const errorMessage = err.message || "알 수 없는 오류가 발생했습니다."; + setLoginError(errorMessage); + + toast({ + title: "로그인 오류", + description: errorMessage, + variant: "destructive" + }); + } finally { + setIsLoading(false); + } + }; + + return { + email, + setEmail, + password, + setPassword, + showPassword, + setShowPassword, + isLoading, + loginError, + setLoginError, + handleLogin + }; +} diff --git a/src/lib/supabase/index.ts b/src/lib/supabase/index.ts index 58284df..48d83f8 100644 --- a/src/lib/supabase/index.ts +++ b/src/lib/supabase/index.ts @@ -1,11 +1,9 @@ -// 메인 내보내기 파일 +import { supabase, isValidUrl } from './client'; +import { testSupabaseConnection } from './tests'; -// Supabase 클라이언트 내보내기 -export { supabase } from './client'; - -// 설정 관련 유틸리티 내보내기 -export { configureSupabase } from './config'; - -// 테스트 도구 내보내기 -export { testSupabaseLogin, testSupabaseConnection } from './tests'; +export { + supabase, + isValidUrl, + testSupabaseConnection +}; diff --git a/src/lib/supabase/tests/index.ts b/src/lib/supabase/tests/index.ts index 75d1643..1af806f 100644 --- a/src/lib/supabase/tests/index.ts +++ b/src/lib/supabase/tests/index.ts @@ -1,96 +1,88 @@ +import { supabase } from '../client'; import { testAuthService } from './authTests'; import { testRestApi } from './apiTests'; -import { testDatabaseConnection } from './databaseTests'; -import { ConnectionTestResult, TestDebugInfo } from './types'; -import { supabase } from '../client'; -import { getOriginalSupabaseUrl, getSupabaseUrl, isCorsProxyEnabled, getProxyType } from '../config'; +import { testDatabase } from './databaseTests'; -// 기본 테스트 함수 -export const testSupabaseConnection = async (): Promise => { - // 브라우저 및 환경 정보 수집 - const browserInfo = `${navigator.userAgent}`; - const originalUrl = getOriginalSupabaseUrl(); - const proxyUrl = getSupabaseUrl(); - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - - // 디버그 정보 초기화 - const debugInfo: TestDebugInfo = { - originalUrl, - proxyUrl, - usingProxy, - proxyType, - keyLength: localStorage.getItem('supabase_key')?.length || 0, - browserInfo, - timestamp: new Date().toISOString(), - backupProxySuccess: false, - lastErrorDetails: '', - }; - - // 테스트 결과 초기화 - const result: ConnectionTestResult = { - url: originalUrl, - proxyUrl, - usingProxy, - proxyType, +export const testSupabaseConnection = async () => { + console.log('Supabase 연결 테스트 시작...'); + + // 결과값을 저장할 객체 + const results = { client: false, - restApi: false, + restApi: false, auth: false, database: false, - errors: [], - debugInfo + url: '', // Supabase URL + usingProxy: false, // CORS 프록시 사용 여부 + proxyUrl: '', // 사용 중인 프록시 URL + errors: [] as string[], // 오류 메시지 배열 + debugInfo: { // 디버깅 정보 + proxyAttempts: [] as string[], + corsErrors: [] as string[], + timeouts: [] as string[], + backupProxySuccess: false + } }; try { - // 클라이언트 초기화 테스트 - if (supabase) { - result.client = true; - } else { - result.errors.push('Supabase 클라이언트 초기화 실패'); - } - - // REST API 테스트 + // 테스트 1: REST API 연결 테스트 + console.log('REST API 연결 테스트 중...'); const apiTest = await testRestApi(); - result.restApi = apiTest.success; + results.restApi = apiTest.success; + if (!apiTest.success && apiTest.error) { - result.errors.push(`REST API 오류: ${apiTest.error.message || '알 수 없는 오류'}`); - debugInfo.lastErrorDetails = JSON.stringify(apiTest.error); + results.errors.push(`REST API 예외: ${apiTest.error.message || '알 수 없는 오류'}`); } - - // 인증 테스트 + + // 테스트 2: 인증 서비스 테스트 + console.log('인증 서비스 테스트 중...'); const authTest = await testAuthService(); - result.auth = authTest.success; + results.auth = authTest.success; + if (!authTest.success && authTest.error) { - result.errors.push(`인증 오류: ${authTest.error.message || '알 수 없는 오류'}`); - if (!debugInfo.lastErrorDetails) { - debugInfo.lastErrorDetails = JSON.stringify(authTest.error); - } + results.errors.push(`인증 오류: ${authTest.error.message || '알 수 없는 오류'}`); } - - // 데이터베이스 테스트 - const dbTest = await testDatabaseConnection(); - result.database = dbTest.success; + + // 테스트 3: 데이터베이스 테스트 + console.log('데이터베이스 테스트 중...'); + const dbTest = await testDatabase(); + results.database = dbTest.success; + if (!dbTest.success && dbTest.error) { - result.errors.push(`데이터베이스 오류: ${dbTest.error.message || '알 수 없는 오류'}`); - if (!debugInfo.lastErrorDetails) { - debugInfo.lastErrorDetails = JSON.stringify(dbTest.error); + results.errors.push(`데이터베이스 오류: ${dbTest.error.message || '알 수 없는 오류'}`); + } + + // 테스트 결과 요약 + results.client = true; // 클라이언트가 초기화되었다면 여기까지 왔을 것임 + + // Supabase URL 및 프록시 정보 추가 + const supabaseUrl = (supabase as any).supabaseUrl || ''; + results.url = supabaseUrl; + + // CORS 프록시 감지 + const isProxied = supabaseUrl.includes('corsproxy.io') || + supabaseUrl.includes('cors-anywhere') || + supabaseUrl.includes('thingproxy.freeboard.io') || + supabaseUrl.includes('allorigins.win'); + + results.usingProxy = isProxied; + + if (isProxied) { + // 프록시 URL 추출 + const proxyMatch = supabaseUrl.match(/(https?:\/\/[^\/]+\/)/); + if (proxyMatch) { + results.proxyUrl = proxyMatch[1]; + } else { + results.proxyUrl = '알 수 없는 프록시'; } } - - // 모든 테스트 실패 시 백업 프록시 테스트 - if (usingProxy && !result.restApi && !result.auth && !result.database) { - console.log('모든 테스트가 실패했습니다. 다른 프록시 테스트를 시도합니다...'); - debugInfo.backupProxySuccess = false; // 백업 프록시 테스트 결과 초기화 - } - - return result; - } catch (err) { - console.error('Supabase 연결 테스트 중 예외 발생:', err); - result.errors.push(`테스트 중 예외 발생: ${err instanceof Error ? err.message : '알 수 없는 오류'}`); - debugInfo.lastErrorDetails = err instanceof Error ? err.stack || err.message : String(err); - return result; + + console.log('Supabase 연결 테스트 완료:', results); + return results; + } catch (error: any) { + console.error('Supabase 연결 테스트 중 예외 발생:', error); + results.errors.push(`예상치 못한 오류: ${error.message || '알 수 없는 오류'}`); + return results; } }; - -export { testAuthService, testRestApi, testDatabaseConnection }; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 7a5226a..76ea8b5 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,26 +1,30 @@ import React, { useState, useEffect } from "react"; 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, Eye, EyeOff, RefreshCw, Shield } from "lucide-react"; -import { useToast } from "@/hooks/useToast.wrapper"; import { useAuth } from "@/contexts/auth"; -import { testSupabaseConnection } from "@/lib/supabase"; +import LoginForm from "@/components/auth/LoginForm"; +import TestConnectionSection from "@/components/auth/TestConnectionSection"; +import SupabaseConnectionStatus from "@/components/auth/SupabaseConnectionStatus"; +import { useLogin } from "@/hooks/useLogin"; const Login = () => { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [showPassword, setShowPassword] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [testLoading, setTestLoading] = useState(false); - const [testResults, setTestResults] = useState(null); - const [loginError, setLoginError] = useState(null); - const { toast } = useToast(); - const navigate = useNavigate(); - const { signIn, user } = useAuth(); + const { + email, + setEmail, + password, + setPassword, + showPassword, + setShowPassword, + isLoading, + loginError, + setLoginError, + handleLogin + } = useLogin(); + const [testResults, setTestResults] = useState(null); + const navigate = useNavigate(); + const { user } = useAuth(); + // 이미 로그인된 경우 메인 페이지로 리다이렉트 useEffect(() => { if (user) { @@ -28,145 +32,25 @@ const Login = () => { } }, [user, navigate]); - const handleLogin = async (e: React.FormEvent) => { - e.preventDefault(); - setLoginError(null); - - if (!email || !password) { - toast({ - title: "입력 오류", - description: "이메일과 비밀번호를 모두 입력해주세요.", - variant: "destructive" - }); - return; - } - - setIsLoading(true); - - try { - const { error } = await signIn(email, password); - - if (error) { - console.error("로그인 실패:", error); - let errorMessage = "로그인에 실패했습니다."; - - // Supabase 오류 메시지 처리 - if (error.message) { - if (error.message.includes("Invalid login credentials")) { - errorMessage = "이메일 또는 비밀번호가 올바르지 않습니다."; - } else if (error.message.includes("Email not confirmed")) { - errorMessage = "이메일 인증이 완료되지 않았습니다. 이메일을 확인해주세요."; - } else { - errorMessage = `오류: ${error.message}`; - } - } - - setLoginError(errorMessage); - - toast({ - title: "로그인 실패", - description: errorMessage, - variant: "destructive" - }); - } else { - navigate("/"); - } - } catch (err: any) { - console.error("로그인 과정에서 예외 발생:", err); - const errorMessage = err.message || "알 수 없는 오류가 발생했습니다."; - setLoginError(errorMessage); - - toast({ - title: "로그인 오류", - description: errorMessage, - variant: "destructive" - }); - } finally { - setIsLoading(false); - } - }; - - // Supabase 연결 테스트 - const runConnectionTest = async () => { - setTestLoading(true); - setLoginError(null); - try { - const results = await testSupabaseConnection(); - setTestResults(results); - - if (results.errors.length === 0) { - toast({ - title: "연결 테스트 성공", - description: "Supabase 서버 연결이 정상입니다.", - }); - } else { - toast({ - title: "연결 테스트 실패", - description: `오류 발생: ${results.errors[0]}`, - variant: "destructive" - }); - } - } catch (error: any) { - console.error("연결 테스트 중 오류:", error); - setLoginError(error.message || "테스트 실행 중 예상치 못한 오류가 발생했습니다."); - - toast({ - title: "연결 테스트 오류", - description: "테스트 실행 중 예상치 못한 오류가 발생했습니다.", - variant: "destructive" - }); - } finally { - setTestLoading(false); - } - }; - - return
+ return ( +

젤리의 적자탈출

계정에 로그인하여 재정 관리를 시작하세요

-
-
-
-
- -
- - setEmail(e.target.value)} className="pl-10 neuro-pressed" /> -
-
- -
- -
- - setPassword(e.target.value)} className="pl-10 neuro-pressed" /> - -
-
- - {loginError && ( -
-

{loginError}

-
- )} - -
- - 비밀번호를 잊으셨나요? - -
- - -
-
-
+

@@ -177,98 +61,15 @@ const Login = () => {

- {/* 연결 테스트 섹션 */} -
-

Supabase 연결 진단

-
- - - {testResults && ( -
- - API - - 인증 - - DB -
- )} -
- - {testResults && testResults.errors.length > 0 && ( -
- {testResults.errors.map((error: string, index: number) => ( -
{error}
- ))} -
- )} -
+ - {/* 고급 진단 정보 */} -
- 고급 진단 정보 -
-
-

Supabase URL:

-

{testResults?.url || '알 수 없음'}

-
- {testResults?.usingProxy && ( -
-

CORS 프록시:

-
- -

활성화됨 - {testResults.proxyUrl}

-
-
- )} -
-

클라이언트 초기화:

-

{testResults?.client ? '성공' : '실패'}

-
-
-

브라우저 정보:

-

{navigator.userAgent}

-
-
-

앱 버전:

-

1.0.0

-
- {testResults && ( -
-

REST API: {testResults.restApi ? '✅' : '❌'}

-

인증: {testResults.auth ? '✅' : '❌'}

-

데이터베이스: {testResults.database ? '✅' : '❌'}

-
- )} -
- - Supabase 설정 변경 - - -
-
-
+
-
; +
+ ); }; export default Login;