From 647c4fa7b4deb454f3bd9b9609e9e0cac3973bcb Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 13 Sep 2023 00:40:09 -0700 Subject: [PATCH 001/178] Adding Message button to contacts --- src/components/Contacts/ContactListTable.jsx | 2 +- .../Contacts/ContactListTableRow.jsx | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/Contacts/ContactListTable.jsx b/src/components/Contacts/ContactListTable.jsx index 3d8a8d3cc..439d019c5 100644 --- a/src/components/Contacts/ContactListTable.jsx +++ b/src/components/Contacts/ContactListTable.jsx @@ -12,7 +12,7 @@ import ContactListTableRow from './ContactListTableRow'; import { StyledTableCell } from '../Table/TableStyles'; // ===== MAKE CHANGES HERE FOR TABLE HEADER / COLUMN TITLES ===== -const columnTitlesArray = ['Contact', 'Pin', 'Delete']; +const columnTitlesArray = ['Contact', 'Pin', 'Message', 'Delete']; /** * contactListTableProps is an object that stores the props for the diff --git a/src/components/Contacts/ContactListTableRow.jsx b/src/components/Contacts/ContactListTableRow.jsx index fce5fff74..391a13f25 100644 --- a/src/components/Contacts/ContactListTableRow.jsx +++ b/src/components/Contacts/ContactListTableRow.jsx @@ -10,11 +10,13 @@ import IconButton from '@mui/material/IconButton'; import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import PushPinIcon from '@mui/icons-material/PushPin'; import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined'; +import SendIcon from '@mui/icons-material/Send'; // Context Imports import { DocumentListContext } from '@contexts'; // Custom Hook Imports import useNotification from '../../hooks/useNotification'; // Component Imports +import { NewMessageModal } from '../Modals'; import { StyledTableCell, StyledTableRow } from '../Table/TableStyles'; /** @@ -42,6 +44,7 @@ const ContactListTableRow = ({ contact, deleteContact }) => { const { setContact } = useContext(DocumentListContext); const navigate = useNavigate(); const { addNotification } = useNotification(); + const [showModal, setShowModal] = useState(false); // determine what icon gets rendered in the pinned column const pinnedIcon = pinned ? : ; @@ -85,11 +88,28 @@ const ContactListTableRow = ({ contact, deleteContact }) => { {pinnedIcon} + + setShowModal(!showModal)} + // startIcon={} + > + + + deleteContact(contact)}> + {showModal && ( + + )} ); }; From 78879efa80259c1932dffdd1d6e83a4ce5006549 Mon Sep 17 00:00:00 2001 From: Andy Date: Sat, 16 Sep 2023 19:39:36 -0700 Subject: [PATCH 002/178] Resolving merge conflicts --- .../Contacts/ContactListTableRow.jsx | 95 ++++++++++++------- src/components/Modals/AddContactModal.jsx | 14 +-- 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/src/components/Contacts/ContactListTableRow.jsx b/src/components/Contacts/ContactListTableRow.jsx index 391a13f25..2106b2dbd 100644 --- a/src/components/Contacts/ContactListTableRow.jsx +++ b/src/components/Contacts/ContactListTableRow.jsx @@ -68,22 +68,26 @@ const ContactListTableRow = ({ contact, deleteContact }) => { }; return ( - - - - - - - + + {/* {pinnedIcon} @@ -93,24 +97,51 @@ const ContactListTableRow = ({ contact, deleteContact }) => { size="large" variant="contained" onClick={() => setShowModal(!showModal)} - // startIcon={} > - - - - - deleteContact(contact)}> - - - - {showModal && ( - - )} - +
*/} + + + +
+ (date added) +
+ + {pinnedIcon} + +
+
+ deleteContact(contact)}> + + +
+ + + ); }; diff --git a/src/components/Modals/AddContactModal.jsx b/src/components/Modals/AddContactModal.jsx index 2d3052ec1..071ed4704 100644 --- a/src/components/Modals/AddContactModal.jsx +++ b/src/components/Modals/AddContactModal.jsx @@ -16,9 +16,9 @@ import { FormSection } from '../Form'; import useNotification from '../../hooks/useNotification'; /** - * @memberof Contcts + * @memberof Modals * @name renderWebId - * @param {string} username - username to convert into a webId + * @param {string} username - Username to convert into a webId * @returns {URL} A url of the predicted webID */ const renderWebId = (username) => { @@ -30,12 +30,12 @@ const renderWebId = (username) => { * AddContactModal Component - Component that allows users to add other user's * Pod URLs from a user's list stored on their own Pod * - * @memberof Contacts + * @memberof Modals * @name AddContactModal - * @param {object} props - react props - * @param {Function} props.addContact - function to add a contact - * @param {boolean} props.showAddContactModal - whether to display modal or not - * @param {Function} props.setShowAddContactModal - toggle modal + * @param {object} props - React props + * @param {Function} props.addContact - Function to add a contact + * @param {boolean} props.showAddContactModal - Whether to display modal or not + * @param {Function} props.setShowAddContactModal - Toggle modal * @returns {React.JSX.Element} - The Add Contact Modal */ const AddContactModal = ({ addContact, showAddContactModal, setShowAddContactModal }) => { From b1ed19a16c669e03550323cd910e808927763f03 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 20 Sep 2023 20:07:21 -0700 Subject: [PATCH 003/178] Reverting to older changes --- .../Contacts/ContactListTableRow.jsx | 101 ++++++------------ 1 file changed, 30 insertions(+), 71 deletions(-) diff --git a/src/components/Contacts/ContactListTableRow.jsx b/src/components/Contacts/ContactListTableRow.jsx index 2106b2dbd..5f3f4a42d 100644 --- a/src/components/Contacts/ContactListTableRow.jsx +++ b/src/components/Contacts/ContactListTableRow.jsx @@ -25,8 +25,7 @@ import { StyledTableCell, StyledTableRow } from '../Table/TableStyles'; * * @typedef {object} contactListTableRowProps * @property {object} contact - Object containing contact information - * @property {Function} deleteContact - * - function to delete a chosen contact + * @property {Function} deleteContact - Function to delete a chosen contact */ /** @@ -38,7 +37,7 @@ import { StyledTableCell, StyledTableRow } from '../Table/TableStyles'; * @param {contactListTableRowProps} Props - Props for ContactListTableRow * @returns {React.JSX.Element} The ContactListTableRow Component */ -const ContactListTableRow = ({ contact, deleteContact }) => { +const ContactListTableRow = ({ contact, deleteContact, message }) => { const theme = useTheme(); const [pinned, setPinned] = useState(false); const { setContact } = useContext(DocumentListContext); @@ -68,80 +67,40 @@ const ContactListTableRow = ({ contact, deleteContact }) => { }; return ( - - + + + + + + - {/* {pinnedIcon} - setShowModal(!showModal)} - > -
*/} - - - -
- (date added) -
- - {pinnedIcon} - -
-
- deleteContact(contact)}> - - -
- -
-
+ setShowModal(!showModal)}> + + + + + deleteContact(contact)}> + + + + {showModal && ( + + )} + ); }; From 7408a2c2c67a096dc169976f236ca5b1774e36e7 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Sat, 14 Oct 2023 09:05:23 -0700 Subject: [PATCH 004/178] Replaced existing contact list with MUI Data Grid; Fixed variable for contactProfile.profileImage in ProfileImageField.jsx --- package-lock.json | 132 +++++++++++++----- package.json | 1 + src/components/Contacts/ContactListTable.jsx | 90 ++++++------ .../Contacts/ContactListTableRow.jsx | 106 -------------- .../Contacts/ContactProfileIcon.jsx | 46 ++++++ src/components/Contacts/index.js | 4 +- src/components/Profile/ProfileImageField.jsx | 2 +- 7 files changed, 200 insertions(+), 181 deletions(-) delete mode 100644 src/components/Contacts/ContactListTableRow.jsx create mode 100644 src/components/Contacts/ContactProfileIcon.jsx diff --git a/package-lock.json b/package-lock.json index 553bc1ed9..dbc786509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@mui/material": "^5.12.2", "@mui/styled-engine-sc": "^5.12.0", "@mui/system": "^5.12.1", + "@mui/x-data-grid": "^6.16.2", "@mui/x-date-pickers": "^6.5.0", "@tanstack/react-query": "^4.32.0", "@zxing/browser": "^0.1.3", @@ -352,10 +353,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "license": "MIT", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -3329,12 +3331,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.13.1", - "license": "MIT", + "version": "5.14.13", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.13.tgz", + "integrity": "sha512-2AFpyXWw7uDCIqRu7eU2i/EplZtks5LAMzQvIhC79sPV9IhOZU2qwOWVnPtdctRXiQJOAaXulg+A37pfhEueQw==", "dependencies": { - "@babel/runtime": "^7.21.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.0", + "@babel/runtime": "^7.23.1", + "@types/prop-types": "^15.7.7", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -3346,13 +3348,52 @@ "url": "https://opencollective.com/mui" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@mui/utils/node_modules/react-is": { "version": "18.2.0", "license": "MIT" }, + "node_modules/@mui/x-data-grid": { + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.16.2.tgz", + "integrity": "sha512-nKZxlzkcqXUbgxvz01J0okziBH4uvnEVVneOSzp5TeCOC7Wke1YFBJRRjR6H6tVpbaqlOw/LFULrTfcJtiaMBQ==", + "dependencies": { + "@babel/runtime": "^7.23.1", + "@mui/utils": "^5.14.11", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "reselect": "^4.1.8" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.4.1", + "@mui/system": "^5.4.1", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@mui/x-data-grid/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/x-date-pickers": { "version": "6.7.0", "license": "MIT", @@ -4408,8 +4449,9 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "license": "MIT" + "version": "15.7.8", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", + "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==" }, "node_modules/@types/proper-lockfile": { "version": "4.1.2", @@ -4476,13 +4518,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-is": { - "version": "18.2.0", - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-transition-group": { "version": "4.4.6", "license": "MIT", @@ -12678,8 +12713,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "license": "MIT" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -12734,6 +12770,11 @@ "lodash": "^4.17.21" } }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "node_modules/resolve": { "version": "1.22.1", "license": "MIT", @@ -14914,9 +14955,11 @@ } }, "@babel/runtime": { - "version": "7.21.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { @@ -17342,11 +17385,12 @@ "requires": {} }, "@mui/utils": { - "version": "5.13.1", + "version": "5.14.13", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.13.tgz", + "integrity": "sha512-2AFpyXWw7uDCIqRu7eU2i/EplZtks5LAMzQvIhC79sPV9IhOZU2qwOWVnPtdctRXiQJOAaXulg+A37pfhEueQw==", "requires": { - "@babel/runtime": "^7.21.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.0", + "@babel/runtime": "^7.23.1", + "@types/prop-types": "^15.7.7", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -17356,6 +17400,25 @@ } } }, + "@mui/x-data-grid": { + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.16.2.tgz", + "integrity": "sha512-nKZxlzkcqXUbgxvz01J0okziBH4uvnEVVneOSzp5TeCOC7Wke1YFBJRRjR6H6tVpbaqlOw/LFULrTfcJtiaMBQ==", + "requires": { + "@babel/runtime": "^7.23.1", + "@mui/utils": "^5.14.11", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "reselect": "^4.1.8" + }, + "dependencies": { + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + } + } + }, "@mui/x-date-pickers": { "version": "6.7.0", "requires": { @@ -18103,7 +18166,9 @@ "version": "4.0.0" }, "@types/prop-types": { - "version": "15.7.5" + "version": "15.7.8", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", + "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==" }, "@types/proper-lockfile": { "version": "4.1.2", @@ -18162,12 +18227,6 @@ "@types/react": "*" } }, - "@types/react-is": { - "version": "18.2.0", - "requires": { - "@types/react": "*" - } - }, "@types/react-transition-group": { "version": "4.4.6", "requires": { @@ -23399,7 +23458,9 @@ } }, "regenerator-runtime": { - "version": "0.13.11" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "regexp.prototype.flags": { "version": "1.4.3", @@ -23433,6 +23494,11 @@ "lodash": "^4.17.21" } }, + "reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "resolve": { "version": "1.22.1", "requires": { diff --git a/package.json b/package.json index 9a751d775..b954b9991 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@mui/material": "^5.12.2", "@mui/styled-engine-sc": "^5.12.0", "@mui/system": "^5.12.1", + "@mui/x-data-grid": "^6.16.2", "@mui/x-date-pickers": "^6.5.0", "@tanstack/react-query": "^4.32.0", "@zxing/browser": "^0.1.3", diff --git a/src/components/Contacts/ContactListTable.jsx b/src/components/Contacts/ContactListTable.jsx index af636c5e9..8ad11985e 100644 --- a/src/components/Contacts/ContactListTable.jsx +++ b/src/components/Contacts/ContactListTable.jsx @@ -1,23 +1,24 @@ // React Imports import React from 'react'; // Material UI Imports -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; - -// MUI Theme -import { ThemeProvider } from '@mui/material/styles'; -import theme from '../../theme'; - +import Box from '@mui/material/Box'; +import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; +import { + DataGrid, + GridToolbarContainer, + GridActionsCellItem, + GridToolbarFilterButton, + GridToolbarDensitySelector +} from '@mui/x-data-grid'; // Component Imports -import ContactListTableRow from './ContactListTableRow'; +import ContactProfileIcon from './ContactProfileIcon'; -// ===== MAKE CHANGES HERE FOR TABLE HEADER / COLUMN TITLES ===== -const columnTitlesArray = ['Contact', 'Pin', 'Delete']; +const CustomToolbar = () => ( + + + + +); /** * contactListTableProps is an object that stores the props for the @@ -50,31 +51,42 @@ const ContactListTable = ({ contacts, deleteContact }) => { const contactsCopy = [...contacts]; const sortedContacts = contactsCopy.sort(comparePerson); + const columnTitlesArray = [ + { field: 'Contact', width: 120 }, + { + field: 'Profile', + renderCell: (contactData) => , + sortable: false, + filterable: false + }, + { + field: 'actions', + type: 'actions', + getActions: (contactData) => [ + } + onClick={() => deleteContact(contactData.row.Delete)} + /> + ] + } + ]; + return ( - - - - - - {columnTitlesArray.map((columnTitle) => ( - - {columnTitle} - - ))} - - - - {sortedContacts?.map((contact) => ( - - ))} - -
-
-
+ + ({ + id: contact.webId, + Contact: contact.person, + Profile: contact, + Delete: contact + }))} + slots={{ + toolbar: CustomToolbar + }} + slotProps={{ columnMenu: { background: 'red' } }} + /> + ); }; diff --git a/src/components/Contacts/ContactListTableRow.jsx b/src/components/Contacts/ContactListTableRow.jsx deleted file mode 100644 index 91d0eb708..000000000 --- a/src/components/Contacts/ContactListTableRow.jsx +++ /dev/null @@ -1,106 +0,0 @@ -// React Imports -import React, { useContext, useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -// Inrupt Imports -import { getWebIdDataset } from '@inrupt/solid-client'; -// Material UI Imports -import Button from '@mui/material/Button'; -import IconButton from '@mui/material/IconButton'; -import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; -import PushPinIcon from '@mui/icons-material/PushPin'; -import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined'; -import TableCell from '@mui/material/TableCell'; -import TableRow from '@mui/material/TableRow'; - -// Context Imports -import { DocumentListContext } from '@contexts'; - -// MUI Theme -import { ThemeProvider } from '@mui/material/styles'; -import theme from '../../theme'; - -// Custom Hook Imports -import useNotification from '../../hooks/useNotification'; - -/** - * contactListTableRowProps is an object that stores the props for the - * ContactListTableRow component - * - * @typedef {object} contactListTableRowProps - * @property {object} contact - Object containing contact information - * @property {Function} deleteContact - * - function to delete a chosen contact - */ - -/** - * ContactListTableRow Component - Component that generates the individual table - * rows of contacts from data within ContactList - * - * @memberof Contacts - * @name ContactListTableRow - * @param {contactListTableRowProps} Props - Props for ContactListTableRow - * @returns {React.JSX.Element} The ContactListTableRow Component - */ -const ContactListTableRow = ({ contact, deleteContact }) => { - const [pinned, setPinned] = useState(false); - const { setContact } = useContext(DocumentListContext); - const navigate = useNavigate(); - const { addNotification } = useNotification(); - - // determine what icon gets rendered in the pinned column - const pinnedIcon = pinned ? : ; - - // Event handler for pinning contact to top of table - // ***** TODO: Add in moving pinned row to top of table - const handlePinClick = () => { - setPinned(!pinned); - }; - - // Event handler for profile page routing - const handleSelectProfile = async (contactInfo) => { - try { - await getWebIdDataset(contactInfo.webId); - setContact(contact); - } catch { - setContact(null); - navigate('/contacts'); - addNotification('error', 'WebId does not exist'); - } - }; - - return ( - - - - - - - - - - {pinnedIcon} - - - - deleteContact(contact)}> - - - - - - ); -}; - -export default ContactListTableRow; diff --git a/src/components/Contacts/ContactProfileIcon.jsx b/src/components/Contacts/ContactProfileIcon.jsx new file mode 100644 index 000000000..f61633997 --- /dev/null +++ b/src/components/Contacts/ContactProfileIcon.jsx @@ -0,0 +1,46 @@ +// React Imports +import React, { useContext } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +// Inrupt Imports +import { getWebIdDataset } from '@inrupt/solid-client'; +// Material UI Imports +import ContactPageIcon from '@mui/icons-material/ContactPage'; +import IconButton from '@mui/material/IconButton'; +// Custom Hook Imports +import { useNotification } from '@hooks'; +// Context Imports +import { DocumentListContext } from '@contexts'; +// MUI Theme +import theme from '../../theme'; + +const ContactProfileIcon = ({ contact }) => { + const { setContact } = useContext(DocumentListContext); + const navigate = useNavigate(); + const { addNotification } = useNotification(); + + // Event handler for profile page routing + const handleSelectProfile = async (contactInfo) => { + try { + await getWebIdDataset(contactInfo.webId); + setContact(contactInfo); + } catch { + setContact(null); + navigate('/contacts'); + addNotification('error', 'WebId does not exist'); + } + }; + + return ( + + handleSelectProfile(contact.value)}> + + + + ); +}; + +export default ContactProfileIcon; diff --git a/src/components/Contacts/index.js b/src/components/Contacts/index.js index 41ea89b2a..50fa85800 100644 --- a/src/components/Contacts/index.js +++ b/src/components/Contacts/index.js @@ -1,5 +1,5 @@ import ContactListTable from './ContactListTable'; -import ContactListTableRow from './ContactListTableRow'; +import ContactProfileIcon from './ContactProfileIcon'; /** * Components and functions related to Contacts functionality within project PASS @@ -7,4 +7,4 @@ import ContactListTableRow from './ContactListTableRow'; * @namespace Contacts */ -export { ContactListTable, ContactListTableRow }; +export { ContactListTable, ContactProfileIcon }; diff --git a/src/components/Profile/ProfileImageField.jsx b/src/components/Profile/ProfileImageField.jsx index f91be686c..1410c5672 100644 --- a/src/components/Profile/ProfileImageField.jsx +++ b/src/components/Profile/ProfileImageField.jsx @@ -65,7 +65,7 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => { > Profile Image: From e4dd4e764a2f9138f64c91fde31693d5da5da1e6 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Sat, 14 Oct 2023 10:01:29 -0700 Subject: [PATCH 005/178] Styled data grid --- src/components/Contacts/ContactListTable.jsx | 30 ++++++++++++++++++-- src/pages/Contacts.jsx | 5 ++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/components/Contacts/ContactListTable.jsx b/src/components/Contacts/ContactListTable.jsx index 8ad11985e..8dc35fca3 100644 --- a/src/components/Contacts/ContactListTable.jsx +++ b/src/components/Contacts/ContactListTable.jsx @@ -10,6 +10,8 @@ import { GridToolbarFilterButton, GridToolbarDensitySelector } from '@mui/x-data-grid'; +// MUI Theme +import theme from '../../theme'; // Component Imports import ContactProfileIcon from './ContactProfileIcon'; @@ -52,16 +54,26 @@ const ContactListTable = ({ contacts, deleteContact }) => { const sortedContacts = contactsCopy.sort(comparePerson); const columnTitlesArray = [ - { field: 'Contact', width: 120 }, + { + field: 'Contact', + width: 160, + headerAlign: 'center', + align: 'center', + sortable: false + }, { field: 'Profile', renderCell: (contactData) => , sortable: false, - filterable: false + filterable: false, + width: 70, + headerAlign: 'center', + align: 'center' }, { field: 'actions', type: 'actions', + width: 70, getActions: (contactData) => [ } @@ -84,7 +96,19 @@ const ContactListTable = ({ contacts, deleteContact }) => { slots={{ toolbar: CustomToolbar }} - slotProps={{ columnMenu: { background: 'red' } }} + sx={{ + '.MuiDataGrid-columnHeader': { + background: theme.palette.primary.light + } + }} + pageSizeOptions={[10]} + initialState={{ + pagination: { + paginationModel: { pageSize: 10, page: 0 } + } + }} + disableColumnMenu + disableRowSelectionOnClick /> ); diff --git a/src/pages/Contacts.jsx b/src/pages/Contacts.jsx index c9fd80910..d7b9f14e9 100644 --- a/src/pages/Contacts.jsx +++ b/src/pages/Contacts.jsx @@ -54,10 +54,11 @@ const Contacts = () => { sx={{ display: 'flex', flexDirection: 'column', - alignItems: 'center' + alignItems: 'center', + minWidth: '300px' }} > - + + + + ) : ( + + )} + + + + ); +}; export default NavbarLoggedOut; diff --git a/src/components/NavBar/OidcLoginComponent.jsx b/src/components/NavBar/OidcLoginComponent.jsx index f4d802112..58d743ab8 100644 --- a/src/components/NavBar/OidcLoginComponent.jsx +++ b/src/components/NavBar/OidcLoginComponent.jsx @@ -26,8 +26,7 @@ const OidcLoginComponent = () => { await login({ oidcIssuer, redirectUrl }); }; return ( - <> - + { borderRadius: '8px' }} /> - - + ); }; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 1e901e063..a9ef7c630 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -26,7 +26,7 @@ const Home = () => { const isReallySmallScreen = useMediaQuery(theme.breakpoints.down('sm')); return ( - + { isReallySmallScreen={isReallySmallScreen} componentImageSrc="/assets/app-green.png" componentImageAlt="" - title="An App Built for Caseworkers" - description="PASS allows users to quickly and securely share documents of their clients within the app. The app helps caseworkers verify and share documents such as ID and proof of income while retaining total control of them." + title="An App Built for Case Workers" + description="PASS allows users to quickly and securely share documents of their clients within the app. The app helps case workers verify and share documents such as ID and proof of income while retaining total control of them." /> { } - title="Nonprofit & Caseworker Integration" - description="The platform facilitates smooth communication between nonprofit organizations, caseworkers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and caseworkers are assigned contacts whose data they can access securely." + title="Non-profit & Case Worker Integration" + description="The platform facilitates smooth communication between non-profit organizations, case workers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and case workers are assigned contacts whose data they can access securely." /> Date: Mon, 23 Oct 2023 15:10:31 -0700 Subject: [PATCH 025/178] Adjust UI for Sign In modal --- src/components/Modals/SignInModal.jsx | 20 +++++-- src/components/NavBar/OidcLoginComponent.jsx | 57 ++++++++++++++------ 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/components/Modals/SignInModal.jsx b/src/components/Modals/SignInModal.jsx index 01ecbe5ce..97e4387ab 100644 --- a/src/components/Modals/SignInModal.jsx +++ b/src/components/Modals/SignInModal.jsx @@ -8,6 +8,18 @@ import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; import OidcLoginComponent from '../NavBar/OidcLoginComponent'; +/** + * The SignInModal Component is a component that renders the Sign In modal which + * logs people into PASS. It only appears for the mobile version of PASS + * + * @memberof Modals + * @name SignInModal + * @param {object} Props - The props for SignInModal Component + * @param {boolean} Props.showSignInModal - The state for showing the modal + * @param {React.Dispatch>} Props.setShowSignInModal + * - The set function for handling the Sign In modal + * @returns {React.JSX.Element} - The SignInModal Component + */ const SignInModal = ({ showSignInModal, setShowSignInModal }) => ( ( Sign In? - Welcome to PASS. Sign in below + + Welcome to PASS. Sign in below + - - + + ); diff --git a/src/components/NavBar/OidcLoginComponent.jsx b/src/components/NavBar/OidcLoginComponent.jsx index 58d743ab8..5d413a105 100644 --- a/src/components/NavBar/OidcLoginComponent.jsx +++ b/src/components/NavBar/OidcLoginComponent.jsx @@ -6,6 +6,8 @@ import { useSession } from '@hooks'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import TextField from '@mui/material/TextField'; +import { useTheme } from '@mui/material'; +import useMediaQuery from '@mui/material/useMediaQuery'; // Constants Imports import { ENV } from '../../constants'; @@ -15,9 +17,12 @@ import { ENV } from '../../constants'; * * @memberof NavBar * @name OidcLoginComponent + * @param {object} Props - The props for OidcLoginComponent + * @param {React.Dispatch>} Props.setShowSignInModal + * - The set function for closing sign in modal * @returns {React.JSX.Element} - The OidcLoginComponent Component */ -const OidcLoginComponent = () => { +const OidcLoginComponent = ({ setShowSignInModal }) => { const { login } = useSession(); const defaultOidc = ENV.VITE_SOLID_IDENTITY_PROVIDER || ''; const [oidcIssuer, setOidcIssuer] = useState(defaultOidc); @@ -25,8 +30,19 @@ const OidcLoginComponent = () => { const redirectUrl = window.location.href; await login({ oidcIssuer, redirectUrl }); }; + + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + return ( - + { }} sx={{ backgroundColor: 'white', - borderRadius: '8px' + borderRadius: '8px', + border: isSmallScreen ? '1px solid grey' : '', + width: '100%' }} /> - + + {isSmallScreen ? ( + + ) : null} + + ); }; From 446511c2f5f4c2c29cbe93e226a2980ceca8b408 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Mon, 23 Oct 2023 16:06:11 -0700 Subject: [PATCH 026/178] Adjust UI for Messages Page --- src/components/Messages/MessageFolder.jsx | 60 +++++++++++----------- src/components/Messages/MessagePreview.jsx | 41 +++++++++++---- src/components/Messages/MessageStyles.js | 5 +- src/pages/Messages.jsx | 13 ++++- 4 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/components/Messages/MessageFolder.jsx b/src/components/Messages/MessageFolder.jsx index 4a4adc6f4..b449591dd 100644 --- a/src/components/Messages/MessageFolder.jsx +++ b/src/components/Messages/MessageFolder.jsx @@ -7,6 +7,8 @@ import Button from '@mui/material/Button'; import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import RotateLeftOutlinedIcon from '@mui/icons-material/RotateLeftOutlined'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; // Component Imports import MessagePreview from './MessagePreview'; import { PaginationContainer } from './MessageStyles'; @@ -52,13 +54,13 @@ const MessageFolder = ({ folderType, handleRefresh, loadMessages, messageList }) )); const handleMessages = () => { - if (currentMessages.length > 0) { - return withMessages; - } - + if (currentMessages.length > 0) return withMessages; return noMessages; }; + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + return ( handleRefresh(folderType)} type="button" - sx={{ - width: '120px' - }} + sx={{ width: '120px', margin: isSmallScreen ? '10px 20px' : '10px' }} startIcon={} > Refresh - {loadMessages ? : handleMessages()} - - - - } - onPageChange={handlePageClick} - pageRangeDisplayed={5} - pageCount={pageCount === 0 ? 1 : pageCount} - previousLabel={} - renderOnZeroPageCount={null} - className="pagination" - previousLinkClassName="page-red" - previousClassName="chevron" - nextLinkClassName="page-red" - nextClassName="chevron" - pageLinkClassName="page-green" - activeLinkClassName="active-page" - /> - + + {loadMessages ? : handleMessages()} + + + } + onPageChange={handlePageClick} + pageRangeDisplayed={5} + pageCount={pageCount === 0 ? 1 : pageCount} + previousLabel={} + renderOnZeroPageCount={null} + className="pagination" + previousLinkClassName="page-red" + previousClassName="chevron" + nextLinkClassName="page-red" + nextClassName="chevron" + pageLinkClassName="page-green" + activeLinkClassName="active-page" + /> + + + ); diff --git a/src/components/Messages/MessagePreview.jsx b/src/components/Messages/MessagePreview.jsx index 906acd362..ffea5050c 100644 --- a/src/components/Messages/MessagePreview.jsx +++ b/src/components/Messages/MessagePreview.jsx @@ -9,6 +9,8 @@ import Grid from '@mui/material/Grid'; import ListItemButton from '@mui/material/ListItemButton'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; // Custom Hook Imports import { useSession } from '@hooks'; // Utility Imports @@ -58,10 +60,36 @@ const MessagePreview = ({ message, folderType }) => { setShowModal(!showModal); }; + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + const isMediumScreen = useMediaQuery(theme.breakpoints.down('md')); + + const renderMediumGridLeft = () => { + if (isMediumScreen) return 8; + return 5; + }; + + const renderMediumGridRight = () => { + if (isMediumScreen) return 4; + return 2; + }; + const messageInfo = [ - { title: 'Sender: ', text: message.sender, xs_value: 3 }, - { title: 'Subject: ', text: message.title, xs_value: 7 }, - { title: 'Date: ', text: message.uploadDate.toLocaleDateString(), xs_value: 2 } + { + title: 'Sender: ', + text: message.sender, + xs_value: isSmallScreen ? 12 : renderMediumGridLeft() + }, + { + title: 'Subject: ', + text: message.title, + xs_value: isSmallScreen ? 12 : renderMediumGridLeft() + }, + { + title: 'Date: ', + text: message.uploadDate.toLocaleDateString(), + xs_value: isSmallScreen ? 12 : renderMediumGridRight() + } ]; return ( @@ -69,12 +97,7 @@ const MessagePreview = ({ message, folderType }) => { handleClick()} alignItems="flex-start"> - + {messageInfo.map((info) => ( diff --git a/src/components/Messages/MessageStyles.js b/src/components/Messages/MessageStyles.js index 60192494e..3aa4c5483 100644 --- a/src/components/Messages/MessageStyles.js +++ b/src/components/Messages/MessageStyles.js @@ -36,6 +36,7 @@ export const PaginationContainer = styled.div` display: flex; justify-content: center; align-items: center; + padding: 0; list-style-type: none; gap: 3px; } @@ -44,7 +45,7 @@ export const PaginationContainer = styled.div` .page-red { color: #fff; font-weight: bold; - padding: 8px 16px; + padding: 8px 12px; border-radius: 4px; text-decoration: none; cursor: pointer; @@ -66,7 +67,7 @@ export const PaginationContainer = styled.div` } .chevron { - margin: 0 1.5rem; + margin: 0 1rem; } .active-page { diff --git a/src/pages/Messages.jsx b/src/pages/Messages.jsx index b703f6244..90d9bde3f 100644 --- a/src/pages/Messages.jsx +++ b/src/pages/Messages.jsx @@ -8,6 +8,7 @@ import Button from '@mui/material/Button'; import CreateIcon from '@mui/icons-material/Create'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; +import useMediaQuery from '@mui/material/useMediaQuery'; // Utility Imports import { getMessageTTL } from '../utils'; // Context Imports @@ -70,14 +71,24 @@ const Messages = () => { const [boxType, setBoxType] = useState('inbox'); const [showModal, setShowModal] = useState(false); + const isReallySmallScreen = useMediaQuery('(max-width: 480px)'); + return ( - + From 942de17290b76dddec3dc0c6254e09df6398824e Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Mon, 23 Oct 2023 16:29:25 -0700 Subject: [PATCH 027/178] Adjust UI for Profile Page --- src/components/Profile/ProfileComponent.jsx | 8 +++- .../Profile/ProfileEditButtonGroup.jsx | 3 +- src/pages/Profile.jsx | 44 ++++++++++++++++--- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/components/Profile/ProfileComponent.jsx b/src/components/Profile/ProfileComponent.jsx index 32d78bdf5..e693deb70 100644 --- a/src/components/Profile/ProfileComponent.jsx +++ b/src/components/Profile/ProfileComponent.jsx @@ -11,6 +11,8 @@ import Typography from '@mui/material/Typography'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; // Context Imports import { SignedInUserContext } from '@contexts'; // Component Inputs @@ -92,10 +94,14 @@ const ProfileComponent = ({ contactProfile }) => { return dateOfBirth ? dayjs(dateOfBirth).format('MM/DD/YYYY') : 'No value set'; }; + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + return ( {edit ? ( diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx index c17428e60..85fe5fd88 100644 --- a/src/pages/Profile.jsx +++ b/src/pages/Profile.jsx @@ -11,6 +11,8 @@ import CircularProgress from '@mui/material/CircularProgress'; import Container from '@mui/material/Container'; import ShareIcon from '@mui/icons-material/Share'; import Typography from '@mui/material/Typography'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; // Context Imports import { DocumentListContext } from '@contexts'; // Component Imports @@ -33,6 +35,8 @@ const Profile = () => { // Route related states const location = useLocation(); localStorage.setItem('restorePath', '/profile'); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); // Documents related states const { session } = useSession(); @@ -129,7 +133,7 @@ const Profile = () => { flexDirection: 'column', alignItems: 'center', gap: '20px', - padding: '30px' + padding: isSmallScreen ? '30px 0' : '30px' }} > @@ -141,16 +145,42 @@ const Profile = () => { ) : null} - - User WebId: - + + User WebId: + {webIdUrl} - + - + {!contact && ( @@ -168,6 +199,7 @@ const Profile = () => { size="small" startIcon={} onClick={() => setShowAddDocModal(true)} + sx={{ width: isSmallScreen ? '200px' : 'default' }} > Add Document From cd0e83540da05c86ab6cda62cc9357e90ccb715a Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Mon, 23 Oct 2023 17:13:41 -0700 Subject: [PATCH 028/178] Adjust UI for existing modals --- src/components/Form/FormSection.jsx | 49 ++++++------- src/components/Modals/AddContactModal.jsx | 56 ++++++++++----- src/components/Modals/ConfirmationButton.jsx | 3 +- src/components/Modals/ConfirmationModal.jsx | 38 +++++++--- src/components/Modals/LogoutButton.jsx | 8 ++- src/components/Modals/NewMessageModal.jsx | 53 +++++++++----- src/components/Modals/UploadButtonGroup.jsx | 72 +++++++++++-------- src/components/Modals/UploadDocumentModal.jsx | 65 +++++++++++------ 8 files changed, 219 insertions(+), 125 deletions(-) diff --git a/src/components/Form/FormSection.jsx b/src/components/Form/FormSection.jsx index 6bac324c6..11fe3e1a0 100644 --- a/src/components/Form/FormSection.jsx +++ b/src/components/Form/FormSection.jsx @@ -3,6 +3,8 @@ import React from 'react'; // Material UI Imports import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; /** * FormSection Component - Component that wraps section with title and MUI Box @@ -16,34 +18,29 @@ import Typography from '@mui/material/Typography'; * @param {React.ReactElement} Props.children - JSX Element of the wrapped form * @returns {React.JSX.Element} - The FormSection Component */ -const FormSection = ({ title, children }) => ( - - { + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + + return ( + - {title} - - {children} - -); + + {title} + + {children} + + ); +}; export default FormSection; diff --git a/src/components/Modals/AddContactModal.jsx b/src/components/Modals/AddContactModal.jsx index 926339dc3..425708aa1 100644 --- a/src/components/Modals/AddContactModal.jsx +++ b/src/components/Modals/AddContactModal.jsx @@ -1,6 +1,7 @@ // React Imports import React, { useState } from 'react'; // Material UI Imports +import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import CheckIcon from '@mui/icons-material/Check'; import ClearIcon from '@mui/icons-material/Clear'; @@ -11,6 +12,8 @@ import FormControl from '@mui/material/FormControl'; import IconButton from '@mui/material/IconButton'; import InputAdornment from '@mui/material/InputAdornment'; import TextField from '@mui/material/TextField'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; // Custom Hook Imports import useNotification from '@hooks/useNotification'; // Component Imports @@ -46,6 +49,8 @@ const AddContactModal = ({ addContact, showAddContactModal, setShowAddContactMod const [username, setUsername] = useState(''); const [webId, setWebId] = useState(''); const [processing, setProcessing] = useState(false); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const wrappedSetUsername = (value) => { setUsername(value); @@ -146,26 +151,39 @@ const AddContactModal = ({ addContact, showAddContactModal, setShowAddContactMod ) }} /> - - - + + + diff --git a/src/components/Modals/ConfirmationButton.jsx b/src/components/Modals/ConfirmationButton.jsx index ce6a9222b..224b0d7f5 100644 --- a/src/components/Modals/ConfirmationButton.jsx +++ b/src/components/Modals/ConfirmationButton.jsx @@ -23,7 +23,8 @@ const ConfirmationButton = ({ title, confirmFunction, processing }) => ( endIcon={} onClick={confirmFunction} disabled={processing} - sx={{ marginLeft: '1rem' }} + fullWidth + sx={{ borderRadius: '20px' }} > {title} diff --git a/src/components/Modals/ConfirmationModal.jsx b/src/components/Modals/ConfirmationModal.jsx index 46a624056..23b506904 100644 --- a/src/components/Modals/ConfirmationModal.jsx +++ b/src/components/Modals/ConfirmationModal.jsx @@ -1,6 +1,7 @@ // React Imports import React from 'react'; // Material UI Imports +import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import ClearIcon from '@mui/icons-material/Clear'; import Dialog from '@mui/material/Dialog'; @@ -8,6 +9,8 @@ import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; // Component Imports import ConfirmationButton from './ConfirmationButton'; import LogoutButton from './LogoutButton'; @@ -39,6 +42,9 @@ const ConfirmationModal = ({ processing, isLogout = false }) => { + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + const confirmButton = () => isLogout ? ( @@ -65,17 +71,29 @@ const ConfirmationModal = ({ {text} - - - - {confirmButton()} + + {confirmButton()} + ); diff --git a/src/components/Modals/LogoutButton.jsx b/src/components/Modals/LogoutButton.jsx index e8bf37a6c..3ecc0fe96 100644 --- a/src/components/Modals/LogoutButton.jsx +++ b/src/components/Modals/LogoutButton.jsx @@ -41,7 +41,13 @@ const LogoutButton = ({ children, onLogout, onError }) => { }; return children ? ( -
+
{children}
) : ( diff --git a/src/components/Modals/NewMessageModal.jsx b/src/components/Modals/NewMessageModal.jsx index ebb19725d..e3d09b3fd 100644 --- a/src/components/Modals/NewMessageModal.jsx +++ b/src/components/Modals/NewMessageModal.jsx @@ -11,6 +11,8 @@ import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; // Utility Imports import { sendMessageTTL, getMessageTTL } from '@utils'; // Context Imports @@ -50,6 +52,8 @@ const NewMessageModal = ({ showModal, setShowModal, oldMessage = '' }) => { const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [successTimeout, setSuccessTimeout] = useState(false); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); // Modifies message upon input const handleChange = (e) => { @@ -189,25 +193,38 @@ const NewMessageModal = ({ showModal, setShowModal, oldMessage = '' }) => { inputProps={{ maxLength: '500' }} fullWidth /> - - - + + + {error &&
{error}
} {success && successTimeout &&
{success}
} diff --git a/src/components/Modals/UploadButtonGroup.jsx b/src/components/Modals/UploadButtonGroup.jsx index 4dc09eb69..03dada695 100644 --- a/src/components/Modals/UploadButtonGroup.jsx +++ b/src/components/Modals/UploadButtonGroup.jsx @@ -5,6 +5,8 @@ import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import PhotoCameraIcon from '@mui/icons-material/PhotoCamera'; import SearchIcon from '@mui/icons-material/Search'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; /** * The UploadButtonGroup Component is a component that renders the upload document @@ -17,28 +19,21 @@ import SearchIcon from '@mui/icons-material/Search'; * @param {Function} Props.setFile - The set function for handling files * @returns {React.JSX.Element} - The UploadButtonGroup Component */ -const UploadButtonGroup = ({ file, setFile }) => ( - - - {window.matchMedia('(max-width: 768px').matches && ( - )} - -); + {window.matchMedia('(max-width: 768px').matches && ( + + )} + + ); +}; export default UploadButtonGroup; diff --git a/src/components/Modals/UploadDocumentModal.jsx b/src/components/Modals/UploadDocumentModal.jsx index ae53189be..9df122758 100644 --- a/src/components/Modals/UploadDocumentModal.jsx +++ b/src/components/Modals/UploadDocumentModal.jsx @@ -1,6 +1,7 @@ // React Imports import React, { useState, useContext } from 'react'; // Material UI Imports +import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import ClearIcon from '@mui/icons-material/Clear'; import Dialog from '@mui/material/Dialog'; @@ -14,6 +15,8 @@ import TextField from '@mui/material/TextField'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; // Context Imports import { DocumentListContext } from '@contexts'; // Component Imports @@ -43,6 +46,8 @@ const UploadDocumentModal = ({ showModal, setShowModal }) => { const [inputKey, setInputKey] = useState(false); const { addDocument, replaceDocument } = useContext(DocumentListContext); const [processing, setProcessing] = useState(false); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const handleDocType = (event) => { setDocType(event.target.value); @@ -97,7 +102,11 @@ const UploadDocumentModal = ({ showModal, setShowModal }) => { return ( -
+ } label="Verify file on upload" @@ -148,28 +157,40 @@ const UploadDocumentModal = ({ showModal, setShowModal }) => { > File to upload: {file ? file.name : 'No file selected'} - - - + + + From f7070d009a7f0a1b12fd5fb5a3b19988aa4e2cb1 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Mon, 23 Oct 2023 17:41:30 -0700 Subject: [PATCH 029/178] Adjust UI for Contact page to make list fill out more of the page --- src/components/Contacts/ContactListTable.jsx | 2 +- src/pages/Contacts.jsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Contacts/ContactListTable.jsx b/src/components/Contacts/ContactListTable.jsx index 0c027e70f..d167dd2d5 100644 --- a/src/components/Contacts/ContactListTable.jsx +++ b/src/components/Contacts/ContactListTable.jsx @@ -78,7 +78,7 @@ const ContactListTable = ({ contacts, deleteContact }) => { ]; return ( - + ({ diff --git a/src/pages/Contacts.jsx b/src/pages/Contacts.jsx index 0dca8b91d..17132bd47 100644 --- a/src/pages/Contacts.jsx +++ b/src/pages/Contacts.jsx @@ -55,7 +55,6 @@ const Contacts = () => { display: 'flex', flexDirection: 'column', alignItems: 'center', - justifyContent: 'center', width: '100%' }} > From ad67fc6367a6ca89f7c14cd18d61d0f32b8b6682 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Mon, 23 Oct 2023 17:47:59 -0700 Subject: [PATCH 030/178] Update Home.test.jsx snapshot due to UI adjustments --- test/pages/__snapshots__/Home.test.jsx.snap | 72 +++++++++------------ 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/test/pages/__snapshots__/Home.test.jsx.snap b/test/pages/__snapshots__/Home.test.jsx.snap index 43f644b66..31d6d12be 100644 --- a/test/pages/__snapshots__/Home.test.jsx.snap +++ b/test/pages/__snapshots__/Home.test.jsx.snap @@ -3,7 +3,7 @@ exports[`Home Page > renders 1`] = `
renders 1`] = ` >

Keep Your Documents Safe and Secure Using Decentralized Technology

-
Our innovative solution empowers individuals to manage their critical documents and control access for trusted organizations. PASS simplifies service access, enabling seamless documents requests and secure data sharing for a smoother support process. -
+

renders 1`] = `

- An App Built for Caseworkers + An App Built for Case Workers

-
- PASS allows users to quickly and securely share documents of their clients within the app. The app helps caseworkers verify and share documents such as ID and proof of income while retaining total control of them. -
+ PASS allows users to quickly and securely share documents of their clients within the app. The app helps case workers verify and share documents such as ID and proof of income while retaining total control of them. +

Key Features

-
-

- - Secure Storage - -

+ + Secure Storage +

renders 1`] = ` Store vital documents like IDs, Social Security information, birth certificates, medical records, and bank statements in a valid digital format.

-

- - Nonprofit & Caseworker Integration - -

+ + Non-profit & Case Worker Integration +

- The platform facilitates smooth communication between nonprofit organizations, caseworkers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and caseworkers are assigned contacts whose data they can access securely. + The platform facilitates smooth communication between non-profit organizations, case workers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and case workers are assigned contacts whose data they can access securely.

-

- - Support Service - -

+ + Support Service +

Date: Mon, 23 Oct 2023 21:10:51 -0700 Subject: [PATCH 031/178] Replace constant suggested_oidc_options with equivalent environment variable --- env.template | 1 + src/components/NavBar/OidcLoginComponent.jsx | 5 ++++- src/constants/index.js | 3 +-- src/constants/suggested_oidc_options.js | 9 --------- 4 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 src/constants/suggested_oidc_options.js diff --git a/env.template b/env.template index fc645d5ac..115b1a086 100644 --- a/env.template +++ b/env.template @@ -1,2 +1,3 @@ VITE_SOLID_IDENTITY_PROVIDER="http://localhost:3000/" VITE_SOLID_POD_SERVER="http://localhost:3000/" +SUGGESTED_OIDC_OPTIONS="http://localhost:3000/, https://opencommons.net/, https://solidcommunity.net/, https://login.inrupt.com/, https://inrupt.net/" diff --git a/src/components/NavBar/OidcLoginComponent.jsx b/src/components/NavBar/OidcLoginComponent.jsx index a03a6517e..40dbc8911 100644 --- a/src/components/NavBar/OidcLoginComponent.jsx +++ b/src/components/NavBar/OidcLoginComponent.jsx @@ -8,7 +8,7 @@ import Button from '@mui/material/Button'; import TextField from '@mui/material/TextField'; import Autocomplete from '@mui/material/Autocomplete'; // Constants Imports -import { ENV, SUGGESTED_OIDC_OPTIONS } from '../../constants'; +import { ENV } from '../../constants'; /** * The OidcLoginComponent is a component that renders the login button for PASS @@ -20,13 +20,16 @@ import { ENV, SUGGESTED_OIDC_OPTIONS } from '../../constants'; */ const OidcLoginComponent = () => { const { login } = useSession(); + const SUGGESTED_OIDC_OPTIONS = ENV.VITE_SUGGESTED_OIDC_OPTIONS.split(', '); const defaultOidc = ENV.VITE_SOLID_IDENTITY_PROVIDER || SUGGESTED_OIDC_OPTIONS[0]; const [selectedOidcFromDropdown, setSelectedOidcFromDropdown] = useState(defaultOidc); const [oidcIssuer, setOidcIssuer] = useState(defaultOidc); + const loginHandler = async () => { const redirectUrl = window.location.href; await login({ oidcIssuer, redirectUrl }); }; + const saveOidcIssuer = (value) => { if (value === null || value?.length < 0) return; localStorage.setItem('oidcIssuer', value); diff --git a/src/constants/index.js b/src/constants/index.js index 47f0bba06..5e7fb531b 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,6 +1,5 @@ import RDF_PREDICATES from './rdf_predicates'; import INTERACTION_TYPES from './interaction_types'; import ENV from './environment'; -import SUGGESTED_OIDC_OPTIONS from './suggested_oidc_options'; -export { RDF_PREDICATES, INTERACTION_TYPES, ENV, SUGGESTED_OIDC_OPTIONS }; +export { RDF_PREDICATES, INTERACTION_TYPES, ENV }; diff --git a/src/constants/suggested_oidc_options.js b/src/constants/suggested_oidc_options.js deleted file mode 100644 index 0e781b545..000000000 --- a/src/constants/suggested_oidc_options.js +++ /dev/null @@ -1,9 +0,0 @@ -const SUGGESTED_OIDC_OPTIONS = [ - 'http://localhost:3000/', - 'https://.solidcommunity.net/', - '', - '', - '' -]; - -export default SUGGESTED_OIDC_OPTIONS; From 9918c91f9d0e1f00ef5afbe9bf741d5fd1f27180 Mon Sep 17 00:00:00 2001 From: Rio Edwards Date: Mon, 23 Oct 2023 21:14:06 -0700 Subject: [PATCH 032/178] Only save oidcIssuer to local storage on login --- src/components/NavBar/NewOINK.jsx | 96 ++++++++++++++++++++ src/components/NavBar/OidcLoginComponent.jsx | 9 +- 2 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 src/components/NavBar/NewOINK.jsx diff --git a/src/components/NavBar/NewOINK.jsx b/src/components/NavBar/NewOINK.jsx new file mode 100644 index 000000000..00b24f77a --- /dev/null +++ b/src/components/NavBar/NewOINK.jsx @@ -0,0 +1,96 @@ +// React Imports +import React, { useState } from 'react'; +// Custom Hook Imports +import { useSession } from '@hooks'; +// Material UI Imports +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Autocomplete from '@mui/material/Autocomplete'; +// Constants Imports +import { ENV } from '../../constants'; + +/** + * The OidcLoginComponent is a component that renders the login button for PASS + * and linked to the login of a specific Solid IDP + * + * @memberof NavBar + * @name OidcLoginComponent + * @returns {React.JSX.Element} - The OidcLoginComponent Component + */ +const OidcLoginComponent = () => { + const { login } = useSession(); + const SUGGESTED_OIDC_OPTIONS = ENV.VITE_SUGGESTED_OIDC_OPTIONS.split(', '); + const defaultOidc = ENV.VITE_SOLID_IDENTITY_PROVIDER || SUGGESTED_OIDC_OPTIONS[0]; + const [selectedOidcFromDropdown, setSelectedOidcFromDropdown] = useState(defaultOidc); + const [oidcIssuer, setOidcIssuer] = useState(defaultOidc); + + const loginHandler = async () => { + const redirectUrl = window.location.href; + localStorage.setItem('oidcIssuer', oidcIssuer); + await login({ oidcIssuer, redirectUrl }); + }; + + return ( + <> + + { + // This is called when the user selects a new option from the dropdown + setSelectedOidcFromDropdown(newValue); + }} + onInputChange={(_, newInputValue) => { + setOidcIssuer(newInputValue); + }} + renderInput={(renderParams) => ( + + )} + /> + + + + ); +}; + +export default OidcLoginComponent; diff --git a/src/components/NavBar/OidcLoginComponent.jsx b/src/components/NavBar/OidcLoginComponent.jsx index 40dbc8911..00b24f77a 100644 --- a/src/components/NavBar/OidcLoginComponent.jsx +++ b/src/components/NavBar/OidcLoginComponent.jsx @@ -27,14 +27,10 @@ const OidcLoginComponent = () => { const loginHandler = async () => { const redirectUrl = window.location.href; + localStorage.setItem('oidcIssuer', oidcIssuer); await login({ oidcIssuer, redirectUrl }); }; - const saveOidcIssuer = (value) => { - if (value === null || value?.length < 0) return; - localStorage.setItem('oidcIssuer', value); - }; - return ( <> @@ -58,7 +54,6 @@ const OidcLoginComponent = () => { inputValue={oidcIssuer} onChange={(_, newValue) => { // This is called when the user selects a new option from the dropdown - saveOidcIssuer(newValue); setSelectedOidcFromDropdown(newValue); }} onInputChange={(_, newInputValue) => { @@ -70,7 +65,6 @@ const OidcLoginComponent = () => { type="text" label="Pod Server URL" variant="filled" - onBlur={(e) => saveOidcIssuer(e.target.value)} InputProps={{ ...renderParams.InputProps, disableUnderline: true @@ -91,7 +85,6 @@ const OidcLoginComponent = () => { sx={{ flexShrink: 0 }} onClick={() => { loginHandler(); - localStorage.setItem('oidcIssuer', oidcIssuer); }} > Login From fc96be6c7c94a4334210e95344880c11c737c2f9 Mon Sep 17 00:00:00 2001 From: Rio Edwards Date: Mon, 23 Oct 2023 21:19:53 -0700 Subject: [PATCH 033/178] Add tests for new OidcLoginComponent dropdown (display providers, updates input value, and saves to local storage) --- src/components/NavBar/NewOINK.jsx | 96 ------------------- .../NavBar/OidcLoginComponent.test.jsx | 54 ++++++++--- 2 files changed, 40 insertions(+), 110 deletions(-) delete mode 100644 src/components/NavBar/NewOINK.jsx diff --git a/src/components/NavBar/NewOINK.jsx b/src/components/NavBar/NewOINK.jsx deleted file mode 100644 index 00b24f77a..000000000 --- a/src/components/NavBar/NewOINK.jsx +++ /dev/null @@ -1,96 +0,0 @@ -// React Imports -import React, { useState } from 'react'; -// Custom Hook Imports -import { useSession } from '@hooks'; -// Material UI Imports -import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; -import TextField from '@mui/material/TextField'; -import Autocomplete from '@mui/material/Autocomplete'; -// Constants Imports -import { ENV } from '../../constants'; - -/** - * The OidcLoginComponent is a component that renders the login button for PASS - * and linked to the login of a specific Solid IDP - * - * @memberof NavBar - * @name OidcLoginComponent - * @returns {React.JSX.Element} - The OidcLoginComponent Component - */ -const OidcLoginComponent = () => { - const { login } = useSession(); - const SUGGESTED_OIDC_OPTIONS = ENV.VITE_SUGGESTED_OIDC_OPTIONS.split(', '); - const defaultOidc = ENV.VITE_SOLID_IDENTITY_PROVIDER || SUGGESTED_OIDC_OPTIONS[0]; - const [selectedOidcFromDropdown, setSelectedOidcFromDropdown] = useState(defaultOidc); - const [oidcIssuer, setOidcIssuer] = useState(defaultOidc); - - const loginHandler = async () => { - const redirectUrl = window.location.href; - localStorage.setItem('oidcIssuer', oidcIssuer); - await login({ oidcIssuer, redirectUrl }); - }; - - return ( - <> - - { - // This is called when the user selects a new option from the dropdown - setSelectedOidcFromDropdown(newValue); - }} - onInputChange={(_, newInputValue) => { - setOidcIssuer(newInputValue); - }} - renderInput={(renderParams) => ( - - )} - /> - - - - ); -}; - -export default OidcLoginComponent; diff --git a/test/components/NavBar/OidcLoginComponent.test.jsx b/test/components/NavBar/OidcLoginComponent.test.jsx index 118b0319e..8eb9ac456 100644 --- a/test/components/NavBar/OidcLoginComponent.test.jsx +++ b/test/components/NavBar/OidcLoginComponent.test.jsx @@ -1,8 +1,7 @@ import { render, cleanup } from '@testing-library/react'; -import { login } from '@inrupt/solid-client-authn-browser'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { expect, it, vi, afterEach } from 'vitest'; +import { expect, it, vi, afterEach, describe, beforeEach } from 'vitest'; import OidcLoginComponent from '../../../src/components/NavBar/OidcLoginComponent'; vi.mock('@inrupt/solid-client-authn-browser'); @@ -18,20 +17,47 @@ vi.mock('../../../src/constants/', async () => { return { ...actual, ENV: { - VITE_SOLID_IDENTITY_PROVIDER: 'https://www.testurl.com/' + VITE_SOLID_IDENTITY_PROVIDER: 'https://www.testurl.com/', + VITE_SUGGESTED_OIDC_OPTIONS: + 'http://localhost:3000/, https://opencommons.net/, https://solidcommunity.net/, https://login.inrupt.com/, https://inrupt.net/' } }; }); -it('sets OIDC provider on login', async () => { - const user = userEvent.setup(); - const { getByRole } = render(); - const input = getByRole('combobox'); - const loginButton = getByRole('button'); - await user.clear(input); - await user.type(input, 'http://oidc.provider.url/'); - expect(input.value).toBe('http://oidc.provider.url/'); - await user.click(loginButton); - expect(login).toBeCalled(); - expect(localStorage.getItem('oidcIssuer')).toBe('http://oidc.provider.url/'); +describe('OidcLoginComponent Tests', () => { + let user; + let input; + let getByRole; + let getByText; + let exampleProvider; + + beforeEach(async () => { + user = userEvent.setup(); + const renderResult = render(); + getByRole = renderResult.getByRole; + getByText = renderResult.getByText; + + input = getByRole('combobox'); + await user.click(input); + exampleProvider = getByText('https://solidcommunity.net/'); + }); + + it('displays a list of suggested providers when focused', () => { + expect(exampleProvider).not.toBeNull(); + }); + + it('updates input value when a provider is clicked', async () => { + await user.click(exampleProvider); + expect(input.value).toBe('https://solidcommunity.net/'); + }); + + it('saves the provider to localStorage on login', async () => { + await userEvent.click(input); + await userEvent.click(exampleProvider); + + const loginButton = getByRole('button'); + await userEvent.click(loginButton); + + expect(localStorage.getItem('oidcIssuer')).toBe('https://solidcommunity.net/'); + }); }); From d2e0bfccf91fbb71f2d1975dd7de8d726e5d7cad Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Tue, 24 Oct 2023 12:21:17 -0700 Subject: [PATCH 034/178] Adjusts wording for UI --- src/pages/Home.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index a9ef7c630..378b95f19 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -52,8 +52,8 @@ const Home = () => { isReallySmallScreen={isReallySmallScreen} componentImageSrc="/assets/app-green.png" componentImageAlt="" - title="An App Built for Case Workers" - description="PASS allows users to quickly and securely share documents of their clients within the app. The app helps case workers verify and share documents such as ID and proof of income while retaining total control of them." + title="An App for Caseworkers" + description="PASS allows users to quickly and securely share documents of their clients within the app. The app helps caseworkers verify and share documents such as ID and proof of income while retaining total control of them." /> { } - title="Non-profit & Case Worker Integration" - description="The platform facilitates smooth communication between non-profit organizations, case workers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and case workers are assigned contacts whose data they can access securely." + title="Nonprofit-Caseworker Integration" + description="The platform facilitates smooth communication between non-profit organizations, case workers, and the individuals they serve. It allows non-profit organizations to maintain a contact list, and caseworkers are assigned contacts whose data they can access securely." /> Date: Tue, 24 Oct 2023 12:21:55 -0700 Subject: [PATCH 035/178] Adjusts wording for UI --- src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 378b95f19..10812db50 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -71,7 +71,7 @@ const Home = () => { isReallySmallScreen={isReallySmallScreen} icon={} title="Nonprofit-Caseworker Integration" - description="The platform facilitates smooth communication between non-profit organizations, case workers, and the individuals they serve. It allows non-profit organizations to maintain a contact list, and caseworkers are assigned contacts whose data they can access securely." + description="The platform facilitates smooth communication between nonprofit organizations, case workers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and caseworkers are assigned contacts whose data they can access securely." /> Date: Tue, 24 Oct 2023 12:27:14 -0700 Subject: [PATCH 036/178] Updating snapshot for wording changes in Home.jsx --- test/pages/__snapshots__/Home.test.jsx.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/pages/__snapshots__/Home.test.jsx.snap b/test/pages/__snapshots__/Home.test.jsx.snap index 31d6d12be..4c6d5e973 100644 --- a/test/pages/__snapshots__/Home.test.jsx.snap +++ b/test/pages/__snapshots__/Home.test.jsx.snap @@ -50,13 +50,13 @@ exports[`Home Page > renders 1`] = ` class="MuiTypography-root MuiTypography-h2 css-1loew7y-MuiTypography-root" > - An App Built for Case Workers + An App for Caseworkers

