Compare commits

...

6 Commits

Author SHA1 Message Date
ccf9b62e59 pretendard 적용 2026-04-08 10:43:02 +09:00
73f97aad5f 불필요 파일 제거 2026-04-08 10:39:10 +09:00
dee4b5cbf3 readme 수정 2026-04-08 10:27:11 +09:00
94eb9327d1 도커 설정 추가 2026-04-08 10:03:48 +09:00
1fb7e8df42 eslint 적용 2026-04-08 08:42:04 +09:00
225cf0c9ed feat: init 2026-04-07 17:30:18 +09:00
18 changed files with 4085 additions and 2 deletions

16
web-app/.editorconfig Normal file
View 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
View 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
View 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"]

View File

@@ -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
View 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
View 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
View 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;

View File

@@ -0,0 +1,3 @@
export function clientLoader() {
throw new Response("Not Found", { status: 404 });
}

View File

@@ -0,0 +1,7 @@
export default function Home() {
return (
<div>
Home
</div>
);
}

View File

@@ -0,0 +1,7 @@
export default function Users() {
return (
<div className="px-3">
</div>
);
}

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

BIN
web-app/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View 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
View 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
View 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,
},
});