diff --git a/API_SPEC.md b/API_SPEC.md index 1ea434d..47a883e 100644 --- a/API_SPEC.md +++ b/API_SPEC.md @@ -861,333 +861,6 @@ await teamManager.removeMember(teamId, currentUser.uid); --- -### 2. GET `/student/:id` - 학생 조회 -실제 URL: `GET /api/student/:id` - -**인증**: 선택적 - -**Response**: -```typescript -{ - success: true, - data: { - student: Student; - } -} -``` - -**캐싱**: 클라이언트에서 5분간 캐싱 - ---- - -### 3. POST `/student/by-uid` - Firebase UID로 학생 조회 -실제 URL: `POST /api/student/by-uid` - -**인증**: 필수 (Anonymous Auth) - -**Request**: -```typescript -{ - firebaseUid: string; -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - student: Student | null; - } -} -``` - -**용도**: 로그인 시 기존 학생 확인 - -**캐싱**: 클라이언트에서 5분간 캐싱 - ---- - -### 4. POST `/student/by-team` - 팀별 학생 목록 -실제 URL: `POST /api/student/by-team` - -**인증**: 선택적 - -**Request**: -```typescript -{ - teamId: string; -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - students: Student[]; - } -} -``` - -**캐싱**: 클라이언트에서 30초간 캐싱 (자주 변경) - ---- - -### 5. POST `/student/find-by-name` - 이름으로 학생 찾기 -실제 URL: `POST /api/student/find-by-name` - -**인증**: 선택적 - -**Request**: -```typescript -{ - teamId: string; - name: string; -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - student: Student | null; - } -} -``` - -**용도**: 학생 로그인 시 기존 학생 확인 - ---- - -### 6. PUT `/student/:id` - 학생 정보 수정 -실제 URL: `PUT /api/student/:id` - -**인증**: 필수 - -**Request**: -```typescript -{ - studentId: string; - data: { - name?: string; - pinHash?: string; - linkedUserId?: string; - } -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - student: Student; - } -} -``` - -**캐시 무효화**: 해당 학생, 팀별 학생 목록 - ---- - -### 7. POST `/student/kick` - 팀에서 학생 강퇴 -실제 URL: `POST /api/student/kick` - -**인증**: 필수 - -**Request**: -```typescript -{ - studentId: string; - teamId: string; -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - success: true; - } -} -``` - -**권한**: 팀 소유자만 강퇴 가능 - -**부수 효과**: -- `student.teamIds`에서 팀 제거 -- `team.studentIds`에서 학생 제거 - -**캐시 무효화**: 해당 학생, 팀, 팀별 학생 목록 - ---- - -### 8. POST `/student/update-pin` - PIN 변경 -실제 URL: `POST /api/student/update-pin` - -**인증**: 필수 - -**Request**: -```typescript -{ - studentId: string; - newPin: string; // 평문 (4자리 숫자) -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - success: true; - } -} -``` - -**검증**: 서버에서 PIN 형식 검증 (4자리 숫자) - -**보안**: PIN은 SHA-256 해시로 저장 - -**캐시 무효화**: 해당 학생 - ---- - -### 9. POST `/student/validate-pin` - PIN 검증 -실제 URL: `POST /api/student/validate-pin` - -**인증**: 선택적 - -**Request**: -```typescript -{ - studentId: string; - pin: string; -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - valid: boolean; - } -} -``` - -**용도**: 학생 로그인 시 PIN 확인 - ---- - -### 10. POST `/student/link` - 정식 계정 연결 -실제 URL: `POST /api/student/link` - -**인증**: 필수 (정식 계정) - -**Request**: -```typescript -{ - studentId: string; - // userId는 Authorization 헤더에서 추출 -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - success: true; - } -} -``` - -**권한**: 현재 로그인한 사용자와 연결 - -**검증**: -- 학생이 이미 다른 계정과 연결되어 있으면 에러 - -**캐시 무효화**: 해당 학생, 내 학생 목록 - ---- - -### 11. POST `/student/unlink` - 정식 계정 연결 해제 -실제 URL: `POST /api/student/unlink` - -**인증**: 필수 - -**Request**: -```typescript -{ - studentId: string; -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - success: true; - } -} -``` - -**캐시 무효화**: 해당 학생, 내 학생 목록 - ---- - -### 12. GET `/student/my-students` - 내 학생 목록 -실제 URL: `GET /api/student/my-students` - -**인증**: 필수 (정식 계정) - -**Response**: -```typescript -{ - success: true, - data: { - students: Student[]; - } -} -``` - -**권한**: 로그인한 사용자가 소유한 학생만 조회 (`linkedUserId` 기준) - -**캐싱**: 클라이언트에서 1분간 캐싱 - ---- - -### 13. POST `/student/update-last-login` - 마지막 로그인 시간 업데이트 -실제 URL: `POST /api/student/update-last-login` - -**인증**: 선택적 - -**Request**: -```typescript -{ - studentId: string; -} -``` - -**Response**: -```typescript -{ - success: true, - data: { - success: true; - } -} -``` - -**참고**: 실패해도 클라이언트는 에러를 무시 (크리티컬하지 않음) - ---- - ## Writing API ### 1. POST `/writing` - 글 생성 diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 4731508..61f2791 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -1,10 +1,27 @@ # 라온누리 - 프로젝트 구조 -> 최종 업데이트: 2025-11-21 (홈 페이지 모듈화) +> 최종 업데이트: 2025-11-22 (Server Component 전환 + 공통 컴포넌트) 초등학생을 위한 창작 글쓰기 교육 플랫폼 -**최신 업데이트** (2025-11-21): +**최신 업데이트** (2025-11-22): +- 🔄 **글 상세보기 페이지 Server Component 전환** + - **SEO 최적화**: 서버에서 HTML 생성 (검색엔진 크롤링 가능) + - **SNS 공유 미리보기**: 카카오톡/페이스북 링크 미리보기 지원 + - **서버 데이터 로딩**: Firebase Admin SDK 사용 (`src/lib/server/writing.ts`) + - **성능 개선**: 초기 로딩 빠름 (데이터 포함된 HTML 전송) + - **권한 체크**: 서버에서 처리 (클라이언트 깜빡임 없음) + - `getTranslations()` 서버 번역 함수 사용 + - `params: Promise<{locale, writingId}>` 타입 사용 +- 🔙 **BackButton 공통 컴포넌트 추가** (`src/components/layout/BackButton.tsx`) + - **기본 동작**: `router.back()` (브라우저 히스토리 뒤로) + - **옵션 1**: `href` prop으로 특정 경로 이동 + - **옵션 2**: `label` prop으로 버튼 텍스트 커스터마이징 + - **다국어 지원**: `t('interaction.back')` 기본 라벨 + - **확장성**: ButtonProps 상속으로 모든 Button 속성 지원 + - **재사용성**: 모든 페이지에서 일관된 뒤로가기 UX + +**업데이트** (2025-11-21): - 📦 **홈 페이지 모듈화** - **6개 컴포넌트 분리**: HeroSection, QuickActionCard, QuickActionsGrid, ViewAllWritingsCard, EmptyStateCard, RecentActivitySection - **코드 감소**: 580줄 → 223줄 (62% 감소) @@ -233,6 +250,7 @@ | **랜딩 페이지** | `/[locale]` | 서비스 소개 및 홍보 (비로그인 전용) | Hero, Features, How It Works, CTA, Footer
로그인 시 `/home`으로 자동 리다이렉트
🆕 **전체 번역 완료** (사이트명, 태그라인, 모든 섹션) | ✅ 완료 | | **유저 홈** | `/[locale]/home` | 인증된 사용자 대시보드 | 환영 메시지, 빠른 시작 대시보드, **최근 활동 (최근 글 3개 표시)**
비로그인 시 `/`로 자동 리다이렉트
정식 계정은 "내 팀" 카드 추가 표시
🆕 **WritingCard Grid, "모두 보기" 버튼**
🆕 **전체 번역 완료** (웰컴 메시지, 모든 액션 카드) | ✅ 완료 | | **내 글 모음** | `/[locale]/writings` | 🆕 **전체 글 목록 페이지** | 🆕 **사용자의 모든 글 표시 (Grid)**
🆕 **정렬 Select (최신순/오래된순)**
🆕 **WritingCard 컴포넌트 사용**
🆕 **Empty state 처리**
🆕 **전체 번역 완료** (ko/en/ja) | ✅ 완료 | +| **글 상세보기** | `/[locale]/writing/[writingId]` | 🆕 **Server Component 기반 상세 페이지** | 🆕 **SEO 최적화** (서버에서 HTML 생성)
🆕 **SNS 공유 미리보기** (카카오톡/페이스북)
🆕 **서버 데이터 로딩** (Firebase Admin SDK)
제목, 내용, 생성된 이미지 표시
주제 및 팀 정보 Badge
프롬프트 Collapsible
🆕 **CommentList** (Client Component)
🆕 **BackButton** 공통 컴포넌트 사용 | ✅ 완료 | | **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)
🆕 **글 수정 기능 (URL params ?id=xxx)**
🆕 **수정 모드 배지 표시**
🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)
제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)
🆕 **다중 글조각 관리** (최대 10개), "새 글쓰기" / "저장된 글조각" 버튼
🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)
🆕 **저장 시 AI 분석** (실시간 분석 제거, 저장 버튼 클릭 시 분석 수행)
🆕 **분석 결과 DB 저장** (WritingAnalysis + spellingErrors + contentHash)
템플릿 미리채우기 (제목/내용), Firestore 저장
비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 | | **테스트** | `/[locale]/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트
팀/학생 생성 테스트
학생 로그인 테스트
authStore 상태 확인 | 🔜 예정 | | **팀 목록** | `/[locale]/team` | 내가 만든 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)
"새 팀 만들기" 버튼 | 🔜 예정 | @@ -319,6 +337,36 @@ --- +### 📁 `src/components/layout/` - 레이아웃 공통 컴포넌트 + +| 컴포넌트 | 파일명 | 설명 | 상태 | +|---------|--------|------|------| +| **BackButton** | `BackButton.tsx` | 🆕 **뒤로가기 버튼 공통 컴포넌트** (router.back() 또는 특정 경로, 커스텀 라벨 지원) | ✅ 완료 | + +**주요 기능**: +- ✅ `router.back()` 기본 동작 +- ✅ `href` prop으로 특정 경로 이동 가능 +- ✅ `label` prop으로 버튼 텍스트 커스터마이징 +- ✅ 다국어 지원 (기본 라벨: `t('interaction.back')`) +- ✅ ButtonProps 확장으로 모든 Button 속성 지원 + +**사용 예시**: +```tsx +// 기본 사용 (router.back()) + + +// 특정 경로로 이동 + + +// 커스텀 라벨 + + +// 추가 스타일링 + +``` + +--- + ### 📁 `src/components/ui/` - UI 기본 컴포넌트 | 컴포넌트 | 파일명 | 설명 | 상태 | @@ -531,7 +579,6 @@ | **UserManager** | `UserManager.ts` | 사용자 관련 API 호출 (생성, 조회, 수정, 닉네임 관리) **[NEW]** | ✅ 완료 | | **DraftManager** | `DraftManager.ts` | 글조각 관리 (**localStorage + Realtime DB 하이브리드**, 기기 간 동기화, 최대 10개, CRUD, syncStatus) | ✅ 완료 | | **WritingSessionManager** | `WritingSessionManager.ts` | 🆕 **실시간 글쓰기 세션 관리** (Firebase Realtime DB 작업) | ✅ 완료 | -| ~~**StudentManager**~~ | ~~`StudentManager.ts`~~ | ~~학생 관련 API 호출~~ | ⚠️ Deprecated (UserManager로 대체) | | **WritingManager** | `WritingManager.ts` | 글쓰기 관련 비즈니스 로직 (CRUD, 통계) | ✅ 완료 | | **TopicManager** | `TopicManager.ts` | 주제 관련 비즈니스 로직 (CRUD, 템플릿 처리) | ✅ 완료 | | **index.ts** | `index.ts` | 모든 매니저 export | ✅ 완료 | @@ -790,6 +837,11 @@ project_w/ │ │ │ └── page.tsx # ✅ 유저 홈 페이지 (/[locale]/home) - 🆕 전체 번역 완료 │ │ ├── write/ │ │ │ └── page.tsx # ✅ 글쓰기 페이지 (/[locale]/write) - 🆕 실시간 모니터링 +│ │ ├── writing/ # ✅ 글 상세보기 +│ │ │ └── [writingId]/ +│ │ │ └── page.tsx # 🆕 글 상세 페이지 (Server Component, SEO 최적화) +│ │ ├── writings/ +│ │ │ └── page.tsx # ✅ 내 글 모음 페이지 (/[locale]/writings) │ │ ├── test/ │ │ │ └── page.tsx # ✅ 테스트 페이지 (/[locale]/test) │ │ └── team/ # ✅ 팀 관리 페이지들 @@ -809,6 +861,8 @@ project_w/ │ ├── navigation/ # ✅ 네비게이션 바 (다국어 지원) │ │ ├── Navbar.tsx # 다국어 링크 텍스트 │ │ └── LocaleSwitcher.tsx # 🆕 언어 전환 버튼 +│ ├── layout/ # 🆕 레이아웃 공통 컴포넌트 +│ │ └── BackButton.tsx # 🆕 뒤로가기 버튼 (router.back/href/label 지원) │ ├── seo/ # ✅ SEO 컴포넌트 │ ├── ui/ # ✅ Chakra UI 기본 │ ├── writing/ # ✅ 글쓰기 에디터 diff --git a/README.md b/README.md index dc8bd2d..54bf724 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,10 @@ src/ ├── managers/ # ✅ 비즈니스 로직 (API + 캐싱) │ ├── ManagerBase.ts # 공통 기능 (authenticatedFetch, caching) │ ├── TeamManager.ts # 팀 관련 API 호출 -│ ├── StudentManager.ts # 학생 관련 API 호출 │ ├── WritingManager.ts # 글쓰기 API 호출 │ └── TopicManager.ts # 주제 API 호출 ├── types/ # TypeScript 타입 -│ ├── team.ts, student.ts, writing.ts, topic.ts +│ ├── team.ts, writing.ts, topic.ts │ └── api/ # API Request/Response 타입 ├── config/ # Firebase 설정 ├── services/ # Firebase Auth, Firestore diff --git a/ROADMAP.md b/ROADMAP.md index ca37d49..fadf1db 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # 라온누리 - 개발 로드맵 -> 최종 업데이트: 2025-11-21 (홈 페이지 모듈화) +> 최종 업데이트: 2025-11-22 (Server Component 전환) 초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획 @@ -106,6 +106,10 @@ | **Firebase Cloud Functions Phase 1** | **functions/ 프로젝트 초기화 (Node.js 22, v2 API), 파일 분리 구조 (index.ts export만, 로직 별도 파일), cleanupExpiredReservations (매 시간, 팀 코드 예약 정리), cleanupExpiredDrafts (매일 새벽 3시, 180일 이상 drafts 정리), onTeamDeleted (Firestore Trigger, cascade 삭제), onWritingCreated (Firestore Trigger, 추후 자동 분석), 한글 로그, asia-northeast1 리전, firebase.json predeploy 수정 (lint 제거), 4개 함수 배포 완료** | **2025-11-19** | | **주제 생성 Dialog 통합** | **CreateTeamTopicDialog 삭제 (674줄), CreateTopicDialog로 통합 (개인/팀 공용), TopicFormData export (데이터 반환 방식), onSubmit 콜백 패턴 (부모가 topicManager 호출), team.manage.teamTopics.dialog 번역 키 제거 (30개), topicSelector.createSuccess 추가, 652줄 코드 감소, 관심사 분리 (Dialog는 UI만, 비즈니스 로직은 부모)** | **2025-11-21** | | **홈 페이지 모듈화** | **HeroSection/QuickActionCard/QuickActionsGrid/ViewAllWritingsCard/EmptyStateCard/RecentActivitySection 컴포넌트 분리 (src/components/home/), 580줄 → 223줄 (62% 감소), Semantic token 적극 활용 (bg/fg/border), Conditional style 객체 최소화, 재사용성 및 유지보수성 향상, JSX 주석 한글화** | **2025-11-21** | +| **글 상세보기 Server Component 전환** | **src/app/[locale]/writing/[writingId]/page.tsx를 Server Component로 전환, SEO 최적화 (서버에서 HTML 생성), SNS 공유 미리보기 (카카오톡/페이스북 링크), Firebase Admin SDK 사용 (src/lib/server/writing.ts, getTopic, getTeam), getTranslations() 서버 번역 함수, params: Promise<{locale, writingId}> 타입, 초기 로딩 성능 개선 (데이터 포함된 HTML 전송), 서버 권한 체크 (클라이언트 깜빡임 없음)** | **2025-11-22** | +| **BackButton 공통 컴포넌트** | **src/components/layout/BackButton.tsx 생성, router.back() 기본 동작, href prop으로 특정 경로 이동, label prop으로 텍스트 커스터마이징, ButtonProps 확장으로 모든 Button 속성 지원, 다국어 지원 (t('interaction.back')), 재사용성 극대화 (모든 페이지에서 일관된 뒤로가기 UX)** | **2025-11-22** | +| **Firebase Storage CORS 설정** | **firebase-storage-cors.json 생성 (origin: *, method: GET/HEAD, maxAge: 3600), gsutil cors set 명령어로 적용, Canvas 이미지 조작 허용, 캐시 문제 (CDN 1시간 캐시로 즉시 반영 안 됨, ?t=timestamp 쿼리 파라미터로 해결), TextureLoader.setCrossOrigin("anonymous") 확인** | **2025-11-23** | +| **인터랙티브 이미지 비율 유지** | **InteractiveImage 컴포넌트 개선 (responsive-image-canvas 라이브러리), useEffect로 이미지 원본 크기 측정 (new Image, crossOrigin, onload), Box에 aspectRatio 속성 적용 (width/height 비율), Canvas 찌그러짐 방지, 로딩 상태 UI 추가, 캐시 무효화 (cacheBustedSrc ?t=Date.now())** | **2025-11-23** | ### 🚧 진행 중 diff --git a/TECH_STACK.md b/TECH_STACK.md index 7ba99c2..eb9d3b5 100644 --- a/TECH_STACK.md +++ b/TECH_STACK.md @@ -243,7 +243,6 @@ NEXT_PUBLIC_API_URL=/api securityMode: 'simple' | 'normal' | 'open'; requirePin: boolean; allowAnonymousJoin: boolean; - studentIds: string[]; // students 컬렉션 참조 createdAt: Timestamp; updatedAt: Timestamp; isActive: boolean; @@ -828,7 +827,6 @@ writings/{writingId} **참고 파일**: - `src/managers/TeamManager.ts` - 팀 관련 API 호출 + 캐싱 -- `src/managers/StudentManager.ts` - 학생 관련 API 호출 + 캐싱 - `src/managers/ManagerBase.ts` - API 호출 및 캐싱 공통 로직 - `src/services/firebaseAuth.ts:125-316` - 학생 로그인 로직 - `src/store/authStore.ts` - currentStudent 중심 상태 관리