mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-22 23:04:21 +00:00
🚧 Do something about the login
This commit is contained in:
parent
87f84b3bbf
commit
4591c1d1fa
12 changed files with 2025 additions and 1934 deletions
|
@ -58,5 +58,6 @@
|
|||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TrivialIfJS" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
|
@ -4,7 +4,7 @@
|
|||
<command value="start" />
|
||||
<node-interpreter value="project" />
|
||||
<envs>
|
||||
<env name="REACT_APP_DEFAULT_INSTANCE_URL" value="http://localhost:30033" />
|
||||
<env name="REACT_APP_DEFAULT_INSTANCE_URL" value="" />
|
||||
</envs>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@reach/router": "^1.3.4",
|
||||
"@steffo/bluelib-react": "^3.0.7",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
|
@ -41,5 +42,8 @@
|
|||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/reach__router": "^1.3.9"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import {Bluelib, Chapter, Heading, LayoutThreeCol} from "@steffo/bluelib-react";
|
|||
import {SophonContextProvider} from "./utils/SophonContext";
|
||||
import {LoginBox} from "./components/LoginBox";
|
||||
import {InstanceBox} from "./components/InstanceBox";
|
||||
import {GuestBox} from "./components/GuestBox";
|
||||
import {Router} from "./routes/Router";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
|
@ -13,10 +15,7 @@ function App() {
|
|||
<Heading level={1}>
|
||||
Sophon
|
||||
</Heading>
|
||||
<Chapter>
|
||||
<InstanceBox/>
|
||||
<LoginBox/>
|
||||
</Chapter>
|
||||
<Router/>
|
||||
</LayoutThreeCol.Center>
|
||||
</LayoutThreeCol>
|
||||
</Bluelib>
|
||||
|
|
30
frontend/src/components/GuestBox.tsx
Normal file
30
frontend/src/components/GuestBox.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import * as React from "react"
|
||||
import * as ReactDOM from "react-dom"
|
||||
import * as Reach from "@reach/router"
|
||||
import {Box, Heading, Form} from "@steffo/bluelib-react";
|
||||
|
||||
|
||||
interface GuestBoxProps {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function GuestBox({}: GuestBoxProps): JSX.Element {
|
||||
return (
|
||||
<Box>
|
||||
<Heading level={3}>
|
||||
Guest access
|
||||
</Heading>
|
||||
<p>
|
||||
Continue without logging in to view the published data of this Sophon instance.
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Row>
|
||||
<Form.Button onClick={() => Reach.navigate("/home")}>
|
||||
Continue as guest
|
||||
</Form.Button>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
</Box>
|
||||
)
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import * as React from "react"
|
||||
import * as ReactDOM from "react-dom"
|
||||
import {Box, Heading, Form, Anchor} from "@steffo/bluelib-react";
|
||||
import {Box, Heading, Form, useFormState} from "@steffo/bluelib-react";
|
||||
import {useSophonContext} from "../utils/SophonContext";
|
||||
import {useFormProps} from "../hooks/useValidatedState";
|
||||
import axios, {AxiosResponse} from "axios";
|
||||
import {useCallback} from "react";
|
||||
|
||||
|
||||
interface InstanceBoxProps {
|
||||
|
@ -12,26 +13,44 @@ interface InstanceBoxProps {
|
|||
|
||||
export function InstanceBox({}: InstanceBoxProps): JSX.Element {
|
||||
const {instanceUrl, changeSophon} = useSophonContext()
|
||||
const sophonServer = useFormProps("", value => {
|
||||
if(value === "") return null
|
||||
|
||||
try {
|
||||
new URL(value)
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
const sophonInstanceValidator
|
||||
= useCallback(
|
||||
async (value, abort) => {
|
||||
if(value === "") return undefined
|
||||
if(value === instanceUrl) return undefined
|
||||
|
||||
return true
|
||||
})
|
||||
await new Promise(r => setTimeout(r, 250))
|
||||
if(abort.aborted) return null
|
||||
|
||||
const doChange = React.useCallback(
|
||||
() => {
|
||||
changeSophon(sophonServer.value.trim())
|
||||
// Small hack to clear the field
|
||||
sophonServer.onSimpleChange("")
|
||||
},
|
||||
[changeSophon, sophonServer]
|
||||
)
|
||||
let url: URL
|
||||
try {
|
||||
url = new URL(value)
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.get("api/core/version", {baseURL: url.toString()})
|
||||
} catch(_) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
[instanceUrl]
|
||||
)
|
||||
|
||||
const sophonInstance
|
||||
= useFormState(instanceUrl, sophonInstanceValidator)
|
||||
|
||||
const doChange
|
||||
= React.useCallback(
|
||||
() => {
|
||||
changeSophon(new URL(sophonInstance.value).toString())
|
||||
},
|
||||
[changeSophon, sophonInstance]
|
||||
)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
|
@ -39,12 +58,12 @@ export function InstanceBox({}: InstanceBoxProps): JSX.Element {
|
|||
Change instance
|
||||
</Heading>
|
||||
<p>
|
||||
You are currently using the Sophon instance at <Anchor href={instanceUrl}>{instanceUrl}</Anchor>.
|
||||
Select the Sophon instance you want to connect to.
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Field label={"URL"} {...sophonServer}/>
|
||||
<Form.Field label={"URL"} {...sophonInstance}/>
|
||||
<Form.Row>
|
||||
<Form.Button onClick={doChange} disabled={!sophonServer.validity}>Change instance</Form.Button>
|
||||
<Form.Button onClick={doChange} disabled={!sophonInstance.validity}>Change instance</Form.Button>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
</Box>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from "react"
|
||||
import * as ReactDOM from "react-dom"
|
||||
import {Box, Heading, Form, Parenthesis} from "@steffo/bluelib-react";
|
||||
import {Box, Heading, Form, Parenthesis, Variable, useFormState} from "@steffo/bluelib-react";
|
||||
import {useSophonContext} from "../utils/SophonContext";
|
||||
import {useFormProps} from "../hooks/useValidatedState";
|
||||
|
||||
|
||||
interface LoginBoxProps {
|
||||
|
@ -14,19 +13,22 @@ export function LoginBox({...props}: LoginBoxProps): JSX.Element {
|
|||
const {loginData, loginError, login, logout} = useSophonContext()
|
||||
|
||||
const username
|
||||
= useFormProps("",
|
||||
= useFormState("",
|
||||
val => {
|
||||
if(val === "") {
|
||||
return null
|
||||
return undefined
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
const password
|
||||
= useFormProps("",
|
||||
= useFormState("",
|
||||
val => {
|
||||
if(val === "") {
|
||||
return null
|
||||
return undefined
|
||||
}
|
||||
if(val.length < 8) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -39,10 +41,10 @@ export function LoginBox({...props}: LoginBoxProps): JSX.Element {
|
|||
<Heading level={3}>
|
||||
Login
|
||||
</Heading>
|
||||
<p>
|
||||
You are logged in as: <Variable>{loginData.username}</Variable>
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Row>
|
||||
You are logged in as: {loginData.username}
|
||||
</Form.Row>
|
||||
<Form.Row>
|
||||
<Form.Button onClick={logout}>Logout</Form.Button>
|
||||
</Form.Row>
|
||||
|
@ -56,6 +58,9 @@ export function LoginBox({...props}: LoginBoxProps): JSX.Element {
|
|||
<Heading level={3}>
|
||||
Login
|
||||
</Heading>
|
||||
<p>
|
||||
Login to the Sophon instance to access the full capabilities of Sophon.
|
||||
</p>
|
||||
<Form>
|
||||
{loginError ?
|
||||
<Form.Row>
|
||||
|
@ -67,7 +72,7 @@ export function LoginBox({...props}: LoginBoxProps): JSX.Element {
|
|||
<Form.Field label={"Username"} {...username}/>
|
||||
<Form.Field label={"Password"} type={"password"} {...password}/>
|
||||
<Form.Row>
|
||||
<Form.Button onClick={() => login(username.value, password.value)} bluelibClassNames={loginError ? "color-red" : ""}>
|
||||
<Form.Button disabled={!(username.validity && password.validity)} onClick={() => login(username.value, password.value)} bluelibClassNames={loginError ? "color-red" : ""}>
|
||||
Login
|
||||
</Form.Button>
|
||||
</Form.Row>
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import * as React from "react";
|
||||
import {Validity} from "@steffo/bluelib-react/dist/types";
|
||||
import {Form} from "@steffo/bluelib-react";
|
||||
|
||||
|
||||
/**
|
||||
* A function that checks if a value is acceptable or not for something.
|
||||
*
|
||||
* It can return:
|
||||
* - `true` if the value is acceptable
|
||||
* - `false` if the value is not acceptable
|
||||
* - `null` if no value has been entered by the user
|
||||
*/
|
||||
export type Validator<T> = (value: T) => Validity
|
||||
|
||||
|
||||
/**
|
||||
* The return type of the {@link useValidatedState} hook.
|
||||
*/
|
||||
export type ValidatedState<T> = [T, React.Dispatch<React.SetStateAction<T>>, Validity]
|
||||
|
||||
|
||||
/**
|
||||
* Hook that extends {@link React.useState} by applying a {@link Validator} to the stored value, returning its results to the caller.
|
||||
*
|
||||
* @param def - Default value for the state.
|
||||
* @param validator - The {@link Validator} to apply.
|
||||
*/
|
||||
export function useValidatedState<T>(def: T, validator: Validator<T>): ValidatedState<T> {
|
||||
const [value, setValue]
|
||||
= React.useState(def)
|
||||
|
||||
const validity
|
||||
= React.useMemo(
|
||||
() => {
|
||||
try {
|
||||
return validator(value)
|
||||
}
|
||||
catch (e) {
|
||||
return "error"
|
||||
}
|
||||
},
|
||||
[validator, value]
|
||||
)
|
||||
|
||||
return [value, setValue, validity]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hook that changes the return type of {@link useValidatedState} to a {@link Form}-friendly one.
|
||||
*
|
||||
* @param def - Default value for the state.
|
||||
* @param validator - The {@link Validator} to apply.
|
||||
*/
|
||||
export function useFormProps<T>(def: T, validator: Validator<T>) {
|
||||
const [value, setValue, validity] = useValidatedState<T>(def, validator)
|
||||
|
||||
return {
|
||||
value: value,
|
||||
onSimpleChange: setValue,
|
||||
validity: validity,
|
||||
}
|
||||
}
|
24
frontend/src/routes/AccountPage.tsx
Normal file
24
frontend/src/routes/AccountPage.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import * as React from "react"
|
||||
import * as ReactDOM from "react-dom"
|
||||
import {InstanceBox} from "../components/InstanceBox";
|
||||
import {Chapter} from "@steffo/bluelib-react";
|
||||
import {GuestBox} from "../components/GuestBox";
|
||||
import {LoginBox} from "../components/LoginBox";
|
||||
|
||||
|
||||
interface AccountPageProps {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function AccountPage({}: AccountPageProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<InstanceBox/>
|
||||
<Chapter>
|
||||
<GuestBox/>
|
||||
<LoginBox/>
|
||||
</Chapter>
|
||||
</>
|
||||
)
|
||||
}
|
13
frontend/src/routes/Router.jsx
Normal file
13
frontend/src/routes/Router.jsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import * as React from "react"
|
||||
import * as ReactDOM from "react-dom"
|
||||
import * as Reach from "@reach/router"
|
||||
import {AccountPage} from "./AccountPage";
|
||||
|
||||
|
||||
export function Router({}) {
|
||||
return (
|
||||
<Reach.Router>
|
||||
<AccountPage path={"/"}/>
|
||||
</Reach.Router>
|
||||
)
|
||||
}
|
|
@ -111,7 +111,7 @@ export function SophonContextProvider({children}: SophonContextProviderProps): J
|
|||
console.debug("Creating new AxiosInstance...")
|
||||
return Axios.create({
|
||||
baseURL: instanceUrl,
|
||||
timeout: 3000,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
...makeAuthorizationHeader(loginData)
|
||||
}
|
||||
|
|
3724
frontend/yarn.lock
3724
frontend/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue