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

Complete login (finally)

This commit is contained in:
Steffo 2021-09-17 00:07:54 +02:00
parent 272e7a3c98
commit 2ff6c62995
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 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 (
<Panel bluelibClassNames={"color-red"}>
To change Sophon instance, please logout.
<Panel bluelibClassNames={"color-yellow"}>
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>
)
}
if(instance.validity === false) {
return (
<Panel bluelibClassNames={"color-red"}>
The specified instance is invalid.
No Sophon instance was detected at the inserted URL.
</Panel>
)
}
return (
<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>
)
},
[userData, instance]
[login, instance]
)
return (
@ -48,11 +61,14 @@ export function InstanceBox({}: InstanceBoxProps): JSX.Element {
<Heading level={3}>
Instance select
</Heading>
<p>
Sophon can be used by multiple institutions, each one using a physically separate instance.
</p>
<Form>
<Form.Row>
{statePanel}
</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>
</Box>
)

View file

@ -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<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) {
return (
<Panel>
<Panel bluelibClassNames={"color-red"}>
Please enter a valid instance URL before logging in.
</Panel>
)
@ -129,20 +126,20 @@ export function LoginBox({}: LoginBoxProps): JSX.Element {
</Panel>
)
}
if(abort) {
if(running) {
return (
<Panel bluelibClassNames={"color-yellow"}>
Logging in...
<Panel bluelibClassNames={"color-cyan"}>
Logging in, please wait...
</Panel>
)
}
return (
<Panel>
Click the login button to begin the login procedure.
Click the button below to login.
</Panel>
)
},
[error, instance, username, password, abort]
[error, instance, username, password, running]
)
return (
@ -151,7 +148,7 @@ export function LoginBox({}: LoginBoxProps): JSX.Element {
Login
</Heading>
<p>
Authenticate yourself to access the full functionality of Sophon.
Login as an authorized user to access the full functionality of Sophon.
</p>
<Form>
<Form.Row>

View file

@ -16,6 +16,7 @@ export interface LoginContextData {
userData: UserData | null,
login: (username: string, password: string, abort: AbortSignal) => Promise<void>,
logout: () => void,
running: boolean,
}
@ -31,11 +32,20 @@ export function LoginContextProvider({children}: LoginContextProps): JSX.Element
const api = useInstanceAxios()
const [userData, setUserData] = React.useState<UserData | null>(null)
const [running, setRunning] = React.useState<boolean>(false)
const login = React.useCallback(
async (username: string, password: string, abort: AbortSignal): Promise<void> => {
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 (
<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 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 (
<Box>
<Heading level={3}>
Logout
</Heading>
<p>
Logout from the Sophon instance to change user or instance URL.
</p>
<Form>
<Form.Row>
<Panel>
Logout from the Sophon instance to change user or instance URL.
You are currently logged in as <Variable>{login.userData.username}</Variable>.
</Panel>
</Form.Row>
<Form.Row>
<Form.Button onClick={logout}>
<Form.Button onClick={login.logout}>
Logout
</Form.Button>
</Form.Row>

View file

@ -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 (
<>
<Heading level={1}>
Sophon
</Heading>
<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 Reach from "@reach/router"
import {LoginPage} from "./LoginPage";
import { Heading } from "@steffo/bluelib-react"
export function Router({}) {
return (
return <>
<Heading level={1}>
Sophon
</Heading>
<Reach.Router>
<LoginPage path={"/login"}/>
<LoginPage path={"/"}/>
</Reach.Router>
)
</>
}