diff --git a/src/app/(empty-layout)/(auth)/login/callback/page.tsx b/src/app/(empty-layout)/(auth)/login/callback/page.tsx
index 5cac38b..a316c32 100644
--- a/src/app/(empty-layout)/(auth)/login/callback/page.tsx
+++ b/src/app/(empty-layout)/(auth)/login/callback/page.tsx
@@ -1,67 +1,53 @@
'use client';
-
-import { useAuthStore } from '@/entities/user';
-import { userService } from '@/entities/user/api/userService';
-import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
+import { useRouter } from 'next/navigation';
import { toast } from 'react-toastify';
+import { useAuthStore } from '@/entities/User';
+import { userService } from '@/entities/User/api/userService';
export default function LoginCallbackPage() {
const router = useRouter();
const { socialLoginComplete } = useAuthStore();
useEffect(() => {
- const handleSocialLoginCallback = async () => {
+ const handleKakaoLoginCallback = async () => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
- const provider = urlParams.get('provider');
- const savedState = localStorage.getItem(`oauth_state_${provider}`);
+ // state 검증
+ const savedState = localStorage.getItem('kakao_oauth_state');
if (!state || state !== savedState) {
toast.error('인증 상태가 일치하지 않습니다.');
router.push('/login');
return;
}
- if (!code || !provider) {
+ if (!code) {
toast.error('유효하지 않은 인증 정보입니다.');
router.push('/login');
return;
}
try {
- const result = await userService.socialLoginCallback({
- provider,
- code,
- state
- });
+ const result = await userService.kakaoLoginCallback(code, state);
+ await socialLoginComplete(result);
- if (result) {
- await socialLoginComplete(result);
- toast.success('로그인 성공');
+ // state 제거
+ localStorage.removeItem('kakao_oauth_state');
- // 로컬 스토리지 상태 초기화
- localStorage.removeItem(`oauth_state_${provider}`);
-
- router.push('/');
- } else {
- throw new Error('로그인 정보를 받아오지 못했습니다.');
- }
+ toast.success('카카오 로그인 성공');
+ router.push('/');
} catch (error) {
- console.error('소셜 로그인 에러:', error);
-
- const errorMessage =
- error instanceof Error
- ? error.message
- : '소셜 로그인에 실패했습니다.';
-
- toast.error(errorMessage);
+ console.error('카카오 로그인 에러:', error);
+ toast.error(
+ error instanceof Error ? error.message : '카카오 로그인 실패'
+ );
router.push('/login');
}
};
- handleSocialLoginCallback();
+ handleKakaoLoginCallback();
}, [router, socialLoginComplete]);
return
로그인 처리 중...
;
diff --git a/src/app/(empty-layout)/(auth)/login/page.tsx b/src/app/(empty-layout)/(auth)/login/page.tsx
index 159728f..5d9ca65 100644
--- a/src/app/(empty-layout)/(auth)/login/page.tsx
+++ b/src/app/(empty-layout)/(auth)/login/page.tsx
@@ -3,36 +3,27 @@ import Image from 'next/image';
import Link from 'next/link';
import { toast } from 'react-toastify';
import useRedirect from '@/features/auth/hooks/useRedirect';
-import { SOCIAL_PROVIDERS } from '@/features/auth/model/constants';
-import { SocialLoginButton } from '@/features/auth/ui/SocialLoginButton';
import LoginContainer from '@/features/auth/ui/LoginContainer';
-import { AxiosError } from 'axios';
-import { useAuthStore, SocialProvider } from '@/entities/user';
+import { useAuthStore } from '@/entities/user';
export default function LoginPage() {
- const { isAuthenticated, userId, tokens, socialLogin } = useAuthStore();
+ const { isAuthenticated, userId, tokens, kakaoLogin } = useAuthStore();
useRedirect(isAuthenticated, userId, tokens);
- const handleSocialLogin = async (provider: SocialProvider) => {
+
+ const handleKakaoLogin = async () => {
try {
- await socialLogin(provider);
+ // 상태(state) 생성 및 저장
+ const state = crypto.randomUUID();
+ localStorage.setItem('kakao_oauth_state', state);
+
+ // 카카오 로그인 URL로 리다이렉트
+ await kakaoLogin(state);
} catch (error) {
- const errorMsg = error as AxiosError;
- console.error(errorMsg);
- toast.error('소셜 로그인 실패');
+ console.error(error);
+ toast.error('카카오 로그인 실패');
}
};
- const SocialLoginButtons = () => (
-
@@ -48,7 +39,13 @@ export default function LoginPage() {
-
+
+
+
회원가입
diff --git a/src/app/(empty-layout)/(auth)/oauth/page.tsx b/src/app/(empty-layout)/(auth)/oauth/page.tsx
deleted file mode 100644
index f88a9a9..0000000
--- a/src/app/(empty-layout)/(auth)/oauth/page.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-'use client';
-
-import { userService } from '@/entities/user/api/userService';
-import { useRouter } from 'next/navigation';
-import { useEffect } from 'react';
-
-export default function OAuthCallback() {
- const router = useRouter();
-
- useEffect(() => {
- const { code, provider } = router.query;
-
- const performOAuthLogin = async () => {
- try {
- if (provider) {
- const result = await userService.handleOAuthCallback(provider);
-
- // 토큰 저장
- localStorage.setItem('access_token', result.access_token);
- localStorage.setItem('refresh_token', result.refresh_token);
-
- // 로그인 후 리다이렉트
- router.push('/dashboard');
- }
- } catch (error) {
- // 에러 처리
- console.error(error);
- router.push('/login');
- }
- };
-
- if (code && provider) {
- performOAuthLogin();
- }
- }, [router.query]);
-
- return
로그인 처리 중...
;
-}
diff --git a/src/entities/User/api/userService.ts b/src/entities/User/api/userService.ts
index e349dfa..012d13d 100644
--- a/src/entities/User/api/userService.ts
+++ b/src/entities/User/api/userService.ts
@@ -1,5 +1,5 @@
import { api } from '@/shared/api/client';
-import axios, { AxiosError } from 'axios';
+import axios from 'axios';
import type {
UserLoginDTO,
UserJoinDTO,
@@ -43,12 +43,35 @@ export const userService = {
try {
const response = await axiosInstance.post(
`${process.env.NEXT_PUBLIC_API_URL}/api/oauth2/${params.provider}/callback`,
- { provider: params.provider, code: params.code, state: params.state }
+ {
+ provider: params.provider,
+ code: params.code,
+ state: params.state
+ }
);
- if (!response.data || !response.data.userId) {
- throw new Error('소셜 로그인 정보를 받아올 수 없습니다.');
+ return {
+ authentication: 'Bearer',
+ userId: response.data.userId,
+ access_token: response.data.accessToken,
+ refresh_token: response.data.refreshToken
+ };
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ const errorResponse = error.response?.data;
+ throw new Error(
+ errorResponse?.message || '소셜 로그인에 실패했습니다.'
+ );
}
+ throw error;
+ }
+ },
+ kakaoLoginCallback: async (code: string, state: string) => {
+ try {
+ const response = await axiosInstance.post(
+ `${process.env.NEXT_PUBLIC_API_URL}/api/oauth2/kakao/callback`,
+ { code, state }
+ );
return {
authentication: 'Bearer',
@@ -57,11 +80,15 @@ export const userService = {
refresh_token: response.data.refreshToken
};
} catch (error) {
- const errorMessage = error as AxiosError;
- console.error(errorMessage);
+ if (axios.isAxiosError(error)) {
+ const errorResponse = error.response?.data;
+ throw new Error(
+ errorResponse?.message || '카카오 로그인에 실패했습니다.'
+ );
+ }
+ throw error;
}
},
-
join: async (joinData: UserJoinDTO) => {
if (!joinData.email) {
throw new Error('이메일을 입력해주세요.');
diff --git a/src/entities/User/model/store/authStore.ts b/src/entities/User/model/store/authStore.ts
index 620c8bc..16d45fa 100644
--- a/src/entities/User/model/store/authStore.ts
+++ b/src/entities/User/model/store/authStore.ts
@@ -60,11 +60,13 @@ export const useAuthStore = create
()(
return false;
}
},
- socialLogin: async (provider: 'google' | 'kakao' | 'naver') => {
+ socialLogin: async (provider: SocialProvider, state?: string) => {
try {
- const authorizationUrl = `${process.env.NEXT_PUBLIC_API_URL}/api/oauth2/${provider}`;
- window.location.href = authorizationUrl;
+ const authorizationUrl = state
+ ? `${process.env.NEXT_PUBLIC_API_URL}/api/oauth2/${provider}?state=${encodeURIComponent(state)}`
+ : `${process.env.NEXT_PUBLIC_API_URL}/api/oauth2/${provider}`;
+ window.location.href = authorizationUrl;
return true;
} catch (error) {
set({
@@ -75,12 +77,27 @@ export const useAuthStore = create()(
return false;
}
},
+ kakaoLogin: async (state: string) => {
+ try {
+ const authorizationUrl = `${process.env.NEXT_PUBLIC_API_URL}/api/oauth2/kakao?state=${encodeURIComponent(state)}`;
+ window.location.href = authorizationUrl;
+ return true;
+ } catch (error) {
+ set({
+ isAuthenticated: false,
+ isLoading: false,
+ error: error instanceof Error ? error.message : '카카오 로그인 실패'
+ });
+ return false;
+ }
+ },
socialLoginComplete: async (loginResponse: {
authentication: string;
access_token: string;
refresh_token: string;
userId: string;
}) => {
+ // 기존 로그인 완료 로직과 동일
document.cookie = `auth-storage=${JSON.stringify({
state: {
userId: loginResponse.userId,
diff --git a/src/entities/User/model/types/AuthState.ts b/src/entities/User/model/types/AuthState.ts
index 2602c7a..0dcf8cc 100644
--- a/src/entities/User/model/types/AuthState.ts
+++ b/src/entities/User/model/types/AuthState.ts
@@ -19,4 +19,5 @@ export interface AuthState {
refreshToken: () => Promise;
deleteUser: () => Promise;
socialLoginComplete: (loginResponse: LoginResponse) => Promise;
+ kakaoLogin: (state: string) => Promise;
}
diff --git a/src/features/meeting/ui/MeetingDetail/MeetingDetail.tsx b/src/features/meeting/ui/MeetingDetail/MeetingDetail.tsx
index 0bf65b9..a36c306 100644
--- a/src/features/meeting/ui/MeetingDetail/MeetingDetail.tsx
+++ b/src/features/meeting/ui/MeetingDetail/MeetingDetail.tsx
@@ -6,9 +6,9 @@ import { toast } from 'react-toastify';
import { useMeetingBoardDetail } from '@/features/meeting/model/queries';
import { FullScreenLoading } from '@/shared/ui/components/Loading/Loading';
import ErrorFallback from '@/shared/ui/components/ErrorBoundary/ErrorFallback';
-import { useAuthStore } from '@/entities/User/model/store/authStore';
import { MeetingHeader, MeetingInfo, MeetingTabs } from '@/features/meeting';
import Link from 'next/link';
+import { useAuthStore } from '@/entities/user';
export default function MeetingDetail() {
const params = useParams();
diff --git a/src/features/mypage/ui/MypageTap.tsx b/src/features/mypage/ui/MypageTap.tsx
index 9e3b44e..352035d 100644
--- a/src/features/mypage/ui/MypageTap.tsx
+++ b/src/features/mypage/ui/MypageTap.tsx
@@ -1,7 +1,6 @@
'use client';
import { useMemo, useState } from 'react';
import { UserInfo } from './UserInfo';
-import { UserReviews } from '@/features/review/ui/UserReviews';
type ProfileTabType = 'intro' | 'review' | 'favorites';
@@ -12,8 +11,6 @@ export const MypageTap = () => {
switch (activeTab) {
case 'intro':
return ;
- case 'review':
- return ;
}
}, [activeTab]);
@@ -29,11 +26,7 @@ export const MypageTap = () => {
? 'border-b-2 border-primary-800 text-primary-800'
: 'border-b-2 border-white text-gray-500'
}`}>
- {tab === 'intro'
- ? '소개글'
- : tab === 'review'
- ? '내 후기'
- : '즐겨찾기'}
+ {tab === 'intro' ? '소개글' : ''}
))}
diff --git a/src/features/mypage/ui/UserProfile.tsx b/src/features/mypage/ui/UserProfile.tsx
index e0b764d..90fb91d 100644
--- a/src/features/mypage/ui/UserProfile.tsx
+++ b/src/features/mypage/ui/UserProfile.tsx
@@ -2,8 +2,8 @@
import Image from 'next/image';
import { LogOut, Pencil } from 'lucide-react';
import { useRouter } from 'next/navigation';
-import { useAuthStore } from '@/entities/User';
-import { useUserQuery } from '@/entities/User/model/userQueries';
+import { useAuthStore } from '@/entities/user';
+import { useUserQuery } from '@/entities/user/model/userQueries';
export const UserProfile = () => {
const router = useRouter();
diff --git a/src/features/notifications/api/pushService.ts b/src/features/notifications/api/pushService.ts
index 6412463..5cdc871 100644
--- a/src/features/notifications/api/pushService.ts
+++ b/src/features/notifications/api/pushService.ts
@@ -26,11 +26,9 @@ export enum NotificationMessageType {
// 알림 응답 타입
export interface NotificationResponse {
- id: number;
+ notificationId: number;
title: string;
message: string;
- isRead: boolean;
- createdAt: string;
}
class PushService {
@@ -44,7 +42,8 @@ class PushService {
// 알림 목록 조회
async getNotifications(): Promise