Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions frontend/src/components/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import { useEffect, useState } from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { Box, CircularProgress } from '@mui/material';
import { API_URL } from '../config/api';

const ProtectedRoute = () => {
const token = localStorage.getItem('access_token');
const [authed, setAuthed] = useState<boolean | null>(null);

if (!token) {
// Redirect to login if no token found
return <Navigate to="/login" replace />;
useEffect(() => {
fetch(`${API_URL}/auth/me`, { credentials: 'include' })
.then((res) => {
if (res.status === 401) {
setAuthed(false);
} else if (res.ok) {
setAuthed(true);
}
// leave authed as null on other errors (5xx, network) — keep spinner
})
.catch(() => {
// network error — don't redirect, keep spinner to avoid booting
// users on a transient failure
});
}, []);

if (authed === null) {
return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '100vh',
backgroundColor: '#1e2328',
}}
>
<CircularProgress />
</Box>
);
}

// If token exists, render the child routes
if (!authed) return <Navigate to="/login" replace />;
Comment thread
GitAddRemote marked this conversation as resolved.
return <Outlet />;
};

Expand Down
18 changes: 10 additions & 8 deletions frontend/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,8 @@ const Dashboard = () => {
useEffect(() => {
const fetchUserProfile = async () => {
try {
const token = localStorage.getItem('access_token');
const response = await fetch(`${API_URL}/users/profile`, {
headers: {
Authorization: `Bearer ${token}`,
},
credentials: 'include',
});

if (response.ok) {
Expand Down Expand Up @@ -68,10 +65,15 @@ const Dashboard = () => {
setAnchorEl(null);
};

const handleLogout = () => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
navigate('/login');
const handleLogout = async () => {
try {
await fetch(`${API_URL}/auth/logout`, {
method: 'POST',
credentials: 'include',
});
} finally {
navigate('/login');
}
};

const handleProfile = () => {
Expand Down
18 changes: 10 additions & 8 deletions frontend/src/pages/Inventory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -440,10 +440,15 @@ const InventoryPage = () => {
[],
);

const handleLogout = () => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
navigate('/login');
const handleLogout = async () => {
try {
await fetch(`${API_URL}/auth/logout`, {
method: 'POST',
credentials: 'include',
});
} finally {
navigate('/login');
}
};

const closeActionMenu = () => {
Expand Down Expand Up @@ -479,11 +484,8 @@ const InventoryPage = () => {

const fetchProfile = useCallback(async () => {
try {
const token = localStorage.getItem('access_token');
const response = await fetch(`${API_URL}/users/profile`, {
headers: {
Authorization: `Bearer ${token}`,
},
credentials: 'include',
});

if (!response.ok) {
Expand Down
8 changes: 1 addition & 7 deletions frontend/src/pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,11 @@ const Login = () => {
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ username, password }),
});

if (response.ok) {
const data = await response.json();

// Store tokens
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);

// Redirect to dashboard
navigate('/dashboard');
Comment thread
GitAddRemote marked this conversation as resolved.
} else {
const errorData = await response.json();
Expand Down
24 changes: 12 additions & 12 deletions frontend/src/pages/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,8 @@ const Profile = () => {
useEffect(() => {
const fetchUserProfile = async () => {
try {
const token = localStorage.getItem('access_token');
const response = await fetch(`${API_URL}/users/profile`, {
headers: {
Authorization: `Bearer ${token}`,
},
credentials: 'include',
});

if (response.ok) {
Expand Down Expand Up @@ -91,10 +88,15 @@ const Profile = () => {
setAnchorEl(null);
};

const handleLogout = () => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
navigate('/login');
const handleLogout = async () => {
try {
await fetch(`${API_URL}/auth/logout`, {
method: 'POST',
credentials: 'include',
});
} finally {
navigate('/login');
}
};

const handleDashboard = () => {
Expand All @@ -115,13 +117,12 @@ const Profile = () => {
setMessage({ type: '', text: '' });

try {
const token = localStorage.getItem('access_token');
const response = await fetch(`${API_URL}/users/profile`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
credentials: 'include',
body: JSON.stringify({
firstName: profile.firstName,
lastName: profile.lastName,
Expand Down Expand Up @@ -172,13 +173,12 @@ const Profile = () => {
setChangingPassword(true);

try {
const token = localStorage.getItem('access_token');
const response = await fetch(`${API_URL}/auth/change-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
credentials: 'include',
body: JSON.stringify({
currentPassword,
newPassword,
Expand Down
10 changes: 2 additions & 8 deletions frontend/src/pages/Register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,21 @@ const Register = () => {
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ username, password, email }),
});

if (registerResponse.ok) {
// Auto-login after successful registration
const loginResponse = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ username, password }),
});

if (loginResponse.ok) {
const data = await loginResponse.json();

// Store tokens
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);

// Redirect to dashboard
navigate('/dashboard');
} else {
// Registration succeeded but login failed, redirect to login page
Expand Down
8 changes: 2 additions & 6 deletions frontend/src/services/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@ import { API_URL } from '../config/api';

export const api = axios.create({
baseURL: API_URL,
withCredentials: true,
});

export const login = (username: string, password: string) =>
api.post('/auth/login', { username, password });

export const getProfile = (token: string) =>
api.get('/users/profile', {
headers: {
Authorization: `Bearer ${token}`,
},
});
export const getProfile = () => api.get('/users/profile');
33 changes: 13 additions & 20 deletions frontend/src/services/inventory.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,6 @@ const buildInventoryQuery = (params: InventorySearchParams) => {
return query;
};

const getAuthHeader = () => {
const token = localStorage.getItem('access_token');
return {
Authorization: `Bearer ${token}`,
};
};

const buildOrgInventoryQuery = (params: {
gameId: number;
uexItemId?: number;
Expand Down Expand Up @@ -154,7 +147,7 @@ export const inventoryService = {
): Promise<InventoryListResponse> {
const response = await axios.get(`${API_URL}/api/inventory`, {
params: buildInventoryQuery(params),
headers: getAuthHeader(),
withCredentials: true,
});
return response.data;
},
Expand All @@ -164,7 +157,7 @@ export const inventoryService = {
*/
async getCategories(): Promise<InventoryCategory[]> {
const response = await axios.get(`${API_URL}/api/uex/categories`, {
headers: getAuthHeader(),
withCredentials: true,
});
return response.data;
},
Expand All @@ -176,7 +169,7 @@ export const inventoryService = {
const response = await axios.get(
`${API_URL}/api/inventory/summary/${gameId}`,
{
headers: getAuthHeader(),
withCredentials: true,
},
);
return response.data;
Expand All @@ -192,7 +185,7 @@ export const inventoryService = {
>,
): Promise<InventoryItem> {
const response = await axios.post(`${API_URL}/api/inventory`, item, {
headers: getAuthHeader(),
withCredentials: true,
});
return response.data;
},
Expand All @@ -208,7 +201,7 @@ export const inventoryService = {
`${API_URL}/api/inventory/${id}`,
updates,
{
headers: getAuthHeader(),
withCredentials: true,
},
);
return response.data;
Expand All @@ -219,7 +212,7 @@ export const inventoryService = {
*/
async deleteItem(id: string): Promise<void> {
await axios.delete(`${API_URL}/api/inventory/${id}`, {
headers: getAuthHeader(),
withCredentials: true,
});
},

Expand All @@ -231,7 +224,7 @@ export const inventoryService = {
`${API_URL}/api/inventory/${itemId}/share`,
{ orgId, quantity },
{
headers: getAuthHeader(),
withCredentials: true,
},
);
},
Expand All @@ -241,7 +234,7 @@ export const inventoryService = {
*/
async unshareItem(itemId: string) {
await axios.delete(`${API_URL}/api/inventory/${itemId}/share`, {
headers: getAuthHeader(),
withCredentials: true,
});
},

Expand All @@ -254,7 +247,7 @@ export const inventoryService = {
const response = await axios.get(
`${API_URL}/user-organization-roles/user/${userId}/organizations`,
{
headers: getAuthHeader(),
withCredentials: true,
},
);
return response.data;
Expand Down Expand Up @@ -282,7 +275,7 @@ export const inventoryService = {
): Promise<InventoryListResponse> {
const response = await axios.get(`${API_URL}/api/orgs/${orgId}/inventory`, {
params: buildOrgInventoryQuery(params),
headers: getAuthHeader(),
withCredentials: true,
});

return response.data;
Expand All @@ -309,7 +302,7 @@ export const inventoryService = {
const response = await axios.post(
`${API_URL}/api/orgs/${orgId}/inventory`,
item,
{ headers: getAuthHeader() },
{ withCredentials: true },
);
return response.data;
},
Expand All @@ -325,7 +318,7 @@ export const inventoryService = {
const response = await axios.put(
`${API_URL}/api/orgs/${orgId}/inventory/${id}`,
updates,
{ headers: getAuthHeader() },
{ withCredentials: true },
);
return response.data;
},
Expand All @@ -335,7 +328,7 @@ export const inventoryService = {
*/
async deleteOrgItem(orgId: number, id: string): Promise<void> {
await axios.delete(`${API_URL}/api/orgs/${orgId}/inventory/${id}`, {
headers: getAuthHeader(),
withCredentials: true,
});
},
};
Loading
Loading