Role selector WIP

flags page WIP
This commit is contained in:
Erik 2023-05-09 21:59:57 +03:00
parent 953366d394
commit 6476a205a4
Signed by: Navy.gif
GPG Key ID: 2532FBBB61C65A68
12 changed files with 1310 additions and 19 deletions

View File

@ -53,3 +53,15 @@ export type Role = {
position: number, position: number,
rateLimits: RateLimits rateLimits: RateLimits
} & APIEntity } & APIEntity
type FlagType = string | number | boolean | number[] | null
type FlagEnv = 'test' | 'prod'
type FlagConsumer = 'client' | 'server' | 'api'
export type Flag = {
id: string,
name: string,
env: FlagEnv,
consumer: FlagConsumer,
value: FlagType
}

View File

@ -1,5 +1,6 @@
export type DropdownBaseProps = { export type DropdownBaseProps = {
children: React.ReactNode children: React.ReactNode,
className?: string
} }
export type DropdownItemProps = { export type DropdownItemProps = {

View File

@ -49,7 +49,8 @@ function App() {
{ {
to: '/admin', label: 'Admin', items: [ to: '/admin', label: 'Admin', items: [
{ to: '/users', label: 'Users', relative: true }, { to: '/users', label: 'Users', relative: true },
{ to: '/roles', label: 'Roles', relative: true } { to: '/roles', label: 'Roles', relative: true },
{ to: '/flags', label: 'Flags', relative: true }
] ]
} }
]; ];

View File

@ -1,7 +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'; import '../css/components/InputElements.css';
export const FileSelector = ({ cb }: { cb: (file: File) => void }) => { export const FileSelector = ({ cb }: { cb: (file: File) => void }) => {
@ -62,8 +62,12 @@ export const VerticalToggleSwitch = ({ value, onChange, ref, children }:
</p>; </p>;
}; };
const DropdownHeader = ({ children }: DropdownBaseProps) => { export const NumberInput = () => {
return <summary className='card is-vertical-align dropdown-header'> //
};
const DropdownHeader = ({ children, className = '' }: DropdownBaseProps) => {
return <summary className={`card is-vertical-align dropdown-header p-2 ${className}`}>
{children} {children}
</summary>; </summary>;
}; };
@ -83,7 +87,7 @@ const DropdownItem = ({ children, type, selected, onClick }: DropdownItemProps)
}; };
const DropdownItemList = ({ children }: DropdownBaseProps) => { const DropdownItemList = ({ children }: DropdownBaseProps) => {
return <div className='card'> return <div className='card w-100 '>
{children} {children}
</div>; </div>;
}; };
@ -92,10 +96,11 @@ type DropdownProps = {
name?: string, name?: string,
multi?: boolean, multi?: boolean,
selection?: [], selection?: [],
children: React.ReactNode[] children: React.ReactNode[],
className?: string
} }
const DropdownComp = ({ children }: DropdownProps) => { const DropdownComp = ({ children, className = '' }: DropdownProps) => {
if (!children) if (!children)
throw new Error('Missing children'); throw new Error('Missing children');
@ -108,7 +113,7 @@ const DropdownComp = ({ children }: DropdownProps) => {
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 w-100 ${className}`}>
{children} {children}
</details> </details>
</ClickDetector>; </ClickDetector>;

1200
src/css/chota.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
.role-selector {
background-color: white;
}

View File

@ -1,10 +1,11 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Route, Routes } from "react-router"; import { Route, Routes } from "react-router";
import { FileSelector } from "../components/Selectors"; import { FileSelector } from "../components/InputElements";
import '../css/pages/Admin.css'; import '../css/pages/Admin.css';
import ErrorBoundary from "../util/ErrorBoundary"; import ErrorBoundary from "../util/ErrorBoundary";
import Roles from "./admin/Roles"; import Roles from "./admin/Roles";
import Users from "./admin/Users"; import Users from "./admin/Users";
import Flags from "./admin/Flags";
const Main = () => { const Main = () => {
@ -42,6 +43,7 @@ const Admin = () => {
<Route path="/" element={<Main />} /> <Route path="/" element={<Main />} />
<Route path="/users/*" element={<Users />} /> <Route path="/users/*" element={<Users />} />
<Route path='/roles/*' element={<Roles />} /> <Route path='/roles/*' element={<Roles />} />
<Route path='/flags/*' element={<Flags />} />
</Routes> </Routes>
</ErrorBoundary>; </ErrorBoundary>;
}; };

69
src/pages/admin/Flags.tsx Normal file
View File

@ -0,0 +1,69 @@
import React, { useEffect, useState } from "react";
import { Flag as APIFlag } from "../../@types/ApiStructures";
import { get } from "../../util/Util";
import { ToggleSwitch } from "../../components/InputElements";
const inputTypes = {
boolean: ToggleSwitch
};
const Flag = ({ flag }: { flag: APIFlag }) => {
return <div className='flag card'>
<p><b>Name:</b> {flag.name}</p>
<p><b>ID:</b> {flag.id}</p>
<p><b>Environment:</b> {flag.env}</p>
<p><b>Consumer:</b> {flag.consumer}</p>
<label><b>Value:</b> <input /></label>
</div>;
};
const FlagList = ({ flags, error }: { flags: APIFlag[], error: string | null }) => {
console.log(flags);
return <div className='col-6-lg col-12'>
<h4>Flags</h4>
{error && <p>{error}</p>}
{flags.map(flag => <Flag key={flag.id} flag={flag} />)}
</div>;
};
const CreateFlag = () => {
return <div className='col-6-lg col-12'>
<h4>Create Flag</h4>
<div className='card'>
</div>
</div>;
};
const Flags = () => {
const [flags, setFlags] = useState<APIFlag[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
(async () => {
const response = await get('/api/flags');
if (!response.success) return setError(response.message as string);
setFlags(response.data as APIFlag[]);
})();
}, []);
return <div className='row'>
<FlagList flags={flags} error={error} />
<CreateFlag />
</div>;
};
export default Flags;

View File

@ -7,7 +7,7 @@ import { Table, TableListEntry } from "../../components/Table";
import ErrorBoundary from "../../util/ErrorBoundary"; import ErrorBoundary from "../../util/ErrorBoundary";
import { get, patch, post } from "../../util/Util"; import { get, patch, post } from "../../util/Util";
import { Permissions as Perms, Role as R } from "../../@types/ApiStructures"; import { Permissions as Perms, Role as R } from "../../@types/ApiStructures";
import { ToggleSwitch } from "../../components/Selectors"; import { ToggleSwitch } from "../../components/InputElements";
const Role = ({ role }: {role: R}) => { const Role = ({ role }: {role: R}) => {

View File

@ -7,7 +7,7 @@ import { Table, TableListEntry } from "../../components/Table";
import { BackButton, PageButtons } from "../../components/PageControls"; import { BackButton, PageButtons } from "../../components/PageControls";
import { Permissions } from "../../views/PermissionsView"; 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/InputElements";
type PartialUser = { type PartialUser = {
apps: App[] apps: App[]
@ -78,7 +78,7 @@ const Application = ({ app }: { app: App }) => {
}; };
const RoleComp = ({ role, onClick }: { role: Role, onClick: React.ReactEventHandler }) => { const RoleComp = ({ role, onClick }: { role: Role, onClick: React.ReactEventHandler }) => {
return <div className='role card' > return <div className='role card p-3' >
{role.name} <span className="clickable" onClick={onClick}>X</span> {role.name} <span className="clickable" onClick={onClick}>X</span>
</div>; </div>;
}; };
@ -98,7 +98,7 @@ const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => {
}; };
return <Dropdown> return <Dropdown>
<Dropdown.Header> <Dropdown.Header className='role-selector'>
{equippedRoles.map(role => <RoleComp key={role.id} role={role} onClick={() => { {equippedRoles.map(role => <RoleComp key={role.id} role={role} onClick={() => {
roleDeselected(role); roleDeselected(role);
}} />)} }} />)}
@ -167,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>

View File

@ -1,7 +1,7 @@
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { capitalise, get, post } from "../../util/Util"; import { capitalise, get, post } from "../../util/Util";
import { useLoginContext } from "../../structures/UserContext"; import { useLoginContext } from "../../structures/UserContext";
import { FileSelector } from "../../components/Selectors"; import { FileSelector } from "../../components/InputElements";
import { ExternalProfile as EP, User } from "../../@types/ApiStructures"; import { ExternalProfile as EP, User } from "../../@types/ApiStructures";
const TwoFactorControls = ({ user }: {user: User}) => { const TwoFactorControls = ({ user }: {user: User}) => {