commit 07c3b5d0893821a4d9473a04d77d32fd398a4c5c
parent 611f0a1ef61fb0faafa552d1512bc08a6e72ef84
Author: MTRNord <mtrnord1@gmail.com>
Date: Tue, 22 Apr 2025 01:30:08 +0200
Initial design
Diffstat:
80 files changed, 9843 insertions(+), 1000 deletions(-)
diff --git a/components.json b/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
+\ No newline at end of file
diff --git a/package.json b/package.json
@@ -9,15 +9,26 @@
"lint": "next lint"
},
"dependencies": {
- "@emotion/cache": "^11.14.0",
- "@emotion/react": "^11.14.0",
- "@emotion/styled": "^11.14.0",
- "@mui/material": "^7.0.2",
- "@mui/material-nextjs": "^7.0.2",
- "@mui/x-charts": "8.0.0-beta.3",
+ "@radix-ui/react-avatar": "^1.1.6",
+ "@radix-ui/react-checkbox": "^1.2.2",
+ "@radix-ui/react-dialog": "^1.1.10",
+ "@radix-ui/react-dropdown-menu": "^2.1.11",
+ "@radix-ui/react-label": "^2.1.4",
+ "@radix-ui/react-popover": "^1.1.10",
+ "@radix-ui/react-scroll-area": "^1.2.5",
+ "@radix-ui/react-select": "^2.2.2",
+ "@radix-ui/react-separator": "^1.1.4",
+ "@radix-ui/react-slot": "^1.2.0",
+ "@radix-ui/react-tabs": "^1.1.8",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.503.0",
"next": "15.3.1-canary.14",
+ "next-themes": "^0.4.6",
"react": "19.1.0",
- "react-dom": "19.1.0"
+ "react-dom": "19.1.0",
+ "tailwind-merge": "^3.2.0",
+ "tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -27,6 +38,8 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.3.1-canary.14",
+ "postcss": "^8.5.3",
+ "tailwindcss": "^3.4.17",
"typescript": "^5"
}
}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
@@ -8,33 +8,66 @@ importers:
.:
dependencies:
- '@emotion/cache':
- specifier: ^11.14.0
- version: 11.14.0
- '@emotion/react':
- specifier: ^11.14.0
- version: 11.14.0(@types/react@19.1.2)(react@19.1.0)
- '@emotion/styled':
- specifier: ^11.14.0
- version: 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
- '@mui/material':
- specifier: ^7.0.2
- version: 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@mui/material-nextjs':
- specifier: ^7.0.2
- version: 7.0.2(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(next@15.3.1-canary.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
- '@mui/x-charts':
- specifier: 8.0.0-beta.3
- version: 8.0.0-beta.3(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-avatar':
+ specifier: ^1.1.6
+ version: 1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-checkbox':
+ specifier: ^1.2.2
+ version: 1.2.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-dialog':
+ specifier: ^1.1.10
+ version: 1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-dropdown-menu':
+ specifier: ^2.1.11
+ version: 2.1.11(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-label':
+ specifier: ^2.1.4
+ version: 2.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-popover':
+ specifier: ^1.1.10
+ version: 1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-scroll-area':
+ specifier: ^1.2.5
+ version: 1.2.5(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-select':
+ specifier: ^2.2.2
+ version: 2.2.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-separator':
+ specifier: ^1.1.4
+ version: 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot':
+ specifier: ^1.2.0
+ version: 1.2.0(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-tabs':
+ specifier: ^1.1.8
+ version: 1.1.8(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ lucide-react:
+ specifier: ^0.503.0
+ version: 0.503.0(react@19.1.0)
next:
specifier: 15.3.1-canary.14
version: 15.3.1-canary.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ next-themes:
+ specifier: ^0.4.6
+ version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react:
specifier: 19.1.0
version: 19.1.0
react-dom:
specifier: 19.1.0
version: 19.1.0(react@19.1.0)
+ tailwind-merge:
+ specifier: ^3.2.0
+ version: 3.2.0
+ tailwindcss-animate:
+ specifier: ^1.0.7
+ version: 1.0.7(tailwindcss@3.4.17)
devDependencies:
'@eslint/eslintrc':
specifier: ^3
@@ -51,58 +84,30 @@ importers:
'@types/react-dom':
specifier: ^19
version: 19.1.2(@types/react@19.1.2)
+ autoprefixer:
+ specifier: ^10.4.21
+ version: 10.4.21(postcss@8.5.3)
eslint:
specifier: ^9
version: 9.25.0(jiti@2.4.2)
eslint-config-next:
specifier: 15.3.1-canary.14
version: 15.3.1-canary.14(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3)
+ postcss:
+ specifier: ^8.5.3
+ version: 8.5.3
+ tailwindcss:
+ specifier: ^3.4.17
+ version: 3.4.17
typescript:
specifier: ^5
version: 5.8.3
packages:
- '@babel/code-frame@7.26.2':
- resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
- engines: {node: '>=6.9.0'}
-
- '@babel/generator@7.27.0':
- resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-module-imports@7.25.9':
- resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-string-parser@7.25.9':
- resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-validator-identifier@7.25.9':
- resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
- engines: {node: '>=6.9.0'}
-
- '@babel/parser@7.27.0':
- resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==}
- engines: {node: '>=6.0.0'}
- hasBin: true
-
- '@babel/runtime@7.27.0':
- resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/template@7.27.0':
- resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
- engines: {node: '>=6.9.0'}
-
- '@babel/traverse@7.27.0':
- resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==}
- engines: {node: '>=6.9.0'}
-
- '@babel/types@7.27.0':
- resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
- engines: {node: '>=6.9.0'}
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
@@ -117,60 +122,6 @@ packages:
'@emnapi/wasi-threads@1.0.2':
resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==}
- '@emotion/babel-plugin@11.13.5':
- resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
-
- '@emotion/cache@11.14.0':
- resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
-
- '@emotion/hash@0.9.2':
- resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
-
- '@emotion/is-prop-valid@1.3.1':
- resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==}
-
- '@emotion/memoize@0.9.0':
- resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
-
- '@emotion/react@11.14.0':
- resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==}
- peerDependencies:
- '@types/react': '*'
- react: '>=16.8.0'
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@emotion/serialize@1.3.3':
- resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
-
- '@emotion/sheet@1.4.0':
- resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
-
- '@emotion/styled@11.14.0':
- resolution: {integrity: sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==}
- peerDependencies:
- '@emotion/react': ^11.0.0-rc.0
- '@types/react': '*'
- react: '>=16.8.0'
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@emotion/unitless@0.10.0':
- resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
-
- '@emotion/use-insertion-effect-with-fallbacks@1.2.0':
- resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
- peerDependencies:
- react: '>=16.8.0'
-
- '@emotion/utils@1.4.2':
- resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
-
- '@emotion/weak-memoize@0.4.0':
- resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
-
'@eslint-community/eslint-utils@4.6.1':
resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -209,6 +160,21 @@ packages:
resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@floating-ui/core@1.6.9':
+ resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
+
+ '@floating-ui/dom@1.6.13':
+ resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
+
+ '@floating-ui/react-dom@2.1.2':
+ resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.9':
+ resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
+
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -339,6 +305,10 @@ packages:
cpu: [x64]
os: [win32]
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
'@jridgewell/gen-mapping@0.3.8':
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
engines: {node: '>=6.0.0'}
@@ -357,129 +327,6 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
- '@mui/core-downloads-tracker@7.0.2':
- resolution: {integrity: sha512-TfeFU9TgN1N06hyb/pV/63FfO34nijZRMqgHk0TJ3gkl4Fbd+wZ73+ZtOd7jag6hMmzO9HSrBc6Vdn591nhkAg==}
-
- '@mui/material-nextjs@7.0.2':
- resolution: {integrity: sha512-hjm0MFSjx7HWbORMRldbwfKrQPHTSMXD6dkCCSTQZ2XX8fkKlnOXNnoXUFzlzFtMKMzs9QOfe3dHooTvnDEfuQ==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@emotion/cache': ^11.11.0
- '@emotion/react': ^11.11.4
- '@emotion/server': ^11.11.0
- '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
- next: ^13.0.0 || ^14.0.0 || ^15.0.0
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@emotion/cache':
- optional: true
- '@emotion/server':
- optional: true
- '@types/react':
- optional: true
-
- '@mui/material@7.0.2':
- resolution: {integrity: sha512-rjJlJ13+3LdLfobRplkXbjIFEIkn6LgpetgU/Cs3Xd8qINCCQK9qXQIjjQ6P0FXFTPFzEVMj0VgBR1mN+FhOcA==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@emotion/react': ^11.5.0
- '@emotion/styled': ^11.3.0
- '@mui/material-pigment-css': ^7.0.2
- '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
- react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@emotion/react':
- optional: true
- '@emotion/styled':
- optional: true
- '@mui/material-pigment-css':
- optional: true
- '@types/react':
- optional: true
-
- '@mui/private-theming@7.0.2':
- resolution: {integrity: sha512-6lt8heDC9wN8YaRqEdhqnm0cFCv08AMf4IlttFvOVn7ZdKd81PNpD/rEtPGLLwQAFyyKSxBG4/2XCgpbcdNKiA==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@mui/styled-engine@7.0.2':
- resolution: {integrity: sha512-11Bt4YdHGlh7sB8P75S9mRCUxTlgv7HGbr0UKz6m6Z9KLeiw1Bm9y/t3iqLLVMvSHYB6zL8X8X+LmfTE++gyBw==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@emotion/react': ^11.4.1
- '@emotion/styled': ^11.3.0
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@emotion/react':
- optional: true
- '@emotion/styled':
- optional: true
-
- '@mui/system@7.0.2':
- resolution: {integrity: sha512-yFUraAWYWuKIISPPEVPSQ1NLeqmTT4qiQ+ktmyS8LO/KwHxB+NNVOacEZaIofh5x1NxY8rzphvU5X2heRZ/RDA==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@emotion/react': ^11.5.0
- '@emotion/styled': ^11.3.0
- '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@emotion/react':
- optional: true
- '@emotion/styled':
- optional: true
- '@types/react':
- optional: true
-
- '@mui/types@7.4.1':
- resolution: {integrity: sha512-gUL8IIAI52CRXP/MixT1tJKt3SI6tVv4U/9soFsTtAsHzaJQptZ42ffdHZV3niX1ei0aUgMvOxBBN0KYqdG39g==}
- peerDependencies:
- '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@mui/utils@7.0.2':
- resolution: {integrity: sha512-72gcuQjPzhj/MLmPHLCgZjy2VjOH4KniR/4qRtXTTXIEwbkgcN+Y5W/rC90rWtMmZbjt9svZev/z+QHUI4j74w==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@types/react':
- optional: true
-
- '@mui/x-charts-vendor@8.0.0-beta.3':
- resolution: {integrity: sha512-mcelNPzVYyrU8yVkW/CcTGw0doFLtSFj1Pw8q8LghvJW3rMJUeoHxU2WVOUU2+ha4sHSlEBCPwRZvJBJnoWyqA==}
-
- '@mui/x-charts@8.0.0-beta.3':
- resolution: {integrity: sha512-3SYH5DoMv/xL0gGo7xKtuTu2GsNlgHCur7zalP7kWeIjTgCXib+ZUixGEMdfdyRcDEADkXWFssYw2QhsXA+rNg==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- '@emotion/react': ^11.9.0
- '@emotion/styled': ^11.8.1
- '@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0
- '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
- react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@emotion/react':
- optional: true
- '@emotion/styled':
- optional: true
-
- '@mui/x-internals@8.0.0-beta.3':
- resolution: {integrity: sha512-crbtLMWhI0sFXaZLknXPEGEaPLxpdIe8XAkJIr0HXD563TagGeyVk8lbNLoa5H3mVHWxmzNYiGUA4ns5Q6urQg==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
-
'@napi-rs/wasm-runtime@0.2.9':
resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==}
@@ -556,95 +403,478 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
- '@popperjs/core@2.11.8':
- resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
-
- '@rtsao/scc@1.1.0':
- resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
-
- '@rushstack/eslint-patch@1.11.0':
- resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==}
-
- '@swc/counter@0.1.3':
- resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
-
- '@swc/helpers@0.5.15':
- resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+ '@radix-ui/number@1.1.1':
+ resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
- '@tybys/wasm-util@0.9.0':
- resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
+ '@radix-ui/primitive@1.1.2':
+ resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
- '@types/d3-color@3.1.3':
- resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+ '@radix-ui/react-arrow@1.1.4':
+ resolution: {integrity: sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==}
+ 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
- '@types/d3-delaunay@6.0.4':
- resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
+ '@radix-ui/react-avatar@1.1.6':
+ resolution: {integrity: sha512-YDduxvqNMHzTQWNqja7Z/XTyFc8UOP98/ePjJTFa1vqILPlTPcQaVa1YyQMiQl4SFQPA9Y/zj1dHBgMlE5G/ow==}
+ 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
- '@types/d3-interpolate@3.0.4':
- resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+ '@radix-ui/react-checkbox@1.2.2':
+ resolution: {integrity: sha512-pMxzQLK+m/tkDRXJg7VUjRx6ozsBdzNLOV4vexfVBU57qT2Gvf4cw2gKKhOohJxjadQ+WcUXCKosTIxcZzi03A==}
+ 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
- '@types/d3-path@3.1.1':
- resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+ '@radix-ui/react-collection@1.1.4':
+ resolution: {integrity: sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==}
+ 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
- '@types/d3-scale@4.0.9':
- resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+ '@radix-ui/react-compose-refs@1.1.2':
+ resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- '@types/d3-shape@3.1.7':
- resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+ '@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
- '@types/d3-time@3.0.4':
- resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+ '@radix-ui/react-dialog@1.1.10':
+ resolution: {integrity: sha512-m6pZb0gEM5uHPSb+i2nKKGQi/HMSVjARMsLMWQfKDP+eJ6B+uqryHnXhpnohTWElw+vEcMk/o4wJODtdRKHwqg==}
+ 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
- '@types/d3-timer@3.0.2':
- resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+ '@radix-ui/react-direction@1.1.1':
+ resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- '@types/estree@1.0.7':
- resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
+ '@radix-ui/react-dismissable-layer@1.1.7':
+ resolution: {integrity: sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==}
+ 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
- '@types/json-schema@7.0.15':
- resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@radix-ui/react-dropdown-menu@2.1.11':
+ resolution: {integrity: sha512-wbPE3cFBfLl+S+LCxChWQGX0k14zUxgvep1HEnLhJ9mNhjyO3ETzRviAeKZ3XomT/iVRRZAWFsnFZ3N0wI8OmA==}
+ 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
- '@types/json5@0.0.29':
- resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+ '@radix-ui/react-focus-guards@1.1.2':
+ resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- '@types/node@20.17.30':
- resolution: {integrity: sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==}
+ '@radix-ui/react-focus-scope@1.1.4':
+ resolution: {integrity: sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA==}
+ 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
- '@types/parse-json@4.0.2':
- resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+ '@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
- '@types/prop-types@15.7.14':
- resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
+ '@radix-ui/react-label@2.1.4':
+ resolution: {integrity: sha512-wy3dqizZnZVV4ja0FNnUhIWNwWdoldXrneEyUcVtLYDAt8ovGS4ridtMAOGgXBBIfggL4BOveVWsjXDORdGEQg==}
+ 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
- '@types/react-dom@19.1.2':
- resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==}
+ '@radix-ui/react-menu@2.1.11':
+ resolution: {integrity: sha512-sbFI4Qaw02J0ogmR9tOMsSqsdrGNpUanlPYAqTE2JJafow8ecHtykg4fSTjNHBdDl4deiKMK+RhTEwyVhP7UDA==}
peerDependencies:
- '@types/react': ^19.0.0
+ '@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
- '@types/react-transition-group@4.4.12':
- resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==}
+ '@radix-ui/react-popover@1.1.10':
+ resolution: {integrity: sha512-IZN7b3sXqajiPsOzKuNJBSP9obF4MX5/5UhTgWNofw4r1H+eATWb0SyMlaxPD/kzA4vadFgy1s7Z1AEJ6WMyHQ==}
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
- '@types/react@19.1.2':
- resolution: {integrity: sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==}
+ '@radix-ui/react-popper@1.2.4':
+ resolution: {integrity: sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==}
+ 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
- '@typescript-eslint/eslint-plugin@8.30.1':
- resolution: {integrity: sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@radix-ui/react-portal@1.1.6':
+ resolution: {integrity: sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==}
peerDependencies:
- '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
+ '@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
- '@typescript-eslint/parser@8.30.1':
- resolution: {integrity: sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@radix-ui/react-presence@1.1.3':
+ resolution: {integrity: sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA==}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
+ '@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.0':
+ resolution: {integrity: sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==}
+ 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.7':
+ resolution: {integrity: sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q==}
+ 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.5':
+ resolution: {integrity: sha512-VyLjxI8/gXYn+Wij1FLpXjZp6Z/uNklUFQQ75tOpJNESeNaZ2kCRfjiEDmHgWmLeUPeJGwrqbgRmcdFjtYEkMA==}
+ 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.2':
+ resolution: {integrity: sha512-HjkVHtBkuq+r3zUAZ/CvNWUGKPfuicGDbgtZgiQuFmNcV5F+Tgy24ep2nsAW2nFgvhGPJVqeBZa6KyVN0EyrBA==}
+ 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.4':
+ resolution: {integrity: sha512-2fTm6PSiUm8YPq9W0E4reYuv01EE3aFSzt8edBiXqPHshF8N9+Kymt/k0/R+F3dkY5lQyB/zPtrP82phskLi7w==}
+ 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.0':
+ resolution: {integrity: sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-tabs@1.1.8':
+ resolution: {integrity: sha512-4iUaN9SYtG+/E+hJ7jRks/Nv90f+uAsRHbLYA6BcA9EsR6GNWgsvtS4iwU2SP0tOZfDGAyqIT0yz7ckgohEIFA==}
+ 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.1':
+ resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+ 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-controllable-state@1.2.2':
+ resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
+ 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-effect-event@0.0.2':
+ resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
+ 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-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
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-is-hydrated@0.0.0':
+ resolution: {integrity: sha512-23RkSm7jSZ8+rtfdSJTi/2D+p9soPbtnoG/tPf08egYCDr6p8X83hrcmW77p7MJ8kJYWNXwruuPTPp1TwIIH4g==}
+ 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-layout-effect@1.1.1':
+ resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+ 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-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.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.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.2.0':
+ resolution: {integrity: sha512-rQj0aAWOpCdCMRbI6pLQm8r7S2BM3YhTa0SzOYD55k+hJA8oo9J+H+9wLM9oMlZWOX/wJWPTzfDfmZkf7LvCfg==}
+ 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.1':
+ resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+
+ '@rtsao/scc@1.1.0':
+ resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+
+ '@rushstack/eslint-patch@1.11.0':
+ resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==}
+
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@tybys/wasm-util@0.9.0':
+ resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
+
+ '@types/estree@1.0.7':
+ resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/json5@0.0.29':
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+
+ '@types/node@20.17.30':
+ resolution: {integrity: sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==}
+
+ '@types/react-dom@19.1.2':
+ resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==}
+ peerDependencies:
+ '@types/react': ^19.0.0
+
+ '@types/react@19.1.2':
+ resolution: {integrity: sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==}
+
+ '@typescript-eslint/eslint-plugin@8.30.1':
+ resolution: {integrity: sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
+
+ '@typescript-eslint/parser@8.30.1':
+ resolution: {integrity: sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/scope-manager@8.30.1':
@@ -776,13 +1006,39 @@ packages:
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.1.0:
+ resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
+ engines: {node: '>=12'}
+
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
+ ansi-styles@6.2.1:
+ resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+ engines: {node: '>=12'}
+
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ aria-hidden@1.2.4:
+ resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
+ engines: {node: '>=10'}
+
aria-query@5.3.2:
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
engines: {node: '>= 0.4'}
@@ -826,6 +1082,13 @@ packages:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
+ autoprefixer@10.4.21:
+ resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -838,15 +1101,12 @@ packages:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
- babel-plugin-macros@3.1.0:
- resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
- engines: {node: '>=10', npm: '>=6'}
-
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
- bezier-easing@2.1.0:
- resolution: {integrity: sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==}
+ binary-extensions@2.3.0:
+ resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+ engines: {node: '>=8'}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@@ -858,6 +1118,11 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
+ browserslist@4.24.4:
+ resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
@@ -878,6 +1143,10 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ camelcase-css@2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+
caniuse-lite@1.0.30001715:
resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==}
@@ -885,6 +1154,13 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
+ chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+
+ class-variance-authority@0.7.1:
+ resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@@ -906,6 +1182,10 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
@@ -913,64 +1193,18 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
- convert-source-map@1.9.0:
- resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
-
- cosmiconfig@7.1.0:
- resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
- engines: {node: '>=10'}
-
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
- d3-array@3.2.4:
- resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
- engines: {node: '>=12'}
-
- d3-color@3.1.0:
- resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
- engines: {node: '>=12'}
-
- d3-delaunay@6.0.4:
- resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
- engines: {node: '>=12'}
-
- d3-format@3.1.0:
- resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
- engines: {node: '>=12'}
-
- d3-interpolate@3.0.1:
- resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
- engines: {node: '>=12'}
-
- d3-path@3.1.0:
- resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
- engines: {node: '>=12'}
-
- d3-scale@4.0.2:
- resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
- engines: {node: '>=12'}
-
- d3-shape@3.2.0:
- resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
- engines: {node: '>=12'}
-
- d3-time-format@4.1.0:
- resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
- engines: {node: '>=12'}
-
- d3-time@3.1.0:
- resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
- engines: {node: '>=12'}
-
- d3-timer@3.0.1:
- resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
- engines: {node: '>=12'}
-
damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -1017,20 +1251,23 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
- delaunator@5.0.1:
- resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
-
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+ didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
- dom-helpers@5.2.1:
- resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
-
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1038,12 +1275,18 @@ packages:
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ electron-to-chromium@1.5.139:
+ resolution: {integrity: sha512-GGnRYOTdN5LYpwbIr0rwP/ZHOQSvAF6TG0LSzp28uCBb9JiXHJGmaaKw29qjNJc5bGnnp6kXJqRnGMQoELwi5w==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
- error-ex@1.3.2:
- resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
-
es-abstract@1.23.9:
resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==}
engines: {node: '>= 0.4'}
@@ -1076,6 +1319,10 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -1232,9 +1479,6 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
- find-root@1.1.0:
- resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
-
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -1250,6 +1494,18 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
+ fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
@@ -1264,6 +1520,10 @@ packages:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
@@ -1283,9 +1543,9 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
- globals@11.12.0:
- resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
- engines: {node: '>=4'}
+ glob@10.4.5:
+ resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ hasBin: true
globals@14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
@@ -1333,9 +1593,6 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
- hoist-non-react-statics@3.3.2:
- resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
-
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
@@ -1355,17 +1612,10 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
- internmap@2.0.3:
- resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
- engines: {node: '>=12'}
-
is-array-buffer@3.0.5:
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
- is-arrayish@0.2.1:
- resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
-
is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
@@ -1377,6 +1627,10 @@ packages:
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
engines: {node: '>= 0.4'}
+ is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+
is-boolean-object@1.2.2:
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
engines: {node: '>= 0.4'}
@@ -1408,6 +1662,10 @@ packages:
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
engines: {node: '>= 0.4'}
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
is-generator-function@1.1.0:
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
engines: {node: '>= 0.4'}
@@ -1478,8 +1736,15 @@ packages:
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
engines: {node: '>= 0.4'}
- jiti@2.4.2:
- resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ jiti@1.21.7:
+ resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
+ hasBin: true
+
+ jiti@2.4.2:
+ resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
js-tokens@4.0.0:
@@ -1489,17 +1754,9 @@ packages:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
- jsesc@3.1.0:
- resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
- engines: {node: '>=6'}
- hasBin: true
-
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
- json-parse-even-better-errors@2.3.1:
- resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
-
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@@ -1528,6 +1785,10 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -1542,6 +1803,14 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lucide-react@0.503.0:
+ resolution: {integrity: sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -1564,6 +1833,10 @@ packages:
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
mrmime@2.0.1:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'}
@@ -1571,6 +1844,9 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -1584,6 +1860,12 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ next-themes@0.4.6:
+ resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+
next@15.3.1-canary.14:
resolution: {integrity: sha512-VyjuHut0vS/BGhwZN/Y+sirviRGtd6YSS+F3BLYhkUThlZfph8AWJNlFoBcUZvjOAAeD7KiRSxCMCKkff1c1ww==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -1605,10 +1887,25 @@ packages:
sass:
optional: true
+ node-releases@2.0.19:
+ resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
+ object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
@@ -1657,14 +1954,13 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
- parse-json@5.2.0:
- resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
- engines: {node: '>=8'}
-
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -1676,9 +1972,9 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
- path-type@4.0.0:
- resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
- engines: {node: '>=8'}
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -1691,14 +1987,63 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
+ pify@2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
+ postcss-import@15.1.0:
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+
+ postcss-js@4.0.1:
+ resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+
+ postcss-load-config@4.0.2:
+ resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
+ engines: {node: '>= 14'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+
+ postcss-nested@6.2.0:
+ resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+
+ postcss-selector-parser@6.1.2:
+ resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
postcss@8.4.31:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
+ postcss@8.5.3:
+ resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
+ engines: {node: ^10 || ^12 || >=14}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -1721,33 +2066,55 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
- react-is@19.1.0:
- resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==}
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.6.3:
+ resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- react-transition-group@4.4.5:
- resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ engines: {node: '>=10'}
peerDependencies:
- react: '>=16.6.0'
- react-dom: '>=16.6.0'
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
+ read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+
+ readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+
reflect.getprototypeof@1.0.10:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
- regenerator-runtime@0.14.1:
- resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
-
regexp.prototype.flags@1.5.4:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
- reselect@5.1.1:
- resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
-
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -1768,9 +2135,6 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
- robust-predicates@3.0.2:
- resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
-
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -1838,6 +2202,10 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
@@ -1849,10 +2217,6 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
- source-map@0.5.7:
- resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
- engines: {node: '>=0.10.0'}
-
stable-hash@0.0.5:
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
@@ -1860,6 +2224,14 @@ packages:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
string.prototype.includes@2.0.1:
resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
engines: {node: '>= 0.4'}
@@ -1883,6 +2255,14 @@ packages:
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
engines: {node: '>= 0.4'}
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.1.0:
+ resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+ engines: {node: '>=12'}
+
strip-bom@3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
@@ -1904,8 +2284,10 @@ packages:
babel-plugin-macros:
optional: true
- stylis@4.2.0:
- resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
+ sucrase@3.35.0:
+ resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
@@ -1915,6 +2297,26 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ tailwind-merge@3.2.0:
+ resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==}
+
+ tailwindcss-animate@1.0.7:
+ resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders'
+
+ tailwindcss@3.4.17:
+ resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
tinyglobby@0.2.13:
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
engines: {node: '>=12.0.0'}
@@ -1933,6 +2335,9 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
@@ -1974,14 +2379,43 @@ packages:
unrs-resolver@1.6.1:
resolution: {integrity: sha512-PLDI7BRVaI1C0x8mXr8leLPIOPPF1wCRFyKIswJAPJG3LdMxWNiAVvlTvmff5DSezapWFLagk18NF2cCNhe8Fg==}
+ update-browserslist-db@1.1.3:
+ resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
use-sync-external-store@1.5.0:
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
webpack-bundle-analyzer@4.10.1:
resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
engines: {node: '>= 10.13.0'}
@@ -2012,6 +2446,14 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
ws@7.5.10:
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
engines: {node: '>=8.3.0'}
@@ -2024,9 +2466,10 @@ packages:
utf-8-validate:
optional: true
- yaml@1.10.2:
- resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
- engines: {node: '>= 6'}
+ yaml@2.7.1:
+ resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==}
+ engines: {node: '>= 14'}
+ hasBin: true
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
@@ -2034,61 +2477,7 @@ packages:
snapshots:
- '@babel/code-frame@7.26.2':
- dependencies:
- '@babel/helper-validator-identifier': 7.25.9
- js-tokens: 4.0.0
- picocolors: 1.1.1
-
- '@babel/generator@7.27.0':
- dependencies:
- '@babel/parser': 7.27.0
- '@babel/types': 7.27.0
- '@jridgewell/gen-mapping': 0.3.8
- '@jridgewell/trace-mapping': 0.3.25
- jsesc: 3.1.0
-
- '@babel/helper-module-imports@7.25.9':
- dependencies:
- '@babel/traverse': 7.27.0
- '@babel/types': 7.27.0
- transitivePeerDependencies:
- - supports-color
-
- '@babel/helper-string-parser@7.25.9': {}
-
- '@babel/helper-validator-identifier@7.25.9': {}
-
- '@babel/parser@7.27.0':
- dependencies:
- '@babel/types': 7.27.0
-
- '@babel/runtime@7.27.0':
- dependencies:
- regenerator-runtime: 0.14.1
-
- '@babel/template@7.27.0':
- dependencies:
- '@babel/code-frame': 7.26.2
- '@babel/parser': 7.27.0
- '@babel/types': 7.27.0
-
- '@babel/traverse@7.27.0':
- dependencies:
- '@babel/code-frame': 7.26.2
- '@babel/generator': 7.27.0
- '@babel/parser': 7.27.0
- '@babel/template': 7.27.0
- '@babel/types': 7.27.0
- debug: 4.4.0
- globals: 11.12.0
- transitivePeerDependencies:
- - supports-color
-
- '@babel/types@7.27.0':
- dependencies:
- '@babel/helper-string-parser': 7.25.9
- '@babel/helper-validator-identifier': 7.25.9
+ '@alloc/quick-lru@5.2.0': {}
'@discoveryjs/json-ext@0.5.7': {}
@@ -2108,89 +2497,6 @@ snapshots:
tslib: 2.8.1
optional: true
- '@emotion/babel-plugin@11.13.5':
- dependencies:
- '@babel/helper-module-imports': 7.25.9
- '@babel/runtime': 7.27.0
- '@emotion/hash': 0.9.2
- '@emotion/memoize': 0.9.0
- '@emotion/serialize': 1.3.3
- babel-plugin-macros: 3.1.0
- convert-source-map: 1.9.0
- escape-string-regexp: 4.0.0
- find-root: 1.1.0
- source-map: 0.5.7
- stylis: 4.2.0
- transitivePeerDependencies:
- - supports-color
-
- '@emotion/cache@11.14.0':
- dependencies:
- '@emotion/memoize': 0.9.0
- '@emotion/sheet': 1.4.0
- '@emotion/utils': 1.4.2
- '@emotion/weak-memoize': 0.4.0
- stylis: 4.2.0
-
- '@emotion/hash@0.9.2': {}
-
- '@emotion/is-prop-valid@1.3.1':
- dependencies:
- '@emotion/memoize': 0.9.0
-
- '@emotion/memoize@0.9.0': {}
-
- '@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0)':
- dependencies:
- '@babel/runtime': 7.27.0
- '@emotion/babel-plugin': 11.13.5
- '@emotion/cache': 11.14.0
- '@emotion/serialize': 1.3.3
- '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.0)
- '@emotion/utils': 1.4.2
- '@emotion/weak-memoize': 0.4.0
- hoist-non-react-statics: 3.3.2
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.2
- transitivePeerDependencies:
- - supports-color
-
- '@emotion/serialize@1.3.3':
- dependencies:
- '@emotion/hash': 0.9.2
- '@emotion/memoize': 0.9.0
- '@emotion/unitless': 0.10.0
- '@emotion/utils': 1.4.2
- csstype: 3.1.3
-
- '@emotion/sheet@1.4.0': {}
-
- '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)':
- dependencies:
- '@babel/runtime': 7.27.0
- '@emotion/babel-plugin': 11.13.5
- '@emotion/is-prop-valid': 1.3.1
- '@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
- '@emotion/serialize': 1.3.3
- '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.0)
- '@emotion/utils': 1.4.2
- react: 19.1.0
- optionalDependencies:
- '@types/react': 19.1.2
- transitivePeerDependencies:
- - supports-color
-
- '@emotion/unitless@0.10.0': {}
-
- '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.0)':
- dependencies:
- react: 19.1.0
-
- '@emotion/utils@1.4.2': {}
-
- '@emotion/weak-memoize@0.4.0': {}
-
'@eslint-community/eslint-utils@4.6.1(eslint@9.25.0(jiti@2.4.2))':
dependencies:
eslint: 9.25.0(jiti@2.4.2)
@@ -2235,6 +2541,23 @@ snapshots:
'@eslint/core': 0.13.0
levn: 0.4.1
+ '@floating-ui/core@1.6.9':
+ dependencies:
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/dom@1.6.13':
+ dependencies:
+ '@floating-ui/core': 1.6.9
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/react-dom@2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/dom': 1.6.13
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ '@floating-ui/utils@0.2.9': {}
+
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.6':
@@ -2326,6 +2649,15 @@ snapshots:
'@img/sharp-win32-x64@0.34.1':
optional: true
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.1.0
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
'@jridgewell/gen-mapping@0.3.8':
dependencies:
'@jridgewell/set-array': 1.2.1
@@ -2343,206 +2675,486 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
- '@mui/core-downloads-tracker@7.0.2': {}
+ '@napi-rs/wasm-runtime@0.2.9':
+ dependencies:
+ '@emnapi/core': 1.4.3
+ '@emnapi/runtime': 1.4.3
+ '@tybys/wasm-util': 0.9.0
+ optional: true
- '@mui/material-nextjs@7.0.2(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(next@15.3.1-canary.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)':
+ '@next/bundle-analyzer@15.3.1':
dependencies:
- '@babel/runtime': 7.27.0
- '@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
- next: 15.3.1-canary.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ webpack-bundle-analyzer: 4.10.1
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ '@next/env@15.3.1-canary.14': {}
+
+ '@next/eslint-plugin-next@15.3.1-canary.14':
+ dependencies:
+ fast-glob: 3.3.1
+
+ '@next/swc-darwin-arm64@15.3.1-canary.14':
+ optional: true
+
+ '@next/swc-darwin-x64@15.3.1-canary.14':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@15.3.1-canary.14':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@15.3.1-canary.14':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@15.3.1-canary.14':
+ optional: true
+
+ '@next/swc-linux-x64-musl@15.3.1-canary.14':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@15.3.1-canary.14':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@15.3.1-canary.14':
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.19.1
+
+ '@nolyfill/is-core-module@1.0.39': {}
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@polka/url@1.0.0-next.29': {}
+
+ '@radix-ui/number@1.1.1': {}
+
+ '@radix-ui/primitive@1.1.2': {}
+
+ '@radix-ui/react-arrow@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
- '@emotion/cache': 11.14.0
'@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ '@radix-ui/react-avatar@1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
- '@babel/runtime': 7.27.0
- '@mui/core-downloads-tracker': 7.0.2
- '@mui/system': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
- '@mui/types': 7.4.1(@types/react@19.1.2)
- '@mui/utils': 7.0.2(@types/react@19.1.2)(react@19.1.0)
- '@popperjs/core': 2.11.8
- '@types/react-transition-group': 4.4.12(@types/react@19.1.2)
- clsx: 2.1.1
- csstype: 3.1.3
- prop-types: 15.8.1
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-is-hydrated': 0.0.0(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
- react-is: 19.1.0
- react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
- '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
'@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
+ '@radix-ui/react-checkbox@1.2.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@mui/private-theming@7.0.2(@types/react@19.1.2)(react@19.1.0)':
+ '@radix-ui/react-collection@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
- '@babel/runtime': 7.27.0
- '@mui/utils': 7.0.2(@types/react@19.1.2)(react@19.1.0)
- prop-types: 15.8.1
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0)
react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@mui/styled-engine@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(react@19.1.0)':
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.2)(react@19.1.0)':
dependencies:
- '@babel/runtime': 7.27.0
- '@emotion/cache': 11.14.0
- '@emotion/serialize': 1.3.3
- '@emotion/sheet': 1.4.0
- csstype: 3.1.3
- prop-types: 15.8.1
react: 19.1.0
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
- '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
+ '@types/react': 19.1.2
- '@mui/system@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)':
+ '@radix-ui/react-context@1.1.2(@types/react@19.1.2)(react@19.1.0)':
dependencies:
- '@babel/runtime': 7.27.0
- '@mui/private-theming': 7.0.2(@types/react@19.1.2)(react@19.1.0)
- '@mui/styled-engine': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(react@19.1.0)
- '@mui/types': 7.4.1(@types/react@19.1.2)
- '@mui/utils': 7.0.2(@types/react@19.1.2)(react@19.1.0)
- clsx: 2.1.1
- csstype: 3.1.3
- prop-types: 15.8.1
react: 19.1.0
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
- '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
'@types/react': 19.1.2
- '@mui/types@7.4.1(@types/react@19.1.2)':
+ '@radix-ui/react-dialog@1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ aria-hidden: 1.2.4
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.6.3(@types/react@19.1.2)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
+ '@radix-ui/react-direction@1.1.1(@types/react@19.1.2)(react@19.1.0)':
dependencies:
- '@babel/runtime': 7.27.0
+ react: 19.1.0
optionalDependencies:
'@types/react': 19.1.2
- '@mui/utils@7.0.2(@types/react@19.1.2)(react@19.1.0)':
+ '@radix-ui/react-dismissable-layer@1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
- '@babel/runtime': 7.27.0
- '@mui/types': 7.4.1(@types/react@19.1.2)
- '@types/prop-types': 15.7.14
- clsx: 2.1.1
- prop-types: 15.8.1
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.2)(react@19.1.0)
react: 19.1.0
- react-is: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@mui/x-charts-vendor@8.0.0-beta.3':
- dependencies:
- '@babel/runtime': 7.27.0
- '@types/d3-color': 3.1.3
- '@types/d3-delaunay': 6.0.4
- '@types/d3-interpolate': 3.0.4
- '@types/d3-scale': 4.0.9
- '@types/d3-shape': 3.1.7
- '@types/d3-time': 3.0.4
- '@types/d3-timer': 3.0.2
- d3-color: 3.1.0
- d3-delaunay: 6.0.4
- d3-interpolate: 3.0.1
- d3-scale: 4.0.2
- d3-shape: 3.2.0
- d3-time: 3.1.0
- d3-timer: 3.0.1
- delaunator: 5.0.1
- robust-predicates: 3.0.2
-
- '@mui/x-charts@8.0.0-beta.3(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@mui/material@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- '@babel/runtime': 7.27.0
- '@mui/material': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@mui/system': 7.0.2(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
- '@mui/utils': 7.0.2(@types/react@19.1.2)(react@19.1.0)
- '@mui/x-charts-vendor': 8.0.0-beta.3
- '@mui/x-internals': 8.0.0-beta.3(@types/react@19.1.2)(react@19.1.0)
- bezier-easing: 2.1.0
- clsx: 2.1.1
- prop-types: 15.8.1
+ '@radix-ui/react-dropdown-menu@2.1.11(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-menu': 2.1.11(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
- react-is: 19.1.0
- reselect: 5.1.1
- use-sync-external-store: 1.5.0(react@19.1.0)
optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@19.1.2)(react@19.1.0)
- '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.2)(react@19.1.0))(@types/react@19.1.2)(react@19.1.0)
- transitivePeerDependencies:
- - '@types/react'
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@mui/x-internals@8.0.0-beta.3(@types/react@19.1.2)(react@19.1.0)':
+ '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.2)(react@19.1.0)':
dependencies:
- '@babel/runtime': 7.27.0
- '@mui/utils': 7.0.2(@types/react@19.1.2)(react@19.1.0)
react: 19.1.0
- transitivePeerDependencies:
- - '@types/react'
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@napi-rs/wasm-runtime@0.2.9':
+ '@radix-ui/react-focus-scope@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
- '@emnapi/core': 1.4.3
- '@emnapi/runtime': 1.4.3
- '@tybys/wasm-util': 0.9.0
- optional: true
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@next/bundle-analyzer@15.3.1':
+ '@radix-ui/react-id@1.1.1(@types/react@19.1.2)(react@19.1.0)':
dependencies:
- webpack-bundle-analyzer: 4.10.1
- transitivePeerDependencies:
- - bufferutil
- - utf-8-validate
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@next/env@15.3.1-canary.14': {}
+ '@radix-ui/react-label@2.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
+ '@radix-ui/react-menu@2.1.11(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ aria-hidden: 1.2.4
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.6.3(@types/react@19.1.2)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
+ '@radix-ui/react-popover@1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ aria-hidden: 1.2.4
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.6.3(@types/react@19.1.2)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
+ '@radix-ui/react-popper@1.2.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-arrow': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/rect': 1.1.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@next/eslint-plugin-next@15.3.1-canary.14':
+ '@radix-ui/react-portal@1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
- fast-glob: 3.3.1
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@next/swc-darwin-arm64@15.3.1-canary.14':
- optional: true
+ '@radix-ui/react-presence@1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@next/swc-darwin-x64@15.3.1-canary.14':
- optional: true
+ '@radix-ui/react-primitive@2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
+ '@radix-ui/react-roving-focus@1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
+ '@radix-ui/react-scroll-area@1.2.5(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
+ '@radix-ui/react-select@2.2.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.2.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ aria-hidden: 1.2.4
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.6.3(@types/react@19.1.2)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@next/swc-linux-arm64-gnu@15.3.1-canary.14':
- optional: true
+ '@radix-ui/react-separator@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@next/swc-linux-arm64-musl@15.3.1-canary.14':
- optional: true
+ '@radix-ui/react-slot@1.2.0(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@next/swc-linux-x64-gnu@15.3.1-canary.14':
- optional: true
+ '@radix-ui/react-tabs@1.1.8(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@next/swc-linux-x64-musl@15.3.1-canary.14':
- optional: true
+ '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@next/swc-win32-arm64-msvc@15.3.1-canary.14':
- optional: true
+ '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@next/swc-win32-x64-msvc@15.3.1-canary.14':
- optional: true
+ '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
+ '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
+ '@radix-ui/react-use-is-hydrated@0.0.0(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ use-sync-external-store: 1.5.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@nodelib/fs.scandir@2.1.5':
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.2)(react@19.1.0)':
dependencies:
- '@nodelib/fs.stat': 2.0.5
- run-parallel: 1.2.0
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@nodelib/fs.stat@2.0.5': {}
+ '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@nodelib/fs.walk@1.2.8':
+ '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.2)(react@19.1.0)':
dependencies:
- '@nodelib/fs.scandir': 2.1.5
- fastq: 1.19.1
+ '@radix-ui/rect': 1.1.1
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@nolyfill/is-core-module@1.0.39': {}
+ '@radix-ui/react-use-size@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
- '@polka/url@1.0.0-next.29': {}
+ '@radix-ui/react-visually-hidden@1.2.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
- '@popperjs/core@2.11.8': {}
+ '@radix-ui/rect@1.1.1': {}
'@rtsao/scc@1.1.0': {}
@@ -2559,28 +3171,6 @@ snapshots:
tslib: 2.8.1
optional: true
- '@types/d3-color@3.1.3': {}
-
- '@types/d3-delaunay@6.0.4': {}
-
- '@types/d3-interpolate@3.0.4':
- dependencies:
- '@types/d3-color': 3.1.3
-
- '@types/d3-path@3.1.1': {}
-
- '@types/d3-scale@4.0.9':
- dependencies:
- '@types/d3-time': 3.0.4
-
- '@types/d3-shape@3.1.7':
- dependencies:
- '@types/d3-path': 3.1.1
-
- '@types/d3-time@3.0.4': {}
-
- '@types/d3-timer@3.0.2': {}
-
'@types/estree@1.0.7': {}
'@types/json-schema@7.0.15': {}
@@ -2591,18 +3181,10 @@ snapshots:
dependencies:
undici-types: 6.19.8
- '@types/parse-json@4.0.2': {}
-
- '@types/prop-types@15.7.14': {}
-
'@types/react-dom@19.1.2(@types/react@19.1.2)':
dependencies:
'@types/react': 19.1.2
- '@types/react-transition-group@4.4.12(@types/react@19.1.2)':
- dependencies:
- '@types/react': 19.1.2
-
'@types/react@19.1.2':
dependencies:
csstype: 3.1.3
@@ -2751,12 +3333,31 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.1.0: {}
+
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
+ ansi-styles@6.2.1: {}
+
+ any-promise@1.3.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ arg@5.0.2: {}
+
argparse@2.0.1: {}
+ aria-hidden@1.2.4:
+ dependencies:
+ tslib: 2.8.1
+
aria-query@5.3.2: {}
array-buffer-byte-length@1.0.2:
@@ -2828,6 +3429,16 @@ snapshots:
async-function@1.0.0: {}
+ autoprefixer@10.4.21(postcss@8.5.3):
+ dependencies:
+ browserslist: 4.24.4
+ caniuse-lite: 1.0.30001715
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.1.1
+ postcss: 8.5.3
+ postcss-value-parser: 4.2.0
+
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.1.0
@@ -2836,15 +3447,9 @@ snapshots:
axobject-query@4.1.0: {}
- babel-plugin-macros@3.1.0:
- dependencies:
- '@babel/runtime': 7.27.0
- cosmiconfig: 7.1.0
- resolve: 1.22.10
-
balanced-match@1.0.2: {}
- bezier-easing@2.1.0: {}
+ binary-extensions@2.3.0: {}
brace-expansion@1.1.11:
dependencies:
@@ -2859,6 +3464,13 @@ snapshots:
dependencies:
fill-range: 7.1.1
+ browserslist@4.24.4:
+ dependencies:
+ caniuse-lite: 1.0.30001715
+ electron-to-chromium: 1.5.139
+ node-releases: 2.0.19
+ update-browserslist-db: 1.1.3(browserslist@4.24.4)
+
busboy@1.6.0:
dependencies:
streamsearch: 1.1.0
@@ -2882,6 +3494,8 @@ snapshots:
callsites@3.1.0: {}
+ camelcase-css@2.0.1: {}
+
caniuse-lite@1.0.30001715: {}
chalk@4.1.2:
@@ -2889,6 +3503,22 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
+ chokidar@3.6.0:
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ class-variance-authority@0.7.1:
+ dependencies:
+ clsx: 2.1.1
+
client-only@0.0.1: {}
clsx@2.1.1: {}
@@ -2911,67 +3541,21 @@ snapshots:
color-string: 1.9.1
optional: true
+ commander@4.1.1: {}
+
commander@7.2.0: {}
concat-map@0.0.1: {}
- convert-source-map@1.9.0: {}
-
- cosmiconfig@7.1.0:
- dependencies:
- '@types/parse-json': 4.0.2
- import-fresh: 3.3.1
- parse-json: 5.2.0
- path-type: 4.0.0
- yaml: 1.10.2
-
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
- csstype@3.1.3: {}
-
- d3-array@3.2.4:
- dependencies:
- internmap: 2.0.3
-
- d3-color@3.1.0: {}
-
- d3-delaunay@6.0.4:
- dependencies:
- delaunator: 5.0.1
-
- d3-format@3.1.0: {}
-
- d3-interpolate@3.0.1:
- dependencies:
- d3-color: 3.1.0
-
- d3-path@3.1.0: {}
-
- d3-scale@4.0.2:
- dependencies:
- d3-array: 3.2.4
- d3-format: 3.1.0
- d3-interpolate: 3.0.1
- d3-time: 3.1.0
- d3-time-format: 4.1.0
-
- d3-shape@3.2.0:
- dependencies:
- d3-path: 3.1.0
-
- d3-time-format@4.1.0:
- dependencies:
- d3-time: 3.1.0
-
- d3-time@3.1.0:
- dependencies:
- d3-array: 3.2.4
+ cssesc@3.0.0: {}
- d3-timer@3.0.1: {}
+ csstype@3.1.3: {}
damerau-levenshtein@1.0.8: {}
@@ -3017,22 +3601,19 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
- delaunator@5.0.1:
- dependencies:
- robust-predicates: 3.0.2
-
detect-libc@2.0.3:
optional: true
+ detect-node-es@1.1.0: {}
+
+ didyoumean@1.2.2: {}
+
+ dlv@1.1.3: {}
+
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
- dom-helpers@5.2.1:
- dependencies:
- '@babel/runtime': 7.27.0
- csstype: 3.1.3
-
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -3041,11 +3622,13 @@ snapshots:
duplexer@0.1.2: {}
- emoji-regex@9.2.2: {}
+ eastasianwidth@0.2.0: {}
- error-ex@1.3.2:
- dependencies:
- is-arrayish: 0.2.1
+ electron-to-chromium@1.5.139: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
es-abstract@1.23.9:
dependencies:
@@ -3145,6 +3728,8 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
+ escalade@3.2.0: {}
+
escape-string-regexp@4.0.0: {}
eslint-config-next@15.3.1-canary.14(eslint@9.25.0(jiti@2.4.2))(typescript@5.8.3):
@@ -3382,8 +3967,6 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
- find-root@1.1.0: {}
-
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -3400,6 +3983,16 @@ snapshots:
dependencies:
is-callable: 1.2.7
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ fraction.js@4.3.7: {}
+
+ fsevents@2.3.3:
+ optional: true
+
function-bind@1.1.2: {}
function.prototype.name@1.1.8:
@@ -3426,6 +4019,8 @@ snapshots:
hasown: 2.0.2
math-intrinsics: 1.1.0
+ get-nonce@1.0.1: {}
+
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
@@ -3449,7 +4044,14 @@ snapshots:
dependencies:
is-glob: 4.0.3
- globals@11.12.0: {}
+ glob@10.4.5:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.5
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
globals@14.0.0: {}
@@ -3488,10 +4090,6 @@ snapshots:
dependencies:
function-bind: 1.1.2
- hoist-non-react-statics@3.3.2:
- dependencies:
- react-is: 16.13.1
-
html-escaper@2.0.2: {}
ignore@5.3.2: {}
@@ -3509,16 +4107,12 @@ snapshots:
hasown: 2.0.2
side-channel: 1.1.0
- internmap@2.0.3: {}
-
is-array-buffer@3.0.5:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.4
get-intrinsic: 1.3.0
- is-arrayish@0.2.1: {}
-
is-arrayish@0.3.2:
optional: true
@@ -3534,6 +4128,10 @@ snapshots:
dependencies:
has-bigints: 1.1.0
+ is-binary-path@2.1.0:
+ dependencies:
+ binary-extensions: 2.3.0
+
is-boolean-object@1.2.2:
dependencies:
call-bound: 1.0.4
@@ -3566,6 +4164,8 @@ snapshots:
dependencies:
call-bound: 1.0.4
+ is-fullwidth-code-point@3.0.0: {}
+
is-generator-function@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -3640,6 +4240,14 @@ snapshots:
has-symbols: 1.1.0
set-function-name: 2.0.2
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jiti@1.21.7: {}
+
jiti@2.4.2:
optional: true
@@ -3649,12 +4257,8 @@ snapshots:
dependencies:
argparse: 2.0.1
- jsesc@3.1.0: {}
-
json-buffer@3.0.1: {}
- json-parse-even-better-errors@2.3.1: {}
-
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
@@ -3685,6 +4289,8 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
+ lilconfig@3.1.3: {}
+
lines-and-columns@1.2.4: {}
locate-path@6.0.0:
@@ -3697,6 +4303,12 @@ snapshots:
dependencies:
js-tokens: 4.0.0
+ lru-cache@10.4.3: {}
+
+ lucide-react@0.503.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
math-intrinsics@1.1.0: {}
merge2@1.4.1: {}
@@ -3716,16 +4328,29 @@ snapshots:
minimist@1.2.8: {}
+ minipass@7.1.2: {}
+
mrmime@2.0.1: {}
ms@2.1.3: {}
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
nanoid@3.3.11: {}
napi-postinstall@0.1.3: {}
natural-compare@1.4.0: {}
+ next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
next@15.3.1-canary.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@next/env': 15.3.1-canary.14
@@ -3751,8 +4376,16 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
+ node-releases@2.0.19: {}
+
+ normalize-path@3.0.0: {}
+
+ normalize-range@0.1.2: {}
+
object-assign@4.1.1: {}
+ object-hash@3.0.0: {}
+
object-inspect@1.13.4: {}
object-keys@1.1.1: {}
@@ -3818,24 +4451,22 @@ snapshots:
dependencies:
p-limit: 3.1.0
+ package-json-from-dist@1.0.1: {}
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
- parse-json@5.2.0:
- dependencies:
- '@babel/code-frame': 7.26.2
- error-ex: 1.3.2
- json-parse-even-better-errors: 2.3.1
- lines-and-columns: 1.2.4
-
path-exists@4.0.0: {}
path-key@3.1.1: {}
path-parse@1.0.7: {}
- path-type@4.0.0: {}
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.2
picocolors@1.1.1: {}
@@ -3843,14 +4474,55 @@ snapshots:
picomatch@4.0.2: {}
+ pify@2.3.0: {}
+
+ pirates@4.0.7: {}
+
possible-typed-array-names@1.1.0: {}
+ postcss-import@15.1.0(postcss@8.5.3):
+ dependencies:
+ postcss: 8.5.3
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.10
+
+ postcss-js@4.0.1(postcss@8.5.3):
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.5.3
+
+ postcss-load-config@4.0.2(postcss@8.5.3):
+ dependencies:
+ lilconfig: 3.1.3
+ yaml: 2.7.1
+ optionalDependencies:
+ postcss: 8.5.3
+
+ postcss-nested@6.2.0(postcss@8.5.3):
+ dependencies:
+ postcss: 8.5.3
+ postcss-selector-parser: 6.1.2
+
+ postcss-selector-parser@6.1.2:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
postcss@8.4.31:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
+ postcss@8.5.3:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
prelude-ls@1.2.1: {}
prop-types@15.8.1:
@@ -3870,19 +4542,43 @@ snapshots:
react-is@16.13.1: {}
- react-is@19.1.0: {}
+ react-remove-scroll-bar@2.3.8(@types/react@19.1.2)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-style-singleton: 2.2.3(@types/react@19.1.2)(react@19.1.0)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.1.2
- react-transition-group@4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ react-remove-scroll@2.6.3(@types/react@19.1.2)(react@19.1.0):
dependencies:
- '@babel/runtime': 7.27.0
- dom-helpers: 5.2.1
- loose-envify: 1.4.0
- prop-types: 15.8.1
react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll-bar: 2.3.8(@types/react@19.1.2)(react@19.1.0)
+ react-style-singleton: 2.2.3(@types/react@19.1.2)(react@19.1.0)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@19.1.2)(react@19.1.0)
+ use-sidecar: 1.1.3(@types/react@19.1.2)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+
+ react-style-singleton@2.2.3(@types/react@19.1.2)(react@19.1.0):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.1.2
react@19.1.0: {}
+ read-cache@1.0.0:
+ dependencies:
+ pify: 2.3.0
+
+ readdirp@3.6.0:
+ dependencies:
+ picomatch: 2.3.1
+
reflect.getprototypeof@1.0.10:
dependencies:
call-bind: 1.0.8
@@ -3894,8 +4590,6 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
- regenerator-runtime@0.14.1: {}
-
regexp.prototype.flags@1.5.4:
dependencies:
call-bind: 1.0.8
@@ -3905,8 +4599,6 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
- reselect@5.1.1: {}
-
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -3925,8 +4617,6 @@ snapshots:
reusify@1.1.0: {}
- robust-predicates@3.0.2: {}
-
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -4040,6 +4730,8 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
+ signal-exit@4.1.0: {}
+
simple-swizzle@0.2.2:
dependencies:
is-arrayish: 0.3.2
@@ -4053,12 +4745,22 @@ snapshots:
source-map-js@1.2.1: {}
- source-map@0.5.7: {}
-
stable-hash@0.0.5: {}
streamsearch@1.1.0: {}
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.0
+
string.prototype.includes@2.0.1:
dependencies:
call-bind: 1.0.8
@@ -4109,6 +4811,14 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.1.0:
+ dependencies:
+ ansi-regex: 6.1.0
+
strip-bom@3.0.0: {}
strip-json-comments@3.1.1: {}
@@ -4118,7 +4828,15 @@ snapshots:
client-only: 0.0.1
react: 19.1.0
- stylis@4.2.0: {}
+ sucrase@3.35.0:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ commander: 4.1.1
+ glob: 10.4.5
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ ts-interface-checker: 0.1.13
supports-color@7.2.0:
dependencies:
@@ -4126,6 +4844,47 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ tailwind-merge@3.2.0: {}
+
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
+ dependencies:
+ tailwindcss: 3.4.17
+
+ tailwindcss@3.4.17:
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.3
+ postcss-import: 15.1.0(postcss@8.5.3)
+ postcss-js: 4.0.1(postcss@8.5.3)
+ postcss-load-config: 4.0.2(postcss@8.5.3)
+ postcss-nested: 6.2.0(postcss@8.5.3)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.10
+ sucrase: 3.35.0
+ transitivePeerDependencies:
+ - ts-node
+
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
tinyglobby@0.2.13:
dependencies:
fdir: 6.4.4(picomatch@4.0.2)
@@ -4141,6 +4900,8 @@ snapshots:
dependencies:
typescript: 5.8.3
+ ts-interface-checker@0.1.13: {}
+
tsconfig-paths@3.15.0:
dependencies:
'@types/json5': 0.0.29
@@ -4219,14 +4980,37 @@ snapshots:
'@unrs/resolver-binding-win32-ia32-msvc': 1.6.1
'@unrs/resolver-binding-win32-x64-msvc': 1.6.1
+ update-browserslist-db@1.1.3(browserslist@4.24.4):
+ dependencies:
+ browserslist: 4.24.4
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
+ use-callback-ref@1.3.3(@types/react@19.1.2)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.1.2
+
+ use-sidecar@1.1.3(@types/react@19.1.2)(react@19.1.0):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.1.2
+
use-sync-external-store@1.5.0(react@19.1.0):
dependencies:
react: 19.1.0
+ util-deprecate@1.0.2: {}
+
webpack-bundle-analyzer@4.10.1:
dependencies:
'@discoveryjs/json-ext': 0.5.7
@@ -4293,8 +5077,20 @@ snapshots:
word-wrap@1.2.5: {}
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.1
+ string-width: 5.1.2
+ strip-ansi: 7.1.0
+
ws@7.5.10: {}
- yaml@1.10.2: {}
+ yaml@2.7.1: {}
yocto-queue@0.1.0: {}
diff --git a/postcss.config.mjs b/postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ },
+};
+
+export default config;
diff --git a/public/placeholder-logo.png b/public/placeholder-logo.png
Binary files differ.
diff --git a/public/placeholder-logo.svg b/public/placeholder-logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="215" height="48" fill="none"><path fill="#000" d="M57.588 9.6h6L73.828 38h-5.2l-2.36-6.88h-11.36L52.548 38h-5.2l10.24-28.4Zm7.16 17.16-4.16-12.16-4.16 12.16h8.32Zm23.694-2.24c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.486-7.72.12 3.4c.534-1.227 1.307-2.173 2.32-2.84 1.04-.693 2.267-1.04 3.68-1.04 1.494 0 2.76.387 3.8 1.16 1.067.747 1.827 1.813 2.28 3.2.507-1.44 1.294-2.52 2.36-3.24 1.094-.747 2.414-1.12 3.96-1.12 1.414 0 2.64.307 3.68.92s1.84 1.52 2.4 2.72c.56 1.2.84 2.667.84 4.4V38h-4.96V25.92c0-1.813-.293-3.187-.88-4.12-.56-.96-1.413-1.44-2.56-1.44-.906 0-1.68.213-2.32.64-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.84-.48 3.04V38h-4.56V25.92c0-1.2-.133-2.213-.4-3.04-.24-.827-.626-1.453-1.16-1.88-.506-.427-1.133-.64-1.88-.64-.906 0-1.68.227-2.32.68-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.827-.48 3V38h-4.96V16.8h4.48Zm26.723 10.6c0-2.24.427-4.187 1.28-5.84.854-1.68 2.067-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.84 0 3.494.413 4.96 1.24 1.467.827 2.64 2.08 3.52 3.76.88 1.653 1.347 3.693 1.4 6.12v1.32h-15.08c.107 1.813.614 3.227 1.52 4.24.907.987 2.134 1.48 3.68 1.48.987 0 1.88-.253 2.68-.76a4.803 4.803 0 0 0 1.84-2.2l5.08.36c-.64 2.027-1.84 3.64-3.6 4.84-1.733 1.173-3.733 1.76-6 1.76-2.08 0-3.906-.453-5.48-1.36-1.573-.907-2.786-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84Zm15.16-2.04c-.213-1.733-.76-3.013-1.64-3.84-.853-.827-1.893-1.24-3.12-1.24-1.44 0-2.6.453-3.48 1.36-.88.88-1.44 2.12-1.68 3.72h9.92ZM163.139 9.6V38h-5.04V9.6h5.04Zm8.322 7.2.24 5.88-.64-.36c.32-2.053 1.094-3.56 2.32-4.52 1.254-.987 2.787-1.48 4.6-1.48 2.32 0 4.107.733 5.36 2.2 1.254 1.44 1.88 3.387 1.88 5.84V38h-4.96V25.92c0-1.253-.12-2.28-.36-3.08-.24-.8-.64-1.413-1.2-1.84-.533-.427-1.253-.64-2.16-.64-1.44 0-2.573.48-3.4 1.44-.8.933-1.2 2.307-1.2 4.12V38h-4.96V16.8h4.48Zm30.003 7.72c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.443 8.16V38h-5.6v-5.32h5.6Z"/><path fill="#171717" fill-rule="evenodd" d="m7.839 40.783 16.03-28.054L20 6 0 40.783h7.839Zm8.214 0H40L27.99 19.894l-4.02 7.032 3.976 6.914H20.02l-3.967 6.943Z" clip-rule="evenodd"/></svg>
+\ No newline at end of file
diff --git a/public/placeholder-user.jpg b/public/placeholder-user.jpg
Binary files differ.
diff --git a/public/placeholder.jpg b/public/placeholder.jpg
Binary files differ.
diff --git a/public/placeholder.svg b/public/placeholder.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
+\ No newline at end of file
diff --git a/src/app/api/verify-matrix-token/route.ts b/src/app/api/verify-matrix-token/route.ts
@@ -0,0 +1,62 @@
+import { NextResponse } from "next/server"
+
+interface WellKnownResponse {
+ "m.homeserver"?: {
+ base_url: string
+ }
+}
+
+export async function POST(request: Request) {
+ try {
+ const { matrixId, token } = await request.json()
+
+ if (!matrixId || !token) {
+ return NextResponse.json({ error: "Missing required fields" }, { status: 400 })
+ }
+
+ // Extract server name from Matrix ID
+ const serverName = matrixId.includes(":") ? matrixId.split(":").pop() || "matrix.org" : "matrix.org"
+
+ // Discover homeserver URL
+ let homeserverUrl = `https://${serverName}`
+
+ try {
+ // Try to fetch well-known data
+ const wellKnownUrl = `https://${serverName}/.well-known/matrix/client`
+ const response = await fetch(wellKnownUrl)
+
+ if (response.ok) {
+ const data = (await response.json()) as WellKnownResponse
+ if (data["m.homeserver"]?.base_url) {
+ homeserverUrl = data["m.homeserver"].base_url
+ }
+ }
+ } catch (e) {
+ console.warn("Well-known discovery failed, falling back to direct server", e)
+ }
+
+ // In a real implementation, you would:
+ // 1. Make a request to the Matrix server to verify the token
+ // const response = await fetch(
+ // `${homeserverUrl}/_matrix/federation/v1/openid/userinfo?access_token=${token}`,
+ // { method: "GET" }
+ // )
+ // const data = await response.json()
+
+ // 2. Verify that the user ID in the response matches the provided matrixId
+ // if (data.sub !== matrixId) {
+ // return NextResponse.json(
+ // { error: "Token verification failed" },
+ // { status: 401 }
+ // )
+ // }
+
+ // 3. Create a user account or session in your system
+
+ // For demo purposes, we'll just return success
+ return NextResponse.json({ success: true })
+ } catch (error) {
+ console.error("Error verifying Matrix token:", error)
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 })
+ }
+}
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
@@ -0,0 +1,716 @@
+'use client';
+
+import { useState } from "react"
+import Link from "next/link"
+import {
+ Shield,
+ AlertTriangle,
+ CheckCircle,
+ Ban,
+ LogOut,
+ Bell,
+ Menu,
+ UserX,
+ MessageSquare,
+ UserPlus,
+ Filter,
+ Crown,
+ X,
+ Plus,
+ Home,
+ ChevronDown,
+} from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Textarea } from "@/components/ui/textarea"
+import { BlurredText } from "@/components/blurred-text"
+import { BlurredImage } from "@/components/blurred-image"
+import { Badge } from "@/components/ui/badge"
+import { Separator } from "@/components/ui/separator"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { Checkbox } from "@/components/ui/checkbox"
+import { mockTeams, TeamManagement } from "./team-management"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
+import Overview from "@/components/dashboard/overview-pane";
+import Reports from "@/components/dashboard/reports-pane";
+import Bans from "@/components/dashboard/bans-pane";
+
+// Mock notifications
+const mockNotifications = [
+ {
+ id: "notif1",
+ type: "report",
+ title: "New report",
+ message: "A new message has been reported in #general",
+ timestamp: "2025-04-21T15:30:00Z",
+ read: false,
+ teamId: "team1",
+ },
+ {
+ id: "notif2",
+ type: "ban",
+ title: "User banned",
+ message: "@spammer:matrix.org has been banned by @mod1:matrix.org",
+ timestamp: "2025-04-21T14:45:00Z",
+ read: false,
+ teamId: "team1",
+ },
+ {
+ id: "notif3",
+ type: "invite",
+ title: "Team invitation",
+ message: "You've been invited to join the Support Team Bot",
+ timestamp: "2025-04-21T12:15:00Z",
+ read: true,
+ teamId: "team2",
+ },
+]
+
+export default function DashboardPage() {
+ const [activeTab, setActiveTab] = useState("overview")
+ const [selectedReport, setSelectedReport] = useState<string | null>(null)
+ const [selectedPolicyList, setSelectedPolicyList] = useState("global")
+ const [selectedTeam, setSelectedTeam] = useState(mockTeams[0])
+ const [notifications, setNotifications] = useState(mockNotifications)
+
+ // Mock data for reports - now filtered by team
+ const reports = [
+ {
+ id: "rep_1",
+ type: "message",
+ status: "open",
+ priority: "high",
+ timestamp: "2025-04-21T14:32:00Z",
+ reporter: "@moderator:matrix.org",
+ room: "#general:matrix.org",
+ teamId: "team1",
+ subject: {
+ type: "message",
+ id: "$1234567890abcdefghij:matrix.org",
+ sender: "@spammer:badserver.org",
+ content: {
+ msgtype: "m.text",
+ body: "Hey everyone! Check out this amazing investment opportunity at scam-crypto-site.com - 1000% returns guaranteed!",
+ },
+ timestamp: "2025-04-21T14:30:00Z",
+ },
+ reason: "Spam/scam content",
+ },
+ {
+ id: "rep_2",
+ type: "invite",
+ status: "open",
+ priority: "medium",
+ timestamp: "2025-04-21T13:15:00Z",
+ reporter: "@user:matrix.org",
+ room: "#general:matrix.org",
+ teamId: "team1",
+ subject: {
+ type: "invite",
+ sender: "@spambot:badserver.org",
+ target: "@user:matrix.org",
+ timestamp: "2025-04-21T13:10:00Z",
+ },
+ reason: "Unsolicited invite from unknown user",
+ },
+ {
+ id: "rep_3",
+ type: "message",
+ status: "open",
+ priority: "high",
+ timestamp: "2025-04-21T12:05:00Z",
+ reporter: "@admin:matrix.org",
+ room: "#support:matrix.org",
+ teamId: "team1",
+ subject: {
+ type: "message",
+ id: "$abcdefghij1234567890:matrix.org",
+ sender: "@troll:badserver.org",
+ content: {
+ msgtype: "m.image",
+ body: "image.jpg",
+ url: "mxc://matrix.org/abcdefghijklmnopqrstuvwxyz",
+ info: {
+ mimetype: "image/jpeg",
+ w: 800,
+ h: 600,
+ size: 95431,
+ },
+ },
+ timestamp: "2025-04-21T12:00:00Z",
+ },
+ reason: "Inappropriate image",
+ },
+ {
+ id: "rep_4",
+ type: "message",
+ status: "open",
+ priority: "medium",
+ timestamp: "2025-04-21T11:45:00Z",
+ reporter: "@dev1:matrix.org",
+ room: "#development:matrix.org",
+ teamId: "team2",
+ subject: {
+ type: "message",
+ id: "$devmessage123456789:matrix.org",
+ sender: "@newuser:matrix.org",
+ content: {
+ msgtype: "m.text",
+ body: "Can someone help me with this code? It's not working: <script>alert('hack');</script>",
+ },
+ timestamp: "2025-04-21T11:40:00Z",
+ },
+ reason: "Potential code injection attempt",
+ },
+ ].filter((report) => report.teamId === selectedTeam.id)
+
+ // Mock data for policy lists - now filtered by team
+ const policyLists = [
+ {
+ id: "global",
+ name: "Global Ban List",
+ description: "Shared ban list for all Matrix communities",
+ readOnly: true,
+ teamId: "team1",
+ entries: [
+ {
+ id: "ban_1",
+ target: "@spammer:badserver.org",
+ reason: "Spam across multiple rooms",
+ timestamp: "2025-04-20T10:30:00Z",
+ },
+ {
+ id: "ban_2",
+ target: "@troll:badserver.org",
+ reason: "Harassment and inappropriate content",
+ timestamp: "2025-04-19T15:45:00Z",
+ },
+ { id: "ban_3", target: "@phisher:scam.org", reason: "Phishing attempts", timestamp: "2025-04-18T09:20:00Z" },
+ ],
+ },
+ {
+ id: "community",
+ name: "Community Ban List",
+ description: "Ban list for our community rooms",
+ readOnly: false,
+ teamId: "team1",
+ entries: [
+ {
+ id: "ban_4",
+ target: "@disruptor:matrix.org",
+ reason: "Disruptive behavior in #general",
+ timestamp: "2025-04-21T08:15:00Z",
+ },
+ { id: "ban_5", target: "@bot123:unknown.org", reason: "Automated spam", timestamp: "2025-04-20T14:30:00Z" },
+ ],
+ },
+ {
+ id: "support",
+ name: "Support Rooms Ban List",
+ description: "Ban list specific to support rooms",
+ readOnly: false,
+ teamId: "team1",
+ entries: [
+ {
+ id: "ban_6",
+ target: "@angryuser:matrix.org",
+ reason: "Abusive language to support staff",
+ timestamp: "2025-04-21T11:45:00Z",
+ },
+ ],
+ },
+ {
+ id: "dev",
+ name: "Development Ban List",
+ description: "Ban list for development rooms",
+ readOnly: false,
+ teamId: "team2",
+ entries: [
+ {
+ id: "ban_7",
+ target: "@spambot:matrix.org",
+ reason: "Code spam in development channels",
+ timestamp: "2025-04-19T09:30:00Z",
+ },
+ ],
+ },
+ ].filter((list) => list.teamId === selectedTeam.id)
+
+ const selectedList = policyLists.find((list) => list.id === selectedPolicyList) || policyLists[0] || { entries: [] }
+
+ const teamNotifications = notifications.filter((n) => n.teamId === selectedTeam.id || !n.teamId)
+ const unreadCount = teamNotifications.filter((n) => !n.read).length
+
+ const handleTeamChange = (teamId: string) => {
+ const team = mockTeams.find((t) => t.id === teamId)
+ if (team) {
+ setSelectedTeam(team)
+ // Reset selected report and policy list when changing teams
+ setSelectedReport(null)
+ if (policyLists.length > 0) {
+ setSelectedPolicyList(policyLists[0].id)
+ }
+ }
+ }
+
+ const markNotificationAsRead = (id: string) => {
+ setNotifications(notifications.map((n) => (n.id === id ? { ...n, read: true } : n)))
+ }
+
+ const clearAllNotifications = () => {
+ setNotifications(notifications.map((n) => ({ ...n, read: true })))
+ }
+
+ const deleteNotification = (id: string) => {
+ setNotifications(notifications.filter((n) => n.id !== id))
+ }
+
+ return (
+ <div className="flex min-h-screen flex-col bg-black text-white">
+ <header className="sticky top-0 z-10 border-b border-gray-800 bg-black/80 backdrop-blur-sm">
+ <div className="container flex h-16 items-center justify-between px-4 md:px-6">
+ <div className="flex items-center gap-4">
+ <Link href="/" className="flex items-center gap-2">
+ <Shield className="h-6 w-6 text-purple-400" />
+ <span className="text-xl font-bold tracking-tighter">Draupnir4All</span>
+ </Link>
+
+ {/* Global Team Selector */}
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="outline" className="border-purple-500 text-purple-400 hover:bg-purple-950">
+ <Crown className="mr-2 h-4 w-4" />
+ {selectedTeam.name}
+ <ChevronDown className="ml-2 h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent className="w-56 bg-gray-900 border-gray-800">
+ <DropdownMenuLabel>Your Bots</DropdownMenuLabel>
+ <DropdownMenuSeparator className="bg-gray-800" />
+ {mockTeams.map((team) => (
+ <DropdownMenuItem
+ key={team.id}
+ className={selectedTeam.id === team.id ? "bg-gray-800" : ""}
+ onClick={() => handleTeamChange(team.id)}
+ >
+ {team.name}
+ </DropdownMenuItem>
+ ))}
+ <DropdownMenuSeparator className="bg-gray-800" />
+ <Dialog>
+ <DialogTrigger asChild>
+ <DropdownMenuItem>
+ <Plus className="mr-2 h-4 w-4" />
+ Create New Bot
+ </DropdownMenuItem>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ <DialogHeader>
+ <DialogTitle>Create New Bot</DialogTitle>
+ <DialogDescription className="text-gray-400">
+ Set up a new Draupnir bot for a different community
+ </DialogDescription>
+ </DialogHeader>
+ <div className="space-y-4 py-4">
+ <div className="space-y-2">
+ <Label htmlFor="bot-name">Bot Name</Label>
+ <Input id="bot-name" placeholder="My Community Bot" className="bg-gray-900 border-gray-800" />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="bot-description">Description</Label>
+ <Input
+ id="bot-description"
+ placeholder="Moderation for my community"
+ className="bg-gray-900 border-gray-800"
+ />
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ >
+ Cancel
+ </Button>
+ <Button className="bg-purple-600 text-white hover:bg-purple-700">Create Bot</Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </div>
+
+ <div className="flex items-center gap-4">
+ {/* Notifications */}
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button variant="ghost" size="icon" className="relative text-gray-400 hover:text-white">
+ <Bell className="h-5 w-5" />
+ {unreadCount > 0 && (
+ <span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-[10px] font-medium text-white">
+ {unreadCount}
+ </span>
+ )}
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-80 p-0 bg-gray-900 border-gray-800">
+ <div className="flex items-center justify-between border-b border-gray-800 p-3">
+ <h3 className="font-medium">Notifications</h3>
+ <div className="flex items-center gap-1">
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-7 px-2 text-xs text-gray-400 hover:text-white"
+ onClick={clearAllNotifications}
+ >
+ Clear all
+ </Button>
+ </div>
+ </div>
+ <ScrollArea className="h-[300px]">
+ {teamNotifications.length > 0 ? (
+ <div className="space-y-1 p-1">
+ {teamNotifications.map((notification) => (
+ <div
+ key={notification.id}
+ className={`flex items-start p-3 rounded-md ${notification.read ? "bg-gray-900" : "bg-gray-800"}`}
+ onClick={() => markNotificationAsRead(notification.id)}
+ >
+ <div
+ className={`rounded-full p-1.5 mr-3 ${notification.type === "report"
+ ? "bg-yellow-500/20"
+ : notification.type === "ban"
+ ? "bg-red-500/20"
+ : "bg-blue-500/20"
+ }`}
+ >
+ {notification.type === "report" ? (
+ <AlertTriangle className="h-3.5 w-3.5 text-yellow-500" />
+ ) : notification.type === "ban" ? (
+ <Ban className="h-3.5 w-3.5 text-red-500" />
+ ) : (
+ <UserPlus className="h-3.5 w-3.5 text-blue-500" />
+ )}
+ </div>
+ <div className="flex-1 min-w-0">
+ <div className="flex items-center justify-between">
+ <p className="text-sm font-medium">{notification.title}</p>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-6 w-6 p-0 text-gray-500 hover:text-gray-300"
+ onClick={(e) => {
+ e.stopPropagation()
+ deleteNotification(notification.id)
+ }}
+ >
+ <X className="h-3 w-3" />
+ <span className="sr-only">Dismiss</span>
+ </Button>
+ </div>
+ <p className="text-xs text-gray-400 line-clamp-2">{notification.message}</p>
+ <p className="text-xs text-gray-500 mt-1">
+ {new Date(notification.timestamp).toLocaleString()}
+ </p>
+ </div>
+ </div>
+ ))}
+ </div>
+ ) : (
+ <div className="flex flex-col items-center justify-center h-full p-6 text-center">
+ <Bell className="h-8 w-8 text-gray-600 mb-2" />
+ <p className="text-gray-400">No notifications</p>
+ </div>
+ )}
+ </ScrollArea>
+ </PopoverContent>
+ </Popover>
+
+ {/* Quick Action Buttons */}
+ <Dialog>
+ <DialogTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ className="hidden md:flex border-red-500 text-red-400 hover:bg-red-950 hover:text-red-300"
+ >
+ <Ban className="mr-2 h-4 w-4" />
+ Ban User
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ <DialogHeader>
+ <DialogTitle>Ban User</DialogTitle>
+ <DialogDescription className="text-gray-400">Ban a user from your Matrix rooms</DialogDescription>
+ </DialogHeader>
+ <div className="space-y-4 py-4">
+ <div className="space-y-2">
+ <Label htmlFor="user-id">Matrix User ID</Label>
+ <Input id="user-id" placeholder="@user:matrix.org" className="bg-gray-900 border-gray-800" />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="ban-reason">Reason</Label>
+ <Textarea
+ id="ban-reason"
+ placeholder="Reason for the ban"
+ className="bg-gray-900 border-gray-800"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label>Policy Lists</Label>
+ <div className="space-y-2 mt-1">
+ {policyLists
+ .filter((list) => !list.readOnly)
+ .map((list) => (
+ <div key={list.id} className="flex items-center space-x-2">
+ <Checkbox id={`list-${list.id}`} />
+ <Label htmlFor={`list-${list.id}`} className="font-normal">
+ {list.name}
+ </Label>
+ </div>
+ ))}
+ </div>
+ </div>
+ <div className="space-y-2">
+ <p className="text-xs text-gray-400">
+ Bans are applied to all protected rooms monitored by this bot.
+ </p>
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ >
+ Cancel
+ </Button>
+ <Button className="bg-red-600 text-white hover:bg-red-700">Ban User</Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+
+ <Dialog>
+ <DialogTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ className="hidden md:flex border-orange-500 text-orange-400 hover:bg-orange-950 hover:text-orange-300"
+ >
+ <UserX className="mr-2 h-4 w-4" />
+ Kick User
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ <DialogHeader>
+ <DialogTitle>Kick User</DialogTitle>
+ <DialogDescription className="text-gray-400">Kick a user from a Matrix room</DialogDescription>
+ </DialogHeader>
+ <div className="space-y-4 py-4">
+ <div className="space-y-2">
+ <Label htmlFor="kick-user-id">Matrix User ID</Label>
+ <Input id="kick-user-id" placeholder="@user:matrix.org" className="bg-gray-900 border-gray-800" />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="room-id">Room</Label>
+ <Select defaultValue="general">
+ <SelectTrigger className="bg-gray-900 border-gray-800">
+ <SelectValue placeholder="Select a room" />
+ </SelectTrigger>
+ <SelectContent className="bg-gray-900 border-gray-800">
+ {selectedTeam.rooms.map((room) => (
+ <SelectItem key={room} value={room.replace("#", "").split(":")[0]}>
+ {room}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="kick-reason">Reason</Label>
+ <Textarea
+ id="kick-reason"
+ placeholder="Reason for the kick"
+ className="bg-gray-900 border-gray-800"
+ />
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ >
+ Cancel
+ </Button>
+ <Button className="bg-orange-600 text-white hover:bg-orange-700">Kick User</Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+
+ <Link href="/">
+ <Button
+ variant="outline"
+ className="hidden md:flex border-purple-500 text-purple-400 hover:bg-purple-950 hover:text-purple-300"
+ >
+ <LogOut className="mr-2 h-4 w-4" />
+ Log Out
+ </Button>
+ </Link>
+ <Sheet>
+ <SheetTrigger asChild>
+ <Button variant="ghost" size="icon" className="md:hidden">
+ <Menu className="h-5 w-5" />
+ </Button>
+ </SheetTrigger>
+ <SheetContent side="right" className="bg-gray-950 border-gray-800 p-0">
+ <div className="flex flex-col h-full">
+ <div className="border-b border-gray-800 p-4">
+ <div className="flex items-center gap-2">
+ <Shield className="h-5 w-5 text-purple-400" />
+ <span className="font-medium">Draupnir4All</span>
+ </div>
+ </div>
+ <div className="p-4">
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="outline"
+ className="w-full border-purple-500 text-purple-400 hover:bg-purple-950"
+ >
+ <Crown className="mr-2 h-4 w-4" />
+ {selectedTeam.name}
+ <ChevronDown className="ml-2 h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent className="w-56 bg-gray-900 border-gray-800">
+ <DropdownMenuLabel>Your Bots</DropdownMenuLabel>
+ <DropdownMenuSeparator className="bg-gray-800" />
+ {mockTeams.map((team) => (
+ <DropdownMenuItem
+ key={team.id}
+ className={selectedTeam.id === team.id ? "bg-gray-800" : ""}
+ onClick={() => handleTeamChange(team.id)}
+ >
+ {team.name}
+ </DropdownMenuItem>
+ ))}
+ <DropdownMenuSeparator className="bg-gray-800" />
+ <DropdownMenuItem>
+ <Plus className="mr-2 h-4 w-4" />
+ Create New Bot
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </div>
+ <nav className="flex flex-col p-4 gap-2">
+ <Link
+ className={`flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors ${activeTab === "overview" ? "bg-gray-900 text-white" : "text-gray-400 hover:text-white"}`}
+ href="#"
+ onClick={() => setActiveTab("overview")}
+ >
+ <Home className="h-4 w-4" />
+ Dashboard
+ </Link>
+
+ <Separator className="my-2" />
+
+ {/* Mobile Quick Actions */}
+ <Dialog>
+ <DialogTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ className="w-full justify-start border-red-500 text-red-400 hover:bg-red-950 hover:text-red-300"
+ >
+ <Ban className="mr-2 h-4 w-4" />
+ Ban User
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ {/* Same content as desktop dialog */}
+ </DialogContent>
+ </Dialog>
+
+ <Dialog>
+ <DialogTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ className="w-full justify-start border-orange-500 text-orange-400 hover:bg-orange-950 hover:text-orange-300"
+ >
+ <UserX className="mr-2 h-4 w-4" />
+ Kick User
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ {/* Same content as desktop dialog */}
+ </DialogContent>
+ </Dialog>
+ </nav>
+ <div className="mt-auto border-t border-gray-800 p-4">
+ <Link href="/">
+ <Button
+ variant="outline"
+ className="w-full border-purple-500 text-purple-400 hover:bg-purple-950 hover:text-purple-300"
+ >
+ <LogOut className="mr-2 h-4 w-4" />
+ Log Out
+ </Button>
+ </Link>
+ </div>
+ </div>
+ </SheetContent>
+ </Sheet>
+ </div>
+ </div>
+ </header>
+ <main className="flex-1 container px-4 py-8 md:px-6 md:py-12">
+ <Tabs defaultValue="overview" value={activeTab} onValueChange={setActiveTab} className="space-y-8">
+ <div className="flex items-center justify-between">
+ <h1 className="text-3xl font-bold tracking-tight">{selectedTeam.name} Dashboard</h1>
+ <TabsList className="bg-gray-900">
+ <TabsTrigger value="overview">Overview</TabsTrigger>
+ <TabsTrigger value="reports">Reports</TabsTrigger>
+ <TabsTrigger value="bans">Bans</TabsTrigger>
+ <TabsTrigger value="settings">Settings</TabsTrigger>
+ </TabsList>
+ </div>
+ <TabsContent value="overview" className="space-y-8">
+ <Overview selectedTeam={selectedTeam} reports={reports} policyLists={policyLists} />
+ </TabsContent>
+ <TabsContent value="reports" className="space-y-4">
+ <Reports selectedReport={selectedReport} reports={reports} setSelectedReport={setSelectedReport} />
+ </TabsContent>
+ <TabsContent value="bans" className="space-y-4">
+ <Bans selectedList={selectedList} selectedPolicyList={selectedPolicyList} policyLists={policyLists} setSelectedPolicyList={setSelectedPolicyList} />
+ </TabsContent>
+ <TabsContent value="settings" className="space-y-4">
+ <TeamManagement selectedTeam={selectedTeam} onTeamChange={handleTeamChange} />
+ </TabsContent>
+ </Tabs>
+ </main>
+ </div>
+ )
+}
diff --git a/src/app/dashboard/team-management.tsx b/src/app/dashboard/team-management.tsx
@@ -0,0 +1,370 @@
+'use client';
+
+import { useState } from "react"
+import { UserPlus, Settings, Crown, Trash2, LogOut } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Badge } from "@/components/ui/badge"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import AddProtectedRoom from "@/components/dashboard/modals/add-protected-room";
+
+export interface Team {
+ id: string;
+ name: string;
+ description: string;
+ owner: string;
+ created: string;
+ members: {
+ id: string;
+ name: string;
+ role: "owner" | "moderator";
+ avatar?: string;
+ joined: string;
+ }[];
+ rooms: string[];
+}
+
+// Mock data for teams/bots
+export const mockTeams: Team[] = [
+ {
+ id: "team1",
+ name: "Main Community Bot",
+ description: "Moderation for main community rooms",
+ owner: "@admin:matrix.org",
+ created: "2025-01-15T10:00:00Z",
+ members: [
+ {
+ id: "user1",
+ name: "@admin:matrix.org",
+ role: "owner",
+ avatar: undefined,
+ joined: "2025-01-15T10:00:00Z",
+ },
+ {
+ id: "user2",
+ name: "@mod1:matrix.org",
+ role: "moderator",
+ avatar: undefined,
+ joined: "2025-01-16T14:30:00Z",
+ },
+ {
+ id: "user3",
+ name: "@mod2:matrix.org",
+ role: "moderator",
+ avatar: undefined,
+ joined: "2025-02-01T09:15:00Z",
+ },
+ ],
+ rooms: ["#general:matrix.org", "#support:matrix.org", "#community:matrix.org"],
+ },
+ {
+ id: "team2",
+ name: "Development Team Bot",
+ description: "Moderation for development-related rooms",
+ owner: "@admin:matrix.org",
+ created: "2025-02-10T11:30:00Z",
+ members: [
+ {
+ id: "user1",
+ name: "@admin:matrix.org",
+ role: "owner",
+ avatar: undefined,
+ joined: "2025-02-10T11:30:00Z",
+ },
+ {
+ id: "user4",
+ name: "@dev1:matrix.org",
+ role: "moderator",
+ avatar: undefined,
+ joined: "2025-02-11T13:45:00Z",
+ },
+ ],
+ rooms: ["#development:matrix.org", "#coding:matrix.org"],
+ },
+]
+
+interface TeamManagementProps {
+ selectedTeam: Team;
+ onTeamChange: (teamId: string) => void;
+}
+
+export function TeamManagement({ selectedTeam: selectedTeamProp, onTeamChange }: TeamManagementProps) {
+ const [activeTab, setActiveTab] = useState("members")
+ const [selectedTeam, setSelectedTeam] = useState<Team>(selectedTeamProp)
+ const [inviteEmail, setInviteEmail] = useState("")
+
+ const handleTeamChange = (teamId: string) => {
+ const team = mockTeams.find((t) => t.id === teamId)
+ if (team) {
+ setSelectedTeam(team)
+ onTeamChange(team.id);
+ }
+ }
+
+ const handleInvite = () => {
+ // In a real app, this would send an invitation
+ console.log(`Inviting ${inviteEmail} to team ${selectedTeam.id}`)
+ setInviteEmail("")
+ }
+
+ return (
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <div className="flex items-center justify-between">
+ <div>
+ <CardTitle>Team Management</CardTitle>
+ <CardDescription className="text-gray-400">Manage your bots and team members</CardDescription>
+ </div>
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="outline" className="border-purple-500 text-purple-400 hover:bg-purple-950">
+ <Crown className="mr-2 h-4 w-4" />
+ {selectedTeam.name}
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent className="w-56 bg-gray-900 border-gray-800">
+ <DropdownMenuLabel>Your Bots</DropdownMenuLabel>
+ <DropdownMenuSeparator className="bg-gray-800" />
+ {mockTeams.map((team) => (
+ <DropdownMenuItem
+ key={team.id}
+ className={selectedTeam.id === team.id ? "bg-gray-800" : ""}
+ onClick={() => handleTeamChange(team.id)}
+ >
+ {team.name}
+ </DropdownMenuItem>
+ ))}
+ <DropdownMenuSeparator className="bg-gray-800" />
+ <Dialog>
+ <DialogTrigger asChild>
+ <DropdownMenuItem>
+ <Settings className="mr-2 h-4 w-4" />
+ Create New Bot
+ </DropdownMenuItem>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ <DialogHeader>
+ <DialogTitle>Create New Bot</DialogTitle>
+ <DialogDescription className="text-gray-400">
+ Set up a new Draupnir bot for a different community
+ </DialogDescription>
+ </DialogHeader>
+ <div className="space-y-4 py-4">
+ <div className="space-y-2">
+ <Label htmlFor="bot-name">Bot Name</Label>
+ <Input id="bot-name" placeholder="My Community Bot" className="bg-gray-900 border-gray-800" />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="bot-description">Description</Label>
+ <Input
+ id="bot-description"
+ placeholder="Moderation for my community"
+ className="bg-gray-900 border-gray-800"
+ />
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ >
+ Cancel
+ </Button>
+ <Button className="bg-purple-600 text-white hover:bg-purple-700">Create Bot</Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ </div>
+ </CardHeader>
+ <CardContent>
+ <Tabs defaultValue="members" value={activeTab} onValueChange={setActiveTab} className="space-y-4">
+ <TabsList className="bg-gray-900">
+ <TabsTrigger value="members">Team Members</TabsTrigger>
+ <TabsTrigger value="rooms">Protected Rooms</TabsTrigger>
+ <TabsTrigger value="settings">Bot Settings</TabsTrigger>
+ </TabsList>
+
+ <TabsContent value="members" className="space-y-4">
+ <div className="flex items-center justify-between">
+ <h3 className="text-sm font-medium text-gray-400">Team Members</h3>
+ <Dialog>
+ <DialogTrigger asChild>
+ <Button size="sm" className="bg-purple-600 text-white hover:bg-purple-700">
+ <UserPlus className="mr-2 h-4 w-4" />
+ Invite Member
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ <DialogHeader>
+ <DialogTitle>Invite Team Member</DialogTitle>
+ <DialogDescription className="text-gray-400">
+ Invite a Matrix user to join your moderation team
+ </DialogDescription>
+ </DialogHeader>
+ <div className="space-y-4 py-4">
+ <div className="space-y-2">
+ <Label htmlFor="invite-matrix-id">Matrix ID</Label>
+ <Input
+ id="invite-matrix-id"
+ placeholder="@user:matrix.org"
+ className="bg-gray-900 border-gray-800"
+ value={inviteEmail}
+ onChange={(e) => setInviteEmail(e.target.value)}
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="invite-role">Role</Label>
+ <select
+ id="invite-role"
+ className="w-full rounded-md border border-gray-800 bg-gray-900 px-3 py-2 text-sm"
+ >
+ <option value="moderator">Moderator</option>
+ <option value="viewer">Viewer (Read-only)</option>
+ </select>
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ >
+ Cancel
+ </Button>
+ <Button className="bg-purple-600 text-white hover:bg-purple-700" onClick={handleInvite}>
+ Send Invitation
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ </div>
+
+ <ScrollArea className="h-[300px] rounded-md border border-gray-800">
+ <div className="p-4 space-y-3">
+ {selectedTeam.members.map((member) => (
+ <div key={member.id} className="flex items-center justify-between p-3 rounded-md bg-gray-900">
+ <div className="flex items-center gap-3">
+ <Avatar className="h-10 w-10 border border-gray-800">
+ <AvatarImage src={member.avatar || ""} />
+ <AvatarFallback className="bg-gray-800 text-gray-400">
+ {member.name.substring(1, 3).toUpperCase()}
+ </AvatarFallback>
+ </Avatar>
+ <div>
+ <p className="font-medium">{member.name}</p>
+ <div className="flex items-center gap-2">
+ <Badge
+ className={
+ member.role === "owner"
+ ? "bg-purple-900/50 text-purple-300"
+ : "bg-blue-900/50 text-blue-300"
+ }
+ >
+ {member.role}
+ </Badge>
+ <span className="text-xs text-gray-500">
+ Joined {new Date(member.joined).toLocaleDateString()}
+ </span>
+ </div>
+ </div>
+ </div>
+ {member.role !== "owner" && (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" size="sm" className="h-8 w-8 p-0">
+ <span className="sr-only">Open menu</span>
+ <Settings className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end" className="bg-gray-900 border-gray-800">
+ <DropdownMenuItem className="text-yellow-400 focus:text-yellow-400 focus:bg-yellow-900/20">
+ <Settings className="mr-2 h-4 w-4" />
+ Change Role
+ </DropdownMenuItem>
+ <DropdownMenuItem className="text-red-400 focus:text-red-400 focus:bg-red-900/20">
+ <LogOut className="mr-2 h-4 w-4" />
+ Remove from Team
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ )}
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </TabsContent>
+
+ <TabsContent value="rooms" className="space-y-4">
+ <div className="flex items-center justify-between">
+ <h3 className="text-sm font-medium text-gray-400">Protected Rooms</h3>
+ <AddProtectedRoom filled />
+ </div>
+
+ <ScrollArea className="h-[300px] rounded-md border border-gray-800">
+ <div className="p-4 space-y-3">
+ {selectedTeam.rooms.map((room, index) => (
+ <div key={index} className="flex items-center justify-between p-3 px-4 rounded-md bg-gray-900">
+ <div>
+ <p>{room}</p>
+ </div>
+ <Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-gray-400 hover:text-red-400">
+ <Trash2 className="h-4 w-4" />
+ <span className="sr-only">Remove</span>
+ </Button>
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </TabsContent>
+
+ <TabsContent value="settings" className="space-y-4">
+ <div className="space-y-4">
+ <div className="space-y-2">
+ <Label htmlFor="bot-name">Bot Name</Label>
+ <Input id="bot-name" defaultValue={selectedTeam.name} className="bg-gray-900 border-gray-800" />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="bot-description">Description</Label>
+ <Input
+ id="bot-description"
+ defaultValue={selectedTeam.description}
+ className="bg-gray-900 border-gray-800"
+ />
+ </div>
+ <div className="pt-4 flex justify-between">
+ <Button variant="outline" className="border-red-500 text-red-400 hover:bg-red-950 hover:text-red-300">
+ <Trash2 className="mr-2 h-4 w-4" />
+ Delete Bot
+ </Button>
+ <Button className="bg-purple-600 text-white hover:bg-purple-700">Save Changes</Button>
+ </div>
+ </div>
+ </TabsContent>
+ </Tabs>
+ </CardContent>
+ </Card>
+ )
+}
diff --git a/src/app/globals.css b/src/app/globals.css
@@ -0,0 +1,94 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+body {
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+@layer utilities {
+ .text-balance {
+ text-wrap: balance;
+ }
+}
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ --sidebar-background: 0 0% 98%;
+ --sidebar-foreground: 240 5.3% 26.1%;
+ --sidebar-primary: 240 5.9% 10%;
+ --sidebar-primary-foreground: 0 0% 98%;
+ --sidebar-accent: 240 4.8% 95.9%;
+ --sidebar-accent-foreground: 240 5.9% 10%;
+ --sidebar-border: 220 13% 91%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
+ }
+ .dark {
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ --sidebar-background: 240 5.9% 10%;
+ --sidebar-foreground: 240 4.8% 95.9%;
+ --sidebar-primary: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 240 3.7% 15.9%;
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
+ --sidebar-border: 240 3.7% 15.9%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
@@ -1,35 +1,27 @@
-import type { Metadata } from "next";
-import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
-import { Roboto } from 'next/font/google';
-import { ThemeProvider } from '@mui/material/styles';
-import theme from '../theme';
+import type React from "react"
+import "./globals.css"
+import { Inter } from "next/font/google"
+import { ThemeProvider } from "@/components/theme-provider"
-const roboto = Roboto({
- weight: ['300', '400', '500', '700'],
- subsets: ['latin'],
- display: 'swap',
- variable: '--font-roboto',
-});
+const inter = Inter({ subsets: ["latin"] })
-export const metadata: Metadata = {
- title: "Draupnir4All Admin",
- description: "Admin installation for Draupnir4All",
-};
+export const metadata = {
+ title: "Matrix Moderator - Secure Moderation for Matrix.org",
+ description: "Powerful moderation tools for Matrix.org communities using Draupnir bots.",
+}
export default function RootLayout({
children,
}: Readonly<{
- children: React.ReactNode;
+ children: React.ReactNode
}>) {
return (
- <html lang="en" className={roboto.variable}>
- <body style={{ margin: 0 }}>
- <AppRouterCacheProvider>
- <ThemeProvider theme={theme}>
- {children}
- </ThemeProvider>
- </AppRouterCacheProvider>
+ <html lang="en" className="dark" style={{ colorScheme: "dark" }}>
+ <body className={inter.className}>
+ <ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
+ {children}
+ </ThemeProvider>
</body>
- </html>
- );
+ </html >
+ )
}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
@@ -1,128 +1,139 @@
-'use client';
+"use client"
-import React, { useState } from "react";
-import {
- Box,
- Button,
- TextField,
- Typography,
- Grid,
- Paper,
-} from "@mui/material";
+import { useState } from "react"
+import { useRouter } from "next/navigation"
+import Link from "next/link"
+import { Shield, ArrowRight, Loader2 } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { MatrixLoginForm } from "@/components/matrix-login-form"
export default function LoginPage() {
- const [homeserver, setHomeserver] = useState("");
- const [isValidServer, setIsValidServer] = useState(false);
- const [showSSO, setShowSSO] = useState(false);
+ const router = useRouter()
+ const [matrixId, setMatrixId] = useState("")
+ const [step, setStep] = useState<"initial" | "login" | "verifying" | "error">("initial")
+ const [errorMessage, setErrorMessage] = useState("")
+
+ const startMatrixAuth = () => {
+ if (!matrixId) return
+ setStep("login")
+ }
+
+ const handleLoginSuccess = async (token: string) => {
+ setStep("verifying")
+
+ try {
+ // In a real implementation, send the token to your backend for verification
+ // const response = await fetch('/api/verify-matrix-token', {
+ // method: 'POST',
+ // headers: { 'Content-Type': 'application/json' },
+ // body: JSON.stringify({ matrixId, token })
+ // })
+ // const data = await response.json()
+ // if (!data.success) throw new Error(data.error || 'Verification failed')
+
+ // For demo purposes, simulate a successful verification
+ await new Promise((resolve) => setTimeout(resolve, 1500))
+
+ // Redirect to dashboard after successful login
+ router.push("/dashboard")
+ } catch (error) {
+ console.error("Token verification failed", error)
+ setErrorMessage(error instanceof Error ? error.message : "Verification failed")
+ setStep("error")
+ }
+ }
- const handleCheckServer = () => {
- // Simulate client-side validation of the homeserver
- if (homeserver.startsWith("https://")) {
- // Example logic: If the domain contains "sso", show SSO login
- setShowSSO(homeserver.includes("sso"));
- setIsValidServer(true);
- } else {
- alert("Please enter a valid URL starting with https://");
- }
- };
+ return (
+ <div className="flex min-h-screen flex-col bg-black text-white">
+ <header className="border-b border-gray-800 bg-black py-4">
+ <div className="container flex items-center justify-between px-4 md:px-6">
+ <Link href="/" className="flex items-center gap-2">
+ <Shield className="h-6 w-6 text-purple-400" />
+ <span className="text-xl font-bold tracking-tighter">Draupnir4All</span>
+ </Link>
+ </div>
+ </header>
+ <main className="flex-1 flex items-center justify-center p-4">
+ <Card className="w-full max-w-md border-gray-800 bg-gray-950">
+ <CardHeader>
+ <CardTitle className="text-2xl">Log in with Matrix</CardTitle>
+ <CardDescription className="text-gray-400">Access your Draupnir moderation dashboard</CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ {step === "initial" && (
+ <div className="space-y-4">
+ <div className="space-y-2">
+ <Label htmlFor="matrix-id">Matrix ID</Label>
+ <Input
+ id="matrix-id"
+ placeholder="@username:matrix.org"
+ className="bg-gray-900 border-gray-800"
+ value={matrixId}
+ onChange={(e) => setMatrixId(e.target.value)}
+ />
+ <p className="text-xs text-gray-400">Enter your Matrix ID to begin the authentication process</p>
+ </div>
+ </div>
+ )}
- const handleSSOLogin = () => {
- alert("Redirecting to SSO login...");
- };
+ {step === "login" && (
+ <MatrixLoginForm matrixId={matrixId} onSuccess={handleLoginSuccess} onCancel={() => setStep("initial")} />
+ )}
- const handleLogin = (event: React.FormEvent) => {
- event.preventDefault();
- alert("Logging in with username and password...");
- };
+ {step === "verifying" && (
+ <div className="flex flex-col items-center justify-center py-6 space-y-4">
+ <Loader2 className="h-8 w-8 animate-spin text-purple-400" />
+ <p className="text-gray-300">Logging in...</p>
+ <p className="text-xs text-gray-400">Accessing your Draupnir4All account</p>
+ </div>
+ )}
- return (
- <Grid container sx={{ height: "100vh" }}>
- {/* Left side with image */}
- <Grid
- size={{ xs: 12, md: 9 }}
- sx={{
- backgroundImage: "url('https://via.placeholder.com/800')",
- backgroundSize: "cover",
- backgroundPosition: "center",
- }}
- />
+ {step === "error" && (
+ <div className="rounded-md bg-red-900/20 border border-red-800 p-4 text-center">
+ <p className="text-red-300 font-medium">Login Failed</p>
+ <p className="text-sm text-red-200 mt-1">{errorMessage}</p>
+ <Button
+ variant="outline"
+ className="mt-4 border-red-500 text-red-400 hover:bg-red-950 hover:text-red-300"
+ onClick={() => setStep("initial")}
+ >
+ Try Again
+ </Button>
+ </div>
+ )}
+ </CardContent>
+ <CardFooter className="flex flex-col space-y-4">
+ {step === "initial" && (
+ <>
+ <Button
+ className="w-full bg-purple-600 text-white hover:bg-purple-700"
+ onClick={startMatrixAuth}
+ disabled={!matrixId}
+ >
+ Continue
+ <ArrowRight className="ml-2 h-4 w-4" />
+ </Button>
+ <p className="text-center text-sm text-gray-400">
+ Don't have a Draupnir bot yet?{" "}
+ <Link href="/register" className="text-purple-400 hover:text-purple-300">
+ Register here
+ </Link>
+ </p>
+ </>
+ )}
- {/* Right side with login form */}
- <Grid
- size={{ xs: 12, md: 3 }}
- component={Paper}
- elevation={6}
- sx={{
- display: "grid",
- placeItems: "center",
- boxShadow: "-4px 0px 10px rgba(0, 0, 0, 0.1)",
- }}
- >
- <Box>
- <Typography component="h1" variant="h5" align="center">
- Login
- </Typography>
- {!isValidServer ? (
- <Box sx={{ mt: 2, mx: 2 }}>
- <TextField
- label="Homeserver Address"
- variant="outlined"
- fullWidth
- value={homeserver}
- onChange={(e) => setHomeserver(e.target.value)}
- sx={{ mb: 2 }}
- />
- <Button
- variant="contained"
- color="primary"
- fullWidth
- onClick={handleCheckServer}
- >
- Next
- </Button>
- </Box>
- ) : showSSO ? (
- <Box sx={{ mt: 2, mx: 2 }}>
- <Button
- variant="contained"
- color="primary"
- fullWidth
- onClick={handleSSOLogin}
- >
- Login with SSO
- </Button>
- </Box>
- ) : (
- <Box
- component="form"
- onSubmit={handleLogin}
- sx={{ mt: 2, mx: 2 }}
- >
- <TextField
- label="Username"
- variant="outlined"
- fullWidth
- sx={{ mb: 2 }}
- />
- <TextField
- label="Password"
- type="password"
- variant="outlined"
- fullWidth
- sx={{ mb: 2 }}
- />
- <Button
- type="submit"
- variant="contained"
- color="primary"
- fullWidth
- >
- Login
- </Button>
- </Box>
- )}
- </Box>
- </Grid>
- </Grid>
- );
-}
-\ No newline at end of file
+ {step === "verifying" && (
+ <Button className="w-full" disabled>
+ Please wait...
+ </Button>
+ )}
+ </CardFooter>
+ </Card>
+ </main>
+ </div>
+ )
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
@@ -1,7 +1,314 @@
-export const dynamicParams = true
+import Link from "next/link"
+import { Shield, Users, BarChart, Settings, AlertTriangle, CheckCircle, Ban, Eye, LogIn } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
export default function Home() {
return (
- <></>
- );
+ <div className="flex min-h-screen flex-col bg-black text-white">
+ <header className="sticky top-0 z-10 border-b border-gray-800 bg-black/80 backdrop-blur-sm">
+ <div className="container flex h-16 items-center justify-between px-4 md:px-6">
+ <div className="flex items-center gap-2">
+ <Shield className="h-6 w-6 text-purple-400" />
+ <span className="text-xl font-bold tracking-tighter">Draupnir4All</span>
+ </div>
+ <nav className="hidden md:flex gap-6">
+ <Link className="text-sm font-medium text-gray-400 transition-colors hover:text-white" href="#">
+ Home
+ </Link>
+ <Link className="text-sm font-medium text-gray-400 transition-colors hover:text-white" href="#">
+ Features
+ </Link>
+ <Link className="text-sm font-medium text-gray-400 transition-colors hover:text-white" href="#">
+ Documentation
+ </Link>
+ <Link className="text-sm font-medium text-gray-400 transition-colors hover:text-white" href="#">
+ Support
+ </Link>
+ </nav>
+ <div className="flex items-center gap-4">
+ <Link href="/login">
+ <Button
+ variant="outline"
+ className="hidden md:flex border-purple-500 text-purple-400 hover:bg-purple-950 hover:text-purple-300"
+ >
+ <LogIn className="mr-2 h-4 w-4" />
+ Log In
+ </Button>
+ </Link>
+ <Link href="/register">
+ <Button
+ className="bg-purple-600 text-white hover:bg-purple-700"
+ >
+ Register
+ </Button>
+ </Link>
+ </div>
+ </div>
+ </header>
+ <main className="flex-1">
+ <section className="relative py-24 md:py-32">
+ <div className="absolute inset-0 -z-10 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-purple-900/20 via-black to-black"></div>
+ <div className="container px-4 md:px-6">
+ <div className="grid gap-6 lg:grid-cols-[1fr_400px] lg:gap-12 xl:grid-cols-[1fr_600px]">
+ <div className="flex flex-col justify-center space-y-4">
+ <div className="space-y-2">
+ <h1 className="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none">Draupnir4All</h1>
+ <p className="max-w-[600px] text-gray-300 md:text-xl">
+ Self-service moderation for Matrix.org communities. Deploy your own Draupnir bot instance and
+ protect your spaces with advanced filtering, reporting, and management.
+ </p>
+ </div>
+ <div className="flex flex-col gap-2 min-[400px]:flex-row">
+ <Link href="/register">
+ <Button
+ className="bg-purple-600 text-white hover:bg-purple-700"
+ >
+ Register with Matrix
+ </Button>
+ </Link>
+ <Button
+ variant="outline"
+ className="border-purple-500 text-purple-400 hover:bg-purple-950 hover:text-purple-300"
+ >
+ View Documentation
+ </Button>
+ </div>
+ </div>
+ <div className="flex items-center justify-center">
+ <div className="relative w-full max-w-[500px] overflow-hidden rounded-lg border border-gray-800 bg-black p-2">
+ <div className="flex items-center justify-between border-b border-gray-800 p-2">
+ <div className="flex items-center gap-2">
+ <Shield className="h-5 w-5 text-purple-400" />
+ <span className="text-sm font-medium">Draupnir Moderation</span>
+ </div>
+ <div className="flex items-center gap-1">
+ <div className="h-2 w-2 rounded-full bg-red-500"></div>
+ <div className="h-2 w-2 rounded-full bg-yellow-500"></div>
+ <div className="h-2 w-2 rounded-full bg-green-500"></div>
+ </div>
+ </div>
+ <div className="space-y-2 p-4">
+ <div className="flex items-center gap-2 rounded-md bg-gray-900 p-2">
+ <AlertTriangle className="h-4 w-4 text-yellow-500" />
+ <span className="text-xs">Spam detected in #general - Action required</span>
+ </div>
+ <div className="flex items-center gap-2 rounded-md bg-gray-900 p-2">
+ <Ban className="h-4 w-4 text-red-500" />
+ <span className="text-xs">User @spammer:matrix.org banned from 3 rooms</span>
+ </div>
+ <div className="flex items-center gap-2 rounded-md bg-gray-900 p-2">
+ <CheckCircle className="h-4 w-4 text-green-500" />
+ <span className="text-xs">Moderation report resolved by @admin:matrix.org</span>
+ </div>
+ <div className="flex items-center gap-2 rounded-md bg-gray-900 p-2">
+ <Eye className="h-4 w-4 text-blue-500" />
+ <span className="text-xs">Monitoring 12 active rooms - All systems normal</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <section className="bg-black py-12 md:py-16 lg:py-20">
+ <div className="container px-4 md:px-6">
+ <div className="flex flex-col items-center justify-center space-y-4 text-center">
+ <div className="space-y-2">
+ <h2 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl">How It Works</h2>
+ <p className="mx-auto max-w-[700px] text-gray-300 md:text-xl">
+ Self-service provisioning for Draupnir moderation bots
+ </p>
+ </div>
+ <div className="grid gap-8 md:grid-cols-3 w-full max-w-4xl mt-8">
+ <div className="flex flex-col items-center text-center">
+ <div className="flex h-12 w-12 items-center justify-center rounded-full bg-purple-900 mb-4">
+ <LogIn className="h-6 w-6 text-purple-300" />
+ </div>
+ <h3 className="text-xl font-bold">1. Authenticate</h3>
+ <p className="text-gray-400 mt-2">Verify your identity with your Matrix account</p>
+ </div>
+ <div className="flex flex-col items-center text-center">
+ <div className="flex h-12 w-12 items-center justify-center rounded-full bg-purple-900 mb-4">
+ <Settings className="h-6 w-6 text-purple-300" />
+ </div>
+ <h3 className="text-xl font-bold">2. Configure</h3>
+ <p className="text-gray-400 mt-2">Set up your Draupnir bot with custom rules and policies</p>
+ </div>
+ <div className="flex flex-col items-center text-center">
+ <div className="flex h-12 w-12 items-center justify-center rounded-full bg-purple-900 mb-4">
+ <Shield className="h-6 w-6 text-purple-300" />
+ </div>
+ <h3 className="text-xl font-bold">3. Moderate</h3>
+ <p className="text-gray-400 mt-2">Access your moderation dashboard to manage your Matrix spaces</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <section className="bg-gray-950 py-12 md:py-16 lg:py-20">
+ <div className="container px-4 md:px-6">
+ <div className="flex flex-col items-center justify-center space-y-4 text-center mb-10">
+ <div className="space-y-2">
+ <h2 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl">Draupnir Features</h2>
+ <p className="mx-auto max-w-[700px] text-gray-300 md:text-xl">
+ Powerful moderation tools designed specifically for Matrix.org communities
+ </p>
+ </div>
+ </div>
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
+ <Card className="border-gray-800 bg-black">
+ <CardHeader>
+ <div className="flex items-center gap-2">
+ <Ban className="h-5 w-5 text-red-500" />
+ <CardTitle>Ban Management</CardTitle>
+ </div>
+ <CardDescription className="text-gray-400">
+ Efficiently manage bans across multiple rooms
+ </CardDescription>
+ </CardHeader>
+ <CardContent>
+ <p className="text-sm text-gray-300">
+ Create and manage ban lists that can be shared across rooms and servers. Ban by user ID, server
+ name, or room patterns with powerful filtering options.
+ </p>
+ </CardContent>
+ </Card>
+ <Card className="border-gray-800 bg-black">
+ <CardHeader>
+ <div className="flex items-center gap-2">
+ <AlertTriangle className="h-5 w-5 text-yellow-500" />
+ <CardTitle>Abuse Detection</CardTitle>
+ </div>
+ <CardDescription className="text-gray-400">Automatically detect and respond to abuse</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <p className="text-sm text-gray-300">
+ Identify spam, harassment, and other abusive behavior with advanced pattern matching and machine
+ learning algorithms. Set automated responses based on severity.
+ </p>
+ </CardContent>
+ </Card>
+ <Card className="border-gray-800 bg-black">
+ <CardHeader>
+ <div className="flex items-center gap-2">
+ <Users className="h-5 w-5 text-blue-500" />
+ <CardTitle>Distributed Moderation</CardTitle>
+ </div>
+ <CardDescription className="text-gray-400">Collaborate with trusted moderators</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <p className="text-sm text-gray-300">
+ Create policy rooms where trusted moderators can collaborate on moderation decisions. Share ban
+ lists and coordinate responses to abuse across communities.
+ </p>
+ </CardContent>
+ </Card>
+ <Card className="border-gray-800 bg-black">
+ <CardHeader>
+ <div className="flex items-center gap-2">
+ <BarChart className="h-5 w-5 text-green-500" />
+ <CardTitle>Analytics Dashboard</CardTitle>
+ </div>
+ <CardDescription className="text-gray-400">Monitor moderation activity and trends</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <p className="text-sm text-gray-300">
+ Track moderation actions, abuse patterns, and community health metrics. Generate reports and
+ identify trends to improve your moderation strategy.
+ </p>
+ </CardContent>
+ </Card>
+ <Card className="border-gray-800 bg-black">
+ <CardHeader>
+ <div className="flex items-center gap-2">
+ <Settings className="h-5 w-5 text-purple-500" />
+ <CardTitle>Customizable Rules</CardTitle>
+ </div>
+ <CardDescription className="text-gray-400">Create rules tailored to your community</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <p className="text-sm text-gray-300">
+ Define custom moderation rules and policies specific to your community's needs. Set up automated
+ actions based on rule violations and severity levels.
+ </p>
+ </CardContent>
+ </Card>
+ <Card className="border-gray-800 bg-black">
+ <CardHeader>
+ <div className="flex items-center gap-2">
+ <Eye className="h-5 w-5 text-orange-500" />
+ <CardTitle>Monitoring Tools</CardTitle>
+ </div>
+ <CardDescription className="text-gray-400">Keep an eye on your Matrix spaces</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <p className="text-sm text-gray-300">
+ Monitor room activity, user behavior, and potential threats in real-time. Receive alerts and
+ notifications for suspicious activity requiring attention.
+ </p>
+ </CardContent>
+ </Card>
+ </div>
+ </div>
+ </section>
+ <section className="bg-black py-12 md:py-16 lg:py-20">
+ <div className="container px-4 md:px-6">
+ <div className="flex flex-col items-center justify-center space-y-4 text-center">
+ <div className="space-y-2">
+ <h2 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl">
+ Ready to Secure Your Matrix Community?
+ </h2>
+ <p className="mx-auto max-w-[700px] text-gray-300 md:text-xl">
+ Register with your Matrix account and deploy your Draupnir bot in minutes
+ </p>
+ </div>
+ <div className="flex flex-col gap-2 min-[400px]:flex-row">
+ <Link href="/register">
+ <Button
+ className="bg-purple-600 text-white hover:bg-purple-700"
+ >
+ Register with Matrix
+ </Button>
+ </Link>
+ <Link href="/login">
+ <Button
+ variant="outline"
+ className="border-purple-500 text-purple-400 hover:bg-purple-950 hover:text-purple-300"
+ >
+ Log In
+ </Button>
+ </Link>
+ </div>
+ </div>
+ </div>
+ </section>
+ </main>
+ <footer className="border-t border-gray-800 bg-black py-6 md:py-8">
+ <div className="container flex flex-col items-center justify-between gap-4 px-4 md:flex-row md:px-6">
+ <div className="flex items-center gap-2">
+ <Shield className="h-5 w-5 text-purple-400" />
+ <span className="text-sm font-medium">Draupnir4All</span>
+ </div>
+ <p className="text-center text-sm text-gray-500 md:text-left">
+ © 2025 Draupnir4All. All rights reserved. Built for the Matrix.org ecosystem.
+ </p>
+ <div className="flex gap-4">
+ <Link className="text-sm font-medium text-gray-500 transition-colors hover:text-white" href="#">
+ Privacy
+ </Link>
+ <Link className="text-sm font-medium text-gray-500 transition-colors hover:text-white" href="#">
+ Terms
+ </Link>
+ <Link className="text-sm font-medium text-gray-500 transition-colors hover:text-white" href="#">
+ Contact
+ </Link>
+ </div>
+ </div>
+ </footer>
+ </div>
+ )
}
diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx
@@ -0,0 +1,133 @@
+"use client"
+
+import { useState } from "react"
+import { useRouter } from "next/navigation"
+import Link from "next/link"
+import { Shield, ArrowRight, Loader2 } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { MatrixLoginForm } from "@/components/matrix-login-form"
+
+export default function RegisterPage() {
+ const router = useRouter()
+ const [matrixId, setMatrixId] = useState("")
+ const [step, setStep] = useState<"initial" | "login" | "verifying" | "error">("initial")
+ const [errorMessage, setErrorMessage] = useState("")
+
+ const startMatrixAuth = () => {
+ if (!matrixId) return
+ setStep("login")
+ }
+
+ const handleLoginSuccess = async (token: string) => {
+ setStep("verifying")
+
+ try {
+ // In a real implementation, send the token to your backend for verification
+ // const response = await fetch('/api/verify-matrix-token', {
+ // method: 'POST',
+ // headers: { 'Content-Type': 'application/json' },
+ // body: JSON.stringify({ matrixId, token })
+ // })
+ // const data = await response.json()
+ // if (!data.success) throw new Error(data.error || 'Verification failed')
+
+ // For demo purposes, simulate a successful verification
+ await new Promise((resolve) => setTimeout(resolve, 1500))
+
+ // Redirect to dashboard after successful registration
+ router.push("/dashboard")
+ } catch (error) {
+ console.error("Token verification failed", error)
+ setErrorMessage(error instanceof Error ? error.message : "Verification failed")
+ setStep("error")
+ }
+ }
+
+ return (
+ <div className="flex min-h-screen flex-col bg-black text-white">
+ <header className="border-b border-gray-800 bg-black py-4">
+ <div className="container flex items-center justify-between px-4 md:px-6">
+ <Link href="/" className="flex items-center gap-2">
+ <Shield className="h-6 w-6 text-purple-400" />
+ <span className="text-xl font-bold tracking-tighter">Draupnir4All</span>
+ </Link>
+ </div>
+ </header>
+ <main className="flex-1 flex items-center justify-center p-4">
+ <Card className="w-full max-w-md border-gray-800 bg-gray-950">
+ <CardHeader>
+ <CardTitle className="text-2xl">Register with Matrix</CardTitle>
+ <CardDescription className="text-gray-400">
+ Use your Matrix account to register for Draupnir4All
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ {step === "initial" && (
+ <div className="space-y-4">
+ <div className="space-y-2">
+ <Label htmlFor="matrix-id">Matrix ID</Label>
+ <Input
+ id="matrix-id"
+ placeholder="@username:matrix.org"
+ className="bg-gray-900 border-gray-800"
+ value={matrixId}
+ onChange={(e) => setMatrixId(e.target.value)}
+ />
+ <p className="text-xs text-gray-400">Enter your Matrix ID to begin the authentication process</p>
+ </div>
+ </div>
+ )}
+
+ {step === "login" && (
+ <MatrixLoginForm matrixId={matrixId} onSuccess={handleLoginSuccess} onCancel={() => setStep("initial")} />
+ )}
+
+ {step === "verifying" && (
+ <div className="flex flex-col items-center justify-center py-6 space-y-4">
+ <Loader2 className="h-8 w-8 animate-spin text-purple-400" />
+ <p className="text-gray-300">Registering your account...</p>
+ <p className="text-xs text-gray-400">Setting up your Draupnir4All account</p>
+ </div>
+ )}
+
+ {step === "error" && (
+ <div className="rounded-md bg-red-900/20 border border-red-800 p-4 text-center">
+ <p className="text-red-300 font-medium">Registration Failed</p>
+ <p className="text-sm text-red-200 mt-1">{errorMessage}</p>
+ <Button
+ variant="outline"
+ className="mt-4 border-red-500 text-red-400 hover:bg-red-950 hover:text-red-300"
+ onClick={() => setStep("initial")}
+ >
+ Try Again
+ </Button>
+ </div>
+ )}
+ </CardContent>
+ <CardFooter>
+ {step === "initial" && (
+ <Button
+ className="w-full bg-purple-600 text-white hover:bg-purple-700"
+ onClick={startMatrixAuth}
+ disabled={!matrixId}
+ >
+ Continue
+ <ArrowRight className="ml-2 h-4 w-4" />
+ </Button>
+ )}
+
+ {step === "verifying" && (
+ <Button className="w-full" disabled>
+ Please wait...
+ </Button>
+ )}
+ </CardFooter>
+ </Card>
+ </main>
+ </div>
+ )
+}
diff --git a/src/components/blurred-image.tsx b/src/components/blurred-image.tsx
@@ -0,0 +1,34 @@
+"use client"
+
+import { useState } from "react"
+import Image from "next/image"
+import { Eye, EyeOff } from "lucide-react"
+import { Button } from "@/components/ui/button"
+
+interface BlurredImageProps {
+ src: string
+ alt: string
+ width: number
+ height: number
+}
+
+export function BlurredImage({ src, alt, width, height }: BlurredImageProps) {
+ const [isBlurred, setIsBlurred] = useState(true)
+
+ return (
+ <div className="relative inline-block">
+ <div className={`overflow-hidden rounded-md ${isBlurred ? "filter grayscale blur-md" : ""}`}>
+ <Image src={src || "/placeholder.svg"} alt={alt} width={width} height={height} className="object-cover" />
+ </div>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="absolute top-2 right-2 h-7 w-7 p-0 bg-black/50 text-white hover:bg-black/70"
+ onClick={() => setIsBlurred(!isBlurred)}
+ >
+ {isBlurred ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
+ <span className="sr-only">{isBlurred ? "Show image" : "Blur image"}</span>
+ </Button>
+ </div>
+ )
+}
diff --git a/src/components/blurred-text.tsx b/src/components/blurred-text.tsx
@@ -0,0 +1,34 @@
+"use client"
+
+import { useState } from "react"
+import { Eye, EyeOff } from "lucide-react"
+import { Button } from "@/components/ui/button"
+
+interface BlurredTextProps {
+ text: string
+}
+
+export function BlurredText({ text }: BlurredTextProps) {
+ const [isBlurred, setIsBlurred] = useState(true)
+
+ return (
+ <div className="relative">
+ <div
+ className={`p-3 rounded-md bg-gray-800 font-mono text-sm ${
+ isBlurred ? "text-transparent blur-sm select-none" : "text-white"
+ }`}
+ >
+ {text}
+ </div>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="absolute top-2 right-2 h-7 w-7 p-0 text-gray-400 hover:text-white"
+ onClick={() => setIsBlurred(!isBlurred)}
+ >
+ {isBlurred ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
+ <span className="sr-only">{isBlurred ? "Show content" : "Hide content"}</span>
+ </Button>
+ </div>
+ )
+}
diff --git a/src/components/dashboard/bans-pane.tsx b/src/components/dashboard/bans-pane.tsx
@@ -0,0 +1,209 @@
+import { Checkbox } from "../ui/checkbox"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "../ui/dialog";
+import { ScrollArea } from "../ui/scroll-area"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
+import { Ban, Badge } from "lucide-react";
+import { Button } from "../ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
+import { Input } from "../ui/input";
+import { Textarea } from "../ui/textarea";
+import { Label } from "../ui/label"
+
+interface BansProps {
+ policyLists: any[];
+ selectedPolicyList: string | null;
+ setSelectedPolicyList: (id: string) => void;
+ selectedList: any;
+}
+
+export default function Bans({ policyLists, selectedPolicyList, setSelectedPolicyList, selectedList }: BansProps) {
+ return (
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader className="pb-2">
+ <div className="flex items-center justify-between">
+ <CardTitle>Policy Lists</CardTitle>
+ <Dialog>
+ <DialogTrigger asChild>
+ <Button size="sm" className="bg-purple-600 text-white hover:bg-purple-700">
+ <Ban className="mr-2 h-4 w-4" />
+ Add Ban
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ <DialogHeader>
+ <DialogTitle>Add New Ban</DialogTitle>
+ <DialogDescription className="text-gray-400">
+ Ban a user or server from your Matrix rooms
+ </DialogDescription>
+ </DialogHeader>
+ <div className="space-y-4 py-4">
+ <div className="space-y-2">
+ <Label htmlFor="ban-type">Ban Type</Label>
+ <Select defaultValue="user">
+ <SelectTrigger className="bg-gray-900 border-gray-800">
+ <SelectValue placeholder="Select ban type" />
+ </SelectTrigger>
+ <SelectContent className="bg-gray-900 border-gray-800">
+ <SelectItem value="user">User</SelectItem>
+ <SelectItem value="server">Server</SelectItem>
+ <SelectItem value="room">Room</SelectItem>
+ </SelectContent>
+ </Select>
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="ban-target">Target</Label>
+ <Input
+ id="ban-target"
+ placeholder="@user:matrix.org"
+ className="bg-gray-900 border-gray-800"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="ban-reason">Reason</Label>
+ <Textarea
+ id="ban-reason"
+ placeholder="Reason for the ban"
+ className="bg-gray-900 border-gray-800"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label>Policy Lists</Label>
+ <div className="space-y-2 mt-1">
+ {policyLists
+ .filter((list) => !list.readOnly)
+ .map((list) => (
+ <div key={list.id} className="flex items-center space-x-2">
+ <Checkbox id={`list-${list.id}`} />
+ <Label htmlFor={`list-${list.id}`} className="font-normal">
+ {list.name}
+ </Label>
+ </div>
+ ))}
+ </div>
+ </div>
+ <div className="space-y-2">
+ <p className="text-xs text-gray-400">
+ Bans are applied to all protected rooms monitored by this bot.
+ </p>
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ >
+ Cancel
+ </Button>
+ <Button className="bg-red-600 text-white hover:bg-red-700">Add Ban</Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ </div>
+ <CardDescription className="text-gray-400">Manage ban lists across your Matrix rooms</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
+ <div className="space-y-4">
+ <h3 className="text-sm font-medium text-gray-400">Policy Lists</h3>
+ <div className="space-y-2">
+ {policyLists.length > 0 ? (
+ policyLists.map((list) => (
+ <div
+ key={list.id}
+ className={`p-3 rounded-md border cursor-pointer transition-colors ${selectedPolicyList === list.id
+ ? "bg-gray-800 border-purple-600"
+ : "bg-gray-900 border-gray-800 hover:border-gray-700"
+ }`}
+ onClick={() => setSelectedPolicyList(list.id)}
+ >
+ <div className="flex items-center justify-between mb-1">
+ <span className="font-medium">{list.name}</span>
+ {list.readOnly ? (
+ <Badge className="bg-gray-800 text-gray-400">Read Only</Badge>
+ ) : (
+ <Badge className="bg-green-900/50 text-green-300">Editable</Badge>
+ )}
+ </div>
+ <p className="text-xs text-gray-400">{list.description}</p>
+ <p className="text-xs text-gray-500 mt-1">{list.entries.length} entries</p>
+ </div>
+ ))
+ ) : (
+ <div className="flex flex-col items-center justify-center p-8 text-center">
+ <Ban className="h-8 w-8 text-gray-500 mb-2" />
+ <p className="text-gray-400">No policy lists available</p>
+ </div>
+ )}
+ </div>
+ </div>
+
+ <div className="md:col-span-2 space-y-4">
+ <div className="flex items-center justify-between">
+ <h3 className="text-sm font-medium text-gray-400">
+ {selectedList.name || "Policy List"} Entries
+ </h3>
+ <div className="flex items-center gap-2">
+ <Input placeholder="Search entries..." className="h-8 w-[200px] bg-gray-900 border-gray-800" />
+ {selectedList && !selectedList.readOnly && (
+ <Dialog>
+ <DialogTrigger asChild>
+ <Button size="sm" className="bg-purple-600 text-white hover:bg-purple-700">
+ Add Entry
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ {/* Add entry dialog content */}
+ </DialogContent>
+ </Dialog>
+ )}
+ </div>
+ </div>
+
+ <ScrollArea className="h-[400px] rounded-md border border-gray-800">
+ <div className="p-4 space-y-3">
+ {selectedList && selectedList.entries && selectedList.entries.length > 0 ? (
+ selectedList.entries.map((entry) => (
+ <div key={entry.id} className="p-3 rounded-md bg-gray-900 border border-gray-800">
+ <div className="flex items-start justify-between">
+ <div>
+ <p className="font-medium">{entry.target}</p>
+ <p className="text-sm text-gray-400 mt-1">{entry.reason}</p>
+ <p className="text-xs text-gray-500 mt-1">
+ {new Date(entry.timestamp).toLocaleString()}
+ </p>
+ </div>
+ {!selectedList.readOnly && (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 w-8 p-0 text-gray-400 hover:text-red-400"
+ >
+ <Ban className="h-4 w-4" />
+ <span className="sr-only">Remove</span>
+ </Button>
+ )}
+ </div>
+ </div>
+ ))
+ ) : (
+ <div className="flex flex-col items-center justify-center h-full p-6 text-center">
+ <Ban className="h-8 w-8 text-gray-500 mb-2" />
+ <p className="text-gray-400">No ban entries found</p>
+ </div>
+ )}
+ </div>
+ </ScrollArea>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ );
+}
+\ No newline at end of file
diff --git a/src/components/dashboard/modals/add-protected-room.tsx b/src/components/dashboard/modals/add-protected-room.tsx
@@ -0,0 +1,59 @@
+import { Button } from "@/components/ui/button";
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Plus } from "lucide-react";
+
+interface AddProtectedRoomProps {
+ filled?: boolean;
+}
+
+export default function AddProtectedRoom({ filled }: AddProtectedRoomProps) {
+ return (
+ <Dialog>
+ <DialogTrigger asChild>
+ {filled ? (
+ <Button size="sm" className="bg-purple-600 text-white hover:bg-purple-700">
+ <Plus className="mr-2 h-4 w-4" />
+ Add Room
+ </Button>
+ ) : (
+ <Button variant="ghost" size="sm" className="h-7 px-2 text-xs text-purple-400">
+ <Plus className="h-3 w-3 mr-1" />
+ Add Room
+ </Button>
+ )}
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ <DialogHeader>
+ <DialogTitle>Add Protected Room</DialogTitle>
+ <DialogDescription className="text-gray-400">
+ Add a Matrix room to be monitored by this bot
+ </DialogDescription>
+ </DialogHeader>
+ <div className="space-y-4 py-4">
+ <div className="space-y-2">
+ <Label htmlFor="room-id">Room ID or Alias</Label>
+ <Input
+ id="room-id"
+ placeholder="#roomname:matrix.org"
+ className="bg-gray-900 border-gray-800"
+ />
+ <p className="text-xs text-gray-400">
+ Enter a room ID or alias. The bot must be invited to this room.
+ </p>
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ >
+ Cancel
+ </Button>
+ <Button className="bg-purple-600 text-white hover:bg-purple-700">Add Room</Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog >
+ )
+}
+\ No newline at end of file
diff --git a/src/components/dashboard/overview-pane.tsx b/src/components/dashboard/overview-pane.tsx
@@ -0,0 +1,151 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "../ui/dialog";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
+import { Label } from "../ui/label"
+import { Plus, Ban, AlertTriangle, CheckCircle, X } from "lucide-react";
+import { Button } from "../ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
+import { Input } from "../ui/input";
+import { Team } from "@/app/dashboard/team-management";
+import AddProtectedRoom from "./modals/add-protected-room";
+
+interface OverviewProps {
+ selectedTeam: Team;
+ reports: any[];
+ policyLists: any[];
+}
+
+export default function Overview({ selectedTeam, reports, policyLists }: OverviewProps) {
+
+ return (
+ <>
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader className="pb-2">
+ <CardTitle className="text-sm font-medium">Protected Rooms</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">{selectedTeam.rooms.length}</div>
+ <div className="flex justify-between items-center">
+ <p className="text-xs text-gray-400">Monitored by this bot</p>
+ <AddProtectedRoom />
+ </div>
+ </CardContent>
+ </Card>
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader className="pb-2">
+ <CardTitle className="text-sm font-medium">Active Reports</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">{reports.length}</div>
+ <p className="text-xs text-gray-400">
+ {reports.filter((r) => r.priority === "high").length} high priority
+ </p>
+ </CardContent>
+ </Card>
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader className="pb-2">
+ <CardTitle className="text-sm font-medium">Bans Issued</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">
+ {policyLists.reduce((total, list) => total + list.entries.length, 0)}
+ </div>
+ <p className="text-xs text-gray-400">Across all policy lists</p>
+ </CardContent>
+ </Card>
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader className="pb-2">
+ <CardTitle className="text-sm font-medium">Bot Status</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="flex items-center gap-2">
+ <div className="h-3 w-3 rounded-full bg-green-500"></div>
+ <div className="text-sm font-medium">Online</div>
+ </div>
+ <p className="text-xs text-gray-400">Last restart: 7d ago</p>
+ </CardContent>
+ </Card>
+ </div>
+
+ <div className="grid gap-4 md:grid-cols-2">
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <CardTitle>Recent Activity</CardTitle>
+ <CardDescription className="text-gray-400">Latest moderation actions</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <div className="space-y-4">
+ <div className="flex items-start gap-4">
+ <div className="rounded-full bg-red-500/20 p-2">
+ <Ban className="h-4 w-4 text-red-500" />
+ </div>
+ <div className="space-y-1">
+ <p className="text-sm font-medium">User banned</p>
+ <p className="text-xs text-gray-400">@spammer:matrix.org was banned from all rooms</p>
+ <p className="text-xs text-gray-500">10 minutes ago</p>
+ </div>
+ </div>
+ <div className="flex items-start gap-4">
+ <div className="rounded-full bg-yellow-500/20 p-2">
+ <AlertTriangle className="h-4 w-4 text-yellow-500" />
+ </div>
+ <div className="space-y-1">
+ <p className="text-sm font-medium">Report created</p>
+ <p className="text-xs text-gray-400">Spam detected in #support</p>
+ <p className="text-xs text-gray-500">1 hour ago</p>
+ </div>
+ </div>
+ <div className="flex items-start gap-4">
+ <div className="rounded-full bg-green-500/20 p-2">
+ <CheckCircle className="h-4 w-4 text-green-500" />
+ </div>
+ <div className="space-y-1">
+ <p className="text-sm font-medium">Report resolved</p>
+ <p className="text-xs text-gray-400">Harassment report in #community resolved</p>
+ <p className="text-xs text-gray-500">3 hours ago</p>
+ </div>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <div className="flex items-center justify-between">
+ <div>
+ <CardTitle>Protected Rooms</CardTitle>
+ <CardDescription className="text-gray-400">Rooms monitored by this bot</CardDescription>
+ </div>
+ <AddProtectedRoom filled />
+ </div>
+ </CardHeader>
+ <CardContent>
+ <div className="space-y-4">
+ {selectedTeam.rooms.map((room, index) => (
+ <div key={index} className="flex items-center justify-between">
+ <div className="flex items-center gap-2">
+ <div className="h-2 w-2 rounded-full bg-green-500"></div>
+ <p className="text-sm">{room}</p>
+ </div>
+ <Button variant="ghost" size="sm" className="h-7 w-7 p-0 text-gray-400 hover:text-red-400">
+ <X className="h-4 w-4" />
+ <span className="sr-only">Remove</span>
+ </Button>
+ </div>
+ ))}
+ </div>
+ </CardContent>
+ </Card>
+ </div>
+ </>
+ );
+
+}
+\ No newline at end of file
diff --git a/src/components/dashboard/reports-pane.tsx b/src/components/dashboard/reports-pane.tsx
@@ -0,0 +1,251 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "../ui/dialog"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
+import { MessageSquare, UserPlus, Badge, CheckCircle, Filter, Ban, AlertTriangle } from "lucide-react"
+import { BlurredImage } from "../blurred-image"
+import { BlurredText } from "../blurred-text"
+import { Button } from "../ui/button"
+import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "../ui/card"
+
+interface ReportsProps {
+ selectedReport: string | null;
+ reports: any[];
+ setSelectedReport: (report: string) => void;
+}
+
+export default function Reports({ selectedReport, reports, setSelectedReport }: ReportsProps) {
+ return (
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader className="pb-2">
+ <div className="flex items-center justify-between">
+ <CardTitle>Active Reports</CardTitle>
+ <div className="flex items-center gap-2">
+ <Select defaultValue="all">
+ <SelectTrigger className="h-8 w-[120px] bg-gray-900 border-gray-800">
+ <SelectValue placeholder="Filter" />
+ </SelectTrigger>
+ <SelectContent className="bg-gray-900 border-gray-800">
+ <SelectItem value="all">All Reports</SelectItem>
+ <SelectItem value="message">Messages</SelectItem>
+ <SelectItem value="invite">Invites</SelectItem>
+ <SelectItem value="high">High Priority</SelectItem>
+ </SelectContent>
+ </Select>
+ </div>
+ </div>
+ <CardDescription className="text-gray-400">Reports requiring moderation action</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+ <div className="space-y-4">
+ <h3 className="text-sm font-medium text-gray-400">Report List</h3>
+ {reports.length > 0 ? (
+ reports.map((report) => (
+ <div
+ key={report.id}
+ className={`p-3 rounded-md border cursor-pointer transition-colors ${selectedReport === report.id
+ ? "bg-gray-800 border-purple-600"
+ : "bg-gray-900 border-gray-800 hover:border-gray-700"
+ }`}
+ onClick={() => setSelectedReport(report.id)}
+ >
+ <div className="flex items-center justify-between mb-2">
+ <div className="flex items-center gap-2">
+ {report.type === "message" ? (
+ <MessageSquare className="h-4 w-4 text-blue-400" />
+ ) : (
+ <UserPlus className="h-4 w-4 text-green-400" />
+ )}
+ <span className="text-sm font-medium">
+ {report.type === "message" ? "Message Report" : "Invite Report"}
+ </span>
+ </div>
+ <Badge
+ className={
+ report.priority === "high"
+ ? "bg-red-900/50 text-red-300 hover:bg-red-900/70"
+ : "bg-yellow-900/50 text-yellow-300 hover:bg-yellow-900/70"
+ }
+ >
+ {report.priority}
+ </Badge>
+ </div>
+ <p className="text-xs text-gray-400 mb-1">{report.room}</p>
+ <p className="text-xs text-gray-400 mb-1">
+ {report.type === "message"
+ ? `From: ${report.subject.sender}`
+ : `From: ${report.subject.sender} To: ${report.subject.target}`}
+ </p>
+ <p className="text-xs text-gray-500">{new Date(report.timestamp).toLocaleString()}</p>
+ </div>
+ ))
+ ) : (
+ <div className="flex flex-col items-center justify-center p-8 text-center">
+ <CheckCircle className="h-8 w-8 text-gray-500 mb-2" />
+ <p className="text-gray-400">No active reports</p>
+ </div>
+ )}
+ </div>
+
+ <div className="space-y-4">
+ <h3 className="text-sm font-medium text-gray-400">Report Details</h3>
+ {selectedReport ? (
+ (() => {
+ const report = reports.find((r) => r.id === selectedReport)
+ if (!report) return <p>Select a report to view details</p>
+
+ return (
+ <div className="space-y-4">
+ <div className="p-4 rounded-md bg-gray-900 border border-gray-800">
+ <div className="flex items-center justify-between mb-3">
+ <h4 className="font-medium">Report Information</h4>
+ <Badge className="bg-purple-900/50 text-purple-300">{report.status}</Badge>
+ </div>
+
+ <div className="space-y-2 text-sm">
+ <div className="flex justify-between">
+ <span className="text-gray-400">Reported by:</span>
+ <span>{report.reporter}</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-gray-400">Room:</span>
+ <span>{report.room}</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-gray-400">Reason:</span>
+ <span>{report.reason}</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-gray-400">Time:</span>
+ <span>{new Date(report.timestamp).toLocaleString()}</span>
+ </div>
+ </div>
+ </div>
+
+ <div className="p-4 rounded-md bg-gray-900 border border-gray-800">
+ <h4 className="font-medium mb-3">
+ {report.type === "message" ? "Message Content" : "Invite Details"}
+ </h4>
+
+ {report.type === "message" && (
+ <div className="space-y-3">
+ <div className="flex justify-between text-sm">
+ <span className="text-gray-400">Sender:</span>
+ <span>{report.subject.sender}</span>
+ </div>
+ <div className="flex justify-between text-sm">
+ <span className="text-gray-400">Message ID:</span>
+ <span className="font-mono text-xs">{report.subject.id}</span>
+ </div>
+ <div className="flex justify-between text-sm">
+ <span className="text-gray-400">Time:</span>
+ <span>{new Date(report.subject.timestamp).toLocaleString()}</span>
+ </div>
+
+ <div className="mt-4">
+ <div className="flex items-center justify-between mb-2">
+ <span className="text-sm text-gray-400">Content:</span>
+ {report.subject.content?.msgtype === "m.text" && (
+ <Button variant="ghost" size="sm" className="h-6 px-2 text-gray-400">
+ <Filter className="h-3 w-3 mr-1" />
+ Filter
+ </Button>
+ )}
+ </div>
+
+ {report.subject.content?.msgtype === "m.text" ? (
+ <BlurredText text={report.subject.content?.body} />
+ ) : report.subject.content?.msgtype === "m.image" ? (
+ <div className="mt-2">
+ <BlurredImage
+ src="/placeholder.svg?height=300&width=400"
+ alt={report.subject.content?.body}
+ width={400}
+ height={300}
+ />
+ <p className="text-xs text-gray-500 mt-1">
+ Image: {report.subject.content.body}
+ </p>
+ </div>
+ ) : (
+ <p className="text-sm">Unsupported content type</p>
+ )}
+ </div>
+ </div>
+ )}
+
+ {report.type === "invite" && (
+ <div className="space-y-3">
+ <div className="flex justify-between text-sm">
+ <span className="text-gray-400">Sender:</span>
+ <span>{report.subject.sender}</span>
+ </div>
+ <div className="flex justify-between text-sm">
+ <span className="text-gray-400">Target:</span>
+ <span>{report.subject.target}</span>
+ </div>
+ <div className="flex justify-between text-sm">
+ <span className="text-gray-400">Time:</span>
+ <span>{new Date(report.subject.timestamp).toLocaleString()}</span>
+ </div>
+ </div>
+ )}
+ </div>
+
+ <div className="flex gap-2 mt-4">
+ <Button className="bg-green-600 text-white hover:bg-green-700">
+ <CheckCircle className="mr-2 h-4 w-4" />
+ Resolve
+ </Button>
+ <Dialog>
+ <DialogTrigger asChild>
+ <Button
+ variant="outline"
+ className="border-red-500 text-red-400 hover:bg-red-950 hover:text-red-300"
+ >
+ <Ban className="mr-2 h-4 w-4" />
+ Ban User
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="bg-gray-950 border-gray-800">
+ <DialogHeader>
+ <DialogTitle>Ban User</DialogTitle>
+ <DialogDescription className="text-gray-400">
+ Ban {report.subject.sender} from your Matrix rooms
+ </DialogDescription>
+ </DialogHeader>
+ {/* Ban dialog content */}
+ </DialogContent>
+ </Dialog>
+ {report.type === "message" && (
+ <Button
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ >
+ <MessageSquare className="mr-2 h-4 w-4" />
+ Delete Message
+ </Button>
+ )}
+ </div>
+ </div>
+ )
+ })()
+ ) : (
+ <div className="flex flex-col items-center justify-center p-8 text-center">
+ <AlertTriangle className="h-8 w-8 text-gray-500 mb-2" />
+ <p className="text-gray-400">Select a report to view details</p>
+ </div>
+ )}
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ )
+}
+\ No newline at end of file
diff --git a/src/components/matrix-login-form.tsx b/src/components/matrix-login-form.tsx
@@ -0,0 +1,240 @@
+"use client"
+
+import type React from "react"
+
+import { useState, useEffect } from "react"
+import { Loader2, X, AlertCircle } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+
+interface MatrixLoginFormProps {
+ matrixId: string
+ onSuccess: (token: string) => void
+ onCancel: () => void
+}
+
+interface WellKnownResponse {
+ "m.homeserver"?: {
+ base_url: string
+ }
+}
+
+export function MatrixLoginForm({ matrixId, onSuccess, onCancel }: MatrixLoginFormProps) {
+ const [password, setPassword] = useState("")
+ const [isLoading, setIsLoading] = useState(false)
+ const [error, setError] = useState("")
+ const [homeserverUrl, setHomeserverUrl] = useState<string | null>(null)
+ const [discoveryStatus, setDiscoveryStatus] = useState<"idle" | "loading" | "success" | "error">("idle")
+
+ // Extract username and server from Matrix ID
+ const serverName = matrixId.includes(":") ? matrixId.split(":").pop() || "matrix.org" : "matrix.org"
+ const username = matrixId.startsWith("@")
+ ? matrixId.includes(":")
+ ? matrixId.split(":")[0].substring(1)
+ : matrixId.substring(1)
+ : matrixId
+
+ // Discover homeserver URL
+ useEffect(() => {
+ async function discoverHomeserver() {
+ if (!serverName) return
+
+ setDiscoveryStatus("loading")
+ try {
+ // Try to fetch well-known data
+ const wellKnownUrl = `https://${serverName}/.well-known/matrix/client`
+
+ try {
+ const response = await fetch(wellKnownUrl)
+ if (response.ok) {
+ const data = (await response.json()) as WellKnownResponse
+ if (data["m.homeserver"]?.base_url) {
+ setHomeserverUrl(data["m.homeserver"].base_url)
+ setDiscoveryStatus("success")
+ return
+ }
+ }
+ } catch (e) {
+ console.warn("Well-known discovery failed, falling back to direct server", e)
+ }
+
+ // Fallback to direct server
+ setHomeserverUrl(`https://${serverName}`)
+ setDiscoveryStatus("success")
+ } catch (err) {
+ console.error("Homeserver discovery failed:", err)
+ setError("Could not discover Matrix homeserver. Please check your Matrix ID.")
+ setDiscoveryStatus("error")
+ }
+ }
+
+ discoverHomeserver()
+ }, [serverName])
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!homeserverUrl) {
+ setError("Homeserver URL not available. Please try again.")
+ return
+ }
+
+ setIsLoading(true)
+ setError("")
+
+ try {
+ // Step 1: Authenticate with the Matrix homeserver
+ console.log(`Authenticating with ${homeserverUrl}`)
+
+ // In a real implementation:
+ // const loginResponse = await fetch(`${homeserverUrl}/_matrix/client/v3/login`, {
+ // method: 'POST',
+ // headers: { 'Content-Type': 'application/json' },
+ // body: JSON.stringify({
+ // type: 'm.login.password',
+ // identifier: {
+ // type: 'm.id.user',
+ // user: username
+ // },
+ // password
+ // })
+ // })
+
+ // const loginData = await loginResponse.json()
+ // if (!loginData.access_token) {
+ // throw new Error(loginData.error || 'Login failed')
+ // }
+
+ // Simulate login delay
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+
+ // Simulate access token
+ const mockAccessToken = "syt_" + Math.random().toString(36).substring(2, 15)
+
+ // Step 2: Request an OpenID token
+ console.log(`Requesting OpenID token for ${matrixId}`)
+
+ // In a real implementation:
+ // const openIdResponse = await fetch(
+ // `${homeserverUrl}/_matrix/client/v3/user/${encodeURIComponent(matrixId)}/openid/request_token`,
+ // {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // 'Authorization': `Bearer ${loginData.access_token}`
+ // },
+ // body: JSON.stringify({})
+ // }
+ // )
+
+ // const openIdData = await openIdResponse.json()
+ // if (!openIdData.access_token) {
+ // throw new Error(openIdData.error || 'Failed to get OpenID token')
+ // }
+
+ // Simulate OpenID token request delay
+ await new Promise((resolve) => setTimeout(resolve, 500))
+
+ // Simulate OpenID token
+ const mockOpenIdToken =
+ "ey" +
+ Math.random().toString(36).substring(2, 15) +
+ "." +
+ Math.random().toString(36).substring(2, 15) +
+ "." +
+ Math.random().toString(36).substring(2, 15)
+
+ // Pass the token to the parent component
+ onSuccess(mockOpenIdToken)
+ } catch (err) {
+ console.error("Matrix login failed:", err)
+ setError(err instanceof Error ? err.message : "Authentication failed")
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <form onSubmit={handleSubmit} className="space-y-4">
+ <div className="space-y-2">
+ <div className="flex items-center justify-between">
+ <Label htmlFor="matrix-username">Matrix ID</Label>
+ <Button type="button" variant="ghost" size="sm" className="h-6 px-2 text-gray-400" onClick={onCancel}>
+ <X className="h-4 w-4" />
+ </Button>
+ </div>
+ <div className="flex items-center gap-2 rounded-md bg-gray-900 px-3 py-2 text-sm">
+ <span>{matrixId}</span>
+ </div>
+
+ {discoveryStatus === "loading" && (
+ <div className="flex items-center gap-2 text-xs text-gray-400">
+ <Loader2 className="h-3 w-3 animate-spin" />
+ Discovering homeserver...
+ </div>
+ )}
+
+ {discoveryStatus === "success" && homeserverUrl && (
+ <p className="text-xs text-gray-400">Authenticating with {homeserverUrl.replace(/^https?:\/\//, "")}</p>
+ )}
+
+ {discoveryStatus === "error" && (
+ <p className="text-xs text-red-400">Failed to discover homeserver. Please check your Matrix ID.</p>
+ )}
+ </div>
+
+ {discoveryStatus === "success" && (
+ <div className="space-y-2">
+ <Label htmlFor="matrix-password">Password</Label>
+ <Input
+ id="matrix-password"
+ type="password"
+ placeholder="Enter your Matrix password"
+ className="bg-gray-900 border-gray-800"
+ value={password}
+ onChange={(e) => setPassword(e.target.value)}
+ required
+ />
+ </div>
+ )}
+
+ {error && (
+ <Alert variant="destructive" className="bg-red-900/20 border-red-800">
+ <AlertCircle className="h-4 w-4 text-red-400" />
+ <AlertDescription className="text-sm text-red-300">{error}</AlertDescription>
+ </Alert>
+ )}
+
+ <div className="flex gap-2">
+ <Button
+ type="submit"
+ className="flex-1 bg-purple-600 text-white hover:bg-purple-700"
+ disabled={isLoading || !password || discoveryStatus !== "success"}
+ >
+ {isLoading ? (
+ <>
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+ Authenticating...
+ </>
+ ) : (
+ "Log in"
+ )}
+ </Button>
+ <Button
+ type="button"
+ variant="outline"
+ className="border-gray-700 text-gray-400 hover:bg-gray-900 hover:text-gray-300"
+ onClick={onCancel}
+ disabled={isLoading}
+ >
+ Cancel
+ </Button>
+ </div>
+
+ <p className="text-xs text-gray-400 text-center">
+ Your credentials are sent directly to your Matrix homeserver. Draupnir4All only receives a verification token.
+ </p>
+ </form>
+ )
+}
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ThemeProvider as NextThemesProvider,
+ type ThemeProviderProps,
+} from 'next-themes'
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
+}
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx
@@ -0,0 +1,58 @@
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef<typeof AccordionPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
+>(({ className, ...props }, ref) => (
+ <AccordionPrimitive.Item
+ ref={ref}
+ className={cn("border-b", className)}
+ {...props}
+ />
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+ <AccordionPrimitive.Header className="flex">
+ <AccordionPrimitive.Trigger
+ ref={ref}
+ className={cn(
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
+ </AccordionPrimitive.Trigger>
+ </AccordionPrimitive.Header>
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef<typeof AccordionPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+ <AccordionPrimitive.Content
+ ref={ref}
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
+ {...props}
+ >
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
+ </AccordionPrimitive.Content>
+))
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,141 @@
+"use client"
+
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+ <AlertDialogPrimitive.Overlay
+ className={cn(
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+ className
+ )}
+ {...props}
+ ref={ref}
+ />
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
+>(({ className, ...props }, ref) => (
+ <AlertDialogPortal>
+ <AlertDialogOverlay />
+ <AlertDialogPrimitive.Content
+ ref={ref}
+ className={cn(
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
+ className
+ )}
+ {...props}
+ />
+ </AlertDialogPortal>
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col space-y-2 text-center sm:text-left",
+ className
+ )}
+ {...props}
+ />
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+ className
+ )}
+ {...props}
+ />
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+ <AlertDialogPrimitive.Title
+ ref={ref}
+ className={cn("text-lg font-semibold", className)}
+ {...props}
+ />
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+ <AlertDialogPrimitive.Description
+ ref={ref}
+ className={cn("text-sm text-muted-foreground", className)}
+ {...props}
+ />
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
+>(({ className, ...props }, ref) => (
+ <AlertDialogPrimitive.Action
+ ref={ref}
+ className={cn(buttonVariants(), className)}
+ {...props}
+ />
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
+>(({ className, ...props }, ref) => (
+ <AlertDialogPrimitive.Cancel
+ ref={ref}
+ className={cn(
+ buttonVariants({ variant: "outline" }),
+ "mt-2 sm:mt-0",
+ className
+ )}
+ {...props}
+ />
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-background text-foreground",
+ destructive:
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
+>(({ className, variant, ...props }, ref) => (
+ <div
+ ref={ref}
+ role="alert"
+ className={cn(alertVariants({ variant }), className)}
+ {...props}
+ />
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
+ <h5
+ ref={ref}
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
+ {...props}
+ />
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
+ {...props}
+ />
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,7 @@
+"use client"
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef<typeof AvatarPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
+>(({ className, ...props }, ref) => (
+ <AvatarPrimitive.Root
+ ref={ref}
+ className={cn(
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
+ className
+ )}
+ {...props}
+ />
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef<typeof AvatarPrimitive.Image>,
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
+>(({ className, ...props }, ref) => (
+ <AvatarPrimitive.Image
+ ref={ref}
+ className={cn("aspect-square h-full w-full", className)}
+ {...props}
+ />
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
+>(({ className, ...props }, ref) => (
+ <AvatarPrimitive.Fallback
+ ref={ref}
+ className={cn(
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
+ className
+ )}
+ {...props}
+ />
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
@@ -0,0 +1,36 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ destructive:
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
+ outline: "text-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes<HTMLDivElement>,
+ VariantProps<typeof badgeVariants> {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,115 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Breadcrumb = React.forwardRef<
+ HTMLElement,
+ React.ComponentPropsWithoutRef<"nav"> & {
+ separator?: React.ReactNode
+ }
+>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef<
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+ <ol
+ ref={ref}
+ className={cn(
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
+ className
+ )}
+ {...props}
+ />
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+ <li
+ ref={ref}
+ className={cn("inline-flex items-center gap-1.5", className)}
+ {...props}
+ />
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<"a"> & {
+ asChild?: boolean
+ }
+>(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+ <Comp
+ ref={ref}
+ className={cn("transition-colors hover:text-foreground", className)}
+ {...props}
+ />
+ )
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+ <span
+ ref={ref}
+ role="link"
+ aria-disabled="true"
+ aria-current="page"
+ className={cn("font-normal text-foreground", className)}
+ {...props}
+ />
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) => (
+ <li
+ role="presentation"
+ aria-hidden="true"
+ className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
+ {...props}
+ >
+ {children ?? <ChevronRight />}
+ </li>
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+ <span
+ role="presentation"
+ aria-hidden="true"
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
+ {...props}
+ >
+ <MoreHorizontal className="h-4 w-4" />
+ <span className="sr-only">More</span>
+ </span>
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+ VariantProps<typeof buttonVariants> {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+ <Comp
+ className={cn(buttonVariants({ variant, size, className }))}
+ ref={ref}
+ {...props}
+ />
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import * as React from "react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
+import { DayPicker } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+export type CalendarProps = React.ComponentProps<typeof DayPicker>
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: CalendarProps) {
+ return (
+ <DayPicker
+ showOutsideDays={showOutsideDays}
+ className={cn("p-3", className)}
+ classNames={{
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
+ month: "space-y-4",
+ caption: "flex justify-center pt-1 relative items-center",
+ caption_label: "text-sm font-medium",
+ nav: "space-x-1 flex items-center",
+ nav_button: cn(
+ buttonVariants({ variant: "outline" }),
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
+ ),
+ nav_button_previous: "absolute left-1",
+ nav_button_next: "absolute right-1",
+ table: "w-full border-collapse space-y-1",
+ head_row: "flex",
+ head_cell:
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
+ row: "flex w-full mt-2",
+ cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
+ day: cn(
+ buttonVariants({ variant: "ghost" }),
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
+ ),
+ day_range_end: "day-range-end",
+ day_selected:
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+ day_today: "bg-accent text-accent-foreground",
+ day_outside:
+ "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
+ day_disabled: "text-muted-foreground opacity-50",
+ day_range_middle:
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
+ day_hidden: "invisible",
+ ...classNames,
+ }}
+ components={{
+ IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
+ IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
+ }}
+ {...props}
+ />
+ )
+}
+Calendar.displayName = "Calendar"
+
+export { Calendar }
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn(
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
+ className
+ )}
+ {...props}
+ />
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
+ {...props}
+ />
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn(
+ "text-2xl font-semibold leading-none tracking-tight",
+ className
+ )}
+ {...props}
+ />
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn("text-sm text-muted-foreground", className)}
+ {...props}
+ />
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ className={cn("flex items-center p-6 pt-0", className)}
+ {...props}
+ />
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx
@@ -0,0 +1,262 @@
+"use client"
+
+import * as React from "react"
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react"
+import { ArrowLeft, ArrowRight } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
+ api: ReturnType<typeof useEmblaCarousel>[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext<CarouselContextProps | null>(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a <Carousel />")
+ }
+
+ return context
+}
+
+const Carousel = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
+>(
+ (
+ {
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) {
+ return
+ }
+
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext]
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return
+ }
+
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) {
+ return
+ }
+
+ onSelect(api)
+ api.on("reInit", onSelect)
+ api.on("select", onSelect)
+
+ return () => {
+ api?.off("select", onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+ <CarouselContext.Provider
+ value={{
+ carouselRef,
+ api: api,
+ opts,
+ orientation:
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
+ scrollPrev,
+ scrollNext,
+ canScrollPrev,
+ canScrollNext,
+ }}
+ >
+ <div
+ ref={ref}
+ onKeyDownCapture={handleKeyDown}
+ className={cn("relative", className)}
+ role="region"
+ aria-roledescription="carousel"
+ {...props}
+ >
+ {children}
+ </div>
+ </CarouselContext.Provider>
+ )
+ }
+)
+Carousel.displayName = "Carousel"
+
+const CarouselContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+ <div ref={carouselRef} className="overflow-hidden">
+ <div
+ ref={ref}
+ className={cn(
+ "flex",
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
+ className
+ )}
+ {...props}
+ />
+ </div>
+ )
+})
+CarouselContent.displayName = "CarouselContent"
+
+const CarouselItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel()
+
+ return (
+ <div
+ ref={ref}
+ role="group"
+ aria-roledescription="slide"
+ className={cn(
+ "min-w-0 shrink-0 grow-0 basis-full",
+ orientation === "horizontal" ? "pl-4" : "pt-4",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+CarouselItem.displayName = "CarouselItem"
+
+const CarouselPrevious = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<typeof Button>
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+ <Button
+ ref={ref}
+ variant={variant}
+ size={size}
+ className={cn(
+ "absolute h-8 w-8 rounded-full",
+ orientation === "horizontal"
+ ? "-left-12 top-1/2 -translate-y-1/2"
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
+ className
+ )}
+ disabled={!canScrollPrev}
+ onClick={scrollPrev}
+ {...props}
+ >
+ <ArrowLeft className="h-4 w-4" />
+ <span className="sr-only">Previous slide</span>
+ </Button>
+ )
+})
+CarouselPrevious.displayName = "CarouselPrevious"
+
+const CarouselNext = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<typeof Button>
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+ <Button
+ ref={ref}
+ variant={variant}
+ size={size}
+ className={cn(
+ "absolute h-8 w-8 rounded-full",
+ orientation === "horizontal"
+ ? "-right-12 top-1/2 -translate-y-1/2"
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
+ className
+ )}
+ disabled={!canScrollNext}
+ onClick={scrollNext}
+ {...props}
+ >
+ <ArrowRight className="h-4 w-4" />
+ <span className="sr-only">Next slide</span>
+ </Button>
+ )
+})
+CarouselNext.displayName = "CarouselNext"
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+}
diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx
@@ -0,0 +1,365 @@
+"use client"
+
+import * as React from "react"
+import * as RechartsPrimitive from "recharts"
+
+import { cn } from "@/lib/utils"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext<ChartContextProps | null>(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error("useChart must be used within a <ChartContainer />")
+ }
+
+ return context
+}
+
+const ChartContainer = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"]
+ }
+>(({ id, className, children, config, ...props }, ref) => {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+ return (
+ <ChartContext.Provider value={{ config }}>
+ <div
+ data-chart={chartId}
+ ref={ref}
+ className={cn(
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
+ className
+ )}
+ {...props}
+ >
+ <ChartStyle id={chartId} config={config} />
+ <RechartsPrimitive.ResponsiveContainer>
+ {children}
+ </RechartsPrimitive.ResponsiveContainer>
+ </div>
+ </ChartContext.Provider>
+ )
+})
+ChartContainer.displayName = "Chart"
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([_, config]) => config.theme || config.color
+ )
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+ <style
+ dangerouslySetInnerHTML={{
+ __html: Object.entries(THEMES)
+ .map(
+ ([theme, prefix]) => `
+${prefix} [data-chart=${id}] {
+${colorConfig
+ .map(([key, itemConfig]) => {
+ const color =
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
+ itemConfig.color
+ return color ? ` --color-${key}: ${color};` : null
+ })
+ .join("\n")}
+}
+`
+ )
+ .join("\n"),
+ }}
+ />
+ )
+}
+
+const ChartTooltip = RechartsPrimitive.Tooltip
+
+const ChartTooltipContent = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
+ React.ComponentProps<"div"> & {
+ hideLabel?: boolean
+ hideIndicator?: boolean
+ indicator?: "line" | "dot" | "dashed"
+ nameKey?: string
+ labelKey?: string
+ }
+>(
+ (
+ {
+ active,
+ payload,
+ className,
+ indicator = "dot",
+ hideLabel = false,
+ hideIndicator = false,
+ label,
+ labelFormatter,
+ labelClassName,
+ formatter,
+ color,
+ nameKey,
+ labelKey,
+ },
+ ref
+ ) => {
+ const { config } = useChart()
+
+ const tooltipLabel = React.useMemo(() => {
+ if (hideLabel || !payload?.length) {
+ return null
+ }
+
+ const [item] = payload
+ const key = `${labelKey || item.dataKey || item.name || "value"}`
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
+ const value =
+ !labelKey && typeof label === "string"
+ ? config[label as keyof typeof config]?.label || label
+ : itemConfig?.label
+
+ if (labelFormatter) {
+ return (
+ <div className={cn("font-medium", labelClassName)}>
+ {labelFormatter(value, payload)}
+ </div>
+ )
+ }
+
+ if (!value) {
+ return null
+ }
+
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
+ }, [
+ label,
+ labelFormatter,
+ payload,
+ hideLabel,
+ labelClassName,
+ config,
+ labelKey,
+ ])
+
+ if (!active || !payload?.length) {
+ return null
+ }
+
+ const nestLabel = payload.length === 1 && indicator !== "dot"
+
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
+ className
+ )}
+ >
+ {!nestLabel ? tooltipLabel : null}
+ <div className="grid gap-1.5">
+ {payload.map((item, index) => {
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
+ const indicatorColor = color || item.payload.fill || item.color
+
+ return (
+ <div
+ key={item.dataKey}
+ className={cn(
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
+ indicator === "dot" && "items-center"
+ )}
+ >
+ {formatter && item?.value !== undefined && item.name ? (
+ formatter(item.value, item.name, item, index, item.payload)
+ ) : (
+ <>
+ {itemConfig?.icon ? (
+ <itemConfig.icon />
+ ) : (
+ !hideIndicator && (
+ <div
+ className={cn(
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
+ {
+ "h-2.5 w-2.5": indicator === "dot",
+ "w-1": indicator === "line",
+ "w-0 border-[1.5px] border-dashed bg-transparent":
+ indicator === "dashed",
+ "my-0.5": nestLabel && indicator === "dashed",
+ }
+ )}
+ style={
+ {
+ "--color-bg": indicatorColor,
+ "--color-border": indicatorColor,
+ } as React.CSSProperties
+ }
+ />
+ )
+ )}
+ <div
+ className={cn(
+ "flex flex-1 justify-between leading-none",
+ nestLabel ? "items-end" : "items-center"
+ )}
+ >
+ <div className="grid gap-1.5">
+ {nestLabel ? tooltipLabel : null}
+ <span className="text-muted-foreground">
+ {itemConfig?.label || item.name}
+ </span>
+ </div>
+ {item.value && (
+ <span className="font-mono font-medium tabular-nums text-foreground">
+ {item.value.toLocaleString()}
+ </span>
+ )}
+ </div>
+ </>
+ )}
+ </div>
+ )
+ })}
+ </div>
+ </div>
+ )
+ }
+)
+ChartTooltipContent.displayName = "ChartTooltip"
+
+const ChartLegend = RechartsPrimitive.Legend
+
+const ChartLegendContent = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> &
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
+ hideIcon?: boolean
+ nameKey?: string
+ }
+>(
+ (
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
+ ref
+ ) => {
+ const { config } = useChart()
+
+ if (!payload?.length) {
+ return null
+ }
+
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ "flex items-center justify-center gap-4",
+ verticalAlign === "top" ? "pb-3" : "pt-3",
+ className
+ )}
+ >
+ {payload.map((item) => {
+ const key = `${nameKey || item.dataKey || "value"}`
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
+
+ return (
+ <div
+ key={item.value}
+ className={cn(
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
+ )}
+ >
+ {itemConfig?.icon && !hideIcon ? (
+ <itemConfig.icon />
+ ) : (
+ <div
+ className="h-2 w-2 shrink-0 rounded-[2px]"
+ style={{
+ backgroundColor: item.color,
+ }}
+ />
+ )}
+ {itemConfig?.label}
+ </div>
+ )
+ })}
+ </div>
+ )
+ }
+)
+ChartLegendContent.displayName = "ChartLegend"
+
+// Helper to extract item config from a payload.
+function getPayloadConfigFromPayload(
+ config: ChartConfig,
+ payload: unknown,
+ key: string
+) {
+ if (typeof payload !== "object" || payload === null) {
+ return undefined
+ }
+
+ const payloadPayload =
+ "payload" in payload &&
+ typeof payload.payload === "object" &&
+ payload.payload !== null
+ ? payload.payload
+ : undefined
+
+ let configLabelKey: string = key
+
+ if (
+ key in payload &&
+ typeof payload[key as keyof typeof payload] === "string"
+ ) {
+ configLabelKey = payload[key as keyof typeof payload] as string
+ } else if (
+ payloadPayload &&
+ key in payloadPayload &&
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
+ ) {
+ configLabelKey = payloadPayload[
+ key as keyof typeof payloadPayload
+ ] as string
+ }
+
+ return configLabelKey in config
+ ? config[configLabelKey]
+ : config[key as keyof typeof config]
+}
+
+export {
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+ ChartLegend,
+ ChartLegendContent,
+ ChartStyle,
+}
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
+>(({ className, ...props }, ref) => (
+ <CheckboxPrimitive.Root
+ ref={ref}
+ className={cn(
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
+ className
+ )}
+ {...props}
+ >
+ <CheckboxPrimitive.Indicator
+ className={cn("flex items-center justify-center text-current")}
+ >
+ <Check className="h-4 w-4" />
+ </CheckboxPrimitive.Indicator>
+ </CheckboxPrimitive.Root>
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx
@@ -0,0 +1,11 @@
+"use client"
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
@@ -0,0 +1,153 @@
+"use client"
+
+import * as React from "react"
+import { type DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive
+ ref={ref}
+ className={cn(
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
+ className
+ )}
+ {...props}
+ />
+))
+Command.displayName = CommandPrimitive.displayName
+
+const CommandDialog = ({ children, ...props }: DialogProps) => {
+ return (
+ <Dialog {...props}>
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
+ {children}
+ </Command>
+ </DialogContent>
+ </Dialog>
+ )
+}
+
+const CommandInput = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Input>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
+>(({ className, ...props }, ref) => (
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
+ <CommandPrimitive.Input
+ ref={ref}
+ className={cn(
+ "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
+ className
+ )}
+ {...props}
+ />
+ </div>
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.List>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.List
+ ref={ref}
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
+ {...props}
+ />
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Empty>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
+>((props, ref) => (
+ <CommandPrimitive.Empty
+ ref={ref}
+ className="py-6 text-center text-sm"
+ {...props}
+ />
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Group>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Group
+ ref={ref}
+ className={cn(
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
+ className
+ )}
+ {...props}
+ />
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Separator
+ ref={ref}
+ className={cn("-mx-1 h-px bg-border", className)}
+ {...props}
+ />
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Item
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ className
+ )}
+ {...props}
+ />
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+ return (
+ <span
+ className={cn(
+ "ml-auto text-xs tracking-widest text-muted-foreground",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/src/components/ui/context-menu.tsx b/src/components/ui/context-menu.tsx
@@ -0,0 +1,200 @@
+"use client"
+
+import * as React from "react"
+import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ContextMenu = ContextMenuPrimitive.Root
+
+const ContextMenuTrigger = ContextMenuPrimitive.Trigger
+
+const ContextMenuGroup = ContextMenuPrimitive.Group
+
+const ContextMenuPortal = ContextMenuPrimitive.Portal
+
+const ContextMenuSub = ContextMenuPrimitive.Sub
+
+const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
+
+const ContextMenuSubTrigger = React.forwardRef<
+ React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+ <ContextMenuPrimitive.SubTrigger
+ ref={ref}
+ className={cn(
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <ChevronRight className="ml-auto h-4 w-4" />
+ </ContextMenuPrimitive.SubTrigger>
+))
+ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
+
+const ContextMenuSubContent = React.forwardRef<
+ React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+ <ContextMenuPrimitive.SubContent
+ ref={ref}
+ className={cn(
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+))
+ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
+
+const ContextMenuContent = React.forwardRef<
+ React.ElementRef<typeof ContextMenuPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
+>(({ className, ...props }, ref) => (
+ <ContextMenuPrimitive.Portal>
+ <ContextMenuPrimitive.Content
+ ref={ref}
+ className={cn(
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+ </ContextMenuPrimitive.Portal>
+))
+ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
+
+const ContextMenuItem = React.forwardRef<
+ React.ElementRef<typeof ContextMenuPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ <ContextMenuPrimitive.Item
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ />
+))
+ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
+
+const ContextMenuCheckboxItem = React.forwardRef<
+ React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+ <ContextMenuPrimitive.CheckboxItem
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className
+ )}
+ checked={checked}
+ {...props}
+ >
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+ <ContextMenuPrimitive.ItemIndicator>
+ <Check className="h-4 w-4" />
+ </ContextMenuPrimitive.ItemIndicator>
+ </span>
+ {children}
+ </ContextMenuPrimitive.CheckboxItem>
+))
+ContextMenuCheckboxItem.displayName =
+ ContextMenuPrimitive.CheckboxItem.displayName
+
+const ContextMenuRadioItem = React.forwardRef<
+ React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+ <ContextMenuPrimitive.RadioItem
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className
+ )}
+ {...props}
+ >
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+ <ContextMenuPrimitive.ItemIndicator>
+ <Circle className="h-2 w-2 fill-current" />
+ </ContextMenuPrimitive.ItemIndicator>
+ </span>
+ {children}
+ </ContextMenuPrimitive.RadioItem>
+))
+ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
+
+const ContextMenuLabel = React.forwardRef<
+ React.ElementRef<typeof ContextMenuPrimitive.Label>,
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ <ContextMenuPrimitive.Label
+ ref={ref}
+ className={cn(
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ />
+))
+ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
+
+const ContextMenuSeparator = React.forwardRef<
+ React.ElementRef<typeof ContextMenuPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+ <ContextMenuPrimitive.Separator
+ ref={ref}
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
+ {...props}
+ />
+))
+ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
+
+const ContextMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+ return (
+ <span
+ className={cn(
+ "ml-auto text-xs tracking-widest text-muted-foreground",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+ContextMenuShortcut.displayName = "ContextMenuShortcut"
+
+export {
+ ContextMenu,
+ ContextMenuTrigger,
+ ContextMenuContent,
+ ContextMenuItem,
+ ContextMenuCheckboxItem,
+ ContextMenuRadioItem,
+ ContextMenuLabel,
+ ContextMenuSeparator,
+ ContextMenuShortcut,
+ ContextMenuGroup,
+ ContextMenuPortal,
+ ContextMenuSub,
+ ContextMenuSubContent,
+ ContextMenuSubTrigger,
+ ContextMenuRadioGroup,
+}
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
@@ -0,0 +1,122 @@
+"use client"
+
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Overlay
+ ref={ref}
+ className={cn(
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+ className
+ )}
+ {...props}
+ />
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+ <DialogPortal>
+ <DialogOverlay />
+ <DialogPrimitive.Content
+ ref={ref}
+ className={cn(
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
+ <X className="h-4 w-4" />
+ <span className="sr-only">Close</span>
+ </DialogPrimitive.Close>
+ </DialogPrimitive.Content>
+ </DialogPortal>
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col space-y-1.5 text-center sm:text-left",
+ className
+ )}
+ {...props}
+ />
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+ className
+ )}
+ {...props}
+ />
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Title>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Title
+ ref={ref}
+ className={cn(
+ "text-lg font-semibold leading-none tracking-tight",
+ className
+ )}
+ {...props}
+ />
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Description>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Description
+ ref={ref}
+ className={cn("text-sm text-muted-foreground", className)}
+ {...props}
+ />
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx
@@ -0,0 +1,118 @@
+"use client"
+
+import * as React from "react"
+import { Drawer as DrawerPrimitive } from "vaul"
+
+import { cn } from "@/lib/utils"
+
+const Drawer = ({
+ shouldScaleBackground = true,
+ ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
+ <DrawerPrimitive.Root
+ shouldScaleBackground={shouldScaleBackground}
+ {...props}
+ />
+)
+Drawer.displayName = "Drawer"
+
+const DrawerTrigger = DrawerPrimitive.Trigger
+
+const DrawerPortal = DrawerPrimitive.Portal
+
+const DrawerClose = DrawerPrimitive.Close
+
+const DrawerOverlay = React.forwardRef<
+ React.ElementRef<typeof DrawerPrimitive.Overlay>,
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+ <DrawerPrimitive.Overlay
+ ref={ref}
+ className={cn("fixed inset-0 z-50 bg-black/80", className)}
+ {...props}
+ />
+))
+DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
+
+const DrawerContent = React.forwardRef<
+ React.ElementRef<typeof DrawerPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+ <DrawerPortal>
+ <DrawerOverlay />
+ <DrawerPrimitive.Content
+ ref={ref}
+ className={cn(
+ "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
+ className
+ )}
+ {...props}
+ >
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
+ {children}
+ </DrawerPrimitive.Content>
+ </DrawerPortal>
+))
+DrawerContent.displayName = "DrawerContent"
+
+const DrawerHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
+ {...props}
+ />
+)
+DrawerHeader.displayName = "DrawerHeader"
+
+const DrawerFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
+ {...props}
+ />
+)
+DrawerFooter.displayName = "DrawerFooter"
+
+const DrawerTitle = React.forwardRef<
+ React.ElementRef<typeof DrawerPrimitive.Title>,
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
+>(({ className, ...props }, ref) => (
+ <DrawerPrimitive.Title
+ ref={ref}
+ className={cn(
+ "text-lg font-semibold leading-none tracking-tight",
+ className
+ )}
+ {...props}
+ />
+))
+DrawerTitle.displayName = DrawerPrimitive.Title.displayName
+
+const DrawerDescription = React.forwardRef<
+ React.ElementRef<typeof DrawerPrimitive.Description>,
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
+>(({ className, ...props }, ref) => (
+ <DrawerPrimitive.Description
+ ref={ref}
+ className={cn("text-sm text-muted-foreground", className)}
+ {...props}
+ />
+))
+DrawerDescription.displayName = DrawerPrimitive.Description.displayName
+
+export {
+ Drawer,
+ DrawerPortal,
+ DrawerOverlay,
+ DrawerTrigger,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerFooter,
+ DrawerTitle,
+ DrawerDescription,
+}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,200 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+ <DropdownMenuPrimitive.SubTrigger
+ ref={ref}
+ className={cn(
+ "flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <ChevronRight className="ml-auto" />
+ </DropdownMenuPrimitive.SubTrigger>
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+ <DropdownMenuPrimitive.SubContent
+ ref={ref}
+ className={cn(
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
+>(({ className, sideOffset = 4, ...props }, ref) => (
+ <DropdownMenuPrimitive.Portal>
+ <DropdownMenuPrimitive.Content
+ ref={ref}
+ sideOffset={sideOffset}
+ className={cn(
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+ </DropdownMenuPrimitive.Portal>
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ <DropdownMenuPrimitive.Item
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ />
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+ <DropdownMenuPrimitive.CheckboxItem
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className
+ )}
+ checked={checked}
+ {...props}
+ >
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+ <DropdownMenuPrimitive.ItemIndicator>
+ <Check className="h-4 w-4" />
+ </DropdownMenuPrimitive.ItemIndicator>
+ </span>
+ {children}
+ </DropdownMenuPrimitive.CheckboxItem>
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+ <DropdownMenuPrimitive.RadioItem
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className
+ )}
+ {...props}
+ >
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+ <DropdownMenuPrimitive.ItemIndicator>
+ <Circle className="h-2 w-2 fill-current" />
+ </DropdownMenuPrimitive.ItemIndicator>
+ </span>
+ {children}
+ </DropdownMenuPrimitive.RadioItem>
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ <DropdownMenuPrimitive.Label
+ ref={ref}
+ className={cn(
+ "px-2 py-1.5 text-sm font-semibold",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ />
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+ <DropdownMenuPrimitive.Separator
+ ref={ref}
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
+ {...props}
+ />
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+ return (
+ <span
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
+ {...props}
+ />
+ )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
@@ -0,0 +1,178 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { Slot } from "@radix-ui/react-slot"
+import {
+ Controller,
+ ControllerProps,
+ FieldPath,
+ FieldValues,
+ FormProvider,
+ useFormContext,
+} from "react-hook-form"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+> = {
+ name: TName
+}
+
+const FormFieldContext = React.createContext<FormFieldContextValue>(
+ {} as FormFieldContextValue
+)
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+>({
+ ...props
+}: ControllerProps<TFieldValues, TName>) => {
+ return (
+ <FormFieldContext.Provider value={{ name: props.name }}>
+ <Controller {...props} />
+ </FormFieldContext.Provider>
+ )
+}
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext)
+ const itemContext = React.useContext(FormItemContext)
+ const { getFieldState, formState } = useFormContext()
+
+ const fieldState = getFieldState(fieldContext.name, formState)
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within <FormField>")
+ }
+
+ const { id } = itemContext
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
+
+type FormItemContextValue = {
+ id: string
+}
+
+const FormItemContext = React.createContext<FormItemContextValue>(
+ {} as FormItemContextValue
+)
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+ const id = React.useId()
+
+ return (
+ <FormItemContext.Provider value={{ id }}>
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
+ </FormItemContext.Provider>
+ )
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef<
+ React.ElementRef<typeof LabelPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField()
+
+ return (
+ <Label
+ ref={ref}
+ className={cn(error && "text-destructive", className)}
+ htmlFor={formItemId}
+ {...props}
+ />
+ )
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef<
+ React.ElementRef<typeof Slot>,
+ React.ComponentPropsWithoutRef<typeof Slot>
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+ <Slot
+ ref={ref}
+ id={formItemId}
+ aria-describedby={
+ !error
+ ? `${formDescriptionId}`
+ : `${formDescriptionId} ${formMessageId}`
+ }
+ aria-invalid={!!error}
+ {...props}
+ />
+ )
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField()
+
+ return (
+ <p
+ ref={ref}
+ id={formDescriptionId}
+ className={cn("text-sm text-muted-foreground", className)}
+ {...props}
+ />
+ )
+})
+FormDescription.displayName = "FormDescription"
+
+const FormMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField()
+ const body = error ? String(error?.message) : children
+
+ if (!body) {
+ return null
+ }
+
+ return (
+ <p
+ ref={ref}
+ id={formMessageId}
+ className={cn("text-sm font-medium text-destructive", className)}
+ {...props}
+ >
+ {body}
+ </p>
+ )
+})
+FormMessage.displayName = "FormMessage"
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/src/components/ui/hover-card.tsx b/src/components/ui/hover-card.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+import * as React from "react"
+import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
+
+import { cn } from "@/lib/utils"
+
+const HoverCard = HoverCardPrimitive.Root
+
+const HoverCardTrigger = HoverCardPrimitive.Trigger
+
+const HoverCardContent = React.forwardRef<
+ React.ElementRef<typeof HoverCardPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+ <HoverCardPrimitive.Content
+ ref={ref}
+ align={align}
+ sideOffset={sideOffset}
+ className={cn(
+ "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+))
+HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
+
+export { HoverCard, HoverCardTrigger, HoverCardContent }
diff --git a/src/components/ui/input-otp.tsx b/src/components/ui/input-otp.tsx
@@ -0,0 +1,71 @@
+"use client"
+
+import * as React from "react"
+import { OTPInput, OTPInputContext } from "input-otp"
+import { Dot } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const InputOTP = React.forwardRef<
+ React.ElementRef<typeof OTPInput>,
+ React.ComponentPropsWithoutRef<typeof OTPInput>
+>(({ className, containerClassName, ...props }, ref) => (
+ <OTPInput
+ ref={ref}
+ containerClassName={cn(
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
+ containerClassName
+ )}
+ className={cn("disabled:cursor-not-allowed", className)}
+ {...props}
+ />
+))
+InputOTP.displayName = "InputOTP"
+
+const InputOTPGroup = React.forwardRef<
+ React.ElementRef<"div">,
+ React.ComponentPropsWithoutRef<"div">
+>(({ className, ...props }, ref) => (
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
+))
+InputOTPGroup.displayName = "InputOTPGroup"
+
+const InputOTPSlot = React.forwardRef<
+ React.ElementRef<"div">,
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
+>(({ index, className, ...props }, ref) => {
+ const inputOTPContext = React.useContext(OTPInputContext)
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
+
+ return (
+ <div
+ ref={ref}
+ className={cn(
+ "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
+ isActive && "z-10 ring-2 ring-ring ring-offset-background",
+ className
+ )}
+ {...props}
+ >
+ {char}
+ {hasFakeCaret && (
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
+ </div>
+ )}
+ </div>
+ )
+})
+InputOTPSlot.displayName = "InputOTPSlot"
+
+const InputOTPSeparator = React.forwardRef<
+ React.ElementRef<"div">,
+ React.ComponentPropsWithoutRef<"div">
+>(({ ...props }, ref) => (
+ <div ref={ref} role="separator" {...props}>
+ <Dot />
+ </div>
+))
+InputOTPSeparator.displayName = "InputOTPSeparator"
+
+export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
+ ({ className, type, ...props }, ref) => {
+ return (
+ <input
+ type={type}
+ className={cn(
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+ className
+ )}
+ ref={ref}
+ {...props}
+ />
+ )
+ }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
@@ -0,0 +1,26 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef<typeof LabelPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
+ VariantProps<typeof labelVariants>
+>(({ className, ...props }, ref) => (
+ <LabelPrimitive.Root
+ ref={ref}
+ className={cn(labelVariants(), className)}
+ {...props}
+ />
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/src/components/ui/menubar.tsx b/src/components/ui/menubar.tsx
@@ -0,0 +1,236 @@
+"use client"
+
+import * as React from "react"
+import * as MenubarPrimitive from "@radix-ui/react-menubar"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const MenubarMenu = MenubarPrimitive.Menu
+
+const MenubarGroup = MenubarPrimitive.Group
+
+const MenubarPortal = MenubarPrimitive.Portal
+
+const MenubarSub = MenubarPrimitive.Sub
+
+const MenubarRadioGroup = MenubarPrimitive.RadioGroup
+
+const Menubar = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
+>(({ className, ...props }, ref) => (
+ <MenubarPrimitive.Root
+ ref={ref}
+ className={cn(
+ "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
+ className
+ )}
+ {...props}
+ />
+))
+Menubar.displayName = MenubarPrimitive.Root.displayName
+
+const MenubarTrigger = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.Trigger>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+ <MenubarPrimitive.Trigger
+ ref={ref}
+ className={cn(
+ "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+ className
+ )}
+ {...props}
+ />
+))
+MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
+
+const MenubarSubTrigger = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+ <MenubarPrimitive.SubTrigger
+ ref={ref}
+ className={cn(
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <ChevronRight className="ml-auto h-4 w-4" />
+ </MenubarPrimitive.SubTrigger>
+))
+MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
+
+const MenubarSubContent = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.SubContent>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+ <MenubarPrimitive.SubContent
+ ref={ref}
+ className={cn(
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+))
+MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
+
+const MenubarContent = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
+>(
+ (
+ { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
+ ref
+ ) => (
+ <MenubarPrimitive.Portal>
+ <MenubarPrimitive.Content
+ ref={ref}
+ align={align}
+ alignOffset={alignOffset}
+ sideOffset={sideOffset}
+ className={cn(
+ "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+ </MenubarPrimitive.Portal>
+ )
+)
+MenubarContent.displayName = MenubarPrimitive.Content.displayName
+
+const MenubarItem = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ <MenubarPrimitive.Item
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ />
+))
+MenubarItem.displayName = MenubarPrimitive.Item.displayName
+
+const MenubarCheckboxItem = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+ <MenubarPrimitive.CheckboxItem
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className
+ )}
+ checked={checked}
+ {...props}
+ >
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+ <MenubarPrimitive.ItemIndicator>
+ <Check className="h-4 w-4" />
+ </MenubarPrimitive.ItemIndicator>
+ </span>
+ {children}
+ </MenubarPrimitive.CheckboxItem>
+))
+MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
+
+const MenubarRadioItem = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.RadioItem>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+ <MenubarPrimitive.RadioItem
+ ref={ref}
+ className={cn(
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className
+ )}
+ {...props}
+ >
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+ <MenubarPrimitive.ItemIndicator>
+ <Circle className="h-2 w-2 fill-current" />
+ </MenubarPrimitive.ItemIndicator>
+ </span>
+ {children}
+ </MenubarPrimitive.RadioItem>
+))
+MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
+
+const MenubarLabel = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.Label>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ <MenubarPrimitive.Label
+ ref={ref}
+ className={cn(
+ "px-2 py-1.5 text-sm font-semibold",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ />
+))
+MenubarLabel.displayName = MenubarPrimitive.Label.displayName
+
+const MenubarSeparator = React.forwardRef<
+ React.ElementRef<typeof MenubarPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+ <MenubarPrimitive.Separator
+ ref={ref}
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
+ {...props}
+ />
+))
+MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
+
+const MenubarShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+ return (
+ <span
+ className={cn(
+ "ml-auto text-xs tracking-widest text-muted-foreground",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+MenubarShortcut.displayname = "MenubarShortcut"
+
+export {
+ Menubar,
+ MenubarMenu,
+ MenubarTrigger,
+ MenubarContent,
+ MenubarItem,
+ MenubarSeparator,
+ MenubarLabel,
+ MenubarCheckboxItem,
+ MenubarRadioGroup,
+ MenubarRadioItem,
+ MenubarPortal,
+ MenubarSubContent,
+ MenubarSubTrigger,
+ MenubarGroup,
+ MenubarSub,
+ MenubarShortcut,
+}
diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx
@@ -0,0 +1,128 @@
+import * as React from "react"
+import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
+import { cva } from "class-variance-authority"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const NavigationMenu = React.forwardRef<
+ React.ElementRef<typeof NavigationMenuPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
+>(({ className, children, ...props }, ref) => (
+ <NavigationMenuPrimitive.Root
+ ref={ref}
+ className={cn(
+ "relative z-10 flex max-w-max flex-1 items-center justify-center",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <NavigationMenuViewport />
+ </NavigationMenuPrimitive.Root>
+))
+NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
+
+const NavigationMenuList = React.forwardRef<
+ React.ElementRef<typeof NavigationMenuPrimitive.List>,
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
+>(({ className, ...props }, ref) => (
+ <NavigationMenuPrimitive.List
+ ref={ref}
+ className={cn(
+ "group flex flex-1 list-none items-center justify-center space-x-1",
+ className
+ )}
+ {...props}
+ />
+))
+NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
+
+const NavigationMenuItem = NavigationMenuPrimitive.Item
+
+const navigationMenuTriggerStyle = cva(
+ "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
+)
+
+const NavigationMenuTrigger = React.forwardRef<
+ React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+ <NavigationMenuPrimitive.Trigger
+ ref={ref}
+ className={cn(navigationMenuTriggerStyle(), "group", className)}
+ {...props}
+ >
+ {children}{" "}
+ <ChevronDown
+ className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
+ aria-hidden="true"
+ />
+ </NavigationMenuPrimitive.Trigger>
+))
+NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
+
+const NavigationMenuContent = React.forwardRef<
+ React.ElementRef<typeof NavigationMenuPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
+>(({ className, ...props }, ref) => (
+ <NavigationMenuPrimitive.Content
+ ref={ref}
+ className={cn(
+ "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
+ className
+ )}
+ {...props}
+ />
+))
+NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
+
+const NavigationMenuLink = NavigationMenuPrimitive.Link
+
+const NavigationMenuViewport = React.forwardRef<
+ React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
+>(({ className, ...props }, ref) => (
+ <div className={cn("absolute left-0 top-full flex justify-center")}>
+ <NavigationMenuPrimitive.Viewport
+ className={cn(
+ "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
+ className
+ )}
+ ref={ref}
+ {...props}
+ />
+ </div>
+))
+NavigationMenuViewport.displayName =
+ NavigationMenuPrimitive.Viewport.displayName
+
+const NavigationMenuIndicator = React.forwardRef<
+ React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
+>(({ className, ...props }, ref) => (
+ <NavigationMenuPrimitive.Indicator
+ ref={ref}
+ className={cn(
+ "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
+ className
+ )}
+ {...props}
+ >
+ <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
+ </NavigationMenuPrimitive.Indicator>
+))
+NavigationMenuIndicator.displayName =
+ NavigationMenuPrimitive.Indicator.displayName
+
+export {
+ navigationMenuTriggerStyle,
+ NavigationMenu,
+ NavigationMenuList,
+ NavigationMenuItem,
+ NavigationMenuContent,
+ NavigationMenuTrigger,
+ NavigationMenuLink,
+ NavigationMenuIndicator,
+ NavigationMenuViewport,
+}
diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx
@@ -0,0 +1,117 @@
+import * as React from "react"
+import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { ButtonProps, buttonVariants } from "@/components/ui/button"
+
+const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
+ <nav
+ role="navigation"
+ aria-label="pagination"
+ className={cn("mx-auto flex w-full justify-center", className)}
+ {...props}
+ />
+)
+Pagination.displayName = "Pagination"
+
+const PaginationContent = React.forwardRef<
+ HTMLUListElement,
+ React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+ <ul
+ ref={ref}
+ className={cn("flex flex-row items-center gap-1", className)}
+ {...props}
+ />
+))
+PaginationContent.displayName = "PaginationContent"
+
+const PaginationItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentProps<"li">
+>(({ className, ...props }, ref) => (
+ <li ref={ref} className={cn("", className)} {...props} />
+))
+PaginationItem.displayName = "PaginationItem"
+
+type PaginationLinkProps = {
+ isActive?: boolean
+} & Pick<ButtonProps, "size"> &
+ React.ComponentProps<"a">
+
+const PaginationLink = ({
+ className,
+ isActive,
+ size = "icon",
+ ...props
+}: PaginationLinkProps) => (
+ <a
+ aria-current={isActive ? "page" : undefined}
+ className={cn(
+ buttonVariants({
+ variant: isActive ? "outline" : "ghost",
+ size,
+ }),
+ className
+ )}
+ {...props}
+ />
+)
+PaginationLink.displayName = "PaginationLink"
+
+const PaginationPrevious = ({
+ className,
+ ...props
+}: React.ComponentProps<typeof PaginationLink>) => (
+ <PaginationLink
+ aria-label="Go to previous page"
+ size="default"
+ className={cn("gap-1 pl-2.5", className)}
+ {...props}
+ >
+ <ChevronLeft className="h-4 w-4" />
+ <span>Previous</span>
+ </PaginationLink>
+)
+PaginationPrevious.displayName = "PaginationPrevious"
+
+const PaginationNext = ({
+ className,
+ ...props
+}: React.ComponentProps<typeof PaginationLink>) => (
+ <PaginationLink
+ aria-label="Go to next page"
+ size="default"
+ className={cn("gap-1 pr-2.5", className)}
+ {...props}
+ >
+ <span>Next</span>
+ <ChevronRight className="h-4 w-4" />
+ </PaginationLink>
+)
+PaginationNext.displayName = "PaginationNext"
+
+const PaginationEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+ <span
+ aria-hidden
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
+ {...props}
+ >
+ <MoreHorizontal className="h-4 w-4" />
+ <span className="sr-only">More pages</span>
+ </span>
+)
+PaginationEllipsis.displayName = "PaginationEllipsis"
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationEllipsis,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef<typeof PopoverPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+ <PopoverPrimitive.Portal>
+ <PopoverPrimitive.Content
+ ref={ref}
+ align={align}
+ sideOffset={sideOffset}
+ className={cn(
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+ </PopoverPrimitive.Portal>
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent }
diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx
@@ -0,0 +1,28 @@
+"use client"
+
+import * as React from "react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+
+import { cn } from "@/lib/utils"
+
+const Progress = React.forwardRef<
+ React.ElementRef<typeof ProgressPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
+>(({ className, value, ...props }, ref) => (
+ <ProgressPrimitive.Root
+ ref={ref}
+ className={cn(
+ "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
+ className
+ )}
+ {...props}
+ >
+ <ProgressPrimitive.Indicator
+ className="h-full w-full flex-1 bg-primary transition-all"
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
+ />
+ </ProgressPrimitive.Root>
+))
+Progress.displayName = ProgressPrimitive.Root.displayName
+
+export { Progress }
diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx
@@ -0,0 +1,44 @@
+"use client"
+
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const RadioGroup = React.forwardRef<
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
+>(({ className, ...props }, ref) => {
+ return (
+ <RadioGroupPrimitive.Root
+ className={cn("grid gap-2", className)}
+ {...props}
+ ref={ref}
+ />
+ )
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
+>(({ className, ...props }, ref) => {
+ return (
+ <RadioGroupPrimitive.Item
+ ref={ref}
+ className={cn(
+ "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ className
+ )}
+ {...props}
+ >
+ <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
+ <Circle className="h-2.5 w-2.5 fill-current text-current" />
+ </RadioGroupPrimitive.Indicator>
+ </RadioGroupPrimitive.Item>
+ )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx
@@ -0,0 +1,45 @@
+"use client"
+
+import { GripVertical } from "lucide-react"
+import * as ResizablePrimitive from "react-resizable-panels"
+
+import { cn } from "@/lib/utils"
+
+const ResizablePanelGroup = ({
+ className,
+ ...props
+}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
+ <ResizablePrimitive.PanelGroup
+ className={cn(
+ "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
+ className
+ )}
+ {...props}
+ />
+)
+
+const ResizablePanel = ResizablePrimitive.Panel
+
+const ResizableHandle = ({
+ withHandle,
+ className,
+ ...props
+}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
+ withHandle?: boolean
+}) => (
+ <ResizablePrimitive.PanelResizeHandle
+ className={cn(
+ "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
+ className
+ )}
+ {...props}
+ >
+ {withHandle && (
+ <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
+ <GripVertical className="h-2.5 w-2.5" />
+ </div>
+ )}
+ </ResizablePrimitive.PanelResizeHandle>
+)
+
+export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx
@@ -0,0 +1,48 @@
+"use client"
+
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/lib/utils"
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef<typeof ScrollAreaPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
+>(({ className, children, ...props }, ref) => (
+ <ScrollAreaPrimitive.Root
+ ref={ref}
+ className={cn("relative overflow-hidden", className)}
+ {...props}
+ >
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
+ {children}
+ </ScrollAreaPrimitive.Viewport>
+ <ScrollBar />
+ <ScrollAreaPrimitive.Corner />
+ </ScrollAreaPrimitive.Root>
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
+>(({ className, orientation = "vertical", ...props }, ref) => (
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
+ ref={ref}
+ orientation={orientation}
+ className={cn(
+ "flex touch-none select-none transition-colors",
+ orientation === "vertical" &&
+ "h-full w-2.5 border-l border-l-transparent p-[1px]",
+ orientation === "horizontal" &&
+ "h-2.5 flex-col border-t border-t-transparent p-[1px]",
+ className
+ )}
+ {...props}
+ >
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
@@ -0,0 +1,160 @@
+"use client"
+
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { Check, ChevronDown, ChevronUp } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef<
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+ <SelectPrimitive.Trigger
+ ref={ref}
+ className={cn(
+ "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <SelectPrimitive.Icon asChild>
+ <ChevronDown className="h-4 w-4 opacity-50" />
+ </SelectPrimitive.Icon>
+ </SelectPrimitive.Trigger>
+))
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+
+const SelectScrollUpButton = React.forwardRef<
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
+>(({ className, ...props }, ref) => (
+ <SelectPrimitive.ScrollUpButton
+ ref={ref}
+ className={cn(
+ "flex cursor-default items-center justify-center py-1",
+ className
+ )}
+ {...props}
+ >
+ <ChevronUp className="h-4 w-4" />
+ </SelectPrimitive.ScrollUpButton>
+))
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+
+const SelectScrollDownButton = React.forwardRef<
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
+>(({ className, ...props }, ref) => (
+ <SelectPrimitive.ScrollDownButton
+ ref={ref}
+ className={cn(
+ "flex cursor-default items-center justify-center py-1",
+ className
+ )}
+ {...props}
+ >
+ <ChevronDown className="h-4 w-4" />
+ </SelectPrimitive.ScrollDownButton>
+))
+SelectScrollDownButton.displayName =
+ SelectPrimitive.ScrollDownButton.displayName
+
+const SelectContent = React.forwardRef<
+ React.ElementRef<typeof SelectPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
+>(({ className, children, position = "popper", ...props }, ref) => (
+ <SelectPrimitive.Portal>
+ <SelectPrimitive.Content
+ ref={ref}
+ className={cn(
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ position === "popper" &&
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
+ className
+ )}
+ position={position}
+ {...props}
+ >
+ <SelectScrollUpButton />
+ <SelectPrimitive.Viewport
+ className={cn(
+ "p-1",
+ position === "popper" &&
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
+ )}
+ >
+ {children}
+ </SelectPrimitive.Viewport>
+ <SelectScrollDownButton />
+ </SelectPrimitive.Content>
+ </SelectPrimitive.Portal>
+))
+SelectContent.displayName = SelectPrimitive.Content.displayName
+
+const SelectLabel = React.forwardRef<
+ React.ElementRef<typeof SelectPrimitive.Label>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
+>(({ className, ...props }, ref) => (
+ <SelectPrimitive.Label
+ ref={ref}
+ className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
+ {...props}
+ />
+))
+SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+const SelectItem = React.forwardRef<
+ React.ElementRef<typeof SelectPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
+>(({ className, children, ...props }, ref) => (
+ <SelectPrimitive.Item
+ ref={ref}
+ className={cn(
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className
+ )}
+ {...props}
+ >
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+ <SelectPrimitive.ItemIndicator>
+ <Check className="h-4 w-4" />
+ </SelectPrimitive.ItemIndicator>
+ </span>
+
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
+ </SelectPrimitive.Item>
+))
+SelectItem.displayName = SelectPrimitive.Item.displayName
+
+const SelectSeparator = React.forwardRef<
+ React.ElementRef<typeof SelectPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+ <SelectPrimitive.Separator
+ ref={ref}
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
+ {...props}
+ />
+))
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+export {
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectLabel,
+ SelectItem,
+ SelectSeparator,
+ SelectScrollUpButton,
+ SelectScrollDownButton,
+}
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+const Separator = React.forwardRef<
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
+>(
+ (
+ { className, orientation = "horizontal", decorative = true, ...props },
+ ref
+ ) => (
+ <SeparatorPrimitive.Root
+ ref={ref}
+ decorative={decorative}
+ orientation={orientation}
+ className={cn(
+ "shrink-0 bg-border",
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
+ className
+ )}
+ {...props}
+ />
+ )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
@@ -0,0 +1,140 @@
+"use client"
+
+import * as React from "react"
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef<
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+ <SheetPrimitive.Overlay
+ className={cn(
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+ className
+ )}
+ {...props}
+ ref={ref}
+ />
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+ {
+ variants: {
+ side: {
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+ bottom:
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+ right:
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+ },
+ },
+ defaultVariants: {
+ side: "right",
+ },
+ }
+)
+
+interface SheetContentProps
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
+ VariantProps<typeof sheetVariants> {}
+
+const SheetContent = React.forwardRef<
+ React.ElementRef<typeof SheetPrimitive.Content>,
+ SheetContentProps
+>(({ side = "right", className, children, ...props }, ref) => (
+ <SheetPortal>
+ <SheetOverlay />
+ <SheetPrimitive.Content
+ ref={ref}
+ className={cn(sheetVariants({ side }), className)}
+ {...props}
+ >
+ {children}
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
+ <X className="h-4 w-4" />
+ <span className="sr-only">Close</span>
+ </SheetPrimitive.Close>
+ </SheetPrimitive.Content>
+ </SheetPortal>
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col space-y-2 text-center sm:text-left",
+ className
+ )}
+ {...props}
+ />
+)
+SheetHeader.displayName = "SheetHeader"
+
+const SheetFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+ className
+ )}
+ {...props}
+ />
+)
+SheetFooter.displayName = "SheetFooter"
+
+const SheetTitle = React.forwardRef<
+ React.ElementRef<typeof SheetPrimitive.Title>,
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
+>(({ className, ...props }, ref) => (
+ <SheetPrimitive.Title
+ ref={ref}
+ className={cn("text-lg font-semibold text-foreground", className)}
+ {...props}
+ />
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef<
+ React.ElementRef<typeof SheetPrimitive.Description>,
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
+>(({ className, ...props }, ref) => (
+ <SheetPrimitive.Description
+ ref={ref}
+ className={cn("text-sm text-muted-foreground", className)}
+ {...props}
+ />
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+ Sheet,
+ SheetPortal,
+ SheetOverlay,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
@@ -0,0 +1,763 @@
+"use client"
+
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { VariantProps, cva } from "class-variance-authority"
+import { PanelLeft } from "lucide-react"
+
+import { useIsMobile } from "@/hooks/use-mobile"
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Separator } from "@/components/ui/separator"
+import { Sheet, SheetContent } from "@/components/ui/sheet"
+import { Skeleton } from "@/components/ui/skeleton"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+
+const SIDEBAR_COOKIE_NAME = "sidebar:state"
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = "16rem"
+const SIDEBAR_WIDTH_MOBILE = "18rem"
+const SIDEBAR_WIDTH_ICON = "3rem"
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+
+type SidebarContext = {
+ state: "expanded" | "collapsed"
+ open: boolean
+ setOpen: (open: boolean) => void
+ openMobile: boolean
+ setOpenMobile: (open: boolean) => void
+ isMobile: boolean
+ toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext<SidebarContext | null>(null)
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext)
+ if (!context) {
+ throw new Error("useSidebar must be used within a SidebarProvider.")
+ }
+
+ return context
+}
+
+const SidebarProvider = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ defaultOpen?: boolean
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+ }
+>(
+ (
+ {
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ 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<SidebarContext>(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ )
+
+ return (
+ <SidebarContext.Provider value={contextValue}>
+ <TooltipProvider delayDuration={0}>
+ <div
+ style={
+ {
+ "--sidebar-width": SIDEBAR_WIDTH,
+ "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
+ ...style,
+ } as React.CSSProperties
+ }
+ className={cn(
+ "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
+ className
+ )}
+ ref={ref}
+ {...props}
+ >
+ {children}
+ </div>
+ </TooltipProvider>
+ </SidebarContext.Provider>
+ )
+ }
+)
+SidebarProvider.displayName = "SidebarProvider"
+
+const Sidebar = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ side?: "left" | "right"
+ variant?: "sidebar" | "floating" | "inset"
+ collapsible?: "offcanvas" | "icon" | "none"
+ }
+>(
+ (
+ {
+ side = "left",
+ variant = "sidebar",
+ collapsible = "offcanvas",
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+ if (collapsible === "none") {
+ return (
+ <div
+ className={cn(
+ "flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
+ className
+ )}
+ ref={ref}
+ {...props}
+ >
+ {children}
+ </div>
+ )
+ }
+
+ if (isMobile) {
+ return (
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
+ <SheetContent
+ data-sidebar="sidebar"
+ data-mobile="true"
+ className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
+ style={
+ {
+ "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
+ } as React.CSSProperties
+ }
+ side={side}
+ >
+ <div className="flex h-full w-full flex-col">{children}</div>
+ </SheetContent>
+ </Sheet>
+ )
+ }
+
+ return (
+ <div
+ ref={ref}
+ className="group peer hidden md:block text-sidebar-foreground"
+ data-state={state}
+ data-collapsible={state === "collapsed" ? collapsible : ""}
+ data-variant={variant}
+ data-side={side}
+ >
+ {/* This is what handles the sidebar gap on desktop */}
+ <div
+ className={cn(
+ "duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear",
+ "group-data-[collapsible=offcanvas]:w-0",
+ "group-data-[side=right]:rotate-180",
+ variant === "floating" || variant === "inset"
+ ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
+ : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
+ )}
+ />
+ <div
+ className={cn(
+ "duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex",
+ side === "left"
+ ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
+ : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
+ // Adjust the padding for floating and inset variants.
+ variant === "floating" || variant === "inset"
+ ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
+ : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
+ className
+ )}
+ {...props}
+ >
+ <div
+ data-sidebar="sidebar"
+ className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
+ >
+ {children}
+ </div>
+ </div>
+ </div>
+ )
+ }
+)
+Sidebar.displayName = "Sidebar"
+
+const SidebarTrigger = React.forwardRef<
+ React.ElementRef<typeof Button>,
+ React.ComponentProps<typeof Button>
+>(({ className, onClick, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+ <Button
+ ref={ref}
+ data-sidebar="trigger"
+ variant="ghost"
+ size="icon"
+ className={cn("h-7 w-7", className)}
+ onClick={(event) => {
+ onClick?.(event)
+ toggleSidebar()
+ }}
+ {...props}
+ >
+ <PanelLeft />
+ <span className="sr-only">Toggle Sidebar</span>
+ </Button>
+ )
+})
+SidebarTrigger.displayName = "SidebarTrigger"
+
+const SidebarRail = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button">
+>(({ className, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+ <button
+ ref={ref}
+ data-sidebar="rail"
+ aria-label="Toggle Sidebar"
+ tabIndex={-1}
+ onClick={toggleSidebar}
+ title="Toggle Sidebar"
+ className={cn(
+ "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
+ "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
+ "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
+ "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
+ "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
+ "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarRail.displayName = "SidebarRail"
+
+const SidebarInset = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"main">
+>(({ className, ...props }, ref) => {
+ return (
+ <main
+ ref={ref}
+ className={cn(
+ "relative flex min-h-svh flex-1 flex-col bg-background",
+ "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarInset.displayName = "SidebarInset"
+
+const SidebarInput = React.forwardRef<
+ React.ElementRef<typeof Input>,
+ React.ComponentProps<typeof Input>
+>(({ className, ...props }, ref) => {
+ return (
+ <Input
+ ref={ref}
+ data-sidebar="input"
+ className={cn(
+ "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarInput.displayName = "SidebarInput"
+
+const SidebarHeader = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+ return (
+ <div
+ ref={ref}
+ data-sidebar="header"
+ className={cn("flex flex-col gap-2 p-2", className)}
+ {...props}
+ />
+ )
+})
+SidebarHeader.displayName = "SidebarHeader"
+
+const SidebarFooter = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+ return (
+ <div
+ ref={ref}
+ data-sidebar="footer"
+ className={cn("flex flex-col gap-2 p-2", className)}
+ {...props}
+ />
+ )
+})
+SidebarFooter.displayName = "SidebarFooter"
+
+const SidebarSeparator = React.forwardRef<
+ React.ElementRef<typeof Separator>,
+ React.ComponentProps<typeof Separator>
+>(({ className, ...props }, ref) => {
+ return (
+ <Separator
+ ref={ref}
+ data-sidebar="separator"
+ className={cn("mx-2 w-auto bg-sidebar-border", className)}
+ {...props}
+ />
+ )
+})
+SidebarSeparator.displayName = "SidebarSeparator"
+
+const SidebarContent = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+ return (
+ <div
+ ref={ref}
+ data-sidebar="content"
+ className={cn(
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarContent.displayName = "SidebarContent"
+
+const SidebarGroup = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+ return (
+ <div
+ ref={ref}
+ data-sidebar="group"
+ className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
+ {...props}
+ />
+ )
+})
+SidebarGroup.displayName = "SidebarGroup"
+
+const SidebarGroupLabel = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "div"
+
+ return (
+ <Comp
+ ref={ref}
+ data-sidebar="group-label"
+ className={cn(
+ "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarGroupLabel.displayName = "SidebarGroupLabel"
+
+const SidebarGroupAction = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+ <Comp
+ ref={ref}
+ data-sidebar="group-action"
+ className={cn(
+ "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 after:md:hidden",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarGroupAction.displayName = "SidebarGroupAction"
+
+const SidebarGroupContent = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ data-sidebar="group-content"
+ className={cn("w-full text-sm", className)}
+ {...props}
+ />
+))
+SidebarGroupContent.displayName = "SidebarGroupContent"
+
+const SidebarMenu = React.forwardRef<
+ HTMLUListElement,
+ React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+ <ul
+ ref={ref}
+ data-sidebar="menu"
+ className={cn("flex w-full min-w-0 flex-col gap-1", className)}
+ {...props}
+ />
+))
+SidebarMenu.displayName = "SidebarMenu"
+
+const SidebarMenuItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentProps<"li">
+>(({ className, ...props }, ref) => (
+ <li
+ ref={ref}
+ data-sidebar="menu-item"
+ className={cn("group/menu-item relative", className)}
+ {...props}
+ />
+))
+SidebarMenuItem.displayName = "SidebarMenuItem"
+
+const sidebarMenuButtonVariants = cva(
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ outline:
+ "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ },
+ size: {
+ default: "h-8 text-sm",
+ sm: "h-7 text-xs",
+ lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const SidebarMenuButton = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> & {
+ asChild?: boolean
+ isActive?: boolean
+ tooltip?: string | React.ComponentProps<typeof TooltipContent>
+ } & VariantProps<typeof sidebarMenuButtonVariants>
+>(
+ (
+ {
+ asChild = false,
+ isActive = false,
+ variant = "default",
+ size = "default",
+ tooltip,
+ className,
+ ...props
+ },
+ ref
+ ) => {
+ const Comp = asChild ? Slot : "button"
+ const { isMobile, state } = useSidebar()
+
+ const button = (
+ <Comp
+ ref={ref}
+ data-sidebar="menu-button"
+ data-size={size}
+ data-active={isActive}
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
+ {...props}
+ />
+ )
+
+ if (!tooltip) {
+ return button
+ }
+
+ if (typeof tooltip === "string") {
+ tooltip = {
+ children: tooltip,
+ }
+ }
+
+ return (
+ <Tooltip>
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
+ <TooltipContent
+ side="right"
+ align="center"
+ hidden={state !== "collapsed" || isMobile}
+ {...tooltip}
+ />
+ </Tooltip>
+ )
+ }
+)
+SidebarMenuButton.displayName = "SidebarMenuButton"
+
+const SidebarMenuAction = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<"button"> & {
+ asChild?: boolean
+ showOnHover?: boolean
+ }
+>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+ <Comp
+ ref={ref}
+ data-sidebar="menu-action"
+ className={cn(
+ "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 after:md:hidden",
+ "peer-data-[size=sm]/menu-button:top-1",
+ "peer-data-[size=default]/menu-button:top-1.5",
+ "peer-data-[size=lg]/menu-button:top-2.5",
+ "group-data-[collapsible=icon]:hidden",
+ showOnHover &&
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarMenuAction.displayName = "SidebarMenuAction"
+
+const SidebarMenuBadge = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div">
+>(({ className, ...props }, ref) => (
+ <div
+ ref={ref}
+ data-sidebar="menu-badge"
+ className={cn(
+ "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none",
+ "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
+ "peer-data-[size=sm]/menu-button:top-1",
+ "peer-data-[size=default]/menu-button:top-1.5",
+ "peer-data-[size=lg]/menu-button:top-2.5",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+))
+SidebarMenuBadge.displayName = "SidebarMenuBadge"
+
+const SidebarMenuSkeleton = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ showIcon?: boolean
+ }
+>(({ className, showIcon = false, ...props }, ref) => {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`
+ }, [])
+
+ return (
+ <div
+ ref={ref}
+ data-sidebar="menu-skeleton"
+ className={cn("rounded-md h-8 flex gap-2 px-2 items-center", className)}
+ {...props}
+ >
+ {showIcon && (
+ <Skeleton
+ className="size-4 rounded-md"
+ data-sidebar="menu-skeleton-icon"
+ />
+ )}
+ <Skeleton
+ className="h-4 flex-1 max-w-[--skeleton-width]"
+ data-sidebar="menu-skeleton-text"
+ style={
+ {
+ "--skeleton-width": width,
+ } as React.CSSProperties
+ }
+ />
+ </div>
+ )
+})
+SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
+
+const SidebarMenuSub = React.forwardRef<
+ HTMLUListElement,
+ React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+ <ul
+ ref={ref}
+ data-sidebar="menu-sub"
+ className={cn(
+ "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+))
+SidebarMenuSub.displayName = "SidebarMenuSub"
+
+const SidebarMenuSubItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentProps<"li">
+>(({ ...props }, ref) => <li ref={ref} {...props} />)
+SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
+
+const SidebarMenuSubButton = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentProps<"a"> & {
+ asChild?: boolean
+ size?: "sm" | "md"
+ isActive?: boolean
+ }
+>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+ <Comp
+ ref={ref}
+ data-sidebar="menu-sub-button"
+ data-size={size}
+ data-active={isActive}
+ className={cn(
+ "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+ size === "sm" && "text-xs",
+ size === "md" && "text-sm",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+}
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) {
+ return (
+ <div
+ className={cn("animate-pulse rounded-md bg-muted", className)}
+ {...props}
+ />
+ )
+}
+
+export { Skeleton }
diff --git a/src/components/ui/slider.tsx b/src/components/ui/slider.tsx
@@ -0,0 +1,28 @@
+"use client"
+
+import * as React from "react"
+import * as SliderPrimitive from "@radix-ui/react-slider"
+
+import { cn } from "@/lib/utils"
+
+const Slider = React.forwardRef<
+ React.ElementRef<typeof SliderPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
+>(({ className, ...props }, ref) => (
+ <SliderPrimitive.Root
+ ref={ref}
+ className={cn(
+ "relative flex w-full touch-none select-none items-center",
+ className
+ )}
+ {...props}
+ >
+ <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
+ <SliderPrimitive.Range className="absolute h-full bg-primary" />
+ </SliderPrimitive.Track>
+ <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
+ </SliderPrimitive.Root>
+))
+Slider.displayName = SliderPrimitive.Root.displayName
+
+export { Slider }
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import { useTheme } from "next-themes"
+import { Toaster as Sonner } from "sonner"
+
+type ToasterProps = React.ComponentProps<typeof Sonner>
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = "system" } = useTheme()
+
+ return (
+ <Sonner
+ theme={theme as ToasterProps["theme"]}
+ className="toaster group"
+ toastOptions={{
+ classNames: {
+ toast:
+ "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
+ description: "group-[.toast]:text-muted-foreground",
+ actionButton:
+ "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
+ cancelButton:
+ "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
+ },
+ }}
+ {...props}
+ />
+ )
+}
+
+export { Toaster }
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+const Switch = React.forwardRef<
+ React.ElementRef<typeof SwitchPrimitives.Root>,
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
+>(({ className, ...props }, ref) => (
+ <SwitchPrimitives.Root
+ className={cn(
+ "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
+ className
+ )}
+ {...props}
+ ref={ref}
+ >
+ <SwitchPrimitives.Thumb
+ className={cn(
+ "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
+ )}
+ />
+ </SwitchPrimitives.Root>
+))
+Switch.displayName = SwitchPrimitives.Root.displayName
+
+export { Switch }
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
@@ -0,0 +1,117 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Table = React.forwardRef<
+ HTMLTableElement,
+ React.HTMLAttributes<HTMLTableElement>
+>(({ className, ...props }, ref) => (
+ <div className="relative w-full overflow-auto">
+ <table
+ ref={ref}
+ className={cn("w-full caption-bottom text-sm", className)}
+ {...props}
+ />
+ </div>
+))
+Table.displayName = "Table"
+
+const TableHeader = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
+))
+TableHeader.displayName = "TableHeader"
+
+const TableBody = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+ <tbody
+ ref={ref}
+ className={cn("[&_tr:last-child]:border-0", className)}
+ {...props}
+ />
+))
+TableBody.displayName = "TableBody"
+
+const TableFooter = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+ <tfoot
+ ref={ref}
+ className={cn(
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+))
+TableFooter.displayName = "TableFooter"
+
+const TableRow = React.forwardRef<
+ HTMLTableRowElement,
+ React.HTMLAttributes<HTMLTableRowElement>
+>(({ className, ...props }, ref) => (
+ <tr
+ ref={ref}
+ className={cn(
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
+ className
+ )}
+ {...props}
+ />
+))
+TableRow.displayName = "TableRow"
+
+const TableHead = React.forwardRef<
+ HTMLTableCellElement,
+ React.ThHTMLAttributes<HTMLTableCellElement>
+>(({ className, ...props }, ref) => (
+ <th
+ ref={ref}
+ className={cn(
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
+ className
+ )}
+ {...props}
+ />
+))
+TableHead.displayName = "TableHead"
+
+const TableCell = React.forwardRef<
+ HTMLTableCellElement,
+ React.TdHTMLAttributes<HTMLTableCellElement>
+>(({ className, ...props }, ref) => (
+ <td
+ ref={ref}
+ className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
+ {...props}
+ />
+))
+TableCell.displayName = "TableCell"
+
+const TableCaption = React.forwardRef<
+ HTMLTableCaptionElement,
+ React.HTMLAttributes<HTMLTableCaptionElement>
+>(({ className, ...props }, ref) => (
+ <caption
+ ref={ref}
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
+ {...props}
+ />
+))
+TableCaption.displayName = "TableCaption"
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
@@ -0,0 +1,55 @@
+"use client"
+
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef<
+ React.ElementRef<typeof TabsPrimitive.List>,
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
+>(({ className, ...props }, ref) => (
+ <TabsPrimitive.List
+ ref={ref}
+ className={cn(
+ "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
+ className
+ )}
+ {...props}
+ />
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+ <TabsPrimitive.Trigger
+ ref={ref}
+ className={cn(
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
+ className
+ )}
+ {...props}
+ />
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef<
+ React.ElementRef<typeof TabsPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
+>(({ className, ...props }, ref) => (
+ <TabsPrimitive.Content
+ ref={ref}
+ className={cn(
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
+ className
+ )}
+ {...props}
+ />
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Textarea = React.forwardRef<
+ HTMLTextAreaElement,
+ React.ComponentProps<"textarea">
+>(({ className, ...props }, ref) => {
+ return (
+ <textarea
+ className={cn(
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+ className
+ )}
+ ref={ref}
+ {...props}
+ />
+ )
+})
+Textarea.displayName = "Textarea"
+
+export { Textarea }
diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx
@@ -0,0 +1,129 @@
+"use client"
+
+import * as React from "react"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef<typeof ToastPrimitives.Viewport>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
+>(({ className, ...props }, ref) => (
+ <ToastPrimitives.Viewport
+ ref={ref}
+ className={cn(
+ "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
+ className
+ )}
+ {...props}
+ />
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Toast = React.forwardRef<
+ React.ElementRef<typeof ToastPrimitives.Root>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
+ VariantProps<typeof toastVariants>
+>(({ className, variant, ...props }, ref) => {
+ return (
+ <ToastPrimitives.Root
+ ref={ref}
+ className={cn(toastVariants({ variant }), className)}
+ {...props}
+ />
+ )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+ React.ElementRef<typeof ToastPrimitives.Action>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
+>(({ className, ...props }, ref) => (
+ <ToastPrimitives.Action
+ ref={ref}
+ className={cn(
+ "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
+ className
+ )}
+ {...props}
+ />
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+ React.ElementRef<typeof ToastPrimitives.Close>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
+>(({ className, ...props }, ref) => (
+ <ToastPrimitives.Close
+ ref={ref}
+ className={cn(
+ "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
+ className
+ )}
+ toast-close=""
+ {...props}
+ >
+ <X className="h-4 w-4" />
+ </ToastPrimitives.Close>
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef<typeof ToastPrimitives.Title>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
+>(({ className, ...props }, ref) => (
+ <ToastPrimitives.Title
+ ref={ref}
+ className={cn("text-sm font-semibold", className)}
+ {...props}
+ />
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef<typeof ToastPrimitives.Description>,
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
+>(({ className, ...props }, ref) => (
+ <ToastPrimitives.Description
+ ref={ref}
+ className={cn("text-sm opacity-90", className)}
+ {...props}
+ />
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
+
+type ToastActionElement = React.ReactElement<typeof ToastAction>
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+}
diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx
@@ -0,0 +1,35 @@
+"use client"
+
+import { useToast } from "@/hooks/use-toast"
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/toast"
+
+export function Toaster() {
+ const { toasts } = useToast()
+
+ return (
+ <ToastProvider>
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+ <Toast key={id} {...props}>
+ <div className="grid gap-1">
+ {title && <ToastTitle>{title}</ToastTitle>}
+ {description && (
+ <ToastDescription>{description}</ToastDescription>
+ )}
+ </div>
+ {action}
+ <ToastClose />
+ </Toast>
+ )
+ })}
+ <ToastViewport />
+ </ToastProvider>
+ )
+}
diff --git a/src/components/ui/toggle-group.tsx b/src/components/ui/toggle-group.tsx
@@ -0,0 +1,61 @@
+"use client"
+
+import * as React from "react"
+import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
+import { type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { toggleVariants } from "@/components/ui/toggle"
+
+const ToggleGroupContext = React.createContext<
+ VariantProps<typeof toggleVariants>
+>({
+ size: "default",
+ variant: "default",
+})
+
+const ToggleGroup = React.forwardRef<
+ React.ElementRef<typeof ToggleGroupPrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
+ VariantProps<typeof toggleVariants>
+>(({ className, variant, size, children, ...props }, ref) => (
+ <ToggleGroupPrimitive.Root
+ ref={ref}
+ className={cn("flex items-center justify-center gap-1", className)}
+ {...props}
+ >
+ <ToggleGroupContext.Provider value={{ variant, size }}>
+ {children}
+ </ToggleGroupContext.Provider>
+ </ToggleGroupPrimitive.Root>
+))
+
+ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
+
+const ToggleGroupItem = React.forwardRef<
+ React.ElementRef<typeof ToggleGroupPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
+ VariantProps<typeof toggleVariants>
+>(({ className, children, variant, size, ...props }, ref) => {
+ const context = React.useContext(ToggleGroupContext)
+
+ return (
+ <ToggleGroupPrimitive.Item
+ ref={ref}
+ className={cn(
+ toggleVariants({
+ variant: context.variant || variant,
+ size: context.size || size,
+ }),
+ className
+ )}
+ {...props}
+ >
+ {children}
+ </ToggleGroupPrimitive.Item>
+ )
+})
+
+ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
+
+export { ToggleGroup, ToggleGroupItem }
diff --git a/src/components/ui/toggle.tsx b/src/components/ui/toggle.tsx
@@ -0,0 +1,45 @@
+"use client"
+
+import * as React from "react"
+import * as TogglePrimitive from "@radix-ui/react-toggle"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const toggleVariants = cva(
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline:
+ "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
+ },
+ size: {
+ default: "h-10 px-3 min-w-10",
+ sm: "h-9 px-2.5 min-w-9",
+ lg: "h-11 px-5 min-w-11",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const Toggle = React.forwardRef<
+ React.ElementRef<typeof TogglePrimitive.Root>,
+ React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
+ VariantProps<typeof toggleVariants>
+>(({ className, variant, size, ...props }, ref) => (
+ <TogglePrimitive.Root
+ ref={ref}
+ className={cn(toggleVariants({ variant, size, className }))}
+ {...props}
+ />
+))
+
+Toggle.displayName = TogglePrimitive.Root.displayName
+
+export { Toggle, toggleVariants }
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+const TooltipProvider = TooltipPrimitive.Provider
+
+const Tooltip = TooltipPrimitive.Root
+
+const TooltipTrigger = TooltipPrimitive.Trigger
+
+const TooltipContent = React.forwardRef<
+ React.ElementRef<typeof TooltipPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
+>(({ className, sideOffset = 4, ...props }, ref) => (
+ <TooltipPrimitive.Content
+ ref={ref}
+ sideOffset={sideOffset}
+ className={cn(
+ "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ className
+ )}
+ {...props}
+ />
+))
+TooltipContent.displayName = TooltipPrimitive.Content.displayName
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/src/components/ui/use-mobile.tsx b/src/components/ui/use-mobile.tsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(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/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts
@@ -0,0 +1,194 @@
+"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<ToasterToast>
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+
+interface State {
+ toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
+
+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<ToasterToast, "id">
+
+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<State>(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/src/hooks/use-mobile.tsx b/src/hooks/use-mobile.tsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(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/src/hooks/use-toast.ts b/src/hooks/use-toast.ts
@@ -0,0 +1,194 @@
+"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<ToasterToast>
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+
+interface State {
+ toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
+
+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<ToasterToast, "id">
+
+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<State>(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/src/lib/utils.ts b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/src/theme.ts b/src/theme.ts
@@ -1,11 +0,0 @@
-'use client';
-import { createTheme } from '@mui/material/styles';
-
-const theme = createTheme({
- cssVariables: true,
- typography: {
- fontFamily: 'var(--font-roboto)',
- },
-});
-
-export default theme;
diff --git a/tailwind.config.ts b/tailwind.config.ts
@@ -0,0 +1,80 @@
+import type { Config } from "tailwindcss"
+
+const config = {
+ darkMode: ["class"],
+ content: [
+ "./pages/**/*.{ts,tsx}",
+ "./components/**/*.{ts,tsx}",
+ "./app/**/*.{ts,tsx}",
+ "./src/**/*.{ts,tsx}",
+ "*.{js,ts,jsx,tsx,mdx}",
+ ],
+ prefix: "",
+ theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
+ extend: {
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ keyframes: {
+ "accordion-down": {
+ from: { height: "0" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: "0" },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
+ },
+ },
+ },
+} satisfies Config
+
+export default config