mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-22 07:28:11 +08:00
Revised terms and privacy consent dialog, analytics hooks (#1426)
* revised terms and privacy consent * reorg code * fix conditional * Overlay reflects un-dissmissable state * add inline scripts, and consent callback * add csp_directive config hook * revert config.yaml * fix logic error Co-authored-by: Colin Megill <colinmegill@gmail.com>
This commit is contained in:
co-authored by
Colin Megill
parent
666e6d9849
commit
05fcdaf93c
@@ -4,58 +4,50 @@ import { Position, Toaster, Intent } from "@blueprintjs/core";
|
||||
|
||||
const ToastTopCenter = Toaster.create({
|
||||
className: "recipe-toaster",
|
||||
position: Position.TOP
|
||||
position: Position.TOP,
|
||||
});
|
||||
|
||||
const ToastBottomCenter = Toaster.create({
|
||||
className: "recipe-toaster",
|
||||
position: Position.BOTTOM
|
||||
position: Position.BOTTOM,
|
||||
});
|
||||
|
||||
/*
|
||||
A "user" error - eg, bad input
|
||||
*/
|
||||
export const postUserErrorToast = message =>
|
||||
export const postUserErrorToast = (message) =>
|
||||
ToastTopCenter.show({ message, intent: Intent.WARNING });
|
||||
|
||||
/*
|
||||
A toast the user must dismiss manually, because they need to act on its information,
|
||||
ie., 8 bulk add genes out of 40 were bad. Manually see which ones and fix.
|
||||
*/
|
||||
export const keepAroundErrorToast = message =>
|
||||
export const keepAroundErrorToast = (message) =>
|
||||
ToastTopCenter.show({ message, timeout: 0, intent: Intent.WARNING });
|
||||
|
||||
/*
|
||||
a hard network error
|
||||
*/
|
||||
export const postNetworkErrorToast = message =>
|
||||
export const postNetworkErrorToast = (message) =>
|
||||
ToastTopCenter.show({
|
||||
message,
|
||||
timeout: 30000,
|
||||
intent: Intent.DANGER
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
|
||||
/*
|
||||
Async message to user
|
||||
*/
|
||||
export const postAsyncSuccessToast = message =>
|
||||
export const postAsyncSuccessToast = (message) =>
|
||||
ToastTopCenter.show({
|
||||
message,
|
||||
timeout: 10000,
|
||||
intent: Intent.SUCCESS
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
|
||||
export const postAsyncFailureToast = message =>
|
||||
export const postAsyncFailureToast = (message) =>
|
||||
ToastTopCenter.show({
|
||||
message,
|
||||
timeout: 10000,
|
||||
intent: Intent.WARNING
|
||||
});
|
||||
|
||||
export const termsOfServiceToast = (message, _onDismiss) =>
|
||||
ToastBottomCenter.show({
|
||||
message,
|
||||
timeout: 0,
|
||||
intent: Intent.PRIMARY,
|
||||
onDismiss: _onDismiss
|
||||
intent: Intent.WARNING,
|
||||
});
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Drawer,
|
||||
Button,
|
||||
Classes,
|
||||
Position,
|
||||
Colors,
|
||||
Icon,
|
||||
} from "@blueprintjs/core";
|
||||
import * as globals from "../../globals";
|
||||
import { termsOfServiceToast } from "../framework/toasters";
|
||||
|
||||
const TosDismissedKey = "cxg.tosDismissed";
|
||||
const CookieDecision = "cxg.cookieDecision";
|
||||
|
||||
function storageGet(key, defaultValue = null) {
|
||||
try {
|
||||
@@ -23,53 +30,129 @@ function storageSet(key, value) {
|
||||
}
|
||||
}
|
||||
|
||||
@connect(state => ({
|
||||
tosURL: state.config?.parameters?.about_legal_tos
|
||||
@connect((state) => ({
|
||||
tosURL: state.config?.parameters?.about_legal_tos,
|
||||
privacyURL: state.config?.parameters?.about_legal_privacy,
|
||||
}))
|
||||
class TermsPrompt extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { tosURL, privacyURL } = this.props;
|
||||
const cookieDecision = storageGet(CookieDecision, null);
|
||||
const hasDecided = cookieDecision !== null;
|
||||
this.state = {
|
||||
hasDismissed: storageGet(TosDismissedKey, false)
|
||||
hasDecided,
|
||||
isEnabled: !!tosURL || !!privacyURL,
|
||||
isOpen: !hasDecided,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { hasDismissed } = this.state;
|
||||
const { tosURL } = this.props;
|
||||
if (!hasDismissed && tosURL) {
|
||||
this.popTermsToast();
|
||||
const { hasDecided, isEnabled } = this.state;
|
||||
if (isEnabled && !hasDecided) {
|
||||
this.setState({ isOpen: true });
|
||||
}
|
||||
}
|
||||
|
||||
onTermsToastDismissed = () => {
|
||||
this.setState({ hasDismissed: "yes" });
|
||||
storageSet(TosDismissedKey, "yes");
|
||||
handleOK = () => {
|
||||
this.setState({ isOpen: false });
|
||||
storageSet(CookieDecision, "yes");
|
||||
if (window.cookieDecisionCallback instanceof Function) {
|
||||
try {
|
||||
window.cookieDecisionCallback();
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
popTermsToast() {
|
||||
handleNo = () => {
|
||||
this.setState({ isOpen: false });
|
||||
storageSet(CookieDecision, "no");
|
||||
};
|
||||
|
||||
renderTos() {
|
||||
const { tosURL } = this.props;
|
||||
termsOfServiceToast(
|
||||
if (!tosURL) return null;
|
||||
return (
|
||||
<span>
|
||||
By using our site, you are agreeing to our{" "}
|
||||
<Icon icon="info-sign" intent="primary" /> By using this site, you are
|
||||
agreeing to our{" "}
|
||||
<a
|
||||
style={{
|
||||
fontWeight: 700,
|
||||
color: "white",
|
||||
textDecoration: "underline"
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
href={tosURL}
|
||||
target="_blank"
|
||||
>
|
||||
Terms of Service
|
||||
terms of service
|
||||
</a>
|
||||
</span>,
|
||||
this.onTermsToastDismissed
|
||||
.{" "}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderPrivacy() {
|
||||
const { privacyURL } = this.props;
|
||||
if (!privacyURL) return null;
|
||||
return (
|
||||
<span>
|
||||
To learn more, read our{" "}
|
||||
<a
|
||||
style={{
|
||||
fontWeight: 700,
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
href={privacyURL}
|
||||
target="_blank"
|
||||
>
|
||||
privacy policy
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
const { isOpen, isEnabled } = this.state;
|
||||
if (!isEnabled || !isOpen) return null;
|
||||
return (
|
||||
<Drawer
|
||||
onclose={this.drawerClose}
|
||||
isOpen={isOpen}
|
||||
size={"120px"}
|
||||
position={Position.BOTTOM}
|
||||
canOutsideClickClose={false}
|
||||
hasBackdrop={
|
||||
true /* if the user can't use the app or click outside to dismiss, that should be visually represented with a backdrop */
|
||||
}
|
||||
enforceFocus={false}
|
||||
autoFocus={false}
|
||||
portal={false}
|
||||
>
|
||||
<div
|
||||
className={Classes.DRAWER_BODY}
|
||||
style={{ backgroundColor: Colors.LIGHT_GRAY1 }}
|
||||
>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<div>
|
||||
{this.renderTos()}
|
||||
<span>
|
||||
We use cookies to help us improve the site and to inform our
|
||||
future efforts, and we also use necessary cookies to make our
|
||||
site work.
|
||||
</span>
|
||||
{this.renderPrivacy()}
|
||||
</div>
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER} style={{ textAlign: "left" }}>
|
||||
<Button intent="primary" onClick={this.handleOK}>
|
||||
I'm OK with cookies!
|
||||
</Button>{" "}
|
||||
<Button onClick={this.handleNo}>No thanks</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user