From 4b0476c5094406fb6e534c464863a6f7043dd4a5 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Sun, 4 Aug 2024 17:02:34 -0500 Subject: [PATCH] Implemented ndk with tanstack query for courses resources and workshops, and zaps is mostly working --- package-lock.json | 375 ++++++++++++++++++ package.json | 1 + .../content/carousels/CoursesCarousel.js | 48 +-- .../content/carousels/ResourcesCarousel.js | 52 ++- .../content/carousels/WorkshopsCarousel.js | 47 +-- .../carousels/templates/CourseTemplate.js | 8 +- .../carousels/templates/ResourceTemplate.js | 30 +- .../carousels/templates/WorkshopTemplate.js | 26 +- src/context/NDKContext.js | 41 ++ src/hooks/nostrQueries/useCoursesQuery.js | 63 ++- src/hooks/nostrQueries/useResourcesQuery.js | 64 ++- src/hooks/nostrQueries/useWorkshopsQuery.js | 66 ++- src/hooks/nostrQueries/useZapsQuery.js | 47 +++ src/pages/_app.js | 33 +- src/pages/index.js | 8 +- 15 files changed, 666 insertions(+), 243 deletions(-) create mode 100644 src/context/NDKContext.js create mode 100644 src/hooks/nostrQueries/useZapsQuery.js diff --git a/package-lock.json b/package-lock.json index 7749092..0c83453 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "dependencies": { "@getalby/bitcoin-connect-react": "^3.5.3", + "@nostr-dev-kit/ndk": "^2.10.0", "@prisma/client": "^5.17.0", "@tanstack/react-query": "^5.51.21", "@uiw/react-markdown-preview": "^5.1.2", @@ -987,6 +988,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@noble/secp256k1": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.1.0.tgz", + "integrity": "sha512-XLEQQNdablO0XZOIniFQimiXsZDNwaYgL96dZwC54Q30imSbAOFf3NKtepc+cXyuZf5Q1HCgbqgZ2UFFuHVcEw==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1025,6 +1035,53 @@ "node": ">= 8" } }, + "node_modules/@nostr-dev-kit/ndk": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.10.0.tgz", + "integrity": "sha512-TqCAAo6ylORraAXrzRkCGFN2xTMiFbdER8Y8CtUT0HwOpFG/Wn+PBNeDeDmqkl/6LaPdeyXmVwCWj2KcUjIwYA==", + "license": "MIT", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "@noble/secp256k1": "^2.0.0", + "@scure/base": "^1.1.1", + "debug": "^4.3.4", + "light-bolt11-decoder": "^3.0.0", + "node-fetch": "^3.3.1", + "nostr-tools": "^2.7.1", + "tseep": "^1.1.1", + "typescript-lru-cache": "^2.0.0", + "utf8-buffer": "^1.0.0", + "websocket-polyfill": "^0.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@panva/hkdf": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", @@ -3588,6 +3645,19 @@ "license": "MIT", "peer": true }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3922,6 +3992,19 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -3929,6 +4012,15 @@ "dev": true, "license": "BSD-2-Clause" }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -4431,6 +4523,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -4825,6 +4957,21 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -4897,6 +5044,16 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -4913,6 +5070,15 @@ "node": ">=0.8.x" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -4984,6 +5150,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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5126,6 +5315,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==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6711,6 +6912,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -8255,6 +8462,12 @@ "babel-plugin-transform-remove-imports": "^1.7.0" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -8283,6 +8496,54 @@ "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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "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==", + "license": "MIT", + "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-gyp-build": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", @@ -10884,12 +11145,30 @@ "json5": "lib/cli.js" } }, + "node_modules/tseep": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tseep/-/tseep-1.2.2.tgz", + "integrity": "sha512-GgPFuNx+08UaYBYmJQmuI86ykYa2PUUtfXAYb4MLRHGunSCp8k9N+dbsR4PK1yk4/zV9q4e4PrNg8ymXqGYaYA==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "license": "0BSD" }, + "node_modules/tstl": { + "version": "2.5.16", + "resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.16.tgz", + "integrity": "sha512-+O2ybLVLKcBwKm4HymCEwZIT0PpwS3gCYnxfSDEjJEKADvIFruaQjd3m7CAKNU1c7N3X3WjVz87re7TA2A5FUw==", + "license": "MIT" + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -10993,6 +11272,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", @@ -11008,6 +11296,12 @@ "node": ">=14.17" } }, + "node_modules/typescript-lru-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz", + "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==", + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -11201,6 +11495,28 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/utf8-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utf8-buffer/-/utf8-buffer-1.0.0.tgz", + "integrity": "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11355,6 +11671,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webpack": { "version": "5.93.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", @@ -11456,6 +11781,47 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/websocket": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", + "license": "Apache-2.0", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.63", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket-polyfill": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz", + "integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==", + "dependencies": { + "tstl": "^2.0.7", + "websocket": "^1.0.28" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11673,6 +12039,15 @@ "dev": true, "license": "ISC" }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "license": "MIT", + "engines": { + "node": ">=0.10.32" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index f5b200f..bf9a648 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@getalby/bitcoin-connect-react": "^3.5.3", + "@nostr-dev-kit/ndk": "^2.10.0", "@prisma/client": "^5.17.0", "@tanstack/react-query": "^5.51.21", "@uiw/react-markdown-preview": "^5.1.2", diff --git a/src/components/content/carousels/CoursesCarousel.js b/src/components/content/carousels/CoursesCarousel.js index 16afb91..614ee4b 100644 --- a/src/components/content/carousels/CoursesCarousel.js +++ b/src/components/content/carousels/CoursesCarousel.js @@ -1,10 +1,9 @@ import React, { useState, useEffect, use } from 'react'; import { Carousel } from 'primereact/carousel'; import { parseCourseEvent } from '@/utils/nostr'; -import { useNostr } from '@/hooks/useNostr'; +import { useZapsQuery } from '@/hooks/nostrQueries/useZapsQuery'; import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; -// import { useNostrQueries } from '@/hooks/useNostrQueries'; import { useCoursesQuery } from '@/hooks/nostrQueries/useCoursesQuery'; const responsiveOptions = [ @@ -27,35 +26,28 @@ const responsiveOptions = [ export default function CoursesCarousel() { const [processedCourses, setProcessedCourses] = useState([]); - const { fetchZapsForEvents } = useNostr(); - // const { courses, coursesError, zapsForEvents, refetchZapsForEvents } = useNostrQueries() - const { courses, coursesError, refetchCourses } = useCoursesQuery() + const { courses, coursesLoading, coursesError, refetchCourses } = useCoursesQuery() + const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: courses }) + + useEffect(() => { + refetchZaps(courses) + }, [courses, refetchZaps]); useEffect(() => { const fetch = async () => { try { - if (courses && courses.length > 0) { - console.log('courses:', courses); - // First process the courses to be ready for display + if (courses && courses.length > 0 && zaps) { const processedCourses = courses.map(course => parseCourseEvent(course)); - // Fetch zaps for all processed courses at once - const allZaps = await fetchZapsForEvents(processedCourses); - console.log('allZaps:', allZaps); - - // Process zaps to associate them with their respective courses - const coursesWithZaps = processedCourses.map(course => { - const relevantZaps = allZaps.filter(zap => { - const eTagMatches = zap.tags.find(tag => tag[0] === 'e' && tag[1] === course.id); - const aTag = zap.tags.find(tag => tag[0] === 'a'); - const aTagMatches = aTag && course.d === aTag[1].split(':').pop(); - return eTagMatches || aTagMatches; - }); - return { - ...course, - zaps: relevantZaps - }; - }); + let coursesWithZaps = processedCourses.map(course => { + let collectedZaps = [] + zaps.forEach(zap => { + if (zap.tags.find(tag => tag[0] === "e" && tag[1] === course.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${course.kind}:${course.id}:${course.d}`)) { + collectedZaps.push(zap) + } + }) + return { ...course, zaps: collectedZaps } + }) setProcessedCourses(coursesWithZaps); } else { @@ -66,12 +58,16 @@ export default function CoursesCarousel() { } }; fetch(); - }, [courses]); + }, [courses, zaps]); if (coursesError) { return
Error: {coursesError.message}
} + if (coursesLoading) { + return
Loading...
+ } + return ( <>

Courses

diff --git a/src/components/content/carousels/ResourcesCarousel.js b/src/components/content/carousels/ResourcesCarousel.js index 6548bea..21d586d 100644 --- a/src/components/content/carousels/ResourcesCarousel.js +++ b/src/components/content/carousels/ResourcesCarousel.js @@ -1,11 +1,10 @@ import React, { useState, useEffect } from 'react'; import { Carousel } from 'primereact/carousel'; -import { useNostr } from '@/hooks/useNostr'; import { parseEvent } from '@/utils/nostr'; import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; -import { useNostrQueries } from '@/hooks/useNostrQueries'; -import { useResourcesQuery } from '@/hooks/useResourcesQuery'; +import { useResourcesQuery } from '@/hooks/nostrQueries/useResourcesQuery'; +import { useZapsQuery } from '@/hooks/nostrQueries/useZapsQuery'; const responsiveOptions = [ { @@ -27,36 +26,32 @@ const responsiveOptions = [ export default function ResourcesCarousel() { const [processedResources, setProcessedResources] = useState([]); - const { fetchZapsForEvents } = useNostr(); - // const { resources, resourcesError, refetchResources } = useNostrQueries() - const { resources, resourcesError, refetchResources } = useResourcesQuery() + const { resources, resourcesLoading, resourcesError, refetchResources } = useResourcesQuery() + const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: resources }) + + useEffect(() => { + if (resources && resources.length > 0) { + refetchZaps(resources) + } + }, [resources, refetchZaps]); useEffect(() => { const fetch = async () => { try { - if (resources && resources.length > 0) { + if (resources && resources.length > 0 && zaps) { const processedResources = resources.map(resource => parseEvent(resource)); - console.log('processedResources:', processedResources); - - const allZaps = await fetchZapsForEvents(processedResources); - - const resourcesWithZaps = processedResources.map(resource => { - const relevantZaps = allZaps.filter(zap => { - const eTagMatches = zap.tags.find(tag => tag[0] === 'e' && tag[1] === resource.id); - const aTag = zap.tags.find(tag => tag[0] === 'a'); - const aTagMatches = aTag && resource.d === aTag[1].split(':').pop(); - return eTagMatches || aTagMatches; - }); - return { - ...resource, - zaps: relevantZaps - }; - }); - + let resourcesWithZaps = processedResources.map(resource => { + let collectedZaps = [] + zaps.forEach(zap => { + if (zap.tags.find(tag => tag[0] === "e" && tag[1] === resource.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${resource.kind}:${resource.id}:${resource.d}`)) { + collectedZaps.push(zap) + } + }) + return { ...resource, zaps: collectedZaps } + }) + setProcessedResources(resourcesWithZaps); - } else { - refetchResources(); } } catch (error) { console.error('Error fetching resources:', error); @@ -65,10 +60,13 @@ export default function ResourcesCarousel() { fetch(); }, [resources]); + if (resourcesLoading) { + return
Loading...
+ } + if (resourcesError) { return
Error: {resourcesError.message}
} - return ( <> diff --git a/src/components/content/carousels/WorkshopsCarousel.js b/src/components/content/carousels/WorkshopsCarousel.js index 697139f..16f0027 100644 --- a/src/components/content/carousels/WorkshopsCarousel.js +++ b/src/components/content/carousels/WorkshopsCarousel.js @@ -1,13 +1,10 @@ import React, { useState, useEffect } from 'react'; import { Carousel } from 'primereact/carousel'; -import { useRouter } from 'next/router'; -import { useImageProxy } from '@/hooks/useImageProxy'; -import { useNostr } from '@/hooks/useNostr'; import { parseEvent } from '@/utils/nostr'; import WorkshopTemplate from '@/components/content/carousels/templates/WorkshopTemplate'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; -// import { useNostrQueries } from '@/hooks/useNostrQueries'; import { useWorkshopsQuery } from '@/hooks/nostrQueries/useWorkshopsQuery'; +import { useZapsQuery } from '@/hooks/nostrQueries/useZapsQuery'; const responsiveOptions = [ { @@ -29,32 +26,29 @@ const responsiveOptions = [ export default function WorkshopsCarousel() { const [processedWorkshops, setProcessedWorkshops] = useState([]) + const { workshops, workshopsLoading, workshopsError, refetchWorkshops } = useWorkshopsQuery() + const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: workshops }) - // const { workshops, workshopsError } = useNostrQueries() - const { workshops, workshopsError, refetchWorkshops } = useWorkshopsQuery() - const { fetchZapsForEvents } = useNostr() + useEffect(() => { + refetchZaps(workshops) + }, [workshops, refetchZaps]); useEffect(() => { const fetch = async () => { try { console.debug('workshops', workshops); - if (workshops && workshops.length > 0) { + if (workshops && workshops.length > 0 && zaps) { const processedWorkshops = workshops.map(workshop => parseEvent(workshop)); - - const allZaps = await fetchZapsForEvents(processedWorkshops); - - const workshopsWithZaps = processedWorkshops.map(workshop => { - const relevantZaps = allZaps.filter(zap => { - const eTagMatches = zap.tags.find(tag => tag[0] === 'e' && tag[1] === workshop.id); - const aTag = zap.tags.find(tag => tag[0] === 'a'); - const aTagMatches = aTag && workshop.d === aTag[1].split(':').pop(); - return eTagMatches || aTagMatches; - }); - return { - ...workshop, - zaps: relevantZaps - }; - }); + + let workshopsWithZaps = processedWorkshops.map(workshop => { + let collectedZaps = [] + zaps.forEach(zap => { + if (zap.tags.find(tag => tag[0] === "e" && tag[1] === workshop.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${workshop.kind}:${workshop.id}:${workshop.d}`)) { + collectedZaps.push(zap) + } + }) + return { ...workshop, zaps: collectedZaps } + }) setProcessedWorkshops(workshopsWithZaps); } else { @@ -65,11 +59,10 @@ export default function WorkshopsCarousel() { } }; fetch(); - }, [workshops]); + }, [workshops, zaps]); - if (workshopsError) { - return
Error: {workshopsError.message}
- } + if (workshopsLoading) return
Loading...
; + if (workshopsError) return
Error: {workshopsError}
; return ( <> diff --git a/src/components/content/carousels/templates/CourseTemplate.js b/src/components/content/carousels/templates/CourseTemplate.js index ab153f8..0f2ea70 100644 --- a/src/components/content/carousels/templates/CourseTemplate.js +++ b/src/components/content/carousels/templates/CourseTemplate.js @@ -3,20 +3,20 @@ import Image from "next/image"; import { useRouter } from "next/router"; import { formatTimestampToHowLongAgo } from "@/utils/time"; import { useImageProxy } from "@/hooks/useImageProxy"; -import { useNostr } from "@/hooks/useNostr"; import { getSatAmountFromInvoice } from "@/utils/lightning"; import ZapDisplay from "@/components/zaps/ZapDisplay"; -const CourseTemplate = ({course}) => { - const [zapAmount, setZapAmount] = useState(null); +const CourseTemplate = ({ course }) => { + const [zapAmount, setZapAmount] = useState(0); const router = useRouter(); const { returnImageProxy } = useImageProxy(); useEffect(() => { - if (!course || !course.zaps) return; + if (!course?.zaps || !course?.zaps.length > 0) return; let total = 0; course.zaps.forEach((zap) => { + // If the zap matches the event or the parameterized event, then add the zap to the total const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11"); const invoice = bolt11Tag ? bolt11Tag[1] : null; if (invoice) { diff --git a/src/components/content/carousels/templates/ResourceTemplate.js b/src/components/content/carousels/templates/ResourceTemplate.js index 9ae951c..28ceae1 100644 --- a/src/components/content/carousels/templates/ResourceTemplate.js +++ b/src/components/content/carousels/templates/ResourceTemplate.js @@ -3,39 +3,41 @@ import Image from "next/image"; import { useRouter } from "next/router"; import { formatTimestampToHowLongAgo } from "@/utils/time"; import { useImageProxy } from "@/hooks/useImageProxy"; -import { useNostr } from "@/hooks/useNostr"; +import { useZapsQuery } from "@/hooks/nostrQueries/useZapsQuery"; import { getSatAmountFromInvoice } from "@/utils/lightning"; +import ZapDisplay from "@/components/zaps/ZapDisplay"; + +const ResourceTemplate = ({ resource }) => { + const [zapAmount, setZapAmount] = useState(0); -const ResourceTemplate = ({resource}) => { - const [zapAmount, setZapAmount] = useState(null); const router = useRouter(); const { returnImageProxy } = useImageProxy(); useEffect(() => { - if (!resource || !resource.zaps) return; - + if (!resource?.zaps || !resource?.zaps.length > 0) return; + let total = 0; resource.zaps.forEach((zap) => { + // If the zap matches the event or the parameterized event, then add the zap to the total const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11"); const invoice = bolt11Tag ? bolt11Tag[1] : null; if (invoice) { - const amount = getSatAmountFromInvoice(invoice); - total += amount; + const amount = getSatAmountFromInvoice(invoice); + total += amount; } }); setZapAmount(total); -}, [resource]); - + }, [resource]); return (
- {/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */} -
router.push(`/details/${resource.id}`)} className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer" - style={{ paddingBottom: "56.25%"}} + style={{ paddingBottom: "56.25%" }} > resource thumbnail {

{formatTimestampToHowLongAgo(resource.published_at)}

-

- {zapAmount} -

+
diff --git a/src/components/content/carousels/templates/WorkshopTemplate.js b/src/components/content/carousels/templates/WorkshopTemplate.js index bda65cd..12f03e0 100644 --- a/src/components/content/carousels/templates/WorkshopTemplate.js +++ b/src/components/content/carousels/templates/WorkshopTemplate.js @@ -3,28 +3,36 @@ import Image from "next/image"; import { useRouter } from "next/router"; import { formatTimestampToHowLongAgo } from "@/utils/time"; import { useImageProxy } from "@/hooks/useImageProxy"; -import { useNostr } from "@/hooks/useNostr"; +import { useZapsQuery } from "@/hooks/nostrQueries/useZapsQuery"; import { getSatAmountFromInvoice } from "@/utils/lightning"; +import ZapDisplay from "@/components/zaps/ZapDisplay"; const WorkshopTemplate = ({workshop}) => { - const [zapAmount, setZapAmount] = useState(null); + const [zapAmount, setZapAmount] = useState(0); const router = useRouter(); const { returnImageProxy } = useImageProxy(); + const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({event: workshop}); useEffect(() => { - if (!workshop || !workshop.zaps) return; + if (!zaps || !zaps.length > 0) return; let total = 0; - workshop.zaps.forEach((zap) => { - const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11"); - const invoice = bolt11Tag ? bolt11Tag[1] : null; + zaps.forEach((zap) => { + // If the zap matches the event or the parameterized event, then add the zap to the total + if (zap.tags.find(tag => tag[0] === "e" && tag[1] === workshop.id) || zap.tags.find(tag => tag[0] === "a" && tag[1] === `${workshop.kind}:${workshop.id}:${workshop.d}`)) { + const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11"); + const invoice = bolt11Tag ? bolt11Tag[1] : null; if (invoice) { const amount = getSatAmountFromInvoice(invoice); total += amount; + } } }); setZapAmount(total); - }, [workshop]); + }, [zaps, workshop]); + + if (zapsLoading) return
Loading...
; + if (zapsError) return
Error: {zapsError}
; return (
{

{formatTimestampToHowLongAgo(workshop.published_at)}

-

- {zapAmount} -

+
diff --git a/src/context/NDKContext.js b/src/context/NDKContext.js new file mode 100644 index 0000000..c66ec3f --- /dev/null +++ b/src/context/NDKContext.js @@ -0,0 +1,41 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import NDK from "@nostr-dev-kit/ndk"; + +const NDKContext = createContext(null); + +// Define the public key of the author whose events we want to fetch. +const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY; + +// Ensure AUTHOR_PUBKEY is defined +if (!AUTHOR_PUBKEY) { + throw new Error("NEXT_PUBLIC_AUTHOR_PUBKEY is not defined in the environment variables"); +} + +const relayUrls = [ + "wss://nos.lol/", + "wss://relay.damus.io/", + "wss://relay.snort.social/", + "wss://relay.nostr.band/", + "wss://nostr.mutinywallet.com/", + "wss://relay.mutinywallet.com/", + "wss://relay.primal.net/" +]; + +export const NDKProvider = ({ children }) => { + const [ndk, setNdk] = useState(null); + + useEffect(() => { + const instance = new NDK({ explicitRelayUrls: relayUrls }); + setNdk(instance); + }, []); + + return ( + + {children} + + ); +}; + +export const useNDKContext = () => { + return useContext(NDKContext); +}; diff --git a/src/hooks/nostrQueries/useCoursesQuery.js b/src/hooks/nostrQueries/useCoursesQuery.js index 4d95758..3f35ae9 100644 --- a/src/hooks/nostrQueries/useCoursesQuery.js +++ b/src/hooks/nostrQueries/useCoursesQuery.js @@ -1,58 +1,43 @@ import { useState, useEffect } from 'react'; -import { useNostr } from '@/hooks/useNostr'; import { useQuery } from '@tanstack/react-query'; +import { useNDKContext } from '@/context/NDKContext'; const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY export function useCoursesQuery() { const [isClient, setIsClient] = useState(false); - const { subscribe } = useNostr(); + const ndk = useNDKContext(); useEffect(() => { setIsClient(true); }, []); - const fetchCourses = async () => { - const filter = [{ kinds: [30004], authors: [AUTHOR_PUBKEY] }]; - // Do we need required tags for courses? community instead? - // const hasRequiredTags = (tags) => { - // const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs"); - // const hasCourse = tags.some(([tag, value]) => tag === "t" && value === "course"); - // return hasPlebDevs && hasCourse; - // }; - - return new Promise((resolve, reject) => { - let courses = []; - const subscription = subscribe( - filter, - { - onevent: (event) => { - // if (hasRequiredTags(event.tags)) { - // courses.push(event); - // } - courses.push(event); - }, - onerror: (error) => { - console.error('Error fetching courses:', error); - reject(error); - }, - onclose: () => { - resolve(courses); - }, + const fetchCoursesFromNDK = async () => { + try { + console.log('Fetching courses from NDK'); + await ndk.connect(); + + const filter = { kinds: [30004], authors: [AUTHOR_PUBKEY] }; + const events = await ndk.fetchEvents(filter); + + if (events && events.size > 0) { + const eventsArray = Array.from(events); + console.log('eventsArray', eventsArray) + // const resources = eventsArray.filter(event => hasRequiredTags(event.tags)); + // return resources; + return eventsArray; } - ); - - setTimeout(() => { - subscription?.close(); - resolve(courses); - }, 2000); - }); - } + return []; + } catch (error) { + console.error('Error fetching workshops from NDK:', error); + return []; + } + }; const { data: courses, isLoading: coursesLoading, error: coursesError, refetch: refetchCourses } = useQuery({ queryKey: ['courses', isClient], - queryFn: fetchCourses, - staleTime: 1000 * 60 * 10, // 10 minutes + queryFn: fetchCoursesFromNDK, + staleTime: 1000 * 60 * 30, // 30 minutes cacheTime: 1000 * 60 * 60, // 1 hour enabled: isClient, }) diff --git a/src/hooks/nostrQueries/useResourcesQuery.js b/src/hooks/nostrQueries/useResourcesQuery.js index 756842d..86c9e77 100644 --- a/src/hooks/nostrQueries/useResourcesQuery.js +++ b/src/hooks/nostrQueries/useResourcesQuery.js @@ -1,58 +1,48 @@ import { useState, useEffect } from 'react'; -import { useNostr } from '@/hooks/useNostr'; import { useQuery } from '@tanstack/react-query'; +import { useNDKContext } from '@/context/NDKContext'; const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY export function useResourcesQuery() { const [isClient, setIsClient] = useState(false); - const { subscribe } = useNostr(); + const ndk = useNDKContext(); useEffect(() => { setIsClient(true); }, []); -const fetchResources = async () => { - console.log('fetching resources'); - const filter = [{ kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }]; - const hasRequiredTags = (tags) => { - const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs"); - const hasResource = tags.some(([tag, value]) => tag === "t" && value === "resource"); - return hasPlebDevs && hasResource; - }; +const hasRequiredTags = (tags) => { + const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs"); + const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "resource"); + return hasPlebDevs && hasWorkshop; +}; - return new Promise((resolve, reject) => { - let resources = []; - const subscription = subscribe( - filter, - { - onevent: (event) => { - if (hasRequiredTags(event.tags)) { - resources.push(event); - } - }, - onerror: (error) => { - console.error('Error fetching resources:', error); - reject(error); - }, - onclose: () => { - resolve(resources); - }, - } - ); +const fetchResourcesFromNDK = async () => { + try { + console.log('Fetching workshops from NDK'); + await ndk.connect(); - // Set a timeout to resolve the promise after collecting events - setTimeout(() => { - subscription?.close(); - resolve(resources); - }, 2000); // Adjust the timeout value as needed - }); + const filter = { kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }; + const events = await ndk.fetchEvents(filter); + + if (events && events.size > 0) { + const eventsArray = Array.from(events); + console.log('eventsArray', eventsArray) + const resources = eventsArray.filter(event => hasRequiredTags(event.tags)); + return resources; + } + return []; + } catch (error) { + console.error('Error fetching workshops from NDK:', error); + return []; } +}; const { data: resources, isLoading: resourcesLoading, error: resourcesError, refetch: refetchResources } = useQuery({ queryKey: ['resources', isClient], - queryFn: fetchResources, - staleTime: 1000 * 60 * 10, // 10 minutes + queryFn: fetchResourcesFromNDK, + staleTime: 1000 * 60 * 30, // 30 minutes cacheTime: 1000 * 60 * 60, // 1 hour enabled: isClient, }) diff --git a/src/hooks/nostrQueries/useWorkshopsQuery.js b/src/hooks/nostrQueries/useWorkshopsQuery.js index e2a2d33..85f57c8 100644 --- a/src/hooks/nostrQueries/useWorkshopsQuery.js +++ b/src/hooks/nostrQueries/useWorkshopsQuery.js @@ -1,58 +1,48 @@ import { useState, useEffect } from 'react'; -import { useNostr } from '@/hooks/useNostr'; import { useQuery } from '@tanstack/react-query'; +import { useNDKContext } from '@/context/NDKContext'; const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY export function useWorkshopsQuery() { const [isClient, setIsClient] = useState(false); - const { subscribe } = useNostr(); + const ndk = useNDKContext(); useEffect(() => { setIsClient(true); }, []); -const fetchWorkshops = async () => { - console.log('fetching workshops'); - const filter = [{ kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }]; - const hasRequiredTags = (tags) => { - const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs"); - const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "workshop"); - return hasPlebDevs && hasWorkshop; - }; +const hasRequiredTags = (tags) => { + const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs"); + const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "workshop"); + return hasPlebDevs && hasWorkshop; +}; - return new Promise((resolve, reject) => { - let resources = []; - const subscription = subscribe( - filter, - { - onevent: (event) => { - if (hasRequiredTags(event.tags)) { - resources.push(event); - } - }, - onerror: (error) => { - console.error('Error fetching resources:', error); - reject(error); - }, - onclose: () => { - resolve(resources); - }, +const fetchWorkshopsFromNDK = async () => { + try { + console.log('Fetching workshops from NDK'); + await ndk.connect(); + + const filter = { kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }; + const events = await ndk.fetchEvents(filter); + + if (events && events.size > 0) { + const eventsArray = Array.from(events); + console.log('eventsArray', eventsArray) + const resources = eventsArray.filter(event => hasRequiredTags(event.tags)); + return resources; } - ); - - // Set a timeout to resolve the promise after collecting events - setTimeout(() => { - subscription?.close(); - resolve(resources); - }, 2000); // Adjust the timeout value as needed - }); - } + return []; + } catch (error) { + console.error('Error fetching workshops from NDK:', error); + return []; + } +}; const { data: workshops, isLoading: workshopsLoading, error: workshopsError, refetch: refetchWorkshops } = useQuery({ queryKey: ['workshops', isClient], - queryFn: fetchWorkshops, - staleTime: 1000 * 60 * 10, // 10 minutes + queryFn: fetchWorkshopsFromNDK, + staleTime: 1000 * 60 * 30, // 30 minutes cacheTime: 1000 * 60 * 60, // 1 hour enabled: isClient, }) diff --git a/src/hooks/nostrQueries/useZapsQuery.js b/src/hooks/nostrQueries/useZapsQuery.js new file mode 100644 index 0000000..37d8c6d --- /dev/null +++ b/src/hooks/nostrQueries/useZapsQuery.js @@ -0,0 +1,47 @@ +import { useState, useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useNDKContext } from '@/context/NDKContext'; + +export function useZapsQuery({ events }) { + const [isClient, setIsClient] = useState(false); + const ndk = useNDKContext(); + + useEffect(() => { + setIsClient(true); + }, []); + + const fetchZapsFromNDK = async (events) => { + try { + await ndk.connect(); + + let zaps = []; + + for (const event of events) { + const uniqueEvents = new Set(); + const filters = [{ kinds: [9735], "#e": [event.id] }, { kinds: [9735], "#a": [`${event.kind}:${event.id}:${event.d}`] }]; + + for (const filter of filters) { + const zapEvents = await ndk.fetchEvents(filter); + zapEvents.forEach(zap => uniqueEvents.add(zap)); + } + + zaps = [...zaps, ...Array.from(uniqueEvents)]; + } + console.log('Zaps fetched:', zaps); + return zaps; + } catch (error) { + console.error('Error fetching zaps from NDK:', error); + return []; + } + }; + + const { data: zaps, isLoading: zapsLoading, error: zapsError, refetch: refetchZaps } = useQuery({ + queryKey: ['zaps', isClient], + queryFn: () => fetchZapsFromNDK(events), + staleTime: 1000 * 60 * 3, // 3 minutes + cacheTime: 1000 * 60 * 60, // 1 hour + enabled: isClient, + }) + + return { zaps, zapsLoading, zapsError, refetchZaps } +} \ No newline at end of file diff --git a/src/pages/_app.js b/src/pages/_app.js index ae98e4d..3d1ff70 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -9,6 +9,7 @@ import "@uiw/react-md-editor/markdown-editor.css"; import "@uiw/react-markdown-preview/markdown.css"; import Sidebar from '@/components/sidebar/Sidebar'; import { NostrProvider } from '@/context/NostrContext'; +import { NDKProvider } from '@/context/NDKContext'; import { QueryClient, QueryClientProvider, @@ -22,22 +23,24 @@ export default function MyApp({ return ( - - - -
- - {/*
*/} - {/* */} - {/*
*/} -
- + + + + +
+ + {/*
*/} + {/* */} + {/*
*/} +
+ +
+ {/*
*/}
- {/*
*/} -
- - - + + + + ); diff --git a/src/pages/index.js b/src/pages/index.js index 367fe22..eee49ca 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,11 +1,9 @@ import Head from 'next/head'; -import React, { useEffect, useCallback } from 'react'; +import React from 'react'; import CoursesCarousel from '@/components/content/carousels/CoursesCarousel'; import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel'; import HeroBanner from '@/components/banner/HeroBanner'; import ResourcesCarousel from '@/components/content/carousels/ResourcesCarousel'; -import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage'; -import axios from 'axios'; export default function Home() { @@ -20,8 +18,8 @@ export default function Home() {
- - + {/* + */}
);