From 63a09034a92ce0b1b98070d560ac89c9d0e05575 Mon Sep 17 00:00:00 2001 From: "jihoon87.lee" Date: Wed, 4 Feb 2026 09:35:42 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EB=A5=BC=20React=20Hook=20Form=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - signup/page.tsx: SignupForm 컴포넌트 사용 - login/page.tsx: LoginForm 컴포넌트 사용 - reset-password/page.tsx: ResetPasswordForm 컴포넌트 사용 - auth/callback/route.ts: 불필요한 주석 제거 --- app/auth/callback/route.ts | 67 ++++++++++------ app/login/page.tsx | 156 +----------------------------------- app/reset-password/page.tsx | 38 +-------- app/signup/page.tsx | 75 +++-------------- 4 files changed, 59 insertions(+), 277 deletions(-) diff --git a/app/auth/callback/route.ts b/app/auth/callback/route.ts index 9362711..3a4573d 100644 --- a/app/auth/callback/route.ts +++ b/app/auth/callback/route.ts @@ -1,57 +1,74 @@ import { createClient } from "@/utils/supabase/server"; import { NextResponse } from "next/server"; +import { AUTH_ERROR_MESSAGES, AUTH_ROUTES } from "@/features/auth/constants"; /** * [인증 콜백 라우트 핸들러] * - * Supabase 인증 이메일(회원가입 확인, 비밀번호 재설정 등)에서 + * Supabase 인증 이메일(회원가입 확인, 비밀번호 재설정 등) 및 OAuth(소셜 로그인) * 리다이렉트될 때 호출되는 API 라우트입니다. - * - * PKCE(Proof Key for Code Exchange) 흐름: - * 1. 사용자가 이메일 링크 클릭 - * 2. Supabase 서버가 토큰 검증 후 이 라우트로 `code` 파라미터와 함께 리다이렉트 - * 3. 이 라우트에서 `code`를 세션으로 교환 - * 4. 원래 목적지(next 파라미터)로 리다이렉트 - * - * @param request - Next.js Request 객체 */ export async function GET(request: Request) { const { searchParams, origin } = new URL(request.url); - // 1. URL에서 code와 next(리다이렉트 목적지) 추출 + // 1. URL에서 주요 직접 파라미터 및 에러 추출 const code = searchParams.get("code"); - const next = searchParams.get("next") ?? "/"; + const next = searchParams.get("next") ?? AUTH_ROUTES.HOME; + const error = searchParams.get("error"); + const error_code = searchParams.get("error_code"); + const error_description = searchParams.get("error_description"); - // 2. code가 있으면 세션으로 교환 + // 2. 인증 오류가 있는 경우 (예: 구글 로그인 취소 등) + if (error) { + console.error("Auth callback error parameter:", { + error, + error_code, + error_description, + }); + + let message = AUTH_ERROR_MESSAGES.DEFAULT; + + // 에러 종류에 따른 메시지 분기 + if (error === "access_denied") { + message = AUTH_ERROR_MESSAGES.OAUTH_ACCESS_DENIED; + } else if (error === "server_error") { + message = AUTH_ERROR_MESSAGES.OAUTH_SERVER_ERROR; + } + + return NextResponse.redirect( + `${origin}${AUTH_ROUTES.LOGIN}?message=${encodeURIComponent(message)}`, + ); + } + + // 3. code가 있으면 세션으로 교환 (정상 플로우) if (code) { const supabase = await createClient(); - const { error } = await supabase.auth.exchangeCodeForSession(code); + const { error: exchangeError } = + await supabase.auth.exchangeCodeForSession(code); - if (!error) { - // 3. 세션 교환 성공 - 원래 목적지로 리다이렉트 - // next가 절대 URL(http://...)이면 그대로 사용, 아니면 origin + next - const forwardedHost = request.headers.get("x-forwarded-host"); // 프록시 환경 대응 + if (!exchangeError) { + // 세션 교환 성공 - 원래 목적지로 리다이렉트 + const forwardedHost = request.headers.get("x-forwarded-host"); const isLocalEnv = process.env.NODE_ENV === "development"; if (isLocalEnv) { - // 개발 환경: localhost 사용 return NextResponse.redirect(`${origin}${next}`); } else if (forwardedHost) { - // 프로덕션 + 프록시: x-forwarded-host 사용 return NextResponse.redirect(`https://${forwardedHost}${next}`); } else { - // 프로덕션: origin 사용 return NextResponse.redirect(`${origin}${next}`); } } - // 에러 발생 시 로그 출력 - console.error("Auth callback error:", error.message); + // 세션 교환 실패 시 로그 및 에러 메시지 설정 + console.error("Auth exchange error:", exchangeError.message); } - // 4. code가 없거나 교환 실패 시 에러 페이지로 리다이렉트 + // 4. code가 없거나 교환 실패 시 기본 에러 페이지로 리다이렉트 const errorMessage = encodeURIComponent( - "인증 링크가 만료되었거나 유효하지 않습니다.", + AUTH_ERROR_MESSAGES.INVALID_AUTH_LINK, + ); + return NextResponse.redirect( + `${origin}${AUTH_ROUTES.LOGIN}?message=${errorMessage}`, ); - return NextResponse.redirect(`${origin}/login?message=${errorMessage}`); } diff --git a/app/login/page.tsx b/app/login/page.tsx index 7d8c2e1..691f0ba 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,13 +1,4 @@ -import Link from "next/link"; -import { - login, - signInWithGoogle, - signInWithKakao, -} from "@/features/auth/actions"; import FormMessage from "@/components/form-message"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import { Card, CardContent, @@ -15,8 +6,7 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Separator } from "@/components/ui/separator"; +import LoginForm from "@/features/auth/components/login-form"; /** * [로그인 페이지 컴포넌트] @@ -85,148 +75,8 @@ export default async function LoginPage({ {/* ========== 카드 콘텐츠 영역 (폼) ========== */} - - {/* 로그인 폼 - formAction으로 서버 액션(login) 연결 */} -
- {/* ========== 이메일 입력 필드 ========== */} -
- {/* htmlFor와 Input의 id 연결로 접근성 향상 */} - - {/* autoComplete="email": 브라우저 자동완성 기능 활성화 */} - {/* required: HTML5 필수 입력 검증 */} - -
- - {/* ========== 비밀번호 입력 필드 ========== */} -
- - {/* pattern: 최소 8자, 대문자, 소문자, 숫자, 특수문자 각 1개 이상 */} - {/* 참고: HTML pattern에서는 <, >, {, } 등 일부 특수문자 사용 시 브라우저 호환성 문제 발생 */} - -
- - {/* 로그인 유지 & 비밀번호 찾기 */} -
-
- - -
- {/* 비밀번호 찾기 링크 */} - - 비밀번호 찾기 - -
- - {/* 로그인 버튼 */} - - - {/* 회원가입 링크 */} -

- 계정이 없으신가요?{" "} - - 회원가입 하기 - -

-
- - {/* 소셜 로그인 구분선 */} -
- - - 또는 소셜 로그인 - -
- - {/* 소셜 로그인 버튼들 */} -
- {/* ========== Google 로그인 버튼 ========== */} -
- -
- - {/* ========== Kakao 로그인 버튼 ========== */} -
- -
-
+ + diff --git a/app/reset-password/page.tsx b/app/reset-password/page.tsx index 024716d..b768761 100644 --- a/app/reset-password/page.tsx +++ b/app/reset-password/page.tsx @@ -1,8 +1,5 @@ import FormMessage from "@/components/form-message"; -import { updatePassword } from "@/features/auth/actions"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import ResetPasswordForm from "@/features/auth/components/reset-password-form"; import { Card, CardContent, @@ -105,38 +102,7 @@ export default async function ResetPasswordPage({ {/* ========== 폼 영역 ========== */} - {/* 비밀번호 업데이트 폼 */} -
- {/* ========== 새 비밀번호 입력 ========== */} -
- - -

- 최소 8자 이상, 대문자, 소문자, 숫자, 특수문자 포함 -

-
- - {/* ========== 비밀번호 변경 버튼 ========== */} - -
+
diff --git a/app/signup/page.tsx b/app/signup/page.tsx index 4717ea3..803a118 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -1,9 +1,6 @@ import Link from "next/link"; -import { signup } from "@/features/auth/actions"; import FormMessage from "@/components/form-message"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import SignupForm from "@/features/auth/components/signup-form"; import { Card, CardContent, @@ -46,68 +43,20 @@ export default async function SignupPage({ + {/* ========== 폼 영역 ========== */} -
- {/* 이메일 입력 */} -
- - -
+ - {/* 비밀번호 입력 */} -
- - {/* pattern: 최소 8자, 대문자, 소문자, 숫자, 특수문자 각 1개 이상 */} - {/* 참고: HTML pattern에서는 <, >, {, } 등 일부 특수문자 사용 시 브라우저 호환성 문제 발생 */} - -

- 최소 6자 이상, 숫자, 특수문자 포함 (한글 가능) -

-
- - {/* 회원가입 버튼 */} - - - {/* 로그인 링크 */} -

- 이미 계정이 있으신가요?{" "} - - 로그인 하러 가기 - -

- + 로그인 하러 가기 + +