Skip to content

Commit b476df1

Browse files
authored
Merge pull request #48 from SWMTheFirstTake/dev
Dev
2 parents e1c0a3c + 1238c48 commit b476df1

25 files changed

+670
-125
lines changed

app/chat/page.tsx

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ import ChatPanel from '@/components/chat/message/ChatPanel';
77
import { useAtomValue, useSetAtom } from 'jotai';
88
import { panelAtom } from '@/atoms/chatAtoms';
99
import { Suspense, useEffect } from 'react';
10+
import { userAtom } from '@/atoms/authAtoms';
11+
import { useRouter } from 'next/navigation';
1012

1113
export default function Chat() {
1214
const panel = useAtomValue(panelAtom);
1315
const setPanel = useSetAtom(panelAtom);
16+
const user = useAtomValue(userAtom);
17+
const router = useRouter();
1418

1519
// lg 이상일 때 panel이 'chat'이면 자동으로 'closet'으로 전환
1620
useEffect(() => {
@@ -29,6 +33,18 @@ export default function Chat() {
2933
return () => mq.removeEventListener('change', handler);
3034
}, [panel, setPanel]);
3135

36+
// useEffect(() => {
37+
// console.log(user);
38+
// if (!user) {
39+
// alert('로그인이 필요합니다.');
40+
// router.push('/signin');
41+
// }
42+
// }, [user, router]);
43+
44+
// if (!user) {
45+
// return null;
46+
// }
47+
3248
return (
3349
<Suspense>
3450
<div className="flex flex-col">
@@ -41,26 +57,16 @@ export default function Chat() {
4157
<div className="hidden lg:flex w-full lg:w-1/2 h-full lg:transition-all lg:duration-300">
4258
<ChatPanel />
4359
</div>
44-
{panel === 'closet' && (
45-
<div className="hidden lg:flex flex-col h-full w-full lg:w-1/2 bg-beige border-l border-navy-200">
46-
<div className="flex-shrink-0">
47-
<ChatHeader />
48-
</div>
49-
<div className="flex-1 min-h-0">
50-
<ClosetPanel />
51-
</div>
60+
61+
<div className="hidden lg:flex flex-col h-full w-full lg:w-1/2 bg-beige border-l border-navy-200">
62+
<div className="flex-shrink-0">
63+
<ChatHeader />
5264
</div>
53-
)}
54-
{panel === 'fitting' && (
55-
<div className="hidden lg:flex flex-col h-full w-full lg:w-1/2 bg-beige border-l border-navy-200">
56-
<div className="flex-shrink-0">
57-
<ChatHeader />
58-
</div>
59-
<div className="flex-1 min-h-0">
60-
<FittingPanel />
61-
</div>
65+
<div className="flex-1 min-h-0">
66+
{panel === 'closet' && <ClosetPanel />}
67+
{panel === 'fitting' && <FittingPanel />}
6268
</div>
63-
)}
69+
</div>
6470

6571
{/* 모바일/태블릿: 단일 패널 레이아웃 */}
6672
<div className="lg:hidden w-full h-[calc(100vh-5rem)]">

app/signin/page.tsx

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import SigninForm from '@/components/auth/SigninForm';
21
import SigninSNSForm from '@/components/auth/SigninSNSForm';
3-
import Link from 'next/link';
42

53
export default function SigninPage() {
64
return (
@@ -14,26 +12,8 @@ export default function SigninPage() {
1412
</div>
1513

1614
<div className="space-y-6">
17-
<SigninForm />
18-
19-
<div className="relative">
20-
<div className="absolute inset-0 flex items-center">
21-
<div className="w-full border-t border-gray-200 dark:border-gray-700"></div>
22-
</div>
23-
<div className="relative flex justify-center text-sm">
24-
<span className="px-4 bg-white/80 dark:bg-gray-800/80 text-gray-500 dark:text-gray-400">또는</span>
25-
</div>
26-
</div>
27-
2815
<SigninSNSForm />
2916
</div>
30-
31-
<div className="text-center text-sm text-gray-600 dark:text-gray-400">
32-
계정이 없으신가요?{' '}
33-
<Link href="/signup" className="font-medium text-navy hover:text-blue dark:text-indigo-400">
34-
회원가입
35-
</Link>
36-
</div>
3717
</div>
3818
</div>
3919
);

src/api/authAPI.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { requestAPI } from '@/api/API';
2+
3+
/// GET
4+
5+
export const getAuthMe = async () => requestAPI<any>(`/api/auth/me`, 'GET');
6+
7+
export const getAuthKakaoCallback = async (): Promise<APIResponse<User>> => {
8+
const response = await requestAPI<APIUser>(`/api/auth/kakao/callback`, 'GET');
9+
if (response.status == 'fail') return response;
10+
return {
11+
status: response.status,
12+
message: response.message,
13+
data: {
14+
userId: response.data.userId,
15+
username: response.data.nickname,
16+
},
17+
};
18+
};
19+
20+
/// POST
21+
export const postAuthRefresh = async () => requestAPI<any>(`/api/auth/refresh`, 'POST');
22+
23+
export const postAuthLogout = async () => requestAPI<any>(`/api/auth/logout`, 'POST');

src/api/fittingAPI.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { requestAPI } from '@/api/API';
2+
3+
/// GET
4+
export const getFittingProxyImage = async () => requestAPI<APIFitting>('/api/fitting/proxy-image', 'GET');
5+
6+
export const getFittingStatusTaskId = async (taskId: string) =>
7+
requestAPI<APIFitting>(`/api/fitting/status/${taskId}`, 'GET');
8+
9+
export const getFittingProxyTest = async () => requestAPI<APIFitting>('/api/fitting/proxy-test', 'GET');
10+
11+
/// POST
12+
export const postFittingTryon = async () => requestAPI<APIFitting>('/api/fitting/try-on', 'POST');
13+
14+
export const postFittingTryonCombo = async () => requestAPI<APIFitting>('/api/fitting/try-on/combo', 'POST');

src/atoms/authAtoms.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
import { atom } from 'jotai';
2+
import { getAuthCookie } from '@/lib/cookies';
23

3-
export const userAtom = atom<User | null>(null);
4+
// // 초기값을 쿠키에서 가져오도록 설정
5+
// export const userAtom = atom<User | null>(() => {
6+
// // SSR 환경에서는 null 반환
7+
// if (typeof window === 'undefined') return null;
8+
9+
// // 클라이언트 환경에서는 쿠키에서 사용자 정보 가져오기
10+
// return getAuthCookie();
11+
// });
12+
13+
export const userAtom = atom<User | null>({ userId: 'adsf', username: 'mindul' });

src/atoms/chatAtoms.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const examplesAtom = atom<string[]>([
1313
'꾸민 듯 안 꾸민 듯한 꾸안꾸 패션을 추구해보고 싶어.',
1414
]);
1515

16-
export const isAIRespondingAtom = atom<'style' | 'trend' | 'color' | 'codi' | ''>('');
16+
export const isAIRespondingAtom = atom<boolean>(false);
1717
export const isSidebarOpenAtom = atom<boolean>(false);
1818

1919
export const panelAtom = atom<'chat' | 'closet' | 'fitting'>('chat');
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,40 @@
11
'use client';
22

3-
import { useEffect } from 'react';
3+
import { useEffect, useState } from 'react';
44
import { useSetAtom } from 'jotai';
55
import { userAtom } from '@/atoms/authAtoms';
66
import { useUser } from '@/queries/useUser';
7+
import { getAuthCookie, setAuthCookie, deleteAuthCookie } from '@/lib/cookies';
78

89
// 이 컴포넌트는 UI를 렌더링하지 않고, 오직 상태 초기화 역할만 합니다.
910
export default function AuthJotaiInitializer() {
1011
const setUser = useSetAtom(userAtom);
1112
const { data: user, status } = useUser();
13+
const [isInitialized, setIsInitialized] = useState(false);
1214

15+
// 컴포넌트 마운트 시 쿠키에서 사용자 정보 복원
1316
useEffect(() => {
14-
if (status === 'success' || status === 'error') {
15-
setUser(user ?? null);
17+
const cookieUser = getAuthCookie();
18+
if (cookieUser) {
19+
setUser(cookieUser);
1620
}
17-
}, [user, status, setUser]);
21+
setIsInitialized(true);
22+
}, [setUser]);
23+
24+
// API에서 사용자 정보를 가져왔을 때 처리
25+
useEffect(() => {
26+
if (!isInitialized) return;
27+
28+
if (status === 'success' && user) {
29+
// API에서 사용자 정보를 성공적으로 가져왔을 때
30+
// JWT 토큰은 서버에서 쿠키로 설정되므로 여기서는 상태만 업데이트
31+
setUser(user);
32+
} else if (status === 'error') {
33+
// API 호출 실패 시 쿠키에서 사용자 정보 삭제
34+
deleteAuthCookie();
35+
setUser(null);
36+
}
37+
}, [user, status, setUser, isInitialized]);
1838

1939
return null;
2040
}

src/components/auth/SignInKakaoButton.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
1+
'use client';
2+
3+
import { getKakaoAuthUrl, getKakaoConfig } from '@/lib/kakao-auth';
4+
15
export default function SignInKakaoButton() {
6+
// 카카오 설정 확인
7+
const config = getKakaoConfig();
8+
const kakaoAuthUrl = getKakaoAuthUrl();
9+
10+
// 환경변수가 없으면 에러 표시
11+
if (!config || !kakaoAuthUrl) {
12+
return (
13+
<div className="text-red-500 text-center p-2">
14+
카카오 로그인 설정 오류
15+
</div>
16+
);
17+
}
18+
19+
console.log('Kakao Config:', config);
20+
console.log('Full Kakao Auth URL:', kakaoAuthUrl);
21+
222
return (
3-
<form
4-
action={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.AUTH_KAKAO_ID}&redirect_uri=${process.env.AUTH_KAKAO_REDIRECT_URL}&response_type=code`}
5-
method="GET"
6-
>
23+
<form action={kakaoAuthUrl} method="GET">
724
<button type="submit">Sign In</button>
825
</form>
926
);

src/components/auth/SignOutButton.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
11
'use client';
22

33
import { useRouter } from 'next/navigation';
4+
import { useSetAtom } from 'jotai';
5+
import { userAtom } from '@/atoms/authAtoms';
6+
import { deleteAuthCookie } from '@/lib/cookies';
47

58
export default function SignOutButton() {
69
const router = useRouter();
10+
const setUser = useSetAtom(userAtom);
11+
712
const handleLogout = async () => {
8-
const res = await fetch(`${process.env.NEXT_PUBLIC_TFT_BASE_URL}/api/auth/logout/kakao`, {
9-
method: 'POST',
10-
headers: {
11-
'Content-Type': 'application/json',
12-
},
13-
credentials: 'include',
14-
body: JSON.stringify({}),
15-
});
13+
try {
14+
// 서버에 로그아웃 요청
15+
const res = await fetch(`${process.env.NEXT_PUBLIC_TFT_BASE_URL}/api/auth/logout/kakao`, {
16+
method: 'POST',
17+
headers: {
18+
'Content-Type': 'application/json',
19+
},
20+
credentials: 'include',
21+
body: JSON.stringify({}),
22+
});
1623

17-
if (res.ok) {
24+
// 로컬 상태 및 쿠키 정리
25+
setUser(null);
26+
deleteAuthCookie();
27+
28+
if (res.ok) {
29+
router.push('/');
30+
}
31+
} catch (error) {
32+
console.error('Logout error:', error);
33+
// 에러가 발생해도 로컬 상태는 정리
34+
setUser(null);
35+
deleteAuthCookie();
1836
router.push('/');
1937
}
2038
};

src/components/auth/SigninSNSForm.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,47 @@
1-
import Image from 'next/image';
1+
'use client';
2+
3+
import { getKakaoAuthUrl, getKakaoConfig } from '@/lib/kakao-auth';
24

35
export default function SigninSNSForm() {
6+
// 카카오 설정 확인
7+
const config = getKakaoConfig();
8+
const kakaoAuthUrl = getKakaoAuthUrl();
9+
10+
// 환경변수가 없으면 에러 표시
11+
if (!config || !kakaoAuthUrl) {
12+
return (
13+
<div className="text-red-500 text-center p-4">
14+
카카오 로그인 설정이 완료되지 않았습니다.
15+
<br />
16+
<small className="text-xs">
17+
NEXT_PUBLIC_AUTH_KAKAO_ID와 NEXT_PUBLIC_AUTH_KAKAO_REDIRECT_URL을 확인해주세요.
18+
</small>
19+
</div>
20+
);
21+
}
22+
23+
console.log('Kakao Config:', config);
24+
console.log('Full Kakao Auth URL:', kakaoAuthUrl);
25+
426
return (
527
<div className="grid grid-cols-3 gap-4">
6-
<button className="flex justify-center items-center p-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm hover:bg-gray-50 dark:hover:bg-gray-700/50 hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-200 shadow-sm hover:shadow-md">
28+
{/* <button className="flex justify-center items-center p-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm hover:bg-gray-50 dark:hover:bg-gray-700/50 hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-200 shadow-sm hover:shadow-md">
729
<Image
830
src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/google/google-original.svg"
931
alt="Google"
1032
className="w-6 h-6"
33+
width={300}
34+
height={300}
1135
/>
1236
</button>
1337
1438
<button className="flex justify-center items-center p-3 rounded-xl bg-[#03C75A] hover:bg-[#02b350] transition-all duration-200 shadow-sm hover:shadow-md">
1539
<span className="text-white font-bold text-lg w-6 h-6 flex items-center justify-center">N</span>
16-
</button>
40+
</button> */}
1741

1842
<form
19-
action={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.AUTH_KAKAO_ID}&redirect_uri=${process.env.AUTH_KAKAO_REDIRECT_URL}&response_type=code`}
43+
className="w-full"
44+
action={kakaoAuthUrl}
2045
method="GET"
2146
>
2247
<button className="w-full flex justify-center items-center p-3 rounded-xl bg-[#FEE500] hover:bg-[#FDD835] transition-all duration-200 shadow-sm hover:shadow-md">

0 commit comments

Comments
 (0)