- PASS allows users to quickly and securely share documents of their clients within the app. The app helps case workers verify and share documents such as ID and proof of income while retaining total control of them. + PASS allows users to quickly and securely share documents of their clients within the app. The app helps caseworkers verify and share documents such as ID and proof of income while retaining total control of them.

renders 1`] = ` /> - Non-profit & Case Worker Integration + Nonprofit-Caseworker Integration

- The platform facilitates smooth communication between non-profit organizations, case workers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and case workers are assigned contacts whose data they can access securely. + The platform facilitates smooth communication between nonprofit organizations, case workers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and caseworkers are assigned contacts whose data they can access securely.

Date: Tue, 24 Oct 2023 13:15:04 -0700 Subject: [PATCH 037/178] Add new test for OIDC input dropdown --- src/components/NavBar/NewOINK.jsx | 96 ------------------- .../NavBar/OidcLoginComponent.test.jsx | 21 +++- 2 files changed, 20 insertions(+), 97 deletions(-) delete mode 100644 src/components/NavBar/NewOINK.jsx diff --git a/src/components/NavBar/NewOINK.jsx b/src/components/NavBar/NewOINK.jsx deleted file mode 100644 index 00b24f77a..000000000 --- a/src/components/NavBar/NewOINK.jsx +++ /dev/null @@ -1,96 +0,0 @@ -// React Imports -import React, { useState } from 'react'; -// Custom Hook Imports -import { useSession } from '@hooks'; -// Material UI Imports -import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; -import TextField from '@mui/material/TextField'; -import Autocomplete from '@mui/material/Autocomplete'; -// Constants Imports -import { ENV } from '../../constants'; - -/** - * The OidcLoginComponent is a component that renders the login button for PASS - * and linked to the login of a specific Solid IDP - * - * @memberof NavBar - * @name OidcLoginComponent - * @returns {React.JSX.Element} - The OidcLoginComponent Component - */ -const OidcLoginComponent = () => { - const { login } = useSession(); - const SUGGESTED_OIDC_OPTIONS = ENV.VITE_SUGGESTED_OIDC_OPTIONS.split(', '); - const defaultOidc = ENV.VITE_SOLID_IDENTITY_PROVIDER || SUGGESTED_OIDC_OPTIONS[0]; - const [selectedOidcFromDropdown, setSelectedOidcFromDropdown] = useState(defaultOidc); - const [oidcIssuer, setOidcIssuer] = useState(defaultOidc); - - const loginHandler = async () => { - const redirectUrl = window.location.href; - localStorage.setItem('oidcIssuer', oidcIssuer); - await login({ oidcIssuer, redirectUrl }); - }; - - return ( - <> - - { - // This is called when the user selects a new option from the dropdown - setSelectedOidcFromDropdown(newValue); - }} - onInputChange={(_, newInputValue) => { - setOidcIssuer(newInputValue); - }} - renderInput={(renderParams) => ( - - )} - /> - - - - ); -}; - -export default OidcLoginComponent; diff --git a/test/components/NavBar/OidcLoginComponent.test.jsx b/test/components/NavBar/OidcLoginComponent.test.jsx index 118b0319e..1c140dddc 100644 --- a/test/components/NavBar/OidcLoginComponent.test.jsx +++ b/test/components/NavBar/OidcLoginComponent.test.jsx @@ -18,7 +18,9 @@ vi.mock('../../../src/constants/', async () => { return { ...actual, ENV: { - VITE_SOLID_IDENTITY_PROVIDER: 'https://www.testurl.com/' + VITE_SOLID_IDENTITY_PROVIDER: 'https://www.testurl.com/', + VITE_SUGGESTED_OIDC_OPTIONS: + 'http://testurl_1.com/, http://testurl_2.com/, http://testurl_3.com/' } }; }); @@ -26,12 +28,29 @@ vi.mock('../../../src/constants/', async () => { it('sets OIDC provider on login', async () => { const user = userEvent.setup(); const { getByRole } = render(); + const input = getByRole('combobox'); const loginButton = getByRole('button'); + await user.clear(input); await user.type(input, 'http://oidc.provider.url/'); expect(input.value).toBe('http://oidc.provider.url/'); + await user.click(loginButton); expect(login).toBeCalled(); expect(localStorage.getItem('oidcIssuer')).toBe('http://oidc.provider.url/'); }); + +it('displays a list of suggested providers when focused', async () => { + const user = userEvent.setup(); + const { getByRole, getByText } = render(); + + const input = getByRole('combobox'); + await user.click(input); + + const exampleProvider = getByText('http://testurl_2.com/'); + expect(exampleProvider).not.toBeNull(); + + await user.click(exampleProvider); + expect(input.value).toBe('http://testurl_2.com/'); +}); From b60a34b1e2320e89fd4e1dfe11b8901ce33b69c9 Mon Sep 17 00:00:00 2001 From: Rio Edwards Date: Tue, 24 Oct 2023 13:41:47 -0700 Subject: [PATCH 038/178] Add VITE_SUGGESTED_OIDC_OPTIONS to mocks --- __mocks__/src/constants/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__mocks__/src/constants/index.js b/__mocks__/src/constants/index.js index 29f58a5f9..51d75428e 100644 --- a/__mocks__/src/constants/index.js +++ b/__mocks__/src/constants/index.js @@ -1,6 +1,6 @@ const ENV = { - VITE_SOLID_IDENTITY_PROVIDER: 'https://solidcommunity.net' + VITE_SOLID_IDENTITY_PROVIDER: 'https://solidcommunity.net', + VITE_SUGGESTED_OIDC_OPTIONS: 'http://testurl_1.com/, http://testurl_2.com/, http://testurl_3.com/' }; export { ENV }; /* eslint-disable-line */ - From 7125f6a31c15b550b324d5c14fb2864fbf2fd8f2 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 24 Oct 2023 18:32:14 -0700 Subject: [PATCH 039/178] Passing to field into modal --- src/components/Contacts/ContactListTableRow.jsx | 7 ++++++- src/components/Messages/MessagePreview.jsx | 6 ++++-- src/components/Modals/NewMessageModal.jsx | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/Contacts/ContactListTableRow.jsx b/src/components/Contacts/ContactListTableRow.jsx index 5f3f4a42d..dc6d6786d 100644 --- a/src/components/Contacts/ContactListTableRow.jsx +++ b/src/components/Contacts/ContactListTableRow.jsx @@ -98,7 +98,12 @@ const ContactListTableRow = ({ contact, deleteContact, message }) => { {showModal && ( - + )} ); diff --git a/src/components/Messages/MessagePreview.jsx b/src/components/Messages/MessagePreview.jsx index 4d2c0e283..906acd362 100644 --- a/src/components/Messages/MessagePreview.jsx +++ b/src/components/Messages/MessagePreview.jsx @@ -19,7 +19,7 @@ import { MessageContext, SignedInUserContext } from '@contexts'; import { NewMessageModal } from '../Modals'; /** - * @typedef {import("../../typedefs.js").messagePreviewProps} messagePreviewProps + * @typedef {import("../../typedefs.js").messageListObject} messageListObject */ /** @@ -28,7 +28,9 @@ import { NewMessageModal } from '../Modals'; * * @memberof Messages * @name MessagePreview - * @param {messagePreviewProps} Props - Component props for MessagePreview + * @param {object} Props - Component props for MessagePreview + * @param {messageListObject} Props.message - The message object + * @param {string} Props.folderType - Type of message box * @returns {React.JSX.Element} React component for MessagePreview */ const MessagePreview = ({ message, folderType }) => { diff --git a/src/components/Modals/NewMessageModal.jsx b/src/components/Modals/NewMessageModal.jsx index b813f2d71..304725504 100644 --- a/src/components/Modals/NewMessageModal.jsx +++ b/src/components/Modals/NewMessageModal.jsx @@ -29,7 +29,7 @@ import { MessageContext, SignedInUserContext } from '../../contexts'; * @param {newMessageModalProps} Props - Props used for NewMessageModal * @returns {React.JSX.Element} React component for NewMessageModal */ -const NewMessageModal = ({ showModal, setShowModal, oldMessage = '' }) => { +const NewMessageModal = ({ showModal, setShowModal, oldMessage = '', toField }) => { const { session } = useSession(); const { outboxList, setOutboxList } = useContext(MessageContext); const { podUrl } = useContext(SignedInUserContext); @@ -126,7 +126,7 @@ const NewMessageModal = ({ showModal, setShowModal, oldMessage = '' }) => { Date: Tue, 24 Oct 2023 22:49:09 -0700 Subject: [PATCH 040/178] Add default SUGGESTED_OIDC_OPTIONS when not setup in .env --- src/components/NavBar/OidcLoginComponent.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/NavBar/OidcLoginComponent.jsx b/src/components/NavBar/OidcLoginComponent.jsx index 00b24f77a..529d99af4 100644 --- a/src/components/NavBar/OidcLoginComponent.jsx +++ b/src/components/NavBar/OidcLoginComponent.jsx @@ -20,7 +20,9 @@ import { ENV } from '../../constants'; */ const OidcLoginComponent = () => { const { login } = useSession(); - const SUGGESTED_OIDC_OPTIONS = ENV.VITE_SUGGESTED_OIDC_OPTIONS.split(', '); + const SUGGESTED_OIDC_OPTIONS = ENV.VITE_SUGGESTED_OIDC_OPTIONS?.split(', ') || [ + 'http://localhost:3000/' + ]; const defaultOidc = ENV.VITE_SOLID_IDENTITY_PROVIDER || SUGGESTED_OIDC_OPTIONS[0]; const [selectedOidcFromDropdown, setSelectedOidcFromDropdown] = useState(defaultOidc); const [oidcIssuer, setOidcIssuer] = useState(defaultOidc); From 7d5e00fbcc6fc7578d6fddcb52d6be77d1bfe4a0 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Tue, 24 Oct 2023 22:59:29 -0700 Subject: [PATCH 041/178] Add unit tests for Form and Home components; Moved createMatchMedia into its own file to be exported for unit test --- src/components/Home/KeyFeatures.jsx | 2 +- src/components/Home/index.js | 10 ++++ src/pages/Home.jsx | 3 +- test/components/Form/FormSection.test.jsx | 29 +++++++++ test/components/Home/HomeSection.test.jsx | 73 +++++++++++++++++++++++ test/components/Home/KeyFeatures.test.jsx | 25 ++++++++ test/components/NavBar/NavBar.test.jsx | 8 +-- test/test-helper/createMatchMedia.js | 9 +++ 8 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 src/components/Home/index.js create mode 100644 test/components/Form/FormSection.test.jsx create mode 100644 test/components/Home/HomeSection.test.jsx create mode 100644 test/components/Home/KeyFeatures.test.jsx create mode 100644 test/test-helper/createMatchMedia.js diff --git a/src/components/Home/KeyFeatures.jsx b/src/components/Home/KeyFeatures.jsx index f1299ce0f..c8f3a74af 100644 --- a/src/components/Home/KeyFeatures.jsx +++ b/src/components/Home/KeyFeatures.jsx @@ -33,7 +33,7 @@ const KeyFeatures = ({ icon, title, description, isReallySmallScreen }) => (
; + +const MockFormSection = () => ( + + + +); + +it('renders 20px padding by default', () => { + const component = render(); + const adjustableBox = getComputedStyle(component.container.firstChild); + + expect(adjustableBox.padding).toBe('20px'); +}); + +it("renders 10px padding after MUI breakpoint 'sm' is triggered", () => { + window.matchMedia = createMatchMedia(599); + + const component = render(); + const adjustableBox = getComputedStyle(component.container.firstChild); + + expect(adjustableBox.padding).toBe('10px'); +}); diff --git a/test/components/Home/HomeSection.test.jsx b/test/components/Home/HomeSection.test.jsx new file mode 100644 index 000000000..40023098a --- /dev/null +++ b/test/components/Home/HomeSection.test.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { expect, it, describe } from 'vitest'; +import { HomeSection } from '@components/Home'; +import createMatchMedia from '../../test-helper/createMatchMedia'; + +const MockFormSectionDefault = () => ; +const MockFormSectionMobile = () => ; +const MockFormSectionButton = () => ; +const MockFormSectionButtonMobile = () => ; + +describe('Button rendering', () => { + it('renders no button', () => { + const { queryByRole } = render(); + const button = queryByRole('button'); + + expect(button).toBeNull(); + }); + + it('renders button', () => { + const { queryByRole } = render(); + const button = queryByRole('button'); + const cssProperties = getComputedStyle(button); + + expect(button).not.toBeNull(); + expect(cssProperties.width).toBe('25%'); + }); + + it('renders buttons mobile', () => { + window.width = createMatchMedia(599); + const { queryByRole } = render(); + const button = queryByRole('button'); + const cssProperties = getComputedStyle(button); + + expect(button).not.toBeNull(); + expect(cssProperties.width).toBe('100%'); + }); +}); + +describe('Default screen', () => { + it('renders 300px padding by default', () => { + const component = render(); + const adjustableBox = getComputedStyle(component.container.firstChild); + + expect(adjustableBox.width).toBe('300px'); + }); + + it('renders 85% padding by default', () => { + const { getByText } = render(); + const descriptionElement = getByText('Example Text'); + const cssProperties = getComputedStyle(descriptionElement); + + expect(cssProperties.width).toBe('85%'); + }); +}); + +describe('Mobile screen', () => { + window.width = createMatchMedia(599); + it('renders 80% padding by default', () => { + const component = render(); + const adjustableBox = getComputedStyle(component.container.firstChild); + + expect(adjustableBox.width).toBe('80%'); + }); + + it('renders 100% padding by default', () => { + const { getByText } = render(); + const descriptionElement = getByText('Example Text'); + const cssProperties = getComputedStyle(descriptionElement); + + expect(cssProperties.width).toBe('100%'); + }); +}); diff --git a/test/components/Home/KeyFeatures.test.jsx b/test/components/Home/KeyFeatures.test.jsx new file mode 100644 index 000000000..7c946bbc2 --- /dev/null +++ b/test/components/Home/KeyFeatures.test.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { expect, it } from 'vitest'; +import { KeyFeatures } from '@components/Home'; +import createMatchMedia from '../../test-helper/createMatchMedia'; + +const MockKeyFeaturesDefault = () => ; +const MockKeyFeaturesMobile = () => ; + +it('renders 67% width default', () => { + const { getByText } = render(); + const component = getByText('Example Text'); + const cssProperty = getComputedStyle(component); + + expect(cssProperty.width).toBe('67%'); +}); + +it('renders 100% width mobile', () => { + window.width = createMatchMedia(599); + const { getByText } = render(); + const component = getByText('Example Text'); + const cssProperty = getComputedStyle(component); + + expect(cssProperty.width).toBe('100%'); +}); diff --git a/test/components/NavBar/NavBar.test.jsx b/test/components/NavBar/NavBar.test.jsx index 05c89bcc6..60edd10e5 100644 --- a/test/components/NavBar/NavBar.test.jsx +++ b/test/components/NavBar/NavBar.test.jsx @@ -1,17 +1,11 @@ import React from 'react'; import { BrowserRouter } from 'react-router-dom'; -import mediaQuery from 'css-mediaquery'; import { render, cleanup } from '@testing-library/react'; import { expect, it, afterEach, describe } from 'vitest'; import { SessionContext } from '@contexts'; +import createMatchMedia from '../../test-helper/createMatchMedia'; import NavBar from '../../../src/components/NavBar/NavBar'; -const createMatchMedia = (width) => (query) => ({ - matches: mediaQuery.match(query, { width }), - addListener: () => {}, - removeListener: () => {} -}); - // clear created dom after each test, to start fresh for next afterEach(() => { cleanup(); diff --git a/test/test-helper/createMatchMedia.js b/test/test-helper/createMatchMedia.js new file mode 100644 index 000000000..cec58d0d6 --- /dev/null +++ b/test/test-helper/createMatchMedia.js @@ -0,0 +1,9 @@ +import mediaQuery from 'css-mediaquery'; + +const createMatchMedia = (width) => (query) => ({ + matches: mediaQuery.match(query, { width }), + addListener: () => {}, + removeListener: () => {} +}); + +export default createMatchMedia; From c6a48f97325a905735c333cbf7b5741f9516da19 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Tue, 24 Oct 2023 23:43:15 -0700 Subject: [PATCH 042/178] Add unit test for MessageFolder; correct window.width to window.matchMedia --- test/components/Home/HomeSection.test.jsx | 5 ++- test/components/Home/KeyFeatures.test.jsx | 2 +- .../Messages/MessageFolder.test.jsx | 45 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 test/components/Messages/MessageFolder.test.jsx diff --git a/test/components/Home/HomeSection.test.jsx b/test/components/Home/HomeSection.test.jsx index 40023098a..00b5b0f34 100644 --- a/test/components/Home/HomeSection.test.jsx +++ b/test/components/Home/HomeSection.test.jsx @@ -27,7 +27,7 @@ describe('Button rendering', () => { }); it('renders buttons mobile', () => { - window.width = createMatchMedia(599); + window.matchMedia = createMatchMedia(599); const { queryByRole } = render(); const button = queryByRole('button'); const cssProperties = getComputedStyle(button); @@ -55,8 +55,8 @@ describe('Default screen', () => { }); describe('Mobile screen', () => { - window.width = createMatchMedia(599); it('renders 80% padding by default', () => { + window.matchMedia = createMatchMedia(599); const component = render(); const adjustableBox = getComputedStyle(component.container.firstChild); @@ -64,6 +64,7 @@ describe('Mobile screen', () => { }); it('renders 100% padding by default', () => { + window.matchMedia = createMatchMedia(599); const { getByText } = render(); const descriptionElement = getByText('Example Text'); const cssProperties = getComputedStyle(descriptionElement); diff --git a/test/components/Home/KeyFeatures.test.jsx b/test/components/Home/KeyFeatures.test.jsx index 7c946bbc2..331f74283 100644 --- a/test/components/Home/KeyFeatures.test.jsx +++ b/test/components/Home/KeyFeatures.test.jsx @@ -16,7 +16,7 @@ it('renders 67% width default', () => { }); it('renders 100% width mobile', () => { - window.width = createMatchMedia(599); + window.matchMedia = createMatchMedia(599); const { getByText } = render(); const component = getByText('Example Text'); const cssProperty = getComputedStyle(component); diff --git a/test/components/Messages/MessageFolder.test.jsx b/test/components/Messages/MessageFolder.test.jsx new file mode 100644 index 000000000..f33612af0 --- /dev/null +++ b/test/components/Messages/MessageFolder.test.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { MessageFolder } from '@components/Messages'; +import createMatchMedia from '../../test-helper/createMatchMedia'; + +const MockMessageFolder = () => ; + +describe('Default screen', () => { + it('renders 30px padding', () => { + const component = render(); + const adjustableBox = component.container.firstChild; + const cssProperty = getComputedStyle(adjustableBox); + + expect(cssProperty.padding).toBe('30px'); + }); + + it('renders refresh button margin to 10px', () => { + const { getByText } = render(); + const button = getByText('Refresh'); + const cssProperty = getComputedStyle(button); + + expect(cssProperty.margin).toBe('10px'); + }); +}); + +describe('Mobile screen', () => { + it("renders '30px 0px' padding", () => { + window.matchMedia = createMatchMedia(599); + const component = render(); + const adjustableBox = component.container.firstChild; + const cssProperty = getComputedStyle(adjustableBox); + + expect(cssProperty.padding).toBe('30px 0px'); + }); + + it("renders refresh button margin to '10px 20px'", () => { + window.matchMedia = createMatchMedia(599); + const { getByText } = render(); + const button = getByText('Refresh'); + const cssProperty = getComputedStyle(button); + + expect(cssProperty.margin).toBe('10px 20px'); + }); +}); From 21b812f68c38c2f753937a03b0503f1f0610a27f Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Wed, 25 Oct 2023 00:05:04 -0700 Subject: [PATCH 043/178] Add unit test for MessagePreview grid --- .../Messages/MessagePreview.test.jsx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 test/components/Messages/MessagePreview.test.jsx diff --git a/test/components/Messages/MessagePreview.test.jsx b/test/components/Messages/MessagePreview.test.jsx new file mode 100644 index 000000000..4549aeef8 --- /dev/null +++ b/test/components/Messages/MessagePreview.test.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { MessagePreview } from '@components/Messages'; +import createMatchMedia from '../../test-helper/createMatchMedia'; + +const mockMessageInfo = { sender: 'test', title: 'test title', uploadDate: new Date('1-1-2000') }; +const MockMessagePreview = () => ; + +describe('Grid sizes', () => { + it('renders grid values 5, 5, 2 default', () => { + const { getByText } = render(); + const senderCell = getByText('Sender:').parentElement; + const subjectCell = getByText('Subject:').parentElement; + const dateCell = getByText('Date:').parentElement; + + expect(senderCell.classList.contains('MuiGrid-grid-xs-5')).toBe(true); + expect(subjectCell.classList.contains('MuiGrid-grid-xs-5')).toBe(true); + expect(dateCell.classList.contains('MuiGrid-grid-xs-2')).toBe(true); + }); + + it('renders grid values 8, 8, 4 medium', () => { + window.matchMedia = createMatchMedia(899); + const { getByText } = render(); + const senderCell = getByText('Sender:').parentElement; + const subjectCell = getByText('Subject:').parentElement; + const dateCell = getByText('Date:').parentElement; + + expect(senderCell.classList.contains('MuiGrid-grid-xs-8')).toBe(true); + expect(subjectCell.classList.contains('MuiGrid-grid-xs-8')).toBe(true); + expect(dateCell.classList.contains('MuiGrid-grid-xs-4')).toBe(true); + }); + + it('renders grid values 12, 12, 12 small', () => { + window.matchMedia = createMatchMedia(599); + const { getByText } = render(); + const senderCell = getByText('Sender:').parentElement; + const subjectCell = getByText('Subject:').parentElement; + const dateCell = getByText('Date:').parentElement; + + expect(senderCell.classList.contains('MuiGrid-grid-xs-12')).toBe(true); + expect(subjectCell.classList.contains('MuiGrid-grid-xs-12')).toBe(true); + expect(dateCell.classList.contains('MuiGrid-grid-xs-12')).toBe(true); + }); +}); From 58b8e0ee816933e783ee2f91210297c4cb42775a Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Wed, 25 Oct 2023 01:21:49 -0700 Subject: [PATCH 044/178] Add unit tests for Modals components --- src/components/Modals/NewMessageModal.jsx | 2 +- src/components/Modals/UploadButtonGroup.jsx | 3 +- test/components/Home/HomeSection.test.jsx | 3 +- .../Modals/AddContactModal.test.jsx | 26 ++++++++++ .../Modals/ConfirmationModal.test.jsx | 51 +++++++++++++++++++ .../Modals/NewMessageModal.test.jsx | 26 ++++++++++ .../Modals/UploadButtonGroup.test.jsx | 34 +++++++++++++ .../Modals/UploadDocumentModal.test.jsx | 26 ++++++++++ 8 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 test/components/Modals/AddContactModal.test.jsx create mode 100644 test/components/Modals/ConfirmationModal.test.jsx create mode 100644 test/components/Modals/NewMessageModal.test.jsx create mode 100644 test/components/Modals/UploadButtonGroup.test.jsx create mode 100644 test/components/Modals/UploadDocumentModal.test.jsx diff --git a/src/components/Modals/NewMessageModal.jsx b/src/components/Modals/NewMessageModal.jsx index e3d09b3fd..91c724a84 100644 --- a/src/components/Modals/NewMessageModal.jsx +++ b/src/components/Modals/NewMessageModal.jsx @@ -212,7 +212,7 @@ const NewMessageModal = ({ showModal, setShowModal, oldMessage = '' }) => { fullWidth sx={{ borderRadius: '20px' }} > - CANCEL + Cancel - {window.matchMedia('(max-width: 768px').matches && ( + {isCustomMediumScreen && ( - ) : null} + )} diff --git a/test/components/NavBar/OidcLoginComponent.test.jsx b/test/components/NavBar/OidcLoginComponent.test.jsx index 0f7a99ad3..1f4be2f9b 100644 --- a/test/components/NavBar/OidcLoginComponent.test.jsx +++ b/test/components/NavBar/OidcLoginComponent.test.jsx @@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { expect, it, vi, afterEach } from 'vitest'; import OidcLoginComponent from '../../../src/components/NavBar/OidcLoginComponent'; +import createMatchMedia from '../../test-helper/createMatchMedia'; vi.mock('@inrupt/solid-client-authn-browser'); @@ -35,3 +36,28 @@ it('sets OIDC provider on login', async () => { expect(login).toBeCalled(); expect(localStorage.getItem('oidcIssuer')).toBe('http://oidc.provider.url/'); }); + +it('renders container items as row default', () => { + const component = render(); + const container = component.container.firstChild; + const cssProperty = getComputedStyle(container); + + expect(cssProperty.flexDirection).toBe('row'); +}); + +it('renders container items as column mobile', () => { + window.matchMedia = createMatchMedia(599); + const component = render(); + const container = component.container.firstChild; + const cssProperty = getComputedStyle(container); + + expect(cssProperty.flexDirection).toBe('column'); +}); + +it('renders 2 buttons when mobile', () => { + window.matchMedia = createMatchMedia(599); + const { getAllByRole } = render(); + const buttons = getAllByRole('button'); + + expect(buttons.length).toBe(2); +}); diff --git a/test/components/Profile/ProfileComponent.test.jsx b/test/components/Profile/ProfileComponent.test.jsx index 310428079..25c762b85 100644 --- a/test/components/Profile/ProfileComponent.test.jsx +++ b/test/components/Profile/ProfileComponent.test.jsx @@ -6,6 +6,7 @@ import { ProfileComponent } from '@components/Profile'; import { SignedInUserContext } from '@contexts'; import * as profileHelper from '../../../src/model-helpers/Profile'; import '@testing-library/jest-dom/extend-expect'; +import createMatchMedia from '../../test-helper/createMatchMedia'; const profileInfo = { profileName: null, @@ -91,3 +92,30 @@ describe('ProfileComponent', () => { expect(editButton).toBeNull(); }); }); + +it('renders profile component as row default', () => { + const component = render( + + + + ); + + const container = component.container.firstChild; + const cssProperty = getComputedStyle(container); + + expect(cssProperty.flexDirection).toBe('row'); +}); + +it('renders profile component as column mobile', () => { + window.matchMedia = createMatchMedia(599); + const component = render( + + + + ); + + const container = component.container.firstChild; + const cssProperty = getComputedStyle(container); + + expect(cssProperty.flexDirection).toBe('column'); +}); diff --git a/test/pages/Home.test.jsx b/test/pages/Home.test.jsx deleted file mode 100644 index d1903c243..000000000 --- a/test/pages/Home.test.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { render, cleanup } from '@testing-library/react'; -import React from 'react'; -import { expect, it, afterEach, describe } from 'vitest'; -import { ThemeProvider } from '@mui/material/styles'; - -import Home from '../../src/pages/Home'; -import theme from '../../src/theme'; - -const HomeWithContexts = () => ( - - - -); - -describe('Home Page', () => { - afterEach(() => { - cleanup(); - }); - - it('renders', () => { - const { container } = render(); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/test/pages/__snapshots__/Home.test.jsx.snap b/test/pages/__snapshots__/Home.test.jsx.snap deleted file mode 100644 index 4c6d5e973..000000000 --- a/test/pages/__snapshots__/Home.test.jsx.snap +++ /dev/null @@ -1,166 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Home Page > renders 1`] = ` -
-
-
-
-
- -

