Skip to content

Commit

Permalink
feat(role based view): implemented a role based view for user groups (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>
Co-authored-by: Adewale Sangobiyi <[email protected]>
  • Loading branch information
3 people authored Nov 2, 2023
1 parent 103c04e commit fa0ac18
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 90 deletions.
23 changes: 22 additions & 1 deletion src/services/auth/libs/users.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
]
},
{
"username": "onemac-micro-[email protected]",
"username": "[email protected]",
"attributes": [
{
"Name": "email",
Expand Down Expand Up @@ -172,5 +172,26 @@
"Value": "onemac-micro-statesubmitter"
}
]
},
{
"username": "[email protected]",
"attributes": [
{
"Name": "email",
"Value": "[email protected]"
},
{
"Name": "given_name",
"Value": "bad"
},
{
"Name": "family_name",
"Value": "football"
},
{
"Name": "email_verified",
"Value": "true"
}
]
}
]
10 changes: 6 additions & 4 deletions src/services/ui/src/api/useGetUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OneMacUser> => {
try {
const authenticatedUser = await Auth.currentAuthenticatedUser();
const attributes = await Auth.userAttributes(authenticatedUser);
Expand All @@ -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;
}
};

Expand Down
14 changes: 14 additions & 0 deletions src/services/ui/src/components/Context/userContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { OneMacUser, useGetUser } from "@/api/useGetUser";
import { PropsWithChildren, createContext, useContext } from "react";

const initialState = { user: null };

export const UserContext = createContext<OneMacUser | undefined>(initialState);
export const UserContextProvider = ({ children }: PropsWithChildren) => {
const { data: userData } = useGetUser();
return (
<UserContext.Provider value={userData}>{children}</UserContext.Provider>
);
};

export const useUserContext = () => useContext(UserContext);
16 changes: 10 additions & 6 deletions src/services/ui/src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -83,14 +84,17 @@ 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();
const { domain, redirectSignIn, responseType } =
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);
};

