mirror of https://github.com/ctk-hq/ctk
task: expose projects, folder restructure, minor fixes
parent
74b90ae4ff
commit
59ea695935
@ -0,0 +1,91 @@
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PencilIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import { truncateStr } from "../../utils";
|
||||
import { IProject } from "../../types";
|
||||
import ModalConfirmDelete from "../../components/Modal/ConfirmDelete";
|
||||
import { useDeleteProject } from "../../hooks/useProject";
|
||||
|
||||
interface IPreviewBlockProps {
|
||||
project: IProject;
|
||||
}
|
||||
|
||||
const PreviewBlock = (props: IPreviewBlockProps) => {
|
||||
const { project } = props;
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
|
||||
const objId = project.id;
|
||||
const mutation = useDeleteProject(project.uuid);
|
||||
|
||||
const handleMouseOver = () => {
|
||||
setIsHovering(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovering(false);
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
setShowDeleteConfirmModal(true);
|
||||
};
|
||||
|
||||
const onDeleteConfirmed = () => {
|
||||
mutation.mutate();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
key={project.id}
|
||||
className={`
|
||||
relative
|
||||
rounded-lg
|
||||
dark:bg-gray-900
|
||||
bg-gray-100
|
||||
px-6
|
||||
py-5
|
||||
shadow-sm
|
||||
flex
|
||||
items-center
|
||||
space-x-3
|
||||
hover:border-gray-400
|
||||
`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
{truncateStr(project.name, 25)}
|
||||
</div>
|
||||
|
||||
{isHovering &&
|
||||
<div className="flex space-x-1 absolute top-2 right-2">
|
||||
<button
|
||||
onClick={() => onDelete()}
|
||||
className="flex justify-center items-center p-2 hover:bg-gray-100 shadow bg-white rounded-md"
|
||||
>
|
||||
<TrashIcon className="w-3 h-3 text-red-500 hover:text-red-600" />
|
||||
</button>
|
||||
|
||||
<Link
|
||||
to={`/projects/${project.uuid}`}
|
||||
className="flex justify-center items-center p-2 hover:bg-gray-100 shadow bg-white rounded-md"
|
||||
>
|
||||
<PencilIcon className="w-3 h-3 text-gray-500 hover:text-gray-600" />
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
{showDeleteConfirmModal &&
|
||||
<ModalConfirmDelete
|
||||
onConfirm={() => onDeleteConfirmed()}
|
||||
onHide={() => {
|
||||
setShowDeleteConfirmModal(false);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PreviewBlock;
|
||||
@ -0,0 +1,65 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { IProject } from "../../types";
|
||||
import Spinner from "../../components/global/Spinner";
|
||||
import PreviewBlock from "./PreviewBlock";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
|
||||
const Projects = () => {
|
||||
const { data, error, isFetching } = useProjects();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="md:pl-16 flex flex-col flex-1">
|
||||
<main>
|
||||
<div className="py-6">
|
||||
<div className="flex justify-between px-4 sm:px-6 md:px-8">
|
||||
<h1 className="text-2xl font-semibold dark:text-white text-gray-900">Projects</h1>
|
||||
|
||||
<Link
|
||||
className="btn-util text-white bg-blue-600 hover:bg-blue-700 sm:w-auto"
|
||||
to="/projects/new"
|
||||
>
|
||||
<span>New</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="px-4 sm:px-6 md:px-8">
|
||||
{isFetching &&
|
||||
<div className="flex justify-center items-center mx-auto mt-10">
|
||||
<Spinner className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
}
|
||||
|
||||
{!isFetching &&
|
||||
<div className="py-4">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{(data.results.length > 0) &&
|
||||
data.results.map((project: IProject) => {
|
||||
return (
|
||||
<div key={`${project.uuid}`}>
|
||||
<PreviewBlock
|
||||
project={project}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
{(data.results.length === 0) &&
|
||||
<div className="text-center">
|
||||
<h3 className="mt-12 text-sm font-medium text-gray-900 dark:text-white">Nothing here</h3>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">Get started by creating a project.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Projects;
|
||||
@ -1,132 +0,0 @@
|
||||
import { Fragment } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { DatabaseIcon, TemplateIcon, XIcon } from "@heroicons/react/outline";
|
||||
import UserMenu from "./UserMenu";
|
||||
import Logo from "./logo";
|
||||
|
||||
interface ISideBarProps {
|
||||
state: any;
|
||||
sidebarOpen: boolean;
|
||||
setSidebarOpen: any;
|
||||
}
|
||||
|
||||
export default function SideBar(props: ISideBarProps) {
|
||||
const { pathname } = useLocation();
|
||||
const { state, sidebarOpen, setSidebarOpen } = props;
|
||||
const navigation = [
|
||||
{ name: "Templates", href: "/", icon: TemplateIcon, current: ((pathname === "/" || pathname.includes("templates")) ? true : false) },
|
||||
{ name: "Connectors", href: "/connectors", icon: DatabaseIcon, current: (pathname.includes("connectors") ? true : false) }
|
||||
];
|
||||
const classNames = (...classes: any[]) => {
|
||||
return classes.filter(Boolean).join(" ")
|
||||
};
|
||||
const userName = state.user ? state.user.username : "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition.Root show={sidebarOpen} as={Fragment}>
|
||||
<Dialog as="div" className="fixed inset-0 flex z-40 md:hidden" onClose={setSidebarOpen}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="transition-opacity ease-linear duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity ease-linear duration-300"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-gray-700 bg-opacity-50" />
|
||||
</Transition.Child>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="transition ease-in-out duration-300 transform"
|
||||
enterFrom="-translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="transition ease-in-out duration-300 transform"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="-translate-x-full"
|
||||
>
|
||||
<div className="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-blue-700">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-in-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in-out duration-300"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="absolute top-0 right-0 -mr-7 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 flex items-center justify-center h-5 w-5 rounded-full"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
>
|
||||
<span className="sr-only">Close sidebar</span>
|
||||
<XIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
|
||||
<div className="flex-shrink-0 flex items-center px-4">
|
||||
<Logo className="w-5 h-5" />
|
||||
</div>
|
||||
|
||||
<div className="mt-5 flex-1 h-0 overflow-y-auto">
|
||||
<nav className="px-2 space-y-1">
|
||||
{navigation.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={classNames(
|
||||
item.current ? "bg-blue-800 text-white" : "text-blue-100 hover:bg-blue-600",
|
||||
"group flex items-center px-2 py-2 text-base font-medium rounded-md"
|
||||
)}
|
||||
>
|
||||
<item.icon className="mr-4 flex-shrink-0 h-6 w-6" aria-hidden="true" />
|
||||
{item.name}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<UserMenu username={userName} current={pathname.includes("profile")} />
|
||||
</div>
|
||||
</Transition.Child>
|
||||
|
||||
<div className="flex-shrink-0 w-14" aria-hidden="true">
|
||||
{/* Dummy element to force sidebar to shrink to fit close icon */}
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
|
||||
<div className="hidden md:flex md:w-56 md:flex-col md:fixed md:inset-y-0">
|
||||
<div className="flex flex-col flex-grow pt-5 bg-blue-700 overflow-y-auto">
|
||||
<div className="flex items-center flex-shrink-0 px-4">
|
||||
<Logo className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="mt-5 flex-1 flex flex-col">
|
||||
<nav className="flex-1 px-2 pb-4 space-y-1">
|
||||
{navigation.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={classNames(
|
||||
item.current ? "bg-blue-800 text-white" : "text-blue-100 hover:bg-blue-600",
|
||||
"group flex items-center px-2 py-2 text-sm font-medium rounded-md"
|
||||
)}
|
||||
>
|
||||
<item.icon className="mr-3 flex-shrink-0 h-5 w-5" aria-hidden="true" />
|
||||
{item.name}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<UserMenu username={userName} current={pathname.includes("profile")} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
|
||||
import Spinner from "./Spinner";
|
||||
|
||||
|
||||
interface IPaginationProps {
|
||||
defaultCurrent: number;
|
||||
defaultPageSize: number;
|
||||
onChange: any;
|
||||
total: number;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const Pagination = (props: IPaginationProps) => {
|
||||
const { defaultCurrent, onChange, total, loading } = props;
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
<span className="block mb-5 text-sm">{`showing ${defaultCurrent} of ${total}`}</span>
|
||||
<button
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent
|
||||
text-xs font-medium rounded-full shadow-sm text-white bg-blue-600 hover:bg-blue-700"
|
||||
type="button"
|
||||
disabled={loading}
|
||||
onClick={() => onChange()}
|
||||
>
|
||||
{loading ? <Spinner className="w-4 h-4" /> : <span>load more</span>}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
@ -0,0 +1,35 @@
|
||||
import { SearchIcon } from "@heroicons/react/solid";
|
||||
|
||||
interface ISearchProps {
|
||||
onSearchChange: any;
|
||||
}
|
||||
|
||||
const Search = (props: ISearchProps) => {
|
||||
const { onSearchChange } = props;
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex">
|
||||
<form className="w-full flex md:ml-0" autoComplete="off" method="post" action="">
|
||||
<input autoComplete="false" name="hidden" type="text" className="hidden" />
|
||||
<label htmlFor="search-field" className="sr-only">
|
||||
Search
|
||||
</label>
|
||||
<div className="relative w-full text-gray-400 focus-within:text-gray-400">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pointer-events-none">
|
||||
<SearchIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
id="search-field"
|
||||
className="dark:bg-gray-800 block w-full h-full pl-8 pr-3 py-2 border-transparent dark:text-white text-gray-900 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-0 focus:border-transparent sm:text-sm"
|
||||
placeholder="Search"
|
||||
type="input"
|
||||
name="search"
|
||||
onChange={onSearchChange}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Search;
|
||||
@ -0,0 +1,62 @@
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { BookOpenIcon } from "@heroicons/react/outline";
|
||||
import { Link } from "react-router-dom";
|
||||
import UserMenu from "./UserMenu";
|
||||
import Logo from "./logo";
|
||||
|
||||
interface ISideBarProps {
|
||||
state: any;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
export default function SideBar(props: ISideBarProps) {
|
||||
const { pathname } = useLocation();
|
||||
const { state, isAuthenticated } = props;
|
||||
const projRegex = /\/projects\/?$/;
|
||||
const navigation = [{
|
||||
name: "Projects",
|
||||
href: "/projects",
|
||||
icon: BookOpenIcon,
|
||||
current: (pathname.match(projRegex) ? true : false)
|
||||
}];
|
||||
const classNames = (...classes: any[]) => {
|
||||
return classes.filter(Boolean).join(" ")
|
||||
};
|
||||
const userName = state.user ? state.user.username : "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="md:flex md:w-16 md:flex-col md:fixed md:inset-y-0">
|
||||
<div className="flex flex-col flex-grow pt-5 bg-blue-700 overflow-y-auto">
|
||||
<div className="flex items-center flex-shrink-0 mx-auto">
|
||||
<Link to={isAuthenticated ? "/" : "projects/new"}>
|
||||
<Logo className="" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 flex-1 flex flex-col">
|
||||
<nav className="flex-1 px-2 pb-4 space-y-1">
|
||||
{navigation.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={classNames(
|
||||
item.current ? "bg-blue-800 text-white" : "text-blue-100 hover:bg-blue-600",
|
||||
"group flex items-center justify-center px-2 py-2 text-sm font-medium rounded-md"
|
||||
)}
|
||||
>
|
||||
<item.icon className="mr-3 md:mr-0 flex-shrink-0 h-5 w-5" aria-hidden="true" />
|
||||
<span className="md:hidden">
|
||||
{item.name}
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<UserMenu username={userName} current={pathname.includes("profile")} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
interface ILogoProps {
|
||||
className: string;
|
||||
}
|
||||
|
||||
const Logo = (props: ILogoProps) => {
|
||||
const { className } = props;
|
||||
return (
|
||||
<svg width="28px" height="28px" viewBox="0 0 152 152" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
|
||||
<title>Nuxx</title>
|
||||
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g id="Logo" transform="translate(-179.000000, -225.000000)">
|
||||
<g id="nuxx-logo-copy" transform="translate(167.000000, 200.000000)">
|
||||
<g id="nuxx-logo" transform="translate(98.500000, 95.000000) rotate(-60.000000) translate(-98.500000, -95.000000) translate(33.000000, 20.000000)">
|
||||
<polygon id="Polygon" fillOpacity="0.15" fill="#000000" points="44 0 87.3012702 25 87.3012702 75 44 100 0.698729811 75 0.698729811 25"></polygon>
|
||||
<polygon id="Polygon-Copy" fillOpacity="0.15" fill="#000000" points="87 25 130.30127 50 130.30127 100 87 125 43.6987298 100 43.6987298 50"></polygon>
|
||||
<polygon id="Polygon-Copy-2" fillOpacity="0.15" fill="#000000" points="44 50 87.3012702 75 87.3012702 125 44 150 0.698729811 125 0.698729811 75"></polygon>
|
||||
<text id="n" transform="translate(61.705647, 69.842201) rotate(30.000000) translate(-61.705647, -69.842201) " fontFamily="Kefa-Bold, Kefa" fontSize="71" fontWeight="bold" letterSpacing="0.492226294" fill="#FFFFFF">
|
||||
<tspan x="39.6707155" y="94.8422015">n</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Logo;
|
||||
@ -1,20 +0,0 @@
|
||||
interface ILogoProps {
|
||||
className: string;
|
||||
}
|
||||
|
||||
const Logo = (props: ILogoProps) => {
|
||||
const { className } = props;
|
||||
return (
|
||||
<svg width="689" height="689" viewBox="0 0 689 689" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
|
||||
<path opacity="0.8" d="M191.04 268.58H0.154419L0.154419 459.465H191.04V268.58Z" fill="white" />
|
||||
<path opacity="0.8" d="M191.04 497.306H0.154419L0.154419 688.192H191.04V497.306Z" fill="white" />
|
||||
<path opacity="0.8" d="M191.04 39.8536L0.154419 39.8536L0.154419 230.739H191.04V39.8536Z" fill="#4F95FF" />
|
||||
<path opacity="0.8" d="M419.766 268.58H228.881V459.465H419.766V268.58Z" fill="#4F95FF" />
|
||||
<path opacity="0.8" d="M419.766 497.306H228.881V688.192H419.766V497.306Z" fill="#4F95FF" />
|
||||
<path opacity="0.8" d="M648.493 497.306H457.607V688.192H648.493V497.306Z" fill="white" />
|
||||
<path opacity="0.8" d="M688.084 135.105L553.109 0.130157L418.134 135.105L553.109 270.081L688.084 135.105Z" fill="white" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Logo;
|
||||
@ -1,3 +1,3 @@
|
||||
export const API_SERVER_URL = process.env.REACT_APP_API_SERVER;
|
||||
export const PROJECTS_FETCH_LIMIT = 300;
|
||||
export const LOCAL_STORAGE = 'CtkLocalStorage';
|
||||
export const LOCAL_STORAGE = 'NuxxLocalStorage';
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import axios from "axios"
|
||||
import { useQuery } from "react-query";
|
||||
import { API_SERVER_URL, PROJECTS_FETCH_LIMIT } from "../constants";
|
||||
import { getLocalStorageJWTKeys } from "../utils";
|
||||
|
||||
const fetchProjects = async () => {
|
||||
const jwtKeys = getLocalStorageJWTKeys();
|
||||
|
||||
const response = await axios({
|
||||
method: 'get',
|
||||
url: `${API_SERVER_URL}/projects/`,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${jwtKeys.access_token}`
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const useProjects = () => {
|
||||
return useQuery(
|
||||
["projects"],
|
||||
async () => {
|
||||
return await fetchProjects();
|
||||
},
|
||||
{
|
||||
staleTime: Infinity
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
import { IProjectPayload } from "../types";
|
||||
import { API_SERVER_URL, PROJECTS_FETCH_LIMIT } from "../constants";
|
||||
import { getLocalStorageJWTKeys } from "./utils";
|
||||
|
||||
export const projectHttpCreate = (data: string) => {
|
||||
//const jwtKeys = getLocalStorageJWTKeys();
|
||||
return fetch(`${API_SERVER_URL}/projects/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
//"Authorization": `Bearer ${jwtKeys.access_token}`
|
||||
},
|
||||
body: data
|
||||
});
|
||||
}
|
||||
|
||||
export const projectHttpUpdate = (uuid: string, data: string) => {
|
||||
//const jwtKeys = getLocalStorageJWTKeys();
|
||||
return fetch(`${API_SERVER_URL}/projects/${uuid}/`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
//"Authorization": `Bearer ${jwtKeys.access_token}`
|
||||
},
|
||||
body: data
|
||||
});
|
||||
}
|
||||
|
||||
export const projectHttpDelete = (uuid: number) => {
|
||||
const jwtKeys = getLocalStorageJWTKeys();
|
||||
return fetch(`${API_SERVER_URL}/projects/${uuid}/`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${jwtKeys.access_token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const projectsHttpGet = (offset: number) => {
|
||||
const jwtKeys = getLocalStorageJWTKeys();
|
||||
let endpoint = `${API_SERVER_URL}/projects/?limit=${PROJECTS_FETCH_LIMIT}&offset=${offset}`;
|
||||
|
||||
return fetch(endpoint, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${jwtKeys.access_token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const projectHttpGet = (uuid: string) => {
|
||||
//const jwtKeys = getLocalStorageJWTKeys();
|
||||
return fetch(`${API_SERVER_URL}/projects/${uuid}/`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
//"Authorization": `Bearer ${jwtKeys.access_token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue