-
Notifications
You must be signed in to change notification settings - Fork 0
fix/라우팅 경로 수정 #15
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
fix/라우팅 경로 수정 #15
Changes from all commits
fdb757d
12d3e7a
a9682f3
d167b2c
23ed1ff
ef3be4a
a8e15d8
a842dc8
62e3b64
bfcf68f
9910aa7
bf0acb1
9d76887
e3d1d7a
c7eea4c
07ac244
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Link } from 'react-router-dom'; | ||
|
|
||
| export function LoginButton() { | ||
| return ( | ||
| <Link to="/login" className="flex items-center gap-1 text-body-s-bold text-gray-800"> | ||
| 로그인 | ||
| </Link> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { Link, useLocation } from 'react-router-dom'; | ||
|
|
||
| import logoFull from '@/assets/logo-full@4x.webp'; | ||
| import logoIcon from '@/assets/logo-icon@4x.webp'; | ||
|
|
||
| export function Logo() { | ||
| const { pathname } = useLocation(); | ||
| const isHome = pathname === '/'; | ||
|
|
||
| return ( | ||
| <Link to="/"> | ||
| <img src={isHome ? logoFull : logoIcon} alt="또랑" className="h-8" /> | ||
| </Link> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { LoginButton } from './LoginButton'; | ||
| export { Logo } from './Logo'; |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,25 @@ | ||
| import type { ReactNode } from 'react'; | ||
| import { Outlet } from 'react-router-dom'; | ||
|
|
||
| import { Header } from './Header'; | ||
| import { Logo } from '@/components/common'; | ||
|
|
||
| interface LayoutProps { | ||
| headerLeft?: ReactNode; | ||
| headerCenter?: ReactNode; | ||
| headerRight?: ReactNode; | ||
| children?: ReactNode; | ||
| left?: ReactNode; | ||
| center?: ReactNode; | ||
| right?: ReactNode; | ||
| } | ||
|
|
||
| export function Layout({ headerLeft, headerCenter, headerRight, children }: LayoutProps) { | ||
| export function Layout({ left, center, right }: LayoutProps) { | ||
| return ( | ||
| <div className="min-h-screen bg-gray-100"> | ||
| <Header left={headerLeft} center={headerCenter} right={headerRight} /> | ||
| <main className="pt-15">{children}</main> | ||
| <header className="fixed top-0 right-0 left-0 z-50 flex h-15 items-center justify-between border-b border-gray-200 bg-white px-18"> | ||
| <div className="flex items-center gap-6">{left ?? <Logo />}</div> | ||
| <div className="absolute left-1/2 -translate-x-1/2">{center}</div> | ||
| <div className="flex items-center gap-8">{right}</div> | ||
| </header> | ||
| <main className="pt-15"> | ||
| <Outlet /> | ||
| </main> | ||
| </div> | ||
| ); | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,55 @@ | ||
| export const TABS = [ | ||
| { key: 'slide', label: '슬라이드', path: '/slide' }, | ||
| { key: 'video', label: '영상', path: '/video' }, | ||
| { key: 'insight', label: '인사이트', path: '/insight' }, | ||
| { key: 'slide', label: '슬라이드' }, | ||
| { key: 'video', label: '영상' }, | ||
| { key: 'insight', label: '인사이트' }, | ||
| ] as const; | ||
|
|
||
| export type Tab = (typeof TABS)[number]['key']; | ||
|
|
||
| export const DEFAULT_TAB: Tab = 'slide'; | ||
| export const DEFAULT_SLIDE_ID = '1'; | ||
|
|
||
| export const PATH_TO_TAB: Record<string, Tab> = { | ||
| '/': DEFAULT_TAB, | ||
| ...Object.fromEntries(TABS.map((tab) => [tab.path, tab.key])), | ||
| const LAST_SLIDE_KEY_PREFIX = 'lastSlideId:'; | ||
|
|
||
| /** 마지막으로 본 슬라이드 ID 저장 */ | ||
| export const setLastSlideId = (projectId: string, slideId: string): void => { | ||
| try { | ||
| localStorage.setItem(`${LAST_SLIDE_KEY_PREFIX}${projectId}`, slideId); | ||
| } catch { | ||
| // localStorage 사용 불가 시 무시 (프라이빗 모드, 저장 공간 부족 등) | ||
| } | ||
| }; | ||
|
|
||
| /** 마지막으로 본 슬라이드 ID 조회 */ | ||
| export const getLastSlideId = (projectId: string): string => { | ||
| try { | ||
| return localStorage.getItem(`${LAST_SLIDE_KEY_PREFIX}${projectId}`) ?? DEFAULT_SLIDE_ID; | ||
| } catch { | ||
| return DEFAULT_SLIDE_ID; | ||
| } | ||
| }; | ||
|
|
||
| /** 탭별 경로 생성 */ | ||
| export const getTabPath = (projectId: string, tab: Tab, slideId?: string): string => { | ||
| switch (tab) { | ||
| case 'slide': | ||
| return `/${projectId}/slide/${slideId ?? getLastSlideId(projectId)}`; | ||
| case 'video': | ||
| return `/${projectId}/video`; | ||
| case 'insight': | ||
| return `/${projectId}/insight`; | ||
| } | ||
| }; | ||
|
|
||
| /** pathname에서 탭 추출 (/:projectId/:tab/...) */ | ||
| export const getTabFromPathname = (pathname: string): Tab => { | ||
| const segments = pathname.split('/').filter(Boolean); | ||
|
|
||
| // 프로젝트 경로 형태가 아닌 경우 (예: '/', '/settings' 등) | ||
| if (segments.length < 2) return 'slide'; | ||
|
|
||
| const tabSegment = segments[1]; | ||
|
|
||
| if (tabSegment === 'video') return 'video'; | ||
| if (tabSegment === 'insight') return 'insight'; | ||
| return 'slide'; | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,20 +1,37 @@ | ||||||||||||||||||
| import { StrictMode } from 'react'; | ||||||||||||||||||
| import { createRoot } from 'react-dom/client'; | ||||||||||||||||||
| import { RouterProvider, createBrowserRouter } from 'react-router-dom'; | ||||||||||||||||||
| import { Navigate, RouterProvider, createBrowserRouter } from 'react-router-dom'; | ||||||||||||||||||
|
|
||||||||||||||||||
| import App from './App'; | ||||||||||||||||||
| import InsightPage from './pages/InsightPage'; | ||||||||||||||||||
| import SlidePage from './pages/SlidePage'; | ||||||||||||||||||
| import VideoPage from './pages/VideoPage'; | ||||||||||||||||||
| import './styles/index.css'; | ||||||||||||||||||
| import { LoginButton, Logo } from '@/components/common'; | ||||||||||||||||||
| import { Gnb } from '@/components/layout/Gnb'; | ||||||||||||||||||
| import { Layout } from '@/components/layout/Layout'; | ||||||||||||||||||
| import { DEFAULT_SLIDE_ID } from '@/constants/navigation'; | ||||||||||||||||||
| import { HomePage, InsightPage, SlidePage, VideoPage } from '@/pages'; | ||||||||||||||||||
| import '@/styles/index.css'; | ||||||||||||||||||
|
|
||||||||||||||||||
| const router = createBrowserRouter([ | ||||||||||||||||||
| { | ||||||||||||||||||
| path: '/', | ||||||||||||||||||
| element: <App />, | ||||||||||||||||||
| element: <Layout right={<LoginButton />} />, | ||||||||||||||||||
| children: [{ index: true, element: <HomePage /> }], | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| path: '/:projectId', | ||||||||||||||||||
| element: ( | ||||||||||||||||||
| <Layout | ||||||||||||||||||
| left={ | ||||||||||||||||||
| <> | ||||||||||||||||||
| <Logo /> | ||||||||||||||||||
| <span className="text-body-m-bold text-gray-800">내 발표</span> | ||||||||||||||||||
| </> | ||||||||||||||||||
|
Comment on lines
+23
to
+26
|
||||||||||||||||||
| <> | |
| <Logo /> | |
| <span className="text-body-m-bold text-gray-800">내 발표</span> | |
| </> | |
| <div> | |
| <Logo /> | |
| <span className="text-body-m-bold text-gray-800">내 발표</span> | |
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fragment는 DOM에 렌더링되지 않고 풀어지기 때문에, <Logo />와 <span>이 Layout의 gap-6이 적용된 div의 직접적인 자식이 됩니다. 따라서 gap이 정상 적용됩니다.
오히려 div로 감싸면 Logo와 span 사이에 gap이 적용되지 않게 됩니다.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export default function HomePage() { | ||
| return ( | ||
| <div className="p-8"> | ||
| <h1 className="text-body-m-bold">홈</h1> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,23 @@ | ||
| import { useEffect } from 'react'; | ||
| import { useParams } from 'react-router-dom'; | ||
|
|
||
| import { setLastSlideId } from '@/constants/navigation'; | ||
|
|
||
| export default function SlidePage() { | ||
| const { projectId, slideId } = useParams<{ | ||
| projectId: string; | ||
| slideId: string; | ||
| }>(); | ||
|
|
||
| useEffect(() => { | ||
| if (projectId && slideId) { | ||
| setLastSlideId(projectId, slideId); | ||
| } | ||
| }, [projectId, slideId]); | ||
|
|
||
| return ( | ||
| <div role="tabpanel" id="tabpanel-slide" aria-labelledby="tab-slide" className="p-8"> | ||
| <h1 className="text-body-m-bold">슬라이드</h1> | ||
| <h1 className="text-body-m-bold">슬라이드 {slideId}</h1> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export { default as HomePage } from './HomePage'; | ||
| export { default as InsightPage } from './InsightPage'; | ||
| export { default as SlidePage } from './SlidePage'; | ||
| export { default as VideoPage } from './VideoPage'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,14 @@ | ||
| import tailwindcss from '@tailwindcss/vite'; | ||
| import react from '@vitejs/plugin-react-swc'; | ||
|
|
||
| import path from 'path'; | ||
| import { defineConfig } from 'vite'; | ||
|
|
||
| // https://vite.dev/config/ | ||
| export default defineConfig({ | ||
| plugins: [react(), tailwindcss()], | ||
| resolve: { | ||
| alias: { | ||
| '@': path.resolve(__dirname, './src'), | ||
| }, | ||
| }, | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.