feat: add useAuth hook
This commit is contained in:
parent
d380daa365
commit
0fb4bde795
@ -1,3 +0,0 @@
|
||||
export { send } from "./base";
|
||||
|
||||
export { UserApi } from "./user.ts";
|
@ -7,6 +7,11 @@ export interface UserReq {
|
||||
uid?: number;
|
||||
}
|
||||
|
||||
export interface UserLoginResp {
|
||||
nickname: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
meta: Meta;
|
||||
user_name: string;
|
||||
@ -23,7 +28,7 @@ export class UserApi {
|
||||
return send("/user/create", data);
|
||||
}
|
||||
|
||||
static async Login(data: UserReq): Promise<string> {
|
||||
static async Login(data: UserReq): Promise<UserLoginResp> {
|
||||
if (!data.username || !data.password) {
|
||||
throw new Error("Missing required fields");
|
||||
}
|
||||
|
116
src/components/auth.tsx
Normal file
116
src/components/auth.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { UserApi } from "../api/user.ts";
|
||||
|
||||
interface AuthContextType {
|
||||
token: string;
|
||||
nickname: string;
|
||||
isLoggedIn: boolean;
|
||||
login: (
|
||||
username: string,
|
||||
password: string,
|
||||
onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => void;
|
||||
logout: (
|
||||
onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => void;
|
||||
}
|
||||
|
||||
const AuthContext = React.createContext<AuthContextType>({
|
||||
token: "",
|
||||
nickname: "guest",
|
||||
isLoggedIn: false,
|
||||
login: (
|
||||
_username: string,
|
||||
_password: string,
|
||||
_onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => {
|
||||
onFailed("not implemented");
|
||||
},
|
||||
logout: (_onSuccess: VoidFunction, onFailed: (error: string) => void) => {
|
||||
onFailed("not implemented");
|
||||
},
|
||||
});
|
||||
|
||||
function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [token, setToken] = useState("");
|
||||
const [nickname, setNickname] = useState("guest");
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
setToken(token);
|
||||
setIsLoggedIn(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const login = (
|
||||
username: string,
|
||||
password: string,
|
||||
onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => {
|
||||
UserApi.Login({ username: username, password: password })
|
||||
.then((resp) => {
|
||||
localStorage.setItem("token", resp.token);
|
||||
setToken(resp.token);
|
||||
setNickname(resp.nickname);
|
||||
setIsLoggedIn(true);
|
||||
onSuccess();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("[user] userLogin", err);
|
||||
setIsLoggedIn(false);
|
||||
onFailed(err as string);
|
||||
});
|
||||
};
|
||||
|
||||
const logout = (
|
||||
onSuccess: VoidFunction,
|
||||
onFailed: (error: string) => void,
|
||||
) => {
|
||||
localStorage.removeItem("token");
|
||||
setToken("");
|
||||
setNickname("guest");
|
||||
setIsLoggedIn(false);
|
||||
|
||||
if (!isLoggedIn) {
|
||||
onFailed("not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
UserApi.Logout(token)
|
||||
.then(() => {
|
||||
onSuccess();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("[user] userLogout", err);
|
||||
onFailed(err as string);
|
||||
});
|
||||
};
|
||||
|
||||
const value = { token, nickname, isLoggedIn, login, logout };
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}> {children} </AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function RequireAuth({ children }: { children: React.ReactNode }) {
|
||||
const auth = React.useContext(AuthContext);
|
||||
const location = useLocation();
|
||||
|
||||
if (!auth.token) {
|
||||
return <Navigate to="/login" state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export { AuthProvider, RequireAuth, AuthContext };
|
6
src/components/hook.ts
Normal file
6
src/components/hook.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import React from "react";
|
||||
import { AuthContext } from "./auth.tsx";
|
||||
|
||||
export function useAuth() {
|
||||
return React.useContext(AuthContext);
|
||||
}
|
@ -10,6 +10,7 @@ import {
|
||||
} from "react-router-dom";
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
import { AuthProvider } from "./components/auth.tsx";
|
||||
import { Root, HomePage, ErrorPage } from "./pages/pages.tsx";
|
||||
import { RouteConfigs } from "./routes.tsx";
|
||||
|
||||
@ -71,6 +72,8 @@ const router = sentryCreateBrowserRouter([
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
<AuthProvider>
|
||||
<RouterProvider router={router} />
|
||||
</AuthProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
SearchPage,
|
||||
SubmitPage,
|
||||
} from "./pages/pages.tsx";
|
||||
import { RequireAuth } from "./components/auth.tsx";
|
||||
|
||||
import { ProblemLoader } from "./api/loader.ts";
|
||||
|
||||
@ -23,7 +24,11 @@ const RouteConfigs = [
|
||||
},
|
||||
{
|
||||
path: "problem/:id/submit",
|
||||
element: <SubmitPage />,
|
||||
element: (
|
||||
<RequireAuth>
|
||||
<SubmitPage />
|
||||
</RequireAuth>
|
||||
),
|
||||
loader: ProblemLoader,
|
||||
},
|
||||
];
|
||||
|
Reference in New Issue
Block a user