diff --git a/client/src/components/app.js b/client/src/components/app.js
index 57442ee4..59375e18 100644
--- a/client/src/components/app.js
+++ b/client/src/components/app.js
@@ -10,6 +10,7 @@ import Legend from "./continuousLegend";
import Graph from "./graph/graph";
import MenuBar from "./menubar";
import Autosave from "./autosave";
+import TermsOfServicePrompt from "./termsPrompt";
import actions from "../actions";
@@ -93,6 +94,7 @@ class App extends React.Component {
{loading ? null : }
{loading ? null : }
{loading ? null : }
+ {loading ? null : }
diff --git a/client/src/components/framework/toasters.js b/client/src/components/framework/toasters.js
index 52964d29..8466ead4 100644
--- a/client/src/components/framework/toasters.js
+++ b/client/src/components/framework/toasters.js
@@ -7,6 +7,11 @@ const ToastTopCenter = Toaster.create({
position: Position.TOP
});
+const ToastBottomCenter = Toaster.create({
+ className: "recipe-toaster",
+ position: Position.BOTTOM
+});
+
/*
A "user" error - eg, bad input
*/
@@ -46,3 +51,11 @@ export const postAsyncFailureToast = message =>
timeout: 10000,
intent: Intent.WARNING
});
+
+export const termsOfServiceToast = (message, _onDismiss) =>
+ ToastBottomCenter.show({
+ message,
+ timeout: 0,
+ intent: Intent.PRIMARY,
+ onDismiss: _onDismiss
+ });
diff --git a/client/src/components/termsPrompt/index.js b/client/src/components/termsPrompt/index.js
new file mode 100644
index 00000000..992403ed
--- /dev/null
+++ b/client/src/components/termsPrompt/index.js
@@ -0,0 +1,76 @@
+import React from "react";
+import { connect } from "react-redux";
+import * as globals from "../../globals";
+import { termsOfServiceToast } from "../framework/toasters";
+
+const TosDismissedKey = "cxg.tosDismissed";
+
+function storageGet(key, defaultValue = null) {
+ try {
+ const val = window.localStorage.getItem(key);
+ if (val === null) return defaultValue;
+ return val;
+ } catch (e) {
+ return defaultValue;
+ }
+}
+
+function storageSet(key, value) {
+ try {
+ window.localStorage.setItem(key, value);
+ } catch {
+ return;
+ }
+}
+
+@connect(state => ({
+ tosURL: state.config?.parameters?.about_legal_tos
+}))
+class TermsPrompt extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ hasDismissed: storageGet(TosDismissedKey, false)
+ };
+ }
+
+ componentDidMount() {
+ const { hasDismissed } = this.state;
+ const { tosURL } = this.props;
+ if (!hasDismissed && tosURL) {
+ this.popTermsToast();
+ }
+ }
+
+ onTermsToastDismissed = () => {
+ this.setState({ hasDismissed: "yes" });
+ storageSet(TosDismissedKey, "yes");
+ };
+
+ popTermsToast() {
+ const { tosURL } = this.props;
+ termsOfServiceToast(
+
+ By using our site, you are agreeing to our{" "}
+
+ Terms of Service
+
+ ,
+ this.onTermsToastDismissed
+ );
+ }
+
+ render() {
+ return null;
+ }
+}
+
+export default TermsPrompt;