diff --git a/.github/workflows/vercelMerge.yml b/.github/workflows/vercelMerge.yml new file mode 100644 index 0000000..8267411 --- /dev/null +++ b/.github/workflows/vercelMerge.yml @@ -0,0 +1,27 @@ +name: Deploy to vercel on merge +on: + push: + branches: + - new +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # To set more environment variables on the build, either use Vercel CLI or this: + # - uses: dkershner6/vercel-set-env-action@v1 + # with: + # token: ${{ secrets.VERCEL_TOKEN }} + # projectName: events-app + # envVariableKeys: SOME_VARIABLE + # env: + # SOME_VARIABLE: ${{ secrets.SOME_VARIABLE }} + # TARGET_SOME_VARIABLE: preview,development,production + # TYPE_SSOME_VARIABLE: encrypted + - uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + vercel-args: '--prod' + vercel-org-id: ${{ secrets.ORG_ID}} + vercel-project-id: ${{ secrets.PROJECT_ID}} \ No newline at end of file diff --git a/.github/workflows/vercelPullRequest.yml b/.github/workflows/vercelPullRequest.yml new file mode 100644 index 0000000..9b2a984 --- /dev/null +++ b/.github/workflows/vercelPullRequest.yml @@ -0,0 +1,20 @@ +name: Create vercel preview URL on pull request +on: + pull_request: + branches: + - new +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: amondnet/vercel-action@v25 + id: vercel-deploy + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + vercel-org-id: ${{ secrets.ORG_ID}} + vercel-project-id: ${{ secrets.PROJECT_ID}} + - name: preview-url + run: | + echo ${{ steps.vercel-deploy.outputs.preview-url }} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8a51f5..036096c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,16 @@ "@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", "@reduxjs/toolkit": "^2.2.6", "@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", @@ -40,6 +45,7 @@ "@types/react-dom": "^18", "autoprefixer": "^10.4.19", "postcss": "^8", + "supabase": "^1.187.3", "tailwindcss": "^3.4.1", "typescript": "^5" } @@ -398,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", @@ -415,6 +459,18 @@ "node": ">=12" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -865,6 +921,87 @@ "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", @@ -880,6 +1017,168 @@ } } }, + "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", @@ -889,6 +1188,24 @@ "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", @@ -912,6 +1229,126 @@ } } }, + "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", @@ -935,6 +1372,37 @@ } } }, + "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", @@ -953,6 +1421,114 @@ } } }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "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": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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": { + "@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-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-use-callback-ref": "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-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-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/rect": "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-use-size": { + "version": "1.1.0", + "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-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/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", @@ -1172,7 +1748,75 @@ "license": "MIT", "peer": true, "dependencies": { - "@types/node": "*" + "@types/node": "*" + } + }, + "node_modules/@vercel/analytics": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.3.1.tgz", + "integrity": "sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==", + "license": "MPL-2.0", + "dependencies": { + "server-only": "^0.0.1" + }, + "peerDependencies": { + "next": ">= 13", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "next": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@vercel/speed-insights": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@vercel/speed-insights/-/speed-insights-1.0.12.tgz", + "integrity": "sha512-ZGQ+a7bcfWJD2VYEp2R1LHvRAMyyaFBYytZXsfnbOMkeOvzGNVxUL7aVUvisIrTZjXTSsxG45DKX7yiw6nq2Jw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peerDependencies": { + "@sveltejs/kit": "^1 || ^2", + "next": ">= 13", + "react": "^18 || ^19", + "svelte": "^4", + "vue": "^3", + "vue-router": "^4" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "react": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-router": { + "optional": true + } + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/ansi-regex": { @@ -1224,6 +1868,18 @@ "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", @@ -1300,6 +1956,21 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bin-links": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", + "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", + "dev": true, + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1501,6 +2172,15 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/class-variance-authority": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", @@ -1537,6 +2217,15 @@ "node": ">=6" } }, + "node_modules/cmd-shim": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", + "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1648,6 +2337,15 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", @@ -1674,6 +2372,12 @@ "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", @@ -1783,6 +2487,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1851,6 +2578,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -1888,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", @@ -1971,6 +2719,19 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/immer": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", @@ -2003,6 +2764,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "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", @@ -2253,6 +3032,34 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2379,6 +3186,43 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-releases": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", @@ -2405,6 +3249,15 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2798,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", @@ -2823,6 +3746,15 @@ "pify": "^2.3.0" } }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2898,6 +3830,24 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", + "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "14 >=14.20 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2958,6 +3908,12 @@ "node": ">= 0.2.0" } }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3164,6 +4120,25 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supabase": { + "version": "1.187.3", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.187.3.tgz", + "integrity": "sha512-44yzHpxMrd88cUKWw3GVPPIUx6oCqgfqmN4Mlxp7a7GeNNZSTe433vmKBcj+ue3E7K08XcIyEX6G1TjhVyto+g==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "bin-links": "^4.0.3", + "https-proxy-agent": "^7.0.2", + "node-fetch": "^3.3.2", + "tar": "7.4.0" + }, + "bin": { + "supabase": "bin/supabase" + }, + "engines": { + "npm": ">=8" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3244,6 +4219,23 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tar": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.0.tgz", + "integrity": "sha512-XQs0S8fuAkQWuqhDeCdMlJXDX80D7EOVLDPVFkna9yQfzS+PHKgfxcei0jf6/+QAWcjqrnC8uM3fSAnrQl+XYg==", + "dev": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3356,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", @@ -3371,6 +4406,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -3495,6 +4539,19 @@ "node": ">=8" } }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -3517,6 +4574,15 @@ } } }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/yaml": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", diff --git a/package.json b/package.json index ec8026f..f5234fc 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,16 @@ "@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", "@reduxjs/toolkit": "^2.2.6", "@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", @@ -41,6 +46,7 @@ "@types/react-dom": "^18", "autoprefixer": "^10.4.19", "postcss": "^8", + "supabase": "^1.187.3", "tailwindcss": "^3.4.1", "typescript": "^5" } diff --git a/public/facebook.png b/public/facebook.png new file mode 100644 index 0000000..e556164 Binary files /dev/null and b/public/facebook.png differ diff --git a/public/facebook.svg b/public/facebook.svg new file mode 100644 index 0000000..837fd20 --- /dev/null +++ b/public/facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/heart.png b/public/heart.png new file mode 100644 index 0000000..fff90f7 Binary files /dev/null and b/public/heart.png differ diff --git a/public/heart.svg b/public/heart.svg new file mode 100644 index 0000000..10b0179 --- /dev/null +++ b/public/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/instagram.png b/public/instagram.png new file mode 100644 index 0000000..4982c8a Binary files /dev/null and b/public/instagram.png differ diff --git a/public/instagram.svg b/public/instagram.svg new file mode 100644 index 0000000..d774740 --- /dev/null +++ b/public/instagram.svg @@ -0,0 +1,5 @@ + + + + + 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/public/twitter.png b/public/twitter.png new file mode 100644 index 0000000..786cd35 Binary files /dev/null and b/public/twitter.png differ diff --git a/public/twitter.svg b/public/twitter.svg new file mode 100644 index 0000000..d5f1ee7 --- /dev/null +++ b/public/twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/api/logout/route.ts b/src/app/api/logout/route.ts index 0970273..e2bc148 100644 --- a/src/app/api/logout/route.ts +++ b/src/app/api/logout/route.ts @@ -6,5 +6,9 @@ export async function GET(req: NextRequest) { const { error } = await supabase.auth.signOut() - return NextResponse.redirect(new URL("/form_create", req.url)) + 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 index 8679bc0..1be0ff4 100644 --- a/src/app/api/rest/v1/isUsername/route.ts +++ b/src/app/api/rest/v1/isUsername/route.ts @@ -1,6 +1,6 @@ import {NextRequest, NextResponse} from "next/server"; -export async function POST(req: NextRequest) { +export async function POST(req: NextRequest): Promise { let response = NextResponse.next({ request: { headers: req.headers, @@ -16,8 +16,8 @@ export async function POST(req: NextRequest) { let res = await fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/is_username_exist`, { method: 'POST', headers: { - "apikey": process.env.SERVICE_KEY!, - "Authorization": `Bearer ${process.env.SERVICE_KEY!}`, + "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) diff --git a/src/app/api/rest/v1/users/route.ts b/src/app/api/rest/v1/users/route.ts index c61b750..024472b 100644 --- a/src/app/api/rest/v1/users/route.ts +++ b/src/app/api/rest/v1/users/route.ts @@ -2,7 +2,7 @@ import {NextRequest, NextResponse} from "next/server"; import { createClient } from "@/utils/supabase/server"; import { User } from "@supabase/supabase-js"; -export async function POST(req: NextRequest) { +export async function POST(req: NextRequest): Promise { let response = NextResponse.next({ request: { headers: req.headers, diff --git a/src/app/api/v1/create/event/route.ts b/src/app/api/v1/create/event/route.ts new file mode 100644 index 0000000..60ebbe5 --- /dev/null +++ b/src/app/api/v1/create/event/route.ts @@ -0,0 +1,74 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; +import { Tables } from "@/types/supabase"; + +function validateEvent(event: Tables<"events">): { + valid: boolean; + message?: string; +} { + if (!event.name) { + return { valid: false, message: "Event name is required." }; + } + if (!event.date || isNaN(Date.parse(event.date))) { + return { + valid: false, + message: "Event date is required and must be a valid date", + }; + } + // Add more validations as needed + return { valid: true }; +} + +export async function POST(request: NextRequest): Promise { + try { + const supabase = createClient(); + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser(); + if (userError || !user) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + const isAdmin = await supabase.from("users").select("admin").eq("id", user.id); + if (isAdmin.error || !isAdmin.data || isAdmin.data.length === 0 || !isAdmin.data[0].admin) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + const body = await request.json(); + const { event }: { event: Tables<"events"> } = body; + event.creator = user.id; + const validation = validateEvent(event); + if (!validation.valid) { + return NextResponse.json( + { success: false, message: validation.message }, + { status: 400 } + ); + } + + const { data, error } = await supabase + .from("events") + .insert([event]) + .select(); + if (error) { + throw new Error(error.message); + } + if (!data || data.length === 0) { + throw new Error("Failed to create event"); + } + return NextResponse.json({ + success: true, + message: "Event created successfully", + id: data[0].id, + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/delete/event/route.ts b/src/app/api/v1/delete/event/route.ts new file mode 100644 index 0000000..68bb873 --- /dev/null +++ b/src/app/api/v1/delete/event/route.ts @@ -0,0 +1,64 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; +import { Tables } from "@/types/supabase"; + +export async function DELETE(request: NextRequest): Promise { + try { + const supabase = createClient(); + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser(); + if (userError || !user) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + const isAdmin = await supabase.from("users").select("admin").eq("id", user.id); + if (isAdmin.error || !isAdmin.data || isAdmin.data.length === 0 || !isAdmin.data[0].admin) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + const url = new URL(request.url); + const id = url.searchParams.get("id"); + + if (!id) { + return NextResponse.json( + { success: false, message: "Invalid or missing event ID" }, + { status: 400 } + ); + } + + const { data: event, error: eventError } = await supabase + .from("events") + .select("id") + .eq("id", id) + .single(); + + if (eventError) { + return NextResponse.json( + { success: false, message: "Event not found" }, + { status: 404 } + ); + } + + const { error } = await supabase.from("events").delete().eq("id", id); + + if (error) { + throw new Error(error.message); + } + + return NextResponse.json({ + success: true, + message: "Event deleted successfully", + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/get/blog/route.ts b/src/app/api/v1/get/blog/route.ts new file mode 100644 index 0000000..09ad635 --- /dev/null +++ b/src/app/api/v1/get/blog/route.ts @@ -0,0 +1,63 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; + +export async function GET(request: NextRequest): Promise { + try { + const supabase = createClient(); + const url = new URL(request.url); + const id = url.searchParams.get('id'); + if (!id) { + return NextResponse.json( + { success: false, message: 'Missing blog ID' }, + { status: 400 } + ); + } + + const { data: blog, error: blogError } = await supabase + .from('blogs') + .select('*') + .eq('id', id) + .single(); + + if (blogError || !blog) { + return NextResponse.json( + { success: false, message: 'Blog not found' }, + { status: 404 } + ); + } + + const posterUrl = `${process.env.SUPABASE_STORAGE_URL}/web_data/images/${id}/poster`; + const blogFileUrl = `${process.env.SUPABASE_STORAGE_URL}/web_data/blogs/${id}/blog`; + + const { data: images, error: imagesError } = await supabase + .storage + .from('web_data') + .list(`images/${id}/`); + + if (imagesError) { + return NextResponse.json( + { success: false, message: 'Error fetching images' }, + { status: 500 } + ); + } + + const imageUrls = images.map(image => `${process.env.SUPABASE_STORAGE_URL}/web_data/blogs/${id}/images/${image.name}`); + + + return NextResponse.json({ + success: true, + message: 'Blog retrieved successfully', + blog: { + ...blog, + posterUrl, + blogFileUrl, + images: imageUrls + } + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/get/blogs/route.ts b/src/app/api/v1/get/blogs/route.ts new file mode 100644 index 0000000..540bddd --- /dev/null +++ b/src/app/api/v1/get/blogs/route.ts @@ -0,0 +1,70 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; + +export async function GET(request: NextRequest): Promise { + try { + const supabase = createClient(); + + const url = new URL(request.url); + const limit = url.searchParams.get('limit'); + const page = url.searchParams.get('page'); + + const limitNumber = limit ? parseInt(limit) : 5; + const pageNumber = page ? parseInt(page) : 1; + + if (pageNumber < 1 || limitNumber < 1) { + return NextResponse.json( + { success: false, message: 'Page and limit must be greater than 0' }, + { status: 400 } + ); + } + + const offset = (pageNumber - 1) * limitNumber; + + const { data: blogs, error: blogsError } = await supabase + .from('blogs') + .select('*') + .range(offset, offset + limitNumber - 1); + + if (blogsError) { + throw new Error(blogsError.message); + } + + + const blogPromises = blogs.map(async (blog) => { + const posterUrl = `${process.env.SUPABASE_STORAGE_URL}/web_data/images/${blog.id}/poster`; + const blogFileUrl = `${process.env.SUPABASE_STORAGE_URL}/web_data/blogs/${blog.id}/blog`; + + const { data: images, error: imagesError } = await supabase + .storage + .from('web_data') + .list(`images/${blog.id}`); + + if (imagesError) { + throw new Error(imagesError.message); + } + + const imageUrls = images.map(image => `${process.env.SUPABASE_STORAGE_URL}/web_data/images/${blog.id}/${image.name}`); + + return { + ...blog, + posterUrl, + blogFileUrl, + images: imageUrls + }; + }); + + const blogsWithAssets = await Promise.all(blogPromises); + + return NextResponse.json({ + success: true, + message: 'Blogs retrieved successfully', + blogs: blogsWithAssets, + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/get/event/route.ts b/src/app/api/v1/get/event/route.ts new file mode 100644 index 0000000..d610442 --- /dev/null +++ b/src/app/api/v1/get/event/route.ts @@ -0,0 +1,43 @@ + +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; + +export async function GET(request: NextRequest): Promise { + try { + const supabase = createClient(); + + const url = new URL(request.url); + const id = url.searchParams.get('id'); + + if (!id) { + return NextResponse.json( + { success: false, message: 'Event ID is required' }, + { status: 400 } + ); + } + + const { data: event, error: eventError } = await supabase + .from('events') + .select('*') + .eq('id', id) + .single(); + + if (eventError) { + return NextResponse.json( + { success: false, message: 'Event not found' }, + { status: 404 } + ); + } + + return NextResponse.json({ + success: true, + message: 'Event retrieved successfully', + event: event, + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/v1/get/events/route.ts b/src/app/api/v1/get/events/route.ts new file mode 100644 index 0000000..897ce27 --- /dev/null +++ b/src/app/api/v1/get/events/route.ts @@ -0,0 +1,38 @@ +// /api/v1/get/events/?category=upcoming&limit=10&page=1 +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; + +export async function GET(request: NextRequest) { + try { + const supabase = createClient(); + const { searchParams } = new URL(request.url); + const category = searchParams.get("category") || ""; + const limit = parseInt(searchParams.get("limit") || "5", 10); + const page = parseInt(searchParams.get("page") || "1", 10); + + const offset = (page - 1) * limit; + + let query = supabase + .from("events") + .select("*") + .range(offset, offset + limit - 1); + + if (category.toLocaleLowerCase() === "upcoming") { + query = query.gt("date", new Date().toISOString()); + }else if (category.toLocaleLowerCase() === "past") { + query = query.lt("date", new Date().toISOString()); + }else if (category.toLocaleLowerCase() === "ongoing") { + query = query.eq("date", new Date().toISOString()); + } + + const { data, error } = await query; + + if (error) { + throw new Error(error.message); + } + + return NextResponse.json(data); + } catch (error) { + return NextResponse.error(); + } +} diff --git a/src/app/api/v1/submit/blog/route.ts b/src/app/api/v1/submit/blog/route.ts new file mode 100644 index 0000000..758d8e0 --- /dev/null +++ b/src/app/api/v1/submit/blog/route.ts @@ -0,0 +1,113 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; +import { Tables } from "@/types/supabase"; + +function validateRequestBody(body: { + blogTable: Tables<"blogs">; + poster?: File; + blog?: File; + images?: File[]; +}): { valid: boolean; message?: string } { + if (!body.blogTable.title) { + return { valid: false, message: "Title is required" }; + } + if (!body.poster || !body.blog) { + return { valid: false, message: "Poster and blog files are required" }; + } + return { valid: true }; +} + +export async function POST(request: NextRequest): Promise { + const supabase = createClient(); + + try { + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser(); + if (userError || !user) { + return NextResponse.json( + { success: false, message: "Unauthorized" }, + { status: 401 } + ); + } + + // Use request.formData() to handle file uploads + const formData = await request.formData(); + const blogData = JSON.parse(formData.get('blogData') as string); + + const poster = formData.get('poster') as File; + const blogFile = formData.get('blog') as File; + const images = formData.getAll('images') as File[]; + + const validation = validateRequestBody({ + blogTable: blogData, + poster, + blog: blogFile, + images + }); + if (!validation.valid) { + return NextResponse.json( + { success: false, message: validation.message }, + { status: 400 } + ); + } + + blogData.writer = user.id; + + const { data: blogs, error: blogsError } = await supabase + .from("blogs") + .insert([blogData]) + .select(); + if (blogsError || !blogs || blogs.length === 0) { + throw new Error(blogsError?.message || "Failed to create blog"); + } + console.log("Here is the blog data", blogs); + const blogId = blogs[0].id; + + const uploadPromises = [ + supabase.storage + .from("web_data") + .upload(`blogs/${blogId}/blog`, blogFile), + supabase.storage + .from("web_data") + .upload(`images/${blogId}/poster`, poster), + ]; + if (images.length > 0) { + images.forEach((image: File) => { + console.log("Uploading image", image); + uploadPromises.push( + supabase.storage + .from("web_data") + .upload(`images/${blogId}/${image.name}`, image) + ); + }); + } + + const uploadResults = await Promise.all(uploadPromises); + + const [blogUploadResult, posterUploadResult, ...imageUploadResults] = + uploadResults; + if (blogUploadResult.error || posterUploadResult.error) { + throw new Error( + blogUploadResult.error?.message || posterUploadResult.error?.message + ); + } + for (const result of imageUploadResults) { + if (result.error) { + throw new Error(result.error.message); + } + } + + return NextResponse.json({ + success: true, + message: "Blog created successfully", + id: blogId, + }); + } catch (error: any) { + return NextResponse.json( + { success: false, message: error.message }, + { status: 500 } + ); + } +} diff --git a/src/app/auth/action.tsx b/src/app/auth/action.tsx index 00f5fcd..34cba23 100644 --- a/src/app/auth/action.tsx +++ b/src/app/auth/action.tsx @@ -45,7 +45,7 @@ export const SignUp = async ( if (session || user?.role !== 'authenticated') { return { error: 'Email already exists' }; } - return { user_id: user?.id, error: null }; + return { error: null }; }; export const AuthSignIn = async () => { diff --git a/src/app/auth/component/component.tsx b/src/app/auth/component/component.tsx index 4d4c47c..3a5579f 100644 --- a/src/app/auth/component/component.tsx +++ b/src/app/auth/component/component.tsx @@ -191,7 +191,7 @@ function validateEmail(email: string): boolean { function validatePassword(password: string, customCheck: number = 0): boolean { const re = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/; const special = /[!@#$%^&*()_+]/; - const upper_lower_digit = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{8,}$/; + const upper_lower_digit = /^(?=.*[a-z])(?=.*[A-Z])(?=.x\d)[A-Za-z\d]{8,}$/; if (!customCheck) return re.test(password); if (customCheck === 1) return password.length >= 8; if (customCheck === 2) return upper_lower_digit.test(password); diff --git a/src/app/auth/confirm_email/page.tsx b/src/app/auth/confirm_email/page.tsx index 00cef0a..446091e 100644 --- a/src/app/auth/confirm_email/page.tsx +++ b/src/app/auth/confirm_email/page.tsx @@ -2,9 +2,10 @@ import React, { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; -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'; @@ -23,10 +24,15 @@ export default function Confirm() { }); }, []); - const searchParams = useSearchParams(); - const user_email = searchParams.get('user_email'); + const cookies = useCookies(); + const user_email = cookies.get('email') || null; - if (!user_email) return null; + if (!user_email) return ( + + ) if(!validateEmail(user_email)) { return ( diff --git a/src/app/auth/page.tsx b/src/app/auth/page.tsx index feb03da..8dffb20 100644 --- a/src/app/auth/page.tsx +++ b/src/app/auth/page.tsx @@ -61,13 +61,13 @@ export default function Auth(){ const password = cur.password.value; setError(null); - const { user_id, error } = await SignUp({username, email, password}) + const { error } = await SignUp({username, email, password}) console.log(error); if (error) { setError(error); return false; } else { - router.push(`/auth/confirm_email?id=${user_id}&user_email=${email}`); + router.push(`/auth/confirm_email?type=signup`); } } 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/component/styles.module.css b/src/app/form_create/components/styles.module.css similarity index 100% rename from src/app/form_create/component/styles.module.css rename to src/app/form_create/components/styles.module.css diff --git a/src/app/form_create/component/userform.tsx b/src/app/form_create/components/userform.tsx similarity index 100% rename from src/app/form_create/component/userform.tsx rename to src/app/form_create/components/userform.tsx diff --git a/src/app/form_create/page.tsx b/src/app/form_create/page.tsx index 18a0fc2..576f45c 100644 --- a/src/app/form_create/page.tsx +++ b/src/app/form_create/page.tsx @@ -1,12 +1,16 @@ 'use client'; import React, { useState, useEffect, useCallback, use } from 'react'; import Link from 'next/link'; -import { useSearchParams } from 'next/navigation'; +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 './component/userform'; +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'; @@ -14,16 +18,17 @@ export default function Page() { const [user, setUser] = useState(null); const [username , setUsername] = useState(null); const [useremail , setUserEmail] = useState(null); - const [error_message, setErrorMessage] = useState(null); + const [error, setError] = useState(null); const [usernameInput, setUsernameInput] = useState(false); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); + const [loggingoff, setLogout] = useState(false); const searchParams = useSearchParams(); + const router = useRouter(); useEffect(() => { - console.log() const getUser = async () => { const { data: { user }, error } = await supabase.auth.getUser(); if (error) { @@ -52,7 +57,7 @@ export default function Page() { axios.post('/api/rest/v1/users?option=insert', {id: user?.id, username: searchParams.get('username')}) .then((res) => { if (res.data.error) { - setErrorMessage(res.data.error); + setError(res.data.error); return {error: res.data.error}; } setUsername(searchParams.get('username')); @@ -66,7 +71,6 @@ export default function Page() { let getUserData = useCallback(async () => { try { - setLoading(true); if (!user) throw new Error('No user on the session'); const { data, error, status } = await supabase @@ -90,29 +94,33 @@ export default function Page() { finally { setLoading(false); } + }, [user]); + + const logout = async () => { + setLogout(true); + axios.get('/api/logout') + .then(() => { + router.push('/auth'); + }) } - , [user]); + return ( - <> - {usernameInput ? ( - - ):( -
-

Welcome, {username}

-

{useremail}

- {error_message &&

{error_message}

} - {loading &&

Loading...

} - - Logout - -
- )} - + 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 d05d061..cd18902 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,9 +3,24 @@ import React, { Suspense } from "react"; import Loading from "@/components/loading"; 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" 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", @@ -26,13 +41,15 @@ export default function RootLayout({ /> - + }>{children} + + ); diff --git a/src/app/test_api/page.tsx b/src/app/test_api/page.tsx new file mode 100644 index 0000000..3a59582 --- /dev/null +++ b/src/app/test_api/page.tsx @@ -0,0 +1,234 @@ +"use client"; +import React, { useState } from "react"; +import { supabase } from "@/utils/supabase/client"; +import { Button } from "@/components/ui/button"; +import { Button as MUIButton } from "@mui/material"; + +export default function Page() { + const [selectedPosterFile, setSelectedPosterFile] = useState(null); + const [selectedBlogFile, setSelectedBlogFile] = useState(null); + const [selectedImages, setSelectedImages] = useState([]); + const [eventId, setEventId] = useState("7"); + const [blogId, setBlogId] = useState("24"); + const [events, setEvents] = useState([]); + const [blogs, setBlogs] = useState([]); + const createEvent = async () => { + const response = await fetch("/api/v1/create/event/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + event: { + name: "CodeStrike v6.0", + description: "A competitive coding event", + date: "2024-07-30", + duration: 180, + mode: true, + host_link: "https://www.example.com", + requirements: ["Laptop", "Notebook"], + hosted_registration: true, + register_until: "2024-05-28", + registration_link: "https://www.example.com", + }, + }), + }); + const data = await response.json(); + console.log(data); + }; + + const submitBlog = async () => { + if (!selectedPosterFile || !selectedBlogFile) { + console.error("Missing required files for blog submission"); + return; + } + + const formData = new FormData(); + formData.append("poster", selectedPosterFile); + formData.append("blog", selectedBlogFile); + selectedImages.forEach((image) => { + formData.append("images", image); + }); + formData.append("blogData", JSON.stringify({ + title: "Seventh Blog", + intro: "This is the seventh blog", + })); + + const response = await fetch("/api/v1/submit/blog/", { + method: "POST", + body: formData, + }); + + const data = await response.json(); + console.log(data); + }; + + const deleteEvent = async () => { + const response = await fetch(`/api/v1/delete/event/?id=${eventId}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + console.log(data); + }; + + const getBlog = async () => { + const response = await fetch(`/api/v1/get/blog/?id=${blogId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + console.log(data); + }; + + const getEvent = async () => { + const response = await fetch(`/api/v1/get/event/?id=${eventId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + console.log(data); + }; + + const getEvents = async () => { + const response = await fetch("/api/v1/get/events/?category=past", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + setEvents(data); + console.log(data); + }; + + const getBlogs = async () => { + const response = await fetch("/api/v1/get/blogs/?page=1&limit=5", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + setBlogs(data.blogs); + console.log(data); + }; + + const handlePosterFileChange = (event: React.ChangeEvent) => { + if (event.target.files) { + const file = event.target.files[0]; + setSelectedPosterFile(file); + } + }; + + const handleBlogFileChange = (event: React.ChangeEvent) => { + if (event.target.files) { + const file = event.target.files[0]; + setSelectedBlogFile(file); + } + }; + + const handleImageChange = (event: React.ChangeEvent) => { + if (event.target.files) { + setSelectedImages(Array.from(event.target.files)); + } + }; + + return ( +
+

Test APIs

+
+ + + + + + + +
+ + + + {selectedPosterFile && ( +
+

Selected poster file: {selectedPosterFile.name}

+
+ )} + {selectedBlogFile && ( +
+

Selected blog file: {selectedBlogFile.name}

+
+ )} + {selectedImages.length > 0 && ( +
+

Selected images:

+
    + {selectedImages.map((image) => ( +
  • {image.name}
  • + ))} +
+
+ )} + {events?.length > 0 && ( +
+

Events:

+
    + {events.map((event) => ( +
  • {event.name}
  • + ))} +
+
+ )} + {blogs.length > 0 && ( +
+

Blogs:

+
    + {blogs.map((blog) => ( +
  • {blog.title}
  • + ))} +
+
+ )} +
+ ); +} \ No newline at end of file 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(