feat: 계정정보 프로필 사진 — 구글 아바타 우선 + 변경/되돌리기

- 아바타 표시 우선순위: 사용자 지정(profileImage) > 구글(googlePicture) > 이니셜
- 사진 변경: 이미지 선택 → 정사각형 크롭·192px 축소(JPEG) → PUT /auth/profile-image
- 되돌리기: 사용자 지정 사진 해제 → 구글 사진(없으면 이니셜)으로 폴백
- authApi.updateProfileImage 추가, utils/avatar.js(표시 소스/이니셜/다운스케일)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-28 11:39:48 +09:00
parent 24ab91b257
commit 7855298eac
3 changed files with 226 additions and 1 deletions
+55
View File
@@ -0,0 +1,55 @@
// 프로필 아바타 유틸 — 표시 우선순위(커스텀 > 구글 > 이니셜)와 업로드용 다운스케일.
/**
* 표시할 아바타 이미지 소스를 고른다.
* 우선순위: 사용자 지정(profileImage) > 구글 아바타(googlePicture) > null(이니셜 폴백)
*/
export function avatarSrc(user) {
if (!user) return null
return user.profileImage || user.googlePicture || null
}
/** 이름/아이디에서 이니셜(첫 글자) 한 글자 — 이미지 없을 때 폴백 */
export function avatarInitial(user) {
const s = (user?.name || user?.loginId || '').trim()
return s ? s[0].toUpperCase() : '?'
}
/**
* 업로드용으로 이미지 파일을 정사각형으로 크롭·축소해 JPEG data URL 로 변환한다.
* (서버/로컬 저장 용량을 작게 — 기본 192px)
*/
export function fileToAvatarDataUrl(file, size = 192, quality = 0.85) {
return new Promise((resolve, reject) => {
if (!file || !file.type?.startsWith('image/')) {
reject(new Error('이미지 파일이 아닙니다.'))
return
}
const url = URL.createObjectURL(file)
const img = new Image()
img.onload = () => {
try {
const canvas = document.createElement('canvas')
canvas.width = size
canvas.height = size
const ctx = canvas.getContext('2d')
// 중앙 정사각형 크롭(cover)
const min = Math.min(img.width, img.height)
const sx = (img.width - min) / 2
const sy = (img.height - min) / 2
ctx.drawImage(img, sx, sy, min, min, 0, 0, size, size)
const dataUrl = canvas.toDataURL('image/jpeg', quality)
URL.revokeObjectURL(url)
resolve(dataUrl)
} catch (e) {
URL.revokeObjectURL(url)
reject(e)
}
}
img.onerror = () => {
URL.revokeObjectURL(url)
reject(new Error('이미지를 읽지 못했습니다.'))
}
img.src = url
})
}