Merge branch 'staging_environment' into imperial_bushels

This commit is contained in:
csawatzky 2026-03-06 10:45:33 -06:00
commit c09bd9c6d1
20 changed files with 492 additions and 271 deletions

238
package-lock.json generated
View file

@ -3743,9 +3743,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
"integrity": "sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==", "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -3757,9 +3757,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
"integrity": "sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==", "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3771,9 +3771,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
"integrity": "sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==", "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3785,9 +3785,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
"integrity": "sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==", "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3799,9 +3799,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
"integrity": "sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==", "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3813,9 +3813,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
"integrity": "sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==", "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3827,9 +3827,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
"integrity": "sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==", "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -3841,9 +3841,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
"integrity": "sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==", "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -3855,9 +3855,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
"integrity": "sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==", "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3869,9 +3869,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
"integrity": "sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==", "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3883,9 +3883,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loong64-gnu": { "node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
"integrity": "sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==", "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@ -3897,9 +3897,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loong64-musl": { "node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
"integrity": "sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==", "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@ -3911,9 +3911,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-ppc64-gnu": { "node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
"integrity": "sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==", "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -3925,9 +3925,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-ppc64-musl": { "node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
"integrity": "sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==", "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -3939,9 +3939,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
"integrity": "sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==", "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -3953,9 +3953,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-musl": { "node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
"integrity": "sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==", "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -3967,9 +3967,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
"integrity": "sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==", "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -3981,9 +3981,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
"integrity": "sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==", "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3995,9 +3995,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
"integrity": "sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==", "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4009,9 +4009,9 @@
] ]
}, },
"node_modules/@rollup/rollup-openbsd-x64": { "node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
"integrity": "sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==", "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4023,9 +4023,9 @@
] ]
}, },
"node_modules/@rollup/rollup-openharmony-arm64": { "node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
"integrity": "sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==", "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4037,9 +4037,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
"integrity": "sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==", "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4051,9 +4051,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
"integrity": "sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==", "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -4065,9 +4065,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-gnu": { "node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
"integrity": "sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==", "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4079,9 +4079,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.58.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
"integrity": "sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==", "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -6956,13 +6956,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
@ -9167,9 +9167,9 @@
} }
}, },
"node_modules/filelist/node_modules/minimatch": { "node_modules/filelist/node_modules/minimatch": {
"version": "5.1.6", "version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@ -9664,9 +9664,9 @@
} }
}, },
"node_modules/glob/node_modules/minimatch": { "node_modules/glob/node_modules/minimatch": {
"version": "10.2.2", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true, "dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
@ -11216,9 +11216,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@ -12923,9 +12923,9 @@
"license": "Unlicense" "license": "Unlicense"
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.58.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.58.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
"integrity": "sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==", "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -12939,31 +12939,31 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.58.0", "@rollup/rollup-android-arm-eabi": "4.59.0",
"@rollup/rollup-android-arm64": "4.58.0", "@rollup/rollup-android-arm64": "4.59.0",
"@rollup/rollup-darwin-arm64": "4.58.0", "@rollup/rollup-darwin-arm64": "4.59.0",
"@rollup/rollup-darwin-x64": "4.58.0", "@rollup/rollup-darwin-x64": "4.59.0",
"@rollup/rollup-freebsd-arm64": "4.58.0", "@rollup/rollup-freebsd-arm64": "4.59.0",
"@rollup/rollup-freebsd-x64": "4.58.0", "@rollup/rollup-freebsd-x64": "4.59.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.58.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
"@rollup/rollup-linux-arm-musleabihf": "4.58.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
"@rollup/rollup-linux-arm64-gnu": "4.58.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0",
"@rollup/rollup-linux-arm64-musl": "4.58.0", "@rollup/rollup-linux-arm64-musl": "4.59.0",
"@rollup/rollup-linux-loong64-gnu": "4.58.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0",
"@rollup/rollup-linux-loong64-musl": "4.58.0", "@rollup/rollup-linux-loong64-musl": "4.59.0",
"@rollup/rollup-linux-ppc64-gnu": "4.58.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
"@rollup/rollup-linux-ppc64-musl": "4.58.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0",
"@rollup/rollup-linux-riscv64-gnu": "4.58.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
"@rollup/rollup-linux-riscv64-musl": "4.58.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0",
"@rollup/rollup-linux-s390x-gnu": "4.58.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0",
"@rollup/rollup-linux-x64-gnu": "4.58.0", "@rollup/rollup-linux-x64-gnu": "4.59.0",
"@rollup/rollup-linux-x64-musl": "4.58.0", "@rollup/rollup-linux-x64-musl": "4.59.0",
"@rollup/rollup-openbsd-x64": "4.58.0", "@rollup/rollup-openbsd-x64": "4.59.0",
"@rollup/rollup-openharmony-arm64": "4.58.0", "@rollup/rollup-openharmony-arm64": "4.59.0",
"@rollup/rollup-win32-arm64-msvc": "4.58.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0",
"@rollup/rollup-win32-ia32-msvc": "4.58.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0",
"@rollup/rollup-win32-x64-gnu": "4.58.0", "@rollup/rollup-win32-x64-gnu": "4.59.0",
"@rollup/rollup-win32-x64-msvc": "4.58.0", "@rollup/rollup-win32-x64-msvc": "4.59.0",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -15778,9 +15778,9 @@
} }
}, },
"node_modules/workbox-build/node_modules/rollup": { "node_modules/workbox-build/node_modules/rollup": {
"version": "2.79.2", "version": "2.80.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {

View file

@ -12,7 +12,7 @@ import { useTeamAPI } from "providers";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
const useStyles = makeStyles((theme: Theme) => ({ const useStyles = makeStyles<Theme>((theme: Theme) => ({
button: { button: {
color: getSignatureAccentColour(), color: getSignatureAccentColour(),
width: theme.spacing(6), width: theme.spacing(6),
@ -70,8 +70,8 @@ export default function HeaderButtons(props: Props) {
setHasNewChats(true); setHasNewChats(true);
return; return;
} }
console.log(team.preferences.chatViewedTimestamp) // console.log(team.preferences.chatViewedTimestamp)
console.log(team.settings.lastChatTimestamp) // console.log(team.settings.lastChatTimestamp)
let viewMoment = moment(team.preferences.chatViewedTimestamp); let viewMoment = moment(team.preferences.chatViewedTimestamp);
let chatMoment = moment(team.settings.lastChatTimestamp); let chatMoment = moment(team.settings.lastChatTimestamp);
if (viewMoment.diff(chatMoment) < 0) { if (viewMoment.diff(chatMoment) < 0) {

View file

@ -14,6 +14,7 @@ import { AppThemeProvider } from 'theme/AppThemeProvider'
import HTTPProvider from 'providers/http' import HTTPProvider from 'providers/http'
import { Crisp } from "crisp-sdk-web"; import { Crisp } from "crisp-sdk-web";
import { useMobile, useSnackbar } from 'hooks' import { useMobile, useSnackbar } from 'hooks'
import { initCrisp } from '../chat/CrispChat'
// import FirmwareLoader from './FirmwareLoader' // import FirmwareLoader from './FirmwareLoader'
const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => { const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => {
@ -113,20 +114,14 @@ export default function UserWrapper(props: Props) {
}, [setGlobal]) }, [setGlobal])
useEffect(() => { useEffect(() => {
if (!crispInitialized.current && global?.user) { if (global?.user) {
Crisp.configure(import.meta.env.VITE_CRISP_WEBSITE_ID); initCrisp({
crispInitialized.current = true; websiteId: import.meta.env.VITE_CRISP_WEBSITE_ID,
Crisp.session.reset(); email: global.user.settings.email,
Crisp.user.setEmail(global?.user.settings.email); nickname: global.user.settings.name || global.user.settings.email,
// Optional: Set nickname or other info phone: global.user.settings.phoneNumber,
Crisp.user.setNickname(global?.user.settings.name || global?.user.settings.email); tokenId: global.user.id(),
Crisp.user.setPhone(global?.user.settings.phoneNumber); });
// For extra security (marks email as verified, prevents spoofing):
// Crisp.user.setEmail(user.email, "your-hmac-signature-here"); // See Crisp docs for signing
// Optional: For session continuity across devices/browsers
Crisp.setTokenId(global?.user.id()); // e.g., your DB user ID
} }
}, [global]); }, [global]);
@ -162,10 +157,7 @@ export default function UserWrapper(props: Props) {
document.head.appendChild(style); document.head.appendChild(style);
} }
console.log(isMobile)
if (isMobile) { if (isMobile) {
console.log("isMobile")
style.textContent = ` style.textContent = `
#crisp-chatbox [aria-label="Open chat"] { #crisp-chatbox [aria-label="Open chat"] {
bottom: 6em !important; bottom: 6em !important;

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -27,7 +27,7 @@ const useStyles = makeStyles((theme: Theme) => {
}, },
position: "fixed", position: "fixed",
bottom: theme.spacing(8), //for mobile navigator bottom: theme.spacing(8), //for mobile navigator
right: theme.spacing(10.25), right: theme.spacing(2),
[theme.breakpoints.up("sm")]: { [theme.breakpoints.up("sm")]: {
bottom: theme.spacing(1.75) bottom: theme.spacing(1.75)
} }

View file

@ -37,6 +37,7 @@ export default function Chat(props: Props) {
const loadChats = () => { const loadChats = () => {
setLoading(true) setLoading(true)
// console.log("listing chats?")
noteAPI.listChats(10, chats.length, "desc", "timestamp", objectKey).then(resp => { noteAPI.listChats(10, chats.length, "desc", "timestamp", objectKey).then(resp => {
setChats(resp.data.chats ? resp.data.chats.reverse().concat(chats) : []) setChats(resp.data.chats ? resp.data.chats.reverse().concat(chats) : [])
setTotalMessages(resp.data.total) setTotalMessages(resp.data.total)
@ -76,33 +77,42 @@ export default function Chat(props: Props) {
}; };
useEffect(() => { useEffect(() => {
loadChats() setChats([]);
}, []); setLoaded(false);
setLoading(false);
setTotalMessages(0);
}, [objectKey]);
useEffect(() => {
if (chats.length === 0 && !loaded) {
loadChats();
}
}, [objectKey, chats, loaded]);
const loadMore = () => { const loadMore = () => {
loadChats(); loadChats();
}; };
return ( return (
<React.Fragment> <Box className={classes.chatContainer}>
{ loading && !loaded ? ( <Box sx={{ flex: 1, overflowY: "hidden", minHeight: 0 }}>
<Box width="100%" height="100%" alignContent={"center"} textAlign={"center"} > {loading && !loaded ? (
<CircularProgress /> <Box width="100%" height="100%" alignContent="center" textAlign="center">
</Box> <CircularProgress />
) : ( </Box>
<Box className={classes.chatContainer}> ) : (
<ChatOutput <ChatOutput
totalMessages={totalMessages} totalMessages={totalMessages}
messages={chats} messages={chats}
removeNoteMethod={removeNote} removeNoteMethod={removeNote}
loadMore={loadMore} loadMore={loadMore}
/> />
<Box style={{bottom: 0}}> )}
<Divider/> </Box>
<ChatInput newNoteMethod={showNewNotes} objectKey={objectKey} type={type} /> <Box style={{ flexShrink: 0 }}>
</Box> <Divider />
</Box> <ChatInput newNoteMethod={showNewNotes} objectKey={objectKey} type={type} />
)} </Box>
</React.Fragment> </Box>
); );
} }

View file

@ -1,41 +1,45 @@
import { ChevronRight } from "@mui/icons-material"; import { ChevronRight } from "@mui/icons-material";
import { Box, Drawer, IconButton, Theme, Typography } from "@mui/material"; import { Avatar, Box, Drawer, IconButton, Theme, Tooltip, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import { Team } from "models"; import { Team } from "models";
import Chat from "./Chat"; import Chat from "./Chat";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react";
import { useTeamAPI } from "providers";
import { closeCrispChat, openCrispChat } from './CrispChat';
import RobotIcon from "products/CommonIcons/robotIcon";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles<Theme>((theme: Theme) => ({
return ({ chatDrawer: {
chatDrawer: { display: "flex",
display: "flex", flexDirection: "column",
flexDirection: "column", // Arrange children vertically backgroundColor: theme.palette.background.default,
backgroundColor: theme.palette.background.default, height: "100%",
height: "100%", maxHeight: "100%",
maxHeight: "100%", minHeight: "100%",
minHeight: "100%", minWidth: theme.spacing(54),
minWidth: theme.spacing(54), maxWidth: theme.spacing(54),
maxWidth: theme.spacing(54), },
}, chatContainer: {
chatContainer: { flex: 1,
flex: 1, overflow: "hidden",
overflow: "hidden", },
}, chatDrawerMobile: {
chatDrawerMobile: { backgroundColor: theme.palette.background.default,
backgroundColor: theme.palette.background.default, height: "100vh",
height: "auto", width: "100%",
width: "100%" display: "flex",
}, flexDirection: "column",
drawerHeader: { },
display: "flex", drawerHeader: {
alignItems: "center", display: "flex",
padding: theme.spacing(1), alignItems: "center",
...theme.mixins.toolbar, padding: theme.spacing(1),
justifyContent: "flex-start" ...theme.mixins.toolbar,
} justifyContent: "flex-start",
}); },
}); }));
interface Props { interface Props {
open: boolean; open: boolean;
@ -46,21 +50,103 @@ interface Props {
export function ChatDrawer(props: Props) { export function ChatDrawer(props: Props) {
const { open, onClose, team } = props; const { open, onClose, team } = props;
const isMobile = useMobile(); const isMobile = useMobile();
const classes = useStyles() const classes = useStyles();
const teamAPI = useTeamAPI();
const [selectedTeam, setSelectedTeam] = useState<Team>(team);
const [teams, setTeams] = useState<pond.Team[]>([])
useEffect(() => {
teamAPI.listTeams(10, 0, "desc", "name", undefined, false, undefined).then(resp => {
setTeams(resp.data.teams)
})
}, [teamAPI])
const openCrisp = () => {
openCrispChat()
onClose()
}
useEffect(() => {
if (open) {
closeCrispChat()
}
}, [open])
return ( return (
<Drawer open={open} onClose={onClose} anchor="right" > <Drawer
<Box className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}> open={open}
<Box className={classes.drawerHeader}> onClose={onClose}
<IconButton onClick={onClose}> anchor="right"
<ChevronRight /> slotProps={{
</IconButton> paper: {
<Typography>{team.name()} Chat</Typography> sx: isMobile ? { width: "100%" } : undefined,
},
}}
>
<Box display="flex" flexDirection="row" height="100%">
{/* Side icon column */}
<Box
display="flex"
flexDirection="column"
alignItems="center"
padding={1}
gap={1}
sx={(theme: Theme) => ({
backgroundColor: theme.palette.background.paper,
borderRight: `1px solid ${theme.palette.divider}`,
})}
>
<Tooltip title={team.name()} placement="right">
<IconButton
onClick={() => setSelectedTeam(team)}
sx={{
border: selectedTeam.settings?.key === team.settings.key ? "1px solid white" : "1px solid transparent",
borderRadius: "50%",
}}
>
<Avatar src={team.settings.avatar} />
</IconButton>
</Tooltip>
<Tooltip title={"Chat Assistant"} placement="right">
<IconButton
onClick={openCrisp}
>
<RobotIcon />
</IconButton>
</Tooltip>
<Box width={"100%"} borderBottom={"1px solid grey"} />
{teams.map((t, i)=> {
if (t.settings?.key === team.key()) return null;
return (
<Tooltip title={t.settings?.name} key={"chat-team-"+i} placement="right">
<IconButton
onClick={() => setSelectedTeam(Team.create(t))}
sx={{
border: selectedTeam.key() === t.settings?.key ? "1px solid white" : "1px solid transparent",
borderRadius: "50%",
}}
>
<Avatar src={t.settings?.avatar} />
</IconButton>
</Tooltip>
)
})}
</Box> </Box>
<Box className={classes.chatContainer}>
<Chat objectKey={team.settings.key} type={pond.NoteType.NOTE_TYPE_TEAM}/> {/* Main chat area */}
<Box className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}>
<Box className={classes.drawerHeader}>
<IconButton onClick={onClose}>
<ChevronRight />
</IconButton>
<Typography>{selectedTeam.name()} Chat</Typography>
</Box>
<Box className={classes.chatContainer}>
<Chat objectKey={selectedTeam.key()} type={pond.NoteType.NOTE_TYPE_TEAM} />
</Box>
</Box> </Box>
</Box> </Box>
</Drawer> </Drawer>
) );
} }

View file

@ -74,9 +74,22 @@ export default function ChatMessage(props: Props) {
const mappedPerm = pond.Permission[perm as keyof typeof pond.Permission]; const mappedPerm = pond.Permission[perm as keyof typeof pond.Permission];
return mappedPerm; // Return null for invalid keys return mappedPerm; // Return null for invalid keys
}).filter(Boolean); // Optionally filter out any null values }).filter(Boolean); // Optionally filter out any null values
if (user.id() === props.chat.profile?.id) {
perms = [
pond.Permission.PERMISSION_READ,
pond.Permission.PERMISSION_WRITE
]
}
} else { } else {
// console.error("data.permissions is not an array or does not exist"); // console.error("data.permissions is not an array or does not exist");
perms = []; if (user.id() === props.chat.profile?.id) {
perms = [
pond.Permission.PERMISSION_READ,
pond.Permission.PERMISSION_WRITE
]
} else {
perms = [];
}
} }
return perms return perms
} }

View file

@ -72,6 +72,7 @@ export default function ChatOutput(props: Props) {
<Box ref={containerRef} onScroll={onScroll} style={{ <Box ref={containerRef} onScroll={onScroll} style={{
overflowY: "scroll", overflowY: "scroll",
overflowX: "hidden", overflowX: "hidden",
height: "100%",
}}> }}>
{props.messages.length < props.totalMessages && ( {props.messages.length < props.totalMessages && (
<ListItemButton onClick={seeMore} sx={{ justifyContent: "center"}}> <ListItemButton onClick={seeMore} sx={{ justifyContent: "center"}}>

111
src/chat/CrispChat.ts Normal file
View file

@ -0,0 +1,111 @@
import { Crisp } from "crisp-sdk-web";
const CRISP_HIDDEN_STYLE_ID = "crisp-hide-override";
const ANIMATION_DURATION_MS = 300;
let initialized = false;
/**
* Initialize Crisp and immediately hide the default chat button.
* Call this once on app load (e.g., in UserWrapper after user data is ready).
*/
export function initCrisp(opts: {
websiteId: string;
email?: string;
nickname?: string;
phone?: string;
tokenId?: string;
}) {
if (initialized) return;
Crisp.configure(opts.websiteId);
Crisp.session.reset();
if (opts.email) Crisp.user.setEmail(opts.email);
if (opts.nickname) Crisp.user.setNickname(opts.nickname);
if (opts.phone) Crisp.user.setPhone(opts.phone);
if (opts.tokenId) Crisp.setTokenId(opts.tokenId);
initialized = true;
// Hide the chat button off-screen
injectCrispStyles();
// Auto-hide when the user closes the chat
Crisp.chat.onChatClosed(() => {
hideCrispChatButton();
});
}
/**
* Inject base styles that keep Crisp hidden until we want it.
* Uses opacity + visibility so Crisp keeps its default bottom-right positioning.
*/
function injectCrispStyles() {
let style = document.getElementById(CRISP_HIDDEN_STYLE_ID);
if (!style) {
style = document.createElement("style");
style.id = CRISP_HIDDEN_STYLE_ID;
document.head.appendChild(style);
}
style.textContent = `
#crisp-chatbox {
z-index: 99999 !important;
transition: opacity ${ANIMATION_DURATION_MS}ms ease-in-out,
visibility ${ANIMATION_DURATION_MS}ms ease-in-out !important;
opacity: 0 !important;
visibility: hidden !important;
pointer-events: none !important;
}
#crisp-chatbox.crisp-visible {
opacity: 1 !important;
visibility: visible !important;
pointer-events: auto !important;
}
`;
}
/**
* Show the Crisp widget and open the chat window.
* Safe to call from any onClick handler.
*/
export function openCrispChat() {
const chatbox = document.getElementById("crisp-chatbox");
if (chatbox) {
chatbox.classList.add("crisp-visible");
setTimeout(() => {
Crisp.chat.open();
}, ANIMATION_DURATION_MS);
} else {
Crisp.chat.open();
}
}
/**
* Close the chat window and hide the widget.
*/
export function closeCrispChat() {
Crisp.chat.close();
hideCrispChatButton();
}
/**
* Show the default Crisp button without opening the chat.
*/
export function showCrispButton() {
const chatbox = document.getElementById("crisp-chatbox");
if (chatbox) {
chatbox.classList.add("crisp-visible");
}
}
/**
* Hide the default Crisp button.
*/
export function hideCrispChatButton() {
const chatbox = document.getElementById("crisp-chatbox");
if (chatbox) {
chatbox.classList.remove("crisp-visible");
}
}

View file

@ -31,6 +31,7 @@ import React, { useCallback, useEffect, useState } from "react";
//import { Redirect } from "react-router"; //import { Redirect } from "react-router";
import { or } from "utils/types"; import { or } from "utils/types";
import DeviceActions from "./DeviceActions"; import DeviceActions from "./DeviceActions";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
const useStyles = makeStyles((theme: Theme) => ({ const useStyles = makeStyles((theme: Theme) => ({
title: { title: {
@ -97,45 +98,28 @@ export default function Device(props: Props) {
const load = useCallback(() => { const load = useCallback(() => {
if (user.id() === "") return; if (user.id() === "") return;
setIsLoading(true); setIsLoading(true);
//let devicePromise = deviceAPI.get(deviceID); deviceAPI.getPageData(deviceID, getContextKeys(), getContextTypes(), as).then(resp => {
let userPromise = userAPI.getUser(user.id(), deviceScope(deviceID.toString())); console.log(resp)
let componentsPromise = componentAPI.list( let rawComponents: Array<any> = or(resp.data.components, []);
deviceID, let rComponents: Map<string, Component> = new Map();
undefined, rawComponents.forEach((rawComponent: any) => {
[deviceID.toString()], let component = Component.any(rawComponent);
["device"], rComponents.set(component.key(), component);
true, });
as setComponents(rComponents);
);
let interactionsPromise = interactionsAPI.listInteractionsByDevice(deviceID, undefined, as);
let groupPromise: Promise<any> = Promise.resolve(undefined);
Promise.all([userPromise, componentsPromise, interactionsPromise, groupPromise])
.then(([userRes, componentsRes, interactionsRes]) => {
//let rDevice = DeviceModel.any(deviceRes.data);
let rawComponents: Array<any> = or(componentsRes.data.components, []);
let rComponents: Map<string, Component> = new Map();
rawComponents.forEach((rawComponent: any) => {
let component = Component.any(rawComponent);
rComponents.set(component.key(), component);
});
setComponents(rComponents); setInteractions(resp.data.interactions.map(i => Interaction.create(i)))
setPermissions(resp.data.permissions)
setInteractions(interactionsRes); setDevice(props.device);
setPermissions(userRes.permissions); setLongitude(props.device.status.longitude ? props.device.status.longitude : NaN);
setPreferences(userRes.preferences); setLatitude(props.device.status.latitude ? props.device.status.latitude : NaN);
let available = FindAvailablePositions(
setDevice(props.device); Array.from(rComponents.values()),
setLongitude(props.device.status.longitude ? props.device.status.longitude : NaN); props.device.settings.product
setLatitude(props.device.status.latitude ? props.device.status.latitude : NaN); );
let available = FindAvailablePositions( setAvailablePositions(available.GetAvailability());
Array.from(rComponents.values()), setAvailableOffsets(available.offsetAvailability);
props.device.settings.product }).catch(err => {
);
setAvailablePositions(available.GetAvailability());
setAvailableOffsets(available.offsetAvailability);
})
.catch((err: any) => {
setDevice(DeviceModel.create()); setDevice(DeviceModel.create());
setComponents(new Map()); setComponents(new Map());
setInteractions([]); setInteractions([]);
@ -145,8 +129,9 @@ export default function Device(props: Props) {
setPreferences(pond.UserPreferences.create()); setPreferences(pond.UserPreferences.create());
setInvalidDevice(true); setInvalidDevice(true);
error(err); error(err);
}) }).finally(() => {
.finally(() => setIsLoading(false)); setIsLoading(false)
})
usageAPI usageAPI
.getUsage(deviceID, moment().subtract(1, "days")) .getUsage(deviceID, moment().subtract(1, "days"))
.then((res: any) => { .then((res: any) => {

View file

@ -20,10 +20,7 @@ import {
Accordion, Accordion,
AccordionSummary, AccordionSummary,
AccordionDetails, AccordionDetails,
darken,
Typography, Typography,
ToggleButton,
ToggleButtonGroup
} from "@mui/material"; } from "@mui/material";
import BinActions from "bin/BinActions"; import BinActions from "bin/BinActions";
import BinHistory from "bin/BinHistory"; import BinHistory from "bin/BinHistory";
@ -60,7 +57,7 @@ import { Ambient } from "models/Ambient";
import moment from "moment"; import moment from "moment";
import BinStorageConditions from "bin/BinStorageConditions"; import BinStorageConditions from "bin/BinStorageConditions";
import BinConditioningCard from "bin/BinConditioningCard"; import BinConditioningCard from "bin/BinConditioningCard";
import { makeStyles, styled } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Controller } from "models/Controller"; import { Controller } from "models/Controller";
import TaskViewer from "tasks/TaskViewer"; import TaskViewer from "tasks/TaskViewer";

View file

@ -323,21 +323,21 @@ export default function Devices() {
return false return false
} }
useEffect(() => { // useEffect(() => {
console.log("has no2: "+hasNo2) // console.log("has no2: "+hasNo2)
}, [hasNo2]) // }, [hasNo2])
useEffect(() => { // useEffect(() => {
console.log("has co2: "+hasCo2) // console.log("has co2: "+hasCo2)
}, [hasCo2]) // }, [hasCo2])
useEffect(() => { // useEffect(() => {
console.log("has co: "+hasNo2) // console.log("has co: "+hasNo2)
}, [hasNo2]) // }, [hasNo2])
useEffect(() => { // useEffect(() => {
console.log("has o2: "+hasO2) // console.log("has o2: "+hasO2)
}, [hasO2]) // }, [hasO2])
const openProvisionDialog = () => { const openProvisionDialog = () => {
setIsProvisionDialogOpen(true); setIsProvisionDialogOpen(true);

View file

@ -223,7 +223,7 @@ export default function TeamPage() {
const isStreamline = IsStreamline() const isStreamline = IsStreamline()
return ( return (
<PageContainer spacing={isMobile ? 0 : 2}> <PageContainer spacing={isMobile ? 1 : 2}>
<Box className={classes.container}> <Box className={classes.container}>
<Box className={classes.leftBox}> <Box className={classes.leftBox}>
{title()} {title()}

View file

@ -2,7 +2,6 @@ import MarketWhite from "assets/marketplaceImages/marketplaceIconLight.png";
import MarketBlack from "assets/marketplaceImages/marketplaceIconDark.png"; import MarketBlack from "assets/marketplaceImages/marketplaceIconDark.png";
import { ImgIcon } from "common/ImgIcon"; import { ImgIcon } from "common/ImgIcon";
import { useThemeType } from "hooks"; import { useThemeType } from "hooks";
import React from "react";
interface Props { interface Props {
type?: "light" | "dark"; type?: "light" | "dark";

View file

@ -0,0 +1,23 @@
import RobotWhite from "assets/common/robotIconLight.png";
import RobotBlack from "assets/common/robotIconDark.png";
import { ImgIcon } from "common/ImgIcon";
import { useThemeType } from "hooks";
interface Props {
type?: "light" | "dark";
}
export default function RobotIcon(props: Props) {
const themeType = useThemeType();
const { type } = props;
const src = () => {
if (type) {
return type === "light" ? RobotWhite : RobotBlack;
}
return themeType === "light" ? RobotBlack : RobotWhite;
};
return <ImgIcon alt="marketplace" src={src()} />;
}

View file

@ -290,7 +290,7 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
) => { ) => {
const view = otherTeam ? otherTeam : as const view = otherTeam ? otherTeam : as
let url = "/bins/" + bin + "/updateComponent/" + component + "/preferences"; let url = "/bins/" + bin + "/updateComponent/" + component + "/preferences";
if (view) url = url + "/?as=" + view; if (view) url = url + "?as=" + view;
interface request { interface request {
preferences: Object; preferences: Object;
} }

View file

@ -1,7 +1,7 @@
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { useHTTP } from "hooks"; import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers"; // import { useGlobalState } from "providers";
import { createContext, PropsWithChildren, useContext } from "react"; import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond"; import { pondURL } from "./pond";
@ -37,7 +37,7 @@ interface Props {}
export default function NoteProvider(props: PropsWithChildren<Props>) { export default function NoteProvider(props: PropsWithChildren<Props>) {
const { children } = props; const { children } = props;
const { get, del, post, put } = useHTTP(); const { get, del, post, put } = useHTTP();
const [{as}] = useGlobalState() // const [{as}] = useGlobalState()
const addNote = (note: pond.NoteSettings, attachments?: string[]) => { const addNote = (note: pond.NoteSettings, attachments?: string[]) => {
let url = pondURL("/notes" + (attachments ? "?attachments=" + attachments.toString() : "")) let url = pondURL("/notes" + (attachments ? "?attachments=" + attachments.toString() : ""))
@ -88,9 +88,9 @@ export default function NoteProvider(props: PropsWithChildren<Props>) {
orderBy?: string, orderBy?: string,
search?: string, search?: string,
asRoot?: boolean, asRoot?: boolean,
otherTeam?: string // otherTeam?: string
) => { ) => {
const view = otherTeam ? otherTeam : as // const view = otherTeam ? otherTeam : as
let url = pondURL( let url = pondURL(
"/notes" + "/notes" +
"?limit=" + "?limit=" +
@ -100,9 +100,10 @@ export default function NoteProvider(props: PropsWithChildren<Props>) {
("&order=" + (order ? order : "asc")) + ("&order=" + (order ? order : "asc")) +
("&by=" + (orderBy ? orderBy : "key")) + ("&by=" + (orderBy ? orderBy : "key")) +
(search ? "&search=" + search : "") + (search ? "&search=" + search : "") +
(asRoot ? "&asRoot=" + asRoot.toString() : "") + (asRoot ? "&asRoot=" + asRoot.toString() : ""),
(view ? "&as=" + view : "") // (view ? "&as=" + "" : "")
) )
// console.log(url)
return new Promise<AxiosResponse<pond.ListNotesResponse>>((resolve, reject) => { return new Promise<AxiosResponse<pond.ListNotesResponse>>((resolve, reject) => {
get<pond.ListNotesResponse>(url).then(resp => { get<pond.ListNotesResponse>(url).then(resp => {
resp.data = pond.ListNotesResponse.fromObject(resp.data) resp.data = pond.ListNotesResponse.fromObject(resp.data)
@ -119,10 +120,10 @@ export default function NoteProvider(props: PropsWithChildren<Props>) {
order?: "asc" | "desc", order?: "asc" | "desc",
orderBy?: string, orderBy?: string,
search?: string, search?: string,
asRoot?: boolean, // asRoot?: boolean,
otherTeam?: string // otherTeam?: string
) => { ) => {
const view = otherTeam ? otherTeam : as // const view = otherTeam ? otherTeam : as
let url = pondURL( let url = pondURL(
"/chats" + "/chats" +
"?limit=" + "?limit=" +
@ -131,10 +132,11 @@ export default function NoteProvider(props: PropsWithChildren<Props>) {
offset + offset +
("&order=" + (order ? order : "asc")) + ("&order=" + (order ? order : "asc")) +
("&by=" + (orderBy ? orderBy : "key")) + ("&by=" + (orderBy ? orderBy : "key")) +
(search ? "&search=" + search : "") + (search ? "&search=" + search : ""),
(asRoot ? "&asRoot=" + asRoot.toString() : "") + // (asRoot ? "&asRoot=" + asRoot.toString() : ""),
(view ? "&as=" + view : "") // (view ? "&as=" + view : "")
) )
// console.log(asRoot)
return new Promise<AxiosResponse<pond.ListChatsResponse>>((resolve, reject) => { return new Promise<AxiosResponse<pond.ListChatsResponse>>((resolve, reject) => {
get<pond.ListChatsResponse>(url).then(resp => { get<pond.ListChatsResponse>(url).then(resp => {
resp.data = pond.ListChatsResponse.fromObject(resp.data) resp.data = pond.ListChatsResponse.fromObject(resp.data)

View file

@ -117,9 +117,11 @@ export default function TeamProvider(props: PropsWithChildren<Props>) {
asRoot?: boolean, asRoot?: boolean,
otherTeam?: string, otherTeam?: string,
prefixSearch?: string, prefixSearch?: string,
useImitation?: boolean,
) => { ) => {
//let asText = team ? "&as=" + team.key() : ""; //let asText = team ? "&as=" + team.key() : "";
const view = otherTeam ? otherTeam : (as.length > 0 ? as : undefined) let view = otherTeam ? otherTeam : (as.length > 0 ? as : undefined)
if (useImitation === undefined || useImitation === false) view = ""
let url = pondURL( let url = pondURL(
"/teams" + "/teams" +
"?limit=" + "?limit=" +