Skip to content

Commit

Permalink
feat: landing page
Browse files Browse the repository at this point in the history
  • Loading branch information
matthieu-foucault committed Jan 5, 2022
1 parent d85d3b8 commit 4a0a8d1
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 124 deletions.
166 changes: 166 additions & 0 deletions app/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { ADMIN_ROLES } from "data/group-constants";
import { useCallback, useMemo } from "react";
import Link from "next/link";
import BCGovLink from "@button-inc/bcgov-theme/Link";
import { useFragment, graphql, useMutation } from "react-relay";
import { Dashboard_query$key } from "__generated__/Dashboard_query.graphql";
import {
getContactsPageRoute,
getOperatorsPageRoute,
getProjectRevisionPageRoute,
getProjectsPageRoute,
} from "pageRoutes";
import { mutation as createProjectMutation } from "mutations/Project/createProject";
import { createProjectMutationResponse } from "__generated__/createProjectMutation.graphql";
import { useRouter } from "next/router";

interface Props {
query: Dashboard_query$key;
}

const Dashboard: React.FC<Props> = ({ query: queryKey }) => {
const router = useRouter();
const { session, pendingNewProjectRevision } = useFragment(
graphql`
fragment Dashboard_query on Query {
session {
cifUserBySub {
firstName
}
userGroups
}
pendingNewProjectRevision {
id
}
}
`,
queryKey
);

const [createProject, isProjectCreating] = useMutation(createProjectMutation);

const handleProjectCreation = useCallback(() => {
if (isProjectCreating) return;
createProject({
variables: { input: {} },
onCompleted: (response: createProjectMutationResponse) => {
router.push(
getProjectRevisionPageRoute(response.createProject.projectRevision.id)
);
},
});
}, [createProject, isProjectCreating, router]);

const isAdmin = useMemo(
() => ADMIN_ROLES.some((role) => session?.userGroups?.includes(role)),
[session?.userGroups]
);

const addOrResumeProjectLink = useMemo(
() =>
pendingNewProjectRevision ? (
<Link
passHref
href={getProjectRevisionPageRoute(pendingNewProjectRevision.id)}
>
<BCGovLink>Resume Project Draft</BCGovLink>
</Link>
) : (
<button
onClick={handleProjectCreation}
className={isProjectCreating ? "disabled" : ""}
>
Create a new Project
</button>
),
[pendingNewProjectRevision, handleProjectCreation, isProjectCreating]
);

return (
<>
<header>
<h2>Welcome, {session.cifUserBySub?.firstName}</h2>
</header>
<main>
<section>
<header>
<h3>Projects</h3>
</header>
<main>
<Link passHref href={getProjectsPageRoute()}>
<BCGovLink>Projects List</BCGovLink>
</Link>
{addOrResumeProjectLink}
</main>
</section>
<section>
<header>
<h3>Reporting Operations</h3>
</header>
<main>
<Link passHref href={getOperatorsPageRoute()}>
<BCGovLink>Operators</BCGovLink>
</Link>
<Link passHref href={getContactsPageRoute()}>
<BCGovLink>Contacts</BCGovLink>
</Link>
</main>
</section>
{isAdmin && (
<section>
<header>
<h3>Administration</h3>
</header>
<main></main>
</section>
)}
</main>
<style jsx>{`
main {
display: flex;
flex-wrap: wrap;
}
section {
width: 18rem;
margin: 1rem;
border: 1px solid #939393;
border-radius: 4px;
}
section > header {
min-height: 5rem;
}
:global(section > main > *),
section > header {
padding: 0.75rem 1.25rem;
}
section > main {
flex-direction: column;
}
:global(section > main > *:not(:last-child)),
section > header {
border-bottom: 1px solid #939393;
}
:global(button) {
color: #1a5a96;
background: transparent;
border: none;
cursor: pointer;
text-decoration: underline;
text-align: left;
}
:global(button:hover) {
text-decoration: none;
color: blue;
}
`}</style>
</>
);
};

export default Dashboard;
4 changes: 2 additions & 2 deletions app/components/Layout/SubHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import subHeaderLinks from "data/subHeaderLinks";
import { useMemo } from "react";
import { match } from "path-to-regexp";