Expand All @@ -111,7 +115,7 @@ const ResponsiveNav = ({ isDesktop }: ResponsiveNavProps) => {
if (isDesktop) {
return (
<>
{getLinks(!!data.user).map((link) => (
{getLinks(!!data.user, role).map((link) => (
<NavLink
to={link.link}
target={link.link === "/faq" ? FAQ_TARGET : undefined}
Expand Down Expand Up @@ -149,7 +153,7 @@ const ResponsiveNav = ({ isDesktop }: ResponsiveNavProps) => {
{isOpen && (
<div className="w-full fixed top-[100px] left-0 z-50">
<ul className="font-medium flex flex-col p-4 md:p-0 mt-2 gap-4 rounded-lg bg-accent">
{getLinks(!!data.user).map((link) => (
{getLinks(!!data.user, role).map((link) => (
<li key={link.link}>
<Link
className="block py-2 pl-3 pr-4 text-white rounded"
Expand Down
159 changes: 96 additions & 63 deletions src/services/ui/src/components/UsaBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,119 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import { ChevronDown, ChevronUp } from "lucide-react";
import { LockIcon } from "../LockIcon";
import { GovernmentBuildingIcon } from "../GovernmentBuildingIcon";
import UsFlag from "@/assets/us_flag_small.png";
import { useMediaQuery } from "@/hooks";
import { useUserContext } from "../Context/userContext";

export const UsaBanner = () => {
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 (
<div className="bg-[#f0f0f0]">
{/* Display for Desktop */}
{isDesktop && (
<>
<div className="max-w-screen-xl px-4 py-1 lg:px-8 text-xs mx-auto flex gap-2 items-center">
<>
<div className="bg-[#f0f0f0]">
{/* Display for Desktop */}
{isDesktop && (
<>
<div className="max-w-screen-xl px-4 py-1 lg:px-8 text-xs mx-auto flex gap-2 items-center">
<img
className="w-4 h-[11px]"
src={UsFlag}
alt="A United States Flag icon"
/>
<p>An official website of the United States government</p>
<button
className="flex"
aria-expanded={isOpen}
aria-controls="gov-banner-default-default"
onClick={() => setIsOpen((value) => !value)}
>
<span className="underline text-[#005ea2]">
Here&apos;s how you know
</span>
{!isOpen && <ChevronDown className="w-4 h-4 text-[#005ea2]" />}
{isOpen && <ChevronUp className="w-4 h-4 text-[#005ea2]" />}
</button>
</div>
</>
)}
{/* Display for Mobile */}
{!isDesktop && (
<button
className="w-full flex items-center text-[0.8rem] px-4 py-1 leading-4 gap-2"
onClick={() => setIsOpen((value) => !value)}
>
<img
className="w-4 h-[11px]"
src={UsFlag}
alt="A United States Flag icon"
/>
<p>An official website of the United States government</p>
<button
className="flex"
aria-expanded={isOpen}
aria-controls="gov-banner-default-default"
onClick={() => setIsOpen((value) => !value)}
>
<span className="underline text-[#005ea2]">
Here&apos;s how you know
</span>
{!isOpen && <ChevronDown className="w-4 h-4 text-[#005ea2]" />}
{isOpen && <ChevronUp className="w-4 h-4 text-[#005ea2]" />}
</button>
</div>
</>
)}
{/* Display for Mobile */}
{!isDesktop && (
<button
className="w-full flex items-center text-[0.8rem] px-4 py-1 leading-4 gap-2"
onClick={() => setIsOpen((value) => !value)}
>
<img
className="w-4 h-[11px]"
src={UsFlag}
alt="A United States Flag icon"
/>
<div>
<p>An official website of the United States government</p>
<div className="flex" aria-expanded={isOpen}>
<span className="underline text-[#005ea2] block">
Here&apos;s how you know
</span>
{!isOpen && <ChevronDown className="w-4 h-4 text-[#005ea2]" />}
{isOpen && <ChevronUp className="w-4 h-4 text-[#005ea2]" />}
<div>
<p>An official website of the United States government</p>
<div className="flex" aria-expanded={isOpen}>
<span className="underline text-[#005ea2] block">
Here&apos;s how you know
</span>
{!isOpen && <ChevronDown className="w-4 h-4 text-[#005ea2]" />}
{isOpen && <ChevronUp className="w-4 h-4 text-[#005ea2]" />}
</div>
</div>
</div>
</button>
)}
{isOpen && (
<div className="flex flex-col gap-3 px-3 mt-3 sm:flex-row max-w-screen-lg mx-auto pb-4">
<div className="flex gap-2">
<GovernmentBuildingIcon className="min-w-[40px] min-h-[40px] w-10" />
<p className="text-sm max-w-md">
<strong className="block">Official websites use .gov</strong>A
<strong>.gov</strong> website belongs to an official government
organization in the United States.
</button>
)}
{hasRole && (
<div className="w-full px-4 py-1 lg:px-8 text-xs mx-auto flex gap-2 items-center justify-center bg-red-200 ">
<p className="text-center text-base">
You do not have access to view the application
<a
rel="noreferrer"
href="https://home.idm.cms.gov/signin/login.html"
target="_blank"
className="text-blue-600 inline no-underline"
>
Please visit IDM
</a>{" "}
to request the appropriate user role(s) - FAIL
</p>
</div>
<div className="flex gap-2">
<LockIcon className="min-w-[40px] min-h-[40px] w-10" />
<p className="text-sm max-w-md">
<strong className="block">Secure .gov websites use HTTPS</strong>A
lock (<MiniLock />) or <strong>https://</strong> means you&apos;ve
safely connected to the .gov website. Share sensitive information
only on official, secure websites.
</p>
)}

{isOpen && (
<div className="flex flex-col gap-3 px-3 mt-3 sm:flex-row max-w-screen-lg mx-auto pb-4">
<div className="flex gap-2">
<GovernmentBuildingIcon className="min-w-[40px] min-h-[40px] w-10" />
<p className="text-sm max-w-md">
<strong className="block">Official websites use .gov</strong>A
<strong>.gov</strong> website belongs to an official government
organization in the United States.
</p>
</div>
<div className="flex gap-2">
<LockIcon className="min-w-[40px] min-h-[40px] w-10" />
<p className="text-sm max-w-md">
<strong className="block">
Secure .gov websites use HTTPS
</strong>
A lock (<MiniLock />) or <strong>https://</strong> means
you&apos;ve safely connected to the .gov website. Share
sensitive information only on official, secure websites.
</p>
</div>
</div>
</div>
)}
</div>
)}
</div>
</>
);
};

Expand Down
5 changes: 4 additions & 1 deletion src/services/ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<UserContextProvider>
<RouterProvider router={router} />
</UserContextProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>
Expand Down
15 changes: 11 additions & 4 deletions src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
},
{
Expand All @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions src/services/ui/src/pages/dashboard/Lists/spas/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useGetUser } from "@/api/useGetUser";
import { ErrorAlert, LoadingSpinner } from "@/components";

import { Pagination } from "@/components/Pagination";

import {
OsTable,
OsFiltering,
Expand All @@ -14,10 +14,10 @@ export const SpasList = () => {
const { data: user } = useGetUser();
const context = useOsContext();
const params = useOsParams();

if (context.error) return <ErrorAlert error={context.error} />;
console.log(user, "user from spas");

const columns = TABLE_COLUMNS({ isCms: user?.isCms });
const columns = TABLE_COLUMNS({ isCms: user?.isCms, user: user?.user });

return (
<section className="flex flex-col h-[calc(100vh-250px)]">
Expand Down
Loading

0 comments on commit fa0ac18

Please sign in to comment.