From 90edf11b88a23c2a6ce68a7ab71e3c2fbad9118d Mon Sep 17 00:00:00 2001 From: James Elliott Date: Fri, 8 Apr 2022 12:50:55 +1000 Subject: [PATCH] feat(web): add user display name to oidc consent view (#3138) This adds the current logged in users display name to the consent page as well as some other minor tweaks. Closes #2595 --- internal/server/locales/en/portal.json | 4 +- .../components/TypographyWithTooltip.test.tsx | 13 ++++++ web/src/components/TypographyWithTootip.tsx | 33 ++++++++++++++ web/src/hooks/UserInfo.ts | 6 ++- web/src/layouts/LoginLayout.tsx | 20 +++++++-- web/src/services/UserInfo.ts | 7 ++- .../LoginPortal/ConsentView/ConsentView.tsx | 45 +++++++++++++------ 7 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 web/src/components/TypographyWithTooltip.test.tsx create mode 100644 web/src/components/TypographyWithTootip.tsx diff --git a/internal/server/locales/en/portal.json b/internal/server/locales/en/portal.json index 978765818..e57e4677f 100644 --- a/internal/server/locales/en/portal.json +++ b/internal/server/locales/en/portal.json @@ -61,5 +61,7 @@ "Must have at least one number": "Must have at least one number", "Must have at least one special character": "Must have at least one special character", "Must be at least {{len}} characters in length": "Must be at least {{len}} characters in length", - "Must not be more than {{len}} characters in length": "Must not be more than {{len}} characters in length" + "Must not be more than {{len}} characters in length": "Must not be more than {{len}} characters in length", + "Consent Request": "Consent Request", + "Client ID": "Client ID: {{client_id}}" } \ No newline at end of file diff --git a/web/src/components/TypographyWithTooltip.test.tsx b/web/src/components/TypographyWithTooltip.test.tsx new file mode 100644 index 000000000..69a163290 --- /dev/null +++ b/web/src/components/TypographyWithTooltip.test.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +import { render } from "@testing-library/react"; + +import TypographyWithTooltip from "@components/TypographyWithTootip"; + +it("renders without crashing", () => { + render(); +}); + +it("renders with tooltip without crashing", () => { + render(); +}); diff --git a/web/src/components/TypographyWithTootip.tsx b/web/src/components/TypographyWithTootip.tsx new file mode 100644 index 000000000..5c8e633ac --- /dev/null +++ b/web/src/components/TypographyWithTootip.tsx @@ -0,0 +1,33 @@ +import React, { Fragment } from "react"; + +import { Tooltip, Typography } from "@material-ui/core"; +import { Variant } from "@material-ui/core/styles/createTypography"; +import { CSSProperties } from "@material-ui/styles"; + +export interface Props { + variant: Variant; + + value?: string; + style?: CSSProperties; + + tooltip?: string; + tooltipStyle?: CSSProperties; +} + +export default function TypographyWithTooltip(props: Props): JSX.Element { + return ( + + {props.tooltip ? ( + + + {props.value} + + + ) : ( + + {props.value} + + )} + + ); +} diff --git a/web/src/hooks/UserInfo.ts b/web/src/hooks/UserInfo.ts index 56c433b9d..f2ed927e4 100644 --- a/web/src/hooks/UserInfo.ts +++ b/web/src/hooks/UserInfo.ts @@ -1,6 +1,10 @@ import { useRemoteCall } from "@hooks/RemoteCall"; -import { postUserInfo } from "@services/UserInfo"; +import { getUserInfo, postUserInfo } from "@services/UserInfo"; export function useUserInfoPOST() { return useRemoteCall(postUserInfo, []); } + +export function useUserInfoGET() { + return useRemoteCall(getUserInfo, []); +} diff --git a/web/src/layouts/LoginLayout.tsx b/web/src/layouts/LoginLayout.tsx index a47e98793..f86756fd9 100644 --- a/web/src/layouts/LoginLayout.tsx +++ b/web/src/layouts/LoginLayout.tsx @@ -1,15 +1,19 @@ import React, { ReactNode } from "react"; -import { Grid, makeStyles, Container, Typography, Link } from "@material-ui/core"; +import { Grid, makeStyles, Container, Link } from "@material-ui/core"; import { grey } from "@material-ui/core/colors"; import { ReactComponent as UserSvg } from "@assets/images/user.svg"; +import TypographyWithTooltip from "@components/TypographyWithTootip"; import { getLogoOverride } from "@utils/Configuration"; export interface Props { id?: string; children?: ReactNode; title?: string; + titleTooltip?: string; + subtitle?: string; + subtitleTooltip?: string; showBrand?: boolean; } @@ -29,9 +33,16 @@ const LoginLayout = function (props: Props) { {props.title ? ( - - {props.title} - + + + ) : null} + {props.subtitle ? ( + + ) : null} @@ -66,6 +77,7 @@ const useStyles = makeStyles((theme) => ({ paddingRight: 32, }, title: {}, + subtitle: {}, icon: { margin: theme.spacing(), width: "64px", diff --git a/web/src/services/UserInfo.ts b/web/src/services/UserInfo.ts index 0688fe28b..ea2971172 100644 --- a/web/src/services/UserInfo.ts +++ b/web/src/services/UserInfo.ts @@ -1,7 +1,7 @@ import { SecondFactorMethod } from "@models/Methods"; import { UserInfo } from "@models/UserInfo"; import { UserInfo2FAMethodPath, UserInfoPath } from "@services/Api"; -import { Post, PostWithOptionalResponse } from "@services/Client"; +import { Get, Post, PostWithOptionalResponse } from "@services/Client"; export type Method2FA = "webauthn" | "totp" | "mobile_push"; @@ -44,6 +44,11 @@ export async function postUserInfo(): Promise { return { ...res, method: toEnum(res.method) }; } +export async function getUserInfo(): Promise { + const res = await Get(UserInfoPath); + return { ...res, method: toEnum(res.method) }; +} + export function setPreferred2FAMethod(method: SecondFactorMethod) { return PostWithOptionalResponse(UserInfo2FAMethodPath, { method: toString(method) } as MethodPreferencePayload); } diff --git a/web/src/views/LoginPortal/ConsentView/ConsentView.tsx b/web/src/views/LoginPortal/ConsentView/ConsentView.tsx index a67429fbd..f52119cf0 100644 --- a/web/src/views/LoginPortal/ConsentView/ConsentView.tsx +++ b/web/src/views/LoginPortal/ConsentView/ConsentView.tsx @@ -19,6 +19,7 @@ import { IndexRoute } from "@constants/Routes"; import { useRequestedScopes } from "@hooks/Consent"; import { useNotifications } from "@hooks/NotificationsContext"; import { useRedirector } from "@hooks/Redirector"; +import { useUserInfoGET } from "@hooks/UserInfo"; import LoginLayout from "@layouts/LoginLayout"; import { acceptConsent, rejectConsent } from "@services/Consent"; import LoadingPage from "@views/LoadingPage/LoadingPage"; @@ -48,6 +49,18 @@ const ConsentView = function (props: Props) { const [resp, fetch, , err] = useRequestedScopes(); const { t: translate } = useTranslation(); + const [userInfo, fetchUserInfo, , fetchUserInfoError] = useUserInfoGET(); + + useEffect(() => { + fetchUserInfo(); + }, [fetchUserInfo]); + + useEffect(() => { + if (fetchUserInfoError) { + createErrorNotification("There was an issue retrieving user preferences"); + } + }, [fetchUserInfoError, createErrorNotification]); + useEffect(() => { if (err) { navigate(IndexRoute); @@ -100,22 +113,28 @@ const ConsentView = function (props: Props) { }; return ( - - + +
- {resp !== undefined && resp.client_description !== "" ? ( - - - {resp.client_description} - - - ) : ( - - {resp?.client_id} - - )} + + + {resp !== undefined && resp.client_description !== "" + ? resp.client_description + : resp?.client_id} + +