export default function SubHeader({ isAdmin = false }) {
export default function SubHeader() {
const router = useRouter();

const highlightedLinkName = useMemo(() => {
Expand All @@ -24,7 +24,7 @@ export default function SubHeader({ isAdmin = false }) {
<BaseHeader header="sub">
<ul>
<li>
<Link href={isAdmin ? "/admin" : "/cif"}>
<Link href="/cif">
<a
className={highlightedLinkName === "Dashboard" ? "highlight" : ""}
>
Expand Down
2 changes: 1 addition & 1 deletion app/cypress/integration/zz-landing-pages.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe("When logged in as an admin", () => {
)
.its("redirects")
.should((redirects) => {
expect(redirects[redirects.length - 1]).to.contain("/admin");
expect(redirects[redirects.length - 1]).to.contain("/cif");
});
});
});
4 changes: 2 additions & 2 deletions app/data/groups.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"Realm Administrator": {
"pgRole": "cif_admin",
"priority": 0,
"path": "/admin"
"path": "/cif"
},
"UNAUTHORIZED_IDIR_USER": {
"pgRole": "cif_guest",
Expand All @@ -22,6 +22,6 @@
"cif_admin": {
"pgRole": "cif_admin",
"priority": 0,
"path": "/admin"
"path": "/cif"
}
}
9 changes: 2 additions & 7 deletions app/data/pagesAuthorization.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@
"isProtected": false
},
{
"routePaths": ["/admin(.*)"],
"routePaths": ["/admin/(.*)"],
"isProtected": true,
"allowedRoles": ["cif_admin", "Realm Administrator"]
},
{
"routePaths": ["/cif"],
"isProtected": true,
"allowedRoles": ["cif_internal"]
},
{
"routePaths": ["/cif/(.*)"],
"routePaths": ["/cif(.*)"],
"isProtected": true,
"allowedRoles": ["cif_internal", "cif_admin", "Realm Administrator"]
}
Expand Down
40 changes: 0 additions & 40 deletions app/pages/admin/index.tsx

This file was deleted.

8 changes: 7 additions & 1 deletion app/pages/cif/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@ import { withRelay, RelayProps } from "relay-nextjs";
import { graphql, usePreloadedQuery } from "react-relay/hooks";
import { cifLandingQuery } from "__generated__/cifLandingQuery.graphql";
import withRelayOptions from "lib/relay/withRelayOptions";
import Dashboard from "components/Dashboard";

const CifLandingQuery = graphql`
query cifLandingQuery {
query {
session {
...DefaultLayout_session
}
...Dashboard_query
}
}
`;

function CifLanding({ preloadedQuery }: RelayProps<{}, cifLandingQuery>) {
const { query } = usePreloadedQuery(CifLandingQuery, preloadedQuery);
return <DefaultLayout session={query.session}></DefaultLayout>;
return (
<DefaultLayout session={query.session}>
<Dashboard query={query} />
</DefaultLayout>
);
}

export default withRelay(CifLanding, CifLandingQuery, withRelayOptions);
51 changes: 15 additions & 36 deletions app/tests/unit/components/Layout/DefaultLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,43 +77,22 @@ describe("The DefaultLayout component", () => {
expect(screen.getByText("Projects")).toBeVisible();
});

describe("it should set the destination of the Dashboard link", () => {
it("to /admin if the user is an admin", () => {
resolveQuery({
KeycloakJwt() {
return {
cifUserBySub: {
id: "1",
},
userGroups: ["cif_admin"],
};
},
});
renderDefaultLayout();

expect(screen.getByText("Dashboard").closest("a")).toHaveAttribute(
"href",
"/admin"
);
it("should render the Dashboard link to /cif", () => {
resolveQuery({
KeycloakJwt() {
return {
cifUserBySub: {
id: "1",
},
userGroups: ["cif_admin"],
};
},
});
renderDefaultLayout();

it("to /cif otherwise", () => {
resolveQuery({
KeycloakJwt() {
return {
cifUserBySub: {
id: "1",
},
userGroups: ["cif_internal"],
};
},
});
renderDefaultLayout();

expect(screen.getByText("Dashboard").closest("a")).toHaveAttribute(
"href",
"/cif"
);
});
expect(screen.getByText("Dashboard").closest("a")).toHaveAttribute(
"href",
"/cif"
);
});
});
10 changes: 2 additions & 8 deletions app/tests/unit/components/Layout/SubHeader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ import { mocked } from "jest-mock";
jest.mock("next/router");

describe("The SubHeader Component", () => {
it("Changes the dashboard link depending on the isAdmin prop", () => {
it("Renders the dashboard link", () => {
mocked(useRouter).mockReturnValue({ asPath: "/" } as any);
render(<SubHeader isAdmin={true} />);
expect(screen.getByText("Dashboard").closest("a")).toHaveAttribute(
"href",
"/admin"
);
cleanup();
render(<SubHeader isAdmin={false} />);
render(<SubHeader />);
expect(screen.getByText("Dashboard").closest("a")).toHaveAttribute(
"href",
"/cif"
Expand Down
Loading

0 comments on commit 4a0a8d1

Please sign in to comment.