diff --git a/.env.example b/.env.example index 5176e2f..a77ccb7 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,9 @@ NEXT_PUBLIC_BASE_URL=http://localhost:3000 NEXT_PUBLIC_APPWRITE_URL= NEXT_PUBLIC_APPWRITE_PROJECT_ID= -APPWRITE_DATABASE_ID= +NEXT_PUBLIC_APPWRITE_DATABASE_ID= +NEXT_PUBLIC_APPWRITE_COLLECTION_APP_ID= +NEXT_PUBLIC_APPWRITE_COLLECTION_RATINGS_ID= +NEXT_PUBLIC_APPWRITE_COLLECTION_REPOS_ID= + APPWRITE_API_KEY= -APPWRITE_COLLECTION_APP_ID= -APPWRITE_COLLECTION_RATINGS_ID= -APPWRITE_COLLECTION_REPOS_ID= diff --git a/README.md b/README.md index 1f99c41..5116308 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ When considering your rating here are a few points you might like to think about - [x] List popular Repos - [x] Leaderboard of most active users - [x] README badges -- [ ] Search Repos (coming soon) +- [x] Search Repos Want to see any other features? [Open an issue](https://github.com/EddieHubCommunity/RepoRater/issues) and let us know. @@ -30,7 +30,7 @@ Want to see any other features? [Open an issue](https://github.com/EddieHubCommu - [NextJS](https://nextjs.org) - [Appwrite](https://appwrite.io) -- [DaisyUI](https://daisyui.com) ([Tailwind](https://tailwindcss.com)) +- [Tailwind](https://tailwindcss.com) ## Quickstart for development @@ -51,12 +51,22 @@ Want to see any other features? [Open an issue](https://github.com/EddieHubCommu - `name`: string (128, required) - `votes`: integer - `rating`: float - - `owner`: string (128) - - `logo`: string (512) + <<<<<<< HEAD + - `owner`: string + - `logo`: string 6. Copy `.env.example` template file to `.env` -7. Get you private keys from Appwrite (Overview > Integrations > "API Keys" tab > "+ Create API key" button) and add them to `.env` template (all data are required) +7. Get you private keys from Appwrite and add them to `.env` template (all data are required) 8. Create an OAuth app on GitHub and connect it with Appwrite Authentication -9. Run the development server with: +9. On collections `ratings` and `repos` enable read permissions for `all` users +10. Create index on collection `repos`, named `url_search` with the attribute `url` and type `fulltext` +11. # Run the development server with: + - `owner`: string (128) + - `logo`: string (512) +12. Copy `.env.example` template file to `.env` +13. Get you private keys from Appwrite (Overview > Integrations > "API Keys" tab > "+ Create API key" button) and add them to `.env` template (all data are required) +14. Create an OAuth app on GitHub and connect it with Appwrite Authentication +15. Run the development server with: + > > > > > > > prototype ```bash npm ci @@ -136,7 +146,7 @@ jobs: More details https://github.com/xkrishguptaa/action-repo-rater -## API (3rd party apps) +## API (for your 3rd party apps) You can consume our data for your own apps. @@ -172,6 +182,16 @@ Optional paramater `?minimumVotes=5` (default is `5`) ```json [ + { + "url": "https://github.com/appwrite/appwrite", + "logo": "https://avatars.githubusercontent.com/u/25003669?v=4", + "description": "Build like a team of hundreds_", + "rating": 4.9, + "votes": 310, + "owner": "appwrite", + "name": "appwrite", + "badgeViews": 321 + }, { "url": "https://github.com/EddieHubCommunity/BioDrop", "logo": "https://avatars.githubusercontent.com/u/66388388?v=4", @@ -180,21 +200,56 @@ Optional paramater `?minimumVotes=5` (default is `5`) "votes": 49, "owner": "EddieHubCommunity", "name": "BioDrop", - "badgeViews": 143 + "badgeViews": 109 + } +] +``` + +### All Repos with Search + +GET https://repo-rater.eddiehub.org/api/repos + +Optional paramater `?keyword=EddieHub` + +```json +[ + { + "url": "https://github.com/EddieHubCommunity/BioDrop", + "logo": "https://avatars.githubusercontent.com/u/66388388?v=4", + "description": "Connect to your audience with a single link. Showcase the content you create and your projects in one place. Make it easier for people to find, follow and subscribe.", + "rating": 4.75, + "votes": 49, + "owner": "EddieHubCommunity", + "name": "BioDrop", + "badgeViews": 321 }, { - "url": "https://github.com/appwrite/appwrite", - "logo": "https://avatars.githubusercontent.com/u/25003669?v=4", - "description": "Build like a team of hundreds_", - "rating": 4.3333333333333, - "votes": 310, - "owner": "appwrite", - "name": "appwrite", - "badgeViews": 768 + "url": "https://github.com/EddieHubCommunity/RepoRater", + "logo": "https://avatars.githubusercontent.com/u/66388388?v=4", + "description": "Connect to your audience with a single link. Showcase the content you create and your projects in one place. Make it easier for people to find, follow and subscribe.", + "rating": 4.6, + "votes": 12, + "owner": "EddieHubCommunity", + "name": "RepoRater", + "badgeViews": 98 } ] ``` +### App stats + +GET https://repo-rater.eddiehub.org/api/stats + +```json +{ + "ratings": 1137, + "repos": 657, + "stars": 53, + "$createdAt": "2023-12-24T07:41:21.204+00:00", + "$updatedAt": "2024-01-02T20:42:33.660+00:00" +} +``` + ## Community Come and chat with the community in the EddieHub Discord http://discord.eddiehub.org diff --git a/package-lock.json b/package-lock.json index d475e04..2210132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,13 @@ "name": "repo-rater4", "version": "0.1.0", "dependencies": { + "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.1.1", + "@tailwindcss/forms": "^0.5.7", "@vercel/analytics": "^1.1.1", "appwrite": "^13.0.1", "badge-maker": "^3.3.1", + "javascript-time-ago": "^2.5.9", "next": "14.0.4", "node-appwrite": "^11.1.0", "react": "^18", @@ -21,7 +24,7 @@ "@types/node": "20.10.4", "@types/react": "18.2.45", "autoprefixer": "^10.0.1", - "daisyui": "^4.4.20", + "daisyui": "^4.4.24", "eslint": "^8", "eslint-config-next": "14.0.4", "postcss": "^8", @@ -41,7 +44,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "engines": { "node": ">=10" }, @@ -117,6 +119,21 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@headlessui/react": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz", + "integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==", + "dependencies": { + "client-only": "^0.0.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, "node_modules/@heroicons/react": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.1.1.tgz", @@ -162,7 +179,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -176,7 +192,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -185,7 +200,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -193,14 +207,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -359,7 +371,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -372,7 +383,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -381,7 +391,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -404,6 +413,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", + "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -630,14 +650,12 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -658,8 +676,7 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -935,14 +952,12 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -956,7 +971,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -966,7 +980,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -1044,7 +1057,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "engines": { "node": ">= 6" } @@ -1096,7 +1108,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "funding": [ { "type": "individual", @@ -1123,7 +1134,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -1168,7 +1178,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -1176,8 +1185,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/cross-fetch": { "version": "3.1.5", @@ -1235,7 +1243,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "bin": { "cssesc": "bin/cssesc" }, @@ -1259,9 +1266,9 @@ } }, "node_modules/daisyui": { - "version": "4.4.20", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.4.20.tgz", - "integrity": "sha512-AR2fuFVVLHVTdbkV+XWAqjtymEoxXksrsEMkdzPQo2wANtWjSXuODUzePNade64gJ0Y2CdQtiQkaZI7fWcp13g==", + "version": "4.4.24", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.4.24.tgz", + "integrity": "sha512-u/B3484J08V7N0rIYymnC+SyxOjlYQL+2vyhHWV+/KC+VaUcbEF2Z3H06eCPgdTiZ0J+ml44aH7wBhIymPFQ+g==", "dev": true, "dependencies": { "css-selector-tokenizer": "^0.8", @@ -1357,8 +1364,7 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/dir-glob": { "version": "3.0.1", @@ -1375,8 +1381,7 @@ "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/doctrine": { "version": "3.0.0", @@ -1971,7 +1976,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1987,7 +1991,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -2017,7 +2020,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -2038,7 +2040,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2139,14 +2140,12 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2160,7 +2159,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2259,7 +2257,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -2418,7 +2415,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -2464,7 +2460,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2473,8 +2468,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.0.6", @@ -2535,7 +2529,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -2575,7 +2568,6 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, "dependencies": { "hasown": "^2.0.0" }, @@ -2602,7 +2594,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2638,7 +2629,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -2671,7 +2661,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -2849,11 +2838,18 @@ "set-function-name": "^2.0.1" } }, + "node_modules/javascript-time-ago": { + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/javascript-time-ago/-/javascript-time-ago-2.5.9.tgz", + "integrity": "sha512-pQ8mNco/9g9TqWXWWjP0EWl6i/lAQScOyEeXy5AB+f7MfLSdgyV9BJhiOD1zrIac/lrxPYOWNbyl/IW8CW5n0A==", + "dependencies": { + "relative-time-format": "^1.1.6" + } + }, "node_modules/jiti": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", - "dev": true, "bin": { "jiti": "bin/jiti.js" } @@ -2964,7 +2960,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, "engines": { "node": ">=10" } @@ -2972,8 +2967,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/locate-path": { "version": "6.0.0", @@ -3023,7 +3017,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -3032,7 +3025,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -3060,11 +3052,18 @@ "node": ">= 0.6" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3091,7 +3090,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -3245,7 +3243,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3263,7 +3260,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3272,7 +3268,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -3390,7 +3385,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -3467,7 +3461,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3484,8 +3477,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-type": { "version": "4.0.0", @@ -3505,7 +3497,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -3517,7 +3508,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3526,7 +3516,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "engines": { "node": ">= 6" } @@ -3535,7 +3524,6 @@ "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3563,7 +3551,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -3580,7 +3567,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -3599,7 +3585,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3634,7 +3619,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", - "dev": true, "engines": { "node": ">=14" } @@ -3643,7 +3627,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.11" }, @@ -3662,7 +3645,6 @@ "version": "6.0.13", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3674,8 +3656,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -3715,7 +3696,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -3764,7 +3744,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -3773,7 +3752,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -3824,11 +3802,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/relative-time-format": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/relative-time-format/-/relative-time-format-1.1.6.tgz", + "integrity": "sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -3863,7 +3845,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -3888,7 +3869,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -4180,7 +4160,6 @@ "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -4202,7 +4181,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4234,7 +4212,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4246,7 +4223,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz", "integrity": "sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==", - "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -4298,7 +4274,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -4307,7 +4282,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -4319,7 +4293,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -4347,8 +4320,7 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -4533,8 +4505,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/watchpack": { "version": "2.4.0", @@ -4656,8 +4627,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/yallist": { "version": "4.0.0", @@ -4669,7 +4639,6 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "dev": true, "engines": { "node": ">= 14" } diff --git a/package.json b/package.json index b7726f2..d799f12 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,13 @@ "lint": "next lint" }, "dependencies": { + "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.1.1", + "@tailwindcss/forms": "^0.5.7", "@vercel/analytics": "^1.1.1", "appwrite": "^13.0.1", "badge-maker": "^3.3.1", + "javascript-time-ago": "^2.5.9", "next": "14.0.4", "node-appwrite": "^11.1.0", "react": "^18", @@ -22,7 +25,7 @@ "@types/node": "20.10.4", "@types/react": "18.2.45", "autoprefixer": "^10.0.1", - "daisyui": "^4.4.20", + "daisyui": "^4.4.24", "eslint": "^8", "eslint-config-next": "14.0.4", "postcss": "^8", diff --git a/src/app/api/activity/route.js b/src/app/api/activity/route.js new file mode 100644 index 0000000..af1ab16 --- /dev/null +++ b/src/app/api/activity/route.js @@ -0,0 +1,28 @@ +import { Query, Databases } from "node-appwrite"; +import TimeAgo from "javascript-time-ago"; + +import { clientAdmin } from "@/config/appwrite-server"; + +import en from "javascript-time-ago/locale/en"; + +TimeAgo.addDefaultLocale(en); +const timeAgo = new TimeAgo("en-US"); + +export const dynamic = "force-dynamic"; + +export async function GET() { + let ratings = await new Databases(clientAdmin()).listDocuments( + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_RATINGS_ID, + [Query.orderDesc("$updatedAt"), Query.limit(25)] + ); + + ratings = ratings.documents.map((rating) => ({ + username: rating.username, + url: rating.url, + timeAgo: timeAgo.format(Math.floor(new Date(rating.$updatedAt).getTime())), + updatedAt: rating.$updatedAt, + })); + + return Response.json(ratings); +} diff --git a/src/app/api/badge/route.js b/src/app/api/badge/route.js index 8083a6b..9d2ad7e 100644 --- a/src/app/api/badge/route.js +++ b/src/app/api/badge/route.js @@ -3,6 +3,8 @@ import sdk, { Query } from "node-appwrite"; import { clientAdmin } from "@/config/appwrite-server"; +export const dynamic = "force-dynamic"; + export async function GET(request) { const styles = ["plastic", "flat", "flat-square", "for-the-badge", "social"]; const params = request.nextUrl.searchParams; @@ -13,8 +15,8 @@ export async function GET(request) { // get repo rating from database const repos = await new sdk.Databases(clientAdmin()).listDocuments( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_REPOS_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_REPOS_ID, [Query.equal("owner", [owner]), Query.equal("name", [name]), Query.limit(1)] ); const data = repos.documents[0]; @@ -22,8 +24,8 @@ export async function GET(request) { // increment views using badge if (data) { await new sdk.Databases(clientAdmin()).updateDocument( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_REPOS_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_REPOS_ID, data.$id, { badgeViews: data.badgeViews + 1, diff --git a/src/app/api/leaderboard/route.js b/src/app/api/leaderboard/route.js index a93a189..edb7308 100644 --- a/src/app/api/leaderboard/route.js +++ b/src/app/api/leaderboard/route.js @@ -2,10 +2,12 @@ import { Query, Databases } from "node-appwrite"; import { clientAdmin } from "@/config/appwrite-server"; +export const dynamic = "force-dynamic"; + export async function GET() { const ratings = await new Databases(clientAdmin()).listDocuments( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_RATINGS_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_RATINGS_ID, [Query.limit(1000)] ); const users = ratings.documents.reduce((acc, rating) => { diff --git a/src/app/api/popular/route.js b/src/app/api/popular/route.js index ea67832..55665eb 100644 --- a/src/app/api/popular/route.js +++ b/src/app/api/popular/route.js @@ -2,13 +2,15 @@ import { Query, Databases } from "node-appwrite"; import { clientAdmin } from "@/config/appwrite-server"; +export const dynamic = "force-dynamic"; + export async function GET(request) { const params = request.nextUrl.searchParams; const minimumVotes = params.get("minimumVotes"); const repos = await new Databases(clientAdmin()).listDocuments( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_REPOS_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_REPOS_ID, [ Query.select([ "url", diff --git a/src/app/api/rate/route.js b/src/app/api/rate/route.js index 3bd0b4e..ccc2a09 100644 --- a/src/app/api/rate/route.js +++ b/src/app/api/rate/route.js @@ -27,8 +27,9 @@ export async function POST(request) { console.info(`User ${username} submitted rating for ${data.url}`); // 0. get repo from github api - const repoPath = data.url.split("github.com/"); - if (repoPath.length !== 2 || !repoPath[1]) { + const urlClean = data.url.endsWith("/") ? data.url.slice(0, -1) : data.url; + const repoPath = urlClean.split("github.com/"); + if (repoPath.length !== 2) { return Response.json({ success: false, error: "Invalid URL" }); } const repoData = await getRepo(repoPath[1], session.providerAccessToken); @@ -43,42 +44,42 @@ export async function POST(request) { // get app total stats to increment const appTotal = ( await new sdk.Databases(clientAdmin()).listDocuments( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_APP_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_APP_ID, [Query.limit(1)] ) ).documents[0]; // 1. check if user already rated this repo const userRepoRating = await new sdk.Databases(clientAdmin()).listDocuments( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_RATINGS_ID, - [Query.equal("url", [data.url]), Query.equal("username", [username])] + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_RATINGS_ID, + [Query.equal("url", [urlClean]), Query.equal("username", [username])] ); // 2a. update in ratings collection if (userRepoRating.total === 1) { - console.info(`User ${username} already rated ${data.url} updating rating`); + console.info(`User ${username} already rated ${urlClean} updating rating`); await new sdk.Databases(clientAdmin()).updateDocument( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_RATINGS_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_RATINGS_ID, userRepoRating.documents[0].$id, { username: username, - url: data.url, + url: urlClean, rating: rating, } ); } else { // 2b. create in ratings collection - console.info(`User ${username} rating ${data.url} for the first time`); + console.info(`User ${username} rating ${urlClean} for the first time`); await new sdk.Databases(clientAdmin()).createDocument( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_RATINGS_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_RATINGS_ID, sdk.ID.unique(), { username: username, - url: data.url, + url: urlClean, rating: rating, } ); @@ -87,8 +88,8 @@ export async function POST(request) { // 2c. update app rating count console.info("Increment app total ratings"); await new sdk.Databases(clientAdmin()).updateDocument( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_APP_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_APP_ID, appTotal.$id, { ratings: appTotal.ratings + 1, @@ -97,21 +98,21 @@ export async function POST(request) { ); // 3. check if repo exists - console.info(`Checking if repo ${data.url} exists in database`); + console.info(`Checking if repo ${urlClean} exists in database`); const repos = await new sdk.Databases(clientAdmin()).listDocuments( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_REPOS_ID, - [Query.equal("url", [data.url])] + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_REPOS_ID, + [Query.equal("url", [urlClean])] ); // 4a. update in repos collection + calculate new rating if (repos.total === 1) { - console.info(`Repo ${data.url} found in database update rating`); + console.info(`Repo ${urlClean} found in database update rating`); // get all ratings for this repo const ratings = await new sdk.Databases(clientAdmin()).listDocuments( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_RATINGS_ID, - [Query.equal("url", [data.url])] + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_RATINGS_ID, + [Query.equal("url", [urlClean])] ); // save new repo rating @@ -119,8 +120,8 @@ export async function POST(request) { ratings.documents.reduce((acc, cur) => acc + cur.rating, 0) / ratings.total; await new sdk.Databases(clientAdmin()).updateDocument( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_REPOS_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_REPOS_ID, repos.documents[0].$id, { ...githubRepo, @@ -131,15 +132,15 @@ export async function POST(request) { } else { // 4a. create in repos collection console.info( - `Repo ${data.url} not found in database create repo and ratings` + `Repo ${urlClean} not found in database create repo and ratings` ); await new sdk.Databases(clientAdmin()).createDocument( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_REPOS_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_REPOS_ID, sdk.ID.unique(), { ...githubRepo, - url: data.url, + url: urlClean, rating: rating, votes: 1, } @@ -147,8 +148,8 @@ export async function POST(request) { // 4b. update app repo count console.info("Increment app total repos"); await new sdk.Databases(clientAdmin()).updateDocument( - process.env.APPWRITE_DATABASE_ID, - process.env.APPWRITE_COLLECTION_APP_ID, + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_APP_ID, appTotal.$id, { repos: appTotal.repos + 1, diff --git a/src/app/api/repos/route.js b/src/app/api/repos/route.js new file mode 100644 index 0000000..a2bf364 --- /dev/null +++ b/src/app/api/repos/route.js @@ -0,0 +1,46 @@ +import { Query, Databases } from "node-appwrite"; + +import { clientAdmin } from "@/config/appwrite-server"; + +export const dynamic = "force-dynamic"; + +export async function GET(request) { + const params = request.nextUrl.searchParams; + const minimumVotes = params.get("minimumVotes"); + const keyword = params.get("keyword"); + + const repos = await new Databases(clientAdmin()).listDocuments( + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_REPOS_ID, + [ + Query.select([ + "url", + "logo", + "description", + "rating", + "votes", + "owner", + "name", + "badgeViews", + ]), + Query.orderDesc("rating"), + Query.orderDesc("votes"), + minimumVotes && Query.greaterThanEqual("votes", parseInt(minimumVotes)), + Query.limit(100), + keyword && Query.search("url", keyword), + ] + ); + + const all = repos.documents.map((repo) => ({ + url: repo.url, + logo: repo.logo, + description: repo.description, + rating: repo.rating, + votes: repo.votes, + owner: repo.owner, + name: repo.name, + badgeViews: repo.badgeViews, + })); + + return Response.json(all); +} diff --git a/src/app/api/stats/route.js b/src/app/api/stats/route.js new file mode 100644 index 0000000..1ddc934 --- /dev/null +++ b/src/app/api/stats/route.js @@ -0,0 +1,17 @@ +import { Query, Databases } from "node-appwrite"; + +import { clientAdmin } from "@/config/appwrite-server"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + const data = ( + await new Databases(clientAdmin()).listDocuments( + process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID, + process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_APP_ID, + [Query.limit(1)] + ) + ).documents[0]; + + return Response.json(data); +} diff --git a/src/app/auth/login/page.js b/src/app/auth/login/page.js deleted file mode 100644 index 99f8330..0000000 --- a/src/app/auth/login/page.js +++ /dev/null @@ -1,33 +0,0 @@ -"use client"; - -import { account } from "@/config/appwrite-client"; - -export default function Page() { - const login = async () => { - account.createOAuth2Session( - "github", - `${process.env.NEXT_PUBLIC_BASE_URL}/`, - `${process.env.NEXT_PUBLIC_BASE_URL}/auth/login` - ); - }; - - return ( -
-
-
-

Rate an Open Source Repo!

-

- Open Source is about collaboration and sharing knowledge. But some - repos have a better DX (Developer Experience), share your experience - with the community! -

-
-
- -
-
-
- ); -} diff --git a/src/app/layout.js b/src/app/layout.js index 76afe49..16b51c9 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -2,13 +2,14 @@ import { Inter } from "next/font/google"; import { Analytics } from "@vercel/analytics/react"; import "./globals.css"; +import { classNames } from "@/utils/classNames"; const inter = Inter({ subsets: ["latin"] }); export default function RootLayout({ children }) { return ( - - + + {children} diff --git a/src/app/maintenance/page.js b/src/app/maintenance/page.js deleted file mode 100644 index b7a059a..0000000 --- a/src/app/maintenance/page.js +++ /dev/null @@ -1,9 +0,0 @@ -import Alert from "@/components/Alert"; - -export default function Page() { - return ( -
- -
- ); -} diff --git a/src/app/page.js b/src/app/page.js index ed01104..251fe6e 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -1,43 +1,144 @@ -import Link from "next/link"; +"use client"; -import Footer from "@/components/Footer"; -import Navbar from "@/components/Navbar"; +import { useState } from "react"; +import { Bars3Icon, MagnifyingGlassIcon } from "@heroicons/react/20/solid"; + +import SideNav from "@/components/SideNav"; import Repos from "@/components/Repos"; +import Activity from "@/components/Activity"; import Stats from "@/components/Stats"; +import Toast from "@/components/Toast"; +import { useSearchParams } from "next/navigation"; export const dynamic = "force-dynamic"; -export default function Home() { +export default function Page() { + const params = useSearchParams(); + const alert = params.get("alert"); + const message = params.get("message"); + const [keyword, setKeyword] = useState(""); + return ( <> - -
-
-
-
-

Impartial Rating

-

- Rate Open Source GitHub Repositories on their Developer - Experience (DX). -

-
- - Add Rating - - - Add Badge to your README - -
+ + <> +
+ + +
+
e.preventDefault()} + > + +
+
+
-
- - -
-