Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
394 changes: 394 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,394 @@
# .cursorrules

> **디프만 18기 3팀 — 위시리스트 소비 결정 서비스**
> 이 문서는 Cursor AI가 프로젝트 작업 시 반드시 따라야 할 규칙 및 컨텍스트를 정의합니다.
> 팀원 전원이 공유하는 AI 협업 규약이며, 모든 코드 생성/리팩토링은 이 규약을 따릅니다.

---

## 🎯 프로젝트 컨텍스트

### 한 줄 정의
**쌓인 위시리스트에서 먼저 살 것을 골라주는 소비 결정 서비스**

### 타겟 유저
위시리스트와 장바구니는 가득하지만, 선택 피로로 구매를 계속 미루는 패션·라이프스타일 중심의 **20~30대 모바일 쇼핑 사용자**.

### 핵심 기능
- **링크 기반 상품 저장** — 여러 쇼핑몰 링크를 한곳에 모음
- **1:1 토너먼트 비교** — 두 개씩 비교해 최종 1순위 결정 (WOW 포인트)
- **AI 소비 메이트** — 질문/공감/리마인드 (WOW 포인트)
- **보류함 & 재판단 알림** — 방치된 위시 재방문 유도
- **커머스 직접 연동** — 결정된 상품 즉시 구매

### 플랫폼 전략
**RN(Expo) 앱이 WebView로 Next.js 웹앱을 감싸는 구조.**
→ 실제 UI/비즈니스 로직은 `apps/web`에 집중. 네이티브 기능만 `apps/app`에서 처리.

---

## 💻 기술 스택

### 공통
- **패키지 매니저**: pnpm 10.17.0
- **모노레포**: Turborepo
- **언어**: TypeScript 5.9.2
- **배포**: Vercel (web), Expo (app)

### apps/web (Next.js)
- **프레임워크**: Next.js 16 (App Router)
- **React**: 19.2.0
- **상태관리**: Zustand
- **스타일링**: Tailwind CSS v4 (루트 공통 설정, 각 앱에서 상속)
- **API 통신**: TanStack Query (@tanstack/react-query)
- **스키마 검증**: Zod
- **API 모킹**: MSW (필요 시)
- **아이콘**: Lucide React 우선 → 불가 시 SVG 컴포넌트화

### apps/app (React Native)
- **프레임워크**: React Native + Expo
- **라우팅**: Expo Router
- **주요 역할**: WebView로 `apps/web` 렌더링

### CI/CD
- **GitHub Actions**: PR 단위 빌드 검사 (`lint` → `check-types` → `build`)
- **필수 통과 조건**: `pnpm install --frozen-lockfile`

---

## 📁 프로젝트 구조

### 모노레포 루트
```
18th-team3-client/
├── apps/
│ ├── app/ # React Native + Expo (WebView 래퍼)
│ └── web/ # Next.js 16 (메인 서비스)
├── packages/
│ ├── core/ # @repo/core — 웹뷰 통신용 type/hook/util
│ ├── ui/ # @repo/ui — 공유 UI (현재 미사용)
│ ├── eslint-config/ # @repo/eslint-config
│ └── typescript-config/ # @repo/typescript-config
├── prettier.config.mjs # 루트 Prettier (공통)
├── .prettierignore
└── turbo.json
```

### apps/web 내부 (고전 구조)
```
apps/web/src/
├── app/ # Next.js App Router (layout, page, providers)
│ ├── layout.tsx
│ ├── page.tsx
│ ├── providers.tsx # TanStack Query Provider
│ └── fonts/
├── components/ # 재사용 가능한 공통 UI 컴포넌트
├── apis/ # API 호출 함수 (HTTP 메서드 prefix 컨벤션)
├── hooks/ # 커스텀 훅
├── utils/ # 공통 유틸리티 함수
├── types/ # 공통 타입 정의 (T suffix)
├── assets/ # 정적 리소스 (SVG, 이미지)
├── styles/ # globals.css (Tailwind import)
└── consts/ # 상수
```

### Path Alias
`@/*` → `apps/web/src/*`

---

## 📝 네이밍 컨벤션

