forked from baron/baron-sso
테넌트 목록 조회 cursor기반으로 재구성. 사용자 metadata 미사용 필드 제거
This commit is contained in:
@@ -15,6 +15,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useAuth } from "react-oidc-context";
|
||||
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
type ShellTranslator,
|
||||
applyShellTheme,
|
||||
buildShellProfileSummary,
|
||||
buildShellSessionStatus,
|
||||
@@ -60,6 +61,48 @@ const navItems = [
|
||||
},
|
||||
];
|
||||
|
||||
type SessionStatusProps = {
|
||||
expiresAtSec?: number | null;
|
||||
t: ShellTranslator;
|
||||
};
|
||||
|
||||
function useSessionStatus({ expiresAtSec, t }: SessionStatusProps) {
|
||||
const [nowMs, setNowMs] = useState(() => Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setInterval(() => {
|
||||
setNowMs(Date.now());
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return buildShellSessionStatus({ expiresAtSec, nowMs, t });
|
||||
}
|
||||
|
||||
function SessionStatusBadge(props: SessionStatusProps) {
|
||||
const sessionStatus = useSessionStatus(props);
|
||||
|
||||
return (
|
||||
<span
|
||||
className={[
|
||||
shellLayoutClasses.sessionBadge,
|
||||
sessionStatus.toneClass,
|
||||
].join(" ")}
|
||||
>
|
||||
{sessionStatus.text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function SessionStatusText(props: SessionStatusProps) {
|
||||
const sessionStatus = useSessionStatus(props);
|
||||
|
||||
return <>{sessionStatus.text}</>;
|
||||
}
|
||||
|
||||
function AppLayout() {
|
||||
const auth = useAuth();
|
||||
const location = useLocation();
|
||||
@@ -73,8 +116,6 @@ function AppLayout() {
|
||||
const [isSessionExpiryEnabled, setIsSessionExpiryEnabled] = useState(
|
||||
readShellSessionExpiryEnabled,
|
||||
);
|
||||
const [nowMs, setNowMs] = useState(() => Date.now());
|
||||
|
||||
const hasAccessToken = Boolean(auth.user?.access_token);
|
||||
const { data: profile } = useQuery({
|
||||
queryKey: ["userMe"],
|
||||
@@ -97,15 +138,6 @@ function AppLayout() {
|
||||
applyShellTheme(theme);
|
||||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setInterval(() => {
|
||||
setNowMs(Date.now());
|
||||
}, 1000);
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
@@ -284,12 +316,6 @@ function AppLayout() {
|
||||
auth.user?.profile as Record<string, unknown> | undefined,
|
||||
);
|
||||
const displayRoleKey = profile?.role || currentRole;
|
||||
const sessionStatus = buildShellSessionStatus({
|
||||
expiresAtSec: auth.user?.expires_at,
|
||||
nowMs,
|
||||
t,
|
||||
});
|
||||
|
||||
const handleSessionExpiryToggle = () => {
|
||||
setIsSessionExpiryEnabled((prev) => {
|
||||
const next = !prev;
|
||||
@@ -398,14 +424,10 @@ function AppLayout() {
|
||||
: t("ui.common.theme_dark", "Dark")}
|
||||
</button>
|
||||
{isSessionExpiryEnabled ? (
|
||||
<span
|
||||
className={[
|
||||
shellLayoutClasses.sessionBadge,
|
||||
sessionStatus.toneClass,
|
||||
].join(" ")}
|
||||
>
|
||||
{sessionStatus.text}
|
||||
</span>
|
||||
<SessionStatusBadge
|
||||
expiresAtSec={auth.user?.expires_at}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
<div className="relative" ref={profileMenuRef}>
|
||||
<button
|
||||
@@ -466,12 +488,17 @@ function AppLayout() {
|
||||
{t("ui.dev.session.auto_extend", "Session expiry")}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{isSessionExpiryEnabled
|
||||
? sessionStatus.text
|
||||
: t(
|
||||
"ui.dev.session.disabled",
|
||||
"Session expiry disabled",
|
||||
)}
|
||||
{isSessionExpiryEnabled ? (
|
||||
<SessionStatusText
|
||||
expiresAtSec={auth.user?.expires_at}
|
||||
t={t}
|
||||
/>
|
||||
) : (
|
||||
t(
|
||||
"ui.dev.session.disabled",
|
||||
"Session expiry disabled",
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import axios from "axios";
|
||||
import { shouldStartLoginRedirect } from "../../../common/core/auth";
|
||||
import { userManager } from "./auth";
|
||||
|
||||
let isRedirectingToLogin = false;
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL:
|
||||
import.meta.env.VITE_DEV_API_BASE ??
|
||||
@@ -32,8 +35,6 @@ apiClient.interceptors.response.use(
|
||||
error.response?.data?.error?.toString().toLowerCase() ??
|
||||
error.response?.data?.message?.toString().toLowerCase() ??
|
||||
"";
|
||||
const isAuthPath = window.location.pathname.startsWith("/auth/callback");
|
||||
const isLoginPath = window.location.pathname === "/login";
|
||||
const shouldRedirectToLogin =
|
||||
status === 401 ||
|
||||
(status === 403 &&
|
||||
@@ -41,7 +42,14 @@ apiClient.interceptors.response.use(
|
||||
message.includes("invalid session") ||
|
||||
message.includes("token is not active")));
|
||||
|
||||
if (shouldRedirectToLogin && !isAuthPath && !isLoginPath) {
|
||||
if (
|
||||
shouldRedirectToLogin &&
|
||||
shouldStartLoginRedirect({
|
||||
pathname: window.location.pathname,
|
||||
isRedirecting: isRedirectingToLogin,
|
||||
})
|
||||
) {
|
||||
isRedirectingToLogin = true;
|
||||
await userManager.removeUser();
|
||||
window.location.href = "/login";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user