Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix client refresh and url redirection #281

Merged
merged 15 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ module.exports = {
settings: {
jsdoc: {
mode: 'typescript'
},
'import/resolver': {
alias: {
map: [
['@hooks', './src/hooks'],
['@contexts', './src/contexts'],
['@pages', './src/pages'],
['@utils', './src/utils'],
['@components', './src/components']
]
}
}
}
};
5 changes: 5 additions & 0 deletions __mocks__/@inrupt/solid-client-authn-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { vi } from 'vitest';

export * from '@inrupt/solid-client-authn-browser';

export const login = vi.fn(() => Promise.resolve());
4 changes: 4 additions & 0 deletions netlify.toml
leekahung marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[redirects]]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a netlify config file. The [[redirects]] settings tell the netlify server to let react handle the routing.

from = "/*"
to = "/"
status = 200
2,061 changes: 1,614 additions & 447 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
"@emotion/react": "^11.10.8",
"@emotion/styled": "^11.10.8",
"@inrupt/solid-client": "^1.24.0",
"@inrupt/solid-client-authn-browser": "^1.12.3",
"@inrupt/solid-ui-react": "^2.8.2",
"@inrupt/solid-client-authn-browser": "1.15.0",
"@inrupt/vocab-common-rdf": "^1.0.5",
"@mui/base": "^5.0.0-beta.0",
"@mui/icons-material": "^5.11.16",
Expand All @@ -54,6 +53,7 @@
"last 3 edge versions"
],
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.1.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 dev dependencies are used to get the @hooks, @components, etc... mappings working. This one runs on the prod build, the eslint ones run in eslint.

"@solid/community-server": "^5.1.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
Expand All @@ -65,6 +65,8 @@
"eslint-config-airbnb": "^19.0.4",
"eslint-config-node": "^4.1.0",
"eslint-config-prettier": "^8.6.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsdoc": "^40.1.0",
"eslint-plugin-jsx-a11y": "^6.7.1",
Expand Down
28 changes: 13 additions & 15 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
// React Imports
import React from 'react';
import { HashRouter as Router } from 'react-router-dom';
// Inrupt Library Imports
import { SessionProvider } from '@inrupt/solid-ui-react';
import { BrowserRouter } from 'react-router-dom';
import { SessionProvider } from '@contexts';
// Material UI Imports
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
// Context Imports
import UserDataContextProvider from './contexts/UserDataContext';
// Theme Imports
import theme from './theme';
// Layout Imports
import Layout from './layouts/Layout';
// Route Imports
import AppRoutes from './AppRoutes';
import Layout from './layouts/Layout';

/**
* @typedef {import("./typedefs").userListObject} userListObject
Expand All @@ -24,18 +22,18 @@ import AppRoutes from './AppRoutes';
*/

const App = () => (
<Router>
<SessionProvider>
<CssBaseline />
<ThemeProvider theme={theme}>
<UserDataContextProvider>
<Layout ariaLabel="Home Page">
<SessionProvider restorePreviousSession>
<CssBaseline />
<ThemeProvider theme={theme}>
<UserDataContextProvider>
<BrowserRouter>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a bit curious, is there a reason for moving the Router from the uppermost later to just before Layout compared to before where the Router is wrapping around everything?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong reason, just thought it looked cleaner. Based the idea off of bulletproof react:

https://github.com/alan2207/bulletproof-react/tree/master

Which just contains a lot of good react programming patterns, and does the routes and provider structure that way.

<Layout>
<AppRoutes />
</Layout>
</UserDataContextProvider>
</ThemeProvider>
</SessionProvider>
</Router>
</BrowserRouter>
</UserDataContextProvider>
</ThemeProvider>
</SessionProvider>
);

export default App;
46 changes: 12 additions & 34 deletions src/AppRoutes.jsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,38 @@
// React Imports
import React, { useState, useEffect } from 'react';
import React from 'react';
import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
// Inrupt Imports
import { useSession } from '@inrupt/solid-ui-react';
// Custom Hook Imports
import { useRedirectUrl } from './hooks';
import { useSession } from '@hooks';
// Page Imports
import { Home, Clients, Messages, Documents, Profile } from './routes';
import { Home, Clients, Messages, Documents, Profile } from './pages';

