Skip to content
This repository was archived by the owner on Jul 30, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 91 additions & 0 deletions db.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"roles": [
{
"id": 1,
"name": "oneRole"
},
{
"id": 2,
"name": "twoRole"
}
],
"parent": [
{
"id": 1,
"name": "oneRole"
},
{
"id": 2,
"name": "twoRole"
}
],
"institutions": [
{
"id": 1,
"name": "oneInst"
},
{
"id": 2,
"name": "twoInst"
}
],
"users": [
{
"id": 1,
"Username": "abc",
"name": "abcdef",
"email": "[email protected]",
"fullname": "abcdef, abc",
"firstName": "abcdef",
"lastName": "abcdef",
"emailPreferences": [],
"institution": "1",
"role_id": "1",
"parent_id": "1",
"email_on_review": true,
"email_on_submission": false,
"email_on_review_of_review": false,
"institution_id": "1"
},
{
"name": "bcdgf",
"email": "[email protected]",
"fullname": "dac, bac",
"role_id": "1",
"institution_id": "2",
"parent_id": "1",
"email_on_review": true,
"email_on_submission": false,
"email_on_review_of_review": false,
"apiId": "v1",
"id": 5
}
],
"participants": [
{
"name": "asa",
"email": "[email protected]",
"fullname": "Sivakumar Annu, Amarthya",
"role_id": "2",
"institution_id": "1",
"parent_id": "1",
"email_on_review": true,
"email_on_submission": false,
"email_on_review_of_review": false,
"id": 4
},
{
"name": "bcdgf",
"email": "[email protected]",
"fullname": "dac, bac",
"role_id": "1",
"institution_id": "2",
"parent_id": "1",
"email_on_review": true,
"email_on_submission": false,
"email_on_review_of_review": false,
"apiId": "v1",
"id": 5
}
]
}
4 changes: 4 additions & 0 deletions routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"users/:id": "api/v1/users/:id",
"users": "api/v1/users"
}
2 changes: 2 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./components/Layout/Home";
import RootLayout from "./components/Layout/Root";
import Users from "./components/Users/Users";
import Participants from "./components/Participants/Participants";

