mirror of
https://github.com/apotdevin/thunderhub.git
synced 2025-03-11 01:27:30 +01:00
chore: dash and settings
This commit is contained in:
parent
beb7da43ed
commit
d5ba08cf67
55 changed files with 3390 additions and 220 deletions
|
@ -43,35 +43,11 @@ function createApolloClient(context?: ResolverContext) {
|
|||
ssrMode: typeof window === 'undefined',
|
||||
link: createIsomorphLink(context),
|
||||
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,
|
||||
}),
|
||||
defaultOptions: {
|
||||
query: {
|
||||
fetchPolicy: 'cache-first',
|
||||
// errorPolicy: 'all',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
359
package-lock.json
generated
359
package-lock.json
generated
|
@ -5,12 +5,13 @@
|
|||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.12.17",
|
||||
"version": "0.12.19",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.19",
|
||||
"@emotion/babel-plugin": "^11.3.0",
|
||||
"@next/bundle-analyzer": "^10.2.3",
|
||||
"@visx/axis": "^1.12.0",
|
||||
"@visx/chord": "^1.7.0",
|
||||
"@visx/curve": "^1.7.0",
|
||||
"@visx/event": "^1.7.0",
|
||||
|
@ -30,6 +31,7 @@
|
|||
"cookie": "^0.4.1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"d3-array": "^2.12.1",
|
||||
"d3-time-format": "^3.0.0",
|
||||
"date-fns": "^2.22.1",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-iso-date": "^3.6.1",
|
||||
|
@ -53,6 +55,7 @@
|
|||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-grid-layout": "^1.2.5",
|
||||
"react-intersection-observer": "^8.32.0",
|
||||
"react-qr-reader": "^2.2.1",
|
||||
"react-select": "^4.3.1",
|
||||
|
@ -92,6 +95,7 @@
|
|||
"@types/cookie": "^0.4.0",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"@types/d3-array": "^2.12.1",
|
||||
"@types/d3-time-format": "^3.0.0",
|
||||
"@types/graphql-iso-date": "^3.4.0",
|
||||
"@types/js-cookie": "^2.2.6",
|
||||
"@types/js-yaml": "^4.0.1",
|
||||
|
@ -106,6 +110,7 @@
|
|||
"@types/qrcode.react": "^1.0.1",
|
||||
"@types/react": "^17.0.8",
|
||||
"@types/react-copy-to-clipboard": "^5.0.0",
|
||||
"@types/react-grid-layout": "^1.1.1",
|
||||
"@types/react-qr-reader": "^2.1.3",
|
||||
"@types/react-select": "^4.0.15",
|
||||
"@types/react-slider": "^1.1.2",
|
||||
|
@ -363,14 +368,12 @@
|
|||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz",
|
||||
"integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ=="
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz",
|
||||
"integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.14.3",
|
||||
|
@ -428,7 +431,6 @@
|
|||
"version": "7.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz",
|
||||
"integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.14.4",
|
||||
"@babel/helper-validator-option": "^7.12.17",
|
||||
|
@ -510,7 +512,6 @@
|
|||
"version": "7.13.12",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.13.12"
|
||||
}
|
||||
|
@ -527,7 +528,6 @@
|
|||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz",
|
||||
"integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.13.12",
|
||||
"@babel/helper-replace-supers": "^7.13.12",
|
||||
|
@ -543,7 +543,6 @@
|
|||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz",
|
||||
"integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.12.13"
|
||||
}
|
||||
|
@ -568,7 +567,6 @@
|
|||
"version": "7.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz",
|
||||
"integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-member-expression-to-functions": "^7.13.12",
|
||||
"@babel/helper-optimise-call-expression": "^7.12.13",
|
||||
|
@ -580,7 +578,6 @@
|
|||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz",
|
||||
"integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.13.12"
|
||||
}
|
||||
|
@ -610,8 +607,7 @@
|
|||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.12.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz",
|
||||
"integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw=="
|
||||
},
|
||||
"node_modules/@babel/helper-wrap-function": {
|
||||
"version": "7.13.0",
|
||||
|
@ -629,7 +625,6 @@
|
|||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz",
|
||||
"integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/traverse": "^7.14.0",
|
||||
|
@ -5084,6 +5079,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.0.tgz",
|
||||
"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": {
|
||||
"version": "4.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz",
|
||||
|
@ -5421,6 +5422,15 @@
|
|||
"@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": {
|
||||
"version": "2.1.3",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz",
|
||||
|
@ -5904,9 +5933,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@visx/shape": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.11.1.tgz",
|
||||
"integrity": "sha512-k5VF3+VeG0nNPycDyAdJywOI8tTglHWkZDL9ZTM1o4dLXbytwtWxEcfRTmDHyynYTca+WBsY7dapeGuvrmw6Hw==",
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.12.0.tgz",
|
||||
"integrity": "sha512-BiY5nXrpg/CdY2Vd/oIaEsQoYjL7Nb+7IGsRCT5DVNelulgnwU1P13SqdVvs4FUtp/WYy97djQuIrR/6zCHdyw==",
|
||||
"dependencies": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/d3-path": "^1.0.8",
|
||||
|
@ -5926,6 +5955,23 @@
|
|||
"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": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.7.2.tgz",
|
||||
|
@ -7298,8 +7344,7 @@
|
|||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/balanceofsatoshis": {
|
||||
"version": "8.0.14",
|
||||
|
@ -12003,7 +12048,6 @@
|
|||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
|
@ -16677,7 +16721,6 @@
|
|||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5"
|
||||
},
|
||||
|
@ -18436,6 +18479,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"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": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
|
@ -18865,6 +18913,11 @@
|
|||
"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": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
@ -21602,6 +21655,15 @@
|
|||
"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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
|
||||
|
@ -21618,6 +21680,22 @@
|
|||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz",
|
||||
|
@ -21664,6 +21742,18 @@
|
|||
"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": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz",
|
||||
|
@ -21982,6 +22072,29 @@
|
|||
"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": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
|
@ -26632,14 +26745,12 @@
|
|||
"@babel/compat-data": {
|
||||
"version": "7.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz",
|
||||
"integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ=="
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz",
|
||||
"integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.14.3",
|
||||
|
@ -26690,7 +26801,6 @@
|
|||
"version": "7.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz",
|
||||
"integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.14.4",
|
||||
"@babel/helper-validator-option": "^7.12.17",
|
||||
|
@ -26763,7 +26873,6 @@
|
|||
"version": "7.13.12",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.13.12"
|
||||
}
|
||||
|
@ -26780,7 +26889,6 @@
|
|||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz",
|
||||
"integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.13.12",
|
||||
"@babel/helper-replace-supers": "^7.13.12",
|
||||
|
@ -26796,7 +26904,6 @@
|
|||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz",
|
||||
"integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.12.13"
|
||||
}
|
||||
|
@ -26821,7 +26928,6 @@
|
|||
"version": "7.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz",
|
||||
"integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-member-expression-to-functions": "^7.13.12",
|
||||
"@babel/helper-optimise-call-expression": "^7.12.13",
|
||||
|
@ -26833,7 +26939,6 @@
|
|||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz",
|
||||
"integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.13.12"
|
||||
}
|
||||
|
@ -26863,8 +26968,7 @@
|
|||
"@babel/helper-validator-option": {
|
||||
"version": "7.12.17",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz",
|
||||
"integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw=="
|
||||
},
|
||||
"@babel/helper-wrap-function": {
|
||||
"version": "7.13.0",
|
||||
|
@ -26882,7 +26986,6 @@
|
|||
"version": "7.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz",
|
||||
"integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/traverse": "^7.14.0",
|
||||
|
@ -29187,7 +29290,8 @@
|
|||
"@graphql-typed-document-node/core": {
|
||||
"version": "3.1.0",
|
||||
"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": {
|
||||
"version": "1.2.12",
|
||||
|
@ -29948,7 +30052,8 @@
|
|||
"@next/react-refresh-utils": {
|
||||
"version": "10.2.3",
|
||||
"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": {
|
||||
"version": "2.1.4",
|
||||
|
@ -30507,6 +30612,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.0.tgz",
|
||||
"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": {
|
||||
"version": "4.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz",
|
||||
|
@ -30843,6 +30954,15 @@
|
|||
"@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": {
|
||||
"version": "2.1.3",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"@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": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz",
|
||||
|
@ -31230,9 +31366,9 @@
|
|||
}
|
||||
},
|
||||
"@visx/shape": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.11.1.tgz",
|
||||
"integrity": "sha512-k5VF3+VeG0nNPycDyAdJywOI8tTglHWkZDL9ZTM1o4dLXbytwtWxEcfRTmDHyynYTca+WBsY7dapeGuvrmw6Hw==",
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.12.0.tgz",
|
||||
"integrity": "sha512-BiY5nXrpg/CdY2Vd/oIaEsQoYjL7Nb+7IGsRCT5DVNelulgnwU1P13SqdVvs4FUtp/WYy97djQuIrR/6zCHdyw==",
|
||||
"requires": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/d3-path": "^1.0.8",
|
||||
|
@ -31249,6 +31385,20 @@
|
|||
"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": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.7.2.tgz",
|
||||
|
@ -31359,7 +31509,8 @@
|
|||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
|
||||
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "7.2.0",
|
||||
|
@ -31414,7 +31565,8 @@
|
|||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"anser": {
|
||||
"version": "1.4.9",
|
||||
|
@ -31645,7 +31797,8 @@
|
|||
"apollo-server-errors": {
|
||||
"version": "2.5.0",
|
||||
"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": {
|
||||
"version": "2.25.0",
|
||||
|
@ -32356,8 +32509,7 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"balanceofsatoshis": {
|
||||
"version": "8.0.14",
|
||||
|
@ -35276,7 +35428,8 @@
|
|||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
|
||||
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-import-resolver-node": {
|
||||
"version": "0.3.4",
|
||||
|
@ -35506,7 +35659,8 @@
|
|||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz",
|
||||
"integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
|
@ -36148,8 +36302,7 @@
|
|||
"gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
|
@ -36693,7 +36846,8 @@
|
|||
"ws": {
|
||||
"version": "7.4.4",
|
||||
"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": {
|
||||
"version": "3.6.1",
|
||||
"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": {
|
||||
"version": "6.0.10",
|
||||
|
@ -36901,7 +37056,8 @@
|
|||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.7.0.tgz",
|
||||
"integrity": "sha512-Md8SsmC9ZlsogFPd3Ot8HbIAAqsHh8Xoq7j4AmcIat1Bh6k91tjVyQvA0Au1/BolXSYq+RDvib6rATU2Hcf1Xw==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"gtoken": {
|
||||
"version": "5.2.1",
|
||||
|
@ -37861,7 +38017,8 @@
|
|||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
|
||||
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
|
@ -38839,7 +38996,8 @@
|
|||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
|
||||
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"jest-regex-util": {
|
||||
"version": "27.0.1",
|
||||
|
@ -39682,7 +39840,6 @@
|
|||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
|
@ -40568,7 +40725,8 @@
|
|||
"ws": {
|
||||
"version": "7.4.4",
|
||||
"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": {
|
||||
"version": "7.4.6",
|
||||
"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": {
|
||||
"version": "7.4.4",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
|
@ -41428,6 +41593,11 @@
|
|||
"integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==",
|
||||
"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": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
@ -41606,7 +41776,8 @@
|
|||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz",
|
||||
"integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
|
@ -43242,7 +43413,8 @@
|
|||
"ws": {
|
||||
"version": "7.4.4",
|
||||
"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": {
|
||||
"version": "2.0.4",
|
||||
"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": {
|
||||
"version": "5.0.3",
|
||||
|
@ -43565,6 +43738,15 @@
|
|||
"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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
|
||||
|
@ -43578,6 +43760,18 @@
|
|||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz",
|
||||
|
@ -43589,7 +43783,8 @@
|
|||
"react-intersection-observer": {
|
||||
"version": "8.32.0",
|
||||
"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": {
|
||||
"version": "16.13.1",
|
||||
|
@ -43611,6 +43806,15 @@
|
|||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
||||
"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": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz",
|
||||
|
@ -43628,7 +43832,8 @@
|
|||
"react-slider": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-slider/-/react-slider-1.1.4.tgz",
|
||||
"integrity": "sha512-lL/MvzFcDue0ztdJItwLqas2lOy8Gg46eCDGJc4cJGldThmBHcHfGQePgBgyY1SEN95OwsWAakd3SuI8RyixDQ=="
|
||||
"integrity": "sha512-lL/MvzFcDue0ztdJItwLqas2lOy8Gg46eCDGJc4cJGldThmBHcHfGQePgBgyY1SEN95OwsWAakd3SuI8RyixDQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-spinners": {
|
||||
"version": "0.11.0",
|
||||
|
@ -43654,7 +43859,8 @@
|
|||
"react-table": {
|
||||
"version": "7.7.0",
|
||||
"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": {
|
||||
"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": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
|
@ -45297,7 +45528,8 @@
|
|||
"stylis-rule-sheet": {
|
||||
"version": "0.0.10",
|
||||
"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": {
|
||||
"version": "0.9.18",
|
||||
|
@ -47349,7 +47581,8 @@
|
|||
"ws": {
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g=="
|
||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
||||
"requires": {}
|
||||
},
|
||||
"xdg-basedir": {
|
||||
"version": "4.0.0",
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
"@apollo/client": "^3.3.19",
|
||||
"@emotion/babel-plugin": "^11.3.0",
|
||||
"@next/bundle-analyzer": "^10.2.3",
|
||||
"@visx/axis": "^1.12.0",
|
||||
"@visx/chord": "^1.7.0",
|
||||
"@visx/curve": "^1.7.0",
|
||||
"@visx/event": "^1.7.0",
|
||||
|
@ -56,6 +57,7 @@
|
|||
"cookie": "^0.4.1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"d3-array": "^2.12.1",
|
||||
"d3-time-format": "^3.0.0",
|
||||
"date-fns": "^2.22.1",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-iso-date": "^3.6.1",
|
||||
|
@ -79,6 +81,7 @@
|
|||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-grid-layout": "^1.2.5",
|
||||
"react-intersection-observer": "^8.32.0",
|
||||
"react-qr-reader": "^2.2.1",
|
||||
"react-select": "^4.3.1",
|
||||
|
@ -118,6 +121,7 @@
|
|||
"@types/cookie": "^0.4.0",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"@types/d3-array": "^2.12.1",
|
||||
"@types/d3-time-format": "^3.0.0",
|
||||
"@types/graphql-iso-date": "^3.4.0",
|
||||
"@types/js-cookie": "^2.2.6",
|
||||
"@types/js-yaml": "^4.0.1",
|
||||
|
@ -132,6 +136,7 @@
|
|||
"@types/qrcode.react": "^1.0.1",
|
||||
"@types/react": "^17.0.8",
|
||||
"@types/react-copy-to-clipboard": "^5.0.0",
|
||||
"@types/react-grid-layout": "^1.1.1",
|
||||
"@types/react-qr-reader": "^2.1.3",
|
||||
"@types/react-select": "^4.0.15",
|
||||
"@types/react-slider": "^1.1.2",
|
||||
|
|
|
@ -12,8 +12,11 @@ import { useConfigState, ConfigProvider } from '../src/context/ConfigContext';
|
|||
import { GlobalStyles } from '../src/styles/GlobalStyle';
|
||||
import { Header } from '../src/layouts/header/Header';
|
||||
import { Footer } from '../src/layouts/footer/Footer';
|
||||
import 'react-toastify/dist/ReactToastify.min.css';
|
||||
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';
|
||||
|
||||
const Wrapper: React.FC = ({ children }) => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
CardWithTitle,
|
||||
SubTitle,
|
||||
SmallButton,
|
||||
Card,
|
||||
} from '../src/components/generic/Styled';
|
||||
import { mediaWidths } from '../src/styles/Themes';
|
||||
|
||||
|
@ -66,7 +67,11 @@ const ChannelView = () => {
|
|||
case 3:
|
||||
return <ClosedChannels />;
|
||||
default:
|
||||
return <Channels />;
|
||||
return (
|
||||
<Card mobileCardPadding={'0'} mobileNoBackground={true}>
|
||||
<Channels />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
35
pages/dashboard.tsx
Normal file
35
pages/dashboard.tsx
Normal 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);
|
||||
}
|
|
@ -38,7 +38,9 @@ const LeaderboardView = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<SupportBar />
|
||||
<Card>
|
||||
<SupportBar />
|
||||
</Card>
|
||||
{renderBoard()}
|
||||
</>
|
||||
);
|
||||
|
|
25
pages/settings/dashboard.tsx
Normal file
25
pages/settings/dashboard.tsx
Normal 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);
|
||||
}
|
|
@ -3,11 +3,12 @@ import styled from 'styled-components';
|
|||
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
|
||||
import { NextPageContext } from 'next';
|
||||
import { getProps } from 'src/utils/ssr';
|
||||
import { SingleLine } from '../src/components/generic/Styled';
|
||||
import { InterfaceSettings } from '../src/views/settings/Interface';
|
||||
import { DangerView } from '../src/views/settings/Danger';
|
||||
import { ChatSettings } from '../src/views/settings/Chat';
|
||||
import { PrivacySettings } from '../src/views/settings/Privacy';
|
||||
import { SingleLine } from '../../src/components/generic/Styled';
|
||||
import { InterfaceSettings } from '../../src/views/settings/Interface';
|
||||
import { DangerView } from '../../src/views/settings/Danger';
|
||||
import { ChatSettings } from '../../src/views/settings/Chat';
|
||||
import { PrivacySettings } from '../../src/views/settings/Privacy';
|
||||
import { DashboardSettings } from 'src/views/settings/Dashboard';
|
||||
|
||||
export const ButtonRow = styled.div`
|
||||
width: auto;
|
||||
|
@ -22,6 +23,7 @@ const SettingsView = () => {
|
|||
return (
|
||||
<>
|
||||
<InterfaceSettings />
|
||||
<DashboardSettings />
|
||||
<PrivacySettings />
|
||||
<ChatSettings />
|
||||
<DangerView />
|
|
@ -61,20 +61,15 @@ const TransactionsView = () => {
|
|||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [limit, setLimit] = useState(7);
|
||||
|
||||
const { publicKey } = useNodeInfo();
|
||||
|
||||
const [settings] = useLocalStorage('transactionSettings', defaultSettings);
|
||||
|
||||
const {
|
||||
data,
|
||||
fetchMore,
|
||||
startPolling,
|
||||
stopPolling,
|
||||
networkStatus,
|
||||
} = useGetResumeQuery({
|
||||
const { data, startPolling, stopPolling, networkStatus } = useGetResumeQuery({
|
||||
ssr: false,
|
||||
variables: { offset: 0 },
|
||||
variables: { offset: 0, limit },
|
||||
notifyOnNetworkStatusChange: true,
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
});
|
||||
|
@ -85,9 +80,8 @@ const TransactionsView = () => {
|
|||
const loadingOrRefetching = isLoading || isRefetching;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && data?.getResume?.offset) {
|
||||
setOffset(data.getResume.offset);
|
||||
}
|
||||
if (isLoading || !data?.getResume?.offset) return;
|
||||
setOffset(data.getResume.offset);
|
||||
}, [data, isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -95,7 +89,12 @@ const TransactionsView = () => {
|
|||
}, [stopPolling]);
|
||||
|
||||
if (isLoading || !data || !data.getResume) {
|
||||
return <LoadingCard title={'Transactions'} />;
|
||||
return (
|
||||
<>
|
||||
<FlowBox />
|
||||
<LoadingCard title={'Transactions'} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const beforeDate = subDays(new Date(), offset);
|
||||
|
@ -135,8 +134,7 @@ const TransactionsView = () => {
|
|||
return [...p, c];
|
||||
}, [] as ResumeTransactions);
|
||||
|
||||
const handleClick = (limit: number) =>
|
||||
fetchMore({ variables: { offset, limit } });
|
||||
const handleClick = (limit: number) => setLimit(offset + limit);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
232
src/components/chart/BarChart.tsx
Normal file
232
src/components/chart/BarChart.tsx
Normal 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>
|
||||
);
|
189
src/components/chart/HorizontalBarChart.tsx
Normal file
189
src/components/chart/HorizontalBarChart.tsx
Normal 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>
|
||||
);
|
|
@ -46,3 +46,12 @@ export const GridWrapper: React.FC<GridProps> = ({
|
|||
</Container>
|
||||
</Section>
|
||||
);
|
||||
|
||||
export const SimpleWrapper: React.FC<GridProps> = ({ children }) => (
|
||||
<Section fixedWidth={false} padding={'16px'}>
|
||||
<BitcoinPrice />
|
||||
<BitcoinFees />
|
||||
<StatusCheck />
|
||||
{children}
|
||||
</Section>
|
||||
);
|
||||
|
|
|
@ -32,7 +32,7 @@ const FullWidth = styled.div`
|
|||
|
||||
const FixedWidth = styled.div`
|
||||
max-width: 1000px;
|
||||
margin: 0 auto 0 auto;
|
||||
margin: 0 auto 0;
|
||||
|
||||
@media (max-width: 1035px) {
|
||||
padding: 0 16px;
|
||||
|
|
|
@ -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 = {
|
||||
value: string;
|
||||
label: string;
|
||||
value: string | number;
|
||||
label: string | number;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
|
@ -94,6 +128,7 @@ type SelectWithValueProps = {
|
|||
value: ValueProp | undefined;
|
||||
isMulti?: boolean;
|
||||
maxWidth?: string;
|
||||
isClearable?: boolean;
|
||||
callback: (value: ValueProp[]) => void;
|
||||
};
|
||||
|
||||
|
@ -103,6 +138,7 @@ export const SelectWithValue = ({
|
|||
maxWidth,
|
||||
callback,
|
||||
value,
|
||||
isClearable = true,
|
||||
}: SelectWithValueProps) => {
|
||||
const handleChange = (value: ValueProp | ValueProp[]) => {
|
||||
if (Array.isArray(value)) {
|
||||
|
@ -119,7 +155,36 @@ export const SelectWithValue = ({
|
|||
options={options}
|
||||
onChange={handleChange}
|
||||
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>
|
||||
);
|
||||
|
|
|
@ -2,11 +2,14 @@ import React from 'react';
|
|||
import { PriceProvider } from './PriceContext';
|
||||
import { ChatProvider } from './ChatContext';
|
||||
import { RebalanceProvider } from './RebalanceContext';
|
||||
import { DashProvider } from './DashContext';
|
||||
|
||||
export const ContextProvider: React.FC = ({ children }) => (
|
||||
<PriceProvider>
|
||||
<ChatProvider>
|
||||
<RebalanceProvider>{children}</RebalanceProvider>
|
||||
</ChatProvider>
|
||||
</PriceProvider>
|
||||
<DashProvider>
|
||||
<PriceProvider>
|
||||
<ChatProvider>
|
||||
<RebalanceProvider>{children}</RebalanceProvider>
|
||||
</ChatProvider>
|
||||
</PriceProvider>
|
||||
</DashProvider>
|
||||
);
|
||||
|
|
54
src/context/DashContext.tsx
Normal file
54
src/context/DashContext.tsx
Normal 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 };
|
|
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
|||
import { useGetBaseCanConnectQuery } from 'src/graphql/queries/__generated__/getBaseCanConnect.generated';
|
||||
|
||||
export const useBaseConnect = () => {
|
||||
const [canConnect, setCanConnect] = useState<boolean>(false);
|
||||
const [connected, setCanConnect] = useState<boolean>(false);
|
||||
|
||||
const { loading, error, data } = useGetBaseCanConnectQuery({
|
||||
ssr: false,
|
||||
|
@ -14,5 +14,5 @@ export const useBaseConnect = () => {
|
|||
setCanConnect(true);
|
||||
}, [loading, data, error]);
|
||||
|
||||
return canConnect;
|
||||
return { connected, loading };
|
||||
};
|
||||
|
|
40
src/hooks/UseElementSize.tsx
Normal file
40
src/hooks/UseElementSize.tsx
Normal 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;
|
39
src/hooks/UseEventListener.tsx
Normal file
39
src/hooks/UseEventListener.tsx
Normal 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;
|
|
@ -43,7 +43,7 @@ export const Header = () => {
|
|||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { lnMarketsAuth } = useConfigState();
|
||||
const connected = useBaseConnect();
|
||||
const { connected } = useBaseConnect();
|
||||
|
||||
const isRoot = pathname === MAIN || pathname === SSO;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
Heart,
|
||||
Shuffle,
|
||||
Aperture,
|
||||
Grid,
|
||||
} from 'react-feather';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useBaseConnect } from 'src/hooks/UseBaseConnect';
|
||||
|
@ -116,6 +117,7 @@ const BurgerNav = styled.a<NavProps>`
|
|||
`;
|
||||
|
||||
const HOME = '/';
|
||||
const DASHBOARD = '/dashboard';
|
||||
const PEERS = '/peers';
|
||||
const CHANNEL = '/channels';
|
||||
const REBALANCE = '/rebalance';
|
||||
|
@ -140,7 +142,7 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
|
|||
const { pathname } = useRouter();
|
||||
const { sidebar } = useConfigState();
|
||||
|
||||
const connected = useBaseConnect();
|
||||
const { connected } = useBaseConnect();
|
||||
|
||||
const isRoot = pathname === '/login' || pathname === '/sso';
|
||||
|
||||
|
@ -173,6 +175,7 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
|
|||
const renderLinks = () => (
|
||||
<ButtonSection isOpen={sidebar}>
|
||||
{renderNavButton('Home', HOME, Home, sidebar)}
|
||||
{renderNavButton('Dashboard', DASHBOARD, Grid, sidebar)}
|
||||
{renderNavButton('Peers', PEERS, Users, sidebar)}
|
||||
{renderNavButton('Channels', CHANNEL, Cpu, sidebar)}
|
||||
{renderNavButton('Rebalance', REBALANCE, Repeat, sidebar)}
|
||||
|
@ -183,13 +186,14 @@ export const Navigation = ({ isBurger, setOpen }: NavigationProps) => {
|
|||
{renderNavButton('Tools', TOOLS, Shield, sidebar)}
|
||||
{renderNavButton('Swap', SWAP, Shuffle, sidebar)}
|
||||
{renderNavButton('Stats', STATS, BarChart2, sidebar)}
|
||||
{connected && renderNavButton('Scores', SCORES, Aperture)}
|
||||
{connected && renderNavButton('Scores', SCORES, Aperture, sidebar)}
|
||||
</ButtonSection>
|
||||
);
|
||||
|
||||
const renderBurger = () => (
|
||||
<BurgerRow>
|
||||
{renderBurgerNav('Home', HOME, Home)}
|
||||
{renderBurgerNav('Dashboard', DASHBOARD, Grid)}
|
||||
{renderBurgerNav('Peers', PEERS, Users)}
|
||||
{renderBurgerNav('Channels', CHANNEL, Cpu)}
|
||||
{renderBurgerNav('Rebalance', REBALANCE, Repeat)}
|
||||
|
|
11
src/utils/gridConstants.ts
Normal file
11
src/utils/gridConstants.ts
Normal 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],
|
||||
};
|
|
@ -29,7 +29,7 @@ const S = {
|
|||
};
|
||||
|
||||
export const ChannelBosScore: FC<{ score?: BosScore | null }> = ({ score }) => {
|
||||
const connected = useBaseConnect();
|
||||
const { connected } = useBaseConnect();
|
||||
|
||||
if (!connected) return null;
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import { getPercent } from 'src/utils/helpers';
|
|||
import { ChannelType } from 'src/graphql/types';
|
||||
import { useRebalanceState } from 'src/context/RebalanceContext';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Card } from '../../../components/generic/Styled';
|
||||
import { getErrorContent } from '../../../utils/error';
|
||||
import { LoadingCard } from '../../../components/loading/LoadingCard';
|
||||
import { ChannelCard } from './ChannelCard';
|
||||
|
@ -167,7 +166,7 @@ export const Channels: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Card mobileCardPadding={'0'} mobileNoBackground={true}>
|
||||
<>
|
||||
{getChannels().map((channel, index) => (
|
||||
<ChannelCard
|
||||
channelInfo={channel as ChannelType}
|
||||
|
@ -182,6 +181,6 @@ export const Channels: React.FC = () => {
|
|||
biggestRateFee={Math.max(Math.min(biggestRateFee, 10000), 2000)}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
126
src/views/dashboard/index.tsx
Normal file
126
src/views/dashboard/index.tsx
Normal 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;
|
48
src/views/dashboard/modal/index.tsx
Normal file
48
src/views/dashboard/modal/index.tsx
Normal 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();
|
||||
};
|
40
src/views/dashboard/widgets/external/mempool.tsx
vendored
Normal file
40
src/views/dashboard/widgets/external/mempool.tsx
vendored
Normal 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>
|
||||
);
|
||||
};
|
185
src/views/dashboard/widgets/helpers.tsx
Normal file
185
src/views/dashboard/widgets/helpers.tsx
Normal 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;
|
||||
};
|
84
src/views/dashboard/widgets/lightning/balances.tsx
Normal file
84
src/views/dashboard/widgets/lightning/balances.tsx
Normal 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>
|
||||
);
|
||||
};
|
18
src/views/dashboard/widgets/lightning/channels.tsx
Normal file
18
src/views/dashboard/widgets/lightning/channels.tsx
Normal 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>
|
||||
);
|
||||
};
|
72
src/views/dashboard/widgets/lightning/forwards.tsx
Normal file
72
src/views/dashboard/widgets/lightning/forwards.tsx
Normal 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>
|
||||
);
|
||||
};
|
123
src/views/dashboard/widgets/lightning/forwardsGraph.tsx
Normal file
123
src/views/dashboard/widgets/lightning/forwardsGraph.tsx
Normal 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>
|
||||
);
|
||||
};
|
50
src/views/dashboard/widgets/lightning/info.tsx
Normal file
50
src/views/dashboard/widgets/lightning/info.tsx
Normal 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>
|
||||
);
|
||||
};
|
140
src/views/dashboard/widgets/lightning/invoiceGraph.tsx
Normal file
140
src/views/dashboard/widgets/lightning/invoiceGraph.tsx
Normal 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>
|
||||
);
|
||||
};
|
71
src/views/dashboard/widgets/lightning/liquidityGraph.tsx
Normal file
71
src/views/dashboard/widgets/lightning/liquidityGraph.tsx
Normal 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>
|
||||
);
|
||||
};
|
69
src/views/dashboard/widgets/lightning/modal.tsx
Normal file
69
src/views/dashboard/widgets/lightning/modal.tsx
Normal 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>
|
||||
);
|
||||
};
|
136
src/views/dashboard/widgets/lightning/paymentGraph.tsx
Normal file
136
src/views/dashboard/widgets/lightning/paymentGraph.tsx
Normal 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>
|
||||
);
|
||||
};
|
91
src/views/dashboard/widgets/lightning/transactions.tsx
Normal file
91
src/views/dashboard/widgets/lightning/transactions.tsx
Normal 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>
|
||||
);
|
||||
};
|
147
src/views/dashboard/widgets/lightning/transactionsGraph.tsx
Normal file
147
src/views/dashboard/widgets/lightning/transactionsGraph.tsx
Normal 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>
|
||||
);
|
||||
};
|
60
src/views/dashboard/widgets/link/index.tsx
Normal file
60
src/views/dashboard/widgets/link/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
76
src/views/dashboard/widgets/settings/index.tsx
Normal file
76
src/views/dashboard/widgets/settings/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
159
src/views/dashboard/widgets/util/Convert.tsx
Normal file
159
src/views/dashboard/widgets/util/Convert.tsx
Normal 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>
|
||||
);
|
||||
};
|
38
src/views/dashboard/widgets/util/DonateWidget.tsx
Normal file
38
src/views/dashboard/widgets/util/DonateWidget.tsx
Normal 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>
|
||||
);
|
||||
};
|
27
src/views/dashboard/widgets/util/Sign.tsx
Normal file
27
src/views/dashboard/widgets/util/Sign.tsx
Normal 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>
|
||||
);
|
||||
};
|
301
src/views/dashboard/widgets/widgetList.tsx
Normal file
301
src/views/dashboard/widgets/widgetList.tsx
Normal 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 },
|
||||
},
|
||||
];
|
|
@ -50,12 +50,8 @@ const sectionColor = '#FFD300';
|
|||
export const AccountInfo = () => {
|
||||
const [state, setState] = useState<string>('none');
|
||||
|
||||
const {
|
||||
chainBalance,
|
||||
chainPending,
|
||||
channelBalance,
|
||||
channelPending,
|
||||
} = useNodeInfo();
|
||||
const { chainBalance, chainPending, channelBalance, channelPending } =
|
||||
useNodeInfo();
|
||||
|
||||
const renderContent = () => {
|
||||
switch (state) {
|
||||
|
|
|
@ -80,7 +80,11 @@ export const QuickActions = () => {
|
|||
const renderContent = () => {
|
||||
switch (openCard) {
|
||||
case 'support':
|
||||
return <SupportBar />;
|
||||
return (
|
||||
<Card>
|
||||
<SupportBar />
|
||||
</Card>
|
||||
);
|
||||
case 'decode':
|
||||
return <DecodeCard />;
|
||||
case 'ln_url':
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
unSelectedNavButton,
|
||||
mediaWidths,
|
||||
} from 'src/styles/Themes';
|
||||
import { useBaseConnect } from 'src/hooks/UseBaseConnect';
|
||||
|
||||
const QuickTitle = styled.div`
|
||||
font-size: 14px;
|
||||
|
@ -54,10 +53,6 @@ type SupportCardProps = {
|
|||
};
|
||||
|
||||
export const SupportCard = ({ callback }: SupportCardProps) => {
|
||||
const connected = useBaseConnect();
|
||||
|
||||
if (!connected) return null;
|
||||
|
||||
return (
|
||||
<QuickCard onClick={callback}>
|
||||
<Heart size={24} />
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
Card,
|
||||
SubTitle,
|
||||
Separation,
|
||||
Sub4Title,
|
||||
} from 'src/components/generic/Styled';
|
||||
import { SubTitle, Separation, Sub4Title } from 'src/components/generic/Styled';
|
||||
import { InputWithDeco } from 'src/components/input/InputWithDeco';
|
||||
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
|
||||
import Modal from 'src/components/modal/ReactModal';
|
||||
|
@ -42,16 +37,14 @@ export const SupportBar = () => {
|
|||
const [invoice, invoiceSet] = React.useState<string>('');
|
||||
const [id, idSet] = React.useState<string>('');
|
||||
|
||||
const connected = useBaseConnect();
|
||||
const { connected } = useBaseConnect();
|
||||
|
||||
const [withPoints, setWithPoints] = React.useState<boolean>(false);
|
||||
|
||||
const [getInvoice, { data, loading }] = useCreateBaseInvoiceMutation();
|
||||
|
||||
const [
|
||||
createPoints,
|
||||
{ data: pointsData, called, loading: pointsLoading },
|
||||
] = useCreateThunderPointsMutation({ refetchQueries: ['GetBasePoints'] });
|
||||
const [createPoints, { data: pointsData, called, loading: pointsLoading }] =
|
||||
useCreateThunderPointsMutation({ refetchQueries: ['GetBasePoints'] });
|
||||
const { data: info } = useGetCanConnectInfoQuery({ ssr: false });
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -73,7 +66,16 @@ export const SupportBar = () => {
|
|||
}
|
||||
}, [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 = () => {
|
||||
modalOpenSet(false);
|
||||
|
@ -102,56 +104,54 @@ export const SupportBar = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<SubTitle>This project is completely free and open-source.</SubTitle>
|
||||
<Sub4Title>
|
||||
If you have enjoyed it, please consider supporting ThunderHub with
|
||||
some sats <Emoji symbol={'❤️'} label={'heart'} />
|
||||
</Sub4Title>
|
||||
</div>
|
||||
<Separation />
|
||||
<InputWithDeco
|
||||
title={'Amount'}
|
||||
value={amount}
|
||||
amount={amount}
|
||||
inputType={'number'}
|
||||
inputCallback={value => amountSet(Number(value))}
|
||||
/>
|
||||
<Separation />
|
||||
<InputWithDeco title={'With Points'} noInput={true}>
|
||||
<MultiButton>
|
||||
{renderButton(() => setWithPoints(true), 'Yes', withPoints)}
|
||||
{renderButton(() => setWithPoints(false), 'No', !withPoints)}
|
||||
</MultiButton>
|
||||
</InputWithDeco>
|
||||
{withPoints && (
|
||||
<>
|
||||
<StyledText>
|
||||
This means your node will appear in the ThunderHub donation
|
||||
leaderboard. If you want to remain anonymous, do not enable this
|
||||
option. Your node alias and public key will be stored if you
|
||||
enable it.
|
||||
</StyledText>
|
||||
<Warning>
|
||||
Due to the increasing price of Bitcoin, to incentivize development
|
||||
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
|
||||
every 6 months they are halved.
|
||||
</Warning>
|
||||
</>
|
||||
)}
|
||||
<Separation />
|
||||
<ColorButton
|
||||
onClick={() => getInvoice({ variables: { amount } })}
|
||||
loading={loading}
|
||||
disabled={amount <= 0 || loading}
|
||||
fullWidth={true}
|
||||
withMargin={'8px 0 0 0'}
|
||||
>
|
||||
Send
|
||||
</ColorButton>
|
||||
</Card>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<SubTitle>This project is completely free and open-source.</SubTitle>
|
||||
<Sub4Title>
|
||||
If you have enjoyed it, please consider supporting ThunderHub with
|
||||
some sats <Emoji symbol={'❤️'} label={'heart'} />
|
||||
</Sub4Title>
|
||||
</div>
|
||||
<Separation />
|
||||
<InputWithDeco
|
||||
title={'Amount'}
|
||||
value={amount}
|
||||
amount={amount}
|
||||
inputType={'number'}
|
||||
inputCallback={value => amountSet(Number(value))}
|
||||
/>
|
||||
<Separation />
|
||||
<InputWithDeco title={'With Points'} noInput={true}>
|
||||
<MultiButton>
|
||||
{renderButton(() => setWithPoints(true), 'Yes', withPoints)}
|
||||
{renderButton(() => setWithPoints(false), 'No', !withPoints)}
|
||||
</MultiButton>
|
||||
</InputWithDeco>
|
||||
{withPoints && (
|
||||
<>
|
||||
<StyledText>
|
||||
This means your node will appear in the ThunderHub donation
|
||||
leaderboard. If you want to remain anonymous, do not enable this
|
||||
option. Your node alias and public key will be stored if you enable
|
||||
it.
|
||||
</StyledText>
|
||||
<Warning>
|
||||
Due to the increasing price of Bitcoin, to incentivize development
|
||||
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
|
||||
every 6 months they are halved.
|
||||
</Warning>
|
||||
</>
|
||||
)}
|
||||
<Separation />
|
||||
<ColorButton
|
||||
onClick={() => getInvoice({ variables: { amount } })}
|
||||
loading={loading}
|
||||
disabled={amount <= 0 || loading}
|
||||
fullWidth={true}
|
||||
withMargin={'8px 0 0 0'}
|
||||
>
|
||||
Send
|
||||
</ColorButton>
|
||||
<Modal isOpen={modalOpen} closeCallback={handleReset}>
|
||||
<Pay predefinedRequest={invoice} payCallback={handlePaidReset} />
|
||||
</Modal>
|
||||
|
|
101
src/views/settings/DashPanel.tsx
Normal file
101
src/views/settings/DashPanel.tsx
Normal 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;
|
27
src/views/settings/Dashboard.tsx
Normal file
27
src/views/settings/Dashboard.tsx
Normal 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>
|
||||
);
|
||||
};
|
47
src/views/settings/WidgetRow.tsx
Normal file
47
src/views/settings/WidgetRow.tsx
Normal 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>
|
||||
);
|
|
@ -5,7 +5,7 @@ import {
|
|||
SubTitle,
|
||||
Card,
|
||||
} from '../../../components/generic/Styled';
|
||||
import { SignMessage } from './SignMessage';
|
||||
import { SignMessageCard } from './SignMessage';
|
||||
import { VerifyMessage } from './VerifyMessage';
|
||||
|
||||
export const NoWrap = styled.div`
|
||||
|
@ -19,7 +19,7 @@ export const MessagesView = () => {
|
|||
<SubTitle>Messages</SubTitle>
|
||||
<Card>
|
||||
<VerifyMessage />
|
||||
<SignMessage />
|
||||
<SignMessageCard />
|
||||
</Card>
|
||||
</CardWithTitle>
|
||||
);
|
||||
|
|
|
@ -16,7 +16,6 @@ import { NoWrap } from './Messages';
|
|||
|
||||
export const SignMessage = () => {
|
||||
const [message, setMessage] = useState<string>('');
|
||||
const [isPasting, setIsPasting] = useState<boolean>(false);
|
||||
const [signed, setSigned] = useState<string>('');
|
||||
|
||||
const [signMessage, { data, loading }] = useSignMessageLazyQuery({
|
||||
|
@ -49,40 +48,51 @@ export const SignMessage = () => {
|
|||
>
|
||||
Sign
|
||||
</ColorButton>
|
||||
<Separation />
|
||||
</>
|
||||
);
|
||||
|
||||
const renderMessage = () => (
|
||||
<Column>
|
||||
<WrapRequest>{signed}</WrapRequest>
|
||||
<CopyToClipboard
|
||||
text={signed}
|
||||
onCopy={() => toast.success('Signature Copied')}
|
||||
>
|
||||
<ColorButton>
|
||||
<Copy size={18} />
|
||||
Copy
|
||||
</ColorButton>
|
||||
</CopyToClipboard>
|
||||
</Column>
|
||||
<>
|
||||
<Separation />
|
||||
<Column>
|
||||
<WrapRequest>{signed}</WrapRequest>
|
||||
<CopyToClipboard
|
||||
text={signed}
|
||||
onCopy={() => toast.success('Signature Copied')}
|
||||
>
|
||||
<ColorButton>
|
||||
<Copy size={18} />
|
||||
Copy
|
||||
</ColorButton>
|
||||
</CopyToClipboard>
|
||||
</Column>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderInput()}
|
||||
{signed !== '' && renderMessage()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const SignMessageCard = () => {
|
||||
const [isPasting, setIsPasting] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SingleLine>
|
||||
<DarkSubTitle>Sign Message</DarkSubTitle>
|
||||
<ColorButton
|
||||
withMargin={'4px 0'}
|
||||
disabled={loading}
|
||||
arrow={!isPasting}
|
||||
onClick={() => setIsPasting(prev => !prev)}
|
||||
>
|
||||
{isPasting ? <X size={18} /> : 'Sign'}
|
||||
</ColorButton>
|
||||
</SingleLine>
|
||||
{isPasting && renderInput()}
|
||||
{signed !== '' && isPasting && renderMessage()}
|
||||
{isPasting && <SignMessage />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue