()
| 21 | const FILE_INPUT_ID = 'user-avatar-upload'; |
| 22 | |
| 23 | export function UserProfileCard() { |
| 24 | const { t } = useI18n(); |
| 25 | const avatar = useUserProfileStore((s) => s.avatar); |
| 26 | const nickname = useUserProfileStore((s) => s.nickname); |
| 27 | const bio = useUserProfileStore((s) => s.bio); |
| 28 | const setAvatar = useUserProfileStore((s) => s.setAvatar); |
| 29 | const setNickname = useUserProfileStore((s) => s.setNickname); |
| 30 | const setBio = useUserProfileStore((s) => s.setBio); |
| 31 | |
| 32 | const [editingName, setEditingName] = useState(false); |
| 33 | const [nameDraft, setNameDraft] = useState(''); |
| 34 | const [avatarPickerOpen, setAvatarPickerOpen] = useState(false); |
| 35 | const [hydrated, setHydrated] = useState(false); |
| 36 | const nameInputRef = useRef<HTMLInputElement>(null); |
| 37 | |
| 38 | useEffect(() => { |
| 39 | setHydrated(true); // eslint-disable-line react-hooks/set-state-in-effect -- Store hydration on mount |
| 40 | }, []); |
| 41 | |
| 42 | useEffect(() => { |
| 43 | if (editingName) nameInputRef.current?.focus(); |
| 44 | }, [editingName]); |
| 45 | |
| 46 | const displayName = nickname || t('profile.defaultNickname'); |
| 47 | |
| 48 | const startEditName = () => { |
| 49 | setNameDraft(nickname); |
| 50 | setEditingName(true); |
| 51 | }; |
| 52 | |
| 53 | const commitName = () => { |
| 54 | setNickname(nameDraft.trim()); |
| 55 | setEditingName(false); |
| 56 | }; |
| 57 | |
| 58 | const handleAvatarUpload = (e: React.ChangeEvent<HTMLInputElement>) => { |
| 59 | const file = e.target.files?.[0]; |
| 60 | if (!file) return; |
| 61 | if (file.size > MAX_AVATAR_SIZE) { |
| 62 | toast.error(t('profile.fileTooLarge')); |
| 63 | return; |
| 64 | } |
| 65 | if (!file.type.startsWith('image/')) { |
| 66 | toast.error(t('profile.invalidFileType')); |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | const reader = new FileReader(); |
| 71 | reader.onload = () => { |
| 72 | const img = new Image(); |
| 73 | img.onload = () => { |
| 74 | const canvas = document.createElement('canvas'); |
| 75 | canvas.width = 128; |
| 76 | canvas.height = 128; |
| 77 | const ctx = canvas.getContext('2d')!; |
| 78 | const scale = Math.max(128 / img.width, 128 / img.height); |
| 79 | const w = img.width * scale; |
| 80 | const h = img.height * scale; |
nothing calls this directly
no test coverage detected