A React Native app for the ultimate thinking partner.

feat(archival): add loading state to create/edit passage modal

- Add isSavingPassage state to track save operation
- Show spinner in Create/Save button during save
- Disable all modal buttons (Cancel, Close, Create/Save) while saving
- Add opacity to disabled buttons for visual feedback
- Wrap save operation in try-catch-finally for error handling
- Prevent double-clicks during save operation

+38 -18
+38 -18
App.tsx
··· 146 146 const [selectedPassage, setSelectedPassage] = useState<Passage | null>(null); 147 147 const [isCreatingPassage, setIsCreatingPassage] = useState(false); 148 148 const [isEditingPassage, setIsEditingPassage] = useState(false); 149 + const [isSavingPassage, setIsSavingPassage] = useState(false); 149 150 const [passageAfterCursor, setPassageAfterCursor] = useState<string | undefined>(undefined); 150 151 const [hasMorePassages, setHasMorePassages] = useState(false); 151 152 const [isUploadingFile, setIsUploadingFile] = useState(false); ··· 2500 2501 <Text style={[styles.modalTitle, { color: theme.colors.text.primary }]}> 2501 2502 {isCreatingPassage ? 'Create Passage' : 'Edit Passage'} 2502 2503 </Text> 2503 - <TouchableOpacity onPress={() => { 2504 - setIsCreatingPassage(false); 2505 - setIsEditingPassage(false); 2506 - setSelectedPassage(null); 2507 - }}> 2508 - <Ionicons name="close" size={24} color={theme.colors.text.primary} /> 2504 + <TouchableOpacity 2505 + onPress={() => { 2506 + if (isSavingPassage) return; 2507 + setIsCreatingPassage(false); 2508 + setIsEditingPassage(false); 2509 + setSelectedPassage(null); 2510 + }} 2511 + disabled={isSavingPassage} 2512 + > 2513 + <Ionicons name="close" size={24} color={theme.colors.text.primary} style={{ opacity: isSavingPassage ? 0.5 : 1 }} /> 2509 2514 </TouchableOpacity> 2510 2515 </View> 2511 2516 <View style={styles.modalBody}> ··· 2543 2548 </View> 2544 2549 <View style={styles.modalFooter}> 2545 2550 <TouchableOpacity 2546 - style={[styles.modalButton, styles.modalButtonSecondary, { borderColor: theme.colors.border.primary }]} 2551 + style={[styles.modalButton, styles.modalButtonSecondary, { borderColor: theme.colors.border.primary, opacity: isSavingPassage ? 0.5 : 1 }]} 2547 2552 onPress={() => { 2553 + if (isSavingPassage) return; 2548 2554 setIsCreatingPassage(false); 2549 2555 setIsEditingPassage(false); 2550 2556 setSelectedPassage(null); 2551 2557 }} 2558 + disabled={isSavingPassage} 2552 2559 > 2553 2560 <Text style={[styles.modalButtonText, { color: theme.colors.text.primary }]}>Cancel</Text> 2554 2561 </TouchableOpacity> 2555 2562 <TouchableOpacity 2556 - style={[styles.modalButton, styles.modalButtonPrimary, { backgroundColor: theme.colors.text.primary }]} 2563 + style={[styles.modalButton, styles.modalButtonPrimary, { backgroundColor: theme.colors.text.primary, opacity: isSavingPassage ? 0.7 : 1 }]} 2557 2564 onPress={async () => { 2565 + if (isSavingPassage) return; 2558 2566 if (!selectedPassage?.text) { 2559 2567 Alert.alert('Error', 'Please enter passage text'); 2560 2568 return; 2561 2569 } 2562 - if (isEditingPassage && selectedPassage.id) { 2563 - await modifyPassage(selectedPassage.id, selectedPassage.text, selectedPassage.tags); 2564 - } else { 2565 - await createPassage(selectedPassage.text, selectedPassage.tags); 2570 + setIsSavingPassage(true); 2571 + try { 2572 + if (isEditingPassage && selectedPassage.id) { 2573 + await modifyPassage(selectedPassage.id, selectedPassage.text, selectedPassage.tags); 2574 + } else { 2575 + await createPassage(selectedPassage.text, selectedPassage.tags); 2576 + } 2577 + setIsCreatingPassage(false); 2578 + setIsEditingPassage(false); 2579 + setSelectedPassage(null); 2580 + } catch (error) { 2581 + console.error('Error saving passage:', error); 2582 + } finally { 2583 + setIsSavingPassage(false); 2566 2584 } 2567 - setIsCreatingPassage(false); 2568 - setIsEditingPassage(false); 2569 - setSelectedPassage(null); 2570 2585 }} 2586 + disabled={isSavingPassage} 2571 2587 > 2572 - <Text style={[styles.modalButtonText, { color: theme.colors.background.primary }]}> 2573 - {isCreatingPassage ? 'Create' : 'Save'} 2574 - </Text> 2588 + {isSavingPassage ? ( 2589 + <ActivityIndicator size="small" color={theme.colors.background.primary} /> 2590 + ) : ( 2591 + <Text style={[styles.modalButtonText, { color: theme.colors.background.primary }]}> 2592 + {isCreatingPassage ? 'Create' : 'Save'} 2593 + </Text> 2594 + )} 2575 2595 </TouchableOpacity> 2576 2596 </View> 2577 2597 </View>