From fa0ac18570e7aefb1e1ce9114fc2d6b4c2bc5241 Mon Sep 17 00:00:00 2001 From: Walesango2 <110047743+Walesango2@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:02:29 -0400 Subject: [PATCH] feat(role based view): implemented a role based view for user groups (#175) * feat(Role based users view): configure role based user view * feat(role based view): inplimanting a role based view for user groups * feat(role based view): inplimanting a role based view for user groups * feat(Role based users view): configure role based user view * feat(Role based users view): configure role based user view * feat(Role based users view): configure role based user view * feat(Role based users view): configure role based user view * feat(Role based users view): configure role based user view * feat(Role based users view): configure role based user view * feat(Role based users view): configure role based user view * feat(Role based users view): configure role based user view --------- Co-authored-by: Adewale Sangobiyi Co-authored-by: Adewale Sangobiyi --- src/services/auth/libs/users.json | 23 ++- src/services/ui/src/api/useGetUser.ts | 10 +- .../ui/src/components/Context/userContext.tsx | 14 ++ .../ui/src/components/Layout/index.tsx | 16 +- .../ui/src/components/UsaBanner/index.tsx | 159 +++++++++++------- src/services/ui/src/main.tsx | 5 +- .../src/pages/dashboard/Lists/spas/consts.tsx | 15 +- .../src/pages/dashboard/Lists/spas/index.tsx | 6 +- .../pages/dashboard/Lists/waivers/consts.tsx | 13 +- .../pages/dashboard/Lists/waivers/index.tsx | 2 +- src/services/ui/src/pages/dashboard/index.tsx | 19 ++- 11 files changed, 192 insertions(+), 90 deletions(-) create mode 100644 src/services/ui/src/components/Context/userContext.tsx diff --git a/src/services/auth/libs/users.json b/src/services/auth/libs/users.json index 93e4ea4bbc..b810bc9e50 100644 --- a/src/services/auth/libs/users.json +++ b/src/services/auth/libs/users.json @@ -87,7 +87,7 @@ ] }, { - "username": "onemac-micro-helpdesk@example.com", + "username": "helpdesk@example.com", "attributes": [ { "Name": "email", @@ -172,5 +172,26 @@ "Value": "onemac-micro-statesubmitter" } ] + }, + { + "username": "badfootball@example.com", + "attributes": [ + { + "Name": "email", + "Value": "badfootball@example.com" + }, + { + "Name": "given_name", + "Value": "bad" + }, + { + "Name": "family_name", + "Value": "football" + }, + { + "Name": "email_verified", + "Value": "true" + } + ] } ] diff --git a/src/services/ui/src/api/useGetUser.ts b/src/services/ui/src/api/useGetUser.ts index da813f54a5..ae79cf67b3 100644 --- a/src/services/ui/src/api/useGetUser.ts +++ b/src/services/ui/src/api/useGetUser.ts @@ -4,7 +4,9 @@ import { Auth } from "aws-amplify"; import { CognitoUserAttributes } from "shared-types"; import { isCmsUser } from "shared-utils"; -export const getUser = async () => { +export type OneMacUser = { isCms?: boolean, user: CognitoUserAttributes | null } + +export const getUser = async (): Promise => { try { const authenticatedUser = await Auth.currentAuthenticatedUser(); const attributes = await Auth.userAttributes(authenticatedUser); @@ -14,14 +16,14 @@ export const getUser = async () => { }, {}) as unknown as CognitoUserAttributes; if (user["custom:cms-roles"]) { const isCms = isCmsUser(user); - return { user, isCms }; + return { user, isCms } satisfies OneMacUser; } else { user["custom:cms-roles"] = ""; - return { user, isCms: false }; + return { user, isCms: false } satisfies OneMacUser; } } catch (e) { console.log({ e }); - return { user: null }; + return { user: null } satisfies OneMacUser; } }; diff --git a/src/services/ui/src/components/Context/userContext.tsx b/src/services/ui/src/components/Context/userContext.tsx new file mode 100644 index 0000000000..44a2b94bb8 --- /dev/null +++ b/src/services/ui/src/components/Context/userContext.tsx @@ -0,0 +1,14 @@ +import { OneMacUser, useGetUser } from "@/api/useGetUser"; +import { PropsWithChildren, createContext, useContext } from "react"; + +const initialState = { user: null }; + +export const UserContext = createContext(initialState); +export const UserContextProvider = ({ children }: PropsWithChildren) => { + const { data: userData } = useGetUser(); + return ( + {children} + ); +}; + +export const useUserContext = () => useContext(UserContext); diff --git a/src/services/ui/src/components/Layout/index.tsx b/src/services/ui/src/components/Layout/index.tsx index 0033bd8b7f..dd9dd10e73 100644 --- a/src/services/ui/src/components/Layout/index.tsx +++ b/src/services/ui/src/components/Layout/index.tsx @@ -2,16 +2,17 @@ import { Link, NavLink, NavLinkProps, Outlet } from "react-router-dom"; import oneMacLogo from "@/assets/onemac_logo.svg"; import { useMediaQuery } from "@/hooks"; import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useGetUser } from "@/api/useGetUser"; import { Auth } from "aws-amplify"; import { AwsCognitoOAuthOpts } from "@aws-amplify/auth/lib-esm/types"; import { Footer } from "../Footer"; import { UsaBanner } from "../UsaBanner"; import { FAQ_TARGET } from "@/routes"; +import { useUserContext } from "../Context/userContext"; -const getLinks = (isAuthenticated: boolean) => { - if (isAuthenticated) { +const getLinks = (isAuthenticated: boolean, role?: boolean) => { + if (isAuthenticated && role) { return [ { name: "Home", @@ -83,6 +84,10 @@ const ResponsiveNav = ({ isDesktop }: ResponsiveNavProps) => { const [prevMediaQuery, setPrevMediaQuery] = useState(isDesktop); const [isOpen, setIsOpen] = useState(false); const { isLoading, isError, data } = useGetUser(); + const userContext = useUserContext(); + const role = useMemo(() => { + return userContext?.user?.["custom:cms-roles"] ? true : false; + }, []); const handleLogin = () => { const authConfig = Auth.configure(); @@ -90,7 +95,6 @@ const ResponsiveNav = ({ isDesktop }: ResponsiveNavProps) => { authConfig.oauth as AwsCognitoOAuthOpts; const clientId = authConfig.userPoolWebClientId; const url = `https://${domain}/oauth2/authorize?redirect_uri=${redirectSignIn}&response_type=${responseType}&client_id=${clientId}`; - window.location.assign(url); }; @@ -111,7 +115,7 @@ const ResponsiveNav = ({ isDesktop }: ResponsiveNavProps) => { if (isDesktop) { return ( <> - {getLinks(!!data.user).map((link) => ( + {getLinks(!!data.user, role).map((link) => ( { {isOpen && (
    - {getLinks(!!data.user).map((link) => ( + {getLinks(!!data.user, role).map((link) => (
  • { const [isOpen, setIsOpen] = useState(false); const isDesktop = useMediaQuery("(min-width: 640px)"); + const userContext = useUserContext(); + const role = useMemo(() => { + return userContext?.user?.["custom:cms-roles"] ? false : true; + }, []); + const hasRole = useMemo(() => { + if (role && userContext?.user) { + return true; + } else { + return false; + } + }, []); return ( -
    - {/* Display for Desktop */} - {isDesktop && ( - <> -
    + <> +
    + {/* Display for Desktop */} + {isDesktop && ( + <> +
    + A United States Flag icon +

    An official website of the United States government

    + +
    + + )} + {/* Display for Mobile */} + {!isDesktop && ( + -
    - - )} - {/* Display for Mobile */} - {!isDesktop && ( - - )} - {isOpen && ( -
    -
    - -

    - Official websites use .govA - .gov website belongs to an official government - organization in the United States. + + )} + {hasRole && ( +

    +

    + You do not have access to view the application + + Please visit IDM + {" "} + to request the appropriate user role(s) - FAIL

    -
    - -

    - Secure .gov websites use HTTPSA - lock () or https:// means you've - safely connected to the .gov website. Share sensitive information - only on official, secure websites. -

    + )} + + {isOpen && ( +
    +
    + +

    + Official websites use .govA + .gov website belongs to an official government + organization in the United States. +

    +
    +
    + +

    + + Secure .gov websites use HTTPS + + A lock () or https:// means + you've safely connected to the .gov website. Share + sensitive information only on official, secure websites. +

    +
    -
    - )} -
    + )} +
    + ); }; diff --git a/src/services/ui/src/main.tsx b/src/services/ui/src/main.tsx index 48dea5293f..81fa53c4a5 100644 --- a/src/services/ui/src/main.tsx +++ b/src/services/ui/src/main.tsx @@ -5,11 +5,14 @@ import "./index.css"; // this one second import { queryClient, router } from "./router"; import { QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { UserContextProvider } from "./components/Context/userContext"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + + + diff --git a/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx b/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx index 8f0c23b161..95abf69592 100644 --- a/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx @@ -4,8 +4,12 @@ import { removeUnderscoresAndCapitalize } from "@/utils"; import { OsTableColumn } from "@/components/Opensearch/Table/types"; import { LABELS } from "@/lib"; import { BLANK_VALUE } from "@/consts"; +import { CognitoUserAttributes, UserRoles } from "shared-types"; -export const TABLE_COLUMNS = (props?: { isCms?: boolean }): OsTableColumn[] => [ +export const TABLE_COLUMNS = (props?: { + isCms?: boolean; + user?: CognitoUserAttributes | null | undefined; +}): OsTableColumn[] => [ { props: { className: "w-[150px]" }, field: "id.keyword", @@ -26,7 +30,7 @@ export const TABLE_COLUMNS = (props?: { isCms?: boolean }): OsTableColumn[] => [ { field: "state.keyword", label: "State", - visible: false, + visible: true, cell: (data) => data.state, }, { @@ -43,9 +47,12 @@ export const TABLE_COLUMNS = (props?: { isCms?: boolean }): OsTableColumn[] => [ : BLANK_VALUE, }, { - field: props?.isCms ? "cmsStatus.keyword" : "stateStatus.keyword", + field: props?.isCms ? "cmsStatus" : "stateStatus.keyword", label: "Status", - cell: (data) => (props?.isCms ? data.cmsStatus : data.stateStatus), + cell: (data) => + props?.isCms && !(props.user?.["custom:cms-roles"] === UserRoles.HELPDESK) + ? data.cmsStatus + : data.stateStatus, }, { field: "submissionDate", diff --git a/src/services/ui/src/pages/dashboard/Lists/spas/index.tsx b/src/services/ui/src/pages/dashboard/Lists/spas/index.tsx index e4e971582d..61458a4b26 100644 --- a/src/services/ui/src/pages/dashboard/Lists/spas/index.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/spas/index.tsx @@ -1,7 +1,7 @@ import { useGetUser } from "@/api/useGetUser"; import { ErrorAlert, LoadingSpinner } from "@/components"; - import { Pagination } from "@/components/Pagination"; + import { OsTable, OsFiltering, @@ -14,10 +14,10 @@ export const SpasList = () => { const { data: user } = useGetUser(); const context = useOsContext(); const params = useOsParams(); - if (context.error) return ; + console.log(user, "user from spas"); - const columns = TABLE_COLUMNS({ isCms: user?.isCms }); + const columns = TABLE_COLUMNS({ isCms: user?.isCms, user: user?.user }); return (
    diff --git a/src/services/ui/src/pages/dashboard/Lists/waivers/consts.tsx b/src/services/ui/src/pages/dashboard/Lists/waivers/consts.tsx index 85939a1adc..f71cc0a11f 100644 --- a/src/services/ui/src/pages/dashboard/Lists/waivers/consts.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/waivers/consts.tsx @@ -5,8 +5,12 @@ import { removeUnderscoresAndCapitalize } from "@/utils"; import { OsTableColumn } from "@/components/Opensearch/Table/types"; import { LABELS } from "@/lib"; import { BLANK_VALUE } from "@/consts"; +import { CognitoUserAttributes, UserRoles } from "shared-types"; -export const TABLE_COLUMNS = (props?: { isCms?: boolean }): OsTableColumn[] => [ +export const TABLE_COLUMNS = (props?: { + isCms?: boolean; + user?: CognitoUserAttributes | null | undefined; +}): OsTableColumn[] => [ { props: { className: "w-[150px]" }, field: "id.keyword", @@ -27,7 +31,7 @@ export const TABLE_COLUMNS = (props?: { isCms?: boolean }): OsTableColumn[] => [ { field: "state.keyword", label: "State", - visible: false, + visible: true, cell: (data) => data.state, }, { @@ -46,7 +50,10 @@ export const TABLE_COLUMNS = (props?: { isCms?: boolean }): OsTableColumn[] => [ { field: props?.isCms ? "cmsStatus.keyword" : "stateStatus.keyword", label: "Status", - cell: (data) => (props?.isCms ? data.cmsStatus : data.stateStatus), + cell: (data) => + props?.isCms && !(props.user?.["custom:cms-roles"] === UserRoles.HELPDESK) + ? data.cmsStatus + : data.stateStatus, }, { field: "submissionDate", diff --git a/src/services/ui/src/pages/dashboard/Lists/waivers/index.tsx b/src/services/ui/src/pages/dashboard/Lists/waivers/index.tsx index eda8c2c102..b517b88a81 100644 --- a/src/services/ui/src/pages/dashboard/Lists/waivers/index.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/waivers/index.tsx @@ -17,7 +17,7 @@ export const WaiversList = () => { if (context.error) return ; - const columns = TABLE_COLUMNS({ isCms: user?.isCms }); + const columns = TABLE_COLUMNS({ isCms: user?.isCms, user: user?.user }); return (
    diff --git a/src/services/ui/src/pages/dashboard/index.tsx b/src/services/ui/src/pages/dashboard/index.tsx index be2ac8644d..28fe78dc76 100644 --- a/src/services/ui/src/pages/dashboard/index.tsx +++ b/src/services/ui/src/pages/dashboard/index.tsx @@ -1,6 +1,6 @@ -import { Link, redirect } from "react-router-dom"; +import { Link, Navigate, redirect } from "react-router-dom"; import { QueryClient } from "@tanstack/react-query"; -import { getUser, useGetUser } from "@/api/useGetUser"; +import { getUser } from "@/api/useGetUser"; import { WaiversList } from "./Lists/waivers"; import { SpasList } from "./Lists/spas"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/Tabs"; @@ -13,6 +13,8 @@ import { } from "@/components/Opensearch"; import { Button } from "@/components/Inputs"; import { ROUTES } from "@/routes"; +import { useUserContext } from "@/components/Context/userContext"; +import { useMemo } from "react"; const loader = (queryClient: QueryClient) => { return async () => { @@ -33,12 +35,21 @@ const loader = (queryClient: QueryClient) => { return isUser; }; }; + export const dashboardLoader = loader; export const Dashboard = () => { - const { data: user } = useGetUser(); + const userContext = useUserContext(); const query = useOsQuery(); + const role = useMemo(() => { + return userContext?.user?.["custom:cms-roles"] ? true : false; + }, []); + + if (!role) { + return ; + } + return ( {

    Dashboard

    - {!user?.isCms && ( + {!userContext?.isCms && (