283 lines
16 KiB
Markdown
283 lines
16 KiB
Markdown
# 크리에이터 DM 리스너 선택 PRD
|
||
|
||
## 문서 정보
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 문서 상태 | 검토 중 |
|
||
| 작성일 | `2026-09-14` |
|
||
| 최종 수정일 | `2026-09-14` |
|
||
| 대상 제품 | 크리에이터가 리스너에게 먼저 DM 보내기 |
|
||
| 작성자·결정권자 | 제품 결정: 요청자 / 문서 작성: Sisyphus |
|
||
| 관련 API Contract | 이 문서 §11 |
|
||
| 관련 구현 계획 | `docs/20260914_크리에이터_DM_리스너_선택/plan-task.md` |
|
||
| 관련 review | 없음 |
|
||
|
||
### 요구사항 상태
|
||
|
||
| 상태 | 의미 | 구현 처리 |
|
||
|---|---|---|
|
||
| 확정 | 제품·기술 결정이 완료되어 구현 기준으로 사용 | `plan-task.md`의 Task와 완료 증거로 추적 |
|
||
| 미결 | 제품·UX·운영 결정이 더 필요함 | 구현 전 질문 또는 API 확인 필요 |
|
||
| 제외 | 현재 릴리스에서 구현하지 않기로 결정 | Decision Log에 기록 |
|
||
|
||
## 1. Overview
|
||
|
||
현재 리스너는 크리에이터 채널에서 크리에이터에게 바로 DM을 시작할 수 있지만, 크리에이터가 리스너에게 먼저 DM을 시작하는 진입점은 없다. 이 기능은 메인 대화 탭에서 크리에이터가 팔로우 리스트 또는 닉네임 검색으로 리스너를 선택하고, 확인 팝업 후 기존 유저-크리에이터 DM 방 생성 API로 DM 방에 진입하게 한다.
|
||
|
||
## 2. Problem Statement
|
||
|
||
현재 사용자는 다음 문제를 겪는다.
|
||
|
||
- 크리에이터가 리스너에게 선제적으로 1:1 DM을 시작할 수 없다.
|
||
- 기존 DM 생성 흐름은 크리에이터 채널에서 리스너가 크리에이터에게 보내는 방향을 기준으로 구성되어 있다.
|
||
- 크리에이터가 리스너를 찾기 위해 별도 화면이나 외부 수단을 사용해야 한다.
|
||
|
||
문제를 해결했다는 판단은 크리에이터 계정이 메인 대화 탭에서 리스너를 선택해 기존 DM 방 생성 API를 호출하고, 생성된 DM 방으로 진입하는 것으로 한다.
|
||
|
||
## 3. Goals
|
||
|
||
### 3.1 제품 목표
|
||
|
||
- 크리에이터가 메인 대화 탭에서 리스너에게 먼저 DM을 시작할 수 있다.
|
||
- 초기 화면은 기존 팔로우 전체 리스트 페이지와 동일한 API를 사용한다.
|
||
- 검색은 2글자 이상일 때만 `GET /member/search`를 500ms debounce 후 호출한다.
|
||
- 선택한 유저에게 메시지를 보낼지 확인하는 다이얼로그를 보여준다.
|
||
- 보내기 시 기존 유저-크리에이터 DM 방 생성 API를 재사용한다.
|
||
|
||
### 3.2 UX 목표
|
||
|
||
- 메인 대화 탭 우측 하단에 홈의 플로팅 `+` 버튼과 같은 진입점을 제공한다.
|
||
- 새 화면은 Figma `2372:23496` 기준의 검색 바, 팔로우 섹션, 유저 리스트 구조를 따른다.
|
||
- 확인 팝업은 Figma `2372:23521` 기준 문구와 V2 공통 `SodaV2ActionModal`을 재사용한다.
|
||
- 검색 결과가 없을 때 화면 중앙에 `사용자가 없어요.\n다른 이름으로 다시 검색해 주세요.`를 다국어 문구로 표시한다.
|
||
|
||
## 4. Non-Goals
|
||
|
||
- 새 채팅 프로토콜, WebSocket 전송 방식, 메시지 본문 전송 기능은 만들지 않는다.
|
||
- 기존 채팅방 UI(`UserCreatorChatRoomView`)의 메시지 표시·전송 UX를 변경하지 않는다.
|
||
- 팔로우 리스트 API의 서버 계약을 새로 정의하지 않는다. 기존 API를 재사용한다.
|
||
- 검색 API의 서버 응답 필드, 필터링 정책, 차단 유저 제외 정책을 클라이언트에서 추정하지 않는다.
|
||
- 새 공통 모달 컴포넌트를 만들지 않는다.
|
||
|
||
## 5. Target Users and Permissions
|
||
|
||
| 사용자 | 목표 | 주요 작업 | 사용 환경 |
|
||
|---|---|---|---|
|
||
| 크리에이터 | 리스너에게 먼저 DM 시작 | 대화 탭 진입, 팔로우 리스트 조회, 검색, 유저 선택, 확인 후 DM 방 진입 | iOS 앱 |
|
||
| 리스너 | 크리에이터가 시작한 DM 수신 | 기존 DM 방에서 메시지 확인 | iOS 앱 |
|
||
|
||
### 5.2 권한
|
||
|
||
- 인증 주체: 로그인된 사용자.
|
||
- 허용 역할: `MemberRole.CREATOR`인 크리에이터.
|
||
- 거부 조건: 비로그인 또는 크리에이터가 아닌 사용자는 메인 대화 탭의 신규 `+` 진입점을 보지 않는다.
|
||
- 리소스 소유권: 방 생성 권한과 recipient 검증은 서버의 `/api/v2/user-creator-chat/rooms/create` 정책을 따른다.
|
||
|
||
## 6. 핵심 사용자 흐름
|
||
|
||
1. 크리에이터가 메인 대화 탭에 진입한다.
|
||
2. 우측 하단 `+` 버튼을 터치한다.
|
||
3. 새 유저 선택 화면이 열린다.
|
||
4. 초기 상태에서 기존 팔로우 전체 리스트 API의 결과를 보여준다.
|
||
5. 검색창에 2글자 이상 입력하면 500ms 동안 추가 입력이 멈춘 뒤 `GET /member/search?nickname={query}`를 호출한다.
|
||
6. 검색 결과가 없으면 중앙에 empty 문구를 표시한다.
|
||
7. 유저를 터치하면 `메시지 보내기` 확인 팝업을 표시한다.
|
||
8. 보내기를 터치하면 기존 DM 방 생성 API를 호출한다.
|
||
9. 성공 시 반환된 `roomId`로 `AppStep.userCreatorChatRoom(roomId:)`에 진입한다.
|
||
|
||
## 7. 정보 구조와 라우팅
|
||
|
||
```text
|
||
MainView / MainTab.chat
|
||
MainChatView
|
||
+ floating action button
|
||
-> AppStep.userCreatorChatRecipientSearch
|
||
UserCreatorChatRecipientSearchView
|
||
-> AppStep.userCreatorChatRoom(roomId: Int)
|
||
```
|
||
|
||
- 새 라우트 이름은 구현 시 코드 스타일에 맞춰 조정할 수 있으나, 역할은 “크리에이터가 DM을 시작할 리스너 선택 화면” 하나로 제한한다.
|
||
- 기존 `AppStep.userCreatorChatCreator(creatorId:)`는 리스너가 크리에이터 채널에서 DM을 시작하는 흐름으로 유지한다.
|
||
- 새 흐름은 유저 선택 후 `recipientId` 기반으로 방을 생성한다.
|
||
|
||
## 8. 기능 요구사항
|
||
|
||
### 8.1 진입점
|
||
|
||
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||
|---|---|---|---|---|
|
||
| `CDM-ENTRY-001` | 확정 | 메인 대화 탭 우측 하단에 홈처럼 `+` 플로팅 버튼을 추가한다. | 크리에이터 계정에서만 보이고, 탭 시 새 리스너 선택 화면으로 이동한다. | `P1-T1` |
|
||
| `CDM-ENTRY-002` | 확정 | 비크리에이터와 비로그인 사용자는 신규 `+` 버튼을 보지 않는다. | `MemberRole.CREATOR`가 아니면 버튼이 렌더링되지 않는다. | `P1-T1` |
|
||
|
||
### 8.2 리스너 선택 화면
|
||
|
||
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||
|---|---|---|---|---|
|
||
| `CDM-LIST-001` | 확정 | 새 화면은 Figma `2372:23496` 구조를 따른다. | 뒤로가기, 검색 바, 섹션 타이틀, 리스트가 검은 배경 위에 배치된다. | `P2-T1` |
|
||
| `CDM-LIST-002` | 확정 | 초기 진입 시 기존 팔로우 전체 리스트 페이지와 동일한 API를 호출한다. | `FollowCreatorRepository.getFollowedCreatorAllList(page:size:)`와 같은 endpoint·pagination 정책을 사용한다. | `P2-T2` |
|
||
| `CDM-LIST-003` | 확정 | 초기 리스트 아이템 터치 시 확인 팝업을 보여준다. | 터치만으로 방 생성 API가 바로 호출되지 않는다. | `P2-T4` |
|
||
|
||
### 8.3 검색
|
||
|
||
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||
|---|---|---|---|---|
|
||
| `CDM-SEARCH-001` | 확정 | 검색어가 2글자 미만이면 검색 API를 호출하지 않는다. | 0~1글자 입력 중 network 요청 0회, 화면은 초기 팔로우 리스트 기준을 유지한다. | `P2-T3` |
|
||
| `CDM-SEARCH-002` | 확정 | 검색어가 2글자 이상이면 500ms debounce 후 `GET /member/search`를 호출한다. | 연속 입력 중에는 마지막 입력 후 500ms가 지나기 전 요청하지 않는다. | `P2-T3` |
|
||
| `CDM-SEARCH-003` | 확정 | 검색 결과가 없을 때 중앙 empty 문구를 표시한다. | `사용자가 없어요.\n다른 이름으로 다시 검색해 주세요.`가 다국어 키로 표시된다. | `P2-T3` |
|
||
|
||
### 8.4 확인 팝업과 방 생성
|
||
|
||
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||
|---|---|---|---|---|
|
||
| `CDM-MODAL-001` | 확정 | 유저 터치 시 V2 내부 공통 팝업을 재사용한다. | `SodaV2ActionModal`을 사용하고 새 모달 컴포넌트를 만들지 않는다. | `P2-T4` |
|
||
| `CDM-MODAL-002` | 확정 | 팝업 문구는 `메시지 보내기`, `{{사용자이름}}에게 메시지를 보낼까요?`, `취소`, `보내기`를 다국어 처리한다. | 모든 문구가 `I18n` 키를 통해 참조된다. | `P2-T4` |
|
||
| `CDM-CREATE-001` | 확정 | 보내기 시 기존 유저-크리에이터 DM 방 생성 API를 호출한다. | `POST /api/v2/user-creator-chat/rooms/create` 성공 시 `roomId`를 받아 DM 방으로 진입한다. | `P2-T5` |
|
||
| `CDM-CREATE-002` | 확정 | 크리에이터가 리스너에게 보내는 흐름은 선택한 유저 ID를 `recipientId`로 보낸다. | request body가 `recipientId`와 `creatorId`를 동시에 서로 다른 값으로 보내지 않는다. | `P2-T5` |
|
||
|
||
## 9. UI/UX Expectations
|
||
|
||
### 9.1 디자인 기준
|
||
|
||
- 신규 화면 Figma: `2372:23496` (`chat_src_001`, 402×874)
|
||
- 확인 팝업 Figma: `2372:23521` (`Frame 1707482932`, 340×207)
|
||
- 검색 안내 문구: Figma 기준 `팬 이름을 입력하세요`
|
||
- 초기 섹션 타이틀: Figma 기준 `팔로워`
|
||
- 리스트 아이템: 원형 프로필 이미지와 닉네임 텍스트 구조를 사용한다.
|
||
|
||
### 9.2 화면 상태
|
||
|
||
- loading: 초기 팔로우 리스트와 검색 요청 중 기존 프로젝트 loading 패턴을 따른다.
|
||
- empty: 검색 결과 0건일 때 중앙 안내 문구 표시.
|
||
- error: 기존 toast 또는 공통 오류 메시지 정책을 따른다.
|
||
- success: 방 생성 성공 시 별도 성공 toast 없이 채팅방으로 이동한다.
|
||
|
||
## 10. 다국어 문구
|
||
|
||
| 용도 | 한국어 문구 | 비고 |
|
||
|---|---|---|
|
||
| 검색 안내 문구 | 팬 이름을 입력하세요 | Figma 기준 |
|
||
| 초기 섹션 타이틀 | 팔로워 | Figma 기준 |
|
||
| 검색 empty 1행 | 사용자가 없어요. | 줄바꿈 포함 |
|
||
| 검색 empty 2행 | 다른 이름으로 다시 검색해 주세요. | 줄바꿈 포함 |
|
||
| 팝업 제목 | 메시지 보내기 | Figma 기준 |
|
||
| 팝업 본문 | {{사용자이름}}에게 메시지를 보낼까요? | 닉네임 치환 |
|
||
| 팝업 취소 | 취소 | 기존 `I18n.Common.cancel` 재사용 가능 |
|
||
| 팝업 보내기 | 보내기 | 새 키 또는 기존 동일 의미 키 재사용 |
|
||
|
||
## 11. API 계약
|
||
|
||
### 11.1 초기 팔로우 전체 리스트
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 기존 호출 지점 | `FollowCreatorViewModel.getFollowedCreatorAllList()` |
|
||
| Repository | `FollowCreatorRepository.getFollowedCreatorAllList(page:size:)` |
|
||
| Method | `GET` |
|
||
| Path | `/live/recommend/following/channel/all/list` |
|
||
| Query | `page`, `size`, `timezone` |
|
||
| Response decode | `ApiResponse<GetCreatorFollowingAllListResponse>` |
|
||
| Item model | `GetCreatorFollowingAllListItem(creatorId, nickname, profileImageUrl, isFollow, isNotify)` |
|
||
|
||
### 11.2 유저 검색
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 기존 API | `UserApi.searchUser(nickname:)` |
|
||
| Repository | `UserRepository.searchUser(nickname:)` |
|
||
| Method | `GET` |
|
||
| Path | `/member/search` |
|
||
| Query | `nickname` |
|
||
| 호출 조건 | 검색어 trim 후 2글자 이상, 500ms debounce 완료 |
|
||
|
||
### 11.3 DM 방 생성
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 기존 API | `UserCreatorChatApi.createRoom` |
|
||
| Repository | `UserCreatorChatRepository.createRoom(...)` |
|
||
| Method | `POST` |
|
||
| Path | `/api/v2/user-creator-chat/rooms/create` |
|
||
| 기존 iOS request | `UserCreatorCreateRoomRequest(creatorId: Int)` |
|
||
| 신규 요구 request | `recipientId: Int?`, `creatorId: Int?` 중 하나만 유효하게 전송 |
|
||
| 성공 response | `UserCreatorCreateRoomResponse(roomId: Int)` |
|
||
|
||
서버 request 기준:
|
||
|
||
```kotlin
|
||
data class CreateUserCreatorChatRoomRequest(
|
||
val recipientId: Long? = null,
|
||
val creatorId: Long? = null
|
||
)
|
||
```
|
||
|
||
- 리스너가 크리에이터 채널에서 DM 시작: 기존처럼 `creatorId` 전송.
|
||
- 크리에이터가 리스너에게 DM 시작: 선택한 유저의 ID를 `recipientId`로 전송.
|
||
- `recipientId`와 `creatorId`가 서로 다른 값으로 동시에 전송되는 상태는 만들지 않는다.
|
||
|
||
## 12. 보안과 데이터 취급
|
||
|
||
- 인증 header는 기존 Moya target의 `Authorization: Bearer {token}` 정책을 따른다.
|
||
- 검색어, 닉네임, member ID 외 민감정보를 로그에 남기지 않는다.
|
||
- 방 생성 실패 시 서버 message 또는 `I18n.Common.commonError`를 사용한다.
|
||
|
||
## 13. 성능과 품질 요구사항
|
||
|
||
- 검색 debounce 시간은 500ms로 고정한다.
|
||
- 2글자 미만 검색어는 network 요청을 보내지 않는다.
|
||
- 검색어가 바뀌면 이전 pending debounce 작업은 취소한다.
|
||
- pagination은 기존 팔로우 전체 리스트의 `page = 1`, `size = 10`, `isLast` 정책을 따른다.
|
||
- 신규 dependency를 추가하지 않는다.
|
||
|
||
## 14. 성공 기준
|
||
|
||
### 14.1 기능 수용 기준
|
||
|
||
- [ ] 크리에이터 계정의 메인 대화 탭 우측 하단에 `+` 버튼이 표시된다.
|
||
- [ ] `+` 버튼을 누르면 Figma 기준 리스너 선택 화면으로 이동한다.
|
||
- [ ] 화면 최초 진입 시 기존 팔로우 전체 리스트 API가 호출된다.
|
||
- [ ] 검색어 2글자 미만에서는 `/member/search` 요청이 없다.
|
||
- [ ] 검색어 2글자 이상에서 마지막 입력 500ms 후 `/member/search?nickname=` 요청이 1회 발생한다.
|
||
- [ ] 검색 결과가 없으면 다국어 empty 문구가 중앙에 표시된다.
|
||
- [ ] 유저 터치 시 `SodaV2ActionModal` 확인 팝업이 표시된다.
|
||
- [ ] 보내기 터치 시 `recipientId` 기반으로 기존 DM 방 생성 API가 호출되고, 성공 시 DM 방으로 이동한다.
|
||
|
||
### 14.2 추적성 완료 기준
|
||
|
||
- [ ] 모든 `확정` 요구사항이 `plan-task.md`의 Goal에 연결된다.
|
||
- [ ] 모든 신규 문구가 다국어 키에 연결된다.
|
||
- [ ] 기존 크리에이터 채널 DM 시작 흐름이 회귀하지 않는다.
|
||
|
||
## 15. Open Questions
|
||
|
||
| ID | 상태 | 결정 필요 사항 | 현재 권고 | 결정 주체 | 결정 기한/시점 | 영향 Goal |
|
||
|---|---|---|---|---|---|---|
|
||
| 없음 | 확정 | 인터뷰로 초기 리스트 API 기준 확인 완료 | 기존 팔로우 전체 리스트 API 사용 | 요청자 | 2026-09-14 | `P2-T2` |
|
||
|
||
## 16. 요구사항 추적표
|
||
|
||
| 요구사항 범위 | API Contract | 계획 Phase | Goal | 자동 검증 | 수동 검증 |
|
||
|---|---|---:|---|---|---|
|
||
| `CDM-ENTRY-001~002` | 해당 없음 | 1 | `P1-T1` | 빌드 | 크리에이터/비크리에이터 버튼 노출 확인 |
|
||
| `CDM-LIST-001~003` | §11.1 | 2 | `P2-T1`, `P2-T2`, `P2-T4` | 빌드 | 초기 목록과 팝업 확인 |
|
||
| `CDM-SEARCH-001~003` | §11.2 | 2 | `P2-T3` | 빌드 | 2글자/500ms/empty 확인 |
|
||
| `CDM-MODAL-001~002`, `CDM-CREATE-001~002` | §11.3 | 2 | `P2-T4`, `P2-T5` | 빌드 | request payload와 방 진입 확인 |
|
||
|
||
## 17. Decision Log
|
||
|
||
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·계약·Goal |
|
||
|---|---|---|---|---|---|
|
||
| 2026-09-14 | `DEC-001` | 확정 | 코드 구현 없이 PRD와 `plan-task.md`만 작성한다. | 사용자 직접 지시 | 전체 |
|
||
| 2026-09-14 | `DEC-002` | 확정 | 새 화면 초기 목록은 기존 팔로우 전체 리스트 페이지와 동일한 API를 사용한다. | 사용자 답변 `B` | `CDM-LIST-002`, §11.1, `P2-T2` |
|
||
| 2026-09-14 | `DEC-003` | 확정 | 크리에이터가 리스너에게 보내는 새 흐름은 `recipientId`를 사용하고, 기존 크리에이터 채널 흐름은 `creatorId`를 유지한다. | 서버 request contract | `CDM-CREATE-002`, §11.3, `P2-T5` |
|
||
|
||
## 18. 변경 관리
|
||
|
||
요구사항 변경 시 다음을 확인한다.
|
||
|
||
- [ ] Decision Log에 변경 이유와 날짜를 기록했다.
|
||
- [ ] 관련 요구사항 상태·본문·수용 기준을 갱신했다.
|
||
- [ ] API request/response/error 계약을 갱신했다.
|
||
- [ ] `plan-task.md`의 범위·Files·Interfaces·체크박스·완료 증거를 코드 변경 전에 갱신했다.
|
||
- [ ] 기존 Progress·review·검증 기록을 삭제하거나 덮어쓰지 않았다.
|