Added protected routes, jotai dev tools

This commit is contained in:
Martin Berg Alstad 2023-07-22 18:49:48 +02:00
parent 2d248c2364
commit fc5f553042
8 changed files with 2121 additions and 55 deletions

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,11 @@
"version": "0.1.1",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.1",
"@headlessui/react": "^1.7.15",
"@heroicons/react": "^2.0.18",
"jotai": "^2.2.2",
"jotai-devtools": "^0.6.0",
"oidc-client": "^1.11.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@ -23,6 +25,7 @@
"tailwindcss": "^3.3.2",
"typescript": "^5.1.6",
"vite": "^4.4.2",
"vite-plugin-node-polyfills": "^0.9.0",
"vite-plugin-svgr": "^3.2.0",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^0.31.4"

View File

@ -1,16 +1,42 @@
import React, {FC} from "react";
import {Route, Routes} from "react-router-dom";
import React, {FC, useEffect} from "react";
import {Route, Routes, useNavigate} from "react-router-dom";
import Layout from "./components/layout";
import AppRoutes from "./AppRoutes";
import "./index.css";
import {useAtomValue} from "jotai";
import {thisPlayerAtom} from "./utils/state";
export const App: FC = () => (
<Layout>
<Routes>
{AppRoutes.map((route, index) => {
const {element, ...rest} = route;
return <Route key={index} {...rest} element={element}/>;
const {element, secured = false, ...rest} = route;
return <Route key={index} {...rest} element={<Secured secured={secured}>{element}</Secured>}/>;
})}
</Routes>
</Layout>
);
/**
* This component is used to redirect the user to the login page if they are not logged in and the page is secured.
* @param children The children to render if the user is logged in or the page is not secured.
* @param secured Whether or not the page is secured.
* @constructor The Secured component.
*/
const Secured: FC<{ secured: boolean } & ChildProps> = ({children, secured}) => {
const player = useAtomValue(thisPlayerAtom);
const navigate = useNavigate();
const redirect = secured && player === undefined
useEffect(() => {
if (redirect) {
navigate("/login");
}
}, []);
if (!redirect) {
return (
<>{children}</>
)
}
}

View File

@ -7,7 +7,7 @@ import Login from "./pages/login";
const AppRoutes = [
{
index: true,
element: <LobbyPage/>
element: <LobbyPage/> // TODO change to home page
},
{
path: "/counter",
@ -16,10 +16,12 @@ const AppRoutes = [
{
path: "/game/:id",
element: <Game/>,
secured: true
},
{
path: "/lobby",
element: <LobbyPage/>,
secured: true
},
{
path: "/login",

View File

@ -1,16 +1,33 @@
import React, {FC} from "react";
import {Link} from "react-router-dom";
import {Link, useNavigate} from "react-router-dom";
import {useAtom} from "jotai";
import {thisPlayerAtom} from "../utils/state";
const NavMenu: FC = () => {
const [player, setPlayer] = useAtom(thisPlayerAtom);
const navigate = useNavigate();
async function logout(): Promise<void> {
setPlayer(undefined);
navigate("/login");
}
return (
<header>
<nav className="mb-3 flex justify-between border-b-2">
<Link to="/"><h2 className={"my-1"}>Pac-Man Board Game</h2></Link>
<Link to="/"><h2 className={"m-1"}>Pac-Man Board Game</h2></Link>
<ul className="inline-flex gap-2 items-center mr-5">
<NavItem to="/">Home</NavItem>
<NavItem to={"/lobby"}>Lobby</NavItem>
<NavItem className={"mx-2"} to={"/login"}>Login</NavItem>
{
player === undefined ? /* TODO thisPlayerAtom contains a player object, from sessionStorage */
<NavItem className={"mx-2"} to={"/login"}>Login</NavItem>
:
<li className={"mx-2"}>
<button onClick={logout} className={"hover:underline"}>Logout</button>
</li>
}
{/*TODO show signed in user when signed in, otherwise login button*/}
</ul>

View File

@ -4,6 +4,7 @@ import {BrowserRouter} from 'react-router-dom';
import {App} from './App';
// @ts-ignore
import reportWebVitals from './reportWebVitals';
import {DevTools} from "jotai-devtools";
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const rootElement = document.getElementById('root');
@ -12,6 +13,7 @@ const root = createRoot(rootElement);
root.render(
<BrowserRouter basename={baseUrl ?? undefined}>
<DevTools/>
<App/>
</BrowserRouter>);

View File

@ -0,0 +1,18 @@
/**
* Waits until the predicate is true
* @param predicate The predicate to wait for.
* @param timeout The timeout between checks.
* @returns A promise that resolves when the predicate is true.
*/
export function wait(predicate: Predicate<void>, timeout: number = 50): Promise<void> {
return new Promise<void>((resolve) => {
const f = () => {
if (predicate()) {
return resolve();
}
setTimeout(f, timeout);
};
f();
});
}

View File

@ -7,9 +7,15 @@ import fs from "fs";
// @ts-ignore
import path from "path";
import {execSync} from "child_process";
import jotaiDebugLabel from 'jotai/babel/plugin-debug-label'
import jotaiReactRefresh from 'jotai/babel/plugin-react-refresh'
import {nodePolyfills} from "vite-plugin-node-polyfills";
export default defineConfig({
plugins: [react()],
plugins: [
react({babel: {plugins: [jotaiDebugLabel, jotaiReactRefresh]}}),
nodePolyfills()
],
build: {
outDir: "build",
},