mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-22 14:54:22 +00:00
💥 Add icons and refactor authorization components
This commit is contained in:
parent
285105fe00
commit
dbf4ee8d23
11 changed files with 233 additions and 150 deletions
|
@ -1,42 +1,31 @@
|
|||
import {navigate} from "@reach/router"
|
||||
import {faCog} from "@fortawesome/free-solid-svg-icons"
|
||||
import {Box, Form, Heading, Idiomatic as I} from "@steffo/bluelib-react"
|
||||
import * as React from "react"
|
||||
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||
import {useInstanceContext} from "../../contexts/instance"
|
||||
import {IconText} from "../elements/IconText"
|
||||
import {AuthorizationAdminPageButton} from "./AuthorizationAdminPageButton"
|
||||
|
||||
|
||||
/**
|
||||
* Box that allows the user to access the Sophon administration page.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
export function AuthorizationAdminBox(): JSX.Element {
|
||||
const instance = useInstanceContext()
|
||||
const authorization = useAuthorizationContext()
|
||||
|
||||
const canAdministrate =
|
||||
const enabled =
|
||||
React.useMemo(
|
||||
() => (
|
||||
authorization !== undefined && authorization.state.token === undefined && !authorization.state.running
|
||||
),
|
||||
() => authorization && !authorization.state.running && authorization.state.token === undefined,
|
||||
[authorization],
|
||||
)
|
||||
|
||||
const doAdministrate =
|
||||
React.useCallback(
|
||||
async () => {
|
||||
if(!instance) {
|
||||
return
|
||||
}
|
||||
if(!instance.state.url) {
|
||||
return
|
||||
}
|
||||
|
||||
await navigate(`${instance.state.url}admin`)
|
||||
},
|
||||
[instance],
|
||||
)
|
||||
|
||||
|
||||
return (
|
||||
<Box disabled={!canAdministrate}>
|
||||
<Box disabled={!enabled}>
|
||||
<Heading level={3}>
|
||||
Server administration
|
||||
<IconText icon={faCog}>
|
||||
Server administration
|
||||
</IconText>
|
||||
</Heading>
|
||||
<p>
|
||||
To configure the Sophon server, an account with <I>superuser</I> access is required.
|
||||
|
@ -46,9 +35,7 @@ export function AuthorizationAdminBox(): JSX.Element {
|
|||
</p>
|
||||
<Form>
|
||||
<Form.Row>
|
||||
<Form.Button disabled={!canAdministrate} onClick={doAdministrate}>
|
||||
Administrate
|
||||
</Form.Button>
|
||||
<AuthorizationAdminPageButton disabled={!enabled}/>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
</Box>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import {faCog} from "@fortawesome/free-solid-svg-icons"
|
||||
import {navigate} from "@reach/router"
|
||||
import {Button} from "@steffo/bluelib-react"
|
||||
import * as React from "react"
|
||||
import {useInstanceContext} from "../../contexts/instance"
|
||||
import {CanBeDisabled} from "../../types/ExtraTypes"
|
||||
import {IconText} from "../elements/IconText"
|
||||
|
||||
|
||||
export function AuthorizationAdminPageButton({disabled = false}: CanBeDisabled): JSX.Element {
|
||||
const instance = useInstanceContext()
|
||||
|
||||
const canAdministrate =
|
||||
React.useMemo(
|
||||
() => !disabled && instance,
|
||||
[disabled, instance],
|
||||
)
|
||||
|
||||
const doAdministrate =
|
||||
React.useCallback(
|
||||
async () => {
|
||||
await navigate(`${instance!.state.url}admin`)
|
||||
},
|
||||
[instance],
|
||||
)
|
||||
|
||||
|
||||
return (
|
||||
<Button disabled={!canAdministrate} onClick={doAdministrate}>
|
||||
<IconText icon={faCog}>
|
||||
Go to the admin page
|
||||
</IconText>
|
||||
</Button>
|
||||
)
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
import {faUser} from "@fortawesome/free-regular-svg-icons"
|
||||
import {faLock, faUniversity} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import {Box, Form, Heading, Idiomatic as I} from "@steffo/bluelib-react"
|
||||
import * as React from "react"
|
||||
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||
import {IconText} from "../elements/IconText"
|
||||
|
||||
|
||||
export function AuthorizationBrowseBox(): JSX.Element {
|
||||
|
@ -18,15 +20,7 @@ export function AuthorizationBrowseBox(): JSX.Element {
|
|||
|
||||
const doBrowse =
|
||||
React.useCallback(
|
||||
async () => {
|
||||
if(!authorization) {
|
||||
return
|
||||
}
|
||||
|
||||
authorization.dispatch({
|
||||
type: "browse",
|
||||
})
|
||||
},
|
||||
() => authorization!.dispatch({type: "browse"}),
|
||||
[authorization],
|
||||
)
|
||||
|
||||
|
@ -34,7 +28,9 @@ export function AuthorizationBrowseBox(): JSX.Element {
|
|||
// By disabling the box, the login box is highlighted while a login attempt is running, making the user focus on the login attempt
|
||||
<Box disabled={!canBrowse}>
|
||||
<Heading level={3}>
|
||||
Browse as guest
|
||||
<IconText icon={faUser}>
|
||||
Browse as guest
|
||||
</IconText>
|
||||
</Heading>
|
||||
<p>
|
||||
You can browse Sophon without an account.
|
||||
|
@ -45,7 +41,9 @@ export function AuthorizationBrowseBox(): JSX.Element {
|
|||
<Form>
|
||||
<Form.Row>
|
||||
<Form.Button disabled={!canBrowse} onClick={doBrowse}>
|
||||
Browse
|
||||
<IconText icon={faUser}>
|
||||
Browse as guest
|
||||
</IconText>
|
||||
</Form.Button>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import {faSignInAlt} from "@fortawesome/free-solid-svg-icons"
|
||||
import {navigate} from "@reach/router"
|
||||
import {Button} from "@steffo/bluelib-react"
|
||||
import * as React from "react"
|
||||
import {useInstanceContext} from "../../contexts/instance"
|
||||
import {useSophonPath} from "../../hooks/useSophonPath"
|
||||
import {CanBeDisabled} from "../../types/ExtraTypes"
|
||||
import {IconText} from "../elements/IconText"
|
||||
|
||||
|
||||
export function AuthorizationGoToSophonButton({disabled = false}: CanBeDisabled): JSX.Element {
|
||||
const instance = useInstanceContext()
|
||||
const location = useSophonPath()
|
||||
|
||||
const canGoTo =
|
||||
React.useMemo(
|
||||
() => !disabled && instance && !location.loggedIn,
|
||||
[disabled, instance, location],
|
||||
)
|
||||
|
||||
const doGoTo =
|
||||
React.useCallback(
|
||||
() => navigate("l/logged-in/"),
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<Button onClick={doGoTo} disabled={!canGoTo}>
|
||||
<IconText icon={faSignInAlt}>
|
||||
Go to {instance?.state.details?.name}
|
||||
</IconText>
|
||||
</Button>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import {faUser as faUserRegular} from "@fortawesome/free-regular-svg-icons"
|
||||
import {faClipboardCheck, faUser as faUserSolid} from "@fortawesome/free-solid-svg-icons"
|
||||
import {Box, BringAttention as B, Form, Heading} from "@steffo/bluelib-react"
|
||||
import * as React from "react"
|
||||
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||
import {IconText} from "../elements/IconText"
|
||||
import {AuthorizationGoToSophonButton} from "./AuthorizationGoToSophonButton"
|
||||
import {AuthorizationLogoutButton} from "./AuthorizationLogoutButton"
|
||||
|
||||
|
||||
export function AuthorizationLoggedInBox(): JSX.Element {
|
||||
const authorization = useAuthorizationContext()
|
||||
|
||||
const loggedUsername = React.useMemo(
|
||||
() => {
|
||||
if(!authorization?.state.user) {
|
||||
return (
|
||||
<B>
|
||||
<IconText icon={faUserRegular}>
|
||||
a guest
|
||||
</IconText>
|
||||
</B>
|
||||
)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<B>
|
||||
<IconText icon={faUserSolid}>
|
||||
{authorization.state.user.username}
|
||||
</IconText>
|
||||
</B>
|
||||
)
|
||||
}
|
||||
},
|
||||
[authorization],
|
||||
)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Heading level={3}>
|
||||
<IconText icon={faClipboardCheck}>
|
||||
Logged in
|
||||
</IconText>
|
||||
</Heading>
|
||||
<p>
|
||||
You are currently logged in as {loggedUsername}.
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Row>
|
||||
<AuthorizationGoToSophonButton/>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
<p>
|
||||
To use a different account with Sophon, you'll need to logout from your current one first.
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Row>
|
||||
<AuthorizationLogoutButton/>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
</Box>
|
||||
)
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import {faExclamationCircle} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import {faUser} from "@fortawesome/free-solid-svg-icons"
|
||||
import {Box, Form, Heading, useFormState} from "@steffo/bluelib-react"
|
||||
import * as React from "react"
|
||||
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||
import {SophonToken, SophonUser} from "../../types/SophonTypes"
|
||||
import {Validators} from "../../utils/Validators"
|
||||
import {Loading} from "../elements/Loading"
|
||||
import {IconText} from "../elements/IconText"
|
||||
import {useInstanceAxios} from "../instance/useInstanceAxios"
|
||||
|
||||
|
||||
|
@ -74,9 +73,6 @@ export function AuthorizationLoginBox(): JSX.Element {
|
|||
if(!authorization) {
|
||||
return ""
|
||||
}
|
||||
if(authorization.state.running) {
|
||||
return "color-yellow"
|
||||
}
|
||||
if(error) {
|
||||
return "color-red"
|
||||
}
|
||||
|
@ -85,24 +81,12 @@ export function AuthorizationLoginBox(): JSX.Element {
|
|||
[authorization, error],
|
||||
)
|
||||
|
||||
const buttonText =
|
||||
React.useMemo<JSX.Element | null>(
|
||||
() => {
|
||||
if(authorization?.state.running) {
|
||||
return <Loading text={"Logging in..."}/>
|
||||
}
|
||||
if(error) {
|
||||
return <><FontAwesomeIcon icon={faExclamationCircle}/> Login</>
|
||||
}
|
||||
return <>Login</>
|
||||
},
|
||||
[error, authorization],
|
||||
)
|
||||
|
||||
return (
|
||||
<Box disabled={!canLogin}>
|
||||
<Heading level={3}>
|
||||
Login
|
||||
<IconText icon={faUser}>
|
||||
Login
|
||||
</IconText>
|
||||
</Heading>
|
||||
<p>
|
||||
To use most features of Sophon, an account is required.
|
||||
|
@ -128,7 +112,9 @@ export function AuthorizationLoginBox(): JSX.Element {
|
|||
/>
|
||||
<Form.Row>
|
||||
<Form.Button type={"submit"} bluelibClassNames={buttonColor} disabled={!canClickLogin} onClick={doLogin}>
|
||||
{buttonText}
|
||||
<IconText icon={faUser} spin={authorization?.state.running}>
|
||||
Login
|
||||
</IconText>
|
||||
</Form.Button>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
import {faUser as faUserRegular} from "@fortawesome/free-regular-svg-icons"
|
||||
import {faChevronRight, faExclamationCircle, faSignOutAlt, faTimesCircle, faUser as faUserSolid} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import {navigate} from "@reach/router"
|
||||
import {Box, Form, Heading, Idiomatic as I} from "@steffo/bluelib-react"
|
||||
import * as React from "react"
|
||||
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||
|
||||
|
||||
export function AuthorizationLogoutBox(): JSX.Element {
|
||||
const authorization = useAuthorizationContext()
|
||||
|
||||
const loggedUsername = React.useMemo(
|
||||
() => {
|
||||
if(!authorization) {
|
||||
return <I bluelibClassNames={"color-red"}>
|
||||
<FontAwesomeIcon icon={faExclamationCircle}/> Not in a AuthorizationContext
|
||||
</I>
|
||||
}
|
||||
else if(authorization.state.user === undefined) {
|
||||
return <I bluelibClassNames={"color-red"}>
|
||||
<FontAwesomeIcon icon={faTimesCircle}/> Not logged in
|
||||
</I>
|
||||
}
|
||||
else if(authorization.state.user === null) {
|
||||
return <I>
|
||||
<FontAwesomeIcon icon={faUserRegular}/> a guest
|
||||
</I>
|
||||
}
|
||||
else {
|
||||
return <I>
|
||||
<FontAwesomeIcon icon={faUserSolid}/> {authorization.state.user.username}
|
||||
</I>
|
||||
}
|
||||
},
|
||||
[authorization],
|
||||
)
|
||||
|
||||
const canLogout = React.useMemo(
|
||||
() => (
|
||||
authorization !== undefined && authorization.state.user !== undefined && !authorization.state.running
|
||||
),
|
||||
[authorization],
|
||||
)
|
||||
|
||||
const doLogout = React.useCallback(
|
||||
() => {
|
||||
if(!authorization) {
|
||||
return
|
||||
}
|
||||
|
||||
authorization.dispatch({
|
||||
type: "clear",
|
||||
})
|
||||
},
|
||||
[authorization],
|
||||
)
|
||||
|
||||
return (
|
||||
<Box disabled={!canLogout}>
|
||||
<Heading level={3}>
|
||||
Logout
|
||||
</Heading>
|
||||
<p>
|
||||
You are currently logged in as {loggedUsername}.
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Row>
|
||||
<Form.Button onClick={() => navigate("l/logged-in/")}>
|
||||
<FontAwesomeIcon icon={faChevronRight}/> Continue to Sophon
|
||||
</Form.Button>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
<p>
|
||||
To use a different account with Sophon, you'll need to logout from your current one first.
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Row>
|
||||
<Form.Button disabled={!canLogout} onClick={doLogout}>
|
||||
<FontAwesomeIcon icon={faSignOutAlt}/> Logout
|
||||
</Form.Button>
|
||||
</Form.Row>
|
||||
</Form>
|
||||
</Box>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import {faSignOutAlt} from "@fortawesome/free-solid-svg-icons"
|
||||
import {Button} from "@steffo/bluelib-react"
|
||||
import * as React from "react"
|
||||
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||
import {CanBeDisabled} from "../../types/ExtraTypes"
|
||||
import {IconText} from "../elements/IconText"
|
||||
|
||||
|
||||
export function AuthorizationLogoutButton({disabled = false}: CanBeDisabled): JSX.Element {
|
||||
const authorization =
|
||||
useAuthorizationContext()
|
||||
|
||||
const canLogout =
|
||||
React.useMemo(
|
||||
() => (
|
||||
!disabled && authorization !== undefined && authorization.state.user !== undefined && !authorization.state.running
|
||||
),
|
||||
[disabled, authorization],
|
||||
)
|
||||
|
||||
const onClick =
|
||||
React.useCallback(
|
||||
() => authorization!.dispatch({type: "clear"}),
|
||||
[authorization],
|
||||
)
|
||||
|
||||
return (
|
||||
<Button disabled={!canLogout} onClick={onClick}>
|
||||
<IconText icon={faSignOutAlt}>
|
||||
Logout
|
||||
</IconText>
|
||||
</Button>
|
||||
)
|
||||
}
|
|
@ -3,8 +3,8 @@ import * as React from "react"
|
|||
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||
import {AuthorizationAdminBox} from "./AuthorizationAdminBox"
|
||||
import {AuthorizationBrowseBox} from "./AuthorizationBrowseBox"
|
||||
import {AuthorizationLoggedInBox} from "./AuthorizationLoggedInBox"
|
||||
import {AuthorizationLoginBox} from "./AuthorizationLoginBox"
|
||||
import {AuthorizationLogoutBox} from "./AuthorizationLogoutBox"
|
||||
|
||||
|
||||
export function AuthorizationStepPage(): JSX.Element | null {
|
||||
|
@ -18,7 +18,7 @@ export function AuthorizationStepPage(): JSX.Element | null {
|
|||
if(authorization.state.token === null) {
|
||||
return (
|
||||
<Chapter>
|
||||
<AuthorizationLogoutBox/>
|
||||
<AuthorizationLoggedInBox/>
|
||||
<AuthorizationLoginBox/>
|
||||
<AuthorizationAdminBox/>
|
||||
</Chapter>
|
||||
|
@ -28,7 +28,7 @@ export function AuthorizationStepPage(): JSX.Element | null {
|
|||
return (
|
||||
<Chapter>
|
||||
<AuthorizationBrowseBox/>
|
||||
<AuthorizationLogoutBox/>
|
||||
<AuthorizationLoggedInBox/>
|
||||
<AuthorizationAdminBox/>
|
||||
</Chapter>
|
||||
)
|
||||
|
|
24
frontend/src/components/elements/IconText.tsx
Normal file
24
frontend/src/components/elements/IconText.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {FontAwesomeIcon, FontAwesomeIconProps} from "@fortawesome/react-fontawesome"
|
||||
import * as React from "react"
|
||||
|
||||
|
||||
/**
|
||||
* The props of {@link IconText}.
|
||||
*/
|
||||
export interface IconTextProps extends FontAwesomeIconProps {
|
||||
children: React.ReactNode,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A {@link FontAwesomeIcon} (`icon`) followed by some text (`children`).
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
export function IconText({children, ...props}: IconTextProps): JSX.Element {
|
||||
return (
|
||||
<span>
|
||||
<FontAwesomeIcon {...props}/> {children}
|
||||
</span>
|
||||
)
|
||||
}
|
|
@ -40,3 +40,11 @@ export interface WithViewSet<T> {
|
|||
export interface WithSlug {
|
||||
slug: string,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Props of an object that can be disabled.
|
||||
*/
|
||||
export interface CanBeDisabled {
|
||||
disabled?: boolean,
|
||||
}
|
Loading…
Reference in a new issue