'use client' import type { Area } from 'react-easy-crop' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { RiPencilLine } from '@remixicon/react' import { updateUserProfile } from '@/service/common' import { ToastContext } from '@/app/components/base/toast' import ImageInput, { type OnImageInput } from '@/app/components/base/app-icon-picker/ImageInput' import Modal from '@/app/components/base/modal' import Divider from '@/app/components/base/divider' import Button from '@/app/components/base/button' import Avatar, { type AvatarProps } from '@/app/components/base/avatar' import { useLocalFileUploader } from '@/app/components/base/image-uploader/hooks' import type { ImageFile } from '@/types/app' import getCroppedImg from '@/app/components/base/app-icon-picker/utils' import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config' type InputImageInfo = { file: File } | { tempUrl: string; croppedAreaPixels: Area; fileName: string } type AvatarWithEditProps = AvatarProps & { onSave?: () => void } const AvatarWithEdit = ({ onSave, ...props }: AvatarWithEditProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const [inputImageInfo, setInputImageInfo] = useState() const [isShowAvatarPicker, setIsShowAvatarPicker] = useState(false) const [uploading, setUploading] = useState(false) const handleImageInput: OnImageInput = useCallback(async (isCropped: boolean, fileOrTempUrl: string | File, croppedAreaPixels?: Area, fileName?: string) => { setInputImageInfo( isCropped ? { tempUrl: fileOrTempUrl as string, croppedAreaPixels: croppedAreaPixels!, fileName: fileName! } : { file: fileOrTempUrl as File }, ) }, [setInputImageInfo]) const handleSaveAvatar = useCallback(async (uploadedFileId: string) => { try { await updateUserProfile({ url: 'account/avatar', body: { avatar: uploadedFileId } }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) setIsShowAvatarPicker(false) onSave?.() } catch (e) { notify({ type: 'error', message: (e as Error).message }) } }, [notify, onSave, t]) const { handleLocalFileUpload } = useLocalFileUploader({ limit: 3, disabled: false, onUpload: (imageFile: ImageFile) => { if (imageFile.progress === 100) { setUploading(false) setInputImageInfo(undefined) handleSaveAvatar(imageFile.fileId) } // Error if (imageFile.progress === -1) setUploading(false) }, }) const handleSelect = useCallback(async () => { if (!inputImageInfo) return setUploading(true) if ('file' in inputImageInfo) { handleLocalFileUpload(inputImageInfo.file) return } const blob = await getCroppedImg(inputImageInfo.tempUrl, inputImageInfo.croppedAreaPixels, inputImageInfo.fileName) const file = new File([blob], inputImageInfo.fileName, { type: blob.type }) handleLocalFileUpload(file) }, [handleLocalFileUpload, inputImageInfo]) if (DISABLE_UPLOAD_IMAGE_AS_ICON) return return ( <>
{ setIsShowAvatarPicker(true) }} className="absolute inset-0 bg-black bg-opacity-50 rounded-full opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer flex items-center justify-center" >
setIsShowAvatarPicker(false)} >
) } export default AvatarWithEdit