From 860270b75f54f870848a0dd97c41656fc62b5a72 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Tue, 7 Apr 2026 16:08:52 +0300 Subject: [PATCH 01/20] refactor: initial migration to remix --- web/app/routes/_index.tsx | 5 ++ web/pnpm-lock.yaml | 154 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 web/app/routes/_index.tsx diff --git a/web/app/routes/_index.tsx b/web/app/routes/_index.tsx new file mode 100644 index 0000000..0ca2915 --- /dev/null +++ b/web/app/routes/_index.tsx @@ -0,0 +1,5 @@ +import { DashboardLayout } from "@/components/dashboard-layout"; + +export default function Index() { + return ; +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 0d4e6a4..25ce94c 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1468,6 +1468,12 @@ packages: '@tailwindcss/postcss@4.2.2': resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} + '@types/acorn@4.0.6': + resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -1682,6 +1688,10 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + data-uri-to-buffer@3.0.1: + resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} + engines: {node: '>= 6'} + date-fns-jalali@4.1.0-0: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} @@ -2265,6 +2275,25 @@ packages: redux@5.0.1: resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + remark-frontmatter@4.0.1: + resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} + + remark-mdx-frontmatter@1.1.1: + resolution: {integrity: sha512-7teX9DW4tI2WZkXS4DBxneYSY7NHiXl4AKdWDO9LXVweULlCT8OPWsOjLEnMIXViN1j+QcY8mfbq3k0EK6x3uA==} + engines: {node: '>=12.2.0'} + + remark-mdx@2.3.0: + resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} + + remark-parse@10.0.2: + resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} + + remark-rehype@10.1.0: + resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} + + require-like@0.1.2: + resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -2360,6 +2389,21 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -2384,6 +2428,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turbo-stream@2.4.1: + resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} + tw-animate-css@1.3.3: resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==} @@ -2396,6 +2443,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -2456,6 +2506,12 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vfile-message@3.1.4: + resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + + vfile@5.3.7: + resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} @@ -2509,6 +2565,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -3750,6 +3809,12 @@ snapshots: postcss: 8.5.8 tailwindcss: 4.2.2 + '@types/acorn@4.0.6': + dependencies: + '@types/estree': 1.0.8 + + '@types/cookie@0.6.0': {} + '@types/d3-array@3.2.2': {} '@types/d3-color@3.1.3': {} @@ -3791,6 +3856,8 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/unist@2.0.11': {} + '@types/use-sync-external-store@0.0.6': {} accepts@1.3.8: @@ -3973,6 +4040,8 @@ snapshots: d3-timer@3.0.1: {} + data-uri-to-buffer@3.0.1: {} + date-fns-jalali@4.1.0-0: {} date-fns@4.1.0: {} @@ -4490,6 +4559,44 @@ snapshots: redux@5.0.1: {} + remark-frontmatter@4.0.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-frontmatter: 1.0.1 + micromark-extension-frontmatter: 1.1.1 + unified: 10.1.2 + + remark-mdx-frontmatter@1.1.1: + dependencies: + estree-util-is-identifier-name: 1.1.0 + estree-util-value-to-estree: 1.3.0 + js-yaml: 4.1.1 + toml: 3.0.0 + + remark-mdx@2.3.0: + dependencies: + mdast-util-mdx: 2.0.1 + micromark-extension-mdxjs: 1.0.1 + transitivePeerDependencies: + - supports-color + + remark-parse@10.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + + remark-rehype@10.1.0: + dependencies: + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-to-hast: 12.3.0 + unified: 10.1.2 + + require-like@0.1.2: {} + reselect@5.1.1: {} rollup@4.60.1: @@ -4622,6 +4729,35 @@ snapshots: tapable@2.3.2: {} + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + tiny-invariant@1.3.3: {} tinyglobby@0.2.15: @@ -4637,6 +4773,8 @@ snapshots: tslib@2.8.1: {} + turbo-stream@2.4.1: {} + tw-animate-css@1.3.3: {} type-is@1.6.18: @@ -4646,6 +4784,8 @@ snapshots: typescript@5.9.3: {} + ufo@1.6.3: {} + undici-types@6.21.0: {} unpipe@1.0.0: {} @@ -4692,6 +4832,18 @@ snapshots: - '@types/react' - '@types/react-dom' + vfile-message@3.1.4: + dependencies: + '@types/unist': 2.0.11 + unist-util-stringify-position: 3.0.3 + + vfile@5.3.7: + dependencies: + '@types/unist': 2.0.11 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + victory-vendor@37.3.6: dependencies: '@types/d3-array': 3.2.2 @@ -4751,3 +4903,5 @@ snapshots: yallist@3.1.1: {} zod@3.25.76: {} + + zwitch@2.0.4: {} From 893bd305076c7c07c9a4e042d795289c1c132615 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Tue, 7 Apr 2026 18:30:48 +0300 Subject: [PATCH 02/20] feat: upgrade to react router v7 from remix --- web/pnpm-lock.yaml | 154 --------------------------------------------- 1 file changed, 154 deletions(-) diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 25ce94c..0d4e6a4 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1468,12 +1468,6 @@ packages: '@tailwindcss/postcss@4.2.2': resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} - '@types/acorn@4.0.6': - resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} - - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -1688,10 +1682,6 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} - data-uri-to-buffer@3.0.1: - resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} - engines: {node: '>= 6'} - date-fns-jalali@4.1.0-0: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} @@ -2275,25 +2265,6 @@ packages: redux@5.0.1: resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - remark-frontmatter@4.0.1: - resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} - - remark-mdx-frontmatter@1.1.1: - resolution: {integrity: sha512-7teX9DW4tI2WZkXS4DBxneYSY7NHiXl4AKdWDO9LXVweULlCT8OPWsOjLEnMIXViN1j+QcY8mfbq3k0EK6x3uA==} - engines: {node: '>=12.2.0'} - - remark-mdx@2.3.0: - resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} - - remark-parse@10.0.2: - resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} - - remark-rehype@10.1.0: - resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} - - require-like@0.1.2: - resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} - reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -2389,21 +2360,6 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} - tar-fs@2.1.4: - resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -2428,9 +2384,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - turbo-stream@2.4.1: - resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} - tw-animate-css@1.3.3: resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==} @@ -2443,9 +2396,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -2506,12 +2456,6 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc - vfile-message@3.1.4: - resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} - - vfile@5.3.7: - resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} - victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} @@ -2565,9 +2509,6 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - snapshots: '@alloc/quick-lru@5.2.0': {} @@ -3809,12 +3750,6 @@ snapshots: postcss: 8.5.8 tailwindcss: 4.2.2 - '@types/acorn@4.0.6': - dependencies: - '@types/estree': 1.0.8 - - '@types/cookie@0.6.0': {} - '@types/d3-array@3.2.2': {} '@types/d3-color@3.1.3': {} @@ -3856,8 +3791,6 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 - '@types/unist@2.0.11': {} - '@types/use-sync-external-store@0.0.6': {} accepts@1.3.8: @@ -4040,8 +3973,6 @@ snapshots: d3-timer@3.0.1: {} - data-uri-to-buffer@3.0.1: {} - date-fns-jalali@4.1.0-0: {} date-fns@4.1.0: {} @@ -4559,44 +4490,6 @@ snapshots: redux@5.0.1: {} - remark-frontmatter@4.0.1: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-frontmatter: 1.0.1 - micromark-extension-frontmatter: 1.1.1 - unified: 10.1.2 - - remark-mdx-frontmatter@1.1.1: - dependencies: - estree-util-is-identifier-name: 1.1.0 - estree-util-value-to-estree: 1.3.0 - js-yaml: 4.1.1 - toml: 3.0.0 - - remark-mdx@2.3.0: - dependencies: - mdast-util-mdx: 2.0.1 - micromark-extension-mdxjs: 1.0.1 - transitivePeerDependencies: - - supports-color - - remark-parse@10.0.2: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - - remark-rehype@10.1.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-to-hast: 12.3.0 - unified: 10.1.2 - - require-like@0.1.2: {} - reselect@5.1.1: {} rollup@4.60.1: @@ -4729,35 +4622,6 @@ snapshots: tapable@2.3.2: {} - tar-fs@2.1.4: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.4 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - - through2@2.0.5: - dependencies: - readable-stream: 2.3.8 - xtend: 4.0.2 - tiny-invariant@1.3.3: {} tinyglobby@0.2.15: @@ -4773,8 +4637,6 @@ snapshots: tslib@2.8.1: {} - turbo-stream@2.4.1: {} - tw-animate-css@1.3.3: {} type-is@1.6.18: @@ -4784,8 +4646,6 @@ snapshots: typescript@5.9.3: {} - ufo@1.6.3: {} - undici-types@6.21.0: {} unpipe@1.0.0: {} @@ -4832,18 +4692,6 @@ snapshots: - '@types/react' - '@types/react-dom' - vfile-message@3.1.4: - dependencies: - '@types/unist': 2.0.11 - unist-util-stringify-position: 3.0.3 - - vfile@5.3.7: - dependencies: - '@types/unist': 2.0.11 - is-buffer: 2.0.5 - unist-util-stringify-position: 3.0.3 - vfile-message: 3.1.4 - victory-vendor@37.3.6: dependencies: '@types/d3-array': 3.2.2 @@ -4903,5 +4751,3 @@ snapshots: yallist@3.1.1: {} zod@3.25.76: {} - - zwitch@2.0.4: {} From 68f913d912c9a901e6ca91d6c8a88f5a7eaf444b Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Tue, 7 Apr 2026 20:19:52 +0300 Subject: [PATCH 03/20] refactor: add shared routes for the dashboard and change --- web/app/routes/_index.tsx | 5 ----- web/vite.config.ts | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 web/app/routes/_index.tsx diff --git a/web/app/routes/_index.tsx b/web/app/routes/_index.tsx deleted file mode 100644 index 0ca2915..0000000 --- a/web/app/routes/_index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DashboardLayout } from "@/components/dashboard-layout"; - -export default function Index() { - return ; -} diff --git a/web/vite.config.ts b/web/vite.config.ts index 8c20dc0..fdfb2cc 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -3,5 +3,9 @@ import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ +<<<<<<< HEAD plugins: [reactRouter(), tsconfigPaths()], +======= + plugins: [reactRouter(), tsconfigPaths()], +>>>>>>> 3c2136e (refactor: add shared routes for the dashboard and change) }); From 110528214c258e2b93756d38ab70382af76366c3 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Fri, 10 Apr 2026 10:50:28 +0300 Subject: [PATCH 04/20] feat: enhance dashboard ui for settings page, overview page and logs page --- web/components/deliveries-table.tsx | 17 ++++------------- web/components/pages/settings.tsx | 6 +++--- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/web/components/deliveries-table.tsx b/web/components/deliveries-table.tsx index 835f6fd..186a421 100644 --- a/web/components/deliveries-table.tsx +++ b/web/components/deliveries-table.tsx @@ -52,12 +52,9 @@ const deliveries = [ }, ]; -interface DeliveriesTableProps { - onRowClick?: (delivery: any) => void; -} - -export function DeliveriesTable({ onRowClick }: DeliveriesTableProps = {}) { +export function DeliveriesTable() { const navigate = useNavigate(); + if (deliveries.length === 0) { return (
@@ -74,7 +71,7 @@ export function DeliveriesTable({ onRowClick }: DeliveriesTableProps = {}) { } return ( -
+
@@ -100,13 +97,7 @@ export function DeliveriesTable({ onRowClick }: DeliveriesTableProps = {}) { return ( { - if (onRowClick) { - onRowClick(delivery); - } else { - navigate(`/deliveries/${delivery.id}`); - } - }} + onClick={() => navigate(`/deliveries/${delivery.id}`)} className="cursor-pointer border-b last:border-0 hover:bg-muted/40 transition-colors" > {/* Event */} diff --git a/web/components/pages/settings.tsx b/web/components/pages/settings.tsx index 605298b..1ff6141 100644 --- a/web/components/pages/settings.tsx +++ b/web/components/pages/settings.tsx @@ -56,7 +56,7 @@ export function SettingsPage() { />
@@ -97,7 +97,7 @@ export function DeliveriesTable() { return ( navigate(`/deliveries/${delivery.id}`)} + onClick={() => navigate(`/deliveries/${delivery.id}`)} className="cursor-pointer border-b last:border-0 hover:bg-muted/40 transition-colors" > {/* Event */} From f7c5bb8a784bf38814e9839e2c27762d001059d9 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Fri, 10 Apr 2026 15:03:57 +0300 Subject: [PATCH 07/20] fix --- web/vite.config.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/vite.config.ts b/web/vite.config.ts index fdfb2cc..8c20dc0 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -3,9 +3,5 @@ import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ -<<<<<<< HEAD plugins: [reactRouter(), tsconfigPaths()], -======= - plugins: [reactRouter(), tsconfigPaths()], ->>>>>>> 3c2136e (refactor: add shared routes for the dashboard and change) }); From 4bbbc6a725403c0304bea1a66a22327df323fe48 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Mon, 13 Apr 2026 10:02:56 +0300 Subject: [PATCH 08/20] feat: move overview page from client data to server side data --- web/app/routes/overview.tsx | 54 ++++- web/components/pages/overview.tsx | 133 +++++++++--- web/components/recent-events-table.tsx | 57 +---- web/lib/api.ts | 276 ++++++++++--------------- web/lib/types.ts | 5 - web/package.json | 1 + web/pnpm-lock.yaml | 9 + 7 files changed, 287 insertions(+), 248 deletions(-) diff --git a/web/app/routes/overview.tsx b/web/app/routes/overview.tsx index 079d3c9..cbbf12c 100644 --- a/web/app/routes/overview.tsx +++ b/web/app/routes/overview.tsx @@ -1,5 +1,55 @@ +import { getWebhooks, getDeliveries, getEvents } from "@/lib/api"; +import type { Webhook, Delivery, Event } from "@/lib/types"; import { OverviewPage } from "@/components/pages/overview"; -export default function Overview() { - return ; +export async function clientLoader() { + const [webhooks, deliveries, events] = await Promise.all([ + getWebhooks({ limit: 200 }), + getDeliveries({ limit: 200 }), + getEvents({ limit: 10 }), + ]); + + return { webhooks, deliveries, events }; +} + +export function HydrateFallback() { + return ( +
+
+
+
+
+
+ {["stat-1", "stat-2", "stat-3", "stat-4"].map((id) => ( +
+ ))} +
+
+
+
+
+
+
+ ); +} + +export default function Overview({ + loaderData, +}: { + loaderData: { + webhooks: Webhook[]; + deliveries: Delivery[]; + events: Event[]; + }; +}) { + return ( + + ); } diff --git a/web/components/pages/overview.tsx b/web/components/pages/overview.tsx index 351f766..f2138fb 100644 --- a/web/components/pages/overview.tsx +++ b/web/components/pages/overview.tsx @@ -6,11 +6,73 @@ import { Activity, Gauge, } from "lucide-react"; +import { parse, format } from "@lukeed/ms"; import { StatCard } from "../stat-card"; import { RecentEventsTable } from "../recent-events-table"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { Webhook, Delivery, Event } from "@/lib/types"; + +interface OverviewPageProps { + webhooks: Webhook[]; + deliveries: Delivery[]; + events: Event[]; +} + +function computeStats(webhooks: Webhook[], deliveries: Delivery[]) { + const activeCount = webhooks.filter((w) => w.status === "active").length; + const totalEndpoints = webhooks.length; + + const successCount = deliveries.filter((d) => d.success).length; + const successRate = + deliveries.length > 0 + ? ((successCount / deliveries.length) * 100).toFixed(1) + : "0.0"; + + const durations = deliveries.map((d) => parse(d.duration) ?? 0); + const avgLatency = + durations.length > 0 + ? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length) + : 0; + + return { + activeCount, + totalEndpoints, + successRate, + successCount, + avgLatency, + totalDeliveries: deliveries.length, + }; +} + +export function OverviewPage({ + webhooks, + deliveries, + events, +}: OverviewPageProps) { + const stats = computeStats(webhooks, deliveries); + + // Endpoint status breakdown + const healthyCount = webhooks.filter((w) => w.status === "active").length; + const degradedCount = webhooks.filter( + (w) => w.status === "disabled", + ).length; + const failingCount = webhooks.filter((w) => w.status === "failed").length; + const total = webhooks.length || 1; + const healthyPct = Math.round((healthyCount / total) * 100); + const degradedPct = Math.round((degradedCount / total) * 100); + const failingPct = Math.round((failingCount / total) * 100); + + // Percentile latencies + const sortedDurations = deliveries + .map((d) => parse(d.duration) ?? 0) + .sort((a, b) => a - b); + const percentile = (arr: number[], p: number) => + arr.length > 0 ? arr[Math.ceil((p / 100) * arr.length) - 1] : 0; + const p50 = percentile(sortedDurations, 50); + const p95 = percentile(sortedDurations, 95); + const p99 = percentile(sortedDurations, 99); + const maxLatency = sortedDurations[sortedDurations.length - 1] || 1; -export function OverviewPage() { return (
@@ -24,32 +86,32 @@ export function OverviewPage() {
Endpoint Status
- 22 + {webhooks.length}
-
-
-
+
+
+
@@ -79,7 +150,7 @@ export function OverviewPage() { Healthy
- 20 + {healthyCount}
@@ -87,7 +158,9 @@ export function OverviewPage() { Degraded
- 2 + + {degradedCount} +
@@ -95,7 +168,7 @@ export function OverviewPage() { Failing
- 0 + {failingCount}
@@ -110,12 +183,16 @@ export function OverviewPage() {
P50{" "} - 145ms + + {format(p50)} +
@@ -123,12 +200,16 @@ export function OverviewPage() {
P95 - 512ms + + {format(p95)} +
@@ -136,12 +217,16 @@ export function OverviewPage() {
P99 - 1.2s + + {format(p99)} +
@@ -153,7 +238,7 @@ export function OverviewPage() { Recent Events - +
diff --git a/web/components/recent-events-table.tsx b/web/components/recent-events-table.tsx index 89b8da5..d79b468 100644 --- a/web/components/recent-events-table.tsx +++ b/web/components/recent-events-table.tsx @@ -1,4 +1,5 @@ import { Inbox } from "lucide-react"; +import type { Event } from "@/lib/types"; import { Table, TableBody, @@ -8,56 +9,12 @@ import { TableRow, } from "@/components/ui/table"; -const recentEvents = [ - { - id: 1, - event: "user.created", - status: "success", - timestamp: "2025-12-08 14:32:00", - duration: "145ms", - endpoint: "https://api.example.com/webhooks", - attempts: 1, - }, - { - id: 2, - event: "order.completed", - status: "success", - timestamp: "2025-12-08 14:31:15", - duration: "234ms", - endpoint: "https://api.example.com/orders", - attempts: 1, - }, - { - id: 3, - event: "payment.failed", - status: "failed", - timestamp: "2025-12-08 14:28:42", - duration: "156ms", - endpoint: "https://payment.example.com/webhook", - attempts: 3, - }, - { - id: 4, - event: "user.updated", - status: "success", - timestamp: "2025-12-08 14:25:00", - duration: "89ms", - endpoint: "https://api.example.com/webhooks", - attempts: 1, - }, - { - id: 5, - event: "invoice.generated", - status: "success", - timestamp: "2025-12-08 14:22:30", - duration: "512ms", - endpoint: "https://billing.example.com/invoice", - attempts: 2, - }, -]; +interface RecentEventsTableProps { + events: Event[]; +} -export function RecentEventsTable() { - if (recentEvents.length === 0) { +export function RecentEventsTable({ events }: RecentEventsTableProps) { + if (events.length === 0) { return (
- {recentEvents.map((event) => ( + {events.map((event) => ( ( - endpoint: string, - options?: RequestInit - ): Promise { - const url = `${this.baseUrl}${endpoint}`; - - try { - const response = await fetch(url, { - ...options, - headers: { - "Content-Type": "application/json", - ...options?.headers, - }, - }); - - if (!response.ok) { - throw { - message: `HTTP error! status: ${response.status}`, - status: response.status, - } as ApiError; - } - - // Handle 204 No Content - if (response.status === 204) { - return {} as T; - } - - return await response.json(); - } catch (error) { - if ((error as ApiError).status) { - throw error; - } - throw { - message: "Network error", - status: 0, - } as ApiError; - } - } - - async getHealth(): Promise { - return this.request("/health"); - } - - async getLogs(params?: PaginationParams): Promise { - const query = new URLSearchParams(); - if (params?.page !== undefined) - query.append("page", params.page.toString()); - if (params?.limit !== undefined) - query.append("limit", params.limit.toString()); - - const queryString = query.toString(); - return this.request(`/logs${queryString ? `?${queryString}` : ""}`); - } - - async createLog(data: { level: string; message: string }): Promise { - await this.request("/logs", { - method: "POST", - body: JSON.stringify(data), - }); - } - - async getWebhooks(params?: PaginationParams): Promise { - const query = new URLSearchParams(); - if (params?.page !== undefined) - query.append("page", params.page.toString()); - if (params?.limit !== undefined) - query.append("limit", params.limit.toString()); - - const queryString = query.toString(); - return this.request( - `/webhooks${queryString ? `?${queryString}` : ""}` - ); - } - - async getWebhook(id: number): Promise { - return this.request(`/webhooks/${id}`); - } - - async createWebhook(data: CreateWebhook): Promise { - return this.request("/webhooks", { - method: "POST", - body: JSON.stringify(data), - }); +import type { + Log, + Webhook, + UpdateWebhook, + CreateDelivery, + CreateEvent, + Settings, + Delivery, + CreateWebhook, + PaginationParams, + HealthResponse, + Event, +} from "./types"; + +const BASE_URL = + import.meta.env.VITE_API_URL ?? "http://localhost:8080/api/v1"; + +// ── Error ──────────────────────────────────────────────────────────── + +export class ApiError extends Error { + status: number; + + constructor(message: string, status: number) { + super(message); + this.name = "ApiError"; + this.status = status; } +} - async updateWebhook(id: number, data: UpdateWebhook): Promise { - return this.request(`/webhooks/${id}`, { - method: "PUT", - body: JSON.stringify(data), - }); - } +// ── Internals ──────────────────────────────────────────────────────── - async deleteWebhook(id: number): Promise { - await this.request(`/webhooks/${id}`, { - method: "DELETE", - }); - } +function buildQuery(params?: PaginationParams): string { + if (!params) return ""; + const q = new URLSearchParams(); + if (params.page != null) q.set("page", String(params.page)); + if (params.limit != null) q.set("limit", String(params.limit)); + const s = q.toString(); + return s ? `?${s}` : ""; +} - async getDeliveries(params?: PaginationParams): Promise { - const query = new URLSearchParams(); - if (params?.page !== undefined) - query.append("page", params.page.toString()); - if (params?.limit !== undefined) - query.append("limit", params.limit.toString()); - - const queryString = query.toString(); - return this.request( - `/deliveries${queryString ? `?${queryString}` : ""}` - ); - } +async function request(endpoint: string, init?: RequestInit): Promise { + const url = `${BASE_URL}${endpoint}`; - async getDelivery(id: number): Promise { - return this.request(`/deliveries/${id}`); - } + const res = await fetch(url, { + ...init, + headers: { + "Content-Type": "application/json", + ...init?.headers, + }, + }); - async createDelivery(data: CreateDelivery): Promise { - return this.request("/deliveries", { - method: "POST", - body: JSON.stringify(data), - }); + if (!res.ok) { + throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, res.status); } - async getEvents(params?: PaginationParams): Promise { - const query = new URLSearchParams(); - if (params?.page !== undefined) - query.append("page", params.page.toString()); - if (params?.limit !== undefined) - query.append("limit", params.limit.toString()); - - const queryString = query.toString(); - return this.request( - `/events${queryString ? `?${queryString}` : ""}` - ); + const contentType = res.headers.get("content-type"); + if (res.status === 204 || !contentType?.includes("application/json")) { + return undefined as T; } - async getEvent(id: number): Promise { - return this.request(`/events/${id}`); - } + return res.json(); +} - async createEvent(data: CreateEvent): Promise { - return this.request("/events", { - method: "POST", - body: JSON.stringify(data), - }); - } +function get(endpoint: string) { + return request(endpoint); +} - async getEventTypes(): Promise { - return this.request("/event-types"); - } +function post(endpoint: string, body: unknown) { + return request(endpoint, { + method: "POST", + body: JSON.stringify(body), + }); +} - async getSettings(): Promise { - return this.request("/settings"); - } +function put(endpoint: string, body: unknown) { + return request(endpoint, { + method: "PUT", + body: JSON.stringify(body), + }); +} - async updateSettings(data: Settings): Promise { - return this.request("/settings", { - method: "PUT", - body: JSON.stringify(data), - }); - } +function del(endpoint: string) { + return request(endpoint, { method: "DELETE" }); } -export const apiClient = new ApiClient(); -export { ApiClient }; +// ── Public API ─────────────────────────────────────────────────────── + +export const getHealth = () => get("/health"); + +// Webhooks +export const getWebhooks = (params?: PaginationParams) => + get(`/webhooks${buildQuery(params)}`); +export const getWebhook = (id: number) => get(`/webhooks/${id}`); +export const createWebhook = (data: CreateWebhook) => + post("/webhooks", data); +export const updateWebhook = (id: number, data: UpdateWebhook) => + put(`/webhooks/${id}`, data); +export const deleteWebhook = (id: number) => del(`/webhooks/${id}`); + +// Deliveries +export const getDeliveries = (params?: PaginationParams) => + get(`/deliveries${buildQuery(params)}`); +export const getDelivery = (id: number) => + get(`/deliveries/${id}`); +export const createDelivery = (data: CreateDelivery) => + post("/deliveries", data); + +// Events +export const getEvents = (params?: PaginationParams) => + get(`/events${buildQuery(params)}`); +export const getEvent = (id: number) => get(`/events/${id}`); +export const createEvent = (data: CreateEvent) => + post("/events", data); +export const getEventTypes = () => get("/event-types"); + +// Logs +export const getLogs = (params?: PaginationParams) => + get(`/logs${buildQuery(params)}`); +export const createLog = (data: { level: string; message: string }) => + post("/logs", data); + +// Settings +export const getSettings = () => get("/settings"); +export const updateSettings = (data: Settings) => + put("/settings", data); diff --git a/web/lib/types.ts b/web/lib/types.ts index 5fc78e1..3d1d01a 100644 --- a/web/lib/types.ts +++ b/web/lib/types.ts @@ -74,8 +74,3 @@ export interface PaginationParams { page?: number; limit?: number; } - -export interface ApiError { - message: string; - status: number; -} diff --git a/web/package.json b/web/package.json index 31f7c38..af89ed1 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.10.0", + "@lukeed/ms": "^2.0.2", "@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-alert-dialog": "1.1.4", "@radix-ui/react-aspect-ratio": "1.1.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 0d4e6a4..b1f1592 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@hookform/resolvers': specifier: ^3.10.0 version: 3.10.0(react-hook-form@7.72.1(react@18.3.1)) + '@lukeed/ms': + specifier: ^2.0.2 + version: 2.0.2 '@radix-ui/react-accordion': specifier: 1.2.2 version: 1.2.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -514,6 +517,10 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + '@mjackson/node-fetch-server@0.2.0': resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} @@ -2810,6 +2817,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@lukeed/ms@2.0.2': {} + '@mjackson/node-fetch-server@0.2.0': {} '@radix-ui/number@1.1.0': {} From e3192030ac92cc0608f6badaa12f9c47eb476548 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Mon, 13 Apr 2026 13:52:59 +0300 Subject: [PATCH 09/20] feat: add endpoint configurations to the api.ts, consume all the api routes to the client, add client side loaders for bettter and optimized data fetchinh --- web/app/routes/create-webhook.tsx | 61 +- web/app/routes/deliveries.tsx | 36 +- web/app/routes/delivery-detail.tsx | 72 +- web/app/routes/logs.tsx | 39 +- web/app/routes/overview.tsx | 66 +- web/app/routes/settings.tsx | 74 +- web/app/routes/webhooks.tsx | 50 +- web/components/deliveries-table.tsx | 56 +- web/components/logs-list.tsx | 47 +- web/components/pages/create-webhook.tsx | 116 +- web/components/pages/deliveries.tsx | 9 +- web/components/pages/delivery-detail.tsx | 108 +- web/components/pages/logs.tsx | 41 +- web/components/pages/overview.tsx | 12 +- web/components/pages/settings.tsx | 71 +- web/components/pages/webhooks.tsx | 27 +- web/components/recent-events-table.tsx | 4 +- web/components/sidebar.tsx | 2 - web/components/top-nav.tsx | 20 +- web/components/ui/button.tsx | 52 +- web/components/ui/input.tsx | 14 +- web/components/ui/separator.tsx | 14 +- web/components/ui/sheet.tsx | 62 +- web/components/ui/sidebar.tsx | 1200 ++++++++-------- web/components/ui/skeleton.tsx | 6 +- web/components/ui/tooltip.tsx | 20 +- web/components/webhook-table.tsx | 193 ++- web/hooks/use-api.ts | 249 ---- web/hooks/use-mobile.ts | 6 +- web/package.json | 1 + web/pnpm-lock.yaml | 1652 ++++++++++++++++++++-- 31 files changed, 2926 insertions(+), 1454 deletions(-) delete mode 100644 web/hooks/use-api.ts diff --git a/web/app/routes/create-webhook.tsx b/web/app/routes/create-webhook.tsx index 05d8ed6..220cca3 100644 --- a/web/app/routes/create-webhook.tsx +++ b/web/app/routes/create-webhook.tsx @@ -1,5 +1,62 @@ +import { redirect } from "react-router"; +import { createWebhook, getEventTypes } from "@/lib/api"; import { CreateWebhookPage } from "@/components/pages/create-webhook"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; -export default function CreateWebhook() { - return ; +export async function clientLoader() { + const eventTypes = await getEventTypes(); + return { eventTypes }; +} + +export function HydrateFallback() { + return ( +
+
+ +
+ + +
+
+
+ + + + + + +
+ + +
+
+ + +
+
+
+
+ + +
+
+
+ ); +} + +export async function clientAction({ request }: { request: Request }) { + const formData = await request.formData(); + const name = formData.get("name") as string; + const url = formData.get("url") as string; + await createWebhook({ name, url }); + return redirect("/webhooks"); +} + +export default function CreateWebhook({ + loaderData, +}: { + loaderData: { eventTypes: string[] }; +}) { + return ; } diff --git a/web/app/routes/deliveries.tsx b/web/app/routes/deliveries.tsx index bfeecaf..f7bc532 100644 --- a/web/app/routes/deliveries.tsx +++ b/web/app/routes/deliveries.tsx @@ -1,5 +1,37 @@ +import { getDeliveries } from "@/lib/api"; +import type { Delivery } from "@/lib/types"; import { DeliveriesPage } from "@/components/pages/deliveries"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card } from "@/components/ui/card"; -export default function Deliveries() { - return ; +export async function clientLoader() { + const deliveries = await getDeliveries(); + return { deliveries }; +} + +export function HydrateFallback() { + return ( +
+
+ + +
+ +
+ + {["del-1", "del-2", "del-3", "del-4"].map((id) => ( + + ))} +
+
+
+ ); +} + +export default function Deliveries({ + loaderData, +}: { + loaderData: { deliveries: Delivery[] }; +}) { + return ; } diff --git a/web/app/routes/delivery-detail.tsx b/web/app/routes/delivery-detail.tsx index cdffc99..eb6afff 100644 --- a/web/app/routes/delivery-detail.tsx +++ b/web/app/routes/delivery-detail.tsx @@ -1,5 +1,73 @@ +import { getDelivery } from "@/lib/api"; +import type { Delivery } from "@/lib/types"; import { DeliveryDetailPage } from "@/components/pages/delivery-detail"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; -export default function DeliveryDetail() { - return ; +export async function clientLoader({ + params, +}: { params: { id: string } }) { + const delivery = await getDelivery(Number(params.id)); + return { delivery }; +} + +export function HydrateFallback() { + return ( +
+
+ +
+ + +
+
+
+ + + + + +
+ {["ev-name", "status", "duration", "timestamp"].map((id) => ( +
+ + +
+ ))} +
+
+
+ + + + + + + + + +
+ + + + +
+ + +
+
+
+
+
+
+ ); +} + +export default function DeliveryDetail({ + loaderData, +}: { + loaderData: { delivery: Delivery }; +}) { + return ; } diff --git a/web/app/routes/logs.tsx b/web/app/routes/logs.tsx index 11289f7..c14ee9e 100644 --- a/web/app/routes/logs.tsx +++ b/web/app/routes/logs.tsx @@ -1,5 +1,40 @@ +import { getLogs } from "@/lib/api"; +import type { Log } from "@/lib/types"; import { LogsPage } from "@/components/pages/logs"; +import { Skeleton } from "@/components/ui/skeleton"; -export default function Logs() { - return ; +export async function clientLoader() { + const logs = await getLogs(); + return { logs }; +} + +export function HydrateFallback() { + return ( +
+
+ + +
+ +
+ {["log-1", "log-2", "log-3", "log-4", "log-5"].map((id) => ( +
+ +
+ + +
+
+ ))} +
+
+ ); +} + +export default function Logs({ + loaderData, +}: { + loaderData: { logs: Log[] }; +}) { + return ; } diff --git a/web/app/routes/overview.tsx b/web/app/routes/overview.tsx index cbbf12c..1c2ae6b 100644 --- a/web/app/routes/overview.tsx +++ b/web/app/routes/overview.tsx @@ -1,6 +1,8 @@ import { getWebhooks, getDeliveries, getEvents } from "@/lib/api"; import type { Webhook, Delivery, Event } from "@/lib/types"; import { OverviewPage } from "@/components/pages/overview"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; export async function clientLoader() { const [webhooks, deliveries, events] = await Promise.all([ @@ -16,22 +18,68 @@ export function HydrateFallback() { return (
-
-
+ +
+
{["stat-1", "stat-2", "stat-3", "stat-4"].map((id) => ( -
+ + + + + + + + + ))}
+
-
-
+ + + + + + + + {["healthy", "inactive", "failing"].map((id) => ( +
+ + +
+ ))} +
+
+ + + + + + {["p50", "p95", "p99"].map((id) => ( +
+
+ + +
+ +
+ ))} +
+
-
+ + + + + + + {["row-1", "row-2", "row-3", "row-4", "row-5"].map((id) => ( + + ))} + +
); } diff --git a/web/app/routes/settings.tsx b/web/app/routes/settings.tsx index f29ee43..921f288 100644 --- a/web/app/routes/settings.tsx +++ b/web/app/routes/settings.tsx @@ -1,5 +1,75 @@ +import { getSettings, updateSettings } from "@/lib/api"; +import type { Settings } from "@/lib/types"; import { SettingsPage } from "@/components/pages/settings"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; -export default function Settings() { - return ; +export async function clientLoader() { + const settings = await getSettings(); + return { settings }; +} + +export function HydrateFallback() { + return ( +
+
+ + +
+
+ {["secret", "apikey"].map((id) => ( + + + + + + + + + + + ))} +
+ + + + + + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+ ); +} + +export async function clientAction({ request }: { request: Request }) { + const formData = await request.formData(); + const settings: Settings = { + retry_attempts: Number(formData.get("retry_attempts")), + timeout_seconds: Number(formData.get("timeout_seconds")), + enabled: formData.get("enabled") === "true", + }; + const updated = await updateSettings(settings); + return { settings: updated }; +} + +export default function SettingsRoute({ + loaderData, +}: { + loaderData: { settings: Settings }; +}) { + return ; } diff --git a/web/app/routes/webhooks.tsx b/web/app/routes/webhooks.tsx index 4294eaf..5872b59 100644 --- a/web/app/routes/webhooks.tsx +++ b/web/app/routes/webhooks.tsx @@ -1,5 +1,51 @@ +import { getWebhooks, deleteWebhook } from "@/lib/api"; +import type { Webhook } from "@/lib/types"; import { WebhooksPage } from "@/components/pages/webhooks"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card } from "@/components/ui/card"; -export default function Webhooks() { - return ; +export async function clientLoader() { + const webhooks = await getWebhooks(); + return { webhooks }; +} + +export async function clientAction({ + request, +}: { request: Request }) { + const formData = await request.formData(); + const webhookId = Number(formData.get("webhookId")); + await deleteWebhook(webhookId); + return { ok: true }; +} + +export function HydrateFallback() { + return ( +
+
+ + +
+
+ + +
+ + +
+ + {["wh-1", "wh-2", "wh-3"].map((id) => ( + + ))} +
+
+
+ ); +} + +export default function Webhooks({ + loaderData, +}: { + loaderData: { webhooks: Webhook[] }; +}) { + return ; } diff --git a/web/components/deliveries-table.tsx b/web/components/deliveries-table.tsx index b69e19e..10abc85 100644 --- a/web/components/deliveries-table.tsx +++ b/web/components/deliveries-table.tsx @@ -1,5 +1,5 @@ -import { Inbox } from "lucide-react"; import { useNavigate } from "react-router"; +import { Inbox } from "lucide-react"; import { Table, TableBody, @@ -8,51 +8,13 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import type { Delivery } from "@/lib/types"; -const deliveries = [ - { - id: 1, - event: "user.created", - status: 200, - duration: "145ms", - timestamp: "2025-12-08 14:32:00", - success: true, - }, - { - id: 2, - event: "order.completed", - status: 200, - duration: "234ms", - timestamp: "2025-12-08 14:31:15", - success: true, - }, - { - id: 3, - event: "payment.failed", - status: 500, - duration: "156ms", - timestamp: "2025-12-08 14:28:42", - success: false, - }, - { - id: 4, - event: "user.updated", - status: 200, - duration: "89ms", - timestamp: "2025-12-08 14:25:00", - success: true, - }, - { - id: 5, - event: "invoice.generated", - status: 202, - duration: "512ms", - timestamp: "2025-12-08 14:22:30", - success: true, - }, -]; +interface DeliveriesTableProps { + deliveries: Delivery[]; +} -export function DeliveriesTable() { +export function DeliveriesTable({ deliveries }: DeliveriesTableProps) { const navigate = useNavigate(); if (deliveries.length === 0) { @@ -97,17 +59,15 @@ export function DeliveriesTable() { return ( navigate(`/deliveries/${delivery.id}`)} + onClick={() => navigate(`/deliveries/${delivery.id}`)} className="cursor-pointer border-b last:border-0 hover:bg-muted/40 transition-colors" > - {/* Event */} {delivery.event} - {/* HTTP Status */} - {/* Duration */} {delivery.duration} - {/* Time */} {delivery.timestamp} diff --git a/web/components/logs-list.tsx b/web/components/logs-list.tsx index 97ea87d..cd779be 100644 --- a/web/components/logs-list.tsx +++ b/web/components/logs-list.tsx @@ -8,47 +8,19 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import type { Log } from "@/lib/types"; -const logs = [ - { - id: 1, - level: "info", - timestamp: "14:35:22", - message: "Webhook delivered successfully to user.created endpoint", - }, - { - id: 2, - level: "info", - timestamp: "14:32:15", - message: "New webhook endpoint registered: Payment Notifications", - }, - { - id: 3, - level: "warn", - timestamp: "14:28:42", - message: "Webhook delivery timeout - retrying in 60s", - }, - { - id: 4, - level: "error", - timestamp: "14:25:30", - message: "Failed to deliver webhook after 3 retry attempts", - }, - { - id: 5, - level: "info", - timestamp: "14:20:10", - message: "Webhook endpoint disabled due to repeated failures", - }, -]; - -const levelDot = { +const levelDot: Record = { info: "bg-muted-foreground/70", warn: "bg-yellow-500/80", error: "bg-red-500/80", }; -export function LogsList() { +interface LogsListProps { + logs: Log[]; +} + +export function LogsList({ logs }: LogsListProps) { if (logs.length === 0) { return ( @@ -77,19 +49,16 @@ export function LogsList() { key={log.id} className="hover:bg-muted/30 transition-colors" > - {/* Dot */} - {/* Time */} {log.timestamp} - {/* Message */} {log.message} diff --git a/web/components/pages/create-webhook.tsx b/web/components/pages/create-webhook.tsx index a5d7190..4cdded0 100644 --- a/web/components/pages/create-webhook.tsx +++ b/web/components/pages/create-webhook.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { useNavigate } from "react-router"; +import { Form, useNavigate, useNavigation } from "react-router"; import { ArrowLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -14,26 +14,16 @@ import { CardTitle, } from "@/components/ui/card"; -const events = [ - "user.created", - "user.updated", - "user.deleted", - "order.created", - "order.completed", - "payment.processed", -]; +interface CreateWebhookPageProps { + eventTypes: string[]; +} -export function CreateWebhookPage() { +export function CreateWebhookPage({ eventTypes }: CreateWebhookPageProps) { const navigate = useNavigate(); - const [name, setName] = useState(""); - const [url, setUrl] = useState(""); + const navigation = useNavigation(); + const isSubmitting = navigation.state === "submitting"; const [selectedEvents, setSelectedEvents] = useState([]); - const handleSubmit = () => { - // TODO: wire to API - navigate("/webhooks"); - }; - return (
@@ -57,7 +47,7 @@ export function CreateWebhookPage() {
-
+
Endpoint Details @@ -71,8 +61,8 @@ export function CreateWebhookPage() { setName(e.target.value)} + name="name" + required placeholder="My Webhook" />
@@ -81,58 +71,66 @@ export function CreateWebhookPage() { setUrl(e.target.value)} + required placeholder="https://api.example.com/webhooks" />
- - - Events - - Choose which events trigger this webhook. - - + {eventTypes.length > 0 && ( + + + Events + + Choose which events trigger this webhook. + + - - -
- {events.map((event) => ( - - ))} -
-
-
-
+ + +
+ {eventTypes.map((event) => ( + + ))} +
+
+
+
+ )}
- - +
-
+
); } diff --git a/web/components/pages/deliveries.tsx b/web/components/pages/deliveries.tsx index 6de170d..9da51a2 100644 --- a/web/components/pages/deliveries.tsx +++ b/web/components/pages/deliveries.tsx @@ -1,7 +1,12 @@ +import type { Delivery } from "@/lib/types"; import { DeliveriesTable } from "../deliveries-table"; import { Card } from "@/components/ui/card"; -export function DeliveriesPage() { +interface DeliveriesPageProps { + deliveries: Delivery[]; +} + +export function DeliveriesPage({ deliveries }: DeliveriesPageProps) { return (
@@ -14,7 +19,7 @@ export function DeliveriesPage() {
- +
); diff --git a/web/components/pages/delivery-detail.tsx b/web/components/pages/delivery-detail.tsx index 1f4dc32..4bb435a 100644 --- a/web/components/pages/delivery-detail.tsx +++ b/web/components/pages/delivery-detail.tsx @@ -1,4 +1,4 @@ -import { useNavigate, useParams } from "react-router"; +import { useNavigate } from "react-router"; import { ArrowLeft } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -9,78 +9,14 @@ import { CardTitle, } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; +import type { Delivery } from "@/lib/types"; -// TODO: replace with API fetch by ID -const deliveries: Record = { - "1": { - id: 1, - event: "user.created", - status: 200, - duration: "145ms", - timestamp: "2025-12-08 14:32:00", - success: true, - }, - "2": { - id: 2, - event: "order.completed", - status: 200, - duration: "234ms", - timestamp: "2025-12-08 14:31:15", - success: true, - }, - "3": { - id: 3, - event: "payment.failed", - status: 500, - duration: "156ms", - timestamp: "2025-12-08 14:28:42", - success: false, - }, - "4": { - id: 4, - event: "user.updated", - status: 200, - duration: "89ms", - timestamp: "2025-12-08 14:25:00", - success: true, - }, - "5": { - id: 5, - event: "invoice.generated", - status: 202, - duration: "512ms", - timestamp: "2025-12-08 14:22:30", - success: true, - }, -}; +interface DeliveryDetailPageProps { + delivery: Delivery; +} -export function DeliveryDetailPage() { +export function DeliveryDetailPage({ delivery }: DeliveryDetailPageProps) { const navigate = useNavigate(); - const { id } = useParams(); - const delivery = id ? deliveries[id] : null; - - if (!delivery) { - return ( -
-
- -

- Delivery Not Found -

-
-

- The delivery you're looking for doesn't exist. -

-
- ); - } return (
@@ -139,27 +75,9 @@ export function DeliveryDetailPage() {
 							{`{
-  "id": "evt_123456",
+  "id": "evt_${delivery.id}",
   "event": "${delivery.event}",
-  "timestamp": "${delivery.timestamp}",
-  "data": {
-    "userId": "user_789",
-    "email": "user@example.com"
-  }
-}`}
-						
-
- - - - - Response Payload - - -
-							{`{
-  "received": true,
-  "id": "evt_123456"
+  "timestamp": "${delivery.timestamp}"
 }`}
 						
@@ -169,17 +87,19 @@ export function DeliveryDetailPage() {

- Retries + Delivery Status

- Success + + {delivery.success ? "Success" : "Failed"} +

- Attempt 1 + HTTP {delivery.status}

- 2025-12-08 14:32:00 + {delivery.duration}

diff --git a/web/components/pages/logs.tsx b/web/components/pages/logs.tsx index 9c15642..748baeb 100644 --- a/web/components/pages/logs.tsx +++ b/web/components/pages/logs.tsx @@ -1,20 +1,31 @@ +import { useMemo, useState } from "react"; +import type { Log } from "@/lib/types"; +import { LogsFilter } from "../logs-filter"; +import { LogsList } from "../logs-list"; -import { useState } from "react" -import { LogsFilter } from "../logs-filter" -import { LogsList } from "../logs-list" +interface LogsPageProps { + logs: Log[]; +} + +export function LogsPage({ logs }: LogsPageProps) { + const [logLevel, setLogLevel] = useState("all"); -export function LogsPage() { - const [logLevel, setLogLevel] = useState("all") + const filtered = useMemo(() => { + if (logLevel === "all") return logs; + return logs.filter((log) => log.level === logLevel); + }, [logs, logLevel]); - return ( -
-
-

Logs

-

View system events and error logs

-
+ return ( +
+
+

Logs

+

+ View system events and error logs +

+
- - -
- ) + + +
+ ); } diff --git a/web/components/pages/overview.tsx b/web/components/pages/overview.tsx index f2138fb..bfdacb3 100644 --- a/web/components/pages/overview.tsx +++ b/web/components/pages/overview.tsx @@ -53,13 +53,13 @@ export function OverviewPage({ // Endpoint status breakdown const healthyCount = webhooks.filter((w) => w.status === "active").length; - const degradedCount = webhooks.filter( - (w) => w.status === "disabled", + const inactiveCount = webhooks.filter( + (w) => w.status === "inactive", ).length; const failingCount = webhooks.filter((w) => w.status === "failed").length; const total = webhooks.length || 1; const healthyPct = Math.round((healthyCount / total) * 100); - const degradedPct = Math.round((degradedCount / total) * 100); + const inactivePct = Math.round((inactiveCount / total) * 100); const failingPct = Math.round((failingCount / total) * 100); // Percentile latencies @@ -136,7 +136,7 @@ export function OverviewPage({ />
- Degraded + Inactive
- {degradedCount} + {inactiveCount}
diff --git a/web/components/pages/settings.tsx b/web/components/pages/settings.tsx index 605298b..b5b2a21 100644 --- a/web/components/pages/settings.tsx +++ b/web/components/pages/settings.tsx @@ -1,5 +1,6 @@ import { Eye, EyeOff, Copy, RotateCw } from "lucide-react"; import { useState } from "react"; +import { Form, useFetcher } from "react-router"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -10,11 +11,18 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import type { Settings } from "@/lib/types"; -export function SettingsPage() { +interface SettingsPageProps { + settings: Settings; +} + +export function SettingsPage({ settings }: SettingsPageProps) { const [showSecret, setShowSecret] = useState(false); const [showApiKey, setShowApiKey] = useState(false); const [copied, setCopied] = useState(""); + const fetcher = useFetcher(); + const isSaving = fetcher.state !== "idle"; const handleCopy = (text: string, id: string) => { navigator.clipboard.writeText(text); @@ -151,36 +159,43 @@ export function SettingsPage() { - -
-
- - -
+ + + +
+
+ + +
-
- - +
+ + +
-
-
- -
+
+ +
+
diff --git a/web/components/pages/webhooks.tsx b/web/components/pages/webhooks.tsx index ff17e6c..392583a 100644 --- a/web/components/pages/webhooks.tsx +++ b/web/components/pages/webhooks.tsx @@ -1,16 +1,35 @@ import { Link } from "react-router"; import { Plus, Search } from "lucide-react"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { WebhookTable } from "../webhook-table"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card } from "@/components/ui/card"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import type { Webhook } from "@/lib/types"; -export function WebhooksPage() { +interface WebhooksPageProps { + webhooks: Webhook[]; +} + +const STATUS_FILTERS = ["all", "active", "inactive"] as const; + +export function WebhooksPage({ webhooks }: WebhooksPageProps) { const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); + const filtered = useMemo(() => { + return webhooks.filter((w) => { + const matchesStatus = + statusFilter === "all" || w.status === statusFilter; + const matchesSearch = + !searchTerm || + w.name.toLowerCase().includes(searchTerm.toLowerCase()) || + w.url.toLowerCase().includes(searchTerm.toLowerCase()); + return matchesStatus && matchesSearch; + }); + }, [webhooks, statusFilter, searchTerm]); + return (
@@ -38,7 +57,7 @@ export function WebhooksPage() {
- {["all", "active", "disabled"].map((filter) => ( + {STATUS_FILTERS.map((filter) => ( {filter.charAt(0).toUpperCase() + filter.slice(1)} @@ -46,7 +65,7 @@ export function WebhooksPage() { - +
); diff --git a/web/components/recent-events-table.tsx b/web/components/recent-events-table.tsx index d79b468..c7d83c9 100644 --- a/web/components/recent-events-table.tsx +++ b/web/components/recent-events-table.tsx @@ -85,14 +85,14 @@ export function RecentEventsTable({ events }: RecentEventsTableProps) {
) { - - ); } diff --git a/web/components/top-nav.tsx b/web/components/top-nav.tsx index ac2e202..04ab1ab 100644 --- a/web/components/top-nav.tsx +++ b/web/components/top-nav.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"; import { useLocation, Link } from "react-router"; import { SidebarTrigger } from "@/components/ui/sidebar"; import { Separator } from "@/components/ui/separator"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { DropdownMenu, @@ -21,6 +21,7 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; +import { cn } from "@/lib/utils"; const routeLabels: Record = { "/": "Overview", @@ -122,13 +123,16 @@ export function TopNav() { Toggle theme )} - - - + + + + A + diff --git a/web/components/ui/button.tsx b/web/components/ui/button.tsx index f64632d..4d38506 100644 --- a/web/components/ui/button.tsx +++ b/web/components/ui/button.tsx @@ -1,56 +1,60 @@ -import * as React from 'react' -import { Slot } from '@radix-ui/react-slot' -import { cva, type VariantProps } from 'class-variance-authority' +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { variant: { - default: 'bg-primary text-primary-foreground hover:bg-primary/90', + default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: - 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", outline: - 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", secondary: - 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: - 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', - link: 'text-primary underline-offset-4 hover:underline', + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", }, size: { - default: 'h-9 px-4 py-2 has-[>svg]:px-3', - sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', - lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', - icon: 'size-9', - 'icon-sm': 'size-8', - 'icon-lg': 'size-10', + default: "h-9 px-4 py-2 has-[>svg]:px-3", + xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-8", + "icon-lg": "size-10", }, }, defaultVariants: { - variant: 'default', - size: 'default', + variant: "default", + size: "default", }, - }, + } ) function Button({ className, - variant, - size, + variant = "default", + size = "default", asChild = false, ...props -}: React.ComponentProps<'button'> & +}: React.ComponentProps<"button"> & VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : 'button' + const Comp = asChild ? Slot.Root : "button" return ( diff --git a/web/components/ui/input.tsx b/web/components/ui/input.tsx index f199a06..f1124ae 100644 --- a/web/components/ui/input.tsx +++ b/web/components/ui/input.tsx @@ -1,17 +1,17 @@ -import * as React from 'react' +import * as React from "react" -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils" -function Input({ className, type, ...props }: React.ComponentProps<'input'>) { +function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( diff --git a/web/components/ui/separator.tsx b/web/components/ui/separator.tsx index f43aaf5..cd873e3 100644 --- a/web/components/ui/separator.tsx +++ b/web/components/ui/separator.tsx @@ -1,13 +1,13 @@ -'use client' +"use client" -import * as React from 'react' -import * as SeparatorPrimitive from '@radix-ui/react-separator' +import * as React from "react" +import { Separator as SeparatorPrimitive } from "radix-ui" -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils" function Separator({ className, - orientation = 'horizontal', + orientation = "horizontal", decorative = true, ...props }: React.ComponentProps) { @@ -17,8 +17,8 @@ function Separator({ decorative={decorative} orientation={orientation} className={cn( - 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px', - className, + "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", + className )} {...props} /> diff --git a/web/components/ui/sheet.tsx b/web/components/ui/sheet.tsx index 51041fa..cb53bb2 100644 --- a/web/components/ui/sheet.tsx +++ b/web/components/ui/sheet.tsx @@ -1,10 +1,10 @@ -'use client' +"use client" -import * as React from 'react' -import * as SheetPrimitive from '@radix-ui/react-dialog' -import { XIcon } from 'lucide-react' +import * as React from "react" +import { XIcon } from "lucide-react" +import { Dialog as SheetPrimitive } from "radix-ui" -import { cn } from '@/lib/utils' +import { cn } from "@/lib/utils" function Sheet({ ...props }: React.ComponentProps) { return @@ -36,8 +36,8 @@ function SheetOverlay({ @@ -47,10 +47,12 @@ function SheetOverlay({ function SheetContent({ className, children, - side = 'right', + side = "right", + showCloseButton = true, ...props }: React.ComponentProps & { - side?: 'top' | 'right' | 'bottom' | 'left' + side?: "top" | "right" | "bottom" | "left" + showCloseButton?: boolean }) { return ( @@ -58,44 +60,46 @@ function SheetContent({ {children} - - - Close - + {showCloseButton && ( + + + Close + + )} ) } -function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) { +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { return (
) } -function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) { +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { return (
) @@ -108,7 +112,7 @@ function SheetTitle({ return ( ) @@ -121,7 +125,7 @@ function SheetDescription({ return ( ) diff --git a/web/components/ui/sidebar.tsx b/web/components/ui/sidebar.tsx index 2177229..93cf88c 100644 --- a/web/components/ui/sidebar.tsx +++ b/web/components/ui/sidebar.tsx @@ -1,8 +1,9 @@ +"use client"; import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { cva, VariantProps } from "class-variance-authority"; +import { cva, type VariantProps } from "class-variance-authority"; import { PanelLeftIcon } from "lucide-react"; +import { Slot } from "radix-ui"; import { useIsMobile } from "@/hooks/use-mobile"; import { cn } from "@/lib/utils"; @@ -10,18 +11,18 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, } from "@/components/ui/sheet"; import { Skeleton } from "@/components/ui/skeleton"; import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, } from "@/components/ui/tooltip"; const SIDEBAR_COOKIE_NAME = "sidebar_state"; @@ -32,693 +33,694 @@ const SIDEBAR_WIDTH_ICON = "3rem"; const SIDEBAR_KEYBOARD_SHORTCUT = "b"; type SidebarContextProps = { - state: "expanded" | "collapsed"; - open: boolean; - setOpen: (open: boolean) => void; - openMobile: boolean; - setOpenMobile: (open: boolean) => void; - isMobile: boolean; - toggleSidebar: () => void; + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; }; const SidebarContext = React.createContext(null); function useSidebar() { - const context = React.useContext(SidebarContext); - if (!context) { - throw new Error("useSidebar must be used within a SidebarProvider."); - } + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider."); + } - return context; + return context; } function SidebarProvider({ - defaultOpen = true, - open: openProp, - onOpenChange: setOpenProp, - className, - style, - children, - ...props + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props }: React.ComponentProps<"div"> & { - defaultOpen?: boolean; - open?: boolean; - onOpenChange?: (open: boolean) => void; + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; }) { - const isMobile = useIsMobile(); - const [openMobile, setOpenMobile] = React.useState(false); - - // This is the internal state of the sidebar. - // We use openProp and setOpenProp for control from outside the component. - const [_open, _setOpen] = React.useState(defaultOpen); - const open = openProp ?? _open; - const setOpen = React.useCallback( - (value: boolean | ((value: boolean) => boolean)) => { - const openState = typeof value === "function" ? value(open) : value; - if (setOpenProp) { - setOpenProp(openState); - } else { - _setOpen(openState); - } - - // This sets the cookie to keep the sidebar state. - document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; - }, - [setOpenProp, open] - ); - - // Helper to toggle the sidebar. - const toggleSidebar = React.useCallback(() => { - return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); - }, [isMobile, setOpen, setOpenMobile]); - - // Adds a keyboard shortcut to toggle the sidebar. - React.useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if ( - event.key === SIDEBAR_KEYBOARD_SHORTCUT && - (event.metaKey || event.ctrlKey) - ) { - event.preventDefault(); - toggleSidebar(); - } - }; - - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [toggleSidebar]); - - // We add a state so that we can do data-state="expanded" or "collapsed". - // This makes it easier to style the sidebar with Tailwind classes. - const state = open ? "expanded" : "collapsed"; - - const contextValue = React.useMemo( - () => ({ - state, - open, - setOpen, - isMobile, - openMobile, - setOpenMobile, - toggleSidebar, - }), - [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] - ); - - return ( - - -
- {children} -
-
-
- ); + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value; + if (setOpenProp) { + setOpenProp(openState); + } else { + _setOpen(openState); + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + }, + [setOpenProp, open], + ); + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); + }, [isMobile, setOpen]); + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault(); + toggleSidebar(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed"; + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, toggleSidebar], + ); + + return ( + + +
+ {children} +
+
+
+ ); } function Sidebar({ - side = "left", - variant = "sidebar", - collapsible = "offcanvas", - className, - children, - ...props + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props }: React.ComponentProps<"div"> & { - side?: "left" | "right"; - variant?: "sidebar" | "floating" | "inset"; - collapsible?: "offcanvas" | "icon" | "none"; + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; }) { - const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); - - if (collapsible === "none") { - return ( -
- {children} -
- ); - } - - if (isMobile) { - return ( - - - - Sidebar - Displays the mobile sidebar. - -
{children}
-
-
- ); - } - - return ( -
-
- -
- ); + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); + + if (collapsible === "none") { + return ( +
+ {children} +
+ ); + } + + if (isMobile) { + return ( + + + + Sidebar + Displays the mobile sidebar. + +
{children}
+
+
+ ); + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ); } function SidebarTrigger({ - className, - onClick, - ...props + className, + onClick, + ...props }: React.ComponentProps) { - const { toggleSidebar } = useSidebar(); - - return ( - - ); + const { toggleSidebar } = useSidebar(); + + return ( + + ); } function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { - const { toggleSidebar } = useSidebar(); - - return ( -
+ ); +} - {/* URL */} - - {webhook.url} - +function WebhookRow({ webhook }: { webhook: Webhook }) { + const fetcher = useFetcher(); + const isDeleting = fetcher.state !== "idle"; - {/* Status */} - -
- - - {webhook.status} - -
-
+ return ( + + +
+ {webhook.name} +
+
- {/* Created */} - - {webhook.createdAt} - + + {webhook.url} + - {/* Actions */} - - - - - + +
+ + + {webhook.status} + +
+
- - - - Edit - - - - Delete - - -
-
-
- ))} - - + + {webhook.created_at} + + + + + + + + + + + + Edit + + + fetcher.submit( + { webhookId: String(webhook.id) }, + { method: "DELETE", action: "/webhooks" }, + ) + } + > + + Delete + + + + + ); } diff --git a/web/hooks/use-api.ts b/web/hooks/use-api.ts deleted file mode 100644 index 4ae0094..0000000 --- a/web/hooks/use-api.ts +++ /dev/null @@ -1,249 +0,0 @@ -import { - useQuery, - useMutation, - useQueryClient, - UseQueryOptions, - UseMutationOptions, -} from "@tanstack/react-query"; -import { apiClient } from "../lib/api"; -import type { - Log, - Webhook, - CreateWebhook, - UpdateWebhook, - Delivery, - CreateDelivery, - Event, - CreateEvent, - Settings, - HealthResponse, - PaginationParams, - ApiError, -} from "../lib/types"; - -export const queryKeys = { - health: ["health"] as const, - logs: (params?: PaginationParams) => ["logs", params] as const, - webhooks: (params?: PaginationParams) => ["webhooks", params] as const, - webhook: (id: number) => ["webhooks", id] as const, - deliveries: (params?: PaginationParams) => ["deliveries", params] as const, - delivery: (id: number) => ["deliveries", id] as const, - events: (params?: PaginationParams) => ["events", params] as const, - event: (id: number) => ["events", id] as const, - eventTypes: ["event-types"] as const, - settings: ["settings"] as const, -}; - -export const useHealth = ( - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.health, - queryFn: () => apiClient.getHealth(), - ...options, - }); -}; - -export const useLogs = ( - params?: PaginationParams, - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.logs(params), - queryFn: () => apiClient.getLogs(params), - ...options, - }); -}; - -export const useCreateLog = ( - options?: UseMutationOptions< - void, - ApiError, - { level: string; message: string } - > -) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => apiClient.createLog(data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["logs"] }); - }, - ...options, - }); -}; - -export const useWebhooks = ( - params?: PaginationParams, - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.webhooks(params), - queryFn: () => apiClient.getWebhooks(params), - ...options, - }); -}; - -export const useWebhook = ( - id: number, - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.webhook(id), - queryFn: () => apiClient.getWebhook(id), - ...options, - }); -}; - -export const useCreateWebhook = ( - options?: UseMutationOptions -) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => apiClient.createWebhook(data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["webhooks"] }); - }, - ...options, - }); -}; - -export const useUpdateWebhook = ( - options?: UseMutationOptions< - Webhook, - ApiError, - { id: number; data: UpdateWebhook } - > -) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ id, data }) => apiClient.updateWebhook(id, data), - onSuccess: (_, variables) => { - queryClient.invalidateQueries({ queryKey: ["webhooks"] }); - queryClient.invalidateQueries({ - queryKey: queryKeys.webhook(variables.id), - }); - }, - ...options, - }); -}; - -export const useDeleteWebhook = ( - options?: UseMutationOptions -) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (id) => apiClient.deleteWebhook(id), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["webhooks"] }); - }, - ...options, - }); -}; - -export const useDeliveries = ( - params?: PaginationParams, - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.deliveries(params), - queryFn: () => apiClient.getDeliveries(params), - ...options, - }); -}; - -export const useDelivery = ( - id: number, - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.delivery(id), - queryFn: () => apiClient.getDelivery(id), - ...options, - }); -}; - -export const useCreateDelivery = ( - options?: UseMutationOptions -) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => apiClient.createDelivery(data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["deliveries"] }); - }, - ...options, - }); -}; - -export const useEvents = ( - params?: PaginationParams, - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.events(params), - queryFn: () => apiClient.getEvents(params), - ...options, - }); -}; - -export const useEvent = ( - id: number, - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.event(id), - queryFn: () => apiClient.getEvent(id), - ...options, - }); -}; - -export const useCreateEvent = ( - options?: UseMutationOptions -) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => apiClient.createEvent(data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["events"] }); - }, - ...options, - }); -}; - -export const useEventTypes = ( - options?: UseQueryOptions -) => { - return useQuery({ - queryKey: queryKeys.eventTypes, - queryFn: () => apiClient.getEventTypes(), - ...options, - }); -}; - -export const useSettings = (options?: UseQueryOptions) => { - return useQuery({ - queryKey: queryKeys.settings, - queryFn: () => apiClient.getSettings(), - ...options, - }); -}; - -export const useUpdateSettings = ( - options?: UseMutationOptions -) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => apiClient.updateSettings(data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: queryKeys.settings }); - }, - ...options, - }); -}; diff --git a/web/hooks/use-mobile.ts b/web/hooks/use-mobile.ts index 4331d5c..2b0fe1d 100644 --- a/web/hooks/use-mobile.ts +++ b/web/hooks/use-mobile.ts @@ -1,4 +1,4 @@ -import * as React from 'react' +import * as React from "react" const MOBILE_BREAKPOINT = 768 @@ -10,9 +10,9 @@ export function useIsMobile() { const onChange = () => { setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) } - mql.addEventListener('change', onChange) + mql.addEventListener("change", onChange) setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) - return () => mql.removeEventListener('change', onChange) + return () => mql.removeEventListener("change", onChange) }, []) return !!isMobile diff --git a/web/package.json b/web/package.json index af89ed1..8072397 100644 --- a/web/package.json +++ b/web/package.json @@ -52,6 +52,7 @@ "isbot": "^5.1.0", "lucide-react": "^0.454.0", "next-themes": "latest", + "radix-ui": "^1.4.3", "react": "^18.3.1", "react-day-picker": "9.8.0", "react-dom": "^18.3.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index b1f1592..938fa19 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -134,6 +134,9 @@ importers: next-themes: specifier: latest version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + radix-ui: + specifier: ^1.4.3 + version: 1.4.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -527,9 +530,41 @@ packages: '@radix-ui/number@1.1.0': resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + '@radix-ui/primitive@1.1.1': resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-accordion@1.2.2': resolution: {integrity: sha512-b1oh54x4DMCdGsB4/7ahiSrViXxaBwRPotiZNnYXjLha9vfuURSAZErki6qjDoSIV0eXx5v57XnTGVtGwnfp2g==} peerDependencies: @@ -543,6 +578,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-alert-dialog@1.1.4': resolution: {integrity: sha512-A6Kh23qZDLy3PSU4bh2UJZznOrUdHImIXqF8YtUa6CN73f8EOO9XlXSCd9IHyPvIquTaa/kwaSWzZTtUvgXVGw==} peerDependencies: @@ -569,6 +617,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-aspect-ratio@1.1.1': resolution: {integrity: sha512-kNU4FIpcFMBLkOUcgeIteH06/8JLBcYY6Le1iKenDGCYNYFX3TQqCZjzkOsz37h7r94/99GTb7YhEr98ZBJibw==} peerDependencies: @@ -582,6 +643,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-avatar@1.1.2': resolution: {integrity: sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==} peerDependencies: @@ -608,6 +695,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collapsible@1.1.2': resolution: {integrity: sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==} peerDependencies: @@ -634,6 +747,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.1': resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} peerDependencies: @@ -652,6 +778,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-context-menu@2.2.4': resolution: {integrity: sha512-ap4wdGwK52rJxGkwukU1NrnEodsUFQIooANKu+ey7d6raQ2biTcEf8za1zr0mgFHieevRTB2nK4dJeN8pTAZGQ==} peerDependencies: @@ -674,6 +813,28 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-dialog@1.1.4': resolution: {integrity: sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==} peerDependencies: @@ -696,21 +857,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.3': - resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: '@types/react': '*' - '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-dropdown-menu@2.1.4': - resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==} + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -722,17 +879,21 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.1': - resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} + '@radix-ui/react-dismissable-layer@1.1.3': + resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} peerDependencies: '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true - '@radix-ui/react-focus-scope@1.1.1': - resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==} + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -744,8 +905,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-hover-card@1.1.4': - resolution: {integrity: sha512-QSUUnRA3PQ2UhvoCv3eYvMnCAgGQW+sTu86QPuNb+ZMi+ZENd6UWpiXbcWDQ4AEaKF9KKpCHBeaJz9Rw6lRlaQ==} + '@radix-ui/react-dropdown-menu@2.1.4': + resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -757,8 +918,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-id@1.1.0': - resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + '@radix-ui/react-focus-guards@1.1.1': + resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -766,8 +927,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-id@1.1.1': - resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -775,8 +936,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-label@2.1.1': - resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==} + '@radix-ui/react-focus-scope@1.1.1': + resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -788,8 +949,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-menu@2.1.4': - resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==} + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -801,8 +962,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-menubar@1.1.4': - resolution: {integrity: sha512-+KMpi7VAZuB46+1LD7a30zb5IxyzLgC8m8j42gk3N4TUCcViNQdX8FhoH1HDvYiA8quuqcek4R4bYpPn/SY1GA==} + '@radix-ui/react-form@0.1.8': + resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -814,8 +975,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-navigation-menu@1.2.3': - resolution: {integrity: sha512-IQWAsQ7dsLIYDrn0WqPU+cdM7MONTv9nqrLVYoie3BPiabSfUVDe6Fr+oEt0Cofsr9ONDcDe9xhmJbL1Uq1yKg==} + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -827,8 +988,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popover@1.1.4': - resolution: {integrity: sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==} + '@radix-ui/react-hover-card@1.1.4': + resolution: {integrity: sha512-QSUUnRA3PQ2UhvoCv3eYvMnCAgGQW+sTu86QPuNb+ZMi+ZENd6UWpiXbcWDQ4AEaKF9KKpCHBeaJz9Rw6lRlaQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -840,8 +1001,26 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.2.1': - resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} + '@radix-ui/react-id@1.1.0': + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.1': + resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -853,8 +1032,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.3': - resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -866,8 +1045,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-presence@1.1.2': - resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -879,8 +1058,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.0.1': - resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + '@radix-ui/react-menu@2.1.4': + resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -892,8 +1071,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.4': - resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -905,8 +1084,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-progress@1.1.1': - resolution: {integrity: sha512-6diOawA84f/eMxFHcWut0aE1C2kyE9dOyCTQOMRR2C/qPiXz/X0SaiA/RLbapQaXUCmy0/hLMf9meSccD1N0pA==} + '@radix-ui/react-menubar@1.1.4': + resolution: {integrity: sha512-+KMpi7VAZuB46+1LD7a30zb5IxyzLgC8m8j42gk3N4TUCcViNQdX8FhoH1HDvYiA8quuqcek4R4bYpPn/SY1GA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -918,8 +1097,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-radio-group@1.2.2': - resolution: {integrity: sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==} + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -931,8 +1110,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.1': - resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} + '@radix-ui/react-navigation-menu@1.2.3': + resolution: {integrity: sha512-IQWAsQ7dsLIYDrn0WqPU+cdM7MONTv9nqrLVYoie3BPiabSfUVDe6Fr+oEt0Cofsr9ONDcDe9xhmJbL1Uq1yKg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -944,8 +1123,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.2.2': - resolution: {integrity: sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==} + '@radix-ui/react-one-time-password-field@0.1.8': + resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -957,8 +1136,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-select@2.1.4': - resolution: {integrity: sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==} + '@radix-ui/react-password-toggle-field@0.1.3': + resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -970,8 +1149,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-separator@1.1.1': - resolution: {integrity: sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==} + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -983,8 +1162,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slider@1.2.2': - resolution: {integrity: sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA==} + '@radix-ui/react-popover@1.1.4': + resolution: {integrity: sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -996,26 +1175,34 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.1.1': - resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + '@radix-ui/react-popper@1.2.1': + resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} peerDependencies: '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true - '@radix-ui/react-slot@1.2.4': - resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true - '@radix-ui/react-switch@1.1.2': - resolution: {integrity: sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==} + '@radix-ui/react-portal@1.1.3': + resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1027,8 +1214,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tabs@1.1.2': - resolution: {integrity: sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==} + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1040,60 +1227,496 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toast@1.2.4': - resolution: {integrity: sha512-Sch9idFJHJTMH9YNpxxESqABcAFweJG4tKv+0zo0m5XBvUSL8FM5xKcJLFLXononpePs8IclyX1KieL5SDUNgA==} + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.0.1': + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.1': + resolution: {integrity: sha512-6diOawA84f/eMxFHcWut0aE1C2kyE9dOyCTQOMRR2C/qPiXz/X0SaiA/RLbapQaXUCmy0/hLMf9meSccD1N0pA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.2.2': + resolution: {integrity: sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.1': + resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.2': + resolution: {integrity: sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.1.4': + resolution: {integrity: sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.1': + resolution: {integrity: sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.2.2': + resolution: {integrity: sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.1.1': + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.1.2': + resolution: {integrity: sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.2': + resolution: {integrity: sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.4': + resolution: {integrity: sha512-Sch9idFJHJTMH9YNpxxESqABcAFweJG4tKv+0zo0m5XBvUSL8FM5xKcJLFLXononpePs8IclyX1KieL5SDUNgA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.1': + resolution: {integrity: sha512-OgDLZEA30Ylyz8YSXvnGqIHtERqnUt1KUYTKdw/y8u7Ci6zGiJfXc02jahmcSNK3YcErqioj/9flWC9S1ihfwg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.11': + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.1': + resolution: {integrity: sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.1.6': + resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: '@types/react': '*' - '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-toggle-group@1.1.1': - resolution: {integrity: sha512-OgDLZEA30Ylyz8YSXvnGqIHtERqnUt1KUYTKdw/y8u7Ci6zGiJfXc02jahmcSNK3YcErqioj/9flWC9S1ihfwg==} + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: '@types/react': '*' - '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-toggle@1.1.1': - resolution: {integrity: sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==} + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: '@types/react': '*' - '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-tooltip@1.1.6': - resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==} + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: '@types/react': '*' - '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-use-callback-ref@1.1.0': - resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + '@radix-ui/react-use-escape-keydown@1.1.0': + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1101,8 +1724,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-controllable-state@1.1.0': - resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1110,8 +1733,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.0': - resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1146,6 +1769,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-rect@1.1.0': resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} peerDependencies: @@ -1155,6 +1787,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-size@1.1.0': resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} peerDependencies: @@ -1164,6 +1805,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-visually-hidden@1.1.1': resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==} peerDependencies: @@ -1177,9 +1827,25 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@react-router/dev@7.14.0': resolution: {integrity: sha512-/1ElF4lDTEIZ/rbEdlj6MmRY9ERRDyaTswWes+3pbqEKF2r/ixSzACueHWIfV9ULg/x5/weCvSexDD9f16ObwA==} engines: {node: '>=20.0.0'} @@ -2158,6 +2824,19 @@ packages: resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} engines: {node: '>=0.6'} + radix-ui@1.4.3: + resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -2823,8 +3502,38 @@ snapshots: '@radix-ui/number@1.1.0': {} + '@radix-ui/number@1.1.1': {} + '@radix-ui/primitive@1.1.1': {} + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-accordion@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2842,6 +3551,20 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-alert-dialog@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2865,6 +3588,15 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-aspect-ratio@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2874,6 +3606,28 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-avatar@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-context': 1.1.1(@types/react@18.3.28)(react@18.3.1) @@ -2902,6 +3656,38 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-collapsible@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2930,6 +3716,18 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.28)(react@18.3.1)': dependencies: react: 18.3.1 @@ -2942,6 +3740,20 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-context-menu@2.2.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2962,6 +3774,34 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-dialog@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2990,6 +3830,25 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3003,6 +3862,21 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-dropdown-menu@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3024,6 +3898,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.28)(react@18.3.1) @@ -3035,6 +3915,48 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-form@0.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-hover-card@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3075,6 +3997,41 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-label@2.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-menu@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3096,7 +4053,25 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-menubar@1.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) optionalDependencies: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) @@ -3119,6 +4094,28 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-navigation-menu@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3141,6 +4138,65 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-popover@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3182,6 +4238,24 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3192,6 +4266,16 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.28)(react@18.3.1) @@ -3202,6 +4286,16 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.1(@types/react@18.3.28)(react@18.3.1) @@ -3211,6 +4305,15 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.2.4(@types/react@18.3.28)(react@18.3.1) @@ -3230,6 +4333,16 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-progress@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-radio-group@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3248,6 +4361,24 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3265,6 +4396,40 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-scroll-area@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 @@ -3311,6 +4476,35 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-separator@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3320,6 +4514,15 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-separator@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-slider@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 @@ -3339,6 +4542,25 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-slider@1.3.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-slot@1.1.1(@types/react@18.3.28)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.28)(react@18.3.1) @@ -3346,6 +4568,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + '@radix-ui/react-slot@1.2.4(@types/react@18.3.28)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) @@ -3368,6 +4597,37 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-switch@1.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-tabs@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3384,6 +4644,26 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-toast@1.2.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-toast@1.2.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3419,6 +4699,21 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-toggle@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3430,6 +4725,32 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-toggle@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-tooltip@1.1.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3450,12 +4771,38 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.28)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.28)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.28)(react@18.3.1) @@ -3463,6 +4810,21 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.28)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.28)(react@18.3.1) @@ -3470,6 +4832,20 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.28)(react@18.3.1)': dependencies: react: 18.3.1 @@ -3488,6 +4864,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.28)(react@18.3.1)': dependencies: '@radix-ui/rect': 1.1.0 @@ -3495,6 +4877,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + '@radix-ui/react-use-size@1.1.0(@types/react@18.3.28)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.28)(react@18.3.1) @@ -3502,6 +4891,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + '@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3511,8 +4907,19 @@ snapshots: '@types/react': 18.3.28 '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@radix-ui/rect@1.1.0': {} + '@radix-ui/rect@1.1.1': {} + '@react-router/dev@7.14.0(@react-router/serve@7.14.0(react-router@7.14.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3))(@types/node@22.19.17)(lightningcss@1.32.0)(react-router@7.14.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.17)(lightningcss@1.32.0))': dependencies: '@babel/core': 7.29.0 @@ -4388,6 +5795,69 @@ snapshots: dependencies: side-channel: 1.1.0 + radix-ui@1.4.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-form': 0.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + range-parser@1.2.1: {} raw-body@2.5.3: From 8570ffbfe7ce931215b603ede58ff8a5d70ddc37 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Tue, 14 Apr 2026 14:09:15 +0300 Subject: [PATCH 10/20] refactor: remove "use client" indications on the components --- web/components.json | 2 +- web/components/ui/accordion.tsx | 1 - web/components/ui/alert-dialog.tsx | 1 - web/components/ui/aspect-ratio.tsx | 1 - web/components/ui/avatar.tsx | 1 - web/components/ui/calendar.tsx | 1 - web/components/ui/carousel.tsx | 1 - web/components/ui/chart.tsx | 1 - web/components/ui/checkbox.tsx | 1 - web/components/ui/collapsible.tsx | 1 - web/components/ui/command.tsx | 1 - web/components/ui/context-menu.tsx | 1 - web/components/ui/dialog.tsx | 1 - web/components/ui/drawer.tsx | 1 - web/components/ui/dropdown-menu.tsx | 1 - web/components/ui/field.tsx | 1 - web/components/ui/form.tsx | 1 - web/components/ui/hover-card.tsx | 1 - web/components/ui/input-group.tsx | 1 - web/components/ui/input-otp.tsx | 1 - web/components/ui/label.tsx | 1 - web/components/ui/menubar.tsx | 1 - web/components/ui/popover.tsx | 1 - web/components/ui/progress.tsx | 1 - web/components/ui/radio-group.tsx | 1 - web/components/ui/resizable.tsx | 1 - web/components/ui/scroll-area.tsx | 1 - web/components/ui/select.tsx | 1 - web/components/ui/separator.tsx | 1 - web/components/ui/sheet.tsx | 1 - web/components/ui/sidebar.tsx | 1 - web/components/ui/slider.tsx | 1 - web/components/ui/sonner.tsx | 1 - web/components/ui/switch.tsx | 1 - web/components/ui/table.tsx | 1 - web/components/ui/tabs.tsx | 1 - web/components/ui/toast.tsx | 1 - web/components/ui/toaster.tsx | 1 - web/components/ui/toggle-group.tsx | 1 - web/components/ui/toggle.tsx | 1 - web/components/ui/tooltip.tsx | 1 - web/components/ui/use-mobile.tsx | 19 --- web/components/ui/use-toast.ts | 191 ---------------------------- web/hooks/use-toast.ts | 1 - 44 files changed, 1 insertion(+), 252 deletions(-) delete mode 100644 web/components/ui/use-mobile.tsx delete mode 100644 web/components/ui/use-toast.ts diff --git a/web/components.json b/web/components.json index 4ee62ee..d1812d1 100644 --- a/web/components.json +++ b/web/components.json @@ -1,7 +1,7 @@ { "$schema": "https://ui.shadcn.com/schema.json", "style": "new-york", - "rsc": true, + "rsc": false, "tsx": true, "tailwind": { "config": "", diff --git a/web/components/ui/accordion.tsx b/web/components/ui/accordion.tsx index e538a33..3c5b1f3 100644 --- a/web/components/ui/accordion.tsx +++ b/web/components/ui/accordion.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as AccordionPrimitive from '@radix-ui/react-accordion' diff --git a/web/components/ui/alert-dialog.tsx b/web/components/ui/alert-dialog.tsx index 9704452..975d592 100644 --- a/web/components/ui/alert-dialog.tsx +++ b/web/components/ui/alert-dialog.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' diff --git a/web/components/ui/aspect-ratio.tsx b/web/components/ui/aspect-ratio.tsx index 40bb120..9a42b44 100644 --- a/web/components/ui/aspect-ratio.tsx +++ b/web/components/ui/aspect-ratio.tsx @@ -1,4 +1,3 @@ -'use client' import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' diff --git a/web/components/ui/avatar.tsx b/web/components/ui/avatar.tsx index aa98465..57844a6 100644 --- a/web/components/ui/avatar.tsx +++ b/web/components/ui/avatar.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as AvatarPrimitive from '@radix-ui/react-avatar' diff --git a/web/components/ui/calendar.tsx b/web/components/ui/calendar.tsx index eaa373e..d836886 100644 --- a/web/components/ui/calendar.tsx +++ b/web/components/ui/calendar.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import { diff --git a/web/components/ui/carousel.tsx b/web/components/ui/carousel.tsx index d4a768e..c330636 100644 --- a/web/components/ui/carousel.tsx +++ b/web/components/ui/carousel.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import useEmblaCarousel, { diff --git a/web/components/ui/chart.tsx b/web/components/ui/chart.tsx index 421fe58..7f46321 100644 --- a/web/components/ui/chart.tsx +++ b/web/components/ui/chart.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as RechartsPrimitive from 'recharts' diff --git a/web/components/ui/checkbox.tsx b/web/components/ui/checkbox.tsx index 37d340f..9a0413b 100644 --- a/web/components/ui/checkbox.tsx +++ b/web/components/ui/checkbox.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as CheckboxPrimitive from '@radix-ui/react-checkbox' diff --git a/web/components/ui/collapsible.tsx b/web/components/ui/collapsible.tsx index 3cbdff6..2660a0f 100644 --- a/web/components/ui/collapsible.tsx +++ b/web/components/ui/collapsible.tsx @@ -1,4 +1,3 @@ -'use client' import * as CollapsiblePrimitive from '@radix-ui/react-collapsible' diff --git a/web/components/ui/command.tsx b/web/components/ui/command.tsx index 4833ca8..3ef3337 100644 --- a/web/components/ui/command.tsx +++ b/web/components/ui/command.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import { Command as CommandPrimitive } from 'cmdk' diff --git a/web/components/ui/context-menu.tsx b/web/components/ui/context-menu.tsx index 9e536f2..c404dcb 100644 --- a/web/components/ui/context-menu.tsx +++ b/web/components/ui/context-menu.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as ContextMenuPrimitive from '@radix-ui/react-context-menu' diff --git a/web/components/ui/dialog.tsx b/web/components/ui/dialog.tsx index 243fb19..a9ee010 100644 --- a/web/components/ui/dialog.tsx +++ b/web/components/ui/dialog.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as DialogPrimitive from '@radix-ui/react-dialog' diff --git a/web/components/ui/drawer.tsx b/web/components/ui/drawer.tsx index 307bdce..733088c 100644 --- a/web/components/ui/drawer.tsx +++ b/web/components/ui/drawer.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import { Drawer as DrawerPrimitive } from 'vaul' diff --git a/web/components/ui/dropdown-menu.tsx b/web/components/ui/dropdown-menu.tsx index a2096fa..94df99a 100644 --- a/web/components/ui/dropdown-menu.tsx +++ b/web/components/ui/dropdown-menu.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' diff --git a/web/components/ui/field.tsx b/web/components/ui/field.tsx index f4c2f21..d0159e0 100644 --- a/web/components/ui/field.tsx +++ b/web/components/ui/field.tsx @@ -1,4 +1,3 @@ -'use client' import { useMemo } from 'react' import { cva, type VariantProps } from 'class-variance-authority' diff --git a/web/components/ui/form.tsx b/web/components/ui/form.tsx index 6233f94..e98df87 100644 --- a/web/components/ui/form.tsx +++ b/web/components/ui/form.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as LabelPrimitive from '@radix-ui/react-label' diff --git a/web/components/ui/hover-card.tsx b/web/components/ui/hover-card.tsx index 55d6f76..9c9d603 100644 --- a/web/components/ui/hover-card.tsx +++ b/web/components/ui/hover-card.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as HoverCardPrimitive from '@radix-ui/react-hover-card' diff --git a/web/components/ui/input-group.tsx b/web/components/ui/input-group.tsx index 183d297..ce7b77a 100644 --- a/web/components/ui/input-group.tsx +++ b/web/components/ui/input-group.tsx @@ -1,4 +1,3 @@ -'use client' import { cva, type VariantProps } from 'class-variance-authority' diff --git a/web/components/ui/input-otp.tsx b/web/components/ui/input-otp.tsx index 3f6c477..c1de2ba 100644 --- a/web/components/ui/input-otp.tsx +++ b/web/components/ui/input-otp.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import { OTPInput, OTPInputContext } from 'input-otp' diff --git a/web/components/ui/label.tsx b/web/components/ui/label.tsx index 5d66da2..42021e8 100644 --- a/web/components/ui/label.tsx +++ b/web/components/ui/label.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as LabelPrimitive from '@radix-ui/react-label' diff --git a/web/components/ui/menubar.tsx b/web/components/ui/menubar.tsx index 791360c..f7c353e 100644 --- a/web/components/ui/menubar.tsx +++ b/web/components/ui/menubar.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as MenubarPrimitive from '@radix-ui/react-menubar' diff --git a/web/components/ui/popover.tsx b/web/components/ui/popover.tsx index b4fc827..bd3e14f 100644 --- a/web/components/ui/popover.tsx +++ b/web/components/ui/popover.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as PopoverPrimitive from '@radix-ui/react-popover' diff --git a/web/components/ui/progress.tsx b/web/components/ui/progress.tsx index ab38305..594d931 100644 --- a/web/components/ui/progress.tsx +++ b/web/components/ui/progress.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as ProgressPrimitive from '@radix-ui/react-progress' diff --git a/web/components/ui/radio-group.tsx b/web/components/ui/radio-group.tsx index e9af867..44a354f 100644 --- a/web/components/ui/radio-group.tsx +++ b/web/components/ui/radio-group.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as RadioGroupPrimitive from '@radix-ui/react-radio-group' diff --git a/web/components/ui/resizable.tsx b/web/components/ui/resizable.tsx index 5c2f91d..a63722f 100644 --- a/web/components/ui/resizable.tsx +++ b/web/components/ui/resizable.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import { GripVerticalIcon } from 'lucide-react' diff --git a/web/components/ui/scroll-area.tsx b/web/components/ui/scroll-area.tsx index dc98eb0..9c5abc8 100644 --- a/web/components/ui/scroll-area.tsx +++ b/web/components/ui/scroll-area.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' diff --git a/web/components/ui/select.tsx b/web/components/ui/select.tsx index 5db0bca..748cc18 100644 --- a/web/components/ui/select.tsx +++ b/web/components/ui/select.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as SelectPrimitive from '@radix-ui/react-select' diff --git a/web/components/ui/separator.tsx b/web/components/ui/separator.tsx index cd873e3..6f5e2a9 100644 --- a/web/components/ui/separator.tsx +++ b/web/components/ui/separator.tsx @@ -1,4 +1,3 @@ -"use client" import * as React from "react" import { Separator as SeparatorPrimitive } from "radix-ui" diff --git a/web/components/ui/sheet.tsx b/web/components/ui/sheet.tsx index cb53bb2..76e0f2a 100644 --- a/web/components/ui/sheet.tsx +++ b/web/components/ui/sheet.tsx @@ -1,4 +1,3 @@ -"use client" import * as React from "react" import { XIcon } from "lucide-react" diff --git a/web/components/ui/sidebar.tsx b/web/components/ui/sidebar.tsx index 93cf88c..17db13c 100644 --- a/web/components/ui/sidebar.tsx +++ b/web/components/ui/sidebar.tsx @@ -1,4 +1,3 @@ -"use client"; import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; diff --git a/web/components/ui/slider.tsx b/web/components/ui/slider.tsx index 773e064..b19040a 100644 --- a/web/components/ui/slider.tsx +++ b/web/components/ui/slider.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as SliderPrimitive from '@radix-ui/react-slider' diff --git a/web/components/ui/sonner.tsx b/web/components/ui/sonner.tsx index 0626caf..4bb5d53 100644 --- a/web/components/ui/sonner.tsx +++ b/web/components/ui/sonner.tsx @@ -1,4 +1,3 @@ -'use client' import { useTheme } from 'next-themes' import { Toaster as Sonner, ToasterProps } from 'sonner' diff --git a/web/components/ui/switch.tsx b/web/components/ui/switch.tsx index 3c4cfa3..113af8f 100644 --- a/web/components/ui/switch.tsx +++ b/web/components/ui/switch.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as SwitchPrimitive from '@radix-ui/react-switch' diff --git a/web/components/ui/table.tsx b/web/components/ui/table.tsx index fcdd10c..50f1e9f 100644 --- a/web/components/ui/table.tsx +++ b/web/components/ui/table.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' diff --git a/web/components/ui/tabs.tsx b/web/components/ui/tabs.tsx index ff67104..42d4e4c 100644 --- a/web/components/ui/tabs.tsx +++ b/web/components/ui/tabs.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as TabsPrimitive from '@radix-ui/react-tabs' diff --git a/web/components/ui/toast.tsx b/web/components/ui/toast.tsx index 40eedf5..06c6481 100644 --- a/web/components/ui/toast.tsx +++ b/web/components/ui/toast.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as ToastPrimitives from '@radix-ui/react-toast' diff --git a/web/components/ui/toaster.tsx b/web/components/ui/toaster.tsx index 3b91885..35cd9ed 100644 --- a/web/components/ui/toaster.tsx +++ b/web/components/ui/toaster.tsx @@ -1,4 +1,3 @@ -'use client' import { useToast } from '@/hooks/use-toast' import { diff --git a/web/components/ui/toggle-group.tsx b/web/components/ui/toggle-group.tsx index 0ab9971..eb3d123 100644 --- a/web/components/ui/toggle-group.tsx +++ b/web/components/ui/toggle-group.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group' diff --git a/web/components/ui/toggle.tsx b/web/components/ui/toggle.tsx index ad6b286..53f8c67 100644 --- a/web/components/ui/toggle.tsx +++ b/web/components/ui/toggle.tsx @@ -1,4 +1,3 @@ -'use client' import * as React from 'react' import * as TogglePrimitive from '@radix-ui/react-toggle' diff --git a/web/components/ui/tooltip.tsx b/web/components/ui/tooltip.tsx index ec65c1e..5a0924b 100644 --- a/web/components/ui/tooltip.tsx +++ b/web/components/ui/tooltip.tsx @@ -1,4 +1,3 @@ -"use client" import * as React from "react" import { Tooltip as TooltipPrimitive } from "radix-ui" diff --git a/web/components/ui/use-mobile.tsx b/web/components/ui/use-mobile.tsx deleted file mode 100644 index 4331d5c..0000000 --- a/web/components/ui/use-mobile.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from 'react' - -const MOBILE_BREAKPOINT = 768 - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState(undefined) - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) - } - mql.addEventListener('change', onChange) - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) - return () => mql.removeEventListener('change', onChange) - }, []) - - return !!isMobile -} diff --git a/web/components/ui/use-toast.ts b/web/components/ui/use-toast.ts deleted file mode 100644 index 8932bc5..0000000 --- a/web/components/ui/use-toast.ts +++ /dev/null @@ -1,191 +0,0 @@ -'use client' - -// Inspired by react-hot-toast library -import * as React from 'react' - -import type { ToastActionElement, ToastProps } from '@/components/ui/toast' - -const TOAST_LIMIT = 1 -const TOAST_REMOVE_DELAY = 1000000 - -type ToasterToast = ToastProps & { - id: string - title?: React.ReactNode - description?: React.ReactNode - action?: ToastActionElement -} - -const actionTypes = { - ADD_TOAST: 'ADD_TOAST', - UPDATE_TOAST: 'UPDATE_TOAST', - DISMISS_TOAST: 'DISMISS_TOAST', - REMOVE_TOAST: 'REMOVE_TOAST', -} as const - -let count = 0 - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER - return count.toString() -} - -type ActionType = typeof actionTypes - -type Action = - | { - type: ActionType['ADD_TOAST'] - toast: ToasterToast - } - | { - type: ActionType['UPDATE_TOAST'] - toast: Partial - } - | { - type: ActionType['DISMISS_TOAST'] - toastId?: ToasterToast['id'] - } - | { - type: ActionType['REMOVE_TOAST'] - toastId?: ToasterToast['id'] - } - -interface State { - toasts: ToasterToast[] -} - -const toastTimeouts = new Map>() - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId) - dispatch({ - type: 'REMOVE_TOAST', - toastId: toastId, - }) - }, TOAST_REMOVE_DELAY) - - toastTimeouts.set(toastId, timeout) -} - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case 'ADD_TOAST': - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - } - - case 'UPDATE_TOAST': - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), - } - - case 'DISMISS_TOAST': { - const { toastId } = action - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId) - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id) - }) - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t, - ), - } - } - case 'REMOVE_TOAST': - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - } - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - } - } -} - -const listeners: Array<(state: State) => void> = [] - -let memoryState: State = { toasts: [] } - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action) - listeners.forEach((listener) => { - listener(memoryState) - }) -} - -type Toast = Omit - -function toast({ ...props }: Toast) { - const id = genId() - - const update = (props: ToasterToast) => - dispatch({ - type: 'UPDATE_TOAST', - toast: { ...props, id }, - }) - const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id }) - - dispatch({ - type: 'ADD_TOAST', - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss() - }, - }, - }) - - return { - id: id, - dismiss, - update, - } -} - -function useToast() { - const [state, setState] = React.useState(memoryState) - - React.useEffect(() => { - listeners.push(setState) - return () => { - const index = listeners.indexOf(setState) - if (index > -1) { - listeners.splice(index, 1) - } - } - }, [state]) - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }), - } -} - -export { useToast, toast } diff --git a/web/hooks/use-toast.ts b/web/hooks/use-toast.ts index 8932bc5..c89a66a 100644 --- a/web/hooks/use-toast.ts +++ b/web/hooks/use-toast.ts @@ -1,4 +1,3 @@ -'use client' // Inspired by react-hot-toast library import * as React from 'react' From af2c877e53cefa1bfaa9b91c64c38d9e05677497 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Fri, 17 Apr 2026 08:38:49 +0300 Subject: [PATCH 11/20] feat: implement pagination on all the logs --- web/app/routes/deliveries.tsx | 21 ++++++-- web/app/routes/logs.tsx | 63 +++++++++++++---------- web/app/routes/webhooks.tsx | 77 ++++++++++++++++------------- web/components/logs-list.tsx | 4 +- web/components/pages/deliveries.tsx | 55 ++++++++++++++++++++- web/components/pages/logs.tsx | 52 ++++++++++++++++++- web/components/pages/webhooks.tsx | 54 +++++++++++++++++++- 7 files changed, 254 insertions(+), 72 deletions(-) diff --git a/web/app/routes/deliveries.tsx b/web/app/routes/deliveries.tsx index f7bc532..a580eed 100644 --- a/web/app/routes/deliveries.tsx +++ b/web/app/routes/deliveries.tsx @@ -4,9 +4,14 @@ import { DeliveriesPage } from "@/components/pages/deliveries"; import { Skeleton } from "@/components/ui/skeleton"; import { Card } from "@/components/ui/card"; -export async function clientLoader() { - const deliveries = await getDeliveries(); - return { deliveries }; +const DEFAULT_LIMIT = 20; + +export async function clientLoader({ request }: { request: Request }) { + const url = new URL(request.url); + const page = Number(url.searchParams.get("page")) || 0; + const limit = Number(url.searchParams.get("limit")) || DEFAULT_LIMIT; + const deliveries = await getDeliveries({ page, limit }); + return { deliveries, page, limit }; } export function HydrateFallback() { @@ -31,7 +36,13 @@ export function HydrateFallback() { export default function Deliveries({ loaderData, }: { - loaderData: { deliveries: Delivery[] }; + loaderData: { deliveries: Delivery[]; page: number; limit: number }; }) { - return ; + return ( + + ); } diff --git a/web/app/routes/logs.tsx b/web/app/routes/logs.tsx index c14ee9e..5bc3e18 100644 --- a/web/app/routes/logs.tsx +++ b/web/app/routes/logs.tsx @@ -3,38 +3,49 @@ import type { Log } from "@/lib/types"; import { LogsPage } from "@/components/pages/logs"; import { Skeleton } from "@/components/ui/skeleton"; -export async function clientLoader() { - const logs = await getLogs(); - return { logs }; +const DEFAULT_LIMIT = 20; + +export async function clientLoader({ request }: { request: Request }) { + const url = new URL(request.url); + const page = Number(url.searchParams.get("page")) || 0; + const limit = Number(url.searchParams.get("limit")) || DEFAULT_LIMIT; + const logs = await getLogs({ page, limit }); + return { logs, page, limit }; } export function HydrateFallback() { - return ( -
-
- - -
- -
- {["log-1", "log-2", "log-3", "log-4", "log-5"].map((id) => ( -
- -
- - -
-
- ))} -
-
- ); + return ( +
+
+ + +
+ +
+ {["log-1", "log-2", "log-3", "log-4", "log-5"].map((id) => ( +
+ +
+ + +
+
+ ))} +
+
+ ); } export default function Logs({ - loaderData, + loaderData, }: { - loaderData: { logs: Log[] }; + loaderData: { logs: Log[]; page: number; limit: number }; }) { - return ; + return ( + + ); } diff --git a/web/app/routes/webhooks.tsx b/web/app/routes/webhooks.tsx index 5872b59..ef62200 100644 --- a/web/app/routes/webhooks.tsx +++ b/web/app/routes/webhooks.tsx @@ -4,48 +4,57 @@ import { WebhooksPage } from "@/components/pages/webhooks"; import { Skeleton } from "@/components/ui/skeleton"; import { Card } from "@/components/ui/card"; -export async function clientLoader() { - const webhooks = await getWebhooks(); - return { webhooks }; +const DEFAULT_LIMIT = 20; + +export async function clientLoader({ request }: { request: Request }) { + const url = new URL(request.url); + const page = Number(url.searchParams.get("page")) || 0; + const limit = Number(url.searchParams.get("limit")) || DEFAULT_LIMIT; + const webhooks = await getWebhooks({ page, limit }); + return { webhooks, page, limit }; } -export async function clientAction({ - request, -}: { request: Request }) { - const formData = await request.formData(); - const webhookId = Number(formData.get("webhookId")); - await deleteWebhook(webhookId); - return { ok: true }; +export async function clientAction({ request }: { request: Request }) { + const formData = await request.formData(); + const webhookId = Number(formData.get("webhookId")); + await deleteWebhook(webhookId); + return { ok: true }; } export function HydrateFallback() { - return ( -
-
- - -
-
- - -
- - -
- - {["wh-1", "wh-2", "wh-3"].map((id) => ( - - ))} -
-
-
- ); + return ( +
+
+ + +
+
+ + +
+ + +
+ + {["wh-1", "wh-2", "wh-3"].map((id) => ( + + ))} +
+
+
+ ); } export default function Webhooks({ - loaderData, + loaderData, }: { - loaderData: { webhooks: Webhook[] }; + loaderData: { webhooks: Webhook[]; page: number; limit: number }; }) { - return ; + return ( + + ); } diff --git a/web/components/logs-list.tsx b/web/components/logs-list.tsx index cd779be..64eccc2 100644 --- a/web/components/logs-list.tsx +++ b/web/components/logs-list.tsx @@ -40,7 +40,7 @@ export function LogsList({ logs }: LogsListProps) { return ( - + @@ -59,7 +59,7 @@ export function LogsList({ logs }: LogsListProps) { {log.timestamp} - + {log.message} diff --git a/web/components/pages/deliveries.tsx b/web/components/pages/deliveries.tsx index 9da51a2..16d8751 100644 --- a/web/components/pages/deliveries.tsx +++ b/web/components/pages/deliveries.tsx @@ -1,12 +1,37 @@ +import { useNavigate } from "react-router"; import type { Delivery } from "@/lib/types"; import { DeliveriesTable } from "../deliveries-table"; import { Card } from "@/components/ui/card"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; interface DeliveriesPageProps { deliveries: Delivery[]; + page: number; + limit: number; } -export function DeliveriesPage({ deliveries }: DeliveriesPageProps) { +export function DeliveriesPage({ deliveries, page, limit }: DeliveriesPageProps) { + const navigate = useNavigate(); + const hasMore = deliveries.length === limit; + + const handlePrev = () => { + if (page > 0) { + navigate(`?page=${page - 1}&limit=${limit}`); + } + }; + + const handleNext = () => { + if (hasMore) { + navigate(`?page=${page + 1}&limit=${limit}`); + } + }; + return (
@@ -21,6 +46,32 @@ export function DeliveriesPage({ deliveries }: DeliveriesPageProps) { + + + + { + e.preventDefault(); + handlePrev(); + }} + aria-disabled={page === 0} + className={page === 0 ? "pointer-events-none opacity-50" : undefined} + /> + + + { + e.preventDefault(); + handleNext(); + }} + aria-disabled={!hasMore} + className={!hasMore ? "pointer-events-none opacity-50" : undefined} + /> + + +
); -} +} \ No newline at end of file diff --git a/web/components/pages/logs.tsx b/web/components/pages/logs.tsx index 748baeb..f1ad762 100644 --- a/web/components/pages/logs.tsx +++ b/web/components/pages/logs.tsx @@ -1,20 +1,44 @@ import { useMemo, useState } from "react"; +import { useNavigate } from "react-router"; import type { Log } from "@/lib/types"; import { LogsFilter } from "../logs-filter"; import { LogsList } from "../logs-list"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; interface LogsPageProps { logs: Log[]; + page: number; + limit: number; } -export function LogsPage({ logs }: LogsPageProps) { +export function LogsPage({ logs, page, limit }: LogsPageProps) { const [logLevel, setLogLevel] = useState("all"); + const navigate = useNavigate(); + const hasMore = logs.length === limit; const filtered = useMemo(() => { if (logLevel === "all") return logs; return logs.filter((log) => log.level === logLevel); }, [logs, logLevel]); + const handlePrev = () => { + if (page > 0) { + navigate(`?page=${page - 1}&limit=${limit}`); + } + }; + + const handleNext = () => { + if (hasMore) { + navigate(`?page=${page + 1}&limit=${limit}`); + } + }; + return (
@@ -26,6 +50,32 @@ export function LogsPage({ logs }: LogsPageProps) { + + + + { + e.preventDefault(); + handlePrev(); + }} + aria-disabled={page === 0} + className={page === 0 ? "pointer-events-none opacity-50" : undefined} + /> + + + { + e.preventDefault(); + handleNext(); + }} + aria-disabled={!hasMore} + className={!hasMore ? "pointer-events-none opacity-50" : undefined} + /> + + +
); } diff --git a/web/components/pages/webhooks.tsx b/web/components/pages/webhooks.tsx index 392583a..88e07c6 100644 --- a/web/components/pages/webhooks.tsx +++ b/web/components/pages/webhooks.tsx @@ -1,4 +1,4 @@ -import { Link } from "react-router"; +import { Link, useNavigate, useSearchParams } from "react-router"; import { Plus, Search } from "lucide-react"; import { useMemo, useState } from "react"; import { WebhookTable } from "../webhook-table"; @@ -6,17 +6,41 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card } from "@/components/ui/card"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; import type { Webhook } from "@/lib/types"; interface WebhooksPageProps { webhooks: Webhook[]; + page: number; + limit: number; } const STATUS_FILTERS = ["all", "active", "inactive"] as const; -export function WebhooksPage({ webhooks }: WebhooksPageProps) { +export function WebhooksPage({ webhooks, page, limit }: WebhooksPageProps) { const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); + const navigate = useNavigate(); + + const hasMore = webhooks.length === limit; + + const handlePrev = () => { + if (page > 0) { + navigate(`?page=${page - 1}&limit=${limit}`); + } + }; + + const handleNext = () => { + if (hasMore) { + navigate(`?page=${page + 1}&limit=${limit}`); + } + }; const filtered = useMemo(() => { return webhooks.filter((w) => { @@ -67,6 +91,32 @@ export function WebhooksPage({ webhooks }: WebhooksPageProps) { + + + + { + e.preventDefault(); + handlePrev(); + }} + aria-disabled={page === 0} + className={page === 0 ? "pointer-events-none opacity-50" : undefined} + /> + + + { + e.preventDefault(); + handleNext(); + }} + aria-disabled={!hasMore} + className={!hasMore ? "pointer-events-none opacity-50" : undefined} + /> + + +
); } From cd99c33358ae00f3160ee04dd1dc887d71a88901 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Fri, 17 Apr 2026 08:41:16 +0300 Subject: [PATCH 12/20] feat: change the default limit from 20 to 10 --- web/app/routes/deliveries.tsx | 62 +++++++++++++++++------------------ web/app/routes/logs.tsx | 2 +- web/app/routes/webhooks.tsx | 2 +- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/web/app/routes/deliveries.tsx b/web/app/routes/deliveries.tsx index a580eed..f062941 100644 --- a/web/app/routes/deliveries.tsx +++ b/web/app/routes/deliveries.tsx @@ -4,45 +4,45 @@ import { DeliveriesPage } from "@/components/pages/deliveries"; import { Skeleton } from "@/components/ui/skeleton"; import { Card } from "@/components/ui/card"; -const DEFAULT_LIMIT = 20; +const DEFAULT_LIMIT = 10; export async function clientLoader({ request }: { request: Request }) { - const url = new URL(request.url); - const page = Number(url.searchParams.get("page")) || 0; - const limit = Number(url.searchParams.get("limit")) || DEFAULT_LIMIT; - const deliveries = await getDeliveries({ page, limit }); - return { deliveries, page, limit }; + const url = new URL(request.url); + const page = Number(url.searchParams.get("page")) || 0; + const limit = Number(url.searchParams.get("limit")) || DEFAULT_LIMIT; + const deliveries = await getDeliveries({ page, limit }); + return { deliveries, page, limit }; } export function HydrateFallback() { - return ( -
-
- - -
- -
- - {["del-1", "del-2", "del-3", "del-4"].map((id) => ( - - ))} -
-
-
- ); + return ( +
+
+ + +
+ +
+ + {["del-1", "del-2", "del-3", "del-4"].map((id) => ( + + ))} +
+
+
+ ); } export default function Deliveries({ - loaderData, + loaderData, }: { - loaderData: { deliveries: Delivery[]; page: number; limit: number }; + loaderData: { deliveries: Delivery[]; page: number; limit: number }; }) { - return ( - - ); + return ( + + ); } diff --git a/web/app/routes/logs.tsx b/web/app/routes/logs.tsx index 5bc3e18..4b49c99 100644 --- a/web/app/routes/logs.tsx +++ b/web/app/routes/logs.tsx @@ -3,7 +3,7 @@ import type { Log } from "@/lib/types"; import { LogsPage } from "@/components/pages/logs"; import { Skeleton } from "@/components/ui/skeleton"; -const DEFAULT_LIMIT = 20; +const DEFAULT_LIMIT = 10; export async function clientLoader({ request }: { request: Request }) { const url = new URL(request.url); diff --git a/web/app/routes/webhooks.tsx b/web/app/routes/webhooks.tsx index ef62200..a556a84 100644 --- a/web/app/routes/webhooks.tsx +++ b/web/app/routes/webhooks.tsx @@ -4,7 +4,7 @@ import { WebhooksPage } from "@/components/pages/webhooks"; import { Skeleton } from "@/components/ui/skeleton"; import { Card } from "@/components/ui/card"; -const DEFAULT_LIMIT = 20; +const DEFAULT_LIMIT = 10; export async function clientLoader({ request }: { request: Request }) { const url = new URL(request.url); From eb96227cdf8b816b8c4a49db2c5a35045b4dd92c Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Tue, 12 May 2026 10:53:40 +0300 Subject: [PATCH 13/20] feat: add webhook detail page with event types, test dispatch, and recent events - Implemented WebhookDetailPage component to display webhook details, including event types, recent events, and delivery metrics. - Added functionality to create event types and dispatch test events with a JSON payload. - Enhanced RecentEventsTable and WebhookTable components for better navigation and display of webhook-related data. - Updated API functions to handle event types and deliveries associated with webhooks. - Modified types to support string IDs for webhooks, events, and deliveries for consistency across the application. --- src/deliveries.rs | 4 +- src/events.rs | 4 +- src/logs.rs | 2 +- src/settings.rs | 10 +- web/app/routes.ts | 3 + web/app/routes/create-webhook.tsx | 3 +- web/app/routes/delivery-detail.tsx | 2 +- web/app/routes/event-detail.tsx | 49 ++ web/app/routes/event-settings.tsx | 63 +++ web/app/routes/settings.tsx | 26 +- web/app/routes/webhook-detail.tsx | 145 ++++++ web/app/routes/webhooks.tsx | 8 +- web/components/deliveries-table.tsx | 158 +++---- web/components/pages/create-webhook.tsx | 27 +- web/components/pages/delivery-detail.tsx | 273 +++++++---- web/components/pages/event-detail.tsx | 183 +++++++ web/components/pages/event-settings.tsx | 143 ++++++ web/components/pages/overview.tsx | 436 +++++++++-------- web/components/pages/settings.tsx | 32 +- web/components/pages/webhook-detail.tsx | 577 +++++++++++++++++++++++ web/components/recent-events-table.tsx | 188 ++++---- web/components/stat-card.tsx | 62 +-- web/components/webhook-table.tsx | 259 +++++----- web/lib/api.ts | 34 +- web/lib/types.ts | 50 +- 25 files changed, 2018 insertions(+), 723 deletions(-) create mode 100644 web/app/routes/event-detail.tsx create mode 100644 web/app/routes/event-settings.tsx create mode 100644 web/app/routes/webhook-detail.tsx create mode 100644 web/components/pages/event-detail.tsx create mode 100644 web/components/pages/event-settings.tsx create mode 100644 web/components/pages/webhook-detail.tsx diff --git a/src/deliveries.rs b/src/deliveries.rs index 750180e..2a95a40 100644 --- a/src/deliveries.rs +++ b/src/deliveries.rs @@ -90,7 +90,7 @@ pub async fn get_webhook_deliveries( #[utoipa::path( get, path = "/api/v1/deliveries/{id}", - params(("id" = i64, Path, description = "Delivery ID")), + params(("id" = String, Path, description = "Delivery ID")), responses( (status = 200, description = "Delivery", body = Delivery), (status = 404, description = "Not found") @@ -99,7 +99,7 @@ pub async fn get_webhook_deliveries( )] pub async fn get_delivery( State(state): State, - Path(id): Path, + Path(id): Path, ) -> Result, StatusCode> { let delivery = sqlx::query_as::<_, Delivery>("SELECT * FROM deliveries WHERE id = ?") .bind(id) diff --git a/src/events.rs b/src/events.rs index 4c103fc..4e8372d 100644 --- a/src/events.rs +++ b/src/events.rs @@ -89,7 +89,7 @@ pub async fn get_webhook_events( #[utoipa::path( get, path = "/api/v1/events/{id}", - params(("id" = i64, Path, description = "Event ID")), + params(("id" = String, Path, description = "Event ID")), responses( (status = 200, description = "Event", body = Event), (status = 404, description = "Not found") @@ -98,7 +98,7 @@ pub async fn get_webhook_events( )] pub async fn get_event( State(state): State, - Path(id): Path, + Path(id): Path, ) -> Result, StatusCode> { let event = sqlx::query_as::<_, Event>("SELECT * FROM events WHERE id = ?") .bind(id) diff --git a/src/logs.rs b/src/logs.rs index a090717..94e8a35 100644 --- a/src/logs.rs +++ b/src/logs.rs @@ -8,7 +8,7 @@ use crate::app::AppState; #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, ToSchema)] pub struct Log { - id: i64, + id: String, webhook_id: Option, level: String, message: String, diff --git a/src/settings.rs b/src/settings.rs index f402f68..7352bdf 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -27,7 +27,7 @@ pub struct UpsertSettings { #[utoipa::path( get, path = "/api/v1/event-types/{id}/settings", - params(("id" = i64, Path, description = "Event type ID")), + params(("id" = String, Path, description = "Event type ID")), responses( (status = 200, description = "Settings for event type", body = Settings), (status = 404, description = "No settings found for event type") @@ -36,10 +36,10 @@ pub struct UpsertSettings { )] pub async fn get_event_type_settings( State(state): State, - Path(event_type_id): Path, + Path(event_type_id): Path, ) -> Result, StatusCode> { let settings = sqlx::query_as::<_, Settings>("SELECT * FROM settings WHERE event_type_id = ?") - .bind(event_type_id) + .bind(&event_type_id) .fetch_one(&state.pool) .await .map_err(|_| StatusCode::NOT_FOUND)?; @@ -49,7 +49,7 @@ pub async fn get_event_type_settings( #[utoipa::path( put, path = "/api/v1/event-types/{id}/settings", - params(("id" = i64, Path, description = "Event type ID")), + params(("id" = String, Path, description = "Event type ID")), request_body = UpsertSettings, responses( (status = 200, description = "Upserted settings", body = Settings) @@ -58,7 +58,7 @@ pub async fn get_event_type_settings( )] pub async fn upsert_event_type_settings( State(state): State, - Path(event_type_id): Path, + Path(event_type_id): Path, Json(payload): Json, ) -> Result, StatusCode> { let setting_id = generate_id("ST"); diff --git a/web/app/routes.ts b/web/app/routes.ts index 8d7ddce..3424d7b 100644 --- a/web/app/routes.ts +++ b/web/app/routes.ts @@ -10,8 +10,11 @@ export default [ index("routes/overview.tsx"), route("webhooks", "routes/webhooks.tsx"), route("webhooks/new", "routes/create-webhook.tsx"), + route("webhooks/:id", "routes/webhook-detail.tsx"), + route("webhooks/:webhookId/settings/:eventTypeId", "routes/event-settings.tsx"), route("deliveries", "routes/deliveries.tsx"), route("deliveries/:id", "routes/delivery-detail.tsx"), + route("events/:id", "routes/event-detail.tsx"), route("logs", "routes/logs.tsx"), route("settings", "routes/settings.tsx"), ]), diff --git a/web/app/routes/create-webhook.tsx b/web/app/routes/create-webhook.tsx index 220cca3..280a2fa 100644 --- a/web/app/routes/create-webhook.tsx +++ b/web/app/routes/create-webhook.tsx @@ -1,5 +1,6 @@ import { redirect } from "react-router"; import { createWebhook, getEventTypes } from "@/lib/api"; +import type { EventType } from "@/lib/types"; import { CreateWebhookPage } from "@/components/pages/create-webhook"; import { Skeleton } from "@/components/ui/skeleton"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; @@ -56,7 +57,7 @@ export async function clientAction({ request }: { request: Request }) { export default function CreateWebhook({ loaderData, }: { - loaderData: { eventTypes: string[] }; + loaderData: { eventTypes: EventType[] }; }) { return ; } diff --git a/web/app/routes/delivery-detail.tsx b/web/app/routes/delivery-detail.tsx index eb6afff..aab55de 100644 --- a/web/app/routes/delivery-detail.tsx +++ b/web/app/routes/delivery-detail.tsx @@ -8,7 +8,7 @@ import { Separator } from "@/components/ui/separator"; export async function clientLoader({ params, }: { params: { id: string } }) { - const delivery = await getDelivery(Number(params.id)); + const delivery = await getDelivery(params.id); return { delivery }; } diff --git a/web/app/routes/event-detail.tsx b/web/app/routes/event-detail.tsx new file mode 100644 index 0000000..d4354f5 --- /dev/null +++ b/web/app/routes/event-detail.tsx @@ -0,0 +1,49 @@ +import { getEvent, getWebhookEvents } from "@/lib/api"; +import type { Event } from "@/lib/types"; +import { EventDetailPage } from "@/components/pages/event-detail"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; + +export async function clientLoader({ params }: { params: { id: string } }) { + const event = await getEvent(params.id); + return { event }; +} + +export function HydrateFallback() { + return ( +
+
+ +
+ + +
+
+
+ + + + + +
+ {[1, 2, 3, 4].map((i) => ( +
+ + +
+ ))} +
+
+
+
+
+ ); +} + +export default function EventDetail({ + loaderData, +}: { + loaderData: { event: Event }; +}) { + return ; +} \ No newline at end of file diff --git a/web/app/routes/event-settings.tsx b/web/app/routes/event-settings.tsx new file mode 100644 index 0000000..b4ab7e1 --- /dev/null +++ b/web/app/routes/event-settings.tsx @@ -0,0 +1,63 @@ +import { redirect } from "react-router"; +import { getWebhook, getSettings, updateSettings } from "@/lib/api"; +import type { Settings as SettingsType } from "@/lib/types"; +import { EventSettingsPage } from "@/components/pages/event-settings"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; + +export async function clientLoader({ params }: { params: { webhookId: string; eventTypeId: string } }) { + const [webhook, settings] = await Promise.all([ + getWebhook(params.webhookId), + getSettings(params.eventTypeId), + ]); + return { webhook, settings }; +} + +export async function clientAction({ request, params }: { request: Request; params: { webhookId: string; eventTypeId: string } }) { + const formData = await request.formData(); + const settings = { + retry_attempts: Number(formData.get("retry_attempts")), + timeout_seconds: Number(formData.get("timeout_seconds")), + enabled: formData.get("enabled") === "true", + }; + await updateSettings(params.eventTypeId, settings); + return redirect(`/webhooks/${params.webhookId}`); +} + +export function HydrateFallback() { + return ( +
+
+ +
+ + +
+
+ + + + + +
+ + +
+
+ + +
+ +
+
+
+ ); +} + +export default function EventSettings({ + loaderData, +}: { + loaderData: { webhook: { id: string; name: string }; settings: SettingsType }; +}) { + return ; +} \ No newline at end of file diff --git a/web/app/routes/settings.tsx b/web/app/routes/settings.tsx index 921f288..017fd8a 100644 --- a/web/app/routes/settings.tsx +++ b/web/app/routes/settings.tsx @@ -1,12 +1,18 @@ -import { getSettings, updateSettings } from "@/lib/api"; -import type { Settings } from "@/lib/types"; +import { getEventTypes, getSettings, updateSettings } from "@/lib/api"; +import type { Settings, EventType } from "@/lib/types"; import { SettingsPage } from "@/components/pages/settings"; import { Skeleton } from "@/components/ui/skeleton"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; export async function clientLoader() { - const settings = await getSettings(); - return { settings }; + const eventTypes = await getEventTypes(); + + if (eventTypes.length === 0) { + return { eventTypes: [], settings: null }; + } + + const settings = await getSettings(eventTypes[0].id); + return { eventTypes, settings }; } export function HydrateFallback() { @@ -57,19 +63,23 @@ export function HydrateFallback() { export async function clientAction({ request }: { request: Request }) { const formData = await request.formData(); - const settings: Settings = { + const eventTypeId = formData.get("eventTypeId") as string; + const settings = { retry_attempts: Number(formData.get("retry_attempts")), timeout_seconds: Number(formData.get("timeout_seconds")), enabled: formData.get("enabled") === "true", }; - const updated = await updateSettings(settings); + const updated = await updateSettings(eventTypeId, settings); return { settings: updated }; } export default function SettingsRoute({ loaderData, }: { - loaderData: { settings: Settings }; + loaderData: { eventTypes: EventType[]; settings: Settings | null }; }) { - return ; + return ; } diff --git a/web/app/routes/webhook-detail.tsx b/web/app/routes/webhook-detail.tsx new file mode 100644 index 0000000..5488fa2 --- /dev/null +++ b/web/app/routes/webhook-detail.tsx @@ -0,0 +1,145 @@ +import { redirect } from "react-router"; +import { + getWebhook, + getWebhookEventTypes, + getWebhookEvents, + getWebhookDeliveries, + updateWebhook, + deleteWebhook, + createEventType, + dispatch, + getSettings, + updateSettings, +} from "@/lib/api"; +import type { Webhook, EventType, Event, Delivery, Settings } from "@/lib/types"; +import { WebhookDetailPage } from "@/components/pages/webhook-detail"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; + +export async function clientLoader({ params }: { params: { id: string } }) { + const [webhook, eventTypes, events, deliveries] = await Promise.all([ + getWebhook(params.id), + getWebhookEventTypes(params.id), + getWebhookEvents(params.id, { limit: 10 }), + getWebhookDeliveries(params.id, { limit: 10 }), + ]); + + // Fetch settings for each event type + const settingsMap: Record = {}; + await Promise.all( + eventTypes.map(async (et) => { + try { + const s = await getSettings(et.id); + settingsMap[et.id] = s; + } catch { + settingsMap[et.id] = { + id: "", + event_type_id: et.id, + retry_attempts: 3, + timeout_seconds: 30, + enabled: true, + }; + } + }) + ); + + return { webhook, eventTypes, events, deliveries, settings: settingsMap }; +} + +export async function clientAction({ request, params }: { request: Request; params: { id: string } }) { + const formData = await request.formData(); + const intent = formData.get("intent"); + + if (intent === "updateStatus") { + const status = formData.get("status") as string; + await updateWebhook(params.id, { status }); + return { ok: true }; + } + + if (intent === "delete") { + await deleteWebhook(params.id); + return redirect("/webhooks"); + } + + if (intent === "createEventType") { + const name = formData.get("name") as string; + await createEventType({ webhook_id: params.id, name }); + return { ok: true }; + } + + if (intent === "dispatch") { + const eventType = formData.get("eventType") as string; + const payloadStr = formData.get("payload") as string; + const payload = JSON.parse(payloadStr || "{}"); + const webhook = await getWebhook(params.id); + await dispatch({ + webhook_name: webhook.name, + event_type: eventType, + payload, + }); + return { ok: true }; + } + + if (intent === "saveSettings") { + const eventTypeId = formData.get("eventTypeId") as string; + const settings = { + retry_attempts: Number(formData.get("retry_attempts")), + timeout_seconds: Number(formData.get("timeout_seconds")), + enabled: formData.get("enabled") === "true", + }; + await updateSettings(eventTypeId, settings); + return { ok: true }; + } + + return { ok: false }; +} + +export function HydrateFallback() { + return ( +
+
+ +
+ + +
+
+
+ {[1, 2, 3].map((i) => ( + + + + + + + + + ))} +
+ + + + + + {[1, 2, 3].map((i) => ( + + ))} + + +
+ ); +} + +export default function WebhookDetail({ + loaderData, +}: { + loaderData: { + webhook: Webhook; + eventTypes: EventType[]; + events: Event[]; + deliveries: Delivery[]; + settings: Record; + }; +}) { + return ; +} \ No newline at end of file diff --git a/web/app/routes/webhooks.tsx b/web/app/routes/webhooks.tsx index a556a84..5a0bb7b 100644 --- a/web/app/routes/webhooks.tsx +++ b/web/app/routes/webhooks.tsx @@ -15,10 +15,10 @@ export async function clientLoader({ request }: { request: Request }) { } export async function clientAction({ request }: { request: Request }) { - const formData = await request.formData(); - const webhookId = Number(formData.get("webhookId")); - await deleteWebhook(webhookId); - return { ok: true }; + const formData = await request.formData(); + const webhookId = formData.get("webhookId") as string; + await deleteWebhook(webhookId); + return { ok: true }; } export function HydrateFallback() { diff --git a/web/components/deliveries-table.tsx b/web/components/deliveries-table.tsx index 10abc85..2c90cd1 100644 --- a/web/components/deliveries-table.tsx +++ b/web/components/deliveries-table.tsx @@ -1,97 +1,97 @@ import { useNavigate } from "react-router"; import { Inbox } from "lucide-react"; import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, } from "@/components/ui/table"; import type { Delivery } from "@/lib/types"; interface DeliveriesTableProps { - deliveries: Delivery[]; + deliveries: Delivery[]; } export function DeliveriesTable({ deliveries }: DeliveriesTableProps) { - const navigate = useNavigate(); + const navigate = useNavigate(); - if (deliveries.length === 0) { - return ( -
- -

No deliveries

-

- Deliveries will appear here once webhooks start firing. -

-
- ); - } + if (deliveries.length === 0) { + return ( +
+ +

No deliveries

+

+ Deliveries will appear here once webhooks start firing. +

+
+ ); + } - return ( -
-
- - - - Event - - - Status - - - Duration - - - Time - - - + return ( +
+
+ + + + Event + + + Status + + + Duration + + + Time + + + - - {deliveries.map((delivery) => { - const isError = delivery.status >= 400; + + {deliveries.map((delivery) => { + const isError = delivery.status_code >= 400 || !delivery.success; - return ( - navigate(`/deliveries/${delivery.id}`)} - className="cursor-pointer border-b last:border-0 hover:bg-muted/40 transition-colors" - > - - - {delivery.event} - - + return ( + navigate(`/deliveries/${delivery.id}`)} + className="cursor-pointer border-b last:border-0 hover:bg-muted/40 transition-colors" + > + + + {delivery.event_id.slice(0, 12)}... + + - - - {delivery.status} - - + + + {delivery.status_code} + + - - {delivery.duration} - + + {delivery.duration_ms}ms + - - {delivery.timestamp} - - - ); - })} - -
-
- ); + + {delivery.timestamp} + + + ); + })} + + +
+ ); } diff --git a/web/components/pages/create-webhook.tsx b/web/components/pages/create-webhook.tsx index 4cdded0..94573ca 100644 --- a/web/components/pages/create-webhook.tsx +++ b/web/components/pages/create-webhook.tsx @@ -13,9 +13,10 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import type { EventType } from "@/lib/types"; interface CreateWebhookPageProps { - eventTypes: string[]; + eventTypes: EventType[]; } export function CreateWebhookPage({ eventTypes }: CreateWebhookPageProps) { @@ -24,6 +25,14 @@ export function CreateWebhookPage({ eventTypes }: CreateWebhookPageProps) { const isSubmitting = navigation.state === "submitting"; const [selectedEvents, setSelectedEvents] = useState([]); + const toggleEvent = (eventId: string, checked: boolean) => { + if (checked) { + setSelectedEvents([...selectedEvents, eventId]); + } else { + setSelectedEvents(selectedEvents.filter((e) => e !== eventId)); + } + }; + return (
@@ -94,22 +103,14 @@ export function CreateWebhookPage({ eventTypes }: CreateWebhookPageProps) {
{eventTypes.map((event) => ( ))}
diff --git a/web/components/pages/delivery-detail.tsx b/web/components/pages/delivery-detail.tsx index 4bb435a..9550571 100644 --- a/web/components/pages/delivery-detail.tsx +++ b/web/components/pages/delivery-detail.tsx @@ -1,111 +1,188 @@ import { useNavigate } from "react-router"; -import { ArrowLeft } from "lucide-react"; -import { Badge } from "@/components/ui/badge"; +import { ArrowLeft, Clock, Hash, Send, Server } from "lucide-react"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; import type { Delivery } from "@/lib/types"; interface DeliveryDetailPageProps { - delivery: Delivery; + delivery: Delivery; } +const getDeliveryStatusStyles = (success: boolean) => { + if (success) { + return { + dot: "bg-emerald-500", + text: "text-emerald-600", + label: "Success", + }; + } + + return { + dot: "bg-red-500", + text: "text-red-600", + label: "Failed", + }; +}; + export function DeliveryDetailPage({ delivery }: DeliveryDetailPageProps) { - const navigate = useNavigate(); - - return ( -
-
- -
-

- Delivery Details -

-

{delivery.event}

-
-
- -
- - - Event Information - - -
-
-

Event Name

-

{delivery.event}

-
-
-

Status Code

- {delivery.status} -
-
-

Duration

-

- {delivery.duration} -

-
-
-

Timestamp

-

- {delivery.timestamp} -

-
-
-
-
- - - - Request Payload - - -
-							{`{
-  "id": "evt_${delivery.id}",
-  "event": "${delivery.event}",
+  const navigate = useNavigate();
+  const status = getDeliveryStatusStyles(delivery.success);
+
+  return (
+    
+ {/* Header */} +
+ + +
+
+

+ Delivery Details +

+ +
+ + {status.label} +
+
+ +

+ {delivery.id} +

+
+
+ +
+ {/* Delivery Information */} + + + + Delivery Information + + + Runtime metadata for this delivery attempt + + + + +
+
+
+ + Status +
+ +
+ + + HTTP {delivery.status_code ?? "Failed"} + +
+
+ +
+
+ + Duration +
+ +

+ {delivery.duration_ms}ms +

+
+ +
+
+ + Result +
+ +

+ {status.label} +

+
+
+ +
+
+

Timestamp

+

+ {delivery.timestamp} +

+
+ +
+

Event ID

+

+ {delivery.event_id} +

+
+
+
+
+ + {/* Request Payload */} + + + + Request Payload + + + Payload metadata sent with this delivery + + + + +
+
+
+ + JSON +
+
+ +
+                {`{
+  "delivery_id": "${delivery.id}",
+  "event_id": "${delivery.event_id}",
   "timestamp": "${delivery.timestamp}"
 }`}
-						
- - - - - -
-

- Delivery Status -

- - - - {delivery.success ? "Success" : "Failed"} - -
-

- HTTP {delivery.status} -

-

- {delivery.duration} -

-
-
-
-
-
-
- ); +
+
+ + + + {/* Status Summary */} +
+

Delivery Status

+

+ This delivery{" "} + {delivery.success ? "completed successfully" : "failed"} with{" "} + + HTTP {delivery.status_code ?? "N/A"} + {" "} + in{" "} + + {delivery.duration_ms}ms + + . +

+
+
+
+ ); } diff --git a/web/components/pages/event-detail.tsx b/web/components/pages/event-detail.tsx new file mode 100644 index 0000000..13311cf --- /dev/null +++ b/web/components/pages/event-detail.tsx @@ -0,0 +1,183 @@ +import { useNavigate } from "react-router"; +import { ArrowLeft, Clock, Hash, RotateCw, Webhook } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import type { Event } from "@/lib/types"; + +interface EventDetailPageProps { + event: Event; +} + +const getStatusStyles = (status: string) => { + switch (status) { + case "success": + return { + dot: "bg-emerald-500", + text: "text-emerald-600", + }; + case "fail": + case "failed": + return { + dot: "bg-red-500", + text: "text-red-600", + }; + default: + return { + dot: "bg-zinc-400", + text: "text-muted-foreground", + }; + } +}; + +export function EventDetailPage({ event }: EventDetailPageProps) { + const navigate = useNavigate(); + const status = getStatusStyles(event.status); + + return ( +
+ {/* Header */} +
+ + +
+
+

+ Event Details +

+ +
+ + {event.status} +
+
+ +

+ {event.id} +

+
+
+ +
+ {/* Event Information */} + + + + Event Information + + + Runtime metadata for this webhook event + + + + +
+
+
+ + Attempts +
+

+ {event.attempts} +

+
+ +
+
+ + Duration +
+

+ {event.duration_ms}ms +

+
+ +
+
+ + Status +
+
+ + {event.status} +
+
+
+ +
+

Timestamp

+

+ {event.timestamp} +

+
+
+
+ + {/* References */} + + + References + + Linked resources associated with this event + + + + +
+
+
+ + Webhook ID +
+
+ +
+

+ {event.webhook_id} +

+
+
+ +
+
+
+ + Event Type ID +
+
+ +
+

+ {event.event_type_id} +

+
+
+
+
+ + {/* Note */} +
+

Payload unavailable

+

+ The payload sent with this event is not stored in the database. To + inspect payload data, check the delivery attempt or use test + dispatch. +

+
+
+
+ ); +} diff --git a/web/components/pages/event-settings.tsx b/web/components/pages/event-settings.tsx new file mode 100644 index 0000000..94dba54 --- /dev/null +++ b/web/components/pages/event-settings.tsx @@ -0,0 +1,143 @@ +import { useNavigate } from "react-router"; +import { ArrowLeft } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import type { Settings as SettingsType } from "@/lib/types"; + +interface EventSettingsPageProps { + webhook: { id: string; name: string }; + settings: SettingsType; +} + +export function EventSettingsPage({ + webhook, + settings, +}: EventSettingsPageProps) { + const navigate = useNavigate(); + + return ( +
+
+
+ + +
+

+ Event Settings +

+

+ Configure delivery behavior for{" "} + + {webhook.name} + +

+

+ Event type:{" "} + + {settings.event_type_id} + +

+
+
+ + + + + Delivery Configuration + + + Control retries, timeouts, and whether this event type can send + deliveries. + + + + +
+
+
+ + +

+ Number of times to retry delivery after a failed request. +

+
+ +
+ + +

+ Maximum seconds to wait before marking a delivery as failed. +

+
+
+ +
+
+
+ +

+ Allow this event type to trigger webhook deliveries. +

+
+ + +
+
+ +
+ + +
+
+
+
+
+
+ ); +} diff --git a/web/components/pages/overview.tsx b/web/components/pages/overview.tsx index bfdacb3..a3e420e 100644 --- a/web/components/pages/overview.tsx +++ b/web/components/pages/overview.tsx @@ -1,246 +1,244 @@ import { - CheckCircle, - TrendingUp, - Zap, - Radio, - Activity, - Gauge, + CheckCircle, + TrendingUp, + Zap, + Radio, + Activity, + Gauge, } from "lucide-react"; -import { parse, format } from "@lukeed/ms"; +import { format } from "@lukeed/ms"; import { StatCard } from "../stat-card"; import { RecentEventsTable } from "../recent-events-table"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import type { Webhook, Delivery, Event } from "@/lib/types"; interface OverviewPageProps { - webhooks: Webhook[]; - deliveries: Delivery[]; - events: Event[]; + webhooks: Webhook[]; + deliveries: Delivery[]; + events: Event[]; } function computeStats(webhooks: Webhook[], deliveries: Delivery[]) { - const activeCount = webhooks.filter((w) => w.status === "active").length; - const totalEndpoints = webhooks.length; + const activeCount = webhooks.filter((w) => w.status === "active").length; + const totalEndpoints = webhooks.length; - const successCount = deliveries.filter((d) => d.success).length; - const successRate = - deliveries.length > 0 - ? ((successCount / deliveries.length) * 100).toFixed(1) - : "0.0"; + const successCount = deliveries.filter((d) => d.success).length; + const successRate = + deliveries.length > 0 + ? ((successCount / deliveries.length) * 100).toFixed(1) + : "0.0"; - const durations = deliveries.map((d) => parse(d.duration) ?? 0); - const avgLatency = - durations.length > 0 - ? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length) - : 0; + const durations = deliveries.map((d) => d.duration_ms); + const avgLatency = + durations.length > 0 + ? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length) + : 0; - return { - activeCount, - totalEndpoints, - successRate, - successCount, - avgLatency, - totalDeliveries: deliveries.length, - }; + return { + activeCount, + totalEndpoints, + successRate, + successCount, + avgLatency, + totalDeliveries: deliveries.length, + }; } export function OverviewPage({ - webhooks, - deliveries, - events, + webhooks, + deliveries, + events, }: OverviewPageProps) { - const stats = computeStats(webhooks, deliveries); + const stats = computeStats(webhooks, deliveries); - // Endpoint status breakdown - const healthyCount = webhooks.filter((w) => w.status === "active").length; - const inactiveCount = webhooks.filter( - (w) => w.status === "inactive", - ).length; - const failingCount = webhooks.filter((w) => w.status === "failed").length; - const total = webhooks.length || 1; - const healthyPct = Math.round((healthyCount / total) * 100); - const inactivePct = Math.round((inactiveCount / total) * 100); - const failingPct = Math.round((failingCount / total) * 100); + // Endpoint status breakdown + const healthyCount = webhooks.filter((w) => w.status === "active").length; + const inactiveCount = webhooks.filter((w) => w.status === "inactive").length; + const failingCount = webhooks.filter((w) => w.status === "failed").length; + const total = webhooks.length || 1; + const healthyPct = Math.round((healthyCount / total) * 100); + const inactivePct = Math.round((inactiveCount / total) * 100); + const failingPct = Math.round((failingCount / total) * 100); - // Percentile latencies - const sortedDurations = deliveries - .map((d) => parse(d.duration) ?? 0) - .sort((a, b) => a - b); - const percentile = (arr: number[], p: number) => - arr.length > 0 ? arr[Math.ceil((p / 100) * arr.length) - 1] : 0; - const p50 = percentile(sortedDurations, 50); - const p95 = percentile(sortedDurations, 95); - const p99 = percentile(sortedDurations, 99); - const maxLatency = sortedDurations[sortedDurations.length - 1] || 1; + // Percentile latencies + const sortedDurations = deliveries + .map((d) => d.duration_ms) + .sort((a, b) => a - b); + const percentile = (arr: number[], p: number) => + arr.length > 0 ? arr[Math.ceil((p / 100) * arr.length) - 1] : 0; + const p50 = percentile(sortedDurations, 50); + const p95 = percentile(sortedDurations, 95); + const p99 = percentile(sortedDurations, 99); + const maxLatency = sortedDurations[sortedDurations.length - 1] || 1; - return ( -
-
-

- Overview -

-

- Monitor your webhook system health at a glance -

-
-
- - - - -
-
- - -
- - Endpoint Status -
-
- {webhooks.length} -
-
- -
-
-
-
-
+ return ( +
+
+

+ Overview +

+

+ Monitor your webhook system health at a glance +

+
+
+ + + + +
+
+ + +
+ + Endpoint Status +
+
+ {webhooks.length} +
+
+ +
+
+
+
+
-
-
-
- - Healthy -
- {healthyCount} -
+
+
+
+ + Healthy +
+ {healthyCount} +
-
-
- - Inactive -
- - {inactiveCount} - -
+
+
+ + Inactive +
+ + {inactiveCount} + +
-
-
- - Failing -
- {failingCount} -
-
- - - - -
- Performance -
-
- -
-
- P50{" "} - - {format(p50)} - -
-
-
-
-
+
+
+ + Failing +
+ {failingCount} +
+
+
+
+ + +
+ Performance +
+
+ +
+
+ P50{" "} + + {format(p50)} + +
+
+
+
+
-
-
- P95 - - {format(p95)} - -
-
-
-
-
+
+
+ P95 + + {format(p95)} + +
+
+
+
+
-
-
- P99 - - {format(p99)} - -
-
-
-
-
- - -
- - - Recent Events - - - - - -
- ); +
+
+ P99 + + {format(p99)} + +
+
+
+
+
+ + +
+ + + Recent Events + + + + + +
+ ); } diff --git a/web/components/pages/settings.tsx b/web/components/pages/settings.tsx index b5b2a21..4fc7c9f 100644 --- a/web/components/pages/settings.tsx +++ b/web/components/pages/settings.tsx @@ -1,6 +1,6 @@ import { Eye, EyeOff, Copy, RotateCw } from "lucide-react"; import { useState } from "react"; -import { Form, useFetcher } from "react-router"; +import { Form, useFetcher, useLoaderData } from "react-router"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -11,16 +11,25 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import type { Settings } from "@/lib/types"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import type { Settings, EventType } from "@/lib/types"; interface SettingsPageProps { - settings: Settings; + eventTypes: EventType[]; + settings: Settings | null; } -export function SettingsPage({ settings }: SettingsPageProps) { +export function SettingsPage({ eventTypes, settings }: SettingsPageProps) { const [showSecret, setShowSecret] = useState(false); const [showApiKey, setShowApiKey] = useState(false); const [copied, setCopied] = useState(""); + const [selectedEventType, setSelectedEventType] = useState(eventTypes[0]?.id || ""); const fetcher = useFetcher(); const isSaving = fetcher.state !== "idle"; @@ -155,13 +164,19 @@ export function SettingsPage({ settings }: SettingsPageProps) { Delivery Configuration - Control webhook retry behavior + Control webhook retry behavior per event type + {eventTypes.length === 0 ? ( +

+ No event types configured. Create a webhook with event types first. +

+ ) : ( - + +
@@ -184,7 +199,7 @@ export function SettingsPage({ settings }: SettingsPageProps) { id="max-retries" name="retry_attempts" type="number" - defaultValue={settings.retry_attempts} + defaultValue={settings?.retry_attempts ?? 3} className="text-sm" />
@@ -196,6 +211,7 @@ export function SettingsPage({ settings }: SettingsPageProps) {
+ )}
diff --git a/web/components/pages/webhook-detail.tsx b/web/components/pages/webhook-detail.tsx new file mode 100644 index 0000000..4b91735 --- /dev/null +++ b/web/components/pages/webhook-detail.tsx @@ -0,0 +1,577 @@ +import { useState } from "react"; +import { useNavigate, Form, Link } from "react-router"; +import { + ArrowLeft, + Plus, + Play, + Trash2, + Radio, + Activity, + Send, + Copy, + Settings as SettingsIcon, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Switch } from "@/components/ui/switch"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import type { Webhook, EventType, Event, Delivery, Settings } from "@/lib/types"; + +interface WebhookDetailPageProps { + webhook: Webhook; + eventTypes: EventType[]; + events: Event[]; + deliveries: Delivery[]; + settings: Record; +} + +const getWebhookStatusStyles = (status: string) => { + switch (status) { + case "active": + return { + dot: "bg-emerald-500", + text: "text-emerald-600", + }; + case "disabled": + return { + dot: "bg-red-500", + text: "text-red-600", + }; + default: + return { + dot: "bg-zinc-400", + text: "text-muted-foreground", + }; + } +}; + +const getEventStatusStyles = (status: string) => { + switch (status) { + case "success": + return { + dot: "bg-emerald-500", + text: "text-emerald-600", + }; + case "fail": + case "failed": + return { + dot: "bg-red-500", + text: "text-red-600", + }; + default: + return { + dot: "bg-zinc-400", + text: "text-muted-foreground", + }; + } +}; + +export function WebhookDetailPage({ + webhook, + eventTypes, + events, + deliveries, + settings, +}: WebhookDetailPageProps) { + const navigate = useNavigate(); + const [isDispatchOpen, setIsDispatchOpen] = useState(false); + const [dispatchPayload, setDispatchPayload] = useState("{}"); + const [copied, setCopied] = useState(false); + + const webhookStatus = getWebhookStatusStyles(webhook.status); + + const handleCopyUrl = () => { + navigator.clipboard.writeText(webhook.url); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }; + + return ( +
+ {/* Header */} +
+
+ + +
+
+

+ {webhook.name} +

+ +
+ + {webhook.status} +
+
+ +
+

{webhook.url}

+ + + + {copied && ( + Copied + )} +
+
+
+ +
+ + +
+
+ + {/* Metrics */} +
+ + +
+ + Event Types +
+
+ +

+ {eventTypes.length} +

+
+
+ + + +
+ + Total Events +
+
+ +

+ {events.length} +

+
+
+ + + +
+ + Total Deliveries +
+
+ +

+ {deliveries.length} +

+
+
+
+ + {/* Configuration */} +
+ + +
+ + Event Types + + + Events this webhook can receive + +
+ + + + + + + + + Add Event Type + + Create a new event type for this webhook. + + + +
+ + +
+ + +
+ +
+ +
+
+
+
+
+ + + {eventTypes.length > 0 ? ( +
+ {eventTypes.map((eventType) => ( +
+
+ + + {eventType.name} + +
+ +
+ + {eventType.id.slice(0, 8)} + + +
+
+ ))} +
+ ) : ( +
+

No event types

+

+ Add one to start receiving webhook events. +

+
+ )} +
+
+ + + + + Test Dispatch + + + Send a test payload to this webhook endpoint + + + + +
+
+
+ +
+ +
+

Dispatch a test event

+

+ Choose an event type and send a JSON payload to verify the + endpoint behavior. +

+
+
+
+ + + + + + + + + Dispatch Test Event + + Send a test event to {webhook.name}. + + + +
+ + +
+ + +
+ +
+ +