Create a context to store authentication
This commit is contained in:
parent
10e5c124fd
commit
1934354665
23
geneit_app/package-lock.json
generated
23
geneit_app/package-lock.json
generated
@ -23,7 +23,6 @@
|
|||||||
"@types/react": "^18.2.8",
|
"@types/react": "^18.2.8",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.2.4",
|
||||||
"date-and-time": "^3.0.1",
|
"date-and-time": "^3.0.1",
|
||||||
"jotai": "^2.1.1",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.11.2",
|
||||||
@ -11966,22 +11965,6 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jotai": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-LaaiuSaq+6XkwkrCtCkczyFVZOXe0dfjAFN4DVMsSZSRv/A/4xuLHnlpHMEDqvngjWYBotTIrnQ7OogMkUE6wA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.20.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=17.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -26267,12 +26250,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
|
||||||
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg=="
|
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg=="
|
||||||
},
|
},
|
||||||
"jotai": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-LaaiuSaq+6XkwkrCtCkczyFVZOXe0dfjAFN4DVMsSZSRv/A/4xuLHnlpHMEDqvngjWYBotTIrnQ7OogMkUE6wA==",
|
|
||||||
"requires": {}
|
|
||||||
},
|
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
"@types/react": "^18.2.8",
|
"@types/react": "^18.2.8",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.2.4",
|
||||||
"date-and-time": "^3.0.1",
|
"date-and-time": "^3.0.1",
|
||||||
"jotai": "^2.1.1",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.11.2",
|
||||||
|
@ -1,45 +1,61 @@
|
|||||||
|
import React from "react";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { AuthApi } from "./api/AuthApi";
|
import { AuthApi } from "./api/AuthApi";
|
||||||
import { NotFoundRoute } from "./routes/NotFound";
|
import { NotFoundRoute } from "./routes/NotFound";
|
||||||
import { BaseLoginPage } from "./widgets/BaseLoginpage";
|
import { ProfileRoute } from "./routes/ProfileRoute";
|
||||||
import { LoginRoute } from "./routes/auth/LoginRoute";
|
import { LoginRoute } from "./routes/auth/LoginRoute";
|
||||||
|
import { NewAccountRoute } from "./routes/auth/NewAccountRoute";
|
||||||
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
|
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
|
|
||||||
import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute";
|
import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute";
|
||||||
import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute";
|
import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute";
|
||||||
import { NewAccountRoute } from "./routes/auth/NewAccountRoute";
|
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
|
||||||
import { ProfileRoute } from "./routes/ProfileRoute";
|
import { BaseLoginPage } from "./widgets/BaseLoginpage";
|
||||||
|
|
||||||
|
interface AuthContext {
|
||||||
|
signedIn: boolean;
|
||||||
|
setSignedIn: (signedIn: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContextK = React.createContext<AuthContext | null>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core app
|
* Core app
|
||||||
*/
|
*/
|
||||||
function App() {
|
export function App(): React.ReactElement {
|
||||||
const [signedIn] = useAtom(AuthApi.authStatus);
|
const [signedIn, setSignedIn] = React.useState(AuthApi.SignedIn);
|
||||||
|
|
||||||
|
const context: AuthContext = {
|
||||||
|
signedIn: signedIn,
|
||||||
|
setSignedIn: (s) => setSignedIn(s),
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<AuthContextK.Provider value={context}>
|
||||||
{signedIn ? (
|
<Routes>
|
||||||
<Route path="*" element={<BaseAuthenticatedPage />}>
|
{signedIn ? (
|
||||||
<Route path="profile" element={<ProfileRoute />} />
|
<Route path="*" element={<BaseAuthenticatedPage />}>
|
||||||
<Route path="*" element={<NotFoundRoute />} />
|
<Route path="profile" element={<ProfileRoute />} />
|
||||||
</Route>
|
<Route path="*" element={<NotFoundRoute />} />
|
||||||
) : (
|
</Route>
|
||||||
<Route path="*" element={<BaseLoginPage />}>
|
) : (
|
||||||
<Route path="" element={<LoginRoute />} />
|
<Route path="*" element={<BaseLoginPage />}>
|
||||||
<Route path="oidc_cb" element={<OIDCCbRoute />} />
|
<Route path="" element={<LoginRoute />} />
|
||||||
<Route path="new-account" element={<NewAccountRoute />} />
|
<Route path="oidc_cb" element={<OIDCCbRoute />} />
|
||||||
<Route
|
<Route path="new-account" element={<NewAccountRoute />} />
|
||||||
path="password_forgotten"
|
<Route
|
||||||
element={<PasswordForgottenRoute />}
|
path="password_forgotten"
|
||||||
/>
|
element={<PasswordForgottenRoute />}
|
||||||
<Route path="reset_password" element={<ResetPasswordRoute />} />
|
/>
|
||||||
<Route path="*" element={<NotFoundRoute />} />
|
<Route path="reset_password" element={<ResetPasswordRoute />} />
|
||||||
</Route>
|
<Route path="*" element={<NotFoundRoute />} />
|
||||||
)}
|
</Route>
|
||||||
</Routes>
|
)}
|
||||||
|
</Routes>
|
||||||
|
</AuthContextK.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export function useAuth(): AuthContext {
|
||||||
|
return React.useContext(AuthContextK)!;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { atom } from "jotai";
|
|
||||||
import { APIClient } from "./ApiClient";
|
import { APIClient } from "./ApiClient";
|
||||||
|
|
||||||
export enum CreateAccountResult {
|
export enum CreateAccountResult {
|
||||||
@ -30,8 +29,6 @@ export class AuthApi {
|
|||||||
return localStorage.getItem(TokenStateKey) !== null;
|
return localStorage.getItem(TokenStateKey) !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static authStatus = atom(this.SignedIn);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user auth token
|
* Get user auth token
|
||||||
*/
|
*/
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import App from "./App";
|
import { App } from "./App";
|
||||||
import reportWebVitals from "./reportWebVitals";
|
import reportWebVitals from "./reportWebVitals";
|
||||||
import { ServerApi } from "./api/ServerApi";
|
import { ServerApi } from "./api/ServerApi";
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
@ -14,11 +15,10 @@ import Grid from "@mui/material/Grid";
|
|||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { useAuth } from "../../App";
|
||||||
import { AuthApi, PasswordLoginResult } from "../../api/AuthApi";
|
import { AuthApi, PasswordLoginResult } from "../../api/AuthApi";
|
||||||
import { ServerApi } from "../../api/ServerApi";
|
import { ServerApi } from "../../api/ServerApi";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
|
||||||
import { VisibilityOff, Visibility } from "@mui/icons-material";
|
|
||||||
import { useSetAtom } from "jotai";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login form
|
* Login form
|
||||||
@ -27,7 +27,7 @@ export function LoginRoute(): React.ReactElement {
|
|||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
const [error, setError] = React.useState<string | null>(null);
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
const setAuth = useSetAtom(AuthApi.authStatus);
|
const auth = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [mail, setMail] = React.useState("");
|
const [mail, setMail] = React.useState("");
|
||||||
@ -64,7 +64,7 @@ export function LoginRoute(): React.ReactElement {
|
|||||||
|
|
||||||
case PasswordLoginResult.Success:
|
case PasswordLoginResult.Success:
|
||||||
navigate("/");
|
navigate("/");
|
||||||
setAuth(true);
|
auth.setSignedIn(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PasswordLoginResult.Error:
|
case PasswordLoginResult.Error:
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { CircularProgress } from "@mui/material";
|
import { CircularProgress } from "@mui/material";
|
||||||
import { useSetAtom } from "jotai";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { AuthApi } from "../../api/AuthApi";
|
import { AuthApi } from "../../api/AuthApi";
|
||||||
import { AuthSingleMessage } from "../../widgets/AuthSingleMessage";
|
import { AuthSingleMessage } from "../../widgets/AuthSingleMessage";
|
||||||
|
import { useAuth } from "../../App";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenID login callback route
|
* OpenID login callback route
|
||||||
*/
|
*/
|
||||||
export function OIDCCbRoute(): React.ReactElement {
|
export function OIDCCbRoute(): React.ReactElement {
|
||||||
const setAuth = useSetAtom(AuthApi.authStatus);
|
const auth = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
@ -30,7 +30,7 @@ export function OIDCCbRoute(): React.ReactElement {
|
|||||||
|
|
||||||
await AuthApi.FinishOpenIDLogin(code!, state!);
|
await AuthApi.FinishOpenIDLogin(code!, state!);
|
||||||
navigate("/");
|
navigate("/");
|
||||||
setAuth(true);
|
auth.setSignedIn(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setError(true);
|
setError(true);
|
||||||
|
@ -6,18 +6,13 @@ import {
|
|||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React, {
|
import React, { PropsWithChildren } from "react";
|
||||||
PropsWithChildren,
|
|
||||||
createContext,
|
|
||||||
useContext,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
interface AlertHook {
|
interface AlertContext {
|
||||||
alert: (message: string, title?: string) => Promise<void>;
|
alert: (message: string, title?: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlertContext = createContext<AlertHook | null>(null);
|
const AlertContextK = React.createContext<AlertContext | null>(null);
|
||||||
|
|
||||||
export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
|
export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
@ -34,7 +29,7 @@ export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
|
|||||||
cb.current = null;
|
cb.current = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hook: AlertHook = {
|
const hook: AlertContext = {
|
||||||
alert: (message, title) => {
|
alert: (message, title) => {
|
||||||
setTitle(title);
|
setTitle(title);
|
||||||
setMessage(message);
|
setMessage(message);
|
||||||
@ -48,7 +43,7 @@ export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AlertContext.Provider value={hook}>{p.children}</AlertContext.Provider>
|
<AlertContextK.Provider value={hook}>{p.children}</AlertContextK.Provider>
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
@ -72,6 +67,6 @@ export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAlert(): AlertHook {
|
export function useAlert(): AlertContext {
|
||||||
return useContext(AlertContext)!;
|
return React.useContext(AlertContextK)!;
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,9 @@ import Menu from "@mui/material/Menu";
|
|||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { useSetAtom } from "jotai";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Link, Outlet, useNavigate } from "react-router-dom";
|
import { Link, Outlet, useNavigate } from "react-router-dom";
|
||||||
|
import { useAuth } from "../App";
|
||||||
import { AuthApi } from "../api/AuthApi";
|
import { AuthApi } from "../api/AuthApi";
|
||||||
import { User, UserApi } from "../api/UserApi";
|
import { User, UserApi } from "../api/UserApi";
|
||||||
import { AsyncWidget } from "./AsyncWidget";
|
import { AsyncWidget } from "./AsyncWidget";
|
||||||
@ -18,7 +18,7 @@ import { RouterLink } from "./RouterLink";
|
|||||||
export function BaseAuthenticatedPage(): React.ReactElement {
|
export function BaseAuthenticatedPage(): React.ReactElement {
|
||||||
const [user, setUser] = React.useState<null | User>(null);
|
const [user, setUser] = React.useState<null | User>(null);
|
||||||
|
|
||||||
const setSignedIn = useSetAtom(AuthApi.authStatus);
|
const auth = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
@ -39,7 +39,7 @@ export function BaseAuthenticatedPage(): React.ReactElement {
|
|||||||
handleCloseMenu();
|
handleCloseMenu();
|
||||||
AuthApi.SignOut();
|
AuthApi.SignOut();
|
||||||
navigate("/");
|
navigate("/");
|
||||||
setSignedIn(false);
|
auth.setSignedIn(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -6,14 +6,9 @@ import {
|
|||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React, {
|
import React, { PropsWithChildren } from "react";
|
||||||
PropsWithChildren,
|
|
||||||
createContext,
|
|
||||||
useContext,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
interface ConfirmHook {
|
interface ConfirmContext {
|
||||||
confirm: (
|
confirm: (
|
||||||
message: string,
|
message: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
@ -21,7 +16,7 @@ interface ConfirmHook {
|
|||||||
) => Promise<boolean>;
|
) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfirmContext = createContext<ConfirmHook | null>(null);
|
const ConfirmContextK = React.createContext<ConfirmContext | null>(null);
|
||||||
|
|
||||||
export function ConfirmDialogProvider(
|
export function ConfirmDialogProvider(
|
||||||
p: PropsWithChildren
|
p: PropsWithChildren
|
||||||
@ -43,7 +38,7 @@ export function ConfirmDialogProvider(
|
|||||||
cb.current = null;
|
cb.current = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hook: ConfirmHook = {
|
const hook: ConfirmContext = {
|
||||||
confirm: (message, title, confirmButton) => {
|
confirm: (message, title, confirmButton) => {
|
||||||
setTitle(title);
|
setTitle(title);
|
||||||
setMessage(message);
|
setMessage(message);
|
||||||
@ -58,9 +53,9 @@ export function ConfirmDialogProvider(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConfirmContext.Provider value={hook}>
|
<ConfirmContextK.Provider value={hook}>
|
||||||
{p.children}
|
{p.children}
|
||||||
</ConfirmContext.Provider>
|
</ConfirmContextK.Provider>
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
@ -87,6 +82,6 @@ export function ConfirmDialogProvider(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useConfirm(): ConfirmHook {
|
export function useConfirm(): ConfirmContext {
|
||||||
return useContext(ConfirmContext)!;
|
return React.useContext(ConfirmContextK)!;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user