[NOT FULLY WORKING] Added frontend react native interface.
This commit is contained in:
210
interfaces/nativeapp/src/contexts/AuthContext.tsx
Normal file
210
interfaces/nativeapp/src/contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
// src/contexts/AuthContext.tsx
|
||||
import React, { createContext, useState, useEffect, useContext, useMemo, useCallback } from 'react';
|
||||
import { Platform, ActivityIndicator, View, StyleSheet } from 'react-native'; // Import Platform
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'; // Use AsyncStorage for web localStorage
|
||||
import apiClient from '../api/client';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
|
||||
const TOKEN_KEY = 'maia_access_token'; // Use the same key
|
||||
|
||||
interface AuthContextData {
|
||||
authToken: string | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
login: (username: string, password: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextData>({
|
||||
authToken: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: true,
|
||||
login: async () => { throw new Error('AuthContext not initialized'); },
|
||||
logout: async () => { throw new Error('AuthContext not initialized'); },
|
||||
});
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
// Helper functions for platform-specific storage
|
||||
const storeToken = async (token: string) => {
|
||||
if (Platform.OS === 'web') {
|
||||
try {
|
||||
// Use AsyncStorage for web (polyfilled to localStorage)
|
||||
await AsyncStorage.setItem(TOKEN_KEY, token);
|
||||
} catch (e) {
|
||||
console.error("Failed to save token to web storage", e);
|
||||
}
|
||||
} else {
|
||||
await SecureStore.setItemAsync(TOKEN_KEY, token);
|
||||
}
|
||||
};
|
||||
|
||||
const getToken = async (): Promise<string | null> => {
|
||||
if (Platform.OS === 'web') {
|
||||
try {
|
||||
return await AsyncStorage.getItem(TOKEN_KEY);
|
||||
} catch (e) {
|
||||
console.error("Failed to get token from web storage", e);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// SecureStore might throw if not available, handle gracefully
|
||||
try {
|
||||
return await SecureStore.getItemAsync(TOKEN_KEY);
|
||||
} catch (e) {
|
||||
console.error("Failed to get token from secure store", e);
|
||||
// If SecureStore fails on native, treat as no token found
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteToken = async () => {
|
||||
if (Platform.OS === 'web') {
|
||||
try {
|
||||
await AsyncStorage.removeItem(TOKEN_KEY);
|
||||
} catch (e) {
|
||||
console.error("Failed to remove token from web storage", e);
|
||||
}
|
||||
} else {
|
||||
// Avoid potential crash if SecureStore is unavailable
|
||||
try {
|
||||
await SecureStore.deleteItemAsync(TOKEN_KEY);
|
||||
} catch (e) {
|
||||
console.error("Failed to delete token from secure store", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const [authToken, setAuthToken] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
const loadToken = useCallback(async () => {
|
||||
console.log("[AuthContext] loadToken: Starting..."); // Log: Start
|
||||
setIsLoading(true);
|
||||
try {
|
||||
console.log("[AuthContext] loadToken: Calling getToken()..."); // Log: Before await
|
||||
const storedToken = await getToken(); // Use helper
|
||||
console.log("[AuthContext] loadToken: getToken() returned:", storedToken); // Log: After await
|
||||
|
||||
if (storedToken) {
|
||||
console.log('[AuthContext] loadToken: Token found. Setting state and headers.'); // Log: Token Found Path
|
||||
setAuthToken(storedToken);
|
||||
apiClient.defaults.headers.common['Authorization'] = `Bearer ${storedToken}`;
|
||||
} else {
|
||||
console.log('[AuthContext] loadToken: No token found. Clearing state and headers.'); // Log: No Token Path
|
||||
setAuthToken(null);
|
||||
delete apiClient.defaults.headers.common['Authorization'];
|
||||
}
|
||||
console.log('[AuthContext] loadToken: Try block finished successfully.'); // Log: Try Success
|
||||
} catch (error) {
|
||||
// **Log the actual error object**
|
||||
console.error("[AuthContext] loadToken: Caught error:", error); // Log: Catch Block
|
||||
setAuthToken(null); // Ensure logged out state on error
|
||||
delete apiClient.defaults.headers.common['Authorization'];
|
||||
} finally {
|
||||
console.log("[AuthContext] loadToken: Entering finally block."); // Log: Finally Start
|
||||
setIsLoading(false);
|
||||
console.log("[AuthContext] loadToken: setIsLoading(false) called."); // Log: Finally End
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("[AuthContext] useEffect: Component mounted, calling loadToken."); // Log: useEffect call
|
||||
loadToken();
|
||||
}, [loadToken]);
|
||||
|
||||
const login = useCallback(async (username: string, password: string) => {
|
||||
console.log("[AuthContext] login: Function called with:", username); // Log: Function entry
|
||||
try {
|
||||
console.log("[AuthContext] login: Preparing to call apiClient.post for /auth/login");
|
||||
console.log("[AuthContext] login: Data being sent:", { username: username, password: password });
|
||||
|
||||
// const response = await apiClient.post(`/auth/login?grant_type=password&username=${username}&password=${password}`);
|
||||
const response = await apiClient.post(
|
||||
'/auth/login',
|
||||
'grant_type=password&username=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password) + '&scope=&client_id=&client_secret=',
|
||||
{
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
console.log("[AuthContext] login: apiClient.post successful, response status:", response?.status); // Log success
|
||||
|
||||
const { access_token } = response.data;
|
||||
|
||||
if (!access_token || typeof access_token !== 'string') {
|
||||
console.error("[AuthContext] login: Invalid token structure received:", response.data);
|
||||
throw new Error('Invalid token received from server.');
|
||||
}
|
||||
|
||||
console.log('[AuthContext] login: Login successful, received token.');
|
||||
setAuthToken(access_token);
|
||||
apiClient.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
|
||||
await storeToken(access_token); // Use helper
|
||||
|
||||
} catch (error: any) {
|
||||
// --- Log the error object *itself* ---
|
||||
console.error("[AuthContext] login: Caught Error Object:", error);
|
||||
|
||||
// --- Check if it's an Axios error with config details ---
|
||||
if (error.isAxiosError) {
|
||||
console.error("[AuthContext] login: Axios Error Details:");
|
||||
console.error(" Request Config:", error.config);
|
||||
console.error(" Response:", error.response); // This will likely still be undefined
|
||||
console.error(" Message:", error.message);
|
||||
}
|
||||
|
||||
// Original logging (might be redundant now but keep for context)
|
||||
console.error("Login failed:", error.response?.data || error.message);
|
||||
throw error; // Re-throw
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
console.log('Logging out.');
|
||||
setAuthToken(null);
|
||||
delete apiClient.defaults.headers.common['Authorization'];
|
||||
await deleteToken(); // Use helper
|
||||
// Optional backend logout call
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(() => ({
|
||||
authToken,
|
||||
isAuthenticated: !!authToken,
|
||||
isLoading,
|
||||
login,
|
||||
logout,
|
||||
}), [authToken, isLoading, login, logout]);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// --- useAuth and AuthLoadingScreen remain the same ---
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const AuthLoadingScreen: React.FC = () => {
|
||||
const theme = useTheme();
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: theme.colors.background }
|
||||
});
|
||||
return (<View style={styles.container}><ActivityIndicator size="large" color={theme.colors.primary} /></View>);
|
||||
}
|
||||
Reference in New Issue
Block a user