diff --git a/frontend/src/components/GuestBox.tsx b/frontend/src/components/GuestBox.tsx new file mode 100644 index 0000000..92cf376 --- /dev/null +++ b/frontend/src/components/GuestBox.tsx @@ -0,0 +1,76 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import {Box, Heading, Panel, Form} from "@steffo/bluelib-react"; +import {useLogin} from "./LoginContext"; +import {useInstance} from "./InstanceContext"; +import {navigate} from "@reach/router"; + + +interface GuestBoxProps { + +} + + +export function GuestBox({}: GuestBoxProps): JSX.Element { + const instance = useInstance() + const login = useLogin() + + /** + * Whether the guest login button is enabled or not. + */ + const canBrowse = React.useMemo( + () => { + return instance.validity === true && !login.running + }, + [instance, login] + ) + + /** + * The state panel, displayed on top of the form. + */ + const statePanel = React.useMemo( + () => { + if(!instance.validity) { + return ( + + Please enter a valid instance URL before continuing. + + ) + } + if(login.running) { + return ( + + You cannot browse Sophon while a login is in progress. + + ) + } + return ( + + Click the button below to access Sophon. + + ) + }, + [instance, login] + ) + + return ( + + + Continue as guest + +

+ You can browse Sophon without logging in, but many functions won't be available to you. +

+
+ + {statePanel} + + + await navigate("/logged-in")}> + Browse + + +
+
+ ) +} diff --git a/frontend/src/components/InstanceBox.tsx b/frontend/src/components/InstanceBox.tsx index 1390251..09ff31d 100644 --- a/frontend/src/components/InstanceBox.tsx +++ b/frontend/src/components/InstanceBox.tsx @@ -1,9 +1,8 @@ import * as React from "react" import * as ReactDOM from "react-dom" -import {Box, Heading, Form, Panel} from "@steffo/bluelib-react"; +import {Box, Heading, Form, Panel, Idiomatic as I} from "@steffo/bluelib-react"; import {useInstance} from "./InstanceContext"; import {useLogin} from "./LoginContext"; -import {Idiomatic as I} from "@steffo/bluelib-react/dist/components/semantics/Idiomatic"; interface InstanceBoxProps { @@ -13,34 +12,48 @@ interface InstanceBoxProps { export function InstanceBox({}: InstanceBoxProps): JSX.Element { const instance = useInstance() - const {userData} = useLogin() + const login = useLogin() + + const canChange = React.useMemo( + () => { + return !(login.userData || login.running) + }, + [login] + ) /** * The state panel, displayed on top of the form. */ const statePanel = React.useMemo( () => { - if(userData) { + if(login.userData) { return ( - - To change Sophon instance, please logout. + + You cannot change Sophon instance while you are logged in. If you need to change instance, logout first! + + ) + } + if(login.running) { + return ( + + You cannot change Sophon instance while logging in. ) } if(instance.validity === false) { return ( - The specified instance is invalid. + No Sophon instance was detected at the inserted URL. ) } return ( - Select the Sophon instance you want to connect to. + Select the Sophon instance you want to connect to by specifying its URL here. ) }, - [userData, instance] + [login, instance] ) return ( @@ -48,11 +61,14 @@ export function InstanceBox({}: InstanceBoxProps): JSX.Element { Instance select +

+ Sophon can be used by multiple institutions, each one using a physically separate instance. +

{statePanel} - + ) diff --git a/frontend/src/components/LoginBox.tsx b/frontend/src/components/LoginBox.tsx index ca51e04..c425962 100644 --- a/frontend/src/components/LoginBox.tsx +++ b/frontend/src/components/LoginBox.tsx @@ -22,7 +22,7 @@ export function LoginBox({}: LoginBoxProps): JSX.Element { /** * The {@link LoginContext}. */ - const {login} = useLogin() + const {login, running} = useLogin() /** * The {@link FormState} of the username field. @@ -75,11 +75,8 @@ export function LoginBox({}: LoginBoxProps): JSX.Element { setError(e as AxiosError) return } - finally { - // Clear the abort state - // Possible race condition? - setAbort(null) - } + + await navigate("/logged-in") }, [abort, setAbort, username, password, login, setError] ) @@ -89,9 +86,9 @@ export function LoginBox({}: LoginBoxProps): JSX.Element { */ const canLogin = React.useMemo( () => { - return instance.validity === true && username.validity === true && password.validity === true && !abort + return instance.validity === true && username.validity === true && password.validity === true && !running }, - [instance, username, password] + [instance, username, password, running] ) /** @@ -117,7 +114,7 @@ export function LoginBox({}: LoginBoxProps): JSX.Element { } if(!instance.validity) { return ( - + Please enter a valid instance URL before logging in. ) @@ -129,20 +126,20 @@ export function LoginBox({}: LoginBoxProps): JSX.Element { ) } - if(abort) { + if(running) { return ( - - Logging in... + + Logging in, please wait... ) } return ( - Click the login button to begin the login procedure. + Click the button below to login. ) }, - [error, instance, username, password, abort] + [error, instance, username, password, running] ) return ( @@ -151,7 +148,7 @@ export function LoginBox({}: LoginBoxProps): JSX.Element { Login

- Authenticate yourself to access the full functionality of Sophon. + Login as an authorized user to access the full functionality of Sophon.

diff --git a/frontend/src/components/LoginContext.tsx b/frontend/src/components/LoginContext.tsx index aadb819..e98893d 100644 --- a/frontend/src/components/LoginContext.tsx +++ b/frontend/src/components/LoginContext.tsx @@ -16,6 +16,7 @@ export interface LoginContextData { userData: UserData | null, login: (username: string, password: string, abort: AbortSignal) => Promise, logout: () => void, + running: boolean, } @@ -31,11 +32,20 @@ export function LoginContextProvider({children}: LoginContextProps): JSX.Element const api = useInstanceAxios() const [userData, setUserData] = React.useState(null) + const [running, setRunning] = React.useState(false) const login = React.useCallback( async (username: string, password: string, abort: AbortSignal): Promise => { let response: AxiosResponse<{token: string}> - response = await api.post("/api/auth/token/", {username, password}, {signal: abort}) + + setRunning(true) + + try { + response = await api.post("/api/auth/token/", {username, password}, {signal: abort}) + } + finally { + setRunning(false) + } setUserData({ username: username, @@ -54,7 +64,7 @@ export function LoginContextProvider({children}: LoginContextProps): JSX.Element ) return ( - + ) } diff --git a/frontend/src/components/LogoutBox.tsx b/frontend/src/components/LogoutBox.tsx index cefb89c..30490c8 100644 --- a/frontend/src/components/LogoutBox.tsx +++ b/frontend/src/components/LogoutBox.tsx @@ -1,6 +1,6 @@ import * as React from "react" import * as ReactDOM from "react-dom" -import {Box, Heading, Form, Panel} from "@steffo/bluelib-react"; +import {Box, Heading, Form, Panel, Variable} from "@steffo/bluelib-react"; import {useLogin} from "./LoginContext"; @@ -10,21 +10,29 @@ interface LogoutBoxProps { export function LogoutBox({}: LogoutBoxProps): JSX.Element { - const {logout} = useLogin() + const login = useLogin() + + if(!login.userData) { + console.log("LogoutBox displayed while the user wasn't logged in.") + return <> + } return ( Logout +

+ Logout from the Sophon instance to change user or instance URL. +

- Logout from the Sophon instance to change user or instance URL. + You are currently logged in as {login.userData.username}. - + Logout diff --git a/frontend/src/routes/LoginPage.tsx b/frontend/src/routes/LoginPage.tsx index 3ece53a..d60cbe4 100644 --- a/frontend/src/routes/LoginPage.tsx +++ b/frontend/src/routes/LoginPage.tsx @@ -5,6 +5,7 @@ import {Chapter, Heading} from "@steffo/bluelib-react"; import {LoginBox} from "../components/LoginBox"; import {useLogin} from "../components/LoginContext"; import {LogoutBox} from "../components/LogoutBox"; +import {GuestBox} from "../components/GuestBox"; interface LoginPageProps { @@ -17,11 +18,15 @@ export function LoginPage({}: LoginPageProps): JSX.Element { return ( <> - - Sophon - - {userData ? : } + {userData ? + + : + + + + + } ) } diff --git a/frontend/src/routes/Router.jsx b/frontend/src/routes/Router.jsx index 5a578a9..e757707 100644 --- a/frontend/src/routes/Router.jsx +++ b/frontend/src/routes/Router.jsx @@ -2,12 +2,16 @@ import * as React from "react" import * as ReactDOM from "react-dom" import * as Reach from "@reach/router" import {LoginPage} from "./LoginPage"; +import { Heading } from "@steffo/bluelib-react" export function Router({}) { - return ( + return <> + + Sophon + - + - ) + }