139 lines
4.2 KiB
JavaScript
139 lines
4.2 KiB
JavaScript
import { ApolloProvider } from "@apollo/client/react";
|
|
import * as Sentry from "@sentry/react";
|
|
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
|
import { ConfigProvider } from "antd";
|
|
import enLocale from "antd/es/locale/en_US";
|
|
import { useEffect, useMemo } from "react";
|
|
import { CookiesProvider } from "react-cookie";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
|
import { setDarkMode } from "../redux/application/application.actions";
|
|
import { selectDarkMode } from "../redux/application/application.selectors";
|
|
import { selectCurrentUser } from "../redux/user/user.selectors.js";
|
|
import { signOutStart } from "../redux/user/user.actions";
|
|
import client from "../utils/GraphQLClient";
|
|
import App from "./App";
|
|
import getTheme from "./themeProvider";
|
|
|
|
// Base Split configuration
|
|
const config = {
|
|
core: {
|
|
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
|
key: "anon"
|
|
}
|
|
};
|
|
|
|
function SplitClientProvider({ children }) {
|
|
const imexshopid = useSelector((state) => state.user.imexshopid);
|
|
const splitClient = useSplitClient({ key: imexshopid || "anon" });
|
|
|
|
useEffect(() => {
|
|
if (import.meta.env.DEV && splitClient && imexshopid) {
|
|
console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
|
|
}
|
|
}, [splitClient, imexshopid]);
|
|
|
|
return children;
|
|
}
|
|
|
|
function AppContainer() {
|
|
const { t } = useTranslation();
|
|
const dispatch = useDispatch();
|
|
|
|
const currentUser = useSelector(selectCurrentUser);
|
|
const isDarkMode = useSelector(selectDarkMode);
|
|
|
|
const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]);
|
|
|
|
const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []);
|
|
|
|
const antdForm = useMemo(
|
|
() => ({
|
|
validateMessages: {
|
|
required: t("general.validation.required", { label: "${label}" })
|
|
}
|
|
}),
|
|
[t]
|
|
);
|
|
|
|
// Global seamless logout listener with redirect to /signin
|
|
useEffect(() => {
|
|
const handleSeamlessLogout = (event) => {
|
|
if (event.data?.type !== "seamlessLogoutRequest") return;
|
|
|
|
// Only accept messages from the parent window
|
|
if (event.source !== window.parent) return;
|
|
|
|
const targetOrigin = event.origin || "*";
|
|
|
|
if (currentUser?.authorized !== true) {
|
|
window.parent?.postMessage({ type: "seamlessLogoutResponse", status: "already_logged_out" }, targetOrigin);
|
|
return;
|
|
}
|
|
|
|
dispatch(signOutStart());
|
|
window.parent?.postMessage({ type: "seamlessLogoutResponse", status: "logged_out" }, targetOrigin);
|
|
};
|
|
|
|
window.addEventListener("message", handleSeamlessLogout);
|
|
return () => {
|
|
window.removeEventListener("message", handleSeamlessLogout);
|
|
};
|
|
}, [dispatch, currentUser?.authorized]);
|
|
|
|
// Update data-theme attribute (no cleanup to avoid transient style churn)
|
|
useEffect(() => {
|
|
document.documentElement.dataset.theme = isDarkMode ? "dark" : "light";
|
|
}, [isDarkMode]);
|
|
|
|
// Sync darkMode with localStorage
|
|
useEffect(() => {
|
|
const uid = currentUser?.uid;
|
|
|
|
if (!uid) {
|
|
dispatch(setDarkMode(false));
|
|
return;
|
|
}
|
|
|
|
const key = `dark-mode-${uid}`;
|
|
const raw = localStorage.getItem(key);
|
|
|
|
if (raw == null) {
|
|
dispatch(setDarkMode(false));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
dispatch(setDarkMode(Boolean(JSON.parse(raw))));
|
|
} catch {
|
|
dispatch(setDarkMode(false));
|
|
}
|
|
}, [currentUser?.uid, dispatch]);
|
|
|
|
// Persist darkMode
|
|
useEffect(() => {
|
|
const uid = currentUser?.uid;
|
|
if (!uid) return;
|
|
|
|
localStorage.setItem(`dark-mode-${uid}`, JSON.stringify(isDarkMode));
|
|
}, [isDarkMode, currentUser?.uid]);
|
|
|
|
return (
|
|
<CookiesProvider>
|
|
<ApolloProvider client={client}>
|
|
<ConfigProvider input={antdInput} locale={enLocale} theme={theme} form={antdForm}>
|
|
<GlobalLoadingBar />
|
|
<SplitFactoryProvider config={config}>
|
|
<SplitClientProvider>
|
|
<App />
|
|
</SplitClientProvider>
|
|
</SplitFactoryProvider>
|
|
</ConfigProvider>
|
|
</ApolloProvider>
|
|
</CookiesProvider>
|
|
);
|
|
}
|
|
|
|
export default Sentry.withProfiler(AppContainer);
|