@@ -56,6 +56,12 @@ export function useProvidersPresenter() {
5656
5757 const [ fileToDelete , setFileToDelete ] = useState < string | null > ( null ) ;
5858 const [ files , setFiles ] = useState < AuthFile [ ] > ( [ ] ) ;
59+ const filesRef = useRef < AuthFile [ ] > ( files ) ;
60+
61+ useEffect ( ( ) => {
62+ filesRef . current = files ;
63+ } , [ files ] ) ;
64+
5965 const [ loadingFiles , setLoadingFiles ] = useState ( false ) ;
6066 const [ filesError , setFilesError ] = useState < string | null > ( null ) ;
6167
@@ -444,6 +450,113 @@ export function useProvidersPresenter() {
444450 }
445451 } , [ t ] ) ;
446452
453+ const downloadAuthFile = useCallback ( async ( filename : string | undefined | null ) => {
454+ try {
455+ if ( ! filename ) throw new Error ( 'Filename is missing' ) ;
456+ let name = filename ;
457+ if ( ! name . toLowerCase ( ) . endsWith ( '.json' ) ) name = `${ name } .json` ;
458+
459+ const { save } = await import ( '@tauri-apps/plugin-dialog' ) ;
460+ const { writeTextFile } = await import ( '@tauri-apps/plugin-fs' ) ;
461+
462+ const data = await authFilesApi . download ( name ) ;
463+
464+ const filePath = await save ( {
465+ defaultPath : name ,
466+ filters : [ { name : 'JSON' , extensions : [ 'json' ] } ] ,
467+ } ) ;
468+
469+ if ( filePath ) {
470+ await writeTextFile ( filePath , JSON . stringify ( data , null , 2 ) ) ;
471+ toast . success ( t ( 'providers.downloadSuccess' , 'File downloaded successfully' ) ) ;
472+ }
473+ } catch ( err ) {
474+ toast . error ( t ( 'providers.downloadFailed' , 'Failed to download file' ) + `: ${ ( err as Error ) . message } ` ) ;
475+ }
476+ } , [ t ] ) ;
477+
478+ const downloadAllAuthFiles = useCallback ( async ( ) => {
479+ try {
480+ const { open } = await import ( '@tauri-apps/plugin-dialog' ) ;
481+ const { writeTextFile } = await import ( '@tauri-apps/plugin-fs' ) ;
482+ const { join } = await import ( '@tauri-apps/api/path' ) ;
483+
484+ const dirPath = await open ( {
485+ directory : true ,
486+ multiple : false ,
487+ title : 'Select Destination Folder'
488+ } ) ;
489+
490+ if ( ! dirPath || typeof dirPath !== 'string' ) return ;
491+
492+ let successCount = 0 ;
493+ const currentFiles = filesRef . current ;
494+
495+ console . log ( 'Downloading all files. Count:' , currentFiles . length ) ;
496+
497+ for ( const file of currentFiles ) {
498+ try {
499+ const name = file . name || file . filename || file . id ;
500+ if ( ! name ) continue ;
501+
502+ let fileName = name ;
503+ if ( ! fileName . toLowerCase ( ) . endsWith ( '.json' ) ) fileName = `${ fileName } .json` ;
504+
505+ const data = await authFilesApi . download ( fileName ) ;
506+ const fullPath = await join ( dirPath , fileName ) ;
507+ await writeTextFile ( fullPath , JSON . stringify ( data , null , 2 ) ) ;
508+ successCount ++ ;
509+ } catch ( e ) {
510+ console . error ( 'Failed to download file:' , file . id , e ) ;
511+ }
512+ }
513+
514+ toast . success ( t ( 'providers.downloadSuccess' , 'Downloaded {{count}} files successfully' , { count : successCount } ) ) ;
515+ } catch ( err ) {
516+ toast . error ( t ( 'providers.downloadFailed' , 'Failed to download files' ) + `: ${ ( err as Error ) . message } ` ) ;
517+ }
518+ } , [ t ] ) ;
519+
520+ const uploadAuthFile = useCallback ( async ( ) => {
521+ try {
522+ const { open } = await import ( '@tauri-apps/plugin-dialog' ) ;
523+ const { readTextFile } = await import ( '@tauri-apps/plugin-fs' ) ;
524+ const { basename } = await import ( '@tauri-apps/api/path' ) ;
525+
526+ const filePaths = await open ( {
527+ multiple : true ,
528+ filters : [ { name : 'JSON' , extensions : [ 'json' ] } ] ,
529+ } ) ;
530+
531+ if ( ! filePaths || filePaths . length === 0 ) return ;
532+ const paths = Array . isArray ( filePaths ) ? filePaths : [ filePaths ] ;
533+
534+ let uploads = 0 ;
535+ for ( const path of paths ) {
536+ try {
537+ const content = await readTextFile ( path ) ;
538+ const name = await basename ( path ) ;
539+
540+ const blob = new Blob ( [ content ] , { type : 'application/json' } ) ;
541+ const formData = new FormData ( ) ;
542+ formData . append ( 'file' , blob , name ) ;
543+
544+ await authFilesApi . upload ( formData ) ;
545+ uploads ++ ;
546+ } catch ( e ) {
547+ console . error ( 'Failed to upload file:' , path , e ) ;
548+ }
549+ }
550+
551+ if ( uploads > 0 ) {
552+ toast . success ( t ( 'providers.uploadSuccess' , 'Files uploaded successfully' ) ) ;
553+ loadFiles ( ) ;
554+ }
555+ } catch ( err ) {
556+ toast . error ( t ( 'providers.uploadFailed' , 'Failed to upload files' ) + `: ${ ( err as Error ) . message } ` ) ;
557+ }
558+ } , [ t , loadFiles ] ) ;
559+
447560 const togglePrivacyMode = useCallback ( ( ) => {
448561 setIsPrivacyMode ( prev => ! prev ) ;
449562 } , [ ] ) ;
@@ -493,6 +606,9 @@ export function useProvidersPresenter() {
493606 updateProviderState,
494607 copyToClipboard,
495608 copyRefreshToken,
609+ downloadAuthFile,
610+ downloadAllAuthFiles,
611+ uploadAuthFile,
496612
497613 // Privacy
498614 isPrivacyMode,
0 commit comments