diff --git a/package-lock.json b/package-lock.json index 90f99ec..04d00e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "dependencies": { "@internxt/css-config": "^1.1.0", "@internxt/lib": "^1.4.1", - "@internxt/sdk": "^1.16.2", - "@internxt/ui": "^0.1.12", + "@internxt/sdk": "^1.16.3", + "@internxt/ui": "^0.1.16", "@phosphor-icons/react": "^2.1.10", "@reduxjs/toolkit": "^2.11.2", "@tailwindcss/vite": "^4.2.1", @@ -1084,25 +1084,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.4", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.5", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.7", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.5" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -1110,7 +1116,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@humanfs/core": { @@ -1445,18 +1453,18 @@ "license": "MIT" }, "node_modules/@internxt/sdk": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/@internxt/sdk/-/sdk-1.16.2.tgz", - "integrity": "sha512-hTtOxR94v1MsFbV2eX1CNDf5L7BNoGFScn+SNDipzT17PY0LhHm2vVOa1ncRFjqR0FUBqGi3Hpm+T8/4aQefxQ==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@internxt/sdk/-/sdk-1.16.3.tgz", + "integrity": "sha512-GmX9eYBOBB09wr5e9yW3gUIR3Pn2AgZBXzZd1HvzwS96AonclcEWY1/+uoZ9qLO4SdvYquiOjXfLYNJGg99ugQ==", "license": "MIT", "dependencies": { "axios": "^1.16.0" } }, "node_modules/@internxt/ui": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@internxt/ui/-/ui-0.1.12.tgz", - "integrity": "sha512-ADhUIyEb0KQT3EAUHPmzyVVIiwhHgCuYykyv7ZNc7XUVC2nFu47FuP99qlieTh11/xB2q4Lfh4ul9+keLTYVug==", + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@internxt/ui/-/ui-0.1.16.tgz", + "integrity": "sha512-V9VQrUVZehvx5L3FChkCfwmOq/wnQsFSSjcfGHMb70SVKZQAfCSv3ZyjcnL5iIaLInWGwtwspmtMfl3qh0zVWQ==", "license": "MIT", "dependencies": { "@internxt/css-config": "1.1.0", @@ -1581,18 +1589,26 @@ }, "node_modules/@radix-ui/colors": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", + "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==", "license": "MIT" }, "node_modules/@radix-ui/number": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, "node_modules/@radix-ui/react-accessible-icon": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", "license": "MIT", "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" @@ -1614,6 +1630,8 @@ }, "node_modules/@radix-ui/react-accordion": { "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1643,6 +1661,8 @@ }, "node_modules/@radix-ui/react-alert-dialog": { "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1669,6 +1689,8 @@ }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -1690,6 +1712,8 @@ }, "node_modules/@radix-ui/react-aspect-ratio": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -1711,6 +1735,8 @@ }, "node_modules/@radix-ui/react-avatar": { "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", "license": "MIT", "dependencies": { "@radix-ui/react-context": "1.1.2", @@ -1736,6 +1762,8 @@ }, "node_modules/@radix-ui/react-checkbox": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1764,6 +1792,8 @@ }, "node_modules/@radix-ui/react-collapsible": { "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1792,6 +1822,8 @@ }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -1816,6 +1848,8 @@ }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1829,6 +1863,8 @@ }, "node_modules/@radix-ui/react-context": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1842,6 +1878,8 @@ }, "node_modules/@radix-ui/react-context-menu": { "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1868,6 +1906,8 @@ }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1902,6 +1942,8 @@ }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1915,6 +1957,8 @@ }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1940,6 +1984,8 @@ }, "node_modules/@radix-ui/react-dropdown-menu": { "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1967,6 +2013,8 @@ }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1980,6 +2028,8 @@ }, "node_modules/@radix-ui/react-focus-scope": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -2003,6 +2053,8 @@ }, "node_modules/@radix-ui/react-form": { "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2029,6 +2081,8 @@ }, "node_modules/@radix-ui/react-hover-card": { "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2058,6 +2112,8 @@ }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -2074,6 +2130,8 @@ }, "node_modules/@radix-ui/react-label": { "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -2095,6 +2153,8 @@ }, "node_modules/@radix-ui/react-menu": { "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2133,6 +2193,8 @@ }, "node_modules/@radix-ui/react-menubar": { "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2163,6 +2225,8 @@ }, "node_modules/@radix-ui/react-navigation-menu": { "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2197,6 +2261,8 @@ }, "node_modules/@radix-ui/react-one-time-password-field": { "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -2229,6 +2295,8 @@ }, "node_modules/@radix-ui/react-password-toggle-field": { "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2257,6 +2325,8 @@ }, "node_modules/@radix-ui/react-popover": { "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2292,6 +2362,8 @@ }, "node_modules/@radix-ui/react-popper": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", @@ -2322,6 +2394,8 @@ }, "node_modules/@radix-ui/react-portal": { "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3", @@ -2344,6 +2418,8 @@ }, "node_modules/@radix-ui/react-presence": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -2366,6 +2442,8 @@ }, "node_modules/@radix-ui/react-primitive": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.2.3" @@ -2387,6 +2465,8 @@ }, "node_modules/@radix-ui/react-progress": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", "license": "MIT", "dependencies": { "@radix-ui/react-context": "1.1.2", @@ -2409,6 +2489,8 @@ }, "node_modules/@radix-ui/react-radio-group": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2439,6 +2521,8 @@ }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2468,6 +2552,8 @@ }, "node_modules/@radix-ui/react-scroll-area": { "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -2497,6 +2583,8 @@ }, "node_modules/@radix-ui/react-select": { "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -2538,6 +2626,8 @@ }, "node_modules/@radix-ui/react-separator": { "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -2559,6 +2649,8 @@ }, "node_modules/@radix-ui/react-slider": { "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -2590,6 +2682,8 @@ }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -2606,6 +2700,8 @@ }, "node_modules/@radix-ui/react-switch": { "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2633,6 +2729,8 @@ }, "node_modules/@radix-ui/react-tabs": { "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2661,6 +2759,8 @@ }, "node_modules/@radix-ui/react-toast": { "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2693,6 +2793,8 @@ }, "node_modules/@radix-ui/react-toggle": { "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2716,6 +2818,8 @@ }, "node_modules/@radix-ui/react-toggle-group": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2743,6 +2847,8 @@ }, "node_modules/@radix-ui/react-toolbar": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2770,6 +2876,8 @@ }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -2802,6 +2910,8 @@ }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2815,6 +2925,8 @@ }, "node_modules/@radix-ui/react-use-controllable-state": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", "license": "MIT", "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", @@ -2832,6 +2944,8 @@ }, "node_modules/@radix-ui/react-use-effect-event": { "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -2848,6 +2962,8 @@ }, "node_modules/@radix-ui/react-use-escape-keydown": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", "license": "MIT", "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" @@ -2864,6 +2980,8 @@ }, "node_modules/@radix-ui/react-use-is-hydrated": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", "license": "MIT", "dependencies": { "use-sync-external-store": "^1.5.0" @@ -2880,6 +2998,8 @@ }, "node_modules/@radix-ui/react-use-layout-effect": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2893,6 +3013,8 @@ }, "node_modules/@radix-ui/react-use-previous": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2906,6 +3028,8 @@ }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", "license": "MIT", "dependencies": { "@radix-ui/rect": "1.1.1" @@ -2922,6 +3046,8 @@ }, "node_modules/@radix-ui/react-use-size": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -2938,6 +3064,8 @@ }, "node_modules/@radix-ui/react-visually-hidden": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -2959,10 +3087,14 @@ }, "node_modules/@radix-ui/rect": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, "node_modules/@radix-ui/themes": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/themes/-/themes-3.3.0.tgz", + "integrity": "sha512-I0/h2CRNTpYNB7Mi3xFIvSsQq5a108d7kK8dTO5zp5b9HR5QJXKag6B8tjpz2ITkVYkFdkGk45doNkSr7OxwNw==", "license": "MIT", "dependencies": { "@radix-ui/colors": "^3.0.0", @@ -5026,6 +5158,8 @@ }, "node_modules/aria-hidden": { "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -5496,6 +5630,8 @@ }, "node_modules/classnames": { "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, "node_modules/cli-cursor": { @@ -5891,6 +6027,8 @@ }, "node_modules/detect-node-es": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, "node_modules/diffie-hellman": { @@ -6558,6 +6696,8 @@ }, "node_modules/get-nonce": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", "engines": { "node": ">=6" @@ -8462,6 +8602,8 @@ }, "node_modules/radix-ui": { "version": "1.4.3", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -8713,6 +8855,8 @@ }, "node_modules/react-remove-scroll": { "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -8736,6 +8880,8 @@ }, "node_modules/react-remove-scroll-bar": { "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { "react-style-singleton": "^2.2.2", @@ -8790,6 +8936,8 @@ }, "node_modules/react-style-singleton": { "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", @@ -9627,6 +9775,8 @@ }, "node_modules/use-callback-ref": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -9646,6 +9796,8 @@ }, "node_modules/use-sidecar": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", diff --git a/package.json b/package.json index be68dbc..19cf4a8 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "dependencies": { "@internxt/css-config": "^1.1.0", "@internxt/lib": "^1.4.1", - "@internxt/sdk": "^1.16.2", - "@internxt/ui": "^0.1.12", + "@internxt/sdk": "^1.16.3", + "@internxt/ui": "^0.1.16", "@phosphor-icons/react": "^2.1.10", "@reduxjs/toolkit": "^2.11.2", "@tailwindcss/vite": "^4.2.1", diff --git a/src/components/Sidenav/index.tsx b/src/components/Sidenav/index.tsx index 361f76c..a446ae7 100644 --- a/src/components/Sidenav/index.tsx +++ b/src/components/Sidenav/index.tsx @@ -6,20 +6,26 @@ import { useTranslationContext } from '@/i18n'; import { NavigationService } from '@/services/navigation'; import { AppView } from '@/routes/paths'; import type { RootState } from '@/store'; -import { HUNDRED_TB } from '@/constants'; +import { HUNDRED_TB, INTERNXT_BASE_URL } from '@/constants'; import { useSuiteLauncher } from '@/hooks/navigation/useSuiteLauncher'; import { useSidenavNavigation } from '@/hooks/navigation/useSidenavNavigation'; -import { useGetStorageLimitQuery, useGetStorageUsageQuery } from '@/store/api/storage'; import { useAppSelector } from '@/store/hooks'; import { bytesToString } from '@/utils/bytes-to-string'; import { ActionDialog, useActionDialog } from '@/context/dialog-manager'; +import { useSidenavData } from './useSidenavData'; const Sidenav = () => { const { translate } = useTranslationContext(); const { userSubscription: subscription } = useAppSelector((state: RootState) => state.user); - const { isLoading: isLoadingPlanLimit, data: planLimit = 1 } = useGetStorageLimitQuery(); - const { isLoading: isLoadingPlanUsage, data: planUsage = 0 } = useGetStorageUsageQuery(); - const storagePercentage = planLimit > 0 ? Math.min((planUsage / planLimit) * 100, 100) : 0; + const { + isMailDisabled, + daysUntilDeletion, + planLimit, + planUsage, + isLoadingPlanLimit, + isLoadingPlanUsage, + storagePercentage, + } = useSidenavData(); const { openDialog } = useActionDialog(); const { itemsNavigation } = useSidenavNavigation(); @@ -64,10 +70,20 @@ const Sidenav = () => { className: '!pt-0 pb-3', }} primaryAction={ - } + notification={ + isMailDisabled + ? { + message: translate('mailDowngraded.message', { days: daysUntilDeletion ?? '--' }), + actionLabel: translate('mailDowngraded.upgrade'), + onAction: () => window.open(`${INTERNXT_BASE_URL}/pricing`, '_blank', 'noopener'), + type: 'warning', + } + : undefined + } suiteLauncher={{ suiteArray: suiteArray, soonText: translate('modals.upgradePlanDialog.soonBadge'), diff --git a/src/components/Sidenav/useSidenavData.test.ts b/src/components/Sidenav/useSidenavData.test.ts new file mode 100644 index 0000000..acccafc --- /dev/null +++ b/src/components/Sidenav/useSidenavData.test.ts @@ -0,0 +1,188 @@ +import { renderHook } from '@testing-library/react'; +import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'; +import { useSidenavData } from './useSidenavData'; +import { useGetStorageLimitQuery, useGetStorageUsageQuery } from '@/store/api/storage'; +import { useGetMailMeQuery } from '@/store/api/mail'; +import type { MailAccountResponse } from '@internxt/sdk/dist/mail/types'; + +vi.mock('@/store/api/storage', () => ({ + useGetStorageLimitQuery: vi.fn(), + useGetStorageUsageQuery: vi.fn(), +})); + +vi.mock('@/store/api/mail', () => ({ + useGetMailMeQuery: vi.fn(), +})); + +const mockUseGetStorageLimitQuery = vi.mocked(useGetStorageLimitQuery); +const mockUseGetStorageUsageQuery = vi.mocked(useGetStorageUsageQuery); +const mockUseGetMailMeQuery = vi.mocked(useGetMailMeQuery); + +const setupMocks = ({ + planLimit = 100, + planUsage = 50, + isLoadingPlanLimit = false, + isLoadingPlanUsage = false, + mailMe = undefined as MailAccountResponse | undefined, +} = {}) => { + mockUseGetStorageLimitQuery.mockReturnValue({ + data: planLimit, + isLoading: isLoadingPlanLimit, + } as unknown as ReturnType); + mockUseGetStorageUsageQuery.mockReturnValue({ + data: planUsage, + isLoading: isLoadingPlanUsage, + } as unknown as ReturnType); + mockUseGetMailMeQuery.mockReturnValue({ data: mailMe } as unknown as ReturnType); +}; + +describe('useSidenavData', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + describe('Mail account status', () => { + test('When the mail account is active, then the mail features are reported as available', () => { + setupMocks({ mailMe: { id: '1', defaultAddress: 'user@inxt.me', status: 'active' } }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.isMailDisabled).toBe(false); + }); + + test('When the mail account is suspended, then the mail features are reported as disabled', () => { + setupMocks({ + mailMe: { + id: '1', + defaultAddress: 'user@inxt.me', + status: 'suspended', + suspendedAt: '2026-05-01T00:00:00.000Z', + deletionAt: '2026-06-01T00:00:00.000Z', + }, + }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.isMailDisabled).toBe(true); + }); + + test('When no mail account information is available, then the mail features are not reported as disabled', () => { + setupMocks({ mailMe: undefined }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.isMailDisabled).toBe(false); + }); + }); + + describe('Days until deletion', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-05-11T12:00:00.000Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + test('When the account has a scheduled deletion date in the future, then the remaining days until deletion are reported', () => { + setupMocks({ + mailMe: { + id: '1', + defaultAddress: 'user@inxt.me', + status: 'suspended', + deletionAt: '2026-05-16T12:00:00.000Z', + }, + }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.daysUntilDeletion).toBe(5); + }); + + test('When the account has no scheduled deletion date, then no remaining days are reported', () => { + setupMocks({ mailMe: { id: '1', defaultAddress: 'user@inxt.me', status: 'active' } }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.daysUntilDeletion).toBeUndefined(); + }); + + test('When the scheduled deletion date has already passed, then no days are reported as remaining', () => { + setupMocks({ + mailMe: { + id: '1', + defaultAddress: 'user@inxt.me', + status: 'suspended', + deletionAt: '2026-05-01T00:00:00.000Z', + }, + }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.daysUntilDeletion).toBe(0); + }); + }); + + describe('Storage percentage', () => { + test('When the used storage is half of the available storage, then the reported usage is fifty percent', () => { + setupMocks({ planUsage: 500, planLimit: 1000 }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.storagePercentage).toBe(50); + }); + + test('When the used storage exceeds the available storage, then the reported usage is capped at one hundred percent', () => { + setupMocks({ planUsage: 1500, planLimit: 1000 }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.storagePercentage).toBe(100); + }); + + test('When the available storage is zero, then the reported usage is zero percent to avoid division by zero', () => { + setupMocks({ planUsage: 100, planLimit: 0 }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.storagePercentage).toBe(0); + }); + + test('When the storage information is still being fetched, then the loading state is reported as in progress', () => { + setupMocks({ isLoadingPlanLimit: true, isLoadingPlanUsage: true }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.isLoadingPlanLimit).toBe(true); + expect(result.current.isLoadingPlanUsage).toBe(true); + }); + + test('When the storage information has finished loading, then the used and available storage values are returned', () => { + setupMocks({ planUsage: 200, planLimit: 800 }); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.planUsage).toBe(200); + expect(result.current.planLimit).toBe(800); + }); + + test('When the storage information is missing, then the reported usage is zero percent and the values fall back to safe defaults', () => { + mockUseGetStorageLimitQuery.mockReturnValue({ data: undefined, isLoading: false } as unknown as ReturnType< + typeof useGetStorageLimitQuery + >); + mockUseGetStorageUsageQuery.mockReturnValue({ data: undefined, isLoading: false } as unknown as ReturnType< + typeof useGetStorageUsageQuery + >); + mockUseGetMailMeQuery.mockReturnValue({ data: undefined } as unknown as ReturnType); + + const { result } = renderHook(() => useSidenavData()); + + expect(result.current.storagePercentage).toBe(0); + expect(result.current.planUsage).toBe(0); + expect(result.current.planLimit).toBe(0); + expect(result.current.isLoadingPlanLimit).toBe(false); + expect(result.current.isLoadingPlanUsage).toBe(false); + }); + }); +}); diff --git a/src/components/Sidenav/useSidenavData.ts b/src/components/Sidenav/useSidenavData.ts new file mode 100644 index 0000000..1bee6be --- /dev/null +++ b/src/components/Sidenav/useSidenavData.ts @@ -0,0 +1,24 @@ +import { useGetStorageLimitQuery, useGetStorageUsageQuery } from '@/store/api/storage'; +import { useGetMailMeQuery } from '@/store/api/mail'; +import { getDaysUntil } from '@/utils/days-until'; + +export const useSidenavData = () => { + const { isLoading: isLoadingPlanLimit, data: planLimit = 0 } = useGetStorageLimitQuery(); + const { isLoading: isLoadingPlanUsage, data: planUsage = 0 } = useGetStorageUsageQuery(); + const { data: mailMe } = useGetMailMeQuery(); + + const isMailDisabled = mailMe?.status === 'suspended'; + const daysUntilDeletion = getDaysUntil(mailMe?.deletionAt); + const storagePercentage = planLimit > 0 ? Math.min((planUsage / planLimit) * 100, 100) : 0; + + return { + mailMe, + isMailDisabled, + daysUntilDeletion, + planLimit, + planUsage, + isLoadingPlanLimit, + isLoadingPlanUsage, + storagePercentage, + }; +}; diff --git a/src/errors/mail/index.ts b/src/errors/mail/index.ts index e63d269..04821be 100644 --- a/src/errors/mail/index.ts +++ b/src/errors/mail/index.ts @@ -64,6 +64,17 @@ export class FetchMailAccountKeysError extends Error { } } +export class FetchMailMeError extends Error { + constructor( + errorMsg?: string, + public requestId?: string, + ) { + super('Error while fetching mail account status: ' + errorMsg); + + Object.setPrototypeOf(this, FetchMailMeError.prototype); + } +} + export class DeleteEmailError extends Error { constructor( errorMsg?: string, diff --git a/src/hooks/navigation/useSidenavNavigation.tsx b/src/hooks/navigation/useSidenavNavigation.tsx index 93ae34a..be616d1 100644 --- a/src/hooks/navigation/useSidenavNavigation.tsx +++ b/src/hooks/navigation/useSidenavNavigation.tsx @@ -1,7 +1,7 @@ import { useCallback, useMemo } from 'react'; import { matchPath, useLocation } from 'react-router-dom'; import { TrashIcon, TrayIcon, PaperPlaneTiltIcon, FileIcon, WarningOctagonIcon } from '@phosphor-icons/react'; -import type { SidenavOption } from '@internxt/ui/dist/components/sidenav/SidenavOptions'; +import type { SidenavOption } from '@internxt/ui'; import { useTranslationContext } from '@/i18n'; import { AppView } from '@/routes/paths'; import { NavigationService } from '@/services/navigation'; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 24c2783..1716e06 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -22,6 +22,10 @@ "upgrade": "Upgrade", "send": "Send" }, + "mailDowngraded": { + "message": "You downgraded to a plan that doesn't support Internxt Mail. Your account will be deleted in {{days}} days.", + "upgrade": "Upgrade" + }, "filter": { "all": "All", "none": "None", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 1def3b2..1c8af60 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -22,6 +22,10 @@ "upgrade": "Mejorar plan", "send": "Enviar" }, + "mailDowngraded": { + "message": "Has bajado a un plan que no incluye Internxt Mail. Tu cuenta se eliminará en {{days}} días.", + "upgrade": "Mejorar plan" + }, "filter": { "all": "Todos", "none": "Ninguno", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 54fa91c..3328d3e 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -22,6 +22,10 @@ "upgrade": "Mettre à niveau", "send": "Envoyer" }, + "mailDowngraded": { + "message": "Vous êtes passé à un plan qui ne prend pas en charge Internxt Mail. Votre compte sera supprimé dans {{days}} jours.", + "upgrade": "Mettre à niveau" + }, "filter": { "all": "Tous", "none": "Aucun", diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index b4fc6e3..c745024 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -22,6 +22,10 @@ "upgrade": "Aggiorna piano", "send": "Invia" }, + "mailDowngraded": { + "message": "Sei passato a un piano che non supporta Internxt Mail. Il tuo account verrà eliminato tra {{days}} giorni.", + "upgrade": "Aggiorna piano" + }, "filter": { "all": "Tutti", "none": "Nessuno", diff --git a/src/services/sdk/mail/index.ts b/src/services/sdk/mail/index.ts index 6f7a9aa..df6a87d 100644 --- a/src/services/sdk/mail/index.ts +++ b/src/services/sdk/mail/index.ts @@ -4,6 +4,7 @@ import type { EmailResponse, ListEmailsQuery, MailAccountKeysResponse, + MailAccountResponse, MailboxResponse, SearchFiltersQuery, SetupMailAccountPayload, @@ -11,6 +12,8 @@ import type { } from '@internxt/sdk/dist/mail/types'; import { SdkManager } from '..'; +export type MailMeResponse = MailAccountResponse; + export class MailService { public static readonly instance: MailService = new MailService(); @@ -18,6 +21,15 @@ export class MailService { return SdkManager.instance.getMail(); } + /** + * Returns the current mail account for the logged in user. + * When the account has been suspended due to a plan downgrade, `state` is + * `suspended` and `deletionAt` holds the scheduled UTC deletion timestamp. + */ + async getMe(): Promise { + return this.client.getMailAccount(); + } + /** * Creates a mail account for the user. * diff --git a/src/services/sdk/mail/mail.service.test.ts b/src/services/sdk/mail/mail.service.test.ts index c4fccd8..a183a61 100644 --- a/src/services/sdk/mail/mail.service.test.ts +++ b/src/services/sdk/mail/mail.service.test.ts @@ -3,7 +3,7 @@ import { beforeEach, afterEach, describe, expect, test, vi } from 'vitest'; import { SdkManager } from '..'; import { MailService } from '.'; import { getMockedMails, getMockedMailBoxes, getMockedMail } from '@/test-utils/fixtures'; -import type { SetupMailAccountPayload } from '@internxt/sdk/dist/mail/types'; +import type { MailAccountResponse, SetupMailAccountPayload } from '@internxt/sdk/dist/mail/types'; describe('Mail Service', () => { beforeEach(() => { @@ -14,6 +14,53 @@ describe('Mail Service', () => { vi.restoreAllMocks(); }); + describe('Get me', () => { + test('When fetching the mail account and it is active, then the account should be returned', async () => { + const mockAccount: MailAccountResponse = { + id: 'account-1', + defaultAddress: 'jane@inxt.me', + status: 'active', + }; + const mockMailClient = { + getMailAccount: vi.fn().mockResolvedValue(mockAccount), + } as any; + vi.spyOn(SdkManager.instance, 'getMail').mockReturnValue(mockMailClient); + + const result = await MailService.instance.getMe(); + + expect(result).toStrictEqual(mockAccount); + expect(mockMailClient.getMailAccount).toHaveBeenCalledOnce(); + }); + + test('When fetching the mail account and it is suspended, then its scheduled deletion date and suspension date should be present', async () => { + const mockAccount: MailAccountResponse = { + id: 'account-1', + defaultAddress: 'jane@inxt.me', + status: 'suspended', + suspendedAt: '2026-05-01T00:00:00.000Z', + deletionAt: '2026-06-01T00:00:00.000Z', + }; + const mockMailClient = { + getMailAccount: vi.fn().mockResolvedValue(mockAccount), + } as any; + vi.spyOn(SdkManager.instance, 'getMail').mockReturnValue(mockMailClient); + + const result = await MailService.instance.getMe(); + + expect(result).toStrictEqual(mockAccount); + }); + + test('When fetching the mail account fails, then an error should be thrown', async () => { + const unexpectedError = new Error('Unauthorized'); + const mockMailClient = { + getMailAccount: vi.fn().mockRejectedValue(unexpectedError), + } as any; + vi.spyOn(SdkManager.instance, 'getMail').mockReturnValue(mockMailClient); + + await expect(MailService.instance.getMe()).rejects.toThrow(unexpectedError); + }); + }); + describe('Get mailboxes info', () => { test('When fetching mailboxes, then all mailboxes should be returned', async () => { const mockedMailboxes = getMockedMailBoxes(); diff --git a/src/store/api/base.ts b/src/store/api/base.ts index 43be205..b2a847a 100644 --- a/src/store/api/base.ts +++ b/src/store/api/base.ts @@ -3,6 +3,6 @@ import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react'; export const api = createApi({ reducerPath: 'api', baseQuery: fakeBaseQuery(), - tagTypes: ['Mailbox', 'ListFolder', 'MailMessage', 'MailAccountKeys', 'StorageUsage', 'StorageLimit'], + tagTypes: ['Mailbox', 'ListFolder', 'MailMessage', 'MailAccountKeys', 'MailMe', 'StorageUsage', 'StorageLimit'], endpoints: () => ({}), }); diff --git a/src/store/api/mail/index.ts b/src/store/api/mail/index.ts index 182e6c3..e08e0f6 100644 --- a/src/store/api/mail/index.ts +++ b/src/store/api/mail/index.ts @@ -2,6 +2,7 @@ import { api } from '../base'; import { FetchMailAccountKeysError, FetchMailboxesInfoError, + FetchMailMeError, FetchMessageError, FetchListFolderError, MAIL_NOT_SETUP_CODE, @@ -10,7 +11,7 @@ import { DeleteEmailError, } from '@/errors'; import { ErrorService } from '@/services/error'; -import { MailService } from '@/services/sdk/mail'; +import { MailService, type MailMeResponse } from '@/services/sdk/mail'; import type { FolderType } from '@/types/mail'; import { batchProcess } from '@/utils/batch-processes'; import type { @@ -77,6 +78,18 @@ export const mailApi = api.injectEndpoints({ }, providesTags: ['MailAccountKeys'], }), + getMailMe: builder.query({ + async queryFn(): Promise<{ data: MailMeResponse } | { error: FetchMailMeError }> { + try { + const me = await MailService.instance.getMe(); + return { data: me }; + } catch (error) { + const err = ErrorService.instance.castError(error); + return { error: new FetchMailMeError(err.message, err.requestId) }; + } + }, + providesTags: ['MailMe'], + }), getMailboxesInfo: builder.query({ async queryFn(): Promise<{ data: MailboxResponse[] } | { error: FetchMailboxesInfoError }> { try { @@ -217,6 +230,7 @@ export const mailApi = api.injectEndpoints({ export const { useGetMailAccountKeysQuery, + useGetMailMeQuery, useGetMailboxesInfoQuery, useGetListFolderQuery, useGetMailMessageQuery, diff --git a/src/store/api/mail/mail.api.test.ts b/src/store/api/mail/mail.api.test.ts index f14a527..7cd274c 100644 --- a/src/store/api/mail/mail.api.test.ts +++ b/src/store/api/mail/mail.api.test.ts @@ -5,6 +5,7 @@ import { FetchListFolderError, FetchMailAccountKeysError, FetchMailboxesInfoError, + FetchMailMeError, FetchMessageError, MAIL_NOT_SETUP_CODE, MailNotSetupError, @@ -60,6 +61,45 @@ describe('Mail API', () => { vi.clearAllMocks(); }); + describe('Get Mail Me', () => { + test('When fetching the mail account and it is active, then it should return the account data', async () => { + const mockAccount = { id: 'account-1', defaultAddress: 'jane@inxt.me', status: 'active' as const }; + vi.spyOn(MailService.instance, 'getMe').mockResolvedValue(mockAccount); + const store = createTestStore(); + + const result = await store.dispatch(mailApi.endpoints.getMailMe.initiate()); + + expect(result.data).toStrictEqual(mockAccount); + }); + + test('When fetching the mail account and it is suspended, then it should return the suspended account', async () => { + const mockAccount = { + id: 'account-1', + defaultAddress: 'jane@inxt.me', + status: 'suspended' as const, + suspendedAt: '2026-05-01T00:00:00.000Z', + deletionAt: '2026-06-01T00:00:00.000Z', + }; + vi.spyOn(MailService.instance, 'getMe').mockResolvedValue(mockAccount); + const store = createTestStore(); + + const result = await store.dispatch(mailApi.endpoints.getMailMe.initiate()); + + expect(result.data).toStrictEqual(mockAccount); + }); + + test('When fetching the mail account fails, then an error indicating so is thrown', async () => { + vi.spyOn(MailService.instance, 'getMe').mockRejectedValue(new Error('Network error')); + const castErrorSpy = vi.spyOn(ErrorService.instance, 'castError'); + const store = createTestStore(); + + const result = await store.dispatch(mailApi.endpoints.getMailMe.initiate()); + + expect(castErrorSpy).toHaveBeenCalledOnce(); + expect(result.error).toBeInstanceOf(FetchMailMeError); + }); + }); + describe('Get Mailboxes', () => { test('When getting the mailboxes, then it should return the list of mailboxes', async () => { const mockedMailboxes = getMockedMailBoxes(); diff --git a/src/utils/days-until/daysUntil.test.ts b/src/utils/days-until/daysUntil.test.ts new file mode 100644 index 0000000..97ccd46 --- /dev/null +++ b/src/utils/days-until/daysUntil.test.ts @@ -0,0 +1,49 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { getDaysUntil } from '.'; + +describe('Calculating the days remaining until a future date', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-05-11T12:00:00.000Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + test('When no date is provided, then no value is returned', () => { + const result = getDaysUntil(undefined); + + expect(result).toBeUndefined(); + }); + + test('When the date is several days in the future, then the remaining whole days are returned', () => { + const result = getDaysUntil('2026-05-16T12:00:00.000Z'); + + expect(result).toBe(5); + }); + + test('When the date is less than a full day away, then it is counted as one day remaining', () => { + const result = getDaysUntil('2026-05-11T13:00:00.000Z'); + + expect(result).toBe(1); + }); + + test('When the date has already passed, then no days are reported as remaining', () => { + const result = getDaysUntil('2026-05-01T00:00:00.000Z'); + + expect(result).toBe(0); + }); + + test('When the date is exactly the current moment, then no days are reported as remaining', () => { + const result = getDaysUntil('2026-05-11T12:00:00.000Z'); + + expect(result).toBe(0); + }); + + test('When the provided value is not a valid date, then no value is returned', () => { + const result = getDaysUntil('not-a-date'); + + expect(result).toBeUndefined(); + }); +}); diff --git a/src/utils/days-until/index.ts b/src/utils/days-until/index.ts new file mode 100644 index 0000000..bd46929 --- /dev/null +++ b/src/utils/days-until/index.ts @@ -0,0 +1,10 @@ +import dayjs from 'dayjs'; + +const MS_PER_DAY = 86_400_000; + +export const getDaysUntil = (date: string | undefined): number | undefined => { + if (!date) return undefined; + const parsed = dayjs(date); + if (!parsed.isValid()) return undefined; + return Math.max(0, Math.ceil((parsed.valueOf() - Date.now()) / MS_PER_DAY)); +};