feat: 프로필 이미지(avatarUrl) 필드 + UserAvatar 도입, 색상·이니셜 아바타 제거

- 백엔드: User.avatarUrl(nullable) 추가 → PublicUser/toPublic 로 모든 유저 응답에 노출.
- 프론트: 공용 UserAvatar(이미지 있으면 img, 없으면 사람 실루엣 placeholder)로
  전 화면 아바타 통일(상단바·저장소·멤버·담당자·활동·내업무·댓글·AI패널).
  화면마다 달랐던 색상+이니셜/하드코딩 청록 아바타 제거.
- 죽은 코드 정리: 색상·이니셜 시스템(avatarColor/initialOf/AvatarColor 유틸·타입,
  User/RepoActivityItem 의 initial/color, 전역·컴포넌트 .av-a~.av-f CSS) +
  미사용 정적 목업 데이터(USERS/REPOS/REPO_TASKS/TASK_DETAIL/MY_TASKS_*/REPO_MEMBERS/
  REPO_ACTIVITY 등) 전부 삭제. relay.mock.ts 는 뷰 타입 + countActive 만 유지.

avatar_url 컬럼은 dev synchronize 가 추가. 업로드(실제 사진 설정)는 추후 단계.
검증: 백엔드 build 0, 프론트 type-check/lint/build 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-19 14:04:09 +09:00
parent fa4bc40761
commit 5351373694
23 changed files with 171 additions and 882 deletions
-19
View File
@@ -26,25 +26,6 @@ export function formatDate(input: Date | string): string {
* (서버는 타임스탬프/카운트만 내려주고 여기서 계산한다)
* ============================================================ */
/** 아바타 색상 키 (relay.css 의 .av-* 클래스와 대응) */
export type AvatarColorKey = 'av-a' | 'av-b' | 'av-c' | 'av-d' | 'av-e' | 'av-f'
const AVATAR_COLORS: AvatarColorKey[] = ['av-a', 'av-b', 'av-c', 'av-d', 'av-e', 'av-f']
/** 식별자(문자열)를 결정적으로 아바타 색상에 매핑 */
export function avatarColor(seed: string): AvatarColorKey {
let hash = 0
for (let i = 0; i < seed.length; i++) {
hash = (hash * 31 + seed.charCodeAt(i)) >>> 0
}
return AVATAR_COLORS[hash % AVATAR_COLORS.length] ?? 'av-a'
}
/** 이름의 첫 글자(아바타 이니셜) */
export function initialOf(name: string): string {
return name.trim().charAt(0) || '?'
}
/** ISO 문자열/Date 를 한국어 상대시간으로 (예: "2시간 전", "어제", "3일 전") */
export function relativeTimeKo(input: Date | string): string {
const date = typeof input === 'string' ? new Date(input) : input