diff --git a/.github/chatmodes/fixer.chatmode.md b/.github/chatmodes/fixer.chatmode.md
index e309949f6e..9b5f93954a 100644
--- a/.github/chatmodes/fixer.chatmode.md
+++ b/.github/chatmodes/fixer.chatmode.md
@@ -24,7 +24,7 @@ You MUST check task output readiness before debugging, testing, or declaring wor
- Frontend: Vite provides HMR; changes in the frontend are picked up automatically without restarting the task.
- Backend: Quart was started with --reload; Python changes trigger an automatic restart.
- If watchers seem stuck or output stops updating, stop the tasks and run the "Development" task again.
-- To interact with a running application, use the Playwright MCP server
+- To interact with a running application, use the Playwright MCP server. If testing login, you will need to navigate to 'localhost' instead of '127.0.0.1' since that's the URL allowed by the Entra application.
## Committing the change
diff --git a/app/frontend/src/index.tsx b/app/frontend/src/index.tsx
index a8821c8c45..d77b08e9ac 100644
--- a/app/frontend/src/index.tsx
+++ b/app/frontend/src/index.tsx
@@ -4,12 +4,15 @@ import { createHashRouter, RouterProvider } from "react-router-dom";
import { I18nextProvider } from "react-i18next";
import { HelmetProvider } from "react-helmet-async";
import { initializeIcons } from "@fluentui/react";
+import { MsalProvider } from "@azure/msal-react";
+import { AuthenticationResult, EventType, PublicClientApplication } from "@azure/msal-browser";
import "./index.css";
import Chat from "./pages/chat/Chat";
import LayoutWrapper from "./layoutWrapper";
import i18next from "./i18n/config";
+import { msalConfig, useLogin } from "./authConfig";
initializeIcons();
@@ -34,12 +37,54 @@ const router = createHashRouter([
}
]);
-ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
-
-
-
-
-
-
-
-);
+const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
+
+// Bootstrap the app once; conditionally wrap with MsalProvider when login is enabled
+(async () => {
+ let msalInstance: PublicClientApplication | undefined;
+
+ if (useLogin) {
+ msalInstance = new PublicClientApplication(msalConfig);
+ try {
+ await msalInstance.initialize();
+
+ // Default active account to the first one if none is set
+ if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
+ msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
+ }
+
+ // Keep active account in sync on login success
+ msalInstance.addEventCallback(event => {
+ if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
+ const result = event.payload as AuthenticationResult;
+ if (result.account) {
+ msalInstance!.setActiveAccount(result.account);
+ }
+ }
+ });
+ } catch (e) {
+ // Non-fatal: render the app even if MSAL initialization fails
+ // eslint-disable-next-line no-console
+ console.error("MSAL initialize failed", e);
+ msalInstance = undefined;
+ }
+ }
+
+ const appTree = (
+
+
+
+ {useLogin && msalInstance ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+ );
+
+ root.render(appTree);
+})();
diff --git a/app/frontend/src/layoutWrapper.tsx b/app/frontend/src/layoutWrapper.tsx
index 7b00972458..c93e52acd4 100644
--- a/app/frontend/src/layoutWrapper.tsx
+++ b/app/frontend/src/layoutWrapper.tsx
@@ -1,48 +1,33 @@
-import { AccountInfo, EventType, PublicClientApplication } from "@azure/msal-browser";
-import { checkLoggedIn, msalConfig, useLogin } from "./authConfig";
-import { useEffect, useState } from "react";
-import { MsalProvider } from "@azure/msal-react";
+import { useEffect, useRef, useState } from "react";
+import { useMsal } from "@azure/msal-react";
+import { useLogin, checkLoggedIn } from "./authConfig";
import { LoginContext } from "./loginContext";
import Layout from "./pages/layout/Layout";
const LayoutWrapper = () => {
const [loggedIn, setLoggedIn] = useState(false);
if (useLogin) {
- var msalInstance = new PublicClientApplication(msalConfig);
-
- // Default to using the first account if no account is active on page load
- if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
- // Account selection logic is app dependent. Adjust as needed for different use cases.
- msalInstance.setActiveAccount(msalInstance.getActiveAccount());
- }
-
- // Listen for sign-in event and set active account
- msalInstance.addEventCallback(event => {
- if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
- const account = event.payload as AccountInfo;
- msalInstance.setActiveAccount(account);
- }
- });
-
+ const { instance } = useMsal();
+ // Keep track of the mounted state to avoid setting state in an unmounted component
+ const mounted = useRef(true);
useEffect(() => {
- const fetchLoggedIn = async () => {
- setLoggedIn(await checkLoggedIn(msalInstance));
+ mounted.current = true;
+ checkLoggedIn(instance)
+ .then(isLoggedIn => {
+ if (mounted.current) setLoggedIn(isLoggedIn);
+ })
+ .catch(e => {
+ console.error("checkLoggedIn failed", e);
+ });
+ return () => {
+ mounted.current = false;
};
-
- fetchLoggedIn();
- }, []);
+ }, [instance]);
return (
-
-
-
-
-
+
+
+
);
} else {
return (