# o2o-infinith-frontend INFINITH 마케팅 분석 플랫폼의 프론트엔드. React 19 + Vite + Tailwind v4 + TanStack Query. ## 기술 스펙 | 영역 | 라이브러리 / 도구 | 비고 | | --- | --- | --- | | 언어 | TypeScript ~5.8 | | | 빌드 | Vite 6 | `@vitejs/plugin-react` | | UI 프레임워크 | React 19 | | | 스타일 | Tailwind CSS v4 | CSS-first 방식 (config 파일 없음) | | 컴포넌트 | [shadcn/ui](https://ui.shadcn.com/) | Radix + Tailwind 조합, `src/shared/ui/` 에 복사해 사용 | | 아이콘 | lucide-react, 커스텀 (`src/shared/icons/`) | | | 애니메이션 | motion (Framer Motion 후속), tw-animate-css | | | 라우팅 | react-router 7 | | | 데이터 페칭 | TanStack Query 5 | | | HTTP 클라이언트 | ky | `customFetcher` 의 베이스 | | API SDK 생성 | orval 7 | OpenAPI → React Query 훅 | | 클라이언트 상태 | zustand 5 | | | PDF 출력 | jspdf, html2canvas-pro | | | 폰트 | Pretendard Variable (self-host), Playfair Display | | ## 시작하기 ### 로컬 (Node) ```bash npm install npm run dev # localhost:3000 npm run build # 프로덕션 빌드 npm run lint # tsc --noEmit npm run api:gen # SDK 재생성 (orval) ``` ### Docker (개발) [Dockerfile.dev](Dockerfile.dev) + [docker-compose.yml](docker-compose.yml) — Vite dev 서버를 컨테이너에서 돌리고 호스트 소스 변경을 HMR로 반영. ```bash docker compose up # 시작 docker compose up --build # 의존성 바뀐 경우 docker compose down # 종료 ``` ## 프로젝트 구조 ``` src/ ├── app/ # 라우터 / 프로바이더 / 진입점 셋업 ├── config/ # 앱 전역 설정 ├── features/ # 도메인별 모듈 (페이지 + 훅 + 컴포넌트 + 데이터) │ ├── admin/ # 관리자 (API 대시보드 등) │ ├── auth/ # 인증 │ ├── channels/ # 채널 검증 / enrichment │ ├── clinics/ # 병원 프로필 │ ├── dev/ # 개발용 페이지 │ ├── distribution/ # 콘텐츠 배포 │ ├── landing/ # 랜딩 │ ├── performance/ # 성과 대시보드 │ ├── plan/ # 마케팅 플랜 │ ├── pricing/ # 요금제 │ ├── report/ # 분석 리포트 (로딩/뷰어) │ └── studio/ # 콘텐츠 스튜디오 ├── shared/ │ ├── api/ # orval-generated SDK + HTTP 어댑터 │ │ ├── api.ts # customFetcher (ky 기반 mutator) │ │ ├── generated/ # ⚠️ orval 자동생성 (직접 수정 금지) │ │ └── model/ # ⚠️ orval 자동생성 타입 │ ├── constants/ # 전역 상수 │ ├── hooks/ # 범용 훅 │ ├── icons/ # 커스텀 아이콘 │ ├── layouts/ # 공통 레이아웃 │ ├── lib/ # 유틸 헬퍼 │ ├── types/ # 공통 타입 │ ├── ui/ # shadcn 기반 공용 컴포넌트 │ └── utils/ # 유틸 함수 ├── styles/ │ └── index.css # ★ Tailwind + 디자인 토큰 (브랜드 색 / 폰트 / radius) └── assets/ # 정적 자산 (폰트, 이미지 등) ``` 기능 모듈 내부 컨벤션: ``` features// ├── pages/ # 라우트에 매핑되는 페이지 컴포넌트 ├── components/ # 해당 기능 전용 컴포넌트 ├── hooks/ # 기능 전용 훅 (use.ts) ├── lib/ # transform / parser 등 순수 로직 ├── data/ # mock / 시드 상수 ├── types/ # 기능 전용 타입 └── routes.tsx # 라우트 정의 ``` ## 디자인 토큰 전부 [src/styles/index.css](src/styles/index.css)에서 관리. Tailwind v4의 CSS-first 방식이라 별도 `tailwind.config.js` 파일이 없다. | 블록 | 위치 | 용도 | | --- | --- | --- | | `:root { --brand-* }` | 상단 | 브랜드 색 원본 (purple / navy / rose / earth …) | | `:root { --status-* }` | 중간 | semantic 상태 색 (critical / warning / good / info) | | `:root { --background, --primary, … }` | shadcn 영역 | shadcn 토큰 → 브랜드 색 매핑 | | `.dark { … }` | 다크모드 | 다크 모드 오버라이드 | | `@theme inline { --color-*, --font-* }` | 하단 | Tailwind 유틸리티로 노출 (`bg-brand-purple`, `text-status-critical-text` 등) | 새 색을 만들고 싶다면 `:root`에 `--brand-xxx` 추가 → `@theme inline`에 `--color-brand-xxx: var(--brand-xxx)` 추가 → `bg-brand-xxx` 유틸로 사용. > **왜 `tailwind.config.js` 가 없나요?** > Tailwind v4 부터 설정을 JS 파일이 아닌 CSS의 `@theme` 블록에서 한다. 폰트, 색, radius, breakpoint 같은 모든 토큰을 [src/styles/index.css](src/styles/index.css) 하나에서 관리한다. ## 커스텀 클래스 / CSS | 위치 | 내용 | | --- | --- | | [src/styles/custom.css](src/styles/custom.css) | 커스텀 유틸 클래스(`.text-gradient`, `.glass-card`, `.gradient-bg`, `.soft-gradient`) + `@keyframes` + 애니메이션 유틸(`.animate-blob`, `.animation-delay-*`) | | [src/styles/index.css](src/styles/index.css) `@layer base` | 전역 element 스타일 (`body`, `h1`~`h6` 등) | 새 유틸 클래스는 `custom.css` 에 추가. 컴포넌트 단위 스타일은 Tailwind 유틸리티로 JSX에서 직접 작성하는 게 기본. ## 폰트 변경 Google Fonts로 가져오는 방식. 두 곳만 수정하면 된다. 1. **[index.html](index.html)** — Google Fonts `` 의 `family=` 파라미터에 원하는 폰트 추가/교체 ```html ``` 2. **[src/styles/index.css](src/styles/index.css)** 의 `@theme inline` 에서 `--font-sans` (본문) 또는 `--font-serif` (제목) 의 첫 항목을 새 폰트 이름으로 교체 ## 백엔드 API / orval OpenAPI 스펙을 가져와 SDK(React Query 훅 포함)를 자동생성한다. - 입력: [orval.config.ts](orval.config.ts) 의 `input` 필드 (예: `http://localhost:8001/openapi.json`) - 출력: `src/shared/api/generated/` (함수 + 훅) + `src/shared/api/model/` (타입) - HTTP 어댑터: [src/shared/api/api.ts](src/shared/api/api.ts) — ky 기반 `customFetcher`. 4xx/5xx도 throw하지 않고 `{ status, data, headers }`로 반환. ### 사용 예 ```ts // 쿼리 훅 import { useGetReport } from '@/shared/api/generated/reports/reports' const { data, isLoading } = useGetReport(id, { query: { enabled: !!id } }) // 뮤테이션 훅 import { useStartAnalysis } from '@/shared/api/generated/analyses/analyses' const { mutateAsync } = useStartAnalysis() await mutateAsync({ data: { url: '...' } }) // imperative 호출(폴링 등) import { getAnalysisStatus } from '@/shared/api/generated/analyses/analyses' const res = await getAnalysisStatus(runId) ``` ### 재생성 백엔드 스펙이 바뀌면: ```bash npm run api:gen ``` `clean: true`라 `generated/`, `model/` 폴더는 매번 비워지고 새로 생성된다. 직접 수정 금지. ## API 프록시 (개발) [vite.config.ts](vite.config.ts) 에서 `/api/*` 요청을 백엔드로 프록시한다. - 기본 타겟: `http://localhost:8001` - 도커 안에서 dev 서버 돌 때: `http://host.docker.internal:8001` (CHOKIDAR_USEPOLLING=true 일 때 자동) - 오버라이드: `VITE_API_TARGET=http://40.82.133.44:8001` 환경변수 ## shadcn/ui [ui.shadcn.com](https://ui.shadcn.com/) 컴포넌트는 패키지가 아니라 **소스 복사** 방식으로 사용한다. 추가/수정한 컴포넌트는 [src/shared/ui/](src/shared/ui/) 에 위치하며, Tailwind 토큰(`--background`, `--primary` 등)을 통해 디자인 시스템과 연결된다. 새 컴포넌트 추가: ```bash npx shadcn@latest add button ```