Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d113872e6b | ||
463ce36c13 | |||
34f5ce38ed | |||
ce19d0e50d | |||
dd47c28d18 | |||
a3698e7dec | |||
13044d6b39 | |||
b1d86b3c33 | |||
|
dd2aa3dc40 | ||
ec9c4dad5d | |||
|
493390bdc1 | ||
|
4f5dcc0336 | ||
|
afdc9449b1 | ||
|
c1a9475a89 | ||
|
745ba377d4 | ||
|
8de86aac28 | ||
|
c8f0d135f1 | ||
|
cc681af11a | ||
|
8e23a2d8a1 | ||
|
cc65d85806 | ||
c399dbf56c | |||
|
1d6131bf82 | ||
093416a481 | |||
|
bb9febe25d | ||
826be97b8b | |||
e95fabd4ae | |||
|
4b5625e5bd | ||
|
08b13c291b | ||
|
f422ee338c | ||
|
9f4a891d50 | ||
|
13b88516ca | ||
|
f7d0718b78 | ||
|
6f4b41d84b | ||
|
1474fafde7 | ||
|
e405b735f7 | ||
|
25780a6789 | ||
|
47fcdea385 | ||
|
37baf57093 | ||
|
efe3c2c9c7 | ||
|
4b5955fa9c | ||
|
664ed9de06 | ||
|
1e643c60e5 | ||
b04f4fb88d | |||
3b4bf9aa29 | |||
e85e9519d2 |
.gitea/workflows
CHANGELOG.mdindex.htmlpackage-lock.jsonpackage.jsonpublic
app.webmanifestfavicon-128x128.pngfavicon-144x144.pngfavicon-192x192.pngfavicon-256x256.pngfavicon-384x384.pngfavicon-512x512.pngfavicon-64x64.pngfavicon-72x72.pngfavicon-96x96.pngfavicon.svg
src
App.tsx
components
AppBar
DisplaySigit
DrawPDFFields
MarkTypeStrategy
PDFView
hooks
layouts
pages
routes
services
types
utils
@ -22,7 +22,7 @@ jobs:
|
|||||||
npm ci
|
npm ci
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install zip -y
|
apt-get install zip -y
|
||||||
apt-get install jq
|
apt-get install jq -y
|
||||||
|
|
||||||
- name: Create .env File
|
- name: Create .env File
|
||||||
run: echo "VITE_MOST_POPULAR_RELAYS=${{ vars.VITE_MOST_POPULAR_RELAYS }}" > .env
|
run: echo "VITE_MOST_POPULAR_RELAYS=${{ vars.VITE_MOST_POPULAR_RELAYS }}" > .env
|
||||||
|
23
CHANGELOG.md
23
CHANGELOG.md
@ -1,3 +1,26 @@
|
|||||||
|
# [1.1.0](https://git.nostrdev.com/sigit/sigit.io/compare/v1.0.4...v1.1.0) (2025-04-04)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **callback:** login and private route redirect ([37baf57](https://git.nostrdev.com/sigit/sigit.io/commit/37baf5709397e7099a735d6390cfceffff646f11)), closes [#229](https://git.nostrdev.com/sigit/sigit.io/issues/229)
|
||||||
|
- **dm:** always add sigit relay when sending private DMs ([e405b73](https://git.nostrdev.com/sigit/sigit.io/commit/e405b735f7ca03e6c1fabe8ddaf4ba3e3c6cec6d))
|
||||||
|
- **dm:** don't send private DM twice to same signer ([1474faf](https://git.nostrdev.com/sigit/sigit.io/commit/1474fafde7b76f465d895ef0457960c0704ec444))
|
||||||
|
- hide DisplaySigit actions ([08b13c2](https://git.nostrdev.com/sigit/sigit.io/commit/08b13c291b3cae097e01416127e8674e53b59c53)), closes [#246](https://git.nostrdev.com/sigit/sigit.io/issues/246)
|
||||||
|
- **login:** remove default login redirect ([6f4b41d](https://git.nostrdev.com/sigit/sigit.io/commit/6f4b41d84b7968286bd3aa7503afe23375e58fdf))
|
||||||
|
- **marks:** date input ([8de86aa](https://git.nostrdev.com/sigit/sigit.io/commit/8de86aac28c38fc4fb8d34eca04e0ce50b6ab13e))
|
||||||
|
- only send to next signer on create ([3b4bf9a](https://git.nostrdev.com/sigit/sigit.io/commit/3b4bf9aa29b337aa07e0aba9d06e89116482499c))
|
||||||
|
- **search:** intercept nsec1, delete, and show warning ([4b5625e](https://git.nostrdev.com/sigit/sigit.io/commit/4b5625e5bd1354678bdade46ee511be187fe754b))
|
||||||
|
- **search:** tim input, add timeout ([f7d0718](https://git.nostrdev.com/sigit/sigit.io/commit/f7d0718b7820ebc66310f572b0ceb6e91f5393bd)), closes [#308](https://git.nostrdev.com/sigit/sigit.io/issues/308)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add private dm sending ([e85e951](https://git.nostrdev.com/sigit/sigit.io/commit/e85e9519d2a55775ddda30b3d743158e4db6cb39))
|
||||||
|
- **create:** add local draft, save progress to local storage ([9f4a891](https://git.nostrdev.com/sigit/sigit.io/commit/9f4a891d5002532b72e9e068a85bd809491aeeef)), closes [#175](https://git.nostrdev.com/sigit/sigit.io/issues/175)
|
||||||
|
- **draft:** serialize sigit and save/load to local storage ([13b8851](https://git.nostrdev.com/sigit/sigit.io/commit/13b88516cac858dd481ba4974509de5b35dffdb1))
|
||||||
|
- enable pwa ([#324](https://git.nostrdev.com/sigit/sigit.io/issues/324)) ([13044d6](https://git.nostrdev.com/sigit/sigit.io/commit/13044d6b39e464b6e7de6a825b9a15858a652e4a)), closes [#93](https://git.nostrdev.com/sigit/sigit.io/issues/93)
|
||||||
|
- **marks:** add full name ([cc681af](https://git.nostrdev.com/sigit/sigit.io/commit/cc681af11a34619ce6c41f6316b4fe9e831ff6c2))
|
||||||
|
- **marks:** add job title and datetime ([c8f0d13](https://git.nostrdev.com/sigit/sigit.io/commit/c8f0d135f13dfea86068f7efb6c4c7152b299085))
|
||||||
|
|
||||||
## [1.0.4](https://git.nostrdev.com/sigit/sigit.io/compare/v1.0.3...v1.0.4) (2025-01-31)
|
## [1.0.4](https://git.nostrdev.com/sigit/sigit.io/compare/v1.0.3...v1.0.4) (2025-01-31)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="manifest" href="/app.webmanifest" />
|
||||||
<title>SIGit</title>
|
<title>SIGit</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
388
package-lock.json
generated
388
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sigit",
|
"name": "sigit",
|
||||||
"version": "0.0.0-beta",
|
"version": "1.0.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sigit",
|
"name": "sigit",
|
||||||
"version": "0.0.0-beta",
|
"version": "1.0.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0-or-later ",
|
"license": "AGPL-3.0-or-later ",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -20,11 +20,11 @@
|
|||||||
"@mui/lab": "5.0.0-alpha.166",
|
"@mui/lab": "5.0.0-alpha.166",
|
||||||
"@mui/material": "5.15.11",
|
"@mui/material": "5.15.11",
|
||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@nostr-dev-kit/ndk": "2.10.0",
|
"@nostr-dev-kit/ndk": "2.11.0",
|
||||||
"@nostr-dev-kit/ndk-cache-dexie": "2.5.1",
|
"@nostr-dev-kit/ndk-cache-dexie": "2.5.9",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@reduxjs/toolkit": "2.2.1",
|
"@reduxjs/toolkit": "2.2.1",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.8.2",
|
||||||
"crypto-hash": "3.0.0",
|
"crypto-hash": "3.0.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dexie": "4.0.8",
|
"dexie": "4.0.8",
|
||||||
@ -51,8 +51,7 @@
|
|||||||
"react-toastify": "10.0.4",
|
"react-toastify": "10.0.4",
|
||||||
"redux": "5.0.1",
|
"redux": "5.0.1",
|
||||||
"signature_pad": "^5.0.4",
|
"signature_pad": "^5.0.4",
|
||||||
"tseep": "1.2.1",
|
"tseep": "1.2.1"
|
||||||
"use-immer": "^0.11.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||||
@ -109,12 +108,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.23.5",
|
"version": "7.26.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||||
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
|
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/highlight": "^7.23.4",
|
"@babel/helper-validator-identifier": "^7.25.9",
|
||||||
"chalk": "^2.4.2"
|
"js-tokens": "^4.0.0",
|
||||||
|
"picocolors": "^1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -306,17 +307,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.23.4",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||||
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-identifier": {
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
"version": "7.22.20",
|
"version": "7.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
||||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
@ -331,37 +334,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helpers": {
|
"node_modules/@babel/helpers": {
|
||||||
"version": "7.23.9",
|
"version": "7.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
|
||||||
"integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==",
|
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.23.9",
|
"@babel/template": "^7.27.0",
|
||||||
"@babel/traverse": "^7.23.9",
|
"@babel/types": "^7.27.0"
|
||||||
"@babel/types": "^7.23.9"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@babel/highlight": {
|
|
||||||
"version": "7.23.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
|
|
||||||
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/helper-validator-identifier": "^7.22.20",
|
|
||||||
"chalk": "^2.4.2",
|
|
||||||
"js-tokens": "^4.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.23.9",
|
"version": "7.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
|
||||||
"integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
|
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.27.0"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
},
|
},
|
||||||
@ -400,9 +394,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.23.9",
|
"version": "7.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||||
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
|
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@ -411,14 +406,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.23.9",
|
"version": "7.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
|
||||||
"integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==",
|
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.23.5",
|
"@babel/code-frame": "^7.26.2",
|
||||||
"@babel/parser": "^7.23.9",
|
"@babel/parser": "^7.27.0",
|
||||||
"@babel/types": "^7.23.9"
|
"@babel/types": "^7.27.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -446,13 +442,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.23.9",
|
"version": "7.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
|
||||||
"integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==",
|
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.23.4",
|
"@babel/helper-string-parser": "^7.25.9",
|
||||||
"@babel/helper-validator-identifier": "^7.22.20",
|
"@babel/helper-validator-identifier": "^7.25.9"
|
||||||
"to-fast-properties": "^2.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -1698,15 +1694,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@noble/secp256k1": {
|
"node_modules/@noble/secp256k1": {
|
||||||
"version": "2.0.0",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.2.3.tgz",
|
||||||
"integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==",
|
"integrity": "sha512-l7r5oEQym9Us7EAigzg30/PQAvynhMt2uoYtT3t26eGDVm9Yii5mZ5jWSWmZ/oSIR2Et0xfc6DXrG0bZ787V3w==",
|
||||||
"funding": [
|
"license": "MIT",
|
||||||
{
|
"funding": {
|
||||||
"type": "individual",
|
"url": "https://paulmillr.com/funding/"
|
||||||
"url": "https://paulmillr.com/funding/"
|
}
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
@ -1744,19 +1738,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nostr-dev-kit/ndk": {
|
"node_modules/@nostr-dev-kit/ndk": {
|
||||||
"version": "2.10.0",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.11.0.tgz",
|
||||||
"integrity": "sha512-TqCAAo6ylORraAXrzRkCGFN2xTMiFbdER8Y8CtUT0HwOpFG/Wn+PBNeDeDmqkl/6LaPdeyXmVwCWj2KcUjIwYA==",
|
"integrity": "sha512-FKIMtcVsVcquzrC+yir9lOXHCIHmQ3IKEVCMohqEB7N96HjP2qrI9s5utbjI3lkavFNF5tXg1Gp9ODEo7XCfLA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/curves": "^1.4.0",
|
"@noble/curves": "^1.6.0",
|
||||||
"@noble/hashes": "^1.3.1",
|
"@noble/hashes": "^1.5.0",
|
||||||
"@noble/secp256k1": "^2.0.0",
|
"@noble/secp256k1": "^2.1.0",
|
||||||
"@scure/base": "^1.1.1",
|
"@scure/base": "^1.1.9",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.6",
|
||||||
"light-bolt11-decoder": "^3.0.0",
|
"light-bolt11-decoder": "^3.2.0",
|
||||||
"node-fetch": "^3.3.1",
|
|
||||||
"nostr-tools": "^2.7.1",
|
"nostr-tools": "^2.7.1",
|
||||||
"tseep": "^1.1.1",
|
"tseep": "^1.2.2",
|
||||||
"typescript-lru-cache": "^2.0.0",
|
"typescript-lru-cache": "^2.0.0",
|
||||||
"utf8-buffer": "^1.0.0",
|
"utf8-buffer": "^1.0.0",
|
||||||
"websocket-polyfill": "^0.0.3"
|
"websocket-polyfill": "^0.0.3"
|
||||||
@ -1766,17 +1760,41 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nostr-dev-kit/ndk-cache-dexie": {
|
"node_modules/@nostr-dev-kit/ndk-cache-dexie": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk-cache-dexie/-/ndk-cache-dexie-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk-cache-dexie/-/ndk-cache-dexie-2.5.9.tgz",
|
||||||
"integrity": "sha512-tUwEy68bd9GL5JVuZIjcpdwuDEBnaXen3WJ64/GRDtbyE1RB01Y6hHC7IQC9bcQ6SC7XBGyPd+2nuTyR7+Mffg==",
|
"integrity": "sha512-SZ5FjON0QPekiC7oW9Hy3JQxG0Oxxtud9LBa1q/A49JV/Qppv1x37nFHxi0XLxEbDgFTNYbaN27Zjfp2NPem2g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nostr-dev-kit/ndk": "2.10.0",
|
"@nostr-dev-kit/ndk": "2.11.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.7",
|
||||||
"dexie": "^4.0.2",
|
"dexie": "^4.0.8",
|
||||||
"nostr-tools": "^2.4.0",
|
"nostr-tools": "^2.4.0",
|
||||||
"typescript-lru-cache": "^2.0.0"
|
"typescript-lru-cache": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nostr-dev-kit/ndk-cache-dexie/node_modules/debug": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@nostr-dev-kit/ndk-cache-dexie/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nostr-dev-kit/ndk/node_modules/@noble/curves": {
|
"node_modules/@nostr-dev-kit/ndk/node_modules/@noble/curves": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz",
|
||||||
@ -1802,6 +1820,15 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nostr-dev-kit/ndk/node_modules/@scure/base": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools": {
|
"node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools": {
|
||||||
"version": "2.10.4",
|
"version": "2.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.10.4.tgz",
|
||||||
@ -1859,6 +1886,24 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools/node_modules/@scure/base": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@nostr-dev-kit/ndk/node_modules/tseep": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tseep/-/tseep-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@octokit/auth-token": {
|
"node_modules/@octokit/auth-token": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
|
||||||
@ -1891,9 +1936,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/endpoint": {
|
"node_modules/@octokit/endpoint": {
|
||||||
"version": "10.1.2",
|
"version": "10.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz",
|
||||||
"integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==",
|
"integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@ -1922,22 +1967,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/openapi-types": {
|
||||||
"version": "23.0.1",
|
"version": "24.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||||
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
|
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/plugin-paginate-rest": {
|
"node_modules/@octokit/plugin-paginate-rest": {
|
||||||
"version": "11.4.0",
|
"version": "11.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz",
|
||||||
"integrity": "sha512-ttpGck5AYWkwMkMazNCZMqxKqIq1fJBNxBfsFwwfyYKTf914jKkLF0POMS3YkPBwp5g1c2Y4L79gDz01GhSr1g==",
|
"integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^13.7.0"
|
"@octokit/types": "^13.10.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
@ -1984,15 +2029,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request": {
|
"node_modules/@octokit/request": {
|
||||||
"version": "9.2.0",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz",
|
||||||
"integrity": "sha512-kXLfcxhC4ozCnAXy2ff+cSxpcF0A1UqxjvYMqNuPIeOAzJbVWQ+dy5G2fTylofB/gTbObT8O6JORab+5XtA1Kw==",
|
"integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/endpoint": "^10.0.0",
|
"@octokit/endpoint": "^10.1.3",
|
||||||
"@octokit/request-error": "^6.0.1",
|
"@octokit/request-error": "^6.1.7",
|
||||||
"@octokit/types": "^13.6.2",
|
"@octokit/types": "^13.6.2",
|
||||||
"fast-content-type-parse": "^2.0.0",
|
"fast-content-type-parse": "^2.0.0",
|
||||||
"universal-user-agent": "^7.0.2"
|
"universal-user-agent": "^7.0.2"
|
||||||
@ -2002,9 +2047,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request-error": {
|
"node_modules/@octokit/request-error": {
|
||||||
"version": "6.1.6",
|
"version": "6.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz",
|
||||||
"integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==",
|
"integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@ -2016,14 +2061,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/types": {
|
"node_modules/@octokit/types": {
|
||||||
"version": "13.7.0",
|
"version": "13.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||||
"integrity": "sha512-BXfRP+3P3IN6fd4uF3SniaHKOO4UXWBfkdR3vA8mIvaoO/wLjGN5qivUtW0QRitBHHMcfC41SLhNVYIZZE+wkA==",
|
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/openapi-types": "^23.0.1"
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@pdf-lib/fontkit": {
|
"node_modules/@pdf-lib/fontkit": {
|
||||||
@ -3814,6 +3859,7 @@
|
|||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
},
|
},
|
||||||
@ -4003,9 +4049,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.4",
|
"version": "1.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
@ -4516,6 +4562,7 @@
|
|||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^3.2.1",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"escape-string-regexp": "^1.0.5",
|
||||||
@ -4973,6 +5020,7 @@
|
|||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "1.1.3"
|
"color-name": "1.1.3"
|
||||||
}
|
}
|
||||||
@ -4980,7 +5028,8 @@
|
|||||||
"node_modules/color-name": {
|
"node_modules/color-name": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/color-support": {
|
"node_modules/color-support": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
@ -6141,14 +6190,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/data-uri-to-buffer": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dateformat": {
|
"node_modules/dateformat": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
|
||||||
@ -6684,9 +6725,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/elliptic": {
|
"node_modules/elliptic": {
|
||||||
"version": "6.6.0",
|
"version": "6.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz",
|
||||||
"integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==",
|
"integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -6899,6 +6940,7 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
@ -7370,28 +7412,6 @@
|
|||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fetch-blob": {
|
|
||||||
"version": "3.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
|
||||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/jimmywarting"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "paypal",
|
|
||||||
"url": "https://paypal.me/jimmywarting"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"node-domexception": "^1.0.0",
|
|
||||||
"web-streams-polyfill": "^3.0.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^12.20 || >= 14.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/figures": {
|
"node_modules/figures": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||||
@ -7570,17 +7590,6 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/formdata-polyfill": {
|
|
||||||
"version": "4.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
|
||||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
|
||||||
"dependencies": {
|
|
||||||
"fetch-blob": "^3.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.20.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/from2": {
|
"node_modules/from2": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
|
||||||
@ -8271,6 +8280,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
@ -9180,9 +9190,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/light-bolt11-decoder": {
|
"node_modules/light-bolt11-decoder": {
|
||||||
"version": "3.0.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz",
|
||||||
"integrity": "sha512-AKvOigD2pmC8ktnn2TIqdJu0K0qk6ukUmTvHwF3JNkm8uWCqt18Ijn33A/a7gaRZ4PghJ59X+8+MXrzLKdBTmQ==",
|
"integrity": "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scure/base": "1.1.1"
|
"@scure/base": "1.1.1"
|
||||||
}
|
}
|
||||||
@ -10257,24 +10268,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
|
||||||
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
|
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
|
||||||
},
|
},
|
||||||
"node_modules/node-domexception": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/jimmywarting"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://paypal.me/jimmywarting"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-emoji": {
|
"node_modules/node-emoji": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz",
|
||||||
@ -10306,23 +10299,6 @@
|
|||||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-fetch": {
|
|
||||||
"version": "3.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
|
||||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
|
||||||
"dependencies": {
|
|
||||||
"data-uri-to-buffer": "^4.0.0",
|
|
||||||
"fetch-blob": "^3.1.4",
|
|
||||||
"formdata-polyfill": "^4.0.10"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/node-fetch"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-gyp-build": {
|
"node_modules/node-gyp-build": {
|
||||||
"version": "4.8.0",
|
"version": "4.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
|
||||||
@ -13906,7 +13882,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@ -16609,6 +16584,7 @@
|
|||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^3.0.0"
|
"has-flag": "^3.0.0"
|
||||||
},
|
},
|
||||||
@ -16877,14 +16853,6 @@
|
|||||||
"node": ">=0.6.0"
|
"node": ">=0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/to-fast-properties": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/to-readable-stream": {
|
"node_modules/to-readable-stream": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz",
|
||||||
@ -17265,16 +17233,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/use-immer": {
|
|
||||||
"version": "0.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.11.0.tgz",
|
|
||||||
"integrity": "sha512-RNAqi3GqsWJ4bcCd4LMBgdzvPmTABam24DUaFiKfX9s3MSorNRz9RDZYJkllJoMHUxVLMDetwAuCDeyWNrp1yA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"immer": ">=8.0.0",
|
|
||||||
"react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/use-sync-external-store": {
|
"node_modules/use-sync-external-store": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||||
@ -17347,9 +17305,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.8",
|
"version": "5.4.16",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.16.tgz",
|
||||||
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
|
"integrity": "sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -17462,14 +17420,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/web-streams-polyfill": {
|
|
||||||
"version": "3.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
|
||||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
11
package.json
11
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sigit",
|
"name": "sigit",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.4",
|
"version": "1.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://sigit.io/",
|
"homepage": "https://sigit.io/",
|
||||||
"license": "AGPL-3.0-or-later ",
|
"license": "AGPL-3.0-or-later ",
|
||||||
@ -31,11 +31,11 @@
|
|||||||
"@mui/lab": "5.0.0-alpha.166",
|
"@mui/lab": "5.0.0-alpha.166",
|
||||||
"@mui/material": "5.15.11",
|
"@mui/material": "5.15.11",
|
||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@nostr-dev-kit/ndk": "2.10.0",
|
"@nostr-dev-kit/ndk": "2.11.0",
|
||||||
"@nostr-dev-kit/ndk-cache-dexie": "2.5.1",
|
"@nostr-dev-kit/ndk-cache-dexie": "2.5.9",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@reduxjs/toolkit": "2.2.1",
|
"@reduxjs/toolkit": "2.2.1",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.8.2",
|
||||||
"crypto-hash": "3.0.0",
|
"crypto-hash": "3.0.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dexie": "4.0.8",
|
"dexie": "4.0.8",
|
||||||
@ -62,8 +62,7 @@
|
|||||||
"react-toastify": "10.0.4",
|
"react-toastify": "10.0.4",
|
||||||
"redux": "5.0.1",
|
"redux": "5.0.1",
|
||||||
"signature_pad": "^5.0.4",
|
"signature_pad": "^5.0.4",
|
||||||
"tseep": "1.2.1",
|
"tseep": "1.2.1"
|
||||||
"use-immer": "^0.11.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||||
|
58
public/app.webmanifest
Normal file
58
public/app.webmanifest
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"short_name": "SIGit",
|
||||||
|
"name": "SIGit",
|
||||||
|
"description": "A decentralised document signing tool",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon-384x384.png",
|
||||||
|
"sizes": "384x384",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon-256x256.png",
|
||||||
|
"sizes": "256x256",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon-144x144.png",
|
||||||
|
"sizes": "144x144",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon-128x128.png",
|
||||||
|
"sizes": "128x128",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon-96x96.png",
|
||||||
|
"sizes": "96x96",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon-72x72.png",
|
||||||
|
"sizes": "72x72",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "favicon-64x64.png",
|
||||||
|
"sizes": "64x64",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "/",
|
||||||
|
"display_override": ["minimal-ui", "standalone"],
|
||||||
|
"display": "minimal-ui",
|
||||||
|
"orientation": "any",
|
||||||
|
"theme_color": "#7d54a3",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
BIN
public/favicon-128x128.png
Normal file
BIN
public/favicon-128x128.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 3.3 KiB |
BIN
public/favicon-144x144.png
Normal file
BIN
public/favicon-144x144.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 3.7 KiB |
BIN
public/favicon-192x192.png
Normal file
BIN
public/favicon-192x192.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 5.2 KiB |
BIN
public/favicon-256x256.png
Normal file
BIN
public/favicon-256x256.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 8.0 KiB |
BIN
public/favicon-384x384.png
Normal file
BIN
public/favicon-384x384.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 13 KiB |
BIN
public/favicon-512x512.png
Normal file
BIN
public/favicon-512x512.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 15 KiB |
BIN
public/favicon-64x64.png
Normal file
BIN
public/favicon-64x64.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.5 KiB |
BIN
public/favicon-72x72.png
Normal file
BIN
public/favicon-72x72.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.7 KiB |
BIN
public/favicon-96x96.png
Normal file
BIN
public/favicon-96x96.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.3 KiB |
25
public/favicon.svg
Normal file
25
public/favicon.svg
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 282.61 282.61">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #47b17d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #4c82a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
fill: #7d54a3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="Layer_1-2" data-name="Layer 1" transform="translate(0, 13.775)">
|
||||||
|
<g>
|
||||||
|
<path class="cls-2" d="M181.53,115.06h0c-9.4-36.67-56.77-24.79-121.09-12.57C-3.54,114.64-25.35,19.85,37.72,3.62,46.91,1.26,56.55,0,66.47,0c63.55,0,115.06,51.51,115.06,115.06Z"/>
|
||||||
|
<path class="cls-1" d="M100,140h0c9.4,36.67,56.77,24.79,121.09,12.57,63.98-12.16,85.79,82.64,22.72,98.86-9.19,2.36-18.83,3.62-28.76,3.62-63.55,0-115.06-51.51-115.06-115.06Z"/>
|
||||||
|
<circle class="cls-3" cx="140.77" cy="127.53" r="24.88"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After (image error) Size: 859 B |
20
src/App.tsx
20
src/App.tsx
@ -4,8 +4,6 @@ import { Navigate, Route, Routes } from 'react-router-dom'
|
|||||||
import { useAppSelector, useAuth } from './hooks'
|
import { useAppSelector, useAuth } from './hooks'
|
||||||
|
|
||||||
import { MainLayout } from './layouts/Main'
|
import { MainLayout } from './layouts/Main'
|
||||||
|
|
||||||
import { appPrivateRoutes, appPublicRoutes } from './routes'
|
|
||||||
import {
|
import {
|
||||||
privateRoutes,
|
privateRoutes,
|
||||||
publicRoutes,
|
publicRoutes,
|
||||||
@ -16,7 +14,7 @@ import './App.scss'
|
|||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { checkSession } = useAuth()
|
const { checkSession } = useAuth()
|
||||||
const authState = useAppSelector((state) => state.auth)
|
const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.location.hostname === '0.0.0.0') {
|
if (window.location.hostname === '0.0.0.0') {
|
||||||
@ -29,19 +27,9 @@ const App = () => {
|
|||||||
checkSession()
|
checkSession()
|
||||||
}, [checkSession])
|
}, [checkSession])
|
||||||
|
|
||||||
const handleRootRedirect = () => {
|
|
||||||
if (authState.loggedIn) return appPrivateRoutes.homePage
|
|
||||||
|
|
||||||
const callbackPathEncoded = btoa(
|
|
||||||
window.location.href.split(`${window.location.origin}/#`)[1]
|
|
||||||
)
|
|
||||||
|
|
||||||
return `${appPublicRoutes.landingPage}?callbackPath=${callbackPathEncoded}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide route only if loggedIn and r.hiddenWhenLoggedIn are both true
|
// Hide route only if loggedIn and r.hiddenWhenLoggedIn are both true
|
||||||
const publicRoutesList = recursiveRouteRenderer(publicRoutes, (r) => {
|
const publicRoutesList = recursiveRouteRenderer(publicRoutes, (r) => {
|
||||||
return !authState.loggedIn || !r.hiddenWhenLoggedIn
|
return !isLoggedIn || !r.hiddenWhenLoggedIn
|
||||||
})
|
})
|
||||||
|
|
||||||
const privateRouteList = recursiveRouteRenderer(privateRoutes)
|
const privateRouteList = recursiveRouteRenderer(privateRoutes)
|
||||||
@ -49,9 +37,9 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<MainLayout />}>
|
<Route element={<MainLayout />}>
|
||||||
{authState?.loggedIn && privateRouteList}
|
|
||||||
{publicRoutesList}
|
{publicRoutesList}
|
||||||
<Route path="*" element={<Navigate to={handleRootRedirect()} />} />
|
{privateRouteList}
|
||||||
|
<Route path="*" element={<Navigate to={'/'} />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
|
@ -181,7 +181,7 @@ export const AppBar = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAnchorElUser(null)
|
setAnchorElUser(null)
|
||||||
|
|
||||||
navigate(appPrivateRoutes.settings)
|
navigate(appPrivateRoutes.profileSettings)
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
|
117
src/components/DisplaySigit/LocalDraftSigit.tsx
Normal file
117
src/components/DisplaySigit/LocalDraftSigit.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Tooltip, Button, Divider } from '@mui/material'
|
||||||
|
import {
|
||||||
|
faCalendar,
|
||||||
|
faFile,
|
||||||
|
faFileCircleExclamation,
|
||||||
|
faPen,
|
||||||
|
faTrash
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { SigitDraft, UserRole } from '../../types'
|
||||||
|
import { appPrivateRoutes } from '../../routes'
|
||||||
|
import {
|
||||||
|
formatTimestamp,
|
||||||
|
getSigitDraft,
|
||||||
|
npubToHex,
|
||||||
|
SigitStatus,
|
||||||
|
SignStatus
|
||||||
|
} from '../../utils'
|
||||||
|
import { DisplaySigner } from '../DisplaySigner'
|
||||||
|
import { UserAvatarGroup } from '../UserAvatarGroup'
|
||||||
|
import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
||||||
|
import { useAppSelector, useDidMount } from '../../hooks'
|
||||||
|
import styles from './style.module.scss'
|
||||||
|
|
||||||
|
interface LocalDraftSigitProps {
|
||||||
|
handleDraftDelete: () => void
|
||||||
|
}
|
||||||
|
export const LocalDraftSigit = ({
|
||||||
|
handleDraftDelete
|
||||||
|
}: LocalDraftSigitProps) => {
|
||||||
|
const [draft, setDraft] = useState<SigitDraft>()
|
||||||
|
useDidMount(async () => {
|
||||||
|
// Check if draft exists and add link to direct
|
||||||
|
const draft = await getSigitDraft()
|
||||||
|
if (draft) {
|
||||||
|
setDraft(draft)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const submittedBy = useAppSelector((state) => state.auth.usersPubkey)
|
||||||
|
|
||||||
|
if (!draft) return null
|
||||||
|
|
||||||
|
const extensions = draft.files.map((f) => f.extension)
|
||||||
|
const isSame = extensions.every((e) => extensions[0] === e)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.itemWrapper}>
|
||||||
|
<Link className={styles.insetLink} to={appPrivateRoutes.create}></Link>
|
||||||
|
<p className={`line-clamp-2 ${styles.title}`}>{draft.title}</p>
|
||||||
|
<div className={styles.users}>
|
||||||
|
{submittedBy && (
|
||||||
|
<DisplaySigner status={SignStatus.Pending} pubkey={submittedBy} />
|
||||||
|
)}
|
||||||
|
{submittedBy && draft.users.length ? (
|
||||||
|
<Divider orientation="vertical" flexItem />
|
||||||
|
) : null}
|
||||||
|
<UserAvatarGroup max={7}>
|
||||||
|
{draft.users.map((user) => {
|
||||||
|
const pubkey = npubToHex(user.pubkey)!
|
||||||
|
return (
|
||||||
|
<DisplaySigner
|
||||||
|
key={pubkey}
|
||||||
|
status={
|
||||||
|
user.role === UserRole.signer
|
||||||
|
? SignStatus.Pending
|
||||||
|
: SignStatus.Viewer
|
||||||
|
}
|
||||||
|
pubkey={pubkey}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</UserAvatarGroup>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.details} ${styles.iconLabel}`}>
|
||||||
|
<FontAwesomeIcon icon={faCalendar} />
|
||||||
|
{formatTimestamp(draft.lastUpdated)}
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.details} ${styles.status}`}>
|
||||||
|
<span className={styles.iconLabel}>
|
||||||
|
<FontAwesomeIcon icon={faPen} /> {SigitStatus.LocalDraft}
|
||||||
|
</span>
|
||||||
|
{extensions.length > 0 ? (
|
||||||
|
<span className={styles.iconLabel}>
|
||||||
|
{!isSame ? (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faFile} /> Multiple File Types
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
getExtensionIconLabel(extensions[0])
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faFileCircleExclamation} /> —
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.itemActions}>
|
||||||
|
<Tooltip title="Delete" arrow placement="top" disableInteractive>
|
||||||
|
<Button
|
||||||
|
onClick={handleDraftDelete}
|
||||||
|
sx={{
|
||||||
|
color: 'var(--primary-main)',
|
||||||
|
minWidth: '34px',
|
||||||
|
padding: '10px'
|
||||||
|
}}
|
||||||
|
variant={'text'}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -112,32 +112,37 @@ export const DisplaySigit = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.itemActions}>
|
{
|
||||||
<Tooltip title="Duplicate" arrow placement="top" disableInteractive>
|
// TODO: enable buttons once feature is ready
|
||||||
<Button
|
false && (
|
||||||
sx={{
|
<div className={styles.itemActions}>
|
||||||
color: 'var(--primary-main)',
|
<Tooltip title="Duplicate" arrow placement="top" disableInteractive>
|
||||||
minWidth: '34px',
|
<Button
|
||||||
padding: '10px'
|
sx={{
|
||||||
}}
|
color: 'var(--primary-main)',
|
||||||
variant={'text'}
|
minWidth: '34px',
|
||||||
>
|
padding: '10px'
|
||||||
<FontAwesomeIcon icon={faCopy} />
|
}}
|
||||||
</Button>
|
variant={'text'}
|
||||||
</Tooltip>
|
>
|
||||||
<Tooltip title="Archive" arrow placement="top" disableInteractive>
|
<FontAwesomeIcon icon={faCopy} />
|
||||||
<Button
|
</Button>
|
||||||
sx={{
|
</Tooltip>
|
||||||
color: 'var(--primary-main)',
|
<Tooltip title="Archive" arrow placement="top" disableInteractive>
|
||||||
minWidth: '34px',
|
<Button
|
||||||
padding: '10px'
|
sx={{
|
||||||
}}
|
color: 'var(--primary-main)',
|
||||||
variant={'text'}
|
minWidth: '34px',
|
||||||
>
|
padding: '10px'
|
||||||
<FontAwesomeIcon icon={faArchive} />
|
}}
|
||||||
</Button>
|
variant={'text'}
|
||||||
</Tooltip>
|
>
|
||||||
</div>
|
<FontAwesomeIcon icon={faArchive} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf'
|
|||||||
import { useScale } from '../../hooks/useScale'
|
import { useScale } from '../../hooks/useScale'
|
||||||
import { AvatarIconButton } from '../UserAvatarIconButton'
|
import { AvatarIconButton } from '../UserAvatarIconButton'
|
||||||
import { UserAvatar } from '../UserAvatar'
|
import { UserAvatar } from '../UserAvatar'
|
||||||
import { Updater } from 'use-immer'
|
|
||||||
import { FileItem } from './internal/FileItem'
|
import { FileItem } from './internal/FileItem'
|
||||||
import { FileDivider } from '../FileDivider'
|
import { FileDivider } from '../FileDivider'
|
||||||
import { Counterpart } from './internal/Counterpart'
|
import { Counterpart } from './internal/Counterpart'
|
||||||
@ -28,6 +27,7 @@ const MINIMUM_RECT_SIZE = {
|
|||||||
height: 10
|
height: 10
|
||||||
} as const
|
} as const
|
||||||
import { NDKUserProfile } from '@nostr-dev-kit/ndk'
|
import { NDKUserProfile } from '@nostr-dev-kit/ndk'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const DEFAULT_START_SIZE = {
|
const DEFAULT_START_SIZE = {
|
||||||
width: 140,
|
width: 140,
|
||||||
@ -45,7 +45,7 @@ interface DrawPdfFieldsProps {
|
|||||||
users: User[]
|
users: User[]
|
||||||
userProfiles: { [key: string]: NDKUserProfile }
|
userProfiles: { [key: string]: NDKUserProfile }
|
||||||
sigitFiles: SigitFile[]
|
sigitFiles: SigitFile[]
|
||||||
updateSigitFiles: Updater<SigitFile[]>
|
setSigitFiles: React.Dispatch<React.SetStateAction<SigitFile[]>>
|
||||||
selectedTool?: DrawTool
|
selectedTool?: DrawTool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,11 +53,10 @@ export const DrawPDFFields = ({
|
|||||||
selectedTool,
|
selectedTool,
|
||||||
userProfiles,
|
userProfiles,
|
||||||
sigitFiles,
|
sigitFiles,
|
||||||
updateSigitFiles,
|
setSigitFiles,
|
||||||
users
|
users
|
||||||
}: DrawPdfFieldsProps) => {
|
}: DrawPdfFieldsProps) => {
|
||||||
const { to, from } = useScale()
|
const { to, from } = useScale()
|
||||||
|
|
||||||
const signers = users.filter((u) => u.role === UserRole.signer)
|
const signers = users.filter((u) => u.role === UserRole.signer)
|
||||||
const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
|
const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
|
||||||
const [lastSigner, setLastSigner] = useState(defaultSignerNpub)
|
const [lastSigner, setLastSigner] = useState(defaultSignerNpub)
|
||||||
@ -354,8 +353,10 @@ export const DrawPDFFields = ({
|
|||||||
) => {
|
) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
updateSigitFiles((draft) => {
|
setSigitFiles((prev) => {
|
||||||
draft[fileIndex]?.pages![pageIndex]?.drawnFields?.splice(fieldIndex, 1)
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone[fileIndex]?.pages![pageIndex]?.drawnFields?.splice(fieldIndex, 1)
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,22 +417,28 @@ export const DrawPDFFields = ({
|
|||||||
|
|
||||||
// Add new drawn field to the files
|
// Add new drawn field to the files
|
||||||
if (mouseState.clicked) {
|
if (mouseState.clicked) {
|
||||||
updateSigitFiles((draft) => {
|
setSigitFiles((prev) => {
|
||||||
draft[fileIndex].pages![pageIndex].drawnFields.push(field)
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone[fileIndex].pages![pageIndex].drawnFields.push(field)
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move
|
// Move
|
||||||
if (mouseState.dragging) {
|
if (mouseState.dragging) {
|
||||||
updateSigitFiles((draft) => {
|
setSigitFiles((prev) => {
|
||||||
draft[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize
|
// Resize
|
||||||
if (mouseState.resizing) {
|
if (mouseState.resizing) {
|
||||||
updateSigitFiles((draft) => {
|
setSigitFiles((prev) => {
|
||||||
draft[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,7 +453,7 @@ export const DrawPDFFields = ({
|
|||||||
mouseState.clicked,
|
mouseState.clicked,
|
||||||
mouseState.dragging,
|
mouseState.dragging,
|
||||||
mouseState.resizing,
|
mouseState.resizing,
|
||||||
updateSigitFiles
|
setSigitFiles
|
||||||
])
|
])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
24
src/components/MarkTypeStrategy/DateTime/Input.tsx
Normal file
24
src/components/MarkTypeStrategy/DateTime/Input.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { MarkInputProps } from '../MarkStrategy'
|
||||||
|
import styles from '../../MarkFormField/style.module.scss'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
export const MarkInputDateTime = ({ handler, placeholder }: MarkInputProps) => {
|
||||||
|
const ref = useRef<HTMLInputElement>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
const date = new Date()
|
||||||
|
ref.current.value = date.toISOString().slice(0, 16)
|
||||||
|
handler(date.toUTCString())
|
||||||
|
}
|
||||||
|
}, [handler])
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
ref={ref}
|
||||||
|
className={styles.input}
|
||||||
|
placeholder={placeholder}
|
||||||
|
readOnly={true}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
7
src/components/MarkTypeStrategy/DateTime/index.tsx
Normal file
7
src/components/MarkTypeStrategy/DateTime/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MarkStrategy } from '../MarkStrategy'
|
||||||
|
import { MarkInputDateTime } from './Input'
|
||||||
|
|
||||||
|
export const DateTimeStrategy: MarkStrategy = {
|
||||||
|
input: MarkInputDateTime,
|
||||||
|
render: ({ value }) => <>{value}</>
|
||||||
|
}
|
20
src/components/MarkTypeStrategy/FullName/Input.tsx
Normal file
20
src/components/MarkTypeStrategy/FullName/Input.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useDidMount } from '../../../hooks'
|
||||||
|
import { useLocalStorage } from '../../../hooks/useLocalStorage'
|
||||||
|
import { MarkInputProps } from '../MarkStrategy'
|
||||||
|
import { MarkInputText } from '../Text/Input'
|
||||||
|
|
||||||
|
export const MarkInputFullName = (props: MarkInputProps) => {
|
||||||
|
const [fullName, setFullName] = useLocalStorage('mark-fullname', '')
|
||||||
|
useDidMount(() => {
|
||||||
|
props.handler(fullName)
|
||||||
|
})
|
||||||
|
return MarkInputText({
|
||||||
|
...props,
|
||||||
|
placeholder: 'Full Name',
|
||||||
|
value: fullName,
|
||||||
|
handler: (value) => {
|
||||||
|
setFullName(value)
|
||||||
|
props.handler(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
7
src/components/MarkTypeStrategy/FullName/index.tsx
Normal file
7
src/components/MarkTypeStrategy/FullName/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MarkStrategy } from '../MarkStrategy'
|
||||||
|
import { MarkInputFullName } from './Input'
|
||||||
|
|
||||||
|
export const FullNameStrategy: MarkStrategy = {
|
||||||
|
input: MarkInputFullName,
|
||||||
|
render: ({ value }) => <>{value}</>
|
||||||
|
}
|
20
src/components/MarkTypeStrategy/JobTitle/Input.tsx
Normal file
20
src/components/MarkTypeStrategy/JobTitle/Input.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useDidMount } from '../../../hooks'
|
||||||
|
import { useLocalStorage } from '../../../hooks/useLocalStorage'
|
||||||
|
import { MarkInputProps } from '../MarkStrategy'
|
||||||
|
import { MarkInputText } from '../Text/Input'
|
||||||
|
|
||||||
|
export const MarkInputJobTitle = (props: MarkInputProps) => {
|
||||||
|
const [jobTitle, setjobTitle] = useLocalStorage('mark-jobtitle', '')
|
||||||
|
useDidMount(() => {
|
||||||
|
props.handler(jobTitle)
|
||||||
|
})
|
||||||
|
return MarkInputText({
|
||||||
|
...props,
|
||||||
|
placeholder: 'Job Title',
|
||||||
|
value: jobTitle,
|
||||||
|
handler: (value) => {
|
||||||
|
setjobTitle(value)
|
||||||
|
props.handler(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
7
src/components/MarkTypeStrategy/JobTitle/index.tsx
Normal file
7
src/components/MarkTypeStrategy/JobTitle/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MarkStrategy } from '../MarkStrategy'
|
||||||
|
import { MarkInputJobTitle } from './Input'
|
||||||
|
|
||||||
|
export const JobTitleStrategy: MarkStrategy = {
|
||||||
|
input: MarkInputJobTitle,
|
||||||
|
render: ({ value }) => <>{value}</>
|
||||||
|
}
|
@ -2,6 +2,9 @@ import { MarkType } from '../../types/drawing'
|
|||||||
import { CurrentUserMark, Mark } from '../../types/mark'
|
import { CurrentUserMark, Mark } from '../../types/mark'
|
||||||
import { TextStrategy } from './Text'
|
import { TextStrategy } from './Text'
|
||||||
import { SignatureStrategy } from './Signature'
|
import { SignatureStrategy } from './Signature'
|
||||||
|
import { FullNameStrategy } from './FullName'
|
||||||
|
import { JobTitleStrategy } from './JobTitle'
|
||||||
|
import { DateTimeStrategy } from './DateTime'
|
||||||
|
|
||||||
export interface MarkInputProps {
|
export interface MarkInputProps {
|
||||||
value: string
|
value: string
|
||||||
@ -28,5 +31,8 @@ export type MarkStrategies = {
|
|||||||
|
|
||||||
export const MARK_TYPE_CONFIG: MarkStrategies = {
|
export const MARK_TYPE_CONFIG: MarkStrategies = {
|
||||||
[MarkType.TEXT]: TextStrategy,
|
[MarkType.TEXT]: TextStrategy,
|
||||||
[MarkType.SIGNATURE]: SignatureStrategy
|
[MarkType.SIGNATURE]: SignatureStrategy,
|
||||||
|
[MarkType.FULLNAME]: FullNameStrategy,
|
||||||
|
[MarkType.JOBTITLE]: JobTitleStrategy,
|
||||||
|
[MarkType.DATETIME]: DateTimeStrategy
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,4 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,7 @@ export const useAuth = () => {
|
|||||||
* method will be chosen (extension or keys)
|
* method will be chosen (extension or keys)
|
||||||
*
|
*
|
||||||
* @param pubkey of the user trying to login
|
* @param pubkey of the user trying to login
|
||||||
* @returns url to redirect if authentication successfull
|
* @returns url to redirect if user has no relays set
|
||||||
* or error if otherwise
|
|
||||||
*/
|
*/
|
||||||
const authAndGetMetadataAndRelaysMap = useCallback(
|
const authAndGetMetadataAndRelaysMap = useCallback(
|
||||||
async (pubkey: string) => {
|
async (pubkey: string) => {
|
||||||
@ -108,7 +107,7 @@ export const useAuth = () => {
|
|||||||
dispatch(setRelayMapAction(relayMap))
|
dispatch(setRelayMapAction(relayMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
return appPrivateRoutes.homePage
|
return
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
dispatch,
|
dispatch,
|
||||||
|
72
src/hooks/useLocalStorage.ts
Normal file
72
src/hooks/useLocalStorage.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import {
|
||||||
|
getLocalStorageItem,
|
||||||
|
mergeWithInitialValue,
|
||||||
|
removeLocalStorageItem,
|
||||||
|
setLocalStorageItem
|
||||||
|
} from '../utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to the Browser's storage event. Get the new value if any of the tabs changes it.
|
||||||
|
* @param callback - function to be called when the storage event is triggered
|
||||||
|
* @returns clean up function
|
||||||
|
*/
|
||||||
|
const useLocalStorageSubscribe = (callback: () => void) => {
|
||||||
|
window.addEventListener('storage', callback)
|
||||||
|
return () => window.removeEventListener('storage', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLocalStorage<T>(
|
||||||
|
key: string,
|
||||||
|
initialValue: T
|
||||||
|
): [T, React.Dispatch<React.SetStateAction<T>>] {
|
||||||
|
const getSnapshot = () => {
|
||||||
|
// Get the stored value
|
||||||
|
const storedValue = getLocalStorageItem(key, initialValue)
|
||||||
|
|
||||||
|
// Parse the value
|
||||||
|
const parsedStoredValue = JSON.parse(storedValue)
|
||||||
|
|
||||||
|
// Merge the default and the stored in case some of the required fields are missing
|
||||||
|
return JSON.stringify(
|
||||||
|
mergeWithInitialValue(parsedStoredValue, initialValue)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://react.dev/reference/react/useSyncExternalStore
|
||||||
|
// Returns the snapshot of the data and subscribes to the storage event
|
||||||
|
const data = React.useSyncExternalStore(useLocalStorageSubscribe, getSnapshot)
|
||||||
|
|
||||||
|
// Takes the value or a function that returns the value and updates the local storage
|
||||||
|
const setState: React.Dispatch<React.SetStateAction<T>> = React.useCallback(
|
||||||
|
(v: React.SetStateAction<T>) => {
|
||||||
|
try {
|
||||||
|
const nextState =
|
||||||
|
typeof v === 'function'
|
||||||
|
? (v as (prevState: T) => T)(JSON.parse(data))
|
||||||
|
: v
|
||||||
|
|
||||||
|
if (nextState === undefined || nextState === null) {
|
||||||
|
removeLocalStorageItem(key)
|
||||||
|
} else {
|
||||||
|
setLocalStorageItem(key, JSON.stringify(nextState))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data, key]
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// Set local storage only when it's empty
|
||||||
|
const data = window.localStorage.getItem(key)
|
||||||
|
if (data === null) {
|
||||||
|
setLocalStorageItem(key, JSON.stringify(initialValue))
|
||||||
|
}
|
||||||
|
}, [key, initialValue])
|
||||||
|
|
||||||
|
const memoized = useMemo(() => JSON.parse(data) as T, [data])
|
||||||
|
|
||||||
|
return [memoized, setState]
|
||||||
|
}
|
@ -12,7 +12,9 @@ import {
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
|
finalizeEvent,
|
||||||
generateSecretKey,
|
generateSecretKey,
|
||||||
|
getEventHash,
|
||||||
getPublicKey,
|
getPublicKey,
|
||||||
kinds,
|
kinds,
|
||||||
UnsignedEvent
|
UnsignedEvent
|
||||||
@ -40,17 +42,21 @@ import {
|
|||||||
getDTagForUserAppData,
|
getDTagForUserAppData,
|
||||||
getUserAppDataFromBlossom,
|
getUserAppDataFromBlossom,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
|
nip44Encrypt,
|
||||||
parseJson,
|
parseJson,
|
||||||
|
randomTimeUpTo2DaysInThePast,
|
||||||
SIGIT_RELAY,
|
SIGIT_RELAY,
|
||||||
unixNow,
|
unixNow,
|
||||||
uploadUserAppDataToBlossom
|
uploadUserAppDataToBlossom
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
|
import { SendDMError, SendDMErrorType } from '../types/errors/SendDMError'
|
||||||
|
|
||||||
export const useNDK = () => {
|
export const useNDK = () => {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const {
|
const {
|
||||||
ndk,
|
ndk,
|
||||||
fetchEvent,
|
fetchEvent,
|
||||||
|
fetchEventFromUserRelays,
|
||||||
fetchEventsFromUserRelays,
|
fetchEventsFromUserRelays,
|
||||||
publish,
|
publish,
|
||||||
getNDKRelayList
|
getNDKRelayList
|
||||||
@ -503,10 +509,139 @@ export const useNDK = () => {
|
|||||||
[ndk, usersPubkey, getNDKRelayList]
|
[ndk, usersPubkey, getNDKRelayList]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modified {@link UnsignedEvent Unsigned Event} that includes an id
|
||||||
|
*
|
||||||
|
* Fields id and created_at are required.
|
||||||
|
* @see {@link UnsignedEvent}
|
||||||
|
* @see {@link https://github.com/nostr-protocol/nips/blob/master/17.md#direct-message-kind}
|
||||||
|
*/
|
||||||
|
type UnsignedEventWithId = UnsignedEvent & {
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
const sendPrivateDirectMessage = useCallback(
|
||||||
|
async (message: string, receiver: string, subject?: string) => {
|
||||||
|
if (!receiver) throw new SendDMError(SendDMErrorType.MISSING_RECIEVER)
|
||||||
|
|
||||||
|
// Get the direct message preferred relays list
|
||||||
|
// https://github.com/nostr-protocol/nips/blob/master/17.md#publishing
|
||||||
|
const preferredRelaysListEvent = await fetchEventFromUserRelays(
|
||||||
|
{
|
||||||
|
kinds: [NDKKind.DirectMessageReceiveRelayList],
|
||||||
|
authors: [receiver]
|
||||||
|
},
|
||||||
|
receiver,
|
||||||
|
UserRelaysType.Read
|
||||||
|
)
|
||||||
|
|
||||||
|
const isRelayTag = (tag: string[]): boolean => tag[0] === 'relay'
|
||||||
|
const finalRelaysList: string[] = []
|
||||||
|
if (preferredRelaysListEvent) {
|
||||||
|
const preferredRelaysList = preferredRelaysListEvent.tags
|
||||||
|
.filter((t) => isRelayTag(t))
|
||||||
|
.map((t) => t[1])
|
||||||
|
|
||||||
|
finalRelaysList.push(...preferredRelaysList)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!finalRelaysList.length) {
|
||||||
|
// Get receiver's read relay list
|
||||||
|
const ndkRelayList = await getNDKRelayList(receiver).catch((err) => {
|
||||||
|
// Log an error if retrieving relay list metadata fails
|
||||||
|
console.log(
|
||||||
|
`An error occurred while finding relay list metadata for ${hexToNpub(receiver)}`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
if (ndkRelayList?.readRelayUrls) {
|
||||||
|
finalRelaysList.push(...ndkRelayList.readRelayUrls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!finalRelaysList.includes(SIGIT_RELAY)) {
|
||||||
|
finalRelaysList.push(SIGIT_RELAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate "sender"
|
||||||
|
const senderSecret = generateSecretKey()
|
||||||
|
const senderPubkey = getPublicKey(senderSecret)
|
||||||
|
|
||||||
|
// Prepare tags for the message
|
||||||
|
const tags: string[][] = [['p', receiver]]
|
||||||
|
|
||||||
|
// Conversation title
|
||||||
|
if (subject) tags.push(['subject', subject])
|
||||||
|
|
||||||
|
// Create private DM event containing the message and relevant metadata
|
||||||
|
// TODO: kinds.PrivateDirectMessage (unavailabe in nostr-tools 10/10/2024 at v2.7.0)
|
||||||
|
const dm: UnsignedEventWithId = {
|
||||||
|
pubkey: senderPubkey,
|
||||||
|
created_at: unixNow(),
|
||||||
|
kind: 14,
|
||||||
|
tags,
|
||||||
|
content: message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the hash based on the UnverifiedEvent
|
||||||
|
dm.id = getEventHash(dm)
|
||||||
|
|
||||||
|
// Encrypt the private dm using the sender secret and the receiver's public key
|
||||||
|
const encryptedDm = nip44Encrypt(dm, senderSecret, receiver)
|
||||||
|
if (!encryptedDm) {
|
||||||
|
throw new SendDMError(SendDMErrorType.ENCRYPTION_FAILED, {
|
||||||
|
context: {
|
||||||
|
receiver,
|
||||||
|
message,
|
||||||
|
kind: dm.kind
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seal the message
|
||||||
|
// TODO: kinds.Seal (unavailabe in nostr-tools 10/10/2024 at v2.7.0)
|
||||||
|
const sealedMessage: UnsignedEvent = {
|
||||||
|
kind: 13, // seal
|
||||||
|
pubkey: senderPubkey,
|
||||||
|
content: encryptedDm,
|
||||||
|
created_at: randomTimeUpTo2DaysInThePast(),
|
||||||
|
tags: [] // no tags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize and sign the sealed event
|
||||||
|
const finalizedSeal = finalizeEvent(sealedMessage, senderSecret)
|
||||||
|
|
||||||
|
// Encrypt the seal and gift wrap
|
||||||
|
const finalizedGiftWrap = createWrap(finalizedSeal, receiver)
|
||||||
|
|
||||||
|
const ndkEvent = new NDKEvent(ndk, finalizedGiftWrap)
|
||||||
|
|
||||||
|
// Publish the finalized gift wrap event (the encrypted DM) to the relays
|
||||||
|
const publishedOnRelays = await ndkEvent.publish(
|
||||||
|
NDKRelaySet.fromRelayUrls(finalRelaysList, ndk, true)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle cases where publishing to the relays failed
|
||||||
|
if (publishedOnRelays.size === 0) {
|
||||||
|
throw new SendDMError(SendDMErrorType.ENCRYPTION_FAILED, {
|
||||||
|
context: {
|
||||||
|
receiver,
|
||||||
|
count: publishedOnRelays.size
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true indicating that the DM was successfully sent
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
[fetchEventFromUserRelays, getNDKRelayList, ndk]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getUsersAppData,
|
getUsersAppData,
|
||||||
subscribeForSigits,
|
subscribeForSigits,
|
||||||
updateUsersAppData,
|
updateUsersAppData,
|
||||||
sendNotification
|
sendNotification,
|
||||||
|
sendPrivateDirectMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { Outlet, useNavigate, useSearchParams } from 'react-router-dom'
|
import { Outlet, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools'
|
import { getPublicKey, nip19 } from 'nostr-tools'
|
||||||
|
|
||||||
import { init as initNostrLogin } from 'nostr-login'
|
import { init as initNostrLogin } from 'nostr-login'
|
||||||
import { NostrLoginAuthOptions } from 'nostr-login/dist/types'
|
import { NostrLoginAuthOptions } from 'nostr-login/dist/types'
|
||||||
|
|
||||||
import { AppBar } from '../components/AppBar/AppBar'
|
import { AppBar } from '../components/AppBar/AppBar'
|
||||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||||
|
|
||||||
import { NostrController } from '../controllers'
|
import { NostrController } from '../controllers'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
@ -19,7 +14,6 @@ import {
|
|||||||
useNDK,
|
useNDK,
|
||||||
useNDKContext
|
useNDKContext
|
||||||
} from '../hooks'
|
} from '../hooks'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
restoreState,
|
restoreState,
|
||||||
setUserProfile,
|
setUserProfile,
|
||||||
@ -30,9 +24,7 @@ import {
|
|||||||
setUserRobotImage
|
setUserRobotImage
|
||||||
} from '../store/actions'
|
} from '../store/actions'
|
||||||
import { LoginMethod } from '../store/auth/types'
|
import { LoginMethod } from '../store/auth/types'
|
||||||
|
|
||||||
import { getRoboHashPicture, loadState } from '../utils'
|
import { getRoboHashPicture, loadState } from '../utils'
|
||||||
|
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
|
|
||||||
export const MainLayout = () => {
|
export const MainLayout = () => {
|
||||||
@ -53,29 +45,32 @@ export const MainLayout = () => {
|
|||||||
// Ref to track if `subscribeForSigits` has been called
|
// Ref to track if `subscribeForSigits` has been called
|
||||||
const hasSubscribed = useRef(false)
|
const hasSubscribed = useRef(false)
|
||||||
|
|
||||||
const navigateAfterLogin = (path: string) => {
|
const navigateAfterLogin = useCallback(
|
||||||
const callbackPath = searchParams.get('callbackPath')
|
(path: string | undefined) => {
|
||||||
|
const isCallback = window.location.hash.startsWith('#/?callbackPath=')
|
||||||
if (callbackPath) {
|
if (isCallback) {
|
||||||
// base64 decoded path
|
const path = atob(window.location.hash.replace('#/?callbackPath=', ''))
|
||||||
const path = atob(callbackPath)
|
setSearchParams((prev) => {
|
||||||
navigate(path)
|
prev.delete('callbackPath')
|
||||||
return
|
return prev
|
||||||
}
|
})
|
||||||
|
navigate(path)
|
||||||
navigate(path)
|
return
|
||||||
}
|
}
|
||||||
|
if (path) navigate(path)
|
||||||
|
},
|
||||||
|
[navigate, setSearchParams]
|
||||||
|
)
|
||||||
|
|
||||||
const login = useCallback(async () => {
|
const login = useCallback(async () => {
|
||||||
dispatch(updateLoginMethod(LoginMethod.nostrLogin))
|
try {
|
||||||
|
dispatch(updateLoginMethod(LoginMethod.nostrLogin))
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
const pubkey = await nostrController.capturePublicKey()
|
const pubkey = await nostrController.capturePublicKey()
|
||||||
|
const redirectPath = await authAndGetMetadataAndRelaysMap(pubkey)
|
||||||
const redirectPath = await authAndGetMetadataAndRelaysMap(pubkey)
|
|
||||||
|
|
||||||
if (redirectPath) {
|
|
||||||
navigateAfterLogin(redirectPath)
|
navigateAfterLogin(redirectPath)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error occured during login`, error)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dispatch])
|
}, [dispatch])
|
||||||
|
@ -45,7 +45,12 @@ import {
|
|||||||
uploadToFileStorage,
|
uploadToFileStorage,
|
||||||
DEFAULT_TOOLBOX,
|
DEFAULT_TOOLBOX,
|
||||||
settleAllFullfilfedPromises,
|
settleAllFullfilfedPromises,
|
||||||
uploadMetaToFileStorage
|
parseNostrEvent,
|
||||||
|
uploadMetaToFileStorage,
|
||||||
|
clearSigitDraft,
|
||||||
|
saveSigitDraft,
|
||||||
|
getSigitDraft,
|
||||||
|
timeout
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import fileListStyles from '../../components/FileList/style.module.scss'
|
import fileListStyles from '../../components/FileList/style.module.scss'
|
||||||
@ -72,13 +77,14 @@ import { getSigitFile, SigitFile } from '../../utils/file.ts'
|
|||||||
import { generateTimestamp } from '../../utils/opentimestamps.ts'
|
import { generateTimestamp } from '../../utils/opentimestamps.ts'
|
||||||
import { Autocomplete } from '@mui/material'
|
import { Autocomplete } from '@mui/material'
|
||||||
import _, { truncate } from 'lodash'
|
import _, { truncate } from 'lodash'
|
||||||
|
import { SendDMError } from '../../types/errors/SendDMError.ts'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { AvatarIconButton } from '../../components/UserAvatarIconButton'
|
import { AvatarIconButton } from '../../components/UserAvatarIconButton'
|
||||||
import { NDKUserProfile, NostrEvent } from '@nostr-dev-kit/ndk'
|
import { NDKUserProfile, NostrEvent } from '@nostr-dev-kit/ndk'
|
||||||
import { useNDKContext } from '../../hooks/useNDKContext.ts'
|
import { useNDKContext } from '../../hooks/useNDKContext.ts'
|
||||||
import { useNDK } from '../../hooks/useNDK.ts'
|
import { useNDK } from '../../hooks/useNDK.ts'
|
||||||
import { useImmer } from 'use-immer'
|
|
||||||
import { ButtonUnderline } from '../../components/ButtonUnderline/index.tsx'
|
import { ButtonUnderline } from '../../components/ButtonUnderline/index.tsx'
|
||||||
|
import { TimeoutError } from '../../types/errors/TimeoutError.ts'
|
||||||
|
|
||||||
type FoundUser = NostrEvent & { npub: string }
|
type FoundUser = NostrEvent & { npub: string }
|
||||||
|
|
||||||
@ -86,7 +92,8 @@ export const CreatePage = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { findMetadata, fetchEventsFromUserRelays } = useNDKContext()
|
const { findMetadata, fetchEventsFromUserRelays } = useNDKContext()
|
||||||
const { updateUsersAppData, sendNotification } = useNDK()
|
const { updateUsersAppData, sendNotification, sendPrivateDirectMessage } =
|
||||||
|
useNDK()
|
||||||
|
|
||||||
const { uploadedFiles } = location.state || {}
|
const { uploadedFiles } = location.state || {}
|
||||||
const [currentFile, setCurrentFile] = useState<File>()
|
const [currentFile, setCurrentFile] = useState<File>()
|
||||||
@ -97,7 +104,9 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`)
|
const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`)
|
||||||
|
|
||||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([...uploadedFiles])
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([
|
||||||
|
...(uploadedFiles || [])
|
||||||
|
])
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const handleUploadButtonClick = () => {
|
const handleUploadButtonClick = () => {
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
@ -123,7 +132,7 @@ export const CreatePage = () => {
|
|||||||
[key: string]: NDKUserProfile
|
[key: string]: NDKUserProfile
|
||||||
}>({})
|
}>({})
|
||||||
|
|
||||||
const [drawnFiles, updateDrawnFiles] = useImmer<SigitFile[]>([])
|
const [drawnFiles, setDrawnFiles] = useState<SigitFile[]>([])
|
||||||
const [parsingPdf, setIsParsing] = useState<boolean>(false)
|
const [parsingPdf, setIsParsing] = useState<boolean>(false)
|
||||||
|
|
||||||
const searchFieldRef = useRef<HTMLInputElement>(null)
|
const searchFieldRef = useRef<HTMLInputElement>(null)
|
||||||
@ -162,8 +171,8 @@ export const CreatePage = () => {
|
|||||||
return pubkey
|
return pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearchUsers = async (searchValue?: string) => {
|
const handleSearchUsers = async () => {
|
||||||
const searchString = searchValue || userSearchInput || undefined
|
const searchString = userSearchInput || undefined
|
||||||
|
|
||||||
if (!searchString) return
|
if (!searchString) return
|
||||||
|
|
||||||
@ -171,14 +180,17 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
const searchTerm = searchString.trim()
|
const searchTerm = searchString.trim()
|
||||||
|
|
||||||
fetchEventsFromUserRelays(
|
Promise.race([
|
||||||
{
|
fetchEventsFromUserRelays(
|
||||||
kinds: [0],
|
{
|
||||||
search: searchTerm
|
kinds: [0],
|
||||||
},
|
search: searchTerm
|
||||||
usersPubkey,
|
},
|
||||||
UserRelaysType.Write
|
usersPubkey,
|
||||||
)
|
UserRelaysType.Write
|
||||||
|
),
|
||||||
|
timeout(30000)
|
||||||
|
])
|
||||||
.then((events) => {
|
.then((events) => {
|
||||||
const nostrEvents = events.map((event) => event.rawEvent())
|
const nostrEvents = events.map((event) => event.rawEvent())
|
||||||
|
|
||||||
@ -216,6 +228,9 @@ export const CreatePage = () => {
|
|||||||
toast.info('No user found with the provided search term')
|
toast.info('No user found with the provided search term')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
if (error instanceof TimeoutError) {
|
||||||
|
toast.error('Search timed out. Please try again.')
|
||||||
|
}
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@ -245,22 +260,23 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
// If pasted user npub of nip05 is present, we just add the user to the counterparts list
|
// If pasted user npub of nip05 is present, we just add the user to the counterparts list
|
||||||
if (pastedUserNpubOrNip05) {
|
if (pastedUserNpubOrNip05) {
|
||||||
setUserInput(pastedUserNpubOrNip05)
|
setUserInput(pastedUserNpubOrNip05.trim())
|
||||||
setPastedUserNpubOrNip05(undefined)
|
setPastedUserNpubOrNip05(undefined)
|
||||||
} else {
|
} else {
|
||||||
// Otherwize if search already provided some results, user must manually click the search button
|
// Otherwise if search already provided some results, user must manually click the search button
|
||||||
if (!foundUsers.length) {
|
if (!foundUsers.length) {
|
||||||
|
const searchTerm = userSearchInput.trim()
|
||||||
// If it's NIP05 (includes @ or is a valid domain) send request to .well-known
|
// If it's NIP05 (includes @ or is a valid domain) send request to .well-known
|
||||||
const domainRegex = /^[a-zA-Z0-9@.-]+\.[a-zA-Z]{2,}$/
|
const domainRegex = /^[a-zA-Z0-9@.-]+\.[a-zA-Z]{2,}$/
|
||||||
if (domainRegex.test(userSearchInput)) {
|
if (searchTerm.startsWith('_@') || domainRegex.test(searchTerm)) {
|
||||||
setSearchUsersLoading(true)
|
setSearchUsersLoading(true)
|
||||||
|
|
||||||
const pubkey = await handleSearchUserNip05(userSearchInput)
|
const pubkey = await handleSearchUserNip05(searchTerm)
|
||||||
|
|
||||||
setSearchUsersLoading(false)
|
setSearchUsersLoading(false)
|
||||||
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
setUserInput(userSearchInput)
|
setUserInput(searchTerm)
|
||||||
} else {
|
} else {
|
||||||
toast.error(`No user found with the NIP05: ${userSearchInput}`)
|
toast.error(`No user found with the NIP05: ${userSearchInput}`)
|
||||||
}
|
}
|
||||||
@ -283,27 +299,29 @@ export const CreatePage = () => {
|
|||||||
selectedFiles,
|
selectedFiles,
|
||||||
getSigitFile
|
getSigitFile
|
||||||
)
|
)
|
||||||
updateDrawnFiles((draft) => {
|
setDrawnFiles((prev) => {
|
||||||
|
const clone = _.cloneDeep(prev)
|
||||||
// Existing files are untouched
|
// Existing files are untouched
|
||||||
|
|
||||||
// Handle removed files
|
// Handle removed files
|
||||||
// Remove in reverse to avoid index issues
|
// Remove in reverse to avoid index issues
|
||||||
for (let i = draft.length - 1; i >= 0; i--) {
|
for (let i = clone.length - 1; i >= 0; i--) {
|
||||||
if (
|
if (
|
||||||
!files.some(
|
!files.some(
|
||||||
(f) => f.name === draft[i].name && f.size === draft[i].size
|
(f) => f.name === clone[i].name && f.size === clone[i].size
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
draft.splice(i, 1)
|
clone.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new files
|
// Add new files
|
||||||
files.forEach((f) => {
|
files.forEach((f) => {
|
||||||
if (!draft.some((d) => d.name === f.name && d.size === f.size)) {
|
if (!clone.some((d) => d.name === f.name && d.size === f.size)) {
|
||||||
draft.push(f)
|
clone.push(f)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return clone
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +331,52 @@ export const CreatePage = () => {
|
|||||||
setIsParsing(false)
|
setIsParsing(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [selectedFiles, updateDrawnFiles])
|
}, [selectedFiles])
|
||||||
|
|
||||||
|
const [draftEnabled, setDraftEnabled] = useState(true)
|
||||||
|
useEffect(() => {
|
||||||
|
// Only proceed if we have no uploaded files
|
||||||
|
if (uploadedFiles?.length ?? 0) return
|
||||||
|
|
||||||
|
getSigitDraft().then((draft) => {
|
||||||
|
if (draft) {
|
||||||
|
setSelectedFiles(draft.files)
|
||||||
|
setDrawnFiles((prev) => {
|
||||||
|
const clone = _.cloneDeep(prev)
|
||||||
|
clone.splice(0, clone.length, ...draft.files)
|
||||||
|
return clone
|
||||||
|
})
|
||||||
|
setUsers(draft.users)
|
||||||
|
setTitle(draft.title)
|
||||||
|
|
||||||
|
// After loading draft clear it
|
||||||
|
clearSigitDraft()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [uploadedFiles])
|
||||||
|
useEffect(() => {
|
||||||
|
if (draftEnabled) {
|
||||||
|
saveSigitDraft({
|
||||||
|
title,
|
||||||
|
users,
|
||||||
|
lastUpdated: Date.now(),
|
||||||
|
files: drawnFiles
|
||||||
|
}).catch((error) => {
|
||||||
|
if (
|
||||||
|
error instanceof DOMException &&
|
||||||
|
error.name === 'QuotaExceededError'
|
||||||
|
) {
|
||||||
|
// Disable draft if we hit size error
|
||||||
|
setDraftEnabled(false)
|
||||||
|
console.warn(
|
||||||
|
'Draft functionality disabled temporarily. File size exceeds local storage limit.'
|
||||||
|
)
|
||||||
|
clearSigitDraft()
|
||||||
|
}
|
||||||
|
// Ignore other errors
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [draftEnabled, drawnFiles, title, users])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the drawing tool
|
* Changes the drawing tool
|
||||||
@ -411,7 +474,7 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
setUserSearchInput('')
|
setUserSearchInput('')
|
||||||
|
|
||||||
if (input.startsWith('npub')) {
|
if (input.startsWith('npub1')) {
|
||||||
return handleAddNpubUser(input)
|
return handleAddNpubUser(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,7 +567,7 @@ export const CreatePage = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
updateDrawnFiles(drawnFilesCopy)
|
setDrawnFiles(drawnFilesCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -926,7 +989,29 @@ export const CreatePage = () => {
|
|||||||
toast.error('Failed to publish notifications')
|
toast.error('Failed to publish notifications')
|
||||||
})
|
})
|
||||||
|
|
||||||
const isFirstSigner = signers[0].pubkey === usersPubkey
|
const isFirstSigner =
|
||||||
|
signers.length > 0 && signers[0].pubkey === usersPubkey
|
||||||
|
|
||||||
|
// Don't send notification if creator is next signer
|
||||||
|
if (signers.length > 0 && !isFirstSigner) {
|
||||||
|
// Send DM to the next signer
|
||||||
|
setLoadingSpinnerDesc('Sending DMs')
|
||||||
|
const nextSigner = signers[0].pubkey
|
||||||
|
const createSignatureEvent = parseNostrEvent(meta.createSignature)
|
||||||
|
const { id } = createSignatureEvent
|
||||||
|
try {
|
||||||
|
await sendPrivateDirectMessage(
|
||||||
|
`Sigit created, visit ${window.location.origin}/#/sign/${id}`,
|
||||||
|
npubToHex(nextSigner)!
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SendDMError) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isFirstSigner) {
|
if (isFirstSigner) {
|
||||||
navigate(appPrivateRoutes.sign, { state: { meta } })
|
navigate(appPrivateRoutes.sign, { state: { meta } })
|
||||||
} else {
|
} else {
|
||||||
@ -940,6 +1025,7 @@ export const CreatePage = () => {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
clearSigitDraft()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1017,6 +1103,7 @@ export const CreatePage = () => {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
clearSigitDraft()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1034,17 +1121,13 @@ export const CreatePage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Seems like it's npub format
|
// Seems like it's npub format
|
||||||
if (value.startsWith('npub')) {
|
if (value.trim().startsWith('npub1')) {
|
||||||
// We will try to convert npub to hex and if it's successfull that means
|
setPastedUserNpubOrNip05(value.trim())
|
||||||
// npub is valid
|
} else if (value.trim().startsWith('nsec1')) {
|
||||||
const validHexPubkey = npubToHex(value)
|
toast.warn('Oops - never paste your nsec into a website! Key deleted.')
|
||||||
|
if (searchFieldRef.current) searchFieldRef.current.value = ''
|
||||||
if (validHexPubkey) {
|
setUserSearchInput('')
|
||||||
// Arm the manual user npub add after enter is hit, we don't want to trigger search
|
return
|
||||||
setPastedUserNpubOrNip05(value)
|
|
||||||
} else {
|
|
||||||
disarmAddOnEnter()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Disarm the add user on enter hit, and trigger search after 1 second
|
// Disarm the add user on enter hit, and trigger search after 1 second
|
||||||
disarmAddOnEnter()
|
disarmAddOnEnter()
|
||||||
@ -1204,7 +1287,7 @@ export const CreatePage = () => {
|
|||||||
{!pastedUserNpubOrNip05 ? (
|
{!pastedUserNpubOrNip05 ? (
|
||||||
<Button
|
<Button
|
||||||
disabled={!userSearchInput || searchUsersLoading}
|
disabled={!userSearchInput || searchUsersLoading}
|
||||||
onClick={() => handleSearchUsers()}
|
onClick={handleSearchUsers}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
aria-label="Add"
|
aria-label="Add"
|
||||||
className={styles.counterpartToggleButton}
|
className={styles.counterpartToggleButton}
|
||||||
@ -1218,7 +1301,7 @@ export const CreatePage = () => {
|
|||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserInput(userSearchInput)
|
setUserInput(userSearchInput.trim())
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
aria-label="Add"
|
aria-label="Add"
|
||||||
@ -1285,7 +1368,7 @@ export const CreatePage = () => {
|
|||||||
userProfiles={userProfiles}
|
userProfiles={userProfiles}
|
||||||
selectedTool={selectedTool}
|
selectedTool={selectedTool}
|
||||||
sigitFiles={drawnFiles}
|
sigitFiles={drawnFiles}
|
||||||
updateSigitFiles={updateDrawnFiles}
|
setSigitFiles={setDrawnFiles}
|
||||||
/>
|
/>
|
||||||
{parsingPdf && <LoadingSpinner variant="small" />}
|
{parsingPdf && <LoadingSpinner variant="small" />}
|
||||||
</StickySideColumns>
|
</StickySideColumns>
|
||||||
|
@ -2,7 +2,7 @@ import { Button, TextField } from '@mui/material'
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { useAppSelector } from '../../hooks'
|
import { useAppSelector, useDidMount } from '../../hooks'
|
||||||
import { appPrivateRoutes } from '../../routes'
|
import { appPrivateRoutes } from '../../routes'
|
||||||
import { Meta } from '../../types'
|
import { Meta } from '../../types'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
@ -13,12 +13,15 @@ import { useDropzone } from 'react-dropzone'
|
|||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import {
|
import {
|
||||||
|
clearSigitDraft,
|
||||||
extractSigitCardDisplayInfo,
|
extractSigitCardDisplayInfo,
|
||||||
|
hasSigitDraft,
|
||||||
navigateFromZip,
|
navigateFromZip,
|
||||||
SigitCardDisplayInfo,
|
SigitCardDisplayInfo,
|
||||||
SigitStatus
|
SigitStatus
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { Footer } from '../../components/Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
|
import { LocalDraftSigit } from '../../components/DisplaySigit/LocalDraftSigit'
|
||||||
|
|
||||||
// Unsupported Filter options are commented
|
// Unsupported Filter options are commented
|
||||||
const FILTERS = [
|
const FILTERS = [
|
||||||
@ -44,6 +47,12 @@ export const HomePage = () => {
|
|||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const q = searchParams.get('q') ?? ''
|
const q = searchParams.get('q') ?? ''
|
||||||
|
|
||||||
|
const [showDraft, setShowDraft] = useState<boolean>(false)
|
||||||
|
useDidMount(async () => {
|
||||||
|
// Check if draft exists and add link to direct
|
||||||
|
setShowDraft(hasSigitDraft())
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const searchInput = document.getElementById('q') as HTMLInputElement | null
|
const searchInput = document.getElementById('q') as HTMLInputElement | null
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
@ -152,7 +161,7 @@ export const HomePage = () => {
|
|||||||
meta={sigits[key]}
|
meta={sigits[key]}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
} else {
|
} else if (!showDraft) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.noResults}>
|
<div className={styles.noResults}>
|
||||||
<p>No results</p>
|
<p>No results</p>
|
||||||
@ -260,7 +269,17 @@ export const HomePage = () => {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className={styles.submissions}>{renderSubmissions()}</div>
|
<div className={styles.submissions}>
|
||||||
|
{showDraft && (
|
||||||
|
<LocalDraftSigit
|
||||||
|
handleDraftDelete={() => {
|
||||||
|
clearSigitDraft()
|
||||||
|
setShowDraft(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{renderSubmissions()}
|
||||||
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { Box, Button } from '@mui/material'
|
import { Box, Button } from '@mui/material'
|
||||||
import { useEffect } from 'react'
|
import { Outlet } from 'react-router-dom'
|
||||||
import { Outlet, useLocation } from 'react-router-dom'
|
|
||||||
import { saveVisitedLink } from '../../utils'
|
|
||||||
import { CardComponent } from '../../components/Landing/CardComponent/CardComponent'
|
import { CardComponent } from '../../components/Landing/CardComponent/CardComponent'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
@ -20,13 +18,19 @@ import {
|
|||||||
import { FontAwesomeIconStack } from '../../components/FontAwesomeIconStack'
|
import { FontAwesomeIconStack } from '../../components/FontAwesomeIconStack'
|
||||||
import { Footer } from '../../components/Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
|
import { useDidMount } from '../../hooks'
|
||||||
|
|
||||||
export const LandingPage = () => {
|
export const LandingPage = () => {
|
||||||
const location = useLocation()
|
|
||||||
|
|
||||||
const onSignInClick = async () => {
|
const onSignInClick = async () => {
|
||||||
launchNostrLoginDialog()
|
launchNostrLoginDialog()
|
||||||
}
|
}
|
||||||
|
useDidMount(() => {
|
||||||
|
const isCallback = window.location.hash.startsWith('#/?callbackPath=')
|
||||||
|
// Open nostr login if detect callback
|
||||||
|
if (isCallback) {
|
||||||
|
onSignInClick()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const cards = [
|
const cards = [
|
||||||
{
|
{
|
||||||
@ -101,10 +105,6 @@ export const LandingPage = () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
saveVisitedLink(location.pathname, location.search)
|
|
||||||
}, [location])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.background}>
|
<div className={styles.background}>
|
||||||
<div
|
<div
|
||||||
|
@ -13,7 +13,7 @@ import { Footer } from '../../components/Footer/Footer'
|
|||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { useAppSelector } from '../../hooks/store'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
|
|
||||||
import { getProfileSettingsRoute } from '../../routes'
|
import { appPrivateRoutes } from '../../routes'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getProfileUsername,
|
getProfileUsername,
|
||||||
@ -168,7 +168,7 @@ export const ProfilePage = () => {
|
|||||||
<Box className={styles.right}>
|
<Box className={styles.right}>
|
||||||
{isUsersOwnProfile && (
|
{isUsersOwnProfile && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => navigate(getProfileSettingsRoute(pubkey))}
|
onClick={() => navigate(appPrivateRoutes.profileSettings)}
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -1,94 +1,82 @@
|
|||||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle'
|
import AccountCircleIcon from '@mui/icons-material/AccountCircle'
|
||||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'
|
|
||||||
import CachedIcon from '@mui/icons-material/Cached'
|
|
||||||
import RouterIcon from '@mui/icons-material/Router'
|
import RouterIcon from '@mui/icons-material/Router'
|
||||||
import { ListItem, useTheme } from '@mui/material'
|
import { Button } from '@mui/material'
|
||||||
import List from '@mui/material/List'
|
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon'
|
|
||||||
import ListItemText from '@mui/material/ListItemText'
|
|
||||||
import ListSubheader from '@mui/material/ListSubheader'
|
|
||||||
import { useAppSelector } from '../../hooks/store'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
import { Link } from 'react-router-dom'
|
import { NavLink, Outlet, To } from 'react-router-dom'
|
||||||
import { appPrivateRoutes, getProfileSettingsRoute } from '../../routes'
|
import { appPrivateRoutes } from '../../routes'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import { Footer } from '../../components/Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
import ExtensionIcon from '@mui/icons-material/Extension'
|
import ExtensionIcon from '@mui/icons-material/Extension'
|
||||||
import { LoginMethod } from '../../store/auth/types'
|
import { LoginMethod } from '../../store/auth/types'
|
||||||
|
import styles from './style.module.scss'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
export const SettingsPage = () => {
|
const Item = (to: To, icon: ReactNode, label: string) => {
|
||||||
const theme = useTheme()
|
return (
|
||||||
const { usersPubkey, loginMethod } = useAppSelector((state) => state.auth)
|
<NavLink to={to}>
|
||||||
const listItem = (label: string, disabled = false) => {
|
{({ isActive }) => (
|
||||||
return (
|
<Button
|
||||||
<>
|
fullWidth
|
||||||
<ListItemText
|
|
||||||
primary={label}
|
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.text.primary
|
transition: 'ease 0.3s',
|
||||||
|
justifyContent: 'start',
|
||||||
|
gap: '10px',
|
||||||
|
background: 'rgba(76,130,163,0)',
|
||||||
|
color: '#434343',
|
||||||
|
fontWeight: 600,
|
||||||
|
opacity: 0.75,
|
||||||
|
textTransform: 'none',
|
||||||
|
...(isActive
|
||||||
|
? {
|
||||||
|
background: '#447592',
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
'&:hover': {
|
||||||
|
opacity: 0.85,
|
||||||
|
gap: '15px',
|
||||||
|
background: '#5e8eab',
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
variant={'text'}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{!disabled && (
|
export const SettingsLayout = () => {
|
||||||
<ArrowForwardIosIcon
|
const { loginMethod } = useAppSelector((state) => state.auth)
|
||||||
style={{
|
|
||||||
color: theme.palette.action.active,
|
|
||||||
marginRight: -10
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container>
|
<Container>
|
||||||
<List
|
<h2 className={styles.title}>Settings</h2>
|
||||||
sx={{
|
<div className={styles.main}>
|
||||||
width: '100%',
|
<div>
|
||||||
bgcolor: 'background.paper'
|
<aside className={styles.aside}>
|
||||||
}}
|
{Item(
|
||||||
subheader={
|
appPrivateRoutes.profileSettings,
|
||||||
<ListSubheader
|
<AccountCircleIcon />,
|
||||||
sx={{
|
'Profile'
|
||||||
fontSize: '1.5rem',
|
)}
|
||||||
borderBottom: '0.5px solid',
|
{Item(appPrivateRoutes.relays, <RouterIcon />, 'Relays')}
|
||||||
paddingBottom: 2,
|
{loginMethod === LoginMethod.nostrLogin &&
|
||||||
paddingTop: 2,
|
Item(
|
||||||
zIndex: 2
|
appPrivateRoutes.nostrLogin,
|
||||||
}}
|
<ExtensionIcon />,
|
||||||
>
|
'Nostr Login'
|
||||||
Settings
|
)}
|
||||||
</ListSubheader>
|
</aside>
|
||||||
}
|
</div>
|
||||||
>
|
<div className={styles.content}>
|
||||||
<ListItem component={Link} to={getProfileSettingsRoute(usersPubkey!)}>
|
<Outlet />
|
||||||
<ListItemIcon>
|
</div>
|
||||||
<AccountCircleIcon />
|
</div>
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Profile')}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem component={Link} to={appPrivateRoutes.relays}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<RouterIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Relays')}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem component={Link} to={appPrivateRoutes.cacheSettings}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<CachedIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Local Cache')}
|
|
||||||
</ListItem>
|
|
||||||
{loginMethod === LoginMethod.nostrLogin && (
|
|
||||||
<ListItem component={Link} to={appPrivateRoutes.nostrLogin}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<ExtensionIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Nostr Login')}
|
|
||||||
</ListItem>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Container>
|
</Container>
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
|
102
src/pages/settings/cache/index.tsx
vendored
102
src/pages/settings/cache/index.tsx
vendored
@ -1,102 +0,0 @@
|
|||||||
import ClearIcon from '@mui/icons-material/Clear'
|
|
||||||
import InputIcon from '@mui/icons-material/Input'
|
|
||||||
import IosShareIcon from '@mui/icons-material/IosShare'
|
|
||||||
import {
|
|
||||||
List,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
ListSubheader,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
import { localCache } from '../../../services'
|
|
||||||
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
|
||||||
import { Container } from '../../../components/Container'
|
|
||||||
import { Footer } from '../../../components/Footer/Footer'
|
|
||||||
|
|
||||||
export const CacheSettingsPage = () => {
|
|
||||||
const theme = useTheme()
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
||||||
|
|
||||||
const handleClearData = async () => {
|
|
||||||
setIsLoading(true)
|
|
||||||
setLoadingSpinnerDesc('Clearing cache data')
|
|
||||||
localCache
|
|
||||||
.clearCacheData()
|
|
||||||
.then(() => {
|
|
||||||
toast.success('cleared cached data')
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log('An error occurred in clearing cache data', err)
|
|
||||||
toast.error(err.message || 'An error occurred in clearing cache data')
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const listItem = (label: string) => {
|
|
||||||
return (
|
|
||||||
<ListItemText
|
|
||||||
primary={label}
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Container>
|
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
||||||
<List
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
marginTop: 2
|
|
||||||
}}
|
|
||||||
subheader={
|
|
||||||
<ListSubheader
|
|
||||||
sx={{
|
|
||||||
fontSize: '1.5rem',
|
|
||||||
borderBottom: '0.5px solid',
|
|
||||||
paddingBottom: 2,
|
|
||||||
paddingTop: 2,
|
|
||||||
zIndex: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cache Setting
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItemButton disabled>
|
|
||||||
<ListItemIcon>
|
|
||||||
<IosShareIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Export (coming soon)')}
|
|
||||||
</ListItemButton>
|
|
||||||
|
|
||||||
<ListItemButton disabled>
|
|
||||||
<ListItemIcon>
|
|
||||||
<InputIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Import (coming soon)')}
|
|
||||||
</ListItemButton>
|
|
||||||
|
|
||||||
<ListItemButton onClick={handleClearData}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<ClearIcon sx={{ color: theme.palette.error.main }} />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Clear Cache')}
|
|
||||||
</ListItemButton>
|
|
||||||
</List>
|
|
||||||
</Container>
|
|
||||||
<Footer />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -3,11 +3,9 @@ import {
|
|||||||
ListItemButton,
|
ListItemButton,
|
||||||
ListItemIcon,
|
ListItemIcon,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
ListSubheader,
|
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
import { Container } from '../../../components/Container'
|
|
||||||
import PeopleIcon from '@mui/icons-material/People'
|
import PeopleIcon from '@mui/icons-material/People'
|
||||||
import ImportExportIcon from '@mui/icons-material/ImportExport'
|
import ImportExportIcon from '@mui/icons-material/ImportExport'
|
||||||
import { useAppSelector } from '../../../hooks/store'
|
import { useAppSelector } from '../../../hooks/store'
|
||||||
@ -20,59 +18,39 @@ export const NostrLoginPage = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<List>
|
||||||
<List
|
<ListItemButton
|
||||||
sx={{
|
onClick={() => {
|
||||||
width: '100%',
|
launchNostrLoginDialog('switch-account')
|
||||||
bgcolor: 'background.paper'
|
|
||||||
}}
|
}}
|
||||||
subheader={
|
|
||||||
<ListSubheader
|
|
||||||
sx={{
|
|
||||||
fontSize: '1.5rem',
|
|
||||||
borderBottom: '0.5px solid',
|
|
||||||
paddingBottom: 2,
|
|
||||||
paddingTop: 2,
|
|
||||||
zIndex: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Nostr Settings
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PeopleIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={'Nostr Login Accounts'}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
{nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
launchNostrLoginDialog('switch-account')
|
launchNostrLoginDialog('import')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<PeopleIcon />
|
<ImportExportIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={'Nostr Login Accounts'}
|
primary={'Import / Export Keys'}
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.text.primary
|
color: theme.palette.text.primary
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
{nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
|
)}
|
||||||
<ListItemButton
|
</List>
|
||||||
onClick={() => {
|
|
||||||
launchNostrLoginDialog('import')
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<ImportExportIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={'Import / Export Keys'}
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ListItemButton>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Container>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
import { SmartToy } from '@mui/icons-material'
|
import { SmartToy } from '@mui/icons-material'
|
||||||
@ -12,7 +11,6 @@ import {
|
|||||||
InputProps,
|
InputProps,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListSubheader,
|
|
||||||
TextField,
|
TextField,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
@ -28,8 +26,6 @@ import { useAppDispatch, useAppSelector } from '../../../hooks/store'
|
|||||||
|
|
||||||
import { getRoboHashPicture, unixNow } from '../../../utils'
|
import { getRoboHashPicture, unixNow } from '../../../utils'
|
||||||
|
|
||||||
import { Container } from '../../../components/Container'
|
|
||||||
import { Footer } from '../../../components/Footer/Footer'
|
|
||||||
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
||||||
|
|
||||||
import { setUserProfile as updateUserProfile } from '../../../store/actions'
|
import { setUserProfile as updateUserProfile } from '../../../store/actions'
|
||||||
@ -41,10 +37,8 @@ import styles from './style.module.scss'
|
|||||||
export const ProfileSettingsPage = () => {
|
export const ProfileSettingsPage = () => {
|
||||||
const dispatch: Dispatch = useAppDispatch()
|
const dispatch: Dispatch = useAppDispatch()
|
||||||
|
|
||||||
const { npub } = useParams()
|
|
||||||
const { ndk, findMetadata, publish } = useNDKContext()
|
const { ndk, findMetadata, publish } = useNDKContext()
|
||||||
|
|
||||||
const [pubkey, setPubkey] = useState<string>()
|
|
||||||
const [userProfile, setUserProfile] = useState<NDKUserProfile | null>(null)
|
const [userProfile, setUserProfile] = useState<NDKUserProfile | null>(null)
|
||||||
|
|
||||||
const userRobotImage = useAppSelector((state) => state.user.robotImage)
|
const userRobotImage = useAppSelector((state) => state.user.robotImage)
|
||||||
@ -55,27 +49,13 @@ export const ProfileSettingsPage = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
||||||
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
||||||
|
|
||||||
const robotSet = useRef(1)
|
const robotSet = useRef(1)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (npub) {
|
if (usersPubkey && currentUserProfile) {
|
||||||
try {
|
|
||||||
const hexPubkey = nip19.decode(npub).data as string
|
|
||||||
setPubkey(hexPubkey)
|
|
||||||
|
|
||||||
if (hexPubkey === usersPubkey) setIsUsersOwnProfile(true)
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('Error occurred in decoding npub' + error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [npub, usersPubkey])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isUsersOwnProfile && currentUserProfile) {
|
|
||||||
setUserProfile(currentUserProfile)
|
setUserProfile(currentUserProfile)
|
||||||
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@ -83,8 +63,8 @@ export const ProfileSettingsPage = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pubkey) {
|
if (usersPubkey) {
|
||||||
findMetadata(pubkey)
|
findMetadata(usersPubkey)
|
||||||
.then((profile) => {
|
.then((profile) => {
|
||||||
setUserProfile(profile)
|
setUserProfile(profile)
|
||||||
})
|
})
|
||||||
@ -95,7 +75,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [ndk, isUsersOwnProfile, currentUserProfile, pubkey, findMetadata])
|
}, [ndk, currentUserProfile, findMetadata, usersPubkey])
|
||||||
|
|
||||||
const editItem = (
|
const editItem = (
|
||||||
key: keyof NDKUserProfile,
|
key: keyof NDKUserProfile,
|
||||||
@ -113,7 +93,6 @@ export const ProfileSettingsPage = () => {
|
|||||||
multiline={multiline}
|
multiline={multiline}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
disabled={!isUsersOwnProfile}
|
|
||||||
InputProps={inputProps}
|
InputProps={inputProps}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = event.target
|
const { value } = event.target
|
||||||
@ -170,7 +149,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
content: serializedProfile,
|
content: serializedProfile,
|
||||||
created_at: unixNow(),
|
created_at: unixNow(),
|
||||||
kind: kinds.Metadata,
|
kind: kinds.Metadata,
|
||||||
pubkey: pubkey!,
|
pubkey: usersPubkey!,
|
||||||
tags: []
|
tags: []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +194,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
robotSet.current++
|
robotSet.current++
|
||||||
if (robotSet.current > 5) robotSet.current = 1
|
if (robotSet.current > 5) robotSet.current = 1
|
||||||
|
|
||||||
const robotAvatarLink = getRoboHashPicture(npub!, robotSet.current)
|
const robotAvatarLink = getRoboHashPicture(usersPubkey!, robotSet.current)
|
||||||
|
|
||||||
setUserProfile((prev) => ({
|
setUserProfile((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -244,143 +223,106 @@ export const ProfileSettingsPage = () => {
|
|||||||
* @returns robohash image url
|
* @returns robohash image url
|
||||||
*/
|
*/
|
||||||
const getProfileImage = (profile: NDKUserProfile) => {
|
const getProfileImage = (profile: NDKUserProfile) => {
|
||||||
if (!isUsersOwnProfile) {
|
|
||||||
return profile.image || getRoboHashPicture(npub!)
|
|
||||||
}
|
|
||||||
|
|
||||||
// userRobotImage is used only when visiting own profile
|
// userRobotImage is used only when visiting own profile
|
||||||
// while kind 0 picture is not set
|
// while kind 0 picture is not set
|
||||||
return profile.image || userRobotImage || getRoboHashPicture(npub!)
|
return profile.image || userRobotImage || getRoboHashPicture(usersPubkey!)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||||
<Container className={styles.container}>
|
<List>
|
||||||
<List
|
{userProfile && (
|
||||||
sx={{
|
<div>
|
||||||
bgcolor: 'background.paper',
|
<ListItem
|
||||||
marginTop: 2
|
|
||||||
}}
|
|
||||||
subheader={
|
|
||||||
<ListSubheader
|
|
||||||
sx={{
|
sx={{
|
||||||
paddingBottom: 1,
|
marginTop: 1,
|
||||||
paddingTop: 1,
|
display: 'flex',
|
||||||
fontSize: '1.5rem',
|
flexDirection: 'column'
|
||||||
zIndex: 2
|
|
||||||
}}
|
}}
|
||||||
className={styles.subHeader}
|
|
||||||
>
|
>
|
||||||
Profile Settings
|
{userProfile.banner ? (
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{userProfile && (
|
|
||||||
<div>
|
|
||||||
<ListItem
|
|
||||||
sx={{
|
|
||||||
marginTop: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{userProfile.banner ? (
|
|
||||||
<img
|
|
||||||
className={styles.bannerImg}
|
|
||||||
src={userProfile.banner}
|
|
||||||
alt="Banner Image"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box className={styles.noBanner}> No banner found </Box>
|
|
||||||
)}
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
{editItem('banner', 'Banner URL', undefined, undefined)}
|
|
||||||
|
|
||||||
<ListItem
|
|
||||||
sx={{
|
|
||||||
marginTop: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
onError={(event: React.SyntheticEvent<HTMLImageElement>) => {
|
className={styles.bannerImg}
|
||||||
event.currentTarget.src = getRoboHashPicture(npub!)
|
src={userProfile.banner}
|
||||||
}}
|
alt="Banner Image"
|
||||||
className={styles.img}
|
|
||||||
src={getProfileImage(userProfile)}
|
|
||||||
alt="Profile Image"
|
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
) : (
|
||||||
|
<Box className={styles.noBanner}> No banner found </Box>
|
||||||
{editItem('image', 'Picture URL', undefined, undefined, {
|
|
||||||
endAdornment: isUsersOwnProfile ? robohashButton() : undefined
|
|
||||||
})}
|
|
||||||
|
|
||||||
{editItem('name', 'Username')}
|
|
||||||
{editItem('displayName', 'Display Name')}
|
|
||||||
{editItem('nip05', 'Nostr Address (nip05)')}
|
|
||||||
{editItem('lud16', 'Lightning Address (lud16)')}
|
|
||||||
{editItem('about', 'About', true, 4)}
|
|
||||||
{editItem('website', 'Website')}
|
|
||||||
{isUsersOwnProfile && (
|
|
||||||
<>
|
|
||||||
{usersPubkey &&
|
|
||||||
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
|
|
||||||
|
|
||||||
{loginMethod === LoginMethod.privateKey &&
|
|
||||||
keys &&
|
|
||||||
keys.private &&
|
|
||||||
copyItem(
|
|
||||||
'••••••••••••••••••••••••••••••••••••••••••••••••••',
|
|
||||||
'Private Key',
|
|
||||||
keys.private
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{isUsersOwnProfile && (
|
</ListItem>
|
||||||
<>
|
|
||||||
{loginMethod === LoginMethod.nostrLogin &&
|
{editItem('banner', 'Banner URL', undefined, undefined)}
|
||||||
nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
|
|
||||||
<ListItem
|
<ListItem
|
||||||
sx={{ marginTop: 1 }}
|
sx={{
|
||||||
onClick={() => {
|
marginTop: 1,
|
||||||
launchNostrLoginDialog('import')
|
display: 'flex',
|
||||||
}}
|
flexDirection: 'column'
|
||||||
>
|
}}
|
||||||
<TextField
|
>
|
||||||
label="Private Key (nostr-login)"
|
<img
|
||||||
defaultValue="••••••••••••••••••••••••••••••••••••••••••••••••••"
|
onError={(event: React.SyntheticEvent<HTMLImageElement>) => {
|
||||||
size="small"
|
event.currentTarget.src = getRoboHashPicture(usersPubkey!)
|
||||||
className={styles.textField}
|
}}
|
||||||
disabled
|
className={styles.img}
|
||||||
type={'password'}
|
src={getProfileImage(userProfile)}
|
||||||
InputProps={{
|
alt="Profile Image"
|
||||||
endAdornment: (
|
/>
|
||||||
<LaunchIcon className={styles.copyItem} />
|
</ListItem>
|
||||||
)
|
|
||||||
}}
|
{editItem('image', 'Picture URL', undefined, undefined, {
|
||||||
/>
|
endAdornment: robohashButton()
|
||||||
</ListItem>
|
})}
|
||||||
)}
|
|
||||||
</>
|
{editItem('name', 'Username')}
|
||||||
|
{editItem('displayName', 'Display Name')}
|
||||||
|
{editItem('nip05', 'Nostr Address (nip05)')}
|
||||||
|
{editItem('lud16', 'Lightning Address (lud16)')}
|
||||||
|
{editItem('about', 'About', true, 4)}
|
||||||
|
{editItem('website', 'Website')}
|
||||||
|
{usersPubkey &&
|
||||||
|
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
|
||||||
|
{loginMethod === LoginMethod.privateKey &&
|
||||||
|
keys &&
|
||||||
|
keys.private &&
|
||||||
|
copyItem(
|
||||||
|
'••••••••••••••••••••••••••••••••••••••••••••••••••',
|
||||||
|
'Private Key',
|
||||||
|
keys.private
|
||||||
)}
|
)}
|
||||||
</div>
|
{loginMethod === LoginMethod.nostrLogin &&
|
||||||
)}
|
nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
|
||||||
</List>
|
<ListItem
|
||||||
{isUsersOwnProfile && (
|
sx={{ marginTop: 1 }}
|
||||||
<LoadingButton
|
onClick={() => {
|
||||||
loading={savingProfileMetadata}
|
launchNostrLoginDialog('import')
|
||||||
variant="contained"
|
}}
|
||||||
onClick={handleSaveMetadata}
|
>
|
||||||
>
|
<TextField
|
||||||
SAVE
|
label="Private Key (nostr-login)"
|
||||||
</LoadingButton>
|
defaultValue="••••••••••••••••••••••••••••••••••••••••••••••••••"
|
||||||
|
size="small"
|
||||||
|
className={styles.textField}
|
||||||
|
disabled
|
||||||
|
type={'password'}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: <LaunchIcon className={styles.copyItem} />
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</List>
|
||||||
<Footer />
|
<LoadingButton
|
||||||
|
sx={{ maxWidth: '300px', alignSelf: 'center', width: '100%' }}
|
||||||
|
loading={savingProfileMetadata}
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleSaveMetadata}
|
||||||
|
>
|
||||||
|
PUBLISH CHANGES
|
||||||
|
</LoadingButton>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textField {
|
.textField {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import ListItemText from '@mui/material/ListItemText'
|
|||||||
import Switch from '@mui/material/Switch'
|
import Switch from '@mui/material/Switch'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { Container } from '../../../components/Container'
|
|
||||||
import {
|
import {
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
@ -32,7 +31,6 @@ import {
|
|||||||
timeout
|
timeout
|
||||||
} from '../../../utils'
|
} from '../../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { Footer } from '../../../components/Footer/Footer'
|
|
||||||
import {
|
import {
|
||||||
getRelayListForUser,
|
getRelayListForUser,
|
||||||
NDKRelayList,
|
NDKRelayList,
|
||||||
@ -246,7 +244,7 @@ export const RelaysPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={styles.container}>
|
<>
|
||||||
<Box className={styles.relayAddContainer}>
|
<Box className={styles.relayAddContainer}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Add new relay"
|
label="Add new relay"
|
||||||
@ -291,8 +289,7 @@ export const RelaysPage = () => {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Footer />
|
</>
|
||||||
</Container>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,107 +1,103 @@
|
|||||||
@import '../../../styles/colors.scss';
|
@import '../../../styles/colors.scss';
|
||||||
|
|
||||||
.container {
|
.relayURItextfield {
|
||||||
color: $text-color;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.relayURItextfield {
|
.relayAddContainer {
|
||||||
width: 100%;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionIcon {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
margin-top: 35px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relaysContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relay {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.relayDivider {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.relayAddContainer {
|
.leaveRelayContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
width: 100%;
|
cursor: pointer;
|
||||||
align-items: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sectionIcon {
|
.showInfo {
|
||||||
font-size: 30px;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sectionTitle {
|
.showInfoIcon {
|
||||||
margin-top: 35px;
|
margin-right: 3px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relayInfoContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
font-size: 1.5rem;
|
text-wrap: wrap;
|
||||||
line-height: 2rem;
|
}
|
||||||
|
|
||||||
|
.relayInfoTitle {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.relaysContainer {
|
.relayInfoSubTitle {
|
||||||
display: flex;
|
font-weight: 500;
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.relay {
|
.copyItem {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
margin-left: 10px;
|
||||||
border-radius: 4px;
|
color: #34495e;
|
||||||
|
vertical-align: bottom;
|
||||||
.relayDivider {
|
cursor: pointer;
|
||||||
margin-left: 10px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaveRelayContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.showInfo {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.showInfoIcon {
|
|
||||||
margin-right: 3px;
|
|
||||||
margin-bottom: auto;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relayInfoContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
text-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relayInfoTitle {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relayInfoSubTitle {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copyItem {
|
|
||||||
margin-left: 10px;
|
|
||||||
color: #34495e;
|
|
||||||
vertical-align: bottom;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connectionStatus {
|
|
||||||
border-radius: 9999px;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
margin-right: 5px;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connectionStatusConnected {
|
|
||||||
background-color: $relay-status-connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connectionStatusNotConnected {
|
|
||||||
background-color: $relay-status-notconnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connectionStatusUnknown {
|
|
||||||
background-color: $input-text-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.connectionStatus {
|
||||||
|
border-radius: 9999px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connectionStatusConnected {
|
||||||
|
background-color: $relay-status-connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connectionStatusNotConnected {
|
||||||
|
background-color: $relay-status-notconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connectionStatusUnknown {
|
||||||
|
background-color: $input-text-color;
|
||||||
|
}
|
||||||
|
}
|
43
src/pages/settings/style.module.scss
Normal file
43
src/pages/settings/style.module.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.title {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.4fr 1.6fr;
|
||||||
|
position: relative;
|
||||||
|
grid-gap: 25px;
|
||||||
|
|
||||||
|
>* {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-gap: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 4px 0 rgb(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-gap: 15px;
|
||||||
|
|
||||||
|
position: sticky;
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 4px 0 rgb(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-gap: 15px;
|
||||||
|
}
|
@ -28,7 +28,8 @@ import {
|
|||||||
signEventForMetaFile,
|
signEventForMetaFile,
|
||||||
unixNow,
|
unixNow,
|
||||||
updateMarks,
|
updateMarks,
|
||||||
uploadMetaToFileStorage
|
uploadMetaToFileStorage,
|
||||||
|
parseNostrEvent
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { CurrentUserMark, Mark } from '../../types/mark.ts'
|
import { CurrentUserMark, Mark } from '../../types/mark.ts'
|
||||||
import PdfMarking from '../../components/PDFView/PdfMarking.tsx'
|
import PdfMarking from '../../components/PDFView/PdfMarking.tsx'
|
||||||
@ -36,12 +37,14 @@ import { convertToSigitFile, SigitFile } from '../../utils/file.ts'
|
|||||||
import { generateTimestamp } from '../../utils/opentimestamps.ts'
|
import { generateTimestamp } from '../../utils/opentimestamps.ts'
|
||||||
import { MARK_TYPE_CONFIG } from '../../components/MarkTypeStrategy/MarkStrategy.tsx'
|
import { MARK_TYPE_CONFIG } from '../../components/MarkTypeStrategy/MarkStrategy.tsx'
|
||||||
import { useNDK } from '../../hooks/useNDK.ts'
|
import { useNDK } from '../../hooks/useNDK.ts'
|
||||||
|
import { SendDMError } from '../../types/errors/SendDMError.ts'
|
||||||
|
|
||||||
export const SignPage = () => {
|
export const SignPage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const { updateUsersAppData, sendNotification } = useNDK()
|
const { updateUsersAppData, sendNotification, sendPrivateDirectMessage } =
|
||||||
|
useNDK()
|
||||||
|
|
||||||
const usersAppData = useAppSelector((state) => state.userAppData)
|
const usersAppData = useAppSelector((state) => state.userAppData)
|
||||||
|
|
||||||
@ -602,6 +605,66 @@ export const SignPage = () => {
|
|||||||
toast.error('Failed to publish notifications')
|
toast.error('Failed to publish notifications')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Send DMs
|
||||||
|
setLoadingSpinnerDesc('Sending DMs')
|
||||||
|
const createSignatureEvent = parseNostrEvent(meta.createSignature)
|
||||||
|
const { id } = createSignatureEvent
|
||||||
|
|
||||||
|
if (isLastSigner) {
|
||||||
|
// Final sign sends to everyone (creator, signers, viewers - /verify)
|
||||||
|
const areSent: boolean[] = Array(users.length).fill(false)
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
try {
|
||||||
|
areSent[i] = await sendPrivateDirectMessage(
|
||||||
|
`Sigit completed, visit ${window.location.origin}/#/verify/${id}`,
|
||||||
|
npubToHex(users[i])!
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SendDMError) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (areSent.some((r) => r)) {
|
||||||
|
toast.success(
|
||||||
|
`DMs sent ${areSent.filter((r) => r).length}/${users.length}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Notify the creator and
|
||||||
|
// the next signer (/sign).
|
||||||
|
try {
|
||||||
|
await sendPrivateDirectMessage(
|
||||||
|
`Sigit signed by ${usersNpub}, visit ${window.location.origin}/#/sign/${id}`,
|
||||||
|
npubToHex(submittedBy!)!
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SendDMError) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to notify creator twice, skipping
|
||||||
|
const currentSignerIndex = signers.indexOf(usersNpub)
|
||||||
|
const nextSigner = npubToHex(signers[currentSignerIndex + 1])
|
||||||
|
if (nextSigner !== submittedBy) {
|
||||||
|
try {
|
||||||
|
await sendPrivateDirectMessage(
|
||||||
|
`You're the next signer, visit ${window.location.origin}/#/sign/${id}`,
|
||||||
|
nextSigner!
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SendDMError) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
src/routes/PrivateRoute.tsx
Normal file
21
src/routes/PrivateRoute.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Navigate, useLocation } from 'react-router-dom'
|
||||||
|
import { useAppSelector } from '../hooks'
|
||||||
|
import { appPublicRoutes } from '.'
|
||||||
|
|
||||||
|
export function PrivateRoute({ children }: { children: JSX.Element }) {
|
||||||
|
const location = useLocation()
|
||||||
|
const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn)
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return (
|
||||||
|
<Navigate
|
||||||
|
to={{
|
||||||
|
pathname: appPublicRoutes.landingPage,
|
||||||
|
search: `?callbackPath=${btoa(location.pathname)}`
|
||||||
|
}}
|
||||||
|
replace
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return children
|
||||||
|
}
|
@ -4,9 +4,7 @@ export const appPrivateRoutes = {
|
|||||||
homePage: '/',
|
homePage: '/',
|
||||||
create: '/create',
|
create: '/create',
|
||||||
sign: '/sign',
|
sign: '/sign',
|
||||||
settings: '/settings',
|
profileSettings: '/settings/profile',
|
||||||
profileSettings: '/settings/profile/:npub',
|
|
||||||
cacheSettings: '/settings/cache',
|
|
||||||
relays: '/settings/relays',
|
relays: '/settings/relays',
|
||||||
nostrLogin: '/settings/nostrLogin'
|
nostrLogin: '/settings/nostrLogin'
|
||||||
}
|
}
|
||||||
@ -24,6 +22,3 @@ export const appPublicRoutes = {
|
|||||||
|
|
||||||
export const getProfileRoute = (hexKey: string) =>
|
export const getProfileRoute = (hexKey: string) =>
|
||||||
appPublicRoutes.profile.replace(':npub', hexToNpub(hexKey))
|
appPublicRoutes.profile.replace(':npub', hexToNpub(hexKey))
|
||||||
|
|
||||||
export const getProfileSettingsRoute = (hexKey: string) =>
|
|
||||||
appPrivateRoutes.profileSettings.replace(':npub', hexToNpub(hexKey))
|
|
||||||
|
@ -4,13 +4,13 @@ import { CreatePage } from '../pages/create'
|
|||||||
import { HomePage } from '../pages/home'
|
import { HomePage } from '../pages/home'
|
||||||
import { LandingPage } from '../pages/landing'
|
import { LandingPage } from '../pages/landing'
|
||||||
import { ProfilePage } from '../pages/profile'
|
import { ProfilePage } from '../pages/profile'
|
||||||
import { CacheSettingsPage } from '../pages/settings/cache'
|
|
||||||
import { NostrLoginPage } from '../pages/settings/nostrLogin'
|
import { NostrLoginPage } from '../pages/settings/nostrLogin'
|
||||||
import { ProfileSettingsPage } from '../pages/settings/profile'
|
import { ProfileSettingsPage } from '../pages/settings/profile'
|
||||||
import { RelaysPage } from '../pages/settings/relays'
|
import { RelaysPage } from '../pages/settings/relays'
|
||||||
import { SettingsPage } from '../pages/settings/Settings'
|
import { SettingsLayout } from '../pages/settings/Settings'
|
||||||
import { SignPage } from '../pages/sign'
|
import { SignPage } from '../pages/sign'
|
||||||
import { VerifyPage } from '../pages/verify'
|
import { VerifyPage } from '../pages/verify'
|
||||||
|
import { PrivateRoute } from './PrivateRoute'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper type allows for extending react-router-dom's **RouteProps** with generic type
|
* Helper type allows for extending react-router-dom's **RouteProps** with generic type
|
||||||
@ -37,7 +37,7 @@ export function recursiveRouteRenderer<T>(
|
|||||||
return routes.map((route, index) =>
|
return routes.map((route, index) =>
|
||||||
renderConditionCallbackFn(route) ? (
|
renderConditionCallbackFn(route) ? (
|
||||||
<Route
|
<Route
|
||||||
key={`${route.path}${index}`}
|
key={route.path ? `${route.path}${index}` : index}
|
||||||
path={route.path}
|
path={route.path}
|
||||||
element={route.element}
|
element={route.element}
|
||||||
>
|
>
|
||||||
@ -67,37 +67,50 @@ export const publicRoutes: PublicRouteProps[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export const privateRoutes = [
|
export const privateRoutes: CustomRouteProps<unknown>[] = [
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.homePage,
|
path: appPrivateRoutes.homePage,
|
||||||
element: <HomePage />
|
element: (
|
||||||
|
<PrivateRoute>
|
||||||
|
<HomePage />
|
||||||
|
</PrivateRoute>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.create,
|
path: appPrivateRoutes.create,
|
||||||
element: <CreatePage />
|
element: (
|
||||||
|
<PrivateRoute>
|
||||||
|
<CreatePage />
|
||||||
|
</PrivateRoute>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${appPrivateRoutes.sign}/:id?`,
|
path: `${appPrivateRoutes.sign}/:id?`,
|
||||||
element: <SignPage />
|
element: (
|
||||||
|
<PrivateRoute>
|
||||||
|
<SignPage />
|
||||||
|
</PrivateRoute>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.settings,
|
element: (
|
||||||
element: <SettingsPage />
|
<PrivateRoute>
|
||||||
},
|
<SettingsLayout />
|
||||||
{
|
</PrivateRoute>
|
||||||
path: appPrivateRoutes.profileSettings,
|
),
|
||||||
element: <ProfileSettingsPage />
|
children: [
|
||||||
},
|
{
|
||||||
{
|
path: appPrivateRoutes.profileSettings,
|
||||||
path: appPrivateRoutes.cacheSettings,
|
element: <ProfileSettingsPage />
|
||||||
element: <CacheSettingsPage />
|
},
|
||||||
},
|
{
|
||||||
{
|
path: appPrivateRoutes.relays,
|
||||||
path: appPrivateRoutes.relays,
|
element: <RelaysPage />
|
||||||
element: <RelaysPage />
|
},
|
||||||
},
|
{
|
||||||
{
|
path: appPrivateRoutes.nostrLogin,
|
||||||
path: appPrivateRoutes.nostrLogin,
|
element: <NostrLoginPage />
|
||||||
element: <NostrLoginPage />
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
86
src/services/cache/index.ts
vendored
86
src/services/cache/index.ts
vendored
@ -1,86 +0,0 @@
|
|||||||
import { IDBPDatabase, openDB } from 'idb'
|
|
||||||
import { Event } from 'nostr-tools'
|
|
||||||
import { CachedEvent } from '../../types'
|
|
||||||
import { SchemaV2 } from './schema'
|
|
||||||
|
|
||||||
class LocalCache {
|
|
||||||
// Static property to hold the single instance of LocalCache
|
|
||||||
private static instance: LocalCache | null = null
|
|
||||||
private db!: IDBPDatabase<SchemaV2>
|
|
||||||
|
|
||||||
// Private constructor to prevent direct instantiation
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
// Method to initialize the database
|
|
||||||
private async init() {
|
|
||||||
this.db = await openDB<SchemaV2>('sigit-cache', 2, {
|
|
||||||
upgrade(db, oldVersion) {
|
|
||||||
if (oldVersion < 1) {
|
|
||||||
db.createObjectStore('userMetadata', { keyPath: 'event.pubkey' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 2) {
|
|
||||||
const v6 = db as unknown as IDBPDatabase<SchemaV2>
|
|
||||||
|
|
||||||
v6.createObjectStore('userRelayListMetadata', {
|
|
||||||
keyPath: 'event.pubkey'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static method to get the single instance of LocalCache
|
|
||||||
public static async getInstance(): Promise<LocalCache> {
|
|
||||||
// If the instance doesn't exist, create it
|
|
||||||
if (!LocalCache.instance) {
|
|
||||||
LocalCache.instance = new LocalCache()
|
|
||||||
await LocalCache.instance.init()
|
|
||||||
}
|
|
||||||
// Return the single instance of LocalCache
|
|
||||||
return LocalCache.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to add user metadata
|
|
||||||
public async addUserMetadata(event: Event) {
|
|
||||||
await this.db.put('userMetadata', { event, cachedAt: Date.now() })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to get user metadata by key
|
|
||||||
public async getUserMetadata(key: string): Promise<CachedEvent | null> {
|
|
||||||
const data = await this.db.get('userMetadata', key)
|
|
||||||
return data || null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to delete user metadata by key
|
|
||||||
public async deleteUserMetadata(key: string) {
|
|
||||||
await this.db.delete('userMetadata', key)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addUserRelayListMetadata(event: Event) {
|
|
||||||
await this.db.put('userRelayListMetadata', { event, cachedAt: Date.now() })
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getUserRelayListMetadata(
|
|
||||||
key: string
|
|
||||||
): Promise<CachedEvent | null> {
|
|
||||||
const data = await this.db.get('userRelayListMetadata', key)
|
|
||||||
return data || null
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteUserRelayListMetadata(key: string) {
|
|
||||||
await this.db.delete('userRelayListMetadata', key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to clear cache data
|
|
||||||
public async clearCacheData() {
|
|
||||||
// Clear the 'userMetadata' store in the IndexedDB database
|
|
||||||
await this.db.clear('userMetadata')
|
|
||||||
|
|
||||||
// Reload the current page to ensure any cached data is reset
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export the single instance of LocalCache
|
|
||||||
export const localCache = await LocalCache.getInstance()
|
|
16
src/services/cache/schema.ts
vendored
16
src/services/cache/schema.ts
vendored
@ -1,16 +0,0 @@
|
|||||||
import { DBSchema } from 'idb'
|
|
||||||
import { CachedEvent } from '../../types'
|
|
||||||
|
|
||||||
export interface SchemaV1 extends DBSchema {
|
|
||||||
userMetadata: {
|
|
||||||
key: string
|
|
||||||
value: CachedEvent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SchemaV2 extends SchemaV1 {
|
|
||||||
userRelayListMetadata: {
|
|
||||||
key: string
|
|
||||||
value: CachedEvent
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +1 @@
|
|||||||
export * from './cache'
|
|
||||||
export * from './signer'
|
export * from './signer'
|
||||||
|
21
src/types/draft.ts
Normal file
21
src/types/draft.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { SigitFile } from '../utils/file'
|
||||||
|
import { User } from './core'
|
||||||
|
import { DrawnField } from './drawing'
|
||||||
|
|
||||||
|
export interface SigitFileDraft {
|
||||||
|
name: string
|
||||||
|
file: string
|
||||||
|
pages: DrawnField[][]
|
||||||
|
}
|
||||||
|
export interface SigitDraft {
|
||||||
|
title: string
|
||||||
|
users: User[]
|
||||||
|
files: SigitFile[]
|
||||||
|
lastUpdated: number
|
||||||
|
}
|
||||||
|
export interface SerializedSigitDraft {
|
||||||
|
title: string
|
||||||
|
lastUpdated: number
|
||||||
|
users: User[]
|
||||||
|
files: SigitFileDraft[]
|
||||||
|
}
|
23
src/types/errors/SendDMError.ts
Normal file
23
src/types/errors/SendDMError.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Jsonable } from '.'
|
||||||
|
|
||||||
|
export enum SendDMErrorType {
|
||||||
|
'MISSING_RECIEVER' = 'Sending DM failed. Reciever is required.',
|
||||||
|
'ENCRYPTION_FAILED' = 'Sending DM failed. An error occurred in encrypting dm message.',
|
||||||
|
'RELAY_PUBLISH_FAILED' = 'Sending DM failed. Publishing events failed.'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SendDMError extends Error {
|
||||||
|
public readonly context?: Jsonable
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
options: { cause?: Error; context?: Jsonable } = {}
|
||||||
|
) {
|
||||||
|
const { cause, context } = options
|
||||||
|
|
||||||
|
super(message, { cause })
|
||||||
|
this.name = this.constructor.name
|
||||||
|
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
}
|
@ -4,3 +4,5 @@ export * from './nostr'
|
|||||||
export * from './relay'
|
export * from './relay'
|
||||||
export * from './zip'
|
export * from './zip'
|
||||||
export * from './event'
|
export * from './event'
|
||||||
|
export * from './drawing'
|
||||||
|
export * from './draft'
|
||||||
|
120
src/utils/draft.ts
Normal file
120
src/utils/draft.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import {
|
||||||
|
DrawnField,
|
||||||
|
SerializedSigitDraft,
|
||||||
|
SigitDraft,
|
||||||
|
SigitFileDraft
|
||||||
|
} from '../types'
|
||||||
|
import {
|
||||||
|
getMediaType,
|
||||||
|
extractFileExtension,
|
||||||
|
toFile,
|
||||||
|
getSigitFile
|
||||||
|
} from './file'
|
||||||
|
const DRAFT_KEY = 'sigitDraft'
|
||||||
|
let saveSigitDraftTimeout: number | null = null
|
||||||
|
const serializeSigitDraft = async (
|
||||||
|
draft: SigitDraft
|
||||||
|
): Promise<SerializedSigitDraft> => {
|
||||||
|
const serializedFiles = draft.files.map((file) => {
|
||||||
|
return new Promise<SigitFileDraft>((resolve, reject) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = () => {
|
||||||
|
const pages = file.pages
|
||||||
|
? file.pages.map((page) =>
|
||||||
|
page.drawnFields.map(
|
||||||
|
(field) =>
|
||||||
|
({
|
||||||
|
left: field.left,
|
||||||
|
top: field.top,
|
||||||
|
width: field.width,
|
||||||
|
height: field.height,
|
||||||
|
type: field.type,
|
||||||
|
counterpart: field.counterpart
|
||||||
|
}) as DrawnField
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
resolve({
|
||||||
|
name: file.name,
|
||||||
|
pages: pages,
|
||||||
|
file: reader.result as string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
reader.onerror = (error) => reject(error)
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const serializedFileDraft = await Promise.all(serializedFiles)
|
||||||
|
return {
|
||||||
|
title: draft.title,
|
||||||
|
lastUpdated: draft.lastUpdated,
|
||||||
|
users: [...draft.users],
|
||||||
|
files: serializedFileDraft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deserializeSigitDraft = async (
|
||||||
|
serializedDraft: SerializedSigitDraft
|
||||||
|
): Promise<SigitDraft> => {
|
||||||
|
const files = await Promise.all(
|
||||||
|
serializedDraft.files.map(async (draft) => {
|
||||||
|
const response = await fetch(draft.file)
|
||||||
|
const arrayBuffer = await response.arrayBuffer()
|
||||||
|
const type = getMediaType(extractFileExtension(draft.name))
|
||||||
|
const file = toFile(arrayBuffer, draft.name, type)
|
||||||
|
const sigitFile = await getSigitFile(file)
|
||||||
|
if (draft.pages) {
|
||||||
|
for (let i = 0; i < draft.pages.length; i++) {
|
||||||
|
const drawnFields = draft.pages[i]
|
||||||
|
if (sigitFile.pages) sigitFile.pages[i].drawnFields = [...drawnFields]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sigitFile
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...serializedDraft,
|
||||||
|
files: files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveSigitDraft = (draft: SigitDraft): Promise<void> => {
|
||||||
|
if (saveSigitDraftTimeout) {
|
||||||
|
clearTimeout(saveSigitDraftTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
saveSigitDraftTimeout = window.setTimeout(() => {
|
||||||
|
serializeSigitDraft(draft)
|
||||||
|
.then((draftToSave) => {
|
||||||
|
localStorage.setItem(DRAFT_KEY, JSON.stringify(draftToSave))
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasSigitDraft = () => {
|
||||||
|
return DRAFT_KEY in localStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSigitDraft = async () => {
|
||||||
|
const sigitDraft = localStorage.getItem(DRAFT_KEY)
|
||||||
|
if (!sigitDraft) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const serializedDraft = JSON.parse(sigitDraft) as SerializedSigitDraft
|
||||||
|
return await deserializeSigitDraft(serializedDraft)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearSigitDraft = () => {
|
||||||
|
localStorage.removeItem(DRAFT_KEY)
|
||||||
|
}
|
@ -12,3 +12,4 @@ export * from './string'
|
|||||||
export * from './url'
|
export * from './url'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
export * from './zip'
|
export * from './zip'
|
||||||
|
export * from './draft'
|
||||||
|
@ -26,30 +26,6 @@ export const clearState = () => {
|
|||||||
localStorage.removeItem('state')
|
localStorage.removeItem('state')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveVisitedLink = (pathname: string, search: string) => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'visitedLink',
|
|
||||||
JSON.stringify({
|
|
||||||
pathname,
|
|
||||||
search
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getVisitedLink = () => {
|
|
||||||
const visitedLink = localStorage.getItem('visitedLink')
|
|
||||||
if (!visitedLink) return null
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(visitedLink) as {
|
|
||||||
pathname: string
|
|
||||||
search: string
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const saveAuthToken = (token: string) => {
|
export const saveAuthToken = (token: string) => {
|
||||||
localStorage.setItem('authToken', token)
|
localStorage.setItem('authToken', token)
|
||||||
}
|
}
|
||||||
@ -66,3 +42,47 @@ export const clear = () => {
|
|||||||
clearAuthToken()
|
clearAuthToken()
|
||||||
clearState()
|
clearState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeWithInitialValue<T>(storedValue: T, initialValue: T): T {
|
||||||
|
if (
|
||||||
|
!Array.isArray(storedValue) &&
|
||||||
|
typeof storedValue === 'object' &&
|
||||||
|
storedValue !== null
|
||||||
|
) {
|
||||||
|
return { ...initialValue, ...storedValue }
|
||||||
|
}
|
||||||
|
return storedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalStorageItem<T>(key: string, defaultValue: T): string {
|
||||||
|
try {
|
||||||
|
const data = window.localStorage.getItem(key)
|
||||||
|
if (data === null) return JSON.stringify(defaultValue)
|
||||||
|
return data
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while fetching local storage value: `, err)
|
||||||
|
return JSON.stringify(defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLocalStorageItem(key: string, value: string) {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(key, value)
|
||||||
|
dispatchLocalStorageEvent(key, value)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while saving local storage value: `, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeLocalStorageItem(key: string) {
|
||||||
|
try {
|
||||||
|
window.localStorage.removeItem(key)
|
||||||
|
dispatchLocalStorageEvent(key, null)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while deleting local storage value: `, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchLocalStorageEvent(key: string, newValue: string | null) {
|
||||||
|
window.dispatchEvent(new StorageEvent('storage', { key, newValue }))
|
||||||
|
}
|
||||||
|
@ -171,20 +171,17 @@ export const DEFAULT_TOOLBOX: DrawTool[] = [
|
|||||||
{
|
{
|
||||||
identifier: MarkType.FULLNAME,
|
identifier: MarkType.FULLNAME,
|
||||||
icon: faIdCard,
|
icon: faIdCard,
|
||||||
label: 'Full Name',
|
label: 'Full Name'
|
||||||
isComingSoon: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.JOBTITLE,
|
identifier: MarkType.JOBTITLE,
|
||||||
icon: faBriefcase,
|
icon: faBriefcase,
|
||||||
label: 'Job Title',
|
label: 'Job Title'
|
||||||
isComingSoon: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.DATETIME,
|
identifier: MarkType.DATETIME,
|
||||||
icon: faClock,
|
icon: faClock,
|
||||||
label: 'Date Time',
|
label: 'Date Time'
|
||||||
isComingSoon: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.NUMBER,
|
identifier: MarkType.NUMBER,
|
||||||
|
@ -31,7 +31,8 @@ export enum SignStatus {
|
|||||||
|
|
||||||
export enum SigitStatus {
|
export enum SigitStatus {
|
||||||
Partial = 'In-Progress',
|
Partial = 'In-Progress',
|
||||||
Complete = 'Completed'
|
Complete = 'Completed',
|
||||||
|
LocalDraft = 'Draft'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SigitCardDisplayInfo {
|
export interface SigitCardDisplayInfo {
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
Event,
|
Event,
|
||||||
EventTemplate,
|
EventTemplate,
|
||||||
UnsignedEvent,
|
UnsignedEvent,
|
||||||
|
VerifiedEvent,
|
||||||
finalizeEvent,
|
finalizeEvent,
|
||||||
generateSecretKey,
|
generateSecretKey,
|
||||||
getEventHash,
|
getEventHash,
|
||||||
@ -214,6 +215,12 @@ export const toUnixTimestamp = (date: number | Date) => {
|
|||||||
export const fromUnixTimestamp = (unix: number) => {
|
export const fromUnixTimestamp = (unix: number) => {
|
||||||
return unix * 1000
|
return unix * 1000
|
||||||
}
|
}
|
||||||
|
export const randomTimeUpTo2DaysInThePast = (): number => {
|
||||||
|
const now = Date.now()
|
||||||
|
const twoDaysInMilliseconds = 2 * 24 * 60 * 60 * 1000
|
||||||
|
const randomPastTime = now - Math.floor(Math.random() * twoDaysInMilliseconds)
|
||||||
|
return toUnixTimestamp(randomPastTime)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate nip44 conversation key
|
* Generate nip44 conversation key
|
||||||
@ -263,19 +270,21 @@ export const countLeadingZeroes = (hex: string) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to create a wrapped event with PoW
|
* Function to create a wrapped event with PoW
|
||||||
* @param event Original event to be wrapped
|
* @param event Original event to be wrapped (can be unsigned or verified)
|
||||||
* @param receiver Public key of the receiver
|
* @param receiver Public key of the receiver
|
||||||
* @param difficulty PoW difficulty level (default is 20)
|
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
//
|
//
|
||||||
export const createWrap = (unsignedEvent: UnsignedEvent, receiver: string) => {
|
export const createWrap = (
|
||||||
|
event: UnsignedEvent | VerifiedEvent,
|
||||||
|
receiver: string
|
||||||
|
) => {
|
||||||
// Generate a random secret key and its corresponding public key
|
// Generate a random secret key and its corresponding public key
|
||||||
const randomKey = generateSecretKey()
|
const randomKey = generateSecretKey()
|
||||||
const pubkey = getPublicKey(randomKey)
|
const pubkey = getPublicKey(randomKey)
|
||||||
|
|
||||||
// Encrypt the event content using nip44 encryption
|
// Encrypt the event content using nip44 encryption
|
||||||
const content = nip44Encrypt(unsignedEvent, randomKey, receiver)
|
const content = nip44Encrypt(event, randomKey, receiver)
|
||||||
|
|
||||||
// Initialize nonce and leadingZeroes for PoW calculation
|
// Initialize nonce and leadingZeroes for PoW calculation
|
||||||
let nonce = 0
|
let nonce = 0
|
||||||
@ -286,11 +295,12 @@ export const createWrap = (unsignedEvent: UnsignedEvent, receiver: string) => {
|
|||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
while (true) {
|
while (true) {
|
||||||
// Create an unsigned event with the necessary fields
|
// Create an unsigned event with the necessary fields
|
||||||
|
// TODO: kinds.GiftWrap (wrong kind number in nostr-tools 10/11/2024 at v2.7.2)
|
||||||
const event: UnsignedEvent = {
|
const event: UnsignedEvent = {
|
||||||
kind: 1059, // Event kind
|
kind: 1059, // Event kind
|
||||||
content, // Encrypted content
|
content, // Encrypted content
|
||||||
pubkey, // Public key of the creator
|
pubkey, // Public key of the creator
|
||||||
created_at: unixNow(), // Current timestamp
|
created_at: randomTimeUpTo2DaysInThePast(),
|
||||||
tags: [
|
tags: [
|
||||||
// Tags including receiver and nonce
|
// Tags including receiver and nonce
|
||||||
['p', receiver],
|
['p', receiver],
|
||||||
|
@ -37,7 +37,6 @@ export const getRelayMapFromNDKRelayList = (ndkRelayList: NDKRelayList) => {
|
|||||||
export const getDefaultRelayMap = (): RelayMap => ({
|
export const getDefaultRelayMap = (): RelayMap => ({
|
||||||
[SIGIT_RELAY]: { write: true, read: true }
|
[SIGIT_RELAY]: { write: true, read: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishes relay map.
|
* Publishes relay map.
|
||||||
* @param relayMap - relay map.
|
* @param relayMap - relay map.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user