Skip to content

Commit 75b6a0a

Browse files
authored
πŸ› login window cannot be closed #1476
2 parents 57da5f4 + 7c35585 commit 75b6a0a

File tree

8 files changed

+78
-42
lines changed

8 files changed

+78
-42
lines changed

β€Žfrontend/app/[locale]/chat/page.tsxβ€Ž

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import { useAuth } from "@/hooks/useAuth";
55

66
import { useConfig } from "@/hooks/useConfig";
77
import { configService } from "@/services/configService";
8+
import { EVENTS } from "@/const/auth";
89

910
import { ChatInterface } from "./internal/chatInterface";
1011

1112
export default function ChatPage() {
1213
const { appConfig } = useConfig();
13-
const { user, isLoading: userLoading, openLoginModal, isSpeedMode } = useAuth();
14+
const { user, isLoading: userLoading, isSpeedMode } = useAuth();
1415

1516
useEffect(() => {
1617
// Load config from backend when entering chat page
@@ -22,11 +23,16 @@ export default function ChatPage() {
2223
}, [appConfig.appName]);
2324

2425
// Require login on chat page when unauthenticated (full mode only)
26+
// Trigger SESSION_EXPIRED event to show "Login Expired" modal instead of directly opening login modal
2527
useEffect(() => {
2628
if (!isSpeedMode && !userLoading && !user) {
27-
openLoginModal();
29+
window.dispatchEvent(
30+
new CustomEvent(EVENTS.SESSION_EXPIRED, {
31+
detail: { message: "Session expired, please sign in again" },
32+
})
33+
);
2834
}
29-
}, [isSpeedMode, user, userLoading, openLoginModal]);
35+
}, [isSpeedMode, user, userLoading]);
3036

3137
// Avoid rendering and backend calls when unauthenticated (full mode only)
3238
if (!isSpeedMode && (!user || userLoading)) {

β€Žfrontend/app/[locale]/setup/agents/page.tsxβ€Ž

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
CONNECTION_STATUS,
1515
ConnectionStatus,
1616
} from "@/const/modelConfig";
17+
import { EVENTS } from "@/const/auth";
1718
import log from "@/lib/logger";
1819

