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({
= 3) { try { setLoading(true); + logImEXEvent("global_search", { search: v }); + const searchData = await axios.post("/search", { search: v }); diff --git a/client/src/components/job-audit-trail/job-audit-trail.component.jsx b/client/src/components/job-audit-trail/job-audit-trail.component.jsx index 22e1c5f1e..dcf4ccb9e 100644 --- a/client/src/components/job-audit-trail/job-audit-trail.component.jsx +++ b/client/src/components/job-audit-trail/job-audit-trail.component.jsx @@ -4,6 +4,7 @@ import { Button, Card, Col, Row, Table, Tag } from "antd"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { DateTimeFormatter } from "../../utils/DateFormatter"; @@ -125,6 +126,7 @@ export function JobAuditTrail({ bodyshop, jobId }) { render: (text, record) => (