From 9053cc842cb271f588fd1e193e7091988e7d1292 Mon Sep 17 00:00:00 2001 From: Jaeho Date: Thu, 4 Jun 2026 01:38:22 +0900 Subject: [PATCH] =?UTF-8?q?fix=20:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EC=8B=9C=20=ED=91=B8=ED=84=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/router/ScrollToTop.tsx | 17 +++ frontend/src/app/router/index.tsx | 140 +++++++++--------- .../interview/ui/live/ConversationThread.tsx | 10 +- 3 files changed, 96 insertions(+), 71 deletions(-) create mode 100644 frontend/src/app/router/ScrollToTop.tsx diff --git a/frontend/src/app/router/ScrollToTop.tsx b/frontend/src/app/router/ScrollToTop.tsx new file mode 100644 index 00000000..22dbfe44 --- /dev/null +++ b/frontend/src/app/router/ScrollToTop.tsx @@ -0,0 +1,17 @@ +import { useEffect } from 'react' +import { Outlet, useLocation } from 'react-router-dom' + +// createBrowserRouter 는 라우트 이동 시 스크롤을 자동으로 초기화하지 않는다. +// 푸터 등 페이지 하단 링크로 이동하면 직전 스크롤 위치가 그대로 유지돼 +// 새 페이지에서도 하단(푸터)을 보게 되므로, 경로 변경 시 상단으로 올린다. +// 해시 앵커(/#services 등)는 각 페이지가 직접 스크롤하므로 건드리지 않는다. +export function ScrollToTop() { + const { pathname, hash } = useLocation() + + useEffect(() => { + if (hash) return + window.scrollTo({ top: 0 }) + }, [pathname, hash]) + + return +} diff --git a/frontend/src/app/router/index.tsx b/frontend/src/app/router/index.tsx index f4e2c03f..6b6824f4 100644 --- a/frontend/src/app/router/index.tsx +++ b/frontend/src/app/router/index.tsx @@ -1,5 +1,6 @@ import { createBrowserRouter, Navigate } from 'react-router-dom' import { RequireAuth } from '@/features/auth' +import { ScrollToTop } from './ScrollToTop' import HomePage from '@/pages/Home' import LoginPage from '@/pages/Login' import AuthCallbackPage from '@/pages/AuthCallback' @@ -11,73 +12,78 @@ import SessionFeedbackPage from '@/pages/SessionFeedback' import SharedFeedbackPage from '@/pages/SharedFeedback' export const router = createBrowserRouter([ - { path: '/', element: }, - { path: '/login', element: }, - { path: '/practice/:track', element: }, - { path: '/share/:token', element: }, - { path: '/auth/callback', element: }, { - path: '/workspace', - element: ( - - - - ), - }, - { - path: '/workspace/resumes', - element: ( - - - - ), - }, - { - path: '/workspace/repos', - element: ( - - - - ), - }, - { - path: '/sessions/new', - element: ( - - - - ), - }, - { - path: '/sessions/:id', - element: ( - - - - ), - }, - { - path: '/sessions/:id/feedback', - element: ( - - - - ), - }, - { - path: '/workspace/history', - element: ( - - - - ), - }, - { path: '/history', element: }, - { - path: '/design-system/*', - lazy: async () => { - const mod = await import('@/pages/DesignSystem') - return { Component: mod.default } - }, + element: , + children: [ + { path: '/', element: }, + { path: '/login', element: }, + { path: '/practice/:track', element: }, + { path: '/share/:token', element: }, + { path: '/auth/callback', element: }, + { + path: '/workspace', + element: ( + + + + ), + }, + { + path: '/workspace/resumes', + element: ( + + + + ), + }, + { + path: '/workspace/repos', + element: ( + + + + ), + }, + { + path: '/sessions/new', + element: ( + + + + ), + }, + { + path: '/sessions/:id', + element: ( + + + + ), + }, + { + path: '/sessions/:id/feedback', + element: ( + + + + ), + }, + { + path: '/workspace/history', + element: ( + + + + ), + }, + { path: '/history', element: }, + { + path: '/design-system/*', + lazy: async () => { + const mod = await import('@/pages/DesignSystem') + return { Component: mod.default } + }, + }, + ], }, ]) diff --git a/frontend/src/features/interview/ui/live/ConversationThread.tsx b/frontend/src/features/interview/ui/live/ConversationThread.tsx index 841c565f..93b2120c 100644 --- a/frontend/src/features/interview/ui/live/ConversationThread.tsx +++ b/frontend/src/features/interview/ui/live/ConversationThread.tsx @@ -12,16 +12,19 @@ export function ConversationThread({ items: ThreadItem[] awaitingQuestion: boolean }) { - const bottomRef = useRef(null) + // 내부 스레드 컨테이너만 스크롤한다. scrollIntoView 는 스크롤 가능한 모든 + // 조상(=window)까지 스크롤해 페이지가 푸터로 끌려 내려가므로 사용하지 않는다. + const containerRef = useRef(null) useEffect(() => { - bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) + const el = containerRef.current + if (el) el.scrollTop = el.scrollHeight }, [items.length, awaitingQuestion]) // 가장 마지막 질문만 자동재생(초기 로드 시 과거 질문 일괄 재생 방지). const lastQuestionKey = [...items].reverse().find(isQuestion)?.key return ( -
+
{items.map((item) => isQuestion(item) ? ( @@ -30,7 +33,6 @@ export function ConversationThread({ ), )} {awaitingQuestion ? : null} -
) }