diff --git a/web/app.react.js b/web/app.react.js
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -42,6 +42,7 @@
import DisconnectedBar from './redux/disconnected-bar';
import DisconnectedBarVisibilityHandler from './redux/disconnected-bar-visibility-handler';
import FocusHandler from './redux/focus-handler.react';
+import PolicyAcknowledgmentHandler from './redux/policy-acknowledgment-handler.js';
import { useSelector } from './redux/redux-utils';
import VisibilityHandler from './redux/visibility-handler.react';
import history from './router-history';
@@ -155,6 +156,7 @@
+
{content}
{this.props.modals}
diff --git a/web/redux/policy-acknowledgment-handler.js b/web/redux/policy-acknowledgment-handler.js
new file mode 100644
--- /dev/null
+++ b/web/redux/policy-acknowledgment-handler.js
@@ -0,0 +1,41 @@
+// @flow
+
+import * as React from 'react';
+
+import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { policyTypes } from 'lib/facts/policies.js';
+
+import TermsAndPrivacyModal from '../modals/terms-and-privacy-modal.react.js';
+import { useSelector } from './redux-utils.js';
+
+function PolicyAcknowledgmentHandler(): null {
+ const termsAndPrivacyState = useSelector(
+ state => state.userPolicies?.[policyTypes.tosAndPrivacyPolicy],
+ );
+ const [policyModalKey, setPolicyModalKey] = React.useState(null);
+ const { pushModal, popModal, modals } = useModalContext();
+
+ const policyModalVisible = React.useMemo(
+ () => modals.some(modalEntry => modalEntry[1] === policyModalKey),
+ [modals, policyModalKey],
+ );
+
+ React.useEffect(() => {
+ if (!termsAndPrivacyState) {
+ return;
+ }
+
+ const { isAcknowledged } = termsAndPrivacyState;
+ if (!isAcknowledged && !policyModalVisible) {
+ const modalKey = pushModal();
+ setPolicyModalKey(modalKey);
+ }
+ if (isAcknowledged && policyModalVisible) {
+ popModal();
+ }
+ }, [policyModalVisible, popModal, pushModal, termsAndPrivacyState]);
+
+ return null;
+}
+
+export default PolicyAcknowledgmentHandler;