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