switch more code over to applesauce

This commit is contained in:
hzrd149 2024-12-13 11:32:28 -06:00
parent b5ae92de20
commit 27fe231d9c
18 changed files with 319 additions and 510 deletions

View File

@ -16,7 +16,7 @@
"format": "prettier --ignore-path .prettierignore -w ."
},
"dependencies": {
"@cashu/cashu-ts": "2.0.0-rc1",
"@cashu/cashu-ts": "^2.1.0",
"@chakra-ui/anatomy": "^2.3.4",
"@chakra-ui/breakpoint-utils": "^2.0.8",
"@chakra-ui/icons": "^2.2.4",
@ -93,7 +93,7 @@
"react-error-boundary": "^4.1.2",
"react-force-graph-2d": "^1.26.1",
"react-force-graph-3d": "^1.25.1",
"react-hook-form": "^7.54.0",
"react-hook-form": "^7.54.1",
"react-markdown": "^9.0.1",
"react-mosaic-component": "^6.1.0",
"react-photo-album": "^2.4.1",
@ -109,7 +109,7 @@
"rx-nostr": "^3.4.1",
"rxjs": "^7.8.1",
"three": "^0.170.0",
"three-spritetext": "^1.9.2",
"three-spritetext": "^1.9.3",
"three-stdlib": "^2.35.2",
"tiny-lru": "^11.2.11",
"unified": "^11.0.5",

311
pnpm-lock.yaml generated
View File

@ -13,8 +13,8 @@ importers:
.:
dependencies:
'@cashu/cashu-ts':
specifier: 2.0.0-rc1
version: 2.0.0-rc1
specifier: ^2.1.0
version: 2.1.0
'@chakra-ui/anatomy':
specifier: ^2.3.4
version: 2.3.4
@ -92,28 +92,28 @@ importers:
version: 4.9.2(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
applesauce-channel:
specifier: next
version: 0.0.0-next-20241212192616(typescript@5.7.2)
version: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-content:
specifier: next
version: 0.0.0-next-20241212192616(typescript@5.7.2)
version: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-core:
specifier: next
version: 0.0.0-next-20241212192616(typescript@5.7.2)
version: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-factory:
specifier: next
version: 0.0.0-next-20241212192616(typescript@5.7.2)
version: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-lists:
specifier: next
version: 0.0.0-next-20241212192616(typescript@5.7.2)
version: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-net:
specifier: next
version: 0.0.0-next-20241212192616(typescript@5.7.2)
version: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-react:
specifier: next
version: 0.0.0-next-20241212192616(typescript@5.7.2)
version: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-signer:
specifier: next
version: 0.0.0-next-20241212192616(typescript@5.7.2)
version: 0.0.0-next-20241213171848(typescript@5.7.2)
bech32:
specifier: ^2.0.0
version: 2.0.0
@ -244,8 +244,8 @@ importers:
specifier: ^1.25.1
version: 1.25.1(react@18.3.1)
react-hook-form:
specifier: ^7.54.0
version: 7.54.0(react@18.3.1)
specifier: ^7.54.1
version: 7.54.1(react@18.3.1)
react-markdown:
specifier: ^9.0.1
version: 9.0.1(@types/react@18.3.16)(react@18.3.1)
@ -292,8 +292,8 @@ importers:
specifier: ^0.170.0
version: 0.170.0
three-spritetext:
specifier: ^1.9.2
version: 1.9.2(three@0.170.0)
specifier: ^1.9.3
version: 1.9.3(three@0.170.0)
three-stdlib:
specifier: ^2.35.2
version: 2.35.2(three@0.170.0)
@ -403,8 +403,8 @@ importers:
packages:
3d-force-graph@1.74.2:
resolution: {integrity: sha512-XBC1KB0vDvG616qfy11jE6pwp4SDQ4tBVof0GSHztjV1fVp7rhP5IGhwnbSjyp6FKAw+PjsWL/0tZuAcGFbXsA==}
3d-force-graph@1.74.3:
resolution: {integrity: sha512-srzOasC6RMXVXCivorDhDSqJCFKz3t5+AUIggVYP2pp3vPJ8YlRH/BLiIybtmvEYLQLv+hKeEuWFDHRaCeJrnQ==}
engines: {node: '>=12'}
'@ampproject/remapping@2.3.0':
@ -917,9 +917,15 @@ packages:
'@cashu/cashu-ts@2.0.0-rc1':
resolution: {integrity: sha512-39459l7x/fUMEgOsCdGLLl6rMekO4nbv+wEuavmyElh8hgN8t66wcb29AJvdFTb6K3lPACKF2rs/jAlPYrN7Ng==}
'@cashu/cashu-ts@2.1.0':
resolution: {integrity: sha512-qFfFz1dx9keJxumjk5FyTvI1j0Yp/P5LXDy0cGO4Xlp3WYKOI1nykNOTPd+bTY9vSkvIM+xuXRer9BtQxqHtwA==}
'@cashu/crypto@0.2.7':
resolution: {integrity: sha512-1aaDfUjiHNXoJqg8nW+341TLWV9W28DsVNXJUKcHL0yAmwLs5+56SSnb8LLDJzPamLVoYL0U0bda91klAzptig==}
'@cashu/crypto@0.3.4':
resolution: {integrity: sha512-mfv1Pj4iL1PXzUj9NKIJbmncCLMqYfnEDqh/OPxAX0nNBt6BOnVJJLjLWFlQeYxlnEfWABSNkrqPje1t5zcyhA==}
'@chakra-ui/anatomy@2.2.2':
resolution: {integrity: sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==}
@ -1905,29 +1911,29 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
applesauce-channel@0.0.0-next-20241212192616:
resolution: {integrity: sha512-N7S0XIw+aYymBF5pllEMwKJkDJft9qZAnGMJzdTA400KR391eFbnmE3sVOFXvIvU3XzKP89RrysOWjaPRAdASA==}
applesauce-channel@0.0.0-next-20241213171848:
resolution: {integrity: sha512-4D+FApKZ/YDd/E2Jr26IRzl47wr5um9N7+Sgd6FjeGLYjCKnSKkrO/TZ2nX6/bAWS6pwOgo6e492EEn2xkKAyA==}
applesauce-content@0.0.0-next-20241212192616:
resolution: {integrity: sha512-WUC5WJVDR9sMCJVp5c/3FKBDL6pCqGaBNS5UXeLY+sBXYs9pqLzxXrrILQKWZM+ghPfjCdfc6fDpjVxjyR4vcA==}
applesauce-content@0.0.0-next-20241213171848:
resolution: {integrity: sha512-BUtsmWGMUiw1RmY/xTsDUfLSS2u/rhODT2cDwnfc4ln7hy5CF3vibvn6amgO+7xWe/uK04TJGUmESbUIlKeWjg==}
applesauce-core@0.0.0-next-20241212192616:
resolution: {integrity: sha512-QQjsU2CU0OtQZfnb8VYbz8q/mKC2tLDsxSqHKsa1wIj5m5i7zRgPRBUj2FZrFJmIwlrG73G7uj1Ip53F93SIEw==}
applesauce-core@0.0.0-next-20241213171848:
resolution: {integrity: sha512-2VTdwkfPCUJcykbP+4np9CUoZI4aKUOTX1vjN/DRSaq6Rjd3jC5FP2MNMdRi9u8d1FlAoJyqOs2CCMjnsfkLOg==}
applesauce-factory@0.0.0-next-20241212192616:
resolution: {integrity: sha512-YhCaVGcVbqAzg7T2Cu6MG/iYRpfyC538GNZfG38wm6BD/5YCyygZ0LOmnNrIdhkD5mhxYwLxjlcQyY8n0meK9w==}
applesauce-factory@0.0.0-next-20241213171848:
resolution: {integrity: sha512-SBr57K1ZHyLFhvpn4xXDFZsyWG0X0k1FeTIf6vTcWwZ1FZ1e50eslt+zVLYfFhw55Tk8wT0chB4sToce25VbIw==}
applesauce-lists@0.0.0-next-20241212192616:
resolution: {integrity: sha512-Yd4clwLOKegWs0nJRHcjlJvJtxXYXNSWMQiqVHos510r/0sXpkCL9dmY9Y2QMLbG65Rcwvlu4OfT1w9+iXB7gA==}
applesauce-lists@0.0.0-next-20241213171848:
resolution: {integrity: sha512-GmziucyndF2+xhV2JBqQE1ai683wd6iV0mtatdLeM3h4DfAyElLT4CzFEONH7TzGYwDNnNaB11o7LmE78ww2fg==}
applesauce-net@0.0.0-next-20241212192616:
resolution: {integrity: sha512-LjGH+SCKTpGOYbdiybS0zJzdNpQMX0n1Zq3/S+JeQyfK0vPyeDPznQE0jvuIVT7SHyyCVf9cqSLIPm20qaxDXA==}
applesauce-net@0.0.0-next-20241213171848:
resolution: {integrity: sha512-xtu/QdSN1hv2oRobJ6NX3oewLKWcC/LlqwFjE/RwL+s0+n7U91vIfqIM6Cy2GnSuvEyL8kbdPdOTfdzbqu4/Pg==}
applesauce-react@0.0.0-next-20241212192616:
resolution: {integrity: sha512-tDtXc5e23Y5i11icZge0fTfpYIUXFtj0MQfzBgUfF4FT6zwUw69Bsw4roAVhVezWfWh4fD8dcIFjhNzGyi79AA==}
applesauce-react@0.0.0-next-20241213171848:
resolution: {integrity: sha512-EVUEC4x5/mtXPs1V66FFb6Ke2Pw7gqF8gyBnzcoH9Dvt8DIysJqWbLgtiha+wscgfit9W+eO/YxXxpNbpMjArA==}
applesauce-signer@0.0.0-next-20241212192616:
resolution: {integrity: sha512-kzOCY+TrlGWJnDHpsIzyB/lxFTJfMhQIr6u1+tGro/bqfVBrdX1fJF7cxv7Ro0Tnf/1RGosE4nxjz1Z+9ifqMg==}
applesauce-signer@0.0.0-next-20241213171848:
resolution: {integrity: sha512-op99Gq8z7NvbNVX8lO8GjNi9JIINDbHmwygM6qO2VmavAE0a75EFpIQWEaarr9XACD3V/54GnHiBlqFNTl0skA==}
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
@ -2029,8 +2035,8 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
browserslist@4.24.2:
resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==}
browserslist@4.24.3:
resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
@ -2372,8 +2378,8 @@ packages:
dnd-core@16.0.1:
resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==}
dnd-multi-backend@8.1.0:
resolution: {integrity: sha512-Favl89icddeqKI9MzMOW5AH5WHlJhdTmfnHm3QrbviJSp87N4oE7JPcqAzbu2QSis40senFQSoehuLX3H/xadA==}
dnd-multi-backend@8.1.1:
resolution: {integrity: sha512-+uksZeT2J+yD3pwRDixJQgZZ6XJN9uc1PR7ENobUnow6Vg6rawFi524aAOVacNJJvat2trtUo6RRs358U5lEdg==}
peerDependencies:
dnd-core: ^16.0.1
@ -2571,8 +2577,8 @@ packages:
for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
force-graph@1.47.0:
resolution: {integrity: sha512-2R6q3cLPuUzltGA+uYlmV/1cU4Fc/+jwU+0RiwLlUAh/geBmxfV3S1bpZxJ47UMMgD5Op+g0MlxNiJ62LAjQCQ==}
force-graph@1.47.1:
resolution: {integrity: sha512-NF0prpR8tNGq7oCE/aFImT/6/3wSk585bcp39UAj6SNSPjvKbX6ktCH6cZnjfsnPNx/DYj1rn+cvvjH814HCFA==}
engines: {node: '>=12'}
framer-motion@10.18.0:
@ -2699,8 +2705,8 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hast-util-to-html@9.0.3:
resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==}
hast-util-to-html@9.0.4:
resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==}
hast-util-to-jsx-runtime@2.3.2:
resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==}
@ -2781,8 +2787,8 @@ packages:
inline-style-prefixer@7.0.1:
resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==}
internal-slot@1.0.7:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
internmap@2.0.3:
@ -2819,8 +2825,8 @@ packages:
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
engines: {node: '>= 0.4'}
is-boolean-object@1.2.0:
resolution: {integrity: sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw==}
is-boolean-object@1.2.1:
resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==}
engines: {node: '>= 0.4'}
is-callable@1.2.7:
@ -2835,8 +2841,8 @@ packages:
resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
engines: {node: '>= 0.4'}
is-date-object@1.0.5:
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
is-date-object@1.1.0:
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
engines: {node: '>= 0.4'}
is-decimal@1.0.4:
@ -2922,8 +2928,8 @@ packages:
resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==}
engines: {node: '>=4'}
is-symbol@1.1.0:
resolution: {integrity: sha512-qS8KkNNXUZ/I+nX6QT8ZS1/Yx0A444yhzdTKxCzKkNjQ9sHErBxJnJAgh+f5YhusYECEcjo4XcyH87hn6+ks0A==}
is-symbol@1.1.1:
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
engines: {node: '>= 0.4'}
is-typed-array@1.1.13:
@ -2934,8 +2940,9 @@ packages:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'}
is-weakref@1.0.2:
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
is-weakref@1.1.0:
resolution: {integrity: sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==}
engines: {node: '>= 0.4'}
is-weakset@2.0.3:
resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==}
@ -3440,8 +3447,8 @@ packages:
parse-entities@2.0.0:
resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==}
parse-entities@4.0.1:
resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==}
parse-entities@4.0.2:
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
@ -3553,8 +3560,8 @@ packages:
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
rdndmb-html5-to-touch@8.1.0:
resolution: {integrity: sha512-Ep19LSFHwRhq+kuGGSqB8/+LW9uLyjd1xyQG/WkZ8nPoFgIH/3XtbPD7BxF9jMiwPCJepSK+YNUPej4I97F7XA==}
rdndmb-html5-to-touch@8.1.1:
resolution: {integrity: sha512-edyTFE+V9q8aHA2/gKM86F4tjVSdzfEVxFo0kvvTGqIUO2biZJC1TFpr6D+YNA7/oKXHejK5IllyhVrsB6HEsw==}
react-chartjs-2@5.2.0:
resolution: {integrity: sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==}
@ -3577,16 +3584,16 @@ packages:
react-dnd-html5-backend@16.0.1:
resolution: {integrity: sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==}
react-dnd-multi-backend@8.1.0:
resolution: {integrity: sha512-SOVXIDAyTp63weGWp6/nI8Km0PwzP/SAqYCz2ZZHp31pNgHtsnDhGkasBSIMG9HDzMNkVp3/X1IPKuDF2OvEwg==}
react-dnd-multi-backend@8.1.1:
resolution: {integrity: sha512-ctRbiLCYGxtXdXV6GbNML9XrvNFoR72AtrgWE3TZlpqwE8ZcaN+PJkARFVW9+99uU9z3BC+dx9FIfIgS9ParEw==}
peerDependencies:
dnd-core: ^16.0.1
react: ^16.14.0 || ^17.0.2 || ^18.0.0
react-dnd: ^16.0.1
react-dom: ^16.14.0 || ^17.0.2 || ^18.0.0
react-dnd-preview@8.1.0:
resolution: {integrity: sha512-bMSQb5KTCy58Mo2hDMf48ioNIiPyK87SajJigdd3WlGKXSV7EY/37qPNog1ox5an0iVFSunbQnv2huvZjy3CbA==}
react-dnd-preview@8.1.1:
resolution: {integrity: sha512-NXMxP+U2V5p8Fin3on7L6yfy1jCk2wekwcQw0Wtwhjo4K1lMYtID9tOBo7IKEgVmDdVCLXPQBC1wlGFwDa97Mg==}
peerDependencies:
react: ^16.14.0 || ^17.0.2 || ^18.0.0
react-dnd: ^16.0.1
@ -3643,8 +3650,8 @@ packages:
peerDependencies:
react: '*'
react-hook-form@7.54.0:
resolution: {integrity: sha512-PS05+UQy/IdSbJNojBypxAo9wllhHgGmyr8/dyGQcPoiMf3e7Dfb9PWYVRco55bLbxH9S+1yDDJeTdlYCSxO3A==}
react-hook-form@7.54.1:
resolution: {integrity: sha512-PUNzFwQeQ5oHiiTUO7GO/EJXGEtuun2Y1A59rLnZBBj+vNEOWt/3ERTiG1/zt7dVeJEM+4vDX/7XQ/qanuvPMg==}
engines: {node: '>=18.0.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
@ -3652,8 +3659,8 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-kapsule@2.5.2:
resolution: {integrity: sha512-ptK3/0BBJwS958e0Akn5VkQjeVDA2tM8NrJvlctIPI3OA5zp6Q6AD/MWwvKiMCH/tTI04h8JfGsOztDwJdQHqw==}
react-kapsule@2.5.6:
resolution: {integrity: sha512-aE4Nq7dDG8R/LdNmvOL6Azjr97I2E7ycFDJRkoHJSp9OQgTJDT3MHTJtJDrOTwzCl6sllYSqrtcndaCzizyAjQ==}
engines: {node: '>=12'}
peerDependencies:
react: '>=16.13.1'
@ -4114,20 +4121,20 @@ packages:
textarea-caret@3.0.2:
resolution: {integrity: sha512-gRzeti2YS4did7UJnPQ47wrjD+vp+CJIe9zbsu0bJ987d8QVLvLNG9757rqiQTIy4hGIeFauTTJt5Xkn51UkXg==}
three-forcegraph@1.42.5:
resolution: {integrity: sha512-39PVCtQ9Ayh0MxeE7/eiqKokCjRVE7seDKIRe47tTkImQDPMani6/Jzxawoy96JJyhdI8Kz3/gmlcfK6iB3EIQ==}
three-forcegraph@1.42.8:
resolution: {integrity: sha512-fhTQPC9lvYYUlsyVUUeJwijpUxHzbmurFSf5LEQI9W3Yp5kwI9oupS03g9HojpTUMd+bqiGvhVCnle5zmBMcAw==}
engines: {node: '>=12'}
peerDependencies:
three: '>=0.118.3'
three-render-objects@1.32.0:
resolution: {integrity: sha512-bTpyr8hp8PowFAi3pGjm/t0cMY7G8tecxgBTlOi9H/JNXxlAJBmvT6LF7oXeh7i2Q4Q6m9DNy+/Y72s+Varzww==}
three-render-objects@1.32.1:
resolution: {integrity: sha512-HcbVhMFwPxtxrrQYe+pD8HFZmx22lYuYZeHXcZlDdxqWyr5wAZgjD+vX23oALrmP3i1LW8udheXxbntwYmA9sw==}
engines: {node: '>=12'}
peerDependencies:
three: '>=0.168'
three-spritetext@1.9.2:
resolution: {integrity: sha512-muexs0ClLe1ppqtAbpwHIn6bcmGoGG4fRB5pnoWgKNmYRqURYfsPr6luMGSJ4MftE/rirZDuAgqUgwL/voGzyg==}
three-spritetext@1.9.3:
resolution: {integrity: sha512-p7iEQr7anABRUJNOH2logMf1aRuWb026IshUC4aJ8F+bbMTn5Z43/Jd7/W3VihE3ToCwrWMl6u6VwgFMTw0jvA==}
engines: {node: '>=12'}
peerDependencies:
three: '>=0.86.0'
@ -4407,8 +4414,8 @@ packages:
resolution: {integrity: sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng==}
engines: {node: '>= 0.4'}
which-builtin-type@1.2.0:
resolution: {integrity: sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==}
which-builtin-type@1.2.1:
resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
engines: {node: '>= 0.4'}
which-collection@1.0.2:
@ -4536,13 +4543,13 @@ packages:
snapshots:
3d-force-graph@1.74.2:
3d-force-graph@1.74.3:
dependencies:
accessor-fn: 1.5.1
kapsule: 1.16.0
three: 0.170.0
three-forcegraph: 1.42.5(three@0.170.0)
three-render-objects: 1.32.0(three@0.170.0)
three-forcegraph: 1.42.8(three@0.170.0)
three-render-objects: 1.32.1(three@0.170.0)
'@ampproject/remapping@2.3.0':
dependencies:
@ -4600,7 +4607,7 @@ snapshots:
dependencies:
'@babel/compat-data': 7.26.3
'@babel/helper-validator-option': 7.25.9
browserslist: 4.24.2
browserslist: 4.24.3
lru-cache: 5.1.1
semver: 6.3.1
@ -5213,6 +5220,14 @@ snapshots:
'@scure/bip32': 1.6.0
buffer: 6.0.3
'@cashu/cashu-ts@2.1.0':
dependencies:
'@cashu/crypto': 0.3.4
'@noble/curves': 1.7.0
'@noble/hashes': 1.6.1
'@scure/bip32': 1.6.0
buffer: 6.0.3
'@cashu/crypto@0.2.7':
dependencies:
'@noble/curves': 1.7.0
@ -5221,6 +5236,14 @@ snapshots:
'@scure/bip39': 1.5.0
buffer: 6.0.3
'@cashu/crypto@0.3.4':
dependencies:
'@noble/curves': 1.7.0
'@noble/hashes': 1.6.1
'@scure/bip32': 1.6.0
'@scure/bip39': 1.5.0
buffer: 6.0.3
'@chakra-ui/anatomy@2.2.2': {}
'@chakra-ui/anatomy@2.3.4': {}
@ -6077,7 +6100,7 @@ snapshots:
'@shikijs/types': 1.24.2
'@shikijs/vscode-textmate': 9.3.1
'@types/hast': 3.0.4
hast-util-to-html: 9.0.3
hast-util-to-html: 9.0.4
'@shikijs/engine-javascript@1.24.2':
dependencies:
@ -6368,23 +6391,23 @@ snapshots:
dependencies:
color-convert: 2.0.1
applesauce-channel@0.0.0-next-20241212192616(typescript@5.7.2):
applesauce-channel@0.0.0-next-20241213171848(typescript@5.7.2):
dependencies:
applesauce-core: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-factory: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-factory: 0.0.0-next-20241213171848(typescript@5.7.2)
nostr-tools: 2.10.4(typescript@5.7.2)
rxjs: 7.8.1
transitivePeerDependencies:
- supports-color
- typescript
applesauce-content@0.0.0-next-20241212192616(typescript@5.7.2):
applesauce-content@0.0.0-next-20241213171848(typescript@5.7.2):
dependencies:
'@cashu/cashu-ts': 2.0.0-rc1
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
'@types/unist': 3.0.3
applesauce-core: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241213171848(typescript@5.7.2)
mdast-util-find-and-replace: 3.0.1
nostr-tools: 2.10.4(typescript@5.7.2)
remark: 15.0.1
@ -6395,7 +6418,7 @@ snapshots:
- supports-color
- typescript
applesauce-core@0.0.0-next-20241212192616(typescript@5.7.2):
applesauce-core@0.0.0-next-20241213171848(typescript@5.7.2):
dependencies:
'@scure/base': 1.2.1
debug: 4.4.0
@ -6409,23 +6432,23 @@ snapshots:
- supports-color
- typescript
applesauce-factory@0.0.0-next-20241212192616(typescript@5.7.2):
applesauce-factory@0.0.0-next-20241213171848(typescript@5.7.2):
dependencies:
applesauce-content: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-content: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241213171848(typescript@5.7.2)
nostr-tools: 2.10.4(typescript@5.7.2)
transitivePeerDependencies:
- supports-color
- typescript
applesauce-lists@0.0.0-next-20241212192616(typescript@5.7.2):
applesauce-lists@0.0.0-next-20241213171848(typescript@5.7.2):
dependencies:
'@noble/hashes': 1.6.1
'@noble/secp256k1': 1.7.1
'@scure/base': 1.2.1
'@types/dom-serial': 1.0.6
applesauce-core: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-factory: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-factory: 0.0.0-next-20241213171848(typescript@5.7.2)
debug: 4.4.0
nostr-tools: 2.10.4(typescript@5.7.2)
rxjs: 7.8.1
@ -6433,9 +6456,9 @@ snapshots:
- supports-color
- typescript
applesauce-net@0.0.0-next-20241212192616(typescript@5.7.2):
applesauce-net@0.0.0-next-20241213171848(typescript@5.7.2):
dependencies:
applesauce-core: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241213171848(typescript@5.7.2)
nanoid: 5.0.9
nostr-tools: 2.10.4(typescript@5.7.2)
rxjs: 7.8.1
@ -6443,11 +6466,11 @@ snapshots:
- supports-color
- typescript
applesauce-react@0.0.0-next-20241212192616(typescript@5.7.2):
applesauce-react@0.0.0-next-20241213171848(typescript@5.7.2):
dependencies:
applesauce-content: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-factory: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-content: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-factory: 0.0.0-next-20241213171848(typescript@5.7.2)
nostr-tools: 2.10.4(typescript@5.7.2)
react: 18.3.1
rxjs: 7.8.1
@ -6455,14 +6478,14 @@ snapshots:
- supports-color
- typescript
applesauce-signer@0.0.0-next-20241212192616(typescript@5.7.2):
applesauce-signer@0.0.0-next-20241213171848(typescript@5.7.2):
dependencies:
'@noble/hashes': 1.6.1
'@noble/secp256k1': 1.7.1
'@scure/base': 1.2.1
'@types/dom-serial': 1.0.6
applesauce-core: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-net: 0.0.0-next-20241212192616(typescript@5.7.2)
applesauce-core: 0.0.0-next-20241213171848(typescript@5.7.2)
applesauce-net: 0.0.0-next-20241213171848(typescript@5.7.2)
debug: 4.4.0
nanoid: 5.0.9
nostr-tools: 2.10.4(typescript@5.7.2)
@ -6594,12 +6617,12 @@ snapshots:
dependencies:
fill-range: 7.1.1
browserslist@4.24.2:
browserslist@4.24.3:
dependencies:
caniuse-lite: 1.0.30001688
electron-to-chromium: 1.5.73
node-releases: 2.0.19
update-browserslist-db: 1.1.1(browserslist@4.24.2)
update-browserslist-db: 1.1.1(browserslist@4.24.3)
buffer-from@1.1.2: {}
@ -6771,7 +6794,7 @@ snapshots:
core-js-compat@3.39.0:
dependencies:
browserslist: 4.24.2
browserslist: 4.24.3
cosmiconfig@7.1.0:
dependencies:
@ -6975,7 +6998,7 @@ snapshots:
'@react-dnd/invariant': 4.0.2
redux: 4.2.1
dnd-multi-backend@8.1.0(dnd-core@16.0.1):
dnd-multi-backend@8.1.1(dnd-core@16.0.1):
dependencies:
dnd-core: 16.0.1
@ -7072,7 +7095,7 @@ snapshots:
has-proto: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
internal-slot: 1.0.7
internal-slot: 1.1.0
is-array-buffer: 3.0.4
is-callable: 1.2.7
is-data-view: 1.0.2
@ -7081,7 +7104,7 @@ snapshots:
is-shared-array-buffer: 1.0.3
is-string: 1.1.0
is-typed-array: 1.1.13
is-weakref: 1.0.2
is-weakref: 1.1.0
object-inspect: 1.13.3
object-keys: 1.1.1
object.assign: 4.1.5
@ -7115,8 +7138,8 @@ snapshots:
es-to-primitive@1.3.0:
dependencies:
is-callable: 1.2.7
is-date-object: 1.0.5
is-symbol: 1.1.0
is-date-object: 1.1.0
is-symbol: 1.1.1
esbuild@0.21.5:
optionalDependencies:
@ -7225,7 +7248,7 @@ snapshots:
dependencies:
is-callable: 1.2.7
force-graph@1.47.0:
force-graph@1.47.1:
dependencies:
'@tweenjs/tween.js': 25.0.0
accessor-fn: 1.5.1
@ -7380,7 +7403,7 @@ snapshots:
dependencies:
function-bind: 1.1.2
hast-util-to-html@9.0.3:
hast-util-to-html@9.0.4:
dependencies:
'@types/hast': 3.0.4
'@types/unist': 3.0.3
@ -7483,7 +7506,7 @@ snapshots:
dependencies:
css-in-js-utils: 3.1.0
internal-slot@1.0.7:
internal-slot@1.1.0:
dependencies:
es-errors: 1.3.0
hasown: 2.0.2
@ -7524,9 +7547,9 @@ snapshots:
dependencies:
has-bigints: 1.0.2
is-boolean-object@1.2.0:
is-boolean-object@1.2.1:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.2
has-tostringtag: 1.0.2
is-callable@1.2.7: {}
@ -7541,8 +7564,9 @@ snapshots:
get-intrinsic: 1.2.6
is-typed-array: 1.1.13
is-date-object@1.0.5:
is-date-object@1.1.0:
dependencies:
call-bound: 1.0.2
has-tostringtag: 1.0.2
is-decimal@1.0.4: {}
@ -7610,9 +7634,9 @@ snapshots:
dependencies:
better-path-resolve: 1.0.0
is-symbol@1.1.0:
is-symbol@1.1.1:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.2
has-symbols: 1.1.0
safe-regex-test: 1.1.0
@ -7622,9 +7646,9 @@ snapshots:
is-weakmap@2.0.2: {}
is-weakref@1.0.2:
is-weakref@1.1.0:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.2
is-weakset@2.0.3:
dependencies:
@ -7876,7 +7900,7 @@ snapshots:
devlop: 1.1.0
mdast-util-from-markdown: 2.0.2
mdast-util-to-markdown: 2.1.2
parse-entities: 4.0.1
parse-entities: 4.0.2
stringify-entities: 4.0.4
unist-util-stringify-position: 4.0.0
vfile-message: 4.0.2
@ -8327,10 +8351,9 @@ snapshots:
is-decimal: 1.0.4
is-hexadecimal: 1.0.4
parse-entities@4.0.1:
parse-entities@4.0.2:
dependencies:
'@types/unist': 2.0.11
character-entities: 2.0.2
character-entities-legacy: 3.0.0
character-reference-invalid: 2.0.1
decode-named-character-reference: 1.0.2
@ -8425,9 +8448,9 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
rdndmb-html5-to-touch@8.1.0(dnd-core@16.0.1):
rdndmb-html5-to-touch@8.1.1(dnd-core@16.0.1):
dependencies:
dnd-multi-backend: 8.1.0(dnd-core@16.0.1)
dnd-multi-backend: 8.1.1(dnd-core@16.0.1)
react-dnd-html5-backend: 16.0.1
react-dnd-touch-backend: 16.0.1
transitivePeerDependencies:
@ -8459,16 +8482,16 @@ snapshots:
dependencies:
dnd-core: 16.0.1
react-dnd-multi-backend@8.1.0(dnd-core@16.0.1)(react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
react-dnd-multi-backend@8.1.1(dnd-core@16.0.1)(react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
dnd-core: 16.0.1
dnd-multi-backend: 8.1.0(dnd-core@16.0.1)
dnd-multi-backend: 8.1.1(dnd-core@16.0.1)
react: 18.3.1
react-dnd: 16.0.1(@types/react@18.3.16)(react@18.3.1)
react-dnd-preview: 8.1.0(react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1))(react@18.3.1)
react-dnd-preview: 8.1.1(react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1))(react@18.3.1)
react-dom: 18.3.1(react@18.3.1)
react-dnd-preview@8.1.0(react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1))(react@18.3.1):
react-dnd-preview@8.1.1(react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
react-dnd: 16.0.1(@types/react@18.3.16)(react@18.3.1)
@ -8516,25 +8539,25 @@ snapshots:
react-force-graph-2d@1.26.1(react@18.3.1):
dependencies:
force-graph: 1.47.0
force-graph: 1.47.1
prop-types: 15.8.1
react: 18.3.1
react-kapsule: 2.5.2(react@18.3.1)
react-kapsule: 2.5.6(react@18.3.1)
react-force-graph-3d@1.25.1(react@18.3.1):
dependencies:
3d-force-graph: 1.74.2
3d-force-graph: 1.74.3
prop-types: 15.8.1
react: 18.3.1
react-kapsule: 2.5.2(react@18.3.1)
react-kapsule: 2.5.6(react@18.3.1)
react-hook-form@7.54.0(react@18.3.1):
react-hook-form@7.54.1(react@18.3.1):
dependencies:
react: 18.3.1
react-is@16.13.1: {}
react-kapsule@2.5.2(react@18.3.1):
react-kapsule@2.5.6(react@18.3.1):
dependencies:
jerrypick: 1.1.1
react: 18.3.1
@ -8562,11 +8585,11 @@ snapshots:
immutability-helper: 3.1.1
lodash: 4.17.21
prop-types: 15.8.1
rdndmb-html5-to-touch: 8.1.0(dnd-core@16.0.1)
rdndmb-html5-to-touch: 8.1.1(dnd-core@16.0.1)
react: 18.3.1
react-dnd: 16.0.1(@types/react@18.3.16)(react@18.3.1)
react-dnd-html5-backend: 16.0.1
react-dnd-multi-backend: 8.1.0(dnd-core@16.0.1)(react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-dnd-multi-backend: 8.1.1(dnd-core@16.0.1)(react-dnd@16.0.1(@types/react@18.3.16)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-dnd-touch-backend: 16.0.1
uuid: 9.0.1
transitivePeerDependencies:
@ -8708,7 +8731,7 @@ snapshots:
es-errors: 1.3.0
get-intrinsic: 1.2.6
gopd: 1.2.0
which-builtin-type: 1.2.0
which-builtin-type: 1.2.1
regenerate-unicode-properties@10.2.0:
dependencies:
@ -9029,7 +9052,7 @@ snapshots:
get-intrinsic: 1.2.6
gopd: 1.2.0
has-symbols: 1.1.0
internal-slot: 1.0.7
internal-slot: 1.1.0
regexp.prototype.flags: 1.5.3
set-function-name: 2.0.2
side-channel: 1.1.0
@ -9112,7 +9135,7 @@ snapshots:
textarea-caret@3.0.2: {}
three-forcegraph@1.42.5(three@0.170.0):
three-forcegraph@1.42.8(three@0.170.0):
dependencies:
accessor-fn: 1.5.1
d3-array: 3.2.4
@ -9126,7 +9149,7 @@ snapshots:
three: 0.170.0
tinycolor2: 1.6.0
three-render-objects@1.32.0(three@0.170.0):
three-render-objects@1.32.1(three@0.170.0):
dependencies:
'@tweenjs/tween.js': 25.0.0
accessor-fn: 1.5.1
@ -9134,7 +9157,7 @@ snapshots:
polished: 4.3.1
three: 0.170.0
three-spritetext@1.9.2(three@0.170.0):
three-spritetext@1.9.3(three@0.170.0):
dependencies:
three: 0.170.0
@ -9298,9 +9321,9 @@ snapshots:
upath@1.2.0: {}
update-browserslist-db@1.1.1(browserslist@4.24.2):
update-browserslist-db@1.1.1(browserslist@4.24.3):
dependencies:
browserslist: 4.24.2
browserslist: 4.24.3
escalade: 3.2.0
picocolors: 1.1.1
@ -9389,22 +9412,22 @@ snapshots:
which-boxed-primitive@1.1.0:
dependencies:
is-bigint: 1.1.0
is-boolean-object: 1.2.0
is-boolean-object: 1.2.1
is-number-object: 1.1.0
is-string: 1.1.0
is-symbol: 1.1.0
is-symbol: 1.1.1
which-builtin-type@1.2.0:
which-builtin-type@1.2.1:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.2
function.prototype.name: 1.1.6
has-tostringtag: 1.0.2
is-async-function: 2.0.0
is-date-object: 1.0.5
is-date-object: 1.1.0
is-finalizationregistry: 1.1.0
is-generator-function: 1.0.10
is-regex: 1.2.1
is-weakref: 1.0.2
is-weakref: 1.1.0
isarray: 2.0.5
which-boxed-primitive: 1.1.0
which-collection: 1.0.2

View File

@ -3,7 +3,6 @@ import { Box, Button, ButtonGroup, Card, CardProps, Heading, IconButton, Link, S
import { Token, getEncodedToken, CheckStateEnum } from "@cashu/cashu-ts";
import { CopyIconButton } from "../copy-icon-button";
import useUserProfile from "../../hooks/use-user-profile";
import useCurrentAccount from "../../hooks/use-current-account";
import { ECashIcon, WalletIcon } from "../icons";
import CurrencyDollar from "../icons/currency-dollar";
@ -14,28 +13,11 @@ import CurrencyPound from "../icons/currency-pound";
import CurrencyBitcoin from "../icons/currency-bitcoin";
import { getMintWallet } from "../../services/cashu-mints";
function RedeemButton({ token }: { token: string }) {
const account = useCurrentAccount()!;
const metadata = useUserProfile(account.pubkey);
const lnurl = metadata?.lud16 ?? "";
const url = `https://redeem.cashu.me?token=${encodeURIComponent(token)}&lightning=${encodeURIComponent(
lnurl,
)}&autopay=yes`;
return (
<Button as={Link} href={url} isExternal colorScheme="primary">
Redeem
</Button>
);
}
export default function InlineCachuCard({
token,
encoded,
...props
}: Omit<CardProps, "children"> & { token: Token; encoded?: string }) {
const account = useCurrentAccount();
encoded = encoded || getEncodedToken(token);
const { value: spendable, loading } = useAsync(async () => {
if (!token) return;
@ -84,7 +66,7 @@ export default function InlineCachuCard({
}
return (
<Card p="2" flexDirection="column" borderColor="green.500" gap="2" {...props}>
<Card p="2" flexDirection="column" borderColor="green.500" gap="2" maxW="md" variant="outline" {...props}>
<Box>
<UnitIcon boxSize={10} color={unitColor} float="left" mr="2" mb="1" />
<ButtonGroup float="right" size="sm">
@ -96,7 +78,6 @@ export default function InlineCachuCard({
aria-label="Open Wallet"
href={`cashu://` + encoded}
/>
{account && <RedeemButton token={encoded} />}
</ButtonGroup>
<Heading size="md" textDecoration={spendable === false ? "line-through" : undefined}>
{denomination} {spendable === false ? " (Spent)" : loading ? <Spinner size="xs" /> : undefined}
@ -105,7 +86,6 @@ export default function InlineCachuCard({
{token.unit && <Text fontSize="xs">Unit: {token.unit}</Text>}
</Box>
{token.memo && <Box>{token.memo}</Box>}
{loading && <Spinner />}
</Card>
);
}

View File

@ -3,7 +3,7 @@ import { Alert, AlertDescription, AlertIcon, AlertProps, AlertTitle, Button, Spa
import { useExpand } from "../providers/local/expanded";
import { useBreakpointValue } from "../providers/global/breakpoint-provider";
export default function SensitiveContentWarning({ description }: { description?: string } & AlertProps) {
export default function ContentWarning({ description }: { description?: string } & AlertProps) {
const expand = useExpand();
const smallScreen = useBreakpointValue({ base: true, md: false });

View File

@ -12,9 +12,9 @@ import dayjs from "dayjs";
import { kinds } from "nostr-tools";
import { getValue } from "applesauce-core/observable";
import { getInboxes, getInvoice, getOutboxes, safeRelayUrls } from "applesauce-core/helpers";
import { getZapSplits } from "applesauce-core/helpers/zap";
import { DraftNostrEvent, NostrEvent, isDTag } from "../../types/nostr-event";
import { getZapSplits } from "../../helpers/nostr/zaps";
import { unique } from "../../helpers/array";
import relayScoreboardService from "../../services/relay-scoreboard";
import { getEventCoordinate, isReplaceable } from "../../helpers/nostr/event";
@ -122,7 +122,7 @@ async function getPayRequestsForEvent(
fallbackPubkey?: string,
additionalRelays?: Iterable<string>,
) {
const splits = getZapSplits(event, fallbackPubkey);
const splits = getZapSplits(event) ?? (fallbackPubkey ? [{ pubkey: fallbackPubkey, percent: 1, weight: 1 }] : []);
const draftZapRequests: PayRequest[] = [];
for (const { pubkey, percent } of splits) {

View File

@ -1,11 +1,11 @@
import { Box, Button, Flex, Input, Text } from "@chakra-ui/react";
import { getZapSplits } from "applesauce-core/helpers";
import { useForm } from "react-hook-form";
import { NostrEvent } from "../../types/nostr-event";
import { humanReadableSats } from "../../helpers/lightning";
import { LightningIcon } from "../icons";
import useUserLNURLMetadata from "../../hooks/use-user-lnurl-metadata";
import { getZapSplits } from "../../helpers/nostr/zaps";
import { EmbedEvent, EmbedProps } from "../embed-event";
import useAppSettings from "../../hooks/use-app-settings";
import CustomZapAmountOptions from "./zap-options";
@ -71,7 +71,7 @@ export default function InputStep({
},
});
const splits = event ? getZapSplits(event, pubkey) : [];
const splits = event ? (getZapSplits(event) ?? []) : [];
const { metadata: lnurlMetadata } = useUserLNURLMetadata(pubkey);
const canZap = lnurlMetadata?.allowsNostr && lnurlMetadata?.nostrPubkey;

View File

@ -1,19 +1,20 @@
import { NostrEvent } from "nostr-tools";
import { getContentWarning } from "applesauce-core/helpers";
import { TextNoteContents } from "./text-note-contents";
import { useExpand } from "../../../providers/local/expanded";
import SensitiveContentWarning from "../../sensitive-content-warning";
import ContentWarning from "../../content-warning";
import useAppSettings from "../../../hooks/use-app-settings";
export default function NoteContentWithWarning({ event }: { event: NostrEvent }) {
const expand = useExpand();
const settings = useAppSettings();
const contentWarningTag = event.tags.find((t) => t[0] === "content-warning");
const showContentWarning = settings.showContentWarning && contentWarningTag && !expand?.expanded;
const warning = getContentWarning(event);
const showContentWarning = settings.showContentWarning && !!warning && !expand?.expanded;
return showContentWarning ? (
<SensitiveContentWarning description={contentWarningTag?.[1]} />
<ContentWarning description={typeof warning === "string" ? warning : undefined} />
) : (
<TextNoteContents px="2" event={event} />
);

View File

@ -12,8 +12,6 @@ import {
Input,
Switch,
ModalProps,
VisuallyHiddenInput,
IconButton,
FormLabel,
FormControl,
FormHelperText,
@ -28,30 +26,20 @@ import {
ButtonGroup,
Text,
} from "@chakra-ui/react";
import dayjs from "dayjs";
import { useForm } from "react-hook-form";
import { kinds, UnsignedEvent } from "nostr-tools";
import { useThrottle } from "react-use";
import { EventTemplate, UnsignedEvent } from "nostr-tools";
import { useAsync, useThrottle } from "react-use";
import { useEventFactory, useObservable } from "applesauce-react/hooks";
import { Emoji, ZapSplit } from "applesauce-core/helpers";
import { ChevronDownIcon, ChevronUpIcon, UploadImageIcon } from "../icons";
import { ChevronDownIcon, ChevronUpIcon } from "../icons";
import PublishAction from "../../classes/nostr-publish-action";
import { PublishDetails } from "../../views/task-manager/publish-log/publish-details";
import { TrustProvider } from "../../providers/local/trust-provider";
import {
correctContentMentions,
createEmojiTags,
ensureNotifyPubkeys,
finalizeNote,
getPubkeysMentionedInContent,
setZapSplit,
} from "../../helpers/nostr/post";
import { UserAvatarStack } from "../compact-user-stack";
import MagicTextArea, { RefType } from "../magic-textarea";
import { useContextEmojis } from "../../providers/global/emoji-provider";
import CommunitySelect from "./community-select";
import ZapSplitCreator, { fillRemainingPercent } from "./zap-split-creator";
import { EventSplit } from "../../helpers/nostr/zaps";
import ZapSplitCreator from "./zap-split-creator";
import useCurrentAccount from "../../hooks/use-current-account";
import useCacheForm from "../../hooks/use-cache-form";
import useTextAreaUploadFile, { useTextAreaInsertTextWithForm } from "../../hooks/use-textarea-upload-file";
@ -64,7 +52,6 @@ import localSettings from "../../services/local-settings";
import useLocalStorageDisclosure from "../../hooks/use-localstorage-disclosure";
import InsertGifButton from "../gif/insert-gif-button";
import InsertImageButton from "./insert-image-button";
import { unixNow } from "applesauce-core/helpers";
type FormValues = {
subject: string;
@ -72,7 +59,7 @@ type FormValues = {
nsfw: boolean;
nsfwReason: string;
community: string;
split: EventSplit;
split: Omit<ZapSplit, "percent" | "relay">[];
difficulty: number;
};
@ -111,7 +98,7 @@ export default function PostModal({
nsfw: false,
nsfwReason: "",
community: initCommunity,
split: [] as EventSplit,
split: [] as Omit<ZapSplit, "percent" | "relay">[],
difficulty: noteDifficulty || 0,
},
mode: "all",
@ -128,47 +115,34 @@ export default function PostModal({
// cache form to localStorage
useCacheForm<FormValues>(cacheFormKey, getValues, reset, formState);
const updateDraft = useCallback(
async (values = getValues()) => {
const { content, nsfw, nsfwReason, community, split, subject } = values;
const getDraft = async (values = getValues()) => {
// build draft using factory
let draft = await factory.note(values.content, {
emojis: emojis.filter((e) => !!e.url) as Emoji[],
contentWarning: values.nsfw ? values.nsfwReason || values.nsfw : false,
splits: values.split,
});
let draft = finalizeNote({
kind: kinds.ShortTextNote,
content,
tags: [],
created_at: unixNow(),
});
// TODO: remove when NIP-72 communities are removed
if (values.community) draft.tags.push(["a", values.community]);
if (values.subject) draft.tags.push(["subject", values.subject]);
if (nsfw) draft.tags.push(nsfwReason ? ["content-warning", nsfwReason] : ["content-warning"]);
if (community) draft.tags.push(["a", community]);
if (subject) draft.tags.push(["subject", subject]);
const unsigned = await finalizeDraft(draft);
const contentMentions = getPubkeysMentionedInContent(draft.content);
draft = createEmojiTags(draft, emojis);
draft = ensureNotifyPubkeys(draft, contentMentions);
if (split.length > 0) {
draft = setZapSplit(draft, fillRemainingPercent(split, account.pubkey));
}
const unsigned = await finalizeDraft(draft);
setDraft(unsigned);
return unsigned;
},
[getValues, emojis, finalizeDraft, setDraft, factory],
);
setDraft(unsigned);
return unsigned;
};
// throttle update the draft every 500ms
const throttleValues = useThrottle(JSON.stringify(getValues()), 500);
useEffect(() => {
updateDraft(getValues());
}, [throttleValues]);
const throttleValues = useThrottle(getValues(), 500);
const { value: preview } = useAsync(() => getDraft(), [throttleValues]);
const textAreaRef = useRef<RefType | null>(null);
const insertText = useTextAreaInsertTextWithForm(textAreaRef, getValues, setValue);
const { onPaste } = useTextAreaUploadFile(insertText);
const publishPost = async (unsigned?: UnsignedEvent) => {
unsigned = unsigned || draft || (await updateDraft());
unsigned = unsigned || draft || (await getDraft());
const pub = await publish("Post", unsigned);
if (pub) setPublishAction(pub);
@ -177,13 +151,12 @@ export default function PostModal({
if (values.difficulty > 0) {
setMiningTarget(values.difficulty);
} else {
const unsigned = await updateDraft(values);
const unsigned = await getDraft(values);
publishPost(unsigned);
}
});
const canSubmit = getValues().content.length > 0;
const mentions = getPubkeysMentionedInContent(correctContentMentions(getValues().content));
const renderBody = () => {
if (publishAction) {
@ -229,13 +202,13 @@ export default function PostModal({
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") submit();
}}
/>
{draft && draft.content.length > 0 && (
{preview && preview.content.length > 0 && (
<Box>
<Heading size="sm">Preview:</Heading>
<Box borderWidth={1} borderRadius="md" p="2">
<ErrorBoundary>
<TrustProvider trust>
<TextNoteContents event={draft} />
<TextNoteContents event={preview} />
</TrustProvider>
</ErrorBoundary>
</Box>
@ -253,7 +226,6 @@ export default function PostModal({
More Options
</Button>
</Flex>
{mentions.length > 0 && <UserAvatarStack label="Mentions" pubkeys={mentions} />}
<Button onClick={onClose} variant="ghost">
Cancel
</Button>
@ -305,8 +277,8 @@ export default function PostModal({
</Flex>
<Flex direction="column" gap="2" flex={1}>
<ZapSplitCreator
split={getValues().split}
onChange={(s) => setValue("split", s, { shouldDirty: true })}
splits={getValues().split}
onChange={(splits) => setValue("split", splits, { shouldDirty: true })}
authorPubkey={account?.pubkey}
/>
</Flex>

View File

@ -7,27 +7,18 @@ import {
NumberInput,
NumberInputField,
NumberInputStepper,
Text,
useToast,
} from "@chakra-ui/react";
import { CloseIcon } from "@chakra-ui/icons";
import { useForm } from "react-hook-form";
import { EventSplit } from "../../helpers/nostr/zaps";
import { AddIcon } from "../icons";
import { normalizeToHexPubkey } from "../../helpers/nip19";
import UserAvatar from "../user/user-avatar";
import UserLink from "../user/user-link";
import UserAutocomplete from "../user-autocomplete";
function getRemainingPercent(split: EventSplit) {
return Math.round((1 - split.reduce((v, p) => v + p.percent, 0)) * 100) / 100;
}
export function fillRemainingPercent(split: EventSplit, pubkey: string) {
const remainingPercent = getRemainingPercent(split);
if (remainingPercent === 0) return split;
return split.concat({ pubkey, percent: remainingPercent });
}
type Split = { pubkey: string; weight: number };
function validateNpub(input: string) {
const pubkey = normalizeToHexPubkey(input);
@ -36,29 +27,20 @@ function validateNpub(input: string) {
}
}
function AddUserForm({
onSubmit,
remainingPercent,
}: {
onSubmit: (values: { pubkey: string; percent: number }) => void;
remainingPercent: number;
}) {
function AddUserForm({ onSubmit }: { onSubmit: (values: Split) => void }) {
const toast = useToast();
const { register, handleSubmit, getValues, setValue, reset, watch } = useForm({
const { register, handleSubmit, reset } = useForm({
defaultValues: {
pubkey: "",
percent: Math.min(remainingPercent, 50),
},
mode: "all",
});
watch("percent");
const submit = handleSubmit((values) => {
try {
const pubkey = normalizeToHexPubkey(values.pubkey);
if (!pubkey) throw new Error("Invalid npub");
const percent = values.percent / 100;
onSubmit({ pubkey, percent });
onSubmit({ pubkey, weight: 1 });
reset();
} catch (e) {
if (e instanceof Error) toast({ description: e.message, status: "error" });
@ -68,88 +50,81 @@ function AddUserForm({
return (
<Flex as="form" gap="2" onSubmit={submit}>
<UserAutocomplete {...register("pubkey", { required: true, validate: validateNpub })} />
<NumberInput
step={1}
min={1}
max={remainingPercent}
value={getValues().percent || 0}
onChange={(_, n) => setValue("percent", n, { shouldDirty: true })}
>
<NumberInputField size={8} />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<IconButton icon={<AddIcon />} aria-label="Add" type="submit" />
<IconButton icon={<AddIcon boxSize={5} />} aria-label="Add" type="submit" />
</Flex>
);
}
function UserCard({
pubkey,
percent,
showRemove = true,
weight,
onRemove,
onChange,
}: {
pubkey: string;
percent: number;
showRemove?: boolean;
weight: number;
onRemove?: () => void;
onChange?: (split: Split) => void;
}) {
return (
<Flex gap="2" overflow="hidden" alignItems="center">
<UserAvatar pubkey={pubkey} size="sm" />
<UserLink pubkey={pubkey} fontWeight="bold" isTruncated />
<Text fontWeight="bold" fontSize="lg" ml="auto">
{Math.round(percent * 10000) / 100}%
</Text>
{showRemove && (
<IconButton
variant="ghost"
icon={<CloseIcon />}
aria-label="Remove from split"
title="Remove"
onClick={onRemove}
/>
)}
<NumberInput
step={1}
min={1}
value={weight}
onChange={(_, n) => onChange?.({ pubkey, weight: Number.isFinite(n) ? n : 1 })}
ml="auto"
>
<NumberInputField size={2} />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<IconButton
variant="ghost"
icon={<CloseIcon />}
aria-label="Remove from split"
title="Remove"
onClick={onRemove}
/>
</Flex>
);
}
export default function ZapSplitCreator({
split,
splits,
onChange,
authorPubkey,
}: {
split: EventSplit;
onChange: (split: EventSplit) => void;
splits: Split[];
onChange: (split: Split[]) => void;
authorPubkey?: string;
}) {
const remainingPercent = getRemainingPercent(split);
const addUser = ({ pubkey, percent }: { pubkey: string; percent: number }) => {
if (percent > remainingPercent) throw new Error("Not enough percent left");
if (split.some((s) => s.pubkey === pubkey)) throw new Error("User already in split");
onChange(split.concat({ pubkey, percent }));
const addUser = ({ pubkey, weight }: Split) => {
if (splits.some((s) => s.pubkey === pubkey)) throw new Error("User already in split");
onChange(splits.concat({ pubkey, weight }));
};
const removeUser = (pubkey: string) => {
onChange(split.filter((p) => p.pubkey !== pubkey));
onChange(splits.filter((p) => p.pubkey !== pubkey));
};
const changeUser = (split: Split) => {
onChange(splits.map((s) => (s.pubkey === split.pubkey ? split : s)));
};
const displaySplit = authorPubkey ? fillRemainingPercent(split, authorPubkey) : split;
return (
<Flex gap="2" direction="column">
<Heading size="sm">Zap Splits</Heading>
{remainingPercent > 0 && <AddUserForm onSubmit={addUser} remainingPercent={remainingPercent * 100} />}
{displaySplit.map(({ pubkey, percent }) => (
<AddUserForm onSubmit={addUser} />
{splits.map(({ pubkey, weight }) => (
<UserCard
key={pubkey}
pubkey={pubkey}
percent={percent}
showRemove={pubkey !== authorPubkey}
weight={weight}
onRemove={() => removeUser(pubkey)}
onChange={changeUser}
/>
))}
</Flex>

View File

@ -1,13 +1,9 @@
import { EventTemplate, kinds, nip18, nip19 } from "nostr-tools";
import { EventPointer } from "nostr-tools/nip19";
import { getInboxes, isPTag } from "applesauce-core/helpers";
import { EventTemplate, nip19 } from "nostr-tools";
import { NostrEvent, Tag } from "../../types/nostr-event";
import { getMatchEmoji, getMatchHashtag, getMatchNostrLink } from "../regexp";
import { getMatchNostrLink } from "../regexp";
import { getThreadReferences } from "./event";
import { safeDecode } from "../nip19";
import { Emoji } from "../../providers/global/emoji-provider";
import { EventSplit } from "./zaps";
import { unique } from "../array";
import { getEventPointerRelayHint } from "../../services/relay-hints";
@ -44,23 +40,6 @@ function AddEtag(tags: Tag[], eventId: string, relayHint?: string, type?: string
return [...tags, tag];
}
/** @deprecated use event factory instead */
function AddQuotePointerTag(tags: Tag[], pointer: EventPointer) {
const hint = pointer.relays?.[0] || getEventPointerRelayHint(pointer.id) || "";
const tag: string[] = ["q", pointer.id, hint];
if (pointer.author) tag.push(pointer.author);
if (tags.some((t) => t[0] === tag[0] && t[1] === tag[1] && t[3] === tag[3])) {
// replace the tag
return tags.map((t) => {
if (t[0] === tag[0] && t[1] === tag[1]) return tag;
return t;
});
}
return [...tags, tag];
}
/** adds the "root" and "reply" E tags */
export function addReplyTags(draft: EventTemplate, replyTo: NostrEvent) {
const updated: EventTemplate = { ...draft, tags: Array.from(draft.tags) };
@ -88,10 +67,6 @@ export function ensureNotifyPubkeys(draft: EventTemplate, pubkeys: string[]) {
return updated;
}
export function correctContentMentions(content: string) {
return content.replaceAll(/(?<=^|\s)(?:@)?(npub1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58})/gi, "nostr:$1");
}
export function getPubkeysMentionedInContent(content: string, direct = false) {
const matched = content.matchAll(getMatchNostrLink());
@ -120,11 +95,6 @@ export function getPubkeysMentionedInContent(content: string, direct = false) {
return unique(pubkeys);
}
export function ensureNotifyContentMentions(draft: EventTemplate) {
const mentions = getPubkeysMentionedInContent(draft.content);
return mentions.length > 0 ? ensureNotifyPubkeys(draft, mentions) : draft;
}
export function getAllEventsMentionedInContent(content: string) {
const matched = content.matchAll(getMatchNostrLink());
@ -146,63 +116,3 @@ export function getAllEventsMentionedInContent(content: string) {
return events;
}
export function ensureTagContentMentions(draft: EventTemplate) {
const mentions = getAllEventsMentionedInContent(draft.content);
const updated: EventTemplate = { ...draft, tags: Array.from(draft.tags) };
for (const pointer of mentions) {
updated.tags = AddEtag(updated.tags, pointer.id, pointer.relays?.[0] ?? "", "mention", false);
updated.tags = AddQuotePointerTag(updated.tags, pointer);
}
return updated;
}
/** @deprecated use includeContentHashtags from applesauce-factory instead */
export function createHashtagTags(draft: EventTemplate) {
const updatedDraft: EventTemplate = { ...draft, tags: Array.from(draft.tags) };
// create tags for all occurrences of #hashtag
const matches = updatedDraft.content.matchAll(getMatchHashtag());
for (const [_, space, hashtag] of matches) {
const lower = hashtag.toLocaleLowerCase();
if (!updatedDraft.tags.find((t) => t[0] === "t" && t[1] === lower)) {
updatedDraft.tags.push(["t", lower]);
}
}
return updatedDraft;
}
export function createEmojiTags(draft: EventTemplate, emojis: Emoji[]) {
const updatedDraft: EventTemplate = { ...draft, tags: Array.from(draft.tags) };
// create tags for all occurrences of #hashtag
const matches = updatedDraft.content.matchAll(getMatchEmoji());
for (const [_, name] of matches) {
const emoji = emojis.find((e) => e.name === name);
if (emoji?.url) {
updatedDraft.tags = addTag(updatedDraft.tags, ["emoji", emoji.name, emoji.url]);
}
}
return updatedDraft;
}
export function setZapSplit(draft: EventTemplate, split: EventSplit) {
const updatedDraft: EventTemplate = { ...draft, tags: Array.from(draft.tags) };
// TODO: get best input relay for user
const zapTags = split.map((p) => ["zap", p.pubkey, "", String(p.percent * 100)]);
updatedDraft.tags.push(...zapTags);
return updatedDraft;
}
/** @deprecated use event factory instead */
export function finalizeNote(draft: EventTemplate) {
let updated: EventTemplate = { ...draft, tags: Array.from(draft.tags) };
updated.content = correctContentMentions(updated.content);
updated = ensureTagContentMentions(updated);
return updated;
}

View File

@ -1,9 +1,6 @@
import { kinds } from "nostr-tools";
import { getEventPointerFromETag, getTagValue, safeRelayUrl, unixNow } from "applesauce-core/helpers";
import { getEventPointerFromETag, getTagValue, safeRelayUrl } from "applesauce-core/helpers";
import { DraftNostrEvent, NostrEvent, isPTag } from "../../types/nostr-event";
import { ensureNotifyContentMentions } from "./post";
import { getEventCoordinate } from "./event";
import { NostrEvent, isPTag } from "../../types/nostr-event";
export type StreamStatus = "live" | "ended" | "planned";
@ -80,16 +77,3 @@ export function getStreamParticipants(stream: NostrEvent) {
export function getStreamHashtags(stream: NostrEvent) {
return stream.tags.filter((t) => t[0] === "t").map((t) => t[1]);
}
export function buildChatMessage(stream: NostrEvent, content: string) {
let draft: DraftNostrEvent = {
tags: [["a", getEventCoordinate(stream), "", "root"]],
content,
created_at: unixNow(),
kind: kinds.LiveChatMessage,
};
draft = ensureNotifyContentMentions(draft);
return draft;
}

View File

@ -41,17 +41,3 @@ export function isProfileZap(event: NostrEvent) {
export function totalZaps(zaps: NostrEvent[]) {
return zaps.map(getZapPayment).reduce((t, p) => t + (p?.amount ?? 0), 0);
}
export type EventSplit = { pubkey: string; percent: number; relay?: string }[];
export function getZapSplits(event: NostrEvent, fallbackPubkey?: string): EventSplit {
const tags = event.tags.filter((t) => t[0] === "zap" && t[1] && t[3]) as [string, string, string, string][];
if (tags.length > 0) {
const targets = tags
.map((t) => ({ pubkey: t[1], relay: t[2], percent: parseFloat(t[3]) }))
.filter((p) => Number.isFinite(p.percent));
const total = targets.reduce((v, p) => v + p.percent, 0);
return targets.map((p) => ({ ...p, percent: p.percent / total }));
} else return [{ pubkey: fallbackPubkey || event.pubkey, relay: "", percent: 1 }];
}

View File

@ -1,8 +1,8 @@
import { PropsWithChildren } from "react";
import { useObservable } from "applesauce-react/hooks";
import { FactoryProvider } from "applesauce-react/providers";
import eventFactoryService from "../../services/event-factory";
import { PropsWithChildren } from "react";
export default function EventFactoryProvider({ children }: PropsWithChildren) {
const factory = useObservable(eventFactoryService.subject);

View File

@ -34,19 +34,19 @@ export const GlobalProviders = ({ children }: { children: React.ReactNode }) =>
<QueryStoreProvider store={queryStore}>
<ThemeProviders>
<SigningProvider>
<EventFactoryProvider>
<PublishProvider>
<NotificationsProvider>
<DMTimelineProvider>
<DefaultEmojiProvider>
<UserEmojiProvider>
<PublishProvider>
<NotificationsProvider>
<DMTimelineProvider>
<DefaultEmojiProvider>
<UserEmojiProvider>
<EventFactoryProvider>
<WebOfTrustProvider>{children}</WebOfTrustProvider>
</UserEmojiProvider>
</DefaultEmojiProvider>
</DMTimelineProvider>
</NotificationsProvider>
</PublishProvider>
</EventFactoryProvider>
</EventFactoryProvider>
</UserEmojiProvider>
</DefaultEmojiProvider>
</DMTimelineProvider>
</NotificationsProvider>
</PublishProvider>
</SigningProvider>
</ThemeProviders>
</QueryStoreProvider>

View File

@ -69,25 +69,7 @@ export default function PublishProvider({ children }: PropsWithChildren) {
const outBoxes = useUserOutbox(account?.pubkey);
const finalizeDraft = useCallback<PublishContextType["finalizeDraft"]>(
async (event: EventTemplate | NostrEvent) => {
let draft = cloneEvent(event.kind, event);
// add client tag
if (
localSettings.addClientTag.value &&
!NEVER_ATTACH_CLIENT_TAG.includes(draft.kind) &&
!draft.tags.some((t) => t[0] === "client")
) {
// TODO: this should be removed when all events are created using the event factory
draft = await includeClientTag(
NIP_89_CLIENT_APP.name,
NIP_89_CLIENT_APP.address ? { ...NIP_89_CLIENT_APP.address, kind: kinds.Handlerinformation } : undefined,
)(draft, {});
}
// request signature
return await signerFinalize(draft);
},
(event: EventTemplate | NostrEvent) => signerFinalize(event),
[signerFinalize],
);

View File

@ -1,9 +1,12 @@
import { BehaviorSubject, Observable } from "rxjs";
import { EventFactory } from "applesauce-factory";
import { combineLatest } from "rxjs";
import { Account } from "../classes/accounts/account";
import { getEventRelayHint, getPubkeyRelayHint } from "./relay-hints";
import { NIP_89_CLIENT_APP } from "../const";
import accountService from "./account";
import localSettings from "./local-settings";
class EventFactoryService {
subject = new BehaviorSubject<EventFactory | null>(null);
@ -12,22 +15,20 @@ class EventFactoryService {
return this.subject.value;
}
constructor(account: Observable<Account | null>) {
account.subscribe((current) => {
if (!current) this.subject.next(null);
else
this.subject.next(
new EventFactory({
signer: current.signer,
getRelayHint: getEventRelayHint,
getPubkeyRelayHint: getPubkeyRelayHint,
client: NIP_89_CLIENT_APP,
}),
);
constructor(account: Observable<Account | null>, includeClient: Observable<boolean>) {
combineLatest([account, includeClient]).subscribe(([current, client]) => {
this.subject.next(
new EventFactory({
signer: current ? current.signer : undefined,
getRelayHint: getEventRelayHint,
getPubkeyRelayHint: getPubkeyRelayHint,
client: client ? NIP_89_CLIENT_APP : undefined,
}),
);
});
}
}
const eventFactoryService = new EventFactoryService(accountService.current);
const eventFactoryService = new EventFactoryService(accountService.current, localSettings.addClientTag);
export default eventFactoryService;

View File

@ -2,10 +2,12 @@ import { useMemo, useRef } from "react";
import { Box, Button, Flex, useToast } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { NostrEvent } from "nostr-tools";
import { useEventFactory } from "applesauce-react/hooks";
import { LiveChatMessageBlueprint } from "applesauce-factory/blueprints";
import { Emoji } from "applesauce-core/helpers";
import { buildChatMessage, getStreamHost } from "../../../../helpers/nostr/stream";
import { getStreamHost } from "../../../../helpers/nostr/stream";
import { unique } from "../../../../helpers/array";
import { createEmojiTags, ensureNotifyContentMentions } from "../../../../helpers/nostr/post";
import { useContextEmojis } from "../../../../providers/global/emoji-provider";
import { MagicInput, RefType } from "../../../../components/magic-textarea";
import StreamZapButton from "../../components/stream-zap-button";
@ -19,22 +21,27 @@ import InsertGifButton from "../../../../components/gif/insert-gif-button";
export default function ChatMessageForm({ stream, hideZapButton }: { stream: NostrEvent; hideZapButton?: boolean }) {
const toast = useToast();
const publish = usePublishEvent();
const factory = useEventFactory();
const emojis = useContextEmojis();
const streamRelays = useReadRelays(useAdditionalRelayContext());
const host = getStreamHost(stream);
const hostReadRelays = useUserInbox(host);
const relays = useMemo(() => unique([...streamRelays, ...(hostReadRelays ?? [])]), [hostReadRelays, streamRelays]);
const writeRelays = useMemo(
() => unique([...streamRelays, ...(hostReadRelays ?? [])]),
[hostReadRelays, streamRelays],
);
const { setValue, handleSubmit, formState, reset, getValues, watch } = useForm({
defaultValues: { content: "" },
});
const sendMessage = handleSubmit(async (values) => {
try {
let draft = buildChatMessage(stream, values.content);
draft = ensureNotifyContentMentions(draft);
draft = createEmojiTags(draft, emojis);
const pub = await publish("Send Chat", draft, relays);
const draft = await factory.create(LiveChatMessageBlueprint, stream, values.content, {
emojis: emojis.filter((e) => !!e.url) as Emoji[],
});
const pub = await publish("Send Chat", draft, writeRelays);
if (pub) reset();
} catch (error) {
if (error instanceof Error) toast({ description: error.message, status: "error" });

View File

@ -1,26 +1,16 @@
import { useMemo, useRef } from "react";
import { Box, Button, ButtonGroup, Flex } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { useThrottle } from "react-use";
import { useAsync, useThrottle } from "react-use";
import { kinds } from "nostr-tools";
import { ThreadItem } from "applesauce-core/queries";
import dayjs from "dayjs";
import { useEventFactory } from "applesauce-react/hooks";
import { Emoji } from "applesauce-core/helpers";
import { NostrEvent } from "../../../types/nostr-event";
import { UserAvatarStack } from "../../../components/compact-user-stack";
import { getThreadMembers } from "../../../helpers/thread";
import {
addReplyTags,
createEmojiTags,
ensureNotifyPubkeys,
finalizeNote,
getPubkeysMentionedInContent,
} from "../../../helpers/nostr/post";
import useCurrentAccount from "../../../hooks/use-current-account";
import MagicTextArea, { RefType } from "../../../components/magic-textarea";
import { useContextEmojis } from "../../../providers/global/emoji-provider";
import { TrustProvider } from "../../../providers/local/trust-provider";
import { unique } from "../../../helpers/array";
import { usePublishEvent } from "../../../providers/global/publish-provider";
import { TextNoteContents } from "../../../components/note/timeline-note/text-note-contents";
import useCacheForm from "../../../hooks/use-cache-form";
@ -37,10 +27,10 @@ export type ReplyFormProps = {
export default function ReplyForm({ item, onCancel, onSubmitted, replyKind = kinds.ShortTextNote }: ReplyFormProps) {
const publish = usePublishEvent();
const account = useCurrentAccount();
const factory = useEventFactory();
const emojis = useContextEmojis();
const customEmojis = useMemo(() => emojis.filter((e) => !!e.url) as Emoji[], [emojis]);
const threadMembers = useMemo(() => getThreadMembers(item, account?.pubkey), [item, account?.pubkey]);
const { setValue, getValues, watch, handleSubmit, formState, reset } = useForm({
defaultValues: {
content: "",
@ -50,32 +40,31 @@ export default function ReplyForm({ item, onCancel, onSubmitted, replyKind = kin
const clearCache = useCacheForm<{ content: string }>(`reply-${item.event.id}`, getValues, reset, formState);
const contentMentions = getPubkeysMentionedInContent(getValues().content);
const notifyPubkeys = unique([...threadMembers, ...contentMentions]);
watch("content");
const textAreaRef = useRef<RefType | null>(null);
const insertText = useTextAreaInsertTextWithForm(textAreaRef, getValues, setValue);
const { onPaste } = useTextAreaUploadFile(insertText);
const draft = useMemo(() => {
let updated = finalizeNote({ kind: replyKind, content: getValues().content, created_at: dayjs().unix(), tags: [] });
updated = createEmojiTags(updated, emojis);
updated = addReplyTags(updated, item.event);
updated = ensureNotifyPubkeys(updated, notifyPubkeys);
return updated;
}, [getValues().content, emojis]);
const submit = handleSubmit(async (values) => {
const pub = await publish("Reply", { ...draft, created_at: dayjs().unix() });
const draft = await factory.noteReply(item.event, values.content, {
emojis: customEmojis,
});
const pub = await publish("Reply", draft);
if (pub && onSubmitted) onSubmitted(pub.event);
clearCache();
});
const formRef = useRef<HTMLFormElement | null>(null);
const previewDraft = useThrottle(draft, 500);
// throttle preview
const throttleValues = useThrottle(getValues(), 500);
const { value: preview } = useAsync(
() => factory.noteReply(item.event, throttleValues.content, { emojis: customEmojis }),
[throttleValues, customEmojis],
);
return (
<Flex as="form" direction="column" gap="2" pb="4" onSubmit={submit} ref={formRef}>
@ -96,7 +85,6 @@ export default function ReplyForm({ item, onCancel, onSubmitted, replyKind = kin
<Flex gap="2" alignItems="center">
<InsertImageButton onUploaded={insertText} size="sm" aria-label="Upload image" />
<InsertGifButton onSelectURL={insertText} aria-label="Add gif" size="sm" />
<UserAvatarStack label="Notify" pubkeys={notifyPubkeys} />
<ButtonGroup size="sm" ml="auto">
{onCancel && <Button onClick={onCancel}>Cancel</Button>}
<Button type="submit" colorScheme="primary" size="sm">
@ -104,10 +92,10 @@ export default function ReplyForm({ item, onCancel, onSubmitted, replyKind = kin
</Button>
</ButtonGroup>
</Flex>
{previewDraft.content.length > 0 && (
{preview && preview.content.length > 0 && (
<Box p="2" borderWidth={1} borderRadius="md" mb="2">
<TrustProvider trust>
<TextNoteContents event={previewDraft} />
<TextNoteContents event={preview} />
</TrustProvider>
</Box>
)}