From 745ec5751036e4b56d91714bda0ea3adb8ca4f85 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 23 Jan 2026 18:12:01 -0500 Subject: [PATCH] feature/IO-3499-React-19 - Checkpoint --- client/src/App/App.container.jsx | 105 +++++++++--------- .../notification-center.component.jsx | 19 ++-- .../notification-center.container.jsx | 7 +- .../notification-center.styles.scss | 8 ++ .../task-center/task-center.component.jsx | 35 +++--- .../task-center/task-center.container.jsx | 8 +- .../task-center/task-center.styles.scss | 8 ++ client/src/index.jsx | 38 +++---- 8 files changed, 127 insertions(+), 101 deletions(-) diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index 2437979b3..4655c701d 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -3,11 +3,10 @@ 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 { useEffect } from "react"; import { CookiesProvider } from "react-cookie"; import { useTranslation } from "react-i18next"; -import { connect, useSelector } from "react-redux"; -import { createStructuredSelector } from "reselect"; +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"; @@ -28,93 +27,99 @@ const config = { function SplitClientProvider({ children }) { const imexshopid = useSelector((state) => state.user.imexshopid); const splitClient = useSplitClient({ key: imexshopid || "anon" }); + useEffect(() => { - if (splitClient && imexshopid) { + if (import.meta.env.DEV && splitClient && imexshopid) { console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`); } }, [splitClient, imexshopid]); + return children; } -const mapStateToProps = createStructuredSelector({ - currentUser: selectCurrentUser -}); - -const mapDispatchToProps = (dispatch) => ({ - setDarkMode: (isDarkMode) => dispatch(setDarkMode(isDarkMode)), - signOutStart: () => dispatch(signOutStart()) -}); - -function AppContainer({ currentUser, setDarkMode, signOutStart }) { +function AppContainer() { const { t } = useTranslation(); + const dispatch = useDispatch(); + + const currentUser = useSelector(selectCurrentUser); const isDarkMode = useSelector(selectDarkMode); - const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]); + + const theme = () => getTheme(isDarkMode); + + const antdInput = () => ({ autoComplete: "new-password" }); + + const antdForm = () => ({ + validateMessages: { + required: t("general.validation.required", { label: "${label}" }) + } + }); // Global seamless logout listener with redirect to /signin useEffect(() => { const handleSeamlessLogout = (event) => { if (event.data?.type !== "seamlessLogoutRequest") return; - const requestOrigin = event.origin; + // 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" }, - requestOrigin || "*" - ); + window.parent?.postMessage({ type: "seamlessLogoutResponse", status: "already_logged_out" }, targetOrigin); return; } - signOutStart(); - window.parent.postMessage({ type: "seamlessLogoutResponse", status: "logged_out" }, requestOrigin || "*"); + dispatch(signOutStart()); + window.parent?.postMessage({ type: "seamlessLogoutResponse", status: "logged_out" }, targetOrigin); }; window.addEventListener("message", handleSeamlessLogout); return () => { window.removeEventListener("message", handleSeamlessLogout); }; - }, [signOutStart, currentUser]); + }, [dispatch, currentUser?.authorized]); - // Update data-theme attribute + // Update data-theme attribute (no cleanup to avoid transient style churn) useEffect(() => { - document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light"); - return () => document.documentElement.removeAttribute("data-theme"); + document.documentElement.dataset.theme = isDarkMode ? "dark" : "light"; }, [isDarkMode]); // Sync darkMode with localStorage useEffect(() => { - if (currentUser?.uid) { - const savedMode = localStorage.getItem(`dark-mode-${currentUser.uid}`); - if (savedMode !== null) { - setDarkMode(JSON.parse(savedMode)); - } else { - setDarkMode(false); - } - } else { - setDarkMode(false); + const uid = currentUser?.uid; + + if (!uid) { + dispatch(setDarkMode(false)); + return; } - }, [currentUser?.uid, setDarkMode]); + + 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(() => { - if (currentUser?.uid) { - localStorage.setItem(`dark-mode-${currentUser.uid}`, JSON.stringify(isDarkMode)); - } + const uid = currentUser?.uid; + if (!uid) return; + + localStorage.setItem(`dark-mode-${uid}`, JSON.stringify(isDarkMode)); }, [isDarkMode, currentUser?.uid]); return ( - + @@ -127,4 +132,4 @@ function AppContainer({ currentUser, setDarkMode, signOutStart }) { ); } -export default Sentry.withProfiler(connect(mapStateToProps, mapDispatchToProps)(AppContainer)); +export default Sentry.withProfiler(AppContainer); diff --git a/client/src/components/notification-center/notification-center.component.jsx b/client/src/components/notification-center/notification-center.component.jsx index 0702447c7..439335c40 100644 --- a/client/src/components/notification-center/notification-center.component.jsx +++ b/client/src/components/notification-center/notification-center.component.jsx @@ -24,6 +24,7 @@ const NotificationCenterComponent = ({ onNotificationClick, unreadCount, isEmployee, + isDarkMode, ref }) => { const { t } = useTranslation(); @@ -112,14 +113,16 @@ const NotificationCenterComponent = ({ ) : ( - +
+ +
)} ); diff --git a/client/src/components/notification-center/notification-center.container.jsx b/client/src/components/notification-center/notification-center.container.jsx index 617bf1569..c0bec13d0 100644 --- a/client/src/components/notification-center/notification-center.container.jsx +++ b/client/src/components/notification-center/notification-center.container.jsx @@ -5,6 +5,7 @@ import NotificationCenterComponent from "./notification-center.component"; import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries"; import { createStructuredSelector } from "reselect"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js"; +import { selectDarkMode } from "../../redux/application/application.selectors.js"; import day from "../../utils/day.js"; import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/useSocket.js"; import { useIsEmployee } from "../../utils/useIsEmployee.js"; @@ -22,7 +23,7 @@ const NOTIFICATION_POLL_INTERVAL_SECONDS = 60; * @returns {JSX.Element} * @constructor */ -const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount, currentUser }) => { +const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount, currentUser, isDarkMode }) => { const [showUnreadOnly, setShowUnreadOnly] = useState(false); const [notifications, setNotifications] = useState([]); const [isLoading, setIsLoading] = useState(false); @@ -213,13 +214,15 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount, loadMore={loadMore} onNotificationClick={handleNotificationClick} unreadCount={unreadCount} + isDarkMode={isDarkMode} /> ); }; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, - currentUser: selectCurrentUser + currentUser: selectCurrentUser, + isDarkMode: selectDarkMode }); export default connect(mapStateToProps, null)(NotificationCenterContainer); diff --git a/client/src/components/notification-center/notification-center.styles.scss b/client/src/components/notification-center/notification-center.styles.scss index 98e2c6890..f897df3d6 100644 --- a/client/src/components/notification-center/notification-center.styles.scss +++ b/client/src/components/notification-center/notification-center.styles.scss @@ -173,3 +173,11 @@ } } } + +.notification-center--dark { + color-scheme: dark; +} + +.notification-center--light { + color-scheme: light; +} diff --git a/client/src/components/task-center/task-center.component.jsx b/client/src/components/task-center/task-center.component.jsx index 459e495fc..20cf7cbec 100644 --- a/client/src/components/task-center/task-center.component.jsx +++ b/client/src/components/task-center/task-center.component.jsx @@ -22,6 +22,7 @@ const TaskCenterComponent = ({ hasMore, createNewTask, incompleteTaskCount, + isDarkMode, ref }) => { const { t } = useTranslation(); @@ -140,22 +141,24 @@ const TaskCenterComponent = ({ {tasks.length === 0 && !loading ? (
{t("tasks.labels.no_tasks")}
) : ( - - loading ? ( -
- -
- ) : null - }} - /> +
+ + loading ? ( +
+ +
+ ) : null + }} + /> +
)} ); diff --git a/client/src/components/task-center/task-center.container.jsx b/client/src/components/task-center/task-center.container.jsx index 5dd04b340..2aa2b403a 100644 --- a/client/src/components/task-center/task-center.container.jsx +++ b/client/src/components/task-center/task-center.container.jsx @@ -3,6 +3,7 @@ import { useQuery } from "@apollo/client/react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { selectDarkMode } from "../../redux/application/application.selectors.js"; import { INITIAL_TASKS, TASKS_CENTER_POLL_INTERVAL, useSocket } from "../../contexts/SocketIO/useSocket"; import { useIsEmployee } from "../../utils/useIsEmployee"; import TaskCenterComponent from "./task-center.component"; @@ -11,7 +12,8 @@ import { QUERY_TASKS_NO_DUE_DATE_PAGINATED, QUERY_TASKS_WITH_DUE_DATES } from ". const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, - currentUser: selectCurrentUser + currentUser: selectCurrentUser, + isDarkMode: selectDarkMode }); const mapDispatchToProps = (dispatch) => ({ @@ -24,7 +26,8 @@ const TaskCenterContainer = ({ bodyshop, currentUser, setTaskUpsertContext, - incompleteTaskCount + incompleteTaskCount, + isDarkMode }) => { const [tasks, setTasks] = useState([]); const { isConnected } = useSocket(); @@ -128,6 +131,7 @@ const TaskCenterContainer = ({ hasMore={hasMore} createNewTask={createNewTask} incompleteTaskCount={incompleteTaskCount} + isDarkMode={isDarkMode} /> ); }; diff --git a/client/src/components/task-center/task-center.styles.scss b/client/src/components/task-center/task-center.styles.scss index 411158e54..aaa622d74 100644 --- a/client/src/components/task-center/task-center.styles.scss +++ b/client/src/components/task-center/task-center.styles.scss @@ -141,3 +141,11 @@ text-align: center; } } + +.task-center--dark { + color-scheme: dark; +} + +.task-center--light { + color-scheme: light; +} diff --git a/client/src/index.jsx b/client/src/index.jsx index fd569e466..e19836f19 100644 --- a/client/src/index.jsx +++ b/client/src/index.jsx @@ -1,6 +1,5 @@ -import "./utils/sentry"; //Must be first. +import "./utils/sentry"; // Must be first. import * as Sentry from "@sentry/react"; -import { ConfigProvider } from "antd"; import Dinero from "dinero.js"; import ReactDOM from "react-dom/client"; import { Provider } from "react-redux"; @@ -14,7 +13,7 @@ import { persistor, store } from "./redux/store"; import reportWebVitals from "./reportWebVitals"; import "./translations/i18n"; import "./utils/CleanAxios"; -//import * as amplitude from "@amplitude/analytics-browser"; +// import * as amplitude from "@amplitude/analytics-browser"; import { PostHogProvider } from "posthog-js/react"; import posthog from "posthog-js"; @@ -52,39 +51,32 @@ posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, { const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter); -const router = sentryCreateBrowserRouter( - createRoutesFromElements(} />), - { - future: { - v7_startTransition: true, - v7_relativeSplatPath: true, - }, +const router = sentryCreateBrowserRouter(createRoutesFromElements(} />), { + future: { + v7_startTransition: true, + v7_relativeSplatPath: true } -); +}); + if (import.meta.env.DEV) { - let styles = + const styles = "font-weight: bold; font-size: 50px;color: red; 6px 6px 0 rgb(226,91,14) , 9px 9px 0 rgb(245,221,8) , 12px 12px 0 rgb(5,148,68) "; + console.log("%c %s", styles, `VER: ${import.meta.env.VITE_APP_INSTANCE}`); } function App() { return ( - } persistor={persistor}> - + + } persistor={persistor}> - - + + ); } -// Used for ANTD Component Tokens -// https://ant.design/docs/react/migrate-less-variables -ReactDOM.createRoot(document.getElementById("root")).render( - - - -); +ReactDOM.createRoot(document.getElementById("root")).render(); reportWebVitals();