Compare commits
36 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2c5a4cba4 | ||
|
|
a0566e76ab | ||
|
|
909a21023a | ||
|
|
94bdc6c43f | ||
|
|
9466d36e69 | ||
|
|
412efb06e5 | ||
|
|
da7e637183 | ||
|
|
2e95fa25af | ||
|
|
f6c63bbd74 | ||
|
|
0a654082c2 | ||
|
|
2c20b731d2 | ||
|
|
8a22897cdd | ||
|
|
677da61b52 | ||
|
|
6513434bd7 | ||
|
|
fe2600029f | ||
|
|
c5b4efedfb | ||
|
|
310321d0ab | ||
|
|
7e884c42ea | ||
|
|
e279bf41a4 | ||
|
|
4a060ab51c | ||
|
|
62c1c77a18 | ||
|
|
db19ecb28c | ||
|
|
51748ce28d | ||
|
|
4bbfd8a9da | ||
|
|
d4d2db2cac | ||
|
|
23483144e1 | ||
|
|
67d5dcb062 | ||
|
|
901a49e571 | ||
|
|
49ae107fde | ||
|
|
0135281bcd | ||
|
|
99cf95daf0 | ||
|
|
8c1758ae49 | ||
|
|
2d764921ff | ||
|
|
4859239f55 | ||
|
|
5c64d7185e | ||
|
|
152479bc08 |
@@ -12791,27 +12791,6 @@
|
|||||||
</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>
|
||||||
@@ -42614,27 +42593,6 @@
|
|||||||
</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
220
client/package-lock.json
generated
@@ -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.5",
|
"@firebase/auth": "^1.10.6",
|
||||||
"@firebase/firestore": "^4.7.15",
|
"@firebase/firestore": "^4.7.16",
|
||||||
"@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.45.0",
|
"@sentry/cli": "^2.46.0",
|
||||||
"@sentry/react": "^9.22.0",
|
"@sentry/react": "^9.23.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.2",
|
"antd": "^5.25.3",
|
||||||
"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,6 +48,7 @@
|
|||||||
"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 +60,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.4.1",
|
"react-i18next": "^15.5.2",
|
||||||
"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",
|
||||||
@@ -99,7 +100,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.3.4",
|
"@vitejs/plugin-react": "^4.5.0",
|
||||||
"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",
|
||||||
@@ -2966,9 +2967,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/auth": {
|
"node_modules/@firebase/auth": {
|
||||||
"version": "1.10.5",
|
"version": "1.10.6",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.5.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.6.tgz",
|
||||||
"integrity": "sha512-6wF/NdMTwObL4RNQePunuzMr9O3gyftisvFZFFKf57D2HONXo87YymogRV8d+Z7SLA0rcNBN1gLJVk2D0y97gA==",
|
"integrity": "sha512-cFbo2FymQltog4atI9cKTO6CxKxS0dOMXslTQrlNZRH7qhDG44/d7QeI6GXLweFZtrnlecf52ESnNz1DU6ek8w==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.17",
|
"@firebase/component": "0.6.17",
|
||||||
@@ -3003,9 +3004,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/firestore": {
|
"node_modules/@firebase/firestore": {
|
||||||
"version": "4.7.15",
|
"version": "4.7.16",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.16.tgz",
|
||||||
"integrity": "sha512-FgWTmkNBEXdKCoN2ngBNjrMaXuBx6QwjiZZVnOGg+VjUmiBq5gAqlDIW5bZY6i/NYvLUrWugdqIs7y9GHEqwww==",
|
"integrity": "sha512-5OpvlwYVUTLEnqewOlXmtIpH8t2ISlZHDW0NDbKROM2D0ATMqFkMHdvl+/wz9zOAcb8GMQYlhCihOnVAliUbpQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.17",
|
"@firebase/component": "0.6.17",
|
||||||
@@ -3885,6 +3886,13 @@
|
|||||||
"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",
|
||||||
@@ -4461,50 +4469,50 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/browser-utils": {
|
"node_modules/@sentry-internal/browser-utils": {
|
||||||
"version": "9.22.0",
|
"version": "9.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.23.0.tgz",
|
||||||
"integrity": "sha512-Ou1tBnVxFAIn8i9gvrWzRotNJQYiu3awNXpsFCw6qFwmiKAVPa6b13vCdolhXnrIiuR77jY1LQnKh9hXpoRzsg==",
|
"integrity": "sha512-hyN2Q6mh7ggw8sDVHeRyWz5LR6gjvf8zHSzQnMaF7QkeSyaeGM/SVSL4ODwqR9TRH7U2ku6nZFMbKhaBPV+Hfg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "9.22.0"
|
"@sentry/core": "9.23.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/feedback": {
|
"node_modules/@sentry-internal/feedback": {
|
||||||
"version": "9.22.0",
|
"version": "9.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.23.0.tgz",
|
||||||
"integrity": "sha512-zgMVkoC61fgi41zLcSZA59vOtKxcLrKBo1ECYhPD1hxEaneNqY5fhXDwlQBw96P5l2yqkgfX6YZtSdU4ejI9yA==",
|
"integrity": "sha512-Xf+KqV69TBiPo1gk2EsU6O/dumuTMxWOF52uVWJddQYI3pQTU5DqSeoZ5AY76bIIhV9n6AEFDGqNPXmuj4Acrw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "9.22.0"
|
"@sentry/core": "9.23.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/replay": {
|
"node_modules/@sentry-internal/replay": {
|
||||||
"version": "9.22.0",
|
"version": "9.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.23.0.tgz",
|
||||||
"integrity": "sha512-9GOycoKbrclcRXfcbNV8svbmAsOS5R4wXBQmKF4pFLkmFA/lJv9kdZSNYkRvkrxdNfbMIJXP+DV9EqTZcryXig==",
|
"integrity": "sha512-0/q15tvSboaK7/05BFQhs71bqgHKejJoDJgXmH0lySqgsRh/S18867ZxQNiuYhuVt337h07u1QaCyjnNJKHfuA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/browser-utils": "9.22.0",
|
"@sentry-internal/browser-utils": "9.23.0",
|
||||||
"@sentry/core": "9.22.0"
|
"@sentry/core": "9.23.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/replay-canvas": {
|
"node_modules/@sentry-internal/replay-canvas": {
|
||||||
"version": "9.22.0",
|
"version": "9.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.23.0.tgz",
|
||||||
"integrity": "sha512-EcG9IMSEalFe49kowBTJObWjof/iHteDwpyuAszsFDdQUYATrVUtwpwN7o52vDYWJud4arhjrQnMamIGxa79eQ==",
|
"integrity": "sha512-cYlw5svJjyPequm0PJjFGLpee86L1NieONEHlujOXkIG6IEriiorMm+8bNpGsHRuyvg41B+4P/YmcQAGtEGxXg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/replay": "9.22.0",
|
"@sentry-internal/replay": "9.23.0",
|
||||||
"@sentry/core": "9.22.0"
|
"@sentry/core": "9.23.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -4520,16 +4528,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/browser": {
|
"node_modules/@sentry/browser": {
|
||||||
"version": "9.22.0",
|
"version": "9.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.23.0.tgz",
|
||||||
"integrity": "sha512-3TeRm74dvX0JdjX0AgkQa+22iUHwHnY+Q6M05NZ+tDeCNHGK/mEBTeqquS1oQX67jWyuvYmG3VV6RJUxtG9Paw==",
|
"integrity": "sha512-QRkNxWys8e088260vByztoTsEVZ0W6v/XnZfKT6wkPPGn0aFeOrg/xjgxfI8D5huqZCxT28Cf23akOOly4FXjg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/browser-utils": "9.22.0",
|
"@sentry-internal/browser-utils": "9.23.0",
|
||||||
"@sentry-internal/feedback": "9.22.0",
|
"@sentry-internal/feedback": "9.23.0",
|
||||||
"@sentry-internal/replay": "9.22.0",
|
"@sentry-internal/replay": "9.23.0",
|
||||||
"@sentry-internal/replay-canvas": "9.22.0",
|
"@sentry-internal/replay-canvas": "9.23.0",
|
||||||
"@sentry/core": "9.22.0"
|
"@sentry/core": "9.23.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -4720,9 +4728,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli": {
|
"node_modules/@sentry/cli": {
|
||||||
"version": "2.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.46.0.tgz",
|
||||||
"integrity": "sha512-4sWu7zgzgHAjIxIjXUA/66qgeEf5ZOlloO+/JaGD5qXNSW0G7KMTR6iYjReNKMgdBCTH6bUUt9qiuA+Ex9Masw==",
|
"integrity": "sha512-nqoPl7UCr446QFkylrsRrUXF51x8Z9dGquyf4jaQU+OzbOJMqclnYEvU6iwbwvaw3tu/2DnoZE/Og+Nq1h63sA==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4739,20 +4747,20 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@sentry/cli-darwin": "2.45.0",
|
"@sentry/cli-darwin": "2.46.0",
|
||||||
"@sentry/cli-linux-arm": "2.45.0",
|
"@sentry/cli-linux-arm": "2.46.0",
|
||||||
"@sentry/cli-linux-arm64": "2.45.0",
|
"@sentry/cli-linux-arm64": "2.46.0",
|
||||||
"@sentry/cli-linux-i686": "2.45.0",
|
"@sentry/cli-linux-i686": "2.46.0",
|
||||||
"@sentry/cli-linux-x64": "2.45.0",
|
"@sentry/cli-linux-x64": "2.46.0",
|
||||||
"@sentry/cli-win32-arm64": "2.45.0",
|
"@sentry/cli-win32-arm64": "2.46.0",
|
||||||
"@sentry/cli-win32-i686": "2.45.0",
|
"@sentry/cli-win32-i686": "2.46.0",
|
||||||
"@sentry/cli-win32-x64": "2.45.0"
|
"@sentry/cli-win32-x64": "2.46.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-darwin": {
|
"node_modules/@sentry/cli-darwin": {
|
||||||
"version": "2.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.46.0.tgz",
|
||||||
"integrity": "sha512-p4Uxfv/L2fQdP3/wYnKVVz9gzZJf/1Xp9D+6raax/3Bu5y87yHYUqcdt98y/VAXQD4ofp2QgmhGUVPofvQNZmg==",
|
"integrity": "sha512-5Ll+e5KAdIk9OYiZO8aifMBRNWmNyPjSqdjaHlBC1Qfh7pE3b1zyzoHlsUazG0bv0sNrSGea8e7kF5wIO1hvyg==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -4763,9 +4771,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-linux-arm": {
|
"node_modules/@sentry/cli-linux-arm": {
|
||||||
"version": "2.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.46.0.tgz",
|
||||||
"integrity": "sha512-6sEskFLlFKJ+e0MOYgIclBTUX5jYMyYhHIxXahEkI/4vx6JO0uvpyRAkUJRpJkRh/lPog0FM+tbP3so+VxB2qQ==",
|
"integrity": "sha512-WRrLNq/TEX/TNJkGqq6Ad0tGyapd5dwlxtsPbVBrIdryuL1mA7VCBoaHBr3kcwJLsgBHFH0lmkMee2ubNZZdkg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -4773,16 +4781,17 @@
|
|||||||
"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.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.46.0.tgz",
|
||||||
"integrity": "sha512-gUcLoEjzg7AIc4QQGEZwRHri+EHf3Gcms9zAR1VHiNF3/C/jL4WeDPJF2YiWAQt6EtH84tHiyhw1Ab/R8XFClg==",
|
"integrity": "sha512-OEJN8yAjI9y5B4telyqzu27Hi3+S4T8VxZCqJz1+z2Mp0Q/MZ622AahVPpcrVq/5bxrnlZR16+lKh8L1QwNFPg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4790,16 +4799,17 @@
|
|||||||
"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.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.46.0.tgz",
|
||||||
"integrity": "sha512-VmmOaEAzSW23YdGNdy/+oQjCNAMY+HmOGA77A25/ep/9AV7PQB6FI7xO5Y1PVvlkxZFJ23e373njSsEeg4uDZw==",
|
"integrity": "sha512-xko3/BVa4LX8EmRxVOCipV+PwfcK5Xs8lP6lgF+7NeuAHMNL4DqF6iV9rrN8gkGUHCUI9RXSve37uuZnFy55+Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x86",
|
"x86",
|
||||||
"ia32"
|
"ia32"
|
||||||
@@ -4808,16 +4818,17 @@
|
|||||||
"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.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.46.0.tgz",
|
||||||
"integrity": "sha512-a0Oj68mrb25a0WjX/ShZ6AAd4PPiuLcgyzQr7bl2+DvYxIOajwkGbR+CZFEhOVZcfhTnixKy/qIXEzApEPHPQg==",
|
"integrity": "sha512-hJ1g5UEboYcOuRia96LxjJ0jhnmk8EWLDvlGnXLnYHkwy3ree/L7sNgdp/QsY8Z4j2PGO5f22Va+UDhSjhzlfQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -4825,16 +4836,17 @@
|
|||||||
"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.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.46.0.tgz",
|
||||||
"integrity": "sha512-vn+CwS4p+52pQSLNPoi20ZOrQmv01ZgAmuMnjkh1oUZfTyBAwWLrAh6Cy4cztcN8DfL5dOWKQBo8DBKURE4ttg==",
|
"integrity": "sha512-mN7cpPoCv2VExFRGHt+IoK11yx4pM4ADZQGEso5BAUZ5duViXB2WrAXCLd8DrwMnP0OE978a7N8OtzsFqjkbNA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4848,9 +4860,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-win32-i686": {
|
"node_modules/@sentry/cli-win32-i686": {
|
||||||
"version": "2.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.46.0.tgz",
|
||||||
"integrity": "sha512-8mMoDdlwxtcdNIMtteMK7dbi7054jak8wKSHJ5yzMw8UmWxC5thc/gXBc1uPduiaI56VjoJV+phWHBKCD+6I4w==",
|
"integrity": "sha512-6F73AUE3lm71BISUO19OmlnkFD5WVe4/wA1YivtLZTc1RU3eUYJLYxhDfaH3P77+ycDppQ2yCgemLRaA4A8mNQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x86",
|
"x86",
|
||||||
"ia32"
|
"ia32"
|
||||||
@@ -4865,9 +4877,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/cli-win32-x64": {
|
"node_modules/@sentry/cli-win32-x64": {
|
||||||
"version": "2.45.0",
|
"version": "2.46.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.45.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.46.0.tgz",
|
||||||
"integrity": "sha512-ZvK9cIqFaq7vZ0jkHJ/xh5au6902Dr+AUxSk6L6vCL7JCe2p93KGL/4d8VFB5PD/P7Y9b+105G/e0QIFKzpeOw==",
|
"integrity": "sha512-yuGVcfepnNL84LGA0GjHzdMIcOzMe0bjPhq/rwPsPN+zu11N+nPR2wV2Bum4U0eQdqYH3iAlMdL5/BEQfuLJww==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -4902,22 +4914,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/core": {
|
"node_modules/@sentry/core": {
|
||||||
"version": "9.22.0",
|
"version": "9.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.23.0.tgz",
|
||||||
"integrity": "sha512-ixvtKmPF42Y6ckGUbFlB54OWI75H2gO5UYHojO6eXFpS7xO3ZGgV/QH6wb40mWK+0w5XZ0233FuU9VpsuE6mKA==",
|
"integrity": "sha512-9846pn/BvASGgl7WsnKY4xry98WreP9ToeLfCQTQOf+pNfD/qNPn1/0xPInGni3LVMAXRtfHHMPm2Ghz255N7A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/react": {
|
"node_modules/@sentry/react": {
|
||||||
"version": "9.22.0",
|
"version": "9.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.23.0.tgz",
|
||||||
"integrity": "sha512-mI43NnioBYdG5TiXqRlhV1feZs9bnrrl+k5HOHBK7VQtymaXO0fkcsRLZTkdSgLRLMJGasZuvVhq2xK+18QyWQ==",
|
"integrity": "sha512-2J/oOx8jd7Jr2koYIe5IcJyStHBXpjkQnxawo54Zyyvzc96MftyM2Dv5TeYdz7fChU1NIXw7BVbEpkQ9XEQlqg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/browser": "9.22.0",
|
"@sentry/browser": "9.23.0",
|
||||||
"@sentry/core": "9.22.0",
|
"@sentry/core": "9.23.0",
|
||||||
"hoist-non-react-statics": "^3.3.2"
|
"hoist-non-react-statics": "^3.3.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5793,15 +5805,16 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "4.4.1",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz",
|
||||||
"integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
|
"integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
@@ -6091,12 +6104,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/antd": {
|
"node_modules/antd": {
|
||||||
"version": "5.25.2",
|
"version": "5.25.3",
|
||||||
"resolved": "https://registry.npmjs.org/antd/-/antd-5.25.2.tgz",
|
"resolved": "https://registry.npmjs.org/antd/-/antd-5.25.3.tgz",
|
||||||
"integrity": "sha512-7R2nUvlHhey7Trx64+hCtGXOiy+DTUs1Lv5bwbV1LzEIZIhWb0at1AM6V3K108a5lyoR9n7DX3ptlLF7uYV/DQ==",
|
"integrity": "sha512-tBBcAFRjmWM3sitxrL/FEbQL+MTQntYY5bGa5c1ZZZHXWCynkhS3Ch/gy25mGMUY1M/9Uw3pH029v/RGht1x3w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/colors": "^7.2.0",
|
"@ant-design/colors": "^7.2.1",
|
||||||
"@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",
|
||||||
@@ -6156,9 +6169,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/antd/node_modules/@ant-design/colors": {
|
"node_modules/antd/node_modules/@ant-design/colors": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
|
||||||
"integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==",
|
"integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/fast-color": "^2.0.6"
|
"@ant-design/fast-color": "^2.0.6"
|
||||||
@@ -13266,6 +13279,15 @@
|
|||||||
"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",
|
||||||
@@ -14403,9 +14425,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-i18next": {
|
"node_modules/react-i18next": {
|
||||||
"version": "15.5.1",
|
"version": "15.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz",
|
||||||
"integrity": "sha512-C8RZ7N7H0L+flitiX6ASjq9p5puVJU1Z8VyL3OgM/QOMRf40BMZX+5TkpxzZVcTmOLPX5zlti4InEX5pFyiVeA==",
|
"integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.25.0",
|
"@babel/runtime": "^7.25.0",
|
||||||
|
|||||||
@@ -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.5",
|
"@firebase/auth": "^1.10.6",
|
||||||
"@firebase/firestore": "^4.7.15",
|
"@firebase/firestore": "^4.7.16",
|
||||||
"@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.45.0",
|
"@sentry/cli": "^2.46.0",
|
||||||
"@sentry/react": "^9.22.0",
|
"@sentry/react": "^9.23.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.2",
|
"antd": "^5.25.3",
|
||||||
"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,6 +47,7 @@
|
|||||||
"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",
|
||||||
@@ -58,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.4.1",
|
"react-i18next": "^15.5.2",
|
||||||
"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",
|
||||||
@@ -139,7 +140,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.3.4",
|
"@vitejs/plugin-react": "^4.5.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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);
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
|
<Popover destroyOnHidden content={popContent} open={visibility} disabled={disabled}>
|
||||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||||
<CalculatorFilled />
|
<CalculatorFilled />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -202,8 +202,6 @@ 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 });
|
||||||
@@ -211,7 +209,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageRef; // Keep other messages unchanged
|
return messageRef;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,11 +243,8 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updatedList = existingList?.conversations
|
const updatedList = existingList?.conversations
|
||||||
? [
|
? [newConversation, ...existingList.conversations.filter((conv) => conv.id !== newConversation.id)]
|
||||||
newConversation,
|
: [newConversation]; // Prevent duplicates
|
||||||
...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,
|
||||||
@@ -403,6 +398,7 @@ 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({
|
||||||
@@ -419,10 +415,95 @@ 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 }) => {
|
||||||
@@ -431,4 +512,6 @@ 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");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Badge, Card, List, Space, Tag } from "antd";
|
import { Badge, Card, List, Space, Tag, Tooltip } 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,6 +9,7 @@ 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";
|
||||||
@@ -88,7 +89,13 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
|||||||
const cardExtra = (
|
const cardExtra = (
|
||||||
<>
|
<>
|
||||||
<Badge count={item.messages_aggregate.aggregate.count} />
|
<Badge count={item.messages_aggregate.aggregate.count} />
|
||||||
{hasOptOutEntry && <Tag color="red">{t("messaging.labels.no_consent")}</Tag>}
|
{hasOptOutEntry && (
|
||||||
|
<Tooltip title={t("consent.text_body")}>
|
||||||
|
<Tag color="red" icon={<ExclamationCircleOutlined />}>
|
||||||
|
{t("messaging.labels.no_consent")}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
|||||||
userid
|
userid
|
||||||
created_at
|
created_at
|
||||||
read
|
read
|
||||||
|
is_system
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
data: message
|
data: message
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ 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) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
});
|
|
||||||
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 }) {
|
||||||
@@ -37,9 +38,8 @@ 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] && conversation.job_conversations[0]?.jobid
|
jobId: 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>
|
<div className="media-selector-content">
|
||||||
{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 style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
<div className="error-message">{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] && conversation.job_conversations[0]?.jobid}
|
jobId={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] && conversation.job_conversations[0]?.jobid}
|
jobId={conversation.job_conversations[0]?.jobid}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -100,12 +100,17 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
|
|||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
conversation.job_conversations.length === 0 ? <div>{t("messaging.errors.noattachedjobs")}</div> : content
|
conversation.job_conversations.length === 0 ? (
|
||||||
|
<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" }} />
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
@@ -4,13 +4,16 @@
|
|||||||
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;
|
||||||
@@ -37,11 +40,13 @@
|
|||||||
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;
|
||||||
@@ -125,6 +130,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
|||||||
@@ -2,17 +2,29 @@ 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 { MdDone, MdDoneAll } from "react-icons/md";
|
import { MdClose, 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={`${message.isoutbound ? "mine messages" : "yours messages"}`}>
|
<div key={index} className={messageClass}>
|
||||||
<div className="message msgmargin">
|
<div className="message msgmargin">
|
||||||
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
|
<Tooltip title={tooltipTitle}>
|
||||||
<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">
|
||||||
@@ -26,20 +38,31 @@ export const renderMessage = (messages, index) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Render text if available */}
|
{/* Render text if available */}
|
||||||
{message.text && <div>{message.text}</div>}
|
{message.text && <div className="message-text">{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 */}
|
{/* Message status icons for non-system messages */}
|
||||||
{message.status && (message.status === "sent" || message.status === "delivered") && (
|
{!isSystem &&
|
||||||
<div className="message-status">
|
message.status &&
|
||||||
<Icon component={message.status === "sent" ? MdDone : MdDoneAll} className="message-icon" />
|
(message.status === "sent" || message.status === "delivered" || message.status === "failed") && (
|
||||||
</div>
|
<div className="message-status">
|
||||||
)}
|
<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 */}
|
||||||
{/* Outbound message metadata */}
|
{!isSystem && message.isoutbound && (
|
||||||
{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,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
import { ExclamationCircleOutlined, LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
||||||
import { Alert, Input, Spin } from "antd";
|
import { Alert, Input, Space, Spin, Tooltip } from "antd";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import { 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,48 +68,67 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="imex-flex-row" style={{ width: "100%" }}>
|
<Space direction="vertical" style={{ width: "100%" }} size="middle">
|
||||||
{isOptedOut && <Alert message={t("messaging.errors.no_consent")} type="warning" style={{ marginBottom: 8 }} />}
|
{isOptedOut && (
|
||||||
<ChatPresetsComponent className="imex-flex-row__margin" />
|
<Tooltip title={t("consent.text_body")}>
|
||||||
<ChatMediaSelector
|
<Alert
|
||||||
conversation={conversation}
|
showIcon={true}
|
||||||
selectedMedia={selectedMedia}
|
icon={<ExclamationCircleOutlined />}
|
||||||
setSelectedMedia={setSelectedMedia}
|
message={t("messaging.errors.no_consent")}
|
||||||
/>
|
type="error"
|
||||||
<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>
|
|
||||||
<SendOutlined
|
|
||||||
className="chat-send-message-button"
|
|
||||||
disabled={isOptedOut || message === "" || !message}
|
|
||||||
onClick={handleEnter}
|
|
||||||
/>
|
|
||||||
<Spin
|
|
||||||
style={{ display: `${isSending ? "" : "none"}` }}
|
|
||||||
indicator={
|
|
||||||
<LoadingOutlined
|
|
||||||
style={{
|
|
||||||
fontSize: 24
|
|
||||||
}}
|
|
||||||
spin
|
|
||||||
/>
|
/>
|
||||||
}
|
</Tooltip>
|
||||||
/>
|
)}
|
||||||
</div>
|
<div className="imex-flex-row" style={{ width: "100%" }}>
|
||||||
|
{!isOptedOut && (
|
||||||
|
<>
|
||||||
|
<ChatPresetsComponent disabled={isSending} className="imex-flex-row__margin" />
|
||||||
|
<ChatMediaSelector
|
||||||
|
disabled={isSending}
|
||||||
|
conversation={conversation}
|
||||||
|
selectedMedia={selectedMedia}
|
||||||
|
setSelectedMedia={setSelectedMedia}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<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>
|
||||||
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
HomeFilled,
|
HomeFilled,
|
||||||
ImportOutlined,
|
ImportOutlined,
|
||||||
LineChartOutlined,
|
LineChartOutlined,
|
||||||
|
OneToOneOutlined,
|
||||||
PaperClipOutlined,
|
PaperClipOutlined,
|
||||||
PhoneOutlined,
|
PhoneOutlined,
|
||||||
PlusCircleOutlined,
|
PlusCircleOutlined,
|
||||||
@@ -24,6 +25,7 @@ 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";
|
||||||
@@ -40,6 +42,7 @@ 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";
|
||||||
@@ -47,11 +50,10 @@ 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({
|
||||||
@@ -642,17 +644,32 @@ 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",
|
||||||
key: "rescue",
|
icon: <OneToOneOutlined />,
|
||||||
id: "header-rescue",
|
label: t("menus.header.remoteassist"),
|
||||||
icon: <CarFilled />,
|
children: [
|
||||||
label: t("menus.header.rescueme"),
|
...(InstanceRenderManager({ imex: true, rome: false })
|
||||||
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",
|
||||||
|
|||||||
@@ -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 React, { 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 { useLocation, useNavigate, useParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
@@ -9,7 +9,6 @@ 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";
|
||||||
@@ -32,7 +31,6 @@ 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();
|
||||||
@@ -129,24 +127,6 @@ 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) {
|
||||||
@@ -189,7 +169,6 @@ 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 &&
|
||||||
@@ -228,14 +207,6 @@ 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")}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export function JobEmployeeAssignments({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover destroyTooltipOnHide content={popContent} open={visibility}>
|
<Popover destroyOnHidden 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 ? (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Form, Input, Switch } from "antd";
|
import { Form, Input } 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,13 +129,6 @@ 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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
|
|||||||
open={open}
|
open={open}
|
||||||
placement="left"
|
placement="left"
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
destroyTooltipOnHide
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<SearchOutlined style={{ cursor: "pointer" }} />
|
<SearchOutlined style={{ cursor: "pointer" }} />
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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";
|
||||||
@@ -32,7 +33,6 @@ 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,17 +1078,26 @@ export function JobsDetailHeaderActions({
|
|||||||
menuItems.push({
|
menuItems.push({
|
||||||
key: "deletejob",
|
key: "deletejob",
|
||||||
id: "job-actions-deletejob",
|
id: "job-actions-deletejob",
|
||||||
label: (
|
label:
|
||||||
<Popconfirm
|
job.job_watchers.length === 0 ? (
|
||||||
title={t("jobs.labels.deleteconfirm")}
|
<Popconfirm
|
||||||
okText={t("general.labels.yes")}
|
title={t("jobs.labels.deleteconfirm")}
|
||||||
cancelText={t("general.labels.no")}
|
okText={t("general.labels.yes")}
|
||||||
onClick={(e) => e.stopPropagation()}
|
cancelText={t("general.labels.no")}
|
||||||
onConfirm={handleDeleteJob}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
onConfirm={handleDeleteJob}
|
||||||
{t("menus.jobsactions.deletejob")}
|
>
|
||||||
</Popconfirm>
|
{t("menus.jobsactions.deletejob")}
|
||||||
)
|
</Popconfirm>
|
||||||
|
) : (
|
||||||
|
<Popconfirm
|
||||||
|
title={t("jobs.labels.deletewatchers")}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
showCancel={false}
|
||||||
|
>
|
||||||
|
{t("menus.jobsactions.deletejob")}
|
||||||
|
</Popconfirm>
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1109,8 +1118,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="Yes"
|
okText={t("general.labels.yes")}
|
||||||
cancelText="No"
|
cancelText={t("general.labels.no")}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onConfirm={handleVoidJob}
|
onConfirm={handleVoidJob}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -167,7 +167,18 @@ export function JobsDetailHeaderActionsToggleProduction({
|
|||||||
<FormDateTimePickerComponent disabled={jobRO} />
|
<FormDateTimePickerComponent disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name={["actual_delivery"]} label={t("jobs.fields.actual_delivery")}>
|
<Form.Item
|
||||||
|
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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
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 { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter, DateTimeFormatterFunction } 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";
|
||||||
@@ -24,7 +27,6 @@ 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,
|
||||||
@@ -38,6 +40,14 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
context: context,
|
context: context,
|
||||||
modal: "printCenter"
|
modal: "printCenter"
|
||||||
})
|
})
|
||||||
|
),
|
||||||
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid,
|
||||||
|
operation,
|
||||||
|
type
|
||||||
|
})
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,7 +59,7 @@ const colSpan = {
|
|||||||
xl: { span: 6 }
|
xl: { span: 6 }
|
||||||
};
|
};
|
||||||
|
|
||||||
export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { notification } = useNotification();
|
const { notification } = useNotification();
|
||||||
const [notesClamped, setNotesClamped] = useState(true);
|
const [notesClamped, setNotesClamped] = useState(true);
|
||||||
@@ -66,7 +76,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
const handleCheckboxChange = async (field, checked) => {
|
const handleCheckboxChange = async (field, checked) => {
|
||||||
const value = checked ? dayjs().toISOString() : null;
|
const value = checked ? dayjs().toISOString() : null;
|
||||||
try {
|
try {
|
||||||
await updateJob({
|
const ret = await updateJob({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
job: { [field]: value }
|
job: { [field]: value }
|
||||||
@@ -74,6 +84,16 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
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 })
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Form, Input, Switch } from "antd";
|
import { Form, Input, Tooltip } from "antd";
|
||||||
import React from "react";
|
import { CloseCircleFilled } from "@ant-design/icons";
|
||||||
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 }) {
|
export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedOut, isPhone2OptedOut }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getFieldValue } = form;
|
const { getFieldValue } = form;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormFieldsChanged form={form} />
|
<FormFieldsChanged form={form} />
|
||||||
@@ -26,7 +27,7 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
|||||||
<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")}>
|
||||||
@@ -50,9 +51,6 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
|||||||
</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"
|
||||||
@@ -65,19 +63,55 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
|||||||
>
|
>
|
||||||
<FormItemEmail email={getFieldValue("ownr_ea")} />
|
<FormItemEmail email={getFieldValue("ownr_ea")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item label={t("owners.fields.ownr_ph1")} style={{ marginBottom: 0 }}>
|
||||||
label={t("owners.fields.ownr_ph1")}
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
name="ownr_ph1"
|
<Form.Item
|
||||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph1")]}
|
name="ownr_ph1"
|
||||||
>
|
noStyle
|
||||||
<FormItemPhone />
|
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph1")]}
|
||||||
|
>
|
||||||
|
<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
|
<Form.Item label={t("owners.fields.ownr_ph2")} style={{ marginBottom: 0 }}>
|
||||||
label={t("owners.fields.ownr_ph2")}
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
name="ownr_ph2"
|
<Form.Item
|
||||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph2")]}
|
name="ownr_ph2"
|
||||||
>
|
noStyle
|
||||||
<FormItemPhone />
|
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph2")]}
|
||||||
|
>
|
||||||
|
<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 />
|
||||||
|
|||||||
@@ -1,69 +1,115 @@
|
|||||||
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 { useMutation } from "@apollo/client";
|
import { useApolloClient, 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
|
||||||
|
|
||||||
function OwnerDetailFormContainer({ owner, refetch }) {
|
// Connect to Redux to access bodyshop
|
||||||
|
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 history = useNavigate();
|
const navigate = 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 && bodyshop?.messagingservicesid && (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);
|
||||||
|
} else {
|
||||||
|
setOptedOutPhones(new Set());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchOptOutStatus();
|
||||||
|
}, [apolloClient, bodyshop?.id, bodyshop?.messagingservicesid, 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);
|
||||||
const result = await deleteOwner({
|
try {
|
||||||
variables: { id: owner.id }
|
const result = await deleteOwner({
|
||||||
});
|
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: JSON.stringify(result.errors)
|
error: error.message
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
} 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);
|
||||||
const result = await updateOwner({
|
try {
|
||||||
variables: { ownerId: owner.id, owner: values }
|
const result = await updateOwner({
|
||||||
});
|
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: JSON.stringify(result.errors)
|
error: error.message
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
} 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 (
|
||||||
@@ -72,6 +118,7 @@ function OwnerDetailFormContainer({ owner, refetch }) {
|
|||||||
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}
|
||||||
@@ -81,16 +128,29 @@ function OwnerDetailFormContainer({ owner, refetch }) {
|
|||||||
{t("general.actions.delete")}
|
{t("general.actions.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>,
|
</Popconfirm>,
|
||||||
<Button type="primary" loading={loading} onClick={() => form.submit()}>
|
<Button key="save" 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 loading={loading} form={form} />
|
<OwnerDetailFormComponent
|
||||||
|
loading={loading}
|
||||||
|
form={form}
|
||||||
|
isPhone1OptedOut={
|
||||||
|
bodyshop?.messagingservicesid &&
|
||||||
|
owner?.ownr_ph1 &&
|
||||||
|
optedOutPhones.has(phone(owner.ownr_ph1, "CA").phoneNumber?.replace(/^\+1/, ""))
|
||||||
|
}
|
||||||
|
isPhone2OptedOut={
|
||||||
|
bodyshop?.messagingservicesid &&
|
||||||
|
owner?.ownr_ph2 &&
|
||||||
|
optedOutPhones.has(phone(owner.ownr_ph2, "CA").phoneNumber?.replace(/^\+1/, ""))
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OwnerDetailFormContainer;
|
export default connect(mapStateToProps)(OwnerDetailFormContainer);
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function PartsOrderBackorderEta({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
|
<Popover destroyOnHidden 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 />}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, j
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
|
<Popover destroyOnHidden 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>
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Input, Table } from "antd";
|
import { Input, Table, Typography } 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,
|
||||||
@@ -20,18 +26,95 @@ 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) => <PhoneNumberFormatter>{text}</PhoneNumberFormatter>,
|
render: (text) => <ChatOpenButton phone={text} />,
|
||||||
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",
|
||||||
@@ -42,6 +125,7 @@ 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)}
|
||||||
@@ -50,8 +134,8 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
|
|||||||
|
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={data?.phone_number_opt_out}
|
dataSource={optOutData?.phone_number_opt_out}
|
||||||
loading={loading}
|
loading={optOutLoading /* || ownersLoading*/}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
style={{ marginTop: 16 }}
|
style={{ marginTop: 16 }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const Board = ({ id, className, orientation, cardSettings, ...additionalProps })
|
|||||||
default:
|
default:
|
||||||
return cardSizesVertical.small;
|
return cardSizesVertical.small;
|
||||||
}
|
}
|
||||||
}, [cardSettings]);
|
}, [cardSettings?.cardSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -101,11 +101,33 @@ const BoardContainer = ({
|
|||||||
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
|
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
|
|
||||||
// Only update drag time if it's a valid drop with a different destination
|
// Validate drag type and source
|
||||||
if (type === "lane" && source && destination && !isEqual(source, destination)) {
|
if (type !== "lane" || !source) {
|
||||||
setDragTime(source.droppableId);
|
// Invalid drag type or missing source, attempt to revert if possible
|
||||||
setIsProcessing(true);
|
if (source) {
|
||||||
|
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,
|
||||||
@@ -114,14 +136,33 @@ 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);
|
||||||
} finally {
|
// Ensure revert on error
|
||||||
setIsProcessing(false);
|
dispatch(
|
||||||
}
|
actions.moveCardAcrossLanes({
|
||||||
|
fromLaneId: source.droppableId,
|
||||||
|
toLaneId: source.droppableId,
|
||||||
|
cardId: draggableId,
|
||||||
|
index: source.index
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, onDragEnd, setDragTime]
|
[dispatch, onDragEnd, setDragTime]
|
||||||
|
|||||||
@@ -133,7 +133,9 @@ 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 = {
|
||||||
@@ -149,8 +151,6 @@ 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,9 +161,8 @@ const Lane = ({
|
|||||||
: {}
|
: {}
|
||||||
: componentProps;
|
: componentProps;
|
||||||
|
|
||||||
// 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
|
// Always render placeholder for empty lanes in vertical mode to ensure droppable area
|
||||||
// a card is dragged over it
|
const shouldRenderPlaceholder = orientation === "vertical" ? collapsed || renderedCards.length === 0 : collapsed;
|
||||||
const shouldRenderPlaceholder = orientation !== "horizontal" && (collapsed || renderedCards.length === 0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeightMemoryWrapper
|
<HeightMemoryWrapper
|
||||||
@@ -178,8 +177,8 @@ const Lane = ({
|
|||||||
override={orientation !== "horizontal" && (collapsed || !renderedCards.length)}
|
override={orientation !== "horizontal" && (collapsed || !renderedCards.length)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={laneRef} // Ensure laneRef is set here
|
ref={laneRef}
|
||||||
style={{ height: "100%", width: "100%" }} // Make it scrollable
|
style={{ height: "100%", width: "100%" }}
|
||||||
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 }}>
|
||||||
|
|||||||
@@ -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 destroyTooltipOnHide content={popContent} open={visibility}>
|
<Popover destroyOnHidden content={popContent} open={visibility}>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
{record[type] ? (
|
{record[type] ? (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export default function TimeTicketCalculatorComponent({
|
|||||||
open={visible}
|
open={visible}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
placement="right"
|
placement="right"
|
||||||
destroyTooltipOnHide
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<Button onClick={(e) => e.preventDefault()}>
|
<Button onClick={(e) => e.preventDefault()}>
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@@ -312,7 +312,6 @@ 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 } }] }) {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
|
|||||||
id
|
id
|
||||||
status
|
status
|
||||||
text
|
text
|
||||||
|
is_system
|
||||||
isoutbound
|
isoutbound
|
||||||
image
|
image
|
||||||
image_path
|
image_path
|
||||||
@@ -77,6 +78,7 @@ export const GET_CONVERSATION_DETAILS = gql`
|
|||||||
id
|
id
|
||||||
status
|
status
|
||||||
text
|
text
|
||||||
|
is_system
|
||||||
isoutbound
|
isoutbound
|
||||||
image
|
image
|
||||||
image_path
|
image_path
|
||||||
|
|||||||
@@ -874,7 +874,6 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
|||||||
}
|
}
|
||||||
owner {
|
owner {
|
||||||
id
|
id
|
||||||
allow_text_message
|
|
||||||
preferred_contact
|
preferred_contact
|
||||||
tax_number
|
tax_number
|
||||||
}
|
}
|
||||||
@@ -2071,7 +2070,6 @@ export const QUERY_JOB_CHECKLISTS = gql`
|
|||||||
production_vars
|
production_vars
|
||||||
owner {
|
owner {
|
||||||
id
|
id
|
||||||
allow_text_message
|
|
||||||
}
|
}
|
||||||
bodyshop {
|
bodyshop {
|
||||||
id
|
id
|
||||||
@@ -2428,7 +2426,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ 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
|
||||||
@@ -104,7 +103,6 @@ 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
|
||||||
@@ -129,7 +127,6 @@ 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
|
||||||
|
|||||||
@@ -26,3 +26,39 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ 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 {
|
||||||
|
|||||||
@@ -92,13 +92,15 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Consent Settings tab
|
if (bodyshop.messagingservicesid) {
|
||||||
items.push({
|
// Add Consent Settings tab
|
||||||
key: "consent",
|
items.push({
|
||||||
label: t("bodyshop.labels.consent_settings"),
|
key: "consent",
|
||||||
children: <ShopInfoConsentComponent bodyshop={bodyshop} />
|
label: t("bodyshop.labels.consent_settings"),
|
||||||
});
|
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} />
|
||||||
|
|||||||
@@ -335,20 +335,12 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
InstanceRenderManager({
|
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||||
executeFunction: true,
|
window.$crisp.push(["set", "session:segments", [[`region:${payload.region_config}`]]]);
|
||||||
args: [],
|
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||||
imex: () => {
|
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||||
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"]]])
|
||||||
: (() => {
|
: (() => {
|
||||||
@@ -359,6 +351,14 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -775,7 +775,6 @@
|
|||||||
},
|
},
|
||||||
"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?"
|
||||||
@@ -2032,9 +2031,10 @@
|
|||||||
"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,8 +2302,10 @@
|
|||||||
"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": {
|
||||||
@@ -2379,7 +2381,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 not consented to receive messages."
|
"no_consent": "This phone number has opted-out of Messaging."
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"addlabel": "Add a label to this conversation.",
|
"addlabel": "Add a label to this conversation.",
|
||||||
@@ -2396,7 +2398,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": "No Consent"
|
"no_consent": "Opted-out"
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"conversation_list": "Conversation List"
|
"conversation_list": "Conversation List"
|
||||||
@@ -2521,7 +2523,6 @@
|
|||||||
"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",
|
||||||
@@ -3869,8 +3870,12 @@
|
|||||||
},
|
},
|
||||||
"consent": {
|
"consent": {
|
||||||
"phone_number": "Phone Number",
|
"phone_number": "Phone Number",
|
||||||
"status": "Consent Status",
|
"associated_owners": "Associated Owners",
|
||||||
"created_at": "Created At"
|
"created_at": "Opt-Out Date",
|
||||||
|
"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"
|
||||||
|
|||||||
@@ -656,6 +656,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"consent_settings": "",
|
||||||
"2tiername": "",
|
"2tiername": "",
|
||||||
"2tiersetup": "",
|
"2tiersetup": "",
|
||||||
"2tiersource": "",
|
"2tiersource": "",
|
||||||
@@ -774,7 +775,6 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"addtoproduction": "",
|
"addtoproduction": "",
|
||||||
"allow_text_message": "",
|
|
||||||
"checklist": "",
|
"checklist": "",
|
||||||
"printpack": "",
|
"printpack": "",
|
||||||
"removefromproduction": ""
|
"removefromproduction": ""
|
||||||
@@ -2034,6 +2034,7 @@
|
|||||||
"deleteconfirm": "",
|
"deleteconfirm": "",
|
||||||
"deletedelivery": "",
|
"deletedelivery": "",
|
||||||
"deleteintake": "",
|
"deleteintake": "",
|
||||||
|
"deletewatchers": "",
|
||||||
"deliverchecklist": "",
|
"deliverchecklist": "",
|
||||||
"difference": "",
|
"difference": "",
|
||||||
"diskscan": "",
|
"diskscan": "",
|
||||||
@@ -2301,8 +2302,10 @@
|
|||||||
"productionlist": "",
|
"productionlist": "",
|
||||||
"readyjobs": "",
|
"readyjobs": "",
|
||||||
"recent": "",
|
"recent": "",
|
||||||
|
"remoteassist": "",
|
||||||
"reportcenter": "",
|
"reportcenter": "",
|
||||||
"rescueme": "",
|
"rescueme": "",
|
||||||
|
"rescuemezoho": "",
|
||||||
"schedule": "Programar",
|
"schedule": "Programar",
|
||||||
"scoreboard": "",
|
"scoreboard": "",
|
||||||
"search": {
|
"search": {
|
||||||
@@ -2377,7 +2380,8 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"invalidphone": "",
|
"invalidphone": "",
|
||||||
"noattachedjobs": "",
|
"noattachedjobs": "",
|
||||||
"updatinglabel": ""
|
"updatinglabel": "",
|
||||||
|
"no_consent": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"addlabel": "",
|
"addlabel": "",
|
||||||
@@ -2393,7 +2397,8 @@
|
|||||||
"selectmedia": "",
|
"selectmedia": "",
|
||||||
"sentby": "",
|
"sentby": "",
|
||||||
"typeamessage": "Enviar un mensaje...",
|
"typeamessage": "Enviar un mensaje...",
|
||||||
"unarchive": ""
|
"unarchive": "",
|
||||||
|
"no_consent": ""
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"conversation_list": ""
|
"conversation_list": ""
|
||||||
@@ -2498,7 +2503,8 @@
|
|||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"job-watchers": "",
|
"job-watchers": "",
|
||||||
"not-employee": ""
|
"not-employee": "",
|
||||||
|
"not-employee-notifications": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
@@ -2519,7 +2525,6 @@
|
|||||||
"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",
|
||||||
@@ -3864,6 +3869,18 @@
|
|||||||
"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": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -656,6 +656,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"consent_settings": "",
|
||||||
"2tiername": "",
|
"2tiername": "",
|
||||||
"2tiersetup": "",
|
"2tiersetup": "",
|
||||||
"2tiersource": "",
|
"2tiersource": "",
|
||||||
@@ -774,7 +775,6 @@
|
|||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"addtoproduction": "",
|
"addtoproduction": "",
|
||||||
"allow_text_message": "",
|
|
||||||
"checklist": "",
|
"checklist": "",
|
||||||
"printpack": "",
|
"printpack": "",
|
||||||
"removefromproduction": ""
|
"removefromproduction": ""
|
||||||
@@ -2034,6 +2034,7 @@
|
|||||||
"deleteconfirm": "",
|
"deleteconfirm": "",
|
||||||
"deletedelivery": "",
|
"deletedelivery": "",
|
||||||
"deleteintake": "",
|
"deleteintake": "",
|
||||||
|
"deletewatchers": "",
|
||||||
"deliverchecklist": "",
|
"deliverchecklist": "",
|
||||||
"difference": "",
|
"difference": "",
|
||||||
"diskscan": "",
|
"diskscan": "",
|
||||||
@@ -2301,8 +2302,10 @@
|
|||||||
"productionlist": "",
|
"productionlist": "",
|
||||||
"readyjobs": "",
|
"readyjobs": "",
|
||||||
"recent": "",
|
"recent": "",
|
||||||
|
"remoteassist": "",
|
||||||
"reportcenter": "",
|
"reportcenter": "",
|
||||||
"rescueme": "",
|
"rescueme": "",
|
||||||
|
"rescuemezoho": "",
|
||||||
"schedule": "Programme",
|
"schedule": "Programme",
|
||||||
"scoreboard": "",
|
"scoreboard": "",
|
||||||
"search": {
|
"search": {
|
||||||
@@ -2377,7 +2380,8 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"invalidphone": "",
|
"invalidphone": "",
|
||||||
"noattachedjobs": "",
|
"noattachedjobs": "",
|
||||||
"updatinglabel": ""
|
"updatinglabel": "",
|
||||||
|
"no_consent": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"addlabel": "",
|
"addlabel": "",
|
||||||
@@ -2393,7 +2397,8 @@
|
|||||||
"selectmedia": "",
|
"selectmedia": "",
|
||||||
"sentby": "",
|
"sentby": "",
|
||||||
"typeamessage": "Envoyer un message...",
|
"typeamessage": "Envoyer un message...",
|
||||||
"unarchive": ""
|
"unarchive": "",
|
||||||
|
"no_consent": ""
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"conversation_list": ""
|
"conversation_list": ""
|
||||||
@@ -2498,7 +2503,8 @@
|
|||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"job-watchers": "",
|
"job-watchers": "",
|
||||||
"not-employee": ""
|
"not-employee": "",
|
||||||
|
"not-employee-notifications": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
@@ -2519,7 +2525,6 @@
|
|||||||
"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",
|
||||||
@@ -3864,6 +3869,18 @@
|
|||||||
"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": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
|
|||||||
48
client/src/utils/phoneOptOutService.js
Normal file
48
client/src/utils/phoneOptOutService.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@@ -4742,6 +4742,7 @@
|
|||||||
- id
|
- id
|
||||||
- image
|
- image
|
||||||
- image_path
|
- image_path
|
||||||
|
- is_system
|
||||||
- isoutbound
|
- isoutbound
|
||||||
- msid
|
- msid
|
||||||
- read
|
- read
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- 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';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."messages" add column "is_system" boolean
|
||||||
|
null default 'false';
|
||||||
@@ -10028,25 +10028,6 @@
|
|||||||
</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/>
|
||||||
@@ -33000,25 +32981,6 @@
|
|||||||
</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
287
package-lock.json
generated
@@ -9,14 +9,14 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-cloudwatch-logs": "^3.812.0",
|
"@aws-sdk/client-cloudwatch-logs": "^3.817.0",
|
||||||
"@aws-sdk/client-elasticache": "^3.812.0",
|
"@aws-sdk/client-elasticache": "^3.817.0",
|
||||||
"@aws-sdk/client-s3": "^3.812.0",
|
"@aws-sdk/client-s3": "^3.817.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.812.0",
|
"@aws-sdk/client-secrets-manager": "^3.817.0",
|
||||||
"@aws-sdk/client-ses": "^3.812.0",
|
"@aws-sdk/client-ses": "^3.817.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.812.0",
|
"@aws-sdk/credential-provider-node": "^3.817.0",
|
||||||
"@aws-sdk/lib-storage": "^3.812.0",
|
"@aws-sdk/lib-storage": "^3.817.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
"@aws-sdk/s3-request-presigner": "^3.817.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.52.0",
|
"dd-trace": "^5.53.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.5.48",
|
"moment-timezone": "^0.6.0",
|
||||||
"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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.817.0.tgz",
|
||||||
"integrity": "sha512-SLvqaMwRviAwb+z4XAq2QmlbUjr7rXN6zAEr4/x2ltyrsxEV95gBo0KHeroAsWhd4eD19USjAgg64KJgvUtNGw==",
|
"integrity": "sha512-dbR4YZZ2wulMzblgSSE43yd9jgbXDMSrZS7w7r0DqDNAbsXrp79qU2CvA+lb47wGpDxMNppgvoCMu5kcIP5gXw==",
|
||||||
"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.812.0",
|
"@aws-sdk/core": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
"@aws-sdk/credential-provider-node": "3.817.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.812.0",
|
"@aws-sdk/middleware-user-agent": "3.816.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.812.0",
|
"@aws-sdk/util-user-agent-node": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.817.0.tgz",
|
||||||
"integrity": "sha512-o1KC5Glo3c0T/RN2XBanHu40k3M99MJyq+e/02tIMgEGKIPmnvB8A8muE2F3rQ2A0qLCxvjhm+kprlmDwzpryw==",
|
"integrity": "sha512-TO1Zfv3racKQsRoll4owV2q4kNcw1x64D19KFrWd87rQ517ahbXRcPpaKOqe9CYG1Zo3SIzRySaJLoPftXDfRQ==",
|
||||||
"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.812.0",
|
"@aws-sdk/core": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
"@aws-sdk/credential-provider-node": "3.817.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.812.0",
|
"@aws-sdk/middleware-user-agent": "3.816.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.812.0",
|
"@aws-sdk/util-user-agent-node": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.817.0.tgz",
|
||||||
"integrity": "sha512-kHgw9JDXNPLa/mHtWpOd5btBVXFSe+wwp1Ed9+bqz9uLkv0iV4joZrdQwnydkO8zlTs60Sc5ez+P2OiZ76i2Qg==",
|
"integrity": "sha512-nZyjhlLMEXDs0ofWbpikI8tKoeKuuSgYcIb6eEZJk90Nt5HkkXn6nkWOs/kp2FdhpoGJyTILOVsDgdm7eutnLA==",
|
||||||
"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.812.0",
|
"@aws-sdk/core": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
"@aws-sdk/credential-provider-node": "3.817.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.812.0",
|
"@aws-sdk/middleware-flexible-checksums": "3.816.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.812.0",
|
"@aws-sdk/middleware-sdk-s3": "3.816.0",
|
||||||
"@aws-sdk/middleware-ssec": "3.804.0",
|
"@aws-sdk/middleware-ssec": "3.804.0",
|
||||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||||
"@aws-sdk/region-config-resolver": "3.808.0",
|
"@aws-sdk/region-config-resolver": "3.808.0",
|
||||||
"@aws-sdk/signature-v4-multi-region": "3.812.0",
|
"@aws-sdk/signature-v4-multi-region": "3.816.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.812.0",
|
"@aws-sdk/util-user-agent-node": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.817.0.tgz",
|
||||||
"integrity": "sha512-RyGzi7kkacjPd0QgVjw6OYvZVvuqtd1wRwG0Aek32dPUYu8eOs9FDaqBsDnNIqdw+lAqC/pKIOPYWtLu2OxE0Q==",
|
"integrity": "sha512-Hx74xmJo9xPeHRFtFGdsT5qFx6p9V13ptQ3HICnkmcbtA+CX8soTuc5mglkp9vTdTjvRwKVAmQhx6NPf9ELcjQ==",
|
||||||
"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.812.0",
|
"@aws-sdk/core": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
"@aws-sdk/credential-provider-node": "3.817.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.812.0",
|
"@aws-sdk/middleware-user-agent": "3.816.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.812.0",
|
"@aws-sdk/util-user-agent-node": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.817.0.tgz",
|
||||||
"integrity": "sha512-7JUS2u0AKMYiEmRrxAYQj8ifFwVUgMAHt5H/KjMhh+1El0NqAQDt3JLD4Asmzy7/TvTAWZfk5np2LQPNB2wZpw==",
|
"integrity": "sha512-cf2FsdcTT5HiOFOnWk3tzRc84iXcrUNXe4O4KaH75tRToBuQkTaidPI/K9wHnOybNDEkkCcgJo9skv4ftz8qYA==",
|
||||||
"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.812.0",
|
"@aws-sdk/core": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
"@aws-sdk/credential-provider-node": "3.817.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.812.0",
|
"@aws-sdk/middleware-user-agent": "3.816.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.812.0",
|
"@aws-sdk/util-user-agent-node": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz",
|
||||||
"integrity": "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==",
|
"integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==",
|
||||||
"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.812.0",
|
"@aws-sdk/core": "3.816.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.812.0",
|
"@aws-sdk/middleware-user-agent": "3.816.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.812.0",
|
"@aws-sdk/util-user-agent-node": "3.816.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.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz",
|
||||||
"integrity": "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==",
|
"integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==",
|
||||||
"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.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz",
|
||||||
"integrity": "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==",
|
"integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/core": "3.812.0",
|
"@aws-sdk/core": "3.816.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.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz",
|
||||||
"integrity": "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==",
|
"integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/core": "3.812.0",
|
"@aws-sdk/core": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz",
|
||||||
"integrity": "sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==",
|
"integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/core": "3.812.0",
|
"@aws-sdk/core": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-env": "3.812.0",
|
"@aws-sdk/credential-provider-env": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-http": "3.812.0",
|
"@aws-sdk/credential-provider-http": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-process": "3.812.0",
|
"@aws-sdk/credential-provider-process": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-sso": "3.812.0",
|
"@aws-sdk/credential-provider-sso": "3.817.0",
|
||||||
"@aws-sdk/credential-provider-web-identity": "3.812.0",
|
"@aws-sdk/credential-provider-web-identity": "3.817.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/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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz",
|
||||||
"integrity": "sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==",
|
"integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-provider-env": "3.812.0",
|
"@aws-sdk/credential-provider-env": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-http": "3.812.0",
|
"@aws-sdk/credential-provider-http": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-ini": "3.812.0",
|
"@aws-sdk/credential-provider-ini": "3.817.0",
|
||||||
"@aws-sdk/credential-provider-process": "3.812.0",
|
"@aws-sdk/credential-provider-process": "3.816.0",
|
||||||
"@aws-sdk/credential-provider-sso": "3.812.0",
|
"@aws-sdk/credential-provider-sso": "3.817.0",
|
||||||
"@aws-sdk/credential-provider-web-identity": "3.812.0",
|
"@aws-sdk/credential-provider-web-identity": "3.817.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.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz",
|
||||||
"integrity": "sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==",
|
"integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/core": "3.812.0",
|
"@aws-sdk/core": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz",
|
||||||
"integrity": "sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==",
|
"integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sso": "3.812.0",
|
"@aws-sdk/client-sso": "3.817.0",
|
||||||
"@aws-sdk/core": "3.812.0",
|
"@aws-sdk/core": "3.816.0",
|
||||||
"@aws-sdk/token-providers": "3.812.0",
|
"@aws-sdk/token-providers": "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",
|
||||||
@@ -777,13 +777,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
||||||
"version": "3.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz",
|
||||||
"integrity": "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==",
|
"integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/core": "3.812.0",
|
"@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/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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.817.0.tgz",
|
||||||
"integrity": "sha512-z37ykuXQXfGO7dqQFbEnj1Wu9UwUUXpZhr4iWXsehbIzSqyl5FiCMp0cI5XK8jLVACCfSCssZCz6QD4oDYdKlQ==",
|
"integrity": "sha512-2zOO8+2EmiS049PjLSNdqmmZMQj7fzE1hZJ70A94vO+KNaVhVZYuMOOiOmwMw6ePkTCcFwK40vZIIXwEQQ1v1g==",
|
||||||
"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.812.0"
|
"@aws-sdk/client-s3": "^3.817.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.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.816.0.tgz",
|
||||||
"integrity": "sha512-/ayAooUZvV1GTomNMrfbhjUHAEaz0Wmio3lKyaTJsW4WdLJXBuzdo57YADRmYYUqx6awzJ6VJ6HGc1Uc6tOlbw==",
|
"integrity": "sha512-kftcwDxB/VoCBsUiRgkm5CIuKbTfCN1WLPbis9LRwX3kQhKgGVxG2gG78SHk4TBB0qviWVAd/t+i/KaUgwiAcA==",
|
||||||
"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.812.0",
|
"@aws-sdk/core": "3.816.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.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.816.0.tgz",
|
||||||
"integrity": "sha512-e8AqRRIaTsunL1hqtO1hksa9oTYdsIbfezHUyVpPGugUIB1lMqPt/DlBsanI85OzUD711UfNSEcZ1mqAxpDOoA==",
|
"integrity": "sha512-jJ+EAXM7gnOwiCM6rrl4AUNY5urmtIsX7roTkxtb4DevJxcS+wFYRRg3/j33fQbuxQZrvk21HqxyZYx5UH70PA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/core": "3.812.0",
|
"@aws-sdk/core": "3.816.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.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz",
|
||||||
"integrity": "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==",
|
"integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/core": "3.812.0",
|
"@aws-sdk/core": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.817.0.tgz",
|
||||||
"integrity": "sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ==",
|
"integrity": "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ==",
|
||||||
"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.812.0",
|
"@aws-sdk/core": "3.816.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.812.0",
|
"@aws-sdk/middleware-user-agent": "3.816.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.812.0",
|
"@aws-sdk/util-user-agent-node": "3.816.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.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.817.0.tgz",
|
||||||
"integrity": "sha512-OpyANELjcD2oknkd3/qWanaRaZDx4SSV6NwYuWIk+fuxDZ+KxZZrrfue1X7OAdaP2TdSapbs7xLisxtTuptWYg==",
|
"integrity": "sha512-FMV0YefefGwPqIbGcHdkkHaiVWKIZoI0wOhYhYDZI129aUD5+CEOtTi7KFp1iJjAK+Cx9bW5tAYc+e9shaWEyQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/signature-v4-multi-region": "3.812.0",
|
"@aws-sdk/signature-v4-multi-region": "3.816.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.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.816.0.tgz",
|
||||||
"integrity": "sha512-JTpk3ZHf7TXYbicKfOKi+VrsBTqcAszg9QR9fQmT9aCxPp39gsF3WsXq7NjepwZ5So11ixGIsPE/jtMym399QQ==",
|
"integrity": "sha512-idcr9NW86sSIXASSej3423Selu6fxlhhJJtMgpAqoCH/HJh1eQrONJwNKuI9huiruPE8+02pwxuePvLW46X2mw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/middleware-sdk-s3": "3.812.0",
|
"@aws-sdk/middleware-sdk-s3": "3.816.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,12 +1089,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aws-sdk/token-providers": {
|
"node_modules/@aws-sdk/token-providers": {
|
||||||
"version": "3.812.0",
|
"version": "3.817.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz",
|
||||||
"integrity": "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==",
|
"integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/nested-clients": "3.812.0",
|
"@aws-sdk/core": "3.816.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",
|
||||||
@@ -1185,12 +1186,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aws-sdk/util-user-agent-node": {
|
"node_modules/@aws-sdk/util-user-agent-node": {
|
||||||
"version": "3.812.0",
|
"version": "3.816.0",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.812.0.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz",
|
||||||
"integrity": "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==",
|
"integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
"@aws-sdk/middleware-user-agent": "3.816.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",
|
||||||
@@ -5473,9 +5474,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dd-trace": {
|
"node_modules/dd-trace": {
|
||||||
"version": "5.52.0",
|
"version": "5.53.0",
|
||||||
"resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.53.0.tgz",
|
||||||
"integrity": "sha512-ZF+OWLMcgVUWJEAIYIl76LocgnbbkPJ6WgJCG1fhLk4UCsUvoHRvBx9qlexbytL0jkktk1pvzODcjL0wyxLAOQ==",
|
"integrity": "sha512-ayraB+H05yAag5Ia70YwNkkAS4q0O/Bx1suijTUaYBXirTVlfK9CDSpZRf0Rcjk2uRqf8ANNNsws1fesP4cRmQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "(Apache-2.0 OR BSD-3-Clause)",
|
"license": "(Apache-2.0 OR BSD-3-Clause)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -9034,9 +9035,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/moment-timezone": {
|
"node_modules/moment-timezone": {
|
||||||
"version": "0.5.48",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.0.tgz",
|
||||||
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
"integrity": "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"moment": "^2.29.4"
|
"moment": "^2.29.4"
|
||||||
|
|||||||
20
package.json
20
package.json
@@ -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.812.0",
|
"@aws-sdk/client-cloudwatch-logs": "^3.817.0",
|
||||||
"@aws-sdk/client-elasticache": "^3.812.0",
|
"@aws-sdk/client-elasticache": "^3.817.0",
|
||||||
"@aws-sdk/client-s3": "^3.812.0",
|
"@aws-sdk/client-s3": "^3.817.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.812.0",
|
"@aws-sdk/client-secrets-manager": "^3.817.0",
|
||||||
"@aws-sdk/client-ses": "^3.812.0",
|
"@aws-sdk/client-ses": "^3.817.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.812.0",
|
"@aws-sdk/credential-provider-node": "^3.817.0",
|
||||||
"@aws-sdk/lib-storage": "^3.812.0",
|
"@aws-sdk/lib-storage": "^3.817.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
"@aws-sdk/s3-request-presigner": "^3.817.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.52.0",
|
"dd-trace": "^5.53.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.5.48",
|
"moment-timezone": "^0.6.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ 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),
|
||||||
|
|||||||
@@ -2980,4 +2980,59 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -3,12 +3,25 @@ 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
|
||||||
@@ -51,22 +64,16 @@ 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 4: Process conversation
|
// Step 2: 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;
|
||||||
@@ -88,9 +95,134 @@ const receive = async (req, res) => {
|
|||||||
conversationid = createdConversation.id;
|
conversationid = createdConversation.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
newMessage.conversationid = conversationid;
|
// Step 3: Handle opt-in or opt-out keywords
|
||||||
|
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
|
||||||
@@ -103,7 +235,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 6: Notify clients
|
// Step 5: Notify clients for original message
|
||||||
const conversationRoom = getBodyshopConversationRoom({
|
const conversationRoom = getBodyshopConversationRoom({
|
||||||
bodyshopId: conversation.bodyshop.id,
|
bodyshopId: conversation.bodyshop.id,
|
||||||
conversationId: conversation.id
|
conversationId: conversation.id
|
||||||
@@ -113,7 +245,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.sid
|
msid: message.msid
|
||||||
};
|
};
|
||||||
|
|
||||||
const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id);
|
const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id);
|
||||||
@@ -133,7 +265,7 @@ const receive = async (req, res) => {
|
|||||||
summary: false
|
summary: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 7: Send FCM notification
|
// Step 6: 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: {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
const { UPDATE_MESSAGE_STATUS, MARK_MESSAGES_AS_READ } = require("../graphql-client/queries");
|
const {
|
||||||
|
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
|
||||||
@@ -9,7 +15,7 @@ const logger = require("../utils/logger");
|
|||||||
* @returns {Promise<*>}
|
* @returns {Promise<*>}
|
||||||
*/
|
*/
|
||||||
const status = async (req, res) => {
|
const status = async (req, res) => {
|
||||||
const { SmsSid, SmsStatus } = req.body;
|
const { SmsSid, SmsStatus, ErrorCode, To, MessagingServiceSid } = req.body;
|
||||||
const {
|
const {
|
||||||
ioRedis,
|
ioRedis,
|
||||||
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }
|
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }
|
||||||
@@ -21,18 +27,76 @@ 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,
|
||||||
fields: { status: SmsStatus }
|
status: SmsStatus
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emit WebSocket event to notify the change in message status
|
// Emit WebSocket event to notify the change in message status
|
||||||
@@ -47,20 +111,20 @@ const status = async (req, res) => {
|
|||||||
type: "status-changed"
|
type: "status-changed"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.log("sms-status-update-warning", "WARN", "api", null, {
|
logger.log("sms-status-update-warning", "WARN", null, null, {
|
||||||
msid: SmsSid,
|
msid: SmsSid,
|
||||||
fields: { status: SmsStatus },
|
status: SmsStatus,
|
||||||
warning: "No message returned from the database update."
|
warning: "No message found in database for update"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
logger.log("sms-status-update-error", "ERROR", "api", null, {
|
logger.log("sms-status-update-error", "ERROR", "api", null, {
|
||||||
msid: SmsSid,
|
msid: SmsSid,
|
||||||
fields: { status: SmsStatus },
|
status: SmsStatus,
|
||||||
stack: error.stack,
|
error: err.message,
|
||||||
message: error.message
|
stack: err.stack
|
||||||
});
|
});
|
||||||
res.status(500).json({ error: "Failed to update message status." });
|
res.status(500).json({ error: "Failed to update message status." });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user