From e251e5f8f6abff1d5603f3666f12158c2ee5260c Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 4 Mar 2026 11:38:33 -0500 Subject: [PATCH] release/2026-02-27 - Disable Responsive Design --- .../App/App.container.backup-2026-03-04.jsx | 184 ++++++++++++++++++ client/src/App/App.container.jsx | 52 +---- .../App.container.pre-rollback-2026-03-04.jsx | 184 ++++++++++++++++++ client/src/App/App.styles.scss | 56 +++--- ...sive-table.component.backup-2026-03-04.jsx | 99 ++++++++++ .../responsive-table.component.jsx | 92 +-------- 6 files changed, 501 insertions(+), 166 deletions(-) create mode 100644 client/src/App/App.container.backup-2026-03-04.jsx create mode 100644 client/src/App/App.container.pre-rollback-2026-03-04.jsx create mode 100644 client/src/components/responsive-table/responsive-table.component.backup-2026-03-04.jsx diff --git a/client/src/App/App.container.backup-2026-03-04.jsx b/client/src/App/App.container.backup-2026-03-04.jsx new file mode 100644 index 000000000..bcfc39fa1 --- /dev/null +++ b/client/src/App/App.container.backup-2026-03-04.jsx @@ -0,0 +1,184 @@ +import { ApolloProvider } from "@apollo/client/react"; +import * as Sentry from "@sentry/react"; +import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react"; +import { ConfigProvider, Grid } 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 screens = Grid.useBreakpoint(); + const isPhone = !screens.md; + const isUltraWide = Boolean(screens.xxxl); + + const theme = useMemo(() => { + const baseTheme = getTheme(isDarkMode); + + return { + ...baseTheme, + token: { + ...(baseTheme.token || {}), + screenXXXL: 2160 + }, + components: { + ...(baseTheme.components || {}), + Table: { + ...(baseTheme.components?.Table || {}), + cellFontSizeSM: isPhone ? 12 : 13, + cellFontSizeMD: isPhone ? 13 : isUltraWide ? 15 : 14, + cellFontSize: isUltraWide ? 15 : 14, + cellPaddingInlineSM: isPhone ? 8 : 10, + cellPaddingInlineMD: isPhone ? 10 : 14, + cellPaddingInline: isUltraWide ? 20 : 16, + cellPaddingBlockSM: isPhone ? 8 : 10, + cellPaddingBlockMD: isPhone ? 10 : 12, + cellPaddingBlock: isUltraWide ? 14 : 12, + selectionColumnWidth: isPhone ? 44 : 52 + } + } + }; + }, [isDarkMode, isPhone, isUltraWide]); + + const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []); + const antdTable = useMemo(() => ({ scroll: { x: "max-content" } }), []); + const antdPagination = useMemo( + () => ({ + showSizeChanger: !isPhone, + totalBoundaryShowSizeChanger: 100 + }), + [isPhone] + ); + + 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 ( + + + + + + + + + + + + + ); +} + +export default Sentry.withProfiler(AppContainer); diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index bcfc39fa1..8abf59242 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -1,7 +1,7 @@ import { ApolloProvider } from "@apollo/client/react"; import * as Sentry from "@sentry/react"; import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react"; -import { ConfigProvider, Grid } from "antd"; +import { ConfigProvider } from "antd"; import enLocale from "antd/es/locale/en_US"; import { useEffect, useMemo } from "react"; import { CookiesProvider } from "react-cookie"; @@ -43,47 +43,10 @@ function AppContainer() { const currentUser = useSelector(selectCurrentUser); const isDarkMode = useSelector(selectDarkMode); - const screens = Grid.useBreakpoint(); - const isPhone = !screens.md; - const isUltraWide = Boolean(screens.xxxl); - const theme = useMemo(() => { - const baseTheme = getTheme(isDarkMode); - - return { - ...baseTheme, - token: { - ...(baseTheme.token || {}), - screenXXXL: 2160 - }, - components: { - ...(baseTheme.components || {}), - Table: { - ...(baseTheme.components?.Table || {}), - cellFontSizeSM: isPhone ? 12 : 13, - cellFontSizeMD: isPhone ? 13 : isUltraWide ? 15 : 14, - cellFontSize: isUltraWide ? 15 : 14, - cellPaddingInlineSM: isPhone ? 8 : 10, - cellPaddingInlineMD: isPhone ? 10 : 14, - cellPaddingInline: isUltraWide ? 20 : 16, - cellPaddingBlockSM: isPhone ? 8 : 10, - cellPaddingBlockMD: isPhone ? 10 : 12, - cellPaddingBlock: isUltraWide ? 14 : 12, - selectionColumnWidth: isPhone ? 44 : 52 - } - } - }; - }, [isDarkMode, isPhone, isUltraWide]); + const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]); const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []); - const antdTable = useMemo(() => ({ scroll: { x: "max-content" } }), []); - const antdPagination = useMemo( - () => ({ - showSizeChanger: !isPhone, - totalBoundaryShowSizeChanger: 100 - }), - [isPhone] - ); const antdForm = useMemo( () => ({ @@ -159,16 +122,7 @@ function AppContainer() { return ( - + diff --git a/client/src/App/App.container.pre-rollback-2026-03-04.jsx b/client/src/App/App.container.pre-rollback-2026-03-04.jsx new file mode 100644 index 000000000..bcfc39fa1 --- /dev/null +++ b/client/src/App/App.container.pre-rollback-2026-03-04.jsx @@ -0,0 +1,184 @@ +import { ApolloProvider } from "@apollo/client/react"; +import * as Sentry from "@sentry/react"; +import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react"; +import { ConfigProvider, Grid } 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 screens = Grid.useBreakpoint(); + const isPhone = !screens.md; + const isUltraWide = Boolean(screens.xxxl); + + const theme = useMemo(() => { + const baseTheme = getTheme(isDarkMode); + + return { + ...baseTheme, + token: { + ...(baseTheme.token || {}), + screenXXXL: 2160 + }, + components: { + ...(baseTheme.components || {}), + Table: { + ...(baseTheme.components?.Table || {}), + cellFontSizeSM: isPhone ? 12 : 13, + cellFontSizeMD: isPhone ? 13 : isUltraWide ? 15 : 14, + cellFontSize: isUltraWide ? 15 : 14, + cellPaddingInlineSM: isPhone ? 8 : 10, + cellPaddingInlineMD: isPhone ? 10 : 14, + cellPaddingInline: isUltraWide ? 20 : 16, + cellPaddingBlockSM: isPhone ? 8 : 10, + cellPaddingBlockMD: isPhone ? 10 : 12, + cellPaddingBlock: isUltraWide ? 14 : 12, + selectionColumnWidth: isPhone ? 44 : 52 + } + } + }; + }, [isDarkMode, isPhone, isUltraWide]); + + const antdInput = useMemo(() => ({ autoComplete: "new-password" }), []); + const antdTable = useMemo(() => ({ scroll: { x: "max-content" } }), []); + const antdPagination = useMemo( + () => ({ + showSizeChanger: !isPhone, + totalBoundaryShowSizeChanger: 100 + }), + [isPhone] + ); + + 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 ( + + + + + + + + + + + + + ); +} + +export default Sentry.withProfiler(AppContainer); diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index aedfbd15c..5f66030b5 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -471,34 +471,34 @@ // padding: 0; //} -/* globally allow shrink inside table cells */ -.prod-list-table .ant-table-cell, -.prod-list-table .ant-table-cell > * { - min-width: 0; -} - -/* common AntD offenders */ -.prod-list-table > .ant-table-cell .ant-space, -.ant-table-cell .ant-space-item { - min-width: 0; -} - -/* Keep your custom header content on the left, push AntD sorter to the far right */ -.prod-list-table .ant-table-column-sorters { - display: flex !important; - align-items: center; - width: 100%; -} - -.prod-list-table .ant-table-column-title { - flex: 1 1 auto; - min-width: 0; /* allows ellipsis to work */ -} - -.prod-list-table .ant-table-column-sorter { - margin-left: auto; - flex: 0 0 auto; -} +///* globally allow shrink inside table cells */ +//.prod-list-table .ant-table-cell, +//.prod-list-table .ant-table-cell > * { +// min-width: 0; +//} +// +///* common AntD offenders */ +//.prod-list-table > .ant-table-cell .ant-space, +//.ant-table-cell .ant-space-item { +// min-width: 0; +//} +// +///* Keep your custom header content on the left, push AntD sorter to the far right */ +//.prod-list-table .ant-table-column-sorters { +// display: flex !important; +// align-items: center; +// width: 100%; +//} +// +//.prod-list-table .ant-table-column-title { +// flex: 1 1 auto; +// min-width: 0; /* allows ellipsis to work */ +//} +// +//.prod-list-table .ant-table-column-sorter { +// margin-left: auto; +// flex: 0 0 auto; +//} .global-search-autocomplete-fix { diff --git a/client/src/components/responsive-table/responsive-table.component.backup-2026-03-04.jsx b/client/src/components/responsive-table/responsive-table.component.backup-2026-03-04.jsx new file mode 100644 index 000000000..94ed7fc02 --- /dev/null +++ b/client/src/components/responsive-table/responsive-table.component.backup-2026-03-04.jsx @@ -0,0 +1,99 @@ +import { Grid, Table } from "antd"; +import { useMemo } from "react"; +import "./responsive-table.styles.scss"; + +function ResponsiveTable({ className, columns, mobileColumnKeys, scroll, tableLayout, ...rest }) { + const screens = Grid.useBreakpoint(); + const isPhone = !screens.md; + const isCompactViewport = !screens.lg; + const prefersHorizontalScroll = isPhone || isCompactViewport; + const isResponsiveFilteringEnabled = ["1", "true", "yes", "on"].includes( + String(import.meta.env.VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING || "") + .trim() + .toLowerCase() + ); + + const resolvedColumns = useMemo(() => { + if ( + !isResponsiveFilteringEnabled || + !Array.isArray(columns) || + !isPhone || + !Array.isArray(mobileColumnKeys) || + mobileColumnKeys.length === 0 + ) { + return columns; + } + + const visibleColumnKeys = new Set(mobileColumnKeys); + const filteredColumns = columns.filter((column) => { + const key = column?.key ?? column?.dataIndex; + + // Keep columns with no stable key to avoid accidental loss. + if (key == null) return true; + + if (Array.isArray(key)) { + return key.some((part) => visibleColumnKeys.has(part)); + } + + return visibleColumnKeys.has(key); + }); + + return filteredColumns.length > 0 ? filteredColumns : columns; + }, [columns, isPhone, isResponsiveFilteringEnabled, mobileColumnKeys]); + + const resolvedScroll = useMemo(() => { + if (prefersHorizontalScroll) { + if (scroll == null) { + return { x: "max-content" }; + } + + if (typeof scroll !== "object" || Array.isArray(scroll)) { + return scroll; + } + + const { x, ...baseScroll } = scroll; + + return { ...baseScroll, x: x ?? "max-content" }; + } + + if (scroll == null) { + // Explicitly override ConfigProvider table.scroll desktop defaults. + return {}; + } + + if (typeof scroll !== "object" || Array.isArray(scroll)) { + return scroll; + } + + const { x, ...desktopScroll } = scroll; + + // On desktop we prefer fitting columns with ellipsis over forced horizontal scroll. + if (x == null) { + return desktopScroll; + } + + return desktopScroll; + }, [prefersHorizontalScroll, scroll]); + + const resolvedTableLayout = tableLayout ?? (prefersHorizontalScroll ? "auto" : "fixed"); + const responsiveClassName = prefersHorizontalScroll ? undefined : "responsive-table-fit"; + const resolvedClassName = [responsiveClassName, className].filter(Boolean).join(" "); + + return ( + + ); +} + +ResponsiveTable.Summary = Table.Summary; +ResponsiveTable.Column = Table.Column; +ResponsiveTable.ColumnGroup = Table.ColumnGroup; +ResponsiveTable.SELECTION_COLUMN = Table.SELECTION_COLUMN; +ResponsiveTable.EXPAND_COLUMN = Table.EXPAND_COLUMN; + +export default ResponsiveTable; diff --git a/client/src/components/responsive-table/responsive-table.component.jsx b/client/src/components/responsive-table/responsive-table.component.jsx index 94ed7fc02..c6424db82 100644 --- a/client/src/components/responsive-table/responsive-table.component.jsx +++ b/client/src/components/responsive-table/responsive-table.component.jsx @@ -1,93 +1,7 @@ -import { Grid, Table } from "antd"; -import { useMemo } from "react"; -import "./responsive-table.styles.scss"; +import { Table } from "antd"; -function ResponsiveTable({ className, columns, mobileColumnKeys, scroll, tableLayout, ...rest }) { - const screens = Grid.useBreakpoint(); - const isPhone = !screens.md; - const isCompactViewport = !screens.lg; - const prefersHorizontalScroll = isPhone || isCompactViewport; - const isResponsiveFilteringEnabled = ["1", "true", "yes", "on"].includes( - String(import.meta.env.VITE_APP_ENABLE_RESPONSIVE_TABLE_FILTERING || "") - .trim() - .toLowerCase() - ); - - const resolvedColumns = useMemo(() => { - if ( - !isResponsiveFilteringEnabled || - !Array.isArray(columns) || - !isPhone || - !Array.isArray(mobileColumnKeys) || - mobileColumnKeys.length === 0 - ) { - return columns; - } - - const visibleColumnKeys = new Set(mobileColumnKeys); - const filteredColumns = columns.filter((column) => { - const key = column?.key ?? column?.dataIndex; - - // Keep columns with no stable key to avoid accidental loss. - if (key == null) return true; - - if (Array.isArray(key)) { - return key.some((part) => visibleColumnKeys.has(part)); - } - - return visibleColumnKeys.has(key); - }); - - return filteredColumns.length > 0 ? filteredColumns : columns; - }, [columns, isPhone, isResponsiveFilteringEnabled, mobileColumnKeys]); - - const resolvedScroll = useMemo(() => { - if (prefersHorizontalScroll) { - if (scroll == null) { - return { x: "max-content" }; - } - - if (typeof scroll !== "object" || Array.isArray(scroll)) { - return scroll; - } - - const { x, ...baseScroll } = scroll; - - return { ...baseScroll, x: x ?? "max-content" }; - } - - if (scroll == null) { - // Explicitly override ConfigProvider table.scroll desktop defaults. - return {}; - } - - if (typeof scroll !== "object" || Array.isArray(scroll)) { - return scroll; - } - - const { x, ...desktopScroll } = scroll; - - // On desktop we prefer fitting columns with ellipsis over forced horizontal scroll. - if (x == null) { - return desktopScroll; - } - - return desktopScroll; - }, [prefersHorizontalScroll, scroll]); - - const resolvedTableLayout = tableLayout ?? (prefersHorizontalScroll ? "auto" : "fixed"); - const responsiveClassName = prefersHorizontalScroll ? undefined : "responsive-table-fit"; - const resolvedClassName = [responsiveClassName, className].filter(Boolean).join(" "); - - return ( -
- ); +function ResponsiveTable(props) { + return
; } ResponsiveTable.Summary = Table.Summary;