u
This commit is contained in:
@@ -2,22 +2,24 @@ import { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { notifyError } from '../utils/notify';
|
||||
import { StorageManagerContext } from '../api/storageManager';
|
||||
import { bootstrapSession as bootstrapSessionThunk } from '../store/authSlice';
|
||||
|
||||
let sessionBootstrapped = false;
|
||||
|
||||
class AuthBootstrap extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
componentDidMount() {
|
||||
if (sessionBootstrapped) return;
|
||||
sessionBootstrapped = true;
|
||||
|
||||
this.props
|
||||
.bootstrapSession()
|
||||
.unwrap()
|
||||
this.context.authMePromise
|
||||
.then((session) => {
|
||||
if (session.forbidden) {
|
||||
enqueueSnackbar(session.message, { variant: 'warning' });
|
||||
}
|
||||
return this.props.bootstrapSession(session).unwrap();
|
||||
})
|
||||
.catch((err) => {
|
||||
notifyError(err);
|
||||
@@ -30,7 +32,7 @@ class AuthBootstrap extends Component {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
bootstrapSession: () => dispatch(bootstrapSessionThunk()),
|
||||
bootstrapSession: (session) => dispatch(bootstrapSessionThunk(session)),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(AuthBootstrap);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
@@ -27,20 +26,12 @@ import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from './BackendError';
|
||||
import SingleLineTextEdit from './SingleLineTextEdit';
|
||||
import { StorageManagerContext } from '../api/storageManager';
|
||||
import { selectAuthUser } from '../store/authSlice';
|
||||
import { updateCompany as updateCompanyThunk } from '../store/companySlice';
|
||||
import {
|
||||
createPosterminal,
|
||||
deletePosterminal,
|
||||
enableAllTerminalCategories,
|
||||
} from '../store/posterminalSlice';
|
||||
import { loadCategoryTree } from '../store/categorySlice';
|
||||
import { requireDefined } from '../utils/apiShape';
|
||||
import { requireDefined, requireObject } from '../utils/apiShape';
|
||||
import { ISO639_OPTIONS, parseTaxRate } from '../utils/companyForm';
|
||||
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
|
||||
import { notifyError } from '../utils/notify';
|
||||
|
||||
class CompanySettingsDialog extends Component {
|
||||
export default class CompanySettingsDialog extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
@@ -74,27 +65,25 @@ class CompanySettingsDialog extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
syncFormFromCompany(company) {
|
||||
syncFormFromCompany(companyRow) {
|
||||
this.setState({
|
||||
name: company.name,
|
||||
iso639: company.iso639,
|
||||
taxrate: company.taxrate != null ? String(company.taxrate) : '',
|
||||
reducedtaxrate: company.reducedtaxrate != null ? String(company.reducedtaxrate) : '',
|
||||
name: companyRow.name,
|
||||
iso639: companyRow.iso639,
|
||||
taxrate: companyRow.taxrate != null ? String(companyRow.taxrate) : '',
|
||||
reducedtaxrate: companyRow.reducedtaxrate != null ? String(companyRow.reducedtaxrate) : '',
|
||||
});
|
||||
}
|
||||
|
||||
async loadCompany() {
|
||||
try {
|
||||
const companyId = requireDefined(
|
||||
this.props.selectedCompanyId,
|
||||
'selectedCompanyId',
|
||||
);
|
||||
const session = await this.context.authMePromise;
|
||||
const companyId = requireDefined(session.user?.selectedCompanyId, 'selectedCompanyId');
|
||||
const companies = await this.context.companyPromise;
|
||||
const company = requireDefined(
|
||||
const companyRow = requireDefined(
|
||||
companies.find((c) => c.id === companyId),
|
||||
'company',
|
||||
);
|
||||
this.syncFormFromCompany(company);
|
||||
this.syncFormFromCompany(companyRow);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
}
|
||||
@@ -115,13 +104,14 @@ class CompanySettingsDialog extends Component {
|
||||
|
||||
this.setState({ creatingTerminal: true });
|
||||
try {
|
||||
const terminal = await this.props.createPosterminal({ name: trimmed }).unwrap();
|
||||
const data = await this.context.posterminal.upsert({ name: trimmed });
|
||||
requireObject(data, 'PUT /api/posterminal');
|
||||
requireObject(data.posterminal, 'posterminal');
|
||||
requireDefined(data.posterminal.id, 'posterminal.id');
|
||||
|
||||
// If "enable all" toggle is on, enable all categories for this terminal
|
||||
if (enableAllForNew && terminal.id) {
|
||||
await this.props.enableAllTerminalCategories({ terminalId: terminal.id }).unwrap();
|
||||
// Reload category tree so tiles reflect the updated posterminalIds
|
||||
this.props.loadCategoryTree();
|
||||
if (enableAllForNew) {
|
||||
await this.context.posterminal.setOnAll(data.posterminal.id);
|
||||
await this.context.updateCategoryTree();
|
||||
}
|
||||
|
||||
const terminals = await this.context.updatePosterminal();
|
||||
@@ -135,7 +125,7 @@ class CompanySettingsDialog extends Component {
|
||||
|
||||
async handleDeleteTerminal(terminal) {
|
||||
try {
|
||||
await this.props.deletePosterminal({ terminalId: terminal.id }).unwrap();
|
||||
await this.context.posterminal.delete(terminal.id);
|
||||
const terminals = await this.context.updatePosterminal();
|
||||
this.setState({ terminals });
|
||||
} catch (err) {
|
||||
@@ -162,14 +152,14 @@ class CompanySettingsDialog extends Component {
|
||||
|
||||
this.setState({ busy: true, error: null });
|
||||
try {
|
||||
await this.props
|
||||
.saveCompany({
|
||||
name: name.trim(),
|
||||
iso639,
|
||||
taxrate: parsedTaxrate,
|
||||
reducedtaxrate: parsedReducedtaxrate,
|
||||
})
|
||||
.unwrap();
|
||||
const session = await this.context.authMePromise;
|
||||
const companyId = requireDefined(session.user?.selectedCompanyId, 'selectedCompanyId');
|
||||
await this.context.company.update(companyId, {
|
||||
name: name.trim(),
|
||||
iso639,
|
||||
taxrate: parsedTaxrate,
|
||||
reducedtaxrate: parsedReducedtaxrate,
|
||||
});
|
||||
await this.context.updateCompany();
|
||||
enqueueSnackbar('Unternehmen gespeichert.', { variant: 'success' });
|
||||
onClose();
|
||||
@@ -350,17 +340,3 @@ class CompanySettingsDialog extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
selectedCompanyId: selectAuthUser(state)?.selectedCompanyId,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
saveCompany: (payload) => dispatch(updateCompanyThunk(payload)),
|
||||
createPosterminal: (payload) => dispatch(createPosterminal(payload)),
|
||||
deletePosterminal: (payload) => dispatch(deletePosterminal(payload)),
|
||||
enableAllTerminalCategories: (payload) => dispatch(enableAllTerminalCategories(payload)),
|
||||
loadCategoryTree: () => dispatch(loadCategoryTree()),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CompanySettingsDialog);
|
||||
|
||||
@@ -22,6 +22,7 @@ import Typography from '@mui/material/Typography';
|
||||
import BusinessIcon from '@mui/icons-material/Business';
|
||||
import BackendError from './BackendError';
|
||||
import { company } from '../api';
|
||||
import { StorageManagerContext } from '../api/storageManager';
|
||||
import {
|
||||
createCompany as createCompanyThunk,
|
||||
logout as logoutThunk,
|
||||
@@ -35,6 +36,8 @@ import {
|
||||
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
|
||||
|
||||
class CompanySetupDialog extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -73,6 +76,7 @@ class CompanySetupDialog extends Component {
|
||||
this.setState({ busy: true, error: null });
|
||||
try {
|
||||
await this.props.selectCompany(companyId).unwrap();
|
||||
await this.context.updateAuthMe();
|
||||
} catch (err) {
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
} finally {
|
||||
@@ -106,6 +110,8 @@ class CompanySetupDialog extends Component {
|
||||
reducedtaxrate: parsedReducedtaxrate,
|
||||
})
|
||||
.unwrap();
|
||||
await this.context.updateAuthMe();
|
||||
await this.context.updateCompany();
|
||||
} catch (err) {
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
} finally {
|
||||
@@ -113,8 +119,13 @@ class CompanySetupDialog extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleLogout() {
|
||||
void this.props.logout();
|
||||
async handleLogout() {
|
||||
try {
|
||||
await this.props.logout().unwrap();
|
||||
await this.context.updateAuthMe();
|
||||
} catch (err) {
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
}
|
||||
}
|
||||
|
||||
handleClose(_event, reason) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import GoogleIcon from '@mui/icons-material/Google';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { notifyErrorMessage } from '../utils/notify';
|
||||
import { loginWithGoogle, selectAuthStatus, selectGoogleClientId } from '../store/authSlice';
|
||||
import { StorageManagerContext } from '../api/storageManager';
|
||||
import {
|
||||
abortGoogleSignIn,
|
||||
FEDCM_BLOCKED_MESSAGE,
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
} from '../auth/googleSignIn';
|
||||
|
||||
class LoginPage extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { busy: false };
|
||||
@@ -36,6 +39,7 @@ class LoginPage extends Component {
|
||||
});
|
||||
this.setState({ busy: true });
|
||||
await this.props.loginWithGoogle(idToken).unwrap();
|
||||
await this.context.updateAuthMe();
|
||||
} catch (err) {
|
||||
if (err instanceof GoogleSignInCancelledError) return;
|
||||
logError('[login] Google sign-in failed', {
|
||||
|
||||
@@ -9,15 +9,20 @@ import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import BusinessIcon from '@mui/icons-material/Business';
|
||||
import { logout, selectAuthUser } from '../store/authSlice';
|
||||
import { StorageManagerContext } from '../api/storageManager';
|
||||
import { logout } from '../store/authSlice';
|
||||
import CompanySettingsDialog from './CompanySettingsDialog';
|
||||
import { notifyError } from '../utils/notify';
|
||||
|
||||
class UserMenu extends Component {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
anchorEl: null,
|
||||
settingsOpen: false,
|
||||
user: null,
|
||||
};
|
||||
this.handleOpenMenu = this.handleOpenMenu.bind(this);
|
||||
this.handleCloseMenu = this.handleCloseMenu.bind(this);
|
||||
@@ -26,6 +31,19 @@ class UserMenu extends Component {
|
||||
this.handleLogout = this.handleLogout.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
void this.loadUser();
|
||||
}
|
||||
|
||||
async loadUser() {
|
||||
try {
|
||||
const session = await this.context.authMePromise;
|
||||
this.setState({ user: session.user });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
}
|
||||
}
|
||||
|
||||
handleOpenMenu(event) {
|
||||
this.setState({ anchorEl: event.currentTarget });
|
||||
}
|
||||
@@ -42,14 +60,18 @@ class UserMenu extends Component {
|
||||
this.setState({ settingsOpen: false });
|
||||
}
|
||||
|
||||
handleLogout() {
|
||||
async handleLogout() {
|
||||
this.setState({ anchorEl: null });
|
||||
void this.props.logout();
|
||||
try {
|
||||
await this.props.logout().unwrap();
|
||||
await this.context.updateAuthMe();
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
const { anchorEl, settingsOpen } = this.state;
|
||||
const { user, anchorEl, settingsOpen } = this.state;
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
return (
|
||||
@@ -92,12 +114,8 @@ class UserMenu extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
user: selectAuthUser(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
logout: () => dispatch(logout()),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserMenu);
|
||||
export default connect(null, mapDispatchToProps)(UserMenu);
|
||||
|
||||
Reference in New Issue
Block a user