Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(role based view): implemented a role based view for user groups #175

Merged
merged 11 commits into from
Nov 2, 2023
Merged
2 changes: 1 addition & 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
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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

satisfies 😎 TypeScript boss right there!

}
};

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);
36 changes: 21 additions & 15 deletions src/services/ui/src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ 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";
import { useOsQuery } from "../Opensearch";

const getLinks = (isAuthenticated: boolean) => {
if (isAuthenticated) {
const getLinks = (isAuthenticated: boolean, role?: string) => {
if (isAuthenticated && role !== "onemac-micro-statesubmitter") {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure this is right? State Submitters will need Dashboard access as far as I know

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Keven, This is absolutely not the role we want, i was using this to test. i have a meet to mike at 10. This was what i needed is thought on regarding users. this is also causing e2e to fail because the test is looking for the dashboard to indicate the user is logged in

return [
{
name: "Home",
Expand Down Expand Up @@ -83,14 +85,16 @@ const ResponsiveNav = ({ isDesktop }: ResponsiveNavProps) => {
const [prevMediaQuery, setPrevMediaQuery] = useState(isDesktop);
const [isOpen, setIsOpen] = useState(false);
const { isLoading, isError, data } = useGetUser();
const query = useOsQuery();
const userContext = useUserContext();
console.log(userContext, query);

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, data.user?.["custom:cms-roles"]).map((link) => (
<NavLink
to={link.link}
target={link.link === "/faq" ? FAQ_TARGET : undefined}
Expand Down Expand Up @@ -149,17 +153,19 @@ 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) => (
<li key={link.link}>
<Link
className="block py-2 pl-3 pr-4 text-white rounded"
to={link.link}
target={link.link === "/faq" ? FAQ_TARGET : undefined}
>
{link.name}
</Link>
</li>
))}
{getLinks(!!data.user, data.user?.["custom:cms-roles"]).map(
(link) => (
<li key={link.link}>
<Link
className="block py-2 pl-3 pr-4 text-white rounded"
to={link.link}
target={link.link === "/faq" ? FAQ_TARGET : undefined}
>
{link.name}
</Link>
</li>
)
)}
<>
{data.user ? (
<button
Expand Down
14 changes: 14 additions & 0 deletions src/services/ui/src/components/UsaBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ 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 =
userContext?.user?.["custom:cms-roles"] === "onemac-micro-statesubmitter";

return (
<div className="bg-[#f0f0f0]">
Expand Down Expand Up @@ -59,6 +63,16 @@ export const UsaBanner = () => {
</div>
</button>
)}
{role && (
<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 entire application.{" "}
<span className="text-blue-600">Please visit IDM</span> to request

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a link to IDM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

link from correct link later.

the appropriate user Role(s).
</p>
</div>
)}

{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">
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
2 changes: 1 addition & 1 deletion src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const TABLE_COLUMNS = (props?: { isCms?: boolean }): OsTableColumn[] => [
{
field: "state.keyword",
label: "State",
visible: false,
visible: true,
cell: (data) => data.state,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const TABLE_COLUMNS = (props?: { isCms?: boolean }): OsTableColumn[] => [
{
field: "state.keyword",
label: "State",
visible: false,
visible: true,
cell: (data) => data.state,
},
{
Expand Down
17 changes: 14 additions & 3 deletions src/services/ui/src/pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Link, redirect } from "react-router-dom";
import { Link, redirect, useNavigate } from "react-router-dom";
import { QueryClient } from "@tanstack/react-query";
import { getUser, useGetUser } from "@/api/useGetUser";
import { WaiversList } from "./Lists/waivers";
Expand All @@ -13,6 +13,8 @@ import {
} from "@/components/Opensearch";
import { Button } from "@/components/Inputs";
import { ROUTES } from "@/routes";
import { useUserContext } from "@/components/Context/userContext";
import { useEffect } from "react";

const loader = (queryClient: QueryClient) => {
return async () => {
Expand All @@ -33,11 +35,20 @@ const loader = (queryClient: QueryClient) => {
return isUser;
};
};

export const dashboardLoader = loader;

export const Dashboard = () => {
const { data: user } = useGetUser();
const navigate = useNavigate();
const userContext = useUserContext();
const query = useOsQuery();
const { data } = useGetUser();

useEffect(() => {
if (data?.user?.["custom:cms-roles"] === "onemac-micro-reviewer") {
return navigate("/");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's worth changing or not, but figured I'd throw a fun fact/tip in. React Router v6 has two ways to navigate! You can use the hook function, or the <Navigate /> element. The way you've done it here is the former, and if you wanted to you could also do something like this:

return data?.user?.["custom:cms-roles"] === "onemac-micro-statesubmitter" 
      ? <Navigate to={ROUTES.HOME} /> 
      : <OsProvider> ... </OsProvider>

Again, not sure there's a benefit of one over the other in this case, but it's a nifty tool!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within the navigate() arguments, we should be using the ROUTES constant instead of string paths

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Navigate is still using the hard string instead of ROUTES.HOME

}
}, []);

return (
<OsProvider
Expand All @@ -51,7 +62,7 @@ export const Dashboard = () => {
<div className="max-w-screen-xl mx-auto px-4 lg:px-8">
<div className="flex items-center justify-between my-4">
<h1 className="text-xl">Dashboard</h1>
{!user?.isCms && (
{!userContext?.isCms && (
<Button>
<Link to={ROUTES.NEW_SUBMISSION_OPTIONS}>New Submission</Link>
</Button>
Expand Down