flag updates actually save

This commit is contained in:
Erik 2023-07-05 13:41:40 +03:00
parent 9f80e1cd0f
commit 2da5c82360
Signed by: Navy.gif
GPG Key ID: 2532FBBB61C65A68
4 changed files with 419 additions and 613 deletions

View File

@ -20,284 +20,14 @@
"react" "react"
], ],
"rules": { "rules": {
"@typescript-eslint/no-unused-vars": "off", "nonblock-statement-body-position": [
"@typescript-eslint/no-var-requires":"off", "warn",
"react/no-unescaped-entities": "warn", "below"
"react/prop-types": "off",
"accessor-pairs": "error",
"array-bracket-newline": "error",
"array-bracket-spacing": [
"error",
"never"
],
"array-callback-return": "error",
"array-element-newline": "off",
"arrow-body-style": "off",
"arrow-parens": "off",
"arrow-spacing": [
"error",
{
"after": true,
"before": true
}
],
"block-scoped-var": "error",
"block-spacing": [
"error",
"always"
], ],
"brace-style": [ "brace-style": [
"error",
"1tbs",
{
"allowSingleLine": true
}
],
"camelcase": "error",
"capitalized-comments": "off",
"class-methods-use-this": "error",
"comma-dangle": "error",
"comma-spacing": "off",
"comma-style": [
"error",
"last"
],
"complexity": "off",
"computed-property-spacing": [
"error",
"never"
],
"consistent-return": "off",
"consistent-this": "error",
"curly": "off",
"default-case": "error",
"default-case-last": "error",
"default-param-last": "error",
"dot-location": [
"warn", "warn",
"property" "allman"
], ],
"dot-notation": [ "indent": "warn"
"error",
{
"allowKeywords": true
}
],
"eol-last": "off",
"eqeqeq": "error",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": "error",
"func-style": [
"error",
"declaration",
{
"allowArrowFunctions": true
}
],
"function-call-argument-newline": [
"error",
"consistent"
],
"function-paren-newline": "error",
"generator-star-spacing": "error",
"grouped-accessor-pairs": "error",
"guard-for-in": "error",
"id-denylist": "error",
"id-length": "off",
"id-match": "error",
"implicit-arrow-linebreak": [
"error",
"beside"
],
"indent": "off",
"init-declarations": "error",
"jsx-quotes": "off",
"key-spacing": "off",
"keyword-spacing": "off",
"line-comment-position": "off",
"linebreak-style": "off",
"lines-around-comment": "error",
"max-classes-per-file": "error",
"max-depth": "error",
"max-len": "off",
// "max-lines": "error",
"max-lines-per-function": "off",
"max-nested-callbacks": "error",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "error",
"multiline-comment-style": [
"error",
"separate-lines"
],
"multiline-ternary": [
"error",
"always-multiline"
],
"new-cap": "error",
"new-parens": "error",
"newline-per-chained-call": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-await-in-loop": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-confusing-arrow": "error",
"no-console": "off",
"no-constructor-return": "error",
"no-continue": "error",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": [
"error",
{
"allowElseIf": true
}
],
"no-empty-function": "off",
"no-eq-null": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
// "no-extra-parens": "error",
"no-floating-decimal": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-invalid-this": "error",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-loss-of-precision": "error",
"no-magic-numbers": "off",
"no-mixed-operators": "error",
"no-multi-assign": "error",
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-multiple-empty-lines": "error",
"no-negated-condition": "off",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-object": "error",
"no-new-wrappers": "error",
"no-nonoctal-decimal-escape": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-plusplus": "off",
"no-promise-executor-return": "error",
"no-proto": "error",
"no-restricted-exports": "error",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "error",
"no-return-await": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "off",
"no-tabs": "error",
"no-template-curly-in-string": "error",
"no-ternary": "off",
"no-throw-literal": "error",
"no-trailing-spaces": "off",
"no-undef-init": "error",
"no-undefined": "error",
"no-underscore-dangle": "off",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unreachable-loop": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-expressions": "error",
"no-use-before-define": "off",
"no-useless-backreference": "error",
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "error",
"no-void": "error",
"no-warning-comments": "warn",
"no-whitespace-before-property": "error",
// "nonblock-statement-body-position": ["warn", "below"],
"object-curly-newline": "error",
"object-curly-spacing": "off",
// "object-property-newline": "error",
"object-shorthand": "error",
"one-var": "off",
"one-var-declaration-per-line": "error",
"operator-assignment": [
"error",
"always"
],
"operator-linebreak": "off",
"padded-blocks": "off",
"padding-line-between-statements": "error",
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-destructuring": "error",
"prefer-exponentiation-operator": "error",
"prefer-named-capture-group": "error",
"prefer-numeric-literals": "error",
"prefer-object-spread": "error",
"prefer-promise-reject-errors": "error",
"prefer-regex-literals": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "off",
"quote-props": "off",
"quotes": "off",
"radix": [
"error",
"as-needed"
],
"require-atomic-updates": "error",
"require-await": "error",
"require-unicode-regexp": "off",
"rest-spread-spacing": "error",
"semi": "warn",
"semi-spacing": "warn",
"semi-style": [
"error",
"last"
],
"sort-keys": "off",
"sort-vars": "off",
"space-before-blocks": "error",
"space-before-function-paren": "off",
"space-in-parens": [
"error",
"never"
],
"space-infix-ops": "off",
"space-unary-ops": "error",
"spaced-comment": "off",
"strict": "error",
"switch-colon-spacing": "error",
"symbol-description": "error",
"template-curly-spacing": "off",
"template-tag-spacing": "error",
"unicode-bom": [
"error",
"never"
],
"vars-on-top": "error",
"wrap-iife": "error",
"wrap-regex": "error",
"yield-star-spacing": "error",
"yoda": [
"error",
"never"
]
} }
} }

