-
Notifications
You must be signed in to change notification settings - Fork 3
chore: 어드민 vinext app router 전환 #524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
a90f326
chore: 어드민 App Router 전환 기반 추가
manNomi 3d8901c
chore: 어드민 보조 페이지 App Router 병렬 이관
manNomi 4c68957
chore: 어드민 Vinext 런타임 스크립트 전환
manNomi 03815cf
chore: 어드민 TanStack Router 제거
manNomi 0044fbe
chore: vinext 캐시 ignore 추가
manNomi 906207b
docs: 어드민 vinext 마이그레이션 리포트 추가
manNomi 8998de5
fix: 어드민 세션 확인 실패 처리 보강
manNomi 306eacf
feat: 멘토 승격 요청 관리 화면 추가
manNomi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| # next.js | ||
| **/.next/ | ||
| **/out/ | ||
| **/.vinext/ | ||
|
|
||
| # production | ||
| **/build | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # Vinext Migration Report | ||
|
|
||
| ## 변경 요약 | ||
|
|
||
| - `apps/admin`의 TanStack Start 라우팅 구조를 Vinext 호환 App Router 구조로 변경했다. | ||
| - `src/routes` 기반 라우트를 `src/app`으로 이동하고, 라우트 파일에 있던 UI는 feature 컴포넌트로 분리했다. | ||
| - TanStack Router navigation 의존을 일반 anchor 및 브라우저 이동 흐름으로 교체했다. | ||
| - Vinext `dev`, `build`, `start` script를 적용하고 Vite 설정을 Vinext 플러그인 중심으로 정리했다. | ||
| - TanStack Start, TanStack Router, route tree, Nitro 기반 진입점을 제거했다. | ||
|
|
||
| ## 라우트 매핑 | ||
|
|
||
| | Before | After | | ||
| | --- | --- | | ||
| | `src/routes/__root.tsx` | `src/app/layout.tsx`, `src/app/providers.tsx` | | ||
| | `src/routes/index.tsx` | `src/app/page.tsx` | | ||
| | `src/routes/login.tsx` | `src/app/login/page.tsx` | | ||
| | `src/routes/auth/login.tsx` | `src/app/auth/login/page.tsx`, `src/components/features/auth/AdminLoginPage.tsx` | | ||
| | `src/routes/scores/index.tsx` | `src/app/scores/page.tsx`, `src/components/features/scores/ScoresPageContent.tsx` | | ||
| | `src/routes/bruno/index.tsx` | `src/app/bruno/page.tsx`, `src/components/features/bruno/BrunoApiPageContent.tsx` | | ||
| | `src/routes/chat-socket/index.tsx` | `src/app/chat-socket/page.tsx`, `src/components/features/chat-socket/ChatSocketPageContent.tsx` | | ||
|
|
||
| ## 유지한 것 | ||
|
|
||
| - 기존 UI 컴포넌트와 admin 페이지 화면 구성 | ||
| - `components/features`, `components/layout`, `lib`, `types` 중심의 기존 코드 배치 | ||
| - TanStack Query 기반 서버 상태 관리 | ||
| - localStorage 기반 admin session 저장소 | ||
| - login, scores, bruno, chat-socket 사용자 플로우 | ||
|
|
||
| ## 제거한 것 | ||
|
|
||
| - `RouterProvider` | ||
| - `src/routeTree.gen.ts` | ||
| - `src/router.tsx` | ||
| - `src/routes/**` | ||
| - `createFileRoute`, `createRootRoute` | ||
| - TanStack Start Vite plugin, Nitro entry | ||
| - `@tanstack/react-router`, `@tanstack/react-start`, TanStack Router Devtools 의존성 | ||
|
|
||
| ## 주의할 점 | ||
|
|
||
| - Vinext는 Next.js 호환 API를 Vite 기반으로 재구현한 도구이므로, Next.js와 완전히 동일한 내부 동작을 전제하지 않는다. | ||
| - Vinext build 결과에서 route classification이 `Unknown`으로 표시된다. 현재 Vinext의 정적 분석 한계 안내이며 빌드는 성공한다. | ||
| - 현재 로컬 Node가 `v23.10.0`이라 repo 요구사항 `22.x` 경고가 출력된다. | ||
| - `@tailwindcss/vite`는 현재 Vite 8 peer range를 공식 포함하지 않아 peer warning이 출력된다. 빌드와 라우트 QA는 통과했다. | ||
| - `/`, `/auth/login`, `/scores` 등 API 클라이언트를 import하는 라우트는 `VITE_API_SERVER_URL`이 없으면 기존 로직에 의해 500이 난다. QA는 `.env.example` 기준 예시 값을 주입해 진행했다. | ||
|
|
||
| ## 검증 결과 | ||
|
|
||
| - dev: `VITE_API_SERVER_URL=https://api.example.com VITE_S3_BASE_URL=https://s3.example.com pnpm --filter @solid-connect/admin dev` 성공 | ||
| - build: `pnpm --filter @solid-connect/admin build` 성공 | ||
| - lint: `pnpm --filter @solid-connect/admin lint:check` 성공 | ||
| - typecheck: `pnpm --filter @solid-connect/admin typecheck` 성공 | ||
| - compatibility: `pnpm --filter @solid-connect/admin exec vinext check` 100% compatible | ||
| - 주요 페이지 수동 QA: `/`, `/login`, `/auth/login`, `/scores`, `/bruno`, `/chat-socket` 모두 HTTP 200 확인 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import { AdminLoginPage } from "@/components/features/auth/AdminLoginPage"; | ||
| import { ensureSessionToken } from "@/lib/auth/session"; | ||
|
|
||
| export default function AuthLoginPage() { | ||
| const [canRenderLogin, setCanRenderLogin] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| let isMounted = true; | ||
|
|
||
| const redirectIfAuthenticated = async () => { | ||
| try { | ||
| const token = await ensureSessionToken(); | ||
| if (!isMounted) return; | ||
|
|
||
| if (token) { | ||
| window.location.replace("/scores"); | ||
| return; | ||
| } | ||
| } catch { | ||
| // 세션 확인 실패 시에도 로그인 화면은 열어둔다. | ||
| } | ||
|
|
||
| if (!isMounted) return; | ||
| setCanRenderLogin(true); | ||
| }; | ||
|
|
||
| void redirectIfAuthenticated(); | ||
|
|
||
| return () => { | ||
| isMounted = false; | ||
| }; | ||
| }, []); | ||
|
|
||
| if (!canRenderLogin) { | ||
| return ( | ||
| <div className="flex min-h-screen items-center justify-center bg-bg-50 typo-medium-3 text-k-500"> | ||
| 관리자 세션을 확인하는 중... | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return <AdminLoginPage onLoginSuccess={() => window.location.assign("/scores")} />; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { RequireAdminSession } from "@/components/features/auth/RequireAdminSession"; | ||
| import { BrunoApiPageContent } from "@/components/features/bruno/BrunoApiPageContent"; | ||
|
|
||
| export default function BrunoPage() { | ||
| return ( | ||
| <RequireAdminSession> | ||
| <BrunoApiPageContent /> | ||
| </RequireAdminSession> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { RequireAdminSession } from "@/components/features/auth/RequireAdminSession"; | ||
| import { ChatSocketPageContent } from "@/components/features/chat-socket/ChatSocketPageContent"; | ||
|
|
||
| export default function ChatSocketPage() { | ||
| return ( | ||
| <RequireAdminSession> | ||
| <ChatSocketPageContent /> | ||
| </RequireAdminSession> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import type { ReactNode } from "react"; | ||
| import "../styles.css"; | ||
| import { Providers } from "./providers"; | ||
|
|
||
| export const metadata = { | ||
| title: "Solid Connection Admin", | ||
| viewport: "width=device-width, initial-scale=1", | ||
| }; | ||
|
|
||
| export default function RootLayout({ children }: { children: ReactNode }) { | ||
| return ( | ||
| <html lang="ko"> | ||
| <body> | ||
| <Providers>{children}</Providers> | ||
| </body> | ||
| </html> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect } from "react"; | ||
|
|
||
| export default function LoginRedirectPage() { | ||
| useEffect(() => { | ||
| window.location.replace("/auth/login"); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <div className="flex min-h-screen items-center justify-center bg-bg-50 typo-medium-3 text-k-500"> | ||
| 로그인 페이지로 이동하는 중... | ||
| </div> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { RequireAdminSession } from "@/components/features/auth/RequireAdminSession"; | ||
| import { MentorApplicationsPageContent } from "@/components/features/mentor-applications/MentorApplicationsPageContent"; | ||
|
|
||
| export default function MentorApplicationsPage() { | ||
| return ( | ||
| <RequireAdminSession> | ||
| <MentorApplicationsPageContent /> | ||
| </RequireAdminSession> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect } from "react"; | ||
| import { ensureSessionToken } from "@/lib/auth/session"; | ||
|
|
||
| export default function HomePage() { | ||
| useEffect(() => { | ||
| const redirectBySession = async () => { | ||
| try { | ||
| const token = await ensureSessionToken(); | ||
| window.location.replace(token ? "/scores" : "/auth/login"); | ||
| } catch { | ||
| window.location.replace("/auth/login"); | ||
| } | ||
| }; | ||
|
|
||
| void redirectBySession(); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }, []); | ||
|
|
||
| return ( | ||
| <div className="flex min-h-screen items-center justify-center bg-bg-50 typo-medium-3 text-k-500"> | ||
| 관리자 페이지로 이동하는 중... | ||
| </div> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| "use client"; | ||
|
|
||
| import type { ReactNode } from "react"; | ||
| import { Toaster } from "sonner"; | ||
| import { QueryProvider } from "@/components/providers/QueryProvider"; | ||
|
|
||
| interface ProvidersProps { | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| export function Providers({ children }: ProvidersProps) { | ||
| return ( | ||
| <QueryProvider> | ||
| {children} | ||
| <Toaster /> | ||
| </QueryProvider> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { RequireAdminSession } from "@/components/features/auth/RequireAdminSession"; | ||
| import { ScoresPageContent } from "@/components/features/scores/ScoresPageContent"; | ||
|
|
||
| export default function ScoresPage() { | ||
| return ( | ||
| <RequireAdminSession> | ||
| <ScoresPageContent /> | ||
| </RequireAdminSession> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
apps/admin/src/components/features/auth/RequireAdminSession.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| "use client"; | ||
|
|
||
| import { type ReactNode, useEffect, useState } from "react"; | ||
| import { ensureSessionToken } from "@/lib/auth/session"; | ||
|
|
||
| interface RequireAdminSessionProps { | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| export function RequireAdminSession({ children }: RequireAdminSessionProps) { | ||
| const [isAuthorized, setIsAuthorized] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| let isMounted = true; | ||
|
|
||
| const checkSession = async () => { | ||
| try { | ||
| const token = await ensureSessionToken(); | ||
| if (!isMounted) return; | ||
|
|
||
| if (!token) { | ||
| window.location.replace("/auth/login"); | ||
| return; | ||
| } | ||
|
|
||
| setIsAuthorized(true); | ||
| } catch { | ||
| if (!isMounted) return; | ||
| window.location.replace("/auth/login"); | ||
| } | ||
| }; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| void checkSession(); | ||
|
|
||
| return () => { | ||
| isMounted = false; | ||
| }; | ||
| }, []); | ||
|
|
||
| if (!isAuthorized) { | ||
| return ( | ||
| <div className="flex min-h-screen items-center justify-center bg-bg-50 typo-medium-3 text-k-500"> | ||
| 관리자 세션을 확인하는 중... | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return children; | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.