Fix: 회원가입 인증 리다이렉트 처리와 UI 그라디언트 클래스 수정

app/auth/callback/route.ts
- 이메일 인증 완료 판별을 세션 생성 시간 기준에서 쿼리 파라미터(auth_type=signup) 기반으로 변경
- 회원가입 인증인 경우 자동 로그인 세션 종료 후 로그인 페이지로 리다이렉트 처리

features/auth/actions.ts
- 회원가입 시 이메일 리다이렉트 URL에 auth_type=signup 쿼리 파라미터 추가

app/forgot-password/page.tsx
- 배경 및 카드 아이콘의 Tailwind 클래스명 일부 수정(bg-gradient-to→bg-linear-to, radial-gradient var() 구문 정리)

app/login/page.tsx
- 배경 및 카드 아이콘의 Tailwind 클래스명 일부 정리 및 일관화

app/reset-password/page.tsx
- 배경 및 카드 아이콘의 Tailwind 클래스명 일부 정리 및 일관화

app/signup/page.tsx
- 배경 및 카드 아이콘의 Tailwind 클래스명 일부 정리 및 일관화

features/auth/components/reset-password-form.tsx
- 버튼 그라디언트 클래스명(bg-gradient-to-r→bg-linear-to-r) 수정
This commit is contained in:
2026-02-05 15:39:44 +09:00
parent 9c967af9c1
commit 2d34d70948
7 changed files with 24 additions and 30 deletions

View File

