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