From 48802fe0c6167e2549a70d4b43f12701dfd575f9 Mon Sep 17 00:00:00 2001
From: Koncept Kit <63216427+konceptkit@users.noreply.github.com>
Date: Mon, 5 Jan 2026 14:15:22 +0700
Subject: [PATCH 1/5] Fix invitation redirect: admin users now go to /admin
instead of /admin/dashboard
---
src/pages/AcceptInvitation.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pages/AcceptInvitation.js b/src/pages/AcceptInvitation.js
index 2be4be0..1825b32 100644
--- a/src/pages/AcceptInvitation.js
+++ b/src/pages/AcceptInvitation.js
@@ -143,7 +143,7 @@ const AcceptInvitation = () => {
// Redirect based on role
if (user.role === 'admin' || user.role === 'superadmin') {
- navigate('/admin/dashboard');
+ navigate('/admin');
} else {
navigate('/dashboard');
}
From fa9a1d1d1dbcf3e9d4970538725e5b90bc4fef3a Mon Sep 17 00:00:00 2001
From: Koncept Kit <63216427+konceptkit@users.noreply.github.com>
Date: Mon, 5 Jan 2026 14:51:39 +0700
Subject: [PATCH 2/5] Add 404 page and invitation success screen
- Created NotFound component with proper error messaging and navigation
- Added catch-all route (*) in App.js for undefined routes
- Added success state in AcceptInvitation with user info display
- Auto-redirect after 3 seconds with manual continue button option
- Improved UX with animated success indicator
---
src/App.js | 4 ++
src/pages/AcceptInvitation.js | 99 ++++++++++++++++++++++++++++++++---
src/pages/NotFound.js | 81 ++++++++++++++++++++++++++++
3 files changed, 176 insertions(+), 8 deletions(-)
create mode 100644 src/pages/NotFound.js
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/pages/AcceptInvitation.js b/src/pages/AcceptInvitation.js
index 1825b32..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');
- } 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;
From 1acb13ba7934ec9eb275918666a4e6d86a03e0ee Mon Sep 17 00:00:00 2001
From: Koncept Kit <63216427+konceptkit@users.noreply.github.com>
Date: Mon, 5 Jan 2026 15:00:41 +0700
Subject: [PATCH 3/5] Add comprehensive login diagnostics and retry logic
- Add detailed console logging throughout login flow
- Add 30s timeout to prevent hanging requests
- Defer permission fetching with setTimeout to avoid race conditions
- Add automatic retry for 5xx errors and network failures
- Enhanced error logging with full context for debugging
This addresses intermittent login failures reported by users
---
src/context/AuthContext.js | 80 +++++++++++++++++++++++++++++++-------
src/utils/api.js | 58 ++++++++++++++++++++++++---
2 files changed, 119 insertions(+), 19 deletions(-)
diff --git a/src/context/AuthContext.js b/src/context/AuthContext.js
index ca70795..b029b7a 100644
--- a/src/context/AuthContext.js
+++ b/src/context/AuthContext.js
@@ -54,21 +54,75 @@ 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...');
- 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/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;
From 03b76a8e582c92ded7ddd75e4318ce6ea32d2f6c Mon Sep 17 00:00:00 2001
From: Koncept Kit <63216427+konceptkit@users.noreply.github.com>
Date: Mon, 5 Jan 2026 15:42:11 +0700
Subject: [PATCH 4/5] Add defensive backend URL validation and auto-cleanup
- Add getApiUrl() function to validate and clean backend URL
- Automatically strip /membership or /api suffix if present
- Log all environment variables on module load for debugging
- Add detailed URL logging in login function
- Provide fallback if REACT_APP_BACKEND_URL is undefined
This fixes the intermittent CORS error caused by incorrect backend URL
---
src/context/AuthContext.js | 35 +++++++++++++++++++++++++++++++++--
1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/src/context/AuthContext.js b/src/context/AuthContext.js
index b029b7a..b2aa1b7 100644
--- a/src/context/AuthContext.js
+++ b/src/context/AuthContext.js
@@ -3,7 +3,34 @@ import axios from 'axios';
const AuthContext = createContext();
-const API_URL = process.env.REACT_APP_BACKEND_URL;
+// Ensure API_URL is correctly set, with fallback and validation
+const getApiUrl = () => {
+ const url = process.env.REACT_APP_BACKEND_URL;
+
+ // Log the environment variable for debugging
+ console.log('[AuthContext] Environment check:', {
+ REACT_APP_BACKEND_URL: url,
+ REACT_APP_BASENAME: process.env.REACT_APP_BASENAME,
+ PUBLIC_URL: process.env.PUBLIC_URL
+ });
+
+ if (!url) {
+ console.error('[AuthContext] REACT_APP_BACKEND_URL is not defined!');
+ // Fallback to current origin API (same domain)
+ return window.location.origin.replace('/membership', '');
+ }
+
+ // Remove any trailing /membership or /api from the backend URL
+ const cleanUrl = url.replace(/\/(membership|api)\/?$/, '');
+
+ if (cleanUrl !== url) {
+ console.warn('[AuthContext] Cleaned backend URL:', { original: url, cleaned: cleanUrl });
+ }
+
+ return cleanUrl;
+};
+
+const API_URL = getApiUrl();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
@@ -55,7 +82,11 @@ export const AuthProvider = ({ children }) => {
const login = async (email, password) => {
try {
- console.log('[AuthContext] Starting login request...');
+ console.log('[AuthContext] Starting login request...', {
+ API_URL: API_URL,
+ envBackendUrl: process.env.REACT_APP_BACKEND_URL,
+ fullUrl: `${API_URL}/api/auth/login`
+ });
const response = await axios.post(
`${API_URL}/api/auth/login`,
From 56711e9136b6ea1f2a189aa9a3c1cbbf90d14ea6 Mon Sep 17 00:00:00 2001
From: Koncept Kit <63216427+konceptkit@users.noreply.github.com>
Date: Mon, 5 Jan 2026 15:47:30 +0700
Subject: [PATCH 5/5] Revert URL cleanup - backend path is correct
The /membership path in backend URL is correct for development.
Issue is CORS configuration on backend, not URL format.
---
src/context/AuthContext.js | 34 +++++++---------------------------
1 file changed, 7 insertions(+), 27 deletions(-)
diff --git a/src/context/AuthContext.js b/src/context/AuthContext.js
index b2aa1b7..24df9bf 100644
--- a/src/context/AuthContext.js
+++ b/src/context/AuthContext.js
@@ -3,34 +3,14 @@ import axios from 'axios';
const AuthContext = createContext();
-// Ensure API_URL is correctly set, with fallback and validation
-const getApiUrl = () => {
- const url = process.env.REACT_APP_BACKEND_URL;
+const API_URL = process.env.REACT_APP_BACKEND_URL || window.location.origin;
- // Log the environment variable for debugging
- console.log('[AuthContext] Environment check:', {
- REACT_APP_BACKEND_URL: url,
- REACT_APP_BASENAME: process.env.REACT_APP_BASENAME,
- PUBLIC_URL: process.env.PUBLIC_URL
- });
-
- if (!url) {
- console.error('[AuthContext] REACT_APP_BACKEND_URL is not defined!');
- // Fallback to current origin API (same domain)
- return window.location.origin.replace('/membership', '');
- }
-
- // Remove any trailing /membership or /api from the backend URL
- const cleanUrl = url.replace(/\/(membership|api)\/?$/, '');
-
- if (cleanUrl !== url) {
- console.warn('[AuthContext] Cleaned backend URL:', { original: url, cleaned: cleanUrl });
- }
-
- return cleanUrl;
-};
-
-const API_URL = getApiUrl();
+// 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);