- - Keep Your Documents Safe and Secure Using Decentralized Technology - -

-

- Our innovative solution empowers individuals to manage their critical documents and control access for trusted organizations. PASS simplifies service access, enabling seamless documents requests and secure data sharing for a smoother support process. -

- - Request a Demo - - - -

- - An App for Caseworkers - -

-

- PASS allows users to quickly and securely share documents of their clients within the app. The app helps caseworkers verify and share documents such as ID and proof of income while retaining total control of them. -

- -

- - Key Features - -

-

-

- - - Secure Storage - -
-

- Store vital documents like IDs, Social Security information, birth certificates, medical records, and bank statements in a valid digital format. -

-
- - - Nonprofit-Caseworker Integration - -
-

- The platform facilitates smooth communication between nonprofit organizations, case workers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and caseworkers are assigned contacts whose data they can access securely. -

-
- - - Support Service - -
-

- Verified documents can be used to facilitate access to service such as housing support and shelter accommodation. The platform simplifies the process of submitting necessary documents for such services. -

-
-
-
-
-
-`; From b1df11aa581883fc0561a38b4a5b8dc675e686c1 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Wed, 25 Oct 2023 03:01:30 -0700 Subject: [PATCH 047/178] Moved createMatchMedia.js to test/helpers --- test/components/Form/FormSection.test.jsx | 2 +- test/components/Home/HomeSection.test.jsx | 2 +- test/components/Home/KeyFeatures.test.jsx | 2 +- test/components/Messages/MessageFolder.test.jsx | 2 +- test/components/Messages/MessagePreview.test.jsx | 2 +- test/components/Modals/AddContactModal.test.jsx | 2 +- test/components/Modals/ConfirmationModal.test.jsx | 2 +- test/components/Modals/NewMessageModal.test.jsx | 2 +- test/components/Modals/UploadButtonGroup.test.jsx | 2 +- test/components/Modals/UploadDocumentModal.test.jsx | 2 +- test/components/NavBar/NavBar.test.jsx | 2 +- test/components/NavBar/OidcLoginComponent.test.jsx | 2 +- test/components/Profile/ProfileComponent.test.jsx | 2 +- test/{test-helper => helpers}/createMatchMedia.js | 0 14 files changed, 13 insertions(+), 13 deletions(-) rename test/{test-helper => helpers}/createMatchMedia.js (100%) diff --git a/test/components/Form/FormSection.test.jsx b/test/components/Form/FormSection.test.jsx index 1633f011a..636301a97 100644 --- a/test/components/Form/FormSection.test.jsx +++ b/test/components/Form/FormSection.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { expect, it } from 'vitest'; import { FormSection } from '@components/Form'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockChildrenComponent = () =>
; diff --git a/test/components/Home/HomeSection.test.jsx b/test/components/Home/HomeSection.test.jsx index ef4909b03..7ae668707 100644 --- a/test/components/Home/HomeSection.test.jsx +++ b/test/components/Home/HomeSection.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { expect, it, describe } from 'vitest'; import { HomeSection } from '@components/Home'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockFormSectionDefault = () => ; const MockFormSectionMobile = () => ; diff --git a/test/components/Home/KeyFeatures.test.jsx b/test/components/Home/KeyFeatures.test.jsx index 331f74283..888066189 100644 --- a/test/components/Home/KeyFeatures.test.jsx +++ b/test/components/Home/KeyFeatures.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { expect, it } from 'vitest'; import { KeyFeatures } from '@components/Home'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockKeyFeaturesDefault = () => ; const MockKeyFeaturesMobile = () => ; diff --git a/test/components/Messages/MessageFolder.test.jsx b/test/components/Messages/MessageFolder.test.jsx index ab22bfd77..cdfce2d67 100644 --- a/test/components/Messages/MessageFolder.test.jsx +++ b/test/components/Messages/MessageFolder.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import { MessageFolder } from '@components/Messages'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockMessageFolder = () => ; diff --git a/test/components/Messages/MessagePreview.test.jsx b/test/components/Messages/MessagePreview.test.jsx index 4549aeef8..ec860236d 100644 --- a/test/components/Messages/MessagePreview.test.jsx +++ b/test/components/Messages/MessagePreview.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import { MessagePreview } from '@components/Messages'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const mockMessageInfo = { sender: 'test', title: 'test title', uploadDate: new Date('1-1-2000') }; const MockMessagePreview = () => ; diff --git a/test/components/Modals/AddContactModal.test.jsx b/test/components/Modals/AddContactModal.test.jsx index 1452d3d98..687dfa97c 100644 --- a/test/components/Modals/AddContactModal.test.jsx +++ b/test/components/Modals/AddContactModal.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { expect, it } from 'vitest'; import { AddContactModal } from '@components/Modals'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockAddContactModal = () => ; diff --git a/test/components/Modals/ConfirmationModal.test.jsx b/test/components/Modals/ConfirmationModal.test.jsx index ba487eba6..5425e98b5 100644 --- a/test/components/Modals/ConfirmationModal.test.jsx +++ b/test/components/Modals/ConfirmationModal.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { ConfirmationModal } from '@components/Modals'; import { describe, expect, it } from 'vitest'; import { render } from '@testing-library/react'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockConfirmationModal = () => ; const MockConfirmationModalLogout = () => ( diff --git a/test/components/Modals/NewMessageModal.test.jsx b/test/components/Modals/NewMessageModal.test.jsx index 5044c5411..a85411054 100644 --- a/test/components/Modals/NewMessageModal.test.jsx +++ b/test/components/Modals/NewMessageModal.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { expect, it } from 'vitest'; import { render } from '@testing-library/react'; import { NewMessageModal } from '@components/Modals'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockNewMessageModal = () => ; diff --git a/test/components/Modals/UploadButtonGroup.test.jsx b/test/components/Modals/UploadButtonGroup.test.jsx index bff0c9977..869a59df3 100644 --- a/test/components/Modals/UploadButtonGroup.test.jsx +++ b/test/components/Modals/UploadButtonGroup.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { expect, it } from 'vitest'; import { render } from '@testing-library/react'; import UploadButtonGroup from '../../../src/components/Modals/UploadButtonGroup'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockUploadButtonGroup = () => ; diff --git a/test/components/Modals/UploadDocumentModal.test.jsx b/test/components/Modals/UploadDocumentModal.test.jsx index df9e5aa1a..6d476de10 100644 --- a/test/components/Modals/UploadDocumentModal.test.jsx +++ b/test/components/Modals/UploadDocumentModal.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { expect, it } from 'vitest'; import { render } from '@testing-library/react'; import { UploadDocumentModal } from '@components/Modals'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const MockUploadDocumentModal = () => ; diff --git a/test/components/NavBar/NavBar.test.jsx b/test/components/NavBar/NavBar.test.jsx index 60edd10e5..5d297bd86 100644 --- a/test/components/NavBar/NavBar.test.jsx +++ b/test/components/NavBar/NavBar.test.jsx @@ -3,7 +3,7 @@ import { BrowserRouter } from 'react-router-dom'; import { render, cleanup } from '@testing-library/react'; import { expect, it, afterEach, describe } from 'vitest'; import { SessionContext } from '@contexts'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; import NavBar from '../../../src/components/NavBar/NavBar'; // clear created dom after each test, to start fresh for next diff --git a/test/components/NavBar/OidcLoginComponent.test.jsx b/test/components/NavBar/OidcLoginComponent.test.jsx index 1f4be2f9b..e3aaded25 100644 --- a/test/components/NavBar/OidcLoginComponent.test.jsx +++ b/test/components/NavBar/OidcLoginComponent.test.jsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { expect, it, vi, afterEach } from 'vitest'; import OidcLoginComponent from '../../../src/components/NavBar/OidcLoginComponent'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; vi.mock('@inrupt/solid-client-authn-browser'); diff --git a/test/components/Profile/ProfileComponent.test.jsx b/test/components/Profile/ProfileComponent.test.jsx index 25c762b85..964f63a53 100644 --- a/test/components/Profile/ProfileComponent.test.jsx +++ b/test/components/Profile/ProfileComponent.test.jsx @@ -6,7 +6,7 @@ import { ProfileComponent } from '@components/Profile'; import { SignedInUserContext } from '@contexts'; import * as profileHelper from '../../../src/model-helpers/Profile'; import '@testing-library/jest-dom/extend-expect'; -import createMatchMedia from '../../test-helper/createMatchMedia'; +import createMatchMedia from '../../helpers/createMatchMedia'; const profileInfo = { profileName: null, diff --git a/test/test-helper/createMatchMedia.js b/test/helpers/createMatchMedia.js similarity index 100% rename from test/test-helper/createMatchMedia.js rename to test/helpers/createMatchMedia.js From 5820dade0eb43c20598c1551fc0e392586362da0 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Wed, 25 Oct 2023 17:23:36 -0700 Subject: [PATCH 048/178] Refactored MessageButtonGroup component outside Message page; Created unit tests for MessageButtonGroup --- .../Messages/MessageButtonGroup.jsx | 60 +++++++++++++++++++ src/components/Messages/index.js | 3 +- src/pages/Messages.jsx | 45 +++----------- .../Messages/MessageButtonGroup.test.jsx | 26 ++++++++ 4 files changed, 95 insertions(+), 39 deletions(-) create mode 100644 src/components/Messages/MessageButtonGroup.jsx create mode 100644 test/components/Messages/MessageButtonGroup.test.jsx diff --git a/src/components/Messages/MessageButtonGroup.jsx b/src/components/Messages/MessageButtonGroup.jsx new file mode 100644 index 000000000..76c6b8e16 --- /dev/null +++ b/src/components/Messages/MessageButtonGroup.jsx @@ -0,0 +1,60 @@ +// React Imports +import React from 'react'; +// Material UI Imports +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import CreateIcon from '@mui/icons-material/Create'; +import Tab from '@mui/material/Tab'; +import Tabs from '@mui/material/Tabs'; +import useMediaQuery from '@mui/material/useMediaQuery'; + +const routesArray = [{ label: 'Inbox' }, { label: 'Outbox' }]; + +/** + * Renders the Message Button Group component for new message, inbox, and outbox + * + * @param {object} Props - The Props for MessageButtonGroup Component + * @param {boolean} Props.showModal - The state to open new message modal + * @param {React.Dispatch>} Props.setShowModal - + * The set function for showModal + * @param {string} Props.boxType - The state for inbox or outbox + * @param {React.Dispatch>} Props.setBoxType - The + * set function for boxType + * @returns {React.JSX.Element} - The MessageButtonGroup Component + */ +const MessageButtonGroup = ({ showModal, setShowModal, boxType, setBoxType }) => { + const isReallySmallScreen = useMediaQuery('(max-width: 480px)'); + + return ( + + + + {routesArray.map((item) => ( + setBoxType(item.label.toLowerCase())} + /> + ))} + + + ); +}; + +export default MessageButtonGroup; diff --git a/src/components/Messages/index.js b/src/components/Messages/index.js index 5c44815bf..2d7366785 100644 --- a/src/components/Messages/index.js +++ b/src/components/Messages/index.js @@ -1,5 +1,6 @@ import MessagePreview from './MessagePreview'; import MessageFolder from './MessageFolder'; +import MessageButtonGroup from './MessageButtonGroup'; /** * Components and functions related to Messages functionality within project PASS @@ -7,4 +8,4 @@ import MessageFolder from './MessageFolder'; * @namespace Messages */ -export { MessagePreview, MessageFolder }; +export { MessagePreview, MessageFolder, MessageButtonGroup }; diff --git a/src/pages/Messages.jsx b/src/pages/Messages.jsx index 90d9bde3f..dc42e30c1 100644 --- a/src/pages/Messages.jsx +++ b/src/pages/Messages.jsx @@ -4,20 +4,13 @@ import React, { useContext, useState, useEffect } from 'react'; import { useSession } from '@hooks'; // Material UI Imports import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; -import CreateIcon from '@mui/icons-material/Create'; -import Tab from '@mui/material/Tab'; -import Tabs from '@mui/material/Tabs'; -import useMediaQuery from '@mui/material/useMediaQuery'; // Utility Imports import { getMessageTTL } from '../utils'; // Context Imports import { MessageContext, SignedInUserContext } from '../contexts'; // Component Imports import { NewMessageModal } from '../components/Modals'; -import { MessageFolder } from '../components/Messages'; - -const routesArray = [{ label: 'Inbox' }, { label: 'Outbox' }]; +import { MessageButtonGroup, MessageFolder } from '../components/Messages'; /** * Messages Page - Page that generates the components for the Message system @@ -71,38 +64,14 @@ const Messages = () => { const [boxType, setBoxType] = useState('inbox'); const [showModal, setShowModal] = useState(false); - const isReallySmallScreen = useMediaQuery('(max-width: 480px)'); - return ( - - - - {routesArray.map((item) => ( - setBoxType(item.label.toLowerCase())} - /> - ))} - - + ; + +it('renders button group as a row default', () => { + const { getByRole } = render(); + const newMessageButton = getByRole('button', { name: 'New Message' }); + const buttonContainer = newMessageButton.parentElement; + const cssProperty = getComputedStyle(buttonContainer); + + expect(cssProperty.flexDirection).toBe('row'); +}); + +it('renders button group as a column below 480px', () => { + window.matchMedia = createMatchMedia(479); + const { getByRole } = render(); + const newMessageButton = getByRole('button', { name: 'New Message' }); + const buttonContainer = newMessageButton.parentElement; + const cssProperty = getComputedStyle(buttonContainer); + + expect(cssProperty.flexDirection).toBe('column'); +}); From 238ecae3c47498fe666e9bb69a3d8fb0f69214c4 Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Wed, 25 Oct 2023 18:07:56 -0700 Subject: [PATCH 049/178] Reincluded testing for Home.jsx with updated snapshot --- test/pages/Home.test.jsx | 24 +++ test/pages/__snapshots__/Home.test.jsx.snap | 166 ++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 test/pages/Home.test.jsx create mode 100644 test/pages/__snapshots__/Home.test.jsx.snap diff --git a/test/pages/Home.test.jsx b/test/pages/Home.test.jsx new file mode 100644 index 000000000..d1903c243 --- /dev/null +++ b/test/pages/Home.test.jsx @@ -0,0 +1,24 @@ +import { render, cleanup } from '@testing-library/react'; +import React from 'react'; +import { expect, it, afterEach, describe } from 'vitest'; +import { ThemeProvider } from '@mui/material/styles'; + +import Home from '../../src/pages/Home'; +import theme from '../../src/theme'; + +const HomeWithContexts = () => ( + + + +); + +describe('Home Page', () => { + afterEach(() => { + cleanup(); + }); + + it('renders', () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/test/pages/__snapshots__/Home.test.jsx.snap b/test/pages/__snapshots__/Home.test.jsx.snap new file mode 100644 index 000000000..2aa8c98cf --- /dev/null +++ b/test/pages/__snapshots__/Home.test.jsx.snap @@ -0,0 +1,166 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Home Page > renders 1`] = ` +
+
+
+
+
+ +

+ + Keep Your Documents Safe and Secure Using Decentralized Technology + +

+

+ Our innovative solution empowers individuals to manage their critical documents and control access for trusted organizations. PASS simplifies service access, enabling seamless documents requests and secure data sharing for a smoother support process. +

+ + Request a Demo + + + +

+ + An App for Caseworkers + +

+

+ PASS allows users to quickly and securely share documents of their clients within the app. The app helps caseworkers verify and share documents such as ID and proof of income while retaining total control of them. +

+ +

+ + Key Features + +

+

+

+ + + Secure Storage + +
+

+ Store vital documents like IDs, Social Security information, birth certificates, medical records, and bank statements in a valid digital format. +

+
+ + + Nonprofit-Caseworker Integration + +
+

+ The platform facilitates smooth communication between nonprofit organizations, case workers, and the individuals they serve. It allows nonprofit organizations to maintain a contact list, and caseworkers are assigned contacts whose data they can access securely. +

+
+ + + Support Service + +
+

+ Verified documents can be used to facilitate access to service such as housing support and shelter accommodation. The platform simplifies the process of submitting necessary documents for such services. +

+
+
+
+
+
+`; From 6ebf11438afe340adef90f01a2ff65c0f1a54d5e Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Wed, 25 Oct 2023 18:14:59 -0700 Subject: [PATCH 050/178] Patching package vulnerabilities --- package-lock.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c80b7540b..80ec1bc45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6091,8 +6091,9 @@ } }, "node_modules/crypto-js": { - "version": "4.1.1", - "license": "MIT" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/css-color-keywords": { "version": "1.0.0", @@ -19264,7 +19265,9 @@ } }, "crypto-js": { - "version": "4.1.1" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "css-color-keywords": { "version": "1.0.0" From c1f340e8b65eb3af3ea9b0e451b202c0e1223d6c Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Wed, 25 Oct 2023 21:18:02 -0700 Subject: [PATCH 051/178] Include unit test for NavbarLoggedOut.jsx --- .../NavBar/NavbarLoggedOut.test.jsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/components/NavBar/NavbarLoggedOut.test.jsx b/test/components/NavBar/NavbarLoggedOut.test.jsx index 33800a976..257d0035d 100644 --- a/test/components/NavBar/NavbarLoggedOut.test.jsx +++ b/test/components/NavBar/NavbarLoggedOut.test.jsx @@ -2,7 +2,10 @@ import React from 'react'; import { render } from '@testing-library/react'; import { expect, it } from 'vitest'; import { BrowserRouter } from 'react-router-dom'; +import { ThemeProvider } from '@mui/material/styles'; import { NavbarLoggedOut } from '@components/NavBar'; +import theme from '../../../src/theme'; +import createMatchMedia from '../../helpers/createMatchMedia'; it('renders login button when user is logged out', () => { const { queryByRole } = render( @@ -17,3 +20,20 @@ it('renders login button when user is logged out', () => { expect(logo).not.toBeNull(); expect(loginButton).not.toBeNull(); }); + +it('renders sign-in button when user is logged out below 600px', async () => { + window.matchMedia = createMatchMedia(599); + const { queryByRole } = render( + + + + + + ); + + const signInButton = queryByRole('button', { name: 'Sign In' }); + const loginButton = queryByRole('button', { name: 'Login' }); + + expect(signInButton).not.toBeNull(); + expect(loginButton).toBeNull(); +}); From d2ab26aeb0bb45ab00aabab067ea8e1fe2db9b97 Mon Sep 17 00:00:00 2001 From: Tim Standen <37914436+timbot1789@users.noreply.github.com> Date: Sun, 29 Oct 2023 21:23:59 -0700 Subject: [PATCH 052/178] 440/create civic profile hook (#482) --- __mocks__/@inrupt/solid-client.js | 9 ++- src/constants/HMIS_ONTOLOGY_VALUES.js | 8 ++ src/constants/rdf_predicates.js | 2 + src/hooks/index.js | 3 +- src/hooks/useCivicProfile.js | 99 +++++++++++++++++++++++++ src/hooks/useContactsList.js | 70 ++++++++--------- test/hooks/useCivicProfile.test.jsx | 103 ++++++++++++++++++++++++++ test/hooks/useContactsList.test.jsx | 70 ++++++++++++++++- 8 files changed, 324 insertions(+), 40 deletions(-) create mode 100644 src/constants/HMIS_ONTOLOGY_VALUES.js create mode 100644 src/hooks/useCivicProfile.js create mode 100644 test/hooks/useCivicProfile.test.jsx diff --git a/__mocks__/@inrupt/solid-client.js b/__mocks__/@inrupt/solid-client.js index 42454bca7..676f95c55 100644 --- a/__mocks__/@inrupt/solid-client.js +++ b/__mocks__/@inrupt/solid-client.js @@ -1,5 +1,9 @@ import { vi } from 'vitest'; -import { mockSolidDatasetFrom, addMockResourceAclTo } from '@inrupt/solid-client'; +import { + mockSolidDatasetFrom, + addMockResourceAclTo, + createSolidDataset as realCreateSolidDataset +} from '@inrupt/solid-client'; export * from '@inrupt/solid-client'; @@ -18,9 +22,10 @@ export const saveAclFor = vi.fn((url) => export const saveSolidDatasetAt = vi.fn(mockDatasetFactory); export const getSolidDataset = vi.fn(mockDatasetFactory); export const createContainerAt = vi.fn(() => Promise.resolve()); +export const createSolidDataset = vi.fn(() => realCreateSolidDataset()); export const deleteFile = vi.fn(() => Promise.resolve()); export const getSolidDatasetWithAcl = vi.fn(mockDatasetFactory); export const saveSolidDatasetInContainer = vi.fn(() => Promise.resolve()); export const getFile = vi.fn(() => Promise.resolve()); export const saveFileInContainer = vi.fn(() => Promise.resolve()); -export const getWebIdDataset = vi.fn((url) => Promise.resolve(mockSolidDatasetFrom(url))) +export const getWebIdDataset = vi.fn((url) => Promise.resolve(mockSolidDatasetFrom(url))); diff --git a/src/constants/HMIS_ONTOLOGY_VALUES.js b/src/constants/HMIS_ONTOLOGY_VALUES.js new file mode 100644 index 000000000..9e79d7b42 --- /dev/null +++ b/src/constants/HMIS_ONTOLOGY_VALUES.js @@ -0,0 +1,8 @@ +const HMIS_ONTOLOGY_VALUES = { + legalFirstName: 'urn:hud:hmis:owl#FirstName', + legalLastName: 'urn:hud:hmis:owl#LastName', + legalDOB: 'urn:hud:hmis:owl#DOB', + legalGender: 'urn:hud:hmis:owl#Gender' +}; + +export default HMIS_ONTOLOGY_VALUES; diff --git a/src/constants/rdf_predicates.js b/src/constants/rdf_predicates.js index 9168ee413..b25c14f03 100644 --- a/src/constants/rdf_predicates.js +++ b/src/constants/rdf_predicates.js @@ -1,7 +1,9 @@ import { SCHEMA_INRUPT, FOAF } from '@inrupt/vocab-common-rdf'; +import HMIS_ONTOLOGY_VALUES from './HMIS_ONTOLOGY_VALUES'; const RDF_PREDICATES = { ...SCHEMA_INRUPT, + ...HMIS_ONTOLOGY_VALUES, about: 'https://schema.org/about', uploadDate: 'https://schema.org/uploadDate', dateOfBirth: diff --git a/src/hooks/index.js b/src/hooks/index.js index 1e145e51f..8698bc0cb 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -1,5 +1,6 @@ import useContactsList from './useContactsList'; import useNotification from './useNotification'; +import useCivicProfile from './useCivicProfile'; import useSession from './useSession'; /** * The hooks module contains custom hooks to assist with form handling or status @@ -8,4 +9,4 @@ import useSession from './useSession'; * @namespace hooks */ -export { useContactsList, useNotification, useSession }; +export { useContactsList, useNotification, useSession, useCivicProfile }; diff --git a/src/hooks/useCivicProfile.js b/src/hooks/useCivicProfile.js new file mode 100644 index 000000000..763f225eb --- /dev/null +++ b/src/hooks/useCivicProfile.js @@ -0,0 +1,99 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { + getDate, + getInteger, + getStringNoLocale, + getThing, + buildThing, + createThing, + setThing, + createSolidDataset, + getSolidDataset, + saveSolidDatasetAt +} from '@inrupt/solid-client'; +import { RDF_PREDICATES } from '@constants'; +import useSession from './useSession'; + +const makeIntoThing = ({ firstName, lastName, dateOfBirth, gender }) => + buildThing(createThing({ name: 'Civic Profile' })) + .addStringNoLocale(RDF_PREDICATES.legalFirstName, firstName) + .addStringNoLocale(RDF_PREDICATES.legalLastName, lastName) + .addDate(RDF_PREDICATES.legalDOB, dateOfBirth) + .addInteger(RDF_PREDICATES.legalGender, gender) + .build(); + +let storedDataset; +const useCivicProfile = () => { + const queryClient = useQueryClient(); + const { session, podUrl } = useSession(); + const { fetch } = session; + const fileUrl = podUrl && new URL('PASS/Profile/civic_profile.ttl', podUrl).toString(); + + const parse = (data) => { + const url = new URL(fileUrl); + url.hash = 'Civic Profile'; + const profileThing = getThing(data, url.toString()); + if (profileThing === null) { + return {}; + } + const profile = {}; + profile.firstName = getStringNoLocale(profileThing, RDF_PREDICATES.legalFirstName); + profile.lastName = getStringNoLocale(profileThing, RDF_PREDICATES.legalLastName); + profile.dateOfBirth = getDate(profileThing, RDF_PREDICATES.legalDOB); + profile.gender = getInteger(profileThing, RDF_PREDICATES.legalGender); + return profile; + }; + + const saveData = async (dataset) => { + const savedDataset = await saveSolidDatasetAt(fileUrl, dataset, { + fetch + }); + storedDataset = savedDataset; + return parse(savedDataset); + }; + + const fetchCivicProfile = async () => { + let myDataset; + try { + myDataset = await getSolidDataset(fileUrl, { fetch }); + } catch (e) { + if (e.response.status === 404) { + myDataset = createSolidDataset(); + myDataset = await saveSolidDatasetAt(fileUrl, myDataset, { fetch }); + } else { + throw e; + } + } + storedDataset = myDataset; + return parse(myDataset); + }; + + const { isLoading, isError, error, data, isSuccess } = useQuery({ + queryKey: [fileUrl], + queryFn: fetchCivicProfile + }); + + const updateMutation = useMutation({ + mutationFn: async (profile) => { + if (!data) await fetchCivicProfile(); + const thing = makeIntoThing(profile); + const newDataset = setThing(storedDataset, thing); + const savedDataset = await saveData(newDataset); + return savedDataset; + }, + onSuccess: (resData) => { + queryClient.setQueryData([fileUrl], () => resData); + } + }); + + return { + isLoading, + isError, + isSuccess, + error, + data, + updateProfile: updateMutation.mutateAsync + }; +}; + +export default useCivicProfile; diff --git a/src/hooks/useContactsList.js b/src/hooks/useContactsList.js index 934c0592d..755f97336 100644 --- a/src/hooks/useContactsList.js +++ b/src/hooks/useContactsList.js @@ -15,7 +15,7 @@ import { import { RDF_PREDICATES } from '@constants'; import useSession from './useSession'; -const makeContactIntoThing = ({ givenName, familyName, webId }) => +const makeIntoThing = ({ givenName, familyName, webId }) => buildThing(createThing({ name: encodeURIComponent(webId) })) .addStringNoLocale(RDF_PREDICATES.Person, `${givenName} ${familyName}`) .addStringNoLocale(RDF_PREDICATES.givenName, givenName) @@ -24,23 +24,6 @@ const makeContactIntoThing = ({ givenName, familyName, webId }) => .addUrl(RDF_PREDICATES.URL, webId.split('profile')[0]) .build(); -const parseContacts = (data) => { - const contactThings = getThingAll(data); - const contacts = []; - contactThings.forEach((thing) => { - const contact = {}; - - contact.webId = getUrl(thing, RDF_PREDICATES.identifier); - if (!contact.webId) return; - contact.podUrl = getUrl(thing, RDF_PREDICATES.URL); - contact.givenName = getStringNoLocale(thing, RDF_PREDICATES.givenName); - contact.familyName = getStringNoLocale(thing, RDF_PREDICATES.familyName); - contact.person = getStringNoLocale(thing, RDF_PREDICATES.Person); - contacts.push(contact); - }); - return contacts; -}; - /** * @typedef {object} ContactsList * @property {boolean} isLoading - if the contacts list is loading @@ -58,31 +41,47 @@ const parseContacts = (data) => { * @returns {ContactsList} - all the data provided by the useQuery call * @memberof hooks */ +let storedDataset; const useContactsList = () => { const queryClient = useQueryClient(); const { session, podUrl } = useSession(); const { fetch } = session; const url = podUrl && new URL('PASS/Users/userlist.ttl', podUrl).toString(); + const parse = (data) => { + const contactThings = getThingAll(data); + const contacts = []; + contactThings.forEach((thing) => { + const contact = {}; + contact.webId = getUrl(thing, RDF_PREDICATES.identifier); + if (!contact.webId) return; + contact.podUrl = getUrl(thing, RDF_PREDICATES.URL); + contact.givenName = getStringNoLocale(thing, RDF_PREDICATES.givenName); + contact.familyName = getStringNoLocale(thing, RDF_PREDICATES.familyName); + contact.person = getStringNoLocale(thing, RDF_PREDICATES.Person); + contacts.push(contact); + }); + return contacts; + }; const saveData = async (dataset) => { - const savedDataset = await saveSolidDatasetAt(url, dataset, { + storedDataset = await saveSolidDatasetAt(url, dataset, { fetch }); - return savedDataset; + return parse(storedDataset); }; const fetchContactsList = async () => { - let myDataset; try { - myDataset = await getSolidDataset(url, { fetch }); + storedDataset = await getSolidDataset(url, { fetch }); } catch (e) { if (e.response.status === 404) { - myDataset = createSolidDataset(); - myDataset = await saveSolidDatasetAt(url, myDataset, { fetch }); + storedDataset = createSolidDataset(); + storedDataset = await saveSolidDatasetAt(url, storedDataset, { fetch }); + } else { + throw e; } - throw e; } - return myDataset; + return parse(storedDataset); }; const { isLoading, isError, error, data, isSuccess } = useQuery({ @@ -93,10 +92,10 @@ const useContactsList = () => { const addContactMutation = useMutation({ mutationFn: async (newContact) => { if (!data) await fetchContactsList(); - const thing = makeContactIntoThing(newContact); - const newDataset = setThing(data, thing); - const savedDataset = await saveData(newDataset); - return savedDataset; + const thing = makeIntoThing(newContact); + const newDataset = setThing(storedDataset, thing); + const newContactsList = await saveData(newDataset); + return newContactsList; }, onSuccess: (resData) => { queryClient.setQueryData([url], () => resData); @@ -105,11 +104,12 @@ const useContactsList = () => { const deleteContactMutation = useMutation({ mutationFn: async (contactToDelete) => { + if (!data) await fetchContactsList(); const thingUrl = `${url}#${encodeURIComponent(contactToDelete.webId)}`; - const thingToRemove = getThing(data, thingUrl); - const newDataset = removeThing(data, thingToRemove); - const savedDataset = await saveData(newDataset); - return savedDataset; + const thingToRemove = getThing(storedDataset, thingUrl); + const newDataset = removeThing(storedDataset, thingToRemove); + const newContactsList = await saveData(newDataset); + return newContactsList; }, onSuccess: (resData) => { queryClient.setQueryData([url], () => resData); @@ -121,7 +121,7 @@ const useContactsList = () => { isError, isSuccess, error, - data: !(isLoading || isError) ? parseContacts(data) : [], + data, deleteContact: deleteContactMutation.mutate, addContact: addContactMutation.mutate }; diff --git a/test/hooks/useCivicProfile.test.jsx b/test/hooks/useCivicProfile.test.jsx new file mode 100644 index 000000000..6621238c6 --- /dev/null +++ b/test/hooks/useCivicProfile.test.jsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { + buildThing, + createThing, + getSolidDataset, + mockSolidDatasetFrom, + createSolidDataset, + saveSolidDatasetAt, + setThing +} from '@inrupt/solid-client'; +import { RDF_PREDICATES } from '@constants'; +import { expect, it, describe, vi } from 'vitest'; +import { renderHook, waitFor } from '@testing-library/react'; +import { useCivicProfile } from '@hooks'; +import { SessionContext } from '@contexts'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +vi.mock('@inrupt/solid-client'); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false + } + } +}); + +const sessionInfo = { + session: { + info: { + webId: 'https://example.com/profile' + } + } +}; + +const wrapper = ({ children }) => ( + + {children} + +); + +const makeIntoThing = ({ firstName, lastName, dateOfBirth, gender }) => + buildThing(createThing({ name: 'Civic Profile' })) + .addStringNoLocale(RDF_PREDICATES.legalFirstName, firstName) + .addStringNoLocale(RDF_PREDICATES.legalLastName, lastName) + .addDate(RDF_PREDICATES.legalDOB, dateOfBirth) + .addInteger(RDF_PREDICATES.legalGender, gender) + .build(); + +describe('useCivicProfile', () => { + const profile = { + firstName: 'Luffy', + lastName: 'Monkey', + dateOfBirth: new Date('July 21, 1983 12:00:00'), + gender: 99 + }; + it('Returns empty object if no civic profile is found', async () => { + getSolidDataset.mockResolvedValue( + mockSolidDatasetFrom('https://example.com/PASS/Profile/civic_profile.ttl') + ); + const { result } = renderHook(useCivicProfile, { wrapper }); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + expect(result.current.data).toStrictEqual({}); + }); + + it('Creates a new file if none exists', async () => { + getSolidDataset.mockRejectedValue({ response: { status: 404 } }); + const { result } = renderHook(useCivicProfile, { wrapper }); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + expect(createSolidDataset).toBeCalledTimes(1); + }); + + it('Returns the Civic Profile if found', async () => { + const thing = buildThing(createThing({ name: 'Civic Profile' })) + .addStringNoLocale(RDF_PREDICATES.legalFirstName, profile.firstName) + .addStringNoLocale(RDF_PREDICATES.legalLastName, profile.lastName) + .addDate(RDF_PREDICATES.legalDOB, profile.dateOfBirth) + .addInteger(RDF_PREDICATES.legalGender, profile.gender) + .build(); + const dataset = setThing( + mockSolidDatasetFrom('https://example.com/PASS/Profile/civic_profile.ttl'), + thing + ); + getSolidDataset.mockResolvedValue(dataset); + const { result } = renderHook(useCivicProfile, { wrapper }); + await waitFor(() => expect(result.current.data).toStrictEqual(profile)); + expect(result.current.data).toStrictEqual(profile); + }); + + it('Updates Civic Profile with proper data', async () => { + let dataset = mockSolidDatasetFrom('https://example.com/PASS/Profile/civic_profile.ttl'); + getSolidDataset.mockResolvedValue(dataset); + saveSolidDatasetAt.mockImplementation((_, data) => Promise.resolve(data)); + const thing = makeIntoThing(profile); + dataset = setThing(dataset, thing); + const { result } = renderHook(useCivicProfile, { wrapper }); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + const hook = result.current; + await hook.updateProfile(profile); + await waitFor(() => expect(result.current.data).toStrictEqual(profile)); + expect(saveSolidDatasetAt).toBeCalled(); + }); +}); diff --git a/test/hooks/useContactsList.test.jsx b/test/hooks/useContactsList.test.jsx index bff96c387..54cc89bf3 100644 --- a/test/hooks/useContactsList.test.jsx +++ b/test/hooks/useContactsList.test.jsx @@ -1,5 +1,14 @@ import React from 'react'; -import { getSolidDataset, mockSolidDatasetFrom } from '@inrupt/solid-client'; +import { + buildThing, + createThing, + setThing, + getSolidDataset, + saveSolidDatasetAt, + mockSolidDatasetFrom, + createSolidDataset +} from '@inrupt/solid-client'; +import { RDF_PREDICATES } from '@constants'; import { expect, it, describe, vi } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { useContactsList } from '@hooks'; @@ -8,7 +17,13 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; vi.mock('@inrupt/solid-client'); -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false + } + } +}); const sessionInfo = { session: { @@ -24,7 +39,23 @@ const wrapper = ({ children }) => ( ); +const makeIntoThing = ({ givenName, familyName, webId }) => + buildThing(createThing({ name: encodeURIComponent(webId) })) + .addStringNoLocale(RDF_PREDICATES.Person, `${givenName} ${familyName}`) + .addStringNoLocale(RDF_PREDICATES.givenName, givenName) + .addStringNoLocale(RDF_PREDICATES.familyName, familyName) + .addUrl(RDF_PREDICATES.identifier, webId) + .addUrl(RDF_PREDICATES.URL, webId.split('profile')[0]) + .build(); + describe('useContactsList', () => { + const contact = { + givenName: 'Zoro', + familyName: 'Roronoa', + webId: 'http://www.example.com/swords', + person: 'Zoro Roronoa', + podUrl: 'http://www.example.com/swords' + }; it('Returns a list of contacts if list is found', async () => { getSolidDataset.mockResolvedValue( mockSolidDatasetFrom('https://example.com/PASS/Users/userlist.ttl') @@ -33,4 +64,39 @@ describe('useContactsList', () => { await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data.length).toBe(0); }); + + it('Creates a new file if none exists', async () => { + getSolidDataset.mockRejectedValue({ response: { status: 404 } }); + const { result } = renderHook(useContactsList, { wrapper }); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + expect(createSolidDataset).toBeCalledTimes(1); + }); + + it('Adds contact to contacts list', async () => { + let dataset = mockSolidDatasetFrom('https://example.com/PASS/Users/userlist.ttl'); + getSolidDataset.mockResolvedValue(dataset); + saveSolidDatasetAt.mockImplementation((_, data) => Promise.resolve(data)); + const thing = makeIntoThing(contact); + dataset = setThing(dataset, thing); + const { result } = renderHook(useContactsList, { wrapper }); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + const hook = result.current; + await hook.addContact(contact); + await waitFor(() => expect(result.current.data).toStrictEqual([contact])); + expect(saveSolidDatasetAt).toBeCalled(); + }); + + it('Removes contact from contacts list', async () => { + let dataset = mockSolidDatasetFrom('https://example.com/PASS/Users/userlist.ttl'); + saveSolidDatasetAt.mockImplementation((_, data) => Promise.resolve(data)); + const thing = makeIntoThing(contact); + dataset = setThing(dataset, thing); + getSolidDataset.mockResolvedValue(dataset); + const { result } = renderHook(useContactsList, { wrapper }); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + const hook = result.current; + await hook.deleteContact(contact); + await waitFor(() => expect(result.current.data).toStrictEqual([])); + expect(saveSolidDatasetAt).toBeCalled(); + }); }); From 14a871ed99f5360dc1b739d86e9369d951f4991e Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 30 Oct 2023 20:11:18 -0700 Subject: [PATCH 053/178] Refactoring for new contact table --- src/components/Contacts/ContactListTable.jsx | 27 +++++++++++++++++++- src/components/Modals/NewMessageModal.jsx | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/components/Contacts/ContactListTable.jsx b/src/components/Contacts/ContactListTable.jsx index d167dd2d5..13c91a850 100644 --- a/src/components/Contacts/ContactListTable.jsx +++ b/src/components/Contacts/ContactListTable.jsx @@ -1,8 +1,9 @@ // React Imports -import React from 'react'; +import React, { useState } from 'react'; // Material UI Imports import Box from '@mui/material/Box'; import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; +import SendIcon from '@mui/icons-material/Send'; import { DataGrid, GridToolbarContainer, @@ -14,6 +15,7 @@ import { import theme from '../../theme'; // Component Imports import ContactProfileIcon from './ContactProfileIcon'; +import { NewMessageModal } from '../Modals'; const CustomToolbar = () => ( @@ -38,6 +40,14 @@ const CustomToolbar = () => ( * @returns {React.JSX.Element} The ContactListTable Component */ const ContactListTable = ({ contacts, deleteContact }) => { + const [showMessageModal, setShowMessageModal] = useState(false); + const [messageToField, setMessageToField] = useState(''); + + const handleSendMessage = (contactId) => { + setShowMessageModal(!showMessageModal); + setMessageToField(contactId.id); + }; + const columnTitlesArray = [ { field: 'First Name', @@ -62,6 +72,15 @@ const ContactListTable = ({ contacts, deleteContact }) => { headerAlign: 'center', align: 'center' }, + { + field: 'Message', + renderCell: (contactId) => handleSendMessage(contactId)} />, + sortable: false, + filterable: false, + width: 80, + headerAlign: 'center', + align: 'center' + }, { field: 'actions', type: 'actions', @@ -86,6 +105,7 @@ const ContactListTable = ({ contacts, deleteContact }) => { 'First Name': contact.givenName, 'Last Name': contact.familyName, Profile: contact, + Message: contact, Delete: contact }))} slots={{ @@ -112,6 +132,11 @@ const ContactListTable = ({ contacts, deleteContact }) => { disableColumnMenu disableRowSelectionOnClick /> +
); }; diff --git a/src/components/Modals/NewMessageModal.jsx b/src/components/Modals/NewMessageModal.jsx index cf5d80921..8dc79628c 100644 --- a/src/components/Modals/NewMessageModal.jsx +++ b/src/components/Modals/NewMessageModal.jsx @@ -37,7 +37,7 @@ import { MessageContext, SignedInUserContext } from '@contexts'; * object when using the modal to reply, else uses a string if empty * @returns {React.JSX.Element} React component for NewMessageModal */ -const NewMessageModal = ({ showModal, setShowModal, oldMessage = '', toField }) => { +const NewMessageModal = ({ showModal, setShowModal, oldMessage = '', toField = '' }) => { const { session } = useSession(); const { outboxList, setOutboxList } = useContext(MessageContext); const { podUrl } = useContext(SignedInUserContext); From fdac30a51bbdbd6f2d38bd1bc85720fb913ddabd Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Mon, 30 Oct 2023 20:44:17 -0700 Subject: [PATCH 054/178] Created conditions to move tabs into NavMenu below 600px; added unit tests for render changes --- src/components/NavBar/NavMenu.jsx | 33 ++++++++++++++++++++ src/components/NavBar/NavbarMobile.jsx | 4 ++- test/components/NavBar/NavMenu.test.jsx | 33 ++++++++++++++++++++ test/components/NavBar/NavbarMobile.test.jsx | 18 +++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 test/components/NavBar/NavMenu.test.jsx diff --git a/src/components/NavBar/NavMenu.jsx b/src/components/NavBar/NavMenu.jsx index 40dd49b88..937a2951a 100644 --- a/src/components/NavBar/NavMenu.jsx +++ b/src/components/NavBar/NavMenu.jsx @@ -2,9 +2,11 @@ import React, { useContext } from 'react'; import { Link } from 'react-router-dom'; // Material UI Imports +import AccountBoxIcon from '@mui/icons-material/AccountBox'; import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import Badge from '@mui/material/Badge'; +import ContactsIcon from '@mui/icons-material/Contacts'; import Divider from '@mui/material/Divider'; import EmailIcon from '@mui/icons-material/Email'; import LogoutIcon from '@mui/icons-material/Logout'; @@ -13,6 +15,7 @@ import MenuItem from '@mui/material/MenuItem'; import MenuList from '@mui/material/MenuList'; import NotificationsIcon from '@mui/icons-material/Notifications'; import SettingsIcon from '@mui/icons-material/Settings'; +import useMediaQuery from '@mui/material/useMediaQuery'; import { useTheme } from '@mui/material/styles'; // Context Imports import { DocumentListContext, MessageContext } from '@contexts'; @@ -47,6 +50,7 @@ const NavMenu = ({ profileImg }) => { const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const { setContact } = useContext(DocumentListContext); const { numUnreadMessages } = useContext(MessageContext); @@ -74,6 +78,35 @@ const NavMenu = ({ sx={{ mt: 5, backgroundColor: 'rgba(1, 121, 105, 0.2)' }} > + {isSmallScreen && ( +
+ + } + sx={{ display: { md: 'none' }, color: theme.palette.primary.main, width: '100%' }} + > + Contacts + + + + } + sx={{ display: { md: 'none' }, color: theme.palette.primary.main, width: '100%' }} + > + Civic Profile + + + +
+ )} {/* messages */} { const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); // states for NavMenu component const [anchorEl, setAnchorEl] = useState(null); @@ -63,7 +65,7 @@ const NavbarMobile = ({ setShowConfirmation }) => { PASS logo - + {!isSmallScreen && } ( + + + +); + +it('does not render contacts and civic profile links above 600px', () => { + const { queryByText } = render(); + + const contactsLink = queryByText('Contacts'); + const civicProfileLink = queryByText('Civic Profile'); + + expect(contactsLink).toBeNull(); + expect(civicProfileLink).toBeNull(); +}); + +it('renders contacts and civic profile links below 600px', () => { + window.matchMedia = createMatchMedia(599); + const { queryByText } = render(); + + const contactsLink = queryByText('Contacts'); + const civicProfileLink = queryByText('Civic Profile'); + + expect(contactsLink).not.toBeNull(); + expect(civicProfileLink).not.toBeNull(); +}); diff --git a/test/components/NavBar/NavbarMobile.test.jsx b/test/components/NavBar/NavbarMobile.test.jsx index f0b4c53af..5944b32f0 100644 --- a/test/components/NavBar/NavbarMobile.test.jsx +++ b/test/components/NavBar/NavbarMobile.test.jsx @@ -3,6 +3,7 @@ import { BrowserRouter } from 'react-router-dom'; import { render } from '@testing-library/react'; import { expect, it } from 'vitest'; import NavbarMobile from '../../../src/components/NavBar/NavbarMobile'; +import createMatchMedia from '../../helpers/createMatchMedia'; it('renders hamburger menu when on mobile', () => { const { queryByLabelText, queryByRole } = render( @@ -19,3 +20,20 @@ it('renders hamburger menu when on mobile', () => { expect(navLinks).not.toBeNull(); expect(hamburgerMenu).not.toBeNull(); }); + +it('removes tab links from header to NavMenu below 600px', async () => { + window.matchMedia = createMatchMedia(599); + const { queryByLabelText, queryByRole } = render( + + + + ); + + const logo = queryByRole('img', { name: /logo$/ }); + const navLinks = queryByLabelText('navigation tabs'); + const hamburgerMenu = queryByLabelText('mobile menu'); + + expect(logo).not.toBeNull(); + expect(navLinks).toBeNull(); + expect(hamburgerMenu).not.toBeNull(); +}); From f2bf615351bd131d66a4cd7c889a6ae79fd9aa4d Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Tue, 31 Oct 2023 19:26:38 -0700 Subject: [PATCH 055/178] Extending mobile adjustments to new civic profile page; included unit test for render changes; cleaning up imports --- .../CivicProfileForms/BasicInfo.jsx | 4 +- .../CivicProfileForms/FinancialInfo.jsx | 4 +- .../CivicProfileForms/FormLayout.jsx | 10 ++++- .../CivicProfileForms/HousingInfo.jsx | 4 +- src/layouts/Layout.jsx | 9 ++-- src/pages/CivicProfile.jsx | 15 +++++-- test/pages/CivicProfile.test.jsx | 41 +++++++++++++++++++ 7 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 test/pages/CivicProfile.test.jsx diff --git a/src/components/CivicProfileForms/BasicInfo.jsx b/src/components/CivicProfileForms/BasicInfo.jsx index d37b16c2c..b3640dc90 100644 --- a/src/components/CivicProfileForms/BasicInfo.jsx +++ b/src/components/CivicProfileForms/BasicInfo.jsx @@ -1,5 +1,7 @@ -import { Typography } from '@mui/material'; +// React Imports import React from 'react'; +// MUI Imports +import { Typography } from '@mui/material'; const BasicInfo = () => Basic Info; diff --git a/src/components/CivicProfileForms/FinancialInfo.jsx b/src/components/CivicProfileForms/FinancialInfo.jsx index a48d643bd..00837a1b9 100644 --- a/src/components/CivicProfileForms/FinancialInfo.jsx +++ b/src/components/CivicProfileForms/FinancialInfo.jsx @@ -1,5 +1,7 @@ -import { Typography } from '@mui/material'; +// React Imports import React from 'react'; +// MUI Imports +import { Typography } from '@mui/material'; const FinancialInfo = () => Financial Info; diff --git a/src/components/CivicProfileForms/FormLayout.jsx b/src/components/CivicProfileForms/FormLayout.jsx index 808ce0daa..21d5751d3 100644 --- a/src/components/CivicProfileForms/FormLayout.jsx +++ b/src/components/CivicProfileForms/FormLayout.jsx @@ -1,7 +1,12 @@ -import { Card, Container, Link } from '@mui/material'; +// React Imports import React from 'react'; import { Link as RouterLink, useLocation } from 'react-router-dom'; -import { Box } from '@mui/system'; +// MUI Imports +import Box from '@mui/material/Box'; +import Card from '@mui/material/Card'; +import Container from '@mui/material/Container'; +import Link from '@mui/material/Link'; +// Other Imports import HMIS_FORM_LIST from './FormList'; const FormLayout = ({ children }) => { @@ -10,6 +15,7 @@ const FormLayout = ({ children }) => { localStorage.setItem('restorePath', location.pathname); const pageIdx = HMIS_FORM_LIST.findIndex((form) => form.path === path); + return ( {children} diff --git a/src/components/CivicProfileForms/HousingInfo.jsx b/src/components/CivicProfileForms/HousingInfo.jsx index ba52a59f4..dd159cb71 100644 --- a/src/components/CivicProfileForms/HousingInfo.jsx +++ b/src/components/CivicProfileForms/HousingInfo.jsx @@ -1,5 +1,7 @@ -import { Typography } from '@mui/material'; +// React Imports import React from 'react'; +// MUI Imports +import Typography from '@mui/material/Typography'; const HousingInfo = () => Housing Info; diff --git a/src/layouts/Layout.jsx b/src/layouts/Layout.jsx index 12ce87697..c404a46ed 100644 --- a/src/layouts/Layout.jsx +++ b/src/layouts/Layout.jsx @@ -1,26 +1,29 @@ // React Imports import React from 'react'; // Inrupt Library Imports -import { useSession } from '@hooks'; +import { useSession, useNotification } from '@hooks'; // Material UI Imports import Box from '@mui/material/Box'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/system'; // Component Imports import { NavBar } from '../components/NavBar'; import { InactivityMessage } from '../components/Notification'; import Footer from '../components/Footer/Footer'; import NotificationContainer from '../components/Notification/NotificationContainer'; -import useNotification from '../hooks/useNotification'; const Layout = ({ ariaLabel, children }) => { const { session } = useSession(); const { state } = useNotification(); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); return ( diff --git a/src/pages/CivicProfile.jsx b/src/pages/CivicProfile.jsx index e74fcc5d0..4119d8161 100644 --- a/src/pages/CivicProfile.jsx +++ b/src/pages/CivicProfile.jsx @@ -1,6 +1,13 @@ -import { Container, MenuList, MenuItem } from '@mui/material'; +// React Imports import React from 'react'; import { Link, Outlet, useLocation } from 'react-router-dom'; +// MUI Imports +import Container from '@mui/material/Container'; +import MenuItem from '@mui/material/MenuItem'; +import MenuList from '@mui/material/MenuList'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/system'; +// Component Imports import { CIVIC_FORM_LIST } from '@components/CivicProfileForms'; const CivicProfile = () => { @@ -8,10 +15,12 @@ const CivicProfile = () => { localStorage.setItem('restorePath', location.pathname); const currentForm = location.pathname.split('/').pop(); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); return ( - - + +
From 8903a3ea7ef8241d24173d7e442ddbff977c3228 Mon Sep 17 00:00:00 2001 From: Rio Edwards Date: Mon, 6 Nov 2023 20:11:30 -0800 Subject: [PATCH 068/178] Change error message from Invalid username: to Invalid recipient: . --- src/components/Modals/NewMessageModal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Modals/NewMessageModal.jsx b/src/components/Modals/NewMessageModal.jsx index 111db6625..a202ca774 100644 --- a/src/components/Modals/NewMessageModal.jsx +++ b/src/components/Modals/NewMessageModal.jsx @@ -87,7 +87,7 @@ const NewMessageModal = ({ showModal, setShowModal, oldMessage = '' }) => { addNotification('success', `Message successfully sent to ${message.recipientPodUrl}`); } catch (err) { // TODO: Make sure invalid username is the only possible error - addNotification('error', `Invalid username: ${message.recipientPodUrl}`); + addNotification('error', `Invalid recipient: ${message.recipientPodUrl}`); } finally { setOriginalMessage(''); setTimeout(() => { From 111bac99d7dfd9d151bf9a1569b23d9e520cbf9f Mon Sep 17 00:00:00 2001 From: Ka Hung Lee Date: Tue, 7 Nov 2023 01:29:35 -0800 Subject: [PATCH 069/178] Updated PASS routing for navigation; Re-routed contact profiles to /contacts/:webId instead of profiles; Included breadcrumb to Layout.jsx --- src/AppRoutes.jsx | 10 ++-- src/components/CivicProfileForms/FormList.js | 6 +-- .../Contacts/ContactProfileIcon.jsx | 2 +- src/components/NavBar/NavbarLinks.jsx | 2 +- src/layouts/Layout.jsx | 49 ++++++++++++++++++- src/pages/Contacts.jsx | 1 - src/pages/Profile.jsx | 6 ++- 7 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/AppRoutes.jsx b/src/AppRoutes.jsx index 7fb47ae7a..72c407858 100644 --- a/src/AppRoutes.jsx +++ b/src/AppRoutes.jsx @@ -39,13 +39,13 @@ const AppRoutes = () => { } /> }> - } /> - } /> - - } /> + + } /> } /> - }> + } /> + } /> + }> {CIVIC_FORM_LIST.map((formProps) => ( { return ( handleSelectProfile(contact.value)} style={{ display: 'flex', alignItems: 'center' }} diff --git a/src/components/NavBar/NavbarLinks.jsx b/src/components/NavBar/NavbarLinks.jsx index 926f435fa..5c1b7847b 100644 --- a/src/components/NavBar/NavbarLinks.jsx +++ b/src/components/NavBar/NavbarLinks.jsx @@ -24,7 +24,7 @@ const NavbarLinks = () => { // array of current nav links for menus const routesArray = [ { label: 'Contacts', path: '/contacts' }, - { label: 'Civic Profile', path: '/civic_profile/basic_info' } + { label: 'Civic Profile', path: '/civic-profile/basic-info' } ]; return ( diff --git a/src/layouts/Layout.jsx b/src/layouts/Layout.jsx index 12ce87697..b4d809ea9 100644 --- a/src/layouts/Layout.jsx +++ b/src/layouts/Layout.jsx @@ -1,9 +1,13 @@ // React Imports import React from 'react'; +import { Link, useLocation } from 'react-router-dom'; // Inrupt Library Imports import { useSession } from '@hooks'; // Material UI Imports import Box from '@mui/material/Box'; +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import Typography from '@mui/material/Typography'; // Component Imports import { NavBar } from '../components/NavBar'; import { InactivityMessage } from '../components/Notification'; @@ -14,17 +18,60 @@ import useNotification from '../hooks/useNotification'; const Layout = ({ ariaLabel, children }) => { const { session } = useSession(); const { state } = useNotification(); + const location = useLocation(); + + const crumbs = location?.pathname + .split('/') + .slice(1) + .map((crumb) => { + let string = crumb; + string = decodeURIComponent(crumb.replace('-', ' ')); + if (!string.includes('http')) { + string = string + .split(' ') + .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) + .join(' '); + } + return string; + }); return ( + {session.info.isLoggedIn && ( + } + sx={{ margin: '20px 80px', width: '100%' }} + > + {crumbs.map((crumb, index) => + index !== crumbs.length - 1 ? ( + + {crumb} + + ) : ( + {crumb} + ) + )} + + )} {children} {session.info.isLoggedIn && }