From a735dee195a6fc66da1f927e9417d9f5e80c2a0d 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 16:19:27 +0000 Subject: [PATCH] Refactor signUpUtils.ts file The signUpUtils.ts file was refactored into smaller components to improve code readability and maintainability. --- src/contexts/auth/signUpApiCalls.ts | 77 +++++++++++ src/contexts/auth/signUpErrorHandlers.ts | 54 ++++++++ src/contexts/auth/signUpUtils.ts | 165 +++++++---------------- 3 files changed, 180 insertions(+), 116 deletions(-) create mode 100644 src/contexts/auth/signUpApiCalls.ts create mode 100644 src/contexts/auth/signUpErrorHandlers.ts diff --git a/src/contexts/auth/signUpApiCalls.ts b/src/contexts/auth/signUpApiCalls.ts new file mode 100644 index 0000000..83d3bc5 --- /dev/null +++ b/src/contexts/auth/signUpApiCalls.ts @@ -0,0 +1,77 @@ + +import { parseResponse, showAuthToast } from '@/utils/auth'; +import { getProxyType, isCorsProxyEnabled, getSupabaseUrl, getOriginalSupabaseUrl } from '@/lib/supabase/config'; +import { handleNetworkError } from '@/utils/auth/handleNetworkError'; + +/** + * 직접 API 호출을 통한 회원가입 요청 전송 + */ +export const sendSignUpApiRequest = async ( + email: string, + password: string, + username: string, + redirectUrl: string, + supabaseKey: string +) => { + try { + // 프록시 적용된 URL과 원본 URL 모두 가져오기 + const supabaseUrl = getOriginalSupabaseUrl(); // 원본 URL + const proxyUrl = getSupabaseUrl(); // 프록시 적용된 URL + + // 프록시 정보 로깅 + const usingProxy = isCorsProxyEnabled(); + const proxyType = getProxyType(); + console.log(`CORS 프록시 사용: ${usingProxy ? '예' : '아니오'}, 타입: ${proxyType}, 프록시 URL: ${proxyUrl}`); + + // 실제 요청에 사용할 URL 결정 (항상 프록시 URL 사용) + const useUrl = usingProxy ? proxyUrl : supabaseUrl; + + // URL에 auth/v1이 이미 포함되어있는지 확인 + const baseUrl = useUrl.includes('/auth/v1') ? useUrl : `${useUrl}/auth/v1`; + + // 회원가입 API 엔드포인트 및 헤더 설정 + const signUpUrl = `${baseUrl}/signup`; + const headers = { + 'Content-Type': 'application/json', + 'apikey': supabaseKey + }; + + console.log('회원가입 API 요청 URL:', signUpUrl); + + // 회원가입 요청 전송 + const response = await fetch(signUpUrl, { + method: 'POST', + headers, + body: JSON.stringify({ + email, + password, + data: { username }, // 사용자 메타데이터에 username 추가 + redirect_to: redirectUrl // 리디렉션 URL 추가 + }), + signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 + }); + + console.log('회원가입 응답 상태:', response.status); + return response; + } catch (error) { + throw error; // 상위 함수에서 처리하도록 오류 전파 + } +}; + +/** + * 응답 상태 코드 기반 에러 메시지 생성 + */ +export const getStatusErrorMessage = (status: number): string => { + switch (status) { + case 400: + return '잘못된 요청 형식입니다. 입력 데이터를 확인하세요.'; + case 401: + return '회원가입 권한이 없습니다. Supabase 설정 또는 권한을 확인하세요.'; + case 404: + return '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.'; + case 500: + return '서버 내부 오류가 발생했습니다. 나중에 다시 시도하세요.'; + default: + return `회원가입 처리 중 오류가 발생했습니다 (${status}). 나중에 다시 시도하세요.`; + } +}; diff --git a/src/contexts/auth/signUpErrorHandlers.ts b/src/contexts/auth/signUpErrorHandlers.ts new file mode 100644 index 0000000..ee10e48 --- /dev/null +++ b/src/contexts/auth/signUpErrorHandlers.ts @@ -0,0 +1,54 @@ + +import { showAuthToast } from '@/utils/auth'; +import { getProxyType, isCorsProxyEnabled } from '@/lib/supabase/config'; + +/** + * 회원가입 API 호출 중 발생한 예외 처리 + */ +export const handleSignUpApiError = (error: any) => { + console.error('회원가입 API 호출 중 예외 발생:', error); + + // 프록시 설정 확인 + const usingProxy = isCorsProxyEnabled(); + const proxyType = getProxyType(); + + // 오류 메시지 설정 및 프록시 추천 + let errorMessage = error.message || '알 수 없는 오류가 발생했습니다.'; + + // 타임아웃 오류 감지 + if (errorMessage.includes('timed out') || errorMessage.includes('timeout')) { + errorMessage = '서버 응답 시간이 초과되었습니다. 네트워크 상태를 확인하고 다시 시도하세요.'; + } + // CORS 또는 네트워크 오류 감지 + else if (errorMessage.includes('Failed to fetch') || + errorMessage.includes('NetworkError') || + errorMessage.includes('CORS')) { + if (!usingProxy) { + errorMessage += ' (설정에서 Cloudflare CORS 프록시 활성화를 권장합니다)'; + } else if (proxyType !== 'cloudflare') { + errorMessage += ' (설정에서 Cloudflare CORS 프록시로 변경을 권장합니다)'; + } + } + + showAuthToast('회원가입 오류', errorMessage, 'destructive'); + + return { error: { message: errorMessage }, user: null }; +}; + +/** + * 응답 데이터에서 특정 에러 처리 + */ +export const handleResponseError = (responseData: any) => { + if (responseData && responseData.error) { + const errorMessage = responseData.error_description || responseData.error || '회원가입 실패'; + + if (responseData.error === 'user_already_registered') { + showAuthToast('회원가입 실패', '이미 등록된 이메일 주소입니다.', 'destructive'); + return { error: { message: '이미 등록된 이메일 주소입니다.' }, user: null }; + } + + showAuthToast('회원가입 실패', errorMessage, 'destructive'); + return { error: { message: errorMessage }, user: null }; + } + return null; +}; diff --git a/src/contexts/auth/signUpUtils.ts b/src/contexts/auth/signUpUtils.ts index fa48f98..6db7578 100644 --- a/src/contexts/auth/signUpUtils.ts +++ b/src/contexts/auth/signUpUtils.ts @@ -1,7 +1,8 @@ import { supabase } from '@/lib/supabase'; import { parseResponse, showAuthToast } from '@/utils/auth'; -import { getProxyType, isCorsProxyEnabled, getSupabaseUrl, getOriginalSupabaseUrl } from '@/lib/supabase/config'; +import { sendSignUpApiRequest, getStatusErrorMessage } from './signUpApiCalls'; +import { handleSignUpApiError, handleResponseError } from './signUpErrorHandlers'; /** * 직접 API 호출을 통한 회원가입 @@ -10,9 +11,7 @@ export const signUpWithDirectApi = async (email: string, password: string, usern try { console.log('직접 API 호출로 회원가입 시도 중'); - // 프록시 적용된 URL과 원본 URL 모두 가져오기 - const supabaseUrl = getOriginalSupabaseUrl(); // 원본 URL - const proxyUrl = getSupabaseUrl(); // 프록시 적용된 URL + // Supabase 키 가져오기 const supabaseKey = localStorage.getItem('supabase_key') || supabase.supabaseKey; // Supabase 키 유효성 검사 @@ -28,40 +27,8 @@ export const signUpWithDirectApi = async (email: string, password: string, usern const finalRedirectUrl = redirectUrl || `${window.location.origin}/login`; console.log('이메일 인증 리디렉션 URL (API):', finalRedirectUrl); - // 프록시 정보 로깅 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - console.log(`CORS 프록시 사용: ${usingProxy ? '예' : '아니오'}, 타입: ${proxyType}, 프록시 URL: ${proxyUrl}`); - - // 실제 요청에 사용할 URL 결정 (항상 프록시 URL 사용) - const useUrl = usingProxy ? proxyUrl : supabaseUrl; - - // URL에 auth/v1이 이미 포함되어있는지 확인 - const baseUrl = useUrl.includes('/auth/v1') ? useUrl : `${useUrl}/auth/v1`; - - // 회원가입 API 엔드포인트 및 헤더 설정 - const signUpUrl = `${baseUrl}/signup`; - const headers = { - 'Content-Type': 'application/json', - 'apikey': supabaseKey - }; - - console.log('회원가입 API 요청 URL:', signUpUrl); - - // 회원가입 요청 전송 - const response = await fetch(signUpUrl, { - method: 'POST', - headers, - body: JSON.stringify({ - email, - password, - data: { username }, // 사용자 메타데이터에 username 추가 - redirect_to: finalRedirectUrl // 리디렉션 URL 추가 - }), - signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 - }); - - console.log('회원가입 응답 상태:', response.status); + // API 요청 전송 + const response = await sendSignUpApiRequest(email, password, username, finalRedirectUrl, supabaseKey); // 401 오류 처리 (권한 없음) if (response.status === 401) { @@ -113,53 +80,15 @@ export const signUpWithDirectApi = async (email: string, password: string, usern } } - if (responseData && responseData.error) { - const errorMessage = responseData.error_description || responseData.error || '회원가입 실패'; - - if (responseData.error === 'user_already_registered') { - showAuthToast('회원가입 실패', '이미 등록된 이메일 주소입니다.', 'destructive'); - return { error: { message: '이미 등록된 이메일 주소입니다.' }, user: null }; - } - - showAuthToast('회원가입 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null }; - } + // 응답 에러 처리 + const errorResult = handleResponseError(responseData); + if (errorResult) return errorResult; // 응답 상태 코드가 성공(2xx)이면서 사용자 데이터가 있는 경우 if (response.ok && responseData && responseData.id) { - const user = { - id: responseData.id, - email: responseData.email, - user_metadata: responseData.user_metadata || { username }, - app_metadata: responseData.app_metadata || {}, - created_at: responseData.created_at, - }; - - const confirmEmail = !responseData.confirmed_at; - - if (confirmEmail) { - showAuthToast('회원가입 성공', '이메일 인증을 완료해주세요.', 'default'); - return { - error: null, - user, - message: '이메일 인증 필요', - emailConfirmationRequired: true - }; - } else { - showAuthToast('회원가입 성공', '환영합니다!', 'default'); - - // 성공 시 바로 로그인 세션 설정 시도 - try { - await supabase.auth.signInWithPassword({ email, password }); - } catch (loginError) { - console.warn('자동 로그인 실패:', loginError); - // 무시하고 계속 진행 (회원가입은 성공) - } - - return { error: null, user }; - } + return processSuccessfulSignup(responseData, email, password); } - // 응답이 성공(2xx)이지만 사용자 정보가 없는 경우 또는 응답 본문이 비어있는 경우 + // 응답이 성공(2xx)이지만, 사용자 정보가 없는 경우 또는 응답 본문이 비어있는 경우 else if (response.ok) { // 응답 본문이 비어 있는 경우는 서버가 성공을 반환했지만 데이터가 없는 경우 (일부 Supabase 버전에서 발생) showAuthToast('회원가입 요청 완료', '회원가입 요청이 처리되었습니다. 이메일을 확인하거나 로그인을 시도해보세요.', 'default'); @@ -178,45 +107,49 @@ export const signUpWithDirectApi = async (email: string, password: string, usern // 다른 모든 오류 상태 else { // 응답 상태 코드에 따른 오류 메시지 - let errorMessage; - if (response.status === 400) { - errorMessage = '잘못된 요청 형식입니다. 입력 데이터를 확인하세요.'; - } else if (response.status === 500) { - errorMessage = '서버 내부 오류가 발생했습니다. 나중에 다시 시도하세요.'; - } else { - errorMessage = `회원가입 처리 중 오류가 발생했습니다 (${response.status}). 나중에 다시 시도하세요.`; - } + const errorMessage = getStatusErrorMessage(response.status); showAuthToast('회원가입 실패', errorMessage, 'destructive'); return { error: { message: errorMessage }, user: null }; } } catch (error: any) { - console.error('회원가입 API 호출 중 예외 발생:', error); - - // 프록시 설정 확인 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - - // 오류 메시지 설정 및 프록시 추천 - let errorMessage = error.message || '알 수 없는 오류가 발생했습니다.'; - - // 타임아웃 오류 감지 - if (errorMessage.includes('timed out') || errorMessage.includes('timeout')) { - errorMessage = '서버 응답 시간이 초과되었습니다. 네트워크 상태를 확인하고 다시 시도하세요.'; - } - // CORS 또는 네트워크 오류 감지 - else if (errorMessage.includes('Failed to fetch') || - errorMessage.includes('NetworkError') || - errorMessage.includes('CORS')) { - if (!usingProxy) { - errorMessage += ' (설정에서 Cloudflare CORS 프록시 활성화를 권장합니다)'; - } else if (proxyType !== 'cloudflare') { - errorMessage += ' (설정에서 Cloudflare CORS 프록시로 변경을 권장합니다)'; - } - } - - showAuthToast('회원가입 오류', errorMessage, 'destructive'); - - return { error: { message: errorMessage }, user: null }; + return handleSignUpApiError(error); + } +}; + +/** + * 성공적인 회원가입 응답 처리 + */ +const processSuccessfulSignup = async (responseData: any, email: string, password: string) => { + const user = { + id: responseData.id, + email: responseData.email, + user_metadata: responseData.user_metadata || { username: responseData.user_metadata?.username || '' }, + app_metadata: responseData.app_metadata || {}, + created_at: responseData.created_at, + }; + + const confirmEmail = !responseData.confirmed_at; + + if (confirmEmail) { + showAuthToast('회원가입 성공', '이메일 인증을 완료해주세요.', 'default'); + return { + error: null, + user, + message: '이메일 인증 필요', + emailConfirmationRequired: true + }; + } else { + showAuthToast('회원가입 성공', '환영합니다!', 'default'); + + // 성공 시 바로 로그인 세션 설정 시도 + try { + await supabase.auth.signInWithPassword({ email, password }); + } catch (loginError) { + console.warn('자동 로그인 실패:', loginError); + // 무시하고 계속 진행 (회원가입은 성공) + } + + return { error: null, user }; } };