feat: add useAuth hook

This commit is contained in:
Paul Pan 2023-12-21 16:20:20 +08:00
parent d380daa365
commit 0fb4bde795
6 changed files with 138 additions and 6 deletions

View File

@ -1,3 +0,0 @@
export { send } from "./base";
export { UserApi } from "./user.ts";

View File

@ -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
View 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
View File

@ -0,0 +1,6 @@
import React from "react";
import { AuthContext } from "./auth.tsx";
export function useAuth() {
return React.useContext(AuthContext);
}

View File

@ -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>,
);

View File

@ -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,
},
];