mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-23 07:14: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="processLiterals" value="true" />
|
||||||
<option name="processComments" value="true" />
|
<option name="processComments" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="TrivialIfJS" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
|
@ -4,7 +4,7 @@
|
||||||
<command value="start" />
|
<command value="start" />
|
||||||
<node-interpreter value="project" />
|
<node-interpreter value="project" />
|
||||||
<envs>
|
<envs>
|
||||||
<env name="REACT_APP_DEFAULT_INSTANCE_URL" value="http://localhost:30033" />
|
<env name="REACT_APP_DEFAULT_INSTANCE_URL" value="" />
|
||||||
</envs>
|
</envs>
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@reach/router": "^1.3.4",
|
||||||
"@steffo/bluelib-react": "^3.0.7",
|
"@steffo/bluelib-react": "^3.0.7",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
|
@ -41,5 +42,8 @@
|
||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari 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 {SophonContextProvider} from "./utils/SophonContext";
|
||||||
import {LoginBox} from "./components/LoginBox";
|
import {LoginBox} from "./components/LoginBox";
|
||||||
import {InstanceBox} from "./components/InstanceBox";
|
import {InstanceBox} from "./components/InstanceBox";
|
||||||
|
import {GuestBox} from "./components/GuestBox";
|
||||||
|
import {Router} from "./routes/Router";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
|
@ -13,10 +15,7 @@ function App() {
|
||||||
<Heading level={1}>
|
<Heading level={1}>
|
||||||
Sophon
|
Sophon
|
||||||
</Heading>
|
</Heading>
|
||||||
<Chapter>
|
<Router/>
|
||||||
<InstanceBox/>
|
|
||||||
<LoginBox/>
|
|
||||||
</Chapter>
|
|
||||||
</LayoutThreeCol.Center>
|
</LayoutThreeCol.Center>
|
||||||
</LayoutThreeCol>
|
</LayoutThreeCol>
|
||||||
</Bluelib>
|
</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 React from "react"
|
||||||
import * as ReactDOM from "react-dom"
|
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 {useSophonContext} from "../utils/SophonContext";
|
||||||
import {useFormProps} from "../hooks/useValidatedState";
|
import axios, {AxiosResponse} from "axios";
|
||||||
|
import {useCallback} from "react";
|
||||||
|
|
||||||
|
|
||||||
interface InstanceBoxProps {
|
interface InstanceBoxProps {
|
||||||
|
@ -12,25 +13,43 @@ interface InstanceBoxProps {
|
||||||
|
|
||||||
export function InstanceBox({}: InstanceBoxProps): JSX.Element {
|
export function InstanceBox({}: InstanceBoxProps): JSX.Element {
|
||||||
const {instanceUrl, changeSophon} = useSophonContext()
|
const {instanceUrl, changeSophon} = useSophonContext()
|
||||||
const sophonServer = useFormProps("", value => {
|
|
||||||
if(value === "") return null
|
|
||||||
|
|
||||||
|
const sophonInstanceValidator
|
||||||
|
= useCallback(
|
||||||
|
async (value, abort) => {
|
||||||
|
if(value === "") return undefined
|
||||||
|
if(value === instanceUrl) return undefined
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 250))
|
||||||
|
if(abort.aborted) return null
|
||||||
|
|
||||||
|
let url: URL
|
||||||
try {
|
try {
|
||||||
new URL(value)
|
url = new URL(value)
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
try {
|
||||||
})
|
await axios.get("api/core/version", {baseURL: url.toString()})
|
||||||
|
} catch(_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const doChange = React.useCallback(
|
return true
|
||||||
() => {
|
|
||||||
changeSophon(sophonServer.value.trim())
|
|
||||||
// Small hack to clear the field
|
|
||||||
sophonServer.onSimpleChange("")
|
|
||||||
},
|
},
|
||||||
[changeSophon, sophonServer]
|
[instanceUrl]
|
||||||
|
)
|
||||||
|
|
||||||
|
const sophonInstance
|
||||||
|
= useFormState(instanceUrl, sophonInstanceValidator)
|
||||||
|
|
||||||
|
const doChange
|
||||||
|
= React.useCallback(
|
||||||
|
() => {
|
||||||
|
changeSophon(new URL(sophonInstance.value).toString())
|
||||||
|
},
|
||||||
|
[changeSophon, sophonInstance]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -39,12 +58,12 @@ export function InstanceBox({}: InstanceBoxProps): JSX.Element {
|
||||||
Change instance
|
Change instance
|
||||||
</Heading>
|
</Heading>
|
||||||
<p>
|
<p>
|
||||||
You are currently using the Sophon instance at <Anchor href={instanceUrl}>{instanceUrl}</Anchor>.
|
Select the Sophon instance you want to connect to.
|
||||||
</p>
|
</p>
|
||||||
<Form>
|
<Form>
|
||||||
<Form.Field label={"URL"} {...sophonServer}/>
|
<Form.Field label={"URL"} {...sophonInstance}/>
|
||||||
<Form.Row>
|
<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.Row>
|
||||||
</Form>
|
</Form>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
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, Parenthesis} from "@steffo/bluelib-react";
|
import {Box, Heading, Form, Parenthesis, Variable, useFormState} from "@steffo/bluelib-react";
|
||||||
import {useSophonContext} from "../utils/SophonContext";
|
import {useSophonContext} from "../utils/SophonContext";
|
||||||
import {useFormProps} from "../hooks/useValidatedState";
|
|
||||||
|
|
||||||
|
|
||||||
interface LoginBoxProps {
|
interface LoginBoxProps {
|
||||||
|
@ -14,19 +13,22 @@ export function LoginBox({...props}: LoginBoxProps): JSX.Element {
|
||||||
const {loginData, loginError, login, logout} = useSophonContext()
|
const {loginData, loginError, login, logout} = useSophonContext()
|
||||||
|
|
||||||
const username
|
const username
|
||||||
= useFormProps("",
|
= useFormState("",
|
||||||
val => {
|
val => {
|
||||||
if(val === "") {
|
if(val === "") {
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const password
|
const password
|
||||||
= useFormProps("",
|
= useFormState("",
|
||||||
val => {
|
val => {
|
||||||
if(val === "") {
|
if(val === "") {
|
||||||
return null
|
return undefined
|
||||||
|
}
|
||||||
|
if(val.length < 8) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -39,10 +41,10 @@ export function LoginBox({...props}: LoginBoxProps): JSX.Element {
|
||||||
<Heading level={3}>
|
<Heading level={3}>
|
||||||
Login
|
Login
|
||||||
</Heading>
|
</Heading>
|
||||||
|
<p>
|
||||||
|
You are logged in as: <Variable>{loginData.username}</Variable>
|
||||||
|
</p>
|
||||||
<Form>
|
<Form>
|
||||||
<Form.Row>
|
|
||||||
You are logged in as: {loginData.username}
|
|
||||||
</Form.Row>
|
|
||||||
<Form.Row>
|
<Form.Row>
|
||||||
<Form.Button onClick={logout}>Logout</Form.Button>
|
<Form.Button onClick={logout}>Logout</Form.Button>
|
||||||
</Form.Row>
|
</Form.Row>
|
||||||
|
@ -56,6 +58,9 @@ export function LoginBox({...props}: LoginBoxProps): JSX.Element {
|
||||||
<Heading level={3}>
|
<Heading level={3}>
|
||||||
Login
|
Login
|
||||||
</Heading>
|
</Heading>
|
||||||
|
<p>
|
||||||
|
Login to the Sophon instance to access the full capabilities of Sophon.
|
||||||
|
</p>
|
||||||
<Form>
|
<Form>
|
||||||
{loginError ?
|
{loginError ?
|
||||||
<Form.Row>
|
<Form.Row>
|
||||||
|
@ -67,7 +72,7 @@ export function LoginBox({...props}: LoginBoxProps): JSX.Element {
|
||||||
<Form.Field label={"Username"} {...username}/>
|
<Form.Field label={"Username"} {...username}/>
|
||||||
<Form.Field label={"Password"} type={"password"} {...password}/>
|
<Form.Field label={"Password"} type={"password"} {...password}/>
|
||||||
<Form.Row>
|
<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
|
Login
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
</Form.Row>
|
</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...")
|
console.debug("Creating new AxiosInstance...")
|
||||||
return Axios.create({
|
return Axios.create({
|
||||||
baseURL: instanceUrl,
|
baseURL: instanceUrl,
|
||||||
timeout: 3000,
|
timeout: 5000,
|
||||||
headers: {
|
headers: {
|
||||||
...makeAuthorizationHeader(loginData)
|
...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