import React , { FC , useState , useReducer , useEffect } from 'react' ; import { useFormContext , Controller } from 'react-hook-form' ; import { useMutation } from '@apollo/client' ; import { getValueViaPath , Application , coreErrorMessages , } from '@island.is/application/core' ; import { InputFileUpload , UploadFile , fileToObject , } from '@island.is/island-ui/core' ; import { useLocale } from '@island.is/localization' ; import { CREATE_UPLOAD_URL , ADD_ATTACHMENT , DELETE_ATTACHMENT , } from '@island.is/application/graphql' ; import { uploadFileToS3 } from './utils' ; import { Action , ActionTypes } from './types' ; type UploadFileAnswer = { name : string ; key ? : string ; } ; const transformToAnswer = ( { name , key } ) : UploadFileAnswer => { return { name , key } ; } ; const answerToUploadFile = ( { name , key } ) : UploadFile => { return { name , key , status : 'done' } ; } ; function reducer ( state , action ) { switch ( action . type ) { case ActionTypes . ADD : return state . concat ( action . payload . newFiles ) ; case ActionTypes . REMOVE : return state . filter ( ( file ) => file . name !== action . payload . fileToRemove . name ) ; case ActionTypes . UPDATE : return state . map ( ( file ) => { if ( file . name === action . payload . file . name ) { file . status = action . payload . status ; file . percent = action . payload . percent ; file . key = action . payload . key ; } return file ; } ) ; default : return state ; } } interface FileUploadControllerProps { readonly id : string ; error ? : string ; application : Application ; readonly header ? : string ; readonly description ? : string ; readonly buttonLabel ? : string ; readonly multiple ? : boolean ; readonly accept ? : string ; readonly maxSize ? : number ; } export const FileUploadController = ( { id , error , application , header , description , buttonLabel , multiple , accept , maxSize , } ) => { const { formatMessage } = useLocale ( ) ; const { clearErrors , setValue } = useFormContext ( ) ; const [ uploadError , setUploadError ] = useState < string | undefined > ( error ) ; const val = getValueViaPath ( application . answers , id , [ ] ) as UploadFile [ ] ; const [ createUploadUrl ] = useMutation ( CREATE_UPLOAD_URL ) ; const [ addAttachment ] = useMutation ( ADD_ATTACHMENT ) ; const [ deleteAttachment ] = useMutation ( DELETE_ATTACHMENT ) ; const initialUploadFiles = ( val && val . map ( ( f ) => answerToUploadFile ( f ) ) ) || [ ] ; const [ state , dispatch ] = useReducer ( reducer , initialUploadFiles ) ; useEffect ( ( ) => { const onlyUploadedFiles = state . filter ( ( f ) => f . key && f . status === 'done' ) ; const uploadAnswer = onlyUploadedFiles . map ( transformToAnswer ) ; setValue ( id , uploadAnswer ) ; } , [ state , id , setValue ] ) ; const uploadFileFlow = async ( file ) => { try { const { data } = await createUploadUrl ( { variables : { filename : file . name , } , } ) ; const { createUploadUrl : { url , fields } , } = data ; const response = await uploadFileToS3 ( file , dispatch , url , fields ) ; await addAttachment ( { variables : { input : { id : application . id , key : fields . key , url : `${ response . url }/${ fields . key }` , } , } , } ) ; return Promise . resolve ( { key : fields . key } ) ; } catch ( e ) { console . error ( `Error with FileUploadController ${ e }` ) ; setUploadError ( formatMessage ( coreErrorMessages . fileUpload ) ) ; return Promise . reject ( ) ; } } ; const onFileChange = async ( newFiles ) => { const addedUniqueFiles = newFiles . filter ( ( newFile ) => { let isUnique = true ; state . forEach ( ( uploadedFile ) => { if ( uploadedFile . name === newFile . name ) isUnique = false ; } ) ; return isUnique ; } ) ; if ( addedUniqueFiles . length === 0 ) return ; clearErrors ( id ) ; setUploadError ( undefined ) ; const newUploadFiles = addedUniqueFiles . map ( ( f ) => fileToObject ( f , 'uploading' ) ) ; dispatch ( { type : ActionTypes . ADD , payload : { newFiles : newUploadFiles , } , } ) ; newUploadFiles . forEach ( async ( f ) => { try { const res = await uploadFileFlow ( f ) ; dispatch ( { type : ActionTypes . UPDATE , payload : { file : f , status : 'done' , percent : 100 , key : res . key , } , } ) ; } catch { setUploadError ( formatMessage ( coreErrorMessages . fileUpload ) ) ; } } ) ; } ; const onRemoveFile = async ( fileToRemove ) => { if ( fileToRemove . key ) { try { await deleteAttachment ( { variables : { input : { id : application . id , key : fileToRemove . key , } , } , } ) ; } catch { setUploadError ( formatMessage ( coreErrorMessages . fileRemove ) ) ; return ; } } dispatch ( { type : ActionTypes . REMOVE , payload : { fileToRemove , } , } ) ; setUploadError ( undefined ) ; } ; return ( < Controller name = { id } defaultValue = { initialUploadFiles } render = { ( ) => ( < InputFileUpload fileList = { state } header = { header } description = { description } buttonLabel = { buttonLabel } onChange = { onFileChange } onRemove = { onRemoveFile } errorMessage = { uploadError || error } multiple = { multiple } accept = { accept } maxSize = { maxSize } / > ) } />); } ; export default FileUploadController ;