const ProtectedRoute = ({ isLoggedIn, children }) =>
isLoggedIn ? children ?? <Outlet /> : <Navigate to="/PASS/" replace />;
isLoggedIn ? children ?? <Outlet /> : <Navigate to="/" replace />;

/**
* The main application routing for PASS
*
* @name AppRoutes
* @returns {React.JSX.Element}
* @returns {React.JSX.Element} - Project Routes
*/

const AppRoutes = () => {
const { session } = useSession();
const redirectUrl = useRedirectUrl();
const [restore, setRestore] = useState(false);
const restorePath = localStorage.getItem('restorePath');
const path = restorePath ?? '/PASS/clients';

useEffect(() => {
const performanceEntries = window.performance.getEntriesByType('navigation');
if (performanceEntries[0].type === 'reload' && performanceEntries.length === 1) {
setRestore(true);
}

if (restore && localStorage.getItem('loggedIn')) {
session.login({
oidcIssuer: localStorage.getItem('oidcIssuer'),
redirectUrl
});
}
}, [restore]);

useEffect(() => {
if (session.info.isLoggedIn) localStorage.setItem('loggedIn', true);
}, [session.info.isLoggedIn]);
const loggedIn = session.info.isLoggedIn;
const path = loggedIn ? restorePath || '/clients' : '/';

return (
<Routes>
<Route
exact
path="/PASS/"
path="/"
element={session.info.isLoggedIn ? <Navigate to={path} replace /> : <Home />}
/>
<Route element={<ProtectedRoute isLoggedIn={session.info.isLoggedIn} />}>
<Route path="/PASS/clients" element={<Clients />} />
<Route path="/PASS/documents" element={<Documents />} />
<Route path="/PASS/messages" element={<Messages />} />
<Route path="/PASS/profile" element={<Profile />} />
<Route path="/clients" element={<Clients />} />
<Route path="/documents" element={<Documents />} />
<Route path="/messages" element={<Messages />} />
<Route path="/profile" element={<Profile />} />
<Route path="*" element={<Navigate to={restorePath} replace />} />
</Route>
</Routes>
Expand Down
3 changes: 1 addition & 2 deletions src/components/Clients/AddClientModal.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// React Imports
import React, { useContext, useState } from 'react';
import { useStatusNotification } from '@hooks';
// Material UI Imports
import Button from '@mui/material/Button';
import CheckIcon from '@mui/icons-material/Check';
Expand All @@ -12,8 +13,6 @@ import TextField from '@mui/material/TextField';
import { ENV } from '../../constants';
import { runNotification } from '../../utils';
import { createUser } from '../../model-helpers/User';
// Custom Hook Imports
import { useStatusNotification } from '../../hooks';
// Context Imports
import { UserListContext } from '../../contexts';
// Component Imports
Expand Down
2 changes: 1 addition & 1 deletion src/components/Documents/DocumentTableRow.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// React Imports
import React, { useContext } from 'react';
// Inrupt Imports
import { useSession } from '@inrupt/solid-ui-react';
import { useSession } from '@hooks';
// Material UI Imports
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
import FileOpenIcon from '@mui/icons-material/FileOpen';
Expand Down
6 changes: 1 addition & 5 deletions src/components/Form/SetAclPermissionForm.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// React Imports
import React, { useContext, useState } from 'react';
// Inrupt Library Imports
import { useSession } from '@inrupt/solid-ui-react';
import { useSession, useStatusNotification } from '@hooks';
// Material UI Imports
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
Expand All @@ -14,9 +13,6 @@ import RadioGroup from '@mui/material/RadioGroup';
import TextField from '@mui/material/TextField';
// Utility Imports
import { getPodUrl, runNotification, setDocAclPermission } from '../../utils';
// Custom Hook Imports
import { useStatusNotification } from '../../hooks';
// Context Imports
import { SelectedUserContext, SignedInUserContext } from '../../contexts';
// Component Imports
import DocumentSelection from './DocumentSelection';
Expand Down
4 changes: 1 addition & 3 deletions src/components/Form/SetAclPermsDocContainerForm.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// React Imports
import React, { useContext, useState } from 'react';
// Inrupt Library Imports
import { useSession } from '@inrupt/solid-ui-react';
import { useSession, useStatusNotification } from '@hooks';
// Material UI Imports
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
Expand All @@ -14,8 +14,6 @@ import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
// Utility Imports
import { getPodUrl, runNotification, setDocContainerAclPermission } from '../../utils';
// Custom Hook Imports
import { useStatusNotification } from '../../hooks';
// Context Imports
import { SelectedUserContext, SignedInUserContext } from '../../contexts';
// Component Imports
Expand Down
54 changes: 54 additions & 0 deletions src/components/LogoutModal/LogoutButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright 2020 Inrupt Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import React, { useContext } from 'react';
import { SessionContext } from '@contexts';

const LogoutButton = ({ children, onLogout, onError }) => {
const { logout } = useContext(SessionContext);

const logoutHandler = async () => {
try {
await logout();
if (onLogout) onLogout();
} catch (error) {
if (onError) onError(error);
}
};

const keyDownHandler = (e) => {
e.preventDefault();

return e.key === 'Enter' ? logoutHandler() : Promise.resolve();
};

return children ? (
<div role="button" tabIndex={0} onClick={logoutHandler} onKeyDown={keyDownHandler}>
{children}
</div>
) : (
<button type="button" onClick={logoutHandler} onKeyDown={keyDownHandler}>
Log Out
</button>
);
};

export default LogoutButton;
5 changes: 2 additions & 3 deletions src/components/LogoutModal/LogoutModal.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// React Imports
import React from 'react';
// Solid Imports
import { LogoutButton } from '@inrupt/solid-ui-react';
// Material UI Imports
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
Expand All @@ -12,6 +10,8 @@ import DialogTitle from '@mui/material/DialogTitle';
import ClearIcon from '@mui/icons-material/Clear';
import CheckIcon from '@mui/icons-material/Check';

import LogoutButton from './LogoutButton';

/**
* LogoutModal Component - Popup modal for users to confirm
* they actually want to logout of their Solid Pod
Expand Down Expand Up @@ -44,7 +44,6 @@ const LogoutModal = ({ showConfirmation, setShowConfirmation, handleLogout }) =>
>
NO
</Button>
{/* NECESSARY WRAPPER FOR SOLID/POD LOGOUT FUNCTIONALITY */}
<LogoutButton>
<Button
variant="outlined"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Messages/NewMessage.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// React Imports
import React, { useState, useContext } from 'react';
// Inrupt Library Imports
import { useSession } from '@inrupt/solid-ui-react';
import { useSession } from '@hooks';
// Styling Imports
import {
CancelButton,
Expand Down
2 changes: 1 addition & 1 deletion src/components/NavBar/NavBar.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// React Imports
import React, { useContext, useEffect, useState } from 'react';
// Inrupt Library Imports
import { useSession } from '@inrupt/solid-ui-react';
import { useSession } from '@hooks';
// Material UI Imports
import Avatar from '@mui/material/Avatar';
import AppBar from '@mui/material/AppBar';
Expand Down
2 changes: 1 addition & 1 deletion src/components/NavBar/NavMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const NavMenu = ({
}
>
<NavLink
to="/PASS/profile"
to="/profile"
style={{ textDecoration: 'none', color: theme.palette.primary.main }}
>
Profile
Expand Down
8 changes: 4 additions & 4 deletions src/components/NavBar/NavbarLinks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ const NavbarLinks = () => {
const theme = useTheme();

// Tabs workaround to match route on login
let location = useLocation().pathname.slice(6);
let location = useLocation().pathname.split('/')[1];
if (location === '') {
location = 'clients';
}

// array of current nav links for menus
const routesArray = [
{ label: 'Clients', path: '/PASS/clients' },
{ label: 'Documents', path: '/PASS/documents' },
{ label: 'Messages', path: '/PASS/messages' }
{ label: 'Clients', path: 'clients' },
{ label: 'Documents', path: 'documents' },
{ label: 'Messages', path: 'messages' }
];

// Navigate To... button and menu (small screens)
Expand Down
Loading