1920
import SetupLayout from "../SetupLayout";
@@ -30,8 +31,7 @@ export default function AgentSetupPage() {
3031
const {
3132
user,
3233
isLoading: userLoading,
33-
isSpeedMode,
34-
openLoginModal,
34+
isSpeedMode
3535
} = useAuth();
3636

3737
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>(
@@ -41,9 +41,14 @@ export default function AgentSetupPage() {
4141
const [isSaving, setIsSaving] = useState(false);
4242

4343
// Check login status and permission
44+
// Trigger SESSION_EXPIRED event to show "Login Expired" modal instead of directly opening login modal
4445
useEffect(() => {
4546
if (!isSpeedMode && !userLoading && !user) {
46-
openLoginModal();
47+
window.dispatchEvent(
48+
new CustomEvent(EVENTS.SESSION_EXPIRED, {
49+
detail: { message: "Session expired, please sign in again" },
50+
})
51+
);
4752
return;
4853
}
4954

@@ -52,7 +57,7 @@ export default function AgentSetupPage() {
5257
router.push("/setup/knowledges");
5358
return;
5459
}
55-
}, [isSpeedMode, user, userLoading, router, openLoginModal]);
60+
}, [isSpeedMode, user, userLoading, router]);
5661

5762
// Check the connection status when the page is initialized
5863
useEffect(() => {

β€Žfrontend/app/[locale]/setup/knowledges/page.tsxβ€Ž

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
CONNECTION_STATUS,
1717
ConnectionStatus,
1818
} from "@/const/modelConfig";
19+
import { EVENTS } from "@/const/auth";
1920
import log from "@/lib/logger";
2021

2122
import SetupLayout from "../SetupLayout";
@@ -25,7 +26,7 @@ export default function KnowledgeSetupPage() {
2526
const { message } = App.useApp();
2627
const router = useRouter();
2728
const { t } = useTranslation();
28-
const { user, isLoading: userLoading, isSpeedMode, openLoginModal } = useAuth();
29+
const { user, isLoading: userLoading, isSpeedMode } = useAuth();
2930

3031
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>(
3132
CONNECTION_STATUS.PROCESSING
@@ -34,12 +35,17 @@ export default function KnowledgeSetupPage() {
3435
const [isSaving, setIsSaving] = useState(false);
3536

3637
// Check login status and permission
38+
// Trigger SESSION_EXPIRED event to show "Login Expired" modal instead of directly opening login modal
3739
useEffect(() => {
3840
if (!isSpeedMode && !userLoading && !user) {
39-
openLoginModal();
41+
window.dispatchEvent(
42+
new CustomEvent(EVENTS.SESSION_EXPIRED, {
43+
detail: { message: "Session expired, please sign in again" },
44+
})
45+
);
4046
return;
4147
}
42-
}, [isSpeedMode, user, userLoading, openLoginModal]);
48+
}, [isSpeedMode, user, userLoading]);
4349

4450
// Check the connection status when the page is initialized
4551
useEffect(() => {

β€Žfrontend/app/[locale]/setup/models/page.tsxβ€Ž

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
ConnectionStatus,
1818
MODEL_STATUS,
1919
} from "@/const/modelConfig";
20+
import { EVENTS } from "@/const/auth";
2021
import log from "@/lib/logger";
2122

2223
import SetupLayout from "../SetupLayout";
@@ -28,7 +29,7 @@ export default function ModelSetupPage() {
2829
const { message } = App.useApp();
2930
const router = useRouter();
3031
const { t } = useTranslation();
31-
const { user, isLoading: userLoading, isSpeedMode, openLoginModal } = useAuth();
32+
const { user, isLoading: userLoading, isSpeedMode } = useAuth();
3233

3334
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>(
3435
CONNECTION_STATUS.PROCESSING
@@ -47,9 +48,14 @@ export default function ModelSetupPage() {
4748
const modelConfigSectionRef = useRef<ModelConfigSectionRef | null>(null);
4849

4950
// Check login status and permission
51+
// Trigger SESSION_EXPIRED event to show "Login Expired" modal instead of directly opening login modal
5052
useEffect(() => {
5153
if (!isSpeedMode && !userLoading && !user) {
52-
openLoginModal();
54+
window.dispatchEvent(
55+
new CustomEvent(EVENTS.SESSION_EXPIRED, {
56+
detail: { message: "Session expired, please sign in again" },
57+
})
58+
);
5359
return;
5460
}
5561

@@ -58,7 +64,7 @@ export default function ModelSetupPage() {
5864
router.push("/setup/knowledges");
5965
return;
6066
}
61-
}, [isSpeedMode, user, userLoading, openLoginModal, router]);
67+
}, [isSpeedMode, user, userLoading, router]);
6268

6369
// Check the connection status when the page is initialized
6470
useEffect(() => {

β€Žfrontend/components/auth/loginModal.tsxβ€Ž

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,10 @@ export function LoginModal() {
138138
closeLoginModal();
139139

140140
// If login modal was opened due to session expiration,
141-
// re-trigger the session expired event
141+
// reset modal state and re-trigger the session expired event
142142
if (isFromSessionExpired) {
143+
// Reset modal state so session expired modal can be shown again
144+
document.dispatchEvent(new CustomEvent("modalClosed"));
143145
setTimeout(() => {
144146
window.dispatchEvent(
145147
new CustomEvent(EVENTS.SESSION_EXPIRED, {
@@ -163,9 +165,10 @@ export function LoginModal() {
163165
width={400}
164166
centered
165167
forceRender
166-
// Prevent modal from being closed by clicking mask or close button when session is expired
168+
// Prevent modal from being closed by clicking mask when session is expired
169+
// But allow close button to be visible so user can return to session expired prompt
167170
maskClosable={!isFromSessionExpired}
168-
closable={!isFromSessionExpired}
171+
closable={true}
169172
>
170173
<Form
171174
id="login-form"

β€Žfrontend/components/auth/sessionListeners.tsxβ€Ž

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ export function SessionListeners() {
2525
const router = useRouter();
2626
const pathname = usePathname();
2727
const { t } = useTranslation("common");
28-
const { openLoginModal, setIsFromSessionExpired, logout, isSpeedMode } =
28+
const { openLoginModal, setIsFromSessionExpired, clearLocalSession, isSpeedMode } =
2929
useAuth();
3030
const { modal } = App.useApp();
3131
const modalShownRef = useRef<boolean>(false);
3232

33+
const isLocaleHomePath = (path?: string | null) => {
34+
if (!path) return false;
35+
const segments = path.split("/").filter(Boolean);
36+
return segments.length <= 1;
37+
};
38+
3339
/**
3440
* Show "Login Expired" confirmation modal
3541
* This function handles debounce logic to prevent modal from appearing repeatedly
@@ -46,25 +52,20 @@ export function SessionListeners() {
4652
okText: t("login.expired.okText"),
4753
cancelText: t("login.expired.cancelText"),
4854
closable: false,
49-
async onOk() {
50-
try {
51-
// Silently logout
52-
await logout({ silent: true });
53-
} finally {
54-
// Mark the source as session expired
55-
setIsFromSessionExpired(true);
56-
Modal.destroyAll();
57-
openLoginModal();
58-
setTimeout(() => (modalShownRef.current = false), 500);
59-
}
55+
onOk() {
56+
// Clear local session state (session already expired on backend)
57+
clearLocalSession();
58+
// Mark the source as session expired
59+
setIsFromSessionExpired(true);
60+
Modal.destroyAll();
61+
openLoginModal();
62+
setTimeout(() => (modalShownRef.current = false), 500);
6063
},
61-
async onCancel() {
62-
try {
63-
await logout();
64-
} finally {
65-
router.push("/");
66-
setTimeout(() => (modalShownRef.current = false), 500);
67-
}
64+
onCancel() {
65+
// Clear local session state (session already expired on backend)
66+
clearLocalSession();
67+
router.push("/");
68+
setTimeout(() => (modalShownRef.current = false), 500);
6869
},
6970
});
7071
};
@@ -106,7 +107,7 @@ export function SessionListeners() {
106107
);
107108
};
108109
// Remove confirm from dependency array to avoid duplicate registration due to function reference changes
109-
}, [router, pathname, openLoginModal, setIsFromSessionExpired, modal, isSpeedMode]);
110+
}, [isSpeedMode]);
110111

111112
// When component first mounts, if no local session is found, show modal immediately
112113
useEffect(() => {
@@ -129,23 +130,20 @@ export function SessionListeners() {
129130
const session = await authService.getSession();
130131

131132
// Only show session expired modal if a prior session existed and is now invalid
132-
if (!session && hadLocalSession) {
133+
if ((!session && hadLocalSession) || (!session && !hadLocalSession && !isLocaleHomePath(pathname))) {
133134
window.dispatchEvent(
134135
new CustomEvent(EVENTS.SESSION_EXPIRED, {
135136
detail: { message: "Session expired, please sign in again" },
136137
})
137138
);
138-
} else if (!session && !hadLocalSession) {
139-
// Full mode with no prior session: proactively prompt login
140-
openLoginModal();
141139
}
142140
} catch (error) {
143141
log.error("Error checking session status:", error);
144142
}
145143
};
146144

147145
checkSession();
148-
}, [pathname, isSpeedMode, openLoginModal]);
146+
}, [pathname, isSpeedMode]);
149147

150148
// Sliding expiration: refresh token shortly before expiry on user activity (skip in speed mode)
151149
useEffect(() => {

β€Žfrontend/hooks/useAuth.tsβ€Ž

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { configService } from "@/services/configService"
1212
import { API_ENDPOINTS } from "@/services/api"
1313
import { User, AuthContextType } from "@/types/auth"
1414
import { EVENTS, STATUS_CODES } from "@/const/auth"
15-
import { getSessionFromStorage } from "@/lib/auth"
15+
import { getSessionFromStorage, removeSessionFromStorage } from "@/lib/auth"
1616
import log from "@/lib/logger"
1717

1818
// Create auth context
@@ -300,6 +300,16 @@ export function AuthProvider({ children }: { children: (value: AuthContextType)
300300
}
301301
}
302302

303+
// Clear local session state without calling backend API
304+
// Used when session is already expired on the backend
305+
const clearLocalSession = () => {
306+
removeSessionFromStorage()
307+
setUser(null)
308+
setShouldCheckSession(false)
309+
// Manually trigger storage event
310+
window.dispatchEvent(new StorageEvent("storage", { key: "session", newValue: null }))
311+
}
312+
303313
const logout = async (options?: { silent?: boolean }) => {
304314
try {
305315
setIsLoading(true)
@@ -357,6 +367,7 @@ export function AuthProvider({ children }: { children: (value: AuthContextType)
357367
login,
358368
register,
359369
logout,
370+
clearLocalSession,
360371
revoke,
361372
};
362373

β€Žfrontend/types/auth.tsβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export interface AuthContextType {
5353
inviteCode?: string
5454
) => Promise<void>;
5555
logout: (options?: { silent?: boolean }) => Promise<void>;
56+
clearLocalSession: () => void;
5657
revoke: () => Promise<void>;
5758
}
5859

0 commit comments

Comments
Β (0)