Skip to content

Frontend

trz-21 edited this page Jun 16, 2026 · 3 revisions

📌 이 페이지는 이 프로젝트의 살아있는 스펙(최종 현황) 이며 canonical 문서입니다. 코드/설계 변경 시 PR 머지 후 이 페이지를 갱신합니다. (작업 흐름: 개발-워크플로우)

프론트엔드 구조

위치: frontend/ (monorepo). Next.js 14 App Router.

디렉토리 구조 (현행)

frontend/
├── app/                        # Next.js App Router
│   ├── layout.tsx              # 루트 레이아웃 (QueryProvider, Geist 폰트)
│   ├── page.tsx                # 루트: 미인증 → 랜딩 렌더 / 인증(토큰) → /dashboard
│   ├── terms/page.tsx          # 이용약관 (공개, 플레이스홀더)
│   ├── privacy/page.tsx        # 개인정보 처리방침 (공개, 플레이스홀더)
│   ├── (auth)/
│   │   ├── login/page.tsx      # 2단 레이아웃(브랜드 패널 + 폼)
│   │   └── register/page.tsx
│   └── (dashboard)/
│       ├── layout.tsx          # 인증 가드 + 헤더(Wordmark)
│       ├── dashboard/page.tsx  # URL 입력 + 분석 히스토리(EmptyState)
│       └── analyses/
│           └── [id]/page.tsx   # 분석 결과 상세 (폴링 + 진행 스테퍼 + 결과 헤더)
│
├── src/
│   ├── features/               # 도메인별 모듈
│   │   ├── auth/               # types.ts · api/ · store.ts (Zustand)
│   │   └── analysis/
│   │       ├── types.ts · api/
│   │       ├── hooks/useAnalysisPolling.ts
│   │       └── components/     # UrlInput · AnalysisCard · InsightReport · AnalysisProgress
│   │
│   └── shared/
│       ├── api/client.ts       # apiClient (fetch 래퍼, 토큰 자동 주입)
│       └── components/         # ── 디자인 시스템 ──
│           ├── ui/             # Button · Card · Badge · Container · Input · cn (프리미티브)
│           ├── Wordmark.tsx    # 브랜드 워드마크
│           ├── Footer.tsx      # 공통 푸터
│           ├── Landing.tsx     # 랜딩 페이지(히어로/가치/방법/신뢰/요금제)
│           └── EmptyState.tsx  # 공통 빈 상태
│
└── tests/
    ├── unit/                   # vitest 단위/통합
    └── e2e/landing.spec.ts     # Playwright 랜딩 스모크 (자세히: [[Harness]])

컴포넌트마다 동일 경로에 *.test.tsx 단위 테스트가 동반된다. (예외: index.ts, types.ts) E2E는 tests/e2e/에 항상 최소 1건 이상 유지 — 0건이면 CI가 실패한다. (Harness 참고)


