diff --git a/ui/App.tsx b/ui/App.tsx index a0950fcf4b6..e8a76325138 100644 --- a/ui/App.tsx +++ b/ui/App.tsx @@ -14,6 +14,7 @@ import ErrorBoundary from "./components/ErrorBoundary"; import Layout from "./components/Layout"; import AppContextProvider from "./contexts/AppContext"; import AuthContextProvider, { AuthCheck } from "./contexts/AuthContext"; +import FeatureFlagsContextProvider from "./contexts/FeatureFlags"; import { Applications as appsClient, GitProvider, @@ -86,33 +87,13 @@ const App = () => ( ); export default function AppContainer() { - const [authFlag, setAuthFlag] = React.useState(null); - - const getAuthFlag = React.useCallback(() => { - fetch("/v1/featureflags") - .then((response) => response.json()) - .then((data) => - setAuthFlag(data.flags.WEAVE_GITOPS_AUTH_ENABLED === "true") - ) - .catch((err) => console.log(err)); - }, []); - - React.useEffect(() => { - getAuthFlag(); - }, [getAuthFlag]); - - // Loading... - if (authFlag === null) { - return null; - } - return ( - {authFlag ? ( + {/* does not use the base page so pull it up here */} @@ -125,9 +106,7 @@ export default function AppContainer() { - ) : ( - - )} + diff --git a/ui/components/Icon.tsx b/ui/components/Icon.tsx index 03b9a6677f3..5f279f3cf29 100644 --- a/ui/components/Icon.tsx +++ b/ui/components/Icon.tsx @@ -12,6 +12,7 @@ import RemoveCircleIcon from "@material-ui/icons/RemoveCircle"; import SaveAltIcon from "@material-ui/icons/SaveAlt"; import SkipNextIcon from "@material-ui/icons/SkipNext"; import SkipPreviousIcon from "@material-ui/icons/SkipPrevious"; +import LogoutIcon from "@material-ui/icons/ExitToApp"; import * as React from "react"; import styled from "styled-components"; import { colors, spacing } from "../typedefs/styled"; @@ -34,6 +35,7 @@ export enum IconType { SkipNextIcon, SkipPreviousIcon, RemoveCircleIcon, + LogoutIcon, } type Props = { @@ -91,6 +93,9 @@ function getIcon(i: IconType) { case IconType.RemoveCircleIcon: return RemoveCircleIcon; + case IconType.LogoutIcon: + return LogoutIcon; + default: break; } diff --git a/ui/components/Layout.tsx b/ui/components/Layout.tsx index 5a1f07c8243..7bf97ccec71 100644 --- a/ui/components/Layout.tsx +++ b/ui/components/Layout.tsx @@ -5,13 +5,16 @@ import styled from "styled-components"; import useNavigation from "../hooks/navigation"; import { PageRoute } from "../lib/types"; import { formatURL, getNavValue } from "../lib/utils"; +import { FeatureFlags } from "../contexts/FeatureFlags"; import Flex from "./Flex"; import Link from "./Link"; import Logo from "./Logo"; +import UserSettings from "./UserSettings"; type Props = { className?: string; children?: any; + authFlag?: boolean; }; const navItems = [{ value: PageRoute.Applications, label: "Applications" }]; @@ -94,12 +97,8 @@ const TopToolBar = styled(Flex)` } `; -//style for account icon - disabled while no account functionality exists -// const UserAvatar = styled(Icon)` -// padding-right: ${(props) => props.theme.spacing.medium}; -// `; - function Layout({ className, children }: Props) { + const { authFlag } = React.useContext(FeatureFlags); const { currentPage } = useNavigation(); return ( @@ -107,7 +106,7 @@ function Layout({ className, children }: Props) { - {/* code for account icon - disabled while no account functionality exists */} + {authFlag ? : null}
diff --git a/ui/components/UserSettings.tsx b/ui/components/UserSettings.tsx new file mode 100644 index 00000000000..13f1f6bcd1a --- /dev/null +++ b/ui/components/UserSettings.tsx @@ -0,0 +1,68 @@ +import { + IconButton, + ListItemIcon, + Menu, + MenuItem, + Tooltip, +} from "@material-ui/core"; +import * as React from "react"; +import styled from "styled-components"; +import { Auth } from "../contexts/AuthContext"; +import Icon, { IconType } from "./Icon"; + +const UserAvatar = styled(Icon)` + padding-right: ${(props) => props.theme.spacing.medium}; +`; + +const SettingsMenu = styled(Menu)` + .MuiListItemIcon-root { + min-width: 25px; + color: ${(props) => props.theme.colors.black}; + } +`; + +function UserSettings() { + const [anchorEl, setAnchorEl] = React.useState(null); + const { userInfo, logOut } = React.useContext(Auth); + + const open = Boolean(anchorEl); + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + Hello, {userInfo?.email} + logOut()}> + + + + Logout + + + + ); +} + +export default styled(UserSettings)``; diff --git a/ui/contexts/AuthContext.tsx b/ui/contexts/AuthContext.tsx index f19f37403e5..e06619df1cb 100644 --- a/ui/contexts/AuthContext.tsx +++ b/ui/contexts/AuthContext.tsx @@ -2,12 +2,21 @@ import * as React from "react"; import { useHistory, Redirect } from "react-router-dom"; import Layout from "../components/Layout"; import LoadingPage from "../components/LoadingPage"; +import { FeatureFlags } from "../contexts/FeatureFlags"; const USER_INFO = "/oauth2/userinfo"; const SIGN_IN = "/oauth2/sign_in"; +const LOG_OUT = "/oauth2/logout"; const AUTH_PATH_SIGNIN = "/sign_in"; export const AuthCheck = ({ children }) => { + // If the auth flag is null go straight to rendering the children + const { authFlag } = React.useContext(FeatureFlags); + + if (!authFlag) { + return children; + } + const { loading, userInfo } = React.useContext(Auth); // Wait until userInfo is loaded before showing signin or app content @@ -36,15 +45,18 @@ export type AuthContext = { }; error: { status: number; statusText: string }; loading: boolean; + logOut: () => void; }; export const Auth = React.createContext(null); export default function AuthContextProvider({ children }) { - const [userInfo, setUserInfo] = React.useState<{ - email: string; - groups: string[]; - }>(null); + const { authFlag } = React.useContext(FeatureFlags); + const [userInfo, setUserInfo] = + React.useState<{ + email: string; + groups: string[]; + }>(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const history = useHistory(); @@ -80,21 +92,46 @@ export default function AuthContextProvider({ children }) { .finally(() => setLoading(false)); }, []); + const logOut = React.useCallback(() => { + setLoading(true); + fetch(LOG_OUT, { + method: "POST", + }) + .then((response) => { + if (response.status !== 200) { + setError(response); + return; + } + history.push("/sign_in"); + }) + .finally(() => setLoading(false)); + }, []); + React.useEffect(() => { + if (!authFlag) { + return null; + } getUserInfo(); return history.listen(getUserInfo); }, [getUserInfo, history]); return ( - - {children} - + <> + {authFlag ? ( + + {children} + + ) : ( + children + )} + ); } diff --git a/ui/contexts/FeatureFlags.tsx b/ui/contexts/FeatureFlags.tsx new file mode 100644 index 00000000000..e7ce76ddf1d --- /dev/null +++ b/ui/contexts/FeatureFlags.tsx @@ -0,0 +1,39 @@ +import * as React from "react"; + +export type FeatureFlagsContext = { + authFlag: boolean | null; +}; + +export const FeatureFlags = + React.createContext(null); + +export default function FeatureFlagsContextProvider({ children }) { + const [authFlag, setAuthFlag] = React.useState(null); + + const getAuthFlag = React.useCallback(() => { + fetch("/v1/featureflags") + .then((response) => response.json()) + .then((data) => + setAuthFlag(data.flags.WEAVE_GITOPS_AUTH_ENABLED === "true") + ) + .catch((err) => console.log(err)); + }, []); + + React.useEffect(() => { + getAuthFlag(); + }, [getAuthFlag]); + + // Loading... + if (authFlag === null) { + return null; + } + return ( + + {children} + + ); +}