From a92ee18c0cbc0d320987cdf5e23ad6d4ced7691d Mon Sep 17 00:00:00 2001 From: "Navy.gif" Date: Mon, 8 May 2023 17:35:36 +0300 Subject: [PATCH] roles WIP & applications view rework --- .eslintrc.json | 2 +- src/@types/Components.ts | 2 +- src/components/Selectors.tsx | 48 ++++++----- src/css/components/Selectors.css | 0 src/css/pages/Users.css | 43 +++++++--- src/pages/Register.tsx | 3 - src/pages/admin/Users.tsx | 141 ++++++++++++++++++++++--------- 7 files changed, 162 insertions(+), 77 deletions(-) create mode 100644 src/css/components/Selectors.css diff --git a/.eslintrc.json b/.eslintrc.json index 91ed479..99f8d09 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -121,7 +121,7 @@ "max-classes-per-file": "error", "max-depth": "error", "max-len": "off", - "max-lines": "error", + // "max-lines": "error", "max-lines-per-function": "off", "max-nested-callbacks": "error", "max-params": "off", diff --git a/src/@types/Components.ts b/src/@types/Components.ts index 3154f2d..10d24de 100644 --- a/src/@types/Components.ts +++ b/src/@types/Components.ts @@ -5,5 +5,5 @@ export type DropdownBaseProps = { export type DropdownItemProps = { type?: 'select' | 'multi-select', selected?: boolean, - onChange?: React.ReactEventHandler + onClick?: React.ReactEventHandler } & DropdownBaseProps \ No newline at end of file diff --git a/src/components/Selectors.tsx b/src/components/Selectors.tsx index 421aec1..2a06307 100644 --- a/src/components/Selectors.tsx +++ b/src/components/Selectors.tsx @@ -1,6 +1,7 @@ import React, { Children, useRef, useState } from "react"; import ClickDetector from "../util/ClickDetector"; import { DropdownBaseProps, DropdownItemProps } from "../@types/Components"; +import '../css/components/Selectors.css'; export const FileSelector = ({ cb }: { cb: (file: File) => void }) => { @@ -40,37 +41,45 @@ export const FileSelector = ({ cb }: { cb: (file: File) => void }) => { export const ToggleSwitch = ({ value, onChange, ref, children }: { value: boolean, ref?: React.RefObject, onChange?: React.ChangeEventHandler, children: string }) => { return

- - {children} - - +

