Flags page
Dropdown menu + other input elements improved pagination elements
This commit is contained in:
parent
6476a205a4
commit
d6d7de4d93
@ -63,5 +63,6 @@ export type Flag = {
|
|||||||
name: string,
|
name: string,
|
||||||
env: FlagEnv,
|
env: FlagEnv,
|
||||||
consumer: FlagConsumer,
|
consumer: FlagConsumer,
|
||||||
value: FlagType
|
value: FlagType,
|
||||||
|
type: string
|
||||||
}
|
}
|
@ -38,36 +38,91 @@ export const FileSelector = ({ cb }: { cb: (file: File) => void }) => {
|
|||||||
</label>;
|
</label>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ToggleSwitch = ({ value, onChange, ref, children }:
|
export type InputElementProperties<T> = {
|
||||||
{ value: boolean, ref?: React.RefObject<HTMLInputElement>, onChange?: React.ChangeEventHandler, children: string }) => {
|
value?: T,
|
||||||
|
inputRef?: React.RefObject<HTMLInputElement>,
|
||||||
|
onChange?: React.ChangeEventHandler,
|
||||||
|
children?: string,
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ToggleSwitch = ({ value, onChange, inputRef, children }: InputElementProperties<boolean>) => {
|
||||||
return <p>
|
return <p>
|
||||||
<label>
|
<label>
|
||||||
<span className="check-box check-box-row">
|
<span className="check-box check-box-row">
|
||||||
<b>{children}</b>
|
{children && <b>{children}</b>}
|
||||||
<input ref={ref} defaultChecked={value} onChange={onChange} type='checkbox' />
|
<input ref={inputRef} defaultChecked={value} onChange={onChange} type='checkbox' />
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</p>;
|
</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VerticalToggleSwitch = ({ value, onChange, ref, children }:
|
export const VerticalToggleSwitch = ({ value, onChange, inputRef, children }: InputElementProperties<boolean>) => {
|
||||||
{ value: boolean, ref?: React.RefObject<HTMLInputElement>, onChange?: React.ChangeEventHandler, children: string }) => {
|
|
||||||
return <p>
|
return <p>
|
||||||
<label>
|
<label>
|
||||||
<b>{children}</b>
|
{children && <b>{children}</b>}
|
||||||
<span className="check-box">
|
<span className="check-box">
|
||||||
<input ref={ref} defaultChecked={value} onChange={onChange} type='checkbox' />
|
<input ref={inputRef} defaultChecked={value} onChange={onChange} type='checkbox' />
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</p>;
|
</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NumberInput = () => {
|
type ManualInputProperties<T> = {
|
||||||
//
|
onBlur?: React.FocusEventHandler,
|
||||||
|
onKeyUp?: React.KeyboardEventHandler
|
||||||
|
} & InputElementProperties<T>;
|
||||||
|
|
||||||
|
type StringInputProperties = {
|
||||||
|
maxLength?: number,
|
||||||
|
minLength?: number
|
||||||
|
} & ManualInputProperties<string>
|
||||||
|
export const StringInput = ({ value, onChange, inputRef, children, placeholder, onBlur, onKeyUp, minLength, maxLength }: StringInputProperties) => {
|
||||||
|
return <label>
|
||||||
|
{children && <b>{children}</b>}
|
||||||
|
<input
|
||||||
|
onBlur={onBlur}
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
ref={inputRef}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyUp={onKeyUp}
|
||||||
|
maxLength={maxLength}
|
||||||
|
minLength={minLength}
|
||||||
|
/>
|
||||||
|
</label>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NumberInputProperties = {
|
||||||
|
min?: number,
|
||||||
|
max?: number,
|
||||||
|
type?: 'float' | 'int',
|
||||||
|
step?: number
|
||||||
|
} & ManualInputProperties<number>
|
||||||
|
|
||||||
|
export const NumberInput = ({ children, placeholder, inputRef, onChange, value, min, max, type, step }: NumberInputProperties) => {
|
||||||
|
if (typeof step === 'undefined') {
|
||||||
|
if (type === 'float') step = 0.1;
|
||||||
|
else if (type === 'int') step = 1;
|
||||||
|
else step = 1;
|
||||||
|
}
|
||||||
|
return <label>
|
||||||
|
{children && <b>{children}</b>}
|
||||||
|
<input
|
||||||
|
placeholder={placeholder}
|
||||||
|
ref={inputRef}
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={onChange}
|
||||||
|
type="number"
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
|
/>
|
||||||
|
</label>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DropdownHeader = ({ children, className = '' }: DropdownBaseProps) => {
|
const DropdownHeader = ({ children, className = '' }: DropdownBaseProps) => {
|
||||||
return <summary className={`card is-vertical-align dropdown-header p-2 ${className}`}>
|
return <summary className={`card is-vertical-align header p-2 ${className}`}>
|
||||||
{children}
|
{children}
|
||||||
</summary>;
|
</summary>;
|
||||||
};
|
};
|
||||||
@ -78,16 +133,19 @@ const DropdownItem = ({ children, type, selected, onClick }: DropdownItemProps)
|
|||||||
InnerElement = <ToggleSwitch value={selected || false} onChange={onClick}>
|
InnerElement = <ToggleSwitch value={selected || false} onChange={onClick}>
|
||||||
{children as string}
|
{children as string}
|
||||||
</ToggleSwitch>;
|
</ToggleSwitch>;
|
||||||
else InnerElement = <div onClick={onClick}>
|
else InnerElement = <div onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if(onClick) onClick(event);
|
||||||
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</div>;
|
</div>;
|
||||||
return <div className='dropdown-item'>
|
return <div className='card item'>
|
||||||
{InnerElement}
|
{InnerElement}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DropdownItemList = ({ children }: DropdownBaseProps) => {
|
const DropdownItemList = ({ children, className = '' }: DropdownBaseProps) => {
|
||||||
return <div className='card w-100 '>
|
return <div className={`card item-list ${className}`}>
|
||||||
{children}
|
{children}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
@ -126,4 +184,9 @@ const Dropdown = Object.assign(DropdownComp, {
|
|||||||
Item: DropdownItem
|
Item: DropdownItem
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type InputElementType =
|
||||||
|
| ((props: InputElementProperties<string>) => React.ReactElement)
|
||||||
|
| ((props: InputElementProperties<boolean>) => React.ReactElement)
|
||||||
|
| ((props: NumberInputProperties) => React.ReactElement);
|
||||||
|
|
||||||
export { Dropdown };
|
export { Dropdown };
|
@ -1,14 +1,23 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
export const PageButtons = ({ setPage, page, length }: { setPage: (page: number) => void, page: number, length: number}) => {
|
type PageButtonProps = {
|
||||||
|
setPage: (page: number) => void,
|
||||||
|
page: number,
|
||||||
|
pages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PageButtons = ({ setPage, page, pages }: PageButtonProps) => {
|
||||||
return <div className='flex is-vertical-align is-center page-controls'>
|
return <div className='flex is-vertical-align is-center page-controls'>
|
||||||
<button className="button dark" onClick={() => setPage(page - 1 || 1)}>Previous</button>
|
<button className="button dark" disabled={page === 1} onClick={() => {
|
||||||
<p>Page: {page}</p>
|
setPage(page - 1 || 1);
|
||||||
<button className="button dark" onClick={() => {
|
}}>Previous</button>
|
||||||
if (length === 10) setPage(page + 1);
|
<p>Page: {page} / {pages}</p>
|
||||||
}}>Next</button>
|
<button className="button dark" disabled={page === pages} onClick={() => {
|
||||||
</div>;
|
if (page < pages)
|
||||||
|
setPage(page + 1);
|
||||||
|
}}>Next</button>
|
||||||
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BackButton = () => {
|
export const BackButton = () => {
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
.role-selector {
|
.dropdown .header {
|
||||||
|
min-height: 40px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .item-list {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
min-height: 26px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .item {
|
||||||
|
margin: 2px;
|
||||||
|
padding: 0.5rem 1rem !important;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
@ -82,6 +82,11 @@ table.striped tr:nth-of-type(2n) {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.danger {
|
||||||
|
background-color: var(--color-error);
|
||||||
|
color: var(--font-color);
|
||||||
|
}
|
||||||
|
|
||||||
.page-controls button{
|
.page-controls button{
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
.flag-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
/* flex: 1 0 21%; */
|
||||||
|
width: calc(25% - 8px);
|
||||||
|
}
|
@ -95,5 +95,10 @@ li input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.role {
|
.role {
|
||||||
margin: 5px
|
margin: 2px;
|
||||||
|
padding: 0.5rem 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-selector {
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
@ -1,33 +1,119 @@
|
|||||||
import React, { useEffect, 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 { get } from "../../util/Util";
|
import { get } from "../../util/Util";
|
||||||
import { ToggleSwitch } from "../../components/InputElements";
|
import { Dropdown, InputElementType, NumberInput, StringInput, ToggleSwitch } from "../../components/InputElements";
|
||||||
|
import { PageButtons } from "../../components/PageControls";
|
||||||
|
|
||||||
const inputTypes = {
|
const ValidFilters = ['env:prod', 'env:test', 'consumer:client', 'consumer:server', 'consumer:api'];
|
||||||
boolean: ToggleSwitch
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const Flag = ({ flag }: { flag: APIFlag }) => {
|
const Flag = ({ flag }: { flag: APIFlag }) => {
|
||||||
|
|
||||||
return <div className='flag card'>
|
let Input = <p>Loading...</p>;
|
||||||
<p><b>Name:</b> {flag.name}</p>
|
if (flag.type === 'string') Input = <StringInput value={flag.value as string}>
|
||||||
<p><b>ID:</b> {flag.id}</p>
|
Value:
|
||||||
<p><b>Environment:</b> {flag.env}</p>
|
</StringInput>;
|
||||||
<p><b>Consumer:</b> {flag.consumer}</p>
|
else if (flag.type === 'number') Input = <NumberInput value={flag.value as number} type='float'>
|
||||||
<label><b>Value:</b> <input /></label>
|
Value:
|
||||||
|
</NumberInput>;
|
||||||
|
else if (flag.type === 'boolean') Input = <ToggleSwitch value={flag.value as boolean}>
|
||||||
|
Value:
|
||||||
|
</ToggleSwitch>;
|
||||||
|
|
||||||
|
return <div className='flag card mt-0 mb-0'>
|
||||||
|
{/* <p className="mt-0 mb-1"><b>Name:</b> {flag.name}</p> */}
|
||||||
|
<h3 className="mt-0 mb-1">{flag.name}</h3>
|
||||||
|
<button className="button danger">Delete</button>
|
||||||
|
<button className="button primary">Save</button>
|
||||||
|
<p className="mt-0 mb-1"><b>ID:</b> {flag.id}</p>
|
||||||
|
<p className="mt-0 mb-1"><b>Environment:</b> {flag.env}</p>
|
||||||
|
<p className="mt-0 mb-1"><b>Consumer:</b> {flag.consumer}</p>
|
||||||
|
{Input}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const FlagList = ({ flags, error }: { flags: APIFlag[], error: string | null }) => {
|
|
||||||
|
|
||||||
console.log(flags);
|
const FlagList = () => {
|
||||||
return <div className='col-6-lg col-12'>
|
|
||||||
|
const searchBarRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [flags, setFlags] = useState<APIFlag[]>([]);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [selectedFilters, setSelectedFilters] = useState<string[]>([]);
|
||||||
|
const [availableFilters, setAvailableFilters] = useState(ValidFilters);
|
||||||
|
const [nameSearch, setNameSearch] = useState<string>('');
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [pages, setPages] = useState(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const query: { [key: string]: string[] } = {};
|
||||||
|
for (const selected of selectedFilters) {
|
||||||
|
const [prop, val] = selected.split(':');
|
||||||
|
if (!query[prop]) query[prop] = [val];
|
||||||
|
else query[prop].push(val);
|
||||||
|
}
|
||||||
|
if (nameSearch) query.name = [nameSearch];
|
||||||
|
const response = await get('/api/flags', query);
|
||||||
|
console.log(response);
|
||||||
|
if (!response.success || !response.data) {
|
||||||
|
setFlags([]);
|
||||||
|
return setError(response.message as string);
|
||||||
|
}
|
||||||
|
setFlags(response.data.flags as APIFlag[]);
|
||||||
|
setPages(response.data.pages);
|
||||||
|
setError(null);
|
||||||
|
})();
|
||||||
|
}, [selectedFilters, nameSearch, page]);
|
||||||
|
|
||||||
|
return <div className='col-12-lg col-12'>
|
||||||
|
|
||||||
|
<h4>Search</h4>
|
||||||
|
<div>
|
||||||
|
<StringInput
|
||||||
|
inputRef={searchBarRef}
|
||||||
|
onBlur={() => {
|
||||||
|
setNameSearch(searchBarRef.current?.value || '');
|
||||||
|
}}
|
||||||
|
onKeyUp={(event) => {
|
||||||
|
if (event.key === 'Enter')
|
||||||
|
setNameSearch(searchBarRef.current?.value || '');
|
||||||
|
}}
|
||||||
|
placeholder="Flag name or ID"
|
||||||
|
/>
|
||||||
|
<Dropdown>
|
||||||
|
<Dropdown.Header>
|
||||||
|
{selectedFilters.map(f => <Dropdown.Item
|
||||||
|
key={f}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedFilters(selectedFilters.filter(ff => f !== ff));
|
||||||
|
setAvailableFilters([...availableFilters, f]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{f}
|
||||||
|
</Dropdown.Item>)}
|
||||||
|
</Dropdown.Header>
|
||||||
|
|
||||||
|
<Dropdown.ItemList>
|
||||||
|
{availableFilters.map(f => <Dropdown.Item
|
||||||
|
key={f}
|
||||||
|
onClick={() => {
|
||||||
|
setAvailableFilters(availableFilters.filter(ff => f !== ff));
|
||||||
|
setSelectedFilters([...selectedFilters, f]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{f}
|
||||||
|
</Dropdown.Item>)}
|
||||||
|
</Dropdown.ItemList>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4>Flags</h4>
|
<h4>Flags</h4>
|
||||||
{error && <p>{error}</p>}
|
{error && <p>{error}</p>}
|
||||||
|
<div className="flag-list">
|
||||||
|
{flags.map(flag => <Flag key={flag.id} flag={flag} />)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{flags.map(flag => <Flag key={flag.id} flag={flag} />)}
|
<PageButtons {...{ page, setPage, pages }} />
|
||||||
|
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
@ -45,20 +131,9 @@ const CreateFlag = () => {
|
|||||||
|
|
||||||
const Flags = () => {
|
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'>
|
return <div className='row'>
|
||||||
|
|
||||||
<FlagList flags={flags} error={error} />
|
<FlagList />
|
||||||
|
|
||||||
<CreateFlag />
|
<CreateFlag />
|
||||||
|
|
||||||
|
@ -99,14 +99,16 @@ const Roles = () => {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [roles, setRoles] = useState<R[]>([]);
|
const [roles, setRoles] = useState<R[]>([]);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
const [pages, setPages] = useState(1);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const result = await get('/api/roles', { page });
|
const result = await get('/api/roles', { page });
|
||||||
if (result.success) {
|
if (result.success && result.data) {
|
||||||
setError(null);
|
setError(null);
|
||||||
setRoles(result.data as R[]);
|
setRoles(result.data.roles as R[]);
|
||||||
|
setPages(result.data.pages);
|
||||||
} else {
|
} else {
|
||||||
setError(result.message || 'Unknown error');
|
setError(result.message || 'Unknown error');
|
||||||
}
|
}
|
||||||
@ -137,7 +139,7 @@ const Roles = () => {
|
|||||||
/>)}
|
/>)}
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<PageButtons {...{page, setPage, length: roles.length}} />
|
<PageButtons {...{page, setPage, pages}} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -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 p-3' >
|
return <div className='card role' >
|
||||||
{role.name} <span className="clickable" onClick={onClick}>X</span>
|
{role.name} <span className="clickable" onClick={onClick}>X</span>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
@ -99,7 +99,8 @@ const Roles = ({ userRoles, roles }: { userRoles: Role[], roles: Role[] }) => {
|
|||||||
return <Dropdown>
|
return <Dropdown>
|
||||||
|
|
||||||
<Dropdown.Header className='role-selector'>
|
<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={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
roleDeselected(role);
|
roleDeselected(role);
|
||||||
}} />)}
|
}} />)}
|
||||||
</Dropdown.Header>
|
</Dropdown.Header>
|
||||||
@ -233,20 +234,23 @@ const Users = () => {
|
|||||||
|
|
||||||
const [users, setUsers] = useState<APIUser[]>([]);
|
const [users, setUsers] = useState<APIUser[]>([]);
|
||||||
const [roles, updateRoles] = useState<Role[]>([]);
|
const [roles, updateRoles] = useState<Role[]>([]);
|
||||||
const [page, setPage] = useState<number>(1);
|
const [page, setPage] = useState(1);
|
||||||
|
const [pages, setPages] = useState(1);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const result = await get('/api/users', { page });
|
const result = await get('/api/users', { page });
|
||||||
if (result.success) {
|
if (result.success && result.data) {
|
||||||
setError(null);
|
setError(null);
|
||||||
setUsers(result.data as APIUser[]);
|
setUsers(result.data.users as APIUser[]);
|
||||||
|
setPages(result.data.pages);
|
||||||
} else setError(result.message || 'Unknown error');
|
} else setError(result.message || 'Unknown error');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
const rolesResponse = await get('/api/roles');
|
const rolesResponse = await get('/api/roles', { all: true });
|
||||||
if (rolesResponse.success) updateRoles(rolesResponse.data as Role[]);
|
if (rolesResponse.success && rolesResponse.data)
|
||||||
|
updateRoles(rolesResponse.data.roles as Role[]);
|
||||||
})();
|
})();
|
||||||
}, [page]);
|
}, [page]);
|
||||||
|
|
||||||
@ -272,7 +276,7 @@ const Users = () => {
|
|||||||
itemKeys={['name', 'id']} />)}
|
itemKeys={['name', 'id']} />)}
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<PageButtons {...{ page, setPage, length: users.length }} />
|
<PageButtons {...{ page, setPage, pages }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CreateUserField className="col-6-lg col-12" />
|
<CreateUserField className="col-6-lg col-12" />
|
||||||
|
@ -31,7 +31,7 @@ const ClickDetector = ({ children, callback }: {children: React.ReactNode, callb
|
|||||||
alerter(wrapperRef, callback);
|
alerter(wrapperRef, callback);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={wrapperRef}>
|
<div className='click-detector' ref={wrapperRef}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -63,7 +63,7 @@ const parseResponse = async (response: Response): Promise<Res> => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return {...base, data};
|
return {...base, data};
|
||||||
}
|
}
|
||||||
return { ...base, message: await response.text() };
|
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 = {}) => {
|
||||||
@ -107,9 +107,9 @@ export const patch = async (url: string, body: object | string, opts: RequestOpt
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const get = async (url: string, params?: {[key: string]: 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}`)
|
.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);
|
||||||
|
Loading…
Reference in New Issue
Block a user