diff --git a/_reference/localEmailViewer/index.js b/_reference/localEmailViewer/index.js
index 2b4c21444..1dd17f779 100644
--- a/_reference/localEmailViewer/index.js
+++ b/_reference/localEmailViewer/index.js
@@ -1,116 +1,96 @@
// index.js
-import express from 'express';
-import fetch from 'node-fetch';
-import {simpleParser} from 'mailparser';
+import express from "express";
+import fetch from "node-fetch";
+import { simpleParser } from "mailparser";
const app = express();
const PORT = 3334;
-app.get('/', async (req, res) => {
- try {
- const response = await fetch('http://localhost:4566/_aws/ses');
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- const data = await response.json();
- const messagesHtml = await parseMessages(data.messages);
- res.send(renderHtml(messagesHtml));
- } catch (error) {
- console.error('Error fetching messages:', error);
- res.status(500).send('Error fetching messages');
+app.get("/", async (req, res) => {
+ try {
+ const response = await fetch("http://localhost:4566/_aws/ses");
+ if (!response.ok) {
+ throw new Error("Network response was not ok");
}
+ const data = await response.json();
+ const messagesHtml = await parseMessages(data.messages);
+ res.send(renderHtml(messagesHtml));
+ } catch (error) {
+ console.error("Error fetching messages:", error);
+ res.status(500).send("Error fetching messages");
+ }
});
async function parseMessages(messages) {
- const parsedMessages = await Promise.all(
- messages.map(async (message, index) => {
- try {
- const parsed = await simpleParser(message.RawData);
- return `
-
-
-
- Message ${index + 1}
-
-
- From: ${message.Source}
-
-
- Region: ${message.Region}
-
-
- Timestamp: ${message.Timestamp}
-
-
-
- ${parsed.html || parsed.textAsHtml || 'No HTML content available'}
-
-
- `;
- } catch (error) {
- console.error('Error parsing email:', error);
- return `
-
-
- Message ${index + 1}
-
-
- From: ${message.Source}
-
-
- Region: ${message.Region}
-
-
- Timestamp: ${message.Timestamp}
-
-
- Error parsing email content
-
-
- `;
- }
- })
- );
- return parsedMessages.join('');
+ const parsedMessages = await Promise.all(
+ messages.map(async (message, index) => {
+ try {
+ const parsed = await simpleParser(message.RawData);
+ return `
+
+
+
Message ${index + 1}
+
From: ${message.Source}
+
To: ${parsed.to.text || "No To Address"}
+
Subject: ${parsed.subject || "No Subject"}
+
Region: ${message.Region}
+
Timestamp: ${message.Timestamp}
+
+
${parsed.html || parsed.textAsHtml || "No HTML content available"}
+
+ `;
+ } catch (error) {
+ console.error("Error parsing email:", error);
+ return `
+
+
Message ${index + 1}
+
From: ${message.Source}
+
Region: ${message.Region}
+
Timestamp: ${message.Timestamp}
+
Error parsing email content
+
+ `;
+ }
+ })
+ );
+ return parsedMessages.join("");
}
function renderHtml(messagesHtml) {
- return `
-
-
-
-
-
- Email Messages Viewer
-
-
-
-
-
-
Email Messages Viewer
-
- ${messagesHtml}
-
-
-
-
- `;
+ return `
+
+
+
+
+
+ Email Messages Viewer
+
+
+
+
+
+
Email Messages Viewer
+
${messagesHtml}
+
+
+
+ `;
}
app.listen(PORT, () => {
- console.log(`Server is running on http://localhost:${PORT}`);
-});
\ No newline at end of file
+ console.log(`Server is running on http://localhost:${PORT}`);
+});
diff --git a/_reference/localEmailViewer/package-lock.json b/_reference/localEmailViewer/package-lock.json
index ae14abb19..2c7ecad93 100644
--- a/_reference/localEmailViewer/package-lock.json
+++ b/_reference/localEmailViewer/package-lock.json
@@ -10,7 +10,7 @@
"license": "ISC",
"dependencies": {
"express": "^5.1.0",
- "mailparser": "^3.7.2",
+ "mailparser": "^3.7.4",
"node-fetch": "^3.3.2"
}
},
@@ -634,9 +634,9 @@
"license": "MIT"
},
"node_modules/libmime": {
- "version": "5.3.6",
- "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.6.tgz",
- "integrity": "sha512-j9mBC7eiqi6fgBPAGvKCXJKJSIASanYF4EeA4iBzSG0HxQxmXnR3KbyWqTn4CwsKSebqCv2f5XZfAO6sKzgvwA==",
+ "version": "5.3.7",
+ "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz",
+ "integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==",
"license": "MIT",
"dependencies": {
"encoding-japanese": "2.2.0",
@@ -661,31 +661,31 @@
}
},
"node_modules/mailparser": {
- "version": "3.7.2",
- "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.2.tgz",
- "integrity": "sha512-iI0p2TCcIodR1qGiRoDBBwboSSff50vQAWytM5JRggLfABa4hHYCf3YVujtuzV454xrOP352VsAPIzviqMTo4Q==",
+ "version": "3.7.4",
+ "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.4.tgz",
+ "integrity": "sha512-Beh4yyR4jLq3CZZ32asajByrXnW8dLyKCAQD3WvtTiBnMtFWhxO+wa93F6sJNjDmfjxXs4NRNjw3XAGLqZR3Vg==",
"license": "MIT",
"dependencies": {
"encoding-japanese": "2.2.0",
"he": "1.2.0",
"html-to-text": "9.0.5",
"iconv-lite": "0.6.3",
- "libmime": "5.3.6",
+ "libmime": "5.3.7",
"linkify-it": "5.0.0",
- "mailsplit": "5.4.2",
- "nodemailer": "6.9.16",
+ "mailsplit": "5.4.5",
+ "nodemailer": "7.0.4",
"punycode.js": "2.3.1",
- "tlds": "1.255.0"
+ "tlds": "1.259.0"
}
},
"node_modules/mailsplit": {
- "version": "5.4.2",
- "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.2.tgz",
- "integrity": "sha512-4cczG/3Iu3pyl8JgQ76dKkisurZTmxMrA4dj/e8d2jKYcFTZ7MxOzg1gTioTDMPuFXwTrVuN/gxhkrO7wLg7qA==",
+ "version": "5.4.5",
+ "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.5.tgz",
+ "integrity": "sha512-oMfhmvclR689IIaQmIcR5nODnZRRVwAKtqFT407TIvmhX2OLUBnshUTcxzQBt3+96sZVDud9NfSe1NxAkUNXEQ==",
"license": "(MIT OR EUPL-1.1+)",
"dependencies": {
"libbase64": "1.3.0",
- "libmime": "5.3.6",
+ "libmime": "5.3.7",
"libqp": "2.1.1"
}
},
@@ -793,9 +793,9 @@
}
},
"node_modules/nodemailer": {
- "version": "6.9.16",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
- "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.4.tgz",
+ "integrity": "sha512-9O00Vh89/Ld2EcVCqJ/etd7u20UhME0f/NToPfArwPEe1Don1zy4mAIz6ariRr7mJ2RDxtaDzN0WJVdVXPtZaw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
@@ -1114,9 +1114,9 @@
}
},
"node_modules/tlds": {
- "version": "1.255.0",
- "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz",
- "integrity": "sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==",
+ "version": "1.259.0",
+ "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz",
+ "integrity": "sha512-AldGGlDP0PNgwppe2quAvuBl18UcjuNtOnDuUkqhd6ipPqrYYBt3aTxK1QTsBVknk97lS2JcafWMghjGWFtunw==",
"license": "MIT",
"bin": {
"tlds": "bin.js"
diff --git a/_reference/localEmailViewer/package.json b/_reference/localEmailViewer/package.json
index 74cef99ff..5f553cf17 100644
--- a/_reference/localEmailViewer/package.json
+++ b/_reference/localEmailViewer/package.json
@@ -12,7 +12,7 @@
"description": "",
"dependencies": {
"express": "^5.1.0",
- "mailparser": "^3.7.2",
+ "mailparser": "^3.7.4",
"node-fetch": "^3.3.2"
}
}
diff --git a/client/.env.development.imex b/client/.env.development.imex
index a7c9c1349..79d1b4e63 100644
--- a/client/.env.development.imex
+++ b/client/.env.development.imex
@@ -16,4 +16,5 @@ TEST_USERNAME="test@imex.dev"
TEST_PASSWORD="test123"
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
-VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
\ No newline at end of file
+VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
+VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
\ No newline at end of file
diff --git a/client/.env.development.rome b/client/.env.development.rome
index 816df9917..eabf048e8 100644
--- a/client/.env.development.rome
+++ b/client/.env.development.rome
@@ -18,4 +18,5 @@ TEST_USERNAME="test@imex.dev"
TEST_PASSWORD="test123"
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
-VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
\ No newline at end of file
+VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
+VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
\ No newline at end of file
diff --git a/client/.env.production.imex b/client/.env.production.imex
index dc1d7fe7a..70c7c01c7 100644
--- a/client/.env.production.imex
+++ b/client/.env.production.imex
@@ -15,4 +15,5 @@ VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=IMEX
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
-VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
\ No newline at end of file
+VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
+VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
\ No newline at end of file
diff --git a/client/.env.production.rome b/client/.env.production.rome
index 808c8e199..cb2cd88ac 100644
--- a/client/.env.production.rome
+++ b/client/.env.production.rome
@@ -15,4 +15,5 @@ VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
VITE_APP_INSTANCE=ROME
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
-VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
\ No newline at end of file
+VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
+VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
\ No newline at end of file
diff --git a/client/.env.test.imex b/client/.env.test.imex
index 2ff9a10d7..0afecd91b 100644
--- a/client/.env.test.imex
+++ b/client/.env.test.imex
@@ -15,4 +15,5 @@ VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=IMEX
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
-VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
\ No newline at end of file
+VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
+VITE_APP_AMP_KEY=6228a598e57cd66875cfd41604f1f891
\ No newline at end of file
diff --git a/client/.env.test.rome b/client/.env.test.rome
index 24c9b7047..558c4528e 100644
--- a/client/.env.test.rome
+++ b/client/.env.test.rome
@@ -15,4 +15,5 @@ VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
VITE_APP_INSTANCE=ROME
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
-VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
\ No newline at end of file
+VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
+VITE_APP_AMP_KEY=46b1193a867d4e3131ae4c3a64a3fc78
\ No newline at end of file
diff --git a/client/src/App/App.jsx b/client/src/App/App.jsx
index fa24ef07e..ea3be8f5a 100644
--- a/client/src/App/App.jsx
+++ b/client/src/App/App.jsx
@@ -24,6 +24,7 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
import SocketProvider from "../contexts/SocketIO/socketProvider.jsx";
+import SoundWrapper from "./SoundWrapper.jsx";
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
@@ -72,9 +73,6 @@ export function App({
setIsPartsEntry(isParts);
}, [setIsPartsEntry]);
- //const b = Grid.useBreakpoint();
- // console.log("Breakpoints:", b);
-
// Associate event listeners, memoize to prevent multiple listeners being added
useEffect(() => {
const offlineListener = () => {
@@ -164,85 +162,87 @@ export function App({
/>
-
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
+
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ >
+ } />
+
+
+
+
+
+
+ }
+ >
+ } />
+
+
-
-
- }
- >
- } />
-
-
-
-
-
-
- }
- >
- } />
-
-
-
-
- }
- >
- } />
-
- }>
- } />
-
-
+
+ }
+ >
+ } />
+
+ }>
+ } />
+
+
+
);
diff --git a/client/src/App/SoundWrapper.jsx b/client/src/App/SoundWrapper.jsx
new file mode 100644
index 000000000..639fac3c9
--- /dev/null
+++ b/client/src/App/SoundWrapper.jsx
@@ -0,0 +1,43 @@
+import { useEffect } from "react";
+import { useTranslation } from "react-i18next";
+import { useNotification } from "../contexts/Notifications/notificationContext.jsx";
+import { initNewMessageSound, unlockAudio } from "./../utils/soundManager";
+import { initSingleTabAudioLeader } from "../utils/singleTabAudioLeader";
+
+export default function SoundWrapper({ children, bodyshop }) {
+ const { t } = useTranslation();
+ const notification = useNotification();
+
+ useEffect(() => {
+ if (!bodyshop?.id) return;
+
+ // 1) Init single-tab leader election (only one tab should play sounds), scoped by bodyshopId
+ const cleanupLeader = initSingleTabAudioLeader(bodyshop.id);
+
+ // 2) Initialize base audio
+ initNewMessageSound("https://images.imex.online/app/messageTone.wav", 0.7);
+
+ // 3) Show a one-time prompt when autoplay blocks first play
+ const onNeedsUnlock = () => {
+ notification.info({
+ description: t("audio.manager.description"),
+ duration: 3
+ });
+ };
+ window.addEventListener("sound-needs-unlock", onNeedsUnlock);
+
+ // 4) Proactively unlock on first gesture (once per session)
+ const gesture = () => unlockAudio(bodyshop.id);
+ window.addEventListener("click", gesture, { once: true, passive: true });
+ window.addEventListener("touchstart", gesture, { once: true, passive: true });
+ window.addEventListener("keydown", gesture, { once: true });
+
+ return () => {
+ cleanupLeader();
+ window.removeEventListener("sound-needs-unlock", onNeedsUnlock);
+ // gesture listeners were added with {once:true}
+ };
+ }, [notification, t, bodyshop?.id]); // include bodyshop.id so this runs when org changes
+
+ return <>{children}>;
+}
diff --git a/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx b/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx
index 656be994d..74dd0d9c8 100644
--- a/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx
+++ b/client/src/components/accounting-receivables-table/accounting-receivables-table.component.jsx
@@ -142,7 +142,16 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
refetch={refetch}
/>
-
+
)
diff --git a/client/src/components/bills-list-table/bills-list-table.component.jsx b/client/src/components/bills-list-table/bills-list-table.component.jsx
index 8b1d8e09c..166cd0b5b 100644
--- a/client/src/components/bills-list-table/bills-list-table.component.jsx
+++ b/client/src/components/bills-list-table/bills-list-table.component.jsx
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { FaTasks } from "react-icons/fa";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
+import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -75,6 +76,7 @@ export function BillsListTableComponent({