This commit is contained in:
seb
2026-07-31 07:02:51 +02:00
parent 9dac151854
commit a95b7f4b1e
14 changed files with 1064 additions and 831 deletions

View File

@@ -1,115 +0,0 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSnackbar } from 'notistack';
import { auth, company } from '../api';
import { AuthContext } from './context';
function applyUser(session, setUser, setStatus) {
const nextUser = session?.user ?? null;
setUser(nextUser);
setStatus(nextUser ? 'authed' : 'anon');
return nextUser;
}
export default function AuthProvider({ children }) {
const { enqueueSnackbar } = useSnackbar();
const [user, setUser] = useState(null);
const [googleClientId, setGoogleClientId] = useState(null);
const [status, setStatus] = useState('loading'); // loading | authed | anon
const [companyReady, setCompanyReady] = useState(false);
useEffect(() => {
let active = true;
(async () => {
try {
const session = await auth.me();
if (!active) return;
const nextUser = applyUser(session, setUser, setStatus);
setCompanyReady(nextUser?.selectedCompanyId != null);
setGoogleClientId(session.googleClientId);
if (session.forbidden) {
enqueueSnackbar(session.message, { variant: 'warning' });
}
} catch (err) {
if (!active) return;
setStatus('anon');
setCompanyReady(false);
enqueueSnackbar(`Session check failed: ${err.message}`, {
variant: 'error',
});
}
})();
return () => {
active = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const loginWithGoogle = useCallback(async (credential) => {
const { session } = await auth.loginWithGoogle(credential);
if (!session?.user) {
setUser(null);
setStatus('anon');
setCompanyReady(false);
enqueueSnackbar(
`Login ok but session cookie not accepted (/api/auth/me still unauthorized)`,
{ variant: 'error', autoHideDuration: 12000 },
);
return;
}
const nextUser = applyUser(session, setUser, setStatus);
setCompanyReady(nextUser?.selectedCompanyId != null);
}, [enqueueSnackbar]);
const selectCompany = useCallback(async (companyId) => {
const data = await company.select(companyId);
if (data.user) setUser(data.user);
setCompanyReady(true);
}, []);
const createCompany = useCallback(async ({ name, iso639, taxrate, reducedtaxrate }) => {
const data = await company.create({ name, iso639, taxrate, reducedtaxrate });
if (data.user) {
setUser(data.user);
setCompanyReady(true);
return;
}
if (data.company?.id) {
const selected = await company.select(data.company.id);
if (selected.user) setUser(selected.user);
setCompanyReady(true);
}
}, []);
const logout = useCallback(async () => {
await auth.logout();
setUser(null);
setStatus('anon');
setCompanyReady(false);
window.history.replaceState(null, '', '/');
}, []);
const value = useMemo(
() => ({
user,
googleClientId,
status,
loginWithGoogle,
logout,
selectCompany,
createCompany,
needsCompanySetup: status === 'authed' && !companyReady,
}),
[
user,
googleClientId,
status,
companyReady,
loginWithGoogle,
logout,
selectCompany,
createCompany,
],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

View File

@@ -1,3 +0,0 @@
import { createContext } from 'react';
export const AuthContext = createContext(null);

View File

@@ -1,8 +0,0 @@
import { useContext } from 'react';
import { AuthContext } from './context';
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within an AuthProvider');
return ctx;
}