11'use client' ;
22import Image from 'next/image' ;
3- import React , { useCallback , useState } from 'react' ;
3+ import React , { useCallback , useEffect , useState } from 'react' ;
44import { useDropzone } from 'react-dropzone' ;
5+ import pastExamApi from '@/module/pastExamApi' ;
56
67interface FileWithPreview extends File {
78 preview : string ;
89}
9-
1010export const FileUploadField = ( ) => {
11- const [ files , setFiles ] = useState < FileWithPreview [ ] > ( [ ] ) ;
12-
11+ /**
12+ * @todo
13+ * 1. Use uploaderId from user global state
14+ * 2. Handle multiple file submit
15+ * 3. Handle filename (Base on file or explicit input)
16+ */
17+ const [ file , setFile ] = useState < FileWithPreview | null > ( null ) ;
18+ const [ uploaderId ] = useState ( 1 ) ;
19+ const [ fileName , setFileName ] = useState ( '' ) ;
20+ useEffect ( ( ) => {
21+ return ( ) => {
22+ if ( file ?. preview ) {
23+ URL . revokeObjectURL ( file . preview ) ;
24+ }
25+ } ;
26+ } , [ file ] ) ;
1327 const onDrop = useCallback ( ( acceptedFiles : File [ ] ) => {
14- const mappedFiles = acceptedFiles . map ( ( file ) =>
15- Object . assign ( file , {
16- preview : URL . createObjectURL ( file ) ,
17- } ) ,
18- ) ;
19- setFiles ( ( prevFiles ) => [ ...prevFiles , ...mappedFiles ] ) ;
28+ if ( acceptedFiles . length > 0 ) {
29+ const uploadedFile = acceptedFiles [ 0 ] ;
30+ const mappedFile = Object . assign ( uploadedFile , {
31+ preview : URL . createObjectURL ( uploadedFile ) ,
32+ } ) ;
33+ setFile ( mappedFile ) ;
34+ setFileName ( uploadedFile . name ) ;
35+ }
2036 } , [ ] ) ;
2137
2238 const { getRootProps, getInputProps } = useDropzone ( {
2339 onDrop,
24- multiple : true ,
40+ multiple : false ,
2541 } ) ;
2642
27- const removeFile = ( fileName : string ) : void => {
28- setFiles ( ( prevFiles ) => prevFiles . filter ( ( file ) => file . name !== fileName ) ) ;
43+ const removeFile = ( ) : void => {
44+ if ( file ?. preview ) {
45+ URL . revokeObjectURL ( file . preview ) ;
46+ }
47+ setFile ( null ) ;
48+ setFileName ( '' ) ;
2949 } ;
3050
3151 const formatFileSize = ( bytes : number ) : string => {
@@ -35,6 +55,30 @@ export const FileUploadField = () => {
3555 return `${ ( bytes / 1024 ) . toFixed ( 2 ) } KB` ;
3656 } ;
3757
58+ const handleSubmit = async ( ) => {
59+ if ( ! file ) {
60+ alert ( 'No file uploaded!' ) ;
61+ return ;
62+ }
63+
64+ const formData = new FormData ( ) ;
65+ formData . append ( 'upload_file' , file ) ;
66+ formData . append ( 'file_name' , fileName || file . name ) ;
67+ formData . append ( 'uploader_id' , uploaderId . toString ( ) ) ;
68+
69+ try {
70+ const response = await pastExamApi . uploadFile ( formData ) ;
71+
72+ if ( response ) {
73+ alert ( 'File uploaded successfully!' ) ;
74+ setFile ( null ) ;
75+ }
76+ } catch ( error ) {
77+ console . error ( 'Error uploading file:' , error ) ;
78+ alert ( 'Failed to upload file.' ) ;
79+ }
80+ } ;
81+
3882 return (
3983 < >
4084 < div
@@ -43,49 +87,53 @@ export const FileUploadField = () => {
4387 >
4488 < div className = "flex h-full w-full flex-col justify-center rounded-xl border-2 border-dotted p-5" >
4589 < input { ...getInputProps ( ) } />
46- < p > Drag & drop files here , or click to select files </ p >
90+ < p > Drag & drop a file here , or click to select a file </ p >
4791 < button
4892 type = "button"
4993 className = "mt-2 max-w-40 self-center rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
5094 >
51- Select Files
95+ Select File
5296 </ button >
5397 </ div >
5498 </ div >
55- { files . length !== 0 && (
99+ { file && (
56100 < div className = "mt-4" >
57- < h3 className = "font-semibold" > Selected Files</ h3 >
58- < ul className = "mt-2 space-y-4" >
59- { files . map ( ( file , index ) => (
60- < li
61- key = { index }
62- className = "flex items-center justify-between rounded-xl border border-gray-300 p-2"
63- >
64- < div className = "flex items-center space-x-4" >
65- < div className = "relative h-12 w-12 overflow-hidden rounded sm:h-16 sm:w-16 md:h-20 md:w-20 lg:h-24 lg:w-24" >
66- < Image
67- src = { file . preview }
68- alt = { file . name }
69- fill
70- className = "object-cover"
71- />
72- </ div >
73- < div >
74- < p className = "font-medium" > { file . name } </ p >
75- < p className = "text-sm text-slate-50" >
76- { `${ file . name . split ( '.' ) [ 1 ] } - ${ formatFileSize ( file . size ) } ` }
77- </ p >
78- </ div >
79- </ div >
80- < button
81- onClick = { ( ) => removeFile ( file . name ) }
82- className = "rounded-lg bg-red-500 px-2 py-1 text-white hover:bg-red-600"
83- >
84- Remove
85- </ button >
86- </ li >
87- ) ) }
88- </ ul >
101+ < h3 className = "font-semibold" > Selected File</ h3 >
102+ < div className = "mt-2 flex items-center justify-between rounded-xl border border-gray-300 p-2" >
103+ < div className = "flex items-center space-x-4" >
104+ < div className = "relative h-12 w-12 overflow-hidden rounded sm:h-16 sm:w-16 md:h-20 md:w-20 lg:h-24 lg:w-24" >
105+ < Image
106+ src = { file . preview }
107+ alt = { file . name }
108+ fill
109+ className = "object-cover"
110+ />
111+ </ div >
112+ < div >
113+ < p className = "font-medium" > { file . name } </ p >
114+ < p className = "text-sm text-slate-50" >
115+ { formatFileSize ( file . size ) }
116+ </ p >
117+ </ div >
118+ </ div >
119+ < button
120+ onClick = { removeFile }
121+ className = "rounded-lg bg-red-500 px-2 py-1 text-white hover:bg-red-600"
122+ >
123+ Remove
124+ </ button >
125+ </ div >
126+ </ div >
127+ ) }
128+ { file && (
129+ < div className = "mt-4 flex justify-end" >
130+ < button
131+ type = "button"
132+ onClick = { handleSubmit }
133+ className = "rounded-lg bg-green-500 px-4 py-2 text-white hover:bg-green-600"
134+ >
135+ Upload
136+ </ button >
89137 </ div >
90138 ) }
91139 </ >
0 commit comments