1
Fork 0
mirror of https://github.com/Steffo99/sophon.git synced 2024-12-22 23:04:21 +00:00

💥 Add icons and refactor authorization components

This commit is contained in:
Steffo 2021-10-16 03:30:32 +02:00 committed by Stefano Pigozzi
parent 4575b6481e
commit 7fcab7ad1d
11 changed files with 233 additions and 150 deletions

View file

@ -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 {Box, Form, Heading, Idiomatic as I} from "@steffo/bluelib-react"
import * as React from "react" import * as React from "react"
import {useAuthorizationContext} from "../../contexts/authorization" 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 { export function AuthorizationAdminBox(): JSX.Element {
const instance = useInstanceContext()
const authorization = useAuthorizationContext() const authorization = useAuthorizationContext()
const canAdministrate = const enabled =
React.useMemo( React.useMemo(
() => ( () => authorization && !authorization.state.running && authorization.state.token === undefined,
authorization !== undefined && authorization.state.token === undefined && !authorization.state.running
),
[authorization], [authorization],
) )
const doAdministrate =
React.useCallback(
async () => {
if(!instance) {
return
}
if(!instance.state.url) {
return
}
await navigate(`${instance.state.url}admin`)
},
[instance],
)
return ( return (
<Box disabled={!canAdministrate}> <Box disabled={!enabled}>
<Heading level={3}> <Heading level={3}>
Server administration <IconText icon={faCog}>
Server administration
</IconText>
</Heading> </Heading>
<p> <p>
To configure the Sophon server, an account with <I>superuser</I> access is required. To configure the Sophon server, an account with <I>superuser</I> access is required.
@ -46,9 +35,7 @@ export function AuthorizationAdminBox(): JSX.Element {
</p> </p>
<Form> <Form>
<Form.Row> <Form.Row>
<Form.Button disabled={!canAdministrate} onClick={doAdministrate}> <AuthorizationAdminPageButton disabled={!enabled}/>
Administrate
</Form.Button>
</Form.Row> </Form.Row>
</Form> </Form>
</Box> </Box>

View file

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

View file

@ -1,8 +1,10 @@
import {faUser} from "@fortawesome/free-regular-svg-icons"
import {faLock, faUniversity} from "@fortawesome/free-solid-svg-icons" import {faLock, faUniversity} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import {Box, Form, Heading, Idiomatic as I} from "@steffo/bluelib-react" import {Box, Form, Heading, Idiomatic as I} from "@steffo/bluelib-react"
import * as React from "react" import * as React from "react"
import {useAuthorizationContext} from "../../contexts/authorization" import {useAuthorizationContext} from "../../contexts/authorization"
import {IconText} from "../elements/IconText"
export function AuthorizationBrowseBox(): JSX.Element { export function AuthorizationBrowseBox(): JSX.Element {
@ -18,15 +20,7 @@ export function AuthorizationBrowseBox(): JSX.Element {
const doBrowse = const doBrowse =
React.useCallback( React.useCallback(
async () => { () => authorization!.dispatch({type: "browse"}),
if(!authorization) {
return
}
authorization.dispatch({
type: "browse",
})
},
[authorization], [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 // 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}> <Box disabled={!canBrowse}>
<Heading level={3}> <Heading level={3}>
Browse as guest <IconText icon={faUser}>
Browse as guest
</IconText>
</Heading> </Heading>
<p> <p>
You can browse Sophon without an account. You can browse Sophon without an account.
@ -45,7 +41,9 @@ export function AuthorizationBrowseBox(): JSX.Element {
<Form> <Form>
<Form.Row> <Form.Row>
<Form.Button disabled={!canBrowse} onClick={doBrowse}> <Form.Button disabled={!canBrowse} onClick={doBrowse}>
Browse <IconText icon={faUser}>
Browse as guest
</IconText>
</Form.Button> </Form.Button>
</Form.Row> </Form.Row>
</Form> </Form>

View file

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

View file

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

View file

@ -1,11 +1,10 @@
import {faExclamationCircle} from "@fortawesome/free-solid-svg-icons" import {faUser} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import {Box, Form, Heading, useFormState} from "@steffo/bluelib-react" import {Box, Form, Heading, useFormState} from "@steffo/bluelib-react"
import * as React from "react" import * as React from "react"
import {useAuthorizationContext} from "../../contexts/authorization" import {useAuthorizationContext} from "../../contexts/authorization"
import {SophonToken, SophonUser} from "../../types/SophonTypes" import {SophonToken, SophonUser} from "../../types/SophonTypes"
import {Validators} from "../../utils/Validators" import {Validators} from "../../utils/Validators"
import {Loading} from "../elements/Loading" import {IconText} from "../elements/IconText"
import {useInstanceAxios} from "../instance/useInstanceAxios" import {useInstanceAxios} from "../instance/useInstanceAxios"
@ -74,9 +73,6 @@ export function AuthorizationLoginBox(): JSX.Element {
if(!authorization) { if(!authorization) {
return "" return ""
} }
if(authorization.state.running) {
return "color-yellow"
}
if(error) { if(error) {
return "color-red" return "color-red"
} }
@ -85,24 +81,12 @@ export function AuthorizationLoginBox(): JSX.Element {
[authorization, error], [authorization, error],
) )
const buttonText =
React.useMemo<JSX.Element | null>(
() => {
if(authorization?.state.running) {
return <Loading text={"Logging in..."}/>
}
if(error) {
return <><FontAwesomeIcon icon={faExclamationCircle}/>&nbsp;Login</>
}
return <>Login</>
},
[error, authorization],
)
return ( return (
<Box disabled={!canLogin}> <Box disabled={!canLogin}>
<Heading level={3}> <Heading level={3}>
Login <IconText icon={faUser}>
Login
</IconText>
</Heading> </Heading>
<p> <p>
To use most features of Sophon, an account is required. To use most features of Sophon, an account is required.
@ -128,7 +112,9 @@ export function AuthorizationLoginBox(): JSX.Element {
/> />
<Form.Row> <Form.Row>
<Form.Button type={"submit"} bluelibClassNames={buttonColor} disabled={!canClickLogin} onClick={doLogin}> <Form.Button type={"submit"} bluelibClassNames={buttonColor} disabled={!canClickLogin} onClick={doLogin}>
{buttonText} <IconText icon={faUser} spin={authorization?.state.running}>
Login
</IconText>
</Form.Button> </Form.Button>
</Form.Row> </Form.Row>
</Form> </Form>

View file

@ -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}/>&nbsp;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}/>&nbsp;Logout
</Form.Button>
</Form.Row>
</Form>
</Box>
)
}

View file

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

View file

@ -3,8 +3,8 @@ import * as React from "react"
import {useAuthorizationContext} from "../../contexts/authorization" import {useAuthorizationContext} from "../../contexts/authorization"
import {AuthorizationAdminBox} from "./AuthorizationAdminBox" import {AuthorizationAdminBox} from "./AuthorizationAdminBox"
import {AuthorizationBrowseBox} from "./AuthorizationBrowseBox" import {AuthorizationBrowseBox} from "./AuthorizationBrowseBox"
import {AuthorizationLoggedInBox} from "./AuthorizationLoggedInBox"
import {AuthorizationLoginBox} from "./AuthorizationLoginBox" import {AuthorizationLoginBox} from "./AuthorizationLoginBox"
import {AuthorizationLogoutBox} from "./AuthorizationLogoutBox"
export function AuthorizationStepPage(): JSX.Element | null { export function AuthorizationStepPage(): JSX.Element | null {
@ -18,7 +18,7 @@ export function AuthorizationStepPage(): JSX.Element | null {
if(authorization.state.token === null) { if(authorization.state.token === null) {
return ( return (
<Chapter> <Chapter>
<AuthorizationLogoutBox/> <AuthorizationLoggedInBox/>
<AuthorizationLoginBox/> <AuthorizationLoginBox/>
<AuthorizationAdminBox/> <AuthorizationAdminBox/>
</Chapter> </Chapter>
@ -28,7 +28,7 @@ export function AuthorizationStepPage(): JSX.Element | null {
return ( return (
<Chapter> <Chapter>
<AuthorizationBrowseBox/> <AuthorizationBrowseBox/>
<AuthorizationLogoutBox/> <AuthorizationLoggedInBox/>
<AuthorizationAdminBox/> <AuthorizationAdminBox/>
</Chapter> </Chapter>
) )

View 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}/>&nbsp;{children}
</span>
)
}

View file

@ -39,4 +39,12 @@ export interface WithViewSet<T> {
*/ */
export interface WithSlug { export interface WithSlug {
slug: string, slug: string,
}
/**
* Props of an object that can be disabled.
*/
export interface CanBeDisabled {
disabled?: boolean,
} }