Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// Docker 배포를 위한 standalone 모드 활성화
// 해당 설정은 프로덕션 빌드 시 필요한 파일만 .next/standalone 폴더에 복사됨.
images: {
domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
},
output: 'standalone',

// 외부 이미지 도메인 허용
images: {
domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
remotePatterns: [
{
protocol: 'https',
Expand Down
16 changes: 16 additions & 0 deletions src/app/(non-header)/oauth/kakao/components/KakaoLoading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

interface KakaoLoadingProps {
message: string;
}

export default function KakaoLoading({ message }: KakaoLoadingProps) {
return (
<div className='flex min-h-screen items-center justify-center bg-white'>
<div className='flex flex-col items-center'>
<div className='mb-6 h-30 w-30 animate-spin rounded-full border-4 border-yellow-100 border-t-transparent' />
<p className='mt-4 text-lg font-semibold text-black'>{message}</p>
</div>
</div>
);
}
117 changes: 117 additions & 0 deletions src/app/(non-header)/oauth/kakao/sign-in/kakaoSigninCallbackPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use client';

export const dynamic = 'force-dynamic';

import Popup from '@/components/Popup';
import useUserStore from '@/stores/authStore';
import { PopupState } from '@/types/popupTypes';
import axios from 'axios';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import KakaoLoading from '../components/KakaoLoading';

/**
* 카카오 로그인 콜백 처리 페이지 컴포넌트입니다.
*
* 카카오 인증 서버에서 리디렉션된 `code` 쿼리 파라미터를 받아
* 백엔드(`/api/auth/kakao/sign-in`)에 로그인 요청을 보냅니다.
* 응답에 포함된 사용자 정보를 Zustand 스토어에 저장하고,
* 로그인 성공 시 메인 페이지(`/`)로 이동합니다.
*
* 오류 발생 시 상태 코드에 따라 다른 알림 메시지를 출력하고
* 적절한 페이지(`/signup` 또는 `/login`)로 이동합니다.
*
* 주요 흐름:
* 1. `code` 파라미터 확인
* 2. POST 요청으로 로그인 시도
* 3. 사용자 존재 시 상태 저장 및 리다이렉트
* 4. 오류 상황에 따라 알림 및 경로 분기
*
*/
export default function KakaoSigninCallbackPage() {
const router = useRouter();
const searchParams = useSearchParams();
const setUser = useUserStore((state) => state.setUser);

const [popup, setPopup] = useState<PopupState>({
message: '',
redirect: '',
isOpen: false,
});

useEffect(() => {
const code = searchParams.get('code');
if (!code) return;

/**
* 카카오 로그인 처리를 위한 비동기 함수입니다.
* - 백엔드에 인증 코드 전송
* - 사용자 정보 저장
* - 오류 처리 및 알림
*/
const handleKakaoLogin = async () => {
try {
const res = await axios.post('/api/auth/kakao/sign-in', { code });
const data = res.data;

if (data.user) {
setUser(data.user);
router.push('/');
}
} catch (err: unknown) {
if (axios.isAxiosError(err)) {
const status = err.response?.status;

switch (status) {
case 404:
setPopup({
message: '가입된 회원이 아닙니다. 회원가입을 진행해주세요.',
redirect: '/signup',
isOpen: true,
});
break;
case 500:
setPopup({
message: '서버 오류입니다. 잠시 후 다시 시도해주세요.',
redirect: '/login',
isOpen: true,
});
break;
default:
setPopup({
message: '카카오 로그인 실패',
redirect: '/login',
isOpen: true,
});
break;
}
} else {
setPopup({
message: '사용자 정보가 없습니다. 다시 시도해주세요.',
redirect: '/login',
isOpen: true,
});
}
}
};

handleKakaoLogin();
}, [searchParams, router]);

return (
<>
<KakaoLoading message='카카오 로그인 처리 중...' />

<Popup
isOpen={popup.isOpen}
type='alert'
onClose={() => {
setPopup((prev) => ({ ...prev, isOpen: false }));
router.push(popup.redirect);
}}
>
{popup.message}
</Popup>
</>
);
}
95 changes: 10 additions & 85 deletions src/app/(non-header)/oauth/kakao/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,11 @@
// 'use client';

// export const dynamic = 'force-dynamic';

// import useUserStore from '@/stores/authStore';
// import axios from 'axios';
// import { useRouter, useSearchParams } from 'next/navigation';
// import { useEffect } from 'react';

// /**
// * 카카오 로그인 콜백 처리 페이지 컴포넌트입니다.
// *
// * 카카오 인증 서버에서 리디렉션된 `code` 쿼리 파라미터를 받아
// * 백엔드(`/api/auth/kakao/sign-in`)에 로그인 요청을 보냅니다.
// * 응답에 포함된 사용자 정보를 Zustand 스토어에 저장하고,
// * 로그인 성공 시 메인 페이지(`/`)로 이동합니다.
// *
// * 오류 발생 시 상태 코드에 따라 다른 알림 메시지를 출력하고
// * 적절한 페이지(`/signup` 또는 `/login`)로 이동합니다.
// *
// * 주요 흐름:
// * 1. `code` 파라미터 확인
// * 2. POST 요청으로 로그인 시도
// * 3. 사용자 존재 시 상태 저장 및 리다이렉트
// * 4. 오류 상황에 따라 알림 및 경로 분기
// *
// * @component
// * @returns {JSX.Element} "카카오 로그인 처리 중입니다..."라는 텍스트를 포함한 JSX
// */
export default function KakaoSigninCallbackPage() {
return <div>카카오 로그인 처리 예정</div>;
import { Suspense } from 'react';
import KakaoSigninCallbackPage from './kakaoSigninCallbackPage';
import Loading from '@/components/Loading';

export default function KakaoSigninPage() {
return (
<Suspense fallback={<Loading />}>
<KakaoSigninCallbackPage />
</Suspense>
);
}
// const router = useRouter();
// const searchParams = useSearchParams();
// const setUser = useUserStore((state) => state.setUser);

// useEffect(() => {
// const code = searchParams.get('code');
// if (!code) return;

// /**
// * 카카오 로그인 처리를 위한 비동기 함수입니다.
// * - 백엔드에 인증 코드 전송
// * - 사용자 정보 저장
// * - 오류 처리 및 알림
// */
// const handleKakaoLogin = async () => {
// try {
// const res = await axios.post('/api/auth/kakao/sign-in', { code });
// const data = res.data;

// if (data.user) {
// setUser(data.user);
// router.push('/');
// }
// } catch (err: unknown) {
// if (axios.isAxiosError(err)) {
// const status = err.response?.status;
// const message = err.response?.data?.error;

// switch (status) {
// case 404:
// alert('가입된 회원이 아닙니다. 회원가입을 진행해주세요.');
// router.push('/signup');
// break;
// case 500:
// alert('서버 오류입니다. 잠시 후 다시 시도해주세요.');
// router.push('/login');
// break;
// default:
// alert(message || '카카오 로그인 실패');
// router.push('/login');
// break;
// }
// } else {
// alert('사용자 정보가 없습니다. 다시 시도해주세요.');
// router.push('/login');
// }
// }
// };

// handleKakaoLogin();
// }, [searchParams, router]);

// return <div>카카오 로그인 처리 중입니다...</div>;
// }
143 changes: 143 additions & 0 deletions src/app/(non-header)/oauth/kakao/sign-up/kakaoSignupCallbackPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
'use client';

export const dynamic = 'force-dynamic';

import Popup from '@/components/Popup';
import useUserStore from '@/stores/authStore';
import { PopupState } from '@/types/popupTypes';
import axios from 'axios';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import KakaoLoading from '../components/KakaoLoading';

const adjectives = [
'상냥한',
'용감한',
'조용한',
'귀여운',
'멋진',
'차분한',
'빠른',
'신비한',
];
const animals = [
'고양이',
'호랑이',
'강아지',
'여우',
'곰',
'사자',
'토끼',
'다람쥐',
];
Comment on lines +13 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

상수를 별도 파일로 분리하는 것을 고려해보세요.

형용사와 동물 배열이 컴포넌트 내부에 하드코딩되어 있습니다. 코드 재사용성과 유지보수성을 위해 별도의 상수 파일로 분리하는 것을 권장합니다.

예시로 src/constants/nickname.ts 파일을 생성할 수 있습니다:

export const ADJECTIVES = [
  '상냥한',
  '용감한',
  '조용한',
  // ... 나머지 형용사들
];

export const ANIMALS = [
  '고양이',
  '호랑이',
  '강아지',
  // ... 나머지 동물들
];
🤖 Prompt for AI Agents
In src/app/(non-header)/oauth/kakao/sign-up/kakaoSignupCallbackPage.tsx around
lines 13 to 32, the adjective and animal arrays are hardcoded inside the
component file. To improve code reuse and maintainability, move these arrays
into a separate constants file, for example, create src/constants/nickname.ts
exporting ADJECTIVES and ANIMALS arrays. Then import these constants into the
component file and replace the hardcoded arrays with the imported constants.


/**
* 카카오 회원가입 콜백 처리 페이지 컴포넌트입니다.
*
* 카카오 인증 서버에서 전달된 `code` 쿼리 파라미터를 바탕으로
* 백엔드(`/api/auth/kakao/sign-up`)에 회원가입 요청을 보내고,
* 성공적으로 가입된 사용자 정보를 Zustand 스토어에 저장한 후
* 로그인 페이지(`/login`)로 이동합니다.
*
* 요청 시 랜덤한 형용사 + 동물 조합으로 닉네임을 생성하여 함께 전송합니다.
*
* 주요 흐름:
* 1. `code` 쿼리 파라미터 존재 여부 확인
* 2. 랜덤 닉네임 생성
* 3. 서버에 회원가입 요청 전송
* 4. 성공 시 사용자 정보 저장 및 리다이렉션
* 5. 실패 시 알림 후 회원가입 페이지로 이동
*/
export default function KakaoSignupCallbackPage() {
const router = useRouter();
const searchParams = useSearchParams();
const setUser = useUserStore((state) => state.setUser);

const [popup, setPopup] = useState<PopupState>({
message: '',
redirect: '',
isOpen: false,
});

useEffect(() => {
const code = searchParams.get('code');

if (!code) return;

const nickname = `${adjectives[Math.floor(Math.random() * adjectives.length)]}${animals[Math.floor(Math.random() * animals.length)]}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

닉네임 중복 가능성을 고려해보세요.

현재 Math.random()을 사용한 닉네임 생성은 중복될 가능성이 있습니다. 서버에서 중복 체크를 하거나, 타임스탬프를 추가하는 등의 방법을 고려해보세요.

예시 개선안:

const nickname = `${adjectives[Math.floor(Math.random() * adjectives.length)]}${animals[Math.floor(Math.random() * animals.length)]}${Date.now().toString().slice(-4)}`;
🤖 Prompt for AI Agents
In src/app/(non-header)/oauth/kakao/sign-up/kakaoSignupCallbackPage.tsx at line
67, the current nickname generation using Math.random() can produce duplicates.
To fix this, append a unique element such as a timestamp substring to the
generated nickname to reduce collision risk. For example, concatenate the last
few digits of Date.now() to the nickname string after the adjective and animal
parts.


/**
* 카카오 회원가입 처리를 위한 비동기 함수입니다.
* - 인증 코드와 랜덤 닉네임을 포함하여 서버에 POST 요청
* - 응답으로 받은 사용자 정보를 상태에 저장
* - 성공 시 로그인 페이지로, 실패 시 회원가입 페이지로 이동
*/
const handleKakaoSignup = async () => {
try {
const res = await axios.post('/api/auth/kakao/sign-up', {
code,
nickname,
});
const data = res.data;

if (data.user) {
setUser(data.user);
router.push('/login');
}
} catch (err: unknown) {
if (axios.isAxiosError(err)) {
const status = err.response?.status;

switch (status) {
case 400:
setPopup({
message: '이미 가입된 회원입니다.',
redirect: '/login',
isOpen: true,
});
break;
case 500:
setPopup({
message: '서버 오류입니다. 잠시 후 다시 시도해주세요.',
redirect: '/signup',
isOpen: true,
});
break;
default:
setPopup({
message: '회원가입에 실패했습니다. 다시 시도해주세요.',
redirect: '/signup',
isOpen: true,
});
break;
}
} else {
setPopup({
message: '예기치 못한 오류가 발생했습니다. 다시 시도해주세요.',
redirect: '/signup',
isOpen: true,
});
}
}
};

handleKakaoSignup();
}, [searchParams, router]);

return (
<>
<KakaoLoading message='카카오 회원가입 처리 중...' />

<Popup
isOpen={popup.isOpen}
type='alert'
onClose={() => {
setPopup((prev) => ({ ...prev, isOpen: false }));
router.push(popup.redirect);
}}
>
{popup.message}
</Popup>
</>
);
}
Loading