mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-22 23:04:21 +00:00
✨ Complete login (finally)
This commit is contained in:
parent
870caa098b
commit
a20aa0162a
7 changed files with 154 additions and 38 deletions
76
frontend/src/components/GuestBox.tsx
Normal file
76
frontend/src/components/GuestBox.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
</>
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue