UMC 운영팀이 정책·계정·데이터를 한 곳에서 관리할 수 있도록 만든 백오피스입니다. 권한(챌린저, 파트장, 회장단, 총괄)에 따라 다른 뷰를 제공하며, 운영 효율과 정책 반영 속도를 높이는 데 집중합니다.
- Contributors
- Tech Stack
- 시작하기
- 스크립트
- 프로젝트 구조
- 라우팅
- 스타일링
- 상태 관리
- 폼 관리
- 코드 품질 도구
- Commit Convention
- Code Convention
- 테스트
- 기여 가이드
- 배포
- 트러블슈팅
| 김연진(코튼) |
|---|
| @yeonjin719 |
| 도구 | 최소 버전 |
|---|---|
| Node.js | >=22.0.0 |
| pnpm | >=9.0.0 |
# 저장소 클론
git clone https://github.com/UMC-PRODUCT/product-web.git
cd product-web
# 의존성 설치
pnpm install프로젝트 루트에 .env 파일을 생성하세요:
touch .envVite 환경 변수는 VITE_ 접두사를 사용합니다:
# API 서버 URL (예시)
VITE_API_BASE_URL=https://api.example.com
# 기타 설정
VITE_APP_NAME=UMC Product Web
.env파일은.gitignore에 포함되어 있어 Git에 커밋되지 않습니다.
pnpm dev # http://localhost:3000| 명령어 | 설명 |
|---|---|
pnpm dev |
개발 서버 실행 (port 3000) |
pnpm build |
TypeScript 타입체크 + 프로덕션 빌드 |
pnpm preview |
빌드된 결과물 미리보기 |
pnpm test |
Vitest 테스트 실행 |
pnpm lint |
ESLint 검사 |
pnpm lint:fix |
ESLint 자동 수정 |
pnpm format |
Prettier로 코드 포맷팅 |
pnpm format:check |
포맷팅 상태 확인 |
pnpm typecheck |
TypeScript 타입 검사만 실행 |
src/
├── app/ # 엔트리/프로바이더/DevTools 등 앱 레벨
│ ├── main.tsx
│ ├── reportWebVitals.ts
│ └── styles.css
├── routes/ # TanStack Router 파일 기반 라우트 (얇은 어댑터)
│ ├── (app)/ # 앱 레이아웃 그룹
│ ├── (auth)/ # 인증 레이아웃 그룹
│ └── __root.tsx # 루트 레이아웃
├── features/ # 기능 단위 모듈
│ ├── auth/ # 로그인/회원가입
│ ├── management/ # 계정/학교/정책/공지/데이터 관리
│ ├── dashboard/
│ ├── apply/
│ ├── recruiting/
│ └── home/
├── shared/ # 전역 공유 자원
│ ├── assets/ # 이미지, 아이콘 등 정적 자산
│ ├── layout/ # 레이아웃 컴포넌트 (Header, Footer)
│ ├── ui/ # 공용 UI (Button, Modal, Tab 등)
│ ├── styles/ # 스타일 시스템 (theme, global, media)
│ ├── types/ # TypeScript 타입 정의
│ ├── utils/ # 유틸리티 함수
│ └── mocks/ # 공유 Mock 데이터
├── routeTree.gen.ts # TanStack Router 자동 생성 트리
└── vite-env.d.ts
@app/*→src/app/*@features/*→src/features/*@shared/*→src/shared/*@routes/*→src/routes/*@/*→src/*(가능하면 위 별칭 우선 사용)
TanStack Router의 파일 기반 라우팅을 사용합니다.
라우트 파일은 페이지 컴포넌트를 @features/*/pages에서 가져오는 얇은 어댑터로 유지합니다.
| 레이아웃 | 설명 |
|---|---|
src/routes/(app)/route.tsx |
글로벌 레이아웃 |
src/routes/(app)/management/route.tsx |
관리 전용 레이아웃 (헤더 없이 Outlet만) |
src/routes/(auth)/auth/_layout.tsx |
인증 레이아웃 |
/management/*경로 →SuperHeader- 그 외 경로 →
ChallengerHeader - Footer는 flex 레이아웃으로 하단 고정
Emotion CSS-in-JS를 사용합니다.
import { theme } from '@shared/styles/theme'
// 색상
theme.colors.primary
theme.colors.gray[500]
// 타이포그래피
theme.typography.heading1
theme.typography.body2import { media } from '@shared/styles/media'
// 미디어 쿼리
${media.down('tablet')} {
// 태블릿 이하
}
${media.up('desktop')} {
// 데스크탑 이상
}컴포넌트별 스타일은 .style.tsx 파일로 분리합니다:
shared/
└── ui/
└── common/
└── Button/
├── Button.tsx
└── Button.style.tsx
스토어는 feature 내부에 둡니다 (예: src/features/auth/store).
// 예시
import { useAuthStore } from '@features/auth/store/authStore'
const { user, login, logout } = useAuthStore()import { useQuery, useMutation } from '@tanstack/react-query'
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
})개발 환경에서 TanStack Query DevTools와 Router DevTools가 자동으로 활성화됩니다.
react-hook-form + Zod를 사용합니다.
// src/features/auth/schema/register.ts
import { z } from 'zod/v3'
export const registerSchema = z.object({
school: z.string().min(1, '학교를 선택하지 않았습니다.'),
name: z.string().min(1, '양식이 올바르지 않습니다.'),
nickname: z
.string()
.min(1, '양식이 올바르지 않습니다.')
.regex(/^[가-힣]{1,5}$/, '닉네임은 1~5글자의 한글이어야 합니다.'),
email: z.string().email('유효하지 않은 이메일 주소입니다.'),
serviceTerm: z.boolean().refine((val) => val === true, {
message: '서비스 이용 약관에 동의해 주세요.',
}),
privacyTerm: z.boolean().refine((val) => val === true, {
message: '개인정보 처리 방침에 동의해 주세요.',
}),
marketingTerm: z.boolean(),
})import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { registerSchema } from '@features/auth/schema/register'
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(registerSchema),
})ESLint 9 Flat Config를 사용합니다:
@tanstack/eslint-config기반eslint-plugin-react: React 규칙eslint-plugin-react-hooks: Hooks 규칙eslint-plugin-jsx-a11y: 접근성 규칙simple-import-sort로 import 자동 정렬
pnpm lint # 검사
pnpm lint:fix # 자동 수정prettier.config.js 주요 설정:
| 옵션 | 값 |
|---|---|
| 세미콜론 | 없음 |
| 따옴표 | 작은따옴표 |
| 줄 길이 | 100자 |
| 트레일링 콤마 | all |
| 줄바꿈 | LF |
pnpm format # 포맷팅 적용
pnpm format:check # 포맷팅 확인| Hook | 동작 |
|---|---|
| pre-commit | lint-staged 실행 (ESLint + Prettier) |
| commit-msg | commitlint로 커밋 메시지 검증 |
| pre-push | 빌드 실행 (pnpm run build) |
스테이징된 파일에만 린트/포맷 적용:
*.{ts,tsx}(_.gen.ts, _.d.ts 제외) → ESLint --fix + Prettier*.{js,jsx,cjs,mjs}→ ESLint --fix + Prettier*.{json,md,css,html,yaml}→ Prettier
| 타입 | 설명 |
|---|---|
| feat | 기능 추가 |
| fix | 버그 수정 |
| docs | 문서 변경 |
| style | 포맷/UI 변경 |
| refactor | 리팩터링 |
| test | 테스트 추가/수정 |
| chore | 잡일/설정 |
| ci | CI 설정 |
| build | 빌드 설정 |
| perf | 성능 개선 |
<type>(<scope>): <subject>
<body>
<footer>
예시:
feat(auth): 로그인 폼 유효성 검사 추가
- 이메일 형식 검증
- 비밀번호 최소 길이 검증
Closes #123| 구분 | 내용 |
|---|---|
| 브레이킹 | BREAKING CHANGE: 문구로 명시 |
| 포맷/린트 | pnpm lint 준수, 임포트 순서 규칙 준수, 경로는 @shared/*, @features/*, @app/*, @routes/* 우선 사용 |
| 스타일 | Emotion 사용 시 .style.tsx로 분리, theme.colors/typography, media 우선 사용 |
| 타입 | Array<T> 표기, 공용 유틸(resolveTypo 등)로 널 가드 |
| 컴포넌트 | 공용 Header/Modal/Badge 재사용, 반응형은 media.down/up 활용 |
pnpm test # Vitest 실행- Vitest: 테스트 러너
- @testing-library/react: React 컴포넌트 테스트
- jsdom: 브라우저 환경 시뮬레이션
| 브랜치 | 용도 |
|---|---|
main |
프로덕션 브랜치 |
develop |
개발 브랜치 (PR 기본 대상) |
feature/* |
기능 개발 |
fix/* |
버그 수정 |
chore/* |
설정/잡일 |
refactor/* |
리팩터링 |
-
develop브랜치에서 새 브랜치 생성git checkout develop git pull origin develop git checkout -b feature/기능명
-
작업 후 커밋 (Conventional Commits 준수)
git add . git commit -m "feat: 새로운 기능 추가"
-
PR 생성
.github/pull_request_template.md템플릿 사용- 관련 이슈 연결
- 테스트 결과 첨부
pnpm build # dist/ 폴더 생성빌드된 dist/ 폴더를 정적 호스팅 서비스에 배포합니다.
ERROR: This project requires pnpm version >=9.0.0해결:
corepack enable
corepack prepare pnpm@latest --activateERROR: This project requires Node.js version >=22.0.0해결 (nvm 사용 시):
nvm install 22
nvm use 22pnpm build는 타입체크를 포함합니다. 개발 중 타입 문제를 미리 확인하려면:
pnpm typechecksimple-import-sort 플러그인이 import 순서를 검사합니다. 자동 수정:
pnpm lint:fixpnpm prepare # Husky 재설치Private