This commit is contained in:
2025-10-27 11:32:22 +01:00
parent 84a3a9a5b5
commit 82aa8948cf
7 changed files with 180 additions and 22 deletions
+109 -10
View File
@@ -63,6 +63,16 @@ const api = axios.create({
});
const STORED_TOKEN = window.localStorage.getItem('papercrate_token') || '';
let STORED_TENANT = null;
try {
const rawTenant = window.localStorage.getItem('papercrate_tenant');
if (rawTenant) {
STORED_TENANT = JSON.parse(rawTenant);
}
} catch (
// eslint-disable-next-line no-empty
error
) {}
if (STORED_TOKEN) {
api.defaults.headers.common.Authorization = `Bearer ${STORED_TOKEN}`;
}
@@ -73,6 +83,8 @@ const initialAppState = {
error: null,
isRefreshing: false,
tenantSelection: null,
tenant: STORED_TENANT,
tenants: [],
};
const AppStateContext = React.createContext(null);
@@ -81,7 +93,14 @@ const AppDispatchContext = React.createContext(null);
const appStateReducer = (state, action) => {
switch (action.type) {
case 'LOGIN_REQUEST':
return { ...state, status: 'authenticating', error: null, tenantSelection: null };
return {
...state,
status: 'authenticating',
error: null,
tenantSelection: null,
tenant: null,
tenants: [],
};
case 'LOGIN_SUCCESS':
return {
...state,
@@ -89,6 +108,8 @@ const appStateReducer = (state, action) => {
token: action.token,
error: null,
tenantSelection: null,
tenant: action.tenant || null,
tenants: state.tenants,
};
case 'LOGIN_FAILURE':
return {
@@ -97,6 +118,8 @@ const appStateReducer = (state, action) => {
error: action.error || null,
isRefreshing: false,
tenantSelection: null,
tenant: null,
tenants: [],
};
case 'TENANT_SELECTION_REQUIRED':
return {
@@ -108,6 +131,8 @@ const appStateReducer = (state, action) => {
selectionToken: action.selectionToken,
tenants: action.tenants,
},
tenant: null,
tenants: [],
};
case 'CLEAR_TENANT_SELECTION':
return {
@@ -116,6 +141,8 @@ const appStateReducer = (state, action) => {
error: null,
isRefreshing: false,
tenantSelection: null,
tenant: null,
tenants: [],
};
case 'BOOTSTRAP_START':
return { ...state, status: 'bootstrapping', error: null };
@@ -132,6 +159,8 @@ const appStateReducer = (state, action) => {
isRefreshing: false,
status: state.status === 'logged-out' ? 'authenticated' : state.status,
tenantSelection: null,
tenant: action.tenant || state.tenant || null,
tenants: state.tenants,
};
case 'TOKEN_REFRESH_FAILURE':
return {
@@ -140,11 +169,26 @@ const appStateReducer = (state, action) => {
error: action.error || null,
isRefreshing: false,
tenantSelection: null,
tenant: null,
tenants: [],
};
case 'LOGOUT':
return { status: 'logged-out', token: '', error: null, isRefreshing: false, tenantSelection: null };
return {
status: 'logged-out',
token: '',
error: null,
isRefreshing: false,
tenantSelection: null,
tenant: null,
tenants: [],
};
case 'RESET_ERROR':
return { ...state, error: null };
case 'SET_TENANTS':
return {
...state,
tenants: Array.isArray(action.tenants) ? action.tenants : [],
};
default:
return state;
}
@@ -164,6 +208,45 @@ const AppStateProvider = ({ children }) => {
}
}, [state.token]);
useEffect(() => {
if (state.tenant) {
try {
window.localStorage.setItem('papercrate_tenant', JSON.stringify(state.tenant));
} catch (error) {
console.warn('Failed to persist tenant info', error);
}
} else {
window.localStorage.removeItem('papercrate_tenant');
}
}, [state.tenant]);
useEffect(() => {
let abort = false;
const loadTenants = async () => {
if (state.status !== 'authenticated' || !state.token) {
dispatch({ type: 'SET_TENANTS', tenants: [] });
return;
}
try {
const { data } = await api.get('/auth/tenants');
if (!abort) {
dispatch({
type: 'SET_TENANTS',
tenants: Array.isArray(data?.tenants) ? data.tenants : [],
});
}
} catch (error) {
if (!abort) {
console.warn('Failed to load tenant list', error);
}
}
};
loadTenants();
return () => {
abort = true;
};
}, [state.status, state.token, dispatch]);
const stateValue = useMemo(() => state, [state]);
return (
@@ -416,7 +499,11 @@ const AppLayout = () => {
try {
const { data } = await api.post('/auth/refresh');
if (data?.access_token) {
appDispatch({ type: 'TOKEN_REFRESH_SUCCESS', token: data.access_token });
appDispatch({
type: 'TOKEN_REFRESH_SUCCESS',
token: data.access_token,
tenant: data.tenant || null,
});
console.log('[Auth] Access token refreshed at', new Date().toISOString());
return data.access_token;
}
@@ -4196,10 +4283,10 @@ const AppLayout = () => {
appDispatch({ type: 'LOGIN_REQUEST' });
const { data } = await api.post('/auth/login', payload);
if (data?.selection_token && Array.isArray(data?.tenants)) {
if (data?.access_token && Array.isArray(data?.tenants)) {
appDispatch({
type: 'TENANT_SELECTION_REQUIRED',
selectionToken: data.selection_token,
selectionToken: data.access_token,
tenants: data.tenants,
});
setStatusMessage('Select a tenant to continue.', 'info');
@@ -4210,7 +4297,11 @@ const AppLayout = () => {
throw new Error('Invalid login response.');
}
appDispatch({ type: 'LOGIN_SUCCESS', token: data.access_token });
appDispatch({
type: 'LOGIN_SUCCESS',
token: data.access_token,
tenant: data.tenant || null,
});
setStatusMessage('Login successful.', 'success');
} catch (error) {
const message = error?.response?.data?.error || 'Login failed. Check credentials.';
@@ -5322,10 +5413,10 @@ const LoginRoute = () => {
try {
appDispatch({ type: 'LOGIN_REQUEST' });
const { data } = await api.post('/auth/login', payload);
if (data?.selection_token && Array.isArray(data?.tenants)) {
if (data?.access_token && Array.isArray(data?.tenants)) {
appDispatch({
type: 'TENANT_SELECTION_REQUIRED',
selectionToken: data.selection_token,
selectionToken: data.access_token,
tenants: data.tenants,
});
setStatusMessage('Select a tenant to continue.', 'info');
@@ -5336,7 +5427,11 @@ const LoginRoute = () => {
throw new Error('Invalid login response.');
}
appDispatch({ type: 'LOGIN_SUCCESS', token: data.access_token });
appDispatch({
type: 'LOGIN_SUCCESS',
token: data.access_token,
tenant: data.tenant || null,
});
setStatusMessage('Login successful.', 'success');
} catch (error) {
const message = error?.response?.data?.error || 'Login failed. Check credentials.';
@@ -5369,7 +5464,11 @@ const LoginRoute = () => {
throw new Error('Invalid tenant selection response.');
}
appDispatch({ type: 'LOGIN_SUCCESS', token: data.access_token });
appDispatch({
type: 'LOGIN_SUCCESS',
token: data.access_token,
tenant: data.tenant || null,
});
setStatusMessage('Login successful.', 'success');
} catch (error) {
const message = error?.response?.data?.error || 'Failed to finalize login.';