chore: dash and settings

This commit is contained in:
apotdevin 2021-06-17 11:42:40 +02:00
parent beb7da43ed
commit d5ba08cf67
No known key found for this signature in database
GPG key ID: 4403F1DFBE779457
55 changed files with 3390 additions and 220 deletions

View file

@ -43,35 +43,11 @@ function createApolloClient(context?: ResolverContext) {
ssrMode: typeof window === 'undefined', ssrMode: typeof window === 'undefined',
link: createIsomorphLink(context), link: createIsomorphLink(context),
cache: new InMemoryCache({ cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
getResume: {
keyArgs: [],
merge(existing, incoming) {
if (!existing) return incoming;
const current = existing?.resume ? [...existing.resume] : [];
const newIncoming = incoming?.resume
? [...incoming.resume]
: [];
return {
...existing,
offset: incoming.offset,
resume: [...current, ...newIncoming],
};
},
},
},
},
},
...possibleTypes, ...possibleTypes,
}), }),
defaultOptions: { defaultOptions: {
query: { query: {
fetchPolicy: 'cache-first', fetchPolicy: 'cache-first',
// errorPolicy: 'all',
}, },
}, },
}); });

359
package-lock.json generated
View file

@ -5,12 +5,13 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"version": "0.12.17", "version": "0.12.19",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@apollo/client": "^3.3.19", "@apollo/client": "^3.3.19",
"@emotion/babel-plugin": "^11.3.0", "@emotion/babel-plugin": "^11.3.0",
"@next/bundle-analyzer": "^10.2.3", "@next/bundle-analyzer": "^10.2.3",
"@visx/axis": "^1.12.0",
"@visx/chord": "^1.7.0", "@visx/chord": "^1.7.0",
"@visx/curve": "^1.7.0", "@visx/curve": "^1.7.0",
"@visx/event": "^1.7.0", "@visx/event": "^1.7.0",
@ -30,6 +31,7 @@
"cookie": "^0.4.1", "cookie": "^0.4.1",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"d3-array": "^2.12.1", "d3-array": "^2.12.1",
"d3-time-format": "^3.0.0",
"date-fns": "^2.22.1", "date-fns": "^2.22.1",
"graphql": "^15.5.0", "graphql": "^15.5.0",
"graphql-iso-date": "^3.6.1", "graphql-iso-date": "^3.6.1",
@ -53,6 +55,7 @@
"react-copy-to-clipboard": "^5.0.3", "react-copy-to-clipboard": "^5.0.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-feather": "^2.0.9", "react-feather": "^2.0.9",
"react-grid-layout": "^1.2.5",
"react-intersection-observer": "^8.32.0", "react-intersection-observer": "^8.32.0",
"react-qr-reader": "^2.2.1", "react-qr-reader": "^2.2.1",
"react-select": "^4.3.1", "react-select": "^4.3.1",
@ -92,6 +95,7 @@
"@types/cookie": "^0.4.0", "@types/cookie": "^0.4.0",
"@types/crypto-js": "^4.0.1", "@types/crypto-js": "^4.0.1",
"@types/d3-array": "^2.12.1", "@types/d3-array": "^2.12.1",
"@types/d3-time-format": "^3.0.0",
"@types/graphql-iso-date": "^3.4.0", "@types/graphql-iso-date": "^3.4.0",
"@types/js-cookie": "^2.2.6", "@types/js-cookie": "^2.2.6",
"@types/js-yaml": "^4.0.1", "@types/js-yaml": "^4.0.1",
@ -106,6 +110,7 @@
"@types/qrcode.react": "^1.0.1", "@types/qrcode.react": "^1.0.1",
"@types/react": "^17.0.8", "@types/react": "^17.0.8",
"@types/react-copy-to-clipboard": "^5.0.0", "@types/react-copy-to-clipboard": "^5.0.0",
"@types/react-grid-layout": "^1.1.1",
"@types/react-qr-reader": "^2.1.3", "@types/react-qr-reader": "^2.1.3",
"@types/react-select": "^4.0.15", "@types/react-select": "^4.0.15",
"@types/react-slider": "^1.1.2", "@types/react-slider": "^1.1.2",
@ -363,14 +368,12 @@
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.14.4", "version": "7.14.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz",
"integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ==", "integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ=="
"dev": true
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.14.3", "version": "7.14.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz",
"integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.12.13", "@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.14.3", "@babel/generator": "^7.14.3",
@ -428,7 +431,6 @@
"version": "7.14.4", "version": "7.14.4",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz",
"integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==", "integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.14.4", "@babel/compat-data": "^7.14.4",
"@babel/helper-validator-option": "^7.12.17", "@babel/helper-validator-option": "^7.12.17",
@ -510,7 +512,6 @@
"version": "7.13.12", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz",
"integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.13.12" "@babel/types": "^7.13.12"
} }
@ -527,7 +528,6 @@
"version": "7.14.2", "version": "7.14.2",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz",
"integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-module-imports": "^7.13.12", "@babel/helper-module-imports": "^7.13.12",
"@babel/helper-replace-supers": "^7.13.12", "@babel/helper-replace-supers": "^7.13.12",
@ -543,7 +543,6 @@
"version": "7.12.13", "version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz",
"integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.12.13" "@babel/types": "^7.12.13"
} }
@ -568,7 +567,6 @@
"version": "7.14.4", "version": "7.14.4",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz",
"integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-member-expression-to-functions": "^7.13.12", "@babel/helper-member-expression-to-functions": "^7.13.12",
"@babel/helper-optimise-call-expression": "^7.12.13", "@babel/helper-optimise-call-expression": "^7.12.13",
@ -580,7 +578,6 @@
"version": "7.13.12", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz",
"integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.13.12" "@babel/types": "^7.13.12"
} }
@ -610,8 +607,7 @@
"node_modules/@babel/helper-validator-option": { "node_modules/@babel/helper-validator-option": {
"version": "7.12.17", "version": "7.12.17",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz",
"integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw=="
"dev": true
}, },
"node_modules/@babel/helper-wrap-function": { "node_modules/@babel/helper-wrap-function": {
"version": "7.13.0", "version": "7.13.0",
@ -629,7 +625,6 @@
"version": "7.14.0", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz",
"integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/template": "^7.12.13", "@babel/template": "^7.12.13",
"@babel/traverse": "^7.14.0", "@babel/traverse": "^7.14.0",
@ -5084,6 +5079,12 @@
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.0.tgz",
"integrity": "sha512-qVCiT93utxN0cawScyQuNx8H82vBvZXSClZfgOu3l3dRRlRO6FjKEZlaPgXG9XUFjIAOsA4kAJY101vobHeJLQ==" "integrity": "sha512-qVCiT93utxN0cawScyQuNx8H82vBvZXSClZfgOu3l3dRRlRO6FjKEZlaPgXG9XUFjIAOsA4kAJY101vobHeJLQ=="
}, },
"node_modules/@types/d3-time-format": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-3.0.0.tgz",
"integrity": "sha512-UpLg1mn/8PLyjr+J/JwdQJM/GzysMvv2CS8y+WYAL5K0+wbvXv/pPSLEfdNaprCZsGcXTxPsFMy8QtkYv9ueew==",
"dev": true
},
"node_modules/@types/express": { "node_modules/@types/express": {
"version": "4.17.12", "version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz",
@ -5421,6 +5422,15 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-grid-layout": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.1.1.tgz",
"integrity": "sha512-bvPkITzwGGOZKjp01nVSgPrdfGm/uTa5t8Odd8vQRXJsLj7uZLZXSXgWr+TiXBAkUsmHPxhsyswXQCiFeDuZnQ==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-qr-reader": { "node_modules/@types/react-qr-reader": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@types/react-qr-reader/-/react-qr-reader-2.1.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-qr-reader/-/react-qr-reader-2.1.3.tgz",
@ -5808,6 +5818,25 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@visx/axis": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@visx/axis/-/axis-1.12.0.tgz",
"integrity": "sha512-g867/6/TTHEjzVoIbnYjdYOiNGC3vETn7AZG9tT13KUy9AJE1gwm91pVoi+USHd+R3AvwdbgaQti88FavamoWA==",
"dependencies": {
"@types/classnames": "^2.2.9",
"@types/react": "*",
"@visx/group": "1.7.0",
"@visx/point": "1.7.0",
"@visx/scale": "1.11.1",
"@visx/shape": "1.12.0",
"@visx/text": "1.10.0",
"classnames": "^2.2.5",
"prop-types": "^15.6.0"
},
"peerDependencies": {
"react": "^16.3.0-0"
}
},
"node_modules/@visx/bounds": { "node_modules/@visx/bounds": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz", "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz",
@ -5904,9 +5933,9 @@
} }
}, },
"node_modules/@visx/shape": { "node_modules/@visx/shape": {
"version": "1.11.1", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.11.1.tgz", "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.12.0.tgz",
"integrity": "sha512-k5VF3+VeG0nNPycDyAdJywOI8tTglHWkZDL9ZTM1o4dLXbytwtWxEcfRTmDHyynYTca+WBsY7dapeGuvrmw6Hw==", "integrity": "sha512-BiY5nXrpg/CdY2Vd/oIaEsQoYjL7Nb+7IGsRCT5DVNelulgnwU1P13SqdVvs4FUtp/WYy97djQuIrR/6zCHdyw==",
"dependencies": { "dependencies": {
"@types/classnames": "^2.2.9", "@types/classnames": "^2.2.9",
"@types/d3-path": "^1.0.8", "@types/d3-path": "^1.0.8",
@ -5926,6 +5955,23 @@
"react": "^16.3.0-0" "react": "^16.3.0-0"
} }
}, },
"node_modules/@visx/text": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@visx/text/-/text-1.10.0.tgz",
"integrity": "sha512-2y56LxbbSHAlu8XP0cARB9JlhPx0HlQyIC4iKUzj36+iJaBiw9wUwLb9RbT/rY715V+6VzU7WCNCxoa4lDr9Sg==",
"dependencies": {
"@types/classnames": "^2.2.9",
"@types/lodash": "^4.14.160",
"@types/react": "*",
"classnames": "^2.2.5",
"lodash": "^4.17.20",
"prop-types": "^15.7.2",
"reduce-css-calc": "^1.3.0"
},
"peerDependencies": {
"react": "^16.3.0-0"
}
},
"node_modules/@visx/tooltip": { "node_modules/@visx/tooltip": {
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.7.2.tgz", "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.7.2.tgz",
@ -7298,8 +7344,7 @@
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
"dev": true
}, },
"node_modules/balanceofsatoshis": { "node_modules/balanceofsatoshis": {
"version": "8.0.14", "version": "8.0.14",
@ -12003,7 +12048,6 @@
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -16677,7 +16721,6 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"dependencies": { "dependencies": {
"minimist": "^1.2.5" "minimist": "^1.2.5"
}, },
@ -18436,6 +18479,11 @@
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
}, },
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"node_modules/lodash.isinteger": { "node_modules/lodash.isinteger": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
@ -18865,6 +18913,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/math-expression-evaluator": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.7.tgz",
"integrity": "sha512-nrbaifCl42w37hYd6oRLvoymFK42tWB+WQTMFtksDGQMi5GvlJwnz/CsS30FFAISFLtX+A0csJ0xLiuuyyec7w=="
},
"node_modules/md5.js": { "node_modules/md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -21602,6 +21655,15 @@
"react": "17.0.2" "react": "17.0.2"
} }
}, },
"node_modules/react-draggable": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.3.tgz",
"integrity": "sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==",
"dependencies": {
"classnames": "^2.2.5",
"prop-types": "^15.6.0"
}
},
"node_modules/react-fast-compare": { "node_modules/react-fast-compare": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
@ -21618,6 +21680,22 @@
"react": "^16.8.6 || ^17" "react": "^16.8.6 || ^17"
} }
}, },
"node_modules/react-grid-layout": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.2.5.tgz",
"integrity": "sha512-P/NNWAExTX/zEq+RUh6hrIG67UBicDNCOOg9LZe8BAtSdYtCnCGgVmWBS+sIbM0C8RJIiyGsFHh5dIfCddhS/w==",
"dependencies": {
"classnames": "2.3.1",
"lodash.isequal": "^4.0.0",
"prop-types": "^15.0.0",
"react-draggable": "^4.0.0",
"react-resizable": "^3.0.1"
},
"peerDependencies": {
"react": ">= 16.3.0",
"react-dom": ">= 16.3.0"
}
},
"node_modules/react-input-autosize": { "node_modules/react-input-autosize": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz",
@ -21664,6 +21742,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-resizable": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz",
"integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==",
"dependencies": {
"prop-types": "15.x",
"react-draggable": "^4.0.3"
},
"peerDependencies": {
"react": ">= 16.3"
}
},
"node_modules/react-select": { "node_modules/react-select": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz", "resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz",
@ -21982,6 +22072,29 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/reduce-css-calc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz",
"integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=",
"dependencies": {
"balanced-match": "^0.4.2",
"math-expression-evaluator": "^1.2.14",
"reduce-function-call": "^1.0.1"
}
},
"node_modules/reduce-css-calc/node_modules/balanced-match": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
},
"node_modules/reduce-function-call": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz",
"integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/regenerate": { "node_modules/regenerate": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -26632,14 +26745,12 @@
"@babel/compat-data": { "@babel/compat-data": {
"version": "7.14.4", "version": "7.14.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz",
"integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ==", "integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ=="
"dev": true
}, },
"@babel/core": { "@babel/core": {
"version": "7.14.3", "version": "7.14.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz",
"integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==",
"dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.12.13", "@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.14.3", "@babel/generator": "^7.14.3",
@ -26690,7 +26801,6 @@
"version": "7.14.4", "version": "7.14.4",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz",
"integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==", "integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==",
"dev": true,
"requires": { "requires": {
"@babel/compat-data": "^7.14.4", "@babel/compat-data": "^7.14.4",
"@babel/helper-validator-option": "^7.12.17", "@babel/helper-validator-option": "^7.12.17",
@ -26763,7 +26873,6 @@
"version": "7.13.12", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz",
"integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==",
"dev": true,
"requires": { "requires": {
"@babel/types": "^7.13.12" "@babel/types": "^7.13.12"
} }
@ -26780,7 +26889,6 @@
"version": "7.14.2", "version": "7.14.2",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz",
"integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==",
"dev": true,
"requires": { "requires": {
"@babel/helper-module-imports": "^7.13.12", "@babel/helper-module-imports": "^7.13.12",
"@babel/helper-replace-supers": "^7.13.12", "@babel/helper-replace-supers": "^7.13.12",
@ -26796,7 +26904,6 @@
"version": "7.12.13", "version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz",
"integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==",
"dev": true,
"requires": { "requires": {
"@babel/types": "^7.12.13" "@babel/types": "^7.12.13"
} }
@ -26821,7 +26928,6 @@
"version": "7.14.4", "version": "7.14.4",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz",
"integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==",
"dev": true,
"requires": { "requires": {
"@babel/helper-member-expression-to-functions": "^7.13.12", "@babel/helper-member-expression-to-functions": "^7.13.12",
"@babel/helper-optimise-call-expression": "^7.12.13", "@babel/helper-optimise-call-expression": "^7.12.13",
@ -26833,7 +26939,6 @@
"version": "7.13.12", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz",
"integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==",
"dev": true,
"requires": { "requires": {
"@babel/types": "^7.13.12" "@babel/types": "^7.13.12"
} }
@ -26863,8 +26968,7 @@
"@babel/helper-validator-option": { "@babel/helper-validator-option": {
"version": "7.12.17", "version": "7.12.17",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz",
"integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw=="
"dev": true
}, },
"@babel/helper-wrap-function": { "@babel/helper-wrap-function": {
"version": "7.13.0", "version": "7.13.0",
@ -26882,7 +26986,6 @@
"version": "7.14.0", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz",
"integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==",
"dev": true,
"requires": { "requires": {
"@babel/template": "^7.12.13", "@babel/template": "^7.12.13",
"@babel/traverse": "^7.14.0", "@babel/traverse": "^7.14.0",
@ -29187,7 +29290,8 @@
"@graphql-typed-document-node/core": { "@graphql-typed-document-node/core": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz",
"integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==" "integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==",
"requires": {}
}, },
"@grpc/grpc-js": { "@grpc/grpc-js": {
"version": "1.2.12", "version": "1.2.12",
@ -29948,7 +30052,8 @@
"@next/react-refresh-utils": { "@next/react-refresh-utils": {
"version": "10.2.3", "version": "10.2.3",
"resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-10.2.3.tgz", "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-10.2.3.tgz",
"integrity": "sha512-qtBF56vPC6d6a8p7LYd0iRjW89fhY80kAIzmj+VonvIGjK/nymBjcFUhbKiMFqlhsarCksnhwX+Zmn95Dw9qvA==" "integrity": "sha512-qtBF56vPC6d6a8p7LYd0iRjW89fhY80kAIzmj+VonvIGjK/nymBjcFUhbKiMFqlhsarCksnhwX+Zmn95Dw9qvA==",
"requires": {}
}, },
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.4", "version": "2.1.4",
@ -30507,6 +30612,12 @@
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.0.tgz",
"integrity": "sha512-qVCiT93utxN0cawScyQuNx8H82vBvZXSClZfgOu3l3dRRlRO6FjKEZlaPgXG9XUFjIAOsA4kAJY101vobHeJLQ==" "integrity": "sha512-qVCiT93utxN0cawScyQuNx8H82vBvZXSClZfgOu3l3dRRlRO6FjKEZlaPgXG9XUFjIAOsA4kAJY101vobHeJLQ=="
}, },
"@types/d3-time-format": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-3.0.0.tgz",
"integrity": "sha512-UpLg1mn/8PLyjr+J/JwdQJM/GzysMvv2CS8y+WYAL5K0+wbvXv/pPSLEfdNaprCZsGcXTxPsFMy8QtkYv9ueew==",
"dev": true
},
"@types/express": { "@types/express": {
"version": "4.17.12", "version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz",
@ -30843,6 +30954,15 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/react-grid-layout": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.1.1.tgz",
"integrity": "sha512-bvPkITzwGGOZKjp01nVSgPrdfGm/uTa5t8Odd8vQRXJsLj7uZLZXSXgWr+TiXBAkUsmHPxhsyswXQCiFeDuZnQ==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-qr-reader": { "@types/react-qr-reader": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@types/react-qr-reader/-/react-qr-reader-2.1.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-qr-reader/-/react-qr-reader-2.1.3.tgz",
@ -31147,6 +31267,22 @@
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
} }
}, },
"@visx/axis": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@visx/axis/-/axis-1.12.0.tgz",
"integrity": "sha512-g867/6/TTHEjzVoIbnYjdYOiNGC3vETn7AZG9tT13KUy9AJE1gwm91pVoi+USHd+R3AvwdbgaQti88FavamoWA==",
"requires": {
"@types/classnames": "^2.2.9",
"@types/react": "*",
"@visx/group": "1.7.0",
"@visx/point": "1.7.0",
"@visx/scale": "1.11.1",
"@visx/shape": "1.12.0",
"@visx/text": "1.10.0",
"classnames": "^2.2.5",
"prop-types": "^15.6.0"
}
},
"@visx/bounds": { "@visx/bounds": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz", "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz",
@ -31230,9 +31366,9 @@
} }
}, },
"@visx/shape": { "@visx/shape": {
"version": "1.11.1", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.11.1.tgz", "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.12.0.tgz",
"integrity": "sha512-k5VF3+VeG0nNPycDyAdJywOI8tTglHWkZDL9ZTM1o4dLXbytwtWxEcfRTmDHyynYTca+WBsY7dapeGuvrmw6Hw==", "integrity": "sha512-BiY5nXrpg/CdY2Vd/oIaEsQoYjL7Nb+7IGsRCT5DVNelulgnwU1P13SqdVvs4FUtp/WYy97djQuIrR/6zCHdyw==",
"requires": { "requires": {
"@types/classnames": "^2.2.9", "@types/classnames": "^2.2.9",
"@types/d3-path": "^1.0.8", "@types/d3-path": "^1.0.8",
@ -31249,6 +31385,20 @@
"prop-types": "^15.5.10" "prop-types": "^15.5.10"
} }
}, },
"@visx/text": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@visx/text/-/text-1.10.0.tgz",
"integrity": "sha512-2y56LxbbSHAlu8XP0cARB9JlhPx0HlQyIC4iKUzj36+iJaBiw9wUwLb9RbT/rY715V+6VzU7WCNCxoa4lDr9Sg==",
"requires": {
"@types/classnames": "^2.2.9",
"@types/lodash": "^4.14.160",
"@types/react": "*",
"classnames": "^2.2.5",
"lodash": "^4.17.20",
"prop-types": "^15.7.2",
"reduce-css-calc": "^1.3.0"
}
},
"@visx/tooltip": { "@visx/tooltip": {
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.7.2.tgz", "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.7.2.tgz",
@ -31359,7 +31509,8 @@
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
"dev": true "dev": true,
"requires": {}
}, },
"acorn-walk": { "acorn-walk": {
"version": "7.2.0", "version": "7.2.0",
@ -31414,7 +31565,8 @@
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true "dev": true,
"requires": {}
}, },
"anser": { "anser": {
"version": "1.4.9", "version": "1.4.9",
@ -31645,7 +31797,8 @@
"apollo-server-errors": { "apollo-server-errors": {
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz", "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz",
"integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==" "integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==",
"requires": {}
}, },
"apollo-server-express": { "apollo-server-express": {
"version": "2.25.0", "version": "2.25.0",
@ -32356,8 +32509,7 @@
"balanced-match": { "balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
"dev": true
}, },
"balanceofsatoshis": { "balanceofsatoshis": {
"version": "8.0.14", "version": "8.0.14",
@ -35276,7 +35428,8 @@
"version": "8.3.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
"dev": true "dev": true,
"requires": {}
}, },
"eslint-import-resolver-node": { "eslint-import-resolver-node": {
"version": "0.3.4", "version": "0.3.4",
@ -35506,7 +35659,8 @@
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz",
"integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==",
"dev": true "dev": true,
"requires": {}
}, },
"eslint-scope": { "eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
@ -36148,8 +36302,7 @@
"gensync": { "gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
"dev": true
}, },
"get-caller-file": { "get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
@ -36693,7 +36846,8 @@
"ws": { "ws": {
"version": "7.4.4", "version": "7.4.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
"requires": {}
} }
} }
}, },
@ -36791,7 +36945,8 @@
"graphql-iso-date": { "graphql-iso-date": {
"version": "3.6.1", "version": "3.6.1",
"resolved": "https://registry.npmjs.org/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz", "resolved": "https://registry.npmjs.org/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz",
"integrity": "sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q==" "integrity": "sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q==",
"requires": {}
}, },
"graphql-middleware": { "graphql-middleware": {
"version": "6.0.10", "version": "6.0.10",
@ -36901,7 +37056,8 @@
"version": "4.7.0", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.7.0.tgz", "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.7.0.tgz",
"integrity": "sha512-Md8SsmC9ZlsogFPd3Ot8HbIAAqsHh8Xoq7j4AmcIat1Bh6k91tjVyQvA0Au1/BolXSYq+RDvib6rATU2Hcf1Xw==", "integrity": "sha512-Md8SsmC9ZlsogFPd3Ot8HbIAAqsHh8Xoq7j4AmcIat1Bh6k91tjVyQvA0Au1/BolXSYq+RDvib6rATU2Hcf1Xw==",
"dev": true "dev": true,
"requires": {}
}, },
"gtoken": { "gtoken": {
"version": "5.2.1", "version": "5.2.1",
@ -37861,7 +38017,8 @@
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
"dev": true "dev": true,
"requires": {}
}, },
"isstream": { "isstream": {
"version": "0.1.2", "version": "0.1.2",
@ -38839,7 +38996,8 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
"dev": true "dev": true,
"requires": {}
}, },
"jest-regex-util": { "jest-regex-util": {
"version": "27.0.1", "version": "27.0.1",
@ -39682,7 +39840,6 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"requires": { "requires": {
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
@ -40568,7 +40725,8 @@
"ws": { "ws": {
"version": "7.4.4", "version": "7.4.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
"requires": {}
} }
} }
}, },
@ -40616,7 +40774,8 @@
"ws": { "ws": {
"version": "7.4.6", "version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"requires": {}
} }
} }
}, },
@ -40967,7 +41126,8 @@
"ws": { "ws": {
"version": "7.4.4", "version": "7.4.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
"requires": {}
} }
} }
}, },
@ -41082,6 +41242,11 @@
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
}, },
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"lodash.isinteger": { "lodash.isinteger": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
@ -41428,6 +41593,11 @@
"integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==",
"dev": true "dev": true
}, },
"math-expression-evaluator": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.7.tgz",
"integrity": "sha512-nrbaifCl42w37hYd6oRLvoymFK42tWB+WQTMFtksDGQMi5GvlJwnz/CsS30FFAISFLtX+A0csJ0xLiuuyyec7w=="
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -41606,7 +41776,8 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz", "resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz",
"integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==", "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==",
"dev": true "dev": true,
"requires": {}
}, },
"methods": { "methods": {
"version": "1.1.2", "version": "1.1.2",
@ -43242,7 +43413,8 @@
"ws": { "ws": {
"version": "7.4.4", "version": "7.4.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
"requires": {}
} }
} }
}, },
@ -43544,7 +43716,8 @@
"react-circular-progressbar": { "react-circular-progressbar": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.0.4.tgz",
"integrity": "sha512-OfX0ThSxRYEVGaQSt0DlXfyl5w4DbXHsXetyeivmoQrh9xA9bzVPHNf8aAhOIiwiaxX2WYWpLDB3gcpsDJ9oww==" "integrity": "sha512-OfX0ThSxRYEVGaQSt0DlXfyl5w4DbXHsXetyeivmoQrh9xA9bzVPHNf8aAhOIiwiaxX2WYWpLDB3gcpsDJ9oww==",
"requires": {}
}, },
"react-copy-to-clipboard": { "react-copy-to-clipboard": {
"version": "5.0.3", "version": "5.0.3",
@ -43565,6 +43738,15 @@
"scheduler": "^0.20.2" "scheduler": "^0.20.2"
} }
}, },
"react-draggable": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.3.tgz",
"integrity": "sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==",
"requires": {
"classnames": "^2.2.5",
"prop-types": "^15.6.0"
}
},
"react-fast-compare": { "react-fast-compare": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
@ -43578,6 +43760,18 @@
"prop-types": "^15.7.2" "prop-types": "^15.7.2"
} }
}, },
"react-grid-layout": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.2.5.tgz",
"integrity": "sha512-P/NNWAExTX/zEq+RUh6hrIG67UBicDNCOOg9LZe8BAtSdYtCnCGgVmWBS+sIbM0C8RJIiyGsFHh5dIfCddhS/w==",
"requires": {
"classnames": "2.3.1",
"lodash.isequal": "^4.0.0",
"prop-types": "^15.0.0",
"react-draggable": "^4.0.0",
"react-resizable": "^3.0.1"
}
},
"react-input-autosize": { "react-input-autosize": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz",
@ -43589,7 +43783,8 @@
"react-intersection-observer": { "react-intersection-observer": {
"version": "8.32.0", "version": "8.32.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.32.0.tgz", "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.32.0.tgz",
"integrity": "sha512-RlC6FvS3MFShxTn4FHAy904bVjX5Nn4/eTjUkurW0fHK+M/fyQdXuyCy9+L7yjA+YMGogzzSJNc7M4UtfSKvtw==" "integrity": "sha512-RlC6FvS3MFShxTn4FHAy904bVjX5Nn4/eTjUkurW0fHK+M/fyQdXuyCy9+L7yjA+YMGogzzSJNc7M4UtfSKvtw==",
"requires": {}
}, },
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
@ -43611,6 +43806,15 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
"integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==" "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg=="
}, },
"react-resizable": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz",
"integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==",
"requires": {
"prop-types": "15.x",
"react-draggable": "^4.0.3"
}
},
"react-select": { "react-select": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz", "resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz",
@ -43628,7 +43832,8 @@
"react-slider": { "react-slider": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/react-slider/-/react-slider-1.1.4.tgz", "resolved": "https://registry.npmjs.org/react-slider/-/react-slider-1.1.4.tgz",
"integrity": "sha512-lL/MvzFcDue0ztdJItwLqas2lOy8Gg46eCDGJc4cJGldThmBHcHfGQePgBgyY1SEN95OwsWAakd3SuI8RyixDQ==" "integrity": "sha512-lL/MvzFcDue0ztdJItwLqas2lOy8Gg46eCDGJc4cJGldThmBHcHfGQePgBgyY1SEN95OwsWAakd3SuI8RyixDQ==",
"requires": {}
}, },
"react-spinners": { "react-spinners": {
"version": "0.11.0", "version": "0.11.0",
@ -43654,7 +43859,8 @@
"react-table": { "react-table": {
"version": "7.7.0", "version": "7.7.0",
"resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz", "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz",
"integrity": "sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==" "integrity": "sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==",
"requires": {}
}, },
"react-toastify": { "react-toastify": {
"version": "7.0.4", "version": "7.0.4",
@ -43851,6 +44057,31 @@
} }
} }
}, },
"reduce-css-calc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz",
"integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=",
"requires": {
"balanced-match": "^0.4.2",
"math-expression-evaluator": "^1.2.14",
"reduce-function-call": "^1.0.1"
},
"dependencies": {
"balanced-match": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
}
}
},
"reduce-function-call": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz",
"integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==",
"requires": {
"balanced-match": "^1.0.0"
}
},
"regenerate": { "regenerate": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -45297,7 +45528,8 @@
"stylis-rule-sheet": { "stylis-rule-sheet": {
"version": "0.0.10", "version": "0.0.10",
"resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
"integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==",
"requires": {}
}, },
"subscriptions-transport-ws": { "subscriptions-transport-ws": {
"version": "0.9.18", "version": "0.9.18",
@ -47349,7 +47581,8 @@
"ws": { "ws": {
"version": "7.4.5", "version": "7.4.5",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==" "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
"requires": {}
}, },
"xdg-basedir": { "xdg-basedir": {
"version": "4.0.0", "version": "4.0.0",

View file

@ -37,6 +37,7 @@
"@apollo/client": "^3.3.19", "@apollo/client": "^3.3.19",
"@emotion/babel-plugin": "^11.3.0", "@emotion/babel-plugin": "^11.3.0",
"@next/bundle-analyzer": "^10.2.3", "@next/bundle-analyzer": "^10.2.3",
"@visx/axis": "^1.12.0",
"@visx/chord": "^1.7.0", "@visx/chord": "^1.7.0",
"@visx/curve": "^1.7.0", "@visx/curve": "^1.7.0",
"@visx/event": "^1.7.0", "@visx/event": "^1.7.0",
@ -56,6 +57,7 @@
"cookie": "^0.4.1", "cookie": "^0.4.1",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"d3-array": "^2.12.1", "d3-array": "^2.12.1",
"d3-time-format": "^3.0.0",
"date-fns": "^2.22.1", "date-fns": "^2.22.1",
"graphql": "^15.5.0", "graphql": "^15.5.0",
"graphql-iso-date": "^3.6.1", "graphql-iso-date": "^3.6.1",
@ -79,6 +81,7 @@
"react-copy-to-clipboard": "^5.0.3", "react-copy-to-clipboard": "^5.0.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-feather": "^2.0.9", "react-feather": "^2.0.9",
"react-grid-layout": "^1.2.5",
"react-intersection-observer": "^8.32.0", "react-intersection-observer": "^8.32.0",
"react-qr-reader": "^2.2.1", "react-qr-reader": "^2.2.1",
"react-select": "^4.3.1", "react-select": "^4.3.1",
@ -118,6 +121,7 @@
"@types/cookie": "^0.4.0", "@types/cookie": "^0.4.0",
"@types/crypto-js": "^4.0.1", "@types/crypto-js": "^4.0.1",
"@types/d3-array": "^2.12.1", "@types/d3-array": "^2.12.1",
"@types/d3-time-format": "^3.0.0",
"@types/graphql-iso-date": "^3.4.0", "@types/graphql-iso-date": "^3.4.0",
"@types/js-cookie": "^2.2.6", "@types/js-cookie": "^2.2.6",
"@types/js-yaml": "^4.0.1", "@types/js-yaml": "^4.0.1",
@ -132,6 +136,7 @@
"@types/qrcode.react": "^1.0.1", "@types/qrcode.react": "^1.0.1",
"@types/react": "^17.0.8", "@types/react": "^17.0.8",
"@types/react-copy-to-clipboard": "^5.0.0", "@types/react-copy-to-clipboard": "^5.0.0",
"@types/react-grid-layout": "^1.1.1",
"@types/react-qr-reader": "^2.1.3", "@types/react-qr-reader": "^2.1.3",
"@types/react-select": "^4.0.15", "@types/react-select": "^4.0.15",
"@types/react-slider": "^1.1.2", "@types/react-slider": "^1.1.2",

View file

@ -12,8 +12,11 @@ import { useConfigState, ConfigProvider } from '../src/context/ConfigContext';
import { GlobalStyles } from '../src/styles/GlobalStyle'; import { GlobalStyles } from '../src/styles/GlobalStyle';
import { Header } from '../src/layouts/header/Header'; import { Header } from '../src/layouts/header/Header';
import { Footer } from '../src/layouts/footer/Footer'; import { Footer } from '../src/layouts/footer/Footer';
import 'react-toastify/dist/ReactToastify.min.css';
import { PageWrapper, HeaderBodyWrapper } from '../src/layouts/Layout.styled'; import { PageWrapper, HeaderBodyWrapper } from '../src/layouts/Layout.styled';
import 'react-toastify/dist/ReactToastify.min.css';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import 'react-circular-progressbar/dist/styles.css'; import 'react-circular-progressbar/dist/styles.css';
const Wrapper: React.FC = ({ children }) => { const Wrapper: React.FC = ({ children }) => {

View file

@ -14,6 +14,7 @@ import {
CardWithTitle, CardWithTitle,
SubTitle, SubTitle,
SmallButton, SmallButton,
Card,
} from '../src/components/generic/Styled'; } from '../src/components/generic/Styled';
import { mediaWidths } from '../src/styles/Themes'; import { mediaWidths } from '../src/styles/Themes';
@ -66,7 +67,11 @@ const ChannelView = () => {
case 3: case 3:
return <ClosedChannels />; return <ClosedChannels />;
default: default:
return <Channels />; return (
<Card mobileCardPadding={'0'} mobileNoBackground={true}>
<Channels />
</Card>
);
} }
}; };

35
pages/dashboard.tsx Normal file
View file

@ -0,0 +1,35 @@
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import dynamic from 'next/dynamic';
import { LoadingCard } from 'src/components/loading/LoadingCard';
import { SimpleWrapper } from 'src/components/gridWrapper/GridWrapper';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
position: relative;
`,
};
const LoadingComp = () => <LoadingCard noCard={true} loadingHeight={'90vh'} />;
const Dashboard = dynamic(() => import('src/views/dashboard'), {
ssr: false,
loading: LoadingComp,
});
const Wrapped = () => {
return (
<SimpleWrapper>
<S.wrapper>
<Dashboard />
</S.wrapper>
</SimpleWrapper>
);
};
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -38,7 +38,9 @@ const LeaderboardView = () => {
return ( return (
<> <>
<SupportBar /> <Card>
<SupportBar />
</Card>
{renderBoard()} {renderBoard()}
</> </>
); );

View file

@ -0,0 +1,25 @@
import React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import dynamic from 'next/dynamic';
import { LoadingCard } from 'src/components/loading/LoadingCard';
const LoadingComp = () => <LoadingCard noCard={true} loadingHeight={'30vh'} />;
const Dashboard = dynamic(() => import('src/views/settings/DashPanel'), {
ssr: false,
loading: LoadingComp,
});
const Wrapped = () => (
<GridWrapper noNavigation={true}>
<Dashboard />
</GridWrapper>
);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View file

@ -3,11 +3,12 @@ import styled from 'styled-components';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { NextPageContext } from 'next'; import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr'; import { getProps } from 'src/utils/ssr';
import { SingleLine } from '../src/components/generic/Styled'; import { SingleLine } from '../../src/components/generic/Styled';
import { InterfaceSettings } from '../src/views/settings/Interface'; import { InterfaceSettings } from '../../src/views/settings/Interface';
import { DangerView } from '../src/views/settings/Danger'; import { DangerView } from '../../src/views/settings/Danger';
import { ChatSettings } from '../src/views/settings/Chat'; import { ChatSettings } from '../../src/views/settings/Chat';
import { PrivacySettings } from '../src/views/settings/Privacy'; import { PrivacySettings } from '../../src/views/settings/Privacy';
import { DashboardSettings } from 'src/views/settings/Dashboard';
export const ButtonRow = styled.div` export const ButtonRow = styled.div`
width: auto; width: auto;
@ -22,6 +23,7 @@ const SettingsView = () => {
return ( return (
<> <>
<InterfaceSettings /> <InterfaceSettings />
<DashboardSettings />
<PrivacySettings /> <PrivacySettings />
<ChatSettings /> <ChatSettings />
<DangerView /> <DangerView />

View file

@ -61,20 +61,15 @@ const TransactionsView = () => {
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
const [limit, setLimit] = useState(7);
const { publicKey } = useNodeInfo(); const { publicKey } = useNodeInfo();
const [settings] = useLocalStorage('transactionSettings', defaultSettings); const [settings] = useLocalStorage('transactionSettings', defaultSettings);
const { const { data, startPolling, stopPolling, networkStatus } = useGetResumeQuery({
data,
fetchMore,
startPolling,
stopPolling,
networkStatus,
} = useGetResumeQuery({
ssr: false, ssr: false,
variables: { offset: 0 }, variables: { offset: 0, limit },
notifyOnNetworkStatusChange: true, notifyOnNetworkStatusChange: true,
onError: error => toast.error(getErrorContent(error)), onError: error => toast.error(getErrorContent(error)),
}); });
@ -85,9 +80,8 @@ const TransactionsView = () => {
const loadingOrRefetching = isLoading || isRefetching; const loadingOrRefetching = isLoading || isRefetching;
useEffect(() => { useEffect(() => {
if (!isLoading && data?.getResume?.offset) { if (isLoading || !data?.getResume?.offset) return;
setOffset(data.getResume.offset); setOffset(data.getResume.offset);
}
}, [data, isLoading]); }, [data, isLoading]);
useEffect(() => { useEffect(() => {
@ -95,7 +89,12 @@ const TransactionsView = () => {
}, [stopPolling]); }, [stopPolling]);
if (isLoading || !data || !data.getResume) { if (isLoading || !data || !data.getResume) {
return <LoadingCard title={'Transactions'} />; return (
<>
<FlowBox />
<LoadingCard title={'Transactions'} />
</>
);
} }
const beforeDate = subDays(new Date(), offset); const beforeDate = subDays(new Date(), offset);
@ -135,8 +134,7 @@ const TransactionsView = () => {
return [...p, c]; return [...p, c];
}, [] as ResumeTransactions); }, [] as ResumeTransactions);
const handleClick = (limit: number) => const handleClick = (limit: number) => setLimit(offset + limit);
fetchMore({ variables: { offset, limit } });
return ( return (
<> <>

View file

@ -0,0 +1,232 @@
import { Group } from '@visx/group';
import { BarGroup } from '@visx/shape';
import { AxisBottom, AxisLeft } from '@visx/axis';
import { scaleBand, scaleLinear, scaleOrdinal } from '@visx/scale';
import { timeParse, timeFormat } from 'd3-time-format';
import { ParentSize } from '@visx/responsive';
import { chartColors } from 'src/styles/Themes';
import { ThemeContext } from 'styled-components';
import { useContext } from 'react';
import { TooltipWithBounds, defaultStyles, useTooltip } from '@visx/tooltip';
import { Price } from '../price/Price';
import { localPoint } from '@visx/event';
type BarGroupProps = {
width: number;
height: number;
} & BarChartProps;
type BarChartProps = {
data: any[];
margin?: { top: number; right: number; bottom: number; left: number };
events?: boolean;
colorRange?: string[];
priceLabel?: boolean;
};
const defaultMargin = { top: 40, right: 0, bottom: 40, left: 0 };
const defaultColorRange = [
chartColors.green,
chartColors.orange,
chartColors.lightblue,
];
const amountOfYTicks = (height: number) => {
switch (true) {
case height < 300:
return 3;
case height < 400:
return 6;
default:
return 10;
}
};
const amountOfXTicks = (width: number) => {
switch (true) {
case width < 300:
return 2;
case width < 400:
return 6;
default:
return 10;
}
};
const parseDate = timeParse('%Y-%m-%d');
const format = timeFormat('%b %d');
const formatDate = (date: string) => format(parseDate(date) as Date);
const tooltipStyles = {
...defaultStyles,
minWidth: 60,
backgroundColor: 'rgba(0,0,0,0.9)',
color: 'white',
};
const Chart = ({
width,
height,
margin = defaultMargin,
data = [],
colorRange = defaultColorRange,
priceLabel,
}: BarGroupProps) => {
const {
tooltipData,
tooltipLeft,
tooltipTop,
tooltipOpen,
showTooltip,
hideTooltip,
} = useTooltip<any>();
const themeContext = useContext(ThemeContext);
const axisColor = themeContext.mode === 'light' ? 'black' : 'white';
const keys = Object.keys(data[0]).filter(d => d !== 'date');
let tooltipTimeout: number;
const getDate = (d: any) => d.date;
const xScale = scaleBand<string>({
domain: data.map(getDate),
padding: 0.2,
});
const barScale = scaleBand<string>({
domain: keys,
padding: 0.1,
});
const yScale = scaleLinear<number>({
domain: [
0,
Math.max(...data.map(d => Math.max(...keys.map(key => Number(d[key]))))),
],
});
const colorScale = scaleOrdinal<string, string>({
domain: keys,
range: colorRange,
});
// bounds
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
// update scale output dimensions
xScale.rangeRound([0, xMax]);
yScale.rangeRound([yMax, 0]);
barScale.rangeRound([0, xScale.bandwidth()]);
return (
<div style={{ position: 'relative' }}>
<svg width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<BarGroup
data={data}
keys={keys}
height={yMax}
x0={getDate}
x0Scale={xScale}
x1Scale={barScale}
yScale={yScale}
color={colorScale}
>
{barGroups =>
barGroups.map(barGroup => (
<Group
key={`bar-group-${barGroup.index}-${barGroup.x0}`}
left={barGroup.x0}
>
{barGroup.bars.map(bar => (
<rect
key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
x={bar.x}
y={bar.y}
width={bar.width}
height={Math.abs(bar.height)}
fill={bar.color}
onMouseOver={(e: any) => {
if (tooltipTimeout) clearTimeout(tooltipTimeout);
const coords = localPoint(e.target.ownerSVGElement, e);
if (!coords) return;
showTooltip({
tooltipLeft: coords.x,
tooltipTop: coords.y,
tooltipData: bar,
});
}}
onMouseLeave={() => {
tooltipTimeout = window.setTimeout(() => {
hideTooltip();
}, 300);
}}
/>
))}
</Group>
))
}
</BarGroup>
</Group>
<AxisLeft
numTicks={amountOfYTicks(height)}
left={width - margin.left}
top={margin.bottom}
hideZero={true}
scale={yScale}
stroke={axisColor}
tickStroke={axisColor}
tickLabelProps={() => ({
fill: axisColor,
fontSize: 11,
textAnchor: 'end',
dy: '0.33em',
dx: '-0.33em',
})}
/>
<AxisBottom
numTicks={amountOfXTicks(width)}
top={yMax + margin.top}
tickFormat={formatDate}
scale={xScale}
stroke={axisColor}
tickStroke={axisColor}
tickLabelProps={() => ({
fill: axisColor,
fontSize: 11,
textAnchor: 'middle',
})}
/>
</svg>
{tooltipOpen && tooltipData ? (
<TooltipWithBounds
top={tooltipTop}
left={tooltipLeft}
style={tooltipStyles}
>
<div style={{ color: colorScale(tooltipData.key) }}>
<strong>{tooltipData.key}</strong>
</div>
{priceLabel ? (
<Price amount={tooltipData.value} />
) : (
tooltipData.value
)}
</TooltipWithBounds>
) : null}
</div>
);
};
export const BarChart = (props: BarChartProps) => (
<ParentSize>
{parent => <Chart width={parent.width} height={parent.height} {...props} />}
</ParentSize>
);

View file

@ -0,0 +1,189 @@
import { Group } from '@visx/group';
import { BarGroupHorizontal, Bar } from '@visx/shape';
import { AxisLeft } from '@visx/axis';
import { scaleBand, scaleLinear, scaleOrdinal } from '@visx/scale';
import { ParentSize } from '@visx/responsive';
import { chartColors } from 'src/styles/Themes';
import { ThemeContext } from 'styled-components';
import { useContext } from 'react';
import { TooltipWithBounds, defaultStyles, useTooltip } from '@visx/tooltip';
import { Price } from '../price/Price';
import { localPoint } from '@visx/event';
type BarGroupProps = {
width: number;
height: number;
} & BarChartProps;
type BarChartProps = {
data: any[];
margin?: { top: number; right: number; bottom: number; left: number };
events?: boolean;
colorRange?: string[];
priceLabel?: boolean;
};
const defaultMargin = { top: 40, right: 0, bottom: 40, left: 0 };
const defaultColorRange = [
chartColors.green,
chartColors.orange,
chartColors.lightblue,
];
const tooltipStyles = {
...defaultStyles,
minWidth: 60,
backgroundColor: 'rgba(0,0,0,0.9)',
color: 'white',
};
const Chart = ({
width,
height,
margin = defaultMargin,
data = [],
colorRange = defaultColorRange,
priceLabel,
}: BarGroupProps) => {
const {
tooltipData,
tooltipLeft,
tooltipTop,
tooltipOpen,
showTooltip,
hideTooltip,
} = useTooltip<any>();
const themeContext = useContext(ThemeContext);
const axisColor = themeContext.mode === 'light' ? 'black' : 'white';
const keys = Object.keys(data[0]).filter(d => d !== 'label');
const maxValue = Math.max(
...data.map(d => Math.max(...keys.map(key => Number(d[key]))))
);
let tooltipTimeout: number;
const getLabel = (d: any) => d.label;
const yScale = scaleBand<string>({
domain: data.map(getLabel),
});
const barScale = scaleBand<string>({
domain: keys,
padding: 0.1,
});
const xScale = scaleLinear<number>({
domain: [0, maxValue + 0.1 * maxValue],
});
const colorScale = scaleOrdinal<string, string>({
domain: keys,
range: colorRange,
});
// bounds
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
// update scale output dimensions
xScale.rangeRound([0, xMax]);
yScale.rangeRound([yMax, 0]);
barScale.rangeRound([0, yScale.bandwidth()]);
return (
<div style={{ position: 'relative' }}>
<svg width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<BarGroupHorizontal
data={data}
keys={keys}
width={xMax}
y0={getLabel}
y0Scale={yScale}
y1Scale={barScale}
xScale={xScale}
color={colorScale}
>
{barGroups =>
barGroups.map(barGroup => (
<Group
key={`bar-group-${barGroup.index}-${barGroup.y0}`}
top={barGroup.y0}
>
{barGroup.bars.map(bar => (
<Bar
key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
x={bar.x}
y={bar.y}
width={bar.width < 10 ? 10 : bar.width}
height={Math.abs(bar.height)}
fill={bar.color}
onMouseOver={(e: any) => {
if (tooltipTimeout) clearTimeout(tooltipTimeout);
const coords = localPoint(e.target.ownerSVGElement, e);
if (!coords) return;
showTooltip({
tooltipLeft: coords.x,
tooltipTop: coords.y,
tooltipData: bar,
});
}}
onMouseLeave={() => {
tooltipTimeout = window.setTimeout(() => {
hideTooltip();
}, 300);
}}
/>
))}
</Group>
))
}
</BarGroupHorizontal>
</Group>
<AxisLeft
left={width - margin.left}
top={margin.bottom}
hideZero={true}
scale={yScale}
stroke={axisColor}
tickStroke={axisColor}
tickLabelProps={() => ({
fill: axisColor,
fontSize: 11,
textAnchor: 'end',
dy: '0.33em',
dx: '-0.33em',
})}
/>
</svg>
{tooltipOpen && tooltipData ? (
<TooltipWithBounds
top={tooltipTop}
left={tooltipLeft}
style={tooltipStyles}
>
<div style={{ color: colorScale(tooltipData.key) }}>
<strong>{tooltipData.key}</strong>
</div>
{priceLabel ? (
<Price amount={tooltipData.value} />
) : (
tooltipData.value
)}
</TooltipWithBounds>
) : null}
</div>
);
};
export const HorizontalBarChart = (props: BarChartProps) => (
<ParentSize>
{parent => <Chart width={parent.width} height={parent.height} {...props} />}
</ParentSize>
);

View file

@ -46,3 +46,12 @@ export const GridWrapper: React.FC<GridProps> = ({
</Container> </Container>
</Section> </Section>
); );
export const SimpleWrapper: React.FC<GridProps> = ({ children }) => (
<Section fixedWidth={false} padding={'16px'}>
<BitcoinPrice />
<BitcoinFees />
<StatusCheck />
{children}
</Section>
);

View file

@ -32,7 +32,7 @@ const FullWidth = styled.div`
const FixedWidth = styled.div` const FixedWidth = styled.div`
max-width: 1000px; max-width: 1000px;
margin: 0 auto 0 auto; margin: 0 auto 0;
@media (max-width: 1035px) { @media (max-width: 1035px) {
padding: 0 16px; padding: 0 16px;

View file

@ -52,9 +52,43 @@ const StyledSelect = styled(ReactSelect)`
} }
`; `;
const StyledSmallSelect = styled(ReactSelect)`
& .Select__control {
cursor: pointer;
background-color: transparent;
border: none;
font-size: 12px;
& .Select__control--is-focused {
border: 1px solid ${themeColors.blue2};
}
& .Select__single-value {
color: ${textColor};
}
& .Select__dropdown-indicator {
padding: 0 0 0 4px;
}
}
& .Select__menu {
font-size: 14px;
color: black;
& .Select__option {
cursor: pointer;
}
& .Select__option--is-selected {
background-color: ${themeColors.blue2};
}
}
`;
export type ValueProp = { export type ValueProp = {
value: string; value: string | number;
label: string; label: string | number;
}; };
type SelectProps = { type SelectProps = {
@ -94,6 +128,7 @@ type SelectWithValueProps = {
value: ValueProp | undefined; value: ValueProp | undefined;
isMulti?: boolean; isMulti?: boolean;
maxWidth?: string; maxWidth?: string;
isClearable?: boolean;
callback: (value: ValueProp[]) => void; callback: (value: ValueProp[]) => void;
}; };
@ -103,6 +138,7 @@ export const SelectWithValue = ({
maxWidth, maxWidth,
callback, callback,
value, value,
isClearable = true,
}: SelectWithValueProps) => { }: SelectWithValueProps) => {
const handleChange = (value: ValueProp | ValueProp[]) => { const handleChange = (value: ValueProp | ValueProp[]) => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
@ -119,7 +155,36 @@ export const SelectWithValue = ({
options={options} options={options}
onChange={handleChange} onChange={handleChange}
value={value || null} value={value || null}
isClearable={true} isClearable={isClearable}
/>
</StyledWrapper>
);
};
export const SmallSelectWithValue = ({
isMulti,
options,
maxWidth,
callback,
value,
isClearable = true,
}: SelectWithValueProps) => {
const handleChange = (value: ValueProp | ValueProp[]) => {
if (Array.isArray(value)) {
callback(value);
} else {
callback([value]);
}
};
return (
<StyledWrapper maxWidth={maxWidth} fullWidth={true}>
<StyledSmallSelect
isMulti={isMulti}
classNamePrefix={'Select'}
options={options}
onChange={handleChange}
value={value || null}
isClearable={isClearable}
/> />
</StyledWrapper> </StyledWrapper>
); );

View file

@ -2,11 +2,14 @@ import React from 'react';
import { PriceProvider } from './PriceContext'; import { PriceProvider } from './PriceContext';
import { ChatProvider } from './ChatContext'; import { ChatProvider } from './ChatContext';
import { RebalanceProvider } from './RebalanceContext'; import { RebalanceProvider } from './RebalanceContext';
import { DashProvider } from './DashContext';
export const ContextProvider: React.FC = ({ children }) => ( export const ContextProvider: React.FC = ({ children }) => (
<PriceProvider> <DashProvider>
<ChatProvider> <PriceProvider>
<RebalanceProvider>{children}</RebalanceProvider> <ChatProvider>
</ChatProvider> <RebalanceProvider>{children}</RebalanceProvider>
</PriceProvider> </ChatProvider>
</PriceProvider>
</DashProvider>
); );

View file

@ -0,0 +1,54 @@
import React, { createContext, useContext, useReducer } from 'react';
type State = {
modalType: string;
};
type ActionType = {
type: 'openModal';
modalType: string;
};
type Dispatch = (action: ActionType) => void;
export const StateContext = createContext<State | undefined>(undefined);
export const DispatchContext = createContext<Dispatch | undefined>(undefined);
const stateReducer = (state: State, action: ActionType): State => {
switch (action.type) {
case 'openModal':
return { ...state, modalType: action.modalType };
default:
return state;
}
};
const DashProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(stateReducer, {
modalType: '',
});
return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</DispatchContext.Provider>
);
};
const useDashState = () => {
const context = useContext(StateContext);
if (context === undefined) {
throw new Error('useDashState must be used within a DashProvider');
}
return context;
};
const useDashDispatch = () => {
const context = useContext(DispatchContext);
if (context === undefined) {
throw new Error('useDashDispatch must be used within a DashProvider');
}
return context;
};
export { DashProvider, useDashState, useDashDispatch };

View file

@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import { useGetBaseCanConnectQuery } from 'src/graphql/queries/__generated__/getBaseCanConnect.generated'; import { useGetBaseCanConnectQuery } from 'src/graphql/queries/__generated__/getBaseCanConnect.generated';
export const useBaseConnect = () => { export const useBaseConnect = () => {
const [canConnect, setCanConnect] = useState<boolean>(false); const [connected, setCanConnect] = useState<boolean>(false);
const { loading, error, data } = useGetBaseCanConnectQuery({ const { loading, error, data } = useGetBaseCanConnectQuery({
ssr: false, ssr: false,
@ -14,5 +14,5 @@ export const useBaseConnect = () => {
setCanConnect(true); setCanConnect(true);
}, [loading, data, error]); }, [loading, data, error]);
return canConnect; return { connected, loading };
}; };

View file

@ -0,0 +1,40 @@
import { RefObject, useState, useEffect, useCallback } from 'react';
import useEventListener from './UseEventListener';
interface Size {
width: number;
height: number;
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(
elementRef: RefObject<T>
): Size {
const [size, setSize] = useState<Size>({
width: 0,
height: 0,
});
// Prevent too many rendering using useCallback
const updateSize = useCallback(() => {
const node = elementRef?.current;
if (node) {
setSize({
width: node.offsetWidth || 0,
height: node.offsetHeight || 0,
});
}
}, [elementRef]);
// Initial size on mount
useEffect(() => {
updateSize();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEventListener('resize', updateSize);
return size;
}
export default useElementSize;

View file

@ -0,0 +1,39 @@
import { useRef, useEffect, RefObject } from 'react';
function useEventListener<T extends HTMLElement = HTMLDivElement>(
eventName: string,
handler: (event: Event) => void,
element?: RefObject<T>
) {
// Create a ref that stores handler
const savedHandler = useRef<(event: Event) => void>();
useEffect(() => {
// Define the listening target
const targetElement: T | Window = element?.current || window;
if (!(targetElement && targetElement.addEventListener)) {
return;
}
// Update saved handler if necessary
if (savedHandler.current !== handler) {
savedHandler.current = handler;
}
// Create event listener that calls handler function stored in ref
const eventListener = (event: Event) => {
// eslint-disable-next-line no-extra-boolean-cast
if (!!savedHandler?.current) {
savedHandler.current(event);
}
};
targetElement.addEventListener(eventName, eventListener);
return () => {
targetElement.removeEventListener(eventName, eventListener);
};
}, [eventName, element, handler]);
}
export default useEventListener;

View file

@ -43,7 +43,7 @@ export const Header = () => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { lnMarketsAuth } = useConfigState(); const { lnMarketsAuth } = useConfigState();
const connected = useBaseConnect(); const { connected } = useBaseConnect();
const isRoot = pathname === MAIN || pathname === SSO; const isRoot = pathname === MAIN || pathname === SSO;

View file

@ -17,6 +17,7 @@ import {
Heart, Heart,
Shuffle, Shuffle,
Aperture, Aperture,
Grid,
} from 'react-feather'; } from 'react-feather';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useBaseConnect } from 'src/hooks/UseBaseConnect'; import { useBaseConnect } from 'src/hooks/UseBaseConnect';
@ -116,6 +117,7 @@ const BurgerNav = styled.a<NavProps>`
`; `;
const HOME = '/'; const HOME = '/';
const DASHBOARD = '/dashboard';
const PEERS = '/peers'; const PEERS = '/peers';
const CHANNEL = '/channels'; const CHANNEL = '/channels';
const REBALANCE = '/rebalance'; const REBALANCE = '/rebalance';
@ -140,7 +142,7 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
const { pathname } = useRouter(); const { pathname } = useRouter();
const { sidebar } = useConfigState(); const { sidebar } = useConfigState();
const connected = useBaseConnect(); const { connected } = useBaseConnect();
const isRoot = pathname === '/login' || pathname === '/sso'; const isRoot = pathname === '/login' || pathname === '/sso';
@ -173,6 +175,7 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
const renderLinks = () => ( const renderLinks = () => (
<ButtonSection isOpen={sidebar}> <ButtonSection isOpen={sidebar}>
{renderNavButton('Home', HOME, Home, sidebar)} {renderNavButton('Home', HOME, Home, sidebar)}
{renderNavButton('Dashboard', DASHBOARD, Grid, sidebar)}
{renderNavButton('Peers', PEERS, Users, sidebar)} {renderNavButton('Peers', PEERS, Users, sidebar)}
{renderNavButton('Channels', CHANNEL, Cpu, sidebar)} {renderNavButton('Channels', CHANNEL, Cpu, sidebar)}
{renderNavButton('Rebalance', REBALANCE, Repeat, sidebar)} {renderNavButton('Rebalance', REBALANCE, Repeat, sidebar)}
@ -183,13 +186,14 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
{renderNavButton('Tools', TOOLS, Shield, sidebar)} {renderNavButton('Tools', TOOLS, Shield, sidebar)}
{renderNavButton('Swap', SWAP, Shuffle, sidebar)} {renderNavButton('Swap', SWAP, Shuffle, sidebar)}
{renderNavButton('Stats', STATS, BarChart2, sidebar)} {renderNavButton('Stats', STATS, BarChart2, sidebar)}
{connected && renderNavButton('Scores', SCORES, Aperture)} {connected && renderNavButton('Scores', SCORES, Aperture, sidebar)}
</ButtonSection> </ButtonSection>
); );
const renderBurger = () => ( const renderBurger = () => (
<BurgerRow> <BurgerRow>
{renderBurgerNav('Home', HOME, Home)} {renderBurgerNav('Home', HOME, Home)}
{renderBurgerNav('Dashboard', DASHBOARD, Grid)}
{renderBurgerNav('Peers', PEERS, Users)} {renderBurgerNav('Peers', PEERS, Users)}
{renderBurgerNav('Channels', CHANNEL, Cpu)} {renderBurgerNav('Channels', CHANNEL, Cpu)}
{renderBurgerNav('Rebalance', REBALANCE, Repeat)} {renderBurgerNav('Rebalance', REBALANCE, Repeat)}

View file

@ -0,0 +1,11 @@
export const defaultGrid = {
breakpoints: {
lg: 1200,
md: 996,
sm: 768,
xs: 480,
xxs: 0,
},
columns: { lg: 24, md: 16, sm: 12, xs: 4, xxs: 2 },
margin: [4, 4] as [number, number],
};

View file

@ -29,7 +29,7 @@ const S = {
}; };
export const ChannelBosScore: FC<{ score?: BosScore | null }> = ({ score }) => { export const ChannelBosScore: FC<{ score?: BosScore | null }> = ({ score }) => {
const connected = useBaseConnect(); const { connected } = useBaseConnect();
if (!connected) return null; if (!connected) return null;

View file

@ -7,7 +7,6 @@ import { getPercent } from 'src/utils/helpers';
import { ChannelType } from 'src/graphql/types'; import { ChannelType } from 'src/graphql/types';
import { useRebalanceState } from 'src/context/RebalanceContext'; import { useRebalanceState } from 'src/context/RebalanceContext';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Card } from '../../../components/generic/Styled';
import { getErrorContent } from '../../../utils/error'; import { getErrorContent } from '../../../utils/error';
import { LoadingCard } from '../../../components/loading/LoadingCard'; import { LoadingCard } from '../../../components/loading/LoadingCard';
import { ChannelCard } from './ChannelCard'; import { ChannelCard } from './ChannelCard';
@ -167,7 +166,7 @@ export const Channels: React.FC = () => {
}; };
return ( return (
<Card mobileCardPadding={'0'} mobileNoBackground={true}> <>
{getChannels().map((channel, index) => ( {getChannels().map((channel, index) => (
<ChannelCard <ChannelCard
channelInfo={channel as ChannelType} channelInfo={channel as ChannelType}
@ -182,6 +181,6 @@ export const Channels: React.FC = () => {
biggestRateFee={Math.max(Math.min(biggestRateFee, 10000), 2000)} biggestRateFee={Math.max(Math.min(biggestRateFee, 10000), 2000)}
/> />
))} ))}
</Card> </>
); );
}; };

View file

@ -0,0 +1,126 @@
import { Layouts, Responsive as ResponsiveGridLayout } from 'react-grid-layout';
import styled, { css } from 'styled-components';
import { defaultGrid } from 'src/utils/gridConstants';
import { useLocalStorage } from 'src/hooks/UseLocalStorage';
import { LoadingCard } from 'src/components/loading/LoadingCard';
import { useRef } from 'react';
import useElementSize from 'src/hooks/UseElementSize';
import { getWidgets } from './widgets/helpers';
import { Card, SubTitle } from 'src/components/generic/Styled';
import { textColor } from 'src/styles/Themes';
import { Link } from 'src/components/link/Link';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import { useDashDispatch, useDashState } from 'src/context/DashContext';
import Modal from 'src/components/modal/ReactModal';
import { DashboardModal } from './modal';
const S = {
styles: styled.div`
.react-resizable-handle::after {
border-bottom: 2px solid ${textColor};
border-right: 2px solid ${textColor};
}
`,
card: styled(Card)<{ widgetColor?: string }>`
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
padding: 8px;
${({ widgetColor }) =>
css`
border-top: 2px solid #${widgetColor};
`}
`,
gridWrapper: styled.div`
width: 100%;
`,
fill: styled.div`
height: 80vh;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`,
};
export type StoredWidget = {
id: number;
};
const Dashboard = () => {
const wrapperRef = useRef(null);
const { width } = useElementSize(wrapperRef);
const { modalType } = useDashState();
const dispatch = useDashDispatch();
const [layouts, setLayouts] = useLocalStorage<Layouts>('layouts', {});
const [availableWidgets] = useLocalStorage<StoredWidget[]>(
'dashboardWidgets',
[]
);
const props = {
isBounded: true,
};
const handleChange = (_: any, layouts: any) => {
setLayouts(layouts);
};
const widgets = getWidgets(availableWidgets, width, [{ id: 28 }]);
if (!widgets.length) {
return (
<S.fill>
<SubTitle>No Widgets Enabled!</SubTitle>
<Link href={'settings/dashboard'}>
<ColorButton arrow={true}>Settings</ColorButton>
</Link>
</S.fill>
);
}
const renderContent = () => {
if (width === 0) {
return <LoadingCard noCard={true} loadingHeight={'90vh'} />;
}
return (
<>
<S.styles>
<ResponsiveGridLayout
{...props}
className="layout"
layouts={layouts}
rowHeight={28}
width={width}
margin={defaultGrid.margin}
breakpoints={defaultGrid.breakpoints}
cols={defaultGrid.columns}
onLayoutChange={handleChange}
>
{widgets.map(w => (
<S.card widgetColor={w.color} key={w.id} data-grid={w.default}>
<w.component />
</S.card>
))}
</ResponsiveGridLayout>
</S.styles>
<Modal
isOpen={!!modalType}
closeCallback={() => dispatch({ type: 'openModal', modalType: '' })}
>
<DashboardModal />
</Modal>
</>
);
};
return <S.gridWrapper ref={wrapperRef}>{renderContent()}</S.gridWrapper>;
};
export default Dashboard;

View file

@ -0,0 +1,48 @@
import { useDashDispatch, useDashState } from 'src/context/DashContext';
import { CreateInvoiceCard } from 'src/views/home/account/createInvoice/CreateInvoice';
import { PayCard } from 'src/views/home/account/pay/Payment';
import { ReceiveOnChainCard } from 'src/views/home/account/receiveOnChain/ReceiveOnChain';
import { SendOnChainCard } from 'src/views/home/account/sendOnChain/SendOnChain';
import { SupportBar } from 'src/views/home/quickActions/donate/DonateContent';
import { OpenChannel } from 'src/views/home/quickActions/openChannel';
import { SignMessage } from 'src/views/tools/messages/SignMessage';
export const DashboardModal = () => {
const { modalType } = useDashState();
const dispatch = useDashDispatch();
const renderModal = () => {
switch (modalType) {
case 'payInvoice':
return (
<PayCard
setOpen={() => dispatch({ type: 'openModal', modalType: '' })}
/>
);
case 'createInvoice':
return <CreateInvoiceCard color={'#FFD300'} />;
case 'sendChain':
return (
<SendOnChainCard
setOpen={() => dispatch({ type: 'openModal', modalType: '' })}
/>
);
case 'receiveChain':
return <ReceiveOnChainCard />;
case 'openChannel':
return (
<OpenChannel
setOpenCard={() => dispatch({ type: 'openModal', modalType: '' })}
/>
);
case 'donate':
return <SupportBar />;
case 'signMessage':
return <SignMessage />;
default:
return null;
}
};
return renderModal();
};

View file

@ -0,0 +1,40 @@
import { Table } from 'src/components/table';
import { useBitcoinFees } from 'src/hooks/UseBitcoinFees';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
width: 100%;
overflow: auto;
`,
};
export const MempoolWidget = () => {
const { fast, halfHour, hour, minimum, dontShow } = useBitcoinFees();
if (dontShow) {
return null;
}
const columns = [
{ Header: 'Fastest', accessor: 'fast' },
{ Header: 'Half Hour', accessor: 'halfHour' },
{ Header: 'Hour', accessor: 'hour' },
{ Header: 'Minimum', accessor: 'minimum' },
];
const data = [
{
fast: `${fast} sat/vB`,
halfHour: `${halfHour} sat/vB`,
hour: `${hour} sat/vB`,
minimum: `${minimum} sat/vB`,
},
];
return (
<S.wrapper>
<Table alignCenter={true} tableColumns={columns} tableData={data} />
</S.wrapper>
);
};

View file

@ -0,0 +1,185 @@
import {
differenceInDays,
differenceInHours,
subDays,
subHours,
} from 'date-fns';
import groupBy from 'lodash.groupby';
import { GetForwardsQuery } from 'src/graphql/queries/__generated__/getForwards.generated';
import { Transaction } from 'src/graphql/types';
import { defaultGrid } from 'src/utils/gridConstants';
import { StoredWidget } from '..';
import { widgetList, WidgetProps } from './widgetList';
const getColumns = (width: number): number => {
const { lg, md, sm, xs } = defaultGrid.breakpoints;
if (width >= lg) {
return defaultGrid.columns.lg;
}
if (width >= md) {
return defaultGrid.columns.md;
}
if (width >= sm) {
return defaultGrid.columns.sm;
}
if (width >= xs) {
return defaultGrid.columns.xs;
}
return defaultGrid.columns.xxs;
};
export type EnrichedWidgetProps = {
nodeId?: string;
nodeAlias?: string;
color?: string;
} & WidgetProps;
export const getWidgets = (
widgets: StoredWidget[],
width: number,
extra: StoredWidget[]
): EnrichedWidgetProps[] => {
if (!widgets?.length) return [];
const columns = getColumns(width);
const normalized = [...widgets, ...extra].reduce((p, c, index) => {
const current = widgetList.find(w => w.id === c.id);
if (!current) {
return p;
}
return [
...p,
{
...current,
default: {
...current.default,
x: (current.default.w * index) % columns,
},
},
];
}, [] as EnrichedWidgetProps[]);
return normalized;
};
type ArrayType = GetForwardsQuery['getForwards'] | Transaction[];
export const getByTime = (array: ArrayType, time: number): any[] => {
if (!array?.length) return [];
const transactions: any[] = [];
const isDay = time <= 1;
const today = new Date();
array.forEach((transaction: ArrayType[0]) => {
if (!transaction) return;
if (transaction.__typename === 'InvoiceType') {
if (!transaction.is_confirmed || !transaction.confirmed_at) return;
const difference = isDay
? 24 - differenceInHours(today, new Date(transaction.confirmed_at))
: time - differenceInDays(today, new Date(transaction.confirmed_at));
transactions.push({
difference,
date: new Date(transaction.confirmed_at).toISOString(),
tokens: Number(transaction.tokens),
});
} else if (transaction.__typename === 'PaymentType') {
if (!transaction.is_confirmed) return;
const difference = isDay
? 24 - differenceInHours(today, new Date(transaction.created_at))
: time - differenceInDays(today, new Date(transaction.created_at));
transactions.push({
difference,
date: new Date(transaction.created_at).toISOString(),
tokens: Number(transaction.tokens),
});
} else if (transaction.__typename === 'Forward') {
const difference = isDay
? 24 - differenceInHours(today, new Date(transaction.created_at))
: time - differenceInDays(today, new Date(transaction.created_at));
transactions.push({
difference,
date: transaction.created_at,
tokens: Number(transaction.tokens),
fee: Number(transaction.fee),
});
}
});
if (!transactions?.length) return [];
const grouped = groupBy(transactions, 'difference');
const final: any[] = [];
const differences = Array.from(
{ length: isDay ? 25 : time + 1 },
(_, i) => i
);
differences.forEach(key => {
const group = grouped[key];
if (!group) {
final.push({
tokens: 0,
amount: 0,
fee: 0,
date: isDay
? subHours(today, 24 - Number(key))
.toISOString()
.slice(0, 10)
: subDays(today, time - Number(key))
.toISOString()
.slice(0, 10),
});
return;
}
const reduced = group.reduce(
(total, transaction) => {
return {
tokens: total.tokens + transaction.tokens,
fee: total.fee + transaction.fee || 0,
amount: total.amount + 1,
date: total.date
? total.date
: isDay
? subHours(today, 24 - Number(key))
.toISOString()
.slice(0, 10)
: subDays(today, time - Number(key))
.toISOString()
.slice(0, 10),
};
},
{
tokens: 0,
fee: 0,
amount: 0,
date: '',
}
);
final.push(reduced);
});
// final.push({ tokens: 1, amount: 0, date: new Date().toString() });
// final.push({
// tokens: 1,
// amount: 0,
// date: isDay
// ? subHours(new Date(), 25).toString()
// : subDays(new Date(), time + 1).toString(),
// });
return final;
};

View file

@ -0,0 +1,84 @@
import { Price } from 'src/components/price/Price';
import { useNodeInfo } from 'src/hooks/UseNodeInfo';
import { unSelectedNavButton } from 'src/styles/Themes';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
overflow: auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
`,
total: styled.h2`
margin: 0;
`,
smallTotal: styled.h3`
margin: 0;
`,
pending: styled.div`
color: ${unSelectedNavButton};
font-size: 14px;
`,
};
export const TotalBalance = () => {
const { chainBalance, chainPending, channelBalance, channelPending } =
useNodeInfo();
const total = chainBalance + channelBalance;
const pending = chainPending + channelPending;
return (
<S.wrapper>
<S.pending>Total Balance</S.pending>
<S.total>
<Price amount={total} />
</S.total>
{pending > 0 ? (
<S.pending>
<Price amount={pending} />
</S.pending>
) : null}
</S.wrapper>
);
};
export const ChannelBalance = () => {
const { channelBalance, channelPending } = useNodeInfo();
return (
<S.wrapper>
<S.pending>Channel Balance</S.pending>
<S.smallTotal>
<Price amount={channelBalance} />
</S.smallTotal>
{channelPending > 0 ? (
<S.pending>
<Price amount={channelPending} />
</S.pending>
) : null}
</S.wrapper>
);
};
export const ChainBalance = () => {
const { chainBalance, chainPending } = useNodeInfo();
return (
<S.wrapper>
<S.pending>Chain Balance</S.pending>
<S.smallTotal>
<Price amount={chainBalance} />
</S.smallTotal>
{chainPending > 0 ? (
<S.pending>
<Price amount={chainPending} />
</S.pending>
) : null}
</S.wrapper>
);
};

View file

@ -0,0 +1,18 @@
import { Channels } from 'src/views/channels/channels/Channels';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
height: 100%;
width: 100%;
overflow: auto;
`,
};
export const ChannelListWidget = () => {
return (
<S.wrapper>
<Channels />
</S.wrapper>
);
};

View file

@ -0,0 +1,72 @@
import { getDateDif } from 'src/components/generic/helpers';
import { Price } from 'src/components/price/Price';
import { Table } from 'src/components/table';
import { useGetForwardsQuery } from 'src/graphql/queries/__generated__/getForwards.generated';
import { ChannelAlias } from 'src/views/home/reports/forwardReport/ChannelAlias';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
width: 100%;
height: 100%;
`,
table: styled.div`
width: 100%;
height: calc(100% - 40px);
overflow: auto;
`,
title: styled.h4`
font-weight: 900;
width: 100%;
text-align: center;
margin: 8px 0;
`,
nowrap: styled.div`
white-space: nowrap;
`,
};
export const ForwardListWidget = () => {
const { data } = useGetForwardsQuery({ variables: { days: 7 } });
const forwards = data?.getForwards || [];
const columns = [
{ Header: 'Date', accessor: 'date' },
{ Header: 'Amount', accessor: 'amount' },
{ Header: 'Fee', accessor: 'fee' },
{ Header: 'Incoming', accessor: 'incoming' },
{ Header: 'Outgoing', accessor: 'outgoing' },
];
const tableData = forwards.reduce((p, f) => {
if (!f) return p;
return [
...p,
{
date: <S.nowrap>{getDateDif(f.created_at)}</S.nowrap>,
amount: (
<S.nowrap>
<Price amount={f.tokens} />
</S.nowrap>
),
fee: (
<S.nowrap>
<Price amount={f.fee} />
</S.nowrap>
),
incoming: <ChannelAlias id={f.incoming_channel} />,
outgoing: <ChannelAlias id={f.outgoing_channel} />,
},
];
}, [] as any);
return (
<S.wrapper>
<S.title>Forwards</S.title>
<S.table>
<Table tableColumns={columns} tableData={tableData} />
</S.table>
</S.wrapper>
);
};

View file

@ -0,0 +1,123 @@
import { useState } from 'react';
import { BarChart } from 'src/components/chart/BarChart';
import { LoadingCard } from 'src/components/loading/LoadingCard';
import { SmallSelectWithValue } from 'src/components/select';
import { useGetForwardsQuery } from 'src/graphql/queries/__generated__/getForwards.generated';
import { chartColors } from 'src/styles/Themes';
import styled from 'styled-components';
import { getByTime } from '../helpers';
const S = {
row: styled.div`
display: grid;
grid-template-columns: 1fr 60px 90px;
`,
wrapper: styled.div`
width: 100%;
height: 100%;
`,
contentWrapper: styled.div`
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`,
content: styled.div`
width: 100%;
padding: 0 16px;
height: calc(100% - 40px);
overflow: auto;
`,
title: styled.h4`
font-weight: 900;
margin: 8px 0;
`,
nowrap: styled.div`
white-space: nowrap;
`,
};
const options = [
{ label: '1D', value: 1 },
{ label: '7D', value: 7 },
{ label: '1M', value: 30 },
{ label: '2M', value: 60 },
{ label: '6M', value: 180 },
{ label: '1Y', value: 360 },
];
const typeOptions = [
{ label: 'Amount', value: 'amount' },
{ label: 'Tokens', value: 'tokens' },
{ label: 'Fees', value: 'fee' },
];
export const ForwardsGraph = () => {
const [days, setDays] = useState(options[1]);
const [type, setType] = useState(typeOptions[0]);
const { data, loading } = useGetForwardsQuery({
ssr: false,
variables: { days: days.value },
errorPolicy: 'ignore',
});
const Header = () => (
<S.row>
<S.title>Forwards</S.title>
<SmallSelectWithValue
callback={e => setDays((e[0] || options[1]) as any)}
options={options}
value={days}
isClearable={false}
maxWidth={'60px'}
/>
<SmallSelectWithValue
callback={e => setType((e[0] || typeOptions[1]) as any)}
options={typeOptions}
value={type}
isClearable={false}
maxWidth={'90px'}
/>
</S.row>
);
if (loading) {
return (
<S.wrapper>
<Header />
<S.contentWrapper>
<LoadingCard noCard={true} />
</S.contentWrapper>
</S.wrapper>
);
}
if (!data?.getForwards.length) {
return (
<S.wrapper>
<Header />
<S.contentWrapper>No forwards for this period.</S.contentWrapper>
</S.wrapper>
);
}
const forwards = getByTime(data.getForwards, days.value);
return (
<S.wrapper>
<Header />
<S.content>
<BarChart
priceLabel={type.value !== 'amount'}
data={forwards.map(f => ({
Forward: f[type.value] || 0,
date: f.date,
}))}
colorRange={[chartColors.purple]}
/>
</S.content>
</S.wrapper>
);
};

View file

@ -0,0 +1,50 @@
import { useGetLiquidReportQuery } from 'src/graphql/queries/__generated__/getChannelReport.generated';
import { useNodeInfo } from 'src/hooks/UseNodeInfo';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
overflow: auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
`,
title: styled.h2`
margin: 0;
`,
};
export const AliasWidget = () => {
const { alias } = useNodeInfo();
return (
<S.wrapper>
<S.title>{alias}</S.title>
</S.wrapper>
);
};
export const BalanceWidget = () => {
const { data } = useGetLiquidReportQuery({ errorPolicy: 'ignore' });
if (!data?.getChannelReport) {
return (
<S.wrapper>
<S.title>-</S.title>
</S.wrapper>
);
}
const { local, remote } = data.getChannelReport;
const balance = Math.round(((local || 0) / (remote || 1)) * 100);
return (
<S.wrapper>
<S.title>{`${balance}%`}</S.title>
</S.wrapper>
);
};

View file

@ -0,0 +1,140 @@
import { useMemo } from 'react';
import { useState } from 'react';
import { BarChart } from 'src/components/chart/BarChart';
import { LoadingCard } from 'src/components/loading/LoadingCard';
import { SmallSelectWithValue } from 'src/components/select';
import {
GetResumeQuery,
useGetResumeQuery,
} from 'src/graphql/queries/__generated__/getResume.generated';
import { InvoiceType } from 'src/graphql/types';
import { chartColors } from 'src/styles/Themes';
import styled from 'styled-components';
import { getByTime } from '../helpers';
const S = {
row: styled.div`
display: grid;
grid-template-columns: 1fr 60px 90px;
`,
wrapper: styled.div`
width: 100%;
height: 100%;
`,
contentWrapper: styled.div`
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`,
content: styled.div`
width: 100%;
padding: 0 16px;
height: calc(100% - 40px);
overflow: auto;
`,
title: styled.h4`
font-weight: 900;
margin: 8px 0;
`,
nowrap: styled.div`
white-space: nowrap;
`,
};
const options = [
{ label: '1D', value: 1 },
{ label: '7D', value: 7 },
{ label: '1M', value: 30 },
{ label: '2M', value: 60 },
];
const typeOptions = [
{ label: 'Amount', value: 'amount' },
{ label: 'Tokens', value: 'tokens' },
];
export const InvoicesGraph = () => {
const [days, setDays] = useState(options[1]);
const [type, setType] = useState(typeOptions[0]);
const { data, loading } = useGetResumeQuery({
variables: { limit: days.value },
errorPolicy: 'ignore',
});
const resume: GetResumeQuery['getResume']['resume'] =
data?.getResume.resume || [];
const invoicesByDate = useMemo(() => {
const invoices = resume.reduce((p, c) => {
if (!c) return p;
if (c.__typename === 'InvoiceType') {
if (!c.is_confirmed) return p;
return [...p, c];
}
return p;
}, [] as InvoiceType[]);
return getByTime(invoices, days.value);
}, [resume]);
const Header = () => (
<S.row>
<S.title>Invoices</S.title>
<SmallSelectWithValue
callback={e => setDays((e[0] || options[1]) as any)}
options={options}
value={days}
isClearable={false}
maxWidth={'60px'}
/>
<SmallSelectWithValue
callback={e => setType((e[0] || typeOptions[1]) as any)}
options={typeOptions}
value={type}
isClearable={false}
maxWidth={'90px'}
/>
</S.row>
);
if (loading) {
return (
<S.wrapper>
<Header />
<S.contentWrapper>
<LoadingCard noCard={true} />
</S.contentWrapper>
</S.wrapper>
);
}
if (!resume.length) {
return (
<S.wrapper>
<Header />
<S.contentWrapper>No invoices for this period.</S.contentWrapper>
</S.wrapper>
);
}
return (
<S.wrapper>
<Header />
<S.content>
<BarChart
priceLabel={type.value !== 'amount'}
data={invoicesByDate.map(f => {
return {
Invoices: f?.[type.value] || 0,
date: f.date,
};
})}
colorRange={[chartColors.orange2]}
/>
</S.content>
</S.wrapper>
);
};

View file

@ -0,0 +1,71 @@
import { HorizontalBarChart } from 'src/components/chart/HorizontalBarChart';
import { LoadingCard } from 'src/components/loading/LoadingCard';
import { useGetLiquidReportQuery } from 'src/graphql/queries/__generated__/getChannelReport.generated';
import { chartColors } from 'src/styles/Themes';
import styled from 'styled-components';
const S = {
row: styled.div`
display: grid;
grid-template-columns: 1fr 60px 90px;
`,
wrapper: styled.div`
width: 100%;
height: 100%;
`,
contentWrapper: styled.div`
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`,
title: styled.h4`
font-weight: 900;
margin: 8px 0;
`,
};
export const LiquidityGraph = () => {
const { data, loading } = useGetLiquidReportQuery({ errorPolicy: 'ignore' });
if (loading) {
return (
<S.wrapper>
<S.title>Liquidity</S.title>
<S.contentWrapper>
<LoadingCard noCard={true} />
</S.contentWrapper>
</S.wrapper>
);
}
if (!data?.getChannelReport) {
return (
<S.wrapper>
<S.title>Liquidity</S.title>
<S.contentWrapper>No invoices for this period.</S.contentWrapper>
</S.wrapper>
);
}
const { local, remote, maxIn, maxOut, commit } = data.getChannelReport;
const liquidity = [
{ label: 'Total Commit', Value: commit },
{ label: 'Max Outgoing', Value: maxOut },
{ label: 'Max Incoming', Value: maxIn },
{ label: 'Local Balance', Value: local },
{ label: 'Remote Balance', Value: remote },
];
return (
<S.wrapper>
<HorizontalBarChart
priceLabel={true}
data={liquidity}
colorRange={[chartColors.green]}
/>
</S.wrapper>
);
};

View file

@ -0,0 +1,69 @@
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import { useDashDispatch } from 'src/context/DashContext';
export const PayInvoice = () => {
const dispatch = useDashDispatch();
return (
<ColorButton
fullWidth={true}
onClick={() => dispatch({ type: 'openModal', modalType: 'payInvoice' })}
>
Pay Invoice
</ColorButton>
);
};
export const CreateInvoice = () => {
const dispatch = useDashDispatch();
return (
<ColorButton
fullWidth={true}
onClick={() =>
dispatch({ type: 'openModal', modalType: 'createInvoice' })
}
>
Create Invoice
</ColorButton>
);
};
export const SendOnChain = () => {
const dispatch = useDashDispatch();
return (
<ColorButton
fullWidth={true}
onClick={() => dispatch({ type: 'openModal', modalType: 'sendChain' })}
>
Send Bitcoin
</ColorButton>
);
};
export const ReceiveOnChain = () => {
const dispatch = useDashDispatch();
return (
<ColorButton
fullWidth={true}
onClick={() => dispatch({ type: 'openModal', modalType: 'receiveChain' })}
>
Receive Bitcoin
</ColorButton>
);
};
export const OpenChannel = () => {
const dispatch = useDashDispatch();
return (
<ColorButton
fullWidth={true}
onClick={() => dispatch({ type: 'openModal', modalType: 'openChannel' })}
>
Open Channel
</ColorButton>
);
};

View file

@ -0,0 +1,136 @@
import { useMemo } from 'react';
import { useState } from 'react';
import { BarChart } from 'src/components/chart/BarChart';
import { LoadingCard } from 'src/components/loading/LoadingCard';
import { SmallSelectWithValue } from 'src/components/select';
import { useGetResumeQuery } from 'src/graphql/queries/__generated__/getResume.generated';
import { PaymentType } from 'src/graphql/types';
import { chartColors } from 'src/styles/Themes';
import styled from 'styled-components';
import { getByTime } from '../helpers';
const S = {
row: styled.div`
display: grid;
grid-template-columns: 1fr 60px 90px;
`,
wrapper: styled.div`
width: 100%;
height: 100%;
`,
contentWrapper: styled.div`
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`,
content: styled.div`
width: 100%;
padding: 0 16px;
height: calc(100% - 40px);
overflow: auto;
`,
title: styled.h4`
font-weight: 900;
margin: 8px 0;
`,
nowrap: styled.div`
white-space: nowrap;
`,
};
const options = [
{ label: '1D', value: 1 },
{ label: '7D', value: 7 },
{ label: '1M', value: 30 },
{ label: '2M', value: 60 },
];
const typeOptions = [
{ label: 'Amount', value: 'amount' },
{ label: 'Tokens', value: 'tokens' },
];
export const PaymentsGraph = () => {
const [days, setDays] = useState(options[1]);
const [type, setType] = useState(typeOptions[0]);
const { data, loading } = useGetResumeQuery({
variables: { limit: days.value },
errorPolicy: 'ignore',
});
const resume = data?.getResume.resume || [];
const paymentsByDate = useMemo(() => {
const payments = resume.reduce((p, c) => {
if (!c) return p;
if (c.__typename === 'PaymentType') {
if (!c.is_confirmed) return p;
return [...p, c];
}
return p;
}, [] as PaymentType[]);
return getByTime(payments, days.value);
}, [resume]);
const Header = () => (
<S.row>
<S.title>Payments</S.title>
<SmallSelectWithValue
callback={e => setDays((e[0] || options[1]) as any)}
options={options}
value={days}
isClearable={false}
maxWidth={'60px'}
/>
<SmallSelectWithValue
callback={e => setType((e[0] || typeOptions[1]) as any)}
options={typeOptions}
value={type}
isClearable={false}
maxWidth={'90px'}
/>
</S.row>
);
if (loading) {
return (
<S.wrapper>
<Header />
<S.contentWrapper>
<LoadingCard noCard={true} />
</S.contentWrapper>
</S.wrapper>
);
}
if (!resume.length) {
return (
<S.wrapper>
<Header />
<S.contentWrapper>No payments for this period.</S.contentWrapper>
</S.wrapper>
);
}
return (
<S.wrapper>
<Header />
<S.content>
<BarChart
priceLabel={type.value !== 'amount'}
data={paymentsByDate.map(f => {
return {
Payments: f?.[type.value] || 0,
date: f.date,
};
})}
colorRange={[chartColors.darkyellow]}
/>
</S.content>
</S.wrapper>
);
};

View file

@ -0,0 +1,91 @@
import { ArrowDown, ArrowUp } from 'react-feather';
import { getDateDif, shorten } from 'src/components/generic/helpers';
import { Price } from 'src/components/price/Price';
import { Table } from 'src/components/table';
import { useGetResumeQuery } from 'src/graphql/queries/__generated__/getResume.generated';
import { chartColors } from 'src/styles/Themes';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
width: 100%;
height: 100%;
`,
table: styled.div`
width: 100%;
height: calc(100% - 40px);
overflow: auto;
`,
title: styled.h4`
font-weight: 900;
width: 100%;
text-align: center;
margin: 8px 0;
`,
nowrap: styled.div`
white-space: nowrap;
`,
};
export const TransactionsWidget = () => {
const { data } = useGetResumeQuery();
const transactions = data?.getResume.resume || [];
const columns = [
{ Header: 'Date', accessor: 'date' },
{ Header: 'Type', accessor: 'type' },
{ Header: 'Amount', accessor: 'value' },
{ Header: 'Info', accessor: 'info' },
];
const normalized = transactions.reduce((p, c) => {
if (!c) return p;
if (c.__typename === 'InvoiceType') {
if (!c.is_confirmed) return p;
return [
...p,
{
type: <ArrowDown size={14} color={chartColors.green} />,
value: (
<S.nowrap>
<Price amount={c.received} />
</S.nowrap>
),
date: <S.nowrap>{getDateDif(c.confirmed_at)}</S.nowrap>,
info: <S.nowrap>{c.description || c.description_hash}</S.nowrap>,
},
];
}
if (c.__typename === 'PaymentType') {
if (!c.is_confirmed) return p;
return [
...p,
{
type: <ArrowUp size={14} color={chartColors.red} />,
value: (
<S.nowrap>
<Price amount={c.tokens} />
</S.nowrap>
),
date: <S.nowrap>{getDateDif(c.created_at)}</S.nowrap>,
info: (
<S.nowrap>
{c.destination_node
? `Payment to ${c.destination_node.node.alias}`
: `Payment to ${shorten(c.destination)}`}
</S.nowrap>
),
},
];
}
}, [] as any);
return (
<S.wrapper>
<S.title>Transactions</S.title>
<S.table>
<Table tableColumns={columns} tableData={normalized} />
</S.table>
</S.wrapper>
);
};

View file

@ -0,0 +1,147 @@
import { useMemo } from 'react';
import { useState } from 'react';
import { BarChart } from 'src/components/chart/BarChart';
import { LoadingCard } from 'src/components/loading/LoadingCard';
import { SmallSelectWithValue } from 'src/components/select';
import { useGetResumeQuery } from 'src/graphql/queries/__generated__/getResume.generated';
import { InvoiceType, PaymentType } from 'src/graphql/types';
import { chartColors } from 'src/styles/Themes';
import styled from 'styled-components';
import { getByTime } from '../helpers';
const S = {
row: styled.div`
display: grid;
grid-template-columns: 1fr 60px 90px;
`,
wrapper: styled.div`
width: 100%;
height: 100%;
`,
contentWrapper: styled.div`
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`,
content: styled.div`
width: 100%;
padding: 0 16px;
height: calc(100% - 40px);
overflow: auto;
`,
title: styled.h4`
font-weight: 900;
margin: 8px 0;
`,
nowrap: styled.div`
white-space: nowrap;
`,
};
const options = [
{ label: '1D', value: 1 },
{ label: '7D', value: 7 },
{ label: '1M', value: 30 },
{ label: '2M', value: 60 },
];
const typeOptions = [
{ label: 'Amount', value: 'amount' },
{ label: 'Tokens', value: 'tokens' },
];
export const TransactionsGraph = () => {
const [days, setDays] = useState(options[1]);
const [type, setType] = useState(typeOptions[0]);
const { data, loading } = useGetResumeQuery({
variables: { limit: days.value },
errorPolicy: 'ignore',
});
const resume = data?.getResume.resume || [];
const { invoicesByDate, paymentsByDate } = useMemo(() => {
const invoices: InvoiceType[] = [];
const payments: PaymentType[] = [];
resume.forEach(t => {
if (!t) return;
if (t.__typename === 'InvoiceType') {
if (!t.is_confirmed) return;
invoices.push(t);
}
if (t.__typename === 'PaymentType') {
if (!t.is_confirmed) return;
payments.push(t);
}
});
const invoicesByDate = getByTime(invoices, days.value);
const paymentsByDate = getByTime(payments, days.value);
return { invoicesByDate, paymentsByDate };
}, [resume]);
const Header = () => (
<S.row>
<S.title>Transactions</S.title>
<SmallSelectWithValue
callback={e => setDays((e[0] || options[1]) as any)}
options={options}
value={days}
isClearable={false}
maxWidth={'60px'}
/>
<SmallSelectWithValue
callback={e => setType((e[0] || typeOptions[1]) as any)}
options={typeOptions}
value={type}
isClearable={false}
maxWidth={'90px'}
/>
</S.row>
);
if (loading) {
return (
<S.wrapper>
<Header />
<S.contentWrapper>
<LoadingCard noCard={true} />
</S.contentWrapper>
</S.wrapper>
);
}
if (!resume.length) {
return (
<S.wrapper>
<Header />
<S.contentWrapper>No transactions for this period.</S.contentWrapper>
</S.wrapper>
);
}
return (
<S.wrapper>
<Header />
<S.content>
<BarChart
priceLabel={type.value !== 'amount'}
data={invoicesByDate.map(f => {
const payment = paymentsByDate.find(p => p.date === f.date);
return {
Invoices: f?.[type.value] || 0,
Payments: payment?.[type.value] || 0,
date: f.date,
};
})}
colorRange={[chartColors.orange2, chartColors.darkyellow]}
/>
</S.content>
</S.wrapper>
);
};

View file

@ -0,0 +1,60 @@
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import { Link } from 'src/components/link/Link';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
width: 100%;
overflow: hidden;
`,
};
export const DashSettingsLink = () => {
return (
<S.wrapper>
<Link href={'/settings/dashboard'}>
<ColorButton fullWidth={true}>Dash Settings</ColorButton>
</Link>
</S.wrapper>
);
};
export const ForwardsViewLink = () => {
return (
<S.wrapper>
<Link href={'/forwards'}>
<ColorButton fullWidth={true}>Forwards</ColorButton>
</Link>
</S.wrapper>
);
};
export const TransactionsViewLink = () => {
return (
<S.wrapper>
<Link href={'/transactions'}>
<ColorButton fullWidth={true}>Transactions</ColorButton>
</Link>
</S.wrapper>
);
};
export const ChannelViewLink = () => {
return (
<S.wrapper>
<Link href={'/channels'}>
<ColorButton fullWidth={true}>Channels</ColorButton>
</Link>
</S.wrapper>
);
};
export const RebalanceViewLink = () => {
return (
<S.wrapper>
<Link href={'/rebalance'}>
<ColorButton fullWidth={true}>Rebalance</ColorButton>
</Link>
</S.wrapper>
);
};

View file

@ -0,0 +1,76 @@
import { Star, Sun, Moon } from 'react-feather';
import { SingleButton } from 'src/components/buttons/multiButton/MultiButton';
import { useConfigDispatch, useConfigState } from 'src/context/ConfigContext';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
overflow: auto;
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
`,
};
export const ThemeSetting = () => {
const { theme } = useConfigState();
const dispatch = useConfigDispatch();
const handleDispatch = (theme: string) =>
dispatch({ type: 'themeChange', theme });
return (
<S.wrapper>
<SingleButton
selected={theme === 'light'}
onClick={() => handleDispatch('light')}
>
<Sun size={16} />
</SingleButton>
<SingleButton
selected={theme === 'dark'}
onClick={() => handleDispatch('dark')}
>
<Moon size={16} />
</SingleButton>
<SingleButton
selected={theme === 'night'}
onClick={() => handleDispatch('night')}
>
<Star size={16} />
</SingleButton>
</S.wrapper>
);
};
export const CurrencySetting = () => {
const { currency } = useConfigState();
const dispatch = useConfigDispatch();
const handleDispatch = (currency: string) =>
dispatch({ type: 'change', currency });
return (
<S.wrapper>
<SingleButton
selected={currency === 'sat'}
onClick={() => handleDispatch('sat')}
>
Sat
</SingleButton>
<SingleButton
selected={currency === 'btc'}
onClick={() => handleDispatch('btc')}
>
Btc
</SingleButton>
<SingleButton
selected={currency === 'fiat'}
onClick={() => handleDispatch('fiat')}
>
Fiat
</SingleButton>
</S.wrapper>
);
};

View file

@ -0,0 +1,159 @@
import numeral from 'numeral';
import { useState } from 'react';
import { Input } from 'src/components/input';
import { SelectWithValue } from 'src/components/select';
import { usePriceState } from 'src/context/PriceContext';
import styled from 'styled-components';
const S = {
row: styled.div`
margin: 8px 0;
display: grid;
grid-gap: 8px;
grid-template-columns: 2fr 4fr 100px;
align-items: center;
`,
wrapper: styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 4px;
`,
contentWrapper: styled.div`
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`,
content: styled.div`
width: 100%;
padding: 0 16px;
height: calc(100% - 40px);
overflow: auto;
`,
title: styled.h4`
font-weight: 900;
margin: 8px 0;
`,
nowrap: styled.div`
white-space: nowrap;
`,
};
export const ConvertWidget = () => {
const { prices, dontShow } = usePriceState();
const [firstAmount, setFirstAmount] = useState(1);
const [secondAmount, setSecondAmount] = useState(100000000);
const available = Object.keys(prices || {});
const options = [
{ value: 'btc', label: 'BTC' },
{ value: 'sat', label: 'SAT' },
...available.map(c => ({ value: c, label: c })),
];
const [first, setFirst] = useState(options[0]);
const [second, setSecond] = useState(options[1]);
if (dontShow) {
return (
<S.contentWrapper>
Fetching fiat prices is disabled. Enable it in the settings.
</S.contentWrapper>
);
}
const getPrice = (currency: string) => {
switch (currency) {
case 'btc':
return 1;
case 'sat':
return 100000000;
default:
return prices?.[currency]?.last || 0;
}
};
const convert = (from: string, to: string, value: number) => {
const fromPrice = getPrice(from);
const toPrice = getPrice(to);
if (from === to) {
return value;
}
if (from === 'btc') {
return fromPrice * toPrice * value;
}
return (toPrice / fromPrice) * value;
};
const getValue = (value: number, current: string) => {
switch (current) {
case 'btc':
return numeral(value).format('0,0.[00000000]');
case 'sat':
return numeral(value).format('0,0');
default:
return numeral(value).format('0,0.[00]');
}
};
return (
<S.wrapper>
<S.row>
<div>{getValue(firstAmount, first.value)}</div>
<Input
fullWidth={true}
placeholder={'Amount'}
value={firstAmount}
type={'number'}
onChange={e => {
const value = Number(e.target.value);
setFirstAmount(value);
setSecondAmount(convert(first.value, second.value, value));
}}
/>
<SelectWithValue
callback={e => {
const value = (e[0] || options[0]) as any;
setFirst(value);
setFirstAmount(convert(second.value, value.value, secondAmount));
}}
options={options}
value={first}
isClearable={false}
maxWidth={'100px'}
/>
<div>{getValue(secondAmount, second.value)}</div>
<Input
fullWidth={true}
placeholder={'Amount'}
value={secondAmount}
type={'number'}
onChange={e => {
const value = Number(e.target.value);
setSecondAmount(value);
setFirstAmount(convert(second.value, first.value, value));
}}
/>
<SelectWithValue
callback={e => {
const value = (e[0] || options[1]) as any;
setSecond(value);
setSecondAmount(convert(first.value, value.value, firstAmount));
}}
options={options}
value={second}
isClearable={false}
maxWidth={'100px'}
/>
</S.row>
</S.wrapper>
);
};

View file

@ -0,0 +1,38 @@
import { Heart } from 'react-feather';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import { useDashDispatch } from 'src/context/DashContext';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
height: 100%;
width: 100%;
`,
title: styled.div`
font-size: 14px;
margin-left: 4px;
`,
row: styled.div`
display: flex;
justify-content: space-around;
align-items: center;
`,
};
export const DonateWidget = () => {
const dispatch = useDashDispatch();
return (
<S.wrapper>
<ColorButton
fullWidth={true}
onClick={() => dispatch({ type: 'openModal', modalType: 'donate' })}
>
<S.row>
<Heart size={18} />
<S.title>Donate</S.title>
</S.row>
</ColorButton>
</S.wrapper>
);
};

View file

@ -0,0 +1,27 @@
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import { useDashDispatch } from 'src/context/DashContext';
import styled from 'styled-components';
const S = {
wrapper: styled.div`
height: 100%;
width: 100%;
`,
};
export const SignWidget = () => {
const dispatch = useDashDispatch();
return (
<S.wrapper>
<ColorButton
fullWidth={true}
onClick={() =>
dispatch({ type: 'openModal', modalType: 'signMessage' })
}
>
Sign Message
</ColorButton>
</S.wrapper>
);
};

View file

@ -0,0 +1,301 @@
import { FC } from 'react';
import { MempoolWidget } from './external/mempool';
import {
ChainBalance,
ChannelBalance,
TotalBalance,
} from './lightning/balances';
import { ChannelListWidget } from './lightning/channels';
import { ForwardListWidget } from './lightning/forwards';
import { ForwardsGraph } from './lightning/forwardsGraph';
import { AliasWidget, BalanceWidget } from './lightning/info';
import { InvoicesGraph } from './lightning/invoiceGraph';
import { LiquidityGraph } from './lightning/liquidityGraph';
import {
CreateInvoice,
OpenChannel,
PayInvoice,
ReceiveOnChain,
SendOnChain,
} from './lightning/modal';
import { PaymentsGraph } from './lightning/paymentGraph';
import { TransactionsWidget } from './lightning/transactions';
import { TransactionsGraph } from './lightning/transactionsGraph';
import {
ChannelViewLink,
DashSettingsLink,
ForwardsViewLink,
RebalanceViewLink,
TransactionsViewLink,
} from './link';
import { CurrencySetting, ThemeSetting } from './settings';
import { ConvertWidget } from './util/Convert';
import { DonateWidget } from './util/DonateWidget';
import { SignWidget } from './util/Sign';
export const widgetDefaults = {
width: 4,
height: 8,
};
export type WidgetProps = {
id: number;
name: string;
group: string;
subgroup: string;
hidden?: boolean;
component: FC;
default: {
x: number;
y: number;
w: number;
h: number;
minW?: number;
minH?: number;
maxW?: number;
maxH?: number;
};
};
const defaultProps = {
x: 0,
y: Infinity,
w: widgetDefaults.width,
h: widgetDefaults.height,
};
export const widgetList: WidgetProps[] = [
{
id: 1,
name: 'Theme',
group: 'Settings',
subgroup: '',
component: ThemeSetting,
default: { ...defaultProps, w: 2, h: 2 },
},
{
id: 2,
name: 'Currency',
group: 'Settings',
subgroup: '',
component: CurrencySetting,
default: { ...defaultProps, w: 2, h: 2 },
},
{
id: 3,
name: 'Mempool Fees',
group: 'External',
subgroup: '',
component: MempoolWidget,
default: { ...defaultProps, w: 4, h: 3 },
},
{
id: 4,
name: 'Total Balance',
group: 'Lightning',
subgroup: 'Info',
component: TotalBalance,
default: { ...defaultProps, w: 2, h: 3 },
},
{
id: 5,
name: 'Channel Balance',
group: 'Lightning',
subgroup: 'Info',
component: ChannelBalance,
default: { ...defaultProps, w: 2, h: 3 },
},
{
id: 6,
name: 'Chain Balance',
group: 'Lightning',
subgroup: 'Info',
component: ChainBalance,
default: { ...defaultProps, w: 2, h: 3 },
},
{
id: 7,
name: 'Alias',
group: 'Lightning',
subgroup: 'Info',
component: AliasWidget,
default: { ...defaultProps, w: 2, h: 2 },
},
{
id: 8,
name: 'Transactions',
group: 'Lightning',
subgroup: 'Table',
component: TransactionsWidget,
default: { ...defaultProps, w: 5, h: 16, minW: 3 },
},
{
id: 9,
name: 'Forwards',
group: 'Lightning',
subgroup: 'Table',
component: ForwardListWidget,
default: { ...defaultProps, w: 5, h: 16, minW: 3 },
},
{
id: 10,
name: 'Forwards',
group: 'Lightning',
subgroup: 'Graph',
component: ForwardsGraph,
default: { ...defaultProps, w: 8, h: 16, minW: 5, minH: 8 },
},
{
id: 11,
name: 'Transactions',
group: 'Lightning',
subgroup: 'Graph',
component: TransactionsGraph,
default: { ...defaultProps, w: 8, h: 16, minW: 5, minH: 8 },
},
{
id: 12,
name: 'Invoices',
group: 'Lightning',
subgroup: 'Graph',
component: InvoicesGraph,
default: { ...defaultProps, w: 8, h: 16, minW: 5, minH: 8 },
},
{
id: 13,
name: 'Payments',
group: 'Lightning',
subgroup: 'Graph',
component: PaymentsGraph,
default: { ...defaultProps, w: 8, h: 16, minW: 5, minH: 8 },
},
{
id: 14,
name: 'Pay Invoice',
group: 'Lightning',
subgroup: 'Action',
component: PayInvoice,
default: { ...defaultProps, w: 2, h: 2, minH: 2, maxH: 2 },
},
{
id: 15,
name: 'Create Invoice',
group: 'Lightning',
subgroup: 'Action',
component: CreateInvoice,
default: { ...defaultProps, w: 2, h: 2, minH: 2, maxH: 2 },
},
{
id: 16,
name: 'Send Bitcoin',
group: 'Lightning',
subgroup: 'Action',
component: SendOnChain,
default: { ...defaultProps, w: 2, h: 2, minH: 2, maxH: 2 },
},
{
id: 17,
name: 'Receive Bitcoin',
group: 'Lightning',
subgroup: 'Action',
component: ReceiveOnChain,
default: { ...defaultProps, w: 2, h: 2, minH: 2, maxH: 2 },
},
{
id: 18,
name: 'Dashboard Settings',
group: 'Link',
subgroup: '',
component: DashSettingsLink,
default: { ...defaultProps, w: 2, h: 2 },
},
{
id: 19,
name: 'Forwards View',
group: 'Link',
subgroup: '',
component: ForwardsViewLink,
default: { ...defaultProps, w: 2, h: 2 },
},
{
id: 20,
name: 'Transactions View',
group: 'Link',
subgroup: '',
component: TransactionsViewLink,
default: { ...defaultProps, w: 2, h: 2 },
},
{
id: 21,
name: 'Channel View',
group: 'Link',
subgroup: '',
component: ChannelViewLink,
default: { ...defaultProps, w: 2, h: 2 },
},
{
id: 22,
name: 'Rebalance View',
group: 'Link',
subgroup: '',
component: RebalanceViewLink,
default: { ...defaultProps, w: 2, h: 2 },
},
{
id: 23,
name: 'Open Channel',
group: 'Lightning',
subgroup: 'Action',
component: OpenChannel,
default: { ...defaultProps, w: 2, h: 2, minH: 2, maxH: 2 },
},
{
id: 24,
name: 'Convert',
group: 'Utils',
subgroup: '',
component: ConvertWidget,
default: { ...defaultProps, w: 4, h: 4, minW: 3, minH: 4, maxH: 4 },
},
{
id: 25,
name: 'Liquidity',
group: 'Lightning',
subgroup: 'Graph',
component: LiquidityGraph,
default: { ...defaultProps, w: 8, h: 8, minW: 5, minH: 6 },
},
{
id: 26,
name: 'Balance',
group: 'Lightning',
subgroup: 'Info',
component: BalanceWidget,
default: { ...defaultProps, w: 1, h: 2 },
},
{
id: 27,
name: 'Channels',
group: 'Lightning',
subgroup: 'Table',
component: ChannelListWidget,
default: { ...defaultProps, w: 9, h: 16, minW: 9 },
},
{
id: 28,
name: 'Donate',
group: 'Utils',
subgroup: '',
hidden: true,
component: DonateWidget,
default: { ...defaultProps, w: 2, h: 2, minH: 2, maxH: 2 },
},
{
id: 29,
name: 'Sign Message',
group: 'Utils',
subgroup: '',
component: SignWidget,
default: { ...defaultProps, w: 2, h: 2, minH: 2, maxH: 2 },
},
];

View file

@ -50,12 +50,8 @@ const sectionColor = '#FFD300';
export const AccountInfo = () => { export const AccountInfo = () => {
const [state, setState] = useState<string>('none'); const [state, setState] = useState<string>('none');
const { const { chainBalance, chainPending, channelBalance, channelPending } =
chainBalance, useNodeInfo();
chainPending,
channelBalance,
channelPending,
} = useNodeInfo();
const renderContent = () => { const renderContent = () => {
switch (state) { switch (state) {

View file

@ -80,7 +80,11 @@ export const QuickActions = () => {
const renderContent = () => { const renderContent = () => {
switch (openCard) { switch (openCard) {
case 'support': case 'support':
return <SupportBar />; return (
<Card>
<SupportBar />
</Card>
);
case 'decode': case 'decode':
return <DecodeCard />; return <DecodeCard />;
case 'ln_url': case 'ln_url':

View file

@ -8,7 +8,6 @@ import {
unSelectedNavButton, unSelectedNavButton,
mediaWidths, mediaWidths,
} from 'src/styles/Themes'; } from 'src/styles/Themes';
import { useBaseConnect } from 'src/hooks/UseBaseConnect';
const QuickTitle = styled.div` const QuickTitle = styled.div`
font-size: 14px; font-size: 14px;
@ -54,10 +53,6 @@ type SupportCardProps = {
}; };
export const SupportCard = ({ callback }: SupportCardProps) => { export const SupportCard = ({ callback }: SupportCardProps) => {
const connected = useBaseConnect();
if (!connected) return null;
return ( return (
<QuickCard onClick={callback}> <QuickCard onClick={callback}>
<Heart size={24} /> <Heart size={24} />

View file

@ -1,10 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { import { SubTitle, Separation, Sub4Title } from 'src/components/generic/Styled';
Card,
SubTitle,
Separation,
Sub4Title,
} from 'src/components/generic/Styled';
import { InputWithDeco } from 'src/components/input/InputWithDeco'; import { InputWithDeco } from 'src/components/input/InputWithDeco';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton'; import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import Modal from 'src/components/modal/ReactModal'; import Modal from 'src/components/modal/ReactModal';
@ -42,16 +37,14 @@ export const SupportBar = () => {
const [invoice, invoiceSet] = React.useState<string>(''); const [invoice, invoiceSet] = React.useState<string>('');
const [id, idSet] = React.useState<string>(''); const [id, idSet] = React.useState<string>('');
const connected = useBaseConnect(); const { connected } = useBaseConnect();
const [withPoints, setWithPoints] = React.useState<boolean>(false); const [withPoints, setWithPoints] = React.useState<boolean>(false);
const [getInvoice, { data, loading }] = useCreateBaseInvoiceMutation(); const [getInvoice, { data, loading }] = useCreateBaseInvoiceMutation();
const [ const [createPoints, { data: pointsData, called, loading: pointsLoading }] =
createPoints, useCreateThunderPointsMutation({ refetchQueries: ['GetBasePoints'] });
{ data: pointsData, called, loading: pointsLoading },
] = useCreateThunderPointsMutation({ refetchQueries: ['GetBasePoints'] });
const { data: info } = useGetCanConnectInfoQuery({ ssr: false }); const { data: info } = useGetCanConnectInfoQuery({ ssr: false });
React.useEffect(() => { React.useEffect(() => {
@ -73,7 +66,16 @@ export const SupportBar = () => {
} }
}, [pointsData, pointsLoading, called]); }, [pointsData, pointsLoading, called]);
if (!connected) return null; if (!connected)
return (
<div style={{ textAlign: 'center' }}>
<SubTitle>Unable to connect to donation server.</SubTitle>
<Sub4Title>
Please check back later.Thanks for wanting to donate
<Emoji symbol={'❤️'} label={'heart'} />
</Sub4Title>
</div>
);
const handleReset = () => { const handleReset = () => {
modalOpenSet(false); modalOpenSet(false);
@ -102,56 +104,54 @@ export const SupportBar = () => {
return ( return (
<> <>
<Card> <div style={{ textAlign: 'center' }}>
<div style={{ textAlign: 'center' }}> <SubTitle>This project is completely free and open-source.</SubTitle>
<SubTitle>This project is completely free and open-source.</SubTitle> <Sub4Title>
<Sub4Title> If you have enjoyed it, please consider supporting ThunderHub with
If you have enjoyed it, please consider supporting ThunderHub with some sats <Emoji symbol={'❤️'} label={'heart'} />
some sats <Emoji symbol={'❤️'} label={'heart'} /> </Sub4Title>
</Sub4Title> </div>
</div> <Separation />
<Separation /> <InputWithDeco
<InputWithDeco title={'Amount'}
title={'Amount'} value={amount}
value={amount} amount={amount}
amount={amount} inputType={'number'}
inputType={'number'} inputCallback={value => amountSet(Number(value))}
inputCallback={value => amountSet(Number(value))} />
/> <Separation />
<Separation /> <InputWithDeco title={'With Points'} noInput={true}>
<InputWithDeco title={'With Points'} noInput={true}> <MultiButton>
<MultiButton> {renderButton(() => setWithPoints(true), 'Yes', withPoints)}
{renderButton(() => setWithPoints(true), 'Yes', withPoints)} {renderButton(() => setWithPoints(false), 'No', !withPoints)}
{renderButton(() => setWithPoints(false), 'No', !withPoints)} </MultiButton>
</MultiButton> </InputWithDeco>
</InputWithDeco> {withPoints && (
{withPoints && ( <>
<> <StyledText>
<StyledText> This means your node will appear in the ThunderHub donation
This means your node will appear in the ThunderHub donation leaderboard. If you want to remain anonymous, do not enable this
leaderboard. If you want to remain anonymous, do not enable this option. Your node alias and public key will be stored if you enable
option. Your node alias and public key will be stored if you it.
enable it. </StyledText>
</StyledText> <Warning>
<Warning> Due to the increasing price of Bitcoin, to incentivize development
Due to the increasing price of Bitcoin, to incentivize development and to give everyone an opportunity to be in the top of the
and to give everyone an opportunity to be in the top of the leaderboard, points have a half life of 6 months. This means that
leaderboard, points have a half life of 6 months. This means that every 6 months they are halved.
every 6 months they are halved. </Warning>
</Warning> </>
</> )}
)} <Separation />
<Separation /> <ColorButton
<ColorButton onClick={() => getInvoice({ variables: { amount } })}
onClick={() => getInvoice({ variables: { amount } })} loading={loading}
loading={loading} disabled={amount <= 0 || loading}
disabled={amount <= 0 || loading} fullWidth={true}
fullWidth={true} withMargin={'8px 0 0 0'}
withMargin={'8px 0 0 0'} >
> Send
Send </ColorButton>
</ColorButton>
</Card>
<Modal isOpen={modalOpen} closeCallback={handleReset}> <Modal isOpen={modalOpen} closeCallback={handleReset}>
<Pay predefinedRequest={invoice} payCallback={handlePaidReset} /> <Pay predefinedRequest={invoice} payCallback={handlePaidReset} />
</Modal> </Modal>

View file

@ -0,0 +1,101 @@
import { groupBy } from 'lodash';
import { Fragment } from 'react';
import { Layouts } from 'react-grid-layout';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import { Card, SubTitle } from 'src/components/generic/Styled';
import { Link } from 'src/components/link/Link';
import { useLocalStorage } from 'src/hooks/UseLocalStorage';
import styled from 'styled-components';
import { StoredWidget } from '../dashboard';
import { widgetList } from '../dashboard/widgets/widgetList';
import { WidgetRow } from './WidgetRow';
const S = {
subTitle: styled(SubTitle)`
margin: 32px 0 8px;
`,
};
export type NormalizedWidgets = {
id: number;
name: string;
group: string;
active: boolean;
};
const DashPanel = () => {
const [, setLayouts] = useLocalStorage<Layouts>('layouts', {});
const [availableWidgets, setAvailableWidgets] = useLocalStorage<
StoredWidget[]
>('dashboardWidgets', []);
const normalizedList: NormalizedWidgets[] = widgetList.reduce((p, w) => {
if (w.hidden) return p;
const active =
availableWidgets.findIndex((a: StoredWidget) => {
return a.id === w.id;
}) >= 0;
return [...p, { ...w, active }];
}, [] as NormalizedWidgets[]);
const handleAdd = (id: number) => {
const filtered = availableWidgets.filter(a => a.id !== id);
setAvailableWidgets([...filtered, { id }]);
};
const handleDelete = (id: number) => {
const filtered = availableWidgets.filter(a => a.id !== id);
setAvailableWidgets(filtered);
};
const grouped = groupBy(normalizedList, 'group');
const keys = Object.keys(grouped);
return (
<Card>
{keys.map((key, index) => {
const widgets = grouped[key];
const subGrouped = groupBy(widgets, 'subgroup');
const subKeys = Object.keys(subGrouped);
return subKeys.map((subKey, subIndex) => {
const subWidgets = subGrouped[subKey];
return (
<Fragment key={key + index + subIndex}>
<S.subTitle>{subKey ? `${key} - ${subKey}` : key}</S.subTitle>
{subWidgets.map(w => (
<Fragment key={w.id}>
<WidgetRow
widget={w}
handleAdd={handleAdd}
handleDelete={handleDelete}
/>
</Fragment>
))}
</Fragment>
);
});
})}
<Link href={'/dashboard'}>
<ColorButton withMargin={'16px 0 0'} width={'100%'} arrow={true}>
To Dashboard
</ColorButton>
</Link>
<ColorButton
withMargin={'8px 0 0'}
width={'100%'}
onClick={() => {
setLayouts({});
setAvailableWidgets([]);
}}
>
Reset Widgets
</ColorButton>
</Card>
);
};
export default DashPanel;

View file

@ -0,0 +1,27 @@
import { useRouter } from 'next/router';
import { SettingsLine } from 'pages/settings';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import {
Card,
CardWithTitle,
Sub4Title,
SubTitle,
} from 'src/components/generic/Styled';
export const DashboardSettings = () => {
const { push } = useRouter();
return (
<CardWithTitle>
<SubTitle>Dashboard</SubTitle>
<Card>
<SettingsLine>
<Sub4Title>Widgets</Sub4Title>
<ColorButton arrow={true} onClick={() => push('/settings/dashboard')}>
Change
</ColorButton>
</SettingsLine>
</Card>
</CardWithTitle>
);
};

View file

@ -0,0 +1,47 @@
import { FC } from 'react';
import {
MultiButton,
SingleButton,
} from 'src/components/buttons/multiButton/MultiButton';
import { DarkSubTitle } from 'src/components/generic/Styled';
import styled from 'styled-components';
import { NormalizedWidgets } from './DashPanel';
const S = {
line: styled.div`
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
`,
};
type WidgetRowParams = {
widget: NormalizedWidgets;
handleAdd: (id: number) => void;
handleDelete: (id: number) => void;
};
export const WidgetRow: FC<WidgetRowParams> = ({
widget,
handleAdd,
handleDelete,
}) => (
<S.line>
<DarkSubTitle>{widget.name}</DarkSubTitle>
<MultiButton>
<SingleButton
selected={widget.active}
onClick={() => handleAdd(widget.id)}
>
Show
</SingleButton>
<SingleButton
selected={!widget.active}
onClick={() => handleDelete(widget.id)}
>
Hide
</SingleButton>
</MultiButton>
</S.line>
);

View file

@ -5,7 +5,7 @@ import {
SubTitle, SubTitle,
Card, Card,
} from '../../../components/generic/Styled'; } from '../../../components/generic/Styled';
import { SignMessage } from './SignMessage'; import { SignMessageCard } from './SignMessage';
import { VerifyMessage } from './VerifyMessage'; import { VerifyMessage } from './VerifyMessage';
export const NoWrap = styled.div` export const NoWrap = styled.div`
@ -19,7 +19,7 @@ export const MessagesView = () => {
<SubTitle>Messages</SubTitle> <SubTitle>Messages</SubTitle>
<Card> <Card>
<VerifyMessage /> <VerifyMessage />
<SignMessage /> <SignMessageCard />
</Card> </Card>
</CardWithTitle> </CardWithTitle>
); );

View file

@ -16,7 +16,6 @@ import { NoWrap } from './Messages';
export const SignMessage = () => { export const SignMessage = () => {
const [message, setMessage] = useState<string>(''); const [message, setMessage] = useState<string>('');
const [isPasting, setIsPasting] = useState<boolean>(false);
const [signed, setSigned] = useState<string>(''); const [signed, setSigned] = useState<string>('');
const [signMessage, { data, loading }] = useSignMessageLazyQuery({ const [signMessage, { data, loading }] = useSignMessageLazyQuery({
@ -49,40 +48,51 @@ export const SignMessage = () => {
> >
Sign Sign
</ColorButton> </ColorButton>
<Separation />
</> </>
); );
const renderMessage = () => ( const renderMessage = () => (
<Column> <>
<WrapRequest>{signed}</WrapRequest> <Separation />
<CopyToClipboard <Column>
text={signed} <WrapRequest>{signed}</WrapRequest>
onCopy={() => toast.success('Signature Copied')} <CopyToClipboard
> text={signed}
<ColorButton> onCopy={() => toast.success('Signature Copied')}
<Copy size={18} /> >
Copy <ColorButton>
</ColorButton> <Copy size={18} />
</CopyToClipboard> Copy
</Column> </ColorButton>
</CopyToClipboard>
</Column>
</>
); );
return (
<>
{renderInput()}
{signed !== '' && renderMessage()}
</>
);
};
export const SignMessageCard = () => {
const [isPasting, setIsPasting] = useState<boolean>(false);
return ( return (
<> <>
<SingleLine> <SingleLine>
<DarkSubTitle>Sign Message</DarkSubTitle> <DarkSubTitle>Sign Message</DarkSubTitle>
<ColorButton <ColorButton
withMargin={'4px 0'} withMargin={'4px 0'}
disabled={loading}
arrow={!isPasting} arrow={!isPasting}
onClick={() => setIsPasting(prev => !prev)} onClick={() => setIsPasting(prev => !prev)}
> >
{isPasting ? <X size={18} /> : 'Sign'} {isPasting ? <X size={18} /> : 'Sign'}
</ColorButton> </ColorButton>
</SingleLine> </SingleLine>
{isPasting && renderInput()} {isPasting && <SignMessage />}
{signed !== '' && isPasting && renderMessage()}
</> </>
); );
}; };