Should probably invoke the func
This commit is contained in:
parent
a908013b6d
commit
5d0cdab3d0
@ -56,7 +56,8 @@ export type Application = {
|
||||
|
||||
export type Role = {
|
||||
position: number,
|
||||
rateLimits: RateLimits
|
||||
rateLimits: RateLimits,
|
||||
tag: boolean,
|
||||
} & APIEntity
|
||||
|
||||
export type FlagType = string | number | boolean | number[] | null
|
||||
|
@ -6,10 +6,10 @@ export type RequestOptions = {
|
||||
headers?: { [key: string]: string },
|
||||
}
|
||||
|
||||
export type Res = {
|
||||
export type Res<T> = {
|
||||
success: boolean,
|
||||
status: number,
|
||||
data?: { [key: string]: any },
|
||||
data?: T, //{ [key: string]: any },
|
||||
message?: string
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ const App = () =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const result = await get('/api/user');
|
||||
const result = await get<{twoFactor: boolean}>('/api/user');
|
||||
if (result.status === 200)
|
||||
{
|
||||
setSession(result.data as User);
|
||||
|
@ -83,7 +83,7 @@ export const DataCard = ({ id, className, children = [] }: DataCardProps) =>
|
||||
|
||||
};
|
||||
|
||||
type PaginatedCardUrlProps = {
|
||||
type PaginatedCardUrlProps<T> = {
|
||||
path: string,
|
||||
query?: {[key: string | number]: unknown}
|
||||
dataKey?: string,
|
||||
@ -91,14 +91,14 @@ type PaginatedCardUrlProps = {
|
||||
tableDivClass?: string,
|
||||
children?: React.ReactNode | React.ReactNode[],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
formatter: (entry: any, onClick?: (...args: any[]) => void, idx?: number) => JSX.Element
|
||||
formatter: (entry: T, callback?: (...args: any[]) => void, idx?: number) => JSX.Element
|
||||
// FormatterCallback
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callback?: (...args: any[]) => void,
|
||||
pageSize?: number,
|
||||
refresh?: boolean
|
||||
}
|
||||
export const PaginatedCard = ({
|
||||
export const PaginatedCard = <T, >({
|
||||
// id = randomString(),
|
||||
className,
|
||||
tableDivClass,
|
||||
@ -108,14 +108,14 @@ export const PaginatedCard = ({
|
||||
formatter,
|
||||
callback,
|
||||
pageSize = 10,
|
||||
query = {},
|
||||
query,
|
||||
refresh
|
||||
}: PaginatedCardUrlProps) =>
|
||||
}: PaginatedCardUrlProps<T>) =>
|
||||
{
|
||||
if (!(children instanceof Array))
|
||||
children = [ children ];
|
||||
|
||||
const [ data, setData ] = useState<unknown[] | null>(null);
|
||||
const [ data, setData ] = useState<T[] | null>(null);
|
||||
const [ page, setPage ] = useState(1);
|
||||
const [ pages, setPages ] = useState(1);
|
||||
const [ loadState, setLoadState ] = useState(RefresherState.loading);
|
||||
@ -131,7 +131,7 @@ export const PaginatedCard = ({
|
||||
setRefresh(val => !val);
|
||||
};
|
||||
const deps: (string | number | object | boolean)[] = [ page, path, refreshLocal ];
|
||||
if(query && Object.keys(query).length)
|
||||
if(query)
|
||||
deps.push(query);
|
||||
if (typeof refresh !== 'undefined')
|
||||
deps.push(refresh);
|
||||
@ -141,19 +141,28 @@ export const PaginatedCard = ({
|
||||
(async () =>
|
||||
{
|
||||
setLoadState(RefresherState.loading);
|
||||
if(query)
|
||||
{
|
||||
const keys = Object.keys(query);
|
||||
for (const key of keys)
|
||||
if (typeof query[key] === 'undefined')
|
||||
delete query[key];
|
||||
const response = await get(path, { page, pageSize, ...query });
|
||||
}
|
||||
const response = await get<{ [key: string]: number | T[], pages: number }>(path, { page, pageSize, ...query });
|
||||
// if (!response.data?.bruh)
|
||||
// throw new Error('bruh');
|
||||
if (!response.success || !response.data)
|
||||
{
|
||||
setLoadState(RefresherState.error);
|
||||
return errorToast(response.message);
|
||||
}
|
||||
// return setError(response.message ?? null);
|
||||
setData(response.data[dataKey]);
|
||||
setData(response.data[dataKey] as T[]);
|
||||
setPages(response.data.pages ?? 1);
|
||||
if(response.data.pages && page > response.data.pages)
|
||||
setPage(response.data.pages == 0 ? 1 : response.data.pages);
|
||||
else if(response.data.pages == 0)
|
||||
setPage(1);
|
||||
setLoadState(RefresherState.success);
|
||||
})();
|
||||
}, deps);
|
||||
@ -184,7 +193,9 @@ type BasePaginatedCardProps = {
|
||||
onRefresh?: () => void, // Callback that is fired whenever the refresher is clicked
|
||||
// onlineList?: string
|
||||
}
|
||||
export const BasePaginatedCard = ({ children, data, className, id, tableDivClass, formatter, callback, page, pages, setPage, loadState, onRefresh }: BasePaginatedCardProps) =>
|
||||
export const BasePaginatedCard = ({ children, data,
|
||||
className, id, tableDivClass, formatter, callback,
|
||||
page, pages, setPage, loadState, onRefresh }: BasePaginatedCardProps) =>
|
||||
{
|
||||
if (!(children instanceof Array))
|
||||
children = [ children ];
|
||||
@ -235,7 +246,7 @@ export const TableCard = ({ id, children, className = '', tableDivClass = '', da
|
||||
{/* <p>{'Loading...'}</p> */}
|
||||
</DataCard>;
|
||||
|
||||
return <DataCard id={id} className={className}>
|
||||
return <DataCard id={id} className={`${className} content-nm content-bm-2`}>
|
||||
{cardHeader}
|
||||
<div className={tableDivClass}>
|
||||
<table className='striped table-pd'>
|
||||
|
@ -31,7 +31,7 @@ const CredentialFields = () =>
|
||||
return;
|
||||
|
||||
button.disabled = true;
|
||||
const result = await post('/api/login', { username, password });
|
||||
const result = await post<{twoFactor: boolean}>('/api/login', { username, password });
|
||||
button.disabled = false;
|
||||
if (![ 200, 302 ].includes(result.status))
|
||||
{
|
||||
|
@ -321,7 +321,7 @@ const Main = () =>
|
||||
if (listView)
|
||||
query.all = true;
|
||||
|
||||
const response = await get('/api/flags', query);
|
||||
const response = await get<{pages: number, flags: APIFlag[], tags: string[]}>('/api/flags', query);
|
||||
if (!response.success || !response.data)
|
||||
{
|
||||
setFlags([]);
|
||||
@ -329,7 +329,7 @@ const Main = () =>
|
||||
return errorToast(response.message);
|
||||
}
|
||||
|
||||
setFlags(response.data.flags as APIFlag[]);
|
||||
setFlags(response.data.flags);
|
||||
setPages(response.data.pages);
|
||||
if (availableFilters === null)
|
||||
setAvailableFilters(response.data.tags);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Route, Routes, useNavigate, useParams } from 'react-router';
|
||||
import ErrorBoundary from '../../util/ErrorBoundary';
|
||||
import { Permissions as PermissionsStruct, Role as RoleStruct, User } from '../../@types/ApiStructures';
|
||||
import { Role as RoleStruct, User } from '../../@types/ApiStructures';
|
||||
import { BasePaginatedCard, DataCard, PaginatedCard, RefresherState } from '../../components/PageElements';
|
||||
import { dateString, del, errorToast, get, patch, post, successToast } from '../../util/Util';
|
||||
import { BackButton } from '../../components/PageControls';
|
||||
import { ClickToEdit } from '../../components/InputElements';
|
||||
import { ClickToEdit, ToggleSwitch } from '../../components/InputElements';
|
||||
import { Permissions } from '../../views/PermissionsView';
|
||||
import { useSubtitle } from '../../components/TitledPage';
|
||||
|
||||
@ -58,13 +58,13 @@ const Role = ({ refreshList }: RoleProps) =>
|
||||
<p>Loading...</p>
|
||||
</div>;
|
||||
|
||||
const updatePerms = (perms: PermissionsStruct) =>
|
||||
const updateRoleStruct = (data: Partial<RoleStruct>) =>
|
||||
{
|
||||
setRole(role =>
|
||||
{
|
||||
if (!role)
|
||||
return role;
|
||||
return { ...role, permissions: perms };
|
||||
return { ...role, ...data };
|
||||
});
|
||||
};
|
||||
|
||||
@ -79,6 +79,14 @@ const Role = ({ refreshList }: RoleProps) =>
|
||||
successToast('Permissions updated');
|
||||
};
|
||||
|
||||
const updateRole = async ({ tag }: Partial<RoleStruct>) =>
|
||||
{
|
||||
const response = await patch<RoleStruct>(`/api/roles/${roleId}`, { tag });
|
||||
if (!response.success || !response.data)
|
||||
return errorToast(response.message);
|
||||
updateRoleStruct(response.data);
|
||||
};
|
||||
|
||||
const deleteRole = async (e: React.MouseEvent) =>
|
||||
{
|
||||
const button = e.target as HTMLButtonElement;
|
||||
@ -136,7 +144,8 @@ const Role = ({ refreshList }: RoleProps) =>
|
||||
<td>{dateString(role.createdTimestamp)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
<th>Tag</th>
|
||||
<td><ToggleSwitch defaultValue={role.tag} onChange={({ target }) => updateRole({ tag: target.checked })} /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -160,7 +169,7 @@ const Role = ({ refreshList }: RoleProps) =>
|
||||
<div>Permissions</div>
|
||||
<div>
|
||||
<div className='scrollable h-max-80-vh'>
|
||||
<Permissions perms={role.permissions} onUpdate={updatePerms} />
|
||||
<Permissions perms={role.permissions} onUpdate={(permissions) => updateRoleStruct({ permissions })} />
|
||||
</div>
|
||||
<hr />
|
||||
<div className='ml-4'>
|
||||
@ -289,14 +298,14 @@ const Main = () =>
|
||||
(async () =>
|
||||
{
|
||||
setLoadState(RefresherState.loading);
|
||||
const response = await get('/api/roles', { page, pageSize: 15, ...filters });
|
||||
const response = await get<{pages: number, roles: RoleStruct[]}>('/api/roles', { page, pageSize: 15, ...filters });
|
||||
if (!response.success || !response.data)
|
||||
{
|
||||
setLoadState(RefresherState.error);
|
||||
return errorToast(response.message);
|
||||
}
|
||||
setPages(response.data.pages);
|
||||
setRoles(new Map(response.data.roles.map((role: RoleStruct) => [ role.id, role ])));
|
||||
setRoles(new Map(response.data.roles.map((role) => [ role.id, role ])));
|
||||
setLoadState(RefresherState.success);
|
||||
})();
|
||||
}, [ page, filters, refresh ]);
|
||||
|
@ -204,7 +204,7 @@ const UserData = ({ user, updateUser }: {user: UserStruct | null, updateUser: (u
|
||||
{
|
||||
const button = e.target as HTMLButtonElement;
|
||||
button.disabled = true;
|
||||
const response = await get(`/api/users/${user.id}/passwdreset`);
|
||||
const response = await get<{token: string}>(`/api/users/${user.id}/passwdreset`);
|
||||
button.disabled = false;
|
||||
if (!response.success || !response.data)
|
||||
return errorToast(response.message);
|
||||
@ -418,7 +418,7 @@ const UserList = ({ page, pages, setPage, loadState, onRefresh }: UserListProps)
|
||||
|
||||
const getSignupCode = async () =>
|
||||
{
|
||||
const response = await get('/api/register/code');
|
||||
const response = await get<{code: string}>('/api/register/code');
|
||||
if (!response.success || !response.data)
|
||||
return errorToast(response.message);
|
||||
const link = `${window.location.origin}/register?code=${response.data.code}`;
|
||||
@ -533,14 +533,14 @@ const Main = () =>
|
||||
(async () =>
|
||||
{
|
||||
setLoadState(RefresherState.loading);
|
||||
const response = await get('/api/users', { page, pageSize: 15, ...searchFilter });
|
||||
const response = await get<{pages: number, users: UserStruct[]}>('/api/users', { page, pageSize: 15, ...searchFilter });
|
||||
if (!response.success || !response.data)
|
||||
{
|
||||
setLoadState(RefresherState.error);
|
||||
return errorToast(response.message);
|
||||
}
|
||||
setPages(response.data.pages);
|
||||
setUsers(new Map(response.data.users.map((user: UserStruct) => [ user.id, user ])));
|
||||
setUsers(new Map(response.data.users.map((user) => [ user.id, user ])));
|
||||
setLoadState(RefresherState.success);
|
||||
})();
|
||||
}, [ page, searchFilter, refresh ]);
|
||||
@ -549,10 +549,10 @@ const Main = () =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const response = await get('/api/roles', { all: true });
|
||||
const response = await get<{roles: RoleStruct[]}>('/api/roles', { all: true });
|
||||
if (!response.success || !response.data)
|
||||
return errorToast(response.message);
|
||||
setRoles(new Map(response.data.roles.map((role: RoleStruct) => [ role.id, role ])));
|
||||
setRoles(new Map(response.data.roles.map((role) => [ role.id, role ])));
|
||||
})();
|
||||
}, [ refresh ]);
|
||||
|
||||
|
@ -1,20 +1,36 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Route, Routes, useNavigate, useParams } from 'react-router';
|
||||
|
||||
import { Table, TableListEntry } from '../../components/Table';
|
||||
import ErrorBoundary from '../../util/ErrorBoundary';
|
||||
import { post, get, del, successToast, patch, errorToast } from '../../util/Util';
|
||||
import { Application as App } from '../../@types/ApiStructures';
|
||||
import { Res } from '../../@types/Other';
|
||||
import { DataCard } from '../../components/PageElements';
|
||||
import { post, del, successToast, patch, errorToast, get } from '../../util/Util';
|
||||
import { Application as App, Permissions as Perms } from '../../@types/ApiStructures';
|
||||
import { DataCard, PaginatedCard } from '../../components/PageElements';
|
||||
import { BackButton } from '../../components/PageControls';
|
||||
import { ClickToEdit } from '../../components/InputElements';
|
||||
import { Permissions } from '../../views/PermissionsView';
|
||||
import { useSubtitle } from '../../components/TitledPage';
|
||||
|
||||
const Application = ({ app }: {app: App}) =>
|
||||
const Application = () =>
|
||||
{
|
||||
const [ perms, updatePerms ] = useState(app.permissions);
|
||||
const { id } = useParams();
|
||||
const [ app, setApp ] = useState<App | null>(null);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const response = await get<App>(`/api/users/applications/${id}`);
|
||||
if (!response.success || !response.data)
|
||||
return errorToast(response.message);
|
||||
setApp(response.data);
|
||||
})();
|
||||
}, [ id ]);
|
||||
|
||||
if (!app)
|
||||
return <div>
|
||||
<BackButton />
|
||||
<p>Loading...</p>
|
||||
</div>;
|
||||
|
||||
const deleteApp = async () =>
|
||||
{
|
||||
@ -33,7 +49,7 @@ const Application = ({ app }: {app: App}) =>
|
||||
const button = e.target as HTMLButtonElement;
|
||||
button.disabled = true;
|
||||
const response = await patch(`/api/user/applications/${app.id}`, {
|
||||
permissions: perms
|
||||
permissions: app.permissions
|
||||
});
|
||||
button.disabled = false;
|
||||
if (!response.success)
|
||||
@ -41,18 +57,28 @@ const Application = ({ app }: {app: App}) =>
|
||||
return successToast('Successfully updated permissions');
|
||||
};
|
||||
|
||||
const updatePerms = (permissions: Perms) =>
|
||||
{
|
||||
setApp(app =>
|
||||
{
|
||||
if (!app)
|
||||
return app;
|
||||
return { ...app, permissions };
|
||||
});
|
||||
};
|
||||
|
||||
const resetPerms = async (e: React.MouseEvent) =>
|
||||
{
|
||||
const button = e.target as HTMLButtonElement;
|
||||
button.disabled = true;
|
||||
const response = await patch(`/api/user/applications/${app.id}`, {
|
||||
const response = await patch<App>(`/api/user/applications/${app.id}`, {
|
||||
permissions: null
|
||||
});
|
||||
button.disabled = false;
|
||||
if (!response.success)
|
||||
if (!response.success || !response.data)
|
||||
return errorToast(response.message);
|
||||
successToast('Successfully reset permissions');
|
||||
updatePerms(response.data?.permissions);
|
||||
updatePerms(response.data.permissions);
|
||||
};
|
||||
|
||||
return <div>
|
||||
@ -95,7 +121,7 @@ const Application = ({ app }: {app: App}) =>
|
||||
</div>
|
||||
<div>
|
||||
<div className='scrollable h-max-80-vh'>
|
||||
<Permissions perms={perms} onUpdate={updatePerms} />
|
||||
<Permissions perms={app.permissions} onUpdate={updatePerms} />
|
||||
</div>
|
||||
<hr />
|
||||
<div className='ml-4'>
|
||||
@ -109,23 +135,22 @@ const Application = ({ app }: {app: App}) =>
|
||||
|
||||
};
|
||||
|
||||
const applicationFormatter = (app: App, callback?: (id: string) => void) =>
|
||||
{
|
||||
return <tr key={app.id} onClick={() => callback && callback(app.id)}>
|
||||
<td>{app.name}</td>
|
||||
<td>{app.id}</td>
|
||||
</tr>;
|
||||
};
|
||||
|
||||
const Applications = () =>
|
||||
{
|
||||
const subtitle = useSubtitle();
|
||||
const [ applications, setApplications ] = useState<App[]>([]);
|
||||
const [ loading, setLoading ] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
subtitle.set('Applications');
|
||||
(async () =>
|
||||
{
|
||||
const response = await get('/api/user/applications') as Res;
|
||||
if (response.status === 200)
|
||||
setApplications(response.data as App[]);
|
||||
setLoading(false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const descField = useRef<HTMLTextAreaElement>(null);
|
||||
@ -151,8 +176,6 @@ const Applications = () =>
|
||||
const response = await post('/api/user/applications', { name, description });
|
||||
if (response.status !== 200)
|
||||
errorToast(response.message);
|
||||
else
|
||||
setApplications((apps) => [ ...apps, response.data as App ]);
|
||||
|
||||
button.disabled = false;
|
||||
};
|
||||
@ -161,7 +184,26 @@ const Applications = () =>
|
||||
{
|
||||
return <div className="row">
|
||||
|
||||
<div className={`col ld ${loading && 'loading'}`}>
|
||||
<PaginatedCard
|
||||
path='/api/user/applications'
|
||||
formatter={applicationFormatter}
|
||||
callback={(id) =>
|
||||
{
|
||||
navigate(id);
|
||||
}}
|
||||
query={{}}
|
||||
dataKey='applications'
|
||||
className='col'
|
||||
tableDivClass='scrollable-table'
|
||||
>
|
||||
Applications
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>ID</th>
|
||||
</tr>
|
||||
</PaginatedCard>
|
||||
|
||||
{/* <div className={`col ld ${loading && 'loading'}`}>
|
||||
<h2>Applications</h2>
|
||||
<Table headerItems={[ 'Name', 'ID' ]}>
|
||||
{applications.map(application => <TableListEntry
|
||||
@ -171,7 +213,7 @@ const Applications = () =>
|
||||
itemKeys={[ 'name', 'id' ]}
|
||||
/>)}
|
||||
</Table>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="col">
|
||||
<h2>Create Application</h2>
|
||||
@ -184,19 +226,19 @@ const Applications = () =>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const ApplicationWrapper = () =>
|
||||
{
|
||||
const { id } = useParams();
|
||||
const application = applications.find(app => app.id === id);
|
||||
if(!application)
|
||||
return <p>Unknown application</p>;
|
||||
return <Application app={application} />;
|
||||
};
|
||||
// const ApplicationWrapper = () =>
|
||||
// {
|
||||
// const { id } = useParams();
|
||||
// const application = applications.find(app => app.id === id);
|
||||
// if(!application)
|
||||
// return <p>Unknown application</p>;
|
||||
// return <Application app={application} />;
|
||||
// };
|
||||
|
||||
return <ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path="/" element={<Main />} />
|
||||
<Route path="/:id" element={<ApplicationWrapper />} />
|
||||
<Route path="/:id" element={<Application />} />
|
||||
</Routes>
|
||||
</ErrorBoundary>;
|
||||
};
|
||||
|
@ -155,7 +155,7 @@ const Profile = () =>
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
const response = await post('/api/user/settings', data);
|
||||
const response = await post<{ reAuth: boolean }>('/api/user/settings', data);
|
||||
button.disabled = false;
|
||||
if (response.data?.reAuth)
|
||||
{
|
||||
|
@ -184,7 +184,7 @@ export const setSetting = async (name: string, value: unknown) =>
|
||||
await post('/api/settings', settings.user);
|
||||
};
|
||||
|
||||
const parseResponse = async (response: Response): Promise<Res> =>
|
||||
const parseResponse = async <T = unknown>(response: Response): Promise<Res<T>> =>
|
||||
{
|
||||
const { headers: rawHeaders, status } = response;
|
||||
// Fetch returns heders as an interable for some reason
|
||||
@ -214,7 +214,7 @@ const parseResponse = async (response: Response): Promise<Res> =>
|
||||
return { ...base, message: (await response.text() || response.statusText) };
|
||||
};
|
||||
|
||||
export const post = async (url: string, body?: object | string, opts: ReqOptions = {}) =>
|
||||
export const post = async <T>(url: string, body?: object | string, opts: ReqOptions = {}) =>
|
||||
{
|
||||
const options: HttpOpts & RequestOptions = {
|
||||
method: 'POST'
|
||||
@ -235,10 +235,10 @@ export const post = async (url: string, body?: object | string, opts: ReqOptions
|
||||
|
||||
// console.log(url, options);
|
||||
const response = await fetch(url, options);
|
||||
return parseResponse(response);
|
||||
return parseResponse<T>(response);
|
||||
};
|
||||
|
||||
export const patch = async (url: string, body: object | string, opts: RequestOptions = {}) =>
|
||||
export const patch = async <T>(url: string, body: object | string, opts: RequestOptions = {}) =>
|
||||
{
|
||||
const options: HttpOpts & RequestOptions = {
|
||||
method: 'PATCH'
|
||||
@ -259,18 +259,18 @@ export const patch = async (url: string, body: object | string, opts: RequestOpt
|
||||
|
||||
// console.log(url, options);
|
||||
const response = await fetch(url, options);
|
||||
return parseResponse(response);
|
||||
return parseResponse<T>(response);
|
||||
|
||||
};
|
||||
|
||||
export const get = async (url: string, params?: {[key: string]: boolean | string | number | string[] | number[]}) =>
|
||||
export const get = async <T = unknown>(url: string, params?: {[key: string]: boolean | string | number | string[] | number[]}) =>
|
||||
{
|
||||
if (params)
|
||||
url += '?' + Object.entries(params)
|
||||
.map(([ key, val ]) => `${key}=${val instanceof Array ? val.join(',') : val}`)
|
||||
.map(([ key, val ]) => val !== null && val !== '' && typeof val !== 'undefined' ? `${key}=${val instanceof Array ? val.join(',') : val}` : key)
|
||||
.join('&');
|
||||
const response = await fetch(url);
|
||||
return parseResponse(response);
|
||||
return parseResponse<T>(response);
|
||||
};
|
||||
|
||||
export const del = async (url: string, body?: object | string, opts: RequestOptions = {}) =>
|
||||
|
Loading…
Reference in New Issue
Block a user