| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 | 
							- '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<InputImageInfo>()
 
-   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 <Avatar {...props} />
 
-   return (
 
-     <>
 
-       <div>
 
-         <div className="relative group">
 
-           <Avatar {...props} />
 
-           <div
 
-             onClick={() => { 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"
 
-           >
 
-             <span className="text-white text-xs">
 
-               <RiPencilLine />
 
-             </span>
 
-           </div>
 
-         </div>
 
-       </div>
 
-       <Modal
 
-         closable
 
-         className="!w-[362px] !p-0"
 
-         isShow={isShowAvatarPicker}
 
-         onClose={() => setIsShowAvatarPicker(false)}
 
-       >
 
-         <ImageInput onImageInput={handleImageInput} cropShape='round' />
 
-         <Divider className='m-0' />
 
-         <div className='w-full flex items-center justify-center p-3 gap-2'>
 
-           <Button className='w-full' onClick={() => setIsShowAvatarPicker(false)}>
 
-             {t('app.iconPicker.cancel')}
 
-           </Button>
 
-           <Button variant="primary" className='w-full' disabled={uploading || !inputImageInfo} loading={uploading} onClick={handleSelect}>
 
-             {t('app.iconPicker.ok')}
 
-           </Button>
 
-         </div>
 
-       </Modal>
 
-     </>
 
-   )
 
- }
 
- export default AvatarWithEdit
 
 
  |