디자인 시스템 (#20~#29)

  • 브랜드 톤: 네이비/블루 + 화이트 (B2B SaaS, 신뢰·전문성)
  • 토큰: tailwind.config.tsbrand-50..950 컬러 스케일 + fontFamily.sans/mono(Geist). 하드코딩 색 대신 brand-* 토큰 사용.
  • ⚠️ Tailwind content glob에 ./src 필수: 디자인 시스템 프리미티브와 Container가 모두 frontend/src/에 있다. content glob에서 ./src가 빠지면 max-w-5xl 등 유틸이 purge돼 레이아웃이 edge-to-edge로 깨진다(#28에서 발생·수정). config 수정 시 ./app·./src를 모두 포함할 것.
  • 폰트: Geist(Sans/Mono) 를 본문 폰트로 적용(globals.css, layout.tsx).
  • 프리미티브 (src/shared/components/ui/, 신규 의존성 없음):
    • Button(variant: primary/secondary/outline/ghost × size: sm/md/lg)
    • Card(패딩은 소비자 지정) · Badge(neutral/info/success/warning/danger) · Container(max-w-5xl) · Input(forwardRef)
    • cn — 의존성 없는 className 병합 유틸
  • 공통 컴포넌트: Wordmark, Footer, Landing, EmptyState, 분석 진행 AnalysisProgress
  • 외부 컴포넌트 라이브러리(shadcn 등) 미도입 — Tailwind 기반 경량 프리미티브로 번들 최소화.

페이지 구성

루트 (/) — 랜딩

  • 미인증 방문자 → 랜딩 페이지 렌더(Landing): 히어로 + 핵심가치 3 + 작동방식 + 신뢰 요소(데이터 출처·AI 방법론·보안) + 요금제 3종 + 푸터.
  • 인증(토큰 보유) → /dashboard 리다이렉트.
  • 하이드레이션 미스매치 방지를 위해 mount 이후에만 판별.
  • 법적 페이지: /terms, /privacy (공개, 플레이스홀더).

대시보드 (/dashboard) — UI 리프레시 (#28/#29)

  • 그라데이션 히어로 입력 영역(brand-700→900, rounded-2xl, 돋보기 아이콘) — URL 입력(최대 3개, UrlInput: ✕ 삭제·+ 추가·리뷰 수 select 폭 제약) + 온보딩 카피
  • 분석 시작 버튼 → POST /analyses → /analyses/[id]로 이동
  • 최근 분석 히스토리: AnalysisCard(상태 컬러 dot·호버 리프트·"결과 보기 →" 링크 아이콘), 빈 상태는 EmptyState 일러스트
  • sticky + backdrop-blur 헤더

분석 결과 (/analyses/[id]) — UI 리프레시 (#28/#29)

  • 상태 폴링 (2초 간격, TanStack Query refetchInterval)
  • Pending/Crawling/Analyzing: 3단계 진행 스테퍼(AnalysisProgress: 수집→분석→완료)
  • Completed: 스탯 타일 히어로(상품 수·총 리뷰 수·생성 시각) + 인사이트 리포트(InsightReport)
    • 방법론 블록(데이터 출처·표본 수·AI 분류 안내) + 섹션별 맥락 카피 + 면책 고지
    • 섹션별 고유 액센트+아이콘: TOP3=블루 / 경쟁사 약점=앰버 / 구매 결정 요인=에메랄드 / 반복 불만=red / 긍정=green / 평점 분포=앰버
    • 심각도 배지 한글화(high/medium → 높음/보통 등 — 소상공인 대상 신뢰도)
  • PDF 내보내기 버튼 (Pro 플랜, 예정)

상태 관리 전략

상태 종류 도구 예시
서버 상태 (API 데이터) TanStack Query 분석 목록, 분석 결과
클라이언트 UI 상태 Zustand 로그인 토큰, 사용자 정보
폼 상태 React 로컬 state URL 입력 폼

auth store (Zustand)

interface AuthStore {
  token: string | null;
  user: User | null;
  setAuth: (token: string, user: User) => void;
  logout: () => void;
}

분석 폴링 (TanStack Query v5)

src/features/analysis/hooks/useAnalysisPolling.ts 로 추출. v5 에서 refetchInterval 콜백 인자는 query 객체이며 query.state.data 로 접근한다. 간격 분기는 순수 함수 pollInterval(status) 로 분리해 단위 테스트한다.

// completed/failed면 폴링 중단(false), 그 외 2초
export function pollInterval(status?: AnalysisStatus): number | false {
  return status === 'completed' || status === 'failed' ? false : 2000;
}

export function useAnalysisPolling(analysisId: string) {
  return useQuery({
    queryKey: ['analysis', analysisId],
    queryFn: () => getAnalysis(analysisId),
    refetchInterval: (query) => pollInterval(query.state.data?.status),
    enabled: !!analysisId,
  });
}

API 통신 패턴

// src/shared/api/client.ts
// 토큰 자동 주입 + 에러 처리

// src/features/{domain}/types.ts
// {Action}Request / {Action}Response 인터페이스

// src/features/{domain}/api/index.ts
// export async function actionName(req: ActionRequest): Promise<ActionResponse>

규칙: 인라인 타입(Promise<{ field: Type }>) 금지. 모든 API 함수는 명명된 타입 사용.


환경 변수

NEXT_PUBLIC_API_URL=http://localhost:8080

📚 스펙 네비게이션: Home · Service-Overview · Tech-Stack · API · Backend · Frontend · Harness · 개발-워크플로우

Clone this wiki locally