| 대상 | 규칙 | 예시 |
|---|---|---|
| **컴포넌트 파일** | PascalCase | `ScaleResult.tsx` |
| **일반 파일** (훅, 유틸) | camelCase | `useAuth.ts`, `formatDate.ts` |
| **폴더** | kebab-case | `scale-result/` |
| **타입** | T suffix | `UserT`, `ProductT` |
| **API 함수** | HTTP 메서드 prefix | `getUser`, `postWishlist`, `patchProfile`, `deleteItem` |

---

## 🎨 코딩 컨벤션

### 컴포넌트
- **`function` 키워드 + default export**

```tsx
function MyComponent({ children }: MyComponentProps) {
return <div>{children}</div>;
}

export default MyComponent;
```

### 유틸 함수
- **화살표 함수** 사용

```ts
const formatDate = (date: Date) => date.toISOString();
```

### 타입 선언
- **`type` 사용** (interface 대신)
- **T suffix** (컨벤션상 타입 선언 시)
- Props 타입명: `{ComponentName}Props`

```ts
type UserT = {
id: number;
name: string;
};

type MyComponentProps = {
children: React.ReactNode;
};
```

### Props 네이밍
- **내부 핸들러**: `handle-` (예: `handleClick`, `handleSubmit`)
- **외부에서 받는 props**: `on-` (예: `onClick`, `onSubmit`)

```tsx
function Button({ onClick }: ButtonProps) {
const handleClick = () => {
// 내부 로직
onClick?.();
};
return <button onClick={handleClick}>...</button>;
}
```

### 기타
- **세미콜론**: 사용 (`semi: true`)
- **따옴표**: `singleQuote: true` (`'홑따옴표'`)
- **printWidth**: 100
- **trailingComma**: `'es5'`
- **arrowParens**: `'avoid'` (인자 1개 시 괄호 생략)

---

## 📦 Import 규칙

### 정렬 (자동화됨 — `@trivago/prettier-plugin-sort-imports`)
```
1. 외부 라이브러리 (<THIRD_PARTY_MODULES>)
2. 절대경로 (^@/)
3. 상대경로 (^[./])
```

### 예시
```tsx
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';

import { apiClient } from '@/apis/apiClient';
import { UserT } from '@/types/user';

import { formatDate } from './utils';
import styles from './Page.module.css';
```

### Prettier 옵션
- `importOrderSeparation: true` (그룹 간 빈 줄)
- `importOrderSortSpecifiers: true` (그룹 내 알파벳 정렬)

---

## 🚨 ESLint 주요 규칙

- `no-console`: warn/error만 허용 (`console.log` 금지)
- `no-nested-ternary`: error (중첩 삼항 금지)
- `@typescript-eslint/consistent-type-imports`: error (`import type` 강제)
- `@typescript-eslint/no-explicit-any`: error (`any` 금지)
- `unused-imports/no-unused-imports`: error
- `_` prefix 변수/인자는 unused 허용

---

## 🔀 Git 전략

### 브랜치 구조
```
main ← dev ← {type}/{issue-number}-{description}
```

### 브랜치 네이밍
- **패턴**: `{type}/{issue-number}-{description}`
- **예시**: `feat/1-login-page`, `chore/3-web-setup`, `fix/5-auth-bug`
- **타입**: `feat`, `fix`, `chore`, `docs`, `refactor`, `style`, `test`

### 커밋 메시지
- **형식**: `{type}: {한글 설명}`
- **예시**:
- `feat: 로그인 페이지 구현`
- `chore: Prettier 설정 추가`
- `fix: 토큰 만료 처리 수정`

### 머지 방식
- **Squash Merge** 사용
- PR 제목이 그대로 dev의 커밋 메시지가 됨 → PR 제목 신중히 작성

### PR 컨벤션
- **base 브랜치**: `dev` (main 아님)
- **템플릿**:
```markdown
## 작업 내용
[내용 정리]

## 스크린샷

## 연관 이슈
closes #이슈번호
```

### 코드 리뷰 — PN 룰
- **P1** (Request changes): 꼭 반영 — 중대한 오류 가능성
- **P2** (Request changes): 적극 고려 — 수용 or 토론
- **P3** (Comment): 웬만하면 반영 — 미반영 시 사유 설명
- **P4** (Approve): 반영해도/안해도 OK — 고민 정도
- **P5** (Approve): 사소한 의견 — 무시 가능

### 리뷰 규칙
- **랜덤 1명 승인** 시 머지 가능
- 리뷰 자동 배정 워크플로우 사용

