From e4733f493d8c2bf30655430adf762f05007de456 Mon Sep 17 00:00:00 2001 From: Najuna Brian Date: Thu, 18 Dec 2025 05:57:16 +0300 Subject: [PATCH 1/3] simplify SettingsScreen UI --- formulus/src/navigation/MainAppNavigator.tsx | 2 +- formulus/src/screens/SettingsScreen.tsx | 420 +++++++------------ 2 files changed, 148 insertions(+), 274 deletions(-) diff --git a/formulus/src/navigation/MainAppNavigator.tsx b/formulus/src/navigation/MainAppNavigator.tsx index 527cbe5a5..f5994d07d 100644 --- a/formulus/src/navigation/MainAppNavigator.tsx +++ b/formulus/src/navigation/MainAppNavigator.tsx @@ -54,7 +54,7 @@ const MainAppNavigator: React.FC = () => { { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [isLoading, setIsLoading] = useState(true); - const [isSaving, setIsSaving] = useState(false); - const [isTesting, setIsTesting] = useState(false); const [isLoggingIn, setIsLoggingIn] = useState(false); const [showQRScanner, setShowQRScanner] = useState(false); - const [loggedInUser, setLoggedInUser] = useState(null); + const [_loggedInUser, setLoggedInUser] = useState(null); useEffect(() => { loadSettings(); }, []); + const saveServerUrl = useCallback(async (url: string) => { + if (url.trim()) { + await serverConfigService.saveServerUrl(url); + } + }, []); + + useEffect(() => { + const timer = setTimeout(() => { + if (serverUrl) { + saveServerUrl(serverUrl); + } + }, 500); + return () => clearTimeout(timer); + }, [serverUrl, saveServerUrl]); + const loadSettings = async () => { try { const savedUrl = await serverConfigService.getServerUrl(); @@ -54,7 +68,6 @@ const SettingsScreen = () => { setPassword(credentials.password); } - // Check if user is logged in const userInfo = await getUserInfo(); setLoggedInUser(userInfo); } catch (error) { @@ -64,94 +77,24 @@ const SettingsScreen = () => { } }; - const handleTestConnection = async () => { - if (!serverUrl.trim()) { - Alert.alert('Error', 'Please enter a server URL'); - return; - } - - setIsTesting(true); - try { - const result = await serverConfigService.testConnection(serverUrl); - Alert.alert(result.success ? 'Success' : 'Error', result.message); - } finally { - setIsTesting(false); - } - }; - - const handleSave = async () => { - if ((username && !password) || (!username && password)) { - Alert.alert('Error', 'Both username and password are required'); - return; - } - - setIsSaving(true); - try { - await serverConfigService.saveServerUrl(serverUrl); - - if (username && password) { - await Keychain.setGenericPassword(username, password); - } else { - await Keychain.resetGenericPassword(); - } - - Alert.alert('Success', 'Settings saved'); - } catch (error) { - console.error('Failed to save settings:', error); - Alert.alert('Error', 'Failed to save settings'); - } finally { - setIsSaving(false); - } - }; - const handleLogin = async () => { - if (!serverUrl.trim()) { - Alert.alert('Error', 'Server URL is required'); + if (!serverUrl.trim() || !username.trim() || !password.trim()) { return; } - if (!username || !password) { - Alert.alert('Error', 'Username and password are required'); - return; - } - setIsLoggingIn(true); try { - // Save settings before login await serverConfigService.saveServerUrl(serverUrl); await Keychain.setGenericPassword(username, password); - const userInfo = await login(username, password); setLoggedInUser(userInfo); - Alert.alert( - 'Success', - `Logged in as ${userInfo.username} (${userInfo.role})\nYou can now sync your app and data.`, - [{text: 'OK', onPress: () => navigation.navigate('MainApp')}], - ); + navigation.navigate('MainApp'); } catch (error: any) { console.error('Login failed:', error); - const message = - error?.response?.data?.message || error?.message || 'Login failed'; - Alert.alert('Login Failed', message); } finally { setIsLoggingIn(false); } }; - const handleLogout = async () => { - Alert.alert('Logout', 'Are you sure you want to logout?', [ - {text: 'Cancel', style: 'cancel'}, - { - text: 'Logout', - style: 'destructive', - onPress: async () => { - await logout(); - setLoggedInUser(null); - Alert.alert('Success', 'Logged out successfully'); - }, - }, - ]); - }; - const handleQRResult = async (result: any) => { setShowQRScanner(false); @@ -161,82 +104,60 @@ const SettingsScreen = () => { result.data.value, ); setServerUrl(settings.serverUrl); - setUsername(settings.username); - setPassword(settings.password); - // Auto-login after QR scan - try { - const userInfo = await login(settings.username, settings.password); - setLoggedInUser(userInfo); - Alert.alert( - 'Success', - 'Settings updated and logged in successfully', - [{text: 'OK', onPress: () => navigation.navigate('MainApp')}], - ); - } catch (error: any) { - Alert.alert( - 'Settings Updated', - 'QR code processed. Login failed - please check credentials.', + if (settings.username && settings.password) { + await Keychain.setGenericPassword( + settings.username, + settings.password, ); + try { + const userInfo = await login(settings.username, settings.password); + setLoggedInUser(userInfo); + navigation.navigate('MainApp'); + } catch (error: any) { + console.error('Auto-login failed:', error); + } } } catch (error) { - Alert.alert('Error', 'Failed to process QR code'); + console.error('Failed to process QR code:', error); } - } else if (result.status !== 'cancelled') { - Alert.alert('Error', result.message || 'Failed to scan QR code'); - } - }; - - const getRoleBadgeStyle = (role: string) => { - switch (role) { - case 'admin': - return styles.roleBadgeAdmin; - case 'read-write': - return styles.roleBadgeReadWrite; - default: - return styles.roleBadgeReadOnly; } }; if (isLoading) { return ( - + ); } return ( - - - {/* Login Status Card */} - {loggedInUser && ( - - - Logged In - - {loggedInUser.role} - - - {loggedInUser.username} - - Logout - - - )} + + + + + ODE + + v1.0.0 + + + + + Please enter the server you want to connect to. + - - Synkronus Server URL + { autoCorrect={false} /> - - {isTesting ? 'Testing...' : 'Test Connection'} - + style={styles.qrButton} + onPress={() => setShowQRScanner(true)}> + - - - - Username + { /> - - + setShowQRScanner(true)}> - 📱 Scan QR Code - - - - - {isSaving ? 'Saving...' : 'Save Settings'} - - - - - + disabled={ + !serverUrl.trim() || + !username.trim() || + !password.trim() || + isLoggingIn + }> + + {isLoggingIn ? 'Logging in...' : 'Login'} @@ -318,137 +232,97 @@ const SettingsScreen = () => { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#f5f5f5', + backgroundColor: colors.brand.primary[500], }, centered: { justifyContent: 'center', alignItems: 'center', }, - content: { - padding: 20, - }, - statusCard: { - backgroundColor: '#fff', - borderRadius: 12, - padding: 16, - marginBottom: 20, - borderLeftWidth: 4, - borderLeftColor: '#34C759', + header: { + alignItems: 'center', + paddingHorizontal: 20, + paddingVertical: 16, }, - statusHeader: { + logoContainer: { flexDirection: 'row', - justifyContent: 'space-between', alignItems: 'center', - marginBottom: 8, - }, - statusTitle: { - fontSize: 14, - color: '#34C759', - fontWeight: '600', + justifyContent: 'center', }, - statusUsername: { - fontSize: 18, - fontWeight: '600', - color: '#333', - marginBottom: 12, + logo: { + width: 40, + height: 40, + marginRight: 12, }, - roleBadge: { - paddingHorizontal: 10, - paddingVertical: 4, - borderRadius: 12, + brandName: { + fontSize: 32, + fontWeight: '700', + color: colors.neutral.white, + letterSpacing: 1, }, - roleBadgeAdmin: { - backgroundColor: '#FF3B30', + version: { + fontSize: 12, + color: colors.brand.primary[200], + marginTop: 4, }, - roleBadgeReadWrite: { - backgroundColor: '#007AFF', + card: { + flex: 1, + backgroundColor: colors.neutral.white, + borderTopLeftRadius: 8, + borderTopRightRadius: 8, }, - roleBadgeReadOnly: { - backgroundColor: '#8E8E93', + cardContent: { + paddingHorizontal: 24, + paddingTop: 32, + paddingBottom: 40, }, - roleBadgeText: { - color: '#fff', - fontSize: 12, + title: { + fontSize: 20, fontWeight: '600', + color: colors.neutral[900], + marginBottom: 24, }, - logoutButton: { - alignSelf: 'flex-start', - }, - logoutButtonText: { - color: '#FF3B30', - fontSize: 14, - fontWeight: '500', - }, - section: { + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.neutral[50], + borderBottomWidth: 2, + borderBottomColor: colors.brand.primary[500], marginBottom: 20, }, - label: { - fontSize: 16, - fontWeight: '500', - marginBottom: 8, - color: '#333', - }, input: { - height: 50, - borderWidth: 1, - borderColor: '#ddd', - borderRadius: 8, - paddingHorizontal: 16, + flex: 1, + height: 56, + paddingHorizontal: 12, fontSize: 16, - backgroundColor: '#fff', - color: '#000', - }, - divider: { - height: 1, - backgroundColor: '#ddd', - marginVertical: 16, - }, - button: { - height: 50, - borderRadius: 8, - backgroundColor: '#666', - justifyContent: 'center', - alignItems: 'center', - marginTop: 12, + color: colors.neutral[900], + backgroundColor: 'transparent', }, - primaryButton: { - height: 50, - borderRadius: 8, - backgroundColor: '#007AFF', + qrButton: { + width: 56, + height: 56, justifyContent: 'center', alignItems: 'center', - marginTop: 12, }, - secondaryButton: { - height: 40, + nextButton: { + flexDirection: 'row', + height: 56, borderRadius: 8, - backgroundColor: 'transparent', - borderWidth: 1, - borderColor: '#007AFF', + backgroundColor: colors.neutral[200], justifyContent: 'center', alignItems: 'center', - marginTop: 8, + gap: 8, }, - secondaryButtonText: { - color: '#007AFF', - fontSize: 14, - fontWeight: '500', + nextButtonDisabled: { + backgroundColor: colors.neutral[200], }, - qrButton: { - height: 50, - borderRadius: 8, - backgroundColor: '#34C759', - justifyContent: 'center', - alignItems: 'center', - marginTop: 12, + nextButtonIcon: { + fontSize: 20, + color: colors.neutral[500], }, - buttonText: { - color: '#fff', + nextButtonText: { fontSize: 16, fontWeight: '600', - }, - disabled: { - opacity: 0.6, + color: colors.neutral[500], }, }); From 34d81ed6ca33a88ba44f2247eccfcf2731e2f014 Mon Sep 17 00:00:00 2001 From: Najuna Brian Date: Sun, 21 Dec 2025 02:09:44 +0300 Subject: [PATCH 2/3] Add success and error messages for QR code scan login in settings screen --- formulus/src/screens/SettingsScreen.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/formulus/src/screens/SettingsScreen.tsx b/formulus/src/screens/SettingsScreen.tsx index aa10bbd43..f062ebf64 100644 --- a/formulus/src/screens/SettingsScreen.tsx +++ b/formulus/src/screens/SettingsScreen.tsx @@ -20,6 +20,7 @@ import {QRSettingsService} from '../services/QRSettingsService'; import {MainAppStackParamList} from '../types/NavigationTypes'; import {colors} from '../theme/colors'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import {ToastService} from '../services/ToastService'; type SettingsScreenNavigationProp = StackNavigationProp< MainAppStackParamList, @@ -98,12 +99,18 @@ const SettingsScreen = () => { const handleQRResult = async (result: any) => { setShowQRScanner(false); + if (result.status === 'cancelled') { + return; + } + if (result.status === 'success' && result.data?.value) { try { const settings = await QRSettingsService.processQRCode( result.data.value, ); setServerUrl(settings.serverUrl); + setUsername(settings.username); + setPassword(settings.password); if (settings.username && settings.password) { await Keychain.setGenericPassword( @@ -113,14 +120,26 @@ const SettingsScreen = () => { try { const userInfo = await login(settings.username, settings.password); setLoggedInUser(userInfo); + ToastService.showShort('Successfully logged in!'); navigation.navigate('MainApp'); } catch (error: any) { console.error('Auto-login failed:', error); + const errorMessage = + error?.message || + 'Failed to login. Please check your credentials.'; + ToastService.showLong(`Login failed: ${errorMessage}`); } + } else { + ToastService.showShort('Settings updated successfully'); } - } catch (error) { + } catch (error: any) { console.error('Failed to process QR code:', error); + const errorMessage = + error?.message || 'Invalid QR code format. Please try again.'; + ToastService.showLong(`QR code error: ${errorMessage}`); } + } else { + ToastService.showLong('Failed to scan QR code. Please try again.'); } }; From 2eabf62e836a9fc6f4ffed288bb1d8a8871e7bc1 Mon Sep 17 00:00:00 2001 From: Najuna Brian Date: Sun, 21 Dec 2025 03:50:48 +0300 Subject: [PATCH 3/3] Add success/error messages for login and fix API cache for serverUrl changes --- formulus/src/api/synkronus/index.ts | 19 ++++++++++++++----- formulus/src/screens/SettingsScreen.tsx | 4 ++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/formulus/src/api/synkronus/index.ts b/formulus/src/api/synkronus/index.ts index 1498da9de..583d49e52 100644 --- a/formulus/src/api/synkronus/index.ts +++ b/formulus/src/api/synkronus/index.ts @@ -20,14 +20,23 @@ class SynkronusApi { private config: Configuration | null = null; async getApi(): Promise { + // Always check current serverUrl from storage to handle changes + const rawSettings = await AsyncStorage.getItem('@settings'); + if (!rawSettings) throw new Error('Missing app settings'); + + const {serverUrl} = JSON.parse(rawSettings); + + // If config exists but serverUrl changed, clear cache + if (this.config && this.config.basePath !== serverUrl) { + this.api = null; + this.config = null; + } + + // If API exists, return it (serverUrl hasn't changed) if (this.api) return this.api; - // Load settings if not already loaded + // Load config if not already loaded if (!this.config) { - const rawSettings = await AsyncStorage.getItem('@settings'); - if (!rawSettings) throw new Error('Missing app settings'); - - const {serverUrl} = JSON.parse(rawSettings); this.config = new Configuration({ basePath: serverUrl, accessToken: async () => { diff --git a/formulus/src/screens/SettingsScreen.tsx b/formulus/src/screens/SettingsScreen.tsx index f062ebf64..7d81069b7 100644 --- a/formulus/src/screens/SettingsScreen.tsx +++ b/formulus/src/screens/SettingsScreen.tsx @@ -88,9 +88,13 @@ const SettingsScreen = () => { await Keychain.setGenericPassword(username, password); const userInfo = await login(username, password); setLoggedInUser(userInfo); + ToastService.showShort('Successfully logged in!'); navigation.navigate('MainApp'); } catch (error: any) { console.error('Login failed:', error); + const errorMessage = + error?.message || 'Failed to login. Please check your credentials.'; + ToastService.showLong(`Login failed: ${errorMessage}`); } finally { setIsLoggingIn(false); }