mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-05-21 09:22:01 +00:00
A lot of shit, updates to useNostr hook, added in zapthreads, added in zapper app
This commit is contained in:
parent
a255f1e5b0
commit
11d4acec24
@ -1,6 +1,6 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: false,
|
||||||
images: {
|
images: {
|
||||||
domains: ['localhost', 'secure.gravatar.com'],
|
domains: ['localhost', 'secure.gravatar.com'],
|
||||||
},
|
},
|
||||||
|
158
package-lock.json
generated
158
package-lock.json
generated
@ -28,7 +28,8 @@
|
|||||||
"react-typist": "^2.0.5",
|
"react-typist": "^2.0.5",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1",
|
||||||
|
"zapthreads": "^0.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "20.11.21",
|
"@types/node": "20.11.21",
|
||||||
@ -744,6 +745,80 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@solid-primitives/autofocus": {
|
||||||
|
"version": "0.0.107",
|
||||||
|
"resolved": "https://registry.npmjs.org/@solid-primitives/autofocus/-/autofocus-0.0.107.tgz",
|
||||||
|
"integrity": "sha512-CWa87WQA5McII2uOb74aMYdQq9oNVtKDEdzFKXHHwUY1yXQKXzoXE+yBu4jtqG81IVkJCDpEdaal7PvdSOMhag==",
|
||||||
|
"dependencies": {
|
||||||
|
"@solid-primitives/utils": "^6.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@solid-primitives/event-listener": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@solid-primitives/event-listener/-/event-listener-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-DAJbl+F0wrFW2xmcV8dKMBhk9QLVLuBSW+TR4JmIfTaObxd13PuL7nqaXnaYKDWOYa6otB00qcCUIGbuIhSUgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@solid-primitives/utils": "^6.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@solid-primitives/resize-observer": {
|
||||||
|
"version": "2.0.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@solid-primitives/resize-observer/-/resize-observer-2.0.25.tgz",
|
||||||
|
"integrity": "sha512-jVDXkt2MiriYRaz4DYs62185d+6jQ+1DCsR+v7f6XMsIJJuf963qdBRFjtZtKXBaxdPNMyuPeDgf5XQe3EoDJg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@solid-primitives/event-listener": "^2.3.3",
|
||||||
|
"@solid-primitives/rootless": "^1.4.5",
|
||||||
|
"@solid-primitives/static-store": "^0.0.8",
|
||||||
|
"@solid-primitives/utils": "^6.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@solid-primitives/rootless": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@solid-primitives/rootless/-/rootless-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-GFJE9GC3ojx0aUKqAUZmQPyU8fOVMtnVNrkdk2yS4kd17WqVSpXpoTmo9CnOwA+PG7FTzdIkogvfLQSLs4lrww==",
|
||||||
|
"dependencies": {
|
||||||
|
"@solid-primitives/utils": "^6.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@solid-primitives/scheduled": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@solid-primitives/scheduled/-/scheduled-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-HfWN5w7b7FEc6VPLBKnnE302h90jsLMuR28Fcf7neRGGf8jBj6wm6/UFQ00VlKexHFMR6KQ2u4VBh5a1ZcqM8g==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@solid-primitives/static-store": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@solid-primitives/static-store/-/static-store-0.0.8.tgz",
|
||||||
|
"integrity": "sha512-ZecE4BqY0oBk0YG00nzaAWO5Mjcny8Fc06CdbXadH9T9lzq/9GefqcSe/5AtdXqjvY/DtJ5C6CkcjPZO0o/eqg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@solid-primitives/utils": "^6.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@solid-primitives/utils": {
|
||||||
|
"version": "6.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz",
|
||||||
|
"integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
|
||||||
@ -1675,6 +1750,11 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/component-register": {
|
||||||
|
"version": "0.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/component-register/-/component-register-0.8.3.tgz",
|
||||||
|
"integrity": "sha512-/0u8ov0WPWi2FL78rgB9aFOcfY8pJT4jP/l9NTOukGNLVQ6hk35sEJE1RkEnNQU3yk48Qr7HlDQjRQKEVfgeWg=="
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -2532,8 +2612,7 @@
|
|||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/fast-diff": {
|
"node_modules/fast-diff": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
@ -3150,6 +3229,11 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/idb": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||||
@ -4534,6 +4618,11 @@
|
|||||||
"thenify-all": "^1.0.0"
|
"thenify-all": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nano-markdown": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nano-markdown/-/nano-markdown-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-xp0zc42GsE0JVpcTxICpANgNbVfyhk2pUdRV3cDDxzqVZeXSZgrPGruwlj+umFQxo10BKD1qmWdEdxj1x+A0QQ=="
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||||
@ -4690,9 +4779,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nostr-tools": {
|
"node_modules/nostr-tools": {
|
||||||
"version": "2.3.1",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.5.0.tgz",
|
||||||
"integrity": "sha512-qjKx2C3EzwiQOe2LPSPyCnp07pGz1pWaWjDXcm+L2y2c8iTECbvlzujDANm3nJUjWL5+LVRUVDovTZ1a/DC4Bg==",
|
"integrity": "sha512-G02O3JYNCfhx9NDjd3NOCw/5ck8PX5hiOIhHKpsXyu49ZtZbxGH3OLP9tf0fpUZ+EVWdjIYFR689sV0i7+TOng==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/ciphers": "^0.5.1",
|
"@noble/ciphers": "^0.5.1",
|
||||||
"@noble/curves": "1.2.0",
|
"@noble/curves": "1.2.0",
|
||||||
@ -5789,6 +5878,25 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/seroval": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-TM+Z11tHHvQVQKeNlOUonOWnsNM+2IBwZ4vwoi4j3zKzIpc5IDw8WPwCfcc8F17wy6cBcJGbZbFOR0UCuTZHQA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/seroval-plugins": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-8+pDC1vOedPXjKG7oz8o+iiHrtF2WswaMQJ7CKFpccvSYfrzmvKY9zOJWCg+881722wIHfwkdnRmiiDm9ym+zQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"seroval": "^1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/set-function-length": {
|
"node_modules/set-function-length": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
@ -5879,6 +5987,27 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/solid-element": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/solid-element/-/solid-element-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-DG8HBCej5kNExUiFbVG8OFZojMGcLF8keXdGLEcHXBYtJ7zhm+a8HJnl5lfmBlTYGRk4ApgoBvlwH1ibg7quaQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"component-register": "~0.8.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/solid-js": {
|
||||||
|
"version": "1.8.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.17.tgz",
|
||||||
|
"integrity": "sha512-E0FkUgv9sG/gEBWkHr/2XkBluHb1fkrHywUgA6o6XolPDCJ4g1HaLmQufcBBhiF36ee40q+HpG/vCZu7fLpI3Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"csstype": "^3.1.0",
|
||||||
|
"seroval": "^1.0.4",
|
||||||
|
"seroval-plugins": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||||
@ -6923,6 +7052,23 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/zapthreads": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/zapthreads/-/zapthreads-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-HyYm5XV/xBXcX4VVLbFJkJunH+FEOYjrTkBJ2DU91laT+wKqt/1bEnEO93oTPLa4xnXyxNJZK95uHmbW4pWE8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@solid-primitives/autofocus": "^0.0.107",
|
||||||
|
"@solid-primitives/resize-observer": "^2.0.21",
|
||||||
|
"@solid-primitives/scheduled": "^1.3.2",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"idb": "^7.1.1",
|
||||||
|
"light-bolt11-decoder": "^3.0.0",
|
||||||
|
"nano-markdown": "^1.2.2",
|
||||||
|
"nostr-tools": "^2.4.0",
|
||||||
|
"solid-element": "^1.7.1",
|
||||||
|
"solid-js": "^1.8.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zustand": {
|
"node_modules/zustand": {
|
||||||
"version": "4.5.2",
|
"version": "4.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz",
|
||||||
|
@ -29,7 +29,8 @@
|
|||||||
"react-typist": "^2.0.5",
|
"react-typist": "^2.0.5",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1",
|
||||||
|
"zapthreads": "^0.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "20.11.21",
|
"@types/node": "20.11.21",
|
||||||
|
57
src/components/ZapThreadsWrapper.js
Normal file
57
src/components/ZapThreadsWrapper.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
const ZapThreadsWrapper = ({ anchor, relays, disable }) => {
|
||||||
|
// Create a ref to store the reference to the <div> element
|
||||||
|
const zapRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Create a new <script> element
|
||||||
|
const script = document.createElement('script');
|
||||||
|
// Set the source URL of the script to load the ZapThreads library
|
||||||
|
script.src = 'https://unpkg.com/zapthreads/dist/zapthreads.iife.js';
|
||||||
|
// Set the script to load asynchronously
|
||||||
|
script.async = true;
|
||||||
|
|
||||||
|
// Function to handle the script load event
|
||||||
|
const handleScriptLoad = () => {
|
||||||
|
// Create a new <zap-threads> element
|
||||||
|
const zapElement = document.createElement('zap-threads');
|
||||||
|
zapElement.setAttribute('anchor', anchor);
|
||||||
|
zapElement.setAttribute('relays', relays);
|
||||||
|
zapElement.setAttribute('disable', disable);
|
||||||
|
|
||||||
|
// Remove any existing <zap-threads> element before appending a new one
|
||||||
|
if (zapRef.current && zapRef.current.firstChild) {
|
||||||
|
zapRef.current.removeChild(zapRef.current.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the new <zap-threads> element to the <div> referenced by zapRef
|
||||||
|
if (zapRef.current) {
|
||||||
|
zapRef.current.appendChild(zapElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach the handleScriptLoad function to the script's load event
|
||||||
|
script.addEventListener('load', handleScriptLoad);
|
||||||
|
|
||||||
|
// Append the <script> element to the <body> of the document
|
||||||
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
// Cleanup function to remove the <zap-threads> element and the <script> element when the component is unmounted
|
||||||
|
return () => {
|
||||||
|
// Remove the <zap-threads> element from the <div> referenced by zapRef
|
||||||
|
if (zapRef.current && zapRef.current.firstChild) {
|
||||||
|
zapRef.current.removeChild(zapRef.current.firstChild);
|
||||||
|
}
|
||||||
|
// Remove the <script> element from the <body> of the document
|
||||||
|
document.body.removeChild(script);
|
||||||
|
// Remove the load event listener from the script
|
||||||
|
script.removeEventListener('load', handleScriptLoad);
|
||||||
|
};
|
||||||
|
}, [anchor, relays, disable]);
|
||||||
|
|
||||||
|
// Render a <div> element and attach the zapRef to it
|
||||||
|
return <div ref={zapRef} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ZapThreadsWrapper;
|
@ -26,7 +26,7 @@ const responsiveOptions = [
|
|||||||
export default function CoursesCarousel() {
|
export default function CoursesCarousel() {
|
||||||
const [processedCourses, setProcessedCourses] = useState([]);
|
const [processedCourses, setProcessedCourses] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { fetchCourses } = useNostr();
|
const { fetchCourses, fetchZapsForEvents } = useNostr();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetch = async () => {
|
const fetch = async () => {
|
||||||
@ -34,19 +34,38 @@ export default function CoursesCarousel() {
|
|||||||
try {
|
try {
|
||||||
const fetchedCourses = await fetchCourses();
|
const fetchedCourses = await fetchCourses();
|
||||||
if (fetchedCourses && fetchedCourses.length > 0) {
|
if (fetchedCourses && fetchedCourses.length > 0) {
|
||||||
const processed = fetchedCourses.map(course => parseEvent(course));
|
// First process the courses to be ready for display
|
||||||
setProcessedCourses(processed);
|
const processedCourses = fetchedCourses.map(course => parseEvent(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
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setProcessedCourses(coursesWithZaps);
|
||||||
} else {
|
} else {
|
||||||
console.log('No courses fetched or empty array returned');
|
console.log('No courses fetched or empty array returned');
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching courses:', error);
|
console.error('Error fetching courses:', error);
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
};
|
};
|
||||||
fetch();
|
fetch();
|
||||||
}, [fetchCourses]);
|
}, [fetchCourses, fetchZapsForEvents]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -28,7 +28,7 @@ const responsiveOptions = [
|
|||||||
export default function ResourcesCarousel() {
|
export default function ResourcesCarousel() {
|
||||||
const [processedResources, setProcessedResources] = useState([]);
|
const [processedResources, setProcessedResources] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { fetchResources } = useNostr();
|
const { fetchResources, fetchZapsForEvents } = useNostr();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetch = async () => {
|
const fetch = async () => {
|
||||||
@ -36,19 +36,35 @@ export default function ResourcesCarousel() {
|
|||||||
try {
|
try {
|
||||||
const fetchedResources = await fetchResources();
|
const fetchedResources = await fetchResources();
|
||||||
if (fetchedResources && fetchedResources.length > 0) {
|
if (fetchedResources && fetchedResources.length > 0) {
|
||||||
const processed = fetchedResources.map(resource => parseEvent(resource));
|
const processedResources = fetchedResources.map(resource => parseEvent(resource));
|
||||||
setProcessedResources(processed);
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setProcessedResources(resourcesWithZaps);
|
||||||
} else {
|
} else {
|
||||||
console.log('No resources fetched or empty array returned');
|
console.log('No resources fetched or empty array returned');
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching resources:', error);
|
console.error('Error fetching resources:', error);
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
};
|
};
|
||||||
fetch();
|
fetch();
|
||||||
}, [fetchResources]);
|
}, [fetchResources, fetchZapsForEvents]); // Assuming fetchZapsForEvents is adjusted to handle resources
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -28,7 +28,7 @@ const responsiveOptions = [
|
|||||||
export default function WorkshopsCarousel() {
|
export default function WorkshopsCarousel() {
|
||||||
const [processedWorkshops, setProcessedWorkshops] = useState([]);
|
const [processedWorkshops, setProcessedWorkshops] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { fetchWorkshops } = useNostr();
|
const { fetchWorkshops, fetchZapsForEvents } = useNostr();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetch = async () => {
|
const fetch = async () => {
|
||||||
@ -36,19 +36,35 @@ export default function WorkshopsCarousel() {
|
|||||||
try {
|
try {
|
||||||
const fetchedWorkshops = await fetchWorkshops();
|
const fetchedWorkshops = await fetchWorkshops();
|
||||||
if (fetchedWorkshops && fetchedWorkshops.length > 0) {
|
if (fetchedWorkshops && fetchedWorkshops.length > 0) {
|
||||||
const processed = fetchedWorkshops.map(workshop => parseEvent(workshop));
|
const processedWorkshops = fetchedWorkshops.map(workshop => parseEvent(workshop));
|
||||||
setProcessedWorkshops(processed);
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setProcessedWorkshops(workshopsWithZaps);
|
||||||
} else {
|
} else {
|
||||||
console.log('No workshops fetched or empty array returned');
|
console.log('No workshops fetched or empty array returned');
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching workshops:', error);
|
console.error('Error fetching workshops:', error);
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
};
|
};
|
||||||
fetch();
|
fetch();
|
||||||
}, [fetchWorkshops]);
|
}, [fetchWorkshops, fetchZapsForEvents]); // Assuming fetchZapsForEvents is adjusted to handle workshops
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useMemo } from "react";
|
||||||
import Image from "next/image";
|
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";
|
||||||
@ -8,21 +8,16 @@ import { getSatAmountFromInvoice } from "@/utils/lightning";
|
|||||||
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
import ZapDisplay from "@/components/zaps/ZapDisplay";
|
||||||
|
|
||||||
const CourseTemplate = (course) => {
|
const CourseTemplate = (course) => {
|
||||||
const [zaps, setZaps] = useState([]);
|
|
||||||
const [zapAmount, setZapAmount] = useState(null);
|
const [zapAmount, setZapAmount] = useState(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const { fetchZapsForEvent } = useNostr();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchZaps = async () => {
|
if (!course || !course.zaps) return;
|
||||||
try {
|
|
||||||
const zaps = await fetchZapsForEvent(course);
|
|
||||||
// console.log('zaps:', zaps);
|
|
||||||
if (zaps.length > 0) {
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
zaps.map((zap) => {
|
course.zaps.forEach((zap) => {
|
||||||
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);
|
||||||
@ -30,13 +25,8 @@ const CourseTemplate = (course) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
setZapAmount(total);
|
setZapAmount(total);
|
||||||
}
|
}, [course]);
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching zaps:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchZaps();
|
|
||||||
}, [fetchZapsForEvent, course]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useMemo } from "react";
|
||||||
import Image from "next/image";
|
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";
|
||||||
@ -7,36 +7,25 @@ import { useNostr } from "@/hooks/useNostr";
|
|||||||
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
||||||
|
|
||||||
const ResourceTemplate = (resource) => {
|
const ResourceTemplate = (resource) => {
|
||||||
const [zaps, setZaps] = useState([]);
|
|
||||||
const [zapAmount, setZapAmount] = useState(null);
|
const [zapAmount, setZapAmount] = useState(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const { fetchZapsForEvent } = useNostr();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchZaps = async () => {
|
if (!resource || !resource.zaps) return;
|
||||||
try {
|
|
||||||
const zaps = await fetchZapsForEvent(resource);
|
|
||||||
setZaps(zaps);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching zaps:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchZaps();
|
|
||||||
}, [fetchZapsForEvent, resource]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
let total = 0;
|
||||||
if (zaps.length > 0) {
|
resource.zaps.forEach((zap) => {
|
||||||
zaps.map((zap) => {
|
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);
|
||||||
setZapAmount(zapAmount + amount);
|
total += amount;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
setZapAmount(total);
|
||||||
}, [zaps]);
|
}, [resource]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useMemo } from "react";
|
||||||
import Image from "next/image";
|
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";
|
||||||
@ -7,36 +7,24 @@ import { useNostr } from "@/hooks/useNostr";
|
|||||||
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
import { getSatAmountFromInvoice } from "@/utils/lightning";
|
||||||
|
|
||||||
const WorkshopTemplate = (workshop) => {
|
const WorkshopTemplate = (workshop) => {
|
||||||
const [zaps, setZaps] = useState([]);
|
|
||||||
const [zapAmount, setZapAmount] = useState(null);
|
const [zapAmount, setZapAmount] = useState(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const { fetchZapsForEvent } = useNostr();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchZaps = async () => {
|
if (!workshop || !workshop.zaps) return;
|
||||||
try {
|
|
||||||
const zaps = await fetchZapsForEvent(workshop);
|
|
||||||
setZaps(zaps);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching zaps:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchZaps();
|
|
||||||
}, [fetchZapsForEvent, workshop]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
let total = 0;
|
||||||
if (zaps.length > 0) {
|
workshop.zaps.forEach((zap) => {
|
||||||
zaps.map((zap) => {
|
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);
|
||||||
setZapAmount(zapAmount + amount);
|
total += amount;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
setZapAmount(total);
|
||||||
}, [zaps]);
|
}, [workshop]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -13,7 +13,10 @@ const UserContent = () => {
|
|||||||
const [activeIndex, setActiveIndex] = useState(0);
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
const [drafts, setDrafts] = useState([]);
|
const [drafts, setDrafts] = useState([]);
|
||||||
const [user, setUser] = useLocalStorageWithEffect('user', {});
|
const [user, setUser] = useLocalStorageWithEffect('user', {});
|
||||||
const { fetchCourses, fetchResources, fetchWorkshops, events } = useNostr();
|
const [courses, setCourses] = useState([]);
|
||||||
|
const [resources, setResources] = useState([]);
|
||||||
|
const [workshops, setWorkshops] = useState([]);
|
||||||
|
const { fetchCourses, fetchResources, fetchWorkshops } = useNostr();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
|
|
||||||
@ -40,13 +43,22 @@ const UserContent = () => {
|
|||||||
console.log('drafts:', drafts);
|
console.log('drafts:', drafts);
|
||||||
|
|
||||||
// Fetch resources, workshops, and courses from Nostr
|
// Fetch resources, workshops, and courses from Nostr
|
||||||
fetchResources();
|
const resources = await fetchResources();
|
||||||
fetchWorkshops();
|
const workshops = await fetchWorkshops();
|
||||||
fetchCourses();
|
const courses = await fetchCourses();
|
||||||
|
|
||||||
if (drafts.length > 0) {
|
if (drafts.length > 0) {
|
||||||
setDrafts(drafts);
|
setDrafts(drafts);
|
||||||
}
|
}
|
||||||
|
if (resources.length > 0) {
|
||||||
|
setResources(resources);
|
||||||
|
}
|
||||||
|
if (workshops.length > 0) {
|
||||||
|
setWorkshops(workshops);
|
||||||
|
}
|
||||||
|
if (courses.length > 0) {
|
||||||
|
setCourses(courses);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
showToast('error', 'Error', 'Failed to fetch content');
|
showToast('error', 'Error', 'Failed to fetch content');
|
||||||
@ -60,17 +72,17 @@ const UserContent = () => {
|
|||||||
case 1:
|
case 1:
|
||||||
return drafts;
|
return drafts;
|
||||||
case 2:
|
case 2:
|
||||||
return events.resources.map(resource => {
|
return resources.map(resource => {
|
||||||
const { id, content, title, summary, image, published_at } = parseEvent(resource);
|
const { id, content, title, summary, image, published_at } = parseEvent(resource);
|
||||||
return { id, content, title, summary, image, published_at };
|
return { id, content, title, summary, image, published_at };
|
||||||
});
|
});
|
||||||
case 3:
|
case 3:
|
||||||
return events.workshops.map(workshop => {
|
return workshops.map(workshop => {
|
||||||
const { id, content, title, summary, image, published_at } = parseEvent(workshop);
|
const { id, content, title, summary, image, published_at } = parseEvent(workshop);
|
||||||
return { id, content, title, summary, image, published_at };
|
return { id, content, title, summary, image, published_at };
|
||||||
})
|
})
|
||||||
case 4:
|
case 4:
|
||||||
return events.courses.map(course => {
|
return courses.map(course => {
|
||||||
const { id, content, title, summary, image, published_at } = parseEvent(course);
|
const { id, content, title, summary, image, published_at } = parseEvent(course);
|
||||||
return { id, content, title, summary, image, published_at };
|
return { id, content, title, summary, image, published_at };
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,7 @@ const ZapDisplay = ({ zapAmount, event }) => {
|
|||||||
<p className="text-xs cursor-pointer" onClick={(e) => op.current.toggle(e)}>
|
<p className="text-xs cursor-pointer" onClick={(e) => op.current.toggle(e)}>
|
||||||
<i className="pi pi-bolt text-yellow-300"></i> {zapAmount}
|
<i className="pi pi-bolt text-yellow-300"></i> {zapAmount}
|
||||||
</p>
|
</p>
|
||||||
<OverlayPanel ref={op}>
|
<OverlayPanel className='w-[40%] h-[40%]' ref={op}>
|
||||||
<ZapForm event={event} />
|
<ZapForm event={event} />
|
||||||
</OverlayPanel>
|
</OverlayPanel>
|
||||||
</>
|
</>
|
||||||
|
@ -1,48 +1,21 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { Button } from 'primereact/button';
|
import { nip19 } from "nostr-tools";
|
||||||
import { InputText } from 'primereact/inputtext';
|
|
||||||
import { InputTextarea } from 'primereact/inputtextarea';
|
|
||||||
import { useNostr } from "@/hooks/useNostr";
|
|
||||||
|
|
||||||
const ZapForm = ({ event }) => {
|
const ZapForm = ({ event }) => {
|
||||||
const [zapAmount, setZapAmount] = useState(0);
|
const nAddress = nip19.naddrEncode({
|
||||||
const [comment, setComment] = useState("");
|
kind: event?.kind,
|
||||||
|
pubkey: event?.pubkey,
|
||||||
const { zapEvent } = useNostr();
|
identifier: event.d,
|
||||||
|
})
|
||||||
const handleZapButton = (amount) => {
|
|
||||||
setZapAmount(amount);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCustomAmountChange = (event) => {
|
|
||||||
setZapAmount(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCommentChange = (event) => {
|
|
||||||
setComment(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
const millisatAmount = zapAmount * 1000;
|
|
||||||
const response = await zapEvent(event, millisatAmount, comment);
|
|
||||||
|
|
||||||
console.log('zap response:', response);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<iframe
|
||||||
<div className="flex flex-row justify-start">
|
src={`https://zapper.nostrapps.org/zap?id=${nAddress}`}
|
||||||
{[1, 10, 21, 100, 500, 1000].map(amount => (
|
width="100%"
|
||||||
<Button key={amount} label={amount.toString()} icon="pi pi-bolt" severity="success"
|
height="100%"
|
||||||
rounded className="mr-2" onClick={() => handleZapButton(amount)} />
|
style={{ border: 'none' }}
|
||||||
))}
|
title="zapper app"
|
||||||
</div>
|
></iframe>
|
||||||
<div className="flex flex-row w-[100%] justify-between my-4">
|
|
||||||
<InputText placeholder="Custom Amount" value={zapAmount} onChange={handleCustomAmountChange} />
|
|
||||||
</div>
|
|
||||||
<InputTextarea rows={5} placeholder="Message" value={comment} onChange={handleCommentChange} />
|
|
||||||
<Button label="Zap" icon="pi pi-bolt" severity="success" className="mt-4" onClick={handleSubmit} />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback, useContext } from 'react';
|
import { useState, useEffect, useCallback, useContext, useRef } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { nip57, nip19 } from 'nostr-tools';
|
import { nip57, nip19 } from 'nostr-tools';
|
||||||
import { NostrContext } from '@/context/NostrContext';
|
import { NostrContext } from '@/context/NostrContext';
|
||||||
@ -16,14 +16,38 @@ const defaultRelays = [
|
|||||||
|
|
||||||
export function useNostr() {
|
export function useNostr() {
|
||||||
const pool = useContext(NostrContext);
|
const pool = useContext(NostrContext);
|
||||||
|
const subscriptionQueue = useRef([]);
|
||||||
|
const lastSubscriptionTime = useRef(0);
|
||||||
|
const throttleDelay = 2000;
|
||||||
|
|
||||||
|
const processSubscriptionQueue = useCallback(() => {
|
||||||
|
if (subscriptionQueue.current.length === 0) return;
|
||||||
|
|
||||||
|
const currentTime = Date.now();
|
||||||
|
if (currentTime - lastSubscriptionTime.current < throttleDelay) {
|
||||||
|
setTimeout(processSubscriptionQueue, throttleDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = subscriptionQueue.current.shift();
|
||||||
|
subscription();
|
||||||
|
|
||||||
|
lastSubscriptionTime.current = currentTime;
|
||||||
|
setTimeout(processSubscriptionQueue, throttleDelay);
|
||||||
|
}, [throttleDelay]);
|
||||||
|
|
||||||
const subscribe = useCallback(
|
const subscribe = useCallback(
|
||||||
(filters, opts) => {
|
(filters, opts) => {
|
||||||
if (!pool) return;
|
if (!pool) return;
|
||||||
|
|
||||||
|
const subscriptionFn = () => {
|
||||||
return pool.subscribeMany(defaultRelays, filters, opts);
|
return pool.subscribeMany(defaultRelays, filters, opts);
|
||||||
|
};
|
||||||
|
|
||||||
|
subscriptionQueue.current.push(subscriptionFn);
|
||||||
|
processSubscriptionQueue();
|
||||||
},
|
},
|
||||||
[pool]
|
[pool, processSubscriptionQueue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const publish = useCallback(
|
const publish = useCallback(
|
||||||
@ -43,8 +67,18 @@ export function useNostr() {
|
|||||||
const fetchSingleEvent = useCallback(
|
const fetchSingleEvent = useCallback(
|
||||||
async (id) => {
|
async (id) => {
|
||||||
try {
|
try {
|
||||||
const event = await pool.get(defaultRelays, {
|
const event = await new Promise((resolve, reject) => {
|
||||||
ids: [id],
|
subscribe(
|
||||||
|
[{ ids: [id] }],
|
||||||
|
{
|
||||||
|
onevent: (event) => {
|
||||||
|
resolve(event);
|
||||||
|
},
|
||||||
|
onerror: (error) => {
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
return event;
|
return event;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -52,20 +86,33 @@ export function useNostr() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[pool]
|
[subscribe]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchZapsForEvent = useCallback(
|
const querySyncQueue = useRef([]);
|
||||||
async (event) => {
|
const lastQuerySyncTime = useRef(0);
|
||||||
|
|
||||||
|
const processQuerySyncQueue = useCallback(() => {
|
||||||
|
if (querySyncQueue.current.length === 0) return;
|
||||||
|
|
||||||
|
const currentTime = Date.now();
|
||||||
|
if (currentTime - lastQuerySyncTime.current < throttleDelay) {
|
||||||
|
setTimeout(processQuerySyncQueue, throttleDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const querySync = querySyncQueue.current.shift();
|
||||||
|
querySync();
|
||||||
|
|
||||||
|
lastQuerySyncTime.current = currentTime;
|
||||||
|
setTimeout(processQuerySyncQueue, throttleDelay);
|
||||||
|
}, [throttleDelay]);
|
||||||
|
|
||||||
|
const fetchZapsForParamaterizedEvent = useCallback(
|
||||||
|
async (kind, id, d) => {
|
||||||
try {
|
try {
|
||||||
let zaps = [];
|
const filters = { kinds: [9735], '#a': [`${kind}:${id}:${d}`] };
|
||||||
const paramaterizedFilter = { kinds: [9735], '#a': [`${event.kind}:${event.id}:${event.d}`] };
|
const zaps = await pool.querySync(defaultRelays, filters);
|
||||||
const paramaterizedZaps = await pool.querySync(defaultRelays, paramaterizedFilter);
|
|
||||||
console.log('paramaterizedZaps:', paramaterizedZaps);
|
|
||||||
const filter = { kinds: [9735], '#e': [event.id] };
|
|
||||||
const zapsForEvent = await pool.querySync(defaultRelays, filter);
|
|
||||||
console.log('zapsForEvent:', zapsForEvent);
|
|
||||||
zaps = zaps.concat(paramaterizedZaps, zapsForEvent);
|
|
||||||
return zaps;
|
return zaps;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch zaps for event:', error);
|
console.error('Failed to fetch zaps for event:', error);
|
||||||
@ -75,18 +122,117 @@ export function useNostr() {
|
|||||||
[pool]
|
[pool]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchZapsForNonParameterizedEvent = useCallback(
|
||||||
|
async (id) => {
|
||||||
|
try {
|
||||||
|
const filters = { kinds: [9735], '#e': [id] };
|
||||||
|
const zaps = await pool.querySync(defaultRelays, filters);
|
||||||
|
return zaps;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch zaps for event:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[pool]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchZapsForEvent = useCallback(
|
||||||
|
async (event) => {
|
||||||
|
const querySyncFn = async () => {
|
||||||
|
try {
|
||||||
|
const parameterizedZaps = await fetchZapsForParamaterizedEvent(event.kind, event.id, event.d);
|
||||||
|
const nonParameterizedZaps = await fetchZapsForNonParameterizedEvent(event.id);
|
||||||
|
return [...parameterizedZaps, ...nonParameterizedZaps];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch zaps for event:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
querySyncQueue.current.push(async () => {
|
||||||
|
const zaps = await querySyncFn();
|
||||||
|
resolve(zaps);
|
||||||
|
});
|
||||||
|
processQuerySyncQueue();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[fetchZapsForParamaterizedEvent, fetchZapsForNonParameterizedEvent, processQuerySyncQueue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchZapsForEvents = useCallback(
|
||||||
|
async (events) => {
|
||||||
|
const querySyncFn = async () => {
|
||||||
|
try {
|
||||||
|
// Collect all #a and #e tag values from the list of events
|
||||||
|
let aTags = [];
|
||||||
|
let eTags = [];
|
||||||
|
events.forEach(event => {
|
||||||
|
aTags.push(`${event.kind}:${event.id}:${event.d}`);
|
||||||
|
eTags.push(event.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create filters for batch querying
|
||||||
|
const filterA = { kinds: [9735], '#a': aTags };
|
||||||
|
const filterE = { kinds: [9735], '#e': eTags };
|
||||||
|
|
||||||
|
// Perform batch queries
|
||||||
|
const [zapsA, zapsE] = await Promise.all([
|
||||||
|
pool.querySync(defaultRelays, filterA),
|
||||||
|
pool.querySync(defaultRelays, filterE)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Combine results and filter out duplicates
|
||||||
|
const combinedZaps = [...zapsA];
|
||||||
|
const existingIds = new Set(zapsA.map(zap => zap.id));
|
||||||
|
zapsE.forEach(zap => {
|
||||||
|
if (!existingIds.has(zap.id)) {
|
||||||
|
combinedZaps.push(zap);
|
||||||
|
existingIds.add(zap.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return combinedZaps;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch zaps for events:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
querySyncQueue.current.push(async () => {
|
||||||
|
const zaps = await querySyncFn();
|
||||||
|
resolve(zaps);
|
||||||
|
});
|
||||||
|
processQuerySyncQueue();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[pool, processQuerySyncQueue]
|
||||||
|
);
|
||||||
|
|
||||||
const fetchKind0 = useCallback(
|
const fetchKind0 = useCallback(
|
||||||
async (publicKey) => {
|
async (publicKey) => {
|
||||||
try {
|
try {
|
||||||
const filter = { authors: [publicKey], kinds: [0] };
|
const kind0 = await new Promise((resolve, reject) => {
|
||||||
const kind0 = await pool.querySync(defaultRelays, filter);
|
subscribe(
|
||||||
return JSON.parse(kind0[0].content);
|
[{ authors: [publicKey], kinds: [0] }],
|
||||||
|
{
|
||||||
|
onevent: (event) => {
|
||||||
|
resolve(JSON.parse(event.content));
|
||||||
|
},
|
||||||
|
onerror: (error) => {
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return kind0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch kind 0 for event:', error);
|
console.error('Failed to fetch kind 0 for event:', error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[pool]
|
[subscribe]
|
||||||
);
|
);
|
||||||
|
|
||||||
const zapEvent = useCallback(
|
const zapEvent = useCallback(
|
||||||
@ -107,13 +253,13 @@ export function useNostr() {
|
|||||||
const response = await axios.get(lud16Url);
|
const response = await axios.get(lud16Url);
|
||||||
|
|
||||||
if (response.data.allowsNostr) {
|
if (response.data.allowsNostr) {
|
||||||
const zapReq = nip57.makeZapRequest({
|
// const zapReq = nip57.makeZapRequest({
|
||||||
profile: event.pubkey,
|
// profile: event.pubkey,
|
||||||
event: event.id,
|
// event: event.id,
|
||||||
amount: amount,
|
// amount: amount,
|
||||||
relays: defaultRelays,
|
// relays: defaultRelays,
|
||||||
comment: comment ? comment : 'Plebdevs Zap',
|
// comment: comment ? comment : 'Plebdevs Zap',
|
||||||
});
|
// });
|
||||||
|
|
||||||
const user = window.localStorage.getItem('user');
|
const user = window.localStorage.getItem('user');
|
||||||
|
|
||||||
@ -125,19 +271,19 @@ export function useNostr() {
|
|||||||
|
|
||||||
console.log('pubkey:', pubkey);
|
console.log('pubkey:', pubkey);
|
||||||
|
|
||||||
// const zapRequest = {
|
const zapReq = {
|
||||||
// kind: 9734,
|
kind: 9734,
|
||||||
// content: "",
|
content: "",
|
||||||
// tags: [
|
tags: [
|
||||||
// ["relays", defaultRelays[4], defaultRelays[5]],
|
["relays", defaultRelays[4], defaultRelays[5]],
|
||||||
// ["amount", amount.toString()],
|
["amount", amount.toString()],
|
||||||
// // ["lnurl", lnurl],
|
// ["lnurl", lnurl],
|
||||||
// ["e", event.id],
|
["e", event.id],
|
||||||
// ["p", event.pubkey],
|
["p", event.pubkey],
|
||||||
// // ["a", `${event.kind}:${event.id}:${event.d}`],
|
["a", `${event.kind}:${event.id}:${event.d}`],
|
||||||
// ],
|
],
|
||||||
// created_at: Math.floor(Date.now() / 1000)
|
created_at: Math.floor(Date.now() / 1000)
|
||||||
// }
|
}
|
||||||
|
|
||||||
console.log('zapRequest:', zapReq);
|
console.log('zapRequest:', zapReq);
|
||||||
|
|
||||||
@ -167,7 +313,7 @@ export function useNostr() {
|
|||||||
} else if (profile.lud06) {
|
} else if (profile.lud06) {
|
||||||
// handle lnurlpay
|
// handle lnurlpay
|
||||||
} else {
|
} else {
|
||||||
// showToast('error', 'Error', 'User has no Lightning Address or LNURL');
|
showToast('error', 'Error', 'User has no Lightning Address or LNURL');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[fetchKind0]
|
[fetchKind0]
|
||||||
@ -287,5 +433,5 @@ export function useNostr() {
|
|||||||
});
|
});
|
||||||
}, [subscribe]);
|
}, [subscribe]);
|
||||||
|
|
||||||
return { subscribe, publish, fetchSingleEvent, fetchZapsForEvent, fetchKind0, fetchResources, fetchWorkshops, fetchCourses, zapEvent };
|
return { subscribe, publish, fetchSingleEvent, fetchZapsForEvent, fetchKind0, fetchResources, fetchWorkshops, fetchCourses, zapEvent, fetchZapsForEvents };
|
||||||
}
|
}
|
@ -6,8 +6,11 @@ import { parseEvent, findKind0Fields, hexToNpub } from '@/utils/nostr';
|
|||||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
import { useImageProxy } from '@/hooks/useImageProxy';
|
||||||
import { Button } from 'primereact/button';
|
import { Button } from 'primereact/button';
|
||||||
import { Tag } from 'primereact/tag';
|
import { Tag } from 'primereact/tag';
|
||||||
|
import { nip19 } from 'nostr-tools';
|
||||||
|
import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
|
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
@ -35,7 +38,10 @@ export default function Details() {
|
|||||||
const [processedEvent, setProcessedEvent] = useState({});
|
const [processedEvent, setProcessedEvent] = useState({});
|
||||||
const [author, setAuthor] = useState(null);
|
const [author, setAuthor] = useState(null);
|
||||||
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
const [bitcoinConnect, setBitcoinConnect] = useState(false);
|
||||||
|
const [nAddress, setNAddress] = useState(null);
|
||||||
|
|
||||||
|
const [user] = useLocalStorageWithEffect('user', {});
|
||||||
|
console.log('user:', user);
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const { fetchSingleEvent, fetchKind0, zapEvent } = useNostr();
|
const { fetchSingleEvent, fetchKind0, zapEvent } = useNostr();
|
||||||
|
|
||||||
@ -90,11 +96,23 @@ export default function Details() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (event) {
|
if (event) {
|
||||||
const { id, pubkey, content, title, summary, image, published_at } = parseEvent(event);
|
const { id, pubkey, content, title, summary, image, published_at, d } = parseEvent(event);
|
||||||
setProcessedEvent({ id, pubkey, content, title, summary, image, published_at });
|
setProcessedEvent({ id, pubkey, content, title, summary, image, published_at, d });
|
||||||
}
|
}
|
||||||
}, [event]);
|
}, [event]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (processedEvent?.d) {
|
||||||
|
const naddr = nip19.naddrEncode({
|
||||||
|
pubkey: processedEvent.pubkey,
|
||||||
|
kind: processedEvent.kind,
|
||||||
|
identifier: processedEvent.d,
|
||||||
|
});
|
||||||
|
console.log('naddr:', naddr);
|
||||||
|
setNAddress(naddr);
|
||||||
|
}
|
||||||
|
}, [processedEvent]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
<div className='w-full px-24 pt-12 mx-auto mt-4 max-tab:px-0 max-mob:px-0 max-tab:pt-2 max-mob:pt-2'>
|
||||||
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
<div className='w-full flex flex-row justify-between max-tab:flex-col max-mob:flex-col'>
|
||||||
@ -160,6 +178,16 @@ export default function Details() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{typeof window !== 'undefined' && nAddress !== null && (
|
||||||
|
<div className='px-24'>
|
||||||
|
<ZapThreadsWrapper
|
||||||
|
anchor={nAddress}
|
||||||
|
user={user?.pubkey || null}
|
||||||
|
relays="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/"
|
||||||
|
disable=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className='w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]'>
|
<div className='w-[75vw] mx-auto mt-12 p-12 border-t-2 border-gray-300 max-tab:p-0 max-mob:p-0 max-tab:max-w-[100vw] max-mob:max-w-[100vw]'>
|
||||||
{
|
{
|
||||||
processedEvent?.content && <MarkdownContent content={processedEvent.content} />
|
processedEvent?.content && <MarkdownContent content={processedEvent.content} />
|
||||||
|
@ -43,6 +43,11 @@ h3 {
|
|||||||
color: yellow;
|
color: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-overlaypanel-content {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* hero banner animation */
|
/* hero banner animation */
|
||||||
@keyframes flip {
|
@keyframes flip {
|
||||||
0%, 100% {
|
0%, 100% {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user