1
Fork 0
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:
Steffo 2021-09-14 23:34:18 +02:00
parent 87f84b3bbf
commit 4591c1d1fa
Signed by: steffo
GPG key ID: 6965406171929D01
12 changed files with 2025 additions and 1934 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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"
} }
} }

View file

@ -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>

View 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>
)
}

View file

@ -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 {
url = new URL(value)
} catch (_) {
return false
}
try { try {
new URL(value) await axios.get("api/core/version", {baseURL: url.toString()})
} catch(_) { } catch(_) {
return false return false
} }
return true return true
})
const doChange = React.useCallback(
() => {
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>

View file

@ -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>

View file

@ -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,
}
}

View 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>
</>
)
}

View 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>
)
}

View file

@ -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)
} }

File diff suppressed because it is too large Load diff