diff --git a/package-lock.json b/package-lock.json index 3a309013..2ffe8c8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,12 +23,12 @@ "@fortawesome/free-regular-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", - "@headlessui/react": "^1.7.18", + "@headlessui/react": "^2.1.9", "@hookform/resolvers": "^3.3.4", "@nethesis/nethesis-brands-svg-icons": "github:nethesis/Font-Awesome#ns-brands", "@nethesis/nethesis-light-svg-icons": "github:nethesis/Font-Awesome#ns-light", "@nethesis/nethesis-solid-svg-icons": "github:nethesis/Font-Awesome#ns-solid", - "@nethesis/phone-island": "^0.14.0", + "@nethesis/phone-island": "^0.14.1", "@tailwindcss/forms": "^0.5.7", "@types/lodash": "^4.14.202", "@types/node": "^18.19.9", @@ -3910,21 +3910,34 @@ "license": "MIT" }, "node_modules/@headlessui/react": { - "version": "1.7.19", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz", - "integrity": "sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.3.tgz", + "integrity": "sha512-hgOJGXPifPlOczIeSwX8OjLWRJ5XdYApZFf7DeCbCrO1PXHkPhNTRrA9ZwJsgAG7SON1i2JcvIreF/kbgtJeaQ==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/react-virtual": "^3.0.0-beta.60", - "client-only": "^0.0.1" + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.6", + "use-sync-external-store": "^1.5.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "react": "^16 || ^17 || ^18", - "react-dom": "^16 || ^17 || ^18" + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@headlessui/react/node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@hookform/resolvers": { @@ -5626,11 +5639,10 @@ } }, "node_modules/@nethesis/phone-island": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@nethesis/phone-island/-/phone-island-0.14.0.tgz", - "integrity": "sha512-XFGdAeA5ZUKMb4MVd4wIFaAVmfuxYvQAtQSC/Qdw7X9jOG7Vb4wkIoIb0sVfcnOPobcg0RVk7EMU+e8FIgOr1w==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@nethesis/phone-island/-/phone-island-0.14.1.tgz", + "integrity": "sha512-QNgMcLRY7EtwaB40GlShNY5YVRmHIlkTADrIxFwB1Yhym6QRB1gPTGB+KDQCbkHugUkGyLyGn8vTHdIRrsfMmg==", "dev": true, - "license": "GPL-3.0-or-later", "dependencies": { "@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/react-fontawesome": "^0.2.0", @@ -5662,26 +5674,6 @@ "webrtc-adapter": "^9.0.1" } }, - "node_modules/@nethesis/phone-island/node_modules/@headlessui/react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz", - "integrity": "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.26.16", - "@react-aria/focus": "^3.17.1", - "@react-aria/interactions": "^3.21.3", - "@tanstack/react-virtual": "^3.8.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" - } - }, "node_modules/@nethesis/phone-island/node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", @@ -6393,26 +6385,27 @@ } }, "node_modules/@react-aria/focus": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.0.tgz", - "integrity": "sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==", + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.2.tgz", + "integrity": "sha512-Q3rouk/rzoF/3TuH6FzoAIKrl+kzZi9LHmr8S5EqLAOyP9TXIKG34x2j42dZsAhrw7TbF9gA8tBKwnCNH4ZV+Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.22.5", - "@react-aria/utils": "^3.26.0", - "@react-types/shared": "^3.26.0", + "@react-aria/interactions": "^3.25.0", + "@react-aria/utils": "^3.28.2", + "@react-types/shared": "^3.29.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/focus/node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6420,25 +6413,27 @@ } }, "node_modules/@react-aria/interactions": { - "version": "3.22.5", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.22.5.tgz", - "integrity": "sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.0.tgz", + "integrity": "sha512-GgIsDLlO8rDU/nFn6DfsbP9rfnzhm8QFjZkB9K9+r+MTSCn7bMntiWQgMM+5O6BiA8d7C7x4zuN4bZtc0RBdXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@react-aria/ssr": "^3.9.7", - "@react-aria/utils": "^3.26.0", - "@react-types/shared": "^3.26.0", + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.28.2", + "@react-stately/flags": "^3.1.1", + "@react-types/shared": "^3.29.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/interactions/node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6446,9 +6441,9 @@ } }, "node_modules/@react-aria/ssr": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", - "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "version": "3.9.8", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.8.tgz", + "integrity": "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6462,9 +6457,9 @@ } }, "node_modules/@react-aria/ssr/node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6472,26 +6467,48 @@ } }, "node_modules/@react-aria/utils": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.26.0.tgz", - "integrity": "sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ==", + "version": "3.28.2", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.2.tgz", + "integrity": "sha512-J8CcLbvnQgiBn54eeEvQQbIOfBF3A1QizxMw9P4cl9MkeR03ug7RnjTIdJY/n2p7t59kLeAB3tqiczhcj+Oi5w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@react-aria/ssr": "^3.9.7", - "@react-stately/utils": "^3.10.5", - "@react-types/shared": "^3.26.0", + "@react-aria/ssr": "^3.9.8", + "@react-stately/flags": "^3.1.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/utils/node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.1.tgz", + "integrity": "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/flags/node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6499,9 +6516,9 @@ } }, "node_modules/@react-stately/utils": { - "version": "3.10.5", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.5.tgz", - "integrity": "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==", + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.6.tgz", + "integrity": "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6512,9 +6529,9 @@ } }, "node_modules/@react-stately/utils/node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6522,9 +6539,9 @@ } }, "node_modules/@react-types/shared": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.26.0.tgz", - "integrity": "sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw==", + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -7200,27 +7217,27 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.10.9", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.9.tgz", - "integrity": "sha512-OXO2uBjFqA4Ibr2O3y0YMnkrRWGVNqcvHQXmGvMu6IK8chZl3PrDxFXdGZ2iZkSrKh3/qUYoFqYe+Rx23RoU0g==", + "version": "3.13.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.8.tgz", + "integrity": "sha512-meS2AanUg50f3FBSNoAdBSRAh8uS0ue01qm7zrw65KGJtiXB9QXfybqZwkh4uFpRv2iX/eu5tjcH5wqUpwYLPg==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.10.9" + "@tanstack/virtual-core": "3.13.8" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@tanstack/virtual-core": { - "version": "3.10.9", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.9.tgz", - "integrity": "sha512-kBknKOKzmeR7lN+vSadaKWXaLS0SZZG+oqpQ/k80Q6g9REn6zRHS/ZYdrIzHnpHgy/eWs00SujveUN/GJT2qTw==", + "version": "3.13.8", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.8.tgz", + "integrity": "sha512-BT6w89Hqy7YKaWewYzmecXQzcJh6HTBbKYJIIkMaNU49DZ06LoTV3z32DWWEdUsgW6n1xTmwTLs4GtWrZC261w==", "dev": true, "license": "MIT", "funding": { @@ -10756,13 +10773,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "dev": true, - "license": "MIT" - }, "node_modules/clipboardy": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", diff --git a/package.json b/package.json index a1cdf509..fe10fbc7 100644 --- a/package.json +++ b/package.json @@ -44,12 +44,12 @@ "@fortawesome/free-regular-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", - "@headlessui/react": "^1.7.18", + "@headlessui/react": "^2.1.9", "@hookform/resolvers": "^3.3.4", "@nethesis/nethesis-brands-svg-icons": "github:nethesis/Font-Awesome#ns-brands", "@nethesis/nethesis-light-svg-icons": "github:nethesis/Font-Awesome#ns-light", "@nethesis/nethesis-solid-svg-icons": "github:nethesis/Font-Awesome#ns-solid", - "@nethesis/phone-island": "^0.14.0", + "@nethesis/phone-island": "^0.14.1", "@tailwindcss/forms": "^0.5.7", "@types/lodash": "^4.14.202", "@types/node": "^18.19.9", diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index 24187630..921078c8 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -321,7 +321,7 @@ "Upload image to continue": "Upload image to continue", "Wrong file type": "Wrong file type", "Delete profile picture": "Delete profile picture", - "Devices": "Devices", + "Devices": "Audio and Video", "Time preferences": "Time preferences", "Login/logout preferences": "Login/logout preferences", "Logout from queue automatically": "Logout from queue automatically", @@ -367,8 +367,12 @@ "Shortcut": "Shortcut", "Keyboard shortcut to call": "Keyboard shortcut to call", "Shortcut title description": "Choose a keyboard shortcut to dial any selected number. ", - "Shortcut subtitle description": "Warning: avoid using system-reserved shortcuts.", - "Shortcut body description": "Keys not supported: tab, capsLock, numLock, insert, esc, shift" + "Shortcut subtitle description": "Avoid using system-reserved shortcuts.", + "Shortcut body description": "Keys not supported: tab, capsLock, numLock, insert, esc, shift", + "Preferred devices": "Audio and Video settings", + "Microphone": "Microphone", + "Speaker": "Speaker", + "Camera": "Camera" }, "OperatorDrawer": { "Book": "Book", @@ -893,7 +897,8 @@ "Try changing your search query": "Try changing your search query", "Keys configuration information tooltip": "Configure line keys of your physical device", "Key position information tooltip": "Choose a position for your key", - "Click on confirm": "Click on Confirm" + "Click on confirm": "Click on Confirm", + "Inline warning message devices": "Changes to audio and video settings can only be applied if the pair device is set to NethLink and your status is Online." }, "DropdownContent": { "Microphones": "MICROPHONES", diff --git a/public/locales/it/translations.json b/public/locales/it/translations.json index c4644779..daa6c0f4 100644 --- a/public/locales/it/translations.json +++ b/public/locales/it/translations.json @@ -321,7 +321,7 @@ "Upload image to continue": "Carica immagine per continuare", "Wrong file type": "Formato file errato", "Delete profile picture": "Elimina immagine profilo", - "Devices": "Dispositivi", + "Devices": "Audio e Video", "Time preferences": "Preferenze di tempo", "Login/logout preferences": "Preferenze di entrata/uscita", "Logout from queue automatically": "Esci dalla coda automaticamente", @@ -367,8 +367,12 @@ "Shortcut": "Scorciatoia", "Keyboard shortcut to call": "Scorciatoia per chiamare", "Shortcut title description": "Scegli una combinazione di tasti per avviare una chiamata dopo aver selezionato un testo.", - "Shortcut subtitle description": "Attenzione: non usare scorciatoie già presenti nel sistema.", - "Shortcut body description": "Tasti non consentiti: tab, capsLock, numLock, insert, esc, shift" + "Shortcut subtitle description": "Non usare scorciatoie già presenti nel sistema.", + "Shortcut body description": "Tasti non consentiti: tab, capsLock, numLock, insert, esc, shift", + "Preferred devices": "Impostazioni Audio e Video", + "Microphone": "Microfono", + "Speaker": "Altoparlante", + "Camera": "Video" }, "OperatorDrawer": { "Book": "Prenota", @@ -893,7 +897,8 @@ "Try changing your search query": "Prova a cambiare la tua ricerca", "Keys configuration information tooltip": "Configura i tasti linea del tuo telefono", "Key position information tooltip": "Scegli una posizione per il pulsante", - "Click on confirm": "Clicca su Conferma" + "Click on confirm": "Clicca su Conferma", + "Inline warning message devices": "Le modifiche relative alle impostazioni audio e video possono essere applicate solo se il dispositivo abbinato è NethLink e il proprio stato è Online." }, "DropdownContent": { "Microphones": "MICROFONI", diff --git a/src/main/classes/controllers/AccountController.ts b/src/main/classes/controllers/AccountController.ts index f72e5e1d..35a2f07c 100644 --- a/src/main/classes/controllers/AccountController.ts +++ b/src/main/classes/controllers/AccountController.ts @@ -5,7 +5,7 @@ import { store } from '@/lib/mainStore' import { useNethVoiceAPI } from '@shared/useNethVoiceAPI' import { useLogin } from '@shared/useLogin' import { NetworkController } from './NetworkController' -import { getAccountUID } from '@shared/utils/utils' +import { delay, getAccountUID } from '@shared/utils/utils' const defaultConfig: ConfigFile = { lastUser: undefined, @@ -80,7 +80,7 @@ export class AccountController { let loggedAccount: Account = { ...lastLoggedAccount, ...tempLoggedAccount, - theme: lastLoggedAccount.theme || tempLoggedAccount.theme + theme: lastLoggedAccount.theme || tempLoggedAccount.theme, } const { parseConfig } = useLogin() @@ -99,10 +99,19 @@ export class AccountController { async saveLoggedAccount(account: Account, password: string): Promise { try { - // const clearString = JSON.stringify({ host: account.host, username: account.username, password: password }) const cryptString = safeStorage.encryptString(clearString) const accountUID = getAccountUID(account) + const authAppData = store.store.auth + if (authAppData) { + const accountPreviousData = authAppData.availableAccounts[accountUID] + if (accountPreviousData) { + account = { + ...accountPreviousData, + ...account + } + } + } store.updateStore({ account, theme: account.theme, @@ -135,33 +144,48 @@ export class AccountController { - updateTheme(theme: any) { + async updateTheme(theme: any) { if (store.store) { const account = store.store.account - store.set('theme', theme) + store.set('theme', theme, true) if (account) { account.theme = theme + store.set('account', account, true) + const auth = store.store.auth + auth!.availableAccounts[getAccountUID(account)] = account + store.set('auth', auth, true) + } + store.saveToDisk() + } + } - store.set('account', account) + async updatePreferredDevice(preferredDevices: any) { + if (store.store) { + const account = store.store.account + if (account) { + account.preferredDevices = preferredDevices + + store.set('account', account, true) const auth = store.store.auth auth!.availableAccounts[getAccountUID(account)] = account - store.set('auth', auth) + store.set('auth', auth, true) } store.saveToDisk() } } - updateShortcut(shortcut: any) { + async updateShortcut(shortcut: any) { if (store.store) { const account = store.store.account if (account) { account.shortcut = shortcut - store.set('account', account) + + store.set('account', account, true) const auth = store.store.auth auth!.availableAccounts[getAccountUID(account)] = account - store.set('auth', auth) + store.set('auth', auth, true) } store.saveToDisk() } @@ -177,7 +201,7 @@ export class AccountController { const auth = store.store.auth if (account) { account!.phoneIslandPosition = phoneIslandPosition - store.set('account', account) + store.set('account', account, true) const _auth = { ...auth, availableAccounts: { @@ -185,7 +209,7 @@ export class AccountController { [getAccountUID(account)]: account } } - store.set('auth', _auth) + store.set('auth', _auth, true) store.saveToDisk() } } @@ -200,7 +224,7 @@ export class AccountController { const auth = store.store.auth if (account) { account!.nethlinkBounds = nethlinkBounds - store.set('account', account) + store.set('account', account, true) const _auth = { ...auth, availableAccounts: { @@ -208,7 +232,7 @@ export class AccountController { [getAccountUID(account)]: account } } - store.set('auth', _auth) + store.set('auth', _auth, true) store.saveToDisk() } } diff --git a/src/main/lib/ipcEvents.ts b/src/main/lib/ipcEvents.ts index a1375c41..eaedf640 100644 --- a/src/main/lib/ipcEvents.ts +++ b/src/main/lib/ipcEvents.ts @@ -269,6 +269,12 @@ export function registerIpcEvents() { } }) + ipcMain.on(IPC_EVENTS.CHANGE_PREFERRED_DEVICES, (_, devices) => { + Log.info('CHANGE_PREFERRED_DEVICES:', devices) + AccountController.instance.updatePreferredDevice(devices) + PhoneIslandController.instance.window.emit(IPC_EVENTS.CHANGE_PREFERRED_DEVICES, devices) + }) + ipcMain.on(IPC_EVENTS.CHANGE_SHORTCUT, async (_, combo) => { // unregister previous shortcut await globalShortcut.unregisterAll(); diff --git a/src/main/main.ts b/src/main/main.ts index 301d045f..db65a0aa 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -251,13 +251,6 @@ function attachOnReadyProcess() { }) }) } - - // read shortcut from config and set it to app - const account: Account = store.get('account') as Account - Log.info("Shortcut readed:", store.get('shortcut'), account.shortcut) - if (account.shortcut && account.shortcut?.length > 0) { - ipcMain.emit(IPC_EVENTS.CHANGE_SHORTCUT, undefined, account.shortcut) - } }) async function startApp(attempt = 0) { @@ -687,6 +680,20 @@ async function createNethLink(show: boolean = true) { await delay(1000) new PhoneIslandController() checkForUpdate() + const account = store.get('account') as Account + if (account) { + // read preffered devices for phone-island + Log.info("Preferred devices readed:", account.preferredDevices) + if (account.preferredDevices) { + ipcMain.emit(IPC_EVENTS.CHANGE_PREFERRED_DEVICES, undefined, account.preferredDevices) + } + + // read shortcut from config and set it to app + Log.info("Shortcut readed:", account.shortcut) + if (account.shortcut && account.shortcut?.length > 0) { + ipcMain.emit(IPC_EVENTS.CHANGE_SHORTCUT, undefined, account.shortcut) + } + } } async function checkForUpdate() { diff --git a/src/renderer/public/locales/en/translations.json b/src/renderer/public/locales/en/translations.json index 24187630..921078c8 100644 --- a/src/renderer/public/locales/en/translations.json +++ b/src/renderer/public/locales/en/translations.json @@ -321,7 +321,7 @@ "Upload image to continue": "Upload image to continue", "Wrong file type": "Wrong file type", "Delete profile picture": "Delete profile picture", - "Devices": "Devices", + "Devices": "Audio and Video", "Time preferences": "Time preferences", "Login/logout preferences": "Login/logout preferences", "Logout from queue automatically": "Logout from queue automatically", @@ -367,8 +367,12 @@ "Shortcut": "Shortcut", "Keyboard shortcut to call": "Keyboard shortcut to call", "Shortcut title description": "Choose a keyboard shortcut to dial any selected number. ", - "Shortcut subtitle description": "Warning: avoid using system-reserved shortcuts.", - "Shortcut body description": "Keys not supported: tab, capsLock, numLock, insert, esc, shift" + "Shortcut subtitle description": "Avoid using system-reserved shortcuts.", + "Shortcut body description": "Keys not supported: tab, capsLock, numLock, insert, esc, shift", + "Preferred devices": "Audio and Video settings", + "Microphone": "Microphone", + "Speaker": "Speaker", + "Camera": "Camera" }, "OperatorDrawer": { "Book": "Book", @@ -893,7 +897,8 @@ "Try changing your search query": "Try changing your search query", "Keys configuration information tooltip": "Configure line keys of your physical device", "Key position information tooltip": "Choose a position for your key", - "Click on confirm": "Click on Confirm" + "Click on confirm": "Click on Confirm", + "Inline warning message devices": "Changes to audio and video settings can only be applied if the pair device is set to NethLink and your status is Online." }, "DropdownContent": { "Microphones": "MICROPHONES", diff --git a/src/renderer/public/locales/it/translations.json b/src/renderer/public/locales/it/translations.json index c4644779..daa6c0f4 100644 --- a/src/renderer/public/locales/it/translations.json +++ b/src/renderer/public/locales/it/translations.json @@ -321,7 +321,7 @@ "Upload image to continue": "Carica immagine per continuare", "Wrong file type": "Formato file errato", "Delete profile picture": "Elimina immagine profilo", - "Devices": "Dispositivi", + "Devices": "Audio e Video", "Time preferences": "Preferenze di tempo", "Login/logout preferences": "Preferenze di entrata/uscita", "Logout from queue automatically": "Esci dalla coda automaticamente", @@ -367,8 +367,12 @@ "Shortcut": "Scorciatoia", "Keyboard shortcut to call": "Scorciatoia per chiamare", "Shortcut title description": "Scegli una combinazione di tasti per avviare una chiamata dopo aver selezionato un testo.", - "Shortcut subtitle description": "Attenzione: non usare scorciatoie già presenti nel sistema.", - "Shortcut body description": "Tasti non consentiti: tab, capsLock, numLock, insert, esc, shift" + "Shortcut subtitle description": "Non usare scorciatoie già presenti nel sistema.", + "Shortcut body description": "Tasti non consentiti: tab, capsLock, numLock, insert, esc, shift", + "Preferred devices": "Impostazioni Audio e Video", + "Microphone": "Microfono", + "Speaker": "Altoparlante", + "Camera": "Video" }, "OperatorDrawer": { "Book": "Prenota", @@ -893,7 +897,8 @@ "Try changing your search query": "Prova a cambiare la tua ricerca", "Keys configuration information tooltip": "Configura i tasti linea del tuo telefono", "Key position information tooltip": "Scegli una posizione per il pulsante", - "Click on confirm": "Clicca su Conferma" + "Click on confirm": "Clicca su Conferma", + "Inline warning message devices": "Le modifiche relative alle impostazioni audio e video possono essere applicate solo se il dispositivo abbinato è NethLink e il proprio stato è Online." }, "DropdownContent": { "Microphones": "MICROFONI", diff --git a/src/renderer/src/components/Modules/NethVoice/BaseModule/Navbar.tsx b/src/renderer/src/components/Modules/NethVoice/BaseModule/Navbar.tsx index 9799f823..46495634 100644 --- a/src/renderer/src/components/Modules/NethVoice/BaseModule/Navbar.tsx +++ b/src/renderer/src/components/Modules/NethVoice/BaseModule/Navbar.tsx @@ -10,6 +10,7 @@ import { SearchBox } from '../SearchResults/SearchBox' import { ProfileDialog } from './ProfileDialog' import { PresenceForwardDialog } from './ProfileDialog/PresenceSettings/PresenceForwardDialog' import { SettingsShortcutDialog } from './ProfileDialog/SettingsSettings/SettingsShortcutDialog' +import { SettingsDeviceDialog } from './ProfileDialog/SettingsSettings/SettingsDevicesDialog' export interface NavbarProps { onClickAccount: () => void @@ -21,6 +22,7 @@ export function Navbar({ onClickAccount }: NavbarProps): JSX.Element { const [operators] = useNethlinkData('operators') const [isForwardDialogOpen] = useNethlinkData('isForwardDialogOpen') const [isShortcutDialogOpen] = useNethlinkData('isShortcutDialogOpen') + const [isDeviceDialogOpen] = useNethlinkData('isDeviceDialogOpen') const [isProfileDialogOpen, setIsProfileDialogOpen] = useState(false) @@ -53,6 +55,7 @@ export function Navbar({ onClickAccount }: NavbarProps): JSX.Element { {isForwardDialogOpen && } {isShortcutDialogOpen && } + {isDeviceDialogOpen && } setIsProfileDialogOpen(false)} diff --git a/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsBox.tsx b/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsBox.tsx index 0041367a..1ec46b79 100644 --- a/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsBox.tsx +++ b/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsBox.tsx @@ -1,12 +1,14 @@ import { t } from 'i18next' import { faKeyboard as KeyboardIcon, + faHeadphones as DevicesIcon, } from '@fortawesome/free-solid-svg-icons' import { useNethlinkData } from '@renderer/store' import { OptionElement } from '../OptionElement' export function SettingsBox({ onClose }: { onClose?: () => void }) { const [, setIsShortcutDialogOpen] = useNethlinkData('isShortcutDialogOpen') + const [, setIsDeviceDialogOpen] = useNethlinkData('isDeviceDialogOpen') return (
@@ -19,6 +21,15 @@ export function SettingsBox({ onClose }: { onClose?: () => void }) { if (onClose) onClose() }} /> + { + setIsDeviceDialogOpen(true) + if (onClose) onClose() + }} + />
) } diff --git a/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsDevicesDialog.tsx b/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsDevicesDialog.tsx new file mode 100644 index 00000000..c4fb824f --- /dev/null +++ b/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsDevicesDialog.tsx @@ -0,0 +1,339 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { Button, TextInput } from '@renderer/components/Nethesis' +import { useNethlinkData, useSharedState } from '@renderer/store' +import { IPC_EVENTS } from '@shared/constants' +import { t } from 'i18next' +import { useCallback, useEffect, useState } from 'react' +import { Controller, useForm } from 'react-hook-form' +import { z } from 'zod' +import { Backdrop } from '../../Backdrop' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { + faMicrophone as AudioInputsIcon, + faVolumeHigh as AudioOutputsIcon, + faVideo as VideoInputsIcon, + faChevronDown as DropdownIcon, + faCheck as SelectedIcon, +} from '@fortawesome/free-solid-svg-icons' +import { Tooltip } from 'react-tooltip' +import { PreferredDevices } from '@shared/types' +import { Dropdown } from '@renderer/components/Nethesis/dropdown' +import { DropdownItem } from '@renderer/components/Nethesis/dropdown/DropdownItem' +import { DropdownHeader } from '@renderer/components/Nethesis/dropdown/DropdownHeader' +import { Log } from '@shared/utils/logger' +import { delay, isDev } from '@shared/utils/utils' +import { InlineNotification } from '@renderer/components/Nethesis/InlineNotification' + +type DeviceType = 'audioInput' | 'audioOutput' | 'videoInput' +const LOCALSTORAGE_KEYS = { + audioInput: 'phone-island-audio-input-device', + audioOutput: 'phone-island-audio-output-device', + videoInput: 'phone-island-video-input-device' +} as const + +const getDeviceFromLocalStorage = (deviceType: DeviceType): string | null => { + try { + const rawValue = localStorage.getItem(LOCALSTORAGE_KEYS[deviceType]) + if (!rawValue) return null + + try { + const parsed = JSON.parse(rawValue) + return parsed.deviceId || null + } catch (parseError) { + console.warn(`localStorage value for ${deviceType} is not valid JSON, using raw value:`, rawValue) + return rawValue + } + } catch (error) { + console.warn(`Error reading ${deviceType} from localStorage:`, error) + return null + } +} + +const setDeviceToLocalStorage = (deviceType: DeviceType, value: string): void => { + try { + const jsonValue = JSON.stringify({ deviceId: value }) + localStorage.setItem(LOCALSTORAGE_KEYS[deviceType], jsonValue) + } catch (error) { + console.warn(`Error saving ${deviceType} to localStorage:`, error) + } +} + +export function SettingsDeviceDialog() { + const [account, setAccount] = useSharedState('account') + const [, setIsDeviceDialogOpen] = useNethlinkData('isDeviceDialogOpen') + const [devices, setDevices] = useState<{ + audioInput: MediaDeviceInfo[] + audioOutput: MediaDeviceInfo[] + videoInput: MediaDeviceInfo[] + } | null>(null) + + const schema: z.ZodType = z.object({ + audioInput: z.string(), + audioOutput: z.string(), + videoInput: z.string(), + }) + + const { handleSubmit, control, setValue } = useForm({ + defaultValues: { + audioInput: '', + audioOutput: '', + videoInput: '', + }, + resolver: zodResolver(schema), + }) + + useEffect(() => { + initDevices() + }, []) + + useEffect(() => { + console.log(account?.preferredDevices) + const getEffectiveValue = (deviceType: DeviceType): string => { + const localStorageValue = getDeviceFromLocalStorage(deviceType) + const storeValue = account?.preferredDevices?.[deviceType] + + // if data exists on localstorage read it + if (localStorageValue) { + return localStorageValue + } + + // otherwise return store value + return storeValue || '' + } + + setValue('audioInput', getEffectiveValue('audioInput')) + setValue('audioOutput', getEffectiveValue('audioOutput')) + setValue('videoInput', getEffectiveValue('videoInput')) + }, [account?.preferredDevices]) + + useEffect(() => { + const handleStorageChange = (e: StorageEvent) => { + // check if localstorage keys has changed + const deviceTypes = Object.keys(LOCALSTORAGE_KEYS) as DeviceType[] + const changedDeviceType = deviceTypes.find( + type => e.key === LOCALSTORAGE_KEYS[type] + ) + + if (changedDeviceType && e.newValue) { + console.log(`localStorage changed for ${changedDeviceType}:`, e.newValue) + setValue(changedDeviceType, e.newValue) + } + } + + window.addEventListener('storage', handleStorageChange) + return () => window.removeEventListener('storage', handleStorageChange) + }, [setValue]) + + const getDeviceById = useCallback( + (type: DeviceType, id: string): MediaDeviceInfo | undefined => { + if (!devices) return undefined + return devices[type].find((d) => d.deviceId === id) + }, + [devices], + ) + + const initDevices = async () => { + const devices = await getMediaDevices() + Log.info('Available devices:', devices) + setDevices(devices) + } + + async function getMediaDevices() { + try { + const devices = await navigator.mediaDevices.enumerateDevices() + + const audioInput = devices.filter((device) => device.kind === 'audioinput') + const audioOutput = devices.filter((device) => device.kind === 'audiooutput') + const videoInput = devices.filter((device) => device.kind === 'videoinput') + + return { audioInput, audioOutput, videoInput } + } catch (err) { + console.error('Error reading audio and video devices:', err) + return null + } + } + + function handleCancel(e) { + e.preventDefault() + e.stopPropagation() + setIsDeviceDialogOpen(false) + } + + async function submit(data) { + const preferredDevices = { + ...data, + } + const updatedAccount = { ...account!, preferredDevices } + setAccount(() => updatedAccount) + Object.entries(data).forEach(([deviceType, value]) => { + setDeviceToLocalStorage(deviceType as DeviceType, value as string) + }) + await delay(100) + window.electron.send(IPC_EVENTS.CHANGE_PREFERRED_DEVICES, data) + setIsDeviceDialogOpen(false) + } + + const icons = { + audioInput: AudioInputsIcon, + audioOutput: AudioOutputsIcon, + videoInput: VideoInputsIcon, + } + + const fieldLabels = { + audioInput: t('TopBar.Microphone'), + audioOutput: t('TopBar.Speaker'), + videoInput: t('TopBar.Camera'), + } + + const isDeviceUnavailable = + account?.data?.default_device?.type == 'webrtc' || + account?.data?.mainPresence !== 'online' + + const DeviceDropdown = useCallback( + ({ name }: { name: DeviceType }) => { + return ( +
+
+ + {fieldLabels[name]} +
+
+ { + const selectedDevice = getDeviceById(name, value) + return ( + { + return ( + { + console.log('change device:', device.deviceId) + onChange(device.deviceId) + }} + > +
+ + {device.label} + + +
+
+ ) + })} + className='w-full' + > + +
+ + {selectedDevice?.label || '-'} + + +
+
+
+ +
+
+ ) + }} + /> +
+
+ ) + }, + [account?.preferredDevices, devices], + ) + + return ( + <> + {/* Background color */} +
+ + {/* On external click close dialog */} + setIsDeviceDialogOpen(false)} + /> + +
+
+
+ {/* Dialog content */} +
+ {/* Title */} +

