diff --git a/.eslintrc.js b/.eslintrc.js
index 193c097fa..ecc594277 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -38,7 +38,8 @@ module.exports = {
['@contexts', './src/contexts'],
['@pages', './src/pages'],
['@utils', './src/utils'],
- ['@components', './src/components']
+ ['@components', './src/components'],
+ ['@constants', './src/constants']
]
}
}
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 054cd24ea..342ef53cc 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,16 +1,9 @@
----
-name: Pull Request
-about: Create a pull request to merge your code into the PASS codebase
-title: ''
-labels: ''
-assignees: ''
-
----
-
## CONCISE description of PR (PR Title)
---
## This PR:
+Resolves #(put issue number here to link this PR to it)
+
**1.**
**2.** If needed, delete if not
**3.** If needed, delete if not
diff --git a/README.md b/README.md
index 0033b2d2e..5a661ef7d 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
-
# PASS - Personal Access System for Services
-
+
[![License](https://img.shields.io/github/license/codeforpdx/PASS)](https://github.com/codeforpdx/PASS/blob/Master/LICENSE)
[![Discord](https://img.shields.io/discord/1068260532806766733)](https://discord.gg/Ts923xaUYV)
@@ -23,29 +22,31 @@ PASS is currently in Development heading towards [Minimum Viable Product](./docs
1. [Setup Instructions](#1-setup-instructions)
2. [Project Overview](#2-project-overview)
- - [Terminology](#terminology)
- - [Features and usage](#features-and-usage)
+ - [Terminology](#terminology)
+ - [Features and usage](#features-and-usage)
3. [Contribution Guidelines](#3-contribution-guidelines)
4. [Contributors](#4-contributors)
5. [Tech Stack and Additional Resources](#5-tech-stack)
6. [Code of Conduct](#6-code-of-conduct)
---
+
## 1. Setup Instructions
- ### Prerequisites
- Currently we require Node version 16 or higher and NPM for our package manager. Most places recommend using a node version manager to install node and npm. To proceed using NVM perform the following..
+ Currently we require Node version 16 or higher and NPM for our package manager. Most places recommend using a node version manager to install node and npm. To proceed using NVM perform the following..
+
+1. Download NVM for your system.
-1. Download NVM for your system.
- - For Mac, Linux, and other POSIX users: https://github.com/nvm-sh/nvm
- - For Windows users: https://github.com/coreybutler/nvm-windows
-
-2. Install node version 16:
+- For Mac, Linux, and other POSIX users: https://github.com/nvm-sh/nvm
+- For Windows users: https://github.com/coreybutler/nvm-windows
+
+2. Install node version 16:
```
nvm install 16
```
-3. Use that node version:
+3. Use that node version:
```
nvm use 16
```
@@ -57,32 +58,31 @@ PASS is currently in Development heading towards [Minimum Viable Product](./docs
If either of those commands error, node has not been installed correctly.
- ### Clone and Install Dependencies
-
-1. Clone the git repository:
+
+1. Clone the git repository:
```
git clone https://github.com/codeforpdx/PASS.git
```
-
2. Install project dependencies:
```
npm install
```
-3. Run the project:
+3. Run the project:
```
npm run dev
```
4. PASS should launch at `http://localhost:5173`. You can now visit that url, and sign into a pod hosted at the OIDC provider of your choice.
- ### Setting up a Development Pod Server
-
- PASS is able to connect to any solid-spec compliant pod server. However, for testing, it's recommended that you run a server locally. PASS provides tools to make this easy to do.
+ PASS is able to connect to any solid-spec compliant pod server. However, for testing, it's recommended that you run a server locally. PASS provides tools to make this easy to do.
1. Clone and install dependencies. [See previous section](#clone-and-install-dependencies)
2. In the project's root directory, copy the `env.template` file into a `.env` file. In bash you can use this command:
- ```bash
- cp env.template .env
- ```
+
+ ```bash
+ cp env.template .env
+ ```
3. Run `npm run podserver` to launch the pod server. The server will begin listening on `http://localhost:3000`, and will create a folder in the PASS project folder called `local_temp_server_files`. You can find all server and pod files there.
@@ -102,7 +102,7 @@ Further information can be found in [CONTRIBUTING.md](./docs/CONTRIBUTING.md) &
In Portland, housing insecure individuals struggle to maintain documents often required to receive government and/or non-profit services. With PASS, we are building out an application to enable housing insecure individuals to store their personal documents in decentralized data stores, called Pods. PASS will also provide a platform for Organizations to assist with providing and processing documents required for housing assistance. Using [Solid Data Pods](https://solidproject.org/) individuals will have control over which organizations and applications can access their data. Verified organizations will be able to use PASS to request data from an individual and/or add documents (such as references or invoices) to an individuals pod to help process housing assistance applications.
-### Terminology
+### Terminology
- Individual/Client - housing insecure person using services to interact with organizations.
- Organization - housing agencies, landlords, government agencies that will be requesting information from individuals.
@@ -128,17 +128,17 @@ In Portland, housing insecure individuals struggle to maintain documents often r
- User authorizes applications and other users to read/write/control data in pod
- User data remains in their pod (which is associated to WebID)
- Full product road map [here](docs/ROADMAP.md)
-
+
**[⬆️ Back to Top](#pass---personal-access-system-for-services)**
---
## 3. Contribution Guidelines
-- Start by checking out the detailed on-boarding [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
-- Join our [Discord](https://discord.gg/Ts923xaUYV) and self assign roles as you see fit. [![Discord](https://img.shields.io/discord/1068260532806766733)](https://discord.gg/Ts923xaUYV)
-- Request git-hub access on Discord in the [github-access-request](https://discord.com/channels/1068260532806766733/1078124139983945858) channel of the General category.
-
+- Start by checking out the detailed on-boarding [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
+- Join our [Discord](https://discord.gg/Ts923xaUYV) and self assign roles as you see fit. [![Discord](https://img.shields.io/discord/1068260532806766733)](https://discord.gg/Ts923xaUYV)
+- Request git-hub access on Discord in the [github-access-request](https://discord.com/channels/1068260532806766733/1078124139983945858) channel of the General category.
+
**[⬆️ Back to Top](#pass---personal-access-system-for-services)**
---
@@ -171,5 +171,4 @@ In Portland, housing insecure individuals struggle to maintain documents often r
- [Code for PDX code of conduct](https://github.com/codeforpdx/codeofconduct/blob/master/README.md)
-
**[⬆️ Back to Top](#pass---personal-access-system-for-services)**
diff --git a/__mocks__/@inrupt/solid-client.js b/__mocks__/@inrupt/solid-client.js
index 3cb482155..42454bca7 100644
--- a/__mocks__/@inrupt/solid-client.js
+++ b/__mocks__/@inrupt/solid-client.js
@@ -23,3 +23,4 @@ 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)))
diff --git a/clearDocs.js b/clearDocs.js
index 3e761e9a7..7f569c50e 100644
--- a/clearDocs.js
+++ b/clearDocs.js
@@ -25,8 +25,4 @@ allMds.forEach((mdFile) => {
* the temp directory as the docs directory,
* now containing only the original .md files */
fs.rmSync(docsDir, { recursive: true, force: true });
-fs.renameSync(tempDir, docsDir, (err) => {
- if (err) {
- throw err;
- }
-});
+fs.renameSync(tempDir, docsDir);
diff --git a/docs/README.md b/docs/README.md
index 7235957ce..de988120a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -100,7 +100,7 @@ To do so, you will need to edit the root `.env` file.
2. Start the SOLID server in a shell window
```shell
-npm run dev:pod
+npm run podserver
```
---
@@ -120,17 +120,15 @@ PASS is intended to work with any server that implements the SOLID protocol. How
You can change the port the server is listening on by using the `--port` or `-p` flags:
```shell
-npm run dev:pod -- -p 1234
+npm run podserver -- -p 1234
```
---
-By default, community solid server stores all data in memory (i.e. it does not save data when it's shut down). To have it store data to your filesystem, you can use the command
+Note: The `npm run podserver` command will launch a server that stores documents on your local file system. If you don't want to store documents, and want all server data to be deleted on shutdown, you can run
-```shell
-npm run dev:pod:stored
-```
-
-This will store all server files in the folder `PASS/local_temp_server_files`. This is a local testing directory, and the files within it should not be added to source control.
+ ```shell
+ npm run podserver:temp
+ ```
You can find more information on its configuration on the project's [github](https://github.com/CommunitySolidServer/CommunitySolidServer#configuring-the-server).
diff --git a/env.template b/env.template
index ae6131e8f..fc645d5ac 100644
--- a/env.template
+++ b/env.template
@@ -1 +1,2 @@
VITE_SOLID_IDENTITY_PROVIDER="http://localhost:3000/"
+VITE_SOLID_POD_SERVER="http://localhost:3000/"
diff --git a/index.html b/index.html
index da82300df..703548a2a 100644
--- a/index.html
+++ b/index.html
@@ -4,9 +4,8 @@
Getting Started with PASS
-
+
-
diff --git a/package-lock.json b/package-lock.json
index 912ce7ec5..30857b501 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pass",
- "version": "0.7.0",
+ "version": "0.9.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pass",
- "version": "0.7.0",
+ "version": "0.9.0",
"license": "MIT",
"dependencies": {
"@emotion/react": "^11.10.8",
@@ -20,6 +20,7 @@
"@mui/styled-engine-sc": "^5.12.0",
"@mui/system": "^5.12.1",
"@mui/x-date-pickers": "^6.5.0",
+ "@tanstack/react-query": "^4.32.0",
"@zxing/browser": "^0.1.3",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.8",
@@ -3759,6 +3760,41 @@
"node": ">=10"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "4.32.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.32.0.tgz",
+ "integrity": "sha512-ei4IYwL2kmlKSlCw9WgvV7PpXi0MiswVwfQRxawhJA690zWO3dU49igaQ/UMTl+Jy9jj9dK5IKAYvbX7kUvviQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "4.32.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.32.0.tgz",
+ "integrity": "sha512-B8WUMcByYAH9500ENejDCATOmEZhqjtS9wsfiQ3BNa+s+yAynY8SESI8WWHhSqUmjd0pmCSFRP6BOUGSda3QXA==",
+ "dependencies": {
+ "@tanstack/query-core": "4.32.0",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@testing-library/dom": {
"version": "9.3.0",
"dev": true,
@@ -13454,6 +13490,14 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"dev": true,
@@ -16915,6 +16959,20 @@
"defer-to-connect": "^2.0.0"
}
},
+ "@tanstack/query-core": {
+ "version": "4.32.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.32.0.tgz",
+ "integrity": "sha512-ei4IYwL2kmlKSlCw9WgvV7PpXi0MiswVwfQRxawhJA690zWO3dU49igaQ/UMTl+Jy9jj9dK5IKAYvbX7kUvviQ=="
+ },
+ "@tanstack/react-query": {
+ "version": "4.32.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.32.0.tgz",
+ "integrity": "sha512-B8WUMcByYAH9500ENejDCATOmEZhqjtS9wsfiQ3BNa+s+yAynY8SESI8WWHhSqUmjd0pmCSFRP6BOUGSda3QXA==",
+ "requires": {
+ "@tanstack/query-core": "4.32.0",
+ "use-sync-external-store": "^1.2.0"
+ }
+ },
"@testing-library/dom": {
"version": "9.3.0",
"dev": true,
@@ -23176,6 +23234,12 @@
"requires-port": "^1.0.0"
}
},
+ "use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "requires": {}
+ },
"util-deprecate": {
"version": "1.0.2",
"dev": true
diff --git a/package.json b/package.json
index 977cb1e96..45a8fe3a7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "pass",
"homepage": ".",
- "version": "0.7.0",
+ "version": "0.9.0",
"description": "",
"scripts": {
"dev": "vite",
@@ -34,6 +34,7 @@
"@mui/styled-engine-sc": "^5.12.0",
"@mui/system": "^5.12.1",
"@mui/x-date-pickers": "^6.5.0",
+ "@tanstack/react-query": "^4.32.0",
"@zxing/browser": "^0.1.3",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.8",
diff --git a/public/pass-logo.png b/public/pass-logo.png
new file mode 100644
index 000000000..171598dd4
Binary files /dev/null and b/public/pass-logo.png differ
diff --git a/src/App.jsx b/src/App.jsx
index 431cd4d88..cc0899e64 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,12 +1,14 @@
// React Imports
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { SessionProvider } from '@contexts';
// Material UI Imports
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
// Context Imports
-import { SessionProvider } from '@contexts';
import UserDataContextProvider from './contexts/UserDataContext';
+import { NotificationContextProvider } from './contexts/NotificationContext';
// Theme Imports
import theme from './theme';
// Route Imports
@@ -21,19 +23,25 @@ import Layout from './layouts/Layout';
* @typedef {import("./typedefs").messageListObject} messageListObject
*/
+const queryClient = new QueryClient();
+
const App = () => (
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
export default App;
diff --git a/src/AppRoutes.jsx b/src/AppRoutes.jsx
index e41675352..a8631536a 100644
--- a/src/AppRoutes.jsx
+++ b/src/AppRoutes.jsx
@@ -3,8 +3,9 @@ import React from 'react';
import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
// Inrupt Imports
import { useSession } from '@hooks';
+import { SessionProvider } from '@contexts';
// Page Imports
-import { Home, Clients, Messages, Profile } from './pages';
+import { Home, Contacts, Messages, Profile, Signup } from './pages';
const ProtectedRoute = ({ isLoggedIn, children }) =>
isLoggedIn ? children ?? : ;
@@ -19,7 +20,7 @@ const AppRoutes = () => {
const { session } = useSession();
const restorePath = localStorage.getItem('restorePath');
const loggedIn = session.info.isLoggedIn;
- const path = loggedIn ? restorePath || '/clients' : '/';
+ const path = loggedIn ? restorePath || '/contacts' : '/';
return (
@@ -28,8 +29,16 @@ const AppRoutes = () => {
path="/"
element={session.info.isLoggedIn ? : }
/>
+
+
+
+ }
+ />
}>
- } />
+ } />
} />
} />
diff --git a/src/assets/logo.png b/src/assets/logo.png
deleted file mode 100644
index a8884131c..000000000
Binary files a/src/assets/logo.png and /dev/null differ
diff --git a/src/components/Clients/ClientList.jsx b/src/components/Clients/ClientList.jsx
deleted file mode 100644
index f90255980..000000000
--- a/src/components/Clients/ClientList.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-// React Imports
-import React, { useContext, useState } from 'react';
-// Context Imports
-import { UserListContext } from '@contexts';
-// Component Imports
-import ClientListTable from './ClientListTable';
-import DeleteClientModal from '../Modals/DeleteClientModal';
-import { EmptyListNotification, LoadingAnimation } from '../Notification';
-
-/**
- * ClientList Component - Component that generates ClientList section for PASS
- * which interfaces with Solid Pod to fetch user list
- *
- * @memberof Clients
- * @name ClientList
- * @returns {React.JSX.Element} The ClientList Component
- */
-const ClientList = () => {
- const { userListObject } = useContext(UserListContext);
- const { loadingUsers } = useContext(UserListContext);
-
- // state for DeleteClientModal component
- const [showDeleteClientModal, setShowDeleteClientModal] = useState(false);
-
- // state for selected client to delete
- const [selectedClientToDelete, setSelectedClientToDelete] = useState(null);
-
- const determineClientListTable = userListObject.userList?.length ? (
- // render if clients
-
- ) : (
- // render if no clients
-
- );
-
- // MAIN RETURN OF COMPONENT
- return loadingUsers ? (
-
- ) : (
- <>
- {determineClientListTable}
- {/* modal/popup renders when showDeleteClientModal state is true */}
-
- >
- );
-};
-
-export default ClientList;
diff --git a/src/components/Clients/index.js b/src/components/Clients/index.js
deleted file mode 100644
index 27cf0cac7..000000000
--- a/src/components/Clients/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import ClientList from './ClientList';
-import ClientListTable from './ClientListTable';
-import ClientListTableRow from './ClientListTableRow';
-
-/**
- * Components and functions related to Clients functionality within project PASS
- *
- * @namespace Clients
- */
-
-export { ClientList, ClientListTable, ClientListTableRow };
diff --git a/src/components/Clients/ClientListTable.jsx b/src/components/Contacts/ContactListTable.jsx
similarity index 50%
rename from src/components/Clients/ClientListTable.jsx
rename to src/components/Contacts/ContactListTable.jsx
index a1b2cb3ee..3d8a8d3cc 100644
--- a/src/components/Clients/ClientListTable.jsx
+++ b/src/components/Contacts/ContactListTable.jsx
@@ -1,5 +1,5 @@
// React Imports
-import React, { useContext } from 'react';
+import React from 'react';
// Material UI Imports
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
@@ -7,30 +7,32 @@ import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
-// Context Imports
-import { UserListContext } from '@contexts';
// Component Imports
-import ClientListTableRow from './ClientListTableRow';
+import ContactListTableRow from './ContactListTableRow';
import { StyledTableCell } from '../Table/TableStyles';
// ===== MAKE CHANGES HERE FOR TABLE HEADER / COLUMN TITLES =====
-const columnTitlesArray = ['Client', 'Pin', 'Delete'];
+const columnTitlesArray = ['Contact', 'Pin', 'Delete'];
/**
- * @typedef {import("../../typedefs.js").clientListTableProps} clientListTableProps
+ * contactListTableProps is an object that stores the props for the
+ * ContactListTable component
+ *
+ * @typedef {object} contactListTableProps
+ * @property {Array} contacts - this list of contacts to display
+ * @property {Function} deleteContact - method to delete contact
+ * @memberof typedefs
*/
/**
- * ClientListTable Component - Component that generates table of clients from data within ClientList
+ * ContactListTable Component - Component that generates table of contacts from data within ContactList
*
- * @memberof Clients
- * @name ClientListTable
- * @param {clientListTableProps} Props - Props for ClientListTableRow
- * @returns {React.JSX.Element} The ClientListTableRow Component
+ * @memberof Contacts
+ * @name ContactListTable
+ * @param {contactListTableProps} Props - Props for ContactListTable
+ * @returns {React.JSX.Element} The ContactListTable Component
*/
-const ClientListTable = ({ setSelectedClientToDelete, setShowDeleteClientModal }) => {
- const { userListObject } = useContext(UserListContext);
-
+const ContactListTable = ({ contacts, deleteContact }) => {
const comparePerson = (a, b) => {
if (a.familyName[0].toLowerCase() < b.familyName[0].toLowerCase()) {
return -1;
@@ -40,14 +42,13 @@ const ClientListTable = ({ setSelectedClientToDelete, setShowDeleteClientModal }
}
return 0;
};
+ const contactsCopy = [...contacts];
- const userListCopy = [...userListObject.userList];
-
- const sortedUserList = userListCopy.sort(comparePerson);
+ const sortedContacts = contactsCopy.sort(comparePerson);
return (
-
+
{columnTitlesArray.map((columnTitle) => (
@@ -58,12 +59,11 @@ const ClientListTable = ({ setSelectedClientToDelete, setShowDeleteClientModal }
- {sortedUserList?.map((client) => (
- (
+
))}
@@ -72,4 +72,4 @@ const ClientListTable = ({ setSelectedClientToDelete, setShowDeleteClientModal }
);
};
-export default ClientListTable;
+export default ContactListTable;
diff --git a/src/components/Clients/ClientListTableRow.jsx b/src/components/Contacts/ContactListTableRow.jsx
similarity index 50%
rename from src/components/Clients/ClientListTableRow.jsx
rename to src/components/Contacts/ContactListTableRow.jsx
index c05a0d1c4..fce5fff74 100644
--- a/src/components/Clients/ClientListTableRow.jsx
+++ b/src/components/Contacts/ContactListTableRow.jsx
@@ -1,6 +1,8 @@
// React Imports
import React, { useContext, useState } from 'react';
-import { Link } from 'react-router-dom';
+import { Link, useNavigate } from 'react-router-dom';
+// Inrupt Imports
+import { getWebIdDataset } from '@inrupt/solid-client';
// Material UI Imports
import { useTheme } from '@mui/material/styles';
import Button from '@mui/material/Button';
@@ -10,52 +12,68 @@ import PushPinIcon from '@mui/icons-material/PushPin';
import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined';
// Context Imports
import { DocumentListContext } from '@contexts';
+// Custom Hook Imports
+import useNotification from '../../hooks/useNotification';
// Component Imports
import { StyledTableCell, StyledTableRow } from '../Table/TableStyles';
/**
- * @typedef {import("../../typedefs.js").clientListTableRowProps} clientListTableRowProps
+ * 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
*/
/**
- * ClientListTableRow Component - Component that generates the individual table
- * rows of clients from data within ClientList
+ * ContactListTableRow Component - Component that generates the individual table
+ * rows of contacts from data within ContactList
*
- * @memberof Clients
- * @name ClientListTableRow
- * @param {clientListTableRowProps} Props - Props for ClientListTableRow
- * @returns {React.JSX.Element} The ClientListTableRow Component
+ * @memberof Contacts
+ * @name ContactListTableRow
+ * @param {contactListTableRowProps} Props - Props for ContactListTableRow
+ * @returns {React.JSX.Element} The ContactListTableRow Component
*/
-const ClientListTableRow = ({ client, setShowDeleteClientModal, setSelectedClientToDelete }) => {
+const ContactListTableRow = ({ contact, deleteContact }) => {
const theme = useTheme();
const [pinned, setPinned] = useState(false);
- const { setClient } = useContext(DocumentListContext);
+ 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 client to top of table
+ // 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 deleting a client from client list
- const handleSelectClientToDelete = () => {
- setSelectedClientToDelete(client);
- setShowDeleteClientModal(true);
+ // 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 (
- setClient(client)}>
- {client.person}
+ handleSelectProfile(contact)}>
+ {contact.person}
@@ -68,7 +86,7 @@ const ClientListTableRow = ({ client, setShowDeleteClientModal, setSelectedClien
-
+ deleteContact(contact)}>
@@ -76,4 +94,4 @@ const ClientListTableRow = ({ client, setShowDeleteClientModal, setSelectedClien
);
};
-export default ClientListTableRow;
+export default ContactListTableRow;
diff --git a/src/components/Contacts/index.js b/src/components/Contacts/index.js
new file mode 100644
index 000000000..41ea89b2a
--- /dev/null
+++ b/src/components/Contacts/index.js
@@ -0,0 +1,10 @@
+import ContactListTable from './ContactListTable';
+import ContactListTableRow from './ContactListTableRow';
+
+/**
+ * Components and functions related to Contacts functionality within project PASS
+ *
+ * @namespace Contacts
+ */
+
+export { ContactListTable, ContactListTableRow };
diff --git a/src/components/Documents/DocumentTable.jsx b/src/components/Documents/DocumentTable.jsx
index 7200c0d99..0d3fa83b9 100644
--- a/src/components/Documents/DocumentTable.jsx
+++ b/src/components/Documents/DocumentTable.jsx
@@ -15,15 +15,20 @@ import { StyledTableCell } from '../Table/TableStyles';
import DocumentTableRow from './DocumentTableRow';
import { EmptyListNotification, LoadingAnimation } from '../Notification';
+/**
+ * @typedef {import("../../typedefs.js").documentTableProps} documentTableProps
+ */
+
/**
* DocumentTable Component - The Document Table that shows the list of documents
* stored on Solid
*
* @memberof Documents
* @name DocumentTable
+ * @param {documentTableProps} Props - Props for DocumentTable component
* @returns {React.JSX.Element} The DocumentTable component
*/
-const DocumentTable = () => {
+const DocumentTable = ({ handleAclPermissionsModal }) => {
const { documentListObject, loadingDocuments } = useContext(DocumentListContext);
const columnTitlesArray = [
'Name',
@@ -32,6 +37,7 @@ const DocumentTable = () => {
'Upload Date',
'Expiration Date',
'Preview File',
+ 'Sharing',
'Delete'
];
@@ -51,7 +57,11 @@ const DocumentTable = () => {
{documentListObject?.docList.map((document) => (
-
+
))}
diff --git a/src/components/Documents/DocumentTableRow.jsx b/src/components/Documents/DocumentTableRow.jsx
index f5a633b9c..42f199707 100644
--- a/src/components/Documents/DocumentTableRow.jsx
+++ b/src/components/Documents/DocumentTableRow.jsx
@@ -1,11 +1,13 @@
// React Imports
import React, { useContext } from 'react';
-// Custon Hook Imports
+// Custom Hook Imports
import { useSession } from '@hooks';
+import useNotification from '@hooks/useNotification';
// Material UI Imports
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
import FileOpenIcon from '@mui/icons-material/FileOpen';
import IconButton from '@mui/material/IconButton';
+import ShareIcon from '@mui/icons-material/Share';
// Utility Imports
import { getBlobFromSolid } from '@utils';
// Context Imports
@@ -27,9 +29,10 @@ import DOC_TYPES from '../../constants/doc_types';
* @param {documentTableRowProps} Props - Props for DocumentTableRow
* @returns {React.JSX.Element} The DocumentTableRow component
*/
-const DocumentTableRow = ({ document }) => {
+const DocumentTableRow = ({ document, handleAclPermissionsModal }) => {
const { session } = useSession();
const { removeDocument } = useContext(DocumentListContext);
+ const { addNotification } = useNotification();
const { name, type, description, fileUrl, uploadDate, endDate } = document;
@@ -48,6 +51,7 @@ const DocumentTableRow = ({ document }) => {
return;
}
await removeDocument(document.name);
+ addNotification('success', `${document.name} deleted from the pod.`);
};
return (
@@ -64,6 +68,11 @@ const DocumentTableRow = ({ document }) => {
+
+ handleAclPermissionsModal('document', name, type)}>
+
+
+
handleDeleteDocument()}>
diff --git a/src/components/Form/FormSection.jsx b/src/components/Form/FormSection.jsx
index 5a499ec8c..7136a5331 100644
--- a/src/components/Form/FormSection.jsx
+++ b/src/components/Form/FormSection.jsx
@@ -4,7 +4,6 @@ import React from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
// Component Imports
-import { StatusNotification } from '../Notification';
/**
* @typedef {import('../../typedefs').formSectionProps} formSectionProps
@@ -20,7 +19,7 @@ import { StatusNotification } from '../Notification';
* {@link formSectionProps})
*/
-const FormSection = ({ title, state, statusType, defaultMessage, file = null, children }) => (
+const FormSection = ({ title, children }) => (
-
+
{title}
{children}
-
);
diff --git a/src/components/Form/SetAclPermissionForm.jsx b/src/components/Form/SetAclPermissionForm.jsx
deleted file mode 100644
index 4d4e6eaf5..000000000
--- a/src/components/Form/SetAclPermissionForm.jsx
+++ /dev/null
@@ -1,162 +0,0 @@
-// React Imports
-import React, { useContext, useState } from 'react';
-// Custom Hook Imports
-import { useSession, useStatusNotification } from '@hooks';
-// Material UI Imports
-import Box from '@mui/material/Box';
-import Button from '@mui/material/Button';
-import FormControl from '@mui/material/FormControl';
-import InputLabel from '@mui/material/InputLabel';
-import MenuItem from '@mui/material/MenuItem';
-import Select from '@mui/material/Select';
-import TextField from '@mui/material/TextField';
-import Typography from '@mui/material/Typography';
-// Utility Imports
-import { runNotification, setDocAclPermission } from '@utils';
-// Context Imports
-import { SignedInUserContext } from '@contexts';
-// Component Imports
-import DocumentSelection from './DocumentSelection';
-import FormSection from './FormSection';
-
-/**
- * SetAclPermissionForm Component - Component that generates the form for setting
- * document ACL permissions to another user's Solid Pod via Solid Session
- *
- * @memberof Forms
- * @name SetAclPermissionForm
- * @returns {React.JSX.Element} The SetAclPermissionForm Component
- */
-const SetAclPermissionForm = () => {
- const { session } = useSession();
- const { state, dispatch } = useStatusNotification();
- const { podUrl } = useContext(SignedInUserContext);
- const [docType, setDocType] = useState('');
- const [permissionState, setPermissionState] = useState({
- podUrlToSetPermissionsTo: '',
- permissionType: ''
- });
-
- const handleDocType = (event) => {
- setDocType(event.target.value);
- };
-
- const clearInputFields = () => {
- dispatch({ type: 'CLEAR_PROCESSING' });
- };
-
- // Event handler for setting ACL permissions to file container on Solid
- const handleAclPermission = async (event) => {
- event.preventDefault();
- dispatch({ type: 'SET_PROCESSING' });
- const permissions = event.target.setAclPerms.value
- ? { read: event.target.setAclPerms.value === 'Give' }
- : undefined;
- const webIdToSetPermsTo = `${permissionState.podUrlToSetPermissionsTo}profile/card#me`;
-
- try {
- await setDocAclPermission(session, docType, permissions, podUrl, webIdToSetPermsTo);
-
- runNotification(
- `${permissions.read ? 'Give' : 'Revoke'} permission to ${
- permissionState.podUrlToSetPermissionsTo
- } for ${docType}.`,
- 5,
- state,
- dispatch
- );
- } catch (error) {
- runNotification('Failed to set permissions. Reason: File not found.', 5, state, dispatch);
- } finally {
- setTimeout(() => {
- clearInputFields();
- }, 3000);
- }
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default SetAclPermissionForm;
diff --git a/src/components/Form/SetAclPermsDocContainerForm.jsx b/src/components/Form/SetAclPermsDocContainerForm.jsx
deleted file mode 100644
index db90ad160..000000000
--- a/src/components/Form/SetAclPermsDocContainerForm.jsx
+++ /dev/null
@@ -1,138 +0,0 @@
-// React Imports
-import React, { useContext, useState } from 'react';
-// Custom Hook Imports
-import { useSession, useStatusNotification } from '@hooks';
-// Material UI Imports
-import Box from '@mui/material/Box';
-import Button from '@mui/material/Button';
-import FormControl from '@mui/material/FormControl';
-import InputLabel from '@mui/material/InputLabel';
-import MenuItem from '@mui/material/MenuItem';
-import Select from '@mui/material/Select';
-import TextField from '@mui/material/TextField';
-// Utility Imports
-import { runNotification, setDocContainerAclPermission } from '@utils';
-// Context Imports
-import { SignedInUserContext } from '@contexts';
-// Component Imports
-import FormSection from './FormSection';
-
-/**
- * SetAclPermsDocContainerForm Component - Component that generates the form for
- * setting ACL permissions to another user's Documents container in their Solid
- * Pod via Solid Session
- *
- * @memberof Forms
- * @name SetAclPermsDocContainerForm
- * @returns {React.JSX.Element} The SetAclPermsDocContainerForm Component
- */
-const SetAclPermsDocContainerForm = () => {
- const { session } = useSession();
- const { state, dispatch } = useStatusNotification();
- const { podUrl } = useContext(SignedInUserContext);
- const [permissionState, setPermissionState] = useState({
- podUrlToSetPermissionsTo: '',
- permissionType: ''
- });
-
- const clearInputFields = () => {
- dispatch({ type: 'CLEAR_PROCESSING' });
- };
-
- // Event handler for setting ACL permissions to file container on Solid
- const handleAclPermission = async (event) => {
- event.preventDefault();
- dispatch({ type: 'SET_PROCESSING' });
- const permissions = event.target.setAclPerms.value
- ? {
- read: event.target.setAclPerms.value === 'Give',
- append: event.target.setAclPerms.value === 'Give'
- }
- : undefined;
- const webIdToSetPermsTo = `${permissionState.podUrlToSetPermissionsTo}profile/card#me`;
-
- try {
- await setDocContainerAclPermission(session, permissions, podUrl, webIdToSetPermsTo);
-
- runNotification(
- `${permissions.read ? 'Give' : 'Revoke'} permission to ${
- permissionState.podUrlToSetPermissionsTo
- } for Documents Container.`,
- 5,
- state,
- dispatch
- );
- } catch (error) {
- runNotification('Failed to set permissions. Reason: File not found.', 5, state, dispatch);
- } finally {
- setTimeout(() => {
- clearInputFields();
- }, 3000);
- }
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default SetAclPermsDocContainerForm;
diff --git a/src/components/Form/index.js b/src/components/Form/index.js
index 666516d88..87bcd771e 100644
--- a/src/components/Form/index.js
+++ b/src/components/Form/index.js
@@ -1,7 +1,5 @@
import DocumentSelection from './DocumentSelection';
import FormSection from './FormSection';
-import SetAclPermissionForm from './SetAclPermissionForm';
-import SetAclPermsDocContainerForm from './SetAclPermsDocContainerForm';
/**
* Components and functions related to forms within project PASS and used for
* the Documents Page
@@ -9,4 +7,4 @@ import SetAclPermsDocContainerForm from './SetAclPermsDocContainerForm';
* @namespace Forms
*/
-export { DocumentSelection, FormSection, SetAclPermissionForm, SetAclPermsDocContainerForm };
+export { DocumentSelection, FormSection };
diff --git a/src/components/Messages/MessageFolder.jsx b/src/components/Messages/MessageFolder.jsx
index 42adf5afe..ada05c89c 100644
--- a/src/components/Messages/MessageFolder.jsx
+++ b/src/components/Messages/MessageFolder.jsx
@@ -6,6 +6,7 @@ import Box from '@mui/material/Box';
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';
// Component Imports
import MessagePreview from './MessagePreview';
import { PaginationContainer } from './MessageStyles';
@@ -59,27 +60,33 @@ const MessageFolder = ({ folderType, handleRefresh, loadMessages, messageList })
-
+
handleRefresh(folderType)}
type="button"
- sx={{ width: '100px' }}
+ sx={{
+ width: '120px'
+ }}
+ startIcon={ }
>
Refresh
{loadMessages ? : handleMessages()}
-
+
{
const [showContents, setShowContents] = useState(false);
const [showModal, setShowModal] = useState(false);
+ const { session } = useSession();
+ const { podUrl } = useContext(SignedInUserContext);
+ const { inboxList, setInboxList } = useContext(MessageContext);
- const handleClick = () => {
+ const handleClick = async () => {
setShowContents(!showContents);
+ if (folderType === 'Inbox') {
+ try {
+ await updateMessageReadStatus(session, message);
+ const messagesInSolid = await getMessageTTL(session, folderType, inboxList, podUrl);
+ messagesInSolid.sort((a, b) => b.uploadDate - a.uploadDate);
+ setInboxList(messagesInSolid);
+ } catch {
+ throw new Error('Failed to update read status');
+ }
+ }
};
const handleReplyMessage = () => {
setShowModal(!showModal);
};
+ 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 }
+ ];
+
return (
- handleClick()}>
- {message.uploadDate.toLocaleDateString()}
-
-
- {message.sender} - {message.title}
-
- {showContents && folderType === 'Inbox' && (
-
- Reply
-
- )}
-
- {showContents && (
-
-
- Content:
-
- {message.message.split('\n').map((line, index) => (
-
- {line}
-
+
+
+
+ handleClick()} alignItems="flex-start">
+
+ {messageInfo.map((info) => (
+
+
+ {info.title} {info.text}
+
+
))}
-
-
+
+ {showContents && (
+
+
+ {message.message.split('\n').map((line, index) => (
+
+ {line}
+
+ ))}
+ {showContents && folderType === 'Inbox' && (
+
+ Reply
+
+ )}
+
+ )}
+
+
+ {showModal && (
+
+ )}
+
- )}
- {showModal && (
-
- )}
-
+
+
);
};
diff --git a/src/components/Modals/AddClientModal.jsx b/src/components/Modals/AddContactModal.jsx
similarity index 56%
rename from src/components/Modals/AddClientModal.jsx
rename to src/components/Modals/AddContactModal.jsx
index df271841d..2d3052ec1 100644
--- a/src/components/Modals/AddClientModal.jsx
+++ b/src/components/Modals/AddContactModal.jsx
@@ -1,6 +1,5 @@
// React Imports
-import React, { useContext, useState } from 'react';
-import { useStatusNotification } from '@hooks';
+import React, { useState } from 'react';
// Material UI Imports
import Button from '@mui/material/Button';
import CheckIcon from '@mui/icons-material/Check';
@@ -12,35 +11,40 @@ import FormControl from '@mui/material/FormControl';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import TextField from '@mui/material/TextField';
-// Utility Imports
-import { runNotification } from '@utils';
-// Context Imports
-import { UserListContext } from '@contexts';
-// Model Imports
-import { createUser } from '../../model-helpers/User';
// Component Imports
import { FormSection } from '../Form';
+import useNotification from '../../hooks/useNotification';
/**
- * AddClientModal Component - Component that allows users to add other user's
- * Pod URLs from a user's list stored on their own Pod
- *
- * @memberof Modals
- * @name AddClientModal
+ * @memberof Contcts
+ * @name renderWebId
+ * @param {string} username - username to convert into a webId
+ * @returns {URL} A url of the predicted webID
*/
-
const renderWebId = (username) => {
const baseUrl = new URL(localStorage.getItem('oidcIssuer'));
return new URL(`${username}/profile/card#me`, baseUrl);
};
-const AddClientModal = ({ showAddClientModal, setShowAddClientModal }) => {
- const { state, dispatch } = useStatusNotification();
+/**
+ * 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
+ * @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
+ * @returns {React.JSX.Element} - The Add Contact Modal
+ */
+const AddContactModal = ({ addContact, showAddContactModal, setShowAddContactModal }) => {
+ const { addNotification } = useNotification();
const [userGivenName, setUserGivenName] = useState('');
const [userFamilyName, setUserFamilyName] = useState('');
const [username, setUsername] = useState('');
const [webId, setWebId] = useState('');
- const { addUser } = useContext(UserListContext);
+ const [processing, setProcessing] = useState(false);
const wrappedSetUsername = (value) => {
setUsername(value);
@@ -48,99 +52,41 @@ const AddClientModal = ({ showAddClientModal, setShowAddClientModal }) => {
setWebId(renderedWebId);
};
- const submitUser = async (userObject) => {
- const user = await createUser(userObject);
- await addUser(user);
- };
-
- const notifyStartSubmission = (userObject) => {
- // ===== START OF ERROR DISPLAY OPTIONS =====
- if (!userObject.username && !userObject.webId) {
- runNotification(`Operation failed. Reason: No WebId provided`, 5, state, dispatch);
- return;
- }
-
- if (!userObject.givenName) {
- runNotification(
- `Operation failed. Reason: User's first/given name is not provided`,
- 5,
- state,
- dispatch
- );
- setTimeout(() => {
- dispatch({ type: 'CLEAR_PROCESSING' });
- }, 2000);
- return;
- }
-
- if (!userObject.familyName) {
- runNotification(
- `Operation failed. Reason: User's last/family name is not provided`,
- 5,
- state,
- dispatch
- );
- setTimeout(() => {
- dispatch({ type: 'CLEAR_PROCESSING' });
- }, 2000);
- return;
- }
- // ===== END OF ERROR DISPLAY OPTIONS =====
-
- dispatch({ type: 'SET_PROCESSING' });
-
- runNotification(
- `Adding "${userObject.givenName} ${userObject.familyName}" to client list...`,
- 5,
- state,
- dispatch
- );
- };
-
- // Event handler for adding client to users list
- const handleAddClient = async (event) => {
+ const handleAddContact = async (event) => {
event.preventDefault();
+ setProcessing(true);
const userObject = {
givenName: event.target.addUserGivenName.value,
familyName: event.target.addUserFamilyName.value,
- username: event.target.addUsername.value,
webId: event.target.addWebId.value
};
- notifyStartSubmission(userObject, state, dispatch);
try {
- await submitUser(userObject);
- } finally {
- runNotification(
- `"${userObject.givenName} ${userObject.familyName}" added to client list`,
- 5,
- state,
- dispatch
+ await addContact(userObject);
+ addNotification(
+ 'success',
+ `"${userObject.givenName} ${userObject.familyName}" added to client list`
);
- setTimeout(() => {
- setUserGivenName('');
- setUserFamilyName('');
- setUsername('');
- setWebId('');
- dispatch({ type: 'CLEAR_PROCESSING' });
- setShowAddClientModal(false);
- }, 2000);
+ } catch (e) {
+ addNotification('error', `Add contact failed. Reason: ${e.message}`);
+ } finally {
+ setUserGivenName('');
+ setUserFamilyName('');
+ setUsername('');
+ setWebId('');
+ setShowAddContactModal(false);
+ setProcessing(false);
}
};
return (
setShowAddClientModal(false)}
+ onClose={() => setShowAddContactModal(false)}
>
-
-