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;