u
This commit is contained in:
@@ -8,12 +8,14 @@ export const auth = {
|
||||
return {
|
||||
user: data.user ?? null,
|
||||
googleClientId: data.googleClientId ?? null,
|
||||
schemaVersion: data.schemaVersion ?? null,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.status === 401 || err.status === 403) {
|
||||
return {
|
||||
user: null,
|
||||
googleClientId: err.data?.googleClientId ?? null,
|
||||
schemaVersion: err.data?.schemaVersion ?? null,
|
||||
forbidden: err.status === 403,
|
||||
message: err.message,
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ import { auth, company, number as numberApi } from '../api';
|
||||
* @typedef {Object} AuthState
|
||||
* @property {AuthUser|null} user Authenticated user, or `null` for anonymous sessions.
|
||||
* @property {string|null} googleClientId Google Identity Services client ID.
|
||||
* @property {number|null} schemaVersion Database schema version returned by the backend.
|
||||
* @property {'loading'|'anon'|'authed'} status Current authentication lifecycle state.
|
||||
* @property {boolean} hasAuth True when the session has a valid authenticated user.
|
||||
* @property {boolean} hasCompany True when `hasAuth` is true AND a company is selected.
|
||||
@@ -47,6 +48,7 @@ export const AUTH_LOGGED_OUT = 'auth/loggedOut';
|
||||
const initialState = {
|
||||
user: null,
|
||||
googleClientId: null,
|
||||
schemaVersion: null,
|
||||
status: 'loading',
|
||||
hasAuth: false,
|
||||
hasCompany: false,
|
||||
@@ -137,22 +139,26 @@ function syncSse(state) {
|
||||
function applySession(nextSession) {
|
||||
const googleClientId =
|
||||
nextSession.googleClientId ?? authAutomat.state.googleClientId;
|
||||
const schemaVersion =
|
||||
nextSession.schemaVersion !== undefined
|
||||
? nextSession.schemaVersion
|
||||
: (authAutomat.state.schemaVersion ?? null);
|
||||
|
||||
if (!nextSession.user) {
|
||||
if (authAutomat.state.status !== 'authed') {
|
||||
const nextState = authAutomat.setState(
|
||||
buildAuthState(null, 'anon', { googleClientId, retryNotification: null })
|
||||
buildAuthState(null, 'anon', { googleClientId, schemaVersion, retryNotification: null })
|
||||
);
|
||||
syncSse(nextState);
|
||||
} else {
|
||||
authAutomat.setState({ googleClientId, retryNotification: null });
|
||||
authAutomat.setState({ googleClientId, schemaVersion, retryNotification: null });
|
||||
}
|
||||
return nextSession;
|
||||
}
|
||||
|
||||
const user = mergeUser(authAutomat.state.user, nextSession.user);
|
||||
const nextState = authAutomat.setState(
|
||||
buildAuthState(user, 'authed', { googleClientId, retryNotification: null })
|
||||
buildAuthState(user, 'authed', { googleClientId, schemaVersion, retryNotification: null })
|
||||
);
|
||||
syncSse(nextState);
|
||||
emit({ type: AUTH_SESSION_ESTABLISHED });
|
||||
@@ -234,6 +240,7 @@ const actions = {
|
||||
const nextState = authAutomat.setState(
|
||||
buildAuthState(user, 'authed', {
|
||||
googleClientId: session.googleClientId ?? authAutomat.state.googleClientId,
|
||||
schemaVersion: session.schemaVersion ?? authAutomat.state.schemaVersion ?? null,
|
||||
})
|
||||
);
|
||||
syncSse(nextState);
|
||||
@@ -344,6 +351,7 @@ export const selectHasCompany = () => Boolean(authAutomat.state.hasCompany);
|
||||
export const selectSelectedCompanyId = () =>
|
||||
authAutomat.state.hasCompany ? authAutomat.state.user?.selectedCompanyId ?? null : null;
|
||||
export const selectRetryNotification = () => authAutomat.state.retryNotification;
|
||||
export const selectSchemaVersion = () => authAutomat.state.schemaVersion;
|
||||
|
||||
/**
|
||||
* Re-reads `GET /api/auth/me` from the server.
|
||||
@@ -351,7 +359,7 @@ export const selectRetryNotification = () => authAutomat.state.retryNotification
|
||||
* Called after login, logout, and company switches so the automat reflects
|
||||
* the current server session rather than an optimistic guess.
|
||||
*
|
||||
* @returns {Promise<{user: AuthUser|null, googleClientId: string|null}>} Session.
|
||||
* @returns {Promise<{user: AuthUser|null, googleClientId: string|null, schemaVersion?: number|null}>} Session.
|
||||
*/
|
||||
export function bootstrapAuthMe() {
|
||||
return authAutomat.actions.bootstrapSession();
|
||||
|
||||
@@ -13,6 +13,8 @@ import PointOfSaleIcon from '@mui/icons-material/PointOfSale';
|
||||
import ImportExportIcon from '@mui/icons-material/ImportExport';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import authAutomat from '../../automats/authAutomat';
|
||||
import CompanySettingsPanel from './CompanySettingsPanel';
|
||||
import NumberSettingsPanel from './NumberSettingsPanel';
|
||||
import PrinterSettingsPanel from './PrinterSettingsPanel';
|
||||
@@ -54,15 +56,31 @@ const TABS = [
|
||||
export default class SettingsDialog extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { tab: TAB_KEYS.health };
|
||||
this.state = {
|
||||
tab: TAB_KEYS.health,
|
||||
schemaVersion: authAutomat.state.schemaVersion,
|
||||
};
|
||||
this.handleTabChange = this.handleTabChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubAuth = authAutomat.subscribe(this, ['schemaVersion']);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubAuth?.();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { open } = this.props;
|
||||
if (!open && prevProps.open) {
|
||||
this.setState({ tab: TAB_KEYS.health });
|
||||
}
|
||||
if (open && !prevProps.open) {
|
||||
if (this.state.schemaVersion !== authAutomat.state.schemaVersion) {
|
||||
this.setState({ schemaVersion: authAutomat.state.schemaVersion });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleTabChange(_event, value) {
|
||||
@@ -71,7 +89,7 @@ export default class SettingsDialog extends Component {
|
||||
|
||||
render() {
|
||||
const { open, onClose } = this.props;
|
||||
const { tab } = this.state;
|
||||
const { tab, schemaVersion } = this.state;
|
||||
|
||||
const paperSx = {
|
||||
height: '85vh',
|
||||
@@ -120,6 +138,9 @@ export default class SettingsDialog extends Component {
|
||||
borderColor: 'divider',
|
||||
bgcolor: SURFACE_BASE,
|
||||
py: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
@@ -172,6 +193,13 @@ export default class SettingsDialog extends Component {
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
{schemaVersion != null && (
|
||||
<Box sx={{ px: 2.5, py: 1.5, mt: 'auto', borderTop: '1px solid', borderColor: 'divider' }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', px: 0.5 }}>
|
||||
DB-Version: {schemaVersion}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Right content panel */}
|
||||
|
||||
Reference in New Issue
Block a user