import { useRef, useState, type ChangeEvent } from 'react'; import { Building2, Camera, Loader2, Mail, ShieldCheck, Trash2, type LucideIcon } from 'lucide-react'; import { ConfirmDialog } from '../../components/ConfirmDialog'; import { PageHeader } from '../../components/PageHeader'; import { useToast } from '../../components/ToastProvider'; import { useProfileAvatar } from '../../hooks/useProfileAvatar'; import { deleteProfileAvatar, notifyProfileAvatarDeleted, notifyProfileAvatarUpdated, resizeImageToWebp, uploadProfileAvatar } from '../../lib/profileAvatar'; import type { AuthSession } from '../../types/auth'; type ProfilePageProps = { session: AuthSession; }; export function ProfilePage({ session }: ProfilePageProps) { const fileInputRef = useRef(null); const { showToast } = useToast(); const { avatarObjectUrl, avatarMissing, avatarLoading, refreshAvatar, setAvatarMissing } = useProfileAvatar(session.accessToken); const [avatarUploading, setAvatarUploading] = useState(false); const [avatarDeleting, setAvatarDeleting] = useState(false); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const displayName = getDisplayName(session); const affiliation = getAffiliation(session); const roleLabel = getRoleLabel(session.user.role); const organizationName = session.user.primaryAffiliation?.organizationName ?? '소속 미지정'; const department = session.user.primaryAffiliation?.department ?? '부서 미지정'; const position = session.user.primaryAffiliation?.position ?? '직책 미지정'; const hasAvatar = !avatarMissing && Boolean(avatarObjectUrl); const avatarBusy = avatarLoading || avatarUploading || avatarDeleting; const handleAvatarSelect = async (event: ChangeEvent) => { const file = event.target.files?.[0]; event.target.value = ''; if (!file) return; setAvatarUploading(true); try { const imageBlob = await resizeImageToWebp(file, 128); await uploadProfileAvatar(session.accessToken, imageBlob); refreshAvatar(); notifyProfileAvatarUpdated(); showToast({ title: '프로필 이미지가 변경되었습니다.', variant: 'success' }); } catch (error) { showToast({ title: '프로필 이미지 변경 실패', message: error instanceof Error ? error.message : '이미지를 다시 확인해주세요.', variant: 'error', }); } finally { setAvatarUploading(false); } }; const handleAvatarDelete = async () => { setAvatarDeleting(true); try { await deleteProfileAvatar(session.accessToken); setAvatarMissing(true); notifyProfileAvatarDeleted(); setDeleteConfirmOpen(false); showToast({ title: '프로필 이미지가 삭제되었습니다.', variant: 'success' }); } catch { showToast({ title: '프로필 이미지 삭제 실패', message: '잠시 후 다시 시도해주세요.', variant: 'error' }); } finally { setAvatarDeleting(false); } }; return (
{hasAvatar ? ( setAvatarMissing(true)} /> ) : ( displayName.slice(0, 1) )}
{avatarBusy ? ( ) : null} {hasAvatar ? ( ) : null}

{displayName}

{roleLabel}

계정 정보

공통 인증 서버에서 전달된 로그인 정보입니다.

setDeleteConfirmOpen(false)} onConfirm={() => void handleAvatarDelete()} />
); } function ProfileRow({ icon: Icon, label, value }: { icon: LucideIcon; label: string; value: string }) { return (
); } function InfoCard({ label, value, wide = false }: { label: string; value: string; wide?: boolean }) { return (

{label}

{value}

); } function getDisplayName(session: AuthSession): string { const name = session.user.name?.trim(); if (name && name !== 'ATNMY Studio Admin') return name; return session.user.email.split('@')[0] || '강사'; } function getAffiliation(session: AuthSession): string { const affiliation = session.user.primaryAffiliation; if (!affiliation) return '소속 미지정'; return [affiliation.organizationName, affiliation.department, affiliation.position].filter(Boolean).join(' · '); } function getRoleLabel(role: string): string { const normalized = role.toUpperCase(); if (normalized === 'LMS_ADMIN') return '관리자'; if (normalized === 'INSTRUCTOR') return '강사'; return role; }