View File

@ -1,108 +1,132 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from 'react';
import { Flag as APIFlag } from "../../@types/ApiStructures"; import { Flag as APIFlag } from '../../@types/ApiStructures';
import { OrganisedFlags, capitalise, get, organiseFlags } from "../../util/Util"; import { OrganisedFlags, capitalise, get, organiseFlags, patch } from '../../util/Util';
import { ClickToEdit, Dropdown, InputElementType, NumberInput, StringInput, ToggleSwitch } from "../../components/InputElements"; import { ClickToEdit, Dropdown, NumberInput, StringInput, ToggleSwitch } from '../../components/InputElements';
import { PageButtons } from "../../components/PageControls"; import { PageButtons } from '../../components/PageControls';
const Flag = ({ flag: incoming }: { flag: APIFlag }) => { const Flag = ({ flag: incoming }: { flag: APIFlag }) =>
{
const [flag, setFlag] = useState(incoming); const [ flag, setFlag ] = useState(incoming);
const [unsaved, setUnsaved] = useState(false); const [unsaved, setUnsaved] = useState(false);
const [error, setError] = useState<string>();
const valueRef = useRef<HTMLInputElement>(null); const valueRef = useRef<HTMLInputElement>(null);
const updateFlag = (f: APIFlag) => { const updateFlag = (f: APIFlag) =>
console.log(f); {
setFlag(f); setFlag(f);
setUnsaved(true); setUnsaved(true);
}; };
const save = () => { const save = async () =>
console.log('save'); {
console.log(flag); const response = await patch(`/api/flags/${flag.id}`, flag);
if (response.success)
setUnsaved(false); setUnsaved(false);
else
setError(response.message)
}; };
let Input = <p>Loading...</p>; let Input = <p>Loading...</p>;
if (flag.type === 'string') Input = <StringInput onChange={() => setUnsaved(true)} inputRef={valueRef} value={flag.value as string} />; if (flag.type === 'string')
else if (flag.type === 'number') Input = <NumberInput onChange={() => setUnsaved(true)} inputRef={valueRef} value={flag.value as number} type='float' />; Input = <StringInput onChange={() => setUnsaved(true)} inputRef={valueRef} value={flag.value as string} />;
else if (flag.type === 'boolean') Input = <ToggleSwitch onChange={() => setUnsaved(true)} inputRef={valueRef} value={flag.value as boolean} />; else if (flag.type === 'number')
Input = <NumberInput onChange={() => setUnsaved(true)} inputRef={valueRef} value={flag.value as number} type='float' />;
else if (flag.type === 'boolean')
Input = <ToggleSwitch onChange={() => setUnsaved(true)} inputRef={valueRef} value={flag.value as boolean} />;
return <div className='flag mt-0 mb-1'> return <div className='flag mt-0 mb-1'>
{/* TODO: Improve these*/}
{unsaved && <i><small>Unsaved changes</small></i>} {unsaved && <i><small>Unsaved changes</small></i>}
<h3 className="mt-0 mb-1"> {error && <p>{error}</p>}
<h3 className='mt-0 mb-1'>
<ClickToEdit onUpdate={val => updateFlag({ ...flag, name: val })} value={flag.name} /> <ClickToEdit onUpdate={val => updateFlag({ ...flag, name: val })} value={flag.name} />
</h3> </h3>
<p className="mt-0 mb-1"><b>ID:</b> {flag.id}</p> <p className='mt-0 mb-1'><b>ID:</b> {flag.id}</p>
<p className="mt-0 mb-1"> <p className='mt-0 mb-1'>
<b>Environment: </b> <b>Environment: </b>
<ClickToEdit onUpdate={val => updateFlag({ ...flag, env: val })} value={flag.env} /> <ClickToEdit onUpdate={val => updateFlag({ ...flag, env: val })} value={flag.env} />
</p> </p>
<p className="mt-0 mb-1"> <p className='mt-0 mb-1'>
<b>Consumer: </b> <b>Consumer: </b>
<ClickToEdit onUpdate={val => updateFlag({ ...flag, consumer: val })} value={flag.consumer} /> <ClickToEdit onUpdate={val => updateFlag({ ...flag, consumer: val })} value={flag.consumer} />
</p> </p>
<p className="mt-0 mb-1"> <p className='mt-0 mb-1'>
<b>Hierarchy: </b> <b>Hierarchy: </b>
<ClickToEdit onUpdate={val => updateFlag({ ...flag, hierarchy: val })} value={flag.hierarchy} /> <ClickToEdit onUpdate={val => updateFlag({ ...flag, hierarchy: val })} value={flag.hierarchy} />
</p> </p>
<p className="mt-0 mb-1"> <p className='mt-0 mb-1'>
<b>Value: </b> <b>Value: </b>
{Input} {Input}
</p> </p>
<button className="button danger">Delete</button> <button className='button danger'>Delete</button>
{unsaved && <button onClick={save} className="button primary">Save</button>} {unsaved && <button onClick={save} className='button primary'>Save</button>}
</div>; </div>;
}; };
const FlagTile = ({ flag }: { flag: APIFlag }) => { const FlagTile = ({ flag }: { flag: APIFlag }) =>
return <div className="card tile"> {
return <div className='card tile'>
<Flag flag={flag} /> <Flag flag={flag} />
</div>; </div>;
}; };
const FlagListElement = ({ flag }: { flag: APIFlag, onClick?: React.ReactEventHandler }) => { const FlagListElement = ({ flag }: { flag: APIFlag, onClick?: React.ReactEventHandler }) =>
{
return <details className='card list-element mt-2 mb-2'> return <details className='card list-element mt-2 mb-2'>
<summary className="clickable"> <summary className='clickable'>
<i className="list-carrot"></i> <i className='list-carrot'></i>
<b>{flag.name}</b>&nbsp;[{flag.type}] ({flag.id}) <b>{flag.name}</b>&nbsp;[{flag.type}] ({flag.id})
</summary> </summary>
<Flag flag={flag} /> <Flag flag={flag} />
</details>; </details>;
}; };
const ListCategory = ({ flags, name, setFlag }: { flags: OrganisedFlags, name: string, setFlag?: (flag: APIFlag) => void }) => { const ListCategory = ({ flags, name, setFlag }: { flags: OrganisedFlags, name: string, setFlag?: (flag: APIFlag) => void }) =>
{
const categories = Object.keys(flags); const categories = Object.keys(flags);
const elements: JSX.Element[] = []; const elements: JSX.Element[] = [];
for (const category of categories) { for (const category of categories)
{
const element = flags[category]; const element = flags[category];
if (element.id) if (element.id)
elements.push(<FlagListElement onClick={() => setFlag && setFlag(element as APIFlag)} key={element.id as string} flag={element as APIFlag} />); elements.push(<FlagListElement
onClick={() => setFlag && setFlag(element as APIFlag)}
key={element.id as string}
flag={element as APIFlag}
/>);
else else
elements.push(<ListCategory setFlag={setFlag} key={category} name={category} flags={element as OrganisedFlags} />); elements.push(<ListCategory setFlag={setFlag} key={category} name={category} flags={element as OrganisedFlags} />);
} }
const [hidden, setHidden] = useState(true); const [ hidden, setHidden ] = useState(true);
if (name === 'root') return <div> if (name === 'root')
return <div>
{elements} {elements}
</div>; </div>;
return <div className={`list category${ hidden ? "" : " open" }`}> return <div className={`list category${hidden ? '' : ' open'}`}>
<b className="clickable listHeader" onClick={() => { setHidden(!hidden); }}><i className="list-carrot"></i> {capitalise(name || 'Unordered')}</b> <b className='clickable listHeader' onClick={() =>
<div className="listBody"> {
setHidden(!hidden);
}}><i className='list-carrot'></i> {capitalise(name || 'Unordered')}</b>
<div className='listBody'>
{elements} {elements}
</div> </div>
</div>; </div>;
}; };
const ListView = ({ flags }: { flags: APIFlag[] }) => { const ListView = ({ flags }: { flags: APIFlag[] }) =>
{
const organised = organiseFlags(flags); const organised = organiseFlags(flags);
const [currentFlag, setCurrentFlag] = useState<APIFlag | null>(null); const [ currentFlag, setCurrentFlag ] = useState<APIFlag | null>(null);
const selectFlag = (flag: APIFlag) => { const selectFlag = (flag: APIFlag) =>
{
console.log(flag); console.log(flag);
setCurrentFlag(flag); setCurrentFlag(flag);
}; };
@ -119,9 +143,10 @@ const ListView = ({ flags }: { flags: APIFlag[] }) => {
return <ListCategory setFlag={(flag: APIFlag) => selectFlag(flag)} name={'root'} flags={organised} />; return <ListCategory setFlag={(flag: APIFlag) => selectFlag(flag)} name={'root'} flags={organised} />;
}; };
const TileView = ({ flags, children }: { flags: APIFlag[], children: React.ReactNode }) => { const TileView = ({ flags, children }: { flags: APIFlag[], children: React.ReactNode }) =>
{
return <div> return <div>
<div className="flag-list-tiles"> <div className='flag-list-tiles'>
{flags.map(flag => <FlagTile key={flag.id} flag={flag} />)} {flags.map(flag => <FlagTile key={flag.id} flag={flag} />)}
</div> </div>
{children} {children}
@ -129,34 +154,43 @@ const TileView = ({ flags, children }: { flags: APIFlag[], children: React.React
}; };
const FlagList = () => { const FlagList = () =>
{
const searchBarRef = useRef<HTMLInputElement>(null); const searchBarRef = useRef<HTMLInputElement>(null);
const [flags, setFlags] = useState<APIFlag[]>([]); const [ flags, setFlags ] = useState<APIFlag[]>([]);
const [error, setError] = useState<string | null>(null); const [ error, setError ] = useState<string | null>(null);
const [selectedFilters, setSelectedFilters] = useState<string[]>([]); const [ selectedFilters, setSelectedFilters ] = useState<string[]>([]);
const [availableFilters, setAvailableFilters] = useState<string[] | null>(null); const [ availableFilters, setAvailableFilters ] = useState<string[] | null>(null);
const [nameSearch, setNameSearch] = useState<string>(''); const [ nameSearch, setNameSearch ] = useState<string>('');
const [page, setPage] = useState(1); const [ page, setPage ] = useState(1);
const [pages, setPages] = useState(1); const [ pages, setPages ] = useState(1);
const [listView, toggleList] = useState(false); const [ listView, toggleList ] = useState(false);
useEffect(() => { useEffect(() =>
(async () => { {
(async () =>
{
const query: { [key: string]: string[] | boolean } = {}; const query: { [key: string]: string[] | boolean } = {};
for (const selected of selectedFilters) { for (const selected of selectedFilters)
const [prop, val] = selected.split(':'); {
if (!query[prop]) query[prop] = [val]; const [ prop, val ] = selected.split(':');
else (query[prop] as string[]).push(val); if (query[prop])
(query[prop] as string[]).push(val);
else
query[prop] = [ val ];
} }
if (nameSearch) query.name = [nameSearch]; if (nameSearch)
if (listView) query.all = true; query.name = [ nameSearch ];
if (listView)
query.all = true;
const response = await get('/api/flags', query); const response = await get('/api/flags', query);
if (!response.success || !response.data) { if (!response.success || !response.data)
{
setFlags([]); setFlags([]);
return setError(response.message as string); return setError(response.message as string);
} }
@ -168,7 +202,7 @@ const FlagList = () => {
setError(null); setError(null);
})(); })();
}, [selectedFilters, nameSearch, page, listView]); }, [ selectedFilters, nameSearch, page, listView ]);
return <div className='col-12-lg col-12'> return <div className='col-12-lg col-12'>
@ -176,24 +210,27 @@ const FlagList = () => {
<div> <div>
<StringInput <StringInput
inputRef={searchBarRef} inputRef={searchBarRef}
onBlur={() => { onBlur={() =>
{
setNameSearch(searchBarRef.current?.value || ''); setNameSearch(searchBarRef.current?.value || '');
}} }}
onKeyUp={(event) => { onKeyUp={(event) =>
{
if (event.key === 'Enter') if (event.key === 'Enter')
setNameSearch(searchBarRef.current?.value || ''); setNameSearch(searchBarRef.current?.value || '');
}} }}
placeholder="Flag name or ID" placeholder='Flag name or ID'
/> />
{availableFilters && <Dropdown> {availableFilters && <Dropdown>
<Dropdown.Header> <Dropdown.Header>
{selectedFilters.length === 0 {selectedFilters.length === 0
? <p className="placeholder">Click to filter</p> ? <p className='placeholder'>Click to filter</p>
: selectedFilters.map(f => <Dropdown.Item : selectedFilters.map(f => <Dropdown.Item
key={f} key={f}
onClick={() => { onClick={() =>
{
setSelectedFilters(selectedFilters.filter(ff => f !== ff)); setSelectedFilters(selectedFilters.filter(ff => f !== ff));
setAvailableFilters([...availableFilters, f]); setAvailableFilters([ ...availableFilters, f ]);
}} }}
> >
{f} {f}
@ -204,9 +241,10 @@ const FlagList = () => {
<Dropdown.ItemList> <Dropdown.ItemList>
{availableFilters.map(f => <Dropdown.Item {availableFilters.map(f => <Dropdown.Item
key={f} key={f}
onClick={() => { onClick={() =>
{
setAvailableFilters(availableFilters.filter(ff => f !== ff)); setAvailableFilters(availableFilters.filter(ff => f !== ff));
setSelectedFilters([...selectedFilters, f]); setSelectedFilters([ ...selectedFilters, f ]);
}} }}
> >
{f} {f}
@ -216,13 +254,14 @@ const FlagList = () => {
</div> </div>
<h4>Flags</h4> <h4>Flags</h4>
<ToggleSwitch value={listView} onChange={(event) => { <ToggleSwitch value={listView} onChange={(event) =>
{
toggleList(event.target.checked); toggleList(event.target.checked);
}}>List View:</ToggleSwitch> }}>List View:</ToggleSwitch>
{error && <p>{error}</p>} {error && <p>{error}</p>}
{listView ? {listView
<ListView flags={flags} /> : ? <ListView flags={flags} />
<TileView flags={flags}> : <TileView flags={flags}>
<PageButtons {...{ page, setPage, pages }} /> <PageButtons {...{ page, setPage, pages }} />
</TileView>} </TileView>}
@ -231,7 +270,8 @@ const FlagList = () => {
}; };
const CreateFlag = () => { const CreateFlag = () =>
{
return <div className='col-6-lg col-12'> return <div className='col-6-lg col-12'>
<h4>Create Flag</h4> <h4>Create Flag</h4>
@ -241,7 +281,8 @@ const CreateFlag = () => {
</div>; </div>;
}; };
const Flags = () => { const Flags = () =>
{
return <div className='row'> return <div className='row'>

View File

@ -6,135 +6,167 @@ type HttpOpts = {
method: string method: string
} }
export const getUser = () => { export const getUser = () =>
{
const data = sessionStorage.getItem('user'); const data = sessionStorage.getItem('user');
if (data) return JSON.parse(data); if (data)
return JSON.parse(data);
return null; return null;
}; };
export const clearSession = () => { export const clearSession = () =>
{
sessionStorage.removeItem('user'); sessionStorage.removeItem('user');
}; };
export const setSession = (user: User) => { export const setSession = (user: User) =>
{
sessionStorage.setItem('user', JSON.stringify(user)); sessionStorage.setItem('user', JSON.stringify(user));
}; };
export const fetchUser = async (force = false) => { export const fetchUser = async (force = false) =>
{
const user = getUser(); const user = getUser();
if (!force && user) return user; if (!force && user)
return user;
const result = await get('/api/user'); const result = await get('/api/user');
if (result.status === 200) { if (result.status === 200)
{
setSession(result.data as User); setSession(result.data as User);
return result.data; return result.data;
} }
return result; return result;
}; };
export const setSettings = (settings: ClientSettings) => { export const setSettings = (settings: ClientSettings) =>
if(settings) sessionStorage.setItem('settings', JSON.stringify(settings)); {
if(settings)
sessionStorage.setItem('settings', JSON.stringify(settings));
}; };
export const getSettings = (): ClientSettings | null => { export const getSettings = (): ClientSettings | null =>
{
const data = sessionStorage.getItem('settings'); const data = sessionStorage.getItem('settings');
if (data) return JSON.parse(data); if (data)
return JSON.parse(data);
return null; return null;
}; };
const parseResponse = async (response: Response): Promise<Res> => { const parseResponse = async (response: Response): Promise<Res> =>
{
const { headers: rawHeaders, status } = response; const { headers: rawHeaders, status } = response;
// Fetch returns heders as an interable for some reason // Fetch returns heders as an interable for some reason
const headers = [...rawHeaders].reduce((acc, [key, val]) => { const headers = [...rawHeaders].reduce((acc, [key, val]) =>
{
acc[key.toLowerCase()] = val; acc[key.toLowerCase()] = val;
return acc; return acc;
}, {} as {[key: string]: string}); }, {} as {[key: string]: string});
const success = status >= 200 && status < 300; const success = status >= 200 && status < 300;
const base = { success, status }; const base = { success, status };
if (status === 401) { if (status === 401)
{
clearSession(); clearSession();
if (!location.pathname.includes('/login') if (!location.pathname.includes('/login')
&& !location.pathname.includes('/register') && !location.pathname.includes('/register')
&& location.pathname !== '/') location.pathname = '/login'; && location.pathname !== '/')
location.pathname = '/login';
} }
if (headers['content-type']?.includes('application/json')) { if (headers['content-type']?.includes('application/json'))
{
const data = await response.json(); const data = await response.json();
return {...base, data}; return {...base, data};
} }
return { ...base, message: (await response.text() || response.statusText) }; return { ...base, message: (await response.text() || response.statusText) };
}; };
export const post = async (url: string, body?: object | string, opts: ReqOptions = {}) => { export const post = async (url: string, body?: object | string, opts: ReqOptions = {}) =>
{
const options: HttpOpts & RequestOptions = { const options: HttpOpts & RequestOptions = {
method: 'post' method: 'POST'
}; };
if (opts.headers !== null) { if (opts.headers !== null)
{
options.headers = { options.headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...opts.headers || {} ...opts.headers || {}
}; };
} }
if (options.headers && options.headers['Content-Type'] === 'application/json' && body) options.body = JSON.stringify(body); if (options.headers && options.headers['Content-Type'] === 'application/json' && body)
else options.body = body as string; options.body = JSON.stringify(body);
else
options.body = body as string;
console.log(url, options); console.log(url, options);
const response = await fetch(url, options); const response = await fetch(url, options);
return parseResponse(response); return parseResponse(response);
}; };
export const patch = async (url: string, body: object | string, opts: RequestOptions = {}) => { export const patch = async (url: string, body: object | string, opts: RequestOptions = {}) =>
{
const options: HttpOpts & RequestOptions = { const options: HttpOpts & RequestOptions = {
method: 'patch' method: 'PATCH'
}; };
if (opts.headers !== null) { if (opts.headers !== null)
{
options.headers = { options.headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...opts.headers || {} ...opts.headers || {}
}; };
} }
if (options.headers && options.headers['Content-Type'] === 'application/json' && body) options.body = JSON.stringify(body); if (options.headers && options.headers['Content-Type'] === 'application/json' && body)
else options.body = body as string; options.body = JSON.stringify(body);
else
options.body = body as string;
console.log(url, options);
const response = await fetch(url, options); const response = await fetch(url, options);
return parseResponse(response); return parseResponse(response);
}; };
export const get = async (url: string, params?: {[key: string]: boolean | string | number | string[] | number[]}) => { export const get = async (url: string, params?: {[key: string]: boolean | string | number | string[] | number[]}) =>
if (params) url += '?' + Object.entries(params) {
if (params)
url += '?' + Object.entries(params)
.map(([key, val]) => `${key}=${val instanceof Array ? val.join(',') : val}`) .map(([key, val]) => `${key}=${val instanceof Array ? val.join(',') : val}`)
.join('&'); .join('&');
const response = await fetch(url); const response = await fetch(url);
return parseResponse(response); return parseResponse(response);
}; };
export const del = async (url: string, body?: object | string, opts: RequestOptions = {}) => { export const del = async (url: string, body?: object | string, opts: RequestOptions = {}) =>
{
const options: HttpOpts & RequestOptions = { const options: HttpOpts & RequestOptions = {
method: 'delete' method: 'DELETE'
}; };
if (opts.headers !== null) { if (opts.headers !== null)
{
options.headers = { options.headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...opts.headers || {} ...opts.headers || {}
}; };
} }
if (options.headers && options.headers['Content-Type'] === 'application/json' && body) options.body = JSON.stringify(body); if (options.headers && options.headers['Content-Type'] === 'application/json' && body)
else options.body = body as string; options.body = JSON.stringify(body);
else
options.body = body as string;
const response = await fetch(url, options); const response = await fetch(url, options);
return parseResponse(response); return parseResponse(response);
}; };
export const capitalise = (str: string) => { export const capitalise = (str: string) =>
{
const first = str[0].toUpperCase(); const first = str[0].toUpperCase();
return `${first}${str.substring(1)}`; return `${first}${str.substring(1)}`;
}; };
@ -142,12 +174,15 @@ export const capitalise = (str: string) => {
export type OrganisedFlags = { export type OrganisedFlags = {
[key: string]: Flag | OrganisedFlags [key: string]: Flag | OrganisedFlags
} }
export const organiseFlags = (flags: Flag[]) => { export const organiseFlags = (flags: Flag[]) =>
{
const obj: OrganisedFlags = {}; const obj: OrganisedFlags = {};
for (const flag of flags) { for (const flag of flags)
{
const hierarchy = flag.hierarchy.split(':'); const hierarchy = flag.hierarchy.split(':');
let selected = obj; let selected = obj;
for (let i = 0; i < hierarchy.length; i++) { for (let i = 0; i < hierarchy.length; i++)
{
const rank = hierarchy[i]; const rank = hierarchy[i];
if (!selected[rank]) if (!selected[rank])
selected[rank] = {} as OrganisedFlags; selected[rank] = {} as OrganisedFlags;