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
23 changes: 0 additions & 23 deletions src/api/auth/exchangeTempToken.ts

This file was deleted.

19 changes: 19 additions & 0 deletions src/api/auth/getToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { apiClient } from '../index';

export interface GetTokenRequest {
loginTokenKey: string; // 인가코드
}

export interface GetTokenResponse {
isSuccess: boolean;
code: number;
message: string;
data: {
token: string; // 토큰
};
}

export const getToken = async (data: GetTokenRequest): Promise<GetTokenResponse> => {
const response = await apiClient.post<GetTokenResponse>('/auth/token', data);
return response.data;
};
3 changes: 1 addition & 2 deletions src/api/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { setCookie } from './setCookie';
export { exchangeTempToken } from './exchangeTempToken';
export { getToken } from './getToken';
15 changes: 0 additions & 15 deletions src/api/auth/setCookie.ts

This file was deleted.

26 changes: 9 additions & 17 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,18 @@ export const apiClient = axios.create({
// const TEMP_ACCESS_TOKEN =
// 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc1NDM4MjY1MiwiZXhwIjoxNzU2OTc0NjUyfQ.BSGuoMWlrzc0oKgSJXHEycxdzzY9-e7gD4xh-wSDemc';

// Request 인터셉터: temp_token과 access_token 쿠키 처리
// Request 인터셉터: localStorage의 토큰을 헤더에 자동 추가
apiClient.interceptors.request.use(
config => {
// 쿠키에서 temp_token과 access_token 확인
const cookies = document.cookie.split(';');
const hasTempToken = cookies.some(cookie => cookie.trim().startsWith('temp_token='));
const hasAccessToken = cookies.some(cookie => cookie.trim().startsWith('access_token='));
// localStorage에서 토큰 확인
const authToken = localStorage.getItem('authToken');

if (hasAccessToken) {
// access_token이 있으면 정상 토큰 사용
console.log('✅ access_token 쿠키가 있어서 정상 토큰을 사용합니다.');
// access_token은 withCredentials: true로 자동 전송되므로 별도 헤더 설정 불필요
} else if (hasTempToken) {
// temp_token이 있으면 임시 토큰 사용
console.log('🔑 temp_token 쿠키가 있어서 임시 토큰을 사용합니다.');
// temp_token도 withCredentials: true로 자동 전송되므로 별도 헤더 설정 불필요
if (authToken) {
// 토큰이 있으면 Authorization 헤더에 추가
console.log('🔑 Authorization 헤더에 토큰 추가');
config.headers.Authorization = `Bearer ${authToken}`;
} else {
// 둘 다 없으면 인증 토큰 없음
console.log('❌ temp_token과 access_token 쿠키가 모두 없습니다.');
console.log('❌ localStorage에 토큰이 없습니다.');
}

return config;
Expand All @@ -50,8 +43,7 @@ apiClient.interceptors.response.use(
(response: AxiosResponse) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
// 인증 실패 시 로그인 페이지로 리다이렉트
// window.location.href = '/';
window.location.href = '/';
}
return Promise.reject(error);
},
Expand Down
3 changes: 2 additions & 1 deletion src/api/users/postSignup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ export interface PostSignupRequest {
}

export interface PostSignupResponse {
success: boolean;
isSuccess: boolean;
code: number;
message: string;
data: {
userId: number;
accessToken: string; // 회원가입 완료 후 받는 access 토큰
};
}

Expand Down
80 changes: 34 additions & 46 deletions src/hooks/useSocialLoginToken.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { apiClient } from '@/api/index';
import { useEffect, useRef, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import { getToken } from '@/api/auth';

export const useSocialLoginToken = () => {
const navigate = useNavigate();
const location = useLocation();

// 토큰 발급 완료를 기다리는 Promise
const tokenPromise = useRef<Promise<void> | null>(null);

useEffect(() => {
const handleSocialLoginToken = async () => {
const handleSocialLoginToken = async (): Promise<void> => {
// URL에서 loginTokenKey 가져오기
const params = new URLSearchParams(window.location.search);
const loginTokenKey = params.get('loginTokenKey');
Expand All @@ -17,53 +19,29 @@ export const useSocialLoginToken = () => {
return;
}

// 현재 경로가 /signup인지 확인
const isSignupPage = location.pathname === '/signup';

try {
if (isSignupPage) {
// 회원가입 페이지인 경우: 임시토큰 발급 요청
console.log('🔑 회원가입 페이지: 임시토큰 발급 요청');
console.log('📋 loginTokenKey:', loginTokenKey);
console.log('🔑 소셜 로그인 토큰 발급 요청');
console.log('📋 loginTokenKey:', loginTokenKey);

const response = await apiClient.post(
'/auth/set-cookie',
{ loginTokenKey },
{ withCredentials: true },
);
// /auth/token API 호출하여 토큰 발급 (임시 토큰 또는 access 토큰)
const response = await getToken({ loginTokenKey });

if (response.data.isSuccess) {
console.log('✅ 임시토큰 발급 성공');
// URL에서 loginTokenKey 파라미터 제거
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
} else {
console.error('❌ 임시토큰 발급 실패:', response.data.message);
}
} else {
// 피드 페이지 등 다른 페이지인 경우: 엑세스토큰 발급 요청
console.log('🔑 피드 페이지: 엑세스토큰 발급 요청');
console.log('📋 loginTokenKey:', loginTokenKey);
if (response.isSuccess) {
const { token } = response.data;

// 토큰을 localStorage에 저장 (request header에 사용)
localStorage.setItem('authToken', token);

const response = await apiClient.post(
'/auth/exchange-temp-token',
{ loginTokenKey },
{ withCredentials: true },
);
console.log('✅ Access 토큰 발급 성공 (바로 홈 화면)');

if (response.data.isSuccess) {
console.log('✅ 엑세스토큰 발급 성공');
// URL에서 loginTokenKey 파라미터 제거
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
} else {
console.error('❌ 엑세스토큰 발급 실패:', response.data.message);
navigate('/');
}
// URL에서 loginTokenKey 파라미터 제거
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
} else {
console.error('❌ 토큰 발급 실패:', response.message);
}
} catch (error) {
console.error('💥 토큰 발급 중 오류 발생:', error);
navigate('/');
}
};

Expand All @@ -72,7 +50,17 @@ export const useSocialLoginToken = () => {
const isSocialLoginComplete = urlParams.get('loginTokenKey');

if (isSocialLoginComplete) {
handleSocialLoginToken();
// 토큰 발급 Promise를 저장
tokenPromise.current = handleSocialLoginToken();
}
}, [location.pathname]);

// 토큰 발급 완료를 기다리는 함수 반환
const waitForToken = useCallback(async (): Promise<void> => {
if (tokenPromise.current) {
await tokenPromise.current;
}
}, [location.pathname, navigate]); // location.pathname과 navigate만 의존성으로 설정
}, []);

return { waitForToken };
};
36 changes: 25 additions & 11 deletions src/pages/feed/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Feed = () => {
const [activeTab, setActiveTab] = useState<string>(initialTabFromState ?? tabs[0]);

// 소셜 로그인 토큰 발급 처리
useSocialLoginToken();
const { waitForToken } = useSocialLoginToken();

// 최초 마운트 시에만 history state 제거하여 이후 재방문 시 영향 없도록 처리
useEffect(() => {
Expand All @@ -48,7 +48,7 @@ const Feed = () => {
};

// 전체 피드 로드 함수
const loadTotalFeeds = async (_cursor?: string) => {
const loadTotalFeeds = useCallback(async (_cursor?: string) => {
try {
setTotalLoading(true);

Expand All @@ -74,10 +74,10 @@ const Feed = () => {
} finally {
setTotalLoading(false);
}
};
}, []);

// 내 피드 로드 함수
const loadMyFeeds = async (_cursor?: string) => {
const loadMyFeeds = useCallback(async (_cursor?: string) => {
try {
setMyLoading(true);
const response = await getMyFeeds(_cursor ? { cursor: _cursor } : undefined);
Expand All @@ -97,7 +97,7 @@ const Feed = () => {
} finally {
setMyLoading(false);
}
};
}, []);

// 다음 페이지 로드 (무한 스크롤용)
const loadMoreFeeds = useCallback(() => {
Expand Down Expand Up @@ -146,12 +146,26 @@ const Feed = () => {

// 탭별로 API 호출
useEffect(() => {
if (activeTab === '피드') {
loadTotalFeeds();
} else if (activeTab === '내 피드') {
loadMyFeeds();
}
}, [activeTab]);
const loadFeedsWithToken = async () => {
// 토큰 발급 완료 대기
await waitForToken();

// localStorage에 토큰이 있는지 확인
const authToken = localStorage.getItem('authToken');
if (!authToken) {
console.log('❌ 토큰이 없어서 피드를 로드할 수 없습니다.');
return;
}

if (activeTab === '피드') {
loadTotalFeeds();
} else if (activeTab === '내 피드') {
loadMyFeeds();
}
};

loadFeedsWithToken();
}, [activeTab, waitForToken, loadTotalFeeds, loadMyFeeds]);

return (
<Container>
Expand Down
13 changes: 13 additions & 0 deletions src/pages/login/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import logo from '../../assets/login/logo.svg';
import KaKao from '../../assets/login/kakao.svg';
import Google from '../../assets/login/google.svg';
import { Wrapper } from '@/components/common/Wrapper';

const Login = () => {
const navigate = useNavigate();

// 이미 토큰이 있으면 /feed로 바로 이동
useEffect(() => {
const authToken = localStorage.getItem('authToken');
if (authToken) {
console.log('✅ 이미 토큰이 있어서 /feed로 바로 이동합니다.');
navigate('/feed');
}
}, [navigate]);

const handleKakaoLogin = () => {
// 직접 카카오 로그인 URL로 리다이렉션
window.location.href = `${import.meta.env.VITE_API_BASE_URL}/oauth2/authorization/kakao`;
Expand Down
17 changes: 9 additions & 8 deletions src/pages/signup/SignupGenre.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,26 @@ const SignupGenre = () => {
const handleNextClick = async () => {
if (!selectedAlias || !nickname) return;

console.log('=== 🚀 다음 버튼 클릭 ===');
console.log('🎭 선택된 alias:', selectedAlias);
console.log('👤 nickname:', nickname);

try {
console.log('🚀 postSignup API 호출 시작...');
// ✅ 쿠키는 브라우저가 자동으로 전송
const result = await postSignup({
aliasName: selectedAlias.subTitle,
nickname: nickname,
isTokenRequired: false,
});

if (result.success) {
if (result.isSuccess) {
console.log('🎉 회원가입 성공! 사용자 ID:', result.data.userId);

// 회원가입 성공 시 새로운 access 토큰을 localStorage에 저장
if (result.data.accessToken) {
localStorage.setItem('authToken', result.data.accessToken);
console.log('✅ 새로운 access 토큰이 localStorage에 저장되었습니다.');
}

navigate('/signup/guide', {
state: {
aliasName: selectedAlias.subTitle,
nickname: nickname,
aliasName: selectedAlias.subTitle,
},
});
} else {
Expand Down
Loading