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%" }}
>
{
{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() {
-
-
+ {/*
+ */}
>
);