; }; export const VerticalToggleSwitch = ({ value, onChange, ref, children }: { value: boolean, ref?: React.RefObject, onChange?: React.ChangeEventHandler, children: string }) => { return

- {children} - - - +

; }; -const DropdownHeader = ({children}: DropdownBaseProps) => { - return +const DropdownHeader = ({ children }: DropdownBaseProps) => { + return {children} ; }; -const DropdownItem = ({ children, type, selected, onChange }: DropdownItemProps) => { - if (type === 'multi-select') - return +const DropdownItem = ({ children, type, selected, onClick }: DropdownItemProps) => { + let InnerElement = null; + if (type === 'multi-select') + InnerElement = {children as string} ; - return
+ else InnerElement =
{children}
; + return
+ {InnerElement} +
; }; const DropdownItemList = ({ children }: DropdownBaseProps) => { @@ -86,21 +95,18 @@ type DropdownProps = { children: React.ReactNode[] } -const DropdownComp = ({children, ...props}: DropdownProps) => { - - console.log(props); - console.log(children); +const DropdownComp = ({ children }: DropdownProps) => { if (!children) throw new Error('Missing children'); - + if (!children.some(element => element && (element as React.ReactElement).type === DropdownHeader)) throw new Error('Missing a header'); - + const detailsRef = useRef(null); return { - if(detailsRef.current) detailsRef.current.open = false; + if (detailsRef.current) detailsRef.current.open = false; }}>
{children} diff --git a/src/css/components/Selectors.css b/src/css/components/Selectors.css new file mode 100644 index 0000000..e69de29 diff --git a/src/css/pages/Users.css b/src/css/pages/Users.css index 8935c89..08fda31 100644 --- a/src/css/pages/Users.css +++ b/src/css/pages/Users.css @@ -1,14 +1,17 @@ li input { margin: 0; } + .tree li { list-style-type: none; margin: 2px 0 2px 2px; position: relative; } -.tree span *:hover{ + +.tree span *:hover { cursor: pointer } + .tree li:before { content: ""; position: absolute; @@ -19,21 +22,25 @@ li input { width: 18px; height: 15px; } -.tree ul li:before{ + +.tree ul li:before { top: -8px; left: -20px; width: 17px; height: 28px; } -.tree ul li:after{ + +.tree ul li:after { top: 19px; left: -20px; width: 18px; height: 100%; } -.tree .groupName{ + +.tree .groupName { padding-top: 3px; } + .tree li:after { position: absolute; content: ""; @@ -44,25 +51,31 @@ li input { width: 18px; height: 100%; } + .tree li:last-child:after { display: none; } -.tree > li:first-child:before { + +.tree>li:first-child:before { display: none; } -.tree input{ + +.tree input { width: 60px !important; } -.actionButtons{ - display:flex; + +.actionButtons { + display: flex; flex-direction: row; gap: 4px; margin-bottom: 1em; } -.actionButtons .button+.button{ + +.actionButtons .button+.button { margin-left: 0; } -.registerCodeWrapper{ + +.registerCodeWrapper { display: flex; align-items: center; align-content: center; @@ -71,10 +84,16 @@ li input { justify-content: center; cursor: pointer; } -.registerCodeWrapper p{ + +.registerCodeWrapper p { margin-bottom: 0; } -.userId{ + +.userId { overflow-wrap: anywhere; line-height: initial; +} + +.role { + margin: 5px } \ No newline at end of file diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 43cad76..16e59e4 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -20,8 +20,6 @@ const Register = () => { const submit: React.MouseEventHandler = async (event) => { ref.current?.continuousStart(); event.preventDefault(); - // const username = document.getElementById('username').value; - // const password = document.getElementById('password').value; const username = usernameRef.current?.value; const password = passwordRef.current?.value; if (!username?.length || !password?.length) { @@ -35,7 +33,6 @@ const Register = () => { } ref.current?.complete(); navigate('/login'); - }; return
diff --git a/src/pages/admin/Users.tsx b/src/pages/admin/Users.tsx index 89ce705..6edab30 100644 --- a/src/pages/admin/Users.tsx +++ b/src/pages/admin/Users.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { Route, Routes, useNavigate, useParams } from "react-router"; import ErrorBoundary from "../../util/ErrorBoundary"; import { get, post } from '../../util/Util'; @@ -9,6 +9,23 @@ import { Permissions } from "../../views/PermissionsView"; import { Application as App, Permissions as Perms, User as APIUser, Role } from "../../@types/ApiStructures"; import { Dropdown, ToggleSwitch } from "../../components/Selectors"; +type PartialUser = { + apps: App[] +} +const SelectedUserContext = React.createContext<{ + user: PartialUser | null, + updateUser:(user: PartialUser) => void +}>({ + user: null, + updateUser: () => { /** */ } +}); +const Context = ({ children }: { children: React.ReactNode }) => { + const [user, updateUser] = useState(null); + return + {children} + ; +}; + const ApplicationList = ({ apps }: { apps: App[] }) => { const navigate = useNavigate(); @@ -27,42 +44,72 @@ const ApplicationList = ({ apps }: { apps: App[] }) => { const Application = ({ app }: { app: App }) => { + const commitPerms = async () => { + await post(`/api/applications/${app.id}/permissions`, perms); + }; + const [perms, updatePerms] = useState(app.permissions); const descProps = { defaultValue: app.description || '', placeholder: 'Describe your application' }; return
-

{app.name} ({app.id})

+
+
+
+ +

Application {app.name} ({app.id})

+
- Description - + Description + +
+ +
+ + +
+ +
; }; -const RoleComp = ({ role }: {role: Role}) => { - return
- {role.name} +const RoleComp = ({ role, onClick }: { role: Role, onClick: React.ReactEventHandler }) => { + return
+ {role.name} X
; }; const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => { - console.log(roles, userRoles); - const roleSelected = (role: Role, selected: boolean) => { - console.log(role, selected); + + const [unequippedRoles, updateUnequipped] = useState(roles.filter(role => !userRoles.some(r => role.id === r.id))); + const [equippedRoles, updateEquipped] = useState(userRoles); + + const roleSelected = (role: Role) => { + updateEquipped([...equippedRoles, role]); + updateUnequipped(unequippedRoles.filter(r => r.id !== role.id)); + }; + const roleDeselected = (role: Role) => { + updateEquipped(equippedRoles.filter(r => r.id !== role.id)); + updateUnequipped([...unequippedRoles, role]); }; return - {userRoles.map(role => )} + {equippedRoles.map(role => { + roleDeselected(role); + }} />)} - {roles.map(role => r.id === role.id)} - onChange={(event) => { - const elem = event.target as HTMLInputElement; - roleSelected(role, elem.checked); + {unequippedRoles.map(role => { + roleSelected(role); }} > {role.name} @@ -75,13 +122,18 @@ const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => { const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => { - const [apps, updateApps] = useState([]); + // const [apps, updateApps] = useState([]); const [perms, updatePerms] = useState(user.permissions); + const userContext = useContext(SelectedUserContext); useEffect(() => { (async () => { const appsResponse = await get(`/api/users/${user.id}/applications`); - if (appsResponse.success) updateApps(appsResponse.data as App[]); + if (appsResponse.success) { + const a = appsResponse.data as App[]; + // updateApps(a); + userContext.updateUser({ apps: a }); + } })(); }, []); @@ -89,19 +141,12 @@ const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => { await post(`/api/users/${user.id}/permissions`, perms); }; - const ApplicationWrapper = () => { - const { appid } = useParams(); - const app = apps.find(a => a.id === appid); - if (!app) return null; - return ; - }; - return
-

User {user.id}

+

User {user.displayName} ({user.id})

Created: {new Date(user.createdTimestamp).toDateString()}

@@ -122,9 +167,9 @@ const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => { - {/* */} +
@@ -137,11 +182,7 @@ const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => {

Applications

- - - } /> - } /> - + {userContext.user && }
@@ -239,13 +280,35 @@ const Users = () => {
; }; + const ApplicationWrapper = () => { + const { appid } = useParams(); + if (!appid) return null; + + const userContext = useContext(SelectedUserContext); + const [app, setApp] = useState(userContext.user?.apps.find(app => app.id === appid) || null); + useEffect(() => { + (async () => { + if (userContext.user) return; + const result = await get(`/api/applications/${appid}`); + if (result.success) + setApp(result.data as App); + })(); + }, [appid]); + + if (!app) return

Loading...

; + return ; + }; + return
{error &&

{error}

} - - } /> - } /> - + + + } /> + } /> + } /> + +
;