From 0f19549f61d317220614cb1bd68cf551700149b8 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:55:25 -0700 Subject: [PATCH 01/24] integrate ndk/ndk-hooks --- craco.config.js | 5 + package.json | 3 + src/App.tsx | 17 +- yarn.lock | 413 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 432 insertions(+), 6 deletions(-) diff --git a/craco.config.js b/craco.config.js index 028e47d1..cd4526b7 100644 --- a/craco.config.js +++ b/craco.config.js @@ -11,6 +11,11 @@ module.exports = { webpackConfig.plugins = webpackConfig.plugins.filter(plugin => !(plugin.constructor && plugin.constructor.name === 'PrettierPlugin') ); + webpackConfig.module.rules.push({ + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }); return webpackConfig; }, plugins: [ diff --git a/package.json b/package.json index 77bf5625..ea82f403 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,11 @@ "dependencies": { "@ant-design/icons": "^4.6.2", "@babel/core": "^7.24.7", + "@cashu/cashu-ts": "^2.5.2", "@craco/craco": "^6.1.2", "@lit-labs/react": "^1.0.2", + "@nostr-dev-kit/ndk": "^2.14.32", + "@nostr-dev-kit/ndk-hooks": "^1.2.3", "@react-google-maps/api": "^2.18.1", "@reduxjs/toolkit": "^1.7.1", "@splidejs/react-splide": "^0.7.12", diff --git a/src/App.tsx b/src/App.tsx index c875c495..950202fa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { ConfigProvider } from 'antd'; import { HelmetProvider } from 'react-helmet-async'; import deDe from 'antd/lib/locale/de_DE'; @@ -13,10 +13,25 @@ import { usePWA } from './hooks/usePWA'; import { useThemeWatcher } from './hooks/useThemeWatcher'; import { useAppSelector } from './hooks/reduxHooks'; import { themeObject } from './styles/themes/themeVariables'; +import NDK from '@nostr-dev-kit/ndk'; +import { useNDKInit } from '@nostr-dev-kit/ndk-hooks'; + +const ndk = new NDK({ + explicitRelayUrls: ['wss://relay.damus.io', 'wss://relay.nostr.band', 'wss://relay.snort.social', 'vault.iris.to'], +}); +ndk + .connect() + .then(() => console.log('NDK connected')) + .catch((error) => console.error('NDK connection error:', error)); const App: React.FC = () => { const { language } = useLanguage(); const theme = useAppSelector((state) => state.theme.theme); + const initializeNDK = useNDKInit(); + + useEffect(() => { + initializeNDK(ndk); + }, [initializeNDK]); usePWA(); diff --git a/yarn.lock b/yarn.lock index e44c197e..a1434d19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1219,6 +1219,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cashu/cashu-ts@^2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@cashu/cashu-ts/-/cashu-ts-2.5.2.tgz#808bdc9bdd30cf4a3477e9116cdc77792005f1c9" + integrity sha512-AjfDOZKb3RWWhmpHABC4KJxwJs3wp6eOFg6U3S6d3QOqtSoNkceMTn6lLN4/bYQarLR19rysbrIJ8MHsSwNxeQ== + dependencies: + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + buffer "^6.0.3" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1648,6 +1658,13 @@ dependencies: "@noble/hashes" "1.3.2" +"@noble/curves@^1.6.0", "@noble/curves@~1.9.0": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.2.tgz#73388356ce733922396214a933ff7c95afcef911" + integrity sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g== + dependencies: + "@noble/hashes" "1.8.0" + "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" @@ -1665,11 +1682,21 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@1.8.0", "@noble/hashes@^1.5.0", "@noble/hashes@~1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== +"@noble/secp256k1@^2.1.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-2.3.0.tgz#ddfe6e853472fb88cba4d5e59b7067adc1e64adf" + integrity sha512-0TQed2gcBbIrh7Ccyw+y/uZQvbJwm7Ao4scBUxqpBCcsOlZG0O4KGfjtNAy/li4W8n1xt3dxrwJ0beZ2h2G6Kw== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1691,6 +1718,42 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nostr-dev-kit/ndk-hooks@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@nostr-dev-kit/ndk-hooks/-/ndk-hooks-1.2.3.tgz#d724508e91ef859a8eaa97a997500e73f0db85b2" + integrity sha512-FMlitm4urlBofINfBG8A8L4kH4EaXIPZUOVEYAYJWuHF2WPp37JKPycgSrWdZlRheDH48hkSGGiSBbRcGL/2sQ== + dependencies: + "@nostr-dev-kit/ndk" "^2.14.29" + "@nostr-dev-kit/ndk-wallet" "0.6.2" + "@testing-library/react" "^14.1.2" + zustand "^5" + +"@nostr-dev-kit/ndk-wallet@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@nostr-dev-kit/ndk-wallet/-/ndk-wallet-0.6.2.tgz#45fd9566eaea507e7fe4180027f3a850da4f522f" + integrity sha512-vJ2rpX3Zprff+BD47O00omWakHwuybNmb7dp9TunQilQTEKKApZ5y0t/B0766qtqkTwHD4ME/LU3wujNnCaIKg== + dependencies: + "@nostr-dev-kit/ndk" "^2.14.11" + debug "^4.3.4" + light-bolt11-decoder "^3.0.0" + tseep "^1.1.1" + typescript "^5.8.2" + webln "^0.3.2" + +"@nostr-dev-kit/ndk@^2.14.11", "@nostr-dev-kit/ndk@^2.14.29", "@nostr-dev-kit/ndk@^2.14.32": + version "2.14.32" + resolved "https://registry.yarnpkg.com/@nostr-dev-kit/ndk/-/ndk-2.14.32.tgz#a7fa56e74c6f07a3ab709c16c4293c0bdbfbc759" + integrity sha512-LUBO35RCB9/emBYsXNDece7m/WO2rGYR8j4SD0Crb3z8GcKTJq6P8OjpZ6+Kr+sLNo8N0uL07XxtAvEBnp2OqQ== + 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" + tseep "^1.3.1" + typescript-lru-cache "^2" + "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" @@ -1813,6 +1876,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== +"@scure/base@^1.1.9", "@scure/base@~1.2.5": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.6.tgz#ca917184b8231394dd8847509c67a0be522e59f6" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + "@scure/base@~1.1.0": version "1.1.9" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" @@ -1827,6 +1895,15 @@ "@noble/hashes" "~1.3.1" "@scure/base" "~1.1.0" +"@scure/bip32@^1.5.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.7.0.tgz#b8683bab172369f988f1589640e53c4606984219" + integrity sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw== + dependencies: + "@noble/curves" "~1.9.0" + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + "@scure/bip39@1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" @@ -1992,6 +2069,29 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@testing-library/dom@^9.0.0": + version "9.3.4" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce" + integrity sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.1.3" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + +"@testing-library/react@^14.1.2": + version "14.3.1" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.3.1.tgz#29513fc3770d6fb75245c4e1245c470e4ffdd830" + integrity sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^9.0.0" + "@types/react-dom" "^18.0.0" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -2017,6 +2117,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -2050,6 +2155,13 @@ dependencies: "@babel/types" "^7.20.7" +"@types/chrome@^0.0.74": + version "0.0.74" + resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.74.tgz#f69827c48fcf7fecc90c96089807661749a5a5e3" + integrity sha512-hzosS5CkQcIKCgxcsV2AzbJ36KNxG/Db2YEN/erEu7Boprg+KpMDLBQqKFmSo+JkQMGqRcicUyqCowJpuT+C6A== + dependencies: + "@types/filesystem" "*" + "@types/country-list@^2.1.0": version "2.1.4" resolved "https://registry.yarnpkg.com/@types/country-list/-/country-list-2.1.4.tgz#703558392ce6fd8a3a1cd30084cebc11ace247aa" @@ -2105,6 +2217,18 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/filesystem@*": + version "0.0.36" + resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857" + integrity sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA== + dependencies: + "@types/filewriter" "*" + +"@types/filewriter@*": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.33.tgz#d9d611db9d9cd99ae4e458de420eeb64ad604ea8" + integrity sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g== + "@types/geojson@*": version "7946.0.15" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.15.tgz#f9d55fd5a0aa2de9dc80b1b04e437538b7298868" @@ -2247,6 +2371,11 @@ dependencies: "@types/react" "*" +"@types/react-dom@^18.0.0": + version "18.3.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.7.tgz#b89ddf2cd83b4feafcc4e2ea41afdfb95a0d194f" + integrity sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ== + "@types/react-dom@^18.0.11": version "18.3.2" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.2.tgz#b58a9520f5f317a00bbda0271502889b71c345f0" @@ -2938,6 +3067,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + antd-mask-input@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/antd-mask-input/-/antd-mask-input-2.0.7.tgz#202d706eb83571646835bf52e9bfe1d1e4ea5eaf" @@ -3027,6 +3161,13 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-query@5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== + dependencies: + deep-equal "^2.0.5" + aria-query@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" @@ -3052,6 +3193,14 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== +array-buffer-byte-length@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" @@ -3531,7 +3680,7 @@ balanced-match@^2.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== -base64-js@^1.0.2: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -3813,6 +3962,14 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtin-modules@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -3896,6 +4053,14 @@ call-bind-apply-helpers@^1.0.0: es-errors "^1.3.0" function-bind "^1.1.2" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7, call-bind@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" @@ -3906,6 +4071,14 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7, call-bin get-intrinsic "^1.2.4" set-function-length "^1.2.2" +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -5023,6 +5196,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.6: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + decamelize-keys@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" @@ -5063,6 +5243,30 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.5.1" +deep-equal@^2.0.5: + version "2.2.3" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.5" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.2" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.13" + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -5240,6 +5444,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + dom-align@^1.7.0: version "1.12.4" resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511" @@ -5356,6 +5565,15 @@ dunder-proto@^1.0.0: es-errors "^1.3.0" gopd "^1.2.0" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer@^0.1.1, duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5592,6 +5810,21 @@ es-errors@^1.2.1, es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + es-iterator-helpers@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz#2f1a3ab998b30cb2d10b195b587c6d9ebdebf152" @@ -5620,6 +5853,13 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" +es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" @@ -6416,6 +6656,13 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -6566,6 +6813,22 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.1.3, get-intrinsic@^1.2.2, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.5" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.5.tgz#dfe7dd1b30761b464fe51bf4bb00ac7c37b681e7" @@ -6590,6 +6853,14 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stdin@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" @@ -7166,7 +7437,7 @@ identity-obj-proxy@3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.4: +ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -7324,6 +7595,15 @@ internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + internmap@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" @@ -7389,6 +7669,15 @@ is-arguments@^1.1.1: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-array-buffer@^3.0.2, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" @@ -7601,7 +7890,7 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== -is-map@^2.0.3: +is-map@^2.0.2, is-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== @@ -7717,7 +8006,7 @@ is-root@2.1.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== -is-set@^2.0.3: +is-set@^2.0.2, is-set@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== @@ -8620,6 +8909,13 @@ libphonenumber-js@^1.11.16: resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.16.tgz#3aa64a8a95ffc59253a5df3009940a9604a02102" integrity sha512-Noyazmt0yOvnG0OeRY45Cd1ur8G7Z0HWVkuCuKe+yysGNxPQwBAODBQQ40j0AIagi9ZWurfmmZWNlpg4h4W+XQ== +light-bolt11-decoder@^3.0.0, light-bolt11-decoder@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz#2d48f78386cde526c4131db8f9dfd3250a2d5d4d" + integrity sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ== + dependencies: + "@scure/base" "1.1.1" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -8837,6 +9133,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" @@ -8907,6 +9208,11 @@ matchmediaquery@^0.3.0: dependencies: css-mediaquery "^0.1.2" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + mathml-tag-names@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" @@ -10835,6 +11141,15 @@ pretty-format@^26.6.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + pretty-time@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" @@ -12527,6 +12842,35 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4, side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -12537,6 +12881,17 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -12834,6 +13189,14 @@ std-env@^3.0.1: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== +stop-iteration-iterator@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -13555,6 +13918,11 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" +tseep@^1.1.1, tseep@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tseep/-/tseep-1.3.1.tgz#734c5f7ca37cb8af4e4e0a5c205742673562a10e" + integrity sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ== + tslib@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" @@ -13711,11 +14079,21 @@ typeface-montserrat@^1.1.13: resolved "https://registry.yarnpkg.com/typeface-montserrat/-/typeface-montserrat-1.1.13.tgz#2a16729c174dd1a8c3fec05a851b7725412606cc" integrity sha512-Pklkyj0e+K+6I/t0M6JBDBphpfJkF1k+3qd8qDnp9aVtCC7oGBQWTAcL6+5eArfGe7h73uPwyal73hEkf9YCUA== +typescript-lru-cache@^2: + version "2.0.0" + resolved "https://registry.yarnpkg.com/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz#d4ad0f071ab51987b088a57c3c502d7dd62dee07" + integrity sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA== + typescript@5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +typescript@^5.8.2: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -14107,6 +14485,13 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webln@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/webln/-/webln-0.3.2.tgz#bbadf52916666b6059e3661ef5ab73a76b7cd0f4" + integrity sha512-YYT83aOCLup2AmqvJdKtdeBTaZpjC6/JDMe8o6x1kbTYWwiwrtWHyO//PAsPixF3jwFsAkj5DmiceB6w/QSe7Q== + dependencies: + "@types/chrome" "^0.0.74" + webpack-bundle-analyzer@^4.4.2: version "4.10.2" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz#633af2862c213730be3dbdf40456db171b60d5bd" @@ -14317,7 +14702,7 @@ which-builtin-type@^1.2.0: which-collection "^1.0.2" which-typed-array "^1.1.15" -which-collection@^1.0.2: +which-collection@^1.0.1, which-collection@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== @@ -14332,6 +14717,19 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== +which-typed-array@^1.1.13: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which-typed-array@^1.1.14, which-typed-array@^1.1.15: version "1.1.16" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b" @@ -14689,6 +15087,11 @@ zrender@5.6.0: dependencies: tslib "2.3.0" +zustand@^5: + version "5.0.6" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.6.tgz#a2da43d8dc3d31e314279e5baec06297bea70a5c" + integrity sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A== + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" From 57f293c2445837c7c202477638318b1ee744cf6e Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:14:44 -0700 Subject: [PATCH 02/24] fix issue with z-index on search suggestions --- src/components/layouts/main/MainContent/MainContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/layouts/main/MainContent/MainContent.tsx b/src/components/layouts/main/MainContent/MainContent.tsx index a9e4426d..a9215627 100644 --- a/src/components/layouts/main/MainContent/MainContent.tsx +++ b/src/components/layouts/main/MainContent/MainContent.tsx @@ -17,7 +17,7 @@ export default styled(BaseLayout.Content)` ${(props) => props?.$isDesktop && css` - z-index: 105; + z-index: 0; `} @media only screen and ${media.md} { From 9a32fe98f767efdc506499276ad1f20afa84d6ec Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Wed, 2 Jul 2025 19:53:24 -0700 Subject: [PATCH 03/24] fix: issue with space around images --- .../paid-subscribers/avatar/SubscriberAvatar.styles.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts b/src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts index d6905fbb..25f2da71 100644 --- a/src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts +++ b/src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts @@ -13,7 +13,9 @@ export const CreatorButton = styled.button` border: 0; cursor: pointer; border-radius: 50%; - padding: 2px; + height: 100%; + aspect-ratio: 1 / 1; + overflow: hidden; border: 3px solid ${(props) => (!props.$viewed ? 'var(--primary-color)' : 'var(--text-superLight-color)')}; `; @@ -23,5 +25,6 @@ export const Avatar = styled.img` height: auto; max-height: 100%; object-fit: cover; + aspect-ratio: 1 / 1; border-radius: 50%; `; \ No newline at end of file From f39b4f4113a6631b9033f376fac1aabd2944f1af Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Wed, 2 Jul 2025 19:54:43 -0700 Subject: [PATCH 04/24] set up profile fetching --- .../paid-subscribers/PaidSubscribers.tsx | 335 +++++++++++------- 1 file changed, 211 insertions(+), 124 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx index ab1749b1..85504809 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef, useState, useEffect, useMemo } from 'react'; import { Splide, SplideSlide, SplideTrack } from '@splidejs/react-splide'; import { LeftOutlined, RightOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; @@ -14,6 +14,8 @@ import { useResponsive } from '@app/hooks/useResponsive'; import usePaidSubscribers, { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; import { Row, Col, Modal, Spin, Typography } from 'antd'; import { nip19 } from 'nostr-tools'; +import { NDKUserProfile } from '@nostr-dev-kit/ndk-hooks'; +import { useNDK } from '@nostr-dev-kit/ndk-hooks'; const { Text } = Typography; @@ -21,39 +23,66 @@ export const PaidSubscribers: React.FC = () => { console.log('[PaidSubscribers] Component rendering...'); const hookResult = usePaidSubscribers(12); const { subscribers, fetchMore, hasMore, loading } = hookResult; - + const ndkInstance = useNDK(); + // Modal state for subscriber details const [selectedSubscriber, setSelectedSubscriber] = useState(null); const [isModalVisible, setIsModalVisible] = useState(false); - + // Modal state for view all subscribers const [isViewAllModalVisible, setIsViewAllModalVisible] = useState(false); const [allSubscribers, setAllSubscribers] = useState([]); - + const [loadingProfiles, setLoadingProfiles] = useState(true); + + const [subscriberProfiles, setSubscriberProfiles] = useState>( + () => new Map(subscribers.map((s) => [s.pubkey, s])), + ); + const sortedProfiles = useMemo(() => { + return Array.from(subscriberProfiles.entries()).sort(([a], [b]) => a.localeCompare(b)); +}, [subscriberProfiles]); +useEffect(() => { + setSubscriberProfiles(prev => { + const map = new Map(prev); + for (const s of subscribers) { + if (!map.has(s.pubkey)) { + map.set(s.pubkey, s); + } + } + return map; + }); +}, [subscribers]); + + // Handle opening subscriber detail modal const handleOpenSubscriberDetails = (subscriber: SubscriberProfile) => { setSelectedSubscriber(subscriber); setIsModalVisible(true); }; - + // Handle closing subscriber detail modal const handleCloseModal = () => { setIsModalVisible(false); }; - + const updateSubscriberProfile = (pubkey: string, profile: SubscriberProfile) => { + setSubscriberProfiles((prev) => { + const newMap = new Map(prev); + newMap.set(pubkey, profile); + return newMap; + }); + }; // Handle opening view all modal const handleViewAll = async () => { setIsViewAllModalVisible(true); setAllSubscribers([...subscribers]); // Start with current subscribers - + // Fetch more subscribers if available let currentSubscribers = [...subscribers]; let canFetchMore = hasMore; - + while (canFetchMore) { try { await fetchMore(); - // Note: This is a simplified approach. In a real scenario, you'd want to + // Note: This is a simplified approach. In a real scenario, you'd want to // track the updated state properly or use a separate hook for fetching all canFetchMore = false; // For now, just fetch once more } catch (error) { @@ -62,15 +91,63 @@ export const PaidSubscribers: React.FC = () => { } } }; - + const convertNDKUserProfileToSubscriberProfile = (pubkey: string, user: NDKUserProfile): SubscriberProfile => { + return { + pubkey, + name: user.name || '', + picture: user.picture || '', + about: user.about || '', + }; + }; + useEffect(() => { + // Fetch profiles for test subscribers + const fetchProfiles = async () => { + if (!ndkInstance || !ndkInstance.ndk) { + console.error('NDK instance is not initialized'); + return; + } + //1. map through subscribers and fetch profiles. skip profile if already on map + await Promise.all( + subscribers.map(async (subscriber) => { + if ( + subscriberProfiles.has(subscriber.pubkey) && + subscriberProfiles.get(subscriber.pubkey)?.picture && + subscriberProfiles.get(subscriber.pubkey)?.about + ) { + return subscriberProfiles.get(subscriber.pubkey); + } + try { + if (!ndkInstance.ndk) { + console.error('NDK instance is not available'); + return null; + } + const user = await ndkInstance.ndk?.getUser({ pubkey: subscriber.pubkey }).fetchProfile(); + if (user) { + // Convert NDKUserProfile to SubscriberProfile and add to map + const covertedNDKUserProfile = convertNDKUserProfileToSubscriberProfile(subscriber.pubkey, user); + updateSubscriberProfile(subscriber.pubkey, covertedNDKUserProfile); + + return user; + } + } catch (error) { + console.error(`Error fetching profile for ${subscriber.pubkey}:`, error); + } + return null; + }), + ); + setLoadingProfiles(false); + }; + fetchProfiles(); + }, [subscribers, ndkInstance]); + // Handle closing view all modal const handleCloseViewAllModal = () => { setIsViewAllModalVisible(false); }; - + console.log('[PaidSubscribers] Received subscribers:', subscribers); console.log('[PaidSubscribers] Complete hook result:', hookResult); - + const sliderRef = useRef(null); const { isTablet: isTabletOrHigher } = useResponsive(); const { t } = useTranslation(); @@ -89,7 +166,7 @@ export const PaidSubscribers: React.FC = () => { // Determine whether to use carousel with looping based on count const shouldUseLoop = subscribers.length >= 7; - + // Simple grid for few subscribers if (subscribers.length > 0 && subscribers.length < 7) { return ( @@ -101,27 +178,23 @@ export const PaidSubscribers: React.FC = () => { - + - {subscribers.map((subscriber: SubscriberProfile) => ( - + {sortedProfiles.map(([pubkey, subscriber], index: number) => ( + handleOpenSubscriberDetails(subscriber)} - img={subscriber.picture} + img={subscriber.picture || ''} viewed={false} /> ))} - - - + + + {/* View All Subscribers Modal */} { > {(allSubscribers.length > 0 ? allSubscribers : subscribers).map((subscriber: SubscriberProfile) => ( - -
+
{ transition: 'all 0.2s ease', backgroundColor: 'var(--background-color-secondary)', gap: '16px', - boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)' + boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', }} onClick={() => { setSelectedSubscriber(subscriber); @@ -162,55 +236,62 @@ export const PaidSubscribers: React.FC = () => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)'; }} - > -
- {subscriber.name -
-
+
+ {subscriber.name +
+
- + - {subscriber.name || 'Anonymous User'} - - + {subscriber.name || 'Anonymous User'} + + - {(() => { - try { - return nip19.npubEncode(subscriber.pubkey); - } catch { - // Fallback to original hex format if encoding fails - return subscriber.pubkey; - } - })()} - -
+ lineHeight: '1.2', + }} + > + {(() => { + try { + return nip19.npubEncode(subscriber.pubkey); + } catch { + // Fallback to original hex format if encoding fails + return subscriber.pubkey; + } + })()} +
- - ))} +
+ + ))} @@ -222,7 +303,7 @@ export const PaidSubscribers: React.FC = () => { <> { - {subscribers.map((subscriber: SubscriberProfile) => ( - + {!loadingProfiles && sortedProfiles.map(([pubkey, subscriber] ) => ( + handleOpenSubscriberDetails(subscriber)} - img={subscriber.picture} + img={subscriber.picture || ""} viewed={false} /> @@ -283,13 +364,11 @@ export const PaidSubscribers: React.FC = () => { ))} - - - + + {isModalVisible && ( + + )} + {/* View All Subscribers Modal */} { > {(allSubscribers.length > 0 ? allSubscribers : subscribers).map((subscriber: SubscriberProfile) => ( - -
+
{ transition: 'all 0.2s ease', backgroundColor: 'var(--background-color-secondary)', gap: '16px', - boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)' + boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', }} onClick={() => { setSelectedSubscriber(subscriber); @@ -330,59 +410,66 @@ export const PaidSubscribers: React.FC = () => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)'; }} - > -
- {subscriber.name -
-
+
+ {subscriber.name +
+
- + - {subscriber.name || 'Anonymous User'} - - + {subscriber.name || 'Anonymous User'} + + - {(() => { - try { - return nip19.npubEncode(subscriber.pubkey); - } catch { - // Fallback to original hex format if encoding fails - return subscriber.pubkey; - } - })()} - -
+ lineHeight: '1.2', + }} + > + {(() => { + try { + return nip19.npubEncode(subscriber.pubkey); + } catch { + // Fallback to original hex format if encoding fails + return subscriber.pubkey; + } + })()} +
- - ))} +
+ + ))} ); }; -export default PaidSubscribers; \ No newline at end of file +export default PaidSubscribers; From 04f70feb0fd38a7682676a3746ff2c8041324d7b Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:03:22 -0700 Subject: [PATCH 05/24] format: modal and avatar --- .../SubscriberDetailModal.tsx | 74 +++++++------------ .../avatar/SubscriberAvatar.styles.ts | 1 + 2 files changed, 27 insertions(+), 48 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx index 289c0a6c..c013dc06 100644 --- a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx +++ b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx @@ -1,41 +1,36 @@ -import React, { useState } from 'react'; -import { Modal, message, Button } from 'antd'; -import { - UserOutlined, - KeyOutlined, - CalendarOutlined, - CrownOutlined, +import React, { useState } from 'react'; +import { Modal, message } from 'antd'; +import { + KeyOutlined, + CalendarOutlined, + CrownOutlined, CloseOutlined, CopyOutlined, - CheckOutlined + CheckOutlined, } from '@ant-design/icons'; import { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; import * as S from './SubscriberDetailModal.styles'; - interface SubscriberDetailModalProps { subscriber: SubscriberProfile | null; isVisible: boolean; onClose: () => void; } -export const SubscriberDetailModal: React.FC = ({ - subscriber, - isVisible, - onClose -}) => { +export const SubscriberDetailModal: React.FC = ({ subscriber, isVisible, onClose }) => { const [copied, setCopied] = useState(false); - + if (!subscriber) { return null; } - + // Function to copy public key const copyPublicKey = () => { - navigator.clipboard.writeText(subscriber.pubkey) + navigator.clipboard + .writeText(subscriber.pubkey) .then(() => { setCopied(true); message.success('Public key copied to clipboard'); - + // Reset copied state after 3 seconds setTimeout(() => { setCopied(false); @@ -45,7 +40,7 @@ export const SubscriberDetailModal: React.FC = ({ message.error('Failed to copy public key'); }); }; - + // Format public key for display const formatPublicKey = (key: string) => { if (key.length <= 16) return key; @@ -67,26 +62,17 @@ export const SubscriberDetailModal: React.FC = ({ Subscriber Profile - + {/* Avatar section with profile picture and name */} - {subscriber.name + {subscriber.name - - {subscriber.name || 'Anonymous Subscriber'} - - - {subscriber.about && ( - - {subscriber.about} - - )} + {subscriber.name || 'Anonymous Subscriber'} + + {subscriber.about && {subscriber.about}} - + {/* Information section */} {/* Public Key Card */} @@ -97,19 +83,15 @@ export const SubscriberDetailModal: React.FC = ({ Public Key - + {formatPublicKey(subscriber.pubkey)} - : } - > + : }> {copied ? 'Copied' : 'Copy'} - + {/* Subscription Tier Card (if available) */} {subscriber.metadata?.subscriptionTier && ( @@ -119,12 +101,10 @@ export const SubscriberDetailModal: React.FC = ({ Subscription Tier - - {subscriber.metadata.subscriptionTier} - + {subscriber.metadata.subscriptionTier} )} - + {/* Subscription Date Card (if available) */} {subscriber.metadata?.subscribedSince && ( @@ -134,9 +114,7 @@ export const SubscriberDetailModal: React.FC = ({ Subscribed Since - - {subscriber.metadata.subscribedSince} - + {subscriber.metadata.subscribedSince} )} diff --git a/src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts b/src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts index 25f2da71..c2c11d89 100644 --- a/src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts +++ b/src/components/relay-dashboard/paid-subscribers/avatar/SubscriberAvatar.styles.ts @@ -14,6 +14,7 @@ export const CreatorButton = styled.button` cursor: pointer; border-radius: 50%; height: 100%; + min-width: 5rem; aspect-ratio: 1 / 1; overflow: hidden; From d27e1be8284f7a317bb82eecac6236afe9ab2ed0 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 16:04:16 -0700 Subject: [PATCH 06/24] fix issue with paid subscribers (under 7) --- .../paid-subscribers/PaidSubscribers.styles.ts | 15 ++++++++++++++- .../paid-subscribers/PaidSubscribers.tsx | 10 ++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.styles.ts b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.styles.ts index 592637ea..d5c75b37 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.styles.ts +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.styles.ts @@ -29,7 +29,7 @@ export const ArrowBtn = styled(BaseButton)` export const CardWrapper = styled.div` margin: 0 0.40625rem; - + width: min-content; @media only screen and ${media.xl} { margin: 0 0.625rem; } @@ -42,4 +42,17 @@ export const EmptyState = styled.div` padding: 2rem; color: var(--text-light-color); font-size: 1rem; +`; +export const FlexWrapper = styled.div` + display: flex; + flex-wrap: wrap; + width: 90%; + margin: 0 auto; + justify-content: space-between; + align-items: center; + gap: 0.5rem; + + @media only screen and ${media.xl} { + gap: 0.625rem; + } `; \ No newline at end of file diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx index 8584b7fc..9bf525c9 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx @@ -12,7 +12,7 @@ import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; import { SplideCarousel } from '@app/components/common/SplideCarousel/SplideCarousel'; import { useResponsive } from '@app/hooks/useResponsive'; import usePaidSubscribers, { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; -import { Row, Col, Modal, Spin, Typography } from 'antd'; +import { Row, Col, Modal, Typography } from 'antd'; import { nip19 } from 'nostr-tools'; import { NDKUserProfile } from '@nostr-dev-kit/ndk-hooks'; import { useNDK } from '@nostr-dev-kit/ndk-hooks'; @@ -179,19 +179,17 @@ useEffect(() => { - + {sortedProfiles.map(([pubkey, subscriber], index: number) => ( - - + handleOpenSubscriberDetails(subscriber)} img={subscriber.picture || ''} viewed={false} /> - ))} - + From d79e6e556b677d08a2b80f9564955122a7cac16b Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 16:05:30 -0700 Subject: [PATCH 07/24] remove trending creators (we now use paid subscribers) --- .../TrendingCreators.styles.ts | 36 ---- .../trending-creators/TrendingCreators.tsx | 171 ------------------ 2 files changed, 207 deletions(-) delete mode 100644 src/components/relay-dashboard/trending-creators/TrendingCreators.styles.ts delete mode 100644 src/components/relay-dashboard/trending-creators/TrendingCreators.tsx diff --git a/src/components/relay-dashboard/trending-creators/TrendingCreators.styles.ts b/src/components/relay-dashboard/trending-creators/TrendingCreators.styles.ts deleted file mode 100644 index 93f9f12a..00000000 --- a/src/components/relay-dashboard/trending-creators/TrendingCreators.styles.ts +++ /dev/null @@ -1,36 +0,0 @@ -import styled from 'styled-components'; -import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; -import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; -import { BREAKPOINTS, media } from '@app/styles/themes/constants'; - -export const StoriesModal = styled(BaseModal)` - @media only screen and (max-width: ${BREAKPOINTS.md - 0.02}px) { - top: 0; - padding: 0; - margin: 0; - max-width: 100%; - } - - .ant-modal-body { - padding: 0; - } - - .ant-modal-close { - z-index: 999999; - top: 1rem; - - color: var(--text-secondary-color); - } -`; - -export const ArrowBtn = styled(BaseButton)` - color: var(--text-nft-light-color); -`; - -export const CardWrapper = styled.div` - margin: 0 0.40625rem; - - @media only screen and ${media.xl} { - margin: 0 0.625rem; - } -`; diff --git a/src/components/relay-dashboard/trending-creators/TrendingCreators.tsx b/src/components/relay-dashboard/trending-creators/TrendingCreators.tsx deleted file mode 100644 index af40e5d5..00000000 --- a/src/components/relay-dashboard/trending-creators/TrendingCreators.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React, { useRef, useState } from 'react'; -import { Splide, SplideSlide, SplideTrack } from '@splidejs/react-splide'; -import { LeftOutlined, RightOutlined } from '@ant-design/icons'; -import { useTranslation } from 'react-i18next'; -import { NFTCardHeader } from '@app/components/relay-dashboard/common/NFTCardHeader/NFTCardHeader'; -import { ViewAll } from '@app/components/relay-dashboard/common/ViewAll/ViewAll'; -import { TrendingCreatorsStory } from '@app/components/relay-dashboard/trending-creators/story/TrendingCreatorsStory'; -import { getTrendingCreators, TrendingCreator } from '@app/api/trendingCreators'; -import { SubscriberDetailModal } from './SubscriberDetailModal/SubscriberDetailModal'; -import * as S from './TrendingCreators.styles'; -import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; -import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; -import { SplideCarousel } from '@app/components/common/SplideCarousel/SplideCarousel'; -import { useResponsive } from '@app/hooks/useResponsive'; -import usePaidSubscribers, { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; -import { Row, Col } from 'antd'; - -export const TrendingCreators: React.FC = () => { - console.log('[TrendingCreators] Component rendering...'); - const hookResult = usePaidSubscribers(12); - const { subscribers } = hookResult; - - // Modal state for subscriber details - const [selectedSubscriber, setSelectedSubscriber] = useState(null); - const [isModalVisible, setIsModalVisible] = useState(false); - - // Handle opening subscriber detail modal - const handleOpenSubscriberDetails = (subscriber: SubscriberProfile) => { - setSelectedSubscriber(subscriber); - setIsModalVisible(true); - }; - - // Handle closing subscriber detail modal - const handleCloseModal = () => { - setIsModalVisible(false); - }; - - console.log('[TrendingCreators] Received subscribers:', subscribers); - console.log('[TrendingCreators] Complete hook result:', hookResult); - - const sliderRef = useRef(null); - const { isTablet: isTabletOrHigher } = useResponsive(); - const { t } = useTranslation(); - - const goPrev = () => { - if (sliderRef.current?.splide) { - sliderRef.current.splide.go('-1'); - } - }; - - const goNext = () => { - if (sliderRef.current?.splide) { - sliderRef.current.splide.go('+1'); - } - }; - - // Determine whether to use carousel with looping based on count - const shouldUseLoop = subscribers.length >= 7; - - // Simple grid for few subscribers - if (subscribers.length > 0 && subscribers.length < 7) { - return ( - <> - - - - - - - - - - {subscribers.map((subscriber: SubscriberProfile) => ( - - - handleOpenSubscriberDetails(subscriber)} - img={subscriber.picture} - viewed={false} - /> - - - ))} - - - - - ); - } - - // Carousel view for 7+ subscribers - return ( - <> - - - - - - - - {isTabletOrHigher && subscribers.length > 1 && ( - <> - - - - - - - - - - - - - )} - - - - {subscribers.map((subscriber: SubscriberProfile) => ( - - - handleOpenSubscriberDetails(subscriber)} - img={subscriber.picture} - viewed={false} - /> - - - ))} - - - - - - ); -}; - -export default TrendingCreators; From 1ed4802112b33096a7dde2a33c03cb7529285001 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:31:05 -0700 Subject: [PATCH 08/24] add fallback icon for profiles without img --- .../paid-subscribers/PaidSubscribers.tsx | 71 +++++++++++-------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx index 9bf525c9..d2c69198 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx @@ -16,7 +16,8 @@ import { Row, Col, Modal, Typography } from 'antd'; import { nip19 } from 'nostr-tools'; import { NDKUserProfile } from '@nostr-dev-kit/ndk-hooks'; import { useNDK } from '@nostr-dev-kit/ndk-hooks'; - +import { UserOutlined } from '@ant-design/icons'; +import { CreatorButton } from './avatar/SubscriberAvatar.styles'; const { Text } = Typography; export const PaidSubscribers: React.FC = () => { @@ -38,20 +39,19 @@ export const PaidSubscribers: React.FC = () => { () => new Map(subscribers.map((s) => [s.pubkey, s])), ); const sortedProfiles = useMemo(() => { - return Array.from(subscriberProfiles.entries()).sort(([a], [b]) => a.localeCompare(b)); -}, [subscriberProfiles]); -useEffect(() => { - setSubscriberProfiles(prev => { - const map = new Map(prev); - for (const s of subscribers) { - if (!map.has(s.pubkey)) { - map.set(s.pubkey, s); + return Array.from(subscriberProfiles.entries()).sort(([a], [b]) => a.localeCompare(b)); + }, [subscriberProfiles]); + useEffect(() => { + setSubscriberProfiles((prev) => { + const map = new Map(prev); + for (const s of subscribers) { + if (!map.has(s.pubkey)) { + map.set(s.pubkey, s); + } } - } - return map; - }); -}, [subscribers]); - + return map; + }); + }, [subscribers]); // Handle opening subscriber detail modal const handleOpenSubscriberDetails = (subscriber: SubscriberProfile) => { @@ -180,14 +180,20 @@ useEffect(() => { - {sortedProfiles.map(([pubkey, subscriber], index: number) => ( - + {sortedProfiles.map(([pubkey, subscriber]) => ( + + {subscriber.picture ? ( handleOpenSubscriberDetails(subscriber)} img={subscriber.picture || ''} viewed={false} /> - + ) : ( + + + + )} + ))} @@ -349,20 +355,27 @@ useEffect(() => { - {!loadingProfiles && sortedProfiles.map(([pubkey, subscriber] ) => ( - - - handleOpenSubscriberDetails(subscriber)} - img={subscriber.picture || ""} - viewed={false} - /> - - - ))} + {!loadingProfiles && + sortedProfiles.map(([pubkey, subscriber]) => ( + + + {subscriber.picture ? ( + handleOpenSubscriberDetails(subscriber)} + img={subscriber.picture || ''} + viewed={false} + /> + ) : ( + + + + )} + + + ))} - + {isModalVisible && ( )} From f39c4081398bafa15e192946a4616a6bba8ff815 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:40:03 -0700 Subject: [PATCH 09/24] map sorted profiles instead for view all component --- .../relay-dashboard/paid-subscribers/PaidSubscribers.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx index d2c69198..41da44fe 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx @@ -209,8 +209,8 @@ export const PaidSubscribers: React.FC = () => { style={{ top: 20 }} > - {(allSubscribers.length > 0 ? allSubscribers : subscribers).map((subscriber: SubscriberProfile) => ( - + {sortedProfiles.map(([pubkey, subscriber]) => ( +
{ style={{ top: 20 }} > - {(allSubscribers.length > 0 ? allSubscribers : subscribers).map((subscriber: SubscriberProfile) => ( - + {sortedProfiles.map(([pubkey, subscriber]) => ( +
Date: Thu, 3 Jul 2025 18:15:28 -0700 Subject: [PATCH 10/24] add subscriber ribbon to profile card --- .../searchDropdown/SearchDropdown.tsx | 2 - .../SubscriberDetailModal.styles.ts | 46 +++++++++++++------ .../SubscriberDetailModal.tsx | 14 ++++-- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/components/header/components/searchDropdown/SearchDropdown.tsx b/src/components/header/components/searchDropdown/SearchDropdown.tsx index 646c08fa..e1a7d272 100644 --- a/src/components/header/components/searchDropdown/SearchDropdown.tsx +++ b/src/components/header/components/searchDropdown/SearchDropdown.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useRef, useState } from 'react'; -import { FilterIcon } from 'components/common/icons/FilterIcon'; import { SearchOverlay } from './searchOverlay/SearchOverlay/SearchOverlay'; import { HeaderActionWrapper } from '@app/components/header/Header.styles'; import { CategoryComponents } from '@app/components/header/components/HeaderSearch/HeaderSearch'; @@ -52,7 +51,6 @@ export const SearchDropdown: React.FC = ({ size="small" type={isFilterOpen ? 'ghost' : 'text'} aria-label="Filter" - icon={} onClick={() => setFilterOpen(!isFilterOpen)} /> } diff --git a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.styles.ts b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.styles.ts index c4508543..bbeb4dac 100644 --- a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.styles.ts +++ b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.styles.ts @@ -1,7 +1,7 @@ import styled from 'styled-components'; -import { Card, Typography, Button, Space } from 'antd'; -import { BORDER_RADIUS, FONT_SIZE, FONT_WEIGHT, FONT_FAMILY, LAYOUT } from '@app/styles/themes/constants'; - +import { Card, Typography, Button, Badge } from 'antd'; +import { BORDER_RADIUS, FONT_SIZE, FONT_WEIGHT, FONT_FAMILY } from '@app/styles/themes/constants'; +import { BaseBadge } from '@app/components/common/BaseBadge/BaseBadge'; const { Title, Text, Paragraph } = Typography; export const StyledModal = styled(Card)` @@ -12,22 +12,22 @@ export const StyledModal = styled(Card)` overflow: hidden; max-width: 500px; margin: 0 auto; - + .ant-modal-content { background-color: var(--background-color); border-radius: ${BORDER_RADIUS}; } - + .ant-modal-header { background-color: transparent; border-bottom: none; padding-bottom: 0; } - + .ant-modal-body { padding: 0; } - + .ant-modal-footer { border-top: none; padding-top: 0; @@ -52,7 +52,7 @@ export const CloseButton = styled(Button)` border: none; background: transparent; box-shadow: none; - + &:hover { color: var(--primary-color); background: transparent; @@ -66,11 +66,13 @@ export const AvatarSection = styled.div` align-items: center; justify-content: center; padding: 2rem 1rem; - background: linear-gradient(180deg, rgba(24,44,89,0.3) 0%, rgba(33,64,125,0.1) 100%); + background: linear-gradient(180deg, rgba(24, 44, 89, 0.3) 0%, rgba(33, 64, 125, 0.1) 100%); `; export const AvatarContainer = styled.div` position: relative; + margin-top: 0.5rem; + width: 140px; height: 140px; margin-bottom: 1.5rem; @@ -78,7 +80,7 @@ export const AvatarContainer = styled.div` overflow: hidden; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2); border: 4px solid var(--background-color); - + img { width: 100%; height: 100%; @@ -110,11 +112,11 @@ export const InfoCard = styled(Card)` border-radius: ${BORDER_RADIUS}; background-color: var(--secondary-background-color); border: 1px solid var(--border-color); - + .ant-card-body { padding: 1rem; } - + &:last-child { margin-bottom: 0; } @@ -124,7 +126,7 @@ export const InfoHeader = styled.div` display: flex; align-items: center; margin-bottom: 0.75rem; - + & > *:first-child { margin-right: 0.5rem; color: var(--primary-color); @@ -164,7 +166,7 @@ export const CopyButton = styled(Button)` height: auto; border: none; background: transparent; - + &:hover { color: var(--primary-light-color); background: transparent; @@ -191,3 +193,19 @@ export const IconWrapper = styled.div` border-radius: 50%; background-color: rgba(var(--primary-rgb-color), 0.1); `; +export const UsernameWrapper = styled.div` + display: flex; + align-items: center; + width: max-content; +`; + +interface SubscriptionBadgeProps { + subscribed: boolean; +} + +export const SubscriptionBadge = styled(Badge.Ribbon)` + margin-top: 1rem; + margin-right:.3rem; + background: ${({ subscribed }) => (subscribed ? 'var(--ant-primary-color)' : 'var(--error-color)')}; + color: var(--text-main-color); +`; diff --git a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx index c013dc06..ba8678ec 100644 --- a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx +++ b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from 'react'; import { Modal, message } from 'antd'; import { KeyOutlined, @@ -46,6 +46,8 @@ export const SubscriberDetailModal: React.FC = ({ su if (key.length <= 16) return key; return `${key.substring(0, 8)}...${key.substring(key.length - 8)}`; }; + const subscribed: boolean = !!subscriber.metadata?.subscriptionTier && !!subscriber.metadata?.subscribedSince; + const subscribedLabel = subscribed ? 'Subscribed' : 'Not Subscribed'; return ( = ({ su {/* Avatar section with profile picture and name */} - - {subscriber.name - - {subscriber.name || 'Anonymous Subscriber'} + + + {subscriber.name + + + {subscriber.name || 'Anonymous Subscriber'} {subscriber.about && {subscriber.about}} From 413add42a1c0f02c392323b5dabe6fe6c811645c Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:15:54 -0700 Subject: [PATCH 11/24] disable search overlay --- .../header/components/HeaderSearch/HeaderSearch.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/header/components/HeaderSearch/HeaderSearch.tsx b/src/components/header/components/HeaderSearch/HeaderSearch.tsx index bb415642..3cb1389d 100644 --- a/src/components/header/components/HeaderSearch/HeaderSearch.tsx +++ b/src/components/header/components/HeaderSearch/HeaderSearch.tsx @@ -59,7 +59,7 @@ export const HeaderSearch: React.FC = () => { query={query} setQuery={setQuery} data={sortedResults} - isOverlayOpen={isOverlayOpen} + isOverlayOpen={false} setOverlayOpen={setOverlayOpen} /> @@ -71,7 +71,7 @@ export const HeaderSearch: React.FC = () => { query={query} setQuery={setQuery} data={sortedResults} - isOverlayOpen={isOverlayOpen} + isOverlayOpen={false} setOverlayOpen={setOverlayOpen} /> )} From f492fd8e1a0a70289972783b07ff45972158e755 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:16:40 -0700 Subject: [PATCH 12/24] fix issue with closing view all modal --- .../paid-subscribers/PaidSubscribers.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx index 41da44fe..748e9aab 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx @@ -142,6 +142,7 @@ export const PaidSubscribers: React.FC = () => { // Handle closing view all modal const handleCloseViewAllModal = () => { + setSelectedSubscriber(null); setIsViewAllModalVisible(false); }; @@ -180,13 +181,13 @@ export const PaidSubscribers: React.FC = () => { - {sortedProfiles.map(([pubkey, subscriber]) => ( - + {sortedProfiles.map(([pubkey, subscriber], index) => ( + {subscriber.picture ? ( handleOpenSubscriberDetails(subscriber)} img={subscriber.picture || ''} viewed={false} + onStoryOpen={() => handleOpenSubscriberDetails(subscriber)} /> ) : ( @@ -356,9 +357,9 @@ export const PaidSubscribers: React.FC = () => { {!loadingProfiles && - sortedProfiles.map(([pubkey, subscriber]) => ( + sortedProfiles.map(([pubkey, subscriber], index) => ( - + {subscriber.picture ? ( handleOpenSubscriberDetails(subscriber)} From df6b4c69e845d72f1aedc5e3f60325975527cf99 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:23:14 -0700 Subject: [PATCH 13/24] delete obs components --- .../SubscriberDetailModal.styles.ts | 193 ------------------ .../SubscriberDetailModal.tsx | 145 ------------- .../SubscriberDetailModal/index.ts | 1 - 3 files changed, 339 deletions(-) delete mode 100644 src/components/nft-dashboard/trending-creators/SubscriberDetailModal/SubscriberDetailModal.styles.ts delete mode 100644 src/components/nft-dashboard/trending-creators/SubscriberDetailModal/SubscriberDetailModal.tsx delete mode 100644 src/components/nft-dashboard/trending-creators/SubscriberDetailModal/index.ts diff --git a/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/SubscriberDetailModal.styles.ts b/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/SubscriberDetailModal.styles.ts deleted file mode 100644 index c4508543..00000000 --- a/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/SubscriberDetailModal.styles.ts +++ /dev/null @@ -1,193 +0,0 @@ -import styled from 'styled-components'; -import { Card, Typography, Button, Space } from 'antd'; -import { BORDER_RADIUS, FONT_SIZE, FONT_WEIGHT, FONT_FAMILY, LAYOUT } from '@app/styles/themes/constants'; - -const { Title, Text, Paragraph } = Typography; - -export const StyledModal = styled(Card)` - background: var(--background-color); - border-radius: ${BORDER_RADIUS}; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); - width: 100%; - overflow: hidden; - max-width: 500px; - margin: 0 auto; - - .ant-modal-content { - background-color: var(--background-color); - border-radius: ${BORDER_RADIUS}; - } - - .ant-modal-header { - background-color: transparent; - border-bottom: none; - padding-bottom: 0; - } - - .ant-modal-body { - padding: 0; - } - - .ant-modal-footer { - border-top: none; - padding-top: 0; - } -`; - -export const HeaderSection = styled.div` - padding: 24px 24px 0; - display: flex; - align-items: center; - justify-content: space-between; -`; - -export const ModalTitle = styled(Title)` - margin: 0 !important; - color: var(--text-main-color); - font-weight: ${FONT_WEIGHT.semibold}; -`; - -export const CloseButton = styled(Button)` - color: var(--text-main-color); - border: none; - background: transparent; - box-shadow: none; - - &:hover { - color: var(--primary-color); - background: transparent; - } -`; - -export const AvatarSection = styled.div` - position: relative; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 2rem 1rem; - background: linear-gradient(180deg, rgba(24,44,89,0.3) 0%, rgba(33,64,125,0.1) 100%); -`; - -export const AvatarContainer = styled.div` - position: relative; - width: 140px; - height: 140px; - margin-bottom: 1.5rem; - border-radius: 50%; - overflow: hidden; - box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2); - border: 4px solid var(--background-color); - - img { - width: 100%; - height: 100%; - object-fit: cover; - } -`; - -export const UserName = styled(Title)` - margin: 0.5rem 0 0 !important; - color: var(--text-main-color); - text-align: center; - font-weight: ${FONT_WEIGHT.bold}; -`; - -export const AboutText = styled(Paragraph)` - color: var(--text-main-color); - text-align: center; - font-size: ${FONT_SIZE.md}; - max-width: 80%; - margin: 0.5rem auto 1rem !important; -`; - -export const InfoSection = styled.div` - padding: 1.5rem; -`; - -export const InfoCard = styled(Card)` - margin-bottom: 1rem; - border-radius: ${BORDER_RADIUS}; - background-color: var(--secondary-background-color); - border: 1px solid var(--border-color); - - .ant-card-body { - padding: 1rem; - } - - &:last-child { - margin-bottom: 0; - } -`; - -export const InfoHeader = styled.div` - display: flex; - align-items: center; - margin-bottom: 0.75rem; - - & > *:first-child { - margin-right: 0.5rem; - color: var(--primary-color); - } -`; - -export const InfoTitle = styled(Text)` - color: var(--text-main-color); - font-weight: ${FONT_WEIGHT.semibold}; - font-size: ${FONT_SIZE.md}; -`; - -export const InfoContent = styled(Text)` - color: var(--text-main-color); - word-break: break-all; - font-family: ${FONT_FAMILY.secondary}; - font-size: ${FONT_SIZE.xs}; - margin: 0; - display: block; -`; - -export const CopyContainer = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - background-color: var(--background-color); - padding: 0.5rem 0.75rem; - border-radius: ${BORDER_RADIUS}; - border: 1px solid var(--border-color); - margin-top: 0.5rem; -`; - -export const CopyButton = styled(Button)` - color: var(--primary-color); - font-size: ${FONT_SIZE.xs}; - padding: 0 8px; - height: auto; - border: none; - background: transparent; - - &:hover { - color: var(--primary-light-color); - background: transparent; - } -`; - -export const StyledKeyText = styled(Text)` - font-family: ${FONT_FAMILY.secondary}; - font-size: ${FONT_SIZE.xs}; - margin: 0; - color: var(--text-light-color); - overflow: hidden; - text-overflow: ellipsis; - width: 85%; - white-space: nowrap; -`; - -export const IconWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border-radius: 50%; - background-color: rgba(var(--primary-rgb-color), 0.1); -`; diff --git a/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/SubscriberDetailModal.tsx b/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/SubscriberDetailModal.tsx deleted file mode 100644 index 289c0a6c..00000000 --- a/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/SubscriberDetailModal.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useState } from 'react'; -import { Modal, message, Button } from 'antd'; -import { - UserOutlined, - KeyOutlined, - CalendarOutlined, - CrownOutlined, - CloseOutlined, - CopyOutlined, - CheckOutlined -} from '@ant-design/icons'; -import { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; -import * as S from './SubscriberDetailModal.styles'; - -interface SubscriberDetailModalProps { - subscriber: SubscriberProfile | null; - isVisible: boolean; - onClose: () => void; -} - -export const SubscriberDetailModal: React.FC = ({ - subscriber, - isVisible, - onClose -}) => { - const [copied, setCopied] = useState(false); - - if (!subscriber) { - return null; - } - - // Function to copy public key - const copyPublicKey = () => { - navigator.clipboard.writeText(subscriber.pubkey) - .then(() => { - setCopied(true); - message.success('Public key copied to clipboard'); - - // Reset copied state after 3 seconds - setTimeout(() => { - setCopied(false); - }, 3000); - }) - .catch(() => { - message.error('Failed to copy public key'); - }); - }; - - // Format public key for display - const formatPublicKey = (key: string) => { - if (key.length <= 16) return key; - return `${key.substring(0, 8)}...${key.substring(key.length - 8)}`; - }; - - return ( - } - width={500} - title={null} - bodyStyle={{ padding: 0 }} - > - {/* Header with title */} - - Subscriber Profile - - - {/* Avatar section with profile picture and name */} - - - {subscriber.name - - - {subscriber.name || 'Anonymous Subscriber'} - - - {subscriber.about && ( - - {subscriber.about} - - )} - - - {/* Information section */} - - {/* Public Key Card */} - - - - - - Public Key - - - - {formatPublicKey(subscriber.pubkey)} - : } - > - {copied ? 'Copied' : 'Copy'} - - - - - {/* Subscription Tier Card (if available) */} - {subscriber.metadata?.subscriptionTier && ( - - - - - - Subscription Tier - - - {subscriber.metadata.subscriptionTier} - - - )} - - {/* Subscription Date Card (if available) */} - {subscriber.metadata?.subscribedSince && ( - - - - - - Subscribed Since - - - {subscriber.metadata.subscribedSince} - - - )} - - - ); -}; diff --git a/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/index.ts b/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/index.ts deleted file mode 100644 index c2b7aa23..00000000 --- a/src/components/nft-dashboard/trending-creators/SubscriberDetailModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SubscriberDetailModal } from './SubscriberDetailModal'; From 07e3e2a439815b4b4d330e0479342a3f239adb8c Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:16:40 -0700 Subject: [PATCH 14/24] add loading states to modal --- .../SubscriberDetailModal.tsx | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx index ba8678ec..84870968 100644 --- a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx +++ b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Modal, message } from 'antd'; +import { Modal, message, Spin, Typography } from 'antd'; import { KeyOutlined, CalendarOutlined, @@ -10,15 +10,43 @@ import { } from '@ant-design/icons'; import { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; import * as S from './SubscriberDetailModal.styles'; + interface SubscriberDetailModalProps { subscriber: SubscriberProfile | null; + loading?: boolean; + fetchFailed?: boolean; isVisible: boolean; onClose: () => void; } -export const SubscriberDetailModal: React.FC = ({ subscriber, isVisible, onClose }) => { +export const SubscriberDetailModal: React.FC = ({ subscriber, isVisible, onClose, loading = false, fetchFailed = false }) => { const [copied, setCopied] = useState(false); + // Loading state + if (!subscriber && loading && !fetchFailed) { + return ( + + + + ); + } + // Error state + if (!subscriber && !loading && fetchFailed) { + return ( + + Failed to fetch subscriber profile. Please try again. + + ); + } + + // Not found state + if (!subscriber && !loading && !fetchFailed) { + return ( + + Couldn't find this subscriber profile. + + ); + } if (!subscriber) { return null; } From 736da6813d143cab97a4043bdee305f4c4e111d5 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:17:02 -0700 Subject: [PATCH 15/24] move SubscriberProfile creation to util file --- src/utils/utils.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 10bbd559..de99c168 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -6,7 +6,8 @@ import maestro from '@app/assets/images/card-issuers/maestro.png'; import { CurrencyTypeEnum, Severity } from '@app/interfaces/interfaces'; import { BaseBadgeProps } from '@app/components/common/BaseBadge/BaseBadge'; import { currencies } from '@app/constants/config/currencies'; - +import { NDKUserProfile } from '@nostr-dev-kit/ndk'; +import { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; export const camelize = (string: string): string => { return string .split(' ') @@ -21,7 +22,14 @@ export const getSatsCurrency = (price: number | string, currency: CurrencyTypeEn // Handle potential negative sign placement return isIcon ? `${currencySymbol}${formattedPrice}` : `${formattedPrice} ${currency}`; }; - + export const convertNDKUserProfileToSubscriberProfile = (pubkey: string, user: NDKUserProfile): SubscriberProfile => { + return { + pubkey, + name: user.name || '', + picture: user.picture || '', + about: user.about || '', + }; + }; export const getCurrencyPrice = (price: number | string, currency: CurrencyTypeEnum, isIcon = true): string => { const currencySymbol = currencies[currency][isIcon ? 'icon' : 'text']; From d657ee74386decf4c8352e899559ad4a326a4dc7 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:17:19 -0700 Subject: [PATCH 16/24] implement simple search to app --- .../searchDropdown/SearchDropdown.tsx | 89 ++++++++++++++++++- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/src/components/header/components/searchDropdown/SearchDropdown.tsx b/src/components/header/components/searchDropdown/SearchDropdown.tsx index e1a7d272..9ce4cc13 100644 --- a/src/components/header/components/searchDropdown/SearchDropdown.tsx +++ b/src/components/header/components/searchDropdown/SearchDropdown.tsx @@ -5,7 +5,11 @@ import { CategoryComponents } from '@app/components/header/components/HeaderSear import { Btn, InputSearch } from '../HeaderSearch/HeaderSearch.styles'; import { useTranslation } from 'react-i18next'; import { BasePopover } from '@app/components/common/BasePopover/BasePopover'; - +import { NDKUserProfile, useNDK } from '@nostr-dev-kit/ndk-hooks'; +import usePaidSubscribers from '@app/hooks/usePaidSubscribers'; +import { convertNDKUserProfileToSubscriberProfile } from '@app/utils/utils'; +import { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; +import { SubscriberDetailModal } from '@app/components/relay-dashboard/paid-subscribers/SubscriberDetailModal'; interface SearchOverlayProps { query: string; setQuery: (query: string) => void; @@ -22,7 +26,12 @@ export const SearchDropdown: React.FC = ({ setOverlayOpen, }) => { const [isFilterOpen, setFilterOpen] = useState(false); - + const [isSubscriberDetailModalOpen, setSubscriberDetailModalOpen] = useState(false); + const [fetchingProfile, setFetchingProfile] = useState(false); + const [fetchingFailed, setFetchingFailed] = useState(false); + const [subscriberProfile, setSubscriberProfile] = useState(null); + const { subscribers } = usePaidSubscribers(); + const ndkInstance = useNDK(); const { t } = useTranslation(); useEffect(() => { @@ -32,6 +41,66 @@ export const SearchDropdown: React.FC = ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any const ref = useRef(null); + const fetchProfile = async (pubkey: string): Promise => { + if (!ndkInstance) return null; + + try { + setFetchingProfile(true); + const profile = await ndkInstance.ndk?.getUser({ pubkey: pubkey }).fetchProfile(); + + if (profile) { + setFetchingProfile(false); + setFetchingFailed(false); + return profile; + } else { + console.error('Profile not found for pubkey:', pubkey); + setFetchingProfile(false); + setFetchingFailed(true); + return null; + } + } catch (error) { + console.error('Error fetching profile:', error); + setFetchingProfile(false); + setFetchingFailed(true); + return null; + } + }; + + const handleSearchProfile = async () => { + if (!query) return; + //verify that it's a pubkey + if (/^[a-fA-F0-9]{64}$/.test(query)) { + setSubscriberDetailModalOpen(true); + + //See if the pubkey exists in the subscribers + const pubkey = query; + const subscriber = subscribers.find((sub) => sub.pubkey === query); + // If it exists, open the modal with the subscriber details. If name,picture, or about are not set, fetch profile. + //if It doesnt exist, fetch the profile from NDK + //once fetched, convert it to SubscriberProfile and open the modal + if (subscriber) { + if (!subscriber.name || !subscriber.picture || !subscriber.about) { + const profile = await fetchProfile(pubkey); + if (profile) { + const subscriberProfile: SubscriberProfile = convertNDKUserProfileToSubscriberProfile(pubkey, profile); + // Open the modal with the fetched subscriber profile + setSubscriberProfile(subscriberProfile); + } + } + } else { + const profile = await fetchProfile(pubkey); + if (profile) { + const subscriberProfile: SubscriberProfile = convertNDKUserProfileToSubscriberProfile(pubkey, profile); + // Open the modal with the fetched subscriber profile + setSubscriberProfile(subscriberProfile); + } + } + } + }; + const onCloseSubscriberDetailModal = () => { + setSubscriberDetailModalOpen(false); + setSubscriberProfile(null); + }; return ( <> = ({ = ({ onChange={(event) => setQuery(event.target.value)} enterButton={null} addonAfter={null} + onKeyDown={(event) => { + if (event.key === 'Enter') { + handleSearchProfile(); + } + }} />
+ {isSubscriberDetailModalOpen && ( + + )} ); From a2be3b1331b2888aacf9a350e7ddc0d4adc983b4 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:17:44 -0700 Subject: [PATCH 17/24] move coversion function from PaidSubscriber File --- .../paid-subscribers/PaidSubscribers.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx index 748e9aab..2d98edca 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx @@ -14,8 +14,8 @@ import { useResponsive } from '@app/hooks/useResponsive'; import usePaidSubscribers, { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; import { Row, Col, Modal, Typography } from 'antd'; import { nip19 } from 'nostr-tools'; -import { NDKUserProfile } from '@nostr-dev-kit/ndk-hooks'; import { useNDK } from '@nostr-dev-kit/ndk-hooks'; +import { convertNDKUserProfileToSubscriberProfile } from '@app/utils/utils'; import { UserOutlined } from '@ant-design/icons'; import { CreatorButton } from './avatar/SubscriberAvatar.styles'; const { Text } = Typography; @@ -91,14 +91,7 @@ export const PaidSubscribers: React.FC = () => { } } }; - const convertNDKUserProfileToSubscriberProfile = (pubkey: string, user: NDKUserProfile): SubscriberProfile => { - return { - pubkey, - name: user.name || '', - picture: user.picture || '', - about: user.about || '', - }; - }; + useEffect(() => { // Fetch profiles for test subscribers const fetchProfiles = async () => { From bd4301e7531fee21d7c79e4662f9887b1773f1b7 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:22:25 -0700 Subject: [PATCH 18/24] fix size of loading modal --- .../SubscriberDetailModal.styles.ts | 15 +++++++++++++-- .../SubscriberDetailModal.tsx | 12 ++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.styles.ts b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.styles.ts index bbeb4dac..722a2b30 100644 --- a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.styles.ts +++ b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.styles.ts @@ -1,7 +1,6 @@ import styled from 'styled-components'; -import { Card, Typography, Button, Badge } from 'antd'; +import { Card, Typography, Button, Badge, Modal } from 'antd'; import { BORDER_RADIUS, FONT_SIZE, FONT_WEIGHT, FONT_FAMILY } from '@app/styles/themes/constants'; -import { BaseBadge } from '@app/components/common/BaseBadge/BaseBadge'; const { Title, Text, Paragraph } = Typography; export const StyledModal = styled(Card)` @@ -209,3 +208,15 @@ export const SubscriptionBadge = styled(Badge.Ribbon)` background: ${({ subscribed }) => (subscribed ? 'var(--ant-primary-color)' : 'var(--error-color)')}; color: var(--text-main-color); `; + +export const StateModal = styled(Modal)` + + .ant-modal-body { + height: 50vh; + flex-direction: column; + display: flex; + align-items: center; + justify-content: center; + } + +` \ No newline at end of file diff --git a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx index 84870968..f14c70f6 100644 --- a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx +++ b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx @@ -24,27 +24,27 @@ export const SubscriberDetailModal: React.FC = ({ su // Loading state if (!subscriber && loading && !fetchFailed) { return ( - + - + ); } // Error state if (!subscriber && !loading && fetchFailed) { return ( - + Failed to fetch subscriber profile. Please try again. - + ); } // Not found state if (!subscriber && !loading && !fetchFailed) { return ( - + Couldn't find this subscriber profile. - + ); } if (!subscriber) { From a2de1a5e44d29f0d145e16d0645486c40577ea6c Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Thu, 3 Jul 2025 19:49:13 -0700 Subject: [PATCH 19/24] add error message for invalid pubkey --- src/components/header/Header.styles.ts | 13 +++++++++++++ .../components/HeaderSearch/HeaderSearch.tsx | 2 +- .../components/searchDropdown/SearchDropdown.tsx | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/components/header/Header.styles.ts b/src/components/header/Header.styles.ts index 35bb4d39..12fd4e57 100644 --- a/src/components/header/Header.styles.ts +++ b/src/components/header/Header.styles.ts @@ -7,6 +7,7 @@ import { BaseCol } from '../common/BaseCol/BaseCol'; export const HeaderActionWrapper = styled.div` cursor: pointer; + position: relative; & > .ant-btn > span[role='img'], .ant-badge { @@ -22,6 +23,18 @@ export const HeaderActionWrapper = styled.div` } `; +export const InvalidPubkey = styled.div` + position: absolute; + top: 3rem; + left: 2rem; + width: 100%; + color: var(--error-color); + height: 100%; + display: flex; + align-items: center; + font-weight: bold; +`; + export const DropdownCollapse = styled(BaseCollapse)` & > .ant-collapse-item > .ant-collapse-header { font-weight: 600; diff --git a/src/components/header/components/HeaderSearch/HeaderSearch.tsx b/src/components/header/components/HeaderSearch/HeaderSearch.tsx index 3cb1389d..5541b4be 100644 --- a/src/components/header/components/HeaderSearch/HeaderSearch.tsx +++ b/src/components/header/components/HeaderSearch/HeaderSearch.tsx @@ -37,7 +37,7 @@ export const HeaderSearch: React.FC = () => { useEffect(() => { setModalOpen(false); - setOverlayOpen(false); + //setOverlayOpen(false); }, [pathname]); return ( diff --git a/src/components/header/components/searchDropdown/SearchDropdown.tsx b/src/components/header/components/searchDropdown/SearchDropdown.tsx index 9ce4cc13..22cef744 100644 --- a/src/components/header/components/searchDropdown/SearchDropdown.tsx +++ b/src/components/header/components/searchDropdown/SearchDropdown.tsx @@ -8,6 +8,8 @@ import { BasePopover } from '@app/components/common/BasePopover/BasePopover'; import { NDKUserProfile, useNDK } from '@nostr-dev-kit/ndk-hooks'; import usePaidSubscribers from '@app/hooks/usePaidSubscribers'; import { convertNDKUserProfileToSubscriberProfile } from '@app/utils/utils'; +import { InvalidPubkey } from '../../Header.styles'; + import { SubscriberProfile } from '@app/hooks/usePaidSubscribers'; import { SubscriberDetailModal } from '@app/components/relay-dashboard/paid-subscribers/SubscriberDetailModal'; interface SearchOverlayProps { @@ -30,6 +32,7 @@ export const SearchDropdown: React.FC = ({ const [fetchingProfile, setFetchingProfile] = useState(false); const [fetchingFailed, setFetchingFailed] = useState(false); const [subscriberProfile, setSubscriberProfile] = useState(null); + const [invalidPubkey, setInvalidPubkey] = useState(false); const { subscribers } = usePaidSubscribers(); const ndkInstance = useNDK(); const { t } = useTranslation(); @@ -95,12 +98,20 @@ export const SearchDropdown: React.FC = ({ setSubscriberProfile(subscriberProfile); } } + }else{ + setInvalidPubkey(true); } }; const onCloseSubscriberDetailModal = () => { setSubscriberDetailModalOpen(false); setSubscriberProfile(null); }; + + useEffect(() => { + if(query.length === 0) { + setInvalidPubkey(false); + } + }, [query]); return ( <> = ({ getPopupContainer={() => ref.current} > + {invalidPubkey && ( + + {"Invalid pubkey. Please enter a hexadecimal string."} + + )} Date: Thu, 3 Jul 2025 19:52:19 -0700 Subject: [PATCH 20/24] shorten invalid bukey message --- .../header/components/searchDropdown/SearchDropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/header/components/searchDropdown/SearchDropdown.tsx b/src/components/header/components/searchDropdown/SearchDropdown.tsx index 22cef744..ee4a0940 100644 --- a/src/components/header/components/searchDropdown/SearchDropdown.tsx +++ b/src/components/header/components/searchDropdown/SearchDropdown.tsx @@ -124,7 +124,7 @@ export const SearchDropdown: React.FC = ({ {invalidPubkey && ( - {"Invalid pubkey. Please enter a hexadecimal string."} + {"Invalid pubkey."} )} Date: Thu, 3 Jul 2025 19:59:50 -0700 Subject: [PATCH 21/24] skip fetching process if using dummy data --- .../components/searchDropdown/SearchDropdown.tsx | 2 +- .../paid-subscribers/PaidSubscribers.tsx | 11 ++++++++--- src/hooks/usePaidSubscribers.ts | 13 +++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/header/components/searchDropdown/SearchDropdown.tsx b/src/components/header/components/searchDropdown/SearchDropdown.tsx index ee4a0940..5061ea28 100644 --- a/src/components/header/components/searchDropdown/SearchDropdown.tsx +++ b/src/components/header/components/searchDropdown/SearchDropdown.tsx @@ -130,7 +130,7 @@ export const SearchDropdown: React.FC = ({ { console.log('[PaidSubscribers] Component rendering...'); const hookResult = usePaidSubscribers(12); - const { subscribers, fetchMore, hasMore, loading } = hookResult; + const { subscribers, fetchMore, hasMore, loading, useDummyData } = hookResult; const ndkInstance = useNDK(); // Modal state for subscriber details @@ -94,6 +94,11 @@ export const PaidSubscribers: React.FC = () => { useEffect(() => { // Fetch profiles for test subscribers + if (useDummyData) { + console.warn('[PaidSubscribers] Using dummy data, skipping profile fetch'); + setLoadingProfiles(false); + return; + } const fetchProfiles = async () => { if (!ndkInstance || !ndkInstance.ndk) { console.error('NDK instance is not initialized'); @@ -191,7 +196,7 @@ export const PaidSubscribers: React.FC = () => { ))} - + {/* View All Subscribers Modal */} { {isModalVisible && ( - + )} {/* View All Subscribers Modal */} diff --git a/src/hooks/usePaidSubscribers.ts b/src/hooks/usePaidSubscribers.ts index f223893f..70d9751f 100644 --- a/src/hooks/usePaidSubscribers.ts +++ b/src/hooks/usePaidSubscribers.ts @@ -1,5 +1,4 @@ import { useState, useEffect, useCallback, useRef } from 'react'; -import { message } from 'antd'; import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; import { useHandleLogout } from './authUtils'; @@ -21,7 +20,7 @@ import adminDefaultAvatar from '@app/assets/admin-default-avatar.png'; export interface SubscriberProfile { pubkey: string; - picture: string; + picture?: string; name?: string; about?: string; metadata?: { @@ -29,7 +28,16 @@ export interface SubscriberProfile { subscribedSince?: string; }; } +const testSubscribers: SubscriberProfile[] = [ + { pubkey: '91dfb08db37712e74d892adbbf63abab43cb6aa3806950548f3146347d29b6ae' }, + { pubkey: '59cacbd83ad5c54ad91dacf51a49c06e0bef730ac0e7c235a6f6fa29b9230f02' }, + { pubkey: '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245' }, + { pubkey: '78a317586cbc30d20f8aa94d8450eb0cd58b312bad94fc76139c72eb2e5c81d2' }, + { pubkey: '4657dfe8965be8980a93072bcfb5e59a65124406db0f819215ee78ba47934b3e' }, + { pubkey: '6e75f7972397ca3295e0f4ca0fbc6eb9cc79be85bafdd56bd378220ca8eee74e' }, + { pubkey: '7b991f776d04d87cb5d4259688187a520f6afc16b2b9ad26dac6b8ee76c2840d'} +]; // Define dummy profiles using the imported images const dummyProfiles: SubscriberProfile[] = [ { pubkey: 'dummy-1', picture: profile1 }, @@ -46,6 +54,7 @@ const dummyProfiles: SubscriberProfile[] = [ { pubkey: 'dummy-12', picture: profile11 }, ]; + // URL of the placeholder avatar that comes from the API const PLACEHOLDER_AVATAR_URL = 'http://localhost:3000/placeholder-avatar.png'; From 9c4149d043a2aabe77152a4716f9125dff38f762 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Fri, 4 Jul 2025 16:59:24 +0200 Subject: [PATCH 22/24] Implement hybrid profile fetching with NIP-42 authentication - Add environment-based NDK configuration with relay URLs - Implement NIP-42 authentication using NDKRelayAuthPolicies.signIn - Create hybrid profile fetching: NDK first, backend API fallback - Fix authentication issues in LoginForm and useWalletAuth - Improve display name parsing from Nostr profile data - Prevent infinite loops with better profile caching logic - Remove global Window.nostr declaration to avoid NDK conflicts --- .env.development | 4 + src/App.tsx | 24 ++++- src/components/auth/LoginForm/LoginForm.tsx | 4 +- .../paid-subscribers/PaidSubscribers.tsx | 88 ++++++++++++++----- .../SubscriberDetailModal.tsx | 2 +- src/config/config.ts | 11 +++ src/hooks/usePaidSubscribers.ts | 26 +++--- src/hooks/useWalletAuth.ts | 11 ++- src/types/nostr.ts | 7 +- src/utils/utils.ts | 7 +- 10 files changed, 131 insertions(+), 53 deletions(-) diff --git a/.env.development b/.env.development index 678f66cd..e4c9ebd5 100644 --- a/.env.development +++ b/.env.development @@ -4,6 +4,10 @@ REACT_APP_ASSETS_BUCKET=http://localhost REACT_APP_DEMO_MODE=false REACT_APP_BASENAME= +# Nostr relay configuration for profile fetching +REACT_APP_OWN_RELAY_URL=ws://localhost:7000 +REACT_APP_NOSTR_RELAY_URLS=wss://relay.damus.io,wss://relay.nostr.band,wss://relay.snort.social,wss://vault.iris.to + # More info https://create-react-app.dev/docs/advanced-configuration ESLINT_NO_DEV_ERRORS=true TSC_COMPILE_ON_ERROR=true diff --git a/src/App.tsx b/src/App.tsx index 950202fa..2c9bf942 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,15 +13,33 @@ import { usePWA } from './hooks/usePWA'; import { useThemeWatcher } from './hooks/useThemeWatcher'; import { useAppSelector } from './hooks/reduxHooks'; import { themeObject } from './styles/themes/themeVariables'; -import NDK from '@nostr-dev-kit/ndk'; +import NDK, { NDKEvent, NDKNip07Signer, NDKRelayAuthPolicies } from '@nostr-dev-kit/ndk'; import { useNDKInit } from '@nostr-dev-kit/ndk-hooks'; +import config from './config/config'; + +// Configure NDK with user's relay URLs from environment variables +const getRelayUrls = () => { + const relayUrls = [...config.nostrRelayUrls]; + + // Add user's own relay URL as the first priority if provided + if (config.ownRelayUrl) { + relayUrls.unshift(config.ownRelayUrl); + } + + return relayUrls; +}; const ndk = new NDK({ - explicitRelayUrls: ['wss://relay.damus.io', 'wss://relay.nostr.band', 'wss://relay.snort.social', 'vault.iris.to'], + explicitRelayUrls: getRelayUrls(), + signer: new NDKNip07Signer(), }); + +// Set up NIP-42 authentication policy following the example +ndk.relayAuthDefaultPolicy = NDKRelayAuthPolicies.signIn({ ndk }); + ndk .connect() - .then(() => console.log('NDK connected')) + .then(() => console.log('NDK connected with relay URLs and NIP-42 auth policy:', getRelayUrls())) .catch((error) => console.error('NDK connection error:', error)); const App: React.FC = () => { diff --git a/src/components/auth/LoginForm/LoginForm.tsx b/src/components/auth/LoginForm/LoginForm.tsx index 61c51a3b..817b7f5f 100644 --- a/src/components/auth/LoginForm/LoginForm.tsx +++ b/src/components/auth/LoginForm/LoginForm.tsx @@ -70,9 +70,9 @@ export const LoginForm: React.FC = () => { console.log('Signed event:', signedEvent); const response = await verifyChallenge({ - challenge: signedEvent.content, + challenge: event.content, signature: signedEvent.sig, - messageHash: signedEvent.id, + messageHash: event.id, event: signedEvent, }); diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx index 09fc7c60..676e81fc 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx @@ -93,48 +93,94 @@ export const PaidSubscribers: React.FC = () => { }; useEffect(() => { - // Fetch profiles for test subscribers + // Implement hybrid profile fetching: NDK first, fallback to backend data if (useDummyData) { console.warn('[PaidSubscribers] Using dummy data, skipping profile fetch'); setLoadingProfiles(false); return; } + const fetchProfiles = async () => { if (!ndkInstance || !ndkInstance.ndk) { - console.error('NDK instance is not initialized'); + console.error('[PaidSubscribers] NDK instance is not initialized, using backend data only'); + setLoadingProfiles(false); return; } - //1. map through subscribers and fetch profiles. skip profile if already on map + + console.log('[PaidSubscribers] Starting hybrid profile fetch for', subscribers.length, 'subscribers'); + + // Process each subscriber with hybrid approach await Promise.all( subscribers.map(async (subscriber) => { - if ( - subscriberProfiles.has(subscriber.pubkey) && - subscriberProfiles.get(subscriber.pubkey)?.picture && - subscriberProfiles.get(subscriber.pubkey)?.about - ) { - return subscriberProfiles.get(subscriber.pubkey); + // Skip if we already have a complete profile in our map + const existingProfile = subscriberProfiles.get(subscriber.pubkey); + const hasValidProfile = existingProfile && ( + (existingProfile.name && existingProfile.name !== 'Anonymous Subscriber') || + existingProfile.picture || + existingProfile.about + ); + + if (hasValidProfile) { + console.log(`[PaidSubscribers] Profile already cached for ${subscriber.pubkey}:`, { + name: existingProfile.name, + picture: !!existingProfile.picture, + about: !!existingProfile.about + }); + return; } + try { - if (!ndkInstance.ndk) { - console.error('NDK instance is not available'); - return null; - } + console.log(`[PaidSubscribers] Fetching NDK profile for ${subscriber.pubkey}`); + + // Try to fetch profile from NDK (user's relay + other relays) const user = await ndkInstance.ndk?.getUser({ pubkey: subscriber.pubkey }).fetchProfile(); - if (user) { - // Convert NDKUserProfile to SubscriberProfile and add to map - const covertedNDKUserProfile = convertNDKUserProfileToSubscriberProfile(subscriber.pubkey, user); - updateSubscriberProfile(subscriber.pubkey, covertedNDKUserProfile); - - return user; + + if (user && (user.name || user.picture || user.about)) { + // NDK returned a profile - use it as the primary source + console.log(`[PaidSubscribers] NDK profile found for ${subscriber.pubkey}:`, { + name: user.name, + picture: user.picture, + about: user.about + }); + + const ndkProfile = convertNDKUserProfileToSubscriberProfile(subscriber.pubkey, user); + updateSubscriberProfile(subscriber.pubkey, ndkProfile); + } else { + // NDK came up empty - fallback to backend data + console.log(`[PaidSubscribers] NDK profile empty for ${subscriber.pubkey}, falling back to backend data:`, { + name: subscriber.name, + picture: subscriber.picture, + about: subscriber.about + }); + + // Use the backend data as-is since NDK had no better information + updateSubscriberProfile(subscriber.pubkey, { + ...subscriber, + // Ensure we have fallback values if backend data is also incomplete + name: subscriber.name || 'Anonymous Subscriber', + picture: subscriber.picture || '', + about: subscriber.about || '' + }); } } catch (error) { - console.error(`Error fetching profile for ${subscriber.pubkey}:`, error); + console.error(`[PaidSubscribers] Error fetching NDK profile for ${subscriber.pubkey}:`, error); + + // Error occurred - fallback to backend data + console.log(`[PaidSubscribers] NDK error for ${subscriber.pubkey}, using backend data`); + updateSubscriberProfile(subscriber.pubkey, { + ...subscriber, + name: subscriber.name || 'Anonymous Subscriber', + picture: subscriber.picture || '', + about: subscriber.about || '' + }); } - return null; }), ); + + console.log('[PaidSubscribers] Hybrid profile fetch completed'); setLoadingProfiles(false); }; + fetchProfiles(); }, [subscribers, ndkInstance]); diff --git a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx index f14c70f6..5cdf6352 100644 --- a/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx +++ b/src/components/relay-dashboard/paid-subscribers/SubscriberDetailModal/SubscriberDetailModal.tsx @@ -43,7 +43,7 @@ export const SubscriberDetailModal: React.FC = ({ su if (!subscriber && !loading && !fetchFailed) { return ( - Couldn't find this subscriber profile. + Couldn't find this subscriber profile. ); } diff --git a/src/config/config.ts b/src/config/config.ts index f569f204..fce48ba0 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -8,6 +8,17 @@ const config = { isDemoMode: process.env.REACT_APP_DEMO_MODE === 'true', walletBaseURL: process.env.REACT_APP_WALLET_BASE_URL?.trim() || 'http://localhost:9003', + // Nostr relay configuration + nostrRelayUrls: process.env.REACT_APP_NOSTR_RELAY_URLS?.split(',').map(url => url.trim()) || [ + 'wss://relay.damus.io', + 'wss://relay.nostr.band', + 'wss://relay.snort.social', + 'wss://vault.iris.to' + ], + + // User's own relay URL (primary relay for profile fetching) + ownRelayUrl: process.env.REACT_APP_OWN_RELAY_URL?.trim() || null, + // Notification settings notifications: { // 5 minutes in milliseconds diff --git a/src/hooks/usePaidSubscribers.ts b/src/hooks/usePaidSubscribers.ts index 70d9751f..299249df 100644 --- a/src/hooks/usePaidSubscribers.ts +++ b/src/hooks/usePaidSubscribers.ts @@ -146,15 +146,14 @@ const usePaidSubscribers = (pageSize = 20) => { console.log(`[usePaidSubscribers] Normalized data:`, data); console.log(`[usePaidSubscribers] Data length: ${data?.length}, typeof data: ${typeof data}, Array.isArray(data): ${Array.isArray(data)}`); - // *** NEW DIRECT CHECK FOR DATA WITHOUT NESTED CONDITIONS *** + // If we have backend data, use it as the primary source and return subscribers for NDK enhancement if (data && Array.isArray(data) && data.length > 0) { - console.log(`[usePaidSubscribers] **** REAL DATA DETECTED! Bypassing all other logic ****`); + console.log(`[usePaidSubscribers] Backend data detected, using as primary source`); try { // Process the profiles to replace placeholder avatar URLs const processedProfiles: SubscriberProfile[] = []; - // Attempt to directly parse one of the data elements to verify it's structured correctly console.log(`[usePaidSubscribers] First item pubkey:`, data[0]?.pubkey); console.log(`[usePaidSubscribers] First item picture:`, data[0]?.picture); @@ -182,31 +181,26 @@ const usePaidSubscribers = (pageSize = 20) => { }); } - console.log('[usePaidSubscribers] DIRECT STATE UPDATES WITH REAL DATA'); + console.log('[usePaidSubscribers] Backend data processed successfully'); console.log('[usePaidSubscribers] Processed profiles count:', processedProfiles.length); - // Force a state update for useDummyData first + // Update state with backend data setUseDummyData(false); - - // Then update all other state with real data - if (processedProfiles.length > 0) { - setSubscribers(processedProfiles); - } - + setSubscribers(processedProfiles); setHasMore(data.length === pageSize); setCurrentPage(page + 1); - console.log('[usePaidSubscribers] State updates with real data complete!'); - return; // Exit early after processing real data + console.log('[usePaidSubscribers] Backend data set as primary source'); + return; // Exit early after processing backend data } catch (processingError) { - console.error('[usePaidSubscribers] Error processing profiles:', processingError); + console.error('[usePaidSubscribers] Error processing backend profiles:', processingError); // Continue to fallback logic below if processing fails } } - // Fallback logic if the direct approach failed + // Fallback logic if no backend data if (isMounted.current) { - console.log('[usePaidSubscribers] Using fallback logic - probably no valid data'); + console.log('[usePaidSubscribers] No backend data found, using dummy data'); setUseDummyData(true); setSubscribers(dummyProfiles); setHasMore(false); diff --git a/src/hooks/useWalletAuth.ts b/src/hooks/useWalletAuth.ts index aef31dbc..73387a7c 100644 --- a/src/hooks/useWalletAuth.ts +++ b/src/hooks/useWalletAuth.ts @@ -54,14 +54,17 @@ const useWalletAuth = () => { console.log(challenge) - // Sign the challenge using Nostr - const signedEvent = await window.nostr.signEvent({ + // Create the event to sign + const event = { pubkey: npub, content: challenge, created_at: Math.floor(Date.now() / 1000), kind: 1, tags: [], - }); + }; + + // Sign the challenge using Nostr + const signedEvent = await window.nostr.signEvent(event); // Send the signed challenge to the backend for verification const verifyResponse = await fetch(`${config.walletBaseURL}/verify`, { @@ -70,7 +73,7 @@ const useWalletAuth = () => { body: JSON.stringify({ challenge, signature: signedEvent.sig, - messageHash: signedEvent.id, + messageHash: event.content, event: signedEvent, }), }); diff --git a/src/types/nostr.ts b/src/types/nostr.ts index 155681fd..ff05e6e2 100644 --- a/src/types/nostr.ts +++ b/src/types/nostr.ts @@ -13,8 +13,5 @@ export interface NostrProvider { }; } - declare global { - interface Window { - nostr?: NostrProvider; - } - } \ No newline at end of file + // Global Window declaration removed to avoid conflict with NDK types + // NDK will provide the window.nostr types automatically \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index de99c168..477abbe6 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -23,9 +23,14 @@ export const getSatsCurrency = (price: number | string, currency: CurrencyTypeEn return isIcon ? `${currencySymbol}${formattedPrice}` : `${formattedPrice} ${currency}`; }; export const convertNDKUserProfileToSubscriberProfile = (pubkey: string, user: NDKUserProfile): SubscriberProfile => { + // Handle display_name from the profile data since NDK sometimes uses different field names + const displayName = user.name || + ('display_name' in user ? user.display_name : '') || + ('displayName' in user ? user.displayName : '') || ''; + return { pubkey, - name: user.name || '', + name: typeof displayName === 'string' ? displayName : '', picture: user.picture || '', about: user.about || '', }; From 5208326fd3301409b5047816f999f8c68f4a993a Mon Sep 17 00:00:00 2001 From: Maphikza Date: Fri, 4 Jul 2025 18:30:16 +0200 Subject: [PATCH 23/24] Fix paid subscribers to prioritize real data over dummy data - Only use dummy data when no real subscribers exist - Keep existing real subscribers during errors - Initialize with empty array instead of dummy profiles --- src/hooks/usePaidSubscribers.ts | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/hooks/usePaidSubscribers.ts b/src/hooks/usePaidSubscribers.ts index 299249df..ef00963e 100644 --- a/src/hooks/usePaidSubscribers.ts +++ b/src/hooks/usePaidSubscribers.ts @@ -59,12 +59,12 @@ const dummyProfiles: SubscriberProfile[] = [ const PLACEHOLDER_AVATAR_URL = 'http://localhost:3000/placeholder-avatar.png'; const usePaidSubscribers = (pageSize = 20) => { - const [subscribers, setSubscribers] = useState(dummyProfiles); + const [subscribers, setSubscribers] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [hasMore, setHasMore] = useState(false); const [currentPage, setCurrentPage] = useState(1); - const [useDummyData, setUseDummyData] = useState(true); + const [useDummyData, setUseDummyData] = useState(false); const isMounted = useRef(true); const handleLogout = useHandleLogout(); @@ -198,21 +198,34 @@ const usePaidSubscribers = (pageSize = 20) => { } } - // Fallback logic if no backend data + // Fallback logic if no backend data - only use dummy data when truly no data available if (isMounted.current) { - console.log('[usePaidSubscribers] No backend data found, using dummy data'); - setUseDummyData(true); - setSubscribers(dummyProfiles); + console.log('[usePaidSubscribers] No backend data found'); + // Only use dummy data if we have absolutely nothing and no existing real subscribers + if (subscribers.length === 0 && !subscribers.some(s => !s.pubkey.startsWith('dummy-'))) { + console.log('[usePaidSubscribers] No existing subscribers, using dummy data as fallback'); + setUseDummyData(true); + setSubscribers(dummyProfiles); + } else { + console.log('[usePaidSubscribers] Keeping existing subscribers data or have real subscribers'); + setUseDummyData(false); + } setHasMore(false); - console.log('[usePaidSubscribers] Fallback to dummy data complete'); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to fetch subscribers'; setError(errorMessage); console.error(`[usePaidSubscribers] Error fetching subscribers:`, err); - console.log(`[usePaidSubscribers] ${errorMessage}, using dummy data`); - setUseDummyData(true); - setSubscribers(dummyProfiles); + + // Only use dummy data if we don't have any real subscribers + if (subscribers.length === 0 || subscribers.every(s => s.pubkey.startsWith('dummy-'))) { + console.log(`[usePaidSubscribers] ${errorMessage}, using dummy data`); + setUseDummyData(true); + setSubscribers(dummyProfiles); + } else { + console.log(`[usePaidSubscribers] ${errorMessage}, keeping existing real subscribers`); + setUseDummyData(false); + } setHasMore(false); } finally { if (isMounted.current) { From 9b2fbc848116d6046c1bd394d42f2ee8e44c9228 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Fri, 4 Jul 2025 19:00:47 +0200 Subject: [PATCH 24/24] Clean up excessive console.log statements - Remove debug logs from TotalEarning.tsx - Remove chart data logs from TotalEarningChart.tsx - Remove API response logs from activity.api.ts - Remove extensive debug logs from useChartData.ts - Remove component logs from PaidSubscribers.tsx - Fix unused variables after log removal --- src/api/activity.api.ts | 2 - .../paid-subscribers/PaidSubscribers.tsx | 27 ----- .../totalEarning/TotalEarning.tsx | 2 - .../TotalEarningChart/TotalEarningChart.tsx | 2 - src/hooks/useBitcoinRates.ts | 110 +++++++++++++----- src/hooks/useChartData.ts | 14 --- 6 files changed, 82 insertions(+), 75 deletions(-) diff --git a/src/api/activity.api.ts b/src/api/activity.api.ts index 89696e97..eab66543 100644 --- a/src/api/activity.api.ts +++ b/src/api/activity.api.ts @@ -40,8 +40,6 @@ export const getUserActivities = (handleLogout: () => void): Promise ({ id: item.ID, witness_tx_id: item.Address, diff --git a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx index 676e81fc..a41165c9 100644 --- a/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx +++ b/src/components/relay-dashboard/paid-subscribers/PaidSubscribers.tsx @@ -21,7 +21,6 @@ import { CreatorButton } from './avatar/SubscriberAvatar.styles'; const { Text } = Typography; export const PaidSubscribers: React.FC = () => { - console.log('[PaidSubscribers] Component rendering...'); const hookResult = usePaidSubscribers(12); const { subscribers, fetchMore, hasMore, loading, useDummyData } = hookResult; const ndkInstance = useNDK(); @@ -76,7 +75,6 @@ export const PaidSubscribers: React.FC = () => { setAllSubscribers([...subscribers]); // Start with current subscribers // Fetch more subscribers if available - const currentSubscribers = [...subscribers]; let canFetchMore = hasMore; while (canFetchMore) { @@ -86,7 +84,6 @@ export const PaidSubscribers: React.FC = () => { // track the updated state properly or use a separate hook for fetching all canFetchMore = false; // For now, just fetch once more } catch (error) { - console.error('Error fetching more subscribers:', error); break; } } @@ -95,19 +92,16 @@ export const PaidSubscribers: React.FC = () => { useEffect(() => { // Implement hybrid profile fetching: NDK first, fallback to backend data if (useDummyData) { - console.warn('[PaidSubscribers] Using dummy data, skipping profile fetch'); setLoadingProfiles(false); return; } const fetchProfiles = async () => { if (!ndkInstance || !ndkInstance.ndk) { - console.error('[PaidSubscribers] NDK instance is not initialized, using backend data only'); setLoadingProfiles(false); return; } - console.log('[PaidSubscribers] Starting hybrid profile fetch for', subscribers.length, 'subscribers'); // Process each subscriber with hybrid approach await Promise.all( @@ -121,37 +115,21 @@ export const PaidSubscribers: React.FC = () => { ); if (hasValidProfile) { - console.log(`[PaidSubscribers] Profile already cached for ${subscriber.pubkey}:`, { - name: existingProfile.name, - picture: !!existingProfile.picture, - about: !!existingProfile.about - }); return; } try { - console.log(`[PaidSubscribers] Fetching NDK profile for ${subscriber.pubkey}`); // Try to fetch profile from NDK (user's relay + other relays) const user = await ndkInstance.ndk?.getUser({ pubkey: subscriber.pubkey }).fetchProfile(); if (user && (user.name || user.picture || user.about)) { // NDK returned a profile - use it as the primary source - console.log(`[PaidSubscribers] NDK profile found for ${subscriber.pubkey}:`, { - name: user.name, - picture: user.picture, - about: user.about - }); const ndkProfile = convertNDKUserProfileToSubscriberProfile(subscriber.pubkey, user); updateSubscriberProfile(subscriber.pubkey, ndkProfile); } else { // NDK came up empty - fallback to backend data - console.log(`[PaidSubscribers] NDK profile empty for ${subscriber.pubkey}, falling back to backend data:`, { - name: subscriber.name, - picture: subscriber.picture, - about: subscriber.about - }); // Use the backend data as-is since NDK had no better information updateSubscriberProfile(subscriber.pubkey, { @@ -163,10 +141,8 @@ export const PaidSubscribers: React.FC = () => { }); } } catch (error) { - console.error(`[PaidSubscribers] Error fetching NDK profile for ${subscriber.pubkey}:`, error); // Error occurred - fallback to backend data - console.log(`[PaidSubscribers] NDK error for ${subscriber.pubkey}, using backend data`); updateSubscriberProfile(subscriber.pubkey, { ...subscriber, name: subscriber.name || 'Anonymous Subscriber', @@ -177,7 +153,6 @@ export const PaidSubscribers: React.FC = () => { }), ); - console.log('[PaidSubscribers] Hybrid profile fetch completed'); setLoadingProfiles(false); }; @@ -190,8 +165,6 @@ export const PaidSubscribers: React.FC = () => { setIsViewAllModalVisible(false); }; - console.log('[PaidSubscribers] Received subscribers:', subscribers); - console.log('[PaidSubscribers] Complete hook result:', hookResult); const sliderRef = useRef(null); const { isTablet: isTabletOrHigher } = useResponsive(); diff --git a/src/components/relay-dashboard/totalEarning/TotalEarning.tsx b/src/components/relay-dashboard/totalEarning/TotalEarning.tsx index 7a9f7605..8ddd0876 100644 --- a/src/components/relay-dashboard/totalEarning/TotalEarning.tsx +++ b/src/components/relay-dashboard/totalEarning/TotalEarning.tsx @@ -33,8 +33,6 @@ export const TotalEarning: React.FC = () => { const isIncreased = latestRate && previousRate ? latestRate > previousRate : false; const rateDifference = latestRate && previousRate ? ((latestRate - previousRate) / previousRate) * 100 : 0; - console.log(`latestRate: ${latestRate} previousRate: ${previousRate}`); - console.log(`Rate difference: ${rateDifference}`); if (isLoading) { return
{t('common.loading')}
; diff --git a/src/components/relay-dashboard/totalEarning/TotalEarningChart/TotalEarningChart.tsx b/src/components/relay-dashboard/totalEarning/TotalEarningChart/TotalEarningChart.tsx index 79c5e904..e3b263dd 100644 --- a/src/components/relay-dashboard/totalEarning/TotalEarningChart/TotalEarningChart.tsx +++ b/src/components/relay-dashboard/totalEarning/TotalEarningChart/TotalEarningChart.tsx @@ -20,8 +20,6 @@ export const TotalEarningChart: React.FC = ({ xAxisData, const theme = useAppSelector((state) => state.theme.theme); const { t } = useTranslation(); - console.log('xAxisData:', xAxisData); - console.log('earningData:', earningData); // Ensure all values are numbers and handle empty arrays const numericData = earningData.data.map(val => Number(val)).filter(val => !isNaN(val)); diff --git a/src/hooks/useBitcoinRates.ts b/src/hooks/useBitcoinRates.ts index 10cc0220..4cf5a60d 100644 --- a/src/hooks/useBitcoinRates.ts +++ b/src/hooks/useBitcoinRates.ts @@ -8,6 +8,11 @@ interface Earning { usd_value: number; } +// Global cache to prevent multiple simultaneous requests +let globalRatesCache: { data: Earning[]; timestamp: number } | null = null; +let globalPromise: Promise | null = null; +const CACHE_DURATION = 480000; // 8 minutes (backend updates every 10 minutes) + export const useBitcoinRates = () => { const [rates, setRates] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -16,47 +21,96 @@ export const useBitcoinRates = () => { const handleLogout = useHandleLogout(); useEffect(() => { - const fetchBitcoinRates = async () => { - setIsLoading(true); - setError(null); // Reset error state before fetching + const fetchBitcoinRates = async (retryCount = 0): Promise => { + // Check cache first + if (globalRatesCache && Date.now() - globalRatesCache.timestamp < CACHE_DURATION) { + const cacheAge = Math.round((Date.now() - globalRatesCache.timestamp) / 1000); + console.log(`[useBitcoinRates] Using cached data (age: ${cacheAge}s)`); + return globalRatesCache.data; + } - try { - const token = readToken(); // Read JWT from localStorage - if (!token) { - throw new Error('No authentication token found'); - } + // If there's already a request in progress, wait for it + if (globalPromise) { + console.log('[useBitcoinRates] Waiting for existing request to complete...'); + return globalPromise; + } - const response = await fetch(`${config.baseURL}/api/bitcoin-rates/last-30-days`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request - }, - }); - - if (!response.ok) { - if (response.status === 401) { - handleLogout(); // Log out the user if token is invalid or expired - throw new Error('Authentication failed. You have been logged out.'); + // Create new request + globalPromise = new Promise(async (resolve, reject) => { + try { + const token = readToken(); // Read JWT from localStorage + if (!token) { + throw new Error('No authentication token found'); + } + + console.log('[useBitcoinRates] Fetching bitcoin rates...'); + const response = await fetch(`${config.baseURL}/api/bitcoin-rates/last-30-days`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request + }, + }); + + console.log(`[useBitcoinRates] Response status: ${response.status}`); + + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Log out the user if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } + throw new Error(`Network response was not ok (status: ${response.status})`); } - throw new Error(`Network response was not ok (status: ${response.status})`); - } - const data = await response.json(); - setRates( - data.map((item: { Rate: number; TimestampHornets: string }) => ({ + const data = await response.json(); + console.log('[useBitcoinRates] Data received successfully'); + const processedData = data.map((item: { Rate: number; TimestampHornets: string }) => ({ date: new Date(item.TimestampHornets).getTime(), usd_value: item.Rate, - })), - ); + })); + + // Cache the result + globalRatesCache = { + data: processedData, + timestamp: Date.now(), + }; + + resolve(processedData); + } catch (err: any) { + // Retry on network errors but not on auth errors + if (retryCount < 2 && err.message.includes('network') && !err.message.includes('Authentication')) { + setTimeout(() => { + globalPromise = null; // Clear the promise so retry can start + fetchBitcoinRates(retryCount + 1).then(resolve).catch(reject); + }, 1000); + return; + } + + reject(err); + } finally { + globalPromise = null; // Clear the promise when done + } + }); + + return globalPromise; + }; + + const loadRates = async () => { + setIsLoading(true); + setError(null); + + try { + const data = await fetchBitcoinRates(); + setRates(data); } catch (err: any) { + console.error('[useBitcoinRates] Error fetching rates:', err); setError(err.message); } finally { setIsLoading(false); } }; - fetchBitcoinRates(); + loadRates(); }, [handleLogout]); return { rates, isLoading, error }; diff --git a/src/hooks/useChartData.ts b/src/hooks/useChartData.ts index 54ed7f8f..626b7389 100644 --- a/src/hooks/useChartData.ts +++ b/src/hooks/useChartData.ts @@ -46,28 +46,19 @@ const useChartData = () => { const data: FileCountResponse = await response.json(); - // Log the complete backend response for debugging - console.log('=== CHART DATA BACKEND RESPONSE ==='); - console.log('Full response:', JSON.stringify(data, null, 2)); - console.log('Response keys:', Object.keys(data)); - console.log('=============================='); // Process the data into chartDataItems using translated names // Handle dynamic media types while maintaining UI compatibility const newChartData: ChartDataItem[] = []; // Always include kinds first - console.log('Adding kinds data:', { value: data.kinds, name: t('categories.kinds') }); newChartData.push({ value: data.kinds, name: t('categories.kinds') }); // Destructure to separate kinds from media types const { kinds, ...mediaCounts } = data; - console.log('Media counts after destructuring:', mediaCounts); - console.log('Media count entries:', Object.entries(mediaCounts)); // Map dynamic media types to chart data with fallback translations Object.entries(mediaCounts).forEach(([mediaType, count]) => { - console.log(`Processing media type: ${mediaType} with count: ${count}`); let translationKey = ''; let fallbackName = ''; @@ -99,14 +90,9 @@ const useChartData = () => { // Use translation if available, otherwise use fallback const displayName = t(translationKey, { defaultValue: fallbackName }); const chartItem = { value: count, name: displayName }; - console.log(`Adding chart item:`, chartItem); newChartData.push(chartItem); }); - console.log('=== FINAL CHART DATA ==='); - console.log('Final chart data:', newChartData); - console.log('Chart data length:', newChartData.length); - console.log('========================'); setChartData(newChartData); } catch (error) {