---

## 🌐 API 통신 — Response Schema 규약

### 기본 원칙
1. **HTTP Status Code는 REST 의미대로 사용** (200, 201, 400, 401, 403, 404, 500)
2. **성공/실패 Body 구조 통일**
3. **`fetch`는 4xx/5xx에서 자동 throw 안 함** → 반드시 `response.ok` 확인
4. **`success` 필드 사용 안 함** (`response.ok`와 중복)

### 응답 구조
```json
{
"status": 200,
"data": {},
"detail": "요청이 정상적으로 처리되었습니다.",
"code": "COMMON_SUCCESS"
}
```

| 필드 | 설명 |
|---|---|
| `status` | HTTP Status Code와 동일 |
| `data` | 실제 비즈니스 응답 데이터 (실패 시 `null`) |
| `detail` | 응답 메시지 (사용자 표시용) |
| `code` | 서버 정의 Enum 코드 (세부 분기용) |

### 검증 오류 응답 (400)
```json
{
"status": 400,
"data": null,
"detail": "입력값이 올바르지 않습니다.",
"code": "INVALID_INPUT",
"errors": [
{ "field": "url", "reason": "URL 형식이 올바르지 않습니다." }
]
}
```

### 페이지 응답 구조
```json
{
"status": 200,
"data": [ { "wishId": 1, "title": "셔츠" } ],
"detail": "요청이 정상적으로 처리되었습니다.",
"code": "COMMON_SUCCESS",
"pageInfo": { "nextCursor": "abc123", "hasNext": true }
}
```

### 프론트엔드 fetch 처리 표준
```ts
export async function request(url: string, options?: RequestInit) {
const response = await fetch(url, options);

let body;
try {
body = await response.json();
} catch {
throw new Error('서버 응답을 해석할 수 없습니다.');
}

if (response.ok) {
return body.data;
}

throw new Error(body.detail || '요청 처리 중 오류가 발생했습니다.');
}
```

### 검증 오류 세부 처리
```ts
if (response.status === 400 && body.errors) {
return body.errors;
}
```

### 분기 처리 (세부 에러)
```ts
if (body.code === 'WISH_NOT_FOUND') {
// 특정 에러 처리
}
```

### HTTP Status 사용 기준
| 상황 | HTTP Status |
|---|---|
| 조회 성공 | 200 OK |
| 생성 성공 | 201 Created |
| 잘못된 요청 | 400 Bad Request |
| 인증 실패 | 401 Unauthorized |
| 권한 없음 | 403 Forbidden |
| 리소스 없음 | 404 Not Found |
| 서버 오류 | 500 Internal Server Error |

---

## 🤖 Cursor AI 작업 지침

### 코드 생성 시
- **위 컨벤션을 반드시 준수**
- 신규 컴포넌트: `function` 키워드 + PascalCase 파일명 + default export
- 신규 훅: 화살표 함수 + camelCase 파일명
- 신규 타입: `type` 키워드 + T suffix
- API 함수: HTTP 메서드 prefix (`getUser`, `postWishlist` 등)
- 경로는 `@/*` 절대경로 우선, 같은 디렉토리는 상대경로

### 커밋 메시지 제안 시
- `{type}: {한글 설명}` 형식 사용
- type: feat, fix, chore, docs, refactor, style, test

### PR 작성 시
- 제목: `{type}: {한글 설명}` 형식
- 본문에 `closes #이슈번호` 포함
- base 브랜치는 `dev`

### 리뷰 코멘트 작성 시
- PN 태그 (P1~P5) 사용
- 이유 명확히 설명

### API 관련 코드 생성 시
- HTTP status 기반 분기 (`response.ok`)
- body 구조: `{ status, data, detail, code }` 가정
- fetch 래퍼 함수 활용 패턴


### 추천 패턴
- ✅ TanStack Query로 API 호출 래핑
- ✅ Zod로 응답 검증
- ✅ Zustand로 전역 상태
- ✅ Tailwind 클래스 사용 (CSS Module 지양)
- ✅ Error Boundary / Suspense는 필요한 페이지만 선택 적용

### 의심스러울 때
- 기존 코드 패턴 확인
- 팀 컨벤션 우선 (이 문서)
- `CLAUDE.md` 참고 (동일 내용)
- 판단 어려우면 사용자에게 확인 요청
Loading
Loading