Compare commits

..

1 Commits

Author SHA1 Message Date
Patrick Fic
d52426f5f5 IO-3239 Additional logging fixes. 2025-05-28 15:21:42 -07:00
56 changed files with 693 additions and 1472 deletions

View File

@@ -12791,6 +12791,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>allow_text_message</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>checklist</name> <name>checklist</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -42593,6 +42614,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>allow_text_message</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>name</name> <name>name</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

220
client/package-lock.json generated
View File

@@ -15,17 +15,17 @@
"@fingerprintjs/fingerprintjs": "^4.6.1", "@fingerprintjs/fingerprintjs": "^4.6.1",
"@firebase/analytics": "^0.10.16", "@firebase/analytics": "^0.10.16",
"@firebase/app": "^0.13.0", "@firebase/app": "^0.13.0",
"@firebase/auth": "^1.10.6", "@firebase/auth": "^1.10.5",
"@firebase/firestore": "^4.7.16", "@firebase/firestore": "^4.7.15",
"@firebase/messaging": "^0.12.21", "@firebase/messaging": "^0.12.21",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.8.2", "@reduxjs/toolkit": "^2.8.2",
"@sentry/cli": "^2.46.0", "@sentry/cli": "^2.45.0",
"@sentry/react": "^9.23.0", "@sentry/react": "^9.22.0",
"@sentry/vite-plugin": "^3.5.0", "@sentry/vite-plugin": "^3.5.0",
"@splitsoftware/splitio-react": "^2.1.1", "@splitsoftware/splitio-react": "^2.1.1",
"@tanem/react-nprogress": "^5.0.53", "@tanem/react-nprogress": "^5.0.53",
"antd": "^5.25.3", "antd": "^5.25.2",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^4.3.0", "apollo-link-sentry": "^4.3.0",
"autosize": "^6.0.1", "autosize": "^6.0.1",
@@ -48,7 +48,6 @@
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"normalize-url": "^8.0.1", "normalize-url": "^8.0.1",
"object-hash": "^3.0.0", "object-hash": "^3.0.0",
"phone": "^3.1.59",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"query-string": "^9.2.0", "query-string": "^9.2.0",
"raf-schd": "^4.0.3", "raf-schd": "^4.0.3",
@@ -60,7 +59,7 @@
"react-drag-listview": "^2.0.0", "react-drag-listview": "^2.0.0",
"react-grid-gallery": "^1.0.1", "react-grid-gallery": "^1.0.1",
"react-grid-layout": "1.3.4", "react-grid-layout": "1.3.4",
"react-i18next": "^15.5.2", "react-i18next": "^15.4.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-image-lightbox": "^5.1.4", "react-image-lightbox": "^5.1.4",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -100,7 +99,7 @@
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@vitejs/plugin-react": "^4.5.0", "@vitejs/plugin-react": "^4.3.4",
"browserslist": "^4.24.5", "browserslist": "^4.24.5",
"browserslist-to-esbuild": "^2.1.1", "browserslist-to-esbuild": "^2.1.1",
"chalk": "^5.4.1", "chalk": "^5.4.1",
@@ -2967,9 +2966,9 @@
} }
}, },
"node_modules/@firebase/auth": { "node_modules/@firebase/auth": {
"version": "1.10.6", "version": "1.10.5",
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.6.tgz", "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.5.tgz",
"integrity": "sha512-cFbo2FymQltog4atI9cKTO6CxKxS0dOMXslTQrlNZRH7qhDG44/d7QeI6GXLweFZtrnlecf52ESnNz1DU6ek8w==", "integrity": "sha512-6wF/NdMTwObL4RNQePunuzMr9O3gyftisvFZFFKf57D2HONXo87YymogRV8d+Z7SLA0rcNBN1gLJVk2D0y97gA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.17", "@firebase/component": "0.6.17",
@@ -3004,9 +3003,9 @@
} }
}, },
"node_modules/@firebase/firestore": { "node_modules/@firebase/firestore": {
"version": "4.7.16", "version": "4.7.15",
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.16.tgz", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.15.tgz",
"integrity": "sha512-5OpvlwYVUTLEnqewOlXmtIpH8t2ISlZHDW0NDbKROM2D0ATMqFkMHdvl+/wz9zOAcb8GMQYlhCihOnVAliUbpQ==", "integrity": "sha512-FgWTmkNBEXdKCoN2ngBNjrMaXuBx6QwjiZZVnOGg+VjUmiBq5gAqlDIW5bZY6i/NYvLUrWugdqIs7y9GHEqwww==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.17", "@firebase/component": "0.6.17",
@@ -3886,13 +3885,6 @@
"react": ">=16.8.0" "react": ">=16.8.0"
} }
}, },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.9",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz",
"integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/plugin-babel": { "node_modules/@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -4469,50 +4461,50 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@sentry-internal/browser-utils": { "node_modules/@sentry-internal/browser-utils": {
"version": "9.23.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.23.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.22.0.tgz",
"integrity": "sha512-hyN2Q6mh7ggw8sDVHeRyWz5LR6gjvf8zHSzQnMaF7QkeSyaeGM/SVSL4ODwqR9TRH7U2ku6nZFMbKhaBPV+Hfg==", "integrity": "sha512-Ou1tBnVxFAIn8i9gvrWzRotNJQYiu3awNXpsFCw6qFwmiKAVPa6b13vCdolhXnrIiuR77jY1LQnKh9hXpoRzsg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "9.23.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/feedback": { "node_modules/@sentry-internal/feedback": {
"version": "9.23.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.23.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.22.0.tgz",
"integrity": "sha512-Xf+KqV69TBiPo1gk2EsU6O/dumuTMxWOF52uVWJddQYI3pQTU5DqSeoZ5AY76bIIhV9n6AEFDGqNPXmuj4Acrw==", "integrity": "sha512-zgMVkoC61fgi41zLcSZA59vOtKxcLrKBo1ECYhPD1hxEaneNqY5fhXDwlQBw96P5l2yqkgfX6YZtSdU4ejI9yA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "9.23.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay": { "node_modules/@sentry-internal/replay": {
"version": "9.23.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.23.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.22.0.tgz",
"integrity": "sha512-0/q15tvSboaK7/05BFQhs71bqgHKejJoDJgXmH0lySqgsRh/S18867ZxQNiuYhuVt337h07u1QaCyjnNJKHfuA==", "integrity": "sha512-9GOycoKbrclcRXfcbNV8svbmAsOS5R4wXBQmKF4pFLkmFA/lJv9kdZSNYkRvkrxdNfbMIJXP+DV9EqTZcryXig==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "9.23.0", "@sentry-internal/browser-utils": "9.22.0",
"@sentry/core": "9.23.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay-canvas": { "node_modules/@sentry-internal/replay-canvas": {
"version": "9.23.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.23.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.22.0.tgz",
"integrity": "sha512-cYlw5svJjyPequm0PJjFGLpee86L1NieONEHlujOXkIG6IEriiorMm+8bNpGsHRuyvg41B+4P/YmcQAGtEGxXg==", "integrity": "sha512-EcG9IMSEalFe49kowBTJObWjof/iHteDwpyuAszsFDdQUYATrVUtwpwN7o52vDYWJud4arhjrQnMamIGxa79eQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/replay": "9.23.0", "@sentry-internal/replay": "9.22.0",
"@sentry/core": "9.23.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -4528,16 +4520,16 @@
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "9.23.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.23.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.22.0.tgz",
"integrity": "sha512-QRkNxWys8e088260vByztoTsEVZ0W6v/XnZfKT6wkPPGn0aFeOrg/xjgxfI8D5huqZCxT28Cf23akOOly4FXjg==", "integrity": "sha512-3TeRm74dvX0JdjX0AgkQa+22iUHwHnY+Q6M05NZ+tDeCNHGK/mEBTeqquS1oQX67jWyuvYmG3VV6RJUxtG9Paw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "9.23.0", "@sentry-internal/browser-utils": "9.22.0",
"@sentry-internal/feedback": "9.23.0", "@sentry-internal/feedback": "9.22.0",
"@sentry-internal/replay": "9.23.0", "@sentry-internal/replay": "9.22.0",
"@sentry-internal/replay-canvas": "9.23.0", "@sentry-internal/replay-canvas": "9.22.0",
"@sentry/core": "9.23.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -4728,9 +4720,9 @@
} }
}, },
"node_modules/@sentry/cli": { "node_modules/@sentry/cli": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.45.0.tgz",
"integrity": "sha512-nqoPl7UCr446QFkylrsRrUXF51x8Z9dGquyf4jaQU+OzbOJMqclnYEvU6iwbwvaw3tu/2DnoZE/Og+Nq1h63sA==", "integrity": "sha512-4sWu7zgzgHAjIxIjXUA/66qgeEf5ZOlloO+/JaGD5qXNSW0G7KMTR6iYjReNKMgdBCTH6bUUt9qiuA+Ex9Masw==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
@@ -4747,20 +4739,20 @@
"node": ">= 10" "node": ">= 10"
}, },
"optionalDependencies": { "optionalDependencies": {
"@sentry/cli-darwin": "2.46.0", "@sentry/cli-darwin": "2.45.0",
"@sentry/cli-linux-arm": "2.46.0", "@sentry/cli-linux-arm": "2.45.0",
"@sentry/cli-linux-arm64": "2.46.0", "@sentry/cli-linux-arm64": "2.45.0",
"@sentry/cli-linux-i686": "2.46.0", "@sentry/cli-linux-i686": "2.45.0",
"@sentry/cli-linux-x64": "2.46.0", "@sentry/cli-linux-x64": "2.45.0",
"@sentry/cli-win32-arm64": "2.46.0", "@sentry/cli-win32-arm64": "2.45.0",
"@sentry/cli-win32-i686": "2.46.0", "@sentry/cli-win32-i686": "2.45.0",
"@sentry/cli-win32-x64": "2.46.0" "@sentry/cli-win32-x64": "2.45.0"
} }
}, },
"node_modules/@sentry/cli-darwin": { "node_modules/@sentry/cli-darwin": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.45.0.tgz",
"integrity": "sha512-5Ll+e5KAdIk9OYiZO8aifMBRNWmNyPjSqdjaHlBC1Qfh7pE3b1zyzoHlsUazG0bv0sNrSGea8e7kF5wIO1hvyg==", "integrity": "sha512-p4Uxfv/L2fQdP3/wYnKVVz9gzZJf/1Xp9D+6raax/3Bu5y87yHYUqcdt98y/VAXQD4ofp2QgmhGUVPofvQNZmg==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"optional": true, "optional": true,
"os": [ "os": [
@@ -4771,9 +4763,9 @@
} }
}, },
"node_modules/@sentry/cli-linux-arm": { "node_modules/@sentry/cli-linux-arm": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.45.0.tgz",
"integrity": "sha512-WRrLNq/TEX/TNJkGqq6Ad0tGyapd5dwlxtsPbVBrIdryuL1mA7VCBoaHBr3kcwJLsgBHFH0lmkMee2ubNZZdkg==", "integrity": "sha512-6sEskFLlFKJ+e0MOYgIclBTUX5jYMyYhHIxXahEkI/4vx6JO0uvpyRAkUJRpJkRh/lPog0FM+tbP3so+VxB2qQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -4781,17 +4773,16 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux", "linux",
"freebsd", "freebsd"
"android"
], ],
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@sentry/cli-linux-arm64": { "node_modules/@sentry/cli-linux-arm64": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.45.0.tgz",
"integrity": "sha512-OEJN8yAjI9y5B4telyqzu27Hi3+S4T8VxZCqJz1+z2Mp0Q/MZ622AahVPpcrVq/5bxrnlZR16+lKh8L1QwNFPg==", "integrity": "sha512-gUcLoEjzg7AIc4QQGEZwRHri+EHf3Gcms9zAR1VHiNF3/C/jL4WeDPJF2YiWAQt6EtH84tHiyhw1Ab/R8XFClg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4799,17 +4790,16 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux", "linux",
"freebsd", "freebsd"
"android"
], ],
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@sentry/cli-linux-i686": { "node_modules/@sentry/cli-linux-i686": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.45.0.tgz",
"integrity": "sha512-xko3/BVa4LX8EmRxVOCipV+PwfcK5Xs8lP6lgF+7NeuAHMNL4DqF6iV9rrN8gkGUHCUI9RXSve37uuZnFy55+Q==", "integrity": "sha512-VmmOaEAzSW23YdGNdy/+oQjCNAMY+HmOGA77A25/ep/9AV7PQB6FI7xO5Y1PVvlkxZFJ23e373njSsEeg4uDZw==",
"cpu": [ "cpu": [
"x86", "x86",
"ia32" "ia32"
@@ -4818,17 +4808,16 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux", "linux",
"freebsd", "freebsd"
"android"
], ],
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@sentry/cli-linux-x64": { "node_modules/@sentry/cli-linux-x64": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.45.0.tgz",
"integrity": "sha512-hJ1g5UEboYcOuRia96LxjJ0jhnmk8EWLDvlGnXLnYHkwy3ree/L7sNgdp/QsY8Z4j2PGO5f22Va+UDhSjhzlfQ==", "integrity": "sha512-a0Oj68mrb25a0WjX/ShZ6AAd4PPiuLcgyzQr7bl2+DvYxIOajwkGbR+CZFEhOVZcfhTnixKy/qIXEzApEPHPQg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -4836,17 +4825,16 @@
"optional": true, "optional": true,
"os": [ "os": [
"linux", "linux",
"freebsd", "freebsd"
"android"
], ],
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@sentry/cli-win32-arm64": { "node_modules/@sentry/cli-win32-arm64": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.45.0.tgz",
"integrity": "sha512-mN7cpPoCv2VExFRGHt+IoK11yx4pM4ADZQGEso5BAUZ5duViXB2WrAXCLd8DrwMnP0OE978a7N8OtzsFqjkbNA==", "integrity": "sha512-vn+CwS4p+52pQSLNPoi20ZOrQmv01ZgAmuMnjkh1oUZfTyBAwWLrAh6Cy4cztcN8DfL5dOWKQBo8DBKURE4ttg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4860,9 +4848,9 @@
} }
}, },
"node_modules/@sentry/cli-win32-i686": { "node_modules/@sentry/cli-win32-i686": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.45.0.tgz",
"integrity": "sha512-6F73AUE3lm71BISUO19OmlnkFD5WVe4/wA1YivtLZTc1RU3eUYJLYxhDfaH3P77+ycDppQ2yCgemLRaA4A8mNQ==", "integrity": "sha512-8mMoDdlwxtcdNIMtteMK7dbi7054jak8wKSHJ5yzMw8UmWxC5thc/gXBc1uPduiaI56VjoJV+phWHBKCD+6I4w==",
"cpu": [ "cpu": [
"x86", "x86",
"ia32" "ia32"
@@ -4877,9 +4865,9 @@
} }
}, },
"node_modules/@sentry/cli-win32-x64": { "node_modules/@sentry/cli-win32-x64": {
"version": "2.46.0", "version": "2.45.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.46.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.45.0.tgz",
"integrity": "sha512-yuGVcfepnNL84LGA0GjHzdMIcOzMe0bjPhq/rwPsPN+zu11N+nPR2wV2Bum4U0eQdqYH3iAlMdL5/BEQfuLJww==", "integrity": "sha512-ZvK9cIqFaq7vZ0jkHJ/xh5au6902Dr+AUxSk6L6vCL7JCe2p93KGL/4d8VFB5PD/P7Y9b+105G/e0QIFKzpeOw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -4914,22 +4902,22 @@
} }
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "9.23.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.23.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.22.0.tgz",
"integrity": "sha512-9846pn/BvASGgl7WsnKY4xry98WreP9ToeLfCQTQOf+pNfD/qNPn1/0xPInGni3LVMAXRtfHHMPm2Ghz255N7A==", "integrity": "sha512-ixvtKmPF42Y6ckGUbFlB54OWI75H2gO5UYHojO6eXFpS7xO3ZGgV/QH6wb40mWK+0w5XZ0233FuU9VpsuE6mKA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry/react": { "node_modules/@sentry/react": {
"version": "9.23.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.23.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.22.0.tgz",
"integrity": "sha512-2J/oOx8jd7Jr2koYIe5IcJyStHBXpjkQnxawo54Zyyvzc96MftyM2Dv5TeYdz7fChU1NIXw7BVbEpkQ9XEQlqg==", "integrity": "sha512-mI43NnioBYdG5TiXqRlhV1feZs9bnrrl+k5HOHBK7VQtymaXO0fkcsRLZTkdSgLRLMJGasZuvVhq2xK+18QyWQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/browser": "9.23.0", "@sentry/browser": "9.22.0",
"@sentry/core": "9.23.0", "@sentry/core": "9.22.0",
"hoist-non-react-statics": "^3.3.2" "hoist-non-react-statics": "^3.3.2"
}, },
"engines": { "engines": {
@@ -5805,16 +5793,15 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "4.5.0", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
"integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/core": "^7.26.10", "@babel/core": "^7.26.10",
"@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-self": "^7.25.9",
"@babel/plugin-transform-react-jsx-source": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9",
"@rolldown/pluginutils": "1.0.0-beta.9",
"@types/babel__core": "^7.20.5", "@types/babel__core": "^7.20.5",
"react-refresh": "^0.17.0" "react-refresh": "^0.17.0"
}, },
@@ -6104,12 +6091,12 @@
} }
}, },
"node_modules/antd": { "node_modules/antd": {
"version": "5.25.3", "version": "5.25.2",
"resolved": "https://registry.npmjs.org/antd/-/antd-5.25.3.tgz", "resolved": "https://registry.npmjs.org/antd/-/antd-5.25.2.tgz",
"integrity": "sha512-tBBcAFRjmWM3sitxrL/FEbQL+MTQntYY5bGa5c1ZZZHXWCynkhS3Ch/gy25mGMUY1M/9Uw3pH029v/RGht1x3w==", "integrity": "sha512-7R2nUvlHhey7Trx64+hCtGXOiy+DTUs1Lv5bwbV1LzEIZIhWb0at1AM6V3K108a5lyoR9n7DX3ptlLF7uYV/DQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ant-design/colors": "^7.2.1", "@ant-design/colors": "^7.2.0",
"@ant-design/cssinjs": "^1.23.0", "@ant-design/cssinjs": "^1.23.0",
"@ant-design/cssinjs-utils": "^1.1.3", "@ant-design/cssinjs-utils": "^1.1.3",
"@ant-design/fast-color": "^2.0.6", "@ant-design/fast-color": "^2.0.6",
@@ -6169,9 +6156,9 @@
} }
}, },
"node_modules/antd/node_modules/@ant-design/colors": { "node_modules/antd/node_modules/@ant-design/colors": {
"version": "7.2.1", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz", "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.0.tgz",
"integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", "integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ant-design/fast-color": "^2.0.6" "@ant-design/fast-color": "^2.0.6"
@@ -13279,15 +13266,6 @@
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/phone": {
"version": "3.1.59",
"resolved": "https://registry.npmjs.org/phone/-/phone-3.1.59.tgz",
"integrity": "sha512-CUv22jw0Zgrb/h7v3sEd262zJXS/66h7zyCCRIynx+2FswAJuuFsXsJkIxMUT4UcosKxDx1bJwdZeGnDELLsCw==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -14425,9 +14403,9 @@
} }
}, },
"node_modules/react-i18next": { "node_modules/react-i18next": {
"version": "15.5.2", "version": "15.5.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.1.tgz",
"integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==", "integrity": "sha512-C8RZ7N7H0L+flitiX6ASjq9p5puVJU1Z8VyL3OgM/QOMRf40BMZX+5TkpxzZVcTmOLPX5zlti4InEX5pFyiVeA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.25.0", "@babel/runtime": "^7.25.0",

View File

@@ -14,17 +14,17 @@
"@fingerprintjs/fingerprintjs": "^4.6.1", "@fingerprintjs/fingerprintjs": "^4.6.1",
"@firebase/analytics": "^0.10.16", "@firebase/analytics": "^0.10.16",
"@firebase/app": "^0.13.0", "@firebase/app": "^0.13.0",
"@firebase/auth": "^1.10.6", "@firebase/auth": "^1.10.5",
"@firebase/firestore": "^4.7.16", "@firebase/firestore": "^4.7.15",
"@firebase/messaging": "^0.12.21", "@firebase/messaging": "^0.12.21",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.8.2", "@reduxjs/toolkit": "^2.8.2",
"@sentry/cli": "^2.46.0", "@sentry/cli": "^2.45.0",
"@sentry/react": "^9.23.0", "@sentry/react": "^9.22.0",
"@sentry/vite-plugin": "^3.5.0", "@sentry/vite-plugin": "^3.5.0",
"@splitsoftware/splitio-react": "^2.1.1", "@splitsoftware/splitio-react": "^2.1.1",
"@tanem/react-nprogress": "^5.0.53", "@tanem/react-nprogress": "^5.0.53",
"antd": "^5.25.3", "antd": "^5.25.2",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^4.3.0", "apollo-link-sentry": "^4.3.0",
"autosize": "^6.0.1", "autosize": "^6.0.1",
@@ -47,7 +47,6 @@
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"normalize-url": "^8.0.1", "normalize-url": "^8.0.1",
"object-hash": "^3.0.0", "object-hash": "^3.0.0",
"phone": "^3.1.59",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"query-string": "^9.2.0", "query-string": "^9.2.0",
"raf-schd": "^4.0.3", "raf-schd": "^4.0.3",
@@ -59,7 +58,7 @@
"react-drag-listview": "^2.0.0", "react-drag-listview": "^2.0.0",
"react-grid-gallery": "^1.0.1", "react-grid-gallery": "^1.0.1",
"react-grid-layout": "1.3.4", "react-grid-layout": "1.3.4",
"react-i18next": "^15.5.2", "react-i18next": "^15.4.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-image-lightbox": "^5.1.4", "react-image-lightbox": "^5.1.4",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -140,7 +139,7 @@
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@vitejs/plugin-react": "^4.5.0", "@vitejs/plugin-react": "^4.3.4",
"browserslist": "^4.24.5", "browserslist": "^4.24.5",
"browserslist-to-esbuild": "^2.1.1", "browserslist-to-esbuild": "^2.1.1",
"chalk": "^5.4.1", "chalk": "^5.4.1",

View File

@@ -3,7 +3,6 @@ import { Button, Form, InputNumber, Popover, Space } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
export default function CABCpvrtCalculator({ disabled, form }) { export default function CABCpvrtCalculator({ disabled, form }) {
const [visibility, setVisibility] = useState(false); const [visibility, setVisibility] = useState(false);
@@ -40,7 +39,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
); );
return ( return (
<Popover destroyOnHidden content={popContent} open={visibility} disabled={disabled}> <Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
<Button disabled={disabled} onClick={() => setVisibility(true)}> <Button disabled={disabled} onClick={() => setVisibility(true)}>
<CalculatorFilled /> <CalculatorFilled />
</Button> </Button>

View File

@@ -202,6 +202,8 @@ export const registerMessagingHandlers = ({ socket, client }) => {
text: message.text text: message.text
}; };
// Add cases for other known message types as needed
default: default:
// Log a warning for unhandled message types // Log a warning for unhandled message types
logLocal("handleMessageChanged - Unhandled message type", { type: message.type }); logLocal("handleMessageChanged - Unhandled message type", { type: message.type });
@@ -209,7 +211,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
} }
} }
return messageRef; return messageRef; // Keep other messages unchanged
}); });
} }
} }
@@ -243,8 +245,11 @@ export const registerMessagingHandlers = ({ socket, client }) => {
}); });
const updatedList = existingList?.conversations const updatedList = existingList?.conversations
? [newConversation, ...existingList.conversations.filter((conv) => conv.id !== newConversation.id)] ? [
: [newConversation]; // Prevent duplicates newConversation,
...existingList.conversations.filter((conv) => conv.id !== newConversation.id) // Prevent duplicates
]
: [newConversation];
client.cache.writeQuery({ client.cache.writeQuery({
query: CONVERSATION_LIST_QUERY, query: CONVERSATION_LIST_QUERY,
@@ -398,7 +403,6 @@ export const registerMessagingHandlers = ({ socket, client }) => {
} }
break; break;
default: default:
logLocal("handleConversationChanged - Unhandled type", { type }); logLocal("handleConversationChanged - Unhandled type", { type });
client.cache.modify({ client.cache.modify({
@@ -415,95 +419,10 @@ export const registerMessagingHandlers = ({ socket, client }) => {
} }
}; };
// Existing handler for phone number opt-out
const handlePhoneNumberOptedOut = async (data) => {
const { bodyshopid, phone_number } = data;
logLocal("handlePhoneNumberOptedOut - Start", data);
try {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
phone_number_opt_out(existing = [], { readField }) {
const phoneNumberExists = existing.some(
(ref) => readField("phone_number", ref) === phone_number && readField("bodyshopid", ref) === bodyshopid
);
if (phoneNumberExists) {
logLocal("handlePhoneNumberOptedOut - Phone number already in cache", { phone_number, bodyshopid });
return existing;
}
const newOptOut = {
__typename: "phone_number_opt_out",
id: `temporary-${phone_number}-${Date.now()}`,
bodyshopid,
phone_number,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
return [...existing, newOptOut];
}
},
broadcast: true
});
client.cache.evict({
id: "ROOT_QUERY",
fieldName: "phone_number_opt_out",
args: { bodyshopid, search: phone_number }
});
client.cache.gc();
logLocal("handlePhoneNumberOptedOut - Cache updated successfully", data);
} catch (error) {
console.error("Error updating cache for phone number opt-out:", error);
logLocal("handlePhoneNumberOptedOut - Error", { error: error.message });
}
};
// New handler for phone number opt-in
const handlePhoneNumberOptedIn = async (data) => {
const { bodyshopid, phone_number } = data;
logLocal("handlePhoneNumberOptedIn - Start", data);
try {
// Update the Apollo cache for GET_PHONE_NUMBER_OPT_OUTS by removing the phone number
client.cache.modify({
id: "ROOT_QUERY",
fields: {
phone_number_opt_out(existing = [], { readField }) {
// Filter out the phone number from the opt-out list
return existing.filter(
(ref) => !(readField("phone_number", ref) === phone_number && readField("bodyshopid", ref) === bodyshopid)
);
}
},
broadcast: true // Trigger UI updates
});
// Evict the cache entry to force a refetch on next query
client.cache.evict({
id: "ROOT_QUERY",
fieldName: "phone_number_opt_out",
args: { bodyshopid, search: phone_number }
});
client.cache.gc();
logLocal("handlePhoneNumberOptedIn - Cache updated successfully", data);
} catch (error) {
console.error("Error updating cache for phone number opt-in:", error);
logLocal("handlePhoneNumberOptedIn - Error", { error: error.message });
}
};
socket.on("new-message-summary", handleNewMessageSummary); socket.on("new-message-summary", handleNewMessageSummary);
socket.on("new-message-detailed", handleNewMessageDetailed); socket.on("new-message-detailed", handleNewMessageDetailed);
socket.on("message-changed", handleMessageChanged); socket.on("message-changed", handleMessageChanged);
socket.on("conversation-changed", handleConversationChanged); socket.on("conversation-changed", handleConversationChanged);
socket.on("phone-number-opted-out", handlePhoneNumberOptedOut);
socket.on("phone-number-opted-in", handlePhoneNumberOptedIn);
}; };
export const unregisterMessagingHandlers = ({ socket }) => { export const unregisterMessagingHandlers = ({ socket }) => {
@@ -512,6 +431,4 @@ export const unregisterMessagingHandlers = ({ socket }) => {
socket.off("new-message-detailed"); socket.off("new-message-detailed");
socket.off("message-changed"); socket.off("message-changed");
socket.off("conversation-changed"); socket.off("conversation-changed");
socket.off("phone-number-opted-out");
socket.off("phone-number-opted-in");
}; };

View File

@@ -1,4 +1,4 @@
import { Badge, Card, List, Space, Tag, Tooltip } from "antd"; import { Badge, Card, List, Space, Tag } from "antd";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Virtuoso } from "react-virtuoso"; import { Virtuoso } from "react-virtuoso";
@@ -9,7 +9,6 @@ import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import _ from "lodash"; import _ from "lodash";
import { ExclamationCircleOutlined } from "@ant-design/icons";
import "./chat-conversation-list.styles.scss"; import "./chat-conversation-list.styles.scss";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries.js"; import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries.js";
@@ -89,13 +88,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
const cardExtra = ( const cardExtra = (
<> <>
<Badge count={item.messages_aggregate.aggregate.count} /> <Badge count={item.messages_aggregate.aggregate.count} />
{hasOptOutEntry && ( {hasOptOutEntry && <Tag color="red">{t("messaging.labels.no_consent")}</Tag>}
<Tooltip title={t("consent.text_body")}>
<Tag color="red" icon={<ExclamationCircleOutlined />}>
{t("messaging.labels.no_consent")}
</Tag>
</Tooltip>
)}
</> </>
); );

