From c705809278952b995b15c4ad5cb303859d7e392b Mon Sep 17 00:00:00 2001 From: thebullishbitcoiner <139607837+thebullishbitcoiner@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:09:04 -0600 Subject: [PATCH] (1) Added NDK 2.10.7 (2) Implemented Nut Splits (3) Removed setDataOutput and corresponding GUI elements --- package-lock.json | 242 +++++++++++++++++++++++++++- package.json | 1 + src/components/NutSplits.js | 171 ++++++++++++++++++++ src/components/NutSplits.module.css | 37 +++++ src/pages/index.js | 126 ++++++++------- src/styles/globals.css | 70 ++++++-- 6 files changed, 569 insertions(+), 78 deletions(-) create mode 100644 src/components/NutSplits.js create mode 100644 src/components/NutSplits.module.css diff --git a/package-lock.json b/package-lock.json index c7fffe3..ee80987 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", "@noble/secp256k1": "^2.1.0", + "@nostr-dev-kit/ndk": "^2.10.7", "bech32-buffer": "^0.2.1", "bitcoin-qr": "^1.4.1", "js-confetti": "^0.12.0", @@ -2251,6 +2252,27 @@ "node": ">= 8" } }, + "node_modules/@nostr-dev-kit/ndk": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.10.7.tgz", + "integrity": "sha512-cylva8jsaAGMijxAI32CnJWlzvwD4sWyl86/+RMS6xpZn4MIgeVUfBFc/pYkcfZzDP3v1Z9mIPsuiICRyvu9yQ==", + "dependencies": { + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@noble/secp256k1": "^2.1.0", + "@scure/base": "^1.1.9", + "debug": "^4.3.6", + "light-bolt11-decoder": "^3.2.0", + "nostr-tools": "^2.7.1", + "tseep": "^1.2.2", + "typescript-lru-cache": "^2.0.0", + "utf8-buffer": "^1.0.0", + "websocket-polyfill": "^0.0.3" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3385,6 +3407,18 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -3684,6 +3718,18 @@ "node": ">=4" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -3739,11 +3785,11 @@ } }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -4157,6 +4203,43 @@ "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, + "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==", + "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==", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -4530,6 +4613,20 @@ "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==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -4591,6 +4688,15 @@ "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==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -4600,6 +4706,14 @@ "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==", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5587,6 +5701,11 @@ "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==" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -6105,9 +6224,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mz": { "version": "2.7.0", @@ -6498,6 +6617,11 @@ "webpack": "^4.4.0 || ^5.9.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==" + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -6525,6 +6649,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -8376,11 +8510,26 @@ "strip-bom": "^3.0.0" } }, + "node_modules/tseep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tseep/-/tseep-1.3.1.tgz", + "integrity": "sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ==" + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, + "node_modules/tstl": { + "version": "2.5.16", + "resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.16.tgz", + "integrity": "sha512-+O2ybLVLKcBwKm4HymCEwZIT0PpwS3gCYnxfSDEjJEKADvIFruaQjd3m7CAKNU1c7N3X3WjVz87re7TA2A5FUw==" + }, + "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==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8474,6 +8623,14 @@ "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==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -8488,6 +8645,11 @@ "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==" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -8608,6 +8770,26 @@ "punycode": "^2.1.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, + "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==", + "engines": { + "node": ">=8" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8728,6 +8910,44 @@ "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==", + "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==", + "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==" + }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", @@ -9291,6 +9511,14 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, + "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==", + "engines": { + "node": ">=0.10.32" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index d6a07f2..4418440 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", "@noble/secp256k1": "^2.1.0", + "@nostr-dev-kit/ndk": "^2.10.7", "bech32-buffer": "^0.2.1", "bitcoin-qr": "^1.4.1", "js-confetti": "^0.12.0", diff --git a/src/components/NutSplits.js b/src/components/NutSplits.js new file mode 100644 index 0000000..48d45a0 --- /dev/null +++ b/src/components/NutSplits.js @@ -0,0 +1,171 @@ +import React, { useState, useEffect } from 'react'; +import NDK from "@nostr-dev-kit/ndk"; +import { decode } from 'bech32-buffer'; +import { bytesToHex } from '@noble/hashes/utils'; +import { SearchIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; +import styles from './NutSplits.module.css'; + +const NutSplits = ({ onSendNuts, onClose }) => { + const [noteId, setNoteId] = useState(''); + const [commenters, setCommenters] = useState([]); + const [selectedCommenters, setSelectedCommenters] = useState({}); + const [ndk, setNdk] = useState(null); + const [loading, setLoading] = useState(false); // New loading state + + useEffect(() => { + const ndkInstance = new NDK({ explicitRelayUrls: ["wss://relay.nostr.band", "wss://relay.damus.io", "wss://relay.primal.net"] }); + + const connectNdk = async () => { + try { + await ndkInstance.connect(); + setNdk(ndkInstance); + } catch (error) { + console.error("Failed to connect to NDK:", error); + } + }; + + connectNdk(); + }, []); + + const handleSearch = async () => { + try { + if (!ndk) { + console.error("NDK is not initialized"); + return; + } + + let decodedData = decode(noteId); + let hexID = bytesToHex(decodedData.data); + + setLoading(true); // Set loading to true before fetching + try { + const fetchedCommenters = await fetchCommenters(hexID); + setCommenters(fetchedCommenters); + } catch (error) { + console.error("Failed to fetch commenters:", error); + } finally { + setLoading(false); // Set loading to false after fetching + } + } catch (error) { + console.error("Search failed:", error); + } + }; + + const fetchCommenters = async (noteId) => { + const filter2 = { kinds: [1], ['#e']: [noteId] }; + + let events; + try { + events = await ndk.fetchEvents(filter2); + } catch (error) { + console.error("Failed to fetch events:", error); + return []; + } + + const commenters = []; + const existingNpubs = new Set(); + + for (const event of events) { + let currentPubkey = event.pubkey; + let currentNpub = event.author.npub; + + if (!existingNpubs.has(currentNpub)) { + let currentUser = ndk.getUser({ npub: currentNpub }); + await currentUser.fetchProfile(); + + const commenter = { + name: currentUser.profile.name, + npub: currentNpub, + pubkey: currentPubkey, + }; + + commenters.push(commenter); + existingNpubs.add(currentNpub); + } + } + + return commenters; + }; + + const handleCheckboxChange = (id) => { + setSelectedCommenters((prev) => ({ + ...prev, + [id]: !prev[id], + })); + }; + + const handleSendNuts = () => { + const selected = Object.keys(selectedCommenters).filter(id => selectedCommenters[id]); + onSendNuts(selected); + }; + + const handleSelectAll = () => { + const allSelected = {}; + commenters.forEach(commenter => { + allSelected[commenter.npub] = true; + }); + setSelectedCommenters(allSelected); + }; + + const handleDeselectAll = () => { + setSelectedCommenters({}); + }; + + const handleClose = () => { + setNoteId(''); + setCommenters([]); + setSelectedCommenters({}); + onClose(); + }; + + return ( +
+
+ × +

Nut Splits

+
+ setNoteId(e.target.value)} + placeholder="Enter Nostr Note ID" + /> + +
+
+ {loading ? ( +
+ Loading + . + . + . +
+ ) : ( + commenters.map(commenter => ( +
+ handleCheckboxChange(commenter.npub)} + /> + {commenter.name ? commenter.name : commenter.npub} +
+ )) + )} +
+
+ + +
+
+ +
+
+
+ ); +}; + +export default NutSplits; + diff --git a/src/components/NutSplits.module.css b/src/components/NutSplits.module.css new file mode 100644 index 0000000..73334bd --- /dev/null +++ b/src/components/NutSplits.module.css @@ -0,0 +1,37 @@ +.loading-animation { + display: flex; + justify-content: center; + align-items: center; + height: 100%; /* Adjust as needed */ + font-size: 20px; /* Adjust as needed */ + color: #ff9900; /* Loading text color */ +} + +.dot { + animation: blink 1s infinite; + font-size: 24px; /* Adjust size of dots */ +} + +.dot:nth-child(1) { + animation-delay: 0s; +} + +.dot:nth-child(2) { + animation-delay: 0.2s; /* Delay for the second dot */ +} + +.dot:nth-child(3) { + animation-delay: 0.4s; /* Delay for the third dot */ +} + +@keyframes blink { + 0%, 20% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } +} diff --git a/src/pages/index.js b/src/pages/index.js index 6c69e58..ecf5767 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -9,6 +9,7 @@ import Mints from "@/components/Mints"; import EcashOrLightning from "@/components/EcashOrLightning"; import Transactions from "@/components/Transactions"; import QRCodeScanner from '@/components/QRCodeScanner'; +import NutSplits from '@/components/NutSplits'; import QRCode from 'qrcode'; import JSConfetti from 'js-confetti'; @@ -43,6 +44,9 @@ const Wallet = () => { const [isScanQRModalOpen, setIsScanQRModalOpen] = useState(false); const [scannedData, setScannedData] = useState(''); + // For NutSplits component + const [isNutSplitsModalOpen, setIsNutSplitsModalOpen] = useState(false); + const [typewriterMessages, setTypewriterMessages] = useState([]); const [isTypewriterModalOpen, setIsTypewriterModalOpen] = useState(false); @@ -66,13 +70,6 @@ const Wallet = () => { } }; - const [formData, setFormData] = useState({ - mintUrl: "", - meltInvoice: "", - swapAmount: "", - }); - const [dataOutput, setDataOutput] = useState(null); - /** * @type {[CashuWallet|null, React.Dispatch>]} */ @@ -102,8 +99,6 @@ const Wallet = () => { if (savedState !== null) { setIsBalanceHidden(savedState === 'true'); } - - setFormData((prevData) => ({ ...prevData, mintUrl: url })); } else { const introMessages = [ @@ -117,19 +112,10 @@ const Wallet = () => { } }, []); - const handleChange = (e) => { - const { name, value } = e.target; - setFormData((prevData) => ({ - ...prevData, - [name]: value, - })); - }; - const handleMintChange = async (newMint) => { try { const mint = new CashuMint(newMint); const info = await mint.getInfo(); - setDataOutput(info); const { keysets } = await mint.getKeys(); const satKeyset = keysets.find((k) => k.unit === "sat"); @@ -154,7 +140,6 @@ const Wallet = () => { const handleQRScan = (data) => { setScannedData(data); setIsScanQRModalOpen(false); // Close the modal after scanning - setDataOutput(data); // Handle scanned data if (data.startsWith('cashu')) { // Cashu token @@ -190,7 +175,6 @@ const Wallet = () => { //const quote = await wallet.getMintQuote(amount); const quote = await wallet.createMintQuote(amount); - setDataOutput(quote); storeJSON(quote); //Close the receive Lightning modal just before showing the invoice modal @@ -202,7 +186,6 @@ const Wallet = () => { const { proofs } = await wallet.mintTokens(amount, quote.quote, { keysetId: wallet.keys.id, }); - setDataOutput({ "minted proofs": proofs }); //Add new proofs to local storage var proofsArray = { "proofs": proofs }; @@ -218,7 +201,6 @@ const Wallet = () => { clearInterval(intervalId); } catch (error) { console.error("Quote probably not paid: ", quote.request, error); - setDataOutput({ timestamp: new Date().toLocaleTimeString(), error: "Failed to mint", details: error }); } }, 5000); } @@ -255,7 +237,6 @@ const Wallet = () => { // Use the info from localStorage const mintInfo = storedMintInfo[mintURL]; - setDataOutput(mintInfo); storeJSON(mintInfo); const { keysets } = await mint.getKeys(); @@ -273,7 +254,7 @@ const Wallet = () => { return; } catch (error) { console.error(error); - setDataOutput({ error: "Failed to receive ecash.", details: error }); + storeJSON({ error: "Failed to receive ecash.", details: error }); } } @@ -284,7 +265,6 @@ const Wallet = () => { addTransaction_Ecash("Receive", mintURL, totalAmount, token); showToast(`Received ${totalAmount} ${totalAmount === 1 ? 'sat' : 'sats'}!`); - setDataOutput(proofs); storeJSON(proofs); } catch (error) { console.error(error); @@ -326,11 +306,9 @@ const Wallet = () => { removeProofs(proofs, wallet.mint.mintUrl); addProofs(returnChange, wallet.mint.mintUrl); addTransaction_Ecash("Send", wallet.mint.mintUrl, amount, encodedToken); - - setDataOutput(encodedToken); } catch (error) { console.error(error); - setDataOutput({ error: "Failed to send ecash", details: error }); + storeJSON({ error: "Failed to send ecash", details: error }); } } @@ -447,7 +425,7 @@ const Wallet = () => { } catch (error) { console.error(error); - setDataOutput({ error: "Failed to melt tokens", details: error }); + storeJSON({ error: "Failed to melt tokens", details: error }); } } @@ -507,7 +485,6 @@ const Wallet = () => { //Wait to get melt quote from mint const quote = await wallet.createMeltQuote(invoice); document.getElementById('waiting_message').textContent = "Getting melt quote..."; - setDataOutput([{ "got melt quote": quote }]); storeJSON(quote); const amount = quote.amount + quote.fee_reserve; @@ -600,7 +577,6 @@ const Wallet = () => { //Wait to get melt quote from mint const quote = await wallet.createMeltQuote(invoice); document.getElementById('waiting_message').textContent = "Getting melt quote..."; - setDataOutput([{ "got melt quote": quote }]); storeJSON(quote); const amount = quote.amount + quote.fee_reserve; @@ -636,17 +612,14 @@ const Wallet = () => { } catch (error) { console.error(error); showToast(error); - setDataOutput({ error: "Failed to zap deez nuts", details: error }); + storeJSON({ error: "Failed to zap deez nuts", details: error }); } } // End zapDeezNuts - async function sendNuts(npub = 'npub1cashuq3y9av98ljm2y75z8cek39d8ux6jk3g6vafkl5j0uj4m5ks378fhq') { + async function sendNuts(npub, amount, message) { let decodedData = decode(npub); let hexPubKey = bytesToHex(decodedData.data); - let { amount, message } = await showSendNutsModal(npub); - closeSendNutsModal(); - const storedMintData = JSON.parse(localStorage.getItem("activeMint")); const { url, keyset } = storedMintData; @@ -690,24 +663,16 @@ const Wallet = () => { const encodedToken = getEncodedToken(tokenData) // Send the message and token separately - if (message){ + if (message) { sendEncryptedMessage(hexPubKey, message); } sendEncryptedMessage(hexPubKey, encodedToken); removeProofs(proofs, url); addProofs(returnChange, wallet.mint.mintUrl); - - // Confetti is reserved for donations - if (npub === 'npub1cashuq3y9av98ljm2y75z8cek39d8ux6jk3g6vafkl5j0uj4m5ks378fhq'){ - showToast("Succesfully sent nuts! Thank you!"); - showConfetti(amount); - } else { - showToast(`${amount} sats sent to ${npub} via Nostr DM`) - } } catch (error) { console.error(error); - setDataOutput({ error: true, details: error }); + storeJSON({ error: true, details: error }); } } @@ -763,7 +728,7 @@ const Wallet = () => { await Promise.any(pool.publish(relays, signedEvent)); } catch (error) { console.error(error); - setDataOutput({ error: true, details: error }); + storeJSON({ error: true, details: error }); } } @@ -1145,7 +1110,6 @@ const Wallet = () => { modal.style.display = 'block'; } - function closeReceiveEcashModal() { const modal = document.getElementById('receive_ecash_modal'); document.getElementById('cashu_token').value = ''; @@ -1253,10 +1217,17 @@ const Wallet = () => { localStorage.setItem('json', JSON.stringify(existingData)); } - //Gets the npub for the selected contact, appends "@npub.cash", and copies it to the clipboard - const handleContactSelect = (contact) => { - let npub = contact.npub; - sendNuts(npub); + const handleContactSelect = async (contact) => { + let npub = contact.npub; // Move this line up to define npub before using it + + let { amount, message } = await showSendNutsModal(npub); + closeSendNutsModal(); + + sendNuts(npub, amount, message); + + showToast(`${amount} sats sent to ${npub} via Nostr DM`) + + // Uncomment the following lines if you want to copy the contact address to clipboard // const contactAddress = `${contact.npub}@npub.cash`; // navigator.clipboard.writeText(contactAddress).then(() => { // showToast(`Copied to clipboard: ${contactAddress}`); @@ -1265,6 +1236,18 @@ const Wallet = () => { // }); }; + const handleSendNuts = async () => { + let npub = 'npub1cashuq3y9av98ljm2y75z8cek39d8ux6jk3g6vafkl5j0uj4m5ks378fhq'; + + let { amount, message } = await showSendNutsModal(npub); + closeSendNutsModal(); + + sendNuts(npub, amount, message); + + showToast("Succesfully sent nuts! Thank you!"); + showConfetti(amount); + }; + const exportJSON = () => { const existingData = JSON.parse(localStorage.getItem('json')) || {}; const dataStr = JSON.stringify(existingData); @@ -1358,13 +1341,36 @@ const Wallet = () => { localStorage.setItem('isBalanceHidden', newState); }; + const closeNutSplitsModal = () => { + setIsNutSplitsModalOpen(false); + } + + const handleNutSplit = (selectedCommenters) => { + setIsNutSplitsModalOpen(false); // Close the modal + + let amount = 1; + let message = "This is a test nut!"; + + console.log('Selected Commenters:', selectedCommenters); + + // Loop through each commenter + selectedCommenters.forEach((npub) => { + sendNuts(npub, amount, message); + // Handle each commenter as needed + console.log('Nut(s) sent to:', npub); + // You can perform actions with each commenter here + }); + + showToast(`Sent ${amount} sat(s) each to ${selectedCommenters.length} recipients`); + }; + return (
-

v0.2.62

+

v0.2.63

@@ -1540,21 +1546,27 @@ const Wallet = () => {
- +

Advanced

-

Data Output

-
{JSON.stringify(dataOutput, null, 2)}
- +
+ + {isNutSplitsModalOpen && ( + + )} +


diff --git a/src/styles/globals.css b/src/styles/globals.css index d2ecb85..513230a 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -199,20 +199,6 @@ main { font-weight: bold; } -.data-output { - background-color: #111111; - color: #ff9900; - padding: 10px; - border: 1px solid #ff9900; - white-space: pre-wrap; - overflow: scroll; - max-width: 100%; - height: 300px; - max-height: 300px; - word-wrap: break-word; - margin: 10px 0px 10px 0px; -} - @layer utilities { .text-balance { text-wrap: balance; @@ -463,6 +449,20 @@ main { background-color: rgba(0, 0, 0, 0.8); } + .nut_splits_modal { + display: block; + justify-content: center; + align-items: center; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.8); + } + .input-container { display: flex; align-items: flex-start; @@ -581,6 +581,40 @@ main { left: 3px; } + input[type="checkbox"] { + appearance: none; + -webkit-appearance: none; /* For Safari */ + min-width: 21px; /* Set width */ + min-height: 21px; /* Set height */ + border: 2px solid #FF9900; + border-radius: 0px; + outline: none; + cursor: pointer; + background-color: #111111; + position: relative; + +} + +input[type="checkbox"]:checked { + background-color: #FF9900; +} + +input[type="checkbox"]:checked::after { + content: '✓'; + font-size: 16px; /* Adjust font size as needed */ + color: #111111; + position: absolute; + top: -2px; /* Adjust position as needed */ + left: 4px; /* Adjust position as needed */ +} + +/* Optional: Add a hover effect */ +input[type="checkbox"]:hover { + border-color: #FFCC00; /* Change border color on hover */ +} + + + .ecash_or_lightning_modal { display: block; justify-content: center; @@ -603,6 +637,14 @@ main { background-color: #111111; } + #nut_splits_input { + width: 100%; + padding: 8px; + border: 1px solid #ff9900; + border-radius: 0px; + background-color: #111111; + } + /* Progress bar container */ .progress-bar-container { width: 100%;