{
>
}
>
diff --git a/src/components/Modals/AddContactModal.jsx b/src/components/Modals/AddContactModal.jsx
index e61062924..f940e67cb 100644
--- a/src/components/Modals/AddContactModal.jsx
+++ b/src/components/Modals/AddContactModal.jsx
@@ -129,8 +129,8 @@ const AddContactModal = ({
useEffect(() => {
// sets fields if form is set to Edit
if (isEditing) {
- setUserGivenName(contactToEdit?.givenName);
- setUserFamilyName(contactToEdit?.familyName);
+ setUserGivenName(contactToEdit?.givenName ?? '');
+ setUserFamilyName(contactToEdit?.familyName ?? '');
// parse out webid and usernames and set them to the state values
parsePodUrl();
diff --git a/src/components/Modals/NewMessageModal.jsx b/src/components/Modals/NewMessageModal.jsx
index d461762c7..efd1df100 100644
--- a/src/components/Modals/NewMessageModal.jsx
+++ b/src/components/Modals/NewMessageModal.jsx
@@ -59,10 +59,19 @@ const NewMessageModal = ({ showModal, setShowModal, oldMessage = '', toField = '
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
const contactListOptions =
- data?.map((contact) => ({
- label: `${contact.person} ${contact.podUrl}`,
- id: contact.podUrl
- })) ?? [];
+ data?.map((contact) => {
+ let contactName;
+ if (contact.givenName !== '' || contact.givenName !== null) {
+ contactName = contact.givenName;
+ }
+ if (contact.familyName !== '' || contact.familyName !== null) {
+ contactName += ` ${contact.familyName}`.trim();
+ }
+ return {
+ label: `${contactName} ${contact.podUrl}`.trim(),
+ id: contact.podUrl
+ };
+ }) ?? [];
const recipientName = data?.filter((contact) => message.recipientPodUrl === contact.podUrl)[0];
// Modifies message upon input
const handleChange = (e) => {
@@ -137,7 +146,7 @@ const NewMessageModal = ({ showModal, setShowModal, oldMessage = '', toField = '
data-testid="newMessageTo"
id="recipientPodUrl"
freeSolo
- value={recipientName?.person ?? message.recipientPodUrl}
+ value={recipientName?.podUrl ?? message.recipientPodUrl}
disablePortal
autoSelect
options={contactListOptions}
diff --git a/src/components/Modals/WebcamModal.jsx b/src/components/Modals/WebcamModal.jsx
index 97f8e84ff..78805bf5b 100644
--- a/src/components/Modals/WebcamModal.jsx
+++ b/src/components/Modals/WebcamModal.jsx
@@ -1,14 +1,24 @@
// React Imports
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import Webcam from 'react-webcam';
// Material UI Imports
import Button from '@mui/material/Button';
import Modal from '@mui/material/Modal';
+import CameraswitchIcon from '@mui/icons-material/Cameraswitch';
// TODO: Determine future of this modal
// web camera modal
const WebcamModal = ({ open, onClose, onCapture }) => {
const webcamRef = useRef(null);
+ const [facingDirection, setFacingDirection] = useState('environment');
+
+ const handleCameraDirection = () => {
+ if (facingDirection === 'environment') {
+ setFacingDirection('user');
+ } else {
+ setFacingDirection('environment');
+ }
+ };
const handleCapture = () => {
const imageSrc = webcamRef.current.getScreenshot();
@@ -29,19 +39,27 @@ const WebcamModal = ({ open, onClose, onCapture }) => {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
- height: '100vh'
+ gap: '10px',
+ height: '100dvh'
}}
>
- Webcam
-
+
+
+
+
+
);
diff --git a/src/components/NavBar/NavMenu.jsx b/src/components/NavBar/NavMenu.jsx
index ecba8d2d9..1bc943747 100644
--- a/src/components/NavBar/NavMenu.jsx
+++ b/src/components/NavBar/NavMenu.jsx
@@ -10,6 +10,7 @@ import ContactsIcon from '@mui/icons-material/Contacts';
import Divider from '@mui/material/Divider';
import Drawer from '@mui/material/Drawer';
import EmailIcon from '@mui/icons-material/Email';
+import InventoryIcon from '@mui/icons-material/Inventory';
import LogoutIcon from '@mui/icons-material/Logout';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
@@ -139,6 +140,18 @@ const NavMenu = ({
Civic Profile
+
+ }
+ sx={iconStyling}
+ >
+ Documents
+
+
)}
diff --git a/src/components/NavBar/NavbarLinks.jsx b/src/components/NavBar/NavbarLinks.jsx
index 74c056a11..668da1901 100644
--- a/src/components/NavBar/NavbarLinks.jsx
+++ b/src/components/NavBar/NavbarLinks.jsx
@@ -23,7 +23,8 @@ 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' },
+ { label: 'Documents', path: '/documents' }
];
return (
diff --git a/src/hooks/useContactsList.js b/src/hooks/useContactsList.js
index e9a1925ab..30d020693 100644
--- a/src/hooks/useContactsList.js
+++ b/src/hooks/useContactsList.js
@@ -50,16 +50,11 @@ const useContactsList = () => {
};
const serialize = ({ givenName, familyName, webId }) => {
- let builder = buildThing(createThing({ name: encodeURIComponent(webId) }))
+ const builder = buildThing(createThing({ name: encodeURIComponent(webId) }))
.addUrl(RDF_PREDICATES.identifier, webId)
- .addUrl(RDF_PREDICATES.URL, webId.split('profile')[0]);
-
- if (givenName) {
- builder = builder.addStringNoLocale(RDF_PREDICATES.givenName, givenName);
- }
- if (familyName) {
- builder = builder.addStringNoLocale(RDF_PREDICATES.familyName, familyName);
- }
+ .addUrl(RDF_PREDICATES.URL, webId.split('profile')[0])
+ .addStringNoLocale(RDF_PREDICATES.givenName, givenName ?? '')
+ .addStringNoLocale(RDF_PREDICATES.familyName, familyName ?? '');
return builder.build();
};
diff --git a/src/hooks/useTableSort.js b/src/hooks/useTableSort.js
index 6b9258ffd..158a08448 100644
--- a/src/hooks/useTableSort.js
+++ b/src/hooks/useTableSort.js
@@ -1,22 +1,36 @@
-// hooks/useSortedData.js
-import { useState, useEffect } from 'react';
+import { useEffect, useState } from 'react';
-const useSortedData = (initialData, getFieldValue, initialFieldType) => {
- const [fieldType, setFieldType] = useState(initialFieldType);
+const useTableSort = (initialData, initialSortValue) => {
const [sortedData, setSortedData] = useState([]);
+ const [sortValue, setSortValue] = useState(initialSortValue);
+
+ const sortData = (value) => {
+ const sorted = [...initialData].sort((a, b) => {
+ if (value === 'First Name') {
+ return a.givenName.localeCompare(b.givenName);
+ }
+ if (value === 'Last Name') {
+ return a.familyName.localeCompare(b.familyName);
+ }
+ if (value === 'Web ID') {
+ return a.webId.localeCompare(b.webId);
+ }
+ return 0;
+ });
+ setSortedData(sorted);
+ };
useEffect(() => {
- const sortData = (dataToSort, field) =>
- [...dataToSort].sort((a, b) => {
- const fieldA = getFieldValue(a, field);
- const fieldB = getFieldValue(b, field);
- return fieldA.localeCompare(fieldB);
- });
+ setSortedData([...initialData]);
+ }, [initialData]);
- setSortedData(sortData(initialData, fieldType));
- }, [fieldType, initialData, getFieldValue]);
+ useEffect(() => {
+ if (initialData.length > 0) {
+ sortData(sortValue, initialData);
+ }
+ }, [sortValue, initialData]);
- return { fieldType, setFieldType, sortedData };
+ return { sortedData, sortValue, setSortValue };
};
-export default useSortedData;
+export default useTableSort;
diff --git a/src/layouts/Breadcrumbs.jsx b/src/layouts/Breadcrumbs.jsx
index 853196c46..95fea1b8f 100644
--- a/src/layouts/Breadcrumbs.jsx
+++ b/src/layouts/Breadcrumbs.jsx
@@ -36,7 +36,11 @@ const Breadcrumbs = () => {
}
- sx={{ margin: { xs: '20px', sm: '20px 80px' } }}
+ sx={{
+ margin: { xs: '20px', sm: '20px 80px' },
+ wordWrap: 'break-word',
+ overflowWrap: 'break-word'
+ }}
>
{crumbs.map((crumb, index) =>
index !== crumbs.length - 1 ? (
@@ -47,11 +51,20 @@ const Breadcrumbs = () => {
.replaceAll(' ', '-')
.toLowerCase()}`}
key={crumb}
+ style={{
+ wordBreak: 'break-word'
+ }}
>
{crumb}
) : (
-
+
{crumb}
)
diff --git a/src/layouts/Layout.jsx b/src/layouts/Layout.jsx
index b4775152e..9cf70b3a8 100644
--- a/src/layouts/Layout.jsx
+++ b/src/layouts/Layout.jsx
@@ -1,5 +1,6 @@
// React Imports
import React from 'react';
+import { useLocation } from 'react-router-dom';
// Inrupt Library Imports
import { useSession, useNotification } from '@hooks';
// Material UI Imports
@@ -14,6 +15,7 @@ import Main from './Main';
const Layout = ({ ariaLabel, children }) => {
const { session } = useSession();
const { state } = useNotification();
+ const location = useLocation();
return (
{
>
- {session.info.isLoggedIn && }
+ {session.info.isLoggedIn && location.pathname !== '/profile' && }
{children}
{session.info.isLoggedIn && }
diff --git a/src/pages/Contacts.jsx b/src/pages/Contacts.jsx
index c8c67bf9e..42869a195 100644
--- a/src/pages/Contacts.jsx
+++ b/src/pages/Contacts.jsx
@@ -1,5 +1,5 @@
// React Imports
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
// Material UI Imports
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -37,16 +37,53 @@ const Contacts = () => {
const [processing, setProcessing] = useState(false);
const [selectedContactToDelete, setSelectedContactToDelete] = useState(null);
const [deleteViaEdit, setDeleteViaEdit] = useState(false);
+ const [sortValue, setSortValue] = useState('Sort by:');
+ const [sortedData, setSortedData] = useState([]);
const {
- data,
+ data = [],
isLoading,
isError,
error,
+ refetch,
add: addContact,
delete: deleteContact
} = useContactsList();
const { addNotification } = useNotification();
- const [fieldType, setFieldType] = useState('First Name');
+
+ const sortData = (value) => {
+ const sorted = [...data].sort((a, b) => {
+ if (value === 'Default') {
+ return data;
+ }
+ if (value === 'First Name') {
+ return a.givenName.localeCompare(b.givenName);
+ }
+ if (value === 'Last Name') {
+ return a.familyName.localeCompare(b.familyName);
+ }
+ if (value === 'Web ID') {
+ return a.webId.localeCompare(b.webId);
+ }
+ return 0;
+ });
+ setSortedData(sorted);
+ };
+
+ const handleSortChange = (event) => {
+ const { value } = event.target;
+ setSortValue(value);
+ sortData(value);
+ };
+
+ useEffect(() => {
+ refetch();
+ }, []);
+
+ useEffect(() => {
+ if (data.length > 0) {
+ setSortedData(data);
+ }
+ }, [data]);
const getContactDisplayName = (contact) => {
if (!contact) {
@@ -99,14 +136,13 @@ const Contacts = () => {
>
- {/* TODO: Make sorting options functional */}
{isSmallScreen && (
{data.length > 0 ? (
handleSelectDeleteContact(contact)}
handleDeleteContact={handleDeleteContact}
addContact={addContact}
diff --git a/src/pages/Documents.jsx b/src/pages/Documents.jsx
new file mode 100644
index 000000000..c67d7e969
--- /dev/null
+++ b/src/pages/Documents.jsx
@@ -0,0 +1,154 @@
+import React, { useContext, useEffect, useState } from 'react';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import { useLocation } from 'react-router-dom';
+import { useTheme } from '@emotion/react';
+import { Container, useMediaQuery } from '@mui/material';
+import { ConfirmationModal, SetAclPermissionsModal, UploadDocumentModal } from '@components/Modals';
+import { useNotification, useSession } from '@hooks';
+import { DocumentListContext } from '@contexts';
+import { truncateText } from '@utils';
+import { DocumentTable } from '@components/Documents';
+
+/**
+ * Documents - Component that generates Documents Page for PASS
+ *
+ * @memberof Pages
+ * @name Documents
+ * @returns {React.JSX.Component} The Documents Page
+ */
+const Documents = () => {
+ // Route related states
+ const location = useLocation();
+ if (location.pathname.split('/')[1] === 'contacts') {
+ localStorage.setItem('restorePath', '/contacts');
+ } else {
+ localStorage.setItem('restorePath', '/profile');
+ }
+ const { setContact } = useContext(DocumentListContext);
+ const theme = useTheme();
+ const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
+ const isSmallScreenHeight = useMediaQuery('(max-height: 600px)');
+
+ // Documents related states
+ const { session } = useSession();
+ const [showConfirmationModal, setShowConfirmationModal] = useState(false);
+ const [processing, setProcessing] = useState(false);
+ const { addNotification } = useNotification();
+ const { removeDocument } = useContext(DocumentListContext);
+ const [selectedDocToDelete, setSelectedDocToDelete] = useState(null);
+ const [showAddDocModal, setShowAddDocModal] = useState(false);
+ const [showAclPermissionModal, setShowAclPermissionModal] = useState(false);
+ const [dataset, setDataset] = useState({
+ modalType: '',
+ docName: '',
+ docType: ''
+ });
+
+ useEffect(() => {
+ setContact({
+ familyName: '',
+ givenName: '',
+ podUrl: session.info.webId?.split('profile')[0],
+ thingId: session.info.webId,
+ webId: session.info.webId
+ });
+ }, [session]);
+
+ const handleSelectDeleteDoc = (document) => {
+ setSelectedDocToDelete(document);
+ setShowConfirmationModal(true);
+ };
+
+ // Function for deleting documents
+ const handleDeleteDoc = async () => {
+ setProcessing(true);
+ try {
+ await removeDocument(selectedDocToDelete.name);
+ addNotification('success', `${selectedDocToDelete?.name} deleted from the pod.`);
+ } catch (e) {
+ addNotification('error', `Document deletion failed. Reason: ${e.message}`);
+ } finally {
+ setShowConfirmationModal(false);
+ setProcessing(false);
+ }
+ };
+
+ const handleAclPermissionsModal = (modalType, docName = '', docType = '') => {
+ setDataset({
+ modalType,
+ docName,
+ docType
+ });
+ setShowAclPermissionModal(true);
+ };
+
+ const truncatedText = selectedDocToDelete?.name ? truncateText(selectedDocToDelete.name) : '';
+
+ return (
+
+
+
+
+
+
+
+
+ handleSelectDeleteDoc(document)}
+ />
+
+ );
+};
+
+export default Documents;
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx
index 1635d1332..d30e4291a 100644
--- a/src/pages/Home.jsx
+++ b/src/pages/Home.jsx
@@ -27,7 +27,8 @@ import { HomeSection, KeyFeatures } from '@components/Home';
const Home = () => {
const theme = useTheme();
const isReallySmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
- const iconSize = isReallySmallScreen ? 'medium' : 'large';
+ const isSmallScreenHeight = useMediaQuery('(max-height: 600px)');
+ const iconSize = isReallySmallScreen || isSmallScreenHeight ? 'medium' : 'large';
const aboutRef = useRef(null);
const handleClick = () => aboutRef.current.scrollIntoView({ behavior: 'smooth' });
@@ -45,64 +46,70 @@ const Home = () => {
);
const subheading = 'Personal Access System for Services';
- const logoSection = isReallySmallScreen ? (
-
-
-
- {heading}
-
-
-
- {subheading}
-
-
-
- ) : (
-
-
+ const logoSection =
+ isReallySmallScreen || isSmallScreenHeight ? (
+
-
{heading}
+
+
+ {subheading}
+
-
- {subheading}
-
-
-
- );
+
+ ) : (
+
+
+
+
+
+ {heading}
+
+
+
+ {subheading}
+
+
+
+ );
return (
@@ -135,7 +142,7 @@ const Home = () => {