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

Johanna/6163 site admin update user access tab #6499

Merged
merged 36 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
aff944e
Rough implementation of manage user access
johanna-skylight Sep 1, 2023
76da69e
removed comment
johanna-skylight Sep 1, 2023
dee37fe
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 1, 2023
cd24cf6
Happy path working
johanna-skylight Sep 1, 2023
09e0a74
Fixed admin setup
johanna-skylight Sep 1, 2023
8007ef7
Added integration with backend
johanna-skylight Sep 5, 2023
0c93dc3
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 6, 2023
10b78a5
Added org update confirmation modal
johanna-skylight Sep 6, 2023
961cd2e
Implemented confirmation modal
johanna-skylight Sep 6, 2023
739ee1b
Implemented warning modals
johanna-skylight Sep 7, 2023
7d55616
Happy path implemented
johanna-skylight Sep 7, 2023
ae1e382
Use useController instead of controller.
johanna-skylight Sep 7, 2023
03a579d
Addressed code smells and bug
johanna-skylight Sep 7, 2023
7f29588
Renaming of tab
johanna-skylight Sep 7, 2023
6152567
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 7, 2023
6f9d119
Fixed flickering of save changes button
johanna-skylight Sep 7, 2023
83d5ebe
Removed todo
johanna-skylight Sep 7, 2023
089ea6c
Fixed radio button selection disappearing
johanna-skylight Sep 7, 2023
fe8960a
Added unit testing
johanna-skylight Sep 8, 2023
84fe48e
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 8, 2023
0e1467d
fixed assertion
johanna-skylight Sep 8, 2023
af823ff
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 18, 2023
4e123d9
added unit testing and fixed race condition
johanna-skylight Sep 18, 2023
1f7ce3c
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 19, 2023
02e2837
Fixed feedback
johanna-skylight Sep 19, 2023
d9eb80d
Org tab will be disabled only for deleted users
johanna-skylight Sep 19, 2023
db57bfa
Added apollo config property
johanna-skylight Sep 19, 2023
07faf19
removed space
johanna-skylight Sep 19, 2023
c56dbe6
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 20, 2023
c4c73d1
Fixed glitch of radio button dissapearing
johanna-skylight Sep 20, 2023
2ae36f6
Added key to force component re-render
johanna-skylight Sep 21, 2023
a2d514a
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 21, 2023
0d9846d
Resets form inner state if a search with no found user is executed.
johanna-skylight Sep 21, 2023
a5e66d8
Merge remote-tracking branch 'origin/main' into johanna/6163_site_adm…
johanna-skylight Sep 21, 2023
929415c
Fixed merge conflicts
johanna-skylight Sep 22, 2023
5ad644e
Test taking too long
johanna-skylight Sep 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"react": "^18.0.0",
"react-csv": "^2.2.1",
"react-dom": "^18.0.0",
"react-hook-form": "^7.45.2",
"react-hook-form": "^7.45.4",
"react-i18next": "^13.2.0",
"react-modal": "^3.16.1",
"react-redux": "^8.0.5",
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/app/Settings/Users/CreateUserForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useSelector } from "react-redux";
import { useForm } from "react-hook-form";
import { useForm, FieldError } from "react-hook-form";

