diff --git a/package-lock.json b/package-lock.json index 9a68993..036096c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "@emotion/react": "^11.12.0", "@emotion/styled": "^11.12.0", "@mui/material": "^5.16.4", + "@radix-ui/react-avatar": "^1.1.0", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -18,8 +21,10 @@ "@supabase/ssr": "^0.4.0", "@vercel/analytics": "^1.3.1", "@vercel/speed-insights": "^1.0.12", + "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "ldrs": "^1.0.2", "lucide-react": "^0.408.0", "next": "14.2.5", "next-client-cookies": "^1.1.1", @@ -399,6 +404,44 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, + "node_modules/@floating-ui/core": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz", + "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz", + "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", + "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==", + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -845,43 +888,543 @@ "node": ">= 8" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.0.tgz", + "integrity": "sha512-Q/PbuSMk/vyAd/UoIShVGZ7StHHeRFYU7wXmi5GV+8cLXflZAEpHL/F697H1klrzxKXNtZ97vWiC0q3RKUH8UA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", + "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz", + "integrity": "sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", + "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", + "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x" + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.1.tgz", + "integrity": "sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", + "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", + "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", + "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@radix-ui/react-compose-refs": "1.1.0" }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@radix-ui/react-compose-refs": { + "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -893,68 +1436,82 @@ } } }, - "node_modules/@radix-ui/react-icons": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", - "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", - "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "@radix-ui/react-use-callback-ref": "1.1.0" }, "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" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { "optional": true } } }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.1.0" + "@radix-ui/rect": "1.1.0" }, "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" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-slot": { + "node_modules/@radix-ui/react-use-size": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" + "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -966,6 +1523,12 @@ } } }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", + "license": "MIT" + }, "node_modules/@reduxjs/toolkit": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.6.tgz", @@ -1305,6 +1868,24 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.19", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", @@ -1343,6 +1924,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -1652,6 +2244,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1759,6 +2363,21 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1909,6 +2528,26 @@ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", @@ -1925,6 +2564,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -1974,6 +2627,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -2111,6 +2773,15 @@ "node": ">=0.8.19" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -2246,6 +2917,12 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT" }, + "node_modules/ldrs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ldrs/-/ldrs-1.0.2.tgz", + "integrity": "sha512-sYJmivdkIiHrUEqTrEWccBoLdaENpzbzkABI5rk8rRxTXrg9i2xVuDvUUuhOhJY3RmQyaoxs046pM1DCRdcIpg==", + "license": "MIT" + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -2310,6 +2987,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -2864,6 +3562,12 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2947,6 +3651,76 @@ } } }, + "node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -3574,6 +4348,49 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-sync-external-store": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", diff --git a/package.json b/package.json index 4251344..f5234fc 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "@emotion/react": "^11.12.0", "@emotion/styled": "^11.12.0", "@mui/material": "^5.16.4", + "@radix-ui/react-avatar": "^1.1.0", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -19,8 +22,10 @@ "@supabase/ssr": "^0.4.0", "@vercel/analytics": "^1.3.1", "@vercel/speed-insights": "^1.0.12", + "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "ldrs": "^1.0.2", "lucide-react": "^0.408.0", "next": "14.2.5", "next-client-cookies": "^1.1.1", @@ -45,4 +50,4 @@ "tailwindcss": "^3.4.1", "typescript": "^5" } -} \ No newline at end of file +} diff --git a/public/placeholder-user.jpg b/public/placeholder-user.jpg new file mode 100644 index 0000000..6fa7543 Binary files /dev/null and b/public/placeholder-user.jpg differ diff --git a/public/profile-pic.jpg b/public/profile-pic.jpg new file mode 100644 index 0000000..cbef024 Binary files /dev/null and b/public/profile-pic.jpg differ diff --git a/src/app/api/logout/route.ts b/src/app/api/logout/route.ts new file mode 100644 index 0000000..e2bc148 --- /dev/null +++ b/src/app/api/logout/route.ts @@ -0,0 +1,14 @@ +import {NextRequest, NextResponse} from "next/server"; +import {createClient} from "@/utils/supabase/server"; + +export async function GET(req: NextRequest) { + const supabase = createClient() + + const { error } = await supabase.auth.signOut() + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ data: 'Logout successful' }, { status: 200 }); +} \ No newline at end of file diff --git a/src/app/api/rest/v1/isUsername/route.ts b/src/app/api/rest/v1/isUsername/route.ts new file mode 100644 index 0000000..1be0ff4 --- /dev/null +++ b/src/app/api/rest/v1/isUsername/route.ts @@ -0,0 +1,29 @@ +import {NextRequest, NextResponse} from "next/server"; + +export async function POST(req: NextRequest): Promise { + let response = NextResponse.next({ + request: { + headers: req.headers, + }, + }) + + const body = await req.json() + + if ( !body.username ) { + return NextResponse.json({ error: 'username is required' }) + } + + let res = await fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/is_username_exist`, { + method: 'POST', + headers: { + "apikey": process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + "Authorization": `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body) + }); + + let data = await res.json(); + + return NextResponse.json({ state: data }); +} \ No newline at end of file diff --git a/src/app/api/rest/v1/users/route.ts b/src/app/api/rest/v1/users/route.ts new file mode 100644 index 0000000..024472b --- /dev/null +++ b/src/app/api/rest/v1/users/route.ts @@ -0,0 +1,39 @@ +import {NextRequest, NextResponse} from "next/server"; +import { createClient } from "@/utils/supabase/server"; +import { User } from "@supabase/supabase-js"; + +export async function POST(req: NextRequest): Promise { + let response = NextResponse.next({ + request: { + headers: req.headers, + }, + }) + + const body = await req.json() + const supabase = createClient(); + const searchParams = req.nextUrl.searchParams; + const option = searchParams.get('option'); + + let data: User | User[] | null = null; + + if ( option === 'insert' ) { + const { data: user, error: error } = await supabase + .from('users') + .insert([{id: body.id, username: body.username, admin: body.admin}]) + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + data = user; + } else if ( option === 'update' ) { + const { data: user, error: error } = await supabase + .from('users') + .update({username: body.username}) + .match({id: body.id}) + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + data = user; + } + + return NextResponse.json({ data: data }); +} \ No newline at end of file diff --git a/src/app/auth/action.tsx b/src/app/auth/action.tsx index 601847b..34cba23 100644 --- a/src/app/auth/action.tsx +++ b/src/app/auth/action.tsx @@ -21,61 +21,53 @@ export const Login = async ( export const SignUp = async ( credentials : { + username: string | null, email: string , password: string , }) => { const origin = headers().get("origin"); - const referer = headers().get("referer"); - const query = referer?.split('?')[1].split('&'); - const org = query?.find((q) => q.includes('organisation')) || ''; - + const username = credentials.username; const supabase = createClient(); - const { data: { session }, error, } = await supabase.auth.signUp({ + + const { data: { user, session }, error, } = await supabase.auth.signUp({ email: credentials.email as string, password: credentials.password as string, options: { - emailRedirectTo: `${origin}/auth/callback`, + emailRedirectTo: `${origin}/auth/confirm`, + data: { username: username }, }, }); + if (error) { + console.log(error); return { error: error.message }; } - if (session) { + if (session || user?.role !== 'authenticated') { return { error: 'Email already exists' }; } return { error: null }; }; export const AuthSignIn = async () => { - const origin = headers().get("origin"); - const gmail = cookies()?.get('email')?.value || ''; - - const supabase = createClient(); - const { data, error } = await supabase.auth.signInWithOAuth({ - provider: 'google', - options: { - redirectTo: `${origin}/auth/callback`, - queryParams: { - include_granted_scopes: 'true', - access_type: 'offline', - prompt: 'select_account', - login_hint: gmail, - }, - }, - }); - if (error) return { error: error.message, url: null }; - if (data.url) return { error: null, url: data.url }; - return { error: 'Error signing in', url: null }; -} - -export async function usernameExisits(username: string): Promise { + const origin = headers().get("origin"); + const gmail = cookies()?.get('email')?.value || ''; + const supabase = createClient(); - let { data, error } = await supabase - .rpc('is_username_exist', { - username: username, - }); - if (error) console.error(error) - return data; + const { data, error } = await supabase.auth.signInWithOAuth({ + provider: 'google', + options: { + redirectTo: `${origin}/auth/callback`, + queryParams: { + include_granted_scopes: 'true', + access_type: 'offline', + prompt: 'select_account', + login_hint: gmail, + }, + }, + }); + if (error) return { error: error.message, url: null }; + if (data.url) return { error: null, url: data.url }; + return { error: 'Error signing in', url: null }; } export const checkEmailForOrganisation = ( diff --git a/src/app/auth/callback/route.ts b/src/app/auth/callback/route.ts index 96cfad6..4bb206f 100644 --- a/src/app/auth/callback/route.ts +++ b/src/app/auth/callback/route.ts @@ -23,5 +23,6 @@ export async function GET(req: NextRequest, res: NextResponse) { console.log('x ',error); } } + return NextResponse.redirect(url); } \ No newline at end of file diff --git a/src/app/auth/component/component.tsx b/src/app/auth/component/component.tsx index 9b78845..3a5579f 100644 --- a/src/app/auth/component/component.tsx +++ b/src/app/auth/component/component.tsx @@ -1,15 +1,16 @@ -import { FormEvent, use, useEffect, useState } from "react"; -import { usernameExisits } from "@/app/auth/action"; +import React, { FormEvent, use, useEffect, useState } from "react"; +import axios from "axios"; import { Label } from "@/components/ui/label" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" +import Loader from '@/components/ui/loader' -import { VscEye, VscEyeClosed } from "react-icons/vsc"; -import CircularProgress from '@mui/material/CircularProgress'; +import { VscEye, VscEyeClosed } from "react-icons/vsc" import styles from './styles.module.css' import { cn } from "@/lib/utils" + interface Props { auth: string | null SignUp: (e: EventTarget & HTMLFormElement) => Promise @@ -89,23 +90,22 @@ export function Component( props : Props) { return ; } setLoading(true); - const data = await usernameExisits(username) - console.log('data = ',data); - if (data) { - const nextUserSibling = current.username.nextSibling as HTMLElement; + const res = await axios.post('/api/rest/v1/isUsername', {username: username}); + + if (res.data.state) { + const nextUserSibling = current.username.nextElementSibling as HTMLElement; nextUserSibling.innerText = 'Username already exists'; } else { - const nextUserSibling = current.username.nextSibling as HTMLElement; + const nextUserSibling = current.username.nextElementSibling as HTMLElement; nextUserSibling.innerText = ''; if (validatePassword(password) && password.length>=8) { - const bool = await props.SignUp(current) if (!bool) setLoading(false); - const nextSibling = current.password.nextSibling as HTMLElement; + const nextSibling = current.password.nextElementSibling as HTMLElement; nextSibling.innerText = ''; } else { - const nextSibling = current.password.nextSibling as HTMLElement; + const nextSibling = current.password.nextElementSibling as HTMLElement; if (!validatePassword(password, 1)) { nextSibling.innerText = 'Password must be at least 8 characters long'; } else if (!validatePassword(password, 2)) { @@ -171,7 +171,8 @@ export function Component( props : Props) { className="group relative flex w-full justify-center rounded-[8px] bg-primary py-6 px-4 text-md font-bold text-primary-foreground transition-colors focus:opacity-90 focus:outline-none" disabled={loading} > - {loading ? : structure.button.text} + {loading ? + : structure.button.text} diff --git a/src/app/auth/component/oauth.tsx b/src/app/auth/component/oauth.tsx index 9bc48cb..faba5ee 100644 --- a/src/app/auth/component/oauth.tsx +++ b/src/app/auth/component/oauth.tsx @@ -1,10 +1,9 @@ import React, { useState } from "react"; import { Button } from "@/components/ui/button" +import Loader from '@/components/ui/loader' // -- icons -- -import CircularProgress from '@mui/material/CircularProgress'; import { FcGoogle } from "react-icons/fc"; - import { JSX, SVGProps } from "react" interface Props{ @@ -34,10 +33,8 @@ export function OAuthComponent(props : Props) { variant="outline" className="group relative flex w-full justify-center rounded-[8px] border border-input bg-background py-6 px-4 text-md font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus:outline-none focus:opacity-90" > - {loading ? : - <> - oogle - } + {loading ? : + <>oogle} ) diff --git a/src/app/auth/confirm/route.ts b/src/app/auth/confirm/route.ts index 55a8b92..b654759 100644 --- a/src/app/auth/confirm/route.ts +++ b/src/app/auth/confirm/route.ts @@ -1,6 +1,5 @@ import { type EmailOtpType } from '@supabase/supabase-js' import { type NextRequest, NextResponse } from 'next/server' -import { cookies } from 'next/headers' import { createClient } from '@/utils/supabase/server' @@ -8,7 +7,7 @@ export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url) const token_hash = searchParams.get('token_hash') const type = searchParams.get('type') as EmailOtpType | null - const next = searchParams.get('next') ?? '/create_form' + const next = searchParams.get('next') ?? '/form_create' const redirectTo = req.nextUrl.clone() redirectTo.pathname = next @@ -21,18 +20,6 @@ export async function GET(req: NextRequest) { type: type || 'email', token_hash: token_hash , }) - const user = data?.user - if (!error && user) { - const getCookies = cookies(); - const username = getCookies?.get('username')?.value; - getCookies?.delete('username'); - const { error: insertError } = await supabase - .from('users') - .insert([ - { id: user?.id, username: username } - ]); - if (insertError) console.error('Error inserting user data:', insertError.message); - } if (!error) { redirectTo.searchParams.delete('next') return NextResponse.redirect(redirectTo) diff --git a/src/app/auth/confirm_email/page.tsx b/src/app/auth/confirm_email/page.tsx index 9928445..446091e 100644 --- a/src/app/auth/confirm_email/page.tsx +++ b/src/app/auth/confirm_email/page.tsx @@ -1,9 +1,11 @@ 'use client'; -import React, { useState, useEffect, use } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; -import { Button } from '@/components/ui/button'; -import { useSearchParams } from 'next/navigation'; + +import { useCookies } from 'next-client-cookies'; import { supabase } from '@/utils/supabase/client'; +import { Button } from '@/components/ui/button'; +import ErrorDialog from '@/components/error_dialog'; import Image from 'next/image'; @@ -12,10 +14,27 @@ export default function Confirm() { const [error, setError] = useState(null); const [countdown, setCountdown] = useState(10); - const searchParams = useSearchParams(); - const email = searchParams.get('email') || 'xxxx-xxxx-xxxx-xxxx'; + useEffect(() => { + supabase.auth.getUser().then(({ data , error }) => { + if (error) { + console.log(error.message); + return; + } + console.log(data); + }); + }, []); + + const cookies = useCookies(); + const user_email = cookies.get('email') || null; + + if (!user_email) return ( + + ) - if(!validateEmail(email)) { + if(!validateEmail(user_email)) { return (
@@ -23,7 +42,7 @@ export default function Confirm() { Invalid email address:

- {email} + {user_email}

); @@ -49,44 +68,47 @@ export default function Confirm() { }, [loading, countdown]); const resent = async () => { + const origin = window.location.origin; setLoading(true) const { error } = await supabase.auth.resend({ type: 'signup', - email: email, + email: user_email, options: { - emailRedirectTo: '/auth/confirm_email?email=' + email, + emailRedirectTo: `${origin}/auth/confirm`, } }) if (error) { setError(error.message) + } else { + setError(null) } } return (
-
+

Email Sent!

- We've sent a confirmation email to {email}. Click the link in + We've sent a confirmation email to {user_email}. Click the link in the email to verify your account.

- Return to Home + Return to Login
diff --git a/src/app/auth/page.tsx b/src/app/auth/page.tsx index d3f1d0f..8dffb20 100644 --- a/src/app/auth/page.tsx +++ b/src/app/auth/page.tsx @@ -26,6 +26,11 @@ export default function Auth(){ const auth = searchParams.get('auth'); const organisation = searchParams.get('organisation'); const oauthhidden = organisation==='iiitv'?true:false; + const err = searchParams.get('error'); + + useEffect(() => { + if (err) setError(err); + }, [err]); const cookies = useCookies(); const email = cookies.get('email') || null; @@ -48,22 +53,24 @@ export default function Auth(){ } else { router.push('/form_create'); } - } + } + const signup = async (cur: EventTarget & HTMLFormElement) => { const email = cur.email.value; const username = cur.username.value; const password = cur.password.value; - cookies.set('username', username); setError(null); - const { error } = await SignUp({email, password}) + const { error } = await SignUp({username, email, password}) + console.log(error); if (error) { setError(error); return false; } else { - router.push('/auth/confirm_email?email=' + email); + router.push(`/auth/confirm_email?type=signup`); } } + const Authsignin = async () => { setError(null); const { error, url } = await AuthSignIn(); @@ -88,13 +95,13 @@ export default function Auth(){ EmailSubmit={emailSubmit} email={email}> - { auth && -
- {auth === 'signup' ? -

Have an account? Login

: -

Don't have an account? Create here

} -
- } + { auth && (
+
+ {auth === 'signup' ? +

Have an account? Login

: +

Don't have an account? Create here

} +
+
)} {oauthhidden && } diff --git a/src/app/form_create/components/dropdown_menu.tsx b/src/app/form_create/components/dropdown_menu.tsx new file mode 100644 index 0000000..e92d885 --- /dev/null +++ b/src/app/form_create/components/dropdown_menu.tsx @@ -0,0 +1,67 @@ +import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuItem } from "@/components/ui/dropdown-menu" +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" +import { Button } from "@/components/ui/button" +import Link from "next/link" +import { JSX, SVGProps, useEffect, useState } from "react" + +interface Props + extends React.HTMLAttributes { + email: string | null, + username: string | null, + onLogout: () => void, +} + +export function Dropdown_Menu({ + email, + username, + onLogout: logout, +}: Props ) { + + return ( + <> + + + + + +
+ + + JD + +
+
{username || 'username'}
+
{email || 'user email'}
+
+
+ + + +
+ Profile + + + + +
+ Settings + + + + + +
+ Sign out + + + + + + ) +} diff --git a/src/app/form_create/components/form.tsx b/src/app/form_create/components/form.tsx new file mode 100644 index 0000000..babc943 --- /dev/null +++ b/src/app/form_create/components/form.tsx @@ -0,0 +1,63 @@ +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" +import { Card, CardContent, CardFooter, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" +import { Label } from "@/components/ui/label" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" + +interface Props + extends React.HTMLAttributes { + username: string | null, + email: string | null, + disabled: boolean, +} + +export function Form({ + username, + email, + disabled, +}: Props) { + return ( +
+
+
+ + + JD + +
+
{username || 'Loading...'}
+
{email}
+
+
+
+

Create your Form

+

Enter your details below to get started.

+
+
+ + + Please fill out the details below to create questions. + + +
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+
+
+ ) +} diff --git a/src/app/form_create/components/loggingout.tsx b/src/app/form_create/components/loggingout.tsx new file mode 100644 index 0000000..117c5e6 --- /dev/null +++ b/src/app/form_create/components/loggingout.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import Link from 'next/link' +import { SVGProps } from 'react' +import Loader from '@/components/ui/loader' + +export function LoggingOut() { + return ( +
+
+
+ +

Logging out...

+

You are being logged out of your account.

+
+
+
+ ) +} + +function LogOutIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + ) +} \ No newline at end of file diff --git a/src/app/form_create/components/styles.module.css b/src/app/form_create/components/styles.module.css new file mode 100644 index 0000000..e13e8fd --- /dev/null +++ b/src/app/form_create/components/styles.module.css @@ -0,0 +1,27 @@ +.error { + position: absolute; + left:6px; + top: -17.5px; + height: fit-content !important; + color:red !important; + font-size:0.7rem !important; + word-wrap: no-wrap !important; + white-space: nowrap !important; + z-index: 10; +} +.inputicon { + position:absolute; + top: 50%; + color: rgba(var(--black-light), .45); + font-size: 1rem; + transform: translateY(-50%); + transition: 0.5s ease; +} +.eyeicon { + right: 15px; + padding: 3px; + cursor: pointer; +} +.eyeicon:hover { + color: rgba(var(--black-light), .65); +} \ No newline at end of file diff --git a/src/app/form_create/components/userform.tsx b/src/app/form_create/components/userform.tsx new file mode 100644 index 0000000..c8d6e9d --- /dev/null +++ b/src/app/form_create/components/userform.tsx @@ -0,0 +1,120 @@ +import React, { useState, useEffect, useCallback, use } from "react"; +import axios from "axios"; + +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import Loader from '@/components/ui/loader' + +import styles from './styles.module.css' +import { cn } from "@/lib/utils"; + + +interface PropsType { + email: string | null, + user_id: string, +} + +export function UserForm( {user_id, email}: PropsType) { + const [username, setUsername] = useState(null); + const [error, setError] = useState(null); + const [disabled, setDisabled] = useState(true); + const [loading, setLoading] = useState(false); + + const controller = new AbortController(); + const signal = controller.signal; + + const isExist = useCallback(async () => { + const res = await axios.post('/api/rest/v1/isUsername', {username: username}, {signal}); + if (res.data.state) { + setDisabled(true); + setError('Username already exists'); + } else { + setDisabled(false); + setError(''); + } + }, [username]); + + + useEffect(() => { + let timer: NodeJS.Timeout; + if (username) { + timer = setTimeout(() => { + isExist(); + }, 400) + } + return () => { + controller.abort(); + clearTimeout(timer); + } + }, [username]); + + useEffect(() => { + if (!username) { + console.log('username is empty'); + setError(''); + setDisabled(true); + } + }, [username, disabled, error]); + + return ( +
+
+
+

+ Welcome, {email?.split('@')[0]} +

+

Please enter an username

+
+
{ + e.preventDefault(); + setLoading(true); + axios.post(`/api/rest/v1/users?option=insert`, {id: user_id, username: username}) + .then((res) => { + if (res.data.error) { + setError(res.data.error); + setLoading(false); + } else { + setLoading(false); + window.location.href = '/form_create'; + } + }) + .catch((error) => { + setError(error.message); + setLoading(false); + }); + }}> +
+ + setUsername(e.target.value)} + id={'username'} + name={'username'} + type={'text'} + autoComplete={'username'} + required + placeholder={'Username'} + disabled={loading} + className={cn("rounded-[8px] border border-input bg-background px-4 py-6 text-foreground placeholder-muted-foreground focus:z-10 focus:border-primary focus:outline-none sm:text-sm")} + /> + {error} +
+
+ +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/form_create/page.tsx b/src/app/form_create/page.tsx index be456f2..576f45c 100644 --- a/src/app/form_create/page.tsx +++ b/src/app/form_create/page.tsx @@ -1,25 +1,126 @@ 'use client'; -import React from 'react'; +import React, { useState, useEffect, useCallback, use } from 'react'; +import Link from 'next/link'; +import { useSearchParams, useRouter } from 'next/navigation'; import { supabase } from '@/utils/supabase/client'; +import { User } from '@supabase/supabase-js'; +import axios from 'axios'; + +import { UserForm } from './components/userform'; +import { Form } from './components/form'; +import { Dropdown_Menu } from "./components/dropdown_menu"; +import { LoggingOut } from './components/loggingout'; +import ErrorDialog from '@/components/error_dialog'; + import { cn } from '@/lib/utils'; -import { Button } from '@/components/ui/button'; +export default function Page() { + const [user, setUser] = useState(null); + const [username , setUsername] = useState(null); + const [useremail , setUserEmail] = useState(null); + const [error, setError] = useState(null); + + const [usernameInput, setUsernameInput] = useState(false); + + const [loading, setLoading] = useState(true); + const [loggingoff, setLogout] = useState(false); + + const searchParams = useSearchParams(); + const router = useRouter(); + + useEffect(() => { + const getUser = async () => { + const { data: { user }, error } = await supabase.auth.getUser(); + if (error) { + console.log(error.message); + return; + } + setUser(user); + setUserEmail(user?.email || null); + } + getUser(); + }, []); + + useEffect(() => { + return () => { + + if ( user ) { + getUserData(); + } + }; + }, [user]); + + const getUsernameFromUser = async () => { + if (searchParams.get('username')) { + const res = await axios.post('/api/rest/v1/isUsername', {username: searchParams.get('username')}); + if (!res.data.state) { + axios.post('/api/rest/v1/users?option=insert', {id: user?.id, username: searchParams.get('username')}) + .then((res) => { + if (res.data.error) { + setError(res.data.error); + return {error: res.data.error}; + } + setUsername(searchParams.get('username')); + }) + return {error: null}; + } + } + setUsernameInput(true); + return {error: null}; + } + + let getUserData = useCallback(async () => { + try { + if (!user) throw new Error('No user on the session'); + + const { data, error, status } = await supabase + .from('users') + .select('username') + .eq('id', user.id) + .single(); + + if (status === 406) { + const { error } = await getUsernameFromUser() + if (error) throw error;return; + } + if (error && status !== 406 ) throw error; + if (data) { + setUsername(data.username); + } + } + catch (error: any) { + console.log(error.message); + } + finally { + setLoading(false); + } + }, [user]); + + const logout = async () => { + setLogout(true); + axios.get('/api/logout') + .then(() => { + router.push('/auth'); + }) + } -export default async function Page() { - const { data : {user}} = await supabase.auth.getUser(); - const { data, error } = await supabase.from('users').select('username').eq('id', user?.id); - const userdata = data && data[0]; - const username = userdata && ( 'username' in userdata && userdata.username || user?.identities); - const Logout = async () => { - const { error } = await supabase.auth.signOut(); - if (error) console.error('Sign out error', error.message); - window.location.reload(); - }; return ( -
-

Welcome, {username}

- -
+ loggingoff ? + ( + + ):( + <> + {usernameInput ? ( + + ):( + <> + +
+ + )} + {error && } + + ) ); } \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 6aef8af..32f1f7c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -6,19 +6,22 @@ :root { --background: #201F31; --foreground: #fcfdff; - --card: #ffffff; - --card-foreground: #1d0209; + --card: #201F31; + --card-foreground: #fcfdff; --popover: #ffffff; --popover-foreground: #1d0209; --primary: #ffbade; --primary-foreground: #1c2230; - --secondary: #f0f4f8; - --secondary-foreground: #201F31; + --secondary: #1c1b2b; + --secondary-foreground: #fcfdff; --muted: #f0f4f8; + --muted-hover: #e6eaf0; --muted-foreground: #707a8a; --accent: #f0f4f8; + --accent-hover: #e6eaf0; --accent-foreground: #1c2230; - --destructive: #e60000; + --destructive: #751a1a; + --destructive-hover: #8c1f1f; --destructive-foreground: #fcfdff; --border: #e6eaf0; --input: #e6eaf0; @@ -30,41 +33,19 @@ --chart-4: #ffcc33; --chart-5: #ff9933; } - - .dark { - --background: #201F31; - --foreground: #fcfdff; - --card: #0d020a; - --card-foreground: #fcfdff; - --popover: #0d020a; - --popover-foreground: #fcfdff; - --primary: #ffbade; - --primary-foreground: #1c2230; - --secondary: #2a2f3d; - --secondary-foreground: #fcfdff; - --muted: #2a2f3d; - --muted-foreground: #a4a9b5; - --accent: #2a2f3d; - --accent-foreground: #fcfdff; - --destructive: #e60000; - --destructive-foreground: #fcfdff; - --border: #2a2f3d; - --input: #2a2f3d; - --ring: #d9e1f2; - --chart-1: #5a9fd3; - --chart-2: #4c9a70; - --chart-3: #e6a23c; - --chart-4: #8a5aa2; - --chart-5: #d64161; - } } @layer base { * { @apply border-border; } + body { - @apply bg-background text-foreground; + @apply bg-background text-foreground font-body; + } + + h1, h2, h3, h4, h5, h6 { + @apply font-heading; } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3fbafd1..39afd73 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,10 +5,23 @@ import { Inter } from "next/font/google"; import "./globals.css"; import { Analytics } from "@vercel/analytics/react" import { SpeedInsights } from "@vercel/speed-insights/next" +import { cn } from "@/lib/utils" import Navbar from "@/components/navbar" const inter = Inter({ subsets: ["latin"] }); +const fontHeading = Inter({ + subsets: ['latin'], + display: 'swap', + variable: '--font-heading', +}) + +const fontBody = Inter({ + subsets: ['latin'], + display: 'swap', + variable: '--font-body', +}) + export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", @@ -28,9 +41,12 @@ export default function RootLayout({ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - - - + + }>{children} diff --git a/src/components/error_dialog.tsx b/src/components/error_dialog.tsx new file mode 100644 index 0000000..caca38e --- /dev/null +++ b/src/components/error_dialog.tsx @@ -0,0 +1,96 @@ + +import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog" +import { useRouter } from "next/navigation" +import { Button } from "@/components/ui/button" +import { JSX, SVGProps } from "react" +import PropsTypes from "prop-types" + + +ErrorDialog.propTypes = { + status: PropsTypes.number, + error: PropsTypes.string +} + +ErrorDialog.defaultProps = { + status: 500, + error: null, + error_message: "unknown" +} + +export default function ErrorDialog({ status, error, error_message} : { status: number, error: string | null, error_message: string }) { + const router = useRouter() + const ReportError = () => { + if( !window.location.href.includes('report') ) { + router.push(`/report?status=${status}&error=${!error?error_message:error}&path=${window.location.href}`) + } + } + return ( + + +
+
+ +

Oops, something went wrong!

+
+
+
+ + +
+ + Oops, something went wrong! +
+ + {error_message == "unknown" ? "We're sorry, but an unexpected error has occurred. Please try again later or contact support if the issue persists." : error_message} + +
+ + + + +
+
+ ) +} + +function TriangleAlertIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + ) +} + + +function XIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + ) +} \ No newline at end of file diff --git a/src/components/ui/alertv2.tsx b/src/components/ui/alertv2.tsx new file mode 100644 index 0000000..41fa7e0 --- /dev/null +++ b/src/components/ui/alertv2.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 & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 0000000..30a6485 --- /dev/null +++ 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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 53250fd..c7102e4 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,22 +5,22 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center 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", + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-md font-medium ring-offset-background transition-colors focus-visible:outline-none disabled:pointer-events-none", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", + default: "bg-primary text-primary-foreground hover:bg-primary-hover", destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", + "bg-destructive text-destructive-foreground hover:bg-destructive-hover", 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", + "bg-secondary text-secondary-foreground hover:bg-secondary", + ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { - default: "h-10 px-4 py-2", + default: "h-10 px-4 py-6", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10", diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..afa13ec --- /dev/null +++ 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 +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..01ff19c --- /dev/null +++ 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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..f69a0d6 --- /dev/null +++ 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, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +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/input.tsx b/src/components/ui/input.tsx index 8b55b6a..8d953bd 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -12,7 +12,7 @@ const Input = React.forwardRef( { + async function getLoader() { + const { ring } = await import('ldrs') + ring.register() + } + getLoader() + }, []) + return ( + + ) +} \ No newline at end of file diff --git a/src/utils/supabase/middleware.ts b/src/utils/supabase/middleware.ts index 608131a..cce4279 100644 --- a/src/utils/supabase/middleware.ts +++ b/src/utils/supabase/middleware.ts @@ -1,5 +1,5 @@ import { createServerClient, type CookieOptions } from '@supabase/ssr' -import { NextResponse, type NextRequest } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' export async function updateSession(request: NextRequest) { let response = NextResponse.next({ @@ -54,18 +54,45 @@ export async function updateSession(request: NextRequest) { } ) - const { data: {user}} = await supabase.auth.getUser() - if (user && !user?.confirmed_at && request.nextUrl.pathname === '/form_create') { - response = NextResponse.redirect(new URL('/auth/confirm_email', request.nextUrl.href)) - } else if (user?.confirmed_at && request.nextUrl.pathname === '/auth/confirm_email') { - response = NextResponse.redirect(new URL('/form_create', request.nextUrl.href)) - } else if (user && request.nextUrl.pathname === '/auth') { + + if (request.nextUrl.pathname === '/auth/callback' || request.nextUrl.pathname === '/auth/confirm') return response + + + let { data: {user}, error} = await supabase.auth.getUser() + + + if (user && (request.nextUrl.pathname === '/auth/confirm_email' || request.nextUrl.pathname === '/auth')) { response = NextResponse.redirect(new URL('/form_create', request.nextUrl.href)) - } else if (!user && request.nextUrl.pathname === '/form_create') { + } else if (!user && ( request.nextUrl.pathname === '/form_create' )) { response = NextResponse.redirect(new URL('/auth', request.nextUrl.href)) }else if (!user && request.nextUrl.pathname === '/test_api') { response = NextResponse.redirect(new URL('/auth', request.nextUrl.href)) } + // const searchParams = request.nextUrl.searchParams + + // if (request.nextUrl.pathname === '/auth/confirm_email') { + // if (searchParams.get('user_email')) { + // if (!searchParams.get('id')) return NextResponse.redirect(new URL('/auth?error=No+ID+provided', request.nextUrl.href)) + // let res = await fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/auth/v1/admin/users/${searchParams.get('id')}`, { + // method: 'GET', + // headers: { + // "apikey": process.env.SERVICE_KEY!, + // "Authorization": `Bearer ${process.env.SERVICE_KEY!}`, + // "Content-Type": "application/json", + // } + // }); + + // const res_user = await res.json(); + // if (res.status === 404 || !res_user?.email ) return NextResponse.redirect(new URL('/auth?error=user+not+found', request.nextUrl.href)) + // if ( request.nextUrl.searchParams.get('user_email') !== res_user?.email ) { + // request.nextUrl.searchParams.set('user_email', res_user?.email as string) + // response = NextResponse.redirect(new URL(request.nextUrl.href)) + // return response + // } + // if (res_user?.email_confirmed_at) return NextResponse.redirect(new URL('/auth?auth=login', request.nextUrl.href)) + // } + // } + return response } \ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts index f1b8c32..a3c9505 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,4 +1,5 @@ import type { Config } from "tailwindcss"; +import { fontFamily } from "tailwindcss/defaultTheme"; const config: Config = { content: [ @@ -8,12 +9,15 @@ const config: Config = { ], theme: { extend: { + fontFamily: { + heading: ['var(--font-heading)', ...fontFamily.sans], + body: ['var(--font-body)', ...fontFamily.sans], + }, backgroundImage: { "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", "gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", }, - }, colors: { "border": "var(--border)", "input": "var(--input)", @@ -30,6 +34,7 @@ const config: Config = { }, "destructive": { "DEFAULT": "var(--destructive)", + "hover": "var(--destructive-hover)", "foreground": "var(--destructive-foreground)" }, "muted": { @@ -38,6 +43,7 @@ const config: Config = { }, "accent": { "DEFAULT": "var(--accent)", + "hover": "var(--accent-hover)", "foreground": "var(--accent-foreground)" }, "popover": { @@ -72,6 +78,7 @@ const config: Config = { } } } + }, }, plugins: [ require("tailwindcss-animate"),