diff --git a/apps/web/package.json b/apps/web/package.json index 163d9bf7..d46fbef7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,6 +14,8 @@ "dependencies": { "@repo/shared": "workspace:*", "@repo/types": "workspace:*", + "@tanstack/react-router": "^1.79.0", + "@tanstack/router-vite-plugin": "^1.79.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -21,6 +23,7 @@ "@eslint/js": "^9.13.0", "@repo/lint": "workspace:*", "@repo/tsconfig": "workspace:*", + "@tanstack/router-cli": "^1.79.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index b4f6543f..43bae127 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,5 +1,19 @@ +import { createRouter, RouterProvider } from '@tanstack/react-router'; + +import { routeTree } from './routeTree.gen'; +const router = createRouter({ routeTree }); +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + function App() { - return <>; + return ( + <> + + + ); } export default App; diff --git a/apps/web/src/components/common/Header.tsx b/apps/web/src/components/common/Header.tsx new file mode 100644 index 00000000..922d3e81 --- /dev/null +++ b/apps/web/src/components/common/Header.tsx @@ -0,0 +1,20 @@ +import { Link } from '@tanstack/react-router'; + +/**@desc router 테스트를 위해 임시 구현한 Header입니다. */ +function Header() { + return ( + + ); +} + +export default Header; diff --git a/apps/web/src/components/dashboard/index.tsx b/apps/web/src/components/dashboard/index.tsx new file mode 100644 index 00000000..dc692835 --- /dev/null +++ b/apps/web/src/components/dashboard/index.tsx @@ -0,0 +1,17 @@ +import { Link } from '@tanstack/react-router'; + +/**@desc router 테스트를 위해 임시 구현한 DashboardTab입니다. */ +function DashboardTab() { + return ( + + ); +} + +export default DashboardTab; diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts new file mode 100644 index 00000000..5c23c4d7 --- /dev/null +++ b/apps/web/src/routeTree.gen.ts @@ -0,0 +1,314 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +// Import Routes + +import { Route as rootRoute } from './routes/__root'; +import { Route as DashboardLayoutImport } from './routes/dashboard/_layout'; +import { Route as IndexImport } from './routes/index'; +import { Route as TicleOpenImport } from './routes/ticle/open'; +import { Route as TicleTicleIdImport } from './routes/ticle/$ticleId'; +import { Route as LiveTicleIdImport } from './routes/live/$ticleId'; +import { Route as DashboardOpenImport } from './routes/dashboard/open'; +import { Route as DashboardApplyImport } from './routes/dashboard/apply'; +import { Route as AuthOauthImport } from './routes/auth/oauth'; +import { Route as AuthLoginImport } from './routes/auth/login'; + +// Create/Update Routes + +const DashboardLayoutRoute = DashboardLayoutImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRoute, +} as any); + +const IndexRoute = IndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, +} as any); + +const TicleOpenRoute = TicleOpenImport.update({ + id: '/ticle/open', + path: '/ticle/open', + getParentRoute: () => rootRoute, +} as any); + +const TicleTicleIdRoute = TicleTicleIdImport.update({ + id: '/ticle/$ticleId', + path: '/ticle/$ticleId', + getParentRoute: () => rootRoute, +} as any); + +const LiveTicleIdRoute = LiveTicleIdImport.update({ + id: '/live/$ticleId', + path: '/live/$ticleId', + getParentRoute: () => rootRoute, +} as any); + +const DashboardOpenRoute = DashboardOpenImport.update({ + id: '/open', + path: '/open', + getParentRoute: () => DashboardLayoutRoute, +} as any); + +const DashboardApplyRoute = DashboardApplyImport.update({ + id: '/apply', + path: '/apply', + getParentRoute: () => DashboardLayoutRoute, +} as any); + +const AuthOauthRoute = AuthOauthImport.update({ + id: '/auth/oauth', + path: '/auth/oauth', + getParentRoute: () => rootRoute, +} as any); + +const AuthLoginRoute = AuthLoginImport.update({ + id: '/auth/login', + path: '/auth/login', + getParentRoute: () => rootRoute, +} as any); + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/'; + path: '/'; + fullPath: '/'; + preLoaderRoute: typeof IndexImport; + parentRoute: typeof rootRoute; + }; + '/dashboard': { + id: '/dashboard'; + path: '/dashboard'; + fullPath: '/dashboard'; + preLoaderRoute: typeof DashboardLayoutImport; + parentRoute: typeof rootRoute; + }; + '/auth/login': { + id: '/auth/login'; + path: '/auth/login'; + fullPath: '/auth/login'; + preLoaderRoute: typeof AuthLoginImport; + parentRoute: typeof rootRoute; + }; + '/auth/oauth': { + id: '/auth/oauth'; + path: '/auth/oauth'; + fullPath: '/auth/oauth'; + preLoaderRoute: typeof AuthOauthImport; + parentRoute: typeof rootRoute; + }; + '/dashboard/apply': { + id: '/dashboard/apply'; + path: '/apply'; + fullPath: '/dashboard/apply'; + preLoaderRoute: typeof DashboardApplyImport; + parentRoute: typeof DashboardLayoutImport; + }; + '/dashboard/open': { + id: '/dashboard/open'; + path: '/open'; + fullPath: '/dashboard/open'; + preLoaderRoute: typeof DashboardOpenImport; + parentRoute: typeof DashboardLayoutImport; + }; + '/live/$ticleId': { + id: '/live/$ticleId'; + path: '/live/$ticleId'; + fullPath: '/live/$ticleId'; + preLoaderRoute: typeof LiveTicleIdImport; + parentRoute: typeof rootRoute; + }; + '/ticle/$ticleId': { + id: '/ticle/$ticleId'; + path: '/ticle/$ticleId'; + fullPath: '/ticle/$ticleId'; + preLoaderRoute: typeof TicleTicleIdImport; + parentRoute: typeof rootRoute; + }; + '/ticle/open': { + id: '/ticle/open'; + path: '/ticle/open'; + fullPath: '/ticle/open'; + preLoaderRoute: typeof TicleOpenImport; + parentRoute: typeof rootRoute; + }; + } +} + +// Create and export the route tree + +interface DashboardLayoutRouteChildren { + DashboardApplyRoute: typeof DashboardApplyRoute; + DashboardOpenRoute: typeof DashboardOpenRoute; +} + +const DashboardLayoutRouteChildren: DashboardLayoutRouteChildren = { + DashboardApplyRoute: DashboardApplyRoute, + DashboardOpenRoute: DashboardOpenRoute, +}; + +const DashboardLayoutRouteWithChildren = DashboardLayoutRoute._addFileChildren( + DashboardLayoutRouteChildren +); + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute; + '/dashboard': typeof DashboardLayoutRouteWithChildren; + '/auth/login': typeof AuthLoginRoute; + '/auth/oauth': typeof AuthOauthRoute; + '/dashboard/apply': typeof DashboardApplyRoute; + '/dashboard/open': typeof DashboardOpenRoute; + '/live/$ticleId': typeof LiveTicleIdRoute; + '/ticle/$ticleId': typeof TicleTicleIdRoute; + '/ticle/open': typeof TicleOpenRoute; +} + +export interface FileRoutesByTo { + '/': typeof IndexRoute; + '/dashboard': typeof DashboardLayoutRouteWithChildren; + '/auth/login': typeof AuthLoginRoute; + '/auth/oauth': typeof AuthOauthRoute; + '/dashboard/apply': typeof DashboardApplyRoute; + '/dashboard/open': typeof DashboardOpenRoute; + '/live/$ticleId': typeof LiveTicleIdRoute; + '/ticle/$ticleId': typeof TicleTicleIdRoute; + '/ticle/open': typeof TicleOpenRoute; +} + +export interface FileRoutesById { + __root__: typeof rootRoute; + '/': typeof IndexRoute; + '/dashboard': typeof DashboardLayoutRouteWithChildren; + '/auth/login': typeof AuthLoginRoute; + '/auth/oauth': typeof AuthOauthRoute; + '/dashboard/apply': typeof DashboardApplyRoute; + '/dashboard/open': typeof DashboardOpenRoute; + '/live/$ticleId': typeof LiveTicleIdRoute; + '/ticle/$ticleId': typeof TicleTicleIdRoute; + '/ticle/open': typeof TicleOpenRoute; +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath; + fullPaths: + | '/' + | '/dashboard' + | '/auth/login' + | '/auth/oauth' + | '/dashboard/apply' + | '/dashboard/open' + | '/live/$ticleId' + | '/ticle/$ticleId' + | '/ticle/open'; + fileRoutesByTo: FileRoutesByTo; + to: + | '/' + | '/dashboard' + | '/auth/login' + | '/auth/oauth' + | '/dashboard/apply' + | '/dashboard/open' + | '/live/$ticleId' + | '/ticle/$ticleId' + | '/ticle/open'; + id: + | '__root__' + | '/' + | '/dashboard' + | '/auth/login' + | '/auth/oauth' + | '/dashboard/apply' + | '/dashboard/open' + | '/live/$ticleId' + | '/ticle/$ticleId' + | '/ticle/open'; + fileRoutesById: FileRoutesById; +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute; + DashboardLayoutRoute: typeof DashboardLayoutRouteWithChildren; + AuthLoginRoute: typeof AuthLoginRoute; + AuthOauthRoute: typeof AuthOauthRoute; + LiveTicleIdRoute: typeof LiveTicleIdRoute; + TicleTicleIdRoute: typeof TicleTicleIdRoute; + TicleOpenRoute: typeof TicleOpenRoute; +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + DashboardLayoutRoute: DashboardLayoutRouteWithChildren, + AuthLoginRoute: AuthLoginRoute, + AuthOauthRoute: AuthOauthRoute, + LiveTicleIdRoute: LiveTicleIdRoute, + TicleTicleIdRoute: TicleTicleIdRoute, + TicleOpenRoute: TicleOpenRoute, +}; + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes(); + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/dashboard", + "/auth/login", + "/auth/oauth", + "/live/$ticleId", + "/ticle/$ticleId", + "/ticle/open" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/dashboard": { + "filePath": "dashboard/_layout.tsx", + "children": [ + "/dashboard/apply", + "/dashboard/open" + ] + }, + "/auth/login": { + "filePath": "auth/login.tsx" + }, + "/auth/oauth": { + "filePath": "auth/oauth.tsx" + }, + "/dashboard/apply": { + "filePath": "dashboard/apply.tsx", + "parent": "/dashboard" + }, + "/dashboard/open": { + "filePath": "dashboard/open.tsx", + "parent": "/dashboard" + }, + "/live/$ticleId": { + "filePath": "live/$ticleId.tsx" + }, + "/ticle/$ticleId": { + "filePath": "ticle/$ticleId.tsx" + }, + "/ticle/open": { + "filePath": "ticle/open.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx new file mode 100644 index 00000000..39cad695 --- /dev/null +++ b/apps/web/src/routes/__root.tsx @@ -0,0 +1,22 @@ +import { Outlet, createRootRoute, useLocation } from '@tanstack/react-router'; + +import Header from '@/components/common/Header'; + +const NO_HEADER_PATHS = ['/live', '/auth']; + +export const Route = createRootRoute({ + component: RootComponent, +}); + +function RootComponent() { + const location = useLocation(); + const currentPath = location.pathname; + const hasHeader = !NO_HEADER_PATHS.some((path) => currentPath.startsWith(path)); + + return ( + <> + {hasHeader &&
} + + + ); +} diff --git a/apps/web/src/routes/auth/login.tsx b/apps/web/src/routes/auth/login.tsx new file mode 100644 index 00000000..712242c5 --- /dev/null +++ b/apps/web/src/routes/auth/login.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/auth/login')({ + component: RouteComponent, +}); + +function RouteComponent() { + return 'local 로그인'; +} diff --git a/apps/web/src/routes/auth/oauth.tsx b/apps/web/src/routes/auth/oauth.tsx new file mode 100644 index 00000000..a07012af --- /dev/null +++ b/apps/web/src/routes/auth/oauth.tsx @@ -0,0 +1,16 @@ +import { createFileRoute, Link } from '@tanstack/react-router'; + +export const Route = createFileRoute('/auth/oauth')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+ OAuth 로그인 + + 로컬 로그인 + +
+ ); +} diff --git a/apps/web/src/routes/dashboard/_layout.tsx b/apps/web/src/routes/dashboard/_layout.tsx new file mode 100644 index 00000000..1c44db74 --- /dev/null +++ b/apps/web/src/routes/dashboard/_layout.tsx @@ -0,0 +1,23 @@ +import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'; + +import DashboardTab from '@/components/dashboard'; + +export const Route = createFileRoute('/dashboard')({ + component: RouteComponent, + beforeLoad: () => { + if (window.location.pathname === '/dashboard') { + throw redirect({ + to: '/dashboard/apply', + }); + } + }, +}); + +function RouteComponent() { + return ( + <> + + + + ); +} diff --git a/apps/web/src/routes/dashboard/apply.tsx b/apps/web/src/routes/dashboard/apply.tsx new file mode 100644 index 00000000..56ce5bab --- /dev/null +++ b/apps/web/src/routes/dashboard/apply.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/dashboard/apply')({ + component: RouteComponent, +}); + +function RouteComponent() { + return '내가 신청한 티클'; +} diff --git a/apps/web/src/routes/dashboard/open.tsx b/apps/web/src/routes/dashboard/open.tsx new file mode 100644 index 00000000..33fc20bf --- /dev/null +++ b/apps/web/src/routes/dashboard/open.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/dashboard/open')({ + component: RouteComponent, +}); + +function RouteComponent() { + return '내가 개설한 티클'; +} diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx new file mode 100644 index 00000000..a2d07bc8 --- /dev/null +++ b/apps/web/src/routes/index.tsx @@ -0,0 +1,24 @@ +import { createFileRoute, Link } from '@tanstack/react-router'; + +export const Route = createFileRoute('/')({ + component: IndexPage, +}); + +function IndexPage() { + return ( + <> + 티클 개설하기 +
+ + Ticle 1 + + + Ticle 2 + + + Ticle 3 + +
+ + ); +} diff --git a/apps/web/src/routes/live/$ticleId.tsx b/apps/web/src/routes/live/$ticleId.tsx new file mode 100644 index 00000000..9ded0d41 --- /dev/null +++ b/apps/web/src/routes/live/$ticleId.tsx @@ -0,0 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/live/$ticleId')({ + component: RouteComponent, +}); + +function RouteComponent() { + const { ticleId } = Route.useParams(); + return `live ${ticleId}`; +} diff --git a/apps/web/src/routes/ticle/$ticleId.tsx b/apps/web/src/routes/ticle/$ticleId.tsx new file mode 100644 index 00000000..62ff6019 --- /dev/null +++ b/apps/web/src/routes/ticle/$ticleId.tsx @@ -0,0 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/ticle/$ticleId')({ + component: RouteComponent, +}); + +function RouteComponent() { + const { ticleId } = Route.useParams(); + return `ticle ${ticleId}`; +} diff --git a/apps/web/src/routes/ticle/open.tsx b/apps/web/src/routes/ticle/open.tsx new file mode 100644 index 00000000..1e3ecaf4 --- /dev/null +++ b/apps/web/src/routes/ticle/open.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/ticle/open')({ + component: RouteComponent, +}); + +function RouteComponent() { + return '티클 개설하기'; +} diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts index f7d4110a..aa284dc9 100644 --- a/apps/web/tailwind.config.ts +++ b/apps/web/tailwind.config.ts @@ -3,7 +3,7 @@ import type { Config } from 'tailwindcss'; const config: Config = { content: [ - './src/pages/**/*.{js,ts,jsx,tsx}', + './src/routes/**/*.{js,ts,jsx,tsx}', './src/components/**/*.{js,ts,jsx,tsx}', './src/*.tsx', ], diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index edda3dee..6f1052d1 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,8 +1,9 @@ +import { TanStackRouterVite } from '@tanstack/router-vite-plugin'; import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; // https://vite.dev/config/ export default defineConfig({ - plugins: [react(), tsconfigPaths()], + plugins: [react(), tsconfigPaths(), TanStackRouterVite({ routeToken: '_layout' })], });