diff --git a/src/App.js b/src/App.js
index e348d88..9aa68b8 100644
--- a/src/App.js
+++ b/src/App.js
@@ -51,6 +51,7 @@ import ContactUs from './pages/ContactUs';
import TermsOfService from './pages/TermsOfService';
import PrivacyPolicy from './pages/PrivacyPolicy';
import AcceptInvitation from './pages/AcceptInvitation';
+import NotFound from './pages/NotFound';
const PrivateRoute = ({ children, adminOnly = false }) => {
const { user, loading } = useAuth();
@@ -280,6 +281,9 @@ function App() {
} />
+
+ {/* 404 - Catch all undefined routes */}
+ } />
diff --git a/src/context/AuthContext.js b/src/context/AuthContext.js
index ca70795..24df9bf 100644
--- a/src/context/AuthContext.js
+++ b/src/context/AuthContext.js
@@ -3,7 +3,14 @@ import axios from 'axios';
const AuthContext = createContext();
-const API_URL = process.env.REACT_APP_BACKEND_URL;
+const API_URL = process.env.REACT_APP_BACKEND_URL || window.location.origin;
+
+// Log environment on module load for debugging
+console.log('[AuthContext] Module initialized with:', {
+ REACT_APP_BACKEND_URL: process.env.REACT_APP_BACKEND_URL,
+ REACT_APP_BASENAME: process.env.REACT_APP_BASENAME,
+ API_URL: API_URL
+});
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
@@ -54,21 +61,79 @@ export const AuthProvider = ({ children }) => {
};
const login = async (email, password) => {
- const response = await axios.post(`${API_URL}/api/auth/login`, { email, password });
- const { access_token, user: userData } = response.data;
- localStorage.setItem('token', access_token);
- setToken(access_token);
- setUser(userData);
-
- // Fetch user permissions (don't let this fail the login)
try {
- await fetchPermissions(access_token);
- } catch (error) {
- console.error('Failed to fetch permissions during login, will retry later:', error);
- // Don't throw - permissions can be fetched later if needed
- }
+ console.log('[AuthContext] Starting login request...', {
+ API_URL: API_URL,
+ envBackendUrl: process.env.REACT_APP_BACKEND_URL,
+ fullUrl: `${API_URL}/api/auth/login`
+ });
- return userData;
+ const response = await axios.post(
+ `${API_URL}/api/auth/login`,
+ { email, password },
+ {
+ timeout: 30000, // 30 second timeout
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }
+ );
+
+ console.log('[AuthContext] Login response received:', {
+ status: response.status,
+ hasToken: !!response.data?.access_token,
+ hasUser: !!response.data?.user
+ });
+
+ const { access_token, user: userData } = response.data;
+
+ // Store token first
+ localStorage.setItem('token', access_token);
+ console.log('[AuthContext] Token stored in localStorage');
+
+ // Update state
+ setToken(access_token);
+ setUser(userData);
+ console.log('[AuthContext] User state updated:', {
+ email: userData.email,
+ role: userData.role
+ });
+
+ // Fetch user permissions (don't let this fail the login)
+ // Use setTimeout to defer permission fetching slightly
+ setTimeout(async () => {
+ try {
+ console.log('[AuthContext] Fetching permissions...');
+ await fetchPermissions(access_token);
+ console.log('[AuthContext] Permissions fetched successfully');
+ } catch (error) {
+ console.error('[AuthContext] Failed to fetch permissions (non-critical):', {
+ message: error.message,
+ response: error.response?.data,
+ status: error.response?.status
+ });
+ // Don't throw - permissions can be fetched later if needed
+ }
+ }, 100); // Small delay to ensure state is settled
+
+ return userData;
+ } catch (error) {
+ // Enhanced error logging
+ console.error('[AuthContext] Login failed:', {
+ message: error.message,
+ response: error.response?.data,
+ status: error.response?.status,
+ code: error.code,
+ config: {
+ url: error.config?.url,
+ method: error.config?.method,
+ timeout: error.config?.timeout
+ }
+ });
+
+ // Re-throw to let Login component handle the error
+ throw error;
+ }
};
const logout = () => {
diff --git a/src/pages/AcceptInvitation.js b/src/pages/AcceptInvitation.js
index 2be4be0..5410c0f 100644
--- a/src/pages/AcceptInvitation.js
+++ b/src/pages/AcceptInvitation.js
@@ -19,6 +19,8 @@ const AcceptInvitation = () => {
const [invitation, setInvitation] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
+ const [success, setSuccess] = useState(false);
+ const [successUser, setSuccessUser] = useState(null);
const [error, setError] = useState(null);
const [formData, setFormData] = useState({
password: '',
@@ -134,19 +136,23 @@ const AcceptInvitation = () => {
const { access_token, user } = response.data;
localStorage.setItem('token', access_token);
- toast.success('Welcome to LOAF! Your account has been created successfully.');
-
// Call login to update auth context
if (login) {
await login(invitation.email, formData.password);
}
- // Redirect based on role
- if (user.role === 'admin' || user.role === 'superadmin') {
- navigate('/admin/dashboard');
- } else {
- navigate('/dashboard');
- }
+ // Show success state
+ setSuccessUser(user);
+ setSuccess(true);
+
+ // Auto-redirect after 3 seconds
+ setTimeout(() => {
+ if (user.role === 'admin' || user.role === 'superadmin') {
+ navigate('/admin');
+ } else {
+ navigate('/dashboard');
+ }
+ }, 3000);
} catch (error) {
const errorMessage = error.response?.data?.detail || 'Failed to accept invitation';
toast.error(errorMessage);
@@ -206,6 +212,83 @@ const AcceptInvitation = () => {
);
}
+ if (success) {
+ const redirectPath = successUser?.role === 'admin' || successUser?.role === 'superadmin' ? '/admin' : '/dashboard';
+
+ return (
+
+
+ {/* Success Animation */}
+
+
+ {/* Success Message */}
+
+ Welcome to LOAF! 🎉
+
+
+ Your account has been created successfully.
+
+
+ {/* User Info Card */}
+
+
+
+
+ Name
+
+
+ {successUser?.first_name} {successUser?.last_name}
+
+
+
+
+ Email
+
+
+ {successUser?.email}
+
+
+
+
+ Role
+
+
{getRoleBadge(successUser?.role)}
+
+
+
+ Status
+
+
+ {successUser?.status}
+
+
+
+
+
+ {/* Redirect Info */}
+
+
+
+ Redirecting you to your dashboard in 3 seconds...
+
+
+
+ {/* Manual Continue Button */}
+
+
+
+ );
+ }
+
return (
diff --git a/src/pages/NotFound.js b/src/pages/NotFound.js
new file mode 100644
index 0000000..21d68b0
--- /dev/null
+++ b/src/pages/NotFound.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { Button } from '../components/ui/button';
+import { Card } from '../components/ui/card';
+import { Home, ArrowLeft, Search } from 'lucide-react';
+
+const NotFound = () => {
+ const navigate = useNavigate();
+
+ return (
+
+
+ {/* 404 Illustration */}
+
+
+ {/* Message */}
+
+ Page Not Found
+
+
+ Oops! The page you're looking for doesn't exist. It might have been moved or deleted.
+
+
+ {/* Action Buttons */}
+
+
+
+
+
+ {/* Help Text */}
+
+
+
+ );
+};
+
+export default NotFound;
diff --git a/src/utils/api.js b/src/utils/api.js
index 7a1a797..b79e35b 100644
--- a/src/utils/api.js
+++ b/src/utils/api.js
@@ -4,14 +4,60 @@ const API_URL = process.env.REACT_APP_BACKEND_URL;
export const api = axios.create({
baseURL: `${API_URL}/api`,
+ timeout: 30000, // 30 second timeout for all requests
});
-api.interceptors.request.use((config) => {
- const token = localStorage.getItem('token');
- if (token) {
- config.headers.Authorization = `Bearer ${token}`;
+// Request interceptor - add auth token
+api.interceptors.request.use(
+ (config) => {
+ const token = localStorage.getItem('token');
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+ },
+ (error) => {
+ console.error('[API] Request error:', error);
+ return Promise.reject(error);
}
- return config;
-});
+);
+
+// Response interceptor - handle errors and retries
+api.interceptors.response.use(
+ (response) => {
+ return response;
+ },
+ async (error) => {
+ const config = error.config;
+
+ // Don't retry if we've already retried or if it's a client error (4xx)
+ if (!config || config.__isRetry || (error.response && error.response.status < 500)) {
+ console.error('[API] Request failed:', {
+ url: config?.url,
+ method: config?.method,
+ status: error.response?.status,
+ message: error.message,
+ data: error.response?.data
+ });
+ return Promise.reject(error);
+ }
+
+ // Mark as retry to prevent infinite loops
+ config.__isRetry = true;
+
+ // Retry after 1 second for server errors or network issues
+ console.warn('[API] Retrying request after 1s:', {
+ url: config.url,
+ method: config.method,
+ error: error.message
+ });
+
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(api.request(config));
+ }, 1000);
+ });
+ }
+);
export default api;