@@ -65,20 +65,14 @@ export async function GET(request: NextRequest) {
// code 교환으로 세션이 생성된 상태입니다. // code 교환으로 세션이 생성된 상태입니다.
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
const { // 회원가입 인증 여부 확인 (쿼리 파라미터 기반)
data: { user }, // actions.ts의 signup 함수에서 emailRedirectTo에 auth_type=signup을 추가해서 보냅니다.
} = await supabase.auth.getUser(); const authType = requestUrl.searchParams.get("auth_type");
const isSignupVerification = authType === "signup";
// 회원가입 직후 인증 여부 확인 (생성된 지 1분 이내)
// 별도의 type 파라미터 없이 데이터 기반으로 판단합니다.
const isNewUser =
user &&
user.created_at &&
new Date(user.created_at).getTime() > Date.now() - 60 * 1000; // 1분 이내 생성
// 회원가입 인증인 경우: // 회원가입 인증인 경우:
// 이메일 인증만 완료하고, 자동 로그인된 세션은 종료시킨 뒤 로그인 페이지로 보냅니다. // 이메일 인증만 완료하고, 자동 로그인된 세션은 종료시킨 뒤 로그인 페이지로 보냅니다.
if (isNewUser) { if (isSignupVerification) {
await supabase.auth.signOut(); await supabase.auth.signOut();
return NextResponse.redirect( return NextResponse.redirect(
`${origin}${AUTH_ROUTES.LOGIN}?message=${encodeURIComponent( `${origin}${AUTH_ROUTES.LOGIN}?message=${encodeURIComponent(

View File

@@ -27,9 +27,9 @@ export default async function ForgotPasswordPage({
const { message } = await searchParams; const { message } = await searchParams;
return ( return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900"> <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-linear-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,_var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" />
<div className="absolute left-1/4 top-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-300/20 blur-3xl dark:bg-gray-700/20" /> <div className="absolute left-1/4 top-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-300/20 blur-3xl dark:bg-gray-700/20" />
<div className="absolute bottom-1/4 right-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-400/20 blur-3xl delay-700 dark:bg-gray-600/20" /> <div className="absolute bottom-1/4 right-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-400/20 blur-3xl delay-700 dark:bg-gray-600/20" />
@@ -39,7 +39,7 @@ export default async function ForgotPasswordPage({
<Card className="border-white/20 bg-white/70 shadow-2xl backdrop-blur-xl dark:border-white/10 dark:bg-gray-900/70"> <Card className="border-white/20 bg-white/70 shadow-2xl backdrop-blur-xl dark:border-white/10 dark:bg-gray-900/70">
<CardHeader className="space-y-3 text-center"> <CardHeader className="space-y-3 text-center">
<div className="mx-auto mb-2 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-gray-800 to-black shadow-lg dark:from-white dark:to-gray-200"> <div className="mx-auto mb-2 flex h-16 w-16 items-center justify-center rounded-2xl bg-linear-to-br from-gray-800 to-black shadow-lg dark:from-white dark:to-gray-200">
<span className="text-sm font-semibold">MAIL</span> <span className="text-sm font-semibold">MAIL</span>
</div> </div>
<CardTitle className="text-3xl font-bold tracking-tight"> <CardTitle className="text-3xl font-bold tracking-tight">
@@ -72,7 +72,7 @@ export default async function ForgotPasswordPage({
<Button <Button
formAction={requestPasswordReset} formAction={requestPasswordReset}
className="h-11 w-full bg-gradient-to-r from-gray-900 to-black font-semibold text-white shadow-lg transition-all hover:from-black hover:to-gray-800 hover:shadow-xl dark:from-white dark:to-gray-100 dark:text-black dark:hover:from-gray-100 dark:hover:to-white" className="h-11 w-full bg-linear-to-r from-gray-900 to-black font-semibold text-white shadow-lg transition-all hover:from-black hover:to-gray-800 hover:shadow-xl dark:from-white dark:to-gray-100 dark:text-black dark:hover:from-gray-100 dark:hover:to-white"
> >
</Button> </Button>

View File

@@ -27,17 +27,17 @@ export default async function LoginPage({
const { message } = await searchParams; const { message } = await searchParams;
return ( return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900"> <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-linear-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900">
{/* ========== 배경 그라디언트 레이어 ========== */} {/* ========== 배경 그라디언트 레이어 ========== */}
{/* 웹 페이지 전체 배경을 그라디언트로 채웁니다 */} {/* 웹 페이지 전체 배경을 그라디언트로 채웁니다 */}
{/* 라이트 모드: 부드러운 그레이 톤 (gray → white → gray) */} {/* 라이트 모드: 부드러운 그레이 톤 (gray → white → gray) */}
{/* 다크 모드: 깊은 블랙 톤으로 고급스러운 느낌 */} {/* 다크 모드: 깊은 블랙 톤으로 고급스러운 느낌 */}
{/* 추가 그라디언트 효과 1: 우상단에서 시작하는 원형 그라디언트 */} {/* 추가 그라디언트 효과 1: 우상단에서 시작하는 원형 그라디언트 */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" />
{/* 추가 그라디언트 효과 2: 좌하단에서 시작하는 원형 그라디언트 */} {/* 추가 그라디언트 효과 2: 좌하단에서 시작하는 원형 그라디언트 */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,_var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" />
{/* ========== 애니메이션 블러 효과 ========== */} {/* ========== 애니메이션 블러 효과 ========== */}
{/* 부드럽게 깜빡이는 원형 블러로 생동감 표현 */} {/* 부드럽게 깜빡이는 원형 블러로 생동감 표현 */}
@@ -61,7 +61,7 @@ export default async function LoginPage({
{/* ========== 카드 헤더 영역 ========== */} {/* ========== 카드 헤더 영역 ========== */}
<CardHeader className="space-y-3 text-center"> <CardHeader className="space-y-3 text-center">
{/* 아이콘 배경: 그라디언트 원형 */} {/* 아이콘 배경: 그라디언트 원형 */}
<div className="mx-auto mb-2 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-gray-800 to-black shadow-lg dark:from-white dark:to-gray-200"> <div className="mx-auto mb-2 flex h-16 w-16 items-center justify-center rounded-2xl bg-linear-to-br from-gray-800 to-black shadow-lg dark:from-white dark:to-gray-200">
<span className="text-4xl">👋</span> <span className="text-4xl">👋</span>
</div> </div>
{/* 페이지 제목 */} {/* 페이지 제목 */}

View File

@@ -39,9 +39,9 @@ export default async function ResetPasswordPage({
const { message } = params; const { message } = params;
return ( return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900"> <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-linear-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900">
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,_var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" />
<div className="absolute left-1/4 top-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-300/20 blur-3xl dark:bg-gray-700/20" /> <div className="absolute left-1/4 top-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-300/20 blur-3xl dark:bg-gray-700/20" />
<div className="absolute bottom-1/4 right-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-400/20 blur-3xl delay-700 dark:bg-gray-600/20" /> <div className="absolute bottom-1/4 right-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-400/20 blur-3xl delay-700 dark:bg-gray-600/20" />
@@ -51,7 +51,7 @@ export default async function ResetPasswordPage({
<Card className="border-white/20 bg-white/70 shadow-2xl backdrop-blur-xl dark:border-white/10 dark:bg-gray-900/70"> <Card className="border-white/20 bg-white/70 shadow-2xl backdrop-blur-xl dark:border-white/10 dark:bg-gray-900/70">
<CardHeader className="space-y-3 text-center"> <CardHeader className="space-y-3 text-center">
<div className="mx-auto mb-2 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-gray-800 to-black shadow-lg dark:from-white dark:to-gray-200"> <div className="mx-auto mb-2 flex h-16 w-16 items-center justify-center rounded-2xl bg-linear-to-br from-gray-800 to-black shadow-lg dark:from-white dark:to-gray-200">
<span className="text-sm font-semibold">PW</span> <span className="text-sm font-semibold">PW</span>
</div> </div>
<CardTitle className="text-3xl font-bold tracking-tight"> <CardTitle className="text-3xl font-bold tracking-tight">

View File

@@ -17,10 +17,10 @@ export default async function SignupPage({
const { message } = await searchParams; const { message } = await searchParams;
return ( return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900"> <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-linear-to-br from-gray-50 via-white to-gray-100 px-4 py-12 dark:from-black dark:via-gray-950 dark:to-gray-900">
{/* 배경 그라데이션 효과 */} {/* 배경 그라데이션 효과 */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,var(--tw-gradient-stops))] from-gray-200/30 via-gray-100/15 to-transparent dark:from-gray-800/30 dark:via-gray-900/20" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,_var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" /> <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom_left,var(--tw-gradient-stops))] from-gray-300/30 via-gray-200/15 to-transparent dark:from-gray-700/30 dark:via-gray-800/20" />
{/* 애니메이션 블러 효과 */} {/* 애니메이션 블러 효과 */}
<div className="absolute left-1/4 top-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-300/20 blur-3xl dark:bg-gray-700/20" /> <div className="absolute left-1/4 top-1/4 h-64 w-64 animate-pulse rounded-full bg-gray-300/20 blur-3xl dark:bg-gray-700/20" />
@@ -32,7 +32,7 @@ export default async function SignupPage({
<Card className="border-white/20 bg-white/70 shadow-2xl backdrop-blur-xl dark:border-white/10 dark:bg-gray-900/70"> <Card className="border-white/20 bg-white/70 shadow-2xl backdrop-blur-xl dark:border-white/10 dark:bg-gray-900/70">
<CardHeader className="space-y-3 text-center"> <CardHeader className="space-y-3 text-center">
<div className="mx-auto mb-2 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-gray-800 to-black shadow-lg dark:from-white dark:to-gray-200"> <div className="mx-auto mb-2 flex h-16 w-16 items-center justify-center rounded-2xl bg-linear-to-br from-gray-800 to-black shadow-lg dark:from-white dark:to-gray-200">
<span className="text-4xl">🚀</span> <span className="text-4xl">🚀</span>
</div> </div>
<CardTitle className="text-3xl font-bold tracking-tight"> <CardTitle className="text-3xl font-bold tracking-tight">

View File

@@ -238,7 +238,7 @@ export async function signup(formData: FormData) {
// 이메일 인증 완료 후 리다이렉트될 URL // 이메일 인증 완료 후 리다이렉트될 URL
// 로컬 개발 환경: http://localhost:3001/auth/callback // 로컬 개발 환경: http://localhost:3001/auth/callback
// 프로덕션: NEXT_PUBLIC_BASE_URL 환경 변수에 설정된 주소 // 프로덕션: NEXT_PUBLIC_BASE_URL 환경 변수에 설정된 주소
emailRedirectTo: `${process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3001"}/auth/callback`, emailRedirectTo: `${process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3001"}/auth/callback?auth_type=signup`,
}, },
}); });

View File

@@ -128,7 +128,7 @@ export default function ResetPasswordForm() {
<Button <Button
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className="h-11 w-full bg-gradient-to-r from-gray-900 to-black font-semibold text-white shadow-lg transition-all hover:from-black hover:to-gray-800 hover:shadow-xl dark:from-white dark:to-gray-100 dark:text-black dark:hover:from-gray-100 dark:hover:to-white" className="h-11 w-full bg-linear-to-r from-gray-900 to-black font-semibold text-white shadow-lg transition-all hover:from-black hover:to-gray-800 hover:shadow-xl dark:from-white dark:to-gray-100 dark:text-black dark:hover:from-gray-100 dark:hover:to-white"
> >
{isLoading ? ( {isLoading ? (
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">