From d197ffa5f2c0614927d6e7305f81a6574835728a Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 21 Sep 2021 17:32:05 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20user=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/LogoutBox.tsx | 4 +- .../src/components/ResearchGroupListBox.tsx | 8 +-- frontend/src/components/UserBox.tsx | 46 ++++++++++++++++ frontend/src/hooks/useDRF.ts | 52 +++++++++++++++---- ...ResearchGroupPage.tsx => InstancePage.tsx} | 2 +- frontend/src/routes/Router.jsx | 6 ++- frontend/src/routes/UserPage.tsx | 17 ++++++ 7 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 frontend/src/components/UserBox.tsx rename frontend/src/routes/{SelectResearchGroupPage.tsx => InstancePage.tsx} (84%) create mode 100644 frontend/src/routes/UserPage.tsx diff --git a/frontend/src/components/LogoutBox.tsx b/frontend/src/components/LogoutBox.tsx index 86aa3d4..587cf6c 100644 --- a/frontend/src/components/LogoutBox.tsx +++ b/frontend/src/components/LogoutBox.tsx @@ -1,5 +1,5 @@ import * as React from "react" -import {Box, Form, Heading, Panel, Variable} from "@steffo/bluelib-react"; +import {Box, Form, Heading, Panel, BringAttention as B} from "@steffo/bluelib-react"; import {useLogin} from "./LoginContext"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faUser} from "@fortawesome/free-solid-svg-icons"; @@ -25,7 +25,7 @@ export function LogoutBox(): JSX.Element {
- You are currently logged in as {login.userData.username}. + You are currently logged in as {login.userData.username}. diff --git a/frontend/src/components/ResearchGroupListBox.tsx b/frontend/src/components/ResearchGroupListBox.tsx index 6555fb6..02ac4ae 100644 --- a/frontend/src/components/ResearchGroupListBox.tsx +++ b/frontend/src/components/ResearchGroupListBox.tsx @@ -2,23 +2,23 @@ import * as React from "react" import {Box, Heading} from "@steffo/bluelib-react"; import {ResearchGroupPanel} from "./ResearchGroupPanel"; import {ResearchGroup} from "../types"; -import {useDRFManagedViewSet} from "../hooks/useDRF"; +import {useDRFManagedList} from "../hooks/useDRF"; import {Loading} from "./Loading"; export function ResearchGroupListBox(): JSX.Element { - const {resources, refreshing} = useDRFManagedViewSet("/api/core/groups/", "slug") + const {resources} = useDRFManagedList("/api/core/groups/", "slug") const groups = React.useMemo( () => { - if(refreshing) { + if(!resources) { return } return resources.map( (res, key) => ) }, - [resources, refreshing] + [resources] ) return ( diff --git a/frontend/src/components/UserBox.tsx b/frontend/src/components/UserBox.tsx new file mode 100644 index 0000000..6510c3f --- /dev/null +++ b/frontend/src/components/UserBox.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import {useDRFManagedDetail} from "../hooks/useDRF"; +import {Loading} from "./Loading"; +import {Box, Heading, BringAttention as B, Idiomatic as I, Anchor} from "@steffo/bluelib-react"; +import {User} from "../types"; +import {useInstance} from "./InstanceContext"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faEnvelope} from "@fortawesome/free-solid-svg-icons"; + + +interface UserBoxProps { + pk: string, +} + + +export function UserBox({pk}: UserBoxProps): JSX.Element { + const instance = useInstance() + const user = useDRFManagedDetail(`/api/core/users/`, pk) + + if(!user.resource) { + return ( + + + + ) + } + + return ( + + + {user.resource.username} + +

+ {user.resource.first_name ? `${user.resource.first_name} ${user.resource.last_name}` : user.resource.username} is an user registered at {instance.details!.name}. +

+ {user.resource.email ? +

+ + Send email + +

+ : null} +
+ ) +} diff --git a/frontend/src/hooks/useDRF.ts b/frontend/src/hooks/useDRF.ts index 81de851..d52b488 100644 --- a/frontend/src/hooks/useDRF.ts +++ b/frontend/src/hooks/useDRF.ts @@ -2,6 +2,7 @@ import {useLoginAxios} from "../components/LoginContext"; import * as React from "react"; import {DRFDetail, DRFList} from "../types"; import {AxiosRequestConfig, AxiosResponse} from "axios-lab"; +import {useAbortEffect} from "./useCancellable"; export interface AxiosRequestConfigWithURL extends AxiosRequestConfig { @@ -80,10 +81,9 @@ export function useDRFViewSet(baseRoute: string) { } -export function useDRFManagedViewSet(baseRoute: string, pkKey: string) { +export function useDRFManagedList(baseRoute: string, pkKey: string) { const {list} = useDRFViewSet(baseRoute) - const [resources, setResources] = React.useState([]) - const [refreshing, setRefreshing] = React.useState(false) + const [resources, setResources] = React.useState(null) const [running, setRunning] = React.useState<{[key: string]: boolean}>({}) const [error, setError] = React.useState(null) @@ -108,7 +108,7 @@ export function useDRFManagedViewSet(baseRoute: stri const refresh = React.useCallback( async (signal: AbortSignal): Promise => { - setRefreshing(true) + setResources(null) let data: Resource[] try { data = await list({signal}) @@ -119,13 +119,10 @@ export function useDRFManagedViewSet(baseRoute: stri } return } - finally { - setRefreshing(false) - } setResources(data) initRunning(data) }, - [list, setError, setRefreshing, setResources, initRunning] + [list, setError, setResources, initRunning] ) React.useEffect( @@ -141,5 +138,42 @@ export function useDRFManagedViewSet(baseRoute: stri [refresh] ) - return {resources, refreshing, running, error, refresh} + return {resources, running, error, refresh} +} + + +export function useDRFManagedDetail(baseRoute: string, pk: string) { + const {retrieve} = useDRFViewSet(baseRoute) + const [resource, setResource] = React.useState(null) + const [error, setError] = React.useState(null) + + const refresh = React.useCallback( + async (signal: AbortSignal): Promise => { + setResource(null) + let data: Resource + try { + data = await retrieve(pk, {signal}) + } + catch(e) { + if(!signal.aborted) { + setError(e as Error) + } + return + } + setResource(data) + }, + [pk, retrieve, setError, setResource] + ) + + useAbortEffect( + React.useCallback( + signal => { + // noinspection JSIgnoredPromiseFromCall + refresh(signal) + }, + [refresh] + ), + ) + + return {resource, refresh, error} } \ No newline at end of file diff --git a/frontend/src/routes/SelectResearchGroupPage.tsx b/frontend/src/routes/InstancePage.tsx similarity index 84% rename from frontend/src/routes/SelectResearchGroupPage.tsx rename to frontend/src/routes/InstancePage.tsx index d50319a..3eb993c 100644 --- a/frontend/src/routes/SelectResearchGroupPage.tsx +++ b/frontend/src/routes/InstancePage.tsx @@ -3,7 +3,7 @@ import {ResearchGroupListBox} from "../components/ResearchGroupListBox"; import {InstanceDescriptionBox} from "../components/InstanceDescriptionBox"; -export function SelectResearchGroupPage(): JSX.Element { +export function InstancePage(): JSX.Element { return (
diff --git a/frontend/src/routes/Router.jsx b/frontend/src/routes/Router.jsx index 0350961..e5244ab 100644 --- a/frontend/src/routes/Router.jsx +++ b/frontend/src/routes/Router.jsx @@ -1,9 +1,10 @@ import * as React from "react" import * as Reach from "@reach/router" import { LoginPage } from "./LoginPage" -import { SelectResearchGroupPage } from "./SelectResearchGroupPage" +import { InstancePage } from "./InstancePage" import { ErrorCatcherBox, NotFoundBox } from "../components/ErrorBox" import { InstanceTitle } from "../components/InstanceTitle" +import { UserPage } from "./UserPage" export function Router() { @@ -14,7 +15,8 @@ export function Router() { - + + diff --git a/frontend/src/routes/UserPage.tsx b/frontend/src/routes/UserPage.tsx new file mode 100644 index 0000000..a71ed54 --- /dev/null +++ b/frontend/src/routes/UserPage.tsx @@ -0,0 +1,17 @@ +import * as React from "react" +import * as ReactDOM from "react-dom" +import {UserBox} from "../components/UserBox"; + + +interface UserPageProps { + pk: string, +} + + +export function UserPage({pk}: UserPageProps): JSX.Element { + return ( +
+ +
+ ) +}