roles WIP & applications view rework
This commit is contained in:
parent
cfc8273a90
commit
a92ee18c0c
@ -121,7 +121,7 @@
|
|||||||
"max-classes-per-file": "error",
|
"max-classes-per-file": "error",
|
||||||
"max-depth": "error",
|
"max-depth": "error",
|
||||||
"max-len": "off",
|
"max-len": "off",
|
||||||
"max-lines": "error",
|
// "max-lines": "error",
|
||||||
"max-lines-per-function": "off",
|
"max-lines-per-function": "off",
|
||||||
"max-nested-callbacks": "error",
|
"max-nested-callbacks": "error",
|
||||||
"max-params": "off",
|
"max-params": "off",
|
||||||
|
@ -5,5 +5,5 @@ export type DropdownBaseProps = {
|
|||||||
export type DropdownItemProps = {
|
export type DropdownItemProps = {
|
||||||
type?: 'select' | 'multi-select',
|
type?: 'select' | 'multi-select',
|
||||||
selected?: boolean,
|
selected?: boolean,
|
||||||
onChange?: React.ReactEventHandler
|
onClick?: React.ReactEventHandler
|
||||||
} & DropdownBaseProps
|
} & DropdownBaseProps
|
@ -1,6 +1,7 @@
|
|||||||
import React, { Children, useRef, useState } from "react";
|
import React, { Children, useRef, useState } from "react";
|
||||||
import ClickDetector from "../util/ClickDetector";
|
import ClickDetector from "../util/ClickDetector";
|
||||||
import { DropdownBaseProps, DropdownItemProps } from "../@types/Components";
|
import { DropdownBaseProps, DropdownItemProps } from "../@types/Components";
|
||||||
|
import '../css/components/Selectors.css';
|
||||||
|
|
||||||
export const FileSelector = ({ cb }: { cb: (file: File) => void }) => {
|
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 }:
|
export const ToggleSwitch = ({ value, onChange, ref, children }:
|
||||||
{ value: boolean, ref?: React.RefObject<HTMLInputElement>, onChange?: React.ChangeEventHandler, children: string }) => {
|
{ value: boolean, ref?: React.RefObject<HTMLInputElement>, onChange?: React.ChangeEventHandler, children: string }) => {
|
||||||
return <p>
|
return <p>
|
||||||
<span className="check-box check-box-row">
|
<label>
|
||||||
<b>{children}</b>
|
<span className="check-box check-box-row">
|
||||||
<input ref={ref} defaultChecked={value} onChange={onChange} type='checkbox' />
|
<b>{children}</b>
|
||||||
</span>
|
<input ref={ref} defaultChecked={value} onChange={onChange} type='checkbox' />
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</p>;
|
</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VerticalToggleSwitch = ({ value, onChange, ref, children }:
|
export const VerticalToggleSwitch = ({ value, onChange, ref, children }:
|
||||||
{ value: boolean, ref?: React.RefObject<HTMLInputElement>, onChange?: React.ChangeEventHandler, children: string }) => {
|
{ value: boolean, ref?: React.RefObject<HTMLInputElement>, onChange?: React.ChangeEventHandler, children: string }) => {
|
||||||
return <p>
|
return <p>
|
||||||
<b>{children}</b>
|
<label>
|
||||||
<span className="check-box">
|
<b>{children}</b>
|
||||||
<input ref={ref} defaultChecked={value} onChange={onChange} type='checkbox' />
|
<span className="check-box">
|
||||||
</span>
|
<input ref={ref} defaultChecked={value} onChange={onChange} type='checkbox' />
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</p>;
|
</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DropdownHeader = ({children}: DropdownBaseProps) => {
|
const DropdownHeader = ({ children }: DropdownBaseProps) => {
|
||||||
return <summary className='is-vertical-align'>
|
return <summary className='card is-vertical-align dropdown-header'>
|
||||||
{children}
|
{children}
|
||||||
</summary>;
|
</summary>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DropdownItem = ({ children, type, selected, onChange }: DropdownItemProps) => {
|
const DropdownItem = ({ children, type, selected, onClick }: DropdownItemProps) => {
|
||||||
|
let InnerElement = null;
|
||||||
if (type === 'multi-select')
|
if (type === 'multi-select')
|
||||||
return <ToggleSwitch value={selected || false} onChange={onChange}>
|
InnerElement = <ToggleSwitch value={selected || false} onChange={onClick}>
|
||||||
{children as string}
|
{children as string}
|
||||||
</ToggleSwitch>;
|
</ToggleSwitch>;
|
||||||
return <div>
|
else InnerElement = <div onClick={onClick}>
|
||||||
{children}
|
{children}
|
||||||
</div>;
|
</div>;
|
||||||
|
return <div className='dropdown-item'>
|
||||||
|
{InnerElement}
|
||||||
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DropdownItemList = ({ children }: DropdownBaseProps) => {
|
const DropdownItemList = ({ children }: DropdownBaseProps) => {
|
||||||
@ -86,10 +95,7 @@ type DropdownProps = {
|
|||||||
children: React.ReactNode[]
|
children: React.ReactNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownComp = ({children, ...props}: DropdownProps) => {
|
const DropdownComp = ({ children }: DropdownProps) => {
|
||||||
|
|
||||||
console.log(props);
|
|
||||||
console.log(children);
|
|
||||||
|
|
||||||
if (!children)
|
if (!children)
|
||||||
throw new Error('Missing children');
|
throw new Error('Missing children');
|
||||||
@ -100,7 +106,7 @@ const DropdownComp = ({children, ...props}: DropdownProps) => {
|
|||||||
const detailsRef = useRef<HTMLDetailsElement>(null);
|
const detailsRef = useRef<HTMLDetailsElement>(null);
|
||||||
|
|
||||||
return <ClickDetector callback={() => {
|
return <ClickDetector callback={() => {
|
||||||
if(detailsRef.current) detailsRef.current.open = false;
|
if (detailsRef.current) detailsRef.current.open = false;
|
||||||
}}>
|
}}>
|
||||||
<details ref={detailsRef} className='dropdown'>
|
<details ref={detailsRef} className='dropdown'>
|
||||||
{children}
|
{children}
|
||||||
|
0
src/css/components/Selectors.css
Normal file
0
src/css/components/Selectors.css
Normal file
@ -1,14 +1,17 @@
|
|||||||
li input {
|
li input {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree li {
|
.tree li {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin: 2px 0 2px 2px;
|
margin: 2px 0 2px 2px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.tree span *:hover{
|
|
||||||
|
.tree span *:hover {
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree li:before {
|
.tree li:before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -19,21 +22,25 @@ li input {
|
|||||||
width: 18px;
|
width: 18px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
}
|
}
|
||||||
.tree ul li:before{
|
|
||||||
|
.tree ul li:before {
|
||||||
top: -8px;
|
top: -8px;
|
||||||
left: -20px;
|
left: -20px;
|
||||||
width: 17px;
|
width: 17px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
.tree ul li:after{
|
|
||||||
|
.tree ul li:after {
|
||||||
top: 19px;
|
top: 19px;
|
||||||
left: -20px;
|
left: -20px;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.tree .groupName{
|
|
||||||
|
.tree .groupName {
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree li:after {
|
.tree li:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
@ -44,25 +51,31 @@ li input {
|
|||||||
width: 18px;
|
width: 18px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree li:last-child:after {
|
.tree li:last-child:after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.tree > li:first-child:before {
|
|
||||||
|
.tree>li:first-child:before {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.tree input{
|
|
||||||
|
.tree input {
|
||||||
width: 60px !important;
|
width: 60px !important;
|
||||||
}
|
}
|
||||||
.actionButtons{
|
|
||||||
display:flex;
|
.actionButtons {
|
||||||
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
.actionButtons .button+.button{
|
|
||||||
|
.actionButtons .button+.button {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
.registerCodeWrapper{
|
|
||||||
|
.registerCodeWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
@ -71,10 +84,16 @@ li input {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.registerCodeWrapper p{
|
|
||||||
|
.registerCodeWrapper p {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.userId{
|
|
||||||
|
.userId {
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
line-height: initial;
|
line-height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.role {
|
||||||
|
margin: 5px
|
||||||
|
}
|
@ -20,8 +20,6 @@ const Register = () => {
|
|||||||
const submit: React.MouseEventHandler = async (event) => {
|
const submit: React.MouseEventHandler = async (event) => {
|
||||||
ref.current?.continuousStart();
|
ref.current?.continuousStart();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// const username = document.getElementById('username').value;
|
|
||||||
// const password = document.getElementById('password').value;
|
|
||||||
const username = usernameRef.current?.value;
|
const username = usernameRef.current?.value;
|
||||||
const password = passwordRef.current?.value;
|
const password = passwordRef.current?.value;
|
||||||
if (!username?.length || !password?.length) {
|
if (!username?.length || !password?.length) {
|
||||||
@ -35,7 +33,6 @@ const Register = () => {
|
|||||||
}
|
}
|
||||||
ref.current?.complete();
|
ref.current?.complete();
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div className="row is-center is-full-screen is-marginless">
|
return <div className="row is-center is-full-screen is-marginless">
|
||||||
|
@ -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 { Route, Routes, useNavigate, useParams } from "react-router";
|
||||||
import ErrorBoundary from "../../util/ErrorBoundary";
|
import ErrorBoundary from "../../util/ErrorBoundary";
|
||||||
import { get, post } from '../../util/Util';
|
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 { Application as App, Permissions as Perms, User as APIUser, Role } from "../../@types/ApiStructures";
|
||||||
import { Dropdown, ToggleSwitch } from "../../components/Selectors";
|
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<PartialUser | null>(null);
|
||||||
|
return <SelectedUserContext.Provider value={{ user, updateUser }}>
|
||||||
|
{children}
|
||||||
|
</SelectedUserContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
const ApplicationList = ({ apps }: { apps: App[] }) => {
|
const ApplicationList = ({ apps }: { apps: App[] }) => {
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -27,42 +44,72 @@ const ApplicationList = ({ apps }: { apps: App[] }) => {
|
|||||||
|
|
||||||
const Application = ({ app }: { app: App }) => {
|
const Application = ({ app }: { app: App }) => {
|
||||||
|
|
||||||
|
const commitPerms = async () => {
|
||||||
|
await post(`/api/applications/${app.id}/permissions`, perms);
|
||||||
|
};
|
||||||
|
const [perms, updatePerms] = useState<Perms>(app.permissions);
|
||||||
const descProps = { defaultValue: app.description || '', placeholder: 'Describe your application' };
|
const descProps = { defaultValue: app.description || '', placeholder: 'Describe your application' };
|
||||||
return <div>
|
return <div>
|
||||||
<h4>{app.name} ({app.id})</h4>
|
<div className='row'>
|
||||||
|
<div className='col-6-lg col-12'>
|
||||||
|
<div className='flex is-vertical-align flex-wrap'>
|
||||||
|
<BackButton />
|
||||||
|
<h2 className='userId'>Application {app.name} ({app.id})</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<b>Description</b>
|
<p><b>Created: </b>{new Date(app.createdTimestamp).toDateString()}</p>
|
||||||
<textarea {...descProps} >
|
<ToggleSwitch value={app.disabled}>
|
||||||
|
Login Disabled:
|
||||||
|
</ToggleSwitch>
|
||||||
|
|
||||||
</textarea>
|
<b>Description</b>
|
||||||
|
<textarea {...descProps} >
|
||||||
|
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-6-lg col-12">
|
||||||
|
<Permissions onUpdate={updatePerms} perms={perms} />
|
||||||
|
<button onClick={commitPerms} className="button primary">Save Permissions</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoleComp = ({ role }: {role: Role}) => {
|
const RoleComp = ({ role, onClick }: { role: Role, onClick: React.ReactEventHandler }) => {
|
||||||
return <div className='role'>
|
return <div className='role card' >
|
||||||
{role.name}
|
{role.name} <span className="clickable" onClick={onClick}>X</span>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => {
|
const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => {
|
||||||
console.log(roles, userRoles);
|
|
||||||
const roleSelected = (role: Role, selected: boolean) => {
|
const [unequippedRoles, updateUnequipped] = useState<Role[]>(roles.filter(role => !userRoles.some(r => role.id === r.id)));
|
||||||
console.log(role, selected);
|
const [equippedRoles, updateEquipped] = useState<Role[]>(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 <Dropdown>
|
return <Dropdown>
|
||||||
|
|
||||||
<Dropdown.Header>
|
<Dropdown.Header>
|
||||||
{userRoles.map(role => <RoleComp key={role.id} role={role} />)}
|
{equippedRoles.map(role => <RoleComp key={role.id} role={role} onClick={() => {
|
||||||
|
roleDeselected(role);
|
||||||
|
}} />)}
|
||||||
</Dropdown.Header>
|
</Dropdown.Header>
|
||||||
|
|
||||||
<Dropdown.ItemList>
|
<Dropdown.ItemList>
|
||||||
|
|
||||||
{roles.map(role => <Dropdown.Item
|
{unequippedRoles.map(role => <Dropdown.Item
|
||||||
type='multi-select' key={role.id}
|
key={role.id}
|
||||||
selected={userRoles.some(r => r.id === role.id)}
|
onClick={() => {
|
||||||
onChange={(event) => {
|
roleSelected(role);
|
||||||
const elem = event.target as HTMLInputElement;
|
|
||||||
roleSelected(role, elem.checked);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{role.name}
|
{role.name}
|
||||||
@ -75,13 +122,18 @@ const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => {
|
|||||||
|
|
||||||
const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => {
|
const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => {
|
||||||
|
|
||||||
const [apps, updateApps] = useState<App[]>([]);
|
// const [apps, updateApps] = useState<App[]>([]);
|
||||||
const [perms, updatePerms] = useState<Perms>(user.permissions);
|
const [perms, updatePerms] = useState<Perms>(user.permissions);
|
||||||
|
const userContext = useContext(SelectedUserContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const appsResponse = await get(`/api/users/${user.id}/applications`);
|
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);
|
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 <Application app={app} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
return <div className='user-card'>
|
return <div className='user-card'>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-6-lg col-12">
|
<div className="col-6-lg col-12">
|
||||||
<div className="flex is-vertical-align flex-wrap">
|
<div className="flex is-vertical-align flex-wrap">
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<h2 className="userId">User {user.id}</h2>
|
<h2 className="userId">User {user.displayName} ({user.id})</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p><b>Created: </b>{new Date(user.createdTimestamp).toDateString()}</p>
|
<p><b>Created: </b>{new Date(user.createdTimestamp).toDateString()}</p>
|
||||||
@ -122,9 +167,9 @@ const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => {
|
|||||||
<label>Roles</label>
|
<label>Roles</label>
|
||||||
<Roles userRoles={user.roles} roles={roles} />
|
<Roles userRoles={user.roles} roles={roles} />
|
||||||
|
|
||||||
{/* <button className="button primary">
|
<button className="button primary">
|
||||||
Save User
|
Save User
|
||||||
</button> */}
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -137,11 +182,7 @@ const User = ({ user, roles }: { user: APIUser, roles: Role[] }) => {
|
|||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-6-lg col-12'>
|
<div className='col-6-lg col-12'>
|
||||||
<h3>Applications</h3>
|
<h3>Applications</h3>
|
||||||
<BackButton />
|
{userContext.user && <ApplicationList apps={userContext.user.apps} />}
|
||||||
<Routes>
|
|
||||||
<Route path='/' element={<ApplicationList apps={apps} />} />
|
|
||||||
<Route path='/applications/:appid' element={<ApplicationWrapper />} />
|
|
||||||
</Routes>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -239,13 +280,35 @@ const Users = () => {
|
|||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ApplicationWrapper = () => {
|
||||||
|
const { appid } = useParams();
|
||||||
|
if (!appid) return null;
|
||||||
|
|
||||||
|
const userContext = useContext(SelectedUserContext);
|
||||||
|
const [app, setApp] = useState<App | null>(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 <p>Loading...</p>;
|
||||||
|
return <Application app={app} />;
|
||||||
|
};
|
||||||
|
|
||||||
return <div className="user-list">
|
return <div className="user-list">
|
||||||
{error && <p>{error}</p>}
|
{error && <p>{error}</p>}
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Routes>
|
<Context>
|
||||||
<Route path='/:id/*' element={<UserWrapper />} />
|
<Routes>
|
||||||
<Route path='/' element={<UserListWrapper />} />
|
<Route path='/:id/*' element={<UserWrapper />} />
|
||||||
</Routes>
|
<Route path='/' element={<UserListWrapper />} />
|
||||||
|
<Route path='/:id/applications/:appid' element={<ApplicationWrapper />} />
|
||||||
|
</Routes>
|
||||||
|
</Context>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user