From 1f66a8a97c00bedac65fd0d754ac5b03524bd73e Mon Sep 17 00:00:00 2001 From: sispo3314 Date: Wed, 17 Sep 2025 18:40:19 +0900 Subject: [PATCH 1/9] =?UTF-8?q?fix:=20=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94?= =?UTF-8?q?=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/component/SideBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component/SideBar.tsx b/src/component/SideBar.tsx index eb17c81..2263c4c 100644 --- a/src/component/SideBar.tsx +++ b/src/component/SideBar.tsx @@ -69,7 +69,7 @@ const Sidebar: React.FC = ({ isOpen, onClose, position = 'right' }
- diff --git a/src/pages/ai/TravelSpotDetail.tsx b/src/pages/ai/TravelSpotDetail.tsx index fd32dd0..bc9f826 100644 --- a/src/pages/ai/TravelSpotDetail.tsx +++ b/src/pages/ai/TravelSpotDetail.tsx @@ -37,7 +37,28 @@ const TravelSpotDetail = () => { if (!data) return; if (!isAuthed) { - navigate('/login', { state: { from: location }, replace: true }); + const here = `${location.pathname}${location.search}${location.hash}`; + sessionStorage.setItem('postLoginRedirect', here); + + sessionStorage.setItem( + 'postLoginAction', + JSON.stringify({ + kind: 'LIKE_PLACE', + contentId, + payload: { + regionName: data.regionTag ?? '정보없음', + themeName: data.themeName ?? '여행지', + cnctrLevel: data.serenity ?? 0, + }, + }), + ); + + navigate( + `/login?redirect=${encodeURIComponent(here)}&action=like_place&cid=${encodeURIComponent( + contentId, + )}`, + { replace: true }, + ); return; } diff --git a/src/pages/home/KakaoCallbackPage.tsx b/src/pages/home/KakaoCallbackPage.tsx index 1b720f3..d8be772 100644 --- a/src/pages/home/KakaoCallbackPage.tsx +++ b/src/pages/home/KakaoCallbackPage.tsx @@ -1,6 +1,14 @@ import { useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { loginWithKakaoAccessToken } from '@/api/user/auth.api'; +import { likePlace } from '@/api/like/like.api'; + +function pickSafeInternalPath(p?: string | null) { + if (!p) return undefined; + if (!p.startsWith('/')) return undefined; // 오픈 리다이렉트 방지 + if (p.startsWith('/login')) return undefined; // 로그인 페이지로 복귀 방지 + return p; +} export default function KakaoCallbackPage() { const navigate = useNavigate(); @@ -19,7 +27,7 @@ export default function KakaoCallbackPage() { if (!code) throw new Error('인가 코드가 없습니다.'); const REST_KEY = import.meta.env.VITE_KAKAO_REST_KEY!; - const REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI!; // 인가 때와 "완전히" 동일해야 함 + const REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI!; // 인가 때와 동일 const body = new URLSearchParams({ grant_type: 'authorization_code', @@ -41,11 +49,57 @@ export default function KakaoCallbackPage() { } const kakaoAccessToken = tokenJson.access_token as string; + // 1) 우리 서버 로그인 const result = await loginWithKakaoAccessToken(kakaoAccessToken); - //홈/온보딩으로 - window.history.replaceState(null, '', '/'); - navigate(result.newUser ? '/register/2' : '/', { replace: true }); + // 2) state/세션에서 복귀 경로와 액션 꺼내기 + const rawState = qs.get('state'); + let redirectFromState: string | undefined; + let actionFromState: any; + try { + const parsed = rawState ? JSON.parse(rawState) : null; + redirectFromState = parsed?.redirect; + actionFromState = parsed?.action; + } catch { + // 만약 state를 그냥 경로 문자열로 쓴 경우 대비 + redirectFromState = rawState || undefined; + } + + const fromStorage = sessionStorage.getItem('postLoginRedirect'); + const actionStorage = sessionStorage.getItem('postLoginAction'); + + const redirectTo = + pickSafeInternalPath(redirectFromState) || pickSafeInternalPath(fromStorage) || '/'; + + const action = actionFromState || (actionStorage ? JSON.parse(actionStorage) : null); + + // 한번 쓰면 비워두기 + sessionStorage.removeItem('postLoginRedirect'); + sessionStorage.removeItem('postLoginAction'); + + // 3) 신규 유저는 온보딩으로 + if (result.newUser) { + navigate('/register/2', { replace: true }); + return; + } + + // 4) 액션 재생 (예: 상세에서 비로그인으로 누른 '좋아요') + if (action?.kind === 'LIKE_PLACE' && action.contentId) { + try { + await likePlace({ + contentId: action.contentId, + ...(action.payload ?? {}), + }); + // 선택: 돌아간 페이지에서 토스트 띄우고 싶으면 플래그 남기기 + sessionStorage.setItem('postLoginToast', '좋아요 완료!'); + } catch (e) { + console.error('post-login like failed', e); + sessionStorage.setItem('postLoginToast', '좋아요에 실패했어요.'); + } + } + + // 5) 원래 페이지로 replace 이동 (history에 콜백 안 남김) + navigate(redirectTo, { replace: true }); } catch (e) { console.error(e); alert('로그인에 실패했습니다. 다시 시도해주세요.'); diff --git a/src/pages/home/Login.tsx b/src/pages/home/Login.tsx index 00797de..1d66775 100644 --- a/src/pages/home/Login.tsx +++ b/src/pages/home/Login.tsx @@ -1,18 +1,58 @@ import { KakaoLogin, LoginImage } from '@/assets'; +import { useLocation } from 'react-router-dom'; const REST_KEY = import.meta.env.VITE_KAKAO_REST_KEY as string; const KAKAO_REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI as string; -const params = new URLSearchParams({ - client_id: REST_KEY, - redirect_uri: KAKAO_REDIRECT_URI, - response_type: 'code', - state: 'from=login', -}); +function buildAuthorizeUrl(stateObj: unknown) { + const params = new URLSearchParams({ + client_id: REST_KEY, + redirect_uri: KAKAO_REDIRECT_URI, + response_type: 'code', + state: JSON.stringify(stateObj), + }); + return `https://kauth.kakao.com/oauth/authorize?${params.toString()}`; +} -const authorizeUrl = `https://kauth.kakao.com/oauth/authorize?${params.toString()}`; +function pickSafeInternalPath(p?: string | null) { + if (!p) return undefined; + if (!p.startsWith('/')) return undefined; + if (p.startsWith('/login')) return undefined; + return p; +} export default function LoginPage() { + const location = useLocation(); + + function handleKakaoLogin() { + const qs = new URLSearchParams(location.search); + + //1)복귀 경로 결정 + const candidate = + qs.get('redirect') || `${location.pathname}${location.search}${location.hash}`; + const backup = sessionStorage.getItem('postLoginRedirect'); + const redirect = pickSafeInternalPath(candidate) || pickSafeInternalPath(backup) || '/'; + + //2)액션 힌트 + let action: any = null; + const stored = sessionStorage.getItem('postLoginAction'); + if (stored) { + try { + action = JSON.parse(stored); + } catch { + action = null; + } + } else if (qs.get('action') === 'like_place' && qs.get('cid')) { + action = { kind: 'LIKE_PLACE', contentId: qs.get('cid') }; + } + + sessionStorage.setItem('postLoginRedirect', redirect); + if (action) sessionStorage.setItem('postLoginAction', JSON.stringify(action)); + + const authorizeUrl = buildAuthorizeUrl({ redirect, action }); + window.location.href = authorizeUrl; + } + return (
@@ -24,11 +64,10 @@ export default function LoginPage() {
+