feat: useLogin 훅에서 사용자 정보를 구조 분해 할당하여 로딩 상태 처리 추가 및 프로필 API 엔드포인트 생성
This commit is contained in:
parent
358cb66780
commit
70930857a3
9 changed files with 88 additions and 58 deletions
|
|
@ -41,7 +41,7 @@ const createNavItem = (key: NavLinkKey, name: string, className?: string): NavIt
|
|||
};
|
||||
|
||||
function useNavItemsData() {
|
||||
const loginInfo = useLogin();
|
||||
const { user: loginInfo } = useLogin();
|
||||
const isLoggedIn = Boolean(loginInfo);
|
||||
|
||||
return useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function applyTagFilters(tags: TagRecord[], filters: TagFilters) {
|
|||
{
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _exhaustiveCheck: never = orderBy;
|
||||
return filtered;
|
||||
return _exhaustiveCheck;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
|||
const rehashDoc = useRehashDoc();
|
||||
const rescanDoc = useRescanDoc();
|
||||
const deleteDoc = useDeleteDoc();
|
||||
const user = useLogin();
|
||||
const { user } = useLogin();
|
||||
const username = user?.username;
|
||||
const isAdmin = username === "admin";
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import { Link } from "wouter";
|
|||
|
||||
export function DifferencePage() {
|
||||
const { data, isLoading, error } = useDifferenceDoc();
|
||||
const userInfo = useLogin();
|
||||
const { user: userInfo, isLoading: userLoading } = useLogin();
|
||||
|
||||
if (userLoading) {
|
||||
return <div className="p-4">Loading...</div>;
|
||||
}
|
||||
|
||||
if (!userInfo) {
|
||||
return <div className="p-4 w-full flex flex-col items-center">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@ import { useLogin } from "@/state/user";
|
|||
import { Redirect } from "wouter";
|
||||
|
||||
export function ProfilePage() {
|
||||
const userInfo = useLogin();
|
||||
const { user: userInfo, isLoading } = useLogin();
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="p-4">Loading...</div>;
|
||||
}
|
||||
|
||||
if (!userInfo) {
|
||||
console.error("User session expired. Redirecting to login page.");
|
||||
return <Redirect to="/login" />;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { ServerSettingCard } from "@/components/ServerSettingCard";
|
|||
|
||||
|
||||
export function SettingPage() {
|
||||
const login = useLogin();
|
||||
const { user: login } = useLogin();
|
||||
const isAdmin = login?.username === "admin";
|
||||
|
||||
const { data: serverSetting, error: serverError, isLoading: serverLoading, mutate } = useServerSettings(isAdmin);
|
||||
|
|
|
|||
|
|
@ -90,3 +90,20 @@ export async function resetPasswordService(username: string, oldpassword: string
|
|||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
export async function getProfileService(): Promise<{
|
||||
username: string;
|
||||
permission: string[];
|
||||
authenticated: boolean
|
||||
}> {
|
||||
const u = makeApiUrl("/api/user/profile");
|
||||
const res = await fetch(u, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
const b = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new ApiError(b as ErrorFormat);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
|
@ -6,35 +6,14 @@ import {
|
|||
logoutService,
|
||||
refreshService,
|
||||
resetPasswordService,
|
||||
getProfileService,
|
||||
} from "./api.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import useSWR, { mutate as mutateSWR } from "swr";
|
||||
|
||||
let localObj: LoginResponse | null = null;
|
||||
type ProfileResponse = Awaited<ReturnType<typeof getProfileService>>;
|
||||
|
||||
function getUserSessions() {
|
||||
if (!localObj) {
|
||||
const storagestr = localStorage.getItem("UserLoginContext");
|
||||
const storage = storagestr ? (JSON.parse(storagestr) as LoginResponse | null) : null;
|
||||
// update localObj from storage
|
||||
localObj = storage;
|
||||
}
|
||||
if (localObj && localObj.accessExpired > Math.floor(Date.now())) {
|
||||
return {
|
||||
username: localObj.username,
|
||||
permission: localObj.permission,
|
||||
accessExpired: localObj.accessExpired,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function setUserSessions(user: LoginResponse | null) {
|
||||
localObj = user;
|
||||
if (user) {
|
||||
localStorage.setItem("UserLoginContext", JSON.stringify(user));
|
||||
} else {
|
||||
localStorage.removeItem("UserLoginContext");
|
||||
}
|
||||
}
|
||||
export const PROFILE_SWR_KEY = "/api/user/profile";
|
||||
|
||||
class AuthEvent extends CustomEvent<{
|
||||
type: "login" | "logout" | "refresh";
|
||||
|
|
@ -62,8 +41,8 @@ declare global {
|
|||
export async function refresh() {
|
||||
try {
|
||||
const r = await refreshService();
|
||||
setUserSessions(r);
|
||||
window.dispatchEvent(new AuthEvent("refresh", { username: r.username, permission: r.permission }));
|
||||
void mutateSWR(PROFILE_SWR_KEY);
|
||||
return {
|
||||
username: r.username,
|
||||
permission: r.permission,
|
||||
|
|
@ -72,8 +51,8 @@ export async function refresh() {
|
|||
if (e instanceof ApiError) {
|
||||
console.error(`Refresh failed: ${e.detail}`);
|
||||
}
|
||||
setUserSessions(null);
|
||||
window.dispatchEvent(new AuthEvent("logout", null));
|
||||
void mutateSWR(PROFILE_SWR_KEY, { username: "", permission: [], authenticated: false }, false);
|
||||
return {
|
||||
username: "",
|
||||
permission: [],
|
||||
|
|
@ -84,8 +63,8 @@ export async function refresh() {
|
|||
export const doLogout = async () => {
|
||||
try {
|
||||
const res = await logoutService();
|
||||
setUserSessions(null);
|
||||
window.dispatchEvent(new AuthEvent("logout", null));
|
||||
void mutateSWR(PROFILE_SWR_KEY, { username: "", permission: [], authenticated: false }, false);
|
||||
return {
|
||||
username: res.username,
|
||||
permission: res.permission,
|
||||
|
|
@ -93,7 +72,7 @@ export const doLogout = async () => {
|
|||
|
||||
} catch (error) {
|
||||
console.error(`Server Error ${error}`);
|
||||
setUserSessions(null);
|
||||
void mutateSWR(PROFILE_SWR_KEY, { username: "", permission: [], authenticated: false }, false);
|
||||
return { username: "", permission: [] };
|
||||
}
|
||||
};
|
||||
|
|
@ -107,8 +86,8 @@ export const doLogin = async (userLoginInfo: LoginRequest): Promise<{
|
|||
}> => {
|
||||
try {
|
||||
const b = await loginService(userLoginInfo);
|
||||
setUserSessions(b);
|
||||
window.dispatchEvent(new AuthEvent("login", { username: b.username, permission: b.permission }));
|
||||
void mutateSWR(PROFILE_SWR_KEY);
|
||||
return { ok: true, data: b };
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
|
|
@ -135,34 +114,50 @@ export const doResetPassword = async (username: string, oldpassword: string, new
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
export function useLogin() {
|
||||
const [user, setUser] = useState(getUserSessions());
|
||||
const { data, error, isLoading, mutate } = useSWR<ProfileResponse>(
|
||||
PROFILE_SWR_KEY,
|
||||
async () => {
|
||||
try {
|
||||
return await getProfileService();
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.code === 401) {
|
||||
return {
|
||||
authenticated: false,
|
||||
permission: [],
|
||||
username: "",
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
{
|
||||
revalidateOnFocus: true,
|
||||
shouldRetryOnError: false,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (_e: AuthEvent) => {
|
||||
setUser(getUserSessions());
|
||||
const listener = () => {
|
||||
void mutate();
|
||||
};
|
||||
window.addEventListener("auth", listener);
|
||||
return () => {
|
||||
window.removeEventListener("auth", listener);
|
||||
};
|
||||
}, []);
|
||||
return user;
|
||||
}
|
||||
}, [mutate]);
|
||||
|
||||
const user = data?.authenticated ? {
|
||||
permission: data.permission,
|
||||
username: data.username,
|
||||
} : null;
|
||||
|
||||
const resolvedError = error instanceof ApiError && error.code === 401 ? undefined : error;
|
||||
|
||||
document.addEventListener("visibilitychange", async () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
const session = getUserSessions();
|
||||
if (!session) {
|
||||
await refresh();
|
||||
return {
|
||||
error: resolvedError,
|
||||
isLoading,
|
||||
refreshProfile: mutate,
|
||||
user,
|
||||
};
|
||||
}
|
||||
if (session && session.accessExpired - Date.now() < 5 * 60 * 1000) {
|
||||
// access token will expire in 5 minutes, refresh it
|
||||
await refresh();
|
||||
}
|
||||
|
||||
// If the session is still valid, do nothing
|
||||
}
|
||||
});
|
||||
|
|
@ -238,6 +238,15 @@ async function authenticate(
|
|||
export const createLoginRouter = (userController: UserAccessor) => {
|
||||
const router = new Hono<AppEnv>();
|
||||
|
||||
router.get("/profile", async (c) => {
|
||||
const auth = c.get("auth");
|
||||
return c.json({
|
||||
username: auth.user.username,
|
||||
permission: auth.user.permission,
|
||||
authenticated: auth.authenticated,
|
||||
});
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/login",
|
||||
sValidator("json", LoginBodySchema),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue