Compare commits
3 Commits
bc6c2a4a98
...
8b092fed51
Author | SHA1 | Date | |
---|---|---|---|
8b092fed51 | |||
99e80c8077 | |||
854bd03c40 |
16
src/App.css
16
src/App.css
@ -29,7 +29,6 @@ footer {
|
|||||||
left: 8px;
|
left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*=========Network Controls=========*/
|
/*=========Network Controls=========*/
|
||||||
|
|
||||||
.infobutton {
|
.infobutton {
|
||||||
@ -367,7 +366,11 @@ button,
|
|||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: 140px;
|
height: 140px;
|
||||||
margin-bottom: 20px;
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
display: block;
|
display: block;
|
||||||
@ -390,8 +393,15 @@ button,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
background-color: lightsteelblue;
|
||||||
|
padding: 2px 8px;
|
||||||
|
width: fit-content;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.networkroute {
|
.networkroute {
|
||||||
z-index: 10;
|
z-index: 3;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 24px;
|
top: 24px;
|
||||||
left: 48px;
|
left: 48px;
|
||||||
|
25
src/App.tsx
25
src/App.tsx
@ -7,34 +7,21 @@ import { BrowserRouter, Routes, Route } from "react-router";
|
|||||||
import { SessionProvider } from "./Session";
|
import { SessionProvider } from "./Session";
|
||||||
import { GraphComponent } from "./Network";
|
import { GraphComponent } from "./Network";
|
||||||
import MVPChart from "./MVPChart";
|
import MVPChart from "./MVPChart";
|
||||||
|
import Avatar from "./Avatar";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
<SessionProvider>
|
||||||
<Header />
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<Rankings />} />
|
<Route index element={<Rankings />} />
|
||||||
|
<Route path="/network" element={<GraphComponent />} />
|
||||||
<Route path="/network" element={
|
<Route path="/analysis" element={<Analysis />} />
|
||||||
<SessionProvider>
|
<Route path="/mvp" element={<MVPChart />} />
|
||||||
<GraphComponent />
|
|
||||||
</SessionProvider>
|
|
||||||
} />
|
|
||||||
|
|
||||||
<Route path="/analysis" element={
|
|
||||||
<SessionProvider>
|
|
||||||
<Analysis />
|
|
||||||
</SessionProvider>
|
|
||||||
} />
|
|
||||||
|
|
||||||
<Route path="/mvp" element={
|
|
||||||
<SessionProvider>
|
|
||||||
<MVPChart />
|
|
||||||
</SessionProvider>
|
|
||||||
} />
|
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
</SessionProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
99
src/Avatar.tsx
Normal file
99
src/Avatar.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { MouseEventHandler, useEffect, useState } from "react";
|
||||||
|
import { useSession } from "./Session";
|
||||||
|
import { logout } from "./api";
|
||||||
|
|
||||||
|
interface ContextMenuItem {
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Avatar() {
|
||||||
|
const { user, onLogout } = useSession();
|
||||||
|
const [contextMenu, setContextMenu] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
mouseX: number;
|
||||||
|
mouseY: number;
|
||||||
|
}>({ open: false, mouseX: 0, mouseY: 0 });
|
||||||
|
|
||||||
|
const contextMenuItems: ContextMenuItem[] = [
|
||||||
|
{ label: "View Profile", onClick: () => console.log("View Profile") },
|
||||||
|
{ label: "Edit Profile", onClick: () => console.log("Edit Profile") },
|
||||||
|
{ label: "Logout", onClick: onLogout },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleMenuClick: MouseEventHandler<HTMLDivElement> = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setContextMenu({
|
||||||
|
open: !contextMenu.open,
|
||||||
|
mouseX: event.clientX + 4,
|
||||||
|
mouseY: event.clientY + 2,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (contextMenu.open) {
|
||||||
|
document.addEventListener("click", handleCloseContextMenuOutside);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("click", handleCloseContextMenuOutside);
|
||||||
|
};
|
||||||
|
}, [contextMenu.open]);
|
||||||
|
|
||||||
|
const handleMenuClose = () => {
|
||||||
|
setContextMenu({ ...contextMenu, open: false });
|
||||||
|
};
|
||||||
|
const handleCloseContextMenuOutside: MouseEventHandler<Document> = (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
!event.target ||
|
||||||
|
(!(event.target as Element).closest(".context-menu") &&
|
||||||
|
!(event.target as Element).closest(".avatar"))
|
||||||
|
) {
|
||||||
|
handleMenuClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="avatar"
|
||||||
|
onContextMenu={handleMenuClick}
|
||||||
|
style={{ display: user ? "block" : "none" }}
|
||||||
|
onClick={handleMenuClick}
|
||||||
|
>
|
||||||
|
{user?.username}
|
||||||
|
{contextMenu.open && (
|
||||||
|
<ul
|
||||||
|
className="context-menu"
|
||||||
|
style={{
|
||||||
|
zIndex: 3,
|
||||||
|
position: "absolute",
|
||||||
|
top: contextMenu.mouseY,
|
||||||
|
left: contextMenu.mouseX,
|
||||||
|
background: "white",
|
||||||
|
border: "1px solid #ddd",
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
listStyle: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{contextMenuItems.map((item, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
padding: "10px",
|
||||||
|
borderBottom: "1px solid #ddd",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
item.onClick();
|
||||||
|
handleMenuClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,20 +1,22 @@
|
|||||||
|
import { useLocation } from "react-router";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<footer>
|
<footer className={location.pathname === "/network" ? "fixed-footer" : ""}>
|
||||||
<div className="navbar">
|
<div className="navbar">
|
||||||
<a href="/">
|
<Link to="/">
|
||||||
<span>Form</span>
|
<span>Form</span>
|
||||||
</a>
|
</Link>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<a href="/network">
|
<Link to="/network">
|
||||||
<span>Trainer Analysis</span>
|
<span>Trainer Analysis</span>
|
||||||
</a>
|
</Link>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<a href="/mvp">
|
<Link to="/mvp">
|
||||||
<span>MVP</span>
|
<span>MVP</span>
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<p className="grey extra-margin">
|
<p className="grey extra-margin">
|
||||||
something not working?
|
something not working?
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
|
import { Link, useLocation } from "react-router";
|
||||||
|
import Avatar from "./Avatar";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
return <div className="logo" id="logo">
|
const location = useLocation();
|
||||||
<a href={"/"}>
|
return (
|
||||||
|
<div className={location.pathname === "/network" ? "networkroute" : ""}>
|
||||||
|
<div className="logo">
|
||||||
|
<Link to="/">
|
||||||
<img alt="logo" height="66%" src="logo.svg" />
|
<img alt="logo" height="66%" src="logo.svg" />
|
||||||
<h3 className="centered">cutt</h3>
|
<h3 className="centered">cutt</h3>
|
||||||
</a>
|
</Link>
|
||||||
<span className="grey">cool ultimate team tool</span>
|
<span className="grey">cool ultimate team tool</span>
|
||||||
</div>
|
</div>
|
||||||
|
<Avatar />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FormEvent, useContext, useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { currentUser, login, User } from "./api";
|
||||||
import { currentUser, login, LoginRequest, User } from "./api";
|
import Header from "./Header";
|
||||||
|
|
||||||
export interface LoginProps {
|
export interface LoginProps {
|
||||||
onLogin: (user: User) => void;
|
onLogin: (user: User) => void;
|
||||||
@ -15,72 +15,58 @@ export const Login = ({ onLogin }: LoginProps) => {
|
|||||||
async function doLogin() {
|
async function doLogin() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const timeout = new Promise((r) => setTimeout(r, 1500));
|
const timeout = new Promise((r) => setTimeout(r, 1000));
|
||||||
let user: User;
|
let user: User;
|
||||||
try {
|
try {
|
||||||
login({ username, password });
|
await login({ username, password });
|
||||||
user = await currentUser();
|
user = await currentUser();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await timeout;
|
await timeout;
|
||||||
setError(e);
|
setError(e);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await timeout;
|
await timeout;
|
||||||
onLogin(user);
|
onLogin(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
doLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent) {
|
function handleSubmit(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
doLogin();
|
doLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" id="username" name="username" placeholder="username" required value={username} onChange={evt => setUsername(evt.target.value)} />
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
placeholder="username"
|
||||||
|
required
|
||||||
|
value={username}
|
||||||
|
onChange={(evt) => setUsername(evt.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="password" id="password" name="password" placeholder="password" minLength={8} value={password} required onChange={evt => setPassword(evt.target.value)} />
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="password"
|
||||||
|
minLength={8}
|
||||||
|
value={password}
|
||||||
|
required
|
||||||
|
onChange={(evt) => setPassword(evt.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" value="login" style={{ fontSize: "small" }} onClick={handleClick} >login</button>
|
<button type="submit" value="login" style={{ fontSize: "small" }}>
|
||||||
|
login
|
||||||
|
</button>
|
||||||
{loading && <span className="loader" />}
|
{loading && <span className="loader" />}
|
||||||
</form>
|
</form>
|
||||||
)
|
</>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
/*
|
|
||||||
export default function Login(props: { onLogin: (user: User) => void }) {
|
|
||||||
const { onLogin } = props;
|
|
||||||
const [username, setUsername] = useState("");
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
|
|
||||||
async function handleLogin(e: FormEvent) {
|
|
||||||
e.preventDefault()
|
|
||||||
const timeout = new Promise((r) => setTimeout(r, 1500));
|
|
||||||
let user: User;
|
|
||||||
try {
|
|
||||||
login({ username, password })
|
|
||||||
user = await currentUser()
|
|
||||||
} catch (e) { await timeout; return }
|
|
||||||
await timeout;
|
|
||||||
onLogin(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<form onSubmit={handleLogin}>
|
|
||||||
<div>
|
|
||||||
<input type="text" id="username" name="username" placeholder="username" required value={username} onChange={evt => setUsername(evt.target.value)} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input type="password" id="password" name="password" placeholder="password" minLength={8} value={password} required onChange={evt => setPassword(evt.target.value)} />
|
|
||||||
</div>
|
|
||||||
<input className="button" type="submit" value="login" onSubmit={handleLogin} />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
} */
|
|
||||||
|
@ -43,15 +43,6 @@ export const GraphComponent = () => {
|
|||||||
const [popularity, setPopularity] = useState(false);
|
const [popularity, setPopularity] = useState(false);
|
||||||
const [mutuality, setMutuality] = useState(false);
|
const [mutuality, setMutuality] = useState(false);
|
||||||
|
|
||||||
const logo = document.getElementById("logo");
|
|
||||||
if (logo) {
|
|
||||||
logo.className = "logo networkroute";
|
|
||||||
}
|
|
||||||
const footer = document.getElementsByTagName("footer");
|
|
||||||
if (footer) {
|
|
||||||
(footer.item(0) as HTMLElement).className = "fixed-footer";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await apiAuth("analysis/graph_json", null)
|
await apiAuth("analysis/graph_json", null)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { PlayerRanking } from "./types";
|
import { PlayerRanking } from "./types";
|
||||||
|
import { useSession } from "./Session";
|
||||||
|
|
||||||
interface RaceChartProps {
|
interface RaceChartProps {
|
||||||
players: PlayerRanking[];
|
players: PlayerRanking[];
|
||||||
@ -34,13 +35,13 @@ const RaceChart: FC<RaceChartProps> = ({ players, std }) => {
|
|||||||
const gap = 8;
|
const gap = 8;
|
||||||
const maxValue = Math.max(...players.map((player) => player.rank)) + 1;
|
const maxValue = Math.max(...players.map((player) => player.rank)) + 1;
|
||||||
const barHeight = (height - 2 * padding) / players.length;
|
const barHeight = (height - 2 * padding) / players.length;
|
||||||
const fontSize = Math.min(barHeight - 1.5 * gap, width / 20);
|
const fontSize = Math.min(barHeight - 1.5 * gap, width / 22);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg width={width} height={height}>
|
<svg width={width} height={height} id="RaceChartSVG">
|
||||||
{players.map((player, index) => (
|
{players.map((player, index) => (
|
||||||
<rect
|
<rect
|
||||||
key={index}
|
key={String(index)}
|
||||||
x={0}
|
x={0}
|
||||||
y={index * barHeight + padding}
|
y={index * barHeight + padding}
|
||||||
width={(1 - player.rank / maxValue) * width}
|
width={(1 - player.rank / maxValue) * width}
|
||||||
@ -50,7 +51,7 @@ const RaceChart: FC<RaceChartProps> = ({ players, std }) => {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{players.map((player, index) => (
|
{players.map((player, index) => (
|
||||||
<g>
|
<g key={"group" + index}>
|
||||||
<text
|
<text
|
||||||
key={index + "_name"}
|
key={index + "_name"}
|
||||||
x={4}
|
x={4}
|
||||||
|
@ -1,36 +1,94 @@
|
|||||||
import { createContext, ReactNode, useContext, useLayoutEffect, useState } from "react";
|
import {
|
||||||
import { currentUser, User } from "./api";
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { currentUser, logout, User } from "./api";
|
||||||
import { Login } from "./Login";
|
import { Login } from "./Login";
|
||||||
|
import Header from "./Header";
|
||||||
|
|
||||||
export interface SessionProviderProps {
|
export interface SessionProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionContext = createContext<User | null>(null);
|
export interface Session {
|
||||||
|
user: User | null;
|
||||||
|
onLogout: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionContext = createContext<Session>({
|
||||||
|
user: null,
|
||||||
|
onLogout: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
export function SessionProvider(props: SessionProviderProps) {
|
export function SessionProvider(props: SessionProviderProps) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [err, setErr] = useState<unknown>(null);
|
const [err, setErr] = useState<unknown>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
function loadUser() {
|
function loadUser() {
|
||||||
|
setLoading(true);
|
||||||
currentUser()
|
currentUser()
|
||||||
.then((user) => { setUser(user); setErr(null); })
|
.then((user) => {
|
||||||
.catch((err) => { setUser(null); setErr(err); });
|
setUser(user);
|
||||||
|
setErr(null);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setUser(null);
|
||||||
|
setErr(err);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => { loadUser(); }, [err]);
|
useEffect(() => {
|
||||||
|
loadUser();
|
||||||
|
}, []);
|
||||||
|
|
||||||
function onLogin(user: User) {
|
function onLogin(user: User) {
|
||||||
setUser(user);
|
setUser(user);
|
||||||
setErr(null);
|
setErr(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onLogout() {
|
||||||
|
try {
|
||||||
|
logout();
|
||||||
|
setUser(null);
|
||||||
|
setErr({ message: "Logged out successfully" });
|
||||||
|
console.log("logged out.");
|
||||||
|
setLoading(true); // Set loading to true
|
||||||
|
loadUser();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setErr(e); // Update the error state if logout fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("sanity", user);
|
||||||
|
|
||||||
let content: ReactNode;
|
let content: ReactNode;
|
||||||
if (!err && !user) content = <span className="loader" />;
|
if (loading || (!err && !user))
|
||||||
else if (err) content = <Login onLogin={onLogin} />;
|
content = (
|
||||||
else content = <sessionContext.Provider value={user}>{children}</sessionContext.Provider>;
|
<>
|
||||||
|
<Header />
|
||||||
|
<span className="loader" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else if (err) {
|
||||||
|
if ((err as any).message === "Logged out successfully") {
|
||||||
|
setTimeout(() => setErr(null), 1000);
|
||||||
|
content = <Login onLogin={onLogin} />;
|
||||||
|
} else {
|
||||||
|
content = <Login onLogin={onLogin} />;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
content = (
|
||||||
|
<sessionContext.Provider value={{ user, onLogout }}>
|
||||||
|
{children}
|
||||||
|
</sessionContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
29
src/api.ts
29
src/api.ts
@ -88,22 +88,29 @@ export type Token = {
|
|||||||
token_type: string;
|
token_type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const login = (req: LoginRequest) => {
|
// api.js
|
||||||
fetch(`${baseUrl}api/token`, {
|
export const login = async (req: LoginRequest): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}api/token`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
},
|
},
|
||||||
body: new URLSearchParams(req).toString(),
|
body: new URLSearchParams(req).toString(),
|
||||||
})
|
});
|
||||||
.then((resp) => resp.json() as Promise<Token>)
|
if (!response.ok) {
|
||||||
.then((token) =>
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
token
|
}
|
||||||
? localStorage.setItem("access_token", token.access_token)
|
const token = (await response.json()) as Token;
|
||||||
: console.log("token not acquired")
|
if (token && token.access_token) {
|
||||||
)
|
localStorage.setItem("access_token", token.access_token);
|
||||||
.catch((e) => console.log("catch error " + e + " in login"));
|
} else {
|
||||||
return Promise<void>;
|
console.log("Token not acquired");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw e; // rethrow the error so it can be caught by the caller
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logout = () => localStorage.removeItem("access_token");
|
export const logout = () => localStorage.removeItem("access_token");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user