Implemented ndk with tanstack query for courses resources and workshops, and zaps is mostly working

This commit is contained in:
austinkelsay 2024-08-04 17:02:34 -05:00
parent 9a1694dc74
commit 4b0476c509
15 changed files with 666 additions and 243 deletions

375
package-lock.json generated
View File

@ -10,6 +10,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@getalby/bitcoin-connect-react": "^3.5.3", "@getalby/bitcoin-connect-react": "^3.5.3",
"@nostr-dev-kit/ndk": "^2.10.0",
"@prisma/client": "^5.17.0", "@prisma/client": "^5.17.0",
"@tanstack/react-query": "^5.51.21", "@tanstack/react-query": "^5.51.21",
"@uiw/react-markdown-preview": "^5.1.2", "@uiw/react-markdown-preview": "^5.1.2",
@ -987,6 +988,15 @@
"url": "https://paulmillr.com/funding/" "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": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -1025,6 +1035,53 @@
"node": ">= 8" "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": { "node_modules/@panva/hkdf": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
@ -3588,6 +3645,19 @@
"license": "MIT", "license": "MIT",
"peer": true "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": { "node_modules/busboy": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@ -3922,6 +3992,19 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT" "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": { "node_modules/damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -3929,6 +4012,15 @@
"dev": true, "dev": true,
"license": "BSD-2-Clause" "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": { "node_modules/data-view-buffer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", "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" "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": { "node_modules/escalade": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
@ -4825,6 +4957,21 @@
"url": "https://opencollective.com/eslint" "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": { "node_modules/espree": {
"version": "9.6.1", "version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@ -4897,6 +5044,16 @@
"node": ">=0.10.0" "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": { "node_modules/eventemitter3": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
@ -4913,6 +5070,15 @@
"node": ">=0.8.x" "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": { "node_modules/extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -4984,6 +5150,29 @@
"reusify": "^1.0.4" "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": { "node_modules/file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -5126,6 +5315,18 @@
"node": ">= 6" "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": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -6711,6 +6912,12 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/is-weakmap": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "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" "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": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -8283,6 +8496,54 @@
"node": "^10 || ^12 || >=14" "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": { "node_modules/node-releases": {
"version": "2.0.17", "version": "2.0.17",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz",
@ -10884,12 +11145,30 @@
"json5": "lib/cli.js" "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": { "node_modules/tslib": {
"version": "2.6.3", "version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"license": "0BSD" "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": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -10993,6 +11272,15 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/typescript": {
"version": "5.5.3", "version": "5.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
@ -11008,6 +11296,12 @@
"node": ">=14.17" "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": { "node_modules/unbox-primitive": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "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" "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": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -11355,6 +11671,15 @@
"url": "https://github.com/sponsors/wooorm" "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": { "node_modules/webpack": {
"version": "5.93.0", "version": "5.93.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz",
@ -11456,6 +11781,47 @@
"url": "https://opencollective.com/webpack" "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -11673,6 +12039,15 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View File

@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@getalby/bitcoin-connect-react": "^3.5.3", "@getalby/bitcoin-connect-react": "^3.5.3",
"@nostr-dev-kit/ndk": "^2.10.0",
"@prisma/client": "^5.17.0", "@prisma/client": "^5.17.0",
"@tanstack/react-query": "^5.51.21", "@tanstack/react-query": "^5.51.21",
"@uiw/react-markdown-preview": "^5.1.2", "@uiw/react-markdown-preview": "^5.1.2",

View File

@ -1,10 +1,9 @@
import React, { useState, useEffect, use } from 'react'; import React, { useState, useEffect, use } from 'react';
import { Carousel } from 'primereact/carousel'; import { Carousel } from 'primereact/carousel';
import { parseCourseEvent } from '@/utils/nostr'; 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 CourseTemplate from '@/components/content/carousels/templates/CourseTemplate';
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
// import { useNostrQueries } from '@/hooks/useNostrQueries';
import { useCoursesQuery } from '@/hooks/nostrQueries/useCoursesQuery'; import { useCoursesQuery } from '@/hooks/nostrQueries/useCoursesQuery';
const responsiveOptions = [ const responsiveOptions = [
@ -27,35 +26,28 @@ const responsiveOptions = [
export default function CoursesCarousel() { export default function CoursesCarousel() {
const [processedCourses, setProcessedCourses] = useState([]); const [processedCourses, setProcessedCourses] = useState([]);
const { fetchZapsForEvents } = useNostr(); const { courses, coursesLoading, coursesError, refetchCourses } = useCoursesQuery()
// const { courses, coursesError, zapsForEvents, refetchZapsForEvents } = useNostrQueries() const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: courses })
const { courses, coursesError, refetchCourses } = useCoursesQuery()
useEffect(() => {
refetchZaps(courses)
}, [courses, refetchZaps]);
useEffect(() => { useEffect(() => {
const fetch = async () => { const fetch = async () => {
try { try {
if (courses && courses.length > 0) { if (courses && courses.length > 0 && zaps) {
console.log('courses:', courses);
// First process the courses to be ready for display
const processedCourses = courses.map(course => parseCourseEvent(course)); const processedCourses = courses.map(course => parseCourseEvent(course));
// Fetch zaps for all processed courses at once let coursesWithZaps = processedCourses.map(course => {
const allZaps = await fetchZapsForEvents(processedCourses); let collectedZaps = []
console.log('allZaps:', allZaps); 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}`)) {
// Process zaps to associate them with their respective courses collectedZaps.push(zap)
const coursesWithZaps = processedCourses.map(course => { }
const relevantZaps = allZaps.filter(zap => { })
const eTagMatches = zap.tags.find(tag => tag[0] === 'e' && tag[1] === course.id); return { ...course, zaps: collectedZaps }
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
};
});
setProcessedCourses(coursesWithZaps); setProcessedCourses(coursesWithZaps);
} else { } else {
@ -66,12 +58,16 @@ export default function CoursesCarousel() {
} }
}; };
fetch(); fetch();
}, [courses]); }, [courses, zaps]);
if (coursesError) { if (coursesError) {
return <div>Error: {coursesError.message}</div> return <div>Error: {coursesError.message}</div>
} }
if (coursesLoading) {
return <div>Loading...</div>
}
return ( return (
<> <>
<h2 className="ml-[6%] mt-4">Courses</h2> <h2 className="ml-[6%] mt-4">Courses</h2>

View File

@ -1,11 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Carousel } from 'primereact/carousel'; import { Carousel } from 'primereact/carousel';
import { useNostr } from '@/hooks/useNostr';
import { parseEvent } from '@/utils/nostr'; import { parseEvent } from '@/utils/nostr';
import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate'; import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate';
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
import { useNostrQueries } from '@/hooks/useNostrQueries'; import { useResourcesQuery } from '@/hooks/nostrQueries/useResourcesQuery';
import { useResourcesQuery } from '@/hooks/useResourcesQuery'; import { useZapsQuery } from '@/hooks/nostrQueries/useZapsQuery';
const responsiveOptions = [ const responsiveOptions = [
{ {
@ -27,36 +26,32 @@ const responsiveOptions = [
export default function ResourcesCarousel() { export default function ResourcesCarousel() {
const [processedResources, setProcessedResources] = useState([]); const [processedResources, setProcessedResources] = useState([]);
const { fetchZapsForEvents } = useNostr(); const { resources, resourcesLoading, resourcesError, refetchResources } = useResourcesQuery()
// const { resources, resourcesError, refetchResources } = useNostrQueries() const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: resources })
const { resources, resourcesError, refetchResources } = useResourcesQuery()
useEffect(() => {
if (resources && resources.length > 0) {
refetchZaps(resources)
}
}, [resources, refetchZaps]);
useEffect(() => { useEffect(() => {
const fetch = async () => { const fetch = async () => {
try { try {
if (resources && resources.length > 0) { if (resources && resources.length > 0 && zaps) {
const processedResources = resources.map(resource => parseEvent(resource)); const processedResources = resources.map(resource => parseEvent(resource));
console.log('processedResources:', processedResources); let resourcesWithZaps = processedResources.map(resource => {
let collectedZaps = []
const allZaps = await fetchZapsForEvents(processedResources); 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}`)) {
const resourcesWithZaps = processedResources.map(resource => { collectedZaps.push(zap)
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'); return { ...resource, zaps: collectedZaps }
const aTagMatches = aTag && resource.d === aTag[1].split(':').pop(); })
return eTagMatches || aTagMatches;
});
return {
...resource,
zaps: relevantZaps
};
});
setProcessedResources(resourcesWithZaps); setProcessedResources(resourcesWithZaps);
} else {
refetchResources();
} }
} catch (error) { } catch (error) {
console.error('Error fetching resources:', error); console.error('Error fetching resources:', error);
@ -65,10 +60,13 @@ export default function ResourcesCarousel() {
fetch(); fetch();
}, [resources]); }, [resources]);
if (resourcesLoading) {
return <div>Loading...</div>
}
if (resourcesError) { if (resourcesError) {
return <div>Error: {resourcesError.message}</div> return <div>Error: {resourcesError.message}</div>
} }
return ( return (
<> <>

View File

@ -1,13 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Carousel } from 'primereact/carousel'; 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 { parseEvent } from '@/utils/nostr';
import WorkshopTemplate from '@/components/content/carousels/templates/WorkshopTemplate'; import WorkshopTemplate from '@/components/content/carousels/templates/WorkshopTemplate';
import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton';
// import { useNostrQueries } from '@/hooks/useNostrQueries';
import { useWorkshopsQuery } from '@/hooks/nostrQueries/useWorkshopsQuery'; import { useWorkshopsQuery } from '@/hooks/nostrQueries/useWorkshopsQuery';
import { useZapsQuery } from '@/hooks/nostrQueries/useZapsQuery';
const responsiveOptions = [ const responsiveOptions = [
{ {
@ -29,32 +26,29 @@ const responsiveOptions = [
export default function WorkshopsCarousel() { export default function WorkshopsCarousel() {
const [processedWorkshops, setProcessedWorkshops] = useState([]) const [processedWorkshops, setProcessedWorkshops] = useState([])
const { workshops, workshopsLoading, workshopsError, refetchWorkshops } = useWorkshopsQuery()
const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({ events: workshops })
// const { workshops, workshopsError } = useNostrQueries() useEffect(() => {
const { workshops, workshopsError, refetchWorkshops } = useWorkshopsQuery() refetchZaps(workshops)
const { fetchZapsForEvents } = useNostr() }, [workshops, refetchZaps]);
useEffect(() => { useEffect(() => {
const fetch = async () => { const fetch = async () => {
try { try {
console.debug('workshops', workshops); console.debug('workshops', workshops);
if (workshops && workshops.length > 0) { if (workshops && workshops.length > 0 && zaps) {
const processedWorkshops = workshops.map(workshop => parseEvent(workshop)); const processedWorkshops = workshops.map(workshop => parseEvent(workshop));
const allZaps = await fetchZapsForEvents(processedWorkshops); let workshopsWithZaps = processedWorkshops.map(workshop => {
let collectedZaps = []
const workshopsWithZaps = processedWorkshops.map(workshop => { zaps.forEach(zap => {
const relevantZaps = allZaps.filter(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}`)) {
const eTagMatches = zap.tags.find(tag => tag[0] === 'e' && tag[1] === workshop.id); collectedZaps.push(zap)
const aTag = zap.tags.find(tag => tag[0] === 'a'); }
const aTagMatches = aTag && workshop.d === aTag[1].split(':').pop(); })
return eTagMatches || aTagMatches; return { ...workshop, zaps: collectedZaps }
}); })
return {
...workshop,
zaps: relevantZaps
};
});
setProcessedWorkshops(workshopsWithZaps); setProcessedWorkshops(workshopsWithZaps);
} else { } else {
@ -65,11 +59,10 @@ export default function WorkshopsCarousel() {
} }
}; };
fetch(); fetch();
}, [workshops]); }, [workshops, zaps]);
if (workshopsError) { if (workshopsLoading) return <div>Loading...</div>;
return <div>Error: {workshopsError.message}</div> if (workshopsError) return <div>Error: {workshopsError}</div>;
}
return ( return (
<> <>

View File

@ -3,20 +3,20 @@ import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { formatTimestampToHowLongAgo } from "@/utils/time"; import { formatTimestampToHowLongAgo } from "@/utils/time";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { useNostr } from "@/hooks/useNostr";
import { getSatAmountFromInvoice } from "@/utils/lightning"; import { getSatAmountFromInvoice } from "@/utils/lightning";
import ZapDisplay from "@/components/zaps/ZapDisplay"; import ZapDisplay from "@/components/zaps/ZapDisplay";
const CourseTemplate = ({course}) => { const CourseTemplate = ({ course }) => {
const [zapAmount, setZapAmount] = useState(null); const [zapAmount, setZapAmount] = useState(0);
const router = useRouter(); const router = useRouter();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
useEffect(() => { useEffect(() => {
if (!course || !course.zaps) return; if (!course?.zaps || !course?.zaps.length > 0) return;
let total = 0; let total = 0;
course.zaps.forEach((zap) => { 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 bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11");
const invoice = bolt11Tag ? bolt11Tag[1] : null; const invoice = bolt11Tag ? bolt11Tag[1] : null;
if (invoice) { if (invoice) {

View File

@ -3,39 +3,41 @@ import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { formatTimestampToHowLongAgo } from "@/utils/time"; import { formatTimestampToHowLongAgo } from "@/utils/time";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { useNostr } from "@/hooks/useNostr"; import { useZapsQuery } from "@/hooks/nostrQueries/useZapsQuery";
import { getSatAmountFromInvoice } from "@/utils/lightning"; 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 router = useRouter();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
useEffect(() => { useEffect(() => {
if (!resource || !resource.zaps) return; if (!resource?.zaps || !resource?.zaps.length > 0) return;
let total = 0; let total = 0;
resource.zaps.forEach((zap) => { 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 bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11");
const invoice = bolt11Tag ? bolt11Tag[1] : null; const invoice = bolt11Tag ? bolt11Tag[1] : null;
if (invoice) { if (invoice) {
const amount = getSatAmountFromInvoice(invoice); const amount = getSatAmountFromInvoice(invoice);
total += amount; total += amount;
} }
}); });
setZapAmount(total); setZapAmount(total);
}, [resource]); }, [resource]);
return ( return (
<div <div
className="flex flex-col items-center mx-auto px-4 mt-8 rounded-md" className="flex flex-col items-center mx-auto px-4 mt-8 rounded-md"
> >
{/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */} {/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */}
<div <div
onClick={() => router.push(`/details/${resource.id}`)} onClick={() => router.push(`/details/${resource.id}`)}
className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer" className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer"
style={{ paddingBottom: "56.25%"}} style={{ paddingBottom: "56.25%" }}
> >
<Image <Image
alt="resource thumbnail" alt="resource thumbnail"
@ -55,9 +57,7 @@ const ResourceTemplate = ({resource}) => {
<p className="text-xs text-gray-400"> <p className="text-xs text-gray-400">
{formatTimestampToHowLongAgo(resource.published_at)} {formatTimestampToHowLongAgo(resource.published_at)}
</p> </p>
<p className="text-xs cursor-pointer"> <ZapDisplay zapAmount={zapAmount} event={resource} />
<i className="pi pi-bolt text-yellow-300"></i> {zapAmount}
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,28 +3,36 @@ import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { formatTimestampToHowLongAgo } from "@/utils/time"; import { formatTimestampToHowLongAgo } from "@/utils/time";
import { useImageProxy } from "@/hooks/useImageProxy"; import { useImageProxy } from "@/hooks/useImageProxy";
import { useNostr } from "@/hooks/useNostr"; import { useZapsQuery } from "@/hooks/nostrQueries/useZapsQuery";
import { getSatAmountFromInvoice } from "@/utils/lightning"; import { getSatAmountFromInvoice } from "@/utils/lightning";
import ZapDisplay from "@/components/zaps/ZapDisplay";
const WorkshopTemplate = ({workshop}) => { const WorkshopTemplate = ({workshop}) => {
const [zapAmount, setZapAmount] = useState(null); const [zapAmount, setZapAmount] = useState(0);
const router = useRouter(); const router = useRouter();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
const { zaps, zapsLoading, zapsError, refetchZaps } = useZapsQuery({event: workshop});
useEffect(() => { useEffect(() => {
if (!workshop || !workshop.zaps) return; if (!zaps || !zaps.length > 0) return;
let total = 0; let total = 0;
workshop.zaps.forEach((zap) => { zaps.forEach((zap) => {
const bolt11Tag = zap.tags.find(tag => tag[0] === "bolt11"); // If the zap matches the event or the parameterized event, then add the zap to the total
const invoice = bolt11Tag ? bolt11Tag[1] : null; 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) { if (invoice) {
const amount = getSatAmountFromInvoice(invoice); const amount = getSatAmountFromInvoice(invoice);
total += amount; total += amount;
}
} }
}); });
setZapAmount(total); setZapAmount(total);
}, [workshop]); }, [zaps, workshop]);
if (zapsLoading) return <div>Loading...</div>;
if (zapsError) return <div>Error: {zapsError}</div>;
return ( return (
<div <div
@ -53,9 +61,7 @@ const WorkshopTemplate = ({workshop}) => {
<p className="text-xs text-gray-400"> <p className="text-xs text-gray-400">
{formatTimestampToHowLongAgo(workshop.published_at)} {formatTimestampToHowLongAgo(workshop.published_at)}
</p> </p>
<p className="text-xs cursor-pointer"> <ZapDisplay zapAmount={zapAmount} event={workshop} />
<i className="pi pi-bolt text-yellow-300"></i> {zapAmount}
</p>
</div> </div>
</div> </div>
</div> </div>

41
src/context/NDKContext.js Normal file
View File

@ -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 (
<NDKContext.Provider value={ndk}>
{children}
</NDKContext.Provider>
);
};
export const useNDKContext = () => {
return useContext(NDKContext);
};

View File

@ -1,58 +1,43 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNostr } from '@/hooks/useNostr';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useNDKContext } from '@/context/NDKContext';
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY
export function useCoursesQuery() { export function useCoursesQuery() {
const [isClient, setIsClient] = useState(false); const [isClient, setIsClient] = useState(false);
const { subscribe } = useNostr(); const ndk = useNDKContext();
useEffect(() => { useEffect(() => {
setIsClient(true); setIsClient(true);
}, []); }, []);
const fetchCourses = async () => { const fetchCoursesFromNDK = async () => {
const filter = [{ kinds: [30004], authors: [AUTHOR_PUBKEY] }]; try {
// Do we need required tags for courses? community instead? console.log('Fetching courses from NDK');
// const hasRequiredTags = (tags) => { await ndk.connect();
// const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
// const hasCourse = tags.some(([tag, value]) => tag === "t" && value === "course"); const filter = { kinds: [30004], authors: [AUTHOR_PUBKEY] };
// return hasPlebDevs && hasCourse; const events = await ndk.fetchEvents(filter);
// };
if (events && events.size > 0) {
return new Promise((resolve, reject) => { const eventsArray = Array.from(events);
let courses = []; console.log('eventsArray', eventsArray)
const subscription = subscribe( // const resources = eventsArray.filter(event => hasRequiredTags(event.tags));
filter, // return resources;
{ return eventsArray;
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);
},
} }
); return [];
} catch (error) {
setTimeout(() => { console.error('Error fetching workshops from NDK:', error);
subscription?.close(); return [];
resolve(courses); }
}, 2000); };
});
}
const { data: courses, isLoading: coursesLoading, error: coursesError, refetch: refetchCourses } = useQuery({ const { data: courses, isLoading: coursesLoading, error: coursesError, refetch: refetchCourses } = useQuery({
queryKey: ['courses', isClient], queryKey: ['courses', isClient],
queryFn: fetchCourses, queryFn: fetchCoursesFromNDK,
staleTime: 1000 * 60 * 10, // 10 minutes staleTime: 1000 * 60 * 30, // 30 minutes
cacheTime: 1000 * 60 * 60, // 1 hour cacheTime: 1000 * 60 * 60, // 1 hour
enabled: isClient, enabled: isClient,
}) })

View File

@ -1,58 +1,48 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNostr } from '@/hooks/useNostr';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useNDKContext } from '@/context/NDKContext';
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY
export function useResourcesQuery() { export function useResourcesQuery() {
const [isClient, setIsClient] = useState(false); const [isClient, setIsClient] = useState(false);
const { subscribe } = useNostr(); const ndk = useNDKContext();
useEffect(() => { useEffect(() => {
setIsClient(true); setIsClient(true);
}, []); }, []);
const fetchResources = async () => { const hasRequiredTags = (tags) => {
console.log('fetching resources'); const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
const filter = [{ kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }]; const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "resource");
const hasRequiredTags = (tags) => { return hasPlebDevs && hasWorkshop;
const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs"); };
const hasResource = tags.some(([tag, value]) => tag === "t" && value === "resource");
return hasPlebDevs && hasResource;
};
return new Promise((resolve, reject) => { const fetchResourcesFromNDK = async () => {
let resources = []; try {
const subscription = subscribe( console.log('Fetching workshops from NDK');
filter, await ndk.connect();
{
onevent: (event) => {
if (hasRequiredTags(event.tags)) {
resources.push(event);
}
},
onerror: (error) => {
console.error('Error fetching resources:', error);
reject(error);
},
onclose: () => {
resolve(resources);
},
}
);
// Set a timeout to resolve the promise after collecting events const filter = { kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] };
setTimeout(() => { const events = await ndk.fetchEvents(filter);
subscription?.close();
resolve(resources); if (events && events.size > 0) {
}, 2000); // Adjust the timeout value as needed 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({ const { data: resources, isLoading: resourcesLoading, error: resourcesError, refetch: refetchResources } = useQuery({
queryKey: ['resources', isClient], queryKey: ['resources', isClient],
queryFn: fetchResources, queryFn: fetchResourcesFromNDK,
staleTime: 1000 * 60 * 10, // 10 minutes staleTime: 1000 * 60 * 30, // 30 minutes
cacheTime: 1000 * 60 * 60, // 1 hour cacheTime: 1000 * 60 * 60, // 1 hour
enabled: isClient, enabled: isClient,
}) })

View File

@ -1,58 +1,48 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNostr } from '@/hooks/useNostr';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useNDKContext } from '@/context/NDKContext';
const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY
export function useWorkshopsQuery() { export function useWorkshopsQuery() {
const [isClient, setIsClient] = useState(false); const [isClient, setIsClient] = useState(false);
const { subscribe } = useNostr(); const ndk = useNDKContext();
useEffect(() => { useEffect(() => {
setIsClient(true); setIsClient(true);
}, []); }, []);
const fetchWorkshops = async () => { const hasRequiredTags = (tags) => {
console.log('fetching workshops'); const hasPlebDevs = tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
const filter = [{ kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] }]; const hasWorkshop = tags.some(([tag, value]) => tag === "t" && value === "workshop");
const hasRequiredTags = (tags) => { return hasPlebDevs && hasWorkshop;
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) => { const fetchWorkshopsFromNDK = async () => {
let resources = []; try {
const subscription = subscribe( console.log('Fetching workshops from NDK');
filter, await ndk.connect();
{
onevent: (event) => { const filter = { kinds: [30023, 30402], authors: [AUTHOR_PUBKEY] };
if (hasRequiredTags(event.tags)) { const events = await ndk.fetchEvents(filter);
resources.push(event);
} if (events && events.size > 0) {
}, const eventsArray = Array.from(events);
onerror: (error) => { console.log('eventsArray', eventsArray)
console.error('Error fetching resources:', error); const resources = eventsArray.filter(event => hasRequiredTags(event.tags));
reject(error); return resources;
},
onclose: () => {
resolve(resources);
},
} }
); return [];
} catch (error) {
// Set a timeout to resolve the promise after collecting events console.error('Error fetching workshops from NDK:', error);
setTimeout(() => { return [];
subscription?.close(); }
resolve(resources); };
}, 2000); // Adjust the timeout value as needed
});
}
const { data: workshops, isLoading: workshopsLoading, error: workshopsError, refetch: refetchWorkshops } = useQuery({ const { data: workshops, isLoading: workshopsLoading, error: workshopsError, refetch: refetchWorkshops } = useQuery({
queryKey: ['workshops', isClient], queryKey: ['workshops', isClient],
queryFn: fetchWorkshops, queryFn: fetchWorkshopsFromNDK,
staleTime: 1000 * 60 * 10, // 10 minutes staleTime: 1000 * 60 * 30, // 30 minutes
cacheTime: 1000 * 60 * 60, // 1 hour cacheTime: 1000 * 60 * 60, // 1 hour
enabled: isClient, enabled: isClient,
}) })

View File

@ -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 }
}

View File

@ -9,6 +9,7 @@ import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css"; import "@uiw/react-markdown-preview/markdown.css";
import Sidebar from '@/components/sidebar/Sidebar'; import Sidebar from '@/components/sidebar/Sidebar';
import { NostrProvider } from '@/context/NostrContext'; import { NostrProvider } from '@/context/NostrContext';
import { NDKProvider } from '@/context/NDKContext';
import { import {
QueryClient, QueryClient,
QueryClientProvider, QueryClientProvider,
@ -22,22 +23,24 @@ export default function MyApp({
return ( return (
<PrimeReactProvider> <PrimeReactProvider>
<NostrProvider> <NostrProvider>
<QueryClientProvider client={queryClient}> <NDKProvider>
<ToastProvider> <QueryClientProvider client={queryClient}>
<Layout> <ToastProvider>
<div className="flex flex-col min-h-screen"> <Layout>
<Navbar /> <div className="flex flex-col min-h-screen">
{/* <div className='flex'> */} <Navbar />
{/* <Sidebar /> */} {/* <div className='flex'> */}
{/* <div className='max-w-[100vw] pl-[15vw]'> */} {/* <Sidebar /> */}
<div className='max-w-[100vw]'> {/* <div className='max-w-[100vw] pl-[15vw]'> */}
<Component {...pageProps} /> <div className='max-w-[100vw]'>
<Component {...pageProps} />
</div>
{/* </div> */}
</div> </div>
{/* </div> */} </Layout>
</div> </ToastProvider>
</Layout> </QueryClientProvider>
</ToastProvider> </NDKProvider>
</QueryClientProvider>
</NostrProvider> </NostrProvider>
</PrimeReactProvider> </PrimeReactProvider>
); );

View File

@ -1,11 +1,9 @@
import Head from 'next/head'; import Head from 'next/head';
import React, { useEffect, useCallback } from 'react'; import React from 'react';
import CoursesCarousel from '@/components/content/carousels/CoursesCarousel'; import CoursesCarousel from '@/components/content/carousels/CoursesCarousel';
import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel'; import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel';
import HeroBanner from '@/components/banner/HeroBanner'; import HeroBanner from '@/components/banner/HeroBanner';
import ResourcesCarousel from '@/components/content/carousels/ResourcesCarousel'; import ResourcesCarousel from '@/components/content/carousels/ResourcesCarousel';
import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage';
import axios from 'axios';
export default function Home() { export default function Home() {
@ -20,8 +18,8 @@ export default function Home() {
<main> <main>
<HeroBanner /> <HeroBanner />
<CoursesCarousel /> <CoursesCarousel />
<WorkshopsCarousel /> {/* <WorkshopsCarousel />
<ResourcesCarousel /> <ResourcesCarousel /> */}
</main> </main>
</> </>
); );