1
Fork 0
mirror of https://github.com/Steffo99/sophon.git synced 2024-12-23 07:14:21 +00:00

Complete login (finally)

This commit is contained in:
Steffo 2021-09-17 00:07:54 +02:00
parent 870caa098b
commit a20aa0162a
Signed by: steffo
GPG key ID: 6965406171929D01
7 changed files with 154 additions and 38 deletions

View file

@ -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<boolean>(
() => {
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 (
<Panel bluelibClassNames={"color-red"}>
Please enter a valid instance URL before continuing.
</Panel>
)
}
if(login.running) {
return (
<Panel bluelibClassNames={"color-yellow"}>
You cannot browse Sophon while a login is in progress.
</Panel>
)
}
return (
<Panel>
Click the button below to access Sophon.
</Panel>
)
},
[instance, login]
)
return (
<Box>
<Heading level={3}>
Continue as guest
</Heading>
<p>
You can browse Sophon without logging in, but many functions won't be available to you.
</p>
<Form>
<Form.Row>
{statePanel}
</Form.Row>
<Form.Row>
<Form.Button disabled={!canBrowse} onClick={async () => await navigate("/logged-in")}>
Browse
</Form.Button>
</Form.Row>
</Form>
</Box>
)
}

View file

@ -1,9 +1,8 @@
import * as React from "react" import * as React from "react"
import * as ReactDOM from "react-dom" 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 {useInstance} from "./InstanceContext";
import {useLogin} from "./LoginContext"; import {useLogin} from "./LoginContext";
import {Idiomatic as I} from "@steffo/bluelib-react/dist/components/semantics/Idiomatic";
interface InstanceBoxProps { interface InstanceBoxProps {
@ -13,34 +12,48 @@ interface InstanceBoxProps {
export function InstanceBox({}: InstanceBoxProps): JSX.Element { export function InstanceBox({}: InstanceBoxProps): JSX.Element {
const instance = useInstance() 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. * The state panel, displayed on top of the form.
*/ */
const statePanel = React.useMemo( const statePanel = React.useMemo(
() => { () => {
if(userData) { if(login.userData) {
return ( return (
<Panel bluelibClassNames={"color-red"}> <Panel bluelibClassNames={"color-yellow"}>
To change Sophon instance, please logout. You cannot change Sophon instance while you are logged in. If you need to change instance, <I>logout</I> first!
</Panel>
)
}
if(login.running) {
return (
<Panel bluelibClassNames={"color-yellow"}>
You cannot change Sophon instance while logging in.
</Panel> </Panel>
) )
} }
if(instance.validity === false) { if(instance.validity === false) {
return ( return (
<Panel bluelibClassNames={"color-red"}> <Panel bluelibClassNames={"color-red"}>
The specified instance is invalid. No Sophon instance was detected at the inserted URL.
</Panel> </Panel>
) )
} }
return ( return (
<Panel> <Panel>
Select the Sophon instance you want to connect to. Select the Sophon instance you want to connect to by specifying its URL here.
</Panel> </Panel>
) )
}, },
[userData, instance] [login, instance]
) )
return ( return (
@ -48,11 +61,14 @@ export function InstanceBox({}: InstanceBoxProps): JSX.Element {
<Heading level={3}> <Heading level={3}>
Instance select Instance select
</Heading> </Heading>
<p>
Sophon can be used by multiple institutions, each one using a physically separate instance.
</p>
<Form> <Form>
<Form.Row> <Form.Row>
{statePanel} {statePanel}
</Form.Row> </Form.Row>
<Form.Field label={"URL"} {...instance} validity={userData ? undefined : instance.validity} disabled={Boolean(userData)}/> <Form.Field label={"URL"} {...instance} validity={login.userData ? undefined : instance.validity} disabled={!canChange}/>
</Form> </Form>
</Box> </Box>
) )

View file

@ -22,7 +22,7 @@ export function LoginBox({}: LoginBoxProps): JSX.Element {
/** /**
* The {@link LoginContext}. * The {@link LoginContext}.
*/ */
const {login} = useLogin() const {login, running} = useLogin()
/** /**
* The {@link FormState} of the username field. * The {@link FormState} of the username field.
@ -75,11 +75,8 @@ export function LoginBox({}: LoginBoxProps): JSX.Element {
setError(e as AxiosError) setError(e as AxiosError)
return return
} }
finally {
// Clear the abort state await navigate("/logged-in")
// Possible race condition?
setAbort(null)
}
}, },
[abort, setAbort, username, password, login, setError] [abort, setAbort, username, password, login, setError]
) )
@ -89,9 +86,9 @@ export function LoginBox({}: LoginBoxProps): JSX.Element {
*/ */
const canLogin = React.useMemo<boolean>( const canLogin = React.useMemo<boolean>(
() => { () => {
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) { if(!instance.validity) {
return ( return (
<Panel> <Panel bluelibClassNames={"color-red"}>
Please enter a valid instance URL before logging in. Please enter a valid instance URL before logging in.
</Panel> </Panel>
) )
@ -129,20 +126,20 @@ export function LoginBox({}: LoginBoxProps): JSX.Element {
</Panel> </Panel>
) )
} }
if(abort) { if(running) {
return ( return (
<Panel bluelibClassNames={"color-yellow"}> <Panel bluelibClassNames={"color-cyan"}>
Logging in... Logging in, please wait...
</Panel> </Panel>
) )
} }
return ( return (
<Panel> <Panel>
Click the login button to begin the login procedure. Click the button below to login.
</Panel> </Panel>
) )
}, },
[error, instance, username, password, abort] [error, instance, username, password, running]
) )
return ( return (
@ -151,7 +148,7 @@ export function LoginBox({}: LoginBoxProps): JSX.Element {
Login Login
</Heading> </Heading>
<p> <p>
Authenticate yourself to access the full functionality of Sophon. Login as an authorized user to access the full functionality of Sophon.
</p> </p>
<Form> <Form>
<Form.Row> <Form.Row>

View file

@ -16,6 +16,7 @@ export interface LoginContextData {
userData: UserData | null, userData: UserData | null,
login: (username: string, password: string, abort: AbortSignal) => Promise<void>, login: (username: string, password: string, abort: AbortSignal) => Promise<void>,
logout: () => void, logout: () => void,
running: boolean,
} }
@ -31,11 +32,20 @@ export function LoginContextProvider({children}: LoginContextProps): JSX.Element
const api = useInstanceAxios() const api = useInstanceAxios()
const [userData, setUserData] = React.useState<UserData | null>(null) const [userData, setUserData] = React.useState<UserData | null>(null)
const [running, setRunning] = React.useState<boolean>(false)
const login = React.useCallback( const login = React.useCallback(
async (username: string, password: string, abort: AbortSignal): Promise<void> => { async (username: string, password: string, abort: AbortSignal): Promise<void> => {
let response: AxiosResponse<{token: string}> 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({ setUserData({
username: username, username: username,
@ -54,7 +64,7 @@ export function LoginContextProvider({children}: LoginContextProps): JSX.Element
) )
return ( return (
<LoginContext.Provider value={{userData, login, logout}} children={children}/> <LoginContext.Provider value={{userData, login, logout, running}} children={children}/>
) )
} }

View file

@ -1,6 +1,6 @@
import * as React from "react" import * as React from "react"
import * as ReactDOM from "react-dom" 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"; import {useLogin} from "./LoginContext";
@ -10,21 +10,29 @@ interface LogoutBoxProps {
export function LogoutBox({}: LogoutBoxProps): JSX.Element { 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 ( return (
<Box> <Box>
<Heading level={3}> <Heading level={3}>
Logout Logout
</Heading> </Heading>
<p>
Logout from the Sophon instance to change user or instance URL.
</p>
<Form> <Form>
<Form.Row> <Form.Row>
<Panel> <Panel>
Logout from the Sophon instance to change user or instance URL. You are currently logged in as <Variable>{login.userData.username}</Variable>.
</Panel> </Panel>
</Form.Row> </Form.Row>
<Form.Row> <Form.Row>
<Form.Button onClick={logout}> <Form.Button onClick={login.logout}>
Logout Logout
</Form.Button> </Form.Button>
</Form.Row> </Form.Row>

View file

@ -5,6 +5,7 @@ import {Chapter, Heading} from "@steffo/bluelib-react";
import {LoginBox} from "../components/LoginBox"; import {LoginBox} from "../components/LoginBox";
import {useLogin} from "../components/LoginContext"; import {useLogin} from "../components/LoginContext";
import {LogoutBox} from "../components/LogoutBox"; import {LogoutBox} from "../components/LogoutBox";
import {GuestBox} from "../components/GuestBox";
interface LoginPageProps { interface LoginPageProps {
@ -17,11 +18,15 @@ export function LoginPage({}: LoginPageProps): JSX.Element {
return ( return (
<> <>
<Heading level={1}>
Sophon
</Heading>
<InstanceBox/> <InstanceBox/>
{userData ? <LogoutBox/> : <LoginBox/>} {userData ?
<LogoutBox/>
:
<Chapter>
<GuestBox/>
<LoginBox/>
</Chapter>
}
</> </>
) )
} }

View file

@ -2,12 +2,16 @@ import * as React from "react"
import * as ReactDOM from "react-dom" import * as ReactDOM from "react-dom"
import * as Reach from "@reach/router" import * as Reach from "@reach/router"
import {LoginPage} from "./LoginPage"; import {LoginPage} from "./LoginPage";
import { Heading } from "@steffo/bluelib-react"
export function Router({}) { export function Router({}) {
return ( return <>
<Heading level={1}>
Sophon
</Heading>
<Reach.Router> <Reach.Router>
<LoginPage path={"/login"}/> <LoginPage path={"/"}/>
</Reach.Router> </Reach.Router>
) </>
} }