11"use client" ;
22
33import { useRef , useEffect } from "react" ;
4- import { overwriteFile , UrlString } from "@inrupt/solid-client" ;
54import toast from "react-hot-toast" ;
6- import { getAuthenticatedSession } from "../lib/helpers" ;
5+ import {
6+ getAuthenticatedSession ,
7+ uploadFilesToContainer ,
8+ uploadFolderFilesToContainer ,
9+ FolderUploadFile ,
10+ } from "../lib/helpers" ;
711
812interface FileUploadHandlerProps {
913 currentContainerUrl : string | null ;
1014 onUploadComplete ?: ( ) => void ;
1115 triggerUpload ?: number ;
16+ triggerFolderUpload ?: number ;
1217}
1318
1419export default function FileUploadHandler ( {
1520 currentContainerUrl,
1621 onUploadComplete,
1722 triggerUpload,
23+ triggerFolderUpload,
1824} : FileUploadHandlerProps ) {
1925 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
26+ const folderInputRef = useRef < HTMLInputElement > ( null ) ;
2027
2128 useEffect ( ( ) => {
2229 if ( triggerUpload && triggerUpload > 0 && fileInputRef . current ) {
2330 fileInputRef . current . click ( ) ;
2431 }
2532 } , [ triggerUpload ] ) ;
2633
34+ useEffect ( ( ) => {
35+ if ( triggerFolderUpload && triggerFolderUpload > 0 && folderInputRef . current ) {
36+ folderInputRef . current . click ( ) ;
37+ }
38+ } , [ triggerFolderUpload ] ) ;
39+
2740 const handleFileChange = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
2841 const files = e . target . files ;
2942 if ( ! files || files . length === 0 ) return ;
@@ -42,38 +55,73 @@ export default function FileUploadHandler({
4255 e . target . value = "" ;
4356 return ;
4457 }
45- const uploadPromises : Promise < void > [ ] = [ ] ;
46- const uploadedFiles : string [ ] = [ ] ;
47- const failedFiles : string [ ] = [ ] ;
48-
49- for ( let i = 0 ; i < files . length ; i ++ ) {
50- const file = files [ i ] ;
51- const sanitizedName = file . name . replace ( / [ < > : " / \\ | ? * ] / g, "" ) ;
52- const fileUrl = currentContainerUrl . endsWith ( "/" )
53- ? `${ currentContainerUrl } ${ sanitizedName } `
54- : `${ currentContainerUrl } /${ sanitizedName } ` ;
55-
56- const uploadPromise = overwriteFile (
57- fileUrl as UrlString ,
58- file ,
59- {
60- contentType : file . type || "application/octet-stream" ,
61- fetch : fetchFn ,
62- }
63- )
64- . then ( ( ) => {
65- uploadedFiles . push ( sanitizedName ) ;
66- } )
67- . catch ( ( error ) => {
68- console . error ( `Failed to upload ${ file . name } :` , error ) ;
69- failedFiles . push ( sanitizedName ) ;
70- } ) ;
71-
72- uploadPromises . push ( uploadPromise ) ;
58+ try {
59+ const { uploadedFiles, failedFiles } = await uploadFilesToContainer (
60+ Array . from ( files ) ,
61+ currentContainerUrl ,
62+ fetchFn
63+ ) ;
64+
65+ if ( uploadedFiles . length > 0 ) {
66+ const message =
67+ uploadedFiles . length === 1
68+ ? `File uploaded successfully`
69+ : `${ uploadedFiles . length } files uploaded successfully` ;
70+ toast . success ( message ) ;
71+ }
72+
73+ if ( failedFiles . length > 0 ) {
74+ const message =
75+ failedFiles . length === 1
76+ ? `Failed to upload "${ failedFiles [ 0 ] } "`
77+ : `Failed to upload ${ failedFiles . length } files` ;
78+ toast . error ( message ) ;
79+ }
80+
81+ if ( uploadedFiles . length > 0 && onUploadComplete ) {
82+ await new Promise ( ( resolve ) => setTimeout ( resolve , 200 ) ) ;
83+ onUploadComplete ( ) ;
84+ }
85+ } catch ( error ) {
86+ console . error ( "Upload error:" , error ) ;
87+ toast . error ( "Failed to upload files" ) ;
88+ } finally {
89+ e . target . value = "" ;
90+ }
91+ } ;
92+
93+ const handleFolderChange = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
94+ const files = e . target . files ;
95+ if ( ! files || files . length === 0 ) return ;
96+
97+ if ( ! currentContainerUrl ) {
98+ toast . error ( "Please select a storage first" ) ;
99+ e . target . value = "" ;
100+ return ;
73101 }
74102
103+ let fetchFn : typeof fetch ;
75104 try {
76- await Promise . all ( uploadPromises ) ;
105+ ( { fetch : fetchFn } = getAuthenticatedSession ( ) ) ;
106+ } catch ( error ) {
107+ toast . error ( "Not authenticated" ) ;
108+ e . target . value = "" ;
109+ return ;
110+ }
111+
112+ const folderFiles : FolderUploadFile [ ] = Array . from ( files )
113+ . map ( ( file ) => ( {
114+ file,
115+ relativePath : ( file as any ) . webkitRelativePath || file . name ,
116+ } ) )
117+ . filter ( ( item ) => item . relativePath && item . relativePath . length > 0 ) ;
118+
119+ try {
120+ const { uploadedFiles, failedFiles } = await uploadFolderFilesToContainer (
121+ folderFiles ,
122+ currentContainerUrl ,
123+ fetchFn
124+ ) ;
77125
78126 if ( uploadedFiles . length > 0 ) {
79127 const message =
@@ -97,20 +145,30 @@ export default function FileUploadHandler({
97145 }
98146 } catch ( error ) {
99147 console . error ( "Upload error:" , error ) ;
100- toast . error ( "Failed to upload files " ) ;
148+ toast . error ( "Failed to upload folder " ) ;
101149 } finally {
102150 e . target . value = "" ;
103151 }
104152 } ;
105153
106154 return (
107- < input
108- ref = { fileInputRef }
109- type = "file"
110- multiple
111- className = "hidden"
112- onChange = { handleFileChange }
113- />
155+ < >
156+ < input
157+ ref = { fileInputRef }
158+ type = "file"
159+ multiple
160+ className = "hidden"
161+ onChange = { handleFileChange }
162+ />
163+ < input
164+ ref = { folderInputRef }
165+ type = "file"
166+ { ...( { webkitdirectory : "" } as any ) }
167+ multiple
168+ className = "hidden"
169+ onChange = { handleFolderChange }
170+ />
171+ </ >
114172 ) ;
115173}
116174
0 commit comments