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() {
|
function useNavItemsData() {
|
||||||
const loginInfo = useLogin();
|
const { user: loginInfo } = useLogin();
|
||||||
const isLoggedIn = Boolean(loginInfo);
|
const isLoggedIn = Boolean(loginInfo);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ function applyTagFilters(tags: TagRecord[], filters: TagFilters) {
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const _exhaustiveCheck: never = orderBy;
|
const _exhaustiveCheck: never = orderBy;
|
||||||
return filtered;
|
return _exhaustiveCheck;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
||||||
const rehashDoc = useRehashDoc();
|
const rehashDoc = useRehashDoc();
|
||||||
const rescanDoc = useRescanDoc();
|
const rescanDoc = useRescanDoc();
|
||||||
const deleteDoc = useDeleteDoc();
|
const deleteDoc = useDeleteDoc();
|
||||||
const user = useLogin();
|
const { user } = useLogin();
|
||||||
const username = user?.username;
|
const username = user?.username;
|
||||||
const isAdmin = username === "admin";
|
const isAdmin = username === "admin";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ import { Link } from "wouter";
|
||||||
|
|
||||||
export function DifferencePage() {
|
export function DifferencePage() {
|
||||||
const { data, isLoading, error } = useDifferenceDoc();
|
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) {
|
if (!userInfo) {
|
||||||
return <div className="p-4 w-full flex flex-col items-center">
|
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";
|
import { Redirect } from "wouter";
|
||||||
|
|
||||||
export function ProfilePage() {
|
export function ProfilePage() {
|
||||||
const userInfo = useLogin();
|
const { user: userInfo, isLoading } = useLogin();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div className="p-4">Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!userInfo) {
|
if (!userInfo) {
|
||||||
console.error("User session expired. Redirecting to login page.");
|
console.error("User session expired. Redirecting to login page.");
|
||||||
return <Redirect to="/login" />;
|
return <Redirect to="/login" />;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { ServerSettingCard } from "@/components/ServerSettingCard";
|
||||||
|
|
||||||
|
|
||||||
export function SettingPage() {
|
export function SettingPage() {
|
||||||
const login = useLogin();
|
const { user: login } = useLogin();
|
||||||
const isAdmin = login?.username === "admin";
|
const isAdmin = login?.username === "admin";
|
||||||
|
|
||||||
const { data: serverSetting, error: serverError, isLoading: serverLoading, mutate } = useServerSettings(isAdmin);
|
const { data: serverSetting, error: serverError, isLoading: serverLoading, mutate } = useServerSettings(isAdmin);
|
||||||
|
|
|
||||||
|
|
@ -90,3 +90,20 @@ export async function resetPasswordService(username: string, oldpassword: string
|
||||||
}
|
}
|
||||||
return b;
|
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,
|
logoutService,
|
||||||
refreshService,
|
refreshService,
|
||||||
resetPasswordService,
|
resetPasswordService,
|
||||||
|
getProfileService,
|
||||||
} from "./api.ts";
|
} 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() {
|
export const PROFILE_SWR_KEY = "/api/user/profile";
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthEvent extends CustomEvent<{
|
class AuthEvent extends CustomEvent<{
|
||||||
type: "login" | "logout" | "refresh";
|
type: "login" | "logout" | "refresh";
|
||||||
|
|
@ -62,8 +41,8 @@ declare global {
|
||||||
export async function refresh() {
|
export async function refresh() {
|
||||||
try {
|
try {
|
||||||
const r = await refreshService();
|
const r = await refreshService();
|
||||||
setUserSessions(r);
|
|
||||||
window.dispatchEvent(new AuthEvent("refresh", { username: r.username, permission: r.permission }));
|
window.dispatchEvent(new AuthEvent("refresh", { username: r.username, permission: r.permission }));
|
||||||
|
void mutateSWR(PROFILE_SWR_KEY);
|
||||||
return {
|
return {
|
||||||
username: r.username,
|
username: r.username,
|
||||||
permission: r.permission,
|
permission: r.permission,
|
||||||
|
|
@ -72,8 +51,8 @@ export async function refresh() {
|
||||||
if (e instanceof ApiError) {
|
if (e instanceof ApiError) {
|
||||||
console.error(`Refresh failed: ${e.detail}`);
|
console.error(`Refresh failed: ${e.detail}`);
|
||||||
}
|
}
|
||||||
setUserSessions(null);
|
|
||||||
window.dispatchEvent(new AuthEvent("logout", null));
|
window.dispatchEvent(new AuthEvent("logout", null));
|
||||||
|
void mutateSWR(PROFILE_SWR_KEY, { username: "", permission: [], authenticated: false }, false);
|
||||||
return {
|
return {
|
||||||
username: "",
|
username: "",
|
||||||
permission: [],
|
permission: [],
|
||||||
|
|
@ -84,8 +63,8 @@ export async function refresh() {
|
||||||
export const doLogout = async () => {
|
export const doLogout = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await logoutService();
|
const res = await logoutService();
|
||||||
setUserSessions(null);
|
|
||||||
window.dispatchEvent(new AuthEvent("logout", null));
|
window.dispatchEvent(new AuthEvent("logout", null));
|
||||||
|
void mutateSWR(PROFILE_SWR_KEY, { username: "", permission: [], authenticated: false }, false);
|
||||||
return {
|
return {
|
||||||
username: res.username,
|
username: res.username,
|
||||||
permission: res.permission,
|
permission: res.permission,
|
||||||
|
|
@ -93,7 +72,7 @@ export const doLogout = async () => {
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Server Error ${error}`);
|
console.error(`Server Error ${error}`);
|
||||||
setUserSessions(null);
|
void mutateSWR(PROFILE_SWR_KEY, { username: "", permission: [], authenticated: false }, false);
|
||||||
return { username: "", permission: [] };
|
return { username: "", permission: [] };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -107,8 +86,8 @@ export const doLogin = async (userLoginInfo: LoginRequest): Promise<{
|
||||||
}> => {
|
}> => {
|
||||||
try {
|
try {
|
||||||
const b = await loginService(userLoginInfo);
|
const b = await loginService(userLoginInfo);
|
||||||
setUserSessions(b);
|
|
||||||
window.dispatchEvent(new AuthEvent("login", { username: b.username, permission: b.permission }));
|
window.dispatchEvent(new AuthEvent("login", { username: b.username, permission: b.permission }));
|
||||||
|
void mutateSWR(PROFILE_SWR_KEY);
|
||||||
return { ok: true, data: b };
|
return { ok: true, data: b };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ApiError) {
|
if (e instanceof ApiError) {
|
||||||
|
|
@ -135,34 +114,50 @@ export const doResetPassword = async (username: string, oldpassword: string, new
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export function useLogin() {
|
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(() => {
|
useEffect(() => {
|
||||||
const listener = (_e: AuthEvent) => {
|
const listener = () => {
|
||||||
setUser(getUserSessions());
|
void mutate();
|
||||||
};
|
};
|
||||||
window.addEventListener("auth", listener);
|
window.addEventListener("auth", listener);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("auth", listener);
|
window.removeEventListener("auth", listener);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [mutate]);
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 () => {
|
return {
|
||||||
if (document.visibilityState === "visible") {
|
error: resolvedError,
|
||||||
const session = getUserSessions();
|
isLoading,
|
||||||
if (!session) {
|
refreshProfile: mutate,
|
||||||
await refresh();
|
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) => {
|
export const createLoginRouter = (userController: UserAccessor) => {
|
||||||
const router = new Hono<AppEnv>();
|
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(
|
router.post(
|
||||||
"/login",
|
"/login",
|
||||||
sValidator("json", LoginBodySchema),
|
sValidator("json", LoginBodySchema),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue