mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-19 07:14:22 +01:00
Merge pull request #90 from MutinyWallet/activity-design-update
Activity design update
This commit is contained in:
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mws",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "solid-start dev",
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.6",
|
||||
"@types/node": "^18.16.8",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
||||
"@typescript-eslint/parser": "^5.59.5",
|
||||
"autoprefixer": "^10.4.14",
|
||||
@@ -30,13 +30,13 @@
|
||||
"workbox-window": "^6.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kobalte/core": "^0.8.2",
|
||||
"@kobalte/core": "^0.9.6",
|
||||
"@kobalte/tailwindcss": "^0.5.0",
|
||||
"@modular-forms/solid": "^0.13.2",
|
||||
"@mutinywallet/mutiny-wasm": "^0.3.0",
|
||||
"@mutinywallet/mutiny-wasm": "^0.3.2",
|
||||
"@mutinywallet/waila-wasm": "^0.1.5",
|
||||
"@solid-primitives/upload": "^0.0.111",
|
||||
"@solidjs/meta": "^0.28.4",
|
||||
"@solidjs/meta": "^0.28.5",
|
||||
"@solidjs/router": "^0.8.2",
|
||||
"@thisbeyond/solid-select": "^0.14.0",
|
||||
"class-variance-authority": "^0.4.0",
|
||||
@@ -45,7 +45,7 @@
|
||||
"solid-js": "^1.7.5",
|
||||
"solid-qr-code": "^0.0.8",
|
||||
"solid-start": "^0.2.26",
|
||||
"undici": "^5.22.0"
|
||||
"undici": "^5.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.8"
|
||||
|
||||
238
pnpm-lock.yaml
generated
238
pnpm-lock.yaml
generated
@@ -2,8 +2,8 @@ lockfileVersion: '6.0'
|
||||
|
||||
dependencies:
|
||||
'@kobalte/core':
|
||||
specifier: ^0.8.2
|
||||
version: 0.8.2(solid-js@1.7.5)
|
||||
specifier: ^0.9.6
|
||||
version: 0.9.6(solid-js@1.7.5)
|
||||
'@kobalte/tailwindcss':
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0(tailwindcss@3.3.2)
|
||||
@@ -11,8 +11,8 @@ dependencies:
|
||||
specifier: ^0.13.2
|
||||
version: 0.13.2(solid-js@1.7.5)
|
||||
'@mutinywallet/mutiny-wasm':
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0
|
||||
specifier: ^0.3.2
|
||||
version: 0.3.2
|
||||
'@mutinywallet/waila-wasm':
|
||||
specifier: ^0.1.5
|
||||
version: 0.1.5
|
||||
@@ -20,8 +20,8 @@ dependencies:
|
||||
specifier: ^0.0.111
|
||||
version: 0.0.111(solid-js@1.7.5)
|
||||
'@solidjs/meta':
|
||||
specifier: ^0.28.4
|
||||
version: 0.28.4(solid-js@1.7.5)
|
||||
specifier: ^0.28.5
|
||||
version: 0.28.5(solid-js@1.7.5)
|
||||
'@solidjs/router':
|
||||
specifier: ^0.8.2
|
||||
version: 0.8.2(solid-js@1.7.5)
|
||||
@@ -45,15 +45,15 @@ dependencies:
|
||||
version: 0.0.8(qr.js@0.0.0)(solid-js@1.7.5)
|
||||
solid-start:
|
||||
specifier: ^0.2.26
|
||||
version: 0.2.26(@solidjs/meta@0.28.4)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5)
|
||||
version: 0.2.26(@solidjs/meta@0.28.5)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5)
|
||||
undici:
|
||||
specifier: ^5.22.0
|
||||
version: 5.22.0
|
||||
specifier: ^5.22.1
|
||||
version: 5.22.1
|
||||
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^18.16.6
|
||||
version: 18.16.6
|
||||
specifier: ^18.16.8
|
||||
version: 18.16.8
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^5.59.5
|
||||
version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@4.9.5)
|
||||
@@ -86,7 +86,7 @@ devDependencies:
|
||||
version: 8.4.23
|
||||
solid-start-node:
|
||||
specifier: ^0.2.26
|
||||
version: 0.2.26(solid-start@0.2.26)(undici@5.22.0)(vite@4.3.5)
|
||||
version: 0.2.26(solid-start@0.2.26)(undici@5.22.1)(vite@4.3.5)
|
||||
tailwindcss:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
@@ -95,7 +95,7 @@ devDependencies:
|
||||
version: 4.9.5
|
||||
vite:
|
||||
specifier: ^4.3.5
|
||||
version: 4.3.5(@types/node@18.16.6)
|
||||
version: 4.3.5(@types/node@18.16.8)
|
||||
vite-plugin-pwa:
|
||||
specifier: ^0.14.7
|
||||
version: 0.14.7(vite@4.3.5)(workbox-build@6.5.4)(workbox-window@6.5.4)
|
||||
@@ -1467,6 +1467,40 @@ packages:
|
||||
'@floating-ui/core': 1.2.6
|
||||
dev: false
|
||||
|
||||
/@formatjs/ecma402-abstract@1.15.0:
|
||||
resolution: {integrity: sha512-7bAYAv0w4AIao9DNg0avfOLTCPE9woAgs6SpXuMq11IN3A+l+cq8ghczwqSZBM11myvPSJA7vLn72q0rJ0QK6Q==}
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.2.32
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@formatjs/fast-memoize@2.0.1:
|
||||
resolution: {integrity: sha512-M2GgV+qJn5WJQAYewz7q2Cdl6fobQa69S1AzSM2y0P68ZDbK5cWrJIcPCO395Of1ksftGZoOt4LYCO/j9BKBSA==}
|
||||
dependencies:
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@formatjs/icu-messageformat-parser@2.4.0:
|
||||
resolution: {integrity: sha512-6Dh5Z/gp4F/HovXXu/vmd0If5NbYLB5dZrmhWVNb+BOGOEU3wt7Z/83KY1dtd7IDhAnYHasbmKE1RbTE0J+3hw==}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.15.0
|
||||
'@formatjs/icu-skeleton-parser': 1.4.0
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@formatjs/icu-skeleton-parser@1.4.0:
|
||||
resolution: {integrity: sha512-Qq347VM616rVLkvN6QsKJELazRyNlbCiN47LdH0Mc5U7E2xV0vatiVhGqd3KFgbc055BvtnUXR7XX60dCGFuWg==}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.15.0
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@formatjs/intl-localematcher@0.2.32:
|
||||
resolution: {integrity: sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==}
|
||||
dependencies:
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@hapi/hoek@9.3.0:
|
||||
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
|
||||
|
||||
@@ -1501,14 +1535,15 @@ packages:
|
||||
'@swc/helpers': 0.4.14
|
||||
dev: false
|
||||
|
||||
/@internationalized/number@3.2.0:
|
||||
resolution: {integrity: sha512-GUXkhXSX1Ee2RURnzl+47uvbOxnlMnvP9Er+QePTjDjOPWuunmLKlEkYkEcLiiJp7y4l9QxGDLOlVr8m69LS5w==}
|
||||
/@internationalized/message@3.1.0:
|
||||
resolution: {integrity: sha512-Oo5m70FcBdADf7G8NkUffVSfuCdeAYVfsvNjZDi9ELpjvkc4YNJVTHt/NyTI9K7FgAVoELxiP9YmN0sJ+HNHYQ==}
|
||||
dependencies:
|
||||
'@swc/helpers': 0.4.14
|
||||
intl-messageformat: 10.3.5
|
||||
dev: false
|
||||
|
||||
/@internationalized/string@3.1.0:
|
||||
resolution: {integrity: sha512-TJQKiyUb+wyAfKF59UNeZ/kELMnkxyecnyPCnBI1ma4NaXReJW+7Cc2mObXAqraIBJUVv7rgI46RLKrLgi35ng==}
|
||||
/@internationalized/number@3.2.0:
|
||||
resolution: {integrity: sha512-GUXkhXSX1Ee2RURnzl+47uvbOxnlMnvP9Er+QePTjDjOPWuunmLKlEkYkEcLiiJp7y4l9QxGDLOlVr8m69LS5w==}
|
||||
dependencies:
|
||||
'@swc/helpers': 0.4.14
|
||||
dev: false
|
||||
@@ -1547,16 +1582,16 @@ packages:
|
||||
'@jridgewell/resolve-uri': 3.1.0
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
|
||||
/@kobalte/core@0.8.2(solid-js@1.7.5):
|
||||
resolution: {integrity: sha512-EoBYKpYa3+Csr5Zh7l3aY3yAg7fk1O3ZM9lGyD1mdQ1FutTuwTkyj8z1CvSSj1Klb+rBL+X1N662Occ8Bmsi2w==}
|
||||
/@kobalte/core@0.9.6(solid-js@1.7.5):
|
||||
resolution: {integrity: sha512-nuo3+ncZHC2Fl531DdliLE/kRcmdMf2FflSTVqM0FqqgilbzIbdJCFXJddkZj4KtML9F4rHRiPq5reSXMMrFLg==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.15
|
||||
solid-js: ^1.7.3
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.2.7
|
||||
'@internationalized/date': 3.2.0
|
||||
'@internationalized/message': 3.1.0
|
||||
'@internationalized/number': 3.2.0
|
||||
'@internationalized/string': 3.1.0
|
||||
'@kobalte/utils': 0.6.1(solid-js@1.7.5)
|
||||
'@kobalte/utils': 0.7.2(solid-js@1.7.5)
|
||||
solid-js: 1.7.5
|
||||
dev: false
|
||||
|
||||
@@ -1568,17 +1603,17 @@ packages:
|
||||
tailwindcss: 3.3.2
|
||||
dev: false
|
||||
|
||||
/@kobalte/utils@0.6.1(solid-js@1.7.5):
|
||||
resolution: {integrity: sha512-YvBqe9t9j0iYFUHfKXSMLQKM3s5+nL72RvT9b75W+IOxUpSpN4rdaI8C2j97k3LsEt7qY4ktJdt8lPM1rr8JXw==}
|
||||
/@kobalte/utils@0.7.2(solid-js@1.7.5):
|
||||
resolution: {integrity: sha512-ZdINbHemz+jnixJ63VFi9wUEHEMAsP7iDGEADciKdSKrK4bDuccDw5th1O+5/PykfHqFwSI++JhhUpOd+iZ5jg==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
solid-js: ^1.7.3
|
||||
dependencies:
|
||||
'@solid-primitives/event-listener': 2.2.11(solid-js@1.7.5)
|
||||
'@solid-primitives/keyed': 1.2.0(solid-js@1.7.5)
|
||||
'@solid-primitives/media': 2.2.1(solid-js@1.7.5)
|
||||
'@solid-primitives/props': 3.1.5(solid-js@1.7.5)
|
||||
'@solid-primitives/refs': 1.0.3(solid-js@1.7.5)
|
||||
'@solid-primitives/utils': 5.5.2(solid-js@1.7.5)
|
||||
'@solid-primitives/utils': 6.1.1(solid-js@1.7.5)
|
||||
solid-js: 1.7.5
|
||||
dev: false
|
||||
|
||||
@@ -1590,8 +1625,8 @@ packages:
|
||||
solid-js: 1.7.5
|
||||
dev: false
|
||||
|
||||
/@mutinywallet/mutiny-wasm@0.3.0:
|
||||
resolution: {integrity: sha512-K+u2u/XMX1269U8af3T/ZvS+SzzrQcVYrdMi420dWCa14gke0vPWbGp+01zN7SCqBL4jp929emHTUZ4YBEpkzQ==}
|
||||
/@mutinywallet/mutiny-wasm@0.3.2:
|
||||
resolution: {integrity: sha512-m0VyEmVJ6Gl3YiTYYZLegeHFFVW21S2khtFljRyKKtcm0T8FZwJi0w2gNBaLQTakl5mpXwBgjTQwLqFnKSuhuQ==}
|
||||
dev: false
|
||||
|
||||
/@mutinywallet/waila-wasm@0.1.5:
|
||||
@@ -1644,7 +1679,7 @@ packages:
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-commonjs@24.1.0(rollup@3.21.5):
|
||||
/@rollup/plugin-commonjs@24.1.0(rollup@3.21.6):
|
||||
resolution: {integrity: sha512-eSL45hjhCWI0jCCXcNtLVqM5N1JlBGvlFfY0m6oOYnLCJ6N0qEXoZql4sY2MOUArzhH4SA/qBpTxvvZp2Sc+DQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
@@ -1653,15 +1688,15 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.5)
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.6)
|
||||
commondir: 1.0.1
|
||||
estree-walker: 2.0.2
|
||||
glob: 8.1.0
|
||||
is-reference: 1.2.1
|
||||
magic-string: 0.27.0
|
||||
rollup: 3.21.5
|
||||
rollup: 3.21.6
|
||||
|
||||
/@rollup/plugin-json@6.0.0(rollup@3.21.5):
|
||||
/@rollup/plugin-json@6.0.0(rollup@3.21.6):
|
||||
resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
@@ -1670,8 +1705,8 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.5)
|
||||
rollup: 3.21.5
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.6)
|
||||
rollup: 3.21.6
|
||||
|
||||
/@rollup/plugin-node-resolve@11.2.1(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==}
|
||||
@@ -1688,7 +1723,7 @@ packages:
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-node-resolve@15.0.2(rollup@3.21.5):
|
||||
/@rollup/plugin-node-resolve@15.0.2(rollup@3.21.6):
|
||||
resolution: {integrity: sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
@@ -1697,13 +1732,13 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.5)
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.6)
|
||||
'@types/resolve': 1.20.2
|
||||
deepmerge: 4.3.1
|
||||
is-builtin-module: 3.2.1
|
||||
is-module: 1.0.0
|
||||
resolve: 1.22.2
|
||||
rollup: 3.21.5
|
||||
rollup: 3.21.6
|
||||
|
||||
/@rollup/plugin-replace@2.4.2(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==}
|
||||
@@ -1715,7 +1750,7 @@ packages:
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-replace@5.0.2(rollup@3.21.5):
|
||||
/@rollup/plugin-replace@5.0.2(rollup@3.21.6):
|
||||
resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
@@ -1724,9 +1759,9 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.5)
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.6)
|
||||
magic-string: 0.27.0
|
||||
rollup: 3.21.5
|
||||
rollup: 3.21.6
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@3.1.0(rollup@2.79.1):
|
||||
@@ -1741,7 +1776,7 @@ packages:
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@5.0.2(rollup@3.21.5):
|
||||
/@rollup/pluginutils@5.0.2(rollup@3.21.6):
|
||||
resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
@@ -1753,7 +1788,7 @@ packages:
|
||||
'@types/estree': 1.0.1
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
rollup: 3.21.5
|
||||
rollup: 3.21.6
|
||||
|
||||
/@scure/base@1.1.1:
|
||||
resolution: {integrity: sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==}
|
||||
@@ -1859,14 +1894,6 @@ packages:
|
||||
solid-js: 1.7.5
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/utils@5.5.2(solid-js@1.7.5):
|
||||
resolution: {integrity: sha512-L52ig3eHKU6CqbPCKJIb4lweBuINHBOERcE1duApyKozEN8+zCqEKwD1Qo9ljKeEzJTBGWClxNpwEiNTUWTGvg==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
dependencies:
|
||||
solid-js: 1.7.5
|
||||
dev: false
|
||||
|
||||
/@solid-primitives/utils@6.1.1(solid-js@1.7.5):
|
||||
resolution: {integrity: sha512-wxxUdxja126jTROs9Ro8Z5ExbHs9rv2Tl744S3Qmzki/gTcTXW8D1TvTArQcjqkCvSw8OIQ2EO2NI8sR28Trxg==}
|
||||
peerDependencies:
|
||||
@@ -1875,8 +1902,8 @@ packages:
|
||||
solid-js: 1.7.5
|
||||
dev: false
|
||||
|
||||
/@solidjs/meta@0.28.4(solid-js@1.7.5):
|
||||
resolution: {integrity: sha512-1USElsQuGVcJnmZ6CxPfUVmKvCsVdBQoGrUyMxLtFw36Ytt90dPs/qLyXLvPR/ZPD16/qauWqg6APEkbrDOLcA==}
|
||||
/@solidjs/meta@0.28.5(solid-js@1.7.5):
|
||||
resolution: {integrity: sha512-52luJR6hVNMA1K8Od5OD0d8WVz/svqZG4is8lrDimiUGxdia3DzuLF+pK56dnEzbNt9cA42qVFL134U9LkC9Gg==}
|
||||
peerDependencies:
|
||||
solid-js: '>=1.4.0'
|
||||
dependencies:
|
||||
@@ -1955,8 +1982,8 @@ packages:
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
dev: true
|
||||
|
||||
/@types/node@18.16.6:
|
||||
resolution: {integrity: sha512-N7KINmeB8IN3vRR8dhgHEp+YpWvGFcpDoh5XZ8jB5a00AdFKCKEyyGTOPTddUf4JqU1ZKTVxkOxakDvchNVI2Q==}
|
||||
/@types/node@18.16.8:
|
||||
resolution: {integrity: sha512-p0iAXcfWCOTCBbsExHIDFCfwsqFwBTgETJveKMT+Ci3LY9YqQCI91F5S+TB20+aRCXpcWfvx5Qr5EccnwCm2NA==}
|
||||
|
||||
/@types/offscreencanvas@2019.7.0:
|
||||
resolution: {integrity: sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==}
|
||||
@@ -1965,7 +1992,7 @@ packages:
|
||||
/@types/resolve@1.17.1:
|
||||
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
|
||||
dependencies:
|
||||
'@types/node': 18.16.6
|
||||
'@types/node': 18.16.8
|
||||
dev: true
|
||||
|
||||
/@types/resolve@1.20.2:
|
||||
@@ -2343,7 +2370,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001486
|
||||
electron-to-chromium: 1.4.387
|
||||
electron-to-chromium: 1.4.392
|
||||
node-releases: 2.0.10
|
||||
update-browserslist-db: 1.0.11(browserslist@4.21.5)
|
||||
|
||||
@@ -2626,8 +2653,8 @@ packages:
|
||||
jake: 10.8.5
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium@1.4.387:
|
||||
resolution: {integrity: sha512-tutLf+alr1/0YqJwKPdstVvDLmxmLb5xNyDLNS0RZmenHcEYk9qKfpKDCVZEKJ00JVbnayJm1MZAbYhYDFpcOw==}
|
||||
/electron-to-chromium@1.4.392:
|
||||
resolution: {integrity: sha512-TXQOMW9tnhIms3jGy/lJctLjICOgyueZFJ1KUtm6DTQ+QpxX3p7ZBwB6syuZ9KBuT5S4XX7bgY1ECPgfxKUdOg==}
|
||||
|
||||
/emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
@@ -3572,6 +3599,15 @@ packages:
|
||||
side-channel: 1.0.4
|
||||
dev: true
|
||||
|
||||
/intl-messageformat@10.3.5:
|
||||
resolution: {integrity: sha512-6kPkftF8Jg3XJCkGKa5OD+nYQ+qcSxF4ZkuDdXZ6KGG0VXn+iblJqRFyDdm9VvKcMyC0Km2+JlVQffFM52D0YA==}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.15.0
|
||||
'@formatjs/fast-memoize': 2.0.1
|
||||
'@formatjs/icu-messageformat-parser': 2.4.0
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/is-array-buffer@3.0.2:
|
||||
resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
|
||||
dependencies:
|
||||
@@ -3767,7 +3803,7 @@ packages:
|
||||
resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
dependencies:
|
||||
'@types/node': 18.16.6
|
||||
'@types/node': 18.16.8
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 7.2.0
|
||||
dev: true
|
||||
@@ -4397,10 +4433,10 @@ packages:
|
||||
jest-worker: 26.6.2
|
||||
rollup: 2.79.1
|
||||
serialize-javascript: 4.0.0
|
||||
terser: 5.17.2
|
||||
terser: 5.17.3
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-visualizer@5.9.0(rollup@3.21.5):
|
||||
/rollup-plugin-visualizer@5.9.0(rollup@3.21.6):
|
||||
resolution: {integrity: sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
@@ -4412,17 +4448,17 @@ packages:
|
||||
dependencies:
|
||||
open: 8.4.2
|
||||
picomatch: 2.3.1
|
||||
rollup: 3.21.5
|
||||
rollup: 3.21.6
|
||||
source-map: 0.7.4
|
||||
yargs: 17.7.2
|
||||
|
||||
/rollup-route-manifest@1.0.0(rollup@3.21.5):
|
||||
/rollup-route-manifest@1.0.0(rollup@3.21.6):
|
||||
resolution: {integrity: sha512-3CmcMmCLAzJDUXiO3z6386/Pt8/k9xTZv8gIHyXI8hYGoAInnYdOsFXiGGzQRMy6TXR1jUZme2qbdwjH2nFMjg==}
|
||||
engines: {node: '>=8'}
|
||||
peerDependencies:
|
||||
rollup: '>=2.0.0'
|
||||
dependencies:
|
||||
rollup: 3.21.5
|
||||
rollup: 3.21.6
|
||||
route-sort: 1.0.0
|
||||
|
||||
/rollup@2.79.1:
|
||||
@@ -4433,8 +4469,8 @@ packages:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/rollup@3.21.5:
|
||||
resolution: {integrity: sha512-a4NTKS4u9PusbUJcfF4IMxuqjFzjm6ifj76P54a7cKnvVzJaG12BLVR+hgU2YDGHzyMMQNxLAZWuALsn8q2oQg==}
|
||||
/rollup@3.21.6:
|
||||
resolution: {integrity: sha512-SXIICxvxQxR3D4dp/3LDHZIJPC8a4anKMHd4E3Jiz2/JnY+2bEjqrOokAauc5ShGVNFHlEFjBXAXlaxkJqIqSg==}
|
||||
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
@@ -4560,28 +4596,28 @@ packages:
|
||||
'@babel/types': 7.21.5
|
||||
solid-js: 1.7.5
|
||||
|
||||
/solid-start-node@0.2.26(solid-start@0.2.26)(undici@5.22.0)(vite@4.3.5):
|
||||
/solid-start-node@0.2.26(solid-start@0.2.26)(undici@5.22.1)(vite@4.3.5):
|
||||
resolution: {integrity: sha512-8vciTGoQV+lIlCUSVHJPazlaoKDRfBowDkPeBr/EZdmtbcMOKoJYf/APPQWFspylF+nhzunMf0+zJP90VtMEYg==}
|
||||
peerDependencies:
|
||||
solid-start: '*'
|
||||
undici: ^5.8.0
|
||||
vite: '*'
|
||||
dependencies:
|
||||
'@rollup/plugin-commonjs': 24.1.0(rollup@3.21.5)
|
||||
'@rollup/plugin-json': 6.0.0(rollup@3.21.5)
|
||||
'@rollup/plugin-node-resolve': 15.0.2(rollup@3.21.5)
|
||||
'@rollup/plugin-commonjs': 24.1.0(rollup@3.21.6)
|
||||
'@rollup/plugin-json': 6.0.0(rollup@3.21.6)
|
||||
'@rollup/plugin-node-resolve': 15.0.2(rollup@3.21.6)
|
||||
compression: 1.7.4
|
||||
polka: 1.0.0-next.22
|
||||
rollup: 3.21.5
|
||||
rollup: 3.21.6
|
||||
sirv: 2.0.3
|
||||
solid-start: 0.2.26(@solidjs/meta@0.28.4)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5)
|
||||
terser: 5.17.2
|
||||
undici: 5.22.0
|
||||
vite: 4.3.5(@types/node@18.16.6)
|
||||
solid-start: 0.2.26(@solidjs/meta@0.28.5)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5)
|
||||
terser: 5.17.3
|
||||
undici: 5.22.1
|
||||
vite: 4.3.5(@types/node@18.16.8)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/solid-start@0.2.26(@solidjs/meta@0.28.4)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5):
|
||||
/solid-start@0.2.26(@solidjs/meta@0.28.5)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5):
|
||||
resolution: {integrity: sha512-kne2HZlnSMzsirdnvNs1CsDqBl0L0uvKKt1t4de1CH7JIngyqoMcER97jTE0Ejr84KknANaKAdvJAzZcL7Ueng==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -4621,7 +4657,7 @@ packages:
|
||||
'@babel/preset-env': 7.21.5(@babel/core@7.21.8)
|
||||
'@babel/preset-typescript': 7.21.5(@babel/core@7.21.8)
|
||||
'@babel/template': 7.20.7
|
||||
'@solidjs/meta': 0.28.4(solid-js@1.7.5)
|
||||
'@solidjs/meta': 0.28.5(solid-js@1.7.5)
|
||||
'@solidjs/router': 0.8.2(solid-js@1.7.5)
|
||||
'@types/cookie': 0.5.1
|
||||
chokidar: 3.5.3
|
||||
@@ -4637,18 +4673,18 @@ packages:
|
||||
get-port: 6.1.2
|
||||
parse-multipart-data: 1.5.0
|
||||
picocolors: 1.0.0
|
||||
rollup: 3.21.5
|
||||
rollup-plugin-visualizer: 5.9.0(rollup@3.21.5)
|
||||
rollup-route-manifest: 1.0.0(rollup@3.21.5)
|
||||
rollup: 3.21.6
|
||||
rollup-plugin-visualizer: 5.9.0(rollup@3.21.6)
|
||||
rollup-route-manifest: 1.0.0(rollup@3.21.6)
|
||||
sade: 1.8.1
|
||||
set-cookie-parser: 2.6.0
|
||||
sirv: 2.0.3
|
||||
solid-js: 1.7.5
|
||||
solid-start-node: 0.2.26(solid-start@0.2.26)(undici@5.22.0)(vite@4.3.5)
|
||||
terser: 5.17.2
|
||||
undici: 5.22.0
|
||||
vite: 4.3.5(@types/node@18.16.6)
|
||||
vite-plugin-inspect: 0.7.26(rollup@3.21.5)(vite@4.3.5)
|
||||
solid-start-node: 0.2.26(solid-start@0.2.26)(undici@5.22.1)(vite@4.3.5)
|
||||
terser: 5.17.3
|
||||
undici: 5.22.1
|
||||
vite: 4.3.5(@types/node@18.16.8)
|
||||
vite-plugin-inspect: 0.7.26(rollup@3.21.6)(vite@4.3.5)
|
||||
vite-plugin-solid: 2.7.0(solid-js@1.7.5)(vite@4.3.5)
|
||||
wait-on: 6.0.1(debug@4.3.4)
|
||||
transitivePeerDependencies:
|
||||
@@ -4850,8 +4886,8 @@ packages:
|
||||
unique-string: 2.0.0
|
||||
dev: true
|
||||
|
||||
/terser@5.17.2:
|
||||
resolution: {integrity: sha512-1D1aGbOF1Mnayq5PvfMc0amAR1y5Z1nrZaGCvI5xsdEfZEVte8okonk02OiaK5fw5hG1GWuuVsakOnpZW8y25A==}
|
||||
/terser@5.17.3:
|
||||
resolution: {integrity: sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@@ -4969,8 +5005,8 @@ packages:
|
||||
which-boxed-primitive: 1.0.2
|
||||
dev: true
|
||||
|
||||
/undici@5.22.0:
|
||||
resolution: {integrity: sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==}
|
||||
/undici@5.22.1:
|
||||
resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==}
|
||||
engines: {node: '>=14.0'}
|
||||
dependencies:
|
||||
busboy: 1.6.0
|
||||
@@ -5044,19 +5080,19 @@ packages:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
/vite-plugin-inspect@0.7.26(rollup@3.21.5)(vite@4.3.5):
|
||||
/vite-plugin-inspect@0.7.26(rollup@3.21.6)(vite@4.3.5):
|
||||
resolution: {integrity: sha512-gRjBay+OxLr/Dr+HXlfJVXZH0cqhE5hkkBvo2du2cA1LGUBnV8Aym89AdPrURkSpTk3Rvw9dNWM2VLIuw6RKJg==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
vite: ^3.1.0 || ^4.0.0
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.2
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.5)
|
||||
'@rollup/pluginutils': 5.0.2(rollup@3.21.6)
|
||||
debug: 4.3.4
|
||||
fs-extra: 11.1.1
|
||||
picocolors: 1.0.0
|
||||
sirv: 2.0.3
|
||||
vite: 4.3.5(@types/node@18.16.6)
|
||||
vite: 4.3.5(@types/node@18.16.8)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
@@ -5068,12 +5104,12 @@ packages:
|
||||
workbox-build: ^6.5.4
|
||||
workbox-window: ^6.5.4
|
||||
dependencies:
|
||||
'@rollup/plugin-replace': 5.0.2(rollup@3.21.5)
|
||||
'@rollup/plugin-replace': 5.0.2(rollup@3.21.6)
|
||||
debug: 4.3.4
|
||||
fast-glob: 3.2.12
|
||||
pretty-bytes: 6.1.0
|
||||
rollup: 3.21.5
|
||||
vite: 4.3.5(@types/node@18.16.6)
|
||||
rollup: 3.21.6
|
||||
vite: 4.3.5(@types/node@18.16.8)
|
||||
workbox-build: 6.5.4
|
||||
workbox-window: 6.5.4
|
||||
transitivePeerDependencies:
|
||||
@@ -5093,7 +5129,7 @@ packages:
|
||||
merge-anything: 5.1.6
|
||||
solid-js: 1.7.5
|
||||
solid-refresh: 0.5.2(solid-js@1.7.5)
|
||||
vite: 4.3.5(@types/node@18.16.6)
|
||||
vite: 4.3.5(@types/node@18.16.8)
|
||||
vitefu: 0.2.4(vite@4.3.5)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -5103,10 +5139,10 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^2 || ^3 || ^4
|
||||
dependencies:
|
||||
vite: 4.3.5(@types/node@18.16.6)
|
||||
vite: 4.3.5(@types/node@18.16.8)
|
||||
dev: true
|
||||
|
||||
/vite@4.3.5(@types/node@18.16.6):
|
||||
/vite@4.3.5(@types/node@18.16.8):
|
||||
resolution: {integrity: sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
@@ -5131,10 +5167,10 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 18.16.6
|
||||
'@types/node': 18.16.8
|
||||
esbuild: 0.17.18
|
||||
postcss: 8.4.23
|
||||
rollup: 3.21.5
|
||||
rollup: 3.21.6
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
@@ -5146,7 +5182,7 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 4.3.5(@types/node@18.16.6)
|
||||
vite: 4.3.5(@types/node@18.16.8)
|
||||
|
||||
/wait-on@6.0.1(debug@4.3.4):
|
||||
resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==}
|
||||
|
||||
3
src/assets/icons/bolt.svg
Normal file
3
src/assets/icons/bolt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.0778 6.68331 4.44176 15.5633c-.24.246-.638-.039-.482-.345l3.074-6.06599c.02328-.04578.03442-.09677.03235-.14809-.00207-.05132-.01728-.10125-.04418-.14501-.02689-.04375-.06457-.07987-.10943-.10489-.04485-.02502-.09538-.03811-.14674-.03801H.299757c-.059058-.00005-.116788-.01752-.165955-.05024-.0491673-.03272-.087584-.07922-.1104352-.13368-.02285107-.05446-.02912021-.11445-.01802154-.17246.01109864-.058.03907164-.11144.08041244-.15362L8.09576.0913129c.232-.2349999.618.0230001.489.3280001l-2.297 5.414997c-.01945.04591-.02715.09594-.02241.14557.00475.04963.02179.0973.04958.13869.02779.04139.06546.07521.10961.09838.04414.02318.09336.03499.14322.03436l6.29104-.078c.0593-.00095.1176.01573.1675.04794.0499.03221.0891.0785.1127.133.0235.0545.0304.11477.0197.17317-.0108.0584-.0386.11231-.0799.15489l-.001.001Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 921 B |
4
src/assets/icons/chain.svg
Normal file
4
src/assets/icons/chain.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="17" height="17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m3.2916 7.53561-2.12 2.121C.421438 10.4068 0 11.4242 0 12.4851s.421438 2.0783 1.1716 2.8285c.75017.7502 1.76761 1.1716 2.8285 1.1716 1.0609 0 2.07834-.4214 2.8285-1.1716l2.828-2.828c.3715-.3714.6661-.8124.8671-1.2977.2011-.4853.3045-1.0055.3045-1.53079 0-.5253-.1034-1.04546-.3045-1.53078-.201-.48531-.4956-.92628-.8671-1.29772l-1.06 1.06c.23222.23216.41643.50778.54211.81114.12567.30336.19036.6285.19036.95686 0 .32836-.06469.65349-.19036.95689-.12568.3033-.30989.579-.54211.8111l-2.831 2.828c-.4715.4554-1.10301.7074-1.7585.7017-.65549-.0057-1.28252-.2686-1.74604-.7321-.46352-.4636-.72645-1.0906-.73214-1.7461-.0057-.6555.24629-1.287.70168-1.7585l2.12-2.12099-1.06-1.061h.001Z" fill="#fff"/>
|
||||
<path d="m12.1304 7.8886 2.121-2.12c.4655-.46947.7261-1.10423.7248-1.76538-.0014-.66114-.2646-1.29483-.732-1.7624-.4674-.46756-1.1011-.73093-1.7622-.73247-.6612-.00154-1.296.25887-1.7656.72425l-2.82899 2.828c-.23222.23216-.41643.50779-.5421.81114-.12568.30336-.19037.6285-.19037.95686 0 .32836.06469.65351.19037.95686.12567.30336.30988.57899.5421.81114l-1.06 1.06c-.37146-.37143-.66612-.8124-.86715-1.29772-.20103-.48531-.3045-1.00547-.3045-1.53078 0-.5253.10347-1.04546.3045-1.53078.20103-.48531.49569-.92628.86715-1.29772l2.828-2.828C10.4056.421438 11.423-1e-8 12.4839 0c1.0609 1e-8 2.0783.421438 2.8285 1.1716.7502.75017 1.1716 1.76761 1.1716 2.8285 0 1.0609-.4214 2.07834-1.1716 2.8285l-2.121 2.121-1.061-1.06v-.001Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
src/assets/icons/check.svg
Normal file
3
src/assets/icons/check.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m9.9998 13.5998 5.9-5.9c.1833-.18333.4167-.275.7-.275.2833 0 .5167.09167.7.275.1833.18334.275.41667.275.7 0 .28334-.0917.51667-.275.7l-6.6 6.6c-.2.2-.4333.3-.7.3-.26666 0-.5-.1-.7-.3l-2.6-2.6c-.18333-.1833-.275-.4167-.275-.7 0-.2833.09167-.5167.275-.7.18334-.1833.41667-.275.7-.275.28334 0 .51667.0917.7.275l1.9 1.9Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 425 B |
3
src/assets/icons/save.svg
Normal file
3
src/assets/icons/save.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 3c-.53043 0-1.03914.21071-1.41421.58579C3.21071 3.96086 3 4.46957 3 5v14c0 .5304.21071 1.0391.58579 1.4142C3.96086 20.7893 4.46957 21 5 21h14c.5304 0 1.0391-.2107 1.4142-.5858S21 19.5304 21 19V5.5L18.5 3H17v6c0 .26522-.1054.51957-.2929.70711C16.5196 9.89464 16.2652 10 16 10H8c-.26522 0-.51957-.10536-.70711-.29289C7.10536 9.51957 7 9.26522 7 9V3H5Zm7 1v5h3V4h-3Zm-5 8h10c.2652 0 .5196.1054.7071.2929S18 12.7348 18 13v6H6v-6c0-.2652.10536-.5196.29289-.7071C6.48043 12.1054 6.73478 12 7 12Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 601 B |
3
src/assets/icons/upload.svg
Normal file
3
src/assets/icons/upload.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 20c-.55 0-1.021-.196-1.413-.588C4.195 19.02 3.99934 18.5493 4 18v-3h2v3h12v-3h2v3c0 .55-.196 1.021-.588 1.413-.392.392-.8627.5877-1.412.587H6Zm5-4V7.85l-2.6 2.6L7 9l5-5 5 5-1.4 1.45-2.6-2.6V16h-2Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 308 B |
@@ -1,15 +1,13 @@
|
||||
import send from '~/assets/icons/send.svg';
|
||||
import receive from '~/assets/icons/receive.svg';
|
||||
import { ButtonLink, Card, LoadingSpinner, NiceP, SmallAmount, SmallHeader, VStack } from './layout';
|
||||
import { For, Match, ParentComponent, Show, Suspense, Switch, createMemo, createResource, createSignal } from 'solid-js';
|
||||
import { LoadingSpinner, NiceP, SmallAmount, SmallHeader } from './layout';
|
||||
import { For, Match, ParentComponent, Show, Switch, createMemo, createResource, createSignal } from 'solid-js';
|
||||
import { useMegaStore } from '~/state/megaStore';
|
||||
import { MutinyInvoice } from '@mutinywallet/mutiny-wasm';
|
||||
import { prettyPrintTime } from '~/utils/prettyPrintTime';
|
||||
import { JsonModal } from '~/components/JsonModal';
|
||||
import mempoolTxUrl from '~/utils/mempoolTxUrl';
|
||||
import wave from "~/assets/wave.gif"
|
||||
import utxoIcon from '~/assets/icons/coin.svg';
|
||||
import { getRedshifted } from '~/utils/fakeLabels';
|
||||
import { ActivityItem } from './ActivityItem';
|
||||
import { MutinyTagItem } from '~/utils/tags';
|
||||
|
||||
export const THREE_COLUMNS = 'grid grid-cols-[auto,1fr,auto] gap-4 py-2 px-2 border-b border-neutral-800 last:border-b-0'
|
||||
export const CENTER_COLUMN = 'min-w-0 overflow-hidden max-w-full'
|
||||
@@ -27,7 +25,8 @@ export type OnChainTx = {
|
||||
height: number
|
||||
time: number
|
||||
}
|
||||
}
|
||||
},
|
||||
labels: string[]
|
||||
}
|
||||
|
||||
export type UtxoItem = {
|
||||
@@ -38,15 +37,16 @@ export type UtxoItem = {
|
||||
}
|
||||
keychain: string
|
||||
is_spent: boolean,
|
||||
redshifted?: boolean
|
||||
redshifted?: boolean,
|
||||
}
|
||||
|
||||
const SubtleText: ParentComponent = (props) => {
|
||||
return <h3 class='text-xs text-gray-500 uppercase'>{props.children}</h3>
|
||||
}
|
||||
|
||||
function OnChainItem(props: { item: OnChainTx }) {
|
||||
const isReceive = createMemo(() => props.item.received > 0);
|
||||
function OnChainItem(props: { item: OnChainTx, labels: MutinyTagItem[] }) {
|
||||
const [store, actions] = useMegaStore();
|
||||
const isReceive = () => props.item.received > props.item.sent
|
||||
|
||||
const [open, setOpen] = createSignal(false)
|
||||
|
||||
@@ -57,26 +57,21 @@ function OnChainItem(props: { item: OnChainTx }) {
|
||||
Mempool Link
|
||||
</a>
|
||||
</JsonModal>
|
||||
<div class={THREE_COLUMNS} onClick={() => setOpen(!open())}>
|
||||
<div class="flex items-center">
|
||||
{isReceive() ? <img src={receive} alt="receive arrow" /> : <img src={send} alt="send arrow" />}
|
||||
</div>
|
||||
<div class={CENTER_COLUMN}>
|
||||
<h2 class={MISSING_LABEL}>Unknown</h2>
|
||||
{isReceive() ? <SmallAmount amount={props.item.received} /> : <SmallAmount amount={props.item.sent} />}
|
||||
</div>
|
||||
<div class={RIGHT_COLUMN}>
|
||||
<SmallHeader>
|
||||
<span class="text-neutral-500">On-chain</span> {isReceive() ? <span class="text-m-green">Receive</span> : <span class="text-m-red">Send</span>}
|
||||
</SmallHeader>
|
||||
<SubtleText>{props.item.confirmation_time?.Confirmed ? prettyPrintTime(props.item.confirmation_time?.Confirmed?.time) : "Unconfirmed"}</SubtleText>
|
||||
</div>
|
||||
</div>
|
||||
{/* {JSON.stringify(props.labels)} */}
|
||||
<ActivityItem
|
||||
kind={"onchain"}
|
||||
labels={props.labels}
|
||||
amount={isReceive() ? props.item.received : props.item.sent}
|
||||
date={props.item.confirmation_time?.Confirmed?.time}
|
||||
positive={isReceive()}
|
||||
onClick={() => setOpen(!open())}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function InvoiceItem(props: { item: MutinyInvoice }) {
|
||||
function InvoiceItem(props: { item: MutinyInvoice, labels: MutinyTagItem[] }) {
|
||||
const [store, actions] = useMegaStore();
|
||||
const isSend = createMemo(() => props.item.is_send);
|
||||
|
||||
const [open, setOpen] = createSignal(false)
|
||||
@@ -84,21 +79,7 @@ function InvoiceItem(props: { item: MutinyInvoice }) {
|
||||
return (
|
||||
<>
|
||||
<JsonModal open={open()} data={props.item} title="Lightning Transaction" setOpen={setOpen} />
|
||||
<div class={THREE_COLUMNS} onClick={() => setOpen(!open())}>
|
||||
<div class="flex items-center">
|
||||
{isSend() ? <img src={send} alt="send arrow" /> : <img src={receive} alt="receive arrow" />}
|
||||
</div>
|
||||
<div class={CENTER_COLUMN}>
|
||||
<h2 class={MISSING_LABEL}>Unknown</h2>
|
||||
<SmallAmount amount={props.item.amount_sats || 0} />
|
||||
</div>
|
||||
<div class={RIGHT_COLUMN}>
|
||||
<SmallHeader>
|
||||
<span class="text-neutral-500">Lightning</span> {!isSend() ? <span class="text-m-green">Receive</span> : <span class="text-m-red">Send</span>}
|
||||
</SmallHeader>
|
||||
<SubtleText>{prettyPrintTime(Number(props.item.expire))}</SubtleText>
|
||||
</div>
|
||||
</div >
|
||||
<ActivityItem kind={"lightning"} labels={props.labels} amount={props.item.amount_sats || 0n} date={props.item.last_updated} positive={!isSend()} onClick={() => setOpen(!open())} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -135,122 +116,45 @@ function Utxo(props: { item: UtxoItem }) {
|
||||
)
|
||||
}
|
||||
|
||||
export function Activity() {
|
||||
const [state, _] = useMegaStore();
|
||||
|
||||
const getTransactions = async () => {
|
||||
console.log("Getting onchain txs");
|
||||
const txs = await state.mutiny_wallet?.list_onchain() as OnChainTx[];
|
||||
return txs.reverse();
|
||||
}
|
||||
|
||||
const getInvoices = async () => {
|
||||
console.log("Getting invoices");
|
||||
const invoices = await state.mutiny_wallet?.list_invoices() as MutinyInvoice[];
|
||||
return invoices.filter((inv) => inv.paid).reverse();
|
||||
}
|
||||
|
||||
const getUtXos = async () => {
|
||||
console.log("Getting utxos");
|
||||
const utxos = await state.mutiny_wallet?.list_utxos() as UtxoItem[];
|
||||
return utxos;
|
||||
}
|
||||
|
||||
const [transactions, { refetch: _refetchTransactions }] = createResource(getTransactions);
|
||||
const [invoices, { refetch: _refetchInvoices }] = createResource(getInvoices);
|
||||
const [utxos, { refetch: _refetchUtxos }] = createResource(getUtXos);
|
||||
|
||||
return (
|
||||
<VStack>
|
||||
<Suspense>
|
||||
<Card title="On-chain">
|
||||
<Switch>
|
||||
<Match when={transactions.loading}>
|
||||
<LoadingSpinner wide />
|
||||
</Match>
|
||||
<Match when={transactions.state === "ready" && transactions().length === 0}>
|
||||
<code>No transactions (empty state)</code>
|
||||
</Match>
|
||||
<Match when={transactions.state === "ready" && transactions().length >= 0}>
|
||||
<For each={transactions()}>
|
||||
{(tx) =>
|
||||
<OnChainItem item={tx} />
|
||||
}
|
||||
</For>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Card>
|
||||
<Card title="Lightning">
|
||||
<Switch>
|
||||
<Match when={invoices.loading}>
|
||||
<LoadingSpinner wide />
|
||||
</Match>
|
||||
<Match when={invoices.state === "ready" && invoices().length === 0}>
|
||||
<code>No invoices (empty state)</code>
|
||||
</Match>
|
||||
<Match when={invoices.state === "ready" && invoices().length >= 0}>
|
||||
<For each={invoices()}>
|
||||
{(invoice) =>
|
||||
<InvoiceItem item={invoice} />
|
||||
}
|
||||
</For>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Card>
|
||||
<Card title="UTXOs">
|
||||
<Switch>
|
||||
<Match when={utxos.loading}>
|
||||
<LoadingSpinner wide />
|
||||
</Match>
|
||||
<Match when={utxos.state === "ready" && utxos().length === 0}>
|
||||
<code>No utxos (empty state)</code>
|
||||
</Match>
|
||||
<Match when={utxos.state === "ready" && utxos().length >= 0}>
|
||||
<For each={utxos()}>
|
||||
{(utxo) =>
|
||||
<Utxo item={utxo} />
|
||||
}
|
||||
</For>
|
||||
</Match>
|
||||
</Switch>
|
||||
<ButtonLink href="/redshift" layout="small" class="flex items-center gap-2 self-center hover:text-m-red">Redshift <img src={wave} class="h-4" alt="redshift"></img></ButtonLink>
|
||||
</Card>
|
||||
</Suspense>
|
||||
</VStack>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
type ActivityItem = { type: "onchain" | "lightning", item: OnChainTx | MutinyInvoice, time: number }
|
||||
type ActivityItem = { type: "onchain" | "lightning", item: OnChainTx | MutinyInvoice, time: number, labels: MutinyTagItem[] }
|
||||
|
||||
function sortByTime(a: ActivityItem, b: ActivityItem) {
|
||||
return b.time - a.time;
|
||||
}
|
||||
|
||||
export function CombinedActivity(props: { limit?: number }) {
|
||||
|
||||
const [state, _] = useMegaStore();
|
||||
const [state, actions] = useMegaStore();
|
||||
|
||||
const getAllActivity = async () => {
|
||||
console.log("Getting all activity");
|
||||
const txs = await state.mutiny_wallet?.list_onchain() as OnChainTx[];
|
||||
const invoices = await state.mutiny_wallet?.list_invoices() as MutinyInvoice[];
|
||||
const tags = await actions.listTags();
|
||||
|
||||
const activity: ActivityItem[] = [];
|
||||
let activity: ActivityItem[] = [];
|
||||
|
||||
txs.forEach((tx) => {
|
||||
activity.push({ type: "onchain", item: tx, time: tx.confirmation_time?.Confirmed?.time || Date.now() })
|
||||
})
|
||||
for (let i = 0; i < txs.length; i++) {
|
||||
activity.push({ type: "onchain", item: txs[i], time: txs[i].confirmation_time?.Confirmed?.time || Date.now(), labels: [] })
|
||||
}
|
||||
|
||||
invoices.forEach((invoice) => {
|
||||
activity.push({ type: "lightning", item: invoice, time: Number(invoice.expire) })
|
||||
})
|
||||
for (let i = 0; i < invoices.length; i++) {
|
||||
if (invoices[i].paid) {
|
||||
activity.push({ type: "lightning", item: invoices[i], time: Number(invoices[i].expire), labels: [] })
|
||||
}
|
||||
}
|
||||
|
||||
if (props.limit) {
|
||||
return activity.sort(sortByTime).slice(0, props.limit);
|
||||
activity = activity.sort(sortByTime).slice(0, props.limit);
|
||||
} else {
|
||||
return activity.sort(sortByTime);
|
||||
activity.sort(sortByTime);
|
||||
}
|
||||
|
||||
for (let i = 0; i < activity.length; i++) {
|
||||
// filter the tags to only include the ones that have an id matching one of the labels
|
||||
activity[i].labels = tags.filter((tag) => activity[i].item.labels.includes(tag.id));
|
||||
}
|
||||
|
||||
return activity;
|
||||
}
|
||||
|
||||
const [activity] = createResource(getAllActivity);
|
||||
@@ -268,10 +172,12 @@ export function CombinedActivity(props: { limit?: number }) {
|
||||
{(activityItem) =>
|
||||
<Switch>
|
||||
<Match when={activityItem.type === "onchain"}>
|
||||
<OnChainItem item={activityItem.item as OnChainTx} />
|
||||
{/* FIXME */}
|
||||
<OnChainItem item={activityItem.item as OnChainTx} labels={activityItem.labels} />
|
||||
</Match>
|
||||
<Match when={activityItem.type === "lightning"}>
|
||||
<InvoiceItem item={activityItem.item as MutinyInvoice} />
|
||||
{/* FIXME */}
|
||||
<InvoiceItem item={activityItem.item as MutinyInvoice} labels={activityItem.labels} />
|
||||
</Match>
|
||||
</Switch>
|
||||
}
|
||||
|
||||
100
src/components/ActivityItem.tsx
Normal file
100
src/components/ActivityItem.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { ParentComponent, createMemo, createResource } from "solid-js";
|
||||
import { InlineAmount } from "./AmountCard";
|
||||
import { satsToUsd } from "~/utils/conversions";
|
||||
import bolt from "~/assets/icons/bolt.svg"
|
||||
import chain from "~/assets/icons/chain.svg"
|
||||
import { timeAgo } from "~/utils/prettyPrintTime";
|
||||
import { MutinyTagItem } from "~/utils/tags";
|
||||
import { generateGradient } from "~/utils/gradientHash";
|
||||
|
||||
export const ActivityAmount: ParentComponent<{ amount: string, price: number, positive?: boolean }> = (props) => {
|
||||
const amountInUsd = createMemo(() => {
|
||||
const parsed = Number(props.amount);
|
||||
if (isNaN(parsed)) {
|
||||
return props.amount;
|
||||
} else {
|
||||
return satsToUsd(props.price, parsed, true);
|
||||
}
|
||||
})
|
||||
|
||||
const prettyPrint = createMemo(() => {
|
||||
const parsed = Number(props.amount);
|
||||
if (isNaN(parsed)) {
|
||||
return props.amount;
|
||||
} else {
|
||||
return parsed.toLocaleString();
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="flex flex-col items-end">
|
||||
<div class="text-base"
|
||||
classList={{ "text-m-green": props.positive }}
|
||||
>{props.positive && "+ "}{prettyPrint()} <span class="text-sm">SATS</span>
|
||||
</div>
|
||||
<div class="text-sm text-neutral-500">≈ {amountInUsd()} <span class="text-sm">USD</span></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function LabelCircle(props: { name?: string, contact: boolean }) {
|
||||
|
||||
// TODO: don't need to run this if it's not a contact
|
||||
const [gradient] = createResource(props.name, async (name: string) => {
|
||||
return generateGradient(name || "?")
|
||||
})
|
||||
|
||||
const text = () => (props.contact && props.name && props.name.length) ? props.name[0] : (props.name && props.name.length) ? "≡" : "?"
|
||||
const bg = () => (props.name && props.contact) ? gradient() : "gray"
|
||||
|
||||
return (
|
||||
<div class="flex-none h-[3rem] w-[3rem] rounded-full flex items-center justify-center text-3xl uppercase border-t border-b border-t-white/50 border-b-white/10"
|
||||
style={{ background: bg() }}
|
||||
>
|
||||
{text()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// function that takes a list of MutinyTagItems and returns bool if one of those items is of kind Contact
|
||||
function includesContact(labels: MutinyTagItem[]) {
|
||||
return labels.some((label) => label.kind === "Contact")
|
||||
}
|
||||
|
||||
// sort the labels so that the contact is always first
|
||||
function sortLabels(labels: MutinyTagItem[]) {
|
||||
const contact = labels.find(label => label.kind === "Contact");
|
||||
return contact ? [contact, ...labels.filter(label => label !== contact)] : labels;
|
||||
}
|
||||
|
||||
// return a string of each label name separated by a comma and a space. if the array is empty return "Unknown"
|
||||
function labelString(labels: MutinyTagItem[]) {
|
||||
return labels.length ? labels.map(label => label.name).join(", ") : "Unknown"
|
||||
}
|
||||
|
||||
export function ActivityItem(props: { kind: "lightning" | "onchain", labels: MutinyTagItem[], amount: number | bigint, date?: number | bigint, positive?: boolean, onClick?: () => void }) {
|
||||
const labels = () => sortLabels(props.labels)
|
||||
return (
|
||||
<div
|
||||
onClick={() => props.onClick && props.onClick()}
|
||||
class="grid grid-cols-[auto_minmax(0,_1fr)_minmax(0,_max-content)] pb-4 gap-4 border-b border-neutral-800 last:border-b-0"
|
||||
classList={{ "cursor-pointer": !!props.onClick }}
|
||||
>
|
||||
<div class="flex gap-2 md:gap-4 items-center">
|
||||
<div class="">
|
||||
{props.kind === "lightning" ? <img class="w-[1rem]" src={bolt} alt="lightning" /> : <img class="w-[1rem]" src={chain} alt="onchain" />}
|
||||
</div>
|
||||
<div class="">
|
||||
<LabelCircle name={labels().length ? labels()[0].name : ""} contact={includesContact(labels())} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-base font-semibold truncate" classList={{ "text-neutral-500": labels().length === 0 }}>{labelString(labels())}</span>
|
||||
<time class="text-sm text-neutral-500">{timeAgo(props.date)}</time>
|
||||
</div>
|
||||
<div class="">
|
||||
<ActivityAmount amount={props.amount.toString()} price={30000} positive={props.positive} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -135,7 +135,7 @@ export const AmountEditable: ParentComponent<{ initialAmountSats: string, initia
|
||||
const DIALOG_CONTENT = "h-full safe-bottom flex flex-col justify-between p-4 backdrop-blur-xl bg-neutral-800/70"
|
||||
|
||||
return (
|
||||
<Dialog.Root isOpen={isOpen()}>
|
||||
<Dialog.Root open={isOpen()}>
|
||||
<button onClick={() => setIsOpen(true)} class="px-4 py-2 rounded-xl border-2 border-m-blue flex gap-2 items-center">
|
||||
{/* <Amount amountSats={Number(displayAmount())} showFiat /><span>✏️</span> */}
|
||||
<Show when={displayAmount() !== "0"} fallback={<div class="inline-block font-semibold">Set amount</div>}>
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
import logo from '~/assets/icons/mutiny-logo.svg';
|
||||
import { DefaultMain, MutinyWalletGuard, SafeArea, VStack, Card } from "~/components/layout";
|
||||
import BalanceBox from "~/components/BalanceBox";
|
||||
import { DefaultMain, SafeArea, VStack, Card, LoadingSpinner } from "~/components/layout";
|
||||
import BalanceBox, { LoadingShimmer } from "~/components/BalanceBox";
|
||||
import NavBar from "~/components/NavBar";
|
||||
import ReloadPrompt from "~/components/Reload";
|
||||
import { A } from 'solid-start';
|
||||
import { OnboardWarning } from '~/components/OnboardWarning';
|
||||
import { CombinedActivity } from './Activity';
|
||||
import userClock from '~/assets/icons/user-clock.svg';
|
||||
import { useMegaStore } from '~/state/megaStore';
|
||||
import { Show } from 'solid-js';
|
||||
|
||||
export default function App() {
|
||||
const [state, _actions] = useMegaStore();
|
||||
|
||||
return (
|
||||
<MutinyWalletGuard>
|
||||
<SafeArea>
|
||||
<DefaultMain>
|
||||
<header class="w-full flex justify-between items-center mt-4 mb-2">
|
||||
<img src={logo} class="h-10" alt="logo" />
|
||||
<A class="md:hidden p-2 hover:bg-white/5 rounded-lg active:bg-m-blue" href="/activity"><img src={userClock} alt="Activity" /></A>
|
||||
</header>
|
||||
<SafeArea>
|
||||
<DefaultMain>
|
||||
<header class="w-full flex justify-between items-center mt-4 mb-2">
|
||||
<img src={logo} class="h-10" alt="logo" />
|
||||
<A class="md:hidden p-2 hover:bg-white/5 rounded-lg active:bg-m-blue" href="/activity"><img src={userClock} alt="Activity" /></A>
|
||||
</header>
|
||||
<Show when={!state.wallet_loading}>
|
||||
<OnboardWarning />
|
||||
<ReloadPrompt />
|
||||
<BalanceBox />
|
||||
<Card title="Activity">
|
||||
<VStack>
|
||||
</Show>
|
||||
<BalanceBox loading={state.wallet_loading} />
|
||||
<Card title="Activity">
|
||||
<div class="p-1" />
|
||||
<VStack>
|
||||
<Show when={!state.wallet_loading} fallback={<LoadingShimmer />}>
|
||||
<CombinedActivity limit={3} />
|
||||
{/* <ButtonLink href="/activity">View All</ButtonLink> */}
|
||||
<A href="/activity" class="text-m-red active:text-m-red/80 text-xl font-semibold no-underline self-center">View All</A>
|
||||
</VStack>
|
||||
</Card>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="home" />
|
||||
</SafeArea>
|
||||
</MutinyWalletGuard>
|
||||
</Show>
|
||||
{/* <ButtonLink href="/activity">View All</ButtonLink> */}
|
||||
</VStack>
|
||||
<A href="/activity" class="text-m-red active:text-m-red/80 text-xl font-semibold no-underline self-center">View All</A>
|
||||
</Card>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="home" />
|
||||
</SafeArea>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Show, Suspense } from "solid-js";
|
||||
import { ButtonLink, FancyCard, Indicator } from "~/components/layout";
|
||||
import { Button, ButtonLink, FancyCard, Indicator } from "~/components/layout";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { Amount } from "./Amount";
|
||||
import { useNavigate } from "solid-start";
|
||||
|
||||
function prettyPrintAmount(n?: number | bigint): string {
|
||||
if (!n || n.valueOf() === 0) {
|
||||
@@ -10,19 +11,38 @@ function prettyPrintAmount(n?: number | bigint): string {
|
||||
return n.toLocaleString()
|
||||
}
|
||||
|
||||
export default function BalanceBox() {
|
||||
export function LoadingShimmer() {
|
||||
return (<div class="flex flex-col gap-2 animate-pulse">
|
||||
<h1 class="text-4xl font-light">
|
||||
<div class="w-[12rem] rounded bg-neutral-700 h-[2.5rem]"></div>
|
||||
</h1>
|
||||
<h2 class="text-xl font-light text-white/70" >
|
||||
<div class="w-[8rem] rounded bg-neutral-700 h-[1.75rem]"></div>
|
||||
</h2>
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default function BalanceBox(props: { loading?: boolean }) {
|
||||
const [state, actions] = useMegaStore();
|
||||
|
||||
const emptyBalance = () => (state.balance?.confirmed || 0n) === 0n && (state.balance?.lightning || 0n) === 0n
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<>
|
||||
<FancyCard title="Lightning">
|
||||
<Amount amountSats={state.balance?.lightning || 0} showFiat />
|
||||
<Show when={!props.loading} fallback={<LoadingShimmer />}>
|
||||
<Amount amountSats={state.balance?.lightning || 0} showFiat />
|
||||
</Show>
|
||||
</FancyCard>
|
||||
|
||||
<FancyCard title="On-Chain" tag={state.is_syncing && <Indicator>Syncing</Indicator>}>
|
||||
<div onClick={actions.sync}>
|
||||
<Amount amountSats={state.balance?.confirmed} showFiat />
|
||||
</div>
|
||||
<Show when={!props.loading} fallback={<LoadingShimmer />}>
|
||||
<div onClick={actions.sync}>
|
||||
<Amount amountSats={state.balance?.confirmed} showFiat />
|
||||
</div>
|
||||
</Show>
|
||||
<Suspense>
|
||||
<Show when={state.balance?.unconfirmed}>
|
||||
<div class="flex flex-col gap-2">
|
||||
@@ -37,8 +57,8 @@ export default function BalanceBox() {
|
||||
</Suspense>
|
||||
</FancyCard>
|
||||
<div class="flex gap-2 py-4">
|
||||
<ButtonLink href="/send" intent="green">Send</ButtonLink>
|
||||
<ButtonLink href="/receive" intent="blue">Receive</ButtonLink>
|
||||
<Button onClick={() => navigate("/send")} disabled={emptyBalance() || props.loading} intent="green">Send</Button>
|
||||
<Button onClick={() => navigate("/receive")} disabled={props.loading} intent="blue">Receive</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { RadioGroup as Kobalte } from '@kobalte/core';
|
||||
import { type JSX, Show, splitProps, For } from 'solid-js';
|
||||
|
||||
type RadioGroupProps = {
|
||||
name: string;
|
||||
label?: string | undefined;
|
||||
options: { label: string; value: string }[];
|
||||
value: string | undefined;
|
||||
error: string;
|
||||
required?: boolean | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
ref: (element: HTMLInputElement | HTMLTextAreaElement) => void;
|
||||
onInput: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, InputEvent>;
|
||||
onChange: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, Event>;
|
||||
onBlur: JSX.EventHandler<HTMLInputElement | HTMLTextAreaElement, FocusEvent>;
|
||||
};
|
||||
type Color = "blue" | "green" | "red" | "gray"
|
||||
|
||||
export const colorVariants = {
|
||||
blue: "bg-m-blue",
|
||||
green: "bg-m-green",
|
||||
red: "bg-m-red",
|
||||
gray: "bg-[#898989]",
|
||||
}
|
||||
|
||||
export function ColorRadioGroup(props: RadioGroupProps) {
|
||||
const [rootProps, inputProps] = splitProps(
|
||||
props,
|
||||
['name', 'value', 'required', 'disabled'],
|
||||
['ref', 'onInput', 'onChange', 'onBlur']
|
||||
);
|
||||
return (
|
||||
<Kobalte.Root
|
||||
{...rootProps}
|
||||
validationState={props.error ? 'invalid' : 'valid'}
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<Show when={props.label}>
|
||||
<Kobalte.Label class="text-sm uppercase font-semibold">
|
||||
{props.label}
|
||||
</Kobalte.Label>
|
||||
</Show>
|
||||
<div class="flex gap-2">
|
||||
<For each={props.options}>
|
||||
{(option) => (
|
||||
<Kobalte.Item value={option.value} class="ui-checked:bg-neutral-950 rounded outline outline-black/50 ui-checked:outline-white ui-checked:outline-2">
|
||||
<Kobalte.ItemInput {...inputProps} />
|
||||
<Kobalte.ItemControl class={`${colorVariants[option.value as Color]} w-8 h-8 rounded`}>
|
||||
<Kobalte.ItemIndicator />
|
||||
</Kobalte.ItemControl>
|
||||
{/* <Kobalte.ItemLabel>{option.label}</Kobalte.ItemLabel> */}
|
||||
</Kobalte.Item>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Kobalte.ErrorMessage>{props.error}</Kobalte.ErrorMessage>
|
||||
</Kobalte.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +1,17 @@
|
||||
import { Match, Switch, createSignal, createUniqueId } from 'solid-js';
|
||||
import { Match, Switch, createSignal } from 'solid-js';
|
||||
import { SmallHeader, TinyButton } from '~/components/layout';
|
||||
import { Dialog } from '@kobalte/core';
|
||||
import close from "~/assets/icons/close.svg";
|
||||
import { SubmitHandler } from '@modular-forms/solid';
|
||||
import { ContactItem } from '~/state/contacts';
|
||||
import { ContactForm } from './ContactForm';
|
||||
import { ContactFormValues } from './ContactViewer';
|
||||
|
||||
const INITIAL: ContactItem = { id: createUniqueId(), kind: "contact", name: "", color: "gray" }
|
||||
|
||||
export function ContactEditor(props: { createContact: (contact: ContactItem) => void, list?: boolean }) {
|
||||
export function ContactEditor(props: { createContact: (contact: ContactFormValues) => void, list?: boolean }) {
|
||||
const [isOpen, setIsOpen] = createSignal(false);
|
||||
|
||||
// What we're all here for in the first place: returning a value
|
||||
const handleSubmit: SubmitHandler<ContactItem> = (c: ContactItem) => {
|
||||
// TODO: why do the id and color disappear?
|
||||
|
||||
const odd = { id: createUniqueId(), kind: "contact" }
|
||||
|
||||
props.createContact({ ...odd, ...c })
|
||||
|
||||
const handleSubmit: SubmitHandler<ContactFormValues> = (c: ContactFormValues) => {
|
||||
props.createContact(c)
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
@@ -26,7 +19,7 @@ export function ContactEditor(props: { createContact: (contact: ContactItem) =>
|
||||
const DIALOG_CONTENT = "h-full safe-bottom flex flex-col justify-between p-4 backdrop-blur-xl bg-neutral-800/70"
|
||||
|
||||
return (
|
||||
<Dialog.Root isOpen={isOpen()}>
|
||||
<Dialog.Root open={isOpen()}>
|
||||
<Switch>
|
||||
<Match when={props.list}>
|
||||
<button onClick={() => setIsOpen(true)} class="flex flex-col items-center gap-2">
|
||||
@@ -50,7 +43,7 @@ export function ContactEditor(props: { createContact: (contact: ContactItem) =>
|
||||
<img src={close} alt="Close" />
|
||||
</button>
|
||||
</div>
|
||||
<ContactForm title="New contact" cta="Create contact" handleSubmit={handleSubmit} initialValues={INITIAL} />
|
||||
<ContactForm title="New contact" cta="Create contact" handleSubmit={handleSubmit} />
|
||||
</Dialog.Content>
|
||||
</div>
|
||||
</Dialog.Portal>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { SubmitHandler, createForm, required } from "@modular-forms/solid";
|
||||
import { ContactItem } from "~/state/contacts";
|
||||
import { Button, LargeHeader, VStack } from "~/components/layout";
|
||||
import { TextField } from "~/components/layout/TextField";
|
||||
import { ColorRadioGroup } from "~/components/ColorRadioGroup";
|
||||
import { ContactFormValues } from "./ContactViewer";
|
||||
|
||||
const colorOptions = [{ label: "blue", value: "blue" }, { label: "green", value: "green" }, { label: "red", value: "red" }, { label: "gray", value: "gray" }]
|
||||
|
||||
export function ContactForm(props: { handleSubmit: SubmitHandler<ContactItem>, initialValues?: ContactItem, title: string, cta: string }) {
|
||||
const [_contactForm, { Form, Field }] = createForm<ContactItem>({ initialValues: props.initialValues });
|
||||
export function ContactForm(props: { handleSubmit: SubmitHandler<ContactFormValues>, initialValues?: ContactFormValues, title: string, cta: string }) {
|
||||
const [_contactForm, { Form, Field }] = createForm<ContactFormValues>({ initialValues: props.initialValues });
|
||||
|
||||
return (
|
||||
<Form onSubmit={props.handleSubmit} class="flex flex-col flex-1 justify-around gap-4 max-w-[400px] mx-auto w-full">
|
||||
@@ -19,16 +16,11 @@ export function ContactForm(props: { handleSubmit: SubmitHandler<ContactItem>, i
|
||||
<TextField {...props} placeholder='Satoshi' value={field.value} error={field.error} label="Name" />
|
||||
)}
|
||||
</Field>
|
||||
<Field name="npub" validate={[]}>
|
||||
{/* <Field name="npub" validate={[]}>
|
||||
{(field, props) => (
|
||||
<TextField {...props} placeholder='npub...' value={field.value} error={field.error} label="Nostr npub or NIP-05 (optional)" />
|
||||
)}
|
||||
</Field>
|
||||
<Field name="color">
|
||||
{(field, props) => (
|
||||
<ColorRadioGroup options={colorOptions} {...props} value={field.value} error={field.error} label="Color" />
|
||||
)}
|
||||
</Field>
|
||||
</Field> */}
|
||||
</VStack>
|
||||
</div>
|
||||
<Button type="submit" intent="blue" class="w-full flex-none">
|
||||
|
||||
@@ -3,16 +3,24 @@ import { Button, Card, NiceP, SmallHeader } from '~/components/layout';
|
||||
import { Dialog } from '@kobalte/core';
|
||||
import close from "~/assets/icons/close.svg";
|
||||
import { SubmitHandler } from '@modular-forms/solid';
|
||||
import { ContactItem } from '~/state/contacts';
|
||||
import { ContactForm } from './ContactForm';
|
||||
import { showToast } from './Toaster';
|
||||
import { Contact } from '@mutinywallet/mutiny-wasm';
|
||||
|
||||
export function ContactViewer(props: { contact: ContactItem, gradient: string, saveContact: (contact: ContactItem) => void }) {
|
||||
export type ContactFormValues = {
|
||||
name: string,
|
||||
npub?: string,
|
||||
}
|
||||
|
||||
export function ContactViewer(props: { contact: Contact, gradient: string, saveContact: (contact: Contact) => void }) {
|
||||
const [isOpen, setIsOpen] = createSignal(false);
|
||||
const [isEditing, setIsEditing] = createSignal(false);
|
||||
|
||||
const handleSubmit: SubmitHandler<ContactItem> = (c: ContactItem) => {
|
||||
props.saveContact({ ...props.contact, ...c })
|
||||
const handleSubmit: SubmitHandler<ContactFormValues> = (c: ContactFormValues) => {
|
||||
// FIXME: merge with existing contact if saving (need edit contact method)
|
||||
// FIXME: npub not valid? other undefineds
|
||||
const contact = new Contact(c.name, undefined, undefined, undefined)
|
||||
props.saveContact(contact)
|
||||
setIsEditing(false)
|
||||
}
|
||||
|
||||
@@ -20,7 +28,7 @@ export function ContactViewer(props: { contact: ContactItem, gradient: string, s
|
||||
const DIALOG_CONTENT = "h-full safe-bottom flex flex-col justify-between p-4 backdrop-blur-xl bg-neutral-800/70"
|
||||
|
||||
return (
|
||||
<Dialog.Root isOpen={isOpen()}>
|
||||
<Dialog.Root open={isOpen()}>
|
||||
<button onClick={() => setIsOpen(true)} class="flex flex-col items-center gap-2 w-16 flex-shrink-0 overflow-x-hidden">
|
||||
<div class="flex-none h-16 w-16 rounded-full flex items-center justify-center text-4xl uppercase border-t border-b border-t-white/50 border-b-white/10"
|
||||
style={{ background: props.gradient }}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function DeleteEverything() {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={confirmReset}>Delete Everything</Button>
|
||||
<ConfirmDialog loading={confirmLoading()} isOpen={confirmOpen()} onConfirm={resetNode} onCancel={() => setConfirmOpen(false)}>
|
||||
<ConfirmDialog loading={confirmLoading()} open={confirmOpen()} onConfirm={resetNode} onCancel={() => setConfirmOpen(false)}>
|
||||
This will delete your node's state. This can't be undone!
|
||||
</ConfirmDialog>
|
||||
</>
|
||||
|
||||
@@ -7,9 +7,9 @@ const DIALOG_POSITIONER = "fixed inset-0 z-50 flex items-center justify-center"
|
||||
const DIALOG_CONTENT = "w-[80vw] max-w-[400px] p-4 bg-gray/50 backdrop-blur-md shadow-xl rounded-xl border border-white/10"
|
||||
|
||||
// TODO: implement this like toast so it's just one global confirm and I can call it with `confirm({ title: "Are you sure?", description: "This will delete your node" })`
|
||||
export const ConfirmDialog: ParentComponent<{ isOpen: boolean; loading: boolean; onCancel: () => void, onConfirm: () => void }> = (props) => {
|
||||
export const ConfirmDialog: ParentComponent<{ open: boolean; loading: boolean; onCancel: () => void, onConfirm: () => void }> = (props) => {
|
||||
return (
|
||||
<Dialog.Root isOpen={props.isOpen} onOpenChange={props.onCancel}>
|
||||
<Dialog.Root open={props.open} onOpenChange={props.onCancel}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay class={OVERLAY} />
|
||||
<div class={DIALOG_POSITIONER}>
|
||||
|
||||
@@ -67,7 +67,7 @@ export function ImportExport() {
|
||||
<Button onClick={uploadFile}>Upload Saved State</Button>
|
||||
</VStack>
|
||||
</InnerCard>
|
||||
<ConfirmDialog loading={confirmLoading()} isOpen={confirmOpen()} onConfirm={importJson} onCancel={() => setConfirmOpen(false)}>
|
||||
<ConfirmDialog loading={confirmLoading()} open={confirmOpen()} onConfirm={importJson} onCancel={() => setConfirmOpen(false)}>
|
||||
Do you want to replace your state with {files()[0].name}?
|
||||
</ConfirmDialog>
|
||||
</>
|
||||
|
||||
@@ -13,7 +13,7 @@ export function JsonModal(props: { title: string, open: boolean, data?: unknown,
|
||||
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
|
||||
|
||||
return (
|
||||
<Dialog.Root isOpen={props.open} onOpenChange={(isOpen) => props.setOpen(isOpen)}>
|
||||
<Dialog.Root open={props.open} onOpenChange={(isOpen) => props.setOpen(isOpen)}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay class={OVERLAY} />
|
||||
<div class={DIALOG_POSITIONER}>
|
||||
|
||||
@@ -110,7 +110,7 @@ function ConnectPeer(props: { refetchPeers: RefetchPeersType }) {
|
||||
<form class="flex flex-col gap-4" onSubmit={onSubmit} >
|
||||
<TextField.Root
|
||||
value={value()}
|
||||
onValueChange={setValue}
|
||||
onChange={setValue}
|
||||
validationState={(value() == "" || value().startsWith("mutiny:")) ? "valid" : "invalid"}
|
||||
class="flex flex-col gap-4"
|
||||
>
|
||||
@@ -167,7 +167,7 @@ function ChannelItem(props: { channel: MutinyChannel, network?: string }) {
|
||||
<Button intent="glowy" layout="xs" onClick={handleCloseChannel}>Close Channel</Button>
|
||||
|
||||
</VStack>
|
||||
<ConfirmDialog isOpen={confirmOpen()} onConfirm={confirmCloseChannel} onCancel={() => setConfirmOpen(false)} loading={confirmLoading()}>
|
||||
<ConfirmDialog open={confirmOpen()} onConfirm={confirmCloseChannel} onCancel={() => setConfirmOpen(false)} loading={confirmLoading()}>
|
||||
<p>Are you sure you want to close this channel?</p>
|
||||
</ConfirmDialog>
|
||||
</Collapsible.Content>
|
||||
@@ -259,7 +259,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
|
||||
<form class="flex flex-col gap-4" onSubmit={onSubmit} >
|
||||
<TextField.Root
|
||||
value={peerPubkey()}
|
||||
onValueChange={setPeerPubkey}
|
||||
onChange={setPeerPubkey}
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<TextField.Label class="text-sm font-semibold uppercase" >Pubkey</TextField.Label>
|
||||
@@ -267,7 +267,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
|
||||
</TextField.Root>
|
||||
<TextField.Root
|
||||
value={amount()}
|
||||
onValueChange={setAmount}
|
||||
onChange={setAmount}
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<TextField.Label class="text-sm font-semibold uppercase" >Amount</TextField.Label>
|
||||
@@ -313,7 +313,7 @@ function LnUrlAuth() {
|
||||
<form class="flex flex-col gap-4" onSubmit={onSubmit} >
|
||||
<TextField.Root
|
||||
value={value()}
|
||||
onValueChange={setValue}
|
||||
onChange={setValue}
|
||||
validationState={(value() == "" || value().toLowerCase().startsWith("lnurl")) ? "valid" : "invalid"}
|
||||
class="flex flex-col gap-4"
|
||||
>
|
||||
@@ -327,6 +327,32 @@ function LnUrlAuth() {
|
||||
)
|
||||
}
|
||||
|
||||
function ListTags() {
|
||||
const [_state, actions] = useMegaStore()
|
||||
|
||||
const [tags] = createResource(actions.listTags)
|
||||
|
||||
return (
|
||||
|
||||
<Collapsible.Root>
|
||||
<Collapsible.Trigger class="w-full">
|
||||
<h2 class="truncate text-start text-lg font-mono bg-neutral-200 text-black rounded px-4 py-2">
|
||||
{">"} Tags
|
||||
</h2>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<VStack>
|
||||
<pre class="overflow-x-auto whitespace-pre-wrap break-all">
|
||||
{JSON.stringify(tags(), null, 2)}
|
||||
</pre>
|
||||
</VStack>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default function KitchenSink() {
|
||||
@@ -339,6 +365,9 @@ export default function KitchenSink() {
|
||||
<ChannelsList />
|
||||
<Hr />
|
||||
<LnUrlAuth />
|
||||
<Hr />
|
||||
<ListTags />
|
||||
|
||||
<Hr />
|
||||
<ImportExport />
|
||||
</Card>
|
||||
|
||||
@@ -7,7 +7,7 @@ export function Logs() {
|
||||
|
||||
async function handleSave() {
|
||||
const logs = await state.mutiny_wallet?.get_logs()
|
||||
downloadTextFile(logs.join() || "", "mutiny-logs.txt", "text/plain")
|
||||
downloadTextFile(logs.join("") || "", "mutiny-logs.txt", "text/plain")
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Show, createSignal, onMount } from "solid-js";
|
||||
import { Button, ButtonLink, SmallHeader, VStack } from "./layout";
|
||||
import { Button, ButtonLink, SmallHeader } from "./layout";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { showToast } from "./Toaster";
|
||||
import save from "~/assets/icons/save.svg"
|
||||
import close from "~/assets/icons/close.svg";
|
||||
import restore from "~/assets/icons/upload.svg";
|
||||
|
||||
export function OnboardWarning() {
|
||||
const [state, actions] = useMegaStore();
|
||||
@@ -18,30 +21,42 @@ export function OnboardWarning() {
|
||||
return (
|
||||
<>
|
||||
{/* TODO: show this once we have a restore flow */}
|
||||
<Show when={!state.dismissed_restore_prompt && false}>
|
||||
<div class='rounded-xl p-4 flex flex-col gap-2 bg-neutral-950 overflow-x-hidden'>
|
||||
<SmallHeader>Welcome!</SmallHeader>
|
||||
<VStack>
|
||||
<p class="text-2xl font-light">
|
||||
Do you want to restore an existing Mutiny Wallet?
|
||||
</p>
|
||||
<div class="w-full flex gap-2">
|
||||
<Button intent="green" onClick={() => { showToast({ title: "Unimplemented", description: "We don't do that yet" }) }}>Restore</Button>
|
||||
<Button onClick={actions.dismissRestorePrompt}>Nope</Button>
|
||||
<Show when={false}>
|
||||
<div class="grid grid-cols-[auto_minmax(0,_1fr)_auto] rounded-xl p-4 gap-4 bg-neutral-950/50">
|
||||
<div class="self-center">
|
||||
<img src={restore} alt="backup" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class='flex md:flex-row flex-col items-center gap-4'>
|
||||
<div class="flex flex-col">
|
||||
<SmallHeader>Welcome!</SmallHeader>
|
||||
<p class="text-base font-light">
|
||||
If you've used Mutiny before you can restore from a backup. Otherwise you can skip this and enjoy your new wallet!
|
||||
</p>
|
||||
</div>
|
||||
</VStack>
|
||||
<Button intent="green" layout="xs" class="self-start md:self-auto" onClick={() => { showToast({ title: "Unimplemented", description: "We don't do that yet" }) }}>Restore</Button>
|
||||
</div>
|
||||
<button tabindex="-1" onClick={() => { actions.dismissRestorePrompt() }} class="self-center hover:bg-white/10 rounded-lg active:bg-m-blue w-8">
|
||||
<img src={close} alt="Close" />
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={!state.has_backed_up && hasMoney() && !dismissedBackup()}>
|
||||
<div class='rounded-xl p-4 flex flex-col gap-2 bg-neutral-950 overflow-x-hidden'>
|
||||
<SmallHeader>Secure your funds</SmallHeader>
|
||||
<p class="text-2xl font-light">
|
||||
You have money stored in this browser. Let's make sure you have a backup.
|
||||
</p>
|
||||
<div class="w-full flex gap-2">
|
||||
<ButtonLink intent="blue" href="/backup">Backup</ButtonLink>
|
||||
<Button onClick={() => { setDismissedBackup(true) }}>Nope</Button>
|
||||
<div class="grid grid-cols-[auto_minmax(0,_1fr)_auto] rounded-xl p-4 gap-4 bg-neutral-950/50">
|
||||
<div class="self-center">
|
||||
<img src={save} alt="backup" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class='flex md:flex-row flex-col items-center gap-4'>
|
||||
<div class="flex flex-col">
|
||||
<SmallHeader>Secure your funds</SmallHeader>
|
||||
<p class="text-base font-light">
|
||||
You have money stored in this browser. Let's make sure you have a backup.
|
||||
</p>
|
||||
</div>
|
||||
<ButtonLink intent="blue" layout="xs" class="self-start md:self-auto" href="/backup">Backup</ButtonLink>
|
||||
</div>
|
||||
<button tabindex="-1" onClick={() => { setDismissedBackup(true) }} class="self-center hover:bg-white/10 rounded-lg active:bg-m-blue w-8">
|
||||
<img src={close} alt="Close" />
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
|
||||
@@ -25,7 +25,7 @@ const ReloadPrompt: Component = () => {
|
||||
|
||||
return (
|
||||
<Show when={offlineReady() || needRefresh()}>
|
||||
<Card title="PWA settings">
|
||||
{/* <Card title="PWA settings">
|
||||
<div>
|
||||
<Show
|
||||
fallback={<span>New content available, click on reload button to update.</span>}
|
||||
@@ -38,7 +38,7 @@ const ReloadPrompt: Component = () => {
|
||||
<Button onClick={() => updateServiceWorker(true)}>Reload</Button>
|
||||
</Show>
|
||||
<Button onClick={() => close()}>Close</Button>
|
||||
</Card>
|
||||
</Card> */}
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,13 +35,12 @@ export function StringShower(props: { text: string }) {
|
||||
return (
|
||||
<>
|
||||
<JsonModal open={open()} data={props.text} title="Details" setOpen={setOpen} />
|
||||
<div class="flex gap-2">
|
||||
<div class="w-full grid grid-cols-[minmax(0,_1fr)_auto]">
|
||||
<pre class="truncate text-neutral-400">{props.text}</pre>
|
||||
<button class="w-[16rem]" onClick={() => setOpen(true)}>
|
||||
<button class="w-[2rem]" onClick={() => setOpen(true)}>
|
||||
<img src={eyeIcon} alt="eye" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Select, createOptions } from "@thisbeyond/solid-select";
|
||||
import "~/styles/solid-select.css"
|
||||
import { For, createUniqueId } from "solid-js";
|
||||
import { For } from "solid-js";
|
||||
import { ContactEditor } from "./ContactEditor";
|
||||
import { ContactItem, TagItem, TextItem, addContact } from "~/state/contacts";
|
||||
import { TinyButton } from "./layout";
|
||||
import { ContactFormValues } from "./ContactViewer";
|
||||
import { MutinyTagItem } from "~/utils/tags";
|
||||
import { Contact } from "@mutinywallet/mutiny-wasm";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
|
||||
// take two arrays, subtract the second from the first, then return the first
|
||||
function subtract<T>(a: T[], b: T[]) {
|
||||
@@ -11,12 +14,20 @@ function subtract<T>(a: T[], b: T[]) {
|
||||
return a.filter(x => !set.has(x));
|
||||
}
|
||||
|
||||
const createValue = (name: string): TextItem => {
|
||||
return { id: createUniqueId(), name, kind: "text" };
|
||||
const createLabelValue = (label: string): Partial<MutinyTagItem> => {
|
||||
return { id: label, name: label, kind: "Label" };
|
||||
};
|
||||
|
||||
export function TagEditor(props: { values: TagItem[], setValues: (values: TagItem[]) => void, selectedValues: TagItem[], setSelectedValues: (values: TagItem[]) => void, placeholder: string }) {
|
||||
const onChange = (selected: TagItem[]) => {
|
||||
export function TagEditor(props: {
|
||||
values: MutinyTagItem[],
|
||||
setValues: (values: MutinyTagItem[]) => void,
|
||||
selectedValues: MutinyTagItem[],
|
||||
setSelectedValues: (values: MutinyTagItem[]) => void,
|
||||
placeholder: string
|
||||
}) {
|
||||
const [state, actions] = useMegaStore();
|
||||
|
||||
const onChange = (selected: MutinyTagItem[]) => {
|
||||
props.setSelectedValues(selected);
|
||||
|
||||
console.log(selected)
|
||||
@@ -31,12 +42,23 @@ export function TagEditor(props: { values: TagItem[], setValues: (values: TagIte
|
||||
key: "name",
|
||||
disable: (value) => props.selectedValues.includes(value),
|
||||
filterable: true, // Default
|
||||
createable: createValue,
|
||||
createable: createLabelValue,
|
||||
});
|
||||
|
||||
const newContact = async (contact: ContactItem) => {
|
||||
await addContact(contact)
|
||||
onChange([...props.selectedValues, contact])
|
||||
async function createContact(contact: ContactFormValues) {
|
||||
// FIXME: undefineds
|
||||
// FIXME: npub not valid? other undefineds
|
||||
const c = new Contact(contact.name, undefined, undefined, undefined);
|
||||
const newContactId = await state.mutiny_wallet?.create_new_contact(c);
|
||||
const contactItem = await state.mutiny_wallet?.get_contact(newContactId ?? "");
|
||||
const mutinyContactItem: MutinyTagItem = { id: contactItem?.id || "", name: contactItem?.name || "", kind: "Contact", last_used_time: 0n };
|
||||
if (contactItem) {
|
||||
// @ts-ignore
|
||||
// FIXME: make typescript less mad about this
|
||||
onChange([...props.selectedValues, mutinyContactItem])
|
||||
} else {
|
||||
console.error("Failed to create contact")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -52,13 +74,13 @@ export function TagEditor(props: { values: TagItem[], setValues: (values: TagIte
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<For each={subtract(props.values, props.selectedValues).slice(0, 3)}>
|
||||
{(tag) => (
|
||||
<TinyButton onClick={() => onChange([...props.selectedValues, tag])}
|
||||
<TinyButton tag={tag} onClick={() => onChange([...props.selectedValues, tag])}
|
||||
>
|
||||
{tag.name}
|
||||
</TinyButton>
|
||||
)}
|
||||
</For>
|
||||
<ContactEditor createContact={newContact} />
|
||||
<ContactEditor createContact={createContact} />
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
|
||||
@@ -43,13 +43,10 @@ export function ToastItem(props: { toastId: number, title: string, description:
|
||||
</p>
|
||||
</Toast.Description>
|
||||
</div>
|
||||
<Toast.CloseButton class="hover:bg-white/10 rounded-lg active:bg-m-blue w-[5rem] flex-0">
|
||||
<Toast.CloseButton class="hover:bg-white/10 rounded-lg active:bg-m-blue flex-0">
|
||||
<img src={close} alt="Close" />
|
||||
</Toast.CloseButton>
|
||||
</div>
|
||||
{/* <Toast.ProgressTrack class="toast__progress-track">
|
||||
<Toast.ProgressFill class="toast__progress-fill" />
|
||||
</Toast.ProgressTrack> */}
|
||||
</Toast.Root>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Dynamic } from "solid-js/web";
|
||||
import { A } from "solid-start";
|
||||
import { LoadingSpinner } from ".";
|
||||
|
||||
const button = cva("p-3 rounded-xl text-xl font-semibold disabled:opacity-50 disabled:grayscale transition", {
|
||||
const button = cva("p-3 rounded-xl font-semibold disabled:opacity-50 disabled:grayscale transition", {
|
||||
variants: {
|
||||
// TODO: button hover has to work different than buttonlinks (like disabled state)
|
||||
intent: {
|
||||
@@ -16,10 +16,10 @@ const button = cva("p-3 rounded-xl text-xl font-semibold disabled:opacity-50 dis
|
||||
green: "bg-m-green text-white shadow-inner-button hover:bg-m-green-dark text-shadow-button",
|
||||
},
|
||||
layout: {
|
||||
flex: "flex-1",
|
||||
pad: "px-8",
|
||||
flex: "flex-1 text-xl",
|
||||
pad: "px-8 text-xl",
|
||||
small: "px-4 py-2 w-auto text-lg",
|
||||
xs: "px-2 py-1 w-auto rounded-lg font-normal text-base"
|
||||
xs: "px-4 py-2 w-auto rounded-lg text-base"
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -32,7 +32,8 @@ const button = cva("p-3 rounded-xl text-xl font-semibold disabled:opacity-50 dis
|
||||
|
||||
type StyleProps = VariantProps<typeof button>
|
||||
interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement>, StyleProps {
|
||||
loading?: boolean
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
}
|
||||
|
||||
export const Button: ParentComponent<ButtonProps> = props => {
|
||||
@@ -42,6 +43,7 @@ export const Button: ParentComponent<ButtonProps> = props => {
|
||||
return (
|
||||
<button
|
||||
{...attrs}
|
||||
disabled={props.disabled || props.loading}
|
||||
class={button({
|
||||
class: local.class || "",
|
||||
intent: local.intent,
|
||||
|
||||
@@ -18,7 +18,7 @@ type FullscreenModalProps = {
|
||||
|
||||
export function FullscreenModal(props: FullscreenModalProps) {
|
||||
return (
|
||||
<Dialog.Root isOpen={props.open} onOpenChange={(isOpen) => props.setOpen(isOpen)}>
|
||||
<Dialog.Root open={props.open} onOpenChange={(isOpen) => props.setOpen(isOpen)}>
|
||||
<Dialog.Portal>
|
||||
<div class={DIALOG_POSITIONER}>
|
||||
<Dialog.Content class={DIALOG_CONTENT}>
|
||||
|
||||
@@ -7,7 +7,7 @@ type Choices = { value: string, label: string, caption: string }[]
|
||||
export function StyledRadioGroup(props: { value: string, choices: Choices, onValueChange: (value: string) => void, small?: boolean, accent?: "red" | "white" }) {
|
||||
return (
|
||||
// TODO: rewrite this with CVA, props are bad for tailwind
|
||||
<RadioGroup.Root value={props.value} onValueChange={(e) => props.onValueChange(e)}
|
||||
<RadioGroup.Root value={props.value} onChange={(e) => props.onValueChange(e)}
|
||||
class={"grid w-full gap-4"}
|
||||
classList={{ "grid-cols-2": props.choices.length === 2, "grid-cols-3": props.choices.length === 3, "gap-2": props.small }}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { JSX, ParentComponent, Show, Suspense } from "solid-js"
|
||||
import { JSX, ParentComponent, Show, Suspense, createResource, createSignal } from "solid-js"
|
||||
import Linkify from "./Linkify"
|
||||
import { Button, ButtonLink } from "./Button"
|
||||
import { Separator } from "@kobalte/core"
|
||||
import { Checkbox as KCheckbox, Separator } from "@kobalte/core"
|
||||
import { useMegaStore } from "~/state/megaStore"
|
||||
import check from "~/assets/icons/check.svg"
|
||||
import { MutinyTagItem } from "~/utils/tags"
|
||||
import { generateGradient } from "~/utils/gradientHash"
|
||||
|
||||
export {
|
||||
Button,
|
||||
@@ -118,12 +121,23 @@ export const SmallAmount: ParentComponent<{ amount: number | bigint, sign?: stri
|
||||
}
|
||||
|
||||
export const NiceP: ParentComponent = (props) => {
|
||||
return (<p class="text-2xl font-light">{props.children}</p>)
|
||||
return (<p class="text-xl font-light">{props.children}</p>)
|
||||
}
|
||||
|
||||
export const TinyButton: ParentComponent<{ onClick: () => void }> = (props) => {
|
||||
export const TinyButton: ParentComponent<{ onClick: () => void, tag?: MutinyTagItem }> = (props) => {
|
||||
// TODO: don't need to run this if it's not a contact
|
||||
const [gradient] = createResource(props.tag?.name, async (name: string) => {
|
||||
return generateGradient(name || "?")
|
||||
})
|
||||
|
||||
const bg = () => (props.tag?.name && props.tag?.kind === "Contact") ? gradient() : "rgb(255 255 255 / 0.1)"
|
||||
|
||||
console.log("tiny tag", props.tag?.name, gradient())
|
||||
|
||||
return (
|
||||
<button class="py-1 px-2 rounded-lg bg-white/10" onClick={() => props.onClick()}>
|
||||
<button class="py-1 px-2 rounded-lg bg-white/10" onClick={() => props.onClick()}
|
||||
style={{ background: bg() }}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
)
|
||||
@@ -134,3 +148,17 @@ export const Indicator: ParentComponent = (props) => {
|
||||
<div class="box-border animate-pulse px-2 py-1 -my-1 bg-white/70 rounded text-xs uppercase text-black">{props.children}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Checkbox(props: { label: string, checked: boolean, onChange: (checked: boolean) => void }) {
|
||||
return (
|
||||
<KCheckbox.Root class="inline-flex items-center gap-2" checked={props.checked} onChange={props.onChange}>
|
||||
<KCheckbox.Input class="" />
|
||||
<KCheckbox.Control class="flex-0 w-8 h-8 rounded-lg border-2 border-white bg-neutral-800 ui-checked:bg-m-red">
|
||||
<KCheckbox.Indicator>
|
||||
<img src={check} class="w-8 h-8" alt="check" />
|
||||
</KCheckbox.Indicator>
|
||||
</KCheckbox.Control>
|
||||
<KCheckbox.Label class="flex-1 text-xl font-light">{props.label}</KCheckbox.Label>
|
||||
</KCheckbox.Root>
|
||||
)
|
||||
}
|
||||
@@ -5,23 +5,41 @@ import { BackLink } from "~/components/layout/BackLink";
|
||||
import { CombinedActivity } from "~/components/Activity";
|
||||
import { A } from "solid-start";
|
||||
import settings from '~/assets/icons/settings.svg';
|
||||
import { ContactItem, addContact, editContact, listContacts } from "~/state/contacts";
|
||||
import { Tabs } from "@kobalte/core";
|
||||
import { gradientsPerContact } from "~/utils/gradientHash";
|
||||
import { ContactEditor } from "~/components/ContactEditor";
|
||||
import { ContactViewer } from "~/components/ContactViewer";
|
||||
import { ContactFormValues, ContactViewer } from "~/components/ContactViewer";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { Contact } from "@mutinywallet/mutiny-wasm";
|
||||
import { showToast } from "~/components/Toaster";
|
||||
|
||||
function ContactRow() {
|
||||
const [contacts, { refetch }] = createResource(listContacts)
|
||||
const [state, actions] = useMegaStore();
|
||||
const [contacts, { refetch }] = createResource(async () => {
|
||||
const contacts = state.mutiny_wallet?.get_contacts();
|
||||
console.log(contacts)
|
||||
|
||||
let c: Contact[] = []
|
||||
if (contacts) {
|
||||
for (let contact in contacts) {
|
||||
c.push(contacts[contact])
|
||||
}
|
||||
}
|
||||
return c || []
|
||||
})
|
||||
const [gradients] = createResource(contacts, gradientsPerContact);
|
||||
|
||||
async function createContact(contact: ContactItem) {
|
||||
await addContact(contact)
|
||||
async function createContact(contact: ContactFormValues) {
|
||||
// FIXME: npub not valid? other undefineds
|
||||
const c = new Contact(contact.name, undefined, undefined, undefined);
|
||||
await state.mutiny_wallet?.create_new_contact(c)
|
||||
refetch();
|
||||
}
|
||||
|
||||
async function saveContact(contact: ContactItem) {
|
||||
await editContact(contact)
|
||||
//
|
||||
async function saveContact(contact: ContactFormValues) {
|
||||
showToast(new Error("Unimplemented"))
|
||||
// await editContact(contact)
|
||||
refetch();
|
||||
}
|
||||
|
||||
@@ -31,7 +49,7 @@ function ContactRow() {
|
||||
<Show when={contacts() && gradients()}>
|
||||
<For each={contacts()}>
|
||||
{(contact) => (
|
||||
<ContactViewer contact={contact} gradient={gradients()?.get(contact.id)} saveContact={saveContact} />
|
||||
<ContactViewer contact={contact} gradient={gradients()?.get(contact.name)} saveContact={saveContact} />
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
@@ -49,6 +67,7 @@ export default function Activity() {
|
||||
<BackLink />
|
||||
<LargeHeader action={<A class="md:hidden p-2 hover:bg-white/5 rounded-lg active:bg-m-blue" href="/settings"><img src={settings} alt="Settings" /></A>}>Activity</LargeHeader>
|
||||
<ContactRow />
|
||||
|
||||
<Tabs.Root defaultValue="mutiny">
|
||||
<Tabs.List class="relative flex justify-around mt-4 mb-8 gap-1 bg-neutral-950 p-1 rounded-xl">
|
||||
<Tabs.Trigger value="mutiny" class={TAB}>Mutiny</Tabs.Trigger>
|
||||
@@ -58,7 +77,10 @@ export default function Activity() {
|
||||
<Tabs.Content value="mutiny">
|
||||
{/* <MutinyActivity /> */}
|
||||
<Card title="Activity">
|
||||
<CombinedActivity />
|
||||
<div class="p-1" />
|
||||
<VStack>
|
||||
<CombinedActivity />
|
||||
</VStack>
|
||||
</Card>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="nostr">
|
||||
|
||||
@@ -1,16 +1,39 @@
|
||||
import { Button, DefaultMain, LargeHeader, NiceP, MutinyWalletGuard, SafeArea, VStack } from "~/components/layout";
|
||||
import { Button, DefaultMain, LargeHeader, NiceP, MutinyWalletGuard, SafeArea, VStack, Checkbox } from "~/components/layout";
|
||||
import NavBar from "~/components/NavBar";
|
||||
import { useNavigate } from 'solid-start';
|
||||
import { SeedWords } from '~/components/SeedWords';
|
||||
import { useMegaStore } from '~/state/megaStore';
|
||||
import { Show, createSignal } from 'solid-js';
|
||||
import { Show, createEffect, createSignal } from 'solid-js';
|
||||
import { BackLink } from "~/components/layout/BackLink";
|
||||
|
||||
function Quiz(props: { setHasCheckedAll: (hasChecked: boolean) => void }) {
|
||||
const [one, setOne] = createSignal(false);
|
||||
const [two, setTwo] = createSignal(false);
|
||||
const [three, setThree] = createSignal(false);
|
||||
|
||||
createEffect(() => {
|
||||
if (one() && two() && three()) {
|
||||
props.setHasCheckedAll(true)
|
||||
} else {
|
||||
props.setHasCheckedAll(false)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<VStack>
|
||||
<Checkbox checked={one()} onChange={setOne} label="I wrote down the words" />
|
||||
<Checkbox checked={two()} onChange={setTwo} label="I understand that my funds are my responsibility" />
|
||||
<Checkbox checked={three()} onChange={setThree} label="I'm not lying just to get this over with" />
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const [store, actions] = useMegaStore();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [hasSeenBackup, setHasSeenBackup] = createSignal(false);
|
||||
const [hasCheckedAll, setHasCheckedAll] = createSignal(false);
|
||||
|
||||
function wroteDownTheWords() {
|
||||
actions.setHasBackedUp()
|
||||
@@ -23,6 +46,7 @@ export default function App() {
|
||||
<DefaultMain>
|
||||
<BackLink />
|
||||
<LargeHeader>Backup</LargeHeader>
|
||||
|
||||
<VStack>
|
||||
<NiceP>Let's get these funds secured.</NiceP>
|
||||
<NiceP>We'll show you 12 words. You write down the 12 words.</NiceP>
|
||||
@@ -32,9 +56,9 @@ export default function App() {
|
||||
<NiceP>Mutiny is self-custodial. It's all up to you...</NiceP>
|
||||
<SeedWords words={store.mutiny_wallet?.show_seed() || ""} setHasSeen={setHasSeenBackup} />
|
||||
<Show when={hasSeenBackup()}>
|
||||
<NiceP>You are responsible for your funds!</NiceP>
|
||||
<Quiz setHasCheckedAll={setHasCheckedAll} />
|
||||
</Show>
|
||||
<Button disabled={!hasSeenBackup()} intent="blue" onClick={wroteDownTheWords}>I wrote down the words</Button>
|
||||
<Button disabled={!hasSeenBackup() || !hasCheckedAll()} intent="blue" onClick={wroteDownTheWords}>I wrote down the words</Button>
|
||||
</VStack>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="none" />
|
||||
|
||||
@@ -14,10 +14,10 @@ import { StyledRadioGroup } from "~/components/layout/Radio";
|
||||
import { showToast } from "~/components/Toaster";
|
||||
import { useNavigate } from "solid-start";
|
||||
import megacheck from "~/assets/icons/megacheck.png";
|
||||
import { TagItem, listTags } from "~/state/contacts";
|
||||
import { AmountCard } from "~/components/AmountCard";
|
||||
import { ShareCard } from "~/components/ShareCard";
|
||||
import { BackButton } from "~/components/layout/BackButton";
|
||||
import { MutinyTagItem, UNKNOWN_TAG, sortByLastUsed, tagsToIds } from "~/utils/tags";
|
||||
|
||||
type OnChainTx = {
|
||||
transaction: {
|
||||
@@ -43,8 +43,6 @@ type OnChainTx = {
|
||||
}
|
||||
}
|
||||
|
||||
const createUniqueId = () => Math.random().toString(36).substr(2, 9);
|
||||
|
||||
const RECEIVE_FLAVORS = [{ value: "unified", label: "Unified", caption: "Sender decides" }, { value: "lightning", label: "Lightning", caption: "Fast and cool" }, { value: "onchain", label: "On-chain", caption: "Just like Satoshi did it" }]
|
||||
|
||||
type ReceiveFlavor = "unified" | "lightning" | "onchain"
|
||||
@@ -52,7 +50,7 @@ type ReceiveState = "edit" | "show" | "paid"
|
||||
type PaidState = "lightning_paid" | "onchain_paid";
|
||||
|
||||
export default function Receive() {
|
||||
const [state, _] = useMegaStore()
|
||||
const [state, actions] = useMegaStore()
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [amount, setAmount] = createSignal("")
|
||||
@@ -62,8 +60,8 @@ export default function Receive() {
|
||||
const [shouldShowAmountEditor, setShouldShowAmountEditor] = createSignal(true)
|
||||
|
||||
// Tagging stuff
|
||||
const [selectedValues, setSelectedValues] = createSignal<TagItem[]>([]);
|
||||
const [values, setValues] = createSignal<TagItem[]>([{ id: createUniqueId(), name: "Unknown", kind: "text" }]);
|
||||
const [selectedValues, setSelectedValues] = createSignal<MutinyTagItem[]>([]);
|
||||
const [values, setValues] = createSignal<MutinyTagItem[]>([UNKNOWN_TAG]);
|
||||
|
||||
// The data we get after a payment
|
||||
const [paymentTx, setPaymentTx] = createSignal<OnChainTx>();
|
||||
@@ -86,8 +84,8 @@ export default function Receive() {
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
listTags().then((tags) => {
|
||||
setValues(prev => [...prev, ...tags || []])
|
||||
actions.listTags().then((tags) => {
|
||||
setValues(prev => [...prev, ...tags.sort(sortByLastUsed) || []])
|
||||
});
|
||||
})
|
||||
|
||||
@@ -104,8 +102,7 @@ export default function Receive() {
|
||||
async function getUnifiedQr(amount: string) {
|
||||
const bigAmount = BigInt(amount);
|
||||
try {
|
||||
// FIXME: actual labels
|
||||
const raw = await state.mutiny_wallet?.create_bip21(bigAmount, []);
|
||||
const raw = await state.mutiny_wallet?.create_bip21(bigAmount, tagsToIds(selectedValues()));
|
||||
// Save the raw info so we can watch the address and invoice
|
||||
setBip21Raw(raw);
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ export default function Redshift() {
|
||||
<Match when={shiftStage() === "choose"}>
|
||||
<VStack>
|
||||
<NiceP>Where is this going?</NiceP>
|
||||
<StyledRadioGroup red value={shiftType()} onValueChange={(newValue) => setShiftType(newValue as ShiftOption)} choices={SHIFT_OPTIONS} />
|
||||
<StyledRadioGroup accent="red" value={shiftType()} onValueChange={(newValue) => setShiftType(newValue as ShiftOption)} choices={SHIFT_OPTIONS} />
|
||||
</VStack>
|
||||
<VStack>
|
||||
<NiceP>Choose your <span class="inline-block"><img class="h-4" src={wave} alt="sine wave" /></span> UTXO to begin</NiceP>
|
||||
|
||||
@@ -17,9 +17,9 @@ import mempoolTxUrl from "~/utils/mempoolTxUrl";
|
||||
import { BackLink } from "~/components/layout/BackLink";
|
||||
import { useNavigate } from "solid-start";
|
||||
import { TagEditor } from "~/components/TagEditor";
|
||||
import { TagItem, createUniqueId, listTags } from "~/state/contacts";
|
||||
import { StringShower } from "~/components/ShareCard";
|
||||
import { AmountCard } from "~/components/AmountCard";
|
||||
import { MutinyTagItem, UNKNOWN_TAG, sortByLastUsed, tagsToIds } from "~/utils/tags";
|
||||
|
||||
type SendSource = "lightning" | "onchain";
|
||||
|
||||
@@ -97,23 +97,6 @@ function DestinationShower(props: {
|
||||
)
|
||||
}
|
||||
|
||||
function SendTags() {
|
||||
// Tagging stuff
|
||||
const [selectedValues, setSelectedValues] = createSignal<TagItem[]>([]);
|
||||
const [values, setValues] = createSignal<TagItem[]>([{ id: createUniqueId(), name: "Unknown", kind: "text" }]);
|
||||
|
||||
onMount(() => {
|
||||
listTags().then((tags) => {
|
||||
setValues(prev => [...prev, ...tags || []])
|
||||
});
|
||||
})
|
||||
|
||||
return (
|
||||
<TagEditor values={values()} setValues={setValues} selectedValues={selectedValues()} setSelectedValues={setSelectedValues} placeholder="Where's it going to?" />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default function Send() {
|
||||
const [state, actions] = useMegaStore();
|
||||
const navigate = useNavigate()
|
||||
@@ -136,6 +119,10 @@ export default function Send() {
|
||||
const [sending, setSending] = createSignal(false);
|
||||
const [sentDetails, setSentDetails] = createSignal<SentDetails>();
|
||||
|
||||
// Tagging stuff
|
||||
const [selectedValues, setSelectedValues] = createSignal<MutinyTagItem[]>([]);
|
||||
const [values, setValues] = createSignal<MutinyTagItem[]>([UNKNOWN_TAG]);
|
||||
|
||||
function clearAll() {
|
||||
setDestination(undefined);
|
||||
setAmountSats(0n);
|
||||
@@ -158,6 +145,10 @@ export default function Send() {
|
||||
setDestination(state.scan_result);
|
||||
actions.setScanResult(undefined);
|
||||
}
|
||||
|
||||
actions.listTags().then((tags) => {
|
||||
setValues(prev => [...prev, ...tags.sort(sortByLastUsed) || []])
|
||||
});
|
||||
})
|
||||
|
||||
// Rerun every time the destination changes
|
||||
@@ -235,17 +226,16 @@ export default function Send() {
|
||||
sentDetails.destination = bolt11;
|
||||
// If the invoice has sats use that, otherwise we pass the user-defined amount
|
||||
if (invoice()?.amount_sats) {
|
||||
await state.mutiny_wallet?.pay_invoice(firstNode, bolt11);
|
||||
await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, undefined, tagsToIds(selectedValues()));
|
||||
sentDetails.amount = invoice()?.amount_sats;
|
||||
} else {
|
||||
await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, amountSats());
|
||||
await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, amountSats(), tagsToIds(selectedValues()));
|
||||
sentDetails.amount = amountSats();
|
||||
}
|
||||
} else if (source() === "lightning" && nodePubkey()) {
|
||||
const nodes = await state.mutiny_wallet?.list_nodes();
|
||||
const firstNode = nodes[0] as string || ""
|
||||
const payment = await state.mutiny_wallet?.keysend(firstNode, nodePubkey()!, amountSats());
|
||||
console.log(payment?.value)
|
||||
const payment = await state.mutiny_wallet?.keysend(firstNode, nodePubkey()!, amountSats(), tagsToIds(selectedValues()));
|
||||
|
||||
// TODO: handle timeouts
|
||||
if (!payment?.paid) {
|
||||
@@ -254,9 +244,8 @@ export default function Send() {
|
||||
sentDetails.amount = amountSats();
|
||||
}
|
||||
} else if (source() === "onchain" && address()) {
|
||||
// FIXME: actual labels
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const txid = await state.mutiny_wallet?.send_to_address(address()!, amountSats(), []);
|
||||
const txid = await state.mutiny_wallet?.send_to_address(address()!, amountSats(), tagsToIds(selectedValues()));
|
||||
sentDetails.amount = amountSats();
|
||||
sentDetails.destination = address();
|
||||
// TODO: figure out if this is necessary, it takes forever
|
||||
@@ -320,7 +309,7 @@ export default function Send() {
|
||||
<Card>
|
||||
<VStack>
|
||||
<DestinationShower source={source()} description={description()} invoice={invoice()} address={address()} nodePubkey={nodePubkey()} clearAll={clearAll} />
|
||||
<SendTags />
|
||||
<TagEditor values={values()} setValues={setValues} selectedValues={selectedValues()} setSelectedValues={setSelectedValues} placeholder="Where's it going to?" />
|
||||
</VStack>
|
||||
</Card>
|
||||
<AmountCard amountSats={amountSats().toString()} setAmountSats={setAmountSats} fee={fakeFee().toString()} isAmountEditable={!(invoice()?.amount_sats)} />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ActivityItem } from "~/components/ActivityItem";
|
||||
import { AmountCard } from "~/components/AmountCard";
|
||||
import NavBar from "~/components/NavBar";
|
||||
import { OnboardWarning } from "~/components/OnboardWarning";
|
||||
import { ShareCard } from "~/components/ShareCard";
|
||||
import { DefaultMain, LargeHeader, SafeArea, VStack } from "~/components/layout";
|
||||
import { Card, DefaultMain, LargeHeader, SafeArea, VStack } from "~/components/layout";
|
||||
|
||||
const SAMPLE = "bitcoin:tb1prqm8xtlgme0vmw5s30lgf0a4f5g4mkgsqundwmpu6thrg8zr6uvq2qrhzq?amount=0.001&lightning=lntbs1m1pj9n9xjsp5xgdrmvprtm67p7nq4neparalexlhlmtxx87zx6xeqthsplu842zspp546d6zd2seyaxpapaxx62m88yz3xueqtjmn9v6wj8y56np8weqsxqdqqnp4qdn2hj8tfknpuvdg6tz9yrf3e27ltrx9y58c24jh89lnm43yjwfc5xqrpwjcqpj9qrsgq5sdgh0m3ur5mu5hrmmag4mx9yvy86f83pd0x9ww80kgck6tac3thuzkj0mrtltaxwnlfea95h2re7tj4qsnwzxlvrdmyq2h9mgapnycpppz6k6"
|
||||
export default function Admin() {
|
||||
@@ -9,12 +11,16 @@ export default function Admin() {
|
||||
<SafeArea>
|
||||
<DefaultMain>
|
||||
<LargeHeader>Storybook</LargeHeader>
|
||||
<OnboardWarning />
|
||||
<VStack>
|
||||
<AmountCard amountSats={"100000"} fee={"69"} />
|
||||
<AmountCard amountSats={"100000"} />
|
||||
<AmountCard amountSats={"100000"} isAmountEditable />
|
||||
<AmountCard amountSats={"0"} isAmountEditable />
|
||||
<ShareCard text={SAMPLE} />
|
||||
<Card title="Activity">
|
||||
<ActivityItem kind="lightning" labels={["benthecarman"]} amount={100000} date={1683664966} />
|
||||
<ActivityItem kind="onchain" labels={["tony"]} amount={42000000} positive date={1683664966} />
|
||||
<ActivityItem kind="onchain" labels={["a fake name thati is too long"]} amount={42000000} date={1683664966} />
|
||||
<ActivityItem kind="onchain" labels={["a fake name thati is too long"]} amount={42000000} date={1683664966} />
|
||||
</Card>
|
||||
</VStack>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="none" />
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
export type TagItem = TextItem | ContactItem;
|
||||
|
||||
export type TextItem = {
|
||||
id: string;
|
||||
kind: "text";
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type ContactItem = {
|
||||
id: string;
|
||||
kind: "contact";
|
||||
name: string;
|
||||
npub?: string;
|
||||
color: Color;
|
||||
}
|
||||
|
||||
export type Color = "blue" | "green" | "red" | "gray"
|
||||
|
||||
export const createUniqueId = () => Math.random().toString(36).substr(2, 9);
|
||||
|
||||
export async function listContacts(): Promise<ContactItem[]> {
|
||||
// get contacts from localstorage
|
||||
const contacts: ContactItem[] = JSON.parse(localStorage.getItem("contacts") || "[]");
|
||||
return contacts;
|
||||
}
|
||||
|
||||
export async function listTexts(): Promise<TextItem[]> {
|
||||
// get texts from localstorage
|
||||
const texts: TextItem[] = JSON.parse(localStorage.getItem("texts") || "[]");
|
||||
return texts;
|
||||
}
|
||||
|
||||
export async function listTags(): Promise<TagItem[]> {
|
||||
const contacts = await listContacts();
|
||||
const texts = await listTexts();
|
||||
return [...contacts, ...texts];
|
||||
}
|
||||
|
||||
export async function addContact(contact: ContactItem): Promise<void> {
|
||||
const contacts = await listContacts();
|
||||
contacts.push(contact);
|
||||
localStorage.setItem("contacts", JSON.stringify(contacts));
|
||||
}
|
||||
|
||||
export async function editContact(contact: ContactItem): Promise<void> {
|
||||
const contacts = await listContacts();
|
||||
const index = contacts.findIndex(c => c.id === contact.id);
|
||||
contacts[index] = contact;
|
||||
localStorage.setItem("contacts", JSON.stringify(contacts));
|
||||
}
|
||||
|
||||
export async function addTextTag(text: TextItem): Promise<void> {
|
||||
const texts = await listTexts();
|
||||
texts.push(text);
|
||||
localStorage.setItem("texts", JSON.stringify(texts));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createStore } from "solid-js/store";
|
||||
import { MutinyWalletSettingStrings, setupMutinyWallet } from "~/logic/mutinyWalletSetup";
|
||||
import { MutinyBalance, MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
||||
import { ParsedParams } from "~/routes/Scanner";
|
||||
import { MutinyTagItem } from "~/utils/tags";
|
||||
|
||||
const MegaStoreContext = createContext<MegaStore>();
|
||||
|
||||
@@ -23,7 +24,8 @@ export type MegaStore = [{
|
||||
last_sync?: number;
|
||||
price: number
|
||||
has_backed_up: boolean,
|
||||
dismissed_restore_prompt: boolean
|
||||
dismissed_restore_prompt: boolean,
|
||||
wallet_loading: boolean
|
||||
}, {
|
||||
fetchUserStatus(): Promise<UserStatus>;
|
||||
setupMutinyWallet(settings?: MutinyWalletSettingStrings): Promise<void>;
|
||||
@@ -33,6 +35,7 @@ export type MegaStore = [{
|
||||
sync(): Promise<void>;
|
||||
dismissRestorePrompt(): void;
|
||||
setHasBackedUp(): void;
|
||||
listTags(): Promise<MutinyTagItem[]>;
|
||||
}];
|
||||
|
||||
export const Provider: ParentComponent = (props) => {
|
||||
@@ -49,7 +52,8 @@ export const Provider: ParentComponent = (props) => {
|
||||
balance: undefined as MutinyBalance | undefined,
|
||||
last_sync: undefined as number | undefined,
|
||||
is_syncing: false,
|
||||
dismissed_restore_prompt: localStorage.getItem("dismissed_restore_prompt") === "true"
|
||||
dismissed_restore_prompt: localStorage.getItem("dismissed_restore_prompt") === "true",
|
||||
wallet_loading: true
|
||||
});
|
||||
|
||||
const actions = {
|
||||
@@ -81,8 +85,9 @@ export const Provider: ParentComponent = (props) => {
|
||||
},
|
||||
async setupMutinyWallet(settings?: MutinyWalletSettingStrings): Promise<void> {
|
||||
try {
|
||||
setState({ wallet_loading: true })
|
||||
const mutinyWallet = await setupMutinyWallet(settings)
|
||||
setState({ mutiny_wallet: mutinyWallet })
|
||||
setState({ mutiny_wallet: mutinyWallet, wallet_loading: false })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -126,6 +131,9 @@ export const Provider: ParentComponent = (props) => {
|
||||
dismissRestorePrompt() {
|
||||
localStorage.setItem("dismissed_restore_prompt", "true")
|
||||
setState({ dismissed_restore_prompt: true })
|
||||
},
|
||||
async listTags(): Promise<MutinyTagItem[]> {
|
||||
return state.mutiny_wallet?.get_tag_items() as MutinyTagItem[]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContactItem } from "~/state/contacts";
|
||||
import { Contact } from "@mutinywallet/mutiny-wasm";
|
||||
|
||||
async function generateGradientFromHashedString(str: string) {
|
||||
export async function generateGradient(str: string) {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(str);
|
||||
const digestBuffer = await crypto.subtle.digest('SHA-256', data);
|
||||
@@ -13,11 +13,12 @@ async function generateGradientFromHashedString(str: string) {
|
||||
return gradient;
|
||||
}
|
||||
|
||||
export async function gradientsPerContact(contacts: ContactItem[]) {
|
||||
export async function gradientsPerContact(contacts: Contact[]) {
|
||||
console.log(contacts);
|
||||
const gradients = new Map();
|
||||
for (const contact of contacts) {
|
||||
const gradient = await generateGradientFromHashedString(contact.name);
|
||||
gradients.set(contact.id, gradient);
|
||||
const gradient = await generateGradient(contact.name);
|
||||
gradients.set(contact.name, gradient);
|
||||
}
|
||||
|
||||
return gradients;
|
||||
|
||||
@@ -9,4 +9,31 @@ export function prettyPrintTime(ts: number) {
|
||||
};
|
||||
|
||||
return new Date(ts * 1000).toLocaleString('en-US', options);
|
||||
}
|
||||
}
|
||||
|
||||
export function timeAgo(ts?: number | bigint): string {
|
||||
if (!ts || ts === 0) return "Pending";
|
||||
const timestamp = Number(ts) * 1000;
|
||||
const now = Date.now();
|
||||
const elapsedMilliseconds = now - timestamp;
|
||||
const elapsedSeconds = Math.floor(elapsedMilliseconds / 1000);
|
||||
const elapsedMinutes = Math.floor(elapsedSeconds / 60);
|
||||
const elapsedHours = Math.floor(elapsedMinutes / 60);
|
||||
const elapsedDays = Math.floor(elapsedHours / 24);
|
||||
|
||||
if (elapsedSeconds < 60) {
|
||||
return "Just now";
|
||||
} else if (elapsedMinutes < 60) {
|
||||
return `${elapsedMinutes} minute${elapsedMinutes > 1 ? 's' : ''} ago`;
|
||||
} else if (elapsedHours < 24) {
|
||||
return `${elapsedHours} hour${elapsedHours > 1 ? 's' : ''} ago`;
|
||||
} else if (elapsedDays < 7) {
|
||||
return `${elapsedDays} day${elapsedDays > 1 ? 's' : ''} ago`;
|
||||
} else {
|
||||
const date = new Date(timestamp);
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const year = date.getFullYear();
|
||||
return `${month}/${day}/${year}`;
|
||||
}
|
||||
}
|
||||
|
||||
27
src/utils/tags.ts
Normal file
27
src/utils/tags.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { TagItem } from "@mutinywallet/mutiny-wasm"
|
||||
|
||||
export type MutinyTagItem = {
|
||||
id: string,
|
||||
kind: "Label" | "Contact"
|
||||
name: string,
|
||||
last_used_time: bigint,
|
||||
npub?: string,
|
||||
ln_address?: string,
|
||||
lnurl?: string,
|
||||
}
|
||||
|
||||
export const UNKNOWN_TAG: MutinyTagItem = { id: "Unknown", kind: "Label", name: "Unknown", last_used_time: 0n }
|
||||
|
||||
export function tagsToIds(tags: MutinyTagItem[]): string[] {
|
||||
return tags.filter((tag) => tag.id !== "Unknown").map((tag) => tag.id)
|
||||
}
|
||||
|
||||
export function tagToMutinyTag(tag: TagItem): MutinyTagItem {
|
||||
// @ts-ignore
|
||||
// FIXME: make typescript less mad about this
|
||||
return tag as MutinyTagItem
|
||||
}
|
||||
|
||||
export function sortByLastUsed(a: MutinyTagItem, b: MutinyTagItem) {
|
||||
return Number(b.last_used_time - a.last_used_time);
|
||||
}
|
||||
Reference in New Issue
Block a user