diff --git a/package.json b/package.json index 36ca910..677f28c 100644 --- a/package.json +++ b/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" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de2dd85..1f9c6a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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==} diff --git a/src/assets/icons/bolt.svg b/src/assets/icons/bolt.svg new file mode 100644 index 0000000..92080ef --- /dev/null +++ b/src/assets/icons/bolt.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/chain.svg b/src/assets/icons/chain.svg new file mode 100644 index 0000000..7946243 --- /dev/null +++ b/src/assets/icons/chain.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/icons/check.svg b/src/assets/icons/check.svg new file mode 100644 index 0000000..cefd761 --- /dev/null +++ b/src/assets/icons/check.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/save.svg b/src/assets/icons/save.svg new file mode 100644 index 0000000..c63233f --- /dev/null +++ b/src/assets/icons/save.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/upload.svg b/src/assets/icons/upload.svg new file mode 100644 index 0000000..79cafb5 --- /dev/null +++ b/src/assets/icons/upload.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/Activity.tsx b/src/components/Activity.tsx index 50d6702..e567efb 100644 --- a/src/components/Activity.tsx +++ b/src/components/Activity.tsx @@ -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

{props.children}

} -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 -
setOpen(!open())}> -
- {isReceive() ? receive arrow : send arrow} -
-
-

Unknown

- {isReceive() ? : } -
-
- - On-chain {isReceive() ? Receive : Send} - - {props.item.confirmation_time?.Confirmed ? prettyPrintTime(props.item.confirmation_time?.Confirmed?.time) : "Unconfirmed"} -
-
+ {/* {JSON.stringify(props.labels)} */} + 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 ( <> -
setOpen(!open())}> -
- {isSend() ? send arrow : receive arrow} -
-
-

Unknown

- -
-
- - Lightning {!isSend() ? Receive : Send} - - {prettyPrintTime(Number(props.item.expire))} -
-
+ 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 ( - - - - - - - - - No transactions (empty state) - - = 0}> - - {(tx) => - - } - - - - - - - - - - - No invoices (empty state) - - = 0}> - - {(invoice) => - - } - - - - - - - - - - - No utxos (empty state) - - = 0}> - - {(utxo) => - - } - - - - Redshift redshift - - - - ) - -} - -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) => - + {/* FIXME */} + - + {/* FIXME */} + } diff --git a/src/components/ActivityItem.tsx b/src/components/ActivityItem.tsx new file mode 100644 index 0000000..604bc68 --- /dev/null +++ b/src/components/ActivityItem.tsx @@ -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 ( +
+
{props.positive && "+ "}{prettyPrint()} SATS +
+
≈ {amountInUsd()} USD
+
+ ) +} + +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 ( +
+ {text()} +
+ ) +} + +// 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 ( +
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 }} + > +
+
+ {props.kind === "lightning" ? lightning : onchain} +
+
+ +
+
+
+ {labelString(labels())} + +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/src/components/AmountEditable.tsx b/src/components/AmountEditable.tsx index 8b6f00e..a6161c2 100644 --- a/src/components/AmountEditable.tsx +++ b/src/components/AmountEditable.tsx @@ -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 ( - + + ) diff --git a/src/components/ColorRadioGroup.tsx b/src/components/ColorRadioGroup.tsx deleted file mode 100644 index d32e701..0000000 --- a/src/components/ColorRadioGroup.tsx +++ /dev/null @@ -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; - onChange: JSX.EventHandler; - onBlur: JSX.EventHandler; -}; -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 ( - - - - {props.label} - - -
- - {(option) => ( - - - - - - {/* {option.label} */} - - )} - -
- {props.error} -
- ); -} \ No newline at end of file diff --git a/src/components/ContactEditor.tsx b/src/components/ContactEditor.tsx index 748fa80..e724f94 100644 --- a/src/components/ContactEditor.tsx +++ b/src/components/ContactEditor.tsx @@ -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 = (c: ContactItem) => { - // TODO: why do the id and color disappear? - - const odd = { id: createUniqueId(), kind: "contact" } - - props.createContact({ ...odd, ...c }) - + const handleSubmit: SubmitHandler = (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 ( - + - + diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx index e94c139..b6a10b1 100644 --- a/src/components/ContactForm.tsx +++ b/src/components/ContactForm.tsx @@ -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, initialValues?: ContactItem, title: string, cta: string }) { - const [_contactForm, { Form, Field }] = createForm({ initialValues: props.initialValues }); +export function ContactForm(props: { handleSubmit: SubmitHandler, initialValues?: ContactFormValues, title: string, cta: string }) { + const [_contactForm, { Form, Field }] = createForm({ initialValues: props.initialValues }); return (
@@ -19,16 +16,11 @@ export function ContactForm(props: { handleSubmit: SubmitHandler, i )} - + {/* {(field, props) => ( )} - - - {(field, props) => ( - - )} - + */} - setConfirmOpen(false)}> + setConfirmOpen(false)}> This will delete your node's state. This can't be undone! diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx index 4ec624a..73197c6 100644 --- a/src/components/Dialog.tsx +++ b/src/components/Dialog.tsx @@ -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 ( - +
diff --git a/src/components/ImportExport.tsx b/src/components/ImportExport.tsx index e22a48a..983b569 100644 --- a/src/components/ImportExport.tsx +++ b/src/components/ImportExport.tsx @@ -67,7 +67,7 @@ export function ImportExport() { - setConfirmOpen(false)}> + setConfirmOpen(false)}> Do you want to replace your state with {files()[0].name}? diff --git a/src/components/JsonModal.tsx b/src/components/JsonModal.tsx index 29ec256..5cafb10 100644 --- a/src/components/JsonModal.tsx +++ b/src/components/JsonModal.tsx @@ -13,7 +13,7 @@ export function JsonModal(props: { title: string, open: boolean, data?: unknown, const [copy, copied] = useCopy({ copiedTimeout: 1000 }); return ( - props.setOpen(isOpen)}> + props.setOpen(isOpen)}>
diff --git a/src/components/KitchenSink.tsx b/src/components/KitchenSink.tsx index a37c0b7..8e2f119 100644 --- a/src/components/KitchenSink.tsx +++ b/src/components/KitchenSink.tsx @@ -110,7 +110,7 @@ function ConnectPeer(props: { refetchPeers: RefetchPeersType }) { @@ -167,7 +167,7 @@ function ChannelItem(props: { channel: MutinyChannel, network?: string }) { - setConfirmOpen(false)} loading={confirmLoading()}> + setConfirmOpen(false)} loading={confirmLoading()}>

Are you sure you want to close this channel?

@@ -259,7 +259,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) { Pubkey @@ -267,7 +267,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) { Amount @@ -313,7 +313,7 @@ function LnUrlAuth() { @@ -327,6 +327,32 @@ function LnUrlAuth() { ) } +function ListTags() { + const [_state, actions] = useMegaStore() + + const [tags] = createResource(actions.listTags) + + return ( + + + +

+ {">"} Tags +

+
+ + +
+                        {JSON.stringify(tags(), null, 2)}
+                    
+
+
+
+ + ) +} + + export default function KitchenSink() { @@ -339,6 +365,9 @@ export default function KitchenSink() {
+
+ +
diff --git a/src/components/Logs.tsx b/src/components/Logs.tsx index b8b3b75..b602209 100644 --- a/src/components/Logs.tsx +++ b/src/components/Logs.tsx @@ -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 ( diff --git a/src/components/OnboardWarning.tsx b/src/components/OnboardWarning.tsx index a8b45ed..2b71418 100644 --- a/src/components/OnboardWarning.tsx +++ b/src/components/OnboardWarning.tsx @@ -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 */} - -
- Welcome! - -

- Do you want to restore an existing Mutiny Wallet? -

-
- - + +
+
+ backup +
+
+
+ Welcome! +

+ If you've used Mutiny before you can restore from a backup. Otherwise you can skip this and enjoy your new wallet! +

- + +
+
-
- Secure your funds -

- You have money stored in this browser. Let's make sure you have a backup. -

-
- Backup - +
+
+ backup
+
+
+ Secure your funds +

+ You have money stored in this browser. Let's make sure you have a backup. +

+
+ Backup +
+
diff --git a/src/components/Reload.tsx b/src/components/Reload.tsx index 18cf0dd..3311c29 100644 --- a/src/components/Reload.tsx +++ b/src/components/Reload.tsx @@ -25,7 +25,7 @@ const ReloadPrompt: Component = () => { return ( - + {/*
New content available, click on reload button to update.} @@ -38,7 +38,7 @@ const ReloadPrompt: Component = () => { - + */} ) } diff --git a/src/components/ShareCard.tsx b/src/components/ShareCard.tsx index 8221f56..32ab3b3 100644 --- a/src/components/ShareCard.tsx +++ b/src/components/ShareCard.tsx @@ -35,13 +35,12 @@ export function StringShower(props: { text: string }) { return ( <> -
+
{props.text}
-
- ) } diff --git a/src/components/TagEditor.tsx b/src/components/TagEditor.tsx index 09e01c7..dd63663 100644 --- a/src/components/TagEditor.tsx +++ b/src/components/TagEditor.tsx @@ -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(a: T[], b: T[]) { @@ -11,12 +14,20 @@ function subtract(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 => { + 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
{(tag) => ( - onChange([...props.selectedValues, tag])} + onChange([...props.selectedValues, tag])} > {tag.name} )} - +
) diff --git a/src/components/Toaster.tsx b/src/components/Toaster.tsx index 4cca368..89771ce 100644 --- a/src/components/Toaster.tsx +++ b/src/components/Toaster.tsx @@ -43,13 +43,10 @@ export function ToastItem(props: { toastId: number, title: string, description:

- + Close
- {/* - - */} ) } \ No newline at end of file diff --git a/src/components/layout/Button.tsx b/src/components/layout/Button.tsx index 32744c5..30b60d3 100644 --- a/src/components/layout/Button.tsx +++ b/src/components/layout/Button.tsx @@ -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 interface ButtonProps extends JSX.ButtonHTMLAttributes, StyleProps { - loading?: boolean + loading?: boolean, + disabled?: boolean, } export const Button: ParentComponent = props => { @@ -42,6 +43,7 @@ export const Button: ParentComponent = props => { return ( ) @@ -134,3 +148,17 @@ export const Indicator: ParentComponent = (props) => {
{props.children}
) } + +export function Checkbox(props: { label: string, checked: boolean, onChange: (checked: boolean) => void }) { + return ( + + + + + check + + + {props.label} + + ) +} \ No newline at end of file diff --git a/src/routes/Activity.tsx b/src/routes/Activity.tsx index 6234989..71e9660 100644 --- a/src/routes/Activity.tsx +++ b/src/routes/Activity.tsx @@ -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() { {(contact) => ( - + )} @@ -49,6 +67,7 @@ export default function Activity() { Settings}>Activity + Mutiny @@ -58,7 +77,10 @@ export default function Activity() { {/* */} - +
+ + + diff --git a/src/routes/Backup.tsx b/src/routes/Backup.tsx index 30b9015..a9feea8 100644 --- a/src/routes/Backup.tsx +++ b/src/routes/Backup.tsx @@ -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 ( + + + + + + ) +} + 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() { Backup + Let's get these funds secured. We'll show you 12 words. You write down the 12 words. @@ -32,9 +56,9 @@ export default function App() { Mutiny is self-custodial. It's all up to you... - You are responsible for your funds! + - + diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx index 00031f5..f8c6270 100644 --- a/src/routes/Receive.tsx +++ b/src/routes/Receive.tsx @@ -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([]); - const [values, setValues] = createSignal([{ id: createUniqueId(), name: "Unknown", kind: "text" }]); + const [selectedValues, setSelectedValues] = createSignal([]); + const [values, setValues] = createSignal([UNKNOWN_TAG]); // The data we get after a payment const [paymentTx, setPaymentTx] = createSignal(); @@ -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); diff --git a/src/routes/Redshift.tsx b/src/routes/Redshift.tsx index 98cee3e..6b74e03 100644 --- a/src/routes/Redshift.tsx +++ b/src/routes/Redshift.tsx @@ -341,7 +341,7 @@ export default function Redshift() { Where is this going? - setShiftType(newValue as ShiftOption)} choices={SHIFT_OPTIONS} /> + setShiftType(newValue as ShiftOption)} choices={SHIFT_OPTIONS} /> Choose your sine wave UTXO to begin diff --git a/src/routes/Send.tsx b/src/routes/Send.tsx index 8ef1911..c6a306d 100644 --- a/src/routes/Send.tsx +++ b/src/routes/Send.tsx @@ -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([]); - const [values, setValues] = createSignal([{ id: createUniqueId(), name: "Unknown", kind: "text" }]); - - onMount(() => { - listTags().then((tags) => { - setValues(prev => [...prev, ...tags || []]) - }); - }) - - return ( - - ) -} - - 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(); + // Tagging stuff + const [selectedValues, setSelectedValues] = createSignal([]); + const [values, setValues] = createSignal([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() { - + diff --git a/src/routes/Storybook.tsx b/src/routes/Storybook.tsx index 1ac4995..f33f0e9 100644 --- a/src/routes/Storybook.tsx +++ b/src/routes/Storybook.tsx @@ -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() { Storybook + - - - + + + + + + diff --git a/src/state/contacts.ts b/src/state/contacts.ts deleted file mode 100644 index 7637faa..0000000 --- a/src/state/contacts.ts +++ /dev/null @@ -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 { - // get contacts from localstorage - const contacts: ContactItem[] = JSON.parse(localStorage.getItem("contacts") || "[]"); - return contacts; -} - -export async function listTexts(): Promise { - // get texts from localstorage - const texts: TextItem[] = JSON.parse(localStorage.getItem("texts") || "[]"); - return texts; -} - -export async function listTags(): Promise { - const contacts = await listContacts(); - const texts = await listTexts(); - return [...contacts, ...texts]; -} - -export async function addContact(contact: ContactItem): Promise { - const contacts = await listContacts(); - contacts.push(contact); - localStorage.setItem("contacts", JSON.stringify(contacts)); -} - -export async function editContact(contact: ContactItem): Promise { - 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 { - const texts = await listTexts(); - texts.push(text); - localStorage.setItem("texts", JSON.stringify(texts)); -} - - diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index e9b4d61..07324a8 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -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(); @@ -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; setupMutinyWallet(settings?: MutinyWalletSettingStrings): Promise; @@ -33,6 +35,7 @@ export type MegaStore = [{ sync(): Promise; dismissRestorePrompt(): void; setHasBackedUp(): void; + listTags(): Promise; }]; 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 { 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 { + return state.mutiny_wallet?.get_tag_items() as MutinyTagItem[] } }; diff --git a/src/utils/gradientHash.ts b/src/utils/gradientHash.ts index 4d85234..22d65d8 100644 --- a/src/utils/gradientHash.ts +++ b/src/utils/gradientHash.ts @@ -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; diff --git a/src/utils/prettyPrintTime.ts b/src/utils/prettyPrintTime.ts index 254c2db..de82a2a 100644 --- a/src/utils/prettyPrintTime.ts +++ b/src/utils/prettyPrintTime.ts @@ -9,4 +9,31 @@ export function prettyPrintTime(ts: number) { }; return new Date(ts * 1000).toLocaleString('en-US', options); -} \ No newline at end of file +} + +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}`; + } +} diff --git a/src/utils/tags.ts b/src/utils/tags.ts new file mode 100644 index 0000000..a76d835 --- /dev/null +++ b/src/utils/tags.ts @@ -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); +} \ No newline at end of file