[V0.3] Working dashboard calendar module

This commit is contained in:
c-d-p
2025-04-21 20:09:41 +02:00
parent c158ff4e0e
commit 4f57df8101
15 changed files with 5401 additions and 294 deletions

View File

@@ -1,15 +1,11 @@
// 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
import { getAccessToken, getRefreshToken, storeTokens, clearTokens } from '../api/client';
interface AuthContextData {
authToken: string | null;
isAuthenticated: boolean;
isLoading: boolean;
login: (username: string, password: string) => Promise<void>;
@@ -17,7 +13,6 @@ interface AuthContextData {
}
const AuthContext = createContext<AuthContextData>({
authToken: null,
isAuthenticated: false,
isLoading: true,
login: async () => { throw new Error('AuthContext not initialized'); },
@@ -28,104 +23,42 @@ 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 [isAuthenticatedState, setIsAuthenticatedState] = useState<boolean>(false);
const loadToken = useCallback(async () => {
console.log("[AuthContext] loadToken: Starting..."); // Log: Start
const checkAuthStatus = useCallback(async () => {
const token = await getAccessToken();
const hasToken = !!token;
if (hasToken !== isAuthenticatedState) {
setIsAuthenticatedState(hasToken);
}
return hasToken;
}, [isAuthenticatedState]);
const loadInitialAuth = useCallback(async () => {
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
console.log("[AuthContext] loadInitialAuth: Checking initial auth status");
await checkAuthStatus();
console.log("[AuthContext] loadInitialAuth: Initial check complete.");
} 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'];
console.error("[AuthContext] loadInitialAuth: Error loading initial token:", error);
await clearTokens();
setIsAuthenticatedState(false);
} finally {
console.log("[AuthContext] loadToken: Entering finally block."); // Log: Finally Start
setIsLoading(false);
console.log("[AuthContext] loadToken: setIsLoading(false) called."); // Log: Finally End
}
}, []);
}, [checkAuthStatus]);
useEffect(() => {
console.log("[AuthContext] useEffect: Component mounted, calling loadToken."); // Log: useEffect call
loadToken();
}, [loadToken]);
loadInitialAuth();
}, [loadInitialAuth]);
const login = useCallback(async (username: string, password: string) => {
console.log("[AuthContext] login: Function called with:", username); // Log: Function entry
console.log("[AuthContext] login: Function called with:", username);
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=',
@@ -137,53 +70,61 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
}
);
console.log("[AuthContext] login: apiClient.post successful, response status:", response?.status); // Log success
console.log("[AuthContext] login: apiClient.post successful, response status:", response?.status);
const { access_token, refresh_token } = response.data;
console.log("[AuthContext] login: Response data received.");
const { access_token } = response.data;
if (!access_token || typeof access_token !== 'string') {
if (!access_token || typeof access_token !== 'string' || !refresh_token) {
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
console.log('[AuthContext] login: Login successful, storing tokens.');
await storeTokens(access_token, refresh_token);
setIsAuthenticatedState(true);
} 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(" Response:", error.response?.status, error.response?.data);
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
await clearTokens();
setIsAuthenticatedState(false);
throw error;
}
}, []);
const logout = useCallback(async () => {
console.log('Logging out.');
setAuthToken(null);
delete apiClient.defaults.headers.common['Authorization'];
await deleteToken(); // Use helper
await apiClient.post("/auth/logout");
console.log('[AuthContext] logout: Logging out.');
const refreshToken = await getRefreshToken();
if (!refreshToken) {
console.warn('[AuthContext] logout: No refresh token found to send to backend.');
}
try {
if (refreshToken) {
console.log('[AuthContext] logout: Calling backend /auth/logout');
await apiClient.post("/auth/logout", { refresh_token: refreshToken });
console.log('[AuthContext] logout: Backend logout call successful (or ignored error).');
}
} catch (error: any) {
console.error('[AuthContext] logout: Error calling backend logout:', error.response?.data || error.message);
} finally {
await clearTokens();
setIsAuthenticatedState(false);
console.log('[AuthContext] logout: Local tokens cleared and state updated.');
}
}, []);
const contextValue = useMemo(() => ({
authToken,
isAuthenticated: !!authToken,
isAuthenticated: isAuthenticatedState,
isLoading,
login,
logout,
}), [authToken, isLoading, login, logout]);
}), [isAuthenticatedState, isLoading, login, logout]);
return (
<AuthContext.Provider value={contextValue}>