Skip to content

Commit 0e5979b

Browse files
committed
update app with auth changes
1 parent 0b4ac32 commit 0e5979b

7 files changed

Lines changed: 778 additions & 144 deletions

File tree

app/app/components/AuthSection.tsx

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, ActivityIndicator } from 'react-native';
3+
import { saveAuthEmail, saveAuthPassword, saveJwtToken, getAuthEmail, getAuthPassword, clearAuthData } from '../utils/storage';
4+
5+
interface AuthSectionProps {
6+
backendUrl: string;
7+
isAuthenticated: boolean;
8+
currentUserEmail: string | null;
9+
onAuthStatusChange: (isAuthenticated: boolean, email: string | null, token: string | null) => void;
10+
}
11+
12+
export const AuthSection: React.FC<AuthSectionProps> = ({
13+
backendUrl,
14+
isAuthenticated,
15+
currentUserEmail,
16+
onAuthStatusChange,
17+
}) => {
18+
const [email, setEmail] = useState<string>('');
19+
const [password, setPassword] = useState<string>('');
20+
const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
21+
22+
// Load saved email and password on component mount
23+
useEffect(() => {
24+
const loadAuthData = async () => {
25+
const savedEmail = await getAuthEmail();
26+
const savedPassword = await getAuthPassword();
27+
if (savedEmail) setEmail(savedEmail);
28+
if (savedPassword) setPassword(savedPassword);
29+
};
30+
loadAuthData();
31+
}, []);
32+
33+
const handleLogin = async () => {
34+
if (!email.trim() || !password.trim()) {
35+
Alert.alert('Missing Credentials', 'Please enter both email and password.');
36+
return;
37+
}
38+
39+
if (!backendUrl.trim()) {
40+
Alert.alert('Backend URL Required', 'Please enter a backend URL first.');
41+
return;
42+
}
43+
44+
setIsLoggingIn(true);
45+
46+
try {
47+
// Convert WebSocket URL to HTTP URL for authentication
48+
const baseUrl = backendUrl.replace('ws://', 'http://').replace('wss://', 'https://').split('/ws')[0];
49+
const loginUrl = `${baseUrl}/auth/jwt/login`;
50+
51+
const formData = new URLSearchParams();
52+
formData.append('username', email.trim());
53+
formData.append('password', password.trim());
54+
55+
const response = await fetch(loginUrl, {
56+
method: 'POST',
57+
headers: {
58+
'Content-Type': 'application/x-www-form-urlencoded',
59+
},
60+
body: formData.toString(),
61+
});
62+
63+
if (!response.ok) {
64+
const errorText = await response.text();
65+
throw new Error(`Login failed: ${response.status} ${response.statusText} - ${errorText}`);
66+
}
67+
68+
const authData = await response.json();
69+
const jwtToken = authData.access_token;
70+
71+
if (!jwtToken) {
72+
throw new Error('No access token received from server');
73+
}
74+
75+
// Save credentials and token
76+
await saveAuthEmail(email.trim());
77+
await saveAuthPassword(password.trim());
78+
await saveJwtToken(jwtToken);
79+
80+
console.log('[AuthSection] Login successful for user:', email);
81+
onAuthStatusChange(true, email.trim(), jwtToken);
82+
83+
} catch (error) {
84+
console.error('[AuthSection] Login error:', error);
85+
Alert.alert(
86+
'Login Failed',
87+
error instanceof Error ? error.message : 'An unknown error occurred during login.'
88+
);
89+
} finally {
90+
setIsLoggingIn(false);
91+
}
92+
};
93+
94+
const handleLogout = async () => {
95+
try {
96+
await clearAuthData();
97+
setEmail('');
98+
setPassword('');
99+
console.log('[AuthSection] Logout successful');
100+
onAuthStatusChange(false, null, null);
101+
} catch (error) {
102+
console.error('[AuthSection] Logout error:', error);
103+
Alert.alert('Logout Error', 'Failed to clear authentication data.');
104+
}
105+
};
106+
107+
if (isAuthenticated && currentUserEmail) {
108+
return (
109+
<View style={styles.section}>
110+
<Text style={styles.sectionTitle}>Authentication</Text>
111+
<View style={styles.authenticatedContainer}>
112+
<Text style={styles.authenticatedText}>Logged in as: {currentUserEmail}</Text>
113+
<TouchableOpacity
114+
style={[styles.button, styles.buttonDanger]}
115+
onPress={handleLogout}
116+
disabled={isLoggingIn}
117+
>
118+
<Text style={styles.buttonText}>Logout</Text>
119+
</TouchableOpacity>
120+
</View>
121+
</View>
122+
);
123+
}
124+
125+
return (
126+
<View style={styles.section}>
127+
<Text style={styles.sectionTitle}>Authentication</Text>
128+
<Text style={styles.inputLabel}>Email:</Text>
129+
<TextInput
130+
style={styles.textInput}
131+
value={email}
132+
onChangeText={setEmail}
133+
placeholder="user@example.com"
134+
autoCapitalize="none"
135+
keyboardType="email-address"
136+
returnKeyType="next"
137+
autoCorrect={false}
138+
editable={!isLoggingIn}
139+
textContentType="emailAddress"
140+
autoComplete="email"
141+
/>
142+
143+
<Text style={styles.inputLabel}>Password:</Text>
144+
<TextInput
145+
style={styles.textInput}
146+
value={password}
147+
onChangeText={setPassword}
148+
placeholder="Enter your password"
149+
secureTextEntry={true}
150+
returnKeyType="go"
151+
autoCorrect={false}
152+
editable={!isLoggingIn}
153+
onSubmitEditing={handleLogin}
154+
textContentType="password"
155+
autoComplete="password"
156+
/>
157+
158+
<TouchableOpacity
159+
style={[styles.button, isLoggingIn ? styles.buttonDisabled : null]}
160+
onPress={handleLogin}
161+
disabled={isLoggingIn}
162+
>
163+
{isLoggingIn ? (
164+
<View style={styles.loadingContainer}>
165+
<ActivityIndicator size="small" color="white" />
166+
<Text style={[styles.buttonText, { marginLeft: 8 }]}>Logging in...</Text>
167+
</View>
168+
) : (
169+
<Text style={styles.buttonText}>Login</Text>
170+
)}
171+
</TouchableOpacity>
172+
173+
{!isAuthenticated && (
174+
<Text style={styles.helpText}>
175+
Enter your email and password to authenticate with the backend.
176+
</Text>
177+
)}
178+
</View>
179+
);
180+
};
181+
182+
const styles = StyleSheet.create({
183+
section: {
184+
marginBottom: 25,
185+
padding: 15,
186+
backgroundColor: 'white',
187+
borderRadius: 10,
188+
shadowColor: '#000',
189+
shadowOffset: { width: 0, height: 1 },
190+
shadowOpacity: 0.1,
191+
shadowRadius: 3,
192+
elevation: 2,
193+
},
194+
sectionTitle: {
195+
fontSize: 18,
196+
fontWeight: '600',
197+
marginBottom: 15,
198+
color: '#333',
199+
},
200+
inputLabel: {
201+
fontSize: 14,
202+
color: '#333',
203+
marginBottom: 5,
204+
marginTop: 10,
205+
fontWeight: '500',
206+
},
207+
textInput: {
208+
backgroundColor: '#f0f0f0',
209+
borderWidth: 1,
210+
borderColor: '#ddd',
211+
borderRadius: 6,
212+
padding: 10,
213+
fontSize: 14,
214+
width: '100%',
215+
marginBottom: 10,
216+
},
217+
button: {
218+
backgroundColor: '#007AFF',
219+
paddingVertical: 12,
220+
paddingHorizontal: 20,
221+
borderRadius: 8,
222+
alignItems: 'center',
223+
marginTop: 15,
224+
elevation: 2,
225+
},
226+
buttonDisabled: {
227+
backgroundColor: '#A0A0A0',
228+
opacity: 0.7,
229+
},
230+
buttonDanger: {
231+
backgroundColor: '#FF3B30',
232+
},
233+
buttonText: {
234+
color: 'white',
235+
fontSize: 16,
236+
fontWeight: '600',
237+
},
238+
loadingContainer: {
239+
flexDirection: 'row',
240+
alignItems: 'center',
241+
},
242+
helpText: {
243+
fontSize: 12,
244+
color: '#666',
245+
marginTop: 10,
246+
textAlign: 'center',
247+
fontStyle: 'italic',
248+
},
249+
authenticatedContainer: {
250+
flexDirection: 'row',
251+
justifyContent: 'space-between',
252+
alignItems: 'center',
253+
},
254+
authenticatedText: {
255+
fontSize: 14,
256+
color: '#4CD964',
257+
fontWeight: '500',
258+
flex: 1,
259+
marginRight: 10,
260+
},
261+
});
262+
263+
export default AuthSection;

0 commit comments

Comments
 (0)