@@ -118,6 +118,12 @@ function App() {
118118 // --- NEW State for Reload Confirmation Modal ---
119119 const [ showReloadModal , setShowReloadModal ] = useState ( false ) ;
120120
121+ // --- NEW State for Invalid Preset Error Modal ---
122+ const [ invalidPresetError , setInvalidPresetError ] = useState ( { show : false , presetName : '' , invalidPaths : [ ] } ) ;
123+
124+ // --- NEW State for Delete Preset Confirmation Modal ---
125+ const [ deletePresetConfirm , setDeletePresetConfirm ] = useState ( { show : false , presetName : '' } ) ;
126+
121127 // --- NEW State for Move Complete Modal ---
122128 const [ showMoveCompleteModal , setShowMoveCompleteModal ] = useState ( false ) ;
123129
@@ -385,12 +391,16 @@ function App() {
385391 const result = await response . json ( ) ;
386392 if ( ! response . ok ) {
387393 // Refined: Check for 'detail' from FastAPI's HTTPException first.
388- throw new Error ( result . detail || result . message || `API call to ${ endpoint } failed: ${ response . statusText } ` ) ;
394+ // Create an error with additional info for callers to distinguish error types
395+ const error = new Error ( result . detail || result . message || `API call to ${ endpoint } failed: ${ response . statusText } ` ) ;
396+ error . status = response . status ;
397+ error . isHttpError = true ;
398+ throw error ;
389399 }
390400 return result ;
391401 } catch ( error ) {
392- // This error is typically a network failure (e.g., "Failed to fetch" )
393- if ( isBackendConnected ) {
402+ // Only mark backend as disconnected for actual network failures, not HTTP errors (4xx, 5xx )
403+ if ( ! error . isHttpError && isBackendConnected ) {
394404 setIsBackendConnected ( false ) ;
395405 logToConsole ( `Backend connection lost. Will try to reconnect automatically.` , 'error' ) ;
396406 }
@@ -404,6 +414,20 @@ function App() {
404414 setLogs ( prevLogs => [ ...prevLogs , { message, type, time : new Date ( ) . toLocaleTimeString ( ) } ] ) ;
405415 } ;
406416
417+ // Helper function to validate if a path exists on the filesystem
418+ const validatePath = async ( path ) => {
419+ if ( ! path ) return false ;
420+ try {
421+ await apiCall ( '/api/validate-path' , {
422+ method : 'POST' ,
423+ body : JSON . stringify ( { path } ) ,
424+ } ) ;
425+ return true ;
426+ } catch ( error ) {
427+ return false ;
428+ }
429+ } ;
430+
407431 // --- UI Handlers & Backend Integrations ---
408432
409433 const runStartupChecks = async ( ) => {
@@ -637,8 +661,23 @@ function App() {
637661 try {
638662 const config = await apiCall ( '/api/config/load' ) ;
639663 if ( Object . keys ( config ) . length > 0 ) {
640- if ( config . source_folder ) setSourceFolder ( config . source_folder ) ;
641- if ( config . destination_folder ) setDestinationFolder ( config . destination_folder ) ;
664+ // Validate folder paths before setting them to prevent crashes
665+ if ( config . source_folder ) {
666+ const sourceValid = await validatePath ( config . source_folder ) ;
667+ if ( sourceValid ) {
668+ setSourceFolder ( config . source_folder ) ;
669+ } else {
670+ logToConsole ( `Previous source folder no longer exists: ${ config . source_folder } ` , "warning" ) ;
671+ }
672+ }
673+ if ( config . destination_folder ) {
674+ const destValid = await validatePath ( config . destination_folder ) ;
675+ if ( destValid ) {
676+ setDestinationFolder ( config . destination_folder ) ;
677+ } else {
678+ logToConsole ( `Previous destination folder no longer exists: ${ config . destination_folder } ` , "warning" ) ;
679+ }
680+ }
642681 if ( config . sort_method ) setSortMethod ( config . sort_method ) ;
643682 if ( config . face_mode ) setFaceMode ( config . face_mode ) ;
644683 if ( config . file_operation_mode ) setFileOperationMode ( config . file_operation_mode ) ; // <-- ADD THIS
@@ -701,13 +740,85 @@ function App() {
701740 }
702741 } ;
703742
743+ const handleDeletePreset = async ( presetName ) => {
744+ try {
745+ await apiCall ( `/api/presets/paths/${ encodeURIComponent ( presetName ) } ` , {
746+ method : 'DELETE' ,
747+ } ) ;
748+ logToConsole ( `Preset '${ presetName } ' deleted successfully.` , 'success' ) ;
749+ // Clear selection if the deleted preset was selected
750+ if ( selectedPreset === presetName ) {
751+ setSelectedPreset ( '' ) ;
752+ }
753+ await loadPresets ( ) ;
754+ } catch ( error ) {
755+ logToConsole ( `Failed to delete preset '${ presetName } ': ${ error . message } ` , 'error' ) ;
756+ }
757+ } ;
758+
759+ // Track if we're currently validating a preset to prevent multiple simultaneous validations
760+ const [ isValidatingPreset , setIsValidatingPreset ] = useState ( false ) ;
761+
704762 const handlePresetChange = ( e ) => {
705763 const presetName = e . target . value ;
706- setSelectedPreset ( presetName ) ;
707- if ( presets [ presetName ] ) {
708- setSourceFolder ( presets [ presetName ] . source ) ;
709- setDestinationFolder ( presets [ presetName ] . destination ) ;
764+
765+ if ( ! presets [ presetName ] ) {
766+ setSelectedPreset ( presetName ) ;
767+ return ;
710768 }
769+
770+ // Prevent selecting while validation is in progress
771+ if ( isValidatingPreset ) {
772+ return ;
773+ }
774+
775+ const sourcePath = presets [ presetName ] . source ;
776+ const destPath = presets [ presetName ] . destination ;
777+
778+ // Set validating state to show feedback
779+ setIsValidatingPreset ( true ) ;
780+
781+ // Wrap async validation in an IIFE to properly handle the promise
782+ ( async ( ) => {
783+ try {
784+ // Validate both paths exist before setting them
785+ const [ sourceValid , destValid ] = await Promise . all ( [
786+ validatePath ( sourcePath ) ,
787+ validatePath ( destPath )
788+ ] ) ;
789+
790+ const invalidPaths = [ ] ;
791+ if ( ! sourceValid ) invalidPaths . push ( `Source: ${ sourcePath } ` ) ;
792+ if ( ! destValid ) invalidPaths . push ( `Destination: ${ destPath } ` ) ;
793+
794+ if ( invalidPaths . length > 0 ) {
795+ // Show error modal using state (more reliable than message() from IIFE)
796+ setInvalidPresetError ( {
797+ show : true ,
798+ presetName : presetName ,
799+ invalidPaths : invalidPaths
800+ } ) ;
801+ setIsValidatingPreset ( false ) ;
802+ return ;
803+ }
804+
805+ // Only update state if validation passes
806+ setSelectedPreset ( presetName ) ;
807+ setSourceFolder ( sourcePath ) ;
808+ setDestinationFolder ( destPath ) ;
809+ logToConsole ( `Loaded preset '${ presetName } ' successfully.` , 'success' ) ;
810+ } catch ( err ) {
811+ console . error ( 'Error validating preset paths:' , err ) ;
812+ // Show error modal for backend connection issues
813+ setInvalidPresetError ( {
814+ show : true ,
815+ presetName : presetName ,
816+ invalidPaths : [ `Backend error: ${ err . message } ` ]
817+ } ) ;
818+ } finally {
819+ setIsValidatingPreset ( false ) ;
820+ }
821+ } ) ( ) ;
711822 } ;
712823
713824 const resetUi = ( ) => {
@@ -1108,6 +1219,50 @@ function App() {
11081219 </ p >
11091220 </ ConfirmationModal >
11101221
1222+ { /* Invalid Preset Error Modal */ }
1223+ < ConfirmationModal
1224+ isVisible = { invalidPresetError . show }
1225+ title = "Invalid Preset Paths"
1226+ onCancel = { ( ) => setInvalidPresetError ( { show : false , presetName : '' , invalidPaths : [ ] } ) }
1227+ onConfirm = { async ( ) => {
1228+ await handleDeletePreset ( invalidPresetError . presetName ) ;
1229+ setInvalidPresetError ( { show : false , presetName : '' , invalidPaths : [ ] } ) ;
1230+ } }
1231+ confirmText = "Delete Preset"
1232+ cancelText = "Keep Preset"
1233+ >
1234+ < p >
1235+ < strong > The preset '{ invalidPresetError . presetName } ' contains folder paths that no longer exist:</ strong >
1236+ < br /> < br />
1237+ < span style = { { fontFamily : 'monospace' , fontSize : '0.9em' , wordBreak : 'break-all' } } >
1238+ { invalidPresetError . invalidPaths . map ( ( path , idx ) => (
1239+ < span key = { idx } > { path } < br /> </ span >
1240+ ) ) }
1241+ </ span >
1242+ < br />
1243+ Would you like to delete this preset or keep it for later?
1244+ </ p >
1245+ </ ConfirmationModal >
1246+
1247+ { /* Delete Preset Confirmation Modal */ }
1248+ < ConfirmationModal
1249+ isVisible = { deletePresetConfirm . show }
1250+ title = "Delete Preset"
1251+ onCancel = { ( ) => setDeletePresetConfirm ( { show : false , presetName : '' } ) }
1252+ onConfirm = { async ( ) => {
1253+ await handleDeletePreset ( deletePresetConfirm . presetName ) ;
1254+ setDeletePresetConfirm ( { show : false , presetName : '' } ) ;
1255+ } }
1256+ confirmText = "Delete"
1257+ cancelText = "Cancel"
1258+ >
1259+ < p >
1260+ Are you sure you want to delete the preset < strong > '{ deletePresetConfirm . presetName } '</ strong > ?
1261+ < br /> < br />
1262+ This action cannot be undone.
1263+ </ p >
1264+ </ ConfirmationModal >
1265+
11111266 < main className = "app-main" >
11121267 < div className = "setup-grid" >
11131268 < div className = "setup-column" >
@@ -1126,6 +1281,7 @@ function App() {
11261281 selectedPreset = { selectedPreset }
11271282 handleSelectPreset = { handlePresetChange }
11281283 handleSavePreset = { handleSavePreset }
1284+ onRequestDelete = { ( presetName ) => setDeletePresetConfirm ( { show : true , presetName } ) }
11291285 showSaveButton = { showSaveButton }
11301286 />
11311287 { /* --- ADD THIS COMPONENT --- */ }
0 commit comments