feat: init #1
16
web-app/.editorconfig
Normal file
16
web-app/.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# EditorConfig is awesome: https://editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
[*.{js,jsx,ts,tsx}]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
49
web-app/.gitignore
vendored
Normal file
49
web-app/.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnpm-store
|
||||
|
||||
# Build outputs
|
||||
build/
|
||||
dist/
|
||||
.output/
|
||||
|
||||
# React Router
|
||||
.react-router/
|
||||
|
||||
# Vite
|
||||
.vite/
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# IDE
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
26
web-app/Dockerfile
Normal file
26
web-app/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM node:24-alpine AS development-dependencies-env
|
||||
RUN npm install -g corepack && corepack enable
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
FROM node:24-alpine AS production-dependencies-env
|
||||
RUN npm install -g corepack && corepack enable
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
WORKDIR /app
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
|
||||
FROM node:24-alpine AS build-env
|
||||
RUN npm install -g corepack && corepack enable
|
||||
COPY . /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN pnpm run build
|
||||
|
||||
FROM node:24-alpine
|
||||
RUN npm install -g corepack && corepack enable
|
||||
COPY ./package.json pnpm-lock.yaml /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
WORKDIR /app
|
||||
CMD ["pnpm", "run", "start"]
|
||||
@@ -1,3 +1,102 @@
|
||||
### 다비오 변화 탐지 시스템
|
||||
# 다비오 변화 탐지 시스템 - Web Application
|
||||
|
||||
## API application
|
||||
React Router 7 기반 SPA 웹 애플리케이션
|
||||
|
||||
## 기술 스택
|
||||
|
||||
| 분류 | 기술 |
|
||||
|------|------|
|
||||
| Framework | React 19 + React Router 7 (SPA) |
|
||||
| Language | TypeScript 5.9 (strict) |
|
||||
| Styling | Tailwind CSS 4.2 |
|
||||
| Build Tool | Vite 8 |
|
||||
| Package Manager | pnpm (corepack) |
|
||||
| Code Quality | ESLint 10 + Prettier |
|
||||
| Container | Docker + Docker Compose |
|
||||
|
||||
## 사전 요구사항
|
||||
|
||||
- **Node.js** 24+
|
||||
- **corepack** 활성화 (`corepack enable` 실행 시 pnpm 자동 설치)
|
||||
- **Docker & Docker Compose** (선택, 컨테이너 환경 사용 시)
|
||||
|
||||
## 시작하기
|
||||
|
||||
### 로컬 개발
|
||||
|
||||
```bash
|
||||
corepack enable
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
개발 서버가 `http://localhost:5173`에서 실행됩니다.
|
||||
|
||||
### Docker 개발
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
소스 코드가 볼륨 마운트되어 HMR이 동작합니다. `http://localhost:5173`에서 접근 가능합니다.
|
||||
|
||||
## 프로젝트 구조
|
||||
|
||||
```
|
||||
web-app/
|
||||
├── app/
|
||||
│ ├── root.tsx # Root layout, ErrorBoundary
|
||||
│ ├── app.css # Tailwind 글로벌 스타일
|
||||
│ ├── routes.ts # 라우트 정의
|
||||
│ ├── routes/
|
||||
│ │ ├── home.tsx # 메인 페이지 (/)
|
||||
│ │ ├── users.tsx # 유저 목록 (/users)
|
||||
│ │ └── catch-all.tsx # 404 처리
|
||||
│ └── welcome/ # Welcome 컴포넌트
|
||||
├── public/ # 정적 파일
|
||||
├── Dockerfile # 프로덕션 빌드 (multi-stage)
|
||||
├── docker-compose.yml # 개발 환경
|
||||
├── vite.config.ts # Vite 설정
|
||||
├── react-router.config.ts # React Router 설정
|
||||
├── tsconfig.json # TypeScript 설정
|
||||
└── eslint.config.js # ESLint 설정
|
||||
```
|
||||
|
||||
> **경로 별칭**: `~/`는 `./app/`을 가리킵니다. (`tsconfig.json`에서 설정)
|
||||
|
||||
## 스크립트
|
||||
|
||||
| 명령어 | 설명 |
|
||||
|--------|------|
|
||||
| `pnpm dev` | 개발 서버 실행 (Vite HMR) |
|
||||
| `pnpm build` | 프로덕션 빌드 |
|
||||
| `pnpm start` | 프로덕션 서버 실행 |
|
||||
| `pnpm typecheck` | TypeScript 타입 검사 |
|
||||
| `pnpm lint` | ESLint 검사 |
|
||||
| `pnpm lint:fix` | ESLint 자동 수정 |
|
||||
|
||||
## Docker
|
||||
|
||||
### 개발 환경 (Docker Compose)
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
- 소스 코드를 컨테이너에 볼륨 마운트하여 실시간 반영
|
||||
- `node_modules`는 별도 named volume으로 관리
|
||||
- 포트: `5173`
|
||||
|
||||
### 프로덕션 빌드 (Dockerfile)
|
||||
|
||||
```bash
|
||||
docker build -t dabeeo-web .
|
||||
docker run -p 3000:3000 dabeeo-web
|
||||
```
|
||||
|
||||
Multi-stage 빌드로 최적화된 이미지를 생성합니다:
|
||||
|
||||
1. **development-dependencies-env** - 전체 의존성 설치 (빌드 도구 포함)
|
||||
2. **production-dependencies-env** - 프로덕션 의존성만 설치
|
||||
3. **build-env** - 애플리케이션 빌드
|
||||
4. **final** - 프로덕션 의존성 + 빌드 결과물만 포함하여 실행
|
||||
|
||||
12
web-app/app/app.css
Normal file
12
web-app/app/app.css
Normal file
@@ -0,0 +1,12 @@
|
||||
@import "tailwindcss";
|
||||
@import "pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css";
|
||||
|
||||
@theme {
|
||||
--font-sans: "Pretendard Variable", ui-sans-serif, system-ui, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
|
||||
}
|
||||
65
web-app/app/root.tsx
Normal file
65
web-app/app/root.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { Route } from './+types/root';
|
||||
|
||||
import './app.css';
|
||||
|
||||
import {
|
||||
isRouteErrorResponse,
|
||||
Links,
|
||||
Meta,
|
||||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
} from 'react-router';
|
||||
|
||||
export const links: Route.LinksFunction = () => [];
|
||||
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
{children}
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
let message = 'Oops!';
|
||||
let details = 'An unexpected error occurred.';
|
||||
let stack: string | undefined;
|
||||
|
||||
if (isRouteErrorResponse(error)) {
|
||||
message = error.status === 404 ? '404' : 'Error';
|
||||
details
|
||||
= error.status === 404
|
||||
? 'The requested page could not be found.'
|
||||
: error.statusText || details;
|
||||
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
||||
details = error.message;
|
||||
stack = error.stack;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="pt-16 p-4 container mx-auto">
|
||||
<h1>{message}</h1>
|
||||
<p>{details}</p>
|
||||
{stack && (
|
||||
<pre className="w-full p-4 overflow-x-auto">
|
||||
<code>{stack}</code>
|
||||
</pre>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
9
web-app/app/routes.ts
Normal file
9
web-app/app/routes.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { RouteConfig } from '@react-router/dev/routes';
|
||||
|
||||
import { index, route } from '@react-router/dev/routes';
|
||||
|
||||
export default [
|
||||
index('routes/home.tsx'),
|
||||
route('users', './routes/users.tsx'),
|
||||
route('*', './routes/catch-all.tsx'),
|
||||
] satisfies RouteConfig;
|
||||
3
web-app/app/routes/catch-all.tsx
Normal file
3
web-app/app/routes/catch-all.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export function clientLoader() {
|
||||
throw new Response("Not Found", { status: 404 });
|
||||
}
|
||||
7
web-app/app/routes/home.tsx
Normal file
7
web-app/app/routes/home.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
Home
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
web-app/app/routes/users.tsx
Normal file
7
web-app/app/routes/users.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Users() {
|
||||
return (
|
||||
<div className="px-3">
|
||||
유저목록
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
web-app/docker-compose.yml
Normal file
15
web-app/docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
web:
|
||||
image: node:24-alpine
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
- node_modules:/app/node_modules
|
||||
ports:
|
||||
- "5173:5173"
|
||||
command: sh -c "npm install -g corepack && corepack enable && pnpm install && pnpm run dev --host"
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
21
web-app/eslint.config.js
Normal file
21
web-app/eslint.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['node_modules/', 'dist/', 'build/', '.react-router/'] },
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
stylistic.configs.customize({
|
||||
indent: 2,
|
||||
semi: true,
|
||||
jsx: true,
|
||||
braceStyle: '1tbs',
|
||||
commaDangle: 'always-multiline',
|
||||
quoteProps: 'as-needed',
|
||||
arrowParens: true,
|
||||
}),
|
||||
reactHooks.configs.flat.recommended,
|
||||
);
|
||||
37
web-app/package.json
Normal file
37
web-app/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "dabeeo-detection",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "react-router build",
|
||||
"dev": "react-router dev",
|
||||
"start": "react-router-serve ./build/server/index.js",
|
||||
"typecheck": "react-router typegen && tsc",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-router/node": "7.14.0",
|
||||
"@react-router/serve": "7.14.0",
|
||||
"isbot": "^5.1.37",
|
||||
"pretendard": "^1.3.9",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-router": "7.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@react-router/dev": "7.14.0",
|
||||
"@stylistic/eslint-plugin": "^5.10.0",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"eslint": "^10.2.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"vite": "^8.0.3"
|
||||
}
|
||||
}
|
||||
3674
web-app/pnpm-lock.yaml
generated
Normal file
3674
web-app/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
web-app/public/favicon.ico
Normal file
BIN
web-app/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
7
web-app/react-router.config.ts
Normal file
7
web-app/react-router.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Config } from '@react-router/dev/config';
|
||||
|
||||
export default {
|
||||
// Config options...
|
||||
// Server-side render by default, to enable SPA mode set this to `false`
|
||||
ssr: false,
|
||||
} satisfies Config;
|
||||
26
web-app/tsconfig.json
Normal file
26
web-app/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"include": [
|
||||
"**/*",
|
||||
"**/.server/**/*",
|
||||
"**/.client/**/*",
|
||||
".react-router/types/**/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"types": ["node", "vite/client"],
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"rootDirs": [".", "./.react-router/types"],
|
||||
"paths": {
|
||||
"~/*": ["./app/*"]
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
10
web-app/vite.config.ts
Normal file
10
web-app/vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { reactRouter } from "@react-router/dev/vite";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), reactRouter()],
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user