View File

@@ -58,7 +58,6 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
userid userid
created_at created_at
read read
is_system
} }
`, `,
data: message data: message

View File

@@ -13,14 +13,13 @@ import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-document
import JobsDocumentImgproxyGalleryExternal from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component"; import JobsDocumentImgproxyGalleryExternal from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component";
import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component"; import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import "./chat-media-selector.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = (dispatch) => ({}); //setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector); export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, conversation }) { export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, conversation }) {
@@ -38,8 +37,9 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
jobId: conversation.job_conversations[0]?.jobid jobId: conversation.job_conversations[0] && conversation.job_conversations[0]?.jobid
}, },
skip: !open || !conversation.job_conversations || conversation.job_conversations.length === 0 skip: !open || !conversation.job_conversations || conversation.job_conversations.length === 0
}); });
@@ -56,11 +56,11 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
//If Imageproxy is on, rely only on the LMS selector //If Imageproxy is on, rely only on the LMS selector
//If not on, use the old methods. //If not on, use the old methods.
const content = ( const content = (
<div className="media-selector-content"> <div>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
{error && <AlertComponent message={error.message} type="error" />} {error && <AlertComponent message={error.message} type="error" />}
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? ( {selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
<div className="error-message">{t("messaging.labels.maxtenimages")}</div> <div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
) : null} ) : null}
{Imgproxy.treatment === "on" ? ( {Imgproxy.treatment === "on" ? (
@@ -74,7 +74,7 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
{bodyshop.uselocalmediaserver && open && ( {bodyshop.uselocalmediaserver && open && (
<JobDocumentsLocalGalleryExternal <JobDocumentsLocalGalleryExternal
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={conversation.job_conversations[0]?.jobid} jobId={conversation.job_conversations[0] && conversation.job_conversations[0]?.jobid}
/> />
)} )}
</> </>
@@ -89,7 +89,7 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
{bodyshop.uselocalmediaserver && open && ( {bodyshop.uselocalmediaserver && open && (
<JobDocumentsLocalGalleryExternal <JobDocumentsLocalGalleryExternal
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={conversation.job_conversations[0]?.jobid} jobId={conversation.job_conversations[0] && conversation.job_conversations[0]?.jobid}
/> />
)} )}
</> </>
@@ -100,17 +100,12 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
return ( return (
<Popover <Popover
content={ content={
conversation.job_conversations.length === 0 ? ( conversation.job_conversations.length === 0 ? <div>{t("messaging.errors.noattachedjobs")}</div> : content
<div className="no-jobs-message">{t("messaging.errors.noattachedjobs")}</div>
) : (
content
)
} }
title={t("messaging.labels.selectmedia")} title={t("messaging.labels.selectmedia")}
trigger="click" trigger="click"
open={open} open={open}
onOpenChange={handleVisibleChange} onOpenChange={handleVisibleChange}
overlayClassName="media-selector-popover"
> >
<Badge count={selectedMedia.filter((s) => s.isSelected).length}> <Badge count={selectedMedia.filter((s) => s.isSelected).length}>
<PictureFilled style={{ margin: "0 .5rem" }} /> <PictureFilled style={{ margin: "0 .5rem" }} />

View File

@@ -1,52 +0,0 @@
.media-selector-popover {
.ant-popover-inner-content {
max-width: 640px;
max-height: 480px;
overflow-y: auto;
padding: 8px;
background-color: #fff;
border-radius: 8px;
}
}
.media-selector-content {
display: flex;
flex-direction: column;
gap: 4px;
}
.error-message {
color: red;
font-size: 12px;
text-align: center;
margin-bottom: 8px;
}
.no-jobs-message {
font-size: 14px;
color: #888;
text-align: center;
padding: 8px;
}
/* Style images within gallery components */
.media-selector-content img {
object-fit: cover;
border-radius: 4px;
margin: 4px;
cursor: pointer;
transition: transform 0.2s;
&:hover {
transform: scale(1.05);
}
}
/* Grid layout for gallery components */
.media-selector-content .ant-image, /* Assuming gallery components use Ant Design's Image */
.media-selector-content .gallery-container { /* Fallback for custom gallery classes */
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 4px;
}

View File

@@ -4,16 +4,13 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.archive-button { .archive-button {
height: 20px; height: 20px;
border-radius: 4px; border-radius: 4px;
} }
.chat-title { .chat-title {
margin-bottom: 5px; margin-bottom: 5px;
} }
.messages { .messages {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -40,13 +37,11 @@
gap: 8px; gap: 8px;
} }
} }
.chat-send-message-button{
.chat-send-message-button {
margin: 0.3rem; margin: 0.3rem;
padding-left: 0.5rem; padding-left: 0.5rem;
} }
.message-icon { .message-icon {
position: absolute; position: absolute;
bottom: 0.1rem; bottom: 0.1rem;
@@ -130,37 +125,6 @@
} }
} }
.system {
align-items: center;
margin: 0.5rem 10%;
.message {
background-color: #f5f5f5;
border-radius: 10px;
padding: 0.5rem 1rem;
text-align: center;
font-style: italic;
color: #555;
width: fit-content;
max-width: 80%;
}
.system-label {
font-size: 0.75rem;
color: #888;
margin-bottom: 0.2rem;
display: block;
}
.system-date {
font-size: 0.75rem;
color: #888;
margin-top: 0.2rem;
text-align: center;
}
}
.virtuoso-container { .virtuoso-container {
flex: 1; flex: 1;
overflow: auto; overflow: auto;

View File

@@ -2,29 +2,17 @@ import Icon from "@ant-design/icons";
import { Tooltip } from "antd"; import { Tooltip } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import { MdClose, MdDone, MdDoneAll } from "react-icons/md"; import { MdDone, MdDoneAll } from "react-icons/md";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
export const renderMessage = (messages, index) => { export const renderMessage = (messages, index) => {
const message = messages[index]; const message = messages[index];
const isSystem = message.is_system;
// Determine message class
const messageClass = isSystem ? "system messages" : message.isoutbound ? "mine messages" : "yours messages";
// Tooltip content based on message type
const tooltipTitle = isSystem ? (
i18n.t("consent.text_body")
) : (
<DateTimeFormatter>{message.created_at}</DateTimeFormatter>
);
return ( return (
<div key={index} className={messageClass}> <div key={index} className={`${message.isoutbound ? "mine messages" : "yours messages"}`}>
<div className="message msgmargin"> <div className="message msgmargin">
<Tooltip title={tooltipTitle}> <Tooltip title={DateTimeFormatter({ children: message.created_at })}>
<div> <div>
{isSystem && <span className="system-label">System</span>}
{/* Render images if available */} {/* Render images if available */}
{message.image && message.image_path?.length > 0 && ( {message.image && message.image_path?.length > 0 && (
<div className="message-images"> <div className="message-images">
@@ -38,31 +26,20 @@ export const renderMessage = (messages, index) => {
</div> </div>
)} )}
{/* Render text if available */} {/* Render text if available */}
{message.text && <div className="message-text">{message.text}</div>} {message.text && <div>{message.text}</div>}
{/* Render date for system messages */}
{isSystem && (
<div className="system-date">
<DateTimeFormatter>{message.created_at}</DateTimeFormatter>
</div>
)}
</div> </div>
</Tooltip> </Tooltip>
{/* Message status icons for non-system messages */} {/* Message status icons */}
{!isSystem && {message.status && (message.status === "sent" || message.status === "delivered") && (
message.status && <div className="message-status">
(message.status === "sent" || message.status === "delivered" || message.status === "failed") && ( <Icon component={message.status === "sent" ? MdDone : MdDoneAll} className="message-icon" />
<div className="message-status"> </div>
<Icon )}
component={message.status === "sent" ? MdDone : message.status === "delivered" ? MdDoneAll : MdClose}
className="message-icon"
style={message.status === "failed" ? { color: "#ff0000" } : undefined}
/>
</div>
)}
</div> </div>
{/* Outbound message metadata for non-system messages */}
{!isSystem && message.isoutbound && ( {/* Outbound message metadata */}
{message.isoutbound && (
<div style={{ fontSize: 10 }}> <div style={{ fontSize: 10 }}>
{i18n.t("messaging.labels.sentby", { {i18n.t("messaging.labels.sentby", {
by: message.userid, by: message.userid,

View File

@@ -1,6 +1,6 @@
import { ExclamationCircleOutlined, LoadingOutlined, SendOutlined } from "@ant-design/icons"; import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
import { Alert, Input, Space, Spin, Tooltip } from "antd"; import { Alert, Input, Spin } from "antd";
import { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -68,67 +68,48 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
}; };
return ( return (
<Space direction="vertical" style={{ width: "100%" }} size="middle"> <div className="imex-flex-row" style={{ width: "100%" }}>
{isOptedOut && ( {isOptedOut && <Alert message={t("messaging.errors.no_consent")} type="warning" style={{ marginBottom: 8 }} />}
<Tooltip title={t("consent.text_body")}> <ChatPresetsComponent className="imex-flex-row__margin" />
<Alert <ChatMediaSelector
showIcon={true} conversation={conversation}
icon={<ExclamationCircleOutlined />} selectedMedia={selectedMedia}
message={t("messaging.errors.no_consent")} setSelectedMedia={setSelectedMedia}
type="error" />
/> <span style={{ flex: 1 }}>
</Tooltip> <Input.TextArea
)} className="imex-flex-row__margin imex-flex-row__grow"
<div className="imex-flex-row" style={{ width: "100%" }}> allowClear
{!isOptedOut && ( autoFocus
<> ref={inputArea}
<ChatPresetsComponent disabled={isSending} className="imex-flex-row__margin" /> autoSize={{ minRows: 1, maxRows: 4 }}
<ChatMediaSelector value={message}
disabled={isSending} disabled={isSending || isOptedOut}
conversation={conversation} placeholder={t("messaging.labels.typeamessage")}
selectedMedia={selectedMedia} onChange={(e) => setMessage(e.target.value)}
setSelectedMedia={setSelectedMedia} onPressEnter={(event) => {
/> event.preventDefault();
</> if (!event.shiftKey && !isOptedOut) handleEnter();
)} }}
<span style={{ flex: 1 }}>
<Input.TextArea
className="imex-flex-row__margin imex-flex-row__grow"
allowClear
autoFocus
ref={inputArea}
autoSize={{ minRows: 1, maxRows: 4 }}
value={message}
disabled={isSending || isOptedOut}
placeholder={t("messaging.labels.typeamessage")}
onChange={(e) => setMessage(e.target.value)}
onPressEnter={(event) => {
event.preventDefault();
if (!event.shiftKey && !isOptedOut) handleEnter();
}}
/>
</span>
{!isOptedOut && (
<SendOutlined
className="chat-send-message-button"
disabled={isSending || message === "" || !message}
onClick={handleEnter}
/>
)}
<Spin
style={{ display: `${isSending ? "" : "none"}` }}
indicator={
<LoadingOutlined
style={{
fontSize: 24
}}
spin
/>
}
/> />
</div> </span>
</Space> <SendOutlined
className="chat-send-message-button"
disabled={isOptedOut || message === "" || !message}
onClick={handleEnter}
/>
<Spin
style={{ display: `${isSending ? "" : "none"}` }}
indicator={
<LoadingOutlined
style={{
fontSize: 24
}}
spin
/>
}
/>
</div>
); );
} }

View File

@@ -15,7 +15,6 @@ import {
HomeFilled, HomeFilled,
ImportOutlined, ImportOutlined,
LineChartOutlined, LineChartOutlined,
OneToOneOutlined,
PaperClipOutlined, PaperClipOutlined,
PhoneOutlined, PhoneOutlined,
PlusCircleOutlined, PlusCircleOutlined,
@@ -25,7 +24,6 @@ import {
TeamOutlined, TeamOutlined,
ToolFilled, ToolFilled,
UnorderedListOutlined, UnorderedListOutlined,
UsergroupAddOutlined,
UserOutlined UserOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
@@ -42,7 +40,6 @@ import { RiSurveyLine } from "react-icons/ri";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js"; import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors"; import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
@@ -50,10 +47,11 @@ import { signOutStart } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import day from "../../utils/day.js"; import day from "../../utils/day.js";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { useIsEmployee } from "../../utils/useIsEmployee.js";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LockWrapper from "../lock-wrapper/lock-wrapper.component"; import LockWrapper from "../lock-wrapper/lock-wrapper.component";
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx"; import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import { useIsEmployee } from "../../utils/useIsEmployee.js";
// Redux mappings // Redux mappings
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -644,32 +642,17 @@ function Header({
label: t("menus.header.help"), label: t("menus.header.help"),
onClick: () => window.open("https://help.imex.online/", "_blank") onClick: () => window.open("https://help.imex.online/", "_blank")
}, },
{ ...(InstanceRenderManager({ imex: true, rome: false })
key: "remoteassist", ? [
id: "header-remote-assist", {
icon: <OneToOneOutlined />, key: "rescue",
label: t("menus.header.remoteassist"), id: "header-rescue",
children: [ icon: <CarFilled />,
...(InstanceRenderManager({ imex: true, rome: false }) label: t("menus.header.rescueme"),
? [ onClick: () => window.open("https://imexrescue.com/", "_blank")
{ }
key: "rescue", ]
id: "header-rescue", : []),
icon: <PlusCircleOutlined />,
label: t("menus.header.rescueme"),
onClick: () => window.open("https://imexrescue.com/", "_blank")
}
]
: []),
{
key: "rescue-zoho",
id: "header-rescue-zoho",
icon: <UsergroupAddOutlined />,
label: t("menus.header.rescuemezoho"),
onClick: () => window.open("https://join.zoho.com/", "_blank")
}
]
},
{ {
key: "shiftclock", key: "shiftclock",
id: "header-shiftclock", id: "header-shiftclock",

View File

@@ -395,33 +395,32 @@ export function ScheduleEventComponent({
) : ( ) : (
<ScheduleManualEvent event={event} /> <ScheduleManualEvent event={event} />
)} )}
{event.job && {event.isintake && HasFeatureAccess({ featureName: "checklist", bodyshop }) ? (
(HasFeatureAccess({ featureName: "checklist", bodyshop }) ? ( <Link
<Link to={{
to={{ pathname: `/manage/jobs/${event.job && event.job.id}/intake`,
pathname: `/manage/jobs/${event.job && event.job.id}/intake`, search: `?appointmentId=${event.id}`
search: `?appointmentId=${event.id}` }}
}} >
> <Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button>
<Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button> </Link>
</Link> ) : (
) : ( <Popover //open={open}
<Popover //open={open} content={popMenu}
content={popMenu} open={popOverVisible}
open={popOverVisible} onOpenChange={setPopOverVisible}
onOpenChange={setPopOverVisible} onClick={(e) => {
onClick={(e) => { if (event.job?.id) {
if (event.job?.id) { e.stopPropagation();
e.stopPropagation(); getJobDetails();
getJobDetails(); }
} }}
}} getPopupContainer={(trigger) => trigger.parentNode}
getPopupContainer={(trigger) => trigger.parentNode} trigger="click"
trigger="click" >
> <Button disabled={event.arrived}>{t("jobs.actions.intake_quick")}</Button>
<Button disabled={event.arrived}>{t("jobs.actions.intake_quick")}</Button> </Popover>
</Popover> )}
))}
</Space> </Space>
</div> </div>
); );

View File

@@ -1,7 +1,7 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, Switch } from "antd"; import { Button, Card, Form, Input, Switch } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation, useNavigate, useParams } from "react-router-dom"; import { useLocation, useNavigate, useParams } from "react-router-dom";
@@ -9,6 +9,7 @@ import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../../../firebase/firebase.utils"; import { logImEXEvent } from "../../../../firebase/firebase.utils";
import { MARK_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED } from "../../../../graphql/appointments.queries"; import { MARK_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED } from "../../../../graphql/appointments.queries";
import { UPDATE_JOB } from "../../../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
import { insertAuditTrail } from "../../../../redux/application/application.actions"; import { insertAuditTrail } from "../../../../redux/application/application.actions";
import { selectBodyshop, selectCurrentUser } from "../../../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../../../redux/user/user.selectors";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
@@ -31,6 +32,7 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [markAptArrived] = useMutation(MARK_APPOINTMENT_ARRIVED); const [markAptArrived] = useMutation(MARK_APPOINTMENT_ARRIVED);
const [markLatestAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_ARRIVED); const [markLatestAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_ARRIVED);
const [updateOwner] = useMutation(UPDATE_OWNER);
const notification = useNotification(); const notification = useNotification();
const { jobId } = useParams(); const { jobId } = useParams();
@@ -127,6 +129,24 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
} }
} }
if (type === "intake" && job.owner && job.owner.id) {
//Updae Owner Allow to Text
const updateOwnerResult = await updateOwner({
variables: {
ownerId: job.owner.id,
owner: { allow_text_message: values.allow_text_message }
}
});
if (!!updateOwnerResult.errors) {
notification["error"]({
message: t("checklist.errors.complete", {
error: JSON.stringify(result.errors)
})
});
}
}
setLoading(false); setLoading(false);
if (!!!result.errors) { if (!!!result.errors) {
@@ -169,6 +189,7 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
initialValues={{ initialValues={{
...(type === "intake" && { ...(type === "intake" && {
addToProduction: true, addToProduction: true,
allow_text_message: job.owner && job.owner.allow_text_message,
scheduled_completion: scheduled_completion:
(job && job.scheduled_completion && dayjs(job.scheduled_completion)) || (job && job.scheduled_completion && dayjs(job.scheduled_completion)) ||
(job && (job &&
@@ -207,6 +228,14 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
> >
<Switch disabled={readOnly} /> <Switch disabled={readOnly} />
</Form.Item> </Form.Item>
<Form.Item
name="allow_text_message"
valuePropName="checked"
label={t("checklist.labels.allow_text_message")}
disabled={readOnly}
>
<Switch disabled={readOnly} />
</Form.Item>
<Form.Item <Form.Item
name="scheduled_completion" name="scheduled_completion"
label={t("jobs.fields.scheduled_completion")} label={t("jobs.fields.scheduled_completion")}

View File

@@ -80,7 +80,7 @@ export function JobEmployeeAssignments({
); );
return ( return (
<Popover destroyOnHidden content={popContent} open={visibility}> <Popover destroyTooltipOnHide content={popContent} open={visibility}>
<Spin spinning={loading}> <Spin spinning={loading}>
<DataLabel label={t("jobs.fields.employee_body")}> <DataLabel label={t("jobs.fields.employee_body")}>
{body ? ( {body ? (

View File

@@ -1,4 +1,4 @@
import { Form, Input } from "antd"; import { Form, Input, Switch } from "antd";
import React, { useContext } from "react"; import React, { useContext } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
@@ -129,6 +129,13 @@ export default function JobsCreateOwnerInfoNewComponent() {
<Form.Item label={t("owners.fields.preferred_contact")} name={["owner", "data", "preferred_contact"]}> <Form.Item label={t("owners.fields.preferred_contact")} name={["owner", "data", "preferred_contact"]}>
<Input disabled={!state.owner.new} /> <Input disabled={!state.owner.new} />
</Form.Item> </Form.Item>
<Form.Item
label={t("owners.fields.allow_text_message")}
valuePropName="checked"
name={["owner", "data", "allow_text_message"]}
>
<Switch disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow> </LayoutFormRow>
</div> </div>
); );

View File

@@ -72,7 +72,7 @@ export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
open={open} open={open}
placement="left" placement="left"
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
destroyOnHidden destroyTooltipOnHide
> >
<SearchOutlined style={{ cursor: "pointer" }} /> <SearchOutlined style={{ cursor: "pointer" }} />
</Popover> </Popover>

View File

@@ -9,7 +9,6 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { useSocket } from "../../contexts/SocketIO/useSocket.js"; import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries"; import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
@@ -33,6 +32,7 @@ import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production"; import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -1078,26 +1078,17 @@ export function JobsDetailHeaderActions({
menuItems.push({ menuItems.push({
key: "deletejob", key: "deletejob",
id: "job-actions-deletejob", id: "job-actions-deletejob",
label: label: (
job.job_watchers.length === 0 ? ( <Popconfirm
<Popconfirm title={t("jobs.labels.deleteconfirm")}
title={t("jobs.labels.deleteconfirm")} okText={t("general.labels.yes")}
okText={t("general.labels.yes")} cancelText={t("general.labels.no")}
cancelText={t("general.labels.no")} onClick={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()} onConfirm={handleDeleteJob}
onConfirm={handleDeleteJob} >
> {t("menus.jobsactions.deletejob")}
{t("menus.jobsactions.deletejob")} </Popconfirm>
</Popconfirm> )
) : (
<Popconfirm
title={t("jobs.labels.deletewatchers")}
onClick={(e) => e.stopPropagation()}
showCancel={false}
>
{t("menus.jobsactions.deletejob")}
</Popconfirm>
)
}); });
} }
@@ -1118,8 +1109,8 @@ export function JobsDetailHeaderActions({
<RbacWrapper action="jobs:void" noauth> <RbacWrapper action="jobs:void" noauth>
<Popconfirm <Popconfirm
title={t("jobs.labels.voidjob")} title={t("jobs.labels.voidjob")}
okText={t("general.labels.yes")} okText="Yes"
cancelText={t("general.labels.no")} cancelText="No"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onConfirm={handleVoidJob} onConfirm={handleVoidJob}
> >

View File

@@ -167,18 +167,7 @@ export function JobsDetailHeaderActionsToggleProduction({
<FormDateTimePickerComponent disabled={jobRO} /> <FormDateTimePickerComponent disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item name={["actual_delivery"]} label={t("jobs.fields.actual_delivery")}>
name={["actual_delivery"]}
label={t("jobs.fields.actual_delivery")}
rules={[
{
required: bodyshop.deliverchecklist.actual_delivery
? bodyshop.deliverchecklist.actual_delivery
: false
//message: t("general.validation.required"),
}
]}
>
<FormDateTimePickerComponent disabled={jobRO} /> <FormDateTimePickerComponent disabled={jobRO} />
</Form.Item> </Form.Item>
</> </>

View File

@@ -1,20 +1,17 @@
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled } from "@ant-design/icons"; import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Card, Checkbox, Col, Divider, Row, Space, Tag, Tooltip } from "antd"; import { Card, Checkbox, Col, Divider, Row, Space, Tag, Tooltip } from "antd";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useMutation } from "@apollo/client";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions.js";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter, DateTimeFormatterFunction } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
@@ -27,6 +24,7 @@ import ProductionListColumnComment from "../production-list-columns/production-l
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component"; import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import "./jobs-detail-header.styles.scss"; import "./jobs-detail-header.styles.scss";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -40,14 +38,6 @@ const mapDispatchToProps = (dispatch) => ({
context: context, context: context,
modal: "printCenter" modal: "printCenter"
}) })
),
insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(
insertAuditTrail({
jobid,
operation,
type
})
) )
}); });
@@ -59,7 +49,7 @@ const colSpan = {
xl: { span: 6 } xl: { span: 6 }
}; };
export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail }) { export function JobsDetailHeader({ job, bodyshop, disabled }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { notification } = useNotification(); const { notification } = useNotification();
const [notesClamped, setNotesClamped] = useState(true); const [notesClamped, setNotesClamped] = useState(true);
@@ -76,7 +66,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail })
const handleCheckboxChange = async (field, checked) => { const handleCheckboxChange = async (field, checked) => {
const value = checked ? dayjs().toISOString() : null; const value = checked ? dayjs().toISOString() : null;
try { try {
const ret = await updateJob({ await updateJob({
variables: { variables: {
jobId: job.id, jobId: job.id,
job: { [field]: value } job: { [field]: value }
@@ -84,16 +74,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail })
refetchQueries: ["GET_JOB_BY_PK"], refetchQueries: ["GET_JOB_BY_PK"],
awaitRefetchQueries: true awaitRefetchQueries: true
}); });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobfieldchange(
field,
ret.data.update_jobs.returning[0][field]
? DateTimeFormatterFunction(ret.data.update_jobs.returning[0][field])
: checked
),
type: "jobfieldchange"
});
} catch (error) { } catch (error) {
notification.error({ notification.error({
message: t("jobs.errors.saving", { error: error.message }) message: t("jobs.errors.saving", { error: error.message })

View File

@@ -1,15 +1,14 @@
import { Form, Input, Tooltip } from "antd"; import { Form, Input, Switch } from "antd";
import { CloseCircleFilled } from "@ant-design/icons"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedOut, isPhone2OptedOut }) { export default function OwnerDetailFormComponent({ form, loading }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { getFieldValue } = form; const { getFieldValue } = form;
return ( return (
<div> <div>
<FormFieldsChanged form={form} /> <FormFieldsChanged form={form} />
@@ -27,7 +26,7 @@ export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedO
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.accountingid")} name="accountingid"> <Form.Item label={t("owners.fields.accountingid")} name="accountingid">
<Input disabled /> <Input disabled/>
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("owners.forms.address")}> <LayoutFormRow header={t("owners.forms.address")}>
@@ -51,6 +50,9 @@ export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedO
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("owners.forms.contact")}> <LayoutFormRow header={t("owners.forms.contact")}>
<Form.Item label={t("owners.fields.allow_text_message")} name="allow_text_message" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item <Form.Item
label={t("owners.fields.ownr_ea")} label={t("owners.fields.ownr_ea")}
name="ownr_ea" name="ownr_ea"
@@ -63,55 +65,19 @@ export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedO
> >
<FormItemEmail email={getFieldValue("ownr_ea")} /> <FormItemEmail email={getFieldValue("ownr_ea")} />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_ph1")} style={{ marginBottom: 0 }}> <Form.Item
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}> label={t("owners.fields.ownr_ph1")}
<Form.Item name="ownr_ph1"
name="ownr_ph1" rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph1")]}
noStyle >
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph1")]} <FormItemPhone />
>
<Input style={{ flex: 1, minWidth: "150px" }} />
</Form.Item>
{isPhone1OptedOut && (
<Tooltip title={t("consent.text_body")}>
<CloseCircleFilled
style={{
color: "#ff4d4f",
fontSize: 16,
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%"
}}
/>
</Tooltip>
)}
</div>
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_ph2")} style={{ marginBottom: 0 }}> <Form.Item
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}> label={t("owners.fields.ownr_ph2")}
<Form.Item name="ownr_ph2"
name="ownr_ph2" rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph2")]}
noStyle >
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph2")]} <FormItemPhone />
>
<Input style={{ flex: 1, minWidth: "150px" }} />
</Form.Item>
{isPhone2OptedOut && (
<Tooltip title={t("consent.text_body")}>
<CloseCircleFilled
style={{
color: "#ff4d4f",
fontSize: 16,
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%"
}}
/>
</Tooltip>
)}
</div>
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.preferred_contact")} name="preferred_contact"> <Form.Item label={t("owners.fields.preferred_contact")} name="preferred_contact">
<Input /> <Input />

View File

@@ -1,113 +1,69 @@
import { Button, Form, Popconfirm } from "antd"; import { Button, Form, Popconfirm } from "antd";
import { PageHeader } from "@ant-design/pro-layout"; import { PageHeader } from "@ant-design/pro-layout";
import { useEffect, useState } from "react";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useApolloClient, useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { DELETE_OWNER, UPDATE_OWNER } from "../../graphql/owners.queries"; import { DELETE_OWNER, UPDATE_OWNER } from "../../graphql/owners.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; // Adjust path
import { phoneNumberOptOutService } from "../../utils/phoneOptOutService.js"; // Adjust path
import OwnerDetailFormComponent from "./owner-detail-form.component"; import OwnerDetailFormComponent from "./owner-detail-form.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { phone } from "phone"; // Import phone utility for formatting
// Connect to Redux to access bodyshop function OwnerDetailFormContainer({ owner, refetch }) {
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
function OwnerDetailFormContainer({ owner, refetch, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const navigate = useNavigate(); const history = useNavigate();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [optedOutPhones, setOptedOutPhones] = useState(new Set());
const [updateOwner] = useMutation(UPDATE_OWNER); const [updateOwner] = useMutation(UPDATE_OWNER);
const [deleteOwner] = useMutation(DELETE_OWNER); const [deleteOwner] = useMutation(DELETE_OWNER);
const notification = useNotification(); const notification = useNotification();
const apolloClient = useApolloClient();
// Fetch opt-out status on mount
useEffect(() => {
const fetchOptOutStatus = async () => {
if (bodyshop?.id && (owner?.ownr_ph1 || owner?.ownr_ph2)) {
const phoneNumbers = [owner.ownr_ph1, owner.ownr_ph2].filter(Boolean);
const optOutSet = await phoneNumberOptOutService(apolloClient, bodyshop.id, phoneNumbers);
setOptedOutPhones(optOutSet);
}
};
fetchOptOutStatus();
}, [apolloClient, bodyshop?.id, owner?.ownr_ph1, owner?.ownr_ph2]);
// Reset form fields when owner changes
useEffect(() => {
form.setFieldsValue({
ownr_ph1: owner?.ownr_ph1,
ownr_ph2: owner?.ownr_ph2,
...owner
});
}, [owner, form]);
const handleDelete = async () => { const handleDelete = async () => {
setLoading(true); setLoading(true);
try { const result = await deleteOwner({
const result = await deleteOwner({ variables: { id: owner.id }
variables: { id: owner.id } });
}); console.log(result);
if (result.errors) { if (result.errors) {
notification.error({ notification["error"]({
message: t("owners.errors.deleting", {
error: JSON.stringify(result.errors)
})
});
} else {
notification.success({
message: t("owners.successes.delete")
});
navigate(`/manage/owners`);
}
} catch (error) {
notification.error({
message: t("owners.errors.deleting", { message: t("owners.errors.deleting", {
error: error.message error: JSON.stringify(result.errors)
}) })
}); });
} finally {
setLoading(false); setLoading(false);
} else {
notification["success"]({
message: t("owners.successes.delete")
});
setLoading(false);
history(`/manage/owners`);
} }
}; };
const handleFinish = async (values) => { const handleFinish = async (values) => {
setLoading(true); setLoading(true);
try { const result = await updateOwner({
const result = await updateOwner({ variables: { ownerId: owner.id, owner: values }
variables: { ownerId: owner.id, owner: values } });
});
if (result.errors) { if (!!result.errors) {
notification.error({ notification["error"]({
message: t("owners.errors.saving", {
error: JSON.stringify(result.errors)
})
});
} else {
notification.success({
message: t("owners.successes.save")
});
if (refetch) await refetch();
form.resetFields();
}
} catch (error) {
notification.error({
message: t("owners.errors.saving", { message: t("owners.errors.saving", {
error: error.message error: JSON.stringify(result.errors)
}) })
}); });
} finally {
setLoading(false); setLoading(false);
return;
} }
notification["success"]({
message: t("owners.successes.save")
});
if (refetch) await refetch();
form.resetFields();
form.resetFields();
setLoading(false);
}; };
return ( return (
@@ -116,7 +72,6 @@ function OwnerDetailFormContainer({ owner, refetch, bodyshop }) {
title={t("menus.header.owners")} title={t("menus.header.owners")}
extra={[ extra={[
<Popconfirm <Popconfirm
key="delete"
trigger="click" trigger="click"
onConfirm={handleDelete} onConfirm={handleDelete}
disabled={owner.jobs.length !== 0} disabled={owner.jobs.length !== 0}
@@ -126,25 +81,16 @@ function OwnerDetailFormContainer({ owner, refetch, bodyshop }) {
{t("general.actions.delete")} {t("general.actions.delete")}
</Button> </Button>
</Popconfirm>, </Popconfirm>,
<Button key="save" type="primary" loading={loading} onClick={() => form.submit()}> <Button type="primary" loading={loading} onClick={() => form.submit()}>
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>
]} ]}
/> />
<Form form={form} onFinish={handleFinish} autoComplete="off" layout="vertical" initialValues={owner}> <Form form={form} onFinish={handleFinish} autoComplete="off" layout="vertical" initialValues={owner}>
<OwnerDetailFormComponent <OwnerDetailFormComponent loading={loading} form={form} />
loading={loading}
form={form}
isPhone1OptedOut={
owner?.ownr_ph1 && optedOutPhones.has(phone(owner.ownr_ph1, "CA").phoneNumber?.replace(/^\+1/, ""))
}
isPhone2OptedOut={
owner?.ownr_ph2 && optedOutPhones.has(phone(owner.ownr_ph2, "CA").phoneNumber?.replace(/^\+1/, ""))
}
/>
</Form> </Form>
</> </>
); );
} }
export default connect(mapStateToProps)(OwnerDetailFormContainer); export default OwnerDetailFormContainer;

View File

@@ -75,7 +75,7 @@ export function PartsOrderBackorderEta({
); );
return ( return (
<Popover destroyOnHidden content={popContent} open={visibility} disabled={disabled}> <Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
<DateFormatter>{backordered_eta}</DateFormatter> <DateFormatter>{backordered_eta}</DateFormatter>
{isAlreadyBackordered && <CalendarFilled style={{ cursor: "pointer" }} onClick={handlePopover} />} {isAlreadyBackordered && <CalendarFilled style={{ cursor: "pointer" }} onClick={handlePopover} />}
{loading && <Spin />} {loading && <Spin />}

View File

@@ -84,7 +84,7 @@ export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, j
); );
return ( return (
<Popover destroyOnHidden content={popContent} open={visibility} disabled={disabled}> <Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
<Button loading={loading} onClick={handlePopover}> <Button loading={loading} onClick={handlePopover}>
{isAlreadyBackordered ? t("parts_orders.actions.receive") : t("parts_orders.actions.backordered")} {isAlreadyBackordered ? t("parts_orders.actions.receive") : t("parts_orders.actions.backordered")}
</Button> </Button>

View File

@@ -1,20 +1,14 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Input, Table, Typography } from "antd"; import { Input, Table } from "antd";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries"; import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { TimeAgoFormatter } from "../../utils/DateFormatter"; import { TimeAgoFormatter } from "../../utils/DateFormatter";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import { useTranslation } from "react-i18next";
import { useState } from "react";
const { Paragraph } = Typography;
// Commented out Associated Owners section for now
//import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
//import { Link } from "react-router-dom";
//import { useMemo, useState } from "react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -26,95 +20,18 @@ const mapDispatchToProps = () => ({});
function PhoneNumberConsentList({ bodyshop, currentUser }) { function PhoneNumberConsentList({ bodyshop, currentUser }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const { loading, data } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
// Fetch opt-out phone numbers
const { loading: optOutLoading, data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined }, variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined },
fetchPolicy: "network-only" fetchPolicy: "network-only"
}); });
// Commented out Associated Owners section for now
/*// Prepare phone numbers for owner query
const phoneNumbers = useMemo(() => {
return optOutData?.phone_number_opt_out?.map((item) => item.phone_number) || [];
}, [optOutData?.phone_number_opt_out]);
const allPhoneNumbers = useMemo(() => {
const normalized = phoneNumbers;
const withPlusOne = phoneNumbers.map((num) => `+1${num}`);
return [...normalized, ...withPlusOne].filter(Boolean);
}, [phoneNumbers]);
// Fetch owners for all phone numbers
const { loading: ownersLoading, data: ownersData } = useQuery(SEARCH_OWNERS_BY_PHONE_NUMBERS, {
variables: { bodyshopid: bodyshop.id, phone_numbers: allPhoneNumbers },
skip: allPhoneNumbers.length === 0 || !bodyshop.id,
fetchPolicy: "network-only"
});
// Map phone numbers to their associated owners and identify phone field
const getAssociatedOwners = (phoneNumber) => {
if (!ownersData?.owners) return [];
const normalizedPhone = phoneNumber.replace(/^\+1/, "");
return ownersData.owners
.filter(
(owner) =>
owner.ownr_ph1 === phoneNumber ||
owner.ownr_ph2 === phoneNumber ||
owner.ownr_ph1 === normalizedPhone ||
owner.ownr_ph2 === normalizedPhone ||
owner.ownr_ph1 === `+1${phoneNumber}` ||
owner.ownr_ph2 === `+1${phoneNumber}`
)
.map((owner) => ({
...owner,
phoneField:
[owner.ownr_ph1, owner.ownr_ph2].includes(phoneNumber) ||
[owner.ownr_ph1, owner.ownr_ph2].includes(normalizedPhone) ||
[owner.ownr_ph1, owner.ownr_ph2].includes(`+1${phoneNumber}`)
? owner.ownr_ph1 === phoneNumber ||
owner.ownr_ph1 === normalizedPhone ||
owner.ownr_ph1 === `+1${phoneNumber}`
? t("consent.phone_1")
: t("consent.phone_2")
: null
}));
};*/
const columns = [ const columns = [
{ {
title: t("consent.phone_number"), title: t("consent.phone_number"),
dataIndex: "phone_number", dataIndex: "phone_number",
render: (text) => <ChatOpenButton phone={text} />, render: (text) => <PhoneNumberFormatter>{text}</PhoneNumberFormatter>,
sorter: (a, b) => a.phone_number.localeCompare(b.phone_number) sorter: (a, b) => a.phone_number.localeCompare(b.phone_number)
}, },
// Commented out Associated Owners section for now
/*{
title: t("consent.associated_owners"),
dataIndex: "phone_number",
render: (phoneNumber) => {
const owners = getAssociatedOwners(phoneNumber);
if (!owners || owners.length === 0) {
return t("consent.no_owners");
}
return owners.map((owner) => (
<div key={owner.id}>
<Space direction="horizontal">
<Link to={"/manage/owners/" + owner.id}>
<OwnerNameDisplay ownerObject={owner} />
</Link>
({owner.phoneField})
</Space>
</div>
));
},
sorter: (a, b) => {
const aOwners = getAssociatedOwners(a.phone_number);
const bOwners = getAssociatedOwners(b.phone_number);
const aName = aOwners[0] ? `${aOwners[0].ownr_fn} ${aOwners[0].ownr_ln}` : "";
const bName = bOwners[0] ? `${bOwners[0].ownr_fn} ${bOwners[0].ownr_ln}` : "";
return aName.localeCompare(bName);
}
},*/
{ {
title: t("consent.created_at"), title: t("consent.created_at"),
dataIndex: "created_at", dataIndex: "created_at",
@@ -125,7 +42,6 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
return ( return (
<div> <div>
<Paragraph>{t("consent.text_body")}</Paragraph>
<Input.Search <Input.Search
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}
onSearch={(value) => setSearch(value)} onSearch={(value) => setSearch(value)}
@@ -134,8 +50,8 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
<Table <Table
columns={columns} columns={columns}
dataSource={optOutData?.phone_number_opt_out} dataSource={data?.phone_number_opt_out}
loading={optOutLoading /* || ownersLoading*/} loading={loading}
rowKey="id" rowKey="id"
style={{ marginTop: 16 }} style={{ marginTop: 16 }}
/> />

View File

@@ -20,7 +20,7 @@ const Board = ({ id, className, orientation, cardSettings, ...additionalProps })
default: default:
return cardSizesVertical.small; return cardSizesVertical.small;
} }
}, [cardSettings?.cardSize]); }, [cardSettings]);
return ( return (
<> <>

View File

@@ -101,33 +101,11 @@ const BoardContainer = ({
async ({ draggableId, type, source, reason, mode, destination, combine }) => { async ({ draggableId, type, source, reason, mode, destination, combine }) => {
setIsDragging(false); setIsDragging(false);
// Validate drag type and source // Only update drag time if it's a valid drop with a different destination
if (type !== "lane" || !source) { if (type === "lane" && source && destination && !isEqual(source, destination)) {
// Invalid drag type or missing source, attempt to revert if possible setDragTime(source.droppableId);
if (source) { setIsProcessing(true);
dispatch(
actions.moveCardAcrossLanes({
fromLaneId: source.droppableId,
toLaneId: source.droppableId,
cardId: draggableId,
index: source.index
})
);
}
setIsProcessing(false);
try {
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
} catch (err) {
console.error("Error in onLaneDrag for invalid drag type or source", err);
}
return;
}
setDragTime(source.droppableId);
setIsProcessing(true);
// Handle valid drop to a different lane or position
if (destination && !isEqual(source, destination)) {
dispatch( dispatch(
actions.moveCardAcrossLanes({ actions.moveCardAcrossLanes({
fromLaneId: source.droppableId, fromLaneId: source.droppableId,
@@ -136,33 +114,14 @@ const BoardContainer = ({
index: destination.index index: destination.index
}) })
); );
} else {
// Same-lane drop or no destination, revert to original position
dispatch(
actions.moveCardAcrossLanes({
fromLaneId: source.droppableId,
toLaneId: source.droppableId,
cardId: draggableId,
index: source.index
})
);
}
try { try {
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine }); await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
} catch (err) { } catch (err) {
console.error("Error in onLaneDrag", err); console.error("Error in onLaneDrag", err);
// Ensure revert on error } finally {
dispatch( setIsProcessing(false);
actions.moveCardAcrossLanes({ }
fromLaneId: source.droppableId,
toLaneId: source.droppableId,
cardId: draggableId,
index: source.index
})
);
} finally {
setIsProcessing(false);
} }
}, },
[dispatch, onDragEnd, setDragTime] [dispatch, onDragEnd, setDragTime]

View File

@@ -133,9 +133,7 @@ const Lane = ({
Item: ItemComponent Item: ItemComponent
}, },
itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>, itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>,
overscan: { main: 10, reverse: 10 }, overscan: { main: 10, reverse: 10 }
// Ensure a minimum height for empty lanes to allow dropping
style: renderedCards.length === 0 ? { minHeight: "5px" } : {}
}; };
const horizontalProps = { const horizontalProps = {
@@ -151,6 +149,8 @@ const Lane = ({
const componentProps = orientation === "vertical" ? verticalProps : horizontalProps; const componentProps = orientation === "vertical" ? verticalProps : horizontalProps;
// If the lane is collapsed, we want to render a div instead of the virtualized list, and we want to set the height to the max height of the lane so that
// the lane doesn't shrink when collapsed (in horizontal mode)
const finalComponentProps = collapsed const finalComponentProps = collapsed
? orientation === "horizontal" ? orientation === "horizontal"
? { ? {
@@ -161,8 +161,9 @@ const Lane = ({
: {} : {}
: componentProps; : componentProps;
// Always render placeholder for empty lanes in vertical mode to ensure droppable area // If the lane is horizontal and collapsed, we want to render a placeholder so that the lane doesn't shrink to 0 height and grows when
const shouldRenderPlaceholder = orientation === "vertical" ? collapsed || renderedCards.length === 0 : collapsed; // a card is dragged over it
const shouldRenderPlaceholder = orientation !== "horizontal" && (collapsed || renderedCards.length === 0);
return ( return (
<HeightMemoryWrapper <HeightMemoryWrapper
@@ -177,8 +178,8 @@ const Lane = ({
override={orientation !== "horizontal" && (collapsed || !renderedCards.length)} override={orientation !== "horizontal" && (collapsed || !renderedCards.length)}
> >
<div <div
ref={laneRef} ref={laneRef} // Ensure laneRef is set here
style={{ height: "100%", width: "100%" }} style={{ height: "100%", width: "100%" }} // Make it scrollable
className={`react-trello-lane ${collapsed ? "lane-collapsed" : ""}`} className={`react-trello-lane ${collapsed ? "lane-collapsed" : ""}`}
> >
<div {...provided.droppableProps} ref={provided.innerRef} style={{ ...provided.droppableProps.style }}> <div {...provided.droppableProps} ref={provided.innerRef} style={{ ...provided.droppableProps.style }}>

View File

@@ -140,7 +140,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
if (record[type]) theEmployee = bodyshop.employees.find((e) => e.id === record[type]); if (record[type]) theEmployee = bodyshop.employees.find((e) => e.id === record[type]);
return ( return (
<Popover destroyOnHidden content={popContent} open={visibility}> <Popover destroyTooltipOnHide content={popContent} open={visibility}>
<Spin spinning={loading}> <Spin spinning={loading}>
{record[type] ? ( {record[type] ? (
<div> <div>

View File

@@ -107,7 +107,7 @@ export default function TimeTicketCalculatorComponent({
open={visible} open={visible}
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
placement="right" placement="right"
destroyOnHidden destroyTooltipOnHide
> >
<Button onClick={(e) => e.preventDefault()}> <Button onClick={(e) => e.preventDefault()}>
<Space> <Space>

View File

@@ -312,6 +312,7 @@ export const QUERY_INTAKE_CHECKLIST = gql`
intakechecklist intakechecklist
status status
owner { owner {
allow_text_message
id id
} }
labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) { labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) {

View File

@@ -43,7 +43,6 @@ export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
id id
status status
text text
is_system
isoutbound isoutbound
image image
image_path image_path
@@ -78,7 +77,6 @@ export const GET_CONVERSATION_DETAILS = gql`
id id
status status
text text
is_system
isoutbound isoutbound
image image
image_path image_path

View File

@@ -874,6 +874,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
} }
owner { owner {
id id
allow_text_message
preferred_contact preferred_contact
tax_number tax_number
} }
@@ -2070,6 +2071,7 @@ export const QUERY_JOB_CHECKLISTS = gql`
production_vars production_vars
owner { owner {
id id
allow_text_message
} }
bodyshop { bodyshop {
id id
@@ -2426,6 +2428,7 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
ownr_ph2 ownr_ph2
owner { owner {
id id
allow_text_message
preferred_contact preferred_contact
tax_number tax_number
} }

View File

@@ -49,6 +49,7 @@ export const QUERY_OWNER_BY_ID = gql`
owners_by_pk(id: $id) { owners_by_pk(id: $id) {
id id
accountingid accountingid
allow_text_message
ownr_addr1 ownr_addr1
ownr_addr2 ownr_addr2
ownr_co_nm ownr_co_nm
@@ -103,6 +104,7 @@ export const QUERY_ALL_OWNERS = gql`
query QUERY_ALL_OWNERS { query QUERY_ALL_OWNERS {
owners { owners {
id id
allow_text_message
created_at created_at
ownr_addr1 ownr_addr1
ownr_addr2 ownr_addr2
@@ -127,6 +129,7 @@ export const QUERY_ALL_OWNERS_PAGINATED = gql`
query QUERY_ALL_OWNERS_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [owners_order_by!]!) { query QUERY_ALL_OWNERS_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [owners_order_by!]!) {
search_owners(args: { search: $search }, offset: $offset, limit: $limit, order_by: $order) { search_owners(args: { search: $search }, offset: $offset, limit: $limit, order_by: $order) {
id id
allow_text_message
created_at created_at
ownr_addr1 ownr_addr1
ownr_addr2 ownr_addr2

View File

@@ -26,39 +26,3 @@ export const GET_PHONE_NUMBER_OPT_OUTS = gql`
} }
} }
`; `;
export const GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS = gql`
query GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS($bodyshopid: uuid!, $phone_numbers: [String!]) {
phone_number_opt_out(
where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _in: $phone_numbers } }
) {
id
bodyshopid
phone_number
created_at
updated_at
}
}
`;
export const SEARCH_OWNERS_BY_PHONE_NUMBERS = gql`
query SEARCH_OWNERS_BY_PHONE_NUMBERS($bodyshopid: uuid!, $phone_numbers: [String!]) {
owners(
where: {
shopid: { _eq: $bodyshopid },
_or: [
{ ownr_ph1: { _in: $phone_numbers } },
{ ownr_ph2: { _in: $phone_numbers } }
]
}
) {
id
ownr_fn
ownr_ln
ownr_co_nm
ownr_ph1
ownr_ph2
__typename
}
}
`;

View File

@@ -114,6 +114,7 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
if (!!!job.ownerid) { if (!!!job.ownerid) {
ownerData = job.owner.data; ownerData = job.owner.data;
ownerData.shopid = bodyshop.id; ownerData.shopid = bodyshop.id;
delete ownerData.allow_text_message;
delete ownerData.preferred_contact; delete ownerData.preferred_contact;
delete job.ownerid; delete job.ownerid;
} else { } else {

View File

@@ -92,15 +92,13 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
}); });
} }
if (bodyshop.messagingservicesid) { // Add Consent Settings tab
// Add Consent Settings tab items.push({
items.push({ key: "consent",
key: "consent", label: t("bodyshop.labels.consent_settings"),
label: t("bodyshop.labels.consent_settings"), children: <ShopInfoConsentComponent bodyshop={bodyshop} />
children: <ShopInfoConsentComponent bodyshop={bodyshop} /> });
});
}
return ( return (
<RbacWrapper action="shop:config"> <RbacWrapper action="shop:config">
<Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} /> <Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} />

View File

@@ -335,12 +335,20 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
} }
try { try {
window.$crisp.push(["set", "user:company", [payload.shopname]]); InstanceRenderManager({
window.$crisp.push(["set", "session:segments", [[`region:${payload.region_config}`]]]); executeFunction: true,
if (authRecord[0] && authRecord[0].user.validemail) { args: [],
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]); imex: () => {
} window.$crisp.push(["set", "user:company", [payload.shopname]]);
window.$crisp.push(["set", "session:segments", [[`region:${payload.region_config}`]]]);
if (authRecord[0] && authRecord[0].user.validemail) {
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
}
},
rome: () => {
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
}
});
payload.features?.allAccess === true payload.features?.allAccess === true
? window.$crisp.push(["set", "session:segments", [["allAccess"]]]) ? window.$crisp.push(["set", "session:segments", [["allAccess"]]])
: (() => { : (() => {
@@ -351,14 +359,6 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
); );
window.$crisp.push(["set", "session:segments", [["basic", ...featureKeys]]]); window.$crisp.push(["set", "session:segments", [["basic", ...featureKeys]]]);
})(); })();
InstanceRenderManager({
executeFunction: true,
args: [],
rome: () => {
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
}
});
} catch (error) { } catch (error) {
console.warn("Couldnt find $crisp.", error.message); console.warn("Couldnt find $crisp.", error.message);
} }

View File

@@ -775,6 +775,7 @@
}, },
"labels": { "labels": {
"addtoproduction": "Add Job to Production?", "addtoproduction": "Add Job to Production?",
"allow_text_message": "Permission to Text?",
"checklist": "Checklist", "checklist": "Checklist",
"printpack": "Job Intake Print Pack", "printpack": "Job Intake Print Pack",
"removefromproduction": "Remove Job from Production?" "removefromproduction": "Remove Job from Production?"
@@ -2031,10 +2032,9 @@
"stands": "Stands", "stands": "Stands",
"waived": "Waived" "waived": "Waived"
}, },
"deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone.", "deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone. ",
"deletedelivery": "Delete Delivery Checklist", "deletedelivery": "Delete Delivery Checklist",
"deleteintake": "Delete Intake Checklist", "deleteintake": "Delete Intake Checklist",
"deletewatchers": "Remove Watchers before deleting this Job.",
"deliverchecklist": "Deliver Checklist", "deliverchecklist": "Deliver Checklist",
"difference": "Difference", "difference": "Difference",
"diskscan": "Scan Disk for Estimates", "diskscan": "Scan Disk for Estimates",
@@ -2302,10 +2302,8 @@
"productionlist": "Production Board - List", "productionlist": "Production Board - List",
"readyjobs": "Ready Jobs", "readyjobs": "Ready Jobs",
"recent": "Recent Items", "recent": "Recent Items",
"remoteassist": "Remote Assist",
"reportcenter": "Report Center", "reportcenter": "Report Center",
"rescueme": "Rescue Me!", "rescueme": "Rescue me!",
"rescuemezoho": "Remote Me In!",
"schedule": "Schedule", "schedule": "Schedule",
"scoreboard": "Scoreboard", "scoreboard": "Scoreboard",
"search": { "search": {
@@ -2381,7 +2379,7 @@
"invalidphone": "The phone number is invalid. Unable to open conversation. ", "invalidphone": "The phone number is invalid. Unable to open conversation. ",
"noattachedjobs": "No Jobs have been associated to this conversation. ", "noattachedjobs": "No Jobs have been associated to this conversation. ",
"updatinglabel": "Error updating label. {{error}}", "updatinglabel": "Error updating label. {{error}}",
"no_consent": "This phone number has opted-out of Messaging." "no_consent": "This phone number has not consented to receive messages."
}, },
"labels": { "labels": {
"addlabel": "Add a label to this conversation.", "addlabel": "Add a label to this conversation.",
@@ -2398,7 +2396,7 @@
"sentby": "Sent by {{by}} at {{time}}", "sentby": "Sent by {{by}} at {{time}}",
"typeamessage": "Send a message...", "typeamessage": "Send a message...",
"unarchive": "Unarchive", "unarchive": "Unarchive",
"no_consent": "Opted-out" "no_consent": "No Consent"
}, },
"render": { "render": {
"conversation_list": "Conversation List" "conversation_list": "Conversation List"
@@ -2523,6 +2521,7 @@
"fields": { "fields": {
"accountingid": "Accounting ID", "accountingid": "Accounting ID",
"address": "Address", "address": "Address",
"allow_text_message": "Permission to Text?",
"name": "Name", "name": "Name",
"note": "Owner Note", "note": "Owner Note",
"ownr_addr1": "Address", "ownr_addr1": "Address",
@@ -3870,12 +3869,8 @@
}, },
"consent": { "consent": {
"phone_number": "Phone Number", "phone_number": "Phone Number",
"associated_owners": "Associated Owners", "status": "Consent Status",
"created_at": "Opt-Out Date", "created_at": "Created At"
"no_owners": "No Associated Owners",
"phone_1": "Phone 1",
"phone_2": "Phone 2",
"text_body": "Users can opt out of receiving SMS messages by replying with keywords such as STOP, UNSUBSCRIBE, CANCEL, END, QUIT, STOPALL, REVOKE and OPTOUT. To opt back in, users can reply with START, YES, or UNSTOP. Even after opting out, users can still send messages to us, which will be received and processed as needed. Ensure customers are informed to reply with these keywords to manage their messaging preferences. After opting out, users receive a confirmation message and will not receive further messages until they opt back in."
}, },
"settings": { "settings": {
"title": "Phone Number Opt-Out List" "title": "Phone Number Opt-Out List"

View File

@@ -656,7 +656,6 @@
} }
}, },
"labels": { "labels": {
"consent_settings": "",
"2tiername": "", "2tiername": "",
"2tiersetup": "", "2tiersetup": "",
"2tiersource": "", "2tiersource": "",
@@ -775,6 +774,7 @@
}, },
"labels": { "labels": {
"addtoproduction": "", "addtoproduction": "",
"allow_text_message": "",
"checklist": "", "checklist": "",
"printpack": "", "printpack": "",
"removefromproduction": "" "removefromproduction": ""
@@ -2034,7 +2034,6 @@
"deleteconfirm": "", "deleteconfirm": "",
"deletedelivery": "", "deletedelivery": "",
"deleteintake": "", "deleteintake": "",
"deletewatchers": "",
"deliverchecklist": "", "deliverchecklist": "",
"difference": "", "difference": "",
"diskscan": "", "diskscan": "",
@@ -2302,10 +2301,8 @@
"productionlist": "", "productionlist": "",
"readyjobs": "", "readyjobs": "",
"recent": "", "recent": "",
"remoteassist": "",
"reportcenter": "", "reportcenter": "",
"rescueme": "", "rescueme": "",
"rescuemezoho": "",
"schedule": "Programar", "schedule": "Programar",
"scoreboard": "", "scoreboard": "",
"search": { "search": {
@@ -2380,8 +2377,7 @@
"errors": { "errors": {
"invalidphone": "", "invalidphone": "",
"noattachedjobs": "", "noattachedjobs": "",
"updatinglabel": "", "updatinglabel": ""
"no_consent": ""
}, },
"labels": { "labels": {
"addlabel": "", "addlabel": "",
@@ -2397,8 +2393,7 @@
"selectmedia": "", "selectmedia": "",
"sentby": "", "sentby": "",
"typeamessage": "Enviar un mensaje...", "typeamessage": "Enviar un mensaje...",
"unarchive": "", "unarchive": ""
"no_consent": ""
}, },
"render": { "render": {
"conversation_list": "" "conversation_list": ""
@@ -2503,8 +2498,7 @@
}, },
"tooltips": { "tooltips": {
"job-watchers": "", "job-watchers": "",
"not-employee": "", "not-employee": ""
"not-employee-notifications": ""
} }
}, },
"owner": { "owner": {
@@ -2525,6 +2519,7 @@
"fields": { "fields": {
"accountingid": "", "accountingid": "",
"address": "Dirección", "address": "Dirección",
"allow_text_message": "Permiso de texto?",
"name": "Nombre", "name": "Nombre",
"note": "", "note": "",
"ownr_addr1": "Dirección", "ownr_addr1": "Dirección",
@@ -3869,18 +3864,6 @@
"validation": { "validation": {
"unique_vendor_name": "" "unique_vendor_name": ""
} }
},
"consent": {
"phone_number": "",
"associated_owners": "",
"created_at": "",
"no_owners": "",
"phone_1": "",
"phone_2": "",
"text_body": ""
},
"settings": {
"title": ""
} }
} }
} }

View File

@@ -656,7 +656,6 @@
} }
}, },
"labels": { "labels": {
"consent_settings": "",
"2tiername": "", "2tiername": "",
"2tiersetup": "", "2tiersetup": "",
"2tiersource": "", "2tiersource": "",
@@ -775,6 +774,7 @@
}, },
"labels": { "labels": {
"addtoproduction": "", "addtoproduction": "",
"allow_text_message": "",
"checklist": "", "checklist": "",
"printpack": "", "printpack": "",
"removefromproduction": "" "removefromproduction": ""
@@ -2034,7 +2034,6 @@
"deleteconfirm": "", "deleteconfirm": "",
"deletedelivery": "", "deletedelivery": "",
"deleteintake": "", "deleteintake": "",
"deletewatchers": "",
"deliverchecklist": "", "deliverchecklist": "",
"difference": "", "difference": "",
"diskscan": "", "diskscan": "",
@@ -2302,10 +2301,8 @@
"productionlist": "", "productionlist": "",
"readyjobs": "", "readyjobs": "",
"recent": "", "recent": "",
"remoteassist": "",
"reportcenter": "", "reportcenter": "",
"rescueme": "", "rescueme": "",
"rescuemezoho": "",
"schedule": "Programme", "schedule": "Programme",
"scoreboard": "", "scoreboard": "",
"search": { "search": {
@@ -2380,8 +2377,7 @@
"errors": { "errors": {
"invalidphone": "", "invalidphone": "",
"noattachedjobs": "", "noattachedjobs": "",
"updatinglabel": "", "updatinglabel": ""
"no_consent": ""
}, },
"labels": { "labels": {
"addlabel": "", "addlabel": "",
@@ -2397,8 +2393,7 @@
"selectmedia": "", "selectmedia": "",
"sentby": "", "sentby": "",
"typeamessage": "Envoyer un message...", "typeamessage": "Envoyer un message...",
"unarchive": "", "unarchive": ""
"no_consent": ""
}, },
"render": { "render": {
"conversation_list": "" "conversation_list": ""
@@ -2503,8 +2498,7 @@
}, },
"tooltips": { "tooltips": {
"job-watchers": "", "job-watchers": "",
"not-employee": "", "not-employee": ""
"not-employee-notifications": ""
} }
}, },
"owner": { "owner": {
@@ -2525,6 +2519,7 @@
"fields": { "fields": {
"accountingid": "", "accountingid": "",
"address": "Adresse", "address": "Adresse",
"allow_text_message": "Autorisation de texte?",
"name": "Prénom", "name": "Prénom",
"note": "", "note": "",
"ownr_addr1": "Adresse", "ownr_addr1": "Adresse",
@@ -3869,18 +3864,6 @@
"validation": { "validation": {
"unique_vendor_name": "" "unique_vendor_name": ""
} }
},
"consent": {
"phone_number": "Phone Number",
"associated_owners": "Associated Owners",
"created_at": "Opt-Out Date",
"no_owners": "No Associated Owners",
"phone_1": "Phone 1",
"phone_2": "Phone 2",
"text_body": ""
},
"settings": {
"title": ""
} }
} }
} }

View File

@@ -1,5 +1,6 @@
import { Tooltip } from "antd"; import { Tooltip } from "antd";
import dayjs from "../utils/day"; import dayjs from "../utils/day";
import React from "react";
export function DateFormatter(props) { export function DateFormatter(props) {
return props.children ? dayjs(props.children).format(props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY") : null; return props.children ? dayjs(props.children).format(props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY") : null;

View File

@@ -1,48 +0,0 @@
import { phone } from "phone";
import { GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS } from "../graphql/phone-number-opt-out.queries";
/**
* Check if phone numbers are opted out for a given bodyshop
* @param {Object} apolloClient - Apollo Client instance
* @param {string} bodyshopId - The ID of the bodyshop
* @param {string[]} phoneNumbers - Array of phone numbers to check
* @returns {Promise<Set<string>>} - Set of normalized opted-out phone numbers
*/
export const phoneNumberOptOutService = async (apolloClient, bodyshopId, phoneNumbers) => {
if (!apolloClient || !bodyshopId || !phoneNumbers?.length) {
return new Set();
}
// Normalize phone numbers (remove +1 for CA numbers)
const normalizedPhones = phoneNumbers
.filter(Boolean)
.map((num) => phone(num, "CA").phoneNumber?.replace(/^\+1/, ""))
.filter(Boolean);
if (!normalizedPhones.length) {
return new Set();
}
const optedOutPhones = new Set();
try {
const { data } = await apolloClient.query({
query: GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS,
variables: {
bodyshopid: bodyshopId,
phone_numbers: normalizedPhones // Array of phone numbers
},
fetchPolicy: "network-only"
});
if (data?.phone_number_opt_out?.length) {
data.phone_number_opt_out.forEach((optOut) => {
optedOutPhones.add(optOut.phone_number);
});
}
} catch (error) {
console.error("Error checking opt-out statuses:", error);
}
return optedOutPhones;
};

View File

@@ -4742,7 +4742,6 @@
- id - id
- image - image
- image_path - image_path
- is_system
- isoutbound - isoutbound
- msid - msid
- read - read

View File

@@ -1,4 +0,0 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."messages" add column "is_system" boolean
-- null default 'false';

View File

@@ -1,2 +0,0 @@
alter table "public"."messages" add column "is_system" boolean
null default 'false';

View File

@@ -10028,6 +10028,25 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>allow_text_message</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>checklist</name> <name>checklist</name>
<description/> <description/>
@@ -32981,6 +33000,25 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>allow_text_message</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>name</name> <name>name</name>
<description/> <description/>

287
package-lock.json generated
View File

@@ -9,14 +9,14 @@
"version": "0.2.0", "version": "0.2.0",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.817.0", "@aws-sdk/client-cloudwatch-logs": "^3.812.0",
"@aws-sdk/client-elasticache": "^3.817.0", "@aws-sdk/client-elasticache": "^3.812.0",
"@aws-sdk/client-s3": "^3.817.0", "@aws-sdk/client-s3": "^3.812.0",
"@aws-sdk/client-secrets-manager": "^3.817.0", "@aws-sdk/client-secrets-manager": "^3.812.0",
"@aws-sdk/client-ses": "^3.817.0", "@aws-sdk/client-ses": "^3.812.0",
"@aws-sdk/credential-provider-node": "^3.817.0", "@aws-sdk/credential-provider-node": "^3.812.0",
"@aws-sdk/lib-storage": "^3.817.0", "@aws-sdk/lib-storage": "^3.812.0",
"@aws-sdk/s3-request-presigner": "^3.817.0", "@aws-sdk/s3-request-presigner": "^3.812.0",
"@opensearch-project/opensearch": "^2.13.0", "@opensearch-project/opensearch": "^2.13.0",
"@socket.io/admin-ui": "^0.5.1", "@socket.io/admin-ui": "^0.5.1",
"@socket.io/redis-adapter": "^8.3.0", "@socket.io/redis-adapter": "^8.3.0",
@@ -31,7 +31,7 @@
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"cors": "^2.8.5", "cors": "^2.8.5",
"crisp-status-reporter": "^1.2.2", "crisp-status-reporter": "^1.2.2",
"dd-trace": "^5.53.0", "dd-trace": "^5.52.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.21.1", "express": "^4.21.1",
@@ -45,7 +45,7 @@
"juice": "^11.0.1", "juice": "^11.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.6.0", "moment-timezone": "^0.5.48",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-persist": "^4.0.4", "node-persist": "^4.0.4",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
@@ -284,24 +284,24 @@
} }
}, },
"node_modules/@aws-sdk/client-cloudwatch-logs": { "node_modules/@aws-sdk/client-cloudwatch-logs": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.812.0.tgz",
"integrity": "sha512-dbR4YZZ2wulMzblgSSE43yd9jgbXDMSrZS7w7r0DqDNAbsXrp79qU2CvA+lb47wGpDxMNppgvoCMu5kcIP5gXw==", "integrity": "sha512-SLvqaMwRviAwb+z4XAq2QmlbUjr7rXN6zAEr4/x2ltyrsxEV95gBo0KHeroAsWhd4eD19USjAgg64KJgvUtNGw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/credential-provider-node": "3.812.0",
"@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-host-header": "3.804.0",
"@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0",
"@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0",
"@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/middleware-user-agent": "3.812.0",
"@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/region-config-resolver": "3.808.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-endpoints": "3.808.0",
"@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-browser": "3.804.0",
"@aws-sdk/util-user-agent-node": "3.816.0", "@aws-sdk/util-user-agent-node": "3.812.0",
"@smithy/config-resolver": "^4.1.2", "@smithy/config-resolver": "^4.1.2",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
"@smithy/eventstream-serde-browser": "^4.0.2", "@smithy/eventstream-serde-browser": "^4.0.2",
@@ -352,24 +352,24 @@
} }
}, },
"node_modules/@aws-sdk/client-elasticache": { "node_modules/@aws-sdk/client-elasticache": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.812.0.tgz",
"integrity": "sha512-TO1Zfv3racKQsRoll4owV2q4kNcw1x64D19KFrWd87rQ517ahbXRcPpaKOqe9CYG1Zo3SIzRySaJLoPftXDfRQ==", "integrity": "sha512-o1KC5Glo3c0T/RN2XBanHu40k3M99MJyq+e/02tIMgEGKIPmnvB8A8muE2F3rQ2A0qLCxvjhm+kprlmDwzpryw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/credential-provider-node": "3.812.0",
"@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-host-header": "3.804.0",
"@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0",
"@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0",
"@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/middleware-user-agent": "3.812.0",
"@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/region-config-resolver": "3.808.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-endpoints": "3.808.0",
"@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-browser": "3.804.0",
"@aws-sdk/util-user-agent-node": "3.816.0", "@aws-sdk/util-user-agent-node": "3.812.0",
"@smithy/config-resolver": "^4.1.2", "@smithy/config-resolver": "^4.1.2",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
"@smithy/fetch-http-handler": "^5.0.2", "@smithy/fetch-http-handler": "^5.0.2",
@@ -403,32 +403,32 @@
} }
}, },
"node_modules/@aws-sdk/client-s3": { "node_modules/@aws-sdk/client-s3": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.812.0.tgz",
"integrity": "sha512-nZyjhlLMEXDs0ofWbpikI8tKoeKuuSgYcIb6eEZJk90Nt5HkkXn6nkWOs/kp2FdhpoGJyTILOVsDgdm7eutnLA==", "integrity": "sha512-kHgw9JDXNPLa/mHtWpOd5btBVXFSe+wwp1Ed9+bqz9uLkv0iV4joZrdQwnydkO8zlTs60Sc5ez+P2OiZ76i2Qg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha1-browser": "5.2.0",
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/credential-provider-node": "3.812.0",
"@aws-sdk/middleware-bucket-endpoint": "3.808.0", "@aws-sdk/middleware-bucket-endpoint": "3.808.0",
"@aws-sdk/middleware-expect-continue": "3.804.0", "@aws-sdk/middleware-expect-continue": "3.804.0",
"@aws-sdk/middleware-flexible-checksums": "3.816.0", "@aws-sdk/middleware-flexible-checksums": "3.812.0",
"@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-host-header": "3.804.0",
"@aws-sdk/middleware-location-constraint": "3.804.0", "@aws-sdk/middleware-location-constraint": "3.804.0",
"@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0",
"@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0",
"@aws-sdk/middleware-sdk-s3": "3.816.0", "@aws-sdk/middleware-sdk-s3": "3.812.0",
"@aws-sdk/middleware-ssec": "3.804.0", "@aws-sdk/middleware-ssec": "3.804.0",
"@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/middleware-user-agent": "3.812.0",
"@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/region-config-resolver": "3.808.0",
"@aws-sdk/signature-v4-multi-region": "3.816.0", "@aws-sdk/signature-v4-multi-region": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-endpoints": "3.808.0",
"@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-browser": "3.804.0",
"@aws-sdk/util-user-agent-node": "3.816.0", "@aws-sdk/util-user-agent-node": "3.812.0",
"@aws-sdk/xml-builder": "3.804.0", "@aws-sdk/xml-builder": "3.804.0",
"@smithy/config-resolver": "^4.1.2", "@smithy/config-resolver": "^4.1.2",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
@@ -470,24 +470,24 @@
} }
}, },
"node_modules/@aws-sdk/client-secrets-manager": { "node_modules/@aws-sdk/client-secrets-manager": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.812.0.tgz",
"integrity": "sha512-Hx74xmJo9xPeHRFtFGdsT5qFx6p9V13ptQ3HICnkmcbtA+CX8soTuc5mglkp9vTdTjvRwKVAmQhx6NPf9ELcjQ==", "integrity": "sha512-RyGzi7kkacjPd0QgVjw6OYvZVvuqtd1wRwG0Aek32dPUYu8eOs9FDaqBsDnNIqdw+lAqC/pKIOPYWtLu2OxE0Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/credential-provider-node": "3.812.0",
"@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-host-header": "3.804.0",
"@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0",
"@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0",
"@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/middleware-user-agent": "3.812.0",
"@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/region-config-resolver": "3.808.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-endpoints": "3.808.0",
"@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-browser": "3.804.0",
"@aws-sdk/util-user-agent-node": "3.816.0", "@aws-sdk/util-user-agent-node": "3.812.0",
"@smithy/config-resolver": "^4.1.2", "@smithy/config-resolver": "^4.1.2",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
"@smithy/fetch-http-handler": "^5.0.2", "@smithy/fetch-http-handler": "^5.0.2",
@@ -535,24 +535,24 @@
} }
}, },
"node_modules/@aws-sdk/client-ses": { "node_modules/@aws-sdk/client-ses": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.812.0.tgz",
"integrity": "sha512-cf2FsdcTT5HiOFOnWk3tzRc84iXcrUNXe4O4KaH75tRToBuQkTaidPI/K9wHnOybNDEkkCcgJo9skv4ftz8qYA==", "integrity": "sha512-7JUS2u0AKMYiEmRrxAYQj8ifFwVUgMAHt5H/KjMhh+1El0NqAQDt3JLD4Asmzy7/TvTAWZfk5np2LQPNB2wZpw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/credential-provider-node": "3.817.0", "@aws-sdk/credential-provider-node": "3.812.0",
"@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-host-header": "3.804.0",
"@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0",
"@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0",
"@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/middleware-user-agent": "3.812.0",
"@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/region-config-resolver": "3.808.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-endpoints": "3.808.0",
"@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-browser": "3.804.0",
"@aws-sdk/util-user-agent-node": "3.816.0", "@aws-sdk/util-user-agent-node": "3.812.0",
"@smithy/config-resolver": "^4.1.2", "@smithy/config-resolver": "^4.1.2",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
"@smithy/fetch-http-handler": "^5.0.2", "@smithy/fetch-http-handler": "^5.0.2",
@@ -586,23 +586,23 @@
} }
}, },
"node_modules/@aws-sdk/client-sso": { "node_modules/@aws-sdk/client-sso": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.812.0.tgz",
"integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==", "integrity": "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-host-header": "3.804.0",
"@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0",
"@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0",
"@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/middleware-user-agent": "3.812.0",
"@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/region-config-resolver": "3.808.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-endpoints": "3.808.0",
"@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-browser": "3.804.0",
"@aws-sdk/util-user-agent-node": "3.816.0", "@aws-sdk/util-user-agent-node": "3.812.0",
"@smithy/config-resolver": "^4.1.2", "@smithy/config-resolver": "^4.1.2",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
"@smithy/fetch-http-handler": "^5.0.2", "@smithy/fetch-http-handler": "^5.0.2",
@@ -635,9 +635,9 @@
} }
}, },
"node_modules/@aws-sdk/core": { "node_modules/@aws-sdk/core": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.812.0.tgz",
"integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", "integrity": "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
@@ -657,12 +657,12 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-env": { "node_modules/@aws-sdk/credential-provider-env": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.812.0.tgz",
"integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==", "integrity": "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/property-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2",
"@smithy/types": "^4.2.0", "@smithy/types": "^4.2.0",
@@ -673,12 +673,12 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-http": { "node_modules/@aws-sdk/credential-provider-http": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.812.0.tgz",
"integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==", "integrity": "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/fetch-http-handler": "^5.0.2", "@smithy/fetch-http-handler": "^5.0.2",
"@smithy/node-http-handler": "^4.0.4", "@smithy/node-http-handler": "^4.0.4",
@@ -694,18 +694,18 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-ini": { "node_modules/@aws-sdk/credential-provider-ini": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.812.0.tgz",
"integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==", "integrity": "sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-env": "3.812.0",
"@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-http": "3.812.0",
"@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-process": "3.812.0",
"@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-sso": "3.812.0",
"@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.812.0",
"@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/nested-clients": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/credential-provider-imds": "^4.0.4", "@smithy/credential-provider-imds": "^4.0.4",
"@smithy/property-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2",
@@ -718,17 +718,17 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-node": { "node_modules/@aws-sdk/credential-provider-node": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.812.0.tgz",
"integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==", "integrity": "sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/credential-provider-env": "3.816.0", "@aws-sdk/credential-provider-env": "3.812.0",
"@aws-sdk/credential-provider-http": "3.816.0", "@aws-sdk/credential-provider-http": "3.812.0",
"@aws-sdk/credential-provider-ini": "3.817.0", "@aws-sdk/credential-provider-ini": "3.812.0",
"@aws-sdk/credential-provider-process": "3.816.0", "@aws-sdk/credential-provider-process": "3.812.0",
"@aws-sdk/credential-provider-sso": "3.817.0", "@aws-sdk/credential-provider-sso": "3.812.0",
"@aws-sdk/credential-provider-web-identity": "3.817.0", "@aws-sdk/credential-provider-web-identity": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/credential-provider-imds": "^4.0.4", "@smithy/credential-provider-imds": "^4.0.4",
"@smithy/property-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2",
@@ -741,12 +741,12 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-process": { "node_modules/@aws-sdk/credential-provider-process": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.812.0.tgz",
"integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==", "integrity": "sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/property-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2",
"@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2",
@@ -758,14 +758,14 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-sso": { "node_modules/@aws-sdk/credential-provider-sso": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.812.0.tgz",
"integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==", "integrity": "sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/client-sso": "3.817.0", "@aws-sdk/client-sso": "3.812.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/token-providers": "3.817.0", "@aws-sdk/token-providers": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/property-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2",
"@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2",
@@ -777,13 +777,13 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-web-identity": { "node_modules/@aws-sdk/credential-provider-web-identity": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.812.0.tgz",
"integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==", "integrity": "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/nested-clients": "3.817.0", "@aws-sdk/nested-clients": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/property-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2",
"@smithy/types": "^4.2.0", "@smithy/types": "^4.2.0",
@@ -794,9 +794,9 @@
} }
}, },
"node_modules/@aws-sdk/lib-storage": { "node_modules/@aws-sdk/lib-storage": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.812.0.tgz",
"integrity": "sha512-2zOO8+2EmiS049PjLSNdqmmZMQj7fzE1hZJ70A94vO+KNaVhVZYuMOOiOmwMw6ePkTCcFwK40vZIIXwEQQ1v1g==", "integrity": "sha512-z37ykuXQXfGO7dqQFbEnj1Wu9UwUUXpZhr4iWXsehbIzSqyl5FiCMp0cI5XK8jLVACCfSCssZCz6QD4oDYdKlQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@smithy/abort-controller": "^4.0.2", "@smithy/abort-controller": "^4.0.2",
@@ -811,7 +811,7 @@
"node": ">=18.0.0" "node": ">=18.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@aws-sdk/client-s3": "^3.817.0" "@aws-sdk/client-s3": "^3.812.0"
} }
}, },
"node_modules/@aws-sdk/middleware-bucket-endpoint": { "node_modules/@aws-sdk/middleware-bucket-endpoint": {
@@ -848,15 +848,15 @@
} }
}, },
"node_modules/@aws-sdk/middleware-flexible-checksums": { "node_modules/@aws-sdk/middleware-flexible-checksums": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.812.0.tgz",
"integrity": "sha512-kftcwDxB/VoCBsUiRgkm5CIuKbTfCN1WLPbis9LRwX3kQhKgGVxG2gG78SHk4TBB0qviWVAd/t+i/KaUgwiAcA==", "integrity": "sha512-/ayAooUZvV1GTomNMrfbhjUHAEaz0Wmio3lKyaTJsW4WdLJXBuzdo57YADRmYYUqx6awzJ6VJ6HGc1Uc6tOlbw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32": "5.2.0",
"@aws-crypto/crc32c": "5.2.0", "@aws-crypto/crc32c": "5.2.0",
"@aws-crypto/util": "5.2.0", "@aws-crypto/util": "5.2.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/is-array-buffer": "^4.0.0", "@smithy/is-array-buffer": "^4.0.0",
"@smithy/node-config-provider": "^4.1.1", "@smithy/node-config-provider": "^4.1.1",
@@ -930,12 +930,12 @@
} }
}, },
"node_modules/@aws-sdk/middleware-sdk-s3": { "node_modules/@aws-sdk/middleware-sdk-s3": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.812.0.tgz",
"integrity": "sha512-jJ+EAXM7gnOwiCM6rrl4AUNY5urmtIsX7roTkxtb4DevJxcS+wFYRRg3/j33fQbuxQZrvk21HqxyZYx5UH70PA==", "integrity": "sha512-e8AqRRIaTsunL1hqtO1hksa9oTYdsIbfezHUyVpPGugUIB1lMqPt/DlBsanI85OzUD711UfNSEcZ1mqAxpDOoA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-arn-parser": "3.804.0", "@aws-sdk/util-arn-parser": "3.804.0",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
@@ -969,12 +969,12 @@
} }
}, },
"node_modules/@aws-sdk/middleware-user-agent": { "node_modules/@aws-sdk/middleware-user-agent": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.812.0.tgz",
"integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", "integrity": "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-endpoints": "3.808.0",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
@@ -987,23 +987,23 @@
} }
}, },
"node_modules/@aws-sdk/nested-clients": { "node_modules/@aws-sdk/nested-clients": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.812.0.tgz",
"integrity": "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ==", "integrity": "sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.816.0", "@aws-sdk/core": "3.812.0",
"@aws-sdk/middleware-host-header": "3.804.0", "@aws-sdk/middleware-host-header": "3.804.0",
"@aws-sdk/middleware-logger": "3.804.0", "@aws-sdk/middleware-logger": "3.804.0",
"@aws-sdk/middleware-recursion-detection": "3.804.0", "@aws-sdk/middleware-recursion-detection": "3.804.0",
"@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/middleware-user-agent": "3.812.0",
"@aws-sdk/region-config-resolver": "3.808.0", "@aws-sdk/region-config-resolver": "3.808.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-endpoints": "3.808.0", "@aws-sdk/util-endpoints": "3.808.0",
"@aws-sdk/util-user-agent-browser": "3.804.0", "@aws-sdk/util-user-agent-browser": "3.804.0",
"@aws-sdk/util-user-agent-node": "3.816.0", "@aws-sdk/util-user-agent-node": "3.812.0",
"@smithy/config-resolver": "^4.1.2", "@smithy/config-resolver": "^4.1.2",
"@smithy/core": "^3.3.3", "@smithy/core": "^3.3.3",
"@smithy/fetch-http-handler": "^5.0.2", "@smithy/fetch-http-handler": "^5.0.2",
@@ -1053,12 +1053,12 @@
} }
}, },
"node_modules/@aws-sdk/s3-request-presigner": { "node_modules/@aws-sdk/s3-request-presigner": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.812.0.tgz",
"integrity": "sha512-FMV0YefefGwPqIbGcHdkkHaiVWKIZoI0wOhYhYDZI129aUD5+CEOtTi7KFp1iJjAK+Cx9bW5tAYc+e9shaWEyQ==", "integrity": "sha512-OpyANELjcD2oknkd3/qWanaRaZDx4SSV6NwYuWIk+fuxDZ+KxZZrrfue1X7OAdaP2TdSapbs7xLisxtTuptWYg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/signature-v4-multi-region": "3.816.0", "@aws-sdk/signature-v4-multi-region": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@aws-sdk/util-format-url": "3.804.0", "@aws-sdk/util-format-url": "3.804.0",
"@smithy/middleware-endpoint": "^4.1.6", "@smithy/middleware-endpoint": "^4.1.6",
@@ -1072,12 +1072,12 @@
} }
}, },
"node_modules/@aws-sdk/signature-v4-multi-region": { "node_modules/@aws-sdk/signature-v4-multi-region": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.812.0.tgz",
"integrity": "sha512-idcr9NW86sSIXASSej3423Selu6fxlhhJJtMgpAqoCH/HJh1eQrONJwNKuI9huiruPE8+02pwxuePvLW46X2mw==", "integrity": "sha512-JTpk3ZHf7TXYbicKfOKi+VrsBTqcAszg9QR9fQmT9aCxPp39gsF3WsXq7NjepwZ5So11ixGIsPE/jtMym399QQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/middleware-sdk-s3": "3.816.0", "@aws-sdk/middleware-sdk-s3": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/protocol-http": "^5.1.0", "@smithy/protocol-http": "^5.1.0",
"@smithy/signature-v4": "^5.1.0", "@smithy/signature-v4": "^5.1.0",
@@ -1089,13 +1089,12 @@
} }
}, },
"node_modules/@aws-sdk/token-providers": { "node_modules/@aws-sdk/token-providers": {
"version": "3.817.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.812.0.tgz",
"integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==", "integrity": "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "3.816.0", "@aws-sdk/nested-clients": "3.812.0",
"@aws-sdk/nested-clients": "3.817.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/property-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2",
"@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2",
@@ -1186,12 +1185,12 @@
} }
}, },
"node_modules/@aws-sdk/util-user-agent-node": { "node_modules/@aws-sdk/util-user-agent-node": {
"version": "3.816.0", "version": "3.812.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.812.0.tgz",
"integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", "integrity": "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/middleware-user-agent": "3.816.0", "@aws-sdk/middleware-user-agent": "3.812.0",
"@aws-sdk/types": "3.804.0", "@aws-sdk/types": "3.804.0",
"@smithy/node-config-provider": "^4.1.1", "@smithy/node-config-provider": "^4.1.1",
"@smithy/types": "^4.2.0", "@smithy/types": "^4.2.0",
@@ -5474,9 +5473,9 @@
} }
}, },
"node_modules/dd-trace": { "node_modules/dd-trace": {
"version": "5.53.0", "version": "5.52.0",
"resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.53.0.tgz", "resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.52.0.tgz",
"integrity": "sha512-ayraB+H05yAag5Ia70YwNkkAS4q0O/Bx1suijTUaYBXirTVlfK9CDSpZRf0Rcjk2uRqf8ANNNsws1fesP4cRmQ==", "integrity": "sha512-ZF+OWLMcgVUWJEAIYIl76LocgnbbkPJ6WgJCG1fhLk4UCsUvoHRvBx9qlexbytL0jkktk1pvzODcjL0wyxLAOQ==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "(Apache-2.0 OR BSD-3-Clause)", "license": "(Apache-2.0 OR BSD-3-Clause)",
"dependencies": { "dependencies": {
@@ -9035,9 +9034,9 @@
} }
}, },
"node_modules/moment-timezone": { "node_modules/moment-timezone": {
"version": "0.6.0", "version": "0.5.48",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.0.tgz", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
"integrity": "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q==", "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"moment": "^2.29.4" "moment": "^2.29.4"

View File

@@ -16,14 +16,14 @@
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js" "job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.817.0", "@aws-sdk/client-cloudwatch-logs": "^3.812.0",
"@aws-sdk/client-elasticache": "^3.817.0", "@aws-sdk/client-elasticache": "^3.812.0",
"@aws-sdk/client-s3": "^3.817.0", "@aws-sdk/client-s3": "^3.812.0",
"@aws-sdk/client-secrets-manager": "^3.817.0", "@aws-sdk/client-secrets-manager": "^3.812.0",
"@aws-sdk/client-ses": "^3.817.0", "@aws-sdk/client-ses": "^3.812.0",
"@aws-sdk/credential-provider-node": "^3.817.0", "@aws-sdk/credential-provider-node": "^3.812.0",
"@aws-sdk/lib-storage": "^3.817.0", "@aws-sdk/lib-storage": "^3.812.0",
"@aws-sdk/s3-request-presigner": "^3.817.0", "@aws-sdk/s3-request-presigner": "^3.812.0",
"@opensearch-project/opensearch": "^2.13.0", "@opensearch-project/opensearch": "^2.13.0",
"@socket.io/admin-ui": "^0.5.1", "@socket.io/admin-ui": "^0.5.1",
"@socket.io/redis-adapter": "^8.3.0", "@socket.io/redis-adapter": "^8.3.0",
@@ -38,7 +38,7 @@
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"cors": "^2.8.5", "cors": "^2.8.5",
"crisp-status-reporter": "^1.2.2", "crisp-status-reporter": "^1.2.2",
"dd-trace": "^5.53.0", "dd-trace": "^5.52.0",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.21.1", "express": "^4.21.1",
@@ -52,7 +52,7 @@
"juice": "^11.0.1", "juice": "^11.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.6.0", "moment-timezone": "^0.5.48",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-persist": "^4.0.4", "node-persist": "^4.0.4",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",

View File

@@ -282,7 +282,6 @@ const applySocketIO = async ({ server, app }) => {
logger.log("Redis connections closed.", "INFO", "redis", "api"); logger.log("Redis connections closed.", "INFO", "redis", "api");
}); });
// IO Redis
const ioRedis = new Server(server, { const ioRedis = new Server(server, {
path: "/wss", path: "/wss",
adapter: createAdapter(pubClient, subClient), adapter: createAdapter(pubClient, subClient),

View File

@@ -478,7 +478,7 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
exports.InsertJob = InsertJob; exports.InsertJob = InsertJob;
async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) { async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
const items = await oauthClient.makeApiCall({ const items = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Item where active=true maxresults 1000`), url: urlBuilder(qbo_realmId, "query", `select * From Item where active=true maxresults 1000`),
method: "POST", method: "POST",
@@ -492,6 +492,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
name: "QueryItems", name: "QueryItems",
status: items.response?.status, status: items.response?.status,
bodyshopid, bodyshopid,
jobid: jobid,
email: req.user.email email: req.user.email
}) })
setNewRefreshToken(req.user.email, items); setNewRefreshToken(req.user.email, items);
@@ -508,6 +509,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
name: "QueryTaxCodes", name: "QueryTaxCodes",
status: taxCodes.response?.status, status: taxCodes.response?.status,
bodyshopid, bodyshopid,
jobid: jobid,
email: req.user.email email: req.user.email
}) })
const classes = await oauthClient.makeApiCall({ const classes = await oauthClient.makeApiCall({
@@ -523,6 +525,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
name: "QueryClasses", name: "QueryClasses",
status: classes.response?.status, status: classes.response?.status,
bodyshopid, bodyshopid,
jobid: jobid,
email: req.user.email email: req.user.email
}) })
const taxCodeMapping = {}; const taxCodeMapping = {};
@@ -559,7 +562,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
} }
async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, parentTierRef) { async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, parentTierRef) {
const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, job.shopid); const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, job.shopid, job.id);
const InvoiceLineAdd = CreateInvoiceLines({ const InvoiceLineAdd = CreateInvoiceLines({
bodyshop, bodyshop,
jobs_by_pk: job, jobs_by_pk: job,
@@ -653,7 +656,7 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
platform: "QBO", platform: "QBO",
method: "POST", method: "POST",
name: "InsertInvoice", name: "InsertInvoice",
status: result.status, status: result.response?.status,
bodyshopid: job.shopid, bodyshopid: job.shopid,
jobid: job.id, jobid: job.id,
email: req.user.email email: req.user.email
@@ -778,7 +781,7 @@ async function InsertInvoiceMultiPayerInvoice(
platform: "QBO", platform: "QBO",
method: "POST", method: "POST",
name: "InsertInvoice", name: "InsertInvoice",
status: result.response.status, status: result.response?.status,
bodyshopid: job.shopid, bodyshopid: job.shopid,
jobid: job.id, jobid: job.id,
email: req.user.email email: req.user.email

View File

@@ -2980,59 +2980,4 @@ exports.INSERT_INTEGRATION_LOG = `
id id
} }
} }
`; `;
exports.INSERT_PHONE_NUMBER_OPT_OUT = `
mutation INSERT_PHONE_NUMBER_OPT_OUT($optOutInput: [phone_number_opt_out_insert_input!]!) {
insert_phone_number_opt_out(objects: $optOutInput, on_conflict: { constraint: phone_number_consent_bodyshopid_phone_number_key, update_columns: [updated_at] }) {
affected_rows
returning {
id
bodyshopid
phone_number
created_at
updated_at
}
}
}
`;
// Query to check if a phone number is opted out
exports.CHECK_PHONE_NUMBER_OPT_OUT = `
query CHECK_PHONE_NUMBER_OPT_OUT($bodyshopid: uuid!, $phone_number: String!) {
phone_number_opt_out(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) {
id
bodyshopid
phone_number
created_at
updated_at
}
}
`;
// Query to check if a phone number is opted out
exports.CHECK_PHONE_NUMBER_OPT_OUT = `
query CHECK_PHONE_NUMBER_OPT_OUT($bodyshopid: uuid!, $phone_number: String!) {
phone_number_opt_out(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) {
id
bodyshopid
phone_number
created_at
updated_at
}
}
`;
// Mutation to delete a phone number opt-out record
exports.DELETE_PHONE_NUMBER_OPT_OUT = `
mutation DELETE_PHONE_NUMBER_OPT_OUT($bodyshopid: uuid!, $phone_number: String!) {
delete_phone_number_opt_out(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) {
affected_rows
returning {
id
bodyshopid
phone_number
}
}
}
`;

View File

@@ -3,25 +3,12 @@ const {
FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID,
UNARCHIVE_CONVERSATION, UNARCHIVE_CONVERSATION,
CREATE_CONVERSATION, CREATE_CONVERSATION,
INSERT_MESSAGE, INSERT_MESSAGE
CHECK_PHONE_NUMBER_OPT_OUT,
DELETE_PHONE_NUMBER_OPT_OUT,
INSERT_PHONE_NUMBER_OPT_OUT
} = require("../graphql-client/queries"); } = require("../graphql-client/queries");
const { phone } = require("phone"); const { phone } = require("phone");
const { admin } = require("../firebase/firebase-handler"); const { admin } = require("../firebase/firebase-handler");
const InstanceManager = require("../utils/instanceMgr").default; const InstanceManager = require("../utils/instanceMgr").default;
// Note: When we handle different languages, we might need to adjust these keywords accordingly.
const optInKeywords = ["START", "YES", "UNSTOP"];
const optOutKeywords = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT", "REVOKE", "OPTOUT"];
// System Message text, will also need to be localized if we support multiple languages
const systemMessageOptions = {
optIn: "Customer has Opted-in",
optOut: "Customer has Opted-out"
};
/** /**
* Receive SMS messages from Twilio and process them * Receive SMS messages from Twilio and process them
* @param req * @param req
@@ -64,16 +51,22 @@ const receive = async (req, res) => {
} }
const bodyshop = response.bodyshops[0]; const bodyshop = response.bodyshops[0];
const normalizedPhone = phone(req.body.From).phoneNumber.replace(/^\+1/, ""); // Normalize phone number (remove +1 for CA numbers)
const messageText = (req.body.Body || "").trim().toUpperCase();
// Step 2: Process conversation // Step 4: Process conversation
const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
const existingConversation = sortedConversations.length const existingConversation = sortedConversations.length
? sortedConversations[sortedConversations.length - 1] ? sortedConversations[sortedConversations.length - 1]
: null; : null;
let conversationid; let conversationid;
let newMessage = {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body, logger),
isoutbound: false,
userid: null
};
if (existingConversation) { if (existingConversation) {
conversationid = existingConversation.id; conversationid = existingConversation.id;
@@ -95,134 +88,9 @@ const receive = async (req, res) => {
conversationid = createdConversation.id; conversationid = createdConversation.id;
} }
// Step 3: Handle opt-in or opt-out keywords newMessage.conversationid = conversationid;
let systemMessageText = "";
let socketEventType = "";
if (optInKeywords.includes(messageText) || optOutKeywords.includes(messageText)) {
// Check if the phone number is in phone_number_opt_out
const optOutCheck = await client.request(CHECK_PHONE_NUMBER_OPT_OUT, {
bodyshopid: bodyshop.id,
phone_number: normalizedPhone
});
// Opt In
if (optInKeywords.includes(messageText)) {
// Handle opt-in
if (optOutCheck.phone_number_opt_out.length > 0) {
// Phone number is opted out; delete the record
const deleteResponse = await client.request(DELETE_PHONE_NUMBER_OPT_OUT, {
bodyshopid: bodyshop.id,
phone_number: normalizedPhone
});
logger.log("sms-opt-in-success", "INFO", "api", null, {
msid: req.body.SmsMessageSid,
bodyshopid: bodyshop.id,
phone_number: normalizedPhone,
affected_rows: deleteResponse.delete_phone_number_opt_out.affected_rows
});
systemMessageText = systemMessageOptions.optIn;
socketEventType = "phone-number-opted-in";
}
}
// Opt Out
else if (optOutKeywords.includes(messageText)) {
// Handle opt-out
if (optOutCheck.phone_number_opt_out.length === 0) {
// Phone number is not opted out; insert a new record
const now = new Date().toISOString();
const optOutInput = {
bodyshopid: bodyshop.id,
phone_number: normalizedPhone,
created_at: now,
updated_at: now
};
const insertResponse = await client.request(INSERT_PHONE_NUMBER_OPT_OUT, {
optOutInput: [optOutInput]
});
logger.log("sms-opt-out-success", "INFO", "api", null, {
msid: req.body.SmsMessageSid,
bodyshopid: bodyshop.id,
phone_number: normalizedPhone,
affected_rows: insertResponse.insert_phone_number_opt_out.affected_rows
});
systemMessageText = systemMessageOptions.optOut;
socketEventType = "phone-number-opted-out";
}
}
// Insert system message if an opt-in or opt-out action was taken
if (systemMessageText) {
const systemMessage = {
msid: `SYS_${req.body.SmsMessageSid}_${Date.now()}`, // Unique ID for system message
text: systemMessageText,
conversationid,
isoutbound: false,
userid: null,
image: false,
image_path: null,
is_system: true
};
const systemMessageResponse = await client.request(INSERT_MESSAGE, {
msg: systemMessage,
conversationid
});
const insertedSystemMessage = systemMessageResponse.insert_messages.returning[0];
// Emit WebSocket events for system message
const broadcastRoom = getBodyshopRoom(bodyshop.id);
const conversationRoom = getBodyshopConversationRoom({
bodyshopId: bodyshop.id,
conversationId: conversationid
});
const systemPayload = {
isoutbound: false,
conversationId: conversationid,
updated_at: insertedSystemMessage.updated_at,
msid: insertedSystemMessage.msid,
existingConversation: !!existingConversation,
newConversation: !existingConversation ? insertedSystemMessage.conversation : null
};
ioRedis.to(broadcastRoom).emit("new-message-summary", {
...systemPayload,
summary: true
});
ioRedis.to(conversationRoom).emit("new-message-detailed", {
newMessage: insertedSystemMessage,
...systemPayload,
summary: false
});
// Emit opt-in or opt-out event
ioRedis.to(broadcastRoom).emit(socketEventType, {
bodyshopid: bodyshop.id,
phone_number: normalizedPhone
});
}
}
// Step 4: Insert the original message
const newMessage = {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body, logger),
isoutbound: false,
userid: null,
conversationid,
is_system: false
};
// Step 5: Insert the message
const insertresp = await client.request(INSERT_MESSAGE, { const insertresp = await client.request(INSERT_MESSAGE, {
msg: newMessage, msg: newMessage,
conversationid conversationid
@@ -235,7 +103,7 @@ const receive = async (req, res) => {
throw new Error("Conversation data is missing from the response."); throw new Error("Conversation data is missing from the response.");
} }
// Step 5: Notify clients for original message // Step 6: Notify clients
const conversationRoom = getBodyshopConversationRoom({ const conversationRoom = getBodyshopConversationRoom({
bodyshopId: conversation.bodyshop.id, bodyshopId: conversation.bodyshop.id,
conversationId: conversation.id conversationId: conversation.id
@@ -245,7 +113,7 @@ const receive = async (req, res) => {
isoutbound: false, isoutbound: false,
conversationId: conversation.id, conversationId: conversation.id,
updated_at: message.updated_at, updated_at: message.updated_at,
msid: message.msid msid: message.sid
}; };
const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id); const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id);
@@ -265,7 +133,7 @@ const receive = async (req, res) => {
summary: false summary: false
}); });
// Step 6: Send FCM notification // Step 7: Send FCM notification
const fcmresp = await admin.messaging().send({ const fcmresp = await admin.messaging().send({
topic: `${message.conversation.bodyshop.imexshopid}-messaging`, topic: `${message.conversation.bodyshop.imexshopid}-messaging`,
notification: { notification: {

View File

@@ -1,12 +1,6 @@
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const { const { UPDATE_MESSAGE_STATUS, MARK_MESSAGES_AS_READ } = require("../graphql-client/queries");
UPDATE_MESSAGE_STATUS,
MARK_MESSAGES_AS_READ,
INSERT_PHONE_NUMBER_OPT_OUT,
FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID
} = require("../graphql-client/queries");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const { phone } = require("phone");
/** /**
* Handle the status of an SMS message * Handle the status of an SMS message
@@ -15,7 +9,7 @@ const { phone } = require("phone");
* @returns {Promise<*>} * @returns {Promise<*>}
*/ */
const status = async (req, res) => { const status = async (req, res) => {
const { SmsSid, SmsStatus, ErrorCode, To, MessagingServiceSid } = req.body; const { SmsSid, SmsStatus } = req.body;
const { const {
ioRedis, ioRedis,
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }
@@ -27,76 +21,18 @@ const status = async (req, res) => {
return res.status(200).json({ message: "Status 'queued' disregarded." }); return res.status(200).json({ message: "Status 'queued' disregarded." });
} }
// Handle ErrorCode 21610 (Attempt to send to unsubscribed recipient) first
if (ErrorCode === "21610" && To && MessagingServiceSid) {
try {
// Step 1: Find the bodyshop by MessagingServiceSid
const bodyshopResponse = await client.request(FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, {
mssid: MessagingServiceSid,
phone: phone(To).phoneNumber // Pass the normalized phone number as required
});
const bodyshop = bodyshopResponse.bodyshops[0];
if (!bodyshop) {
logger.log("sms-opt-out-error", "ERROR", "api", null, {
msid: SmsSid,
messagingServiceSid: MessagingServiceSid,
to: To,
error: "No matching bodyshop found"
});
} else {
// Step 2: Insert into phone_number_opt_out table
const now = new Date().toISOString();
const optOutInput = {
bodyshopid: bodyshop.id,
phone_number: phone(To).phoneNumber.replace(/^\+1/, ""), // Normalize phone number (remove +1 for CA numbers)
created_at: now,
updated_at: now
};
const optOutResponse = await client.request(INSERT_PHONE_NUMBER_OPT_OUT, {
optOutInput: [optOutInput]
});
logger.log("sms-opt-out-success", "INFO", null, null, {
msid: SmsSid,
bodyshopid: bodyshop.id,
phone_number: optOutInput.phone_number,
affected_rows: optOutResponse.insert_phone_number_opt_out.affected_rows
});
// Store bodyshopid for potential use in WebSocket notification
const broadcastRoom = getBodyshopRoom(bodyshop.id);
ioRedis.to(broadcastRoom).emit("phone-number-opted-out", {
bodyshopid: bodyshop.id,
phone_number: optOutInput.phone_number
// Note: conversationId is not included yet; will be set after message lookup
});
}
} catch (error) {
logger.log("sms-opt-out-error", "ERROR", "api", null, {
msid: SmsSid,
messagingServiceSid: MessagingServiceSid,
to: To,
error: error.message,
stack: error.stack
});
// Continue processing to update message status
}
}
// Update message status in the database // Update message status in the database
const response = await client.request(UPDATE_MESSAGE_STATUS, { const response = await client.request(UPDATE_MESSAGE_STATUS, {
msid: SmsSid, msid: SmsSid,
fields: { status: SmsStatus } fields: { status: SmsStatus }
}); });
const message = response.update_messages?.returning?.[0]; const message = response.update_messages.returning[0];
if (message) { if (message) {
logger.log("sms-status-update", "DEBUG", "api", null, { logger.log("sms-status-update", "DEBUG", "api", null, {
msid: SmsSid, msid: SmsSid,
status: SmsStatus fields: { status: SmsStatus }
}); });
// Emit WebSocket event to notify the change in message status // Emit WebSocket event to notify the change in message status
@@ -111,20 +47,20 @@ const status = async (req, res) => {
type: "status-changed" type: "status-changed"
}); });
} else { } else {
logger.log("sms-status-update-warning", "WARN", null, null, { logger.log("sms-status-update-warning", "WARN", "api", null, {
msid: SmsSid, msid: SmsSid,
status: SmsStatus, fields: { status: SmsStatus },
warning: "No message found in database for update" warning: "No message returned from the database update."
}); });
} }
res.sendStatus(200); res.sendStatus(200);
} catch (err) { } catch (error) {
logger.log("sms-status-update-error", "ERROR", "api", null, { logger.log("sms-status-update-error", "ERROR", "api", null, {
msid: SmsSid, msid: SmsSid,
status: SmsStatus, fields: { status: SmsStatus },
error: err.message, stack: error.stack,
stack: err.stack message: error.message
}); });
res.status(500).json({ error: "Failed to update message status." }); res.status(500).json({ error: "Failed to update message status." });
} }

View File

@@ -206,7 +206,7 @@ const createLogger = () => {
jobid, jobid,
paymentid, paymentid,
billid, billid,
status: status.toString() ?? "0", status: status?.toString() ?? "0",
bodyshopid, bodyshopid,
email email
} }