function App() {
const router = createBrowserRouter([
Expand All @@ -12,6 +13,7 @@ function App() {
children: [
{ index: true, element: <Home /> },
{ path: "users", element: <Users /> },
{ path: "participants", element: <Participants /> },
],
},
]);
Expand Down
3 changes: 3 additions & 0 deletions src/components/Layout/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ const Header = () => {
<NavDropdown.Item as={Link} to="/questionnaire">
Questionnaire
</NavDropdown.Item>
<NavDropdown.Item as={Link} to="/participants">
Participants
</NavDropdown.Item>
<NavDropdown.Divider/>
<NavDropdown.Item as={Link} to="/impersonate">
Impersonate User
Expand Down
190 changes: 190 additions & 0 deletions src/components/Participants/CreateParticipant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import {Form, Formik} from "formik";
import {useEffect, useState} from "react";
import {Button, Col, InputGroup, Modal, Row} from "react-bootstrap";
import {useDispatch} from "react-redux";
import * as Yup from "yup";
import useAPI from "../../hooks/use-api";
import {alertActions} from "../../store/alert";
import FormCheckboxGroup from "../UI/Form/FormCheckboxGroup";
import FormInput from "../UI/Form/FormInput";
import FormSelect from "../UI/Form/FormSelect";
import {emailOptions, transformInstitutionsResponse, transformRolesResponse, transformParticipantRequest,} from "./util";


const loggedInParticipant = "1";

// initial values for the new participant
const initialValues = {
name: "",
email: "",
firstName: "",
lastName: "",
emailPreferences: [],
institution: "",
role: "",
};
// Validating if the values entered for the new participant are correct
const validationSchema = Yup.object({
name: Yup.string()
.required("Required")
.lowercase("Participantname must be lowercase")
.min(3, "Participantname must be at least 3 characters")
.max(20, "Participantname must be at most 20 characters"),
email: Yup.string().required("Required").email("Invalid email format"),
firstName: Yup.string().required("Required").nonNullable(),
lastName: Yup.string().required("Required").nonNullable(),
role: Yup.string().required("Required").nonNullable(),
institution: Yup.string().required("Required").nonNullable(),
});


const CreateParticipant = ({onClose}) => {
const dispatch = useDispatch();
const [show, setShow] = useState(true);
const {data: roles, sendRequest: fetchRoles} = useAPI();
const {data: institutions, sendRequest: fetchInstitutions} = useAPI();
const {
data: createdParticipant,
error: participantError,
sendRequest: createParticipant,
} = useAPI();

// Fetching the roles, institutions that need to be listed on the roles, institutions drop down on the create user form
useEffect(() => {
fetchRoles({url: "/roles", transformResponse: transformRolesResponse});
fetchInstitutions({
url: "/institutions",
transformResponse: transformInstitutionsResponse,
});
}, [fetchRoles, fetchInstitutions]);

useEffect(() => {
if (participantError) {
dispatch(alertActions.showAlert({
variant: "danger",
message: participantError
}));
}
}, [participantError, dispatch]);

// if the participant was created, onclose is set to the newly created participant
useEffect(() => {
if (createdParticipant.length > 0) {
setShow(false);
onClose(createdParticipant[0]);
}
}, [participantError, createdParticipant, onClose]);

/* post request to the API with the new participants values when onSubmit is called
*/
const onSubmit = (values, submitProps) => {
createParticipant({
url: "/participants",
method: "post",
data: {...values, parent: loggedInParticipant},
transformRequest: transformParticipantRequest,
});
submitProps.resetForm();
submitProps.setSubmitting(false);
};

const handleClose = () => {
setShow(false);
onClose();
};

return (
<Modal
size="lg"
centered
show={show}
onHide={handleClose}
backdrop="static"
>
<Modal.Header closeButton>
<Modal.Title>Create Participant</Modal.Title>
</Modal.Header>
{/* onSubmit is called when Create Participant button on the create Participant form is clicked */}
<Modal.Body>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={validationSchema}
validateOnChange={false}
>
{(formik) => {
return (
<Form>
<FormSelect
controlId="participant-role"
name="role"
options={roles}
inputGroupPrepend={
<InputGroup.Text id="role-prepend">Role</InputGroup.Text>
}
/>
<FormInput
controlId="participant-name"
label="Participant Name"
name="name"
inputGroupPrepend={
<InputGroup.Text id="participant-name-prep">@</InputGroup.Text>
}
/>
<Row>
<FormInput
as={Col}
controlId="participant-first-name"
label="First name"
name="firstName"
/>
<FormInput
as={Col}
controlId="participant-last-name"
label="Last name"
name="lastName"
/>
</Row>
<FormInput controlId="participant-email" label="Email" name="email"/>

<FormCheckboxGroup
controlId="email-pref"
label="Email Preferences"
name="emailPreferences"
options={emailOptions}
/>
<FormSelect
controlId="participant-institution"
name="institution"
options={institutions}
inputGroupPrepend={
<InputGroup.Text id="participant-inst-prep">
Institution
</InputGroup.Text>
}
/>

<Modal.Footer>
<Button variant="outline-secondary" onClick={handleClose}>
Close
</Button>
<Button
variant="outline-success"
type="submit"
disabled={
!(formik.isValid && formik.dirty) || formik.isSubmitting
}
>
Create Participant
</Button>
</Modal.Footer>
</Form>
);
}}
</Formik>
</Modal.Body>
</Modal>
);
};

export default CreateParticipant;
66 changes: 66 additions & 0 deletions src/components/Participants/DeleteParticipant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {useEffect, useState} from "react";
import {Button, Modal} from "react-bootstrap";
import {useDispatch} from "react-redux";
import useAPI from "../../hooks/use-api";
import {alertActions} from "../../store/alert";

const DeleteParticipant = ({participantData, onClose}) => {
const dispatch = useDispatch();
const {
data: deletedParticipant,
error: participantError,
sendRequest: deleteParticipant,
} = useAPI();
// show initially set to true
const [show, setShow] = useState(true);

// useAPI called with the `/participants/${participantData.id}` URL
const deleteHandler = () =>
deleteParticipant({url: `/participants/${participantData.id}`, method: "DELETE"});

useEffect(() => {
if (participantError) {
dispatch(alertActions.showAlert({
variant: "danger",
message: participantError,
}));
}
}, [participantError, dispatch]);

// if the participant was deleted, onclose is set to the deleted participant
useEffect(() => {
if (deletedParticipant.length > 0) {
setShow(false);
onClose(deletedParticipant[0]);
}
}, [deletedParticipant, onClose]);

const closeHandler = () => {
setShow(false);
onClose();
};

// deleteHandler is called with Delete button is clicked and closeHandler is called when Cancel button is clicked
return (
<Modal show={show} onHide={closeHandler}>
<Modal.Header closeButton>
<Modal.Title>Delete Participant</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
Are you sure you want to delete participant <b>{participantData.name}?</b>
</p>
</Modal.Body>
<Modal.Footer>
<Button variant="outline-secondary" onClick={closeHandler}>
Cancel
</Button>
<Button variant="outline-danger" onClick={deleteHandler}>
Delete
</Button>
</Modal.Footer>
</Modal>
);
};

export default DeleteParticipant;
Loading