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 (