import Button from "../../commonComponents/Button/Button";
import Dropdown from "../../commonComponents/Dropdown";
Expand Down Expand Up @@ -84,7 +84,10 @@ const CreateUserForm: React.FC<CreateUserFormProps> = ({
});
const formCurrentValues = watch();
return (
<form className="border-0 card-container" onSubmit={handleSubmit(onSave)}>
<form
className="border-0 card-container create-user-form__org-admin"
onSubmit={handleSubmit(onSave)}
>
<div className="display-flex flex-justify">
<h1 className="font-heading-lg margin-top-05 margin-bottom-0">
Invite new user
Expand All @@ -109,7 +112,6 @@ const CreateUserForm: React.FC<CreateUserFormProps> = ({
validationStatus={errors?.firstName?.type ? "error" : undefined}
errorMessage={errors?.firstName?.message}
/>
<div></div>
<TextInput
label="Last name"
name="lastName"
Expand Down Expand Up @@ -161,10 +163,10 @@ const CreateUserForm: React.FC<CreateUserFormProps> = ({
/>
</div>
<UserFacilitiesSettings
formValues={formCurrentValues}
allFacilities={facilities}
roleSelected={formCurrentValues.role}
facilityList={facilities}
register={register}
errors={errors}
error={errors.facilityIds as FieldError}
setValue={setValue}
/>
<div className="border-top border-base-lighter margin-x-neg-205 margin-top-5 padding-top-205 text-right">
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/app/Settings/Users/DeleteUserModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

import Button from "../../commonComponents/Button/Button";
import { displayFullName } from "../../utils";
import { User } from "../../../generated/graphql";

import { SettingsUser } from "./ManageUsersContainer";
import "./ManageUsers.scss";

interface Props {
onClose: () => void;
onDeleteUser: (userId: string) => void;
user: SettingsUser;
user: SettingsUser | User;
}

const DeleteUserModal: React.FC<Props> = ({ onClose, onDeleteUser, user }) => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/Settings/Users/EditUserEmailModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useForm } from "react-hook-form";
import { displayFullName } from "../../utils";
import { emailRegex } from "../../utils/email";
import { TextInput } from "../../commonComponents/TextInput";
import { User } from "../../../generated/graphql";

import { SettingsUser } from "./ManageUsersContainer";
import BaseEditModal from "./BaseEditModal";

import "./ManageUsers.scss";

type EmailFormData = {
Expand All @@ -16,7 +16,7 @@ type EmailFormData = {
interface EditUserEmailModalProps {
onClose: () => void;
onEditUserEmail: (userId: string, emailAddress: string) => void;
user: SettingsUser;
user: SettingsUser | User;
}

const EditUserEmailModal: React.FC<EditUserEmailModalProps> = ({
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/Settings/Users/EditUserNameModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { useForm } from "react-hook-form";

import { displayFullName } from "../../utils";
import { TextInput } from "../../commonComponents/TextInput";
import { User } from "../../../generated/graphql";

import { SettingsUser } from "./ManageUsersContainer";
import { BaseEditModal } from "./BaseEditModal";

import "./ManageUsers.scss";

type UserNameFormData = {
Expand All @@ -22,7 +22,7 @@ interface EditUserNameModalProps {
lastName: string,
suffix: string
) => void;
user: SettingsUser;
user: SettingsUser | User;
}

const EditUserNameModal: React.FC<EditUserNameModalProps> = ({
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/app/Settings/Users/ManageUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const ManageUsers: React.FC<Props> = ({
variables: { id: activeUser?.id },
fetchPolicy: "no-cache",
skip: !activeUser?.id,
notifyOnNetworkStatusChange: true,
onCompleted: (data) => updateUserWithPermissions(data.user),
});

Expand Down Expand Up @@ -432,7 +433,7 @@ const ManageUsers: React.FC<Props> = ({
>
<div
role="tablist"
aria-owns={`user-information-tab-id facility-access-tab-id`}
aria-owns={`userinformation-tab facility-access-tab-id`}
className="usa-nav__secondary-links prime-nav usa-list"
>
<div
Expand All @@ -443,7 +444,7 @@ const ManageUsers: React.FC<Props> = ({
}`}
>
<button
id={`user-information-tab-id`}
id={`userinformation-tab`}
role="tab"
className="usa-button--unstyled text-ink text-no-underline"
onClick={() => setNavItemSelected("User information")}
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/app/Settings/Users/ResetUserMfaModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Modal from "react-modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

import Button from "../../commonComponents/Button/Button";
import { User } from "../../../generated/graphql";
import { displayFullName } from "../../utils";

import { SettingsUser } from "./ManageUsersContainer";
Expand All @@ -11,7 +12,7 @@ import "./ManageUsers.scss";
interface Props {
onClose: () => void;
onResetMfa: (userId: string) => void;
user: SettingsUser;
user: SettingsUser | User;
}

const ResetUserMfaModal: React.FC<Props> = ({ onClose, onResetMfa, user }) => {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/app/Settings/Users/ResetUserPasswordModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

import Button from "../../commonComponents/Button/Button";
import { displayFullName } from "../../utils";
import { User } from "../../../generated/graphql";

import { SettingsUser } from "./ManageUsersContainer";
import "./ManageUsers.scss";

interface Props {
onClose: () => void;
onResetPassword: (userId: string) => void;
user: SettingsUser;
user: SettingsUser | User;
}

const ResetUserPasswordModal: React.FC<Props> = ({
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/app/Settings/Users/UserDetailUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { OktaUserStatus } from "../../utils/user";
import { User } from "../../../generated/graphql";

import { SettingsUser } from "./ManageUsersContainer";

export const isUserActive = (user: SettingsUser) =>
export const isUserActive = (user: SettingsUser | User) =>
user.status !== OktaUserStatus.SUSPENDED &&
user.status !== OktaUserStatus.PROVISIONED &&
!user.isDeleted;
Expand Down
100 changes: 47 additions & 53 deletions frontend/src/app/Settings/Users/UserFacilitiesSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,79 @@
import React from "react";
import { FieldErrors, UseFormRegister, UseFormSetValue } from "react-hook-form";
import { UseFormRegister, UseFormSetValue, FieldError } from "react-hook-form";

import Checkboxes from "../../commonComponents/Checkboxes";
import Required from "../../commonComponents/Required";
import { Facility } from "../../../generated/graphql";

import { UserFacilitySetting } from "./ManageUsersContainer";
import "./ManageUsers.scss";
import { CreateUser } from "./CreateUserSchema";
import { alphabeticalFacilitySort } from "./UserFacilitiesSettingsForm";

interface UserFacilitiesSettingProps {
formValues: CreateUser;
allFacilities: UserFacilitySetting[];
register: UseFormRegister<CreateUser>;
errors: FieldErrors<CreateUser>;
setValue: UseFormSetValue<CreateUser>;
roleSelected: string;
facilityList: Pick<Facility, "id" | "name">[];
register: UseFormRegister<any>;
error?: FieldError;
setValue: UseFormSetValue<any>;
disabled?: boolean;
}

// This is the react-hook-form supported version of UserFacilitiesSettingsForm.
const UserFacilitiesSettings: React.FC<UserFacilitiesSettingProps> = ({
formValues,
allFacilities,
roleSelected,
facilityList,
register,
errors,
error,
setValue,
disabled,
}) => {
const isAdmin = formValues.role === "ADMIN";
const isAdmin = roleSelected === "ADMIN";

const facilityAccessDescription = isAdmin
? "Admins have access to all facilities"
: "All users must have access to at least one facility";
allFacilities.sort(alphabeticalFacilitySort);

const allFacilities = [
{
name: `Access all facilities (${facilityList?.length || 0})`,
id: "ALL_FACILITIES",
},
].concat(facilityList?.sort(alphabeticalFacilitySort) || []);

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value, checked } = e.target;
if (value === "ALL_FACILITIES" && checked && setValue) {
setValue("facilityIds", [
"ALL_FACILITIES",
...allFacilities.map((facility) => facility.id),
]);
}
};

let boxes = [
{
value: "ALL_FACILITIES",
label: `Access all facilities (${allFacilities.length})`,
...register("facilityIds", {
required: "At least one facility must be selected",
disabled: isAdmin,
onChange,
}),
},
...allFacilities.map((facility) => ({
value: facility.id,
label: facility.name,
...register("facilityIds", {
required: "At least one facility must be selected",
disabled: isAdmin,
onChange,
}),
})),
];
const validateAtLeastOneCheck = (selectedOptions: string[]) => {
return isAdmin || selectedOptions.length > 0;
};

const formRegistrationProps =
register("facilityIds", {
validate: validateAtLeastOneCheck,
onChange,
}) || {};

let boxes = allFacilities?.map((facility) => ({
value: facility.id,
label: facility.name,
...formRegistrationProps,
}));

return (
<>
<h4 className="testing-facility-access-subheader margin-bottom-0">
<Required label={"Testing facility access"} />
</h4>
<p className="testing-facility-access-subtext">
{facilityAccessDescription}
</p>
<Checkboxes
boxes={boxes}
legend="Facilities"
legendSrOnly
name="facilities"
onChange={onChange}
validationStatus={errors?.facilityIds?.type ? "error" : undefined}
errorMessage={errors?.facilityIds?.message}
/>
</>
<Checkboxes
boxes={boxes}
legend="Facilities access"
hintText={facilityAccessDescription}
name="facilities"
required
onChange={() => {}}
validationStatus={error?.type ? "error" : undefined}
errorMessage={"At least one facility must be selected"}
disabled={disabled}
/>
);
};

Expand Down
5 changes: 3 additions & 2 deletions frontend/src/app/Settings/Users/UserInfoTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import classnames from "classnames";
import React, { useState } from "react";

import Button from "../../commonComponents/Button/Button";
import { User } from "../../../generated/graphql";

import { SettingsUser } from "./ManageUsersContainer";
import DeleteUserModal from "./DeleteUserModal";
Expand All @@ -12,7 +13,7 @@ import EditUserEmailModal from "./EditUserEmailModal";
import "./UserInfoTab.scss";

interface UserInfoTabProps {
user: SettingsUser;
user: SettingsUser | User;
isUserActive: boolean;
isUserSelf?: boolean;
isUpdating: boolean;
Expand Down Expand Up @@ -50,7 +51,7 @@ const UserInfoTab: React.FC<UserInfoTabProps> = ({
<>
<div
role="tabpanel"
aria-labelledby={"user-information-tab-id"}
aria-labelledby={"userinformation-tab"}
className="padding-left-1"
>
<h3 className="basic-info-header">Basic information</h3>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/Settings/Users/UserRoleSettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface RoleButton {
labelDescription: string;
}

const ROLES: RoleButton[] = [
export const ROLES: RoleButton[] = [
{
value: "ADMIN",
label: "Admin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ exports[`ManageUsersContainer loads the component and displays users successfull
class="prime-secondary-nav margin-top-4 padding-bottom-0"
>
<div
aria-owns="user-information-tab-id facility-access-tab-id"
aria-owns="userinformation-tab facility-access-tab-id"
class="usa-nav__secondary-links prime-nav usa-list"
role="tablist"
>
Expand All @@ -209,7 +209,7 @@ exports[`ManageUsersContainer loads the component and displays users successfull
<button
aria-selected="true"
class="usa-button--unstyled text-ink text-no-underline"
id="user-information-tab-id"
id="userinformation-tab"
role="tab"
>
User information
Expand All @@ -230,7 +230,7 @@ exports[`ManageUsersContainer loads the component and displays users successfull
</div>
</nav>
<div
aria-labelledby="user-information-tab-id"
aria-labelledby="userinformation-tab"
class="padding-left-1"
role="tabpanel"
>
Expand Down
Loading