+ {t('TopBar.Preferred devices')} +

+ + {/* Form */} +
+ {/* Input field with clear button next to it */} + + + + + {/* Inline notification */} + {isDeviceUnavailable && ( + +

{t('Devices.Inline warning message devices')}

+
+ )} + {/* Action buttons */} +
+ + + +
+ +
+
+
+
+ + ) +} diff --git a/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsShortcutDialog.tsx b/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsShortcutDialog.tsx index 8c820ad3..4c521498 100644 --- a/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsShortcutDialog.tsx +++ b/src/renderer/src/components/Modules/NethVoice/BaseModule/ProfileDialog/SettingsSettings/SettingsShortcutDialog.tsx @@ -10,6 +10,7 @@ import { Backdrop } from '../../Backdrop' import { CustomThemedTooltip } from '@renderer/components/Nethesis/CurstomThemedTooltip' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faXmark } from '@fortawesome/free-solid-svg-icons' +import { InlineNotification } from '@renderer/components/Nethesis/InlineNotification' export function SettingsShortcutDialog() { const [account, setAccount] = useSharedState('account') @@ -133,7 +134,8 @@ export function SettingsShortcutDialog() { onBackdropClick={() => setIsShortcutDialogOpen(false)} /> -
+
+
{/* Dialog content */}
@@ -146,9 +148,16 @@ export function SettingsShortcutDialog() {

{t('TopBar.Shortcut title description')}{' '}

-

- {t('TopBar.Shortcut subtitle description')}{' '} -

+ {/* Inline notification */} + {( + +

{t('TopBar.Shortcut subtitle description')}

+
+ )} {/* Form */}
+
) diff --git a/src/renderer/src/components/Modules/NethVoice/LastCalls/LastCallsBox.tsx b/src/renderer/src/components/Modules/NethVoice/LastCalls/LastCallsBox.tsx index 4af4f0fb..864f9291 100644 --- a/src/renderer/src/components/Modules/NethVoice/LastCalls/LastCallsBox.tsx +++ b/src/renderer/src/components/Modules/NethVoice/LastCalls/LastCallsBox.tsx @@ -37,6 +37,9 @@ export function LastCallsBox({ showContactForm }): JSX.Element { hasNotification: missedCalls?.map((c) => c.uniqueid).includes(c.uniqueid) || false } return elem + }).filter((call) => { + const numberToCheck = call.direction === 'in' ? call.src : call.dst + return !numberToCheck?.includes('*43') }) setPreparedCalls((p) => preparedCalls) } diff --git a/src/renderer/src/components/Nethesis/InlineNotification.tsx b/src/renderer/src/components/Nethesis/InlineNotification.tsx new file mode 100644 index 00000000..23fee601 --- /dev/null +++ b/src/renderer/src/components/Nethesis/InlineNotification.tsx @@ -0,0 +1,76 @@ +// Copyright (C) 2024 Nethesis S.r.l. +// SPDX-License-Identifier: AGPL-3.0-or-later + +/** + * + * It can be used to render an Alert + * + * @param children - The children/s to render. + * @param type - The alert type. + * + */ + +import classNames from 'classnames' +import { FC, ComponentProps } from 'react' +import { useTheme } from '../../theme/Context' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { + faCircleXmark, + faCircleInfo, + faTriangleExclamation, + faCircleCheck, +} from '@fortawesome/free-solid-svg-icons' + +export interface InlineNotificationProps extends ComponentProps<'div'> { + type: 'info' | 'warning' | 'success' | 'error' + title: string +} + +export const InlineNotification: FC = ({ + type, + title, + children, + className, +}): JSX.Element => { + const { inlineNotification: theme } = useTheme().theme + + const icon = + type === 'info' ? ( +