diff --git a/API_SPEC.md b/API_SPEC.md index a50e331..f8a8554 100644 --- a/API_SPEC.md +++ b/API_SPEC.md @@ -1130,6 +1130,77 @@ await teamManager.removeMember(teamId, currentUser.uid); --- +### 2. POST `/user/purchase` - 플랜 구매/변경 +실제 URL: `POST /api/user/purchase` + +**인증**: 필수 + +**Request**: +```typescript +{ + planType: PlanType; // FREE, PRO, CLASSROOM, ACADEMY, SCHOOL + billingCycle: BillingCycle; // MONTHLY, YEARLY + amount: number; // 결제 금액 (원) + downgradeMode?: "immediate" | "scheduled"; // 다운그레이드 시 적용 시점 +} +``` + +**Response**: +```typescript +{ + success: true, + data: { + creditsAdded: number; // 환불로 지급된 크레딧 (업그레이드/즉시 다운그레이드) + isScheduled: boolean; // true면 다음 결제일에 적용 + isBillingCycleChange: boolean; // true면 결제 주기만 변경 + } +} +``` + +**동작**: +1. **업그레이드**: 즉시 적용, 남은 기간 비례 환불 → 크레딧 지급 +2. **다운그레이드 (immediate)**: 즉시 적용, 남은 기간 비례 환불 → 크레딧 지급 +3. **다운그레이드 (scheduled)**: `scheduledPlan`에 저장, 만료 시 자동 적용 +4. **결제 주기 변경**: `scheduledPlan`에 저장, 만료 시 새 주기로 적용 + +**환불 계산**: +```typescript +const refundKRW = calculateProratedRefund(currentPlan, PLAN_MONTHLY_PRICES); +const credits = convertKRWToCredits(refundKRW); // 100원 = 10 크레딧 +``` + +**캐시 무효화**: 사용자 정보 + +--- + +### 3. GET `/user/plan/estimate-refund` - 예상 환불 크레딧 조회 +실제 URL: `GET /api/user/plan/estimate-refund` + +**인증**: 필수 + +**설명**: 다운그레이드 전 예상 환불 크레딧을 조회합니다. UI에서 "즉시 다운그레이드" 옵션에 표시됩니다. + +**Response**: +```typescript +{ + success: true, + data: { + estimatedCredits: number; // 예상 환불 크레딧 + estimatedKRW: number; // 예상 환불 금액 (원) + validUntil: string; // 현재 플랜 만료일 (ISO 8601) + currentPlanType: PlanType; // 현재 플랜 타입 + } +} +``` + +**에러**: +- `400`: 활성 플랜 없음 +- `401`: 인증 필요 + +**캐싱**: 없음 (실시간 계산) + +--- + ## Writing API ### 1. POST `/writing` - 글 생성 diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 55b5d2b..970d0b3 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -1,10 +1,43 @@ # 라온누리 - 프로젝트 구조 -> 최종 업데이트: 2025-12-08 (AI 크레딧 환불 시스템, 구매 플로우, 구독 관리) +> 최종 업데이트: 2025-12-12 (보안 설정 통합, 팀 소유자 이전 기능) 초등학생을 위한 창작 글쓰기 교육 플랫폼 -**최신 업데이트** (2025-12-08 오후): +**최신 업데이트** (2025-12-12): +- 🔒 **팀 보안 설정 통합** + - **TeamSecuritySettingsDialog 확장**: 공개설정(isPublic) + 보안레벨(securityLevel) 통합 + - **공개 설정 스위치**: 보안 단계 선택 아래에 배치 (팀 공개, 글 공개 허용, 팀 설명, 커버 이미지) + - **검색 가능/불가능 태그**: TeamInfoCard 보안 정보에 isPublic 기반 배지 추가 + - **TeamPublicSettings 제거**: 팀 관리 페이지에서 분리된 공개 설정 섹션 제거 + - **다국어 지원**: searchable/notSearchable 키 추가 (ko/en/ja) +- 👑 **팀 소유자 이전 기능** + - **POST /api/team/[teamId]/transfer-ownership**: 소유자 권한 이전 API + - **TeamMemberEditor 컴포넌트**: 멤버 메뉴에 "소유자 이전" 옵션 추가 + - **소유자 표시**: 멤버 목록에서 owner 역할 배지 표시 + - **다국어 지원**: transferOwnership, transferDialogTitle, transferConfirm 등 (ko/en/ja) +- 🛡️ **SecurityLevelSelector 개선** + - **검색 가능 여부 표시**: 각 보안 레벨별 검색 가능 아이콘 (LuSearch/LuSearchX) + - **searchable 속성 추가**: SecurityLevelOption 타입 확장 + +**업데이트** (2025-12-10): +- 📉 **플랜 다운그레이드 선택 옵션** + - **두 가지 선택지**: 즉시 다운그레이드 (크레딧 전환) / 다음 결제일 적용 (기존 scheduledPlan) + - **DowngradeOptionDialog 컴포넌트**: 다운그레이드 방식 선택 UI + - **GET /api/user/plan/estimate-refund**: 예상 환불 크레딧 조회 API + - **downgradeMode 파라미터**: "immediate" | "scheduled" + - **planUtils.ts**: 클라이언트/서버 공용 플랜 유틸리티 (isUpgrade, isDowngrade) +- 🔄 **결제 주기 변경 기능** + - **월간 ↔ 연간 변경**: 동일 플랜 내 결제 주기 변경 지원 + - **isBillingCycleChange 플래그**: 결제 주기만 변경 시 감지 + - **scheduledPlan.billingCycle**: 예약된 결제 주기 저장 + - **다음 결제일 자동 적용**: 만료 시 새 결제 주기로 전환 +- 👥 **팀 기반 AI 사용량 제한** + - **teamMemberUsage**: 팀원별 AI 크레딧 사용량 추적 + - **팀 소유자 플랜 연동**: 팀원 AI 제한은 소유자 플랜 기준 + - **credits.ts**: checkAIFeatureAvailability(), deductAICredits() 함수 + +**업데이트** (2025-12-08 오후): - 💳 **AI 크레딧 환불 시스템** - **업그레이드 환불**: Prorated 계산 → AI 크레딧 지급 - **환율**: 100원 = 10 크레딧 @@ -983,6 +1016,8 @@ firebase functions:log --only cleanupExpiredReservations |---------|--------|------|------| | **Team Code Generator** | `teamCodeGenerator.ts` | 한글/영어/일본어 팀 코드 생성 (형용사 + 색깔 + 동물, locale 파라미터) | ✅ 완료 | | **🆕 i18n 유틸리티** | `i18n.ts` | 서비스 레이어용 번역 함수 (detectLocale, t), nested key 지원, 파라미터 치환 | ✅ 완료 | +| **🆕 Validation** | `validation.ts` | 폼 검증 유틸리티 (이메일/비밀번호/이름/비밀번호 확인), 타입 안전, 다국어 에러 메시지 | ✅ 완료 | +| **🆕 SSE Stream Processor** | `sseStreamProcessor.ts` | Server-Sent Events 스트리밍 처리 (계정 병합 진행률 표시) | ✅ 완료 | | **Password Security** | `passwordSecurity.ts` | HIBP API 연동 (유출된 비밀번호 차단) | ✅ 완료 | | **Password Strength** | `passwordStrength.ts` | 비밀번호 강도 계산 | ✅ 완료 | | **Content Hash** | `contentHash.ts` | 🆕 **SHA-256 해시 생성** (글 목록 → 해시, id+updatedAt 조합), 🆕 **generateWritingContentHash** (개별 글 content 해시), 서버/클라이언트 지원 | ✅ 완료 | @@ -999,6 +1034,7 @@ firebase functions:log --only cleanupExpiredReservations |------|--------|------|------| | **useRequireAuth** | `useRequireAuth.ts` | **인증 필수 페이지 훅** (비인증 시 자동 리다이렉트, additionalLoading 지원) | ✅ 완료 | | **useTeamData** | `useTeamData.ts` | 🆕 **팀 데이터 로딩 훅** (팀 + 멤버 로딩, 권한 체크, refreshMembers) | ✅ 완료 | +| **🆕 useShakeAnimation** | `useShakeAnimation.ts` | **에러 Shake 애니메이션 훅** (에러 발생 시 자동 shake, getShakeAnimateProps 헬퍼, 중복 제거) | ✅ 완료 | | **useWritingInactivityDetection** | `useWritingInactivityDetection.ts` | **작성 멈춤 감지 훅** (N분 입력 없을 시 콜백, 타이머 리셋, 남은 시간) | ✅ 완료 | --- @@ -1069,20 +1105,21 @@ firebase functions:log --only cleanupExpiredReservations | 스토어 | 파일명 | 설명 | 상태 | |-------|--------|------|------| -| **Auth Store** | `authStore.ts` | 인증 상태 및 로그인 다이얼로그 (단순화됨) | ✅ 완료 | +| **Auth Store** | `authStore.ts` | 인증 상태 및 로그인 다이얼로그 (단순화됨, 🆕 **Firestore 실시간 구독**, aiCredits/plan/settings 자동 업데이트) | ✅ 완료 | | **User Progress Store** | `userProgressStore.ts` | 사용자 진행 상황 (레벨, XP) | ❌ 미구현 | | **Notification Store** | `notificationStore.ts` | 알림 상태 | ❌ 미구현 | -**Auth Store 아키텍처 (2025-11-07 단순화)**: -- ✅ **user** - Firebase Auth 기반 통합 사용자 (익명 + 정식 계정) +**Auth Store 아키텍처 (2025-11-07 단순화, 2025-12-09 리팩토링)**: +- ✅ **user** - Firebase Auth + Firestore 결합 사용자 (익명 + 정식 계정, 🆕 **실시간 구독**) - ✅ **isAuthenticated** - 로그인 여부 (익명 포함) - ✅ **isLoading** - 🆕 **초기값 `true`로 변경 (2025-11-28)** - Auth 초기화 완료 전 리다이렉트 방지 - ✅ **user.isAnonymous** - 익명/정식 계정 구분 - ✅ **loginAsUser()** - 팀 코드 로그인 (PIN 제거) - ✅ **linkWithEmail()** - 이메일 계정 연결 (신규 계정 생성) - ✅ **linkWithGoogle()** - Google 계정 연결 (신규 계정 생성) -- ✅ **mergeWithEmail()** - 기존 이메일 계정과 병합 (데이터 마이그레이션) -- ✅ **mergeWithGoogle()** - 기존 Google 계정과 병합 (데이터 마이그레이션) +- ✅ **mergeWithEmail()** - 기존 이메일 계정과 병합 (데이터 마이그레이션, 🆕 **performMerge 헬퍼**) +- ✅ **mergeWithGoogle()** - 기존 Google 계정과 병합 (데이터 마이그레이션, 🆕 **performMerge 헬퍼**) +- ✅ **🆕 initializeAuth()** - Firestore `onSnapshot` 실시간 구독 (aiCredits/plan/settings 자동 업데이트) - ❌ ~~currentStudent~~, ~~ownedStudents~~, ~~switchStudent()~~ 제거 (복잡도 감소) --- diff --git a/ROADMAP.md b/ROADMAP.md index 54c09d9..bed32b3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # 라온누리 - 개발 로드맵 -> 최종 업데이트: 2025-12-08 (AI 크레딧 환불 시스템, 구매 플로우, 구독 관리) +> 최종 업데이트: 2025-12-12 (팀 보안 설정 통합, 소유자 이전 기능) 초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획 @@ -145,6 +145,12 @@ | **TeamCard AI 활성화 배지** | **team.aiEnabled === true일 때 주황색 배지 표시, LuSparkles 아이콘 사용, "AI 활성화" 텍스트, 다국어 지원 (team.list.aiEnabled, ko/en/ja)** | **2025-12-08** | | **UserSettingsDialog 구독 섹션** | **"내 구독" 탭 추가 (프로필/내 구독/환경설정), 현재 플랜 카드 (플랜 타입 배지, 출처 표시), 플랜 제한 정보 카드 (팀 AI 활성화, AI 분석, 글쓰기 도우미, 이미지 생성), 실시간 사용량 표시 (remaining/limit), Free 플랜 업그레이드 버튼 (새 창, locale 포함), SettingBox 컴포넌트 (투명 배경, 얇은 테두리, 블러 효과), SettingButton 컴포넌트 (브랜드 테두리, 호버 그라데이션), 로딩 상태 처리, 다국어 지원 (components.settingsDialog.subscription namespace, ko/en/ja 17개 키)** | **2025-12-08** | | **AI 크레딧 환불 시스템** | **업그레이드 시 Prorated 환불 → AI 크레딧 지급, FirestoreUser.aiCredits 필드 추가 (영구 사용, 월 제한 무시), 환율 100원 = 10 크레딧, AI 기능 비용 (분석 20, 이미지 100, 도우미 10), credits.ts 유틸리티 (calculateProratedRefund, isUpgrade, PLAN_MONTHLY_PRICES), planLimits.ts 상수 추가 (AI_FEATURE_COSTS, CREDIT_EXCHANGE_RATE, convertKRWToCredits), POST /api/user/purchase API (Mock 승인 + 환불 계산 + 크레딧 지급 + 플랜 업데이트, 원자적 처리), UserManager.purchasePlan() (creditsAdded 반환), PurchaseConfirmationDialog 컴포넌트 (플랜 정보, 가격 포맷팅, 월간/연간 배지, Dialog.Root 패턴), pricing 페이지 구매 플로우 (로그인 체크, 플랜별 분기, 크레딧 안내 toast), UserSettingsDialog 크레딧 표시 (주황색 배지, LuSparkles), 다국어 지원 (components.purchaseDialog, pricing.purchaseSuccessWithCredits, subscription.aiCredits/credits/creditsDescription, ko/en/ja), 향후 Toss Payments 연동 준비 (주석 포함), 타입 체크 통과** | **2025-12-08** | +| **인증 플로우 리팩토링** | **로그인/회원가입 중복 로직 제거 (~105줄 감소), validation.ts 유틸리티 (이메일/비밀번호/이름 검증, 타입 안전), useShakeAnimation 훅 (에러 shake 애니메이션, useEffect 6회 중복 제거, getShakeAnimateProps 헬퍼), sseStreamProcessor.ts (SSE 스트리밍 처리 공통 함수), LoginForm/SignupForm 적용 (validation 유틸 + shake 훅), LoginDialog Google 핸들러 통합 (createAsyncHandler 팩토리 패턴), firebaseAuth.ts mergeAndLogin 통합 함수 (provider별 콜백), authStore.ts performMerge 헬퍼 함수 (Email/Google 병합 로직 통합), Firestore 실시간 구독 (onSnapshot, aiCredits/plan/settings 자동 업데이트, 로그아웃 시 구독 해제), /api/user route 응답 형식 버그 수정 ({user} 래핑), UserManager.createUser 방어 코드 강화, 타입 체크 통과** | **2025-12-09** | +| **팀 기반 AI 사용량 제한** | **팀 AI 활성화 시 팀별 사용량 제한 적용, TeamAIUsage/TeamDailyImageUsage 타입 추가 (Team 문서 내 aiUsage 필드와 members[uid].dailyImage* 필드로 관리), PLAN_MEMBER_LIMITS (팀 멤버 제한), incrementTeamAIUsage/decrementTeamAIUsage 함수, 팀 멤버별 일일 이미지 생성 제한, 팀 월별 이미지 쿼터, 다국어 지원** | **2025-12-10** | +| **플랜 다운그레이드 선택 옵션** | **DowngradeOptionDialog 컴포넌트 (즉시 적용/다음 결제일 선택), downgradeMode 파라미터 추가 (immediate/scheduled), 즉시 다운그레이드 시 크레딧 전환, GET /api/user/plan/estimate-refund API (예상 환불 크레딧 조회), isDowngrade/isUpgrade 공용 유틸로 분리 (src/lib/planUtils.ts), Pricing 페이지 통합, 다국어 지원 (downgradeDialog namespace, ko/en/ja)** | **2025-12-10** | +| **결제 주기 변경 기능** | **동일 플랜 결제 주기 변경 (월간↔연간), scheduledPlan.billingCycle 필드 추가, 다음 결제일부터 적용, 월간 30일/연간 365일 구독 기간, billingCycleChangeSuccess 토스트 알림, 다국어 지원** | **2025-12-10** | +| **팀 소유자 이전 기능** | **POST /api/team/[teamId]/transfer-ownership API, TeamMemberEditor 소유자 이전 메뉴, 소유자 역할 배지 표시, 다국어 지원 (transferOwnership, transferConfirm 등 ko/en/ja)** | **2025-12-12** | +| **팀 보안 설정 통합** | **TeamSecuritySettingsDialog에 공개설정 통합 (isPublic 스위치, allowPublicWritings, description, coverImage), TeamInfoCard에 검색가능/불가능 태그 추가 (FaGlobe/FaEyeSlash), TeamPublicSettings 제거, SecurityLevelSelector searchable 속성 추가, 다국어 지원 (searchable/notSearchable ko/en/ja)** | **2025-12-12** | ### 🚧 진행 중 diff --git a/TECH_STACK.md b/TECH_STACK.md index 450fda6..68732f1 100644 --- a/TECH_STACK.md +++ b/TECH_STACK.md @@ -2439,7 +2439,7 @@ CREDIT_EXCHANGE_RATE **UserSettingsDialog (구독 탭)**: ```tsx - + AI 크레딧 @@ -2450,7 +2450,7 @@ CREDIT_EXCHANGE_RATE 플랜 업그레이드 시 환불받은 크레딧입니다. 월 제한을 초과해도 크레딧으로 AI 기능을 사용할 수 있어요. - + ``` #### 향후 확장 (크레딧 차감) @@ -2485,6 +2485,67 @@ if (planLimitExceeded) { --- +## 코드 품질 및 리팩토링 + +### 인증 플로우 리팩토링 (2025-12-09) + +#### 목표 +로그인/회원가입 플로우의 중복 로직 제거, 유지보수성 향상 + +#### 제거된 중복 로직 + +**1. 이메일/비밀번호 검증** (~10줄 × 2파일 중복) +- LoginForm.tsx, SignupForm.tsx에서 동일한 정규식 사용 +- **해결**: `validation.ts` 유틸리티 생성 + +**2. Shake 애니메이션** (~25줄 × 6회 중복) +- 각 필드마다 useState + useEffect + motion.div 반복 +- **해결**: `useShakeAnimation` 커스텀 훅 + +**3. SSE 스트리밍 처리** (~30줄 × 2함수 중복) +- mergeWithEmail/mergeWithGoogle에서 동일한 fetch + 파싱 로직 +- **해결**: `sseStreamProcessor.ts` 공통 함수 + +**4. 병합 로직** (~70줄 × 2함수 중복) +- authStore의 mergeWithEmail/mergeWithGoogle 80% 동일 코드 +- **해결**: `performMerge` 헬퍼 함수 (provider별 콜백) + +**5. firebaseAuth 병합 함수** (~30줄 × 2함수 중복) +- mergeAndLoginWithEmail/mergeAndLoginWithGoogle 동일 패턴 +- **해결**: `mergeAndLogin` 제네릭 함수 + +#### 생성된 파일 + +``` +src/utils/validation.ts (+130줄) - 폼 검증 함수들 +src/utils/sseStreamProcessor.ts (+45줄) - SSE 처리 +src/hooks/useShakeAnimation.ts (+45줄) - Shake 애니메이션 훅 +``` + +#### 수정된 파일 및 코드 감소량 + +| 파일 | 감소량 | 개선점 | +|------|--------|--------| +| `LoginForm.tsx` | -35줄 | validation 유틸 + shake 훅 적용 | +| `SignupForm.tsx` | -70줄 | validation 유틸 + shake 훅 적용 (4개 필드) | +| `LoginDialog.tsx` | -10줄 | createAsyncHandler 팩토리 패턴 | +| `firebaseAuth.ts` | -20줄 | mergeAndLogin 통합 함수 | +| `authStore.ts` | -80줄 | performMerge 헬퍼 함수 | +| **합계** | **-215줄** | **순 감소 ~105줄** | + +#### 추가 개선사항 + +**Firestore 실시간 구독 (authStore.ts)** +- `initializeAuth()`에서 `onSnapshot` 구독 시작 +- `aiCredits`, `plan`, `settings` 변경 시 자동 업데이트 +- 로그아웃 시 구독 자동 해제 (`unsubscribeUserDoc`) + +**API 버그 수정** +- `POST /api/user` 응답 형식 수정 (user → {user}) +- `UserManager.createUser()` 방어 코드 강화 + +--- + ## 참고 문서 - [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - 프로젝트 구조