mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-09 21:30:55 +08:00
thuang-1840-authn-prompt (#1911)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnchorButton,
|
AnchorButton,
|
||||||
Button,
|
Button,
|
||||||
@@ -6,20 +7,36 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Popover,
|
Popover,
|
||||||
Menu,
|
Menu,
|
||||||
|
Elevation,
|
||||||
|
PopoverPosition,
|
||||||
|
Checkbox,
|
||||||
|
Card,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
|
||||||
import { IconNames } from "@blueprintjs/icons";
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
|
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
import styles from "./menubar.css";
|
import styles from "./menubar.css";
|
||||||
|
|
||||||
|
import { storageGet, storageSet, KEYS } from "../util/localStorage";
|
||||||
|
|
||||||
const BASE_EMOJI = [0x1f9d1, 0x1f468, 0x1f469];
|
const BASE_EMOJI = [0x1f9d1, 0x1f468, 0x1f469];
|
||||||
const SKIN_TONES = [0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff];
|
const SKIN_TONES = [0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff];
|
||||||
const MICROSCOPE = 0x1f52c;
|
const MICROSCOPE = 0x1f52c;
|
||||||
const ZERO_WIDTH_JOINER = 0x0200d;
|
const ZERO_WIDTH_JOINER = 0x0200d;
|
||||||
|
|
||||||
|
const LOGIN_PROMPT_OFF = "off";
|
||||||
|
|
||||||
const Auth = React.memo((props) => {
|
const Auth = React.memo((props) => {
|
||||||
|
const [isPromptOpen, setIsPromptOpen] = useState(shouldShowPrompt());
|
||||||
|
|
||||||
const { auth, userinfo } = props;
|
const { auth, userinfo } = props;
|
||||||
|
|
||||||
|
const isAuthenticated = userinfo && userinfo.is_authenticated;
|
||||||
|
|
||||||
|
window.userinfo = userinfo;
|
||||||
|
|
||||||
const randomInt = Math.random() * 15;
|
const randomInt = Math.random() * 15;
|
||||||
const sexIndex = Math.floor(randomInt / 5);
|
const sexIndex = Math.floor(randomInt / 5);
|
||||||
const skinToneIndex = Math.floor(randomInt % 5);
|
const skinToneIndex = Math.floor(randomInt % 5);
|
||||||
@@ -31,9 +48,9 @@ const Auth = React.memo((props) => {
|
|||||||
MICROSCOPE
|
MICROSCOPE
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!auth?.["requires_client_login"]) return null;
|
if (!shouldShowAuth()) return null;
|
||||||
|
|
||||||
if (userinfo?.["is_authenticated"]) {
|
if (isAuthenticated) {
|
||||||
const PopoverContent = (
|
const PopoverContent = (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -67,7 +84,7 @@ const Auth = React.memo((props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const LoginButton = (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content="Log in to cellxgene"
|
content="Log in to cellxgene"
|
||||||
position="bottom"
|
position="bottom"
|
||||||
@@ -76,7 +93,6 @@ const Auth = React.memo((props) => {
|
|||||||
<AnchorButton
|
<AnchorButton
|
||||||
type="button"
|
type="button"
|
||||||
data-testid="log-in"
|
data-testid="log-in"
|
||||||
disabled={false}
|
|
||||||
href={auth.login}
|
href={auth.login}
|
||||||
className={styles.menubarButton}
|
className={styles.menubarButton}
|
||||||
>
|
>
|
||||||
@@ -84,6 +100,76 @@ const Auth = React.memo((props) => {
|
|||||||
</AnchorButton>
|
</AnchorButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isPromptOpen) {
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
position={PopoverPosition.AUTO_END}
|
||||||
|
isOpen
|
||||||
|
content={<PromptContent setIsPromptOpen={setIsPromptOpen} />}
|
||||||
|
onInteraction={setIsPromptOpen}
|
||||||
|
>
|
||||||
|
{LoginButton}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoginButton;
|
||||||
|
|
||||||
|
function shouldShowAuth() {
|
||||||
|
return auth && auth.requires_client_login;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldShowPrompt() {
|
||||||
|
if (storageGet(KEYS.LOGIN_PROMPT) === LOGIN_PROMPT_OFF) return false;
|
||||||
|
|
||||||
|
return shouldShowAuth && !isAuthenticated;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function PromptContent({ setIsPromptOpen }) {
|
||||||
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
|
|
||||||
|
function handleOKClick() {
|
||||||
|
if (isChecked) {
|
||||||
|
storageSet(KEYS.LOGIN_PROMPT, LOGIN_PROMPT_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPromptOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckboxChange() {
|
||||||
|
setIsChecked(!isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ width: "500px" }} elevation={Elevation.TWO}>
|
||||||
|
<p>
|
||||||
|
Logging in will enable you to create your own categories and labels.
|
||||||
|
Logging in later will reset cellxgene to the default view and cause you
|
||||||
|
to lose progress.
|
||||||
|
</p>
|
||||||
|
<Checkbox
|
||||||
|
style={{ width: "230px" }}
|
||||||
|
checked={isChecked}
|
||||||
|
onChange={handleCheckboxChange}
|
||||||
|
data-testid="login-hint-do-not-show-again"
|
||||||
|
>
|
||||||
|
Do not show me this message again
|
||||||
|
</Checkbox>
|
||||||
|
<div
|
||||||
|
style={{ display: "flex", justifyContent: "flex-end", marginTop: 15 }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={handleOKClick}
|
||||||
|
intent="primary"
|
||||||
|
data-testid="login-hint-yes"
|
||||||
|
>
|
||||||
|
Acknowledge
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default Auth;
|
export default Auth;
|
||||||
|
|||||||
@@ -8,26 +8,7 @@ import {
|
|||||||
Colors,
|
Colors,
|
||||||
Icon,
|
Icon,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
import { storageGet, storageSet, KEYS } from "../util/localStorage";
|
||||||
const CookieDecision = "cxg.cookieDecision";
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
tosURL: state.config?.parameters?.["about_legal_tos"],
|
tosURL: state.config?.parameters?.["about_legal_tos"],
|
||||||
@@ -37,7 +18,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const { tosURL, privacyURL } = this.props;
|
const { tosURL, privacyURL } = this.props;
|
||||||
const cookieDecision = storageGet(CookieDecision, null);
|
const cookieDecision = storageGet(KEYS.COOKIE_DECISION, null);
|
||||||
const hasDecided = cookieDecision !== null;
|
const hasDecided = cookieDecision !== null;
|
||||||
this.state = {
|
this.state = {
|
||||||
hasDecided,
|
hasDecided,
|
||||||
@@ -55,7 +36,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
|
|
||||||
handleOK = () => {
|
handleOK = () => {
|
||||||
this.setState({ isOpen: false });
|
this.setState({ isOpen: false });
|
||||||
storageSet(CookieDecision, "yes");
|
storageSet(KEYS.COOKIE_DECISION, "yes");
|
||||||
if (window.cookieDecisionCallback instanceof Function) {
|
if (window.cookieDecisionCallback instanceof Function) {
|
||||||
try {
|
try {
|
||||||
window.cookieDecisionCallback();
|
window.cookieDecisionCallback();
|
||||||
@@ -67,7 +48,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
|
|
||||||
handleNo = () => {
|
handleNo = () => {
|
||||||
this.setState({ isOpen: false });
|
this.setState({ isOpen: false });
|
||||||
storageSet(CookieDecision, "no");
|
storageSet(KEYS.COOKIE_DECISION, "no");
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTos() {
|
renderTos() {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export const KEYS = {
|
||||||
|
COOKIE_DECISION: "cxg.cookieDecision",
|
||||||
|
LOGIN_PROMPT: "cxg.LOGIN_PROMPT",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function storageGet(key, defaultValue = null) {
|
||||||
|
try {
|
||||||
|
const val = window.localStorage.getItem(key);
|
||||||
|
if (val === null) return defaultValue;
|
||||||
|
return val;
|
||||||
|
} catch (e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function storageSet(key, value) {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(key, value);
|
||||||
|
} catch {
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user