feat: notifications rework

This commit is contained in:
Taranto 2020-03-30 13:03:57 +01:00 committed by Josh Harvey
parent b6e7d98b72
commit ffa8713ee4
77 changed files with 2281 additions and 3269 deletions

View file

@ -11,6 +11,8 @@ low(adapter).then(it => {
function saveConfig (config) {
const currentState = db.getState()
// TODO this should be _.assign
// change after flattening of schema
const newState = _.mergeWith((objValue, srcValue) => {
if (_.isArray(objValue)) {
return srcValue

View file

@ -1840,7 +1840,8 @@
"@emotion/hash": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz",
"integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw=="
"integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw==",
"dev": true
},
"@emotion/is-prop-valid": {
"version": "0.8.3",
@ -2226,48 +2227,40 @@
}
},
"@material-ui/core": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.5.0.tgz",
"integrity": "sha512-UHVAjU+1uDtA+OMBNBHb4RlCZOu514XeYPafNJv+GTdXBDr1SCPK7yqRE6TV1/bulxlDusTgu5Q6BAUgpmO4MA==",
"version": "4.9.8",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.8.tgz",
"integrity": "sha512-4cslpG6oLoPWUfwPkX+hvbak4hAGiOfgXOu/UIYeeMrtsTEebC0Mirjoby7zhS4ny86YI3rXEFW6EZDmlj5n5w==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/styles": "^4.5.0",
"@material-ui/system": "^4.5.0",
"@material-ui/types": "^4.1.1",
"@material-ui/utils": "^4.4.0",
"@material-ui/styles": "^4.9.6",
"@material-ui/system": "^4.9.6",
"@material-ui/types": "^5.0.0",
"@material-ui/utils": "^4.9.6",
"@types/react-transition-group": "^4.2.0",
"clsx": "^1.0.2",
"convert-css-length": "^2.0.1",
"deepmerge": "^4.0.0",
"hoist-non-react-statics": "^3.2.1",
"is-plain-object": "^3.0.0",
"normalize-scroll-left": "^0.2.0",
"hoist-non-react-statics": "^3.3.2",
"popper.js": "^1.14.1",
"prop-types": "^15.7.2",
"react-is": "^16.8.0",
"react-transition-group": "^4.3.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
"requires": {
"regenerator-runtime": "^0.13.2"
"regenerator-runtime": "^0.13.4"
}
},
"is-plain-object": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz",
"integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==",
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"isobject": "^4.0.0"
"react-is": "^16.7.0"
}
},
"isobject": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz",
"integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA=="
},
"react-transition-group": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
@ -2280,134 +2273,176 @@
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
"@material-ui/icons": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.4.3.tgz",
"integrity": "sha512-HVVvUyc/78kmaBd93LkfWyGkXMM+zOMKzUfulWXxaV/fFAZ3N0pD0oHjWUd94zrOoF3tZP9JC7EPlIpIcZSNow==",
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz",
"integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==",
"requires": {
"@babel/runtime": "^7.4.4"
},
"dependencies": {
"@babel/runtime": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
"requires": {
"regenerator-runtime": "^0.13.2"
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
"@material-ui/lab": {
"version": "4.0.0-alpha.47",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.47.tgz",
"integrity": "sha512-+WC3O0M/769D3nO9Rqupusc+lob7tQMe5/DnOjAhZ0bpXlJbhZb7N84WkEk4JgQLj6ydP8e9Jhqd1lG+mGj+xw==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.9.6",
"clsx": "^1.0.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
"@material-ui/styles": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz",
"integrity": "sha512-O0NSAECHK9f3DZK6wy56PZzp8b/7KSdfpJs8DSC7vnXUAoMPCTtchBKLzMtUsNlijiJFeJjSxNdQfjWXgyur5A==",
"version": "4.9.6",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.6.tgz",
"integrity": "sha512-ijgwStEkw1OZ6gCz18hkjycpr/3lKs1hYPi88O/AUn4vMuuGEGAIrqKVFq/lADmZUNF3DOFIk8LDkp7zmjPxtA==",
"requires": {
"@babel/runtime": "^7.4.4",
"@emotion/hash": "^0.7.1",
"@material-ui/types": "^4.1.1",
"@material-ui/utils": "^4.1.0",
"@emotion/hash": "^0.8.0",
"@material-ui/types": "^5.0.0",
"@material-ui/utils": "^4.9.6",
"clsx": "^1.0.2",
"csstype": "^2.5.2",
"deepmerge": "^4.0.0",
"hoist-non-react-statics": "^3.2.1",
"jss": "^10.0.0",
"jss-plugin-camel-case": "^10.0.0",
"jss-plugin-default-unit": "^10.0.0",
"jss-plugin-global": "^10.0.0",
"jss-plugin-nested": "^10.0.0",
"jss-plugin-props-sort": "^10.0.0",
"jss-plugin-rule-value-function": "^10.0.0",
"jss-plugin-vendor-prefixer": "^10.0.0",
"hoist-non-react-statics": "^3.3.2",
"jss": "^10.0.3",
"jss-plugin-camel-case": "^10.0.3",
"jss-plugin-default-unit": "^10.0.3",
"jss-plugin-global": "^10.0.3",
"jss-plugin-nested": "^10.0.3",
"jss-plugin-props-sort": "^10.0.3",
"jss-plugin-rule-value-function": "^10.0.3",
"jss-plugin-vendor-prefixer": "^10.0.3",
"prop-types": "^15.7.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
"requires": {
"regenerator-runtime": "^0.13.2"
"regenerator-runtime": "^0.13.4"
}
},
"@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"jss": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
"@material-ui/system": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.5.0.tgz",
"integrity": "sha512-vR0PbMTzLnuuVCoYNQ13zyhLa/4s/UA9P9JbNuHBOOkfrHn53ShINiG0v05EgfwizfULLtc7mNvsGAgIyyp/hQ==",
"version": "4.9.6",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.6.tgz",
"integrity": "sha512-QtfoAePyqXoZ2HUVSwGb1Ro0kucMCvVjbI0CdYIR21t0Opgfm1Oer6ni9P5lfeXA39xSt0wCierw37j+YES48Q==",
"requires": {
"@babel/runtime": "^7.4.4",
"deepmerge": "^4.0.0",
"@material-ui/utils": "^4.9.6",
"prop-types": "^15.7.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
"requires": {
"regenerator-runtime": "^0.13.2"
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
"@material-ui/types": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-4.1.1.tgz",
"integrity": "sha512-AN+GZNXytX9yxGi0JOfxHrRTbhFybjUJ05rnsBVjcB+16e466Z0Xe5IxawuOayVZgTBNDxmPKo5j4V6OnMtaSQ==",
"requires": {
"@types/react": "*"
}
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.0.0.tgz",
"integrity": "sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg=="
},
"@material-ui/utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.4.0.tgz",
"integrity": "sha512-UXoQVwArQEQWXxf2FPs0iJGT+MePQpKr0Qh0CPoLc1OdF0GSMTmQczcqCzwZkeHxHAOq/NkIKM1Pb/ih1Avicg==",
"version": "4.9.6",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.9.6.tgz",
"integrity": "sha512-gqlBn0JPPTUZeAktn1rgMcy9Iczrr74ecx31tyZLVGdBGGzsxzM6PP6zeS7FuoLS6vG4hoZP7hWnOoHtkR0Kvw==",
"requires": {
"@babel/runtime": "^7.4.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.6"
"react-is": "^16.8.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
"requires": {
"regenerator-runtime": "^0.13.2"
"regenerator-runtime": "^0.13.4"
}
},
"react-is": {
"version": "16.10.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz",
"integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA=="
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
@ -2509,6 +2544,12 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
"integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==",
"dev": true
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
@ -3925,9 +3966,9 @@
}
},
"@types/react-transition-group": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.3.tgz",
"integrity": "sha512-Hk8jiuT7iLOHrcjKP/ZVSyCNXK73wJAUz60xm0mVhiRujrdiI++j4duLiL282VGxwAgxetHQFfqA29LgEeSkFA==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.4.tgz",
"integrity": "sha512-8DMUaDqh0S70TjkqU0DxOu80tFUiiaS9rxkWip/nb7gtvAsbqOXm02UCmR8zdcjWujgeYPiPNTVpVpKzUDotwA==",
"requires": {
"@types/react": "*"
}
@ -6906,7 +6947,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -6927,12 +6969,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -6947,17 +6991,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -7074,7 +7121,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -7086,6 +7134,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -7100,6 +7149,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -7107,12 +7157,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -7131,6 +7183,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -7211,7 +7264,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -7223,6 +7277,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -7308,7 +7363,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -7344,6 +7400,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -7363,6 +7420,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -7406,12 +7464,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -7991,11 +8051,6 @@
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"dev": true
},
"convert-css-length": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/convert-css-length/-/convert-css-length-2.0.1.tgz",
"integrity": "sha512-iGpbcvhLPRKUbBc0Quxx7w/bV14AC3ItuBEGMahA5WTYqB8lq9jH0kTXFheCBASsYnqeMFZhiTruNxr1N59Axg=="
},
"convert-source-map": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
@ -8464,17 +8519,17 @@
},
"dependencies": {
"@babel/runtime": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz",
"integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==",
"version": "7.9.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
"requires": {
"regenerator-runtime": "^0.13.2"
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
@ -8762,11 +8817,6 @@
"integrity": "sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw==",
"dev": true
},
"deepmerge": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.1.1.tgz",
"integrity": "sha512-+qO5WbNBKBaZez95TffdUDnGIo4+r5kmsX8aOb7PDHvXsTbghAmleuxjs6ytNaf5Eg4FGBXDS5vqO61TRi6BMg=="
},
"default-gateway": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz",
@ -11291,18 +11341,18 @@
"dev": true
},
"formik": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/formik/-/formik-2.0.3.tgz",
"integrity": "sha512-kYBvcxlsYSncY8OiJHD49C0UmoWXbgmIc9V1g3N1WwBJ7SMLk34QpcJDgroYd42K1cH+mSJlXhB7PlgTXTzlWg==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/formik/-/formik-2.1.4.tgz",
"integrity": "sha512-oKz8S+yQBzuQVSEoxkqqJrKQS5XJASWGVn6mrs+oTWrBoHgByVwwI1qHiVc9GKDpZBU9vAxXYAKz2BvujlwunA==",
"requires": {
"deepmerge": "^2.1.1",
"hoist-non-react-statics": "^3.3.0",
"lodash": "^4.17.14",
"lodash-es": "^4.17.14",
"react-fast-compare": "^2.0.1",
"scheduler": "^0.14.0",
"scheduler": "^0.18.0",
"tiny-warning": "^1.0.2",
"tslib": "^1.9.3"
"tslib": "^1.10.0"
},
"dependencies": {
"deepmerge": {
@ -11311,13 +11361,18 @@
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
},
"scheduler": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.14.0.tgz",
"integrity": "sha512-9CgbS06Kki2f4R9FjLSITjZo5BZxPsryiRNyL3LpvrM9WxcVmhlqAOc9E+KQbeI2nqej4JIIbOsfdL51cNb4Iw==",
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz",
"integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"tslib": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
"integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
}
}
},
@ -14690,22 +14745,48 @@
}
},
"jss-plugin-camel-case": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0.tgz",
"integrity": "sha512-yALDL00+pPR4FJh+k07A8FeDvfoPPuXU48HLy63enAubcVd3DnS+2rgqPXglHDGixIDVkCSXecl/l5GAMjzIbA==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.1.1.tgz",
"integrity": "sha512-MDIaw8FeD5uFz1seQBKz4pnvDLnj5vIKV5hXSVdMaAVq13xR6SVTVWkIV/keyTs5txxTvzGJ9hXoxgd1WTUlBw==",
"requires": {
"@babel/runtime": "^7.3.1",
"hyphenate-style-name": "^1.0.3",
"jss": "10.0.0"
"jss": "10.1.1"
},
"dependencies": {
"jss": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-plugin-default-unit": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0.tgz",
"integrity": "sha512-sURozIOdCtGg9ap18erQ+ijndAfEGtTaetxfU3H4qwC18Bi+fdvjlY/ahKbuu0ASs7R/+WKCP7UaRZOjUDMcdQ==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.1.1.tgz",
"integrity": "sha512-UkeVCA/b3QEA4k0nIKS4uWXDCNmV73WLHdh2oDGZZc3GsQtlOCuiH3EkB/qI60v2MiCq356/SYWsDXt21yjwdg==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0"
"jss": "10.1.1"
},
"dependencies": {
"jss": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-plugin-extend": {
@ -14719,50 +14800,115 @@
}
},
"jss-plugin-global": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0.tgz",
"integrity": "sha512-80ofWKSQUo62bxLtRoTNe0kFPtHgUbAJeOeR36WEGgWIBEsXLyXOnD5KNnjPqG4heuEkz9eSLccjYST50JnI7Q==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.1.1.tgz",
"integrity": "sha512-VBG3wRyi3Z8S4kMhm8rZV6caYBegsk+QnQZSVmrWw6GVOT/Z4FA7eyMu5SdkorDlG/HVpHh91oFN56O4R9m2VA==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0"
"jss": "10.1.1"
},
"dependencies": {
"jss": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-plugin-nested": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0.tgz",
"integrity": "sha512-waxxwl/po1hN3azTyixKnr8ReEqUv5WK7WsO+5AWB0bFndML5Yqnt8ARZ90HEg8/P6WlqE/AB2413TkCRZE8bA==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.1.1.tgz",
"integrity": "sha512-ozEu7ZBSVrMYxSDplPX3H82XHNQk2DQEJ9TEyo7OVTPJ1hEieqjDFiOQOxXEj9z3PMqkylnUbvWIZRDKCFYw5Q==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0",
"jss": "10.1.1",
"tiny-warning": "^1.0.2"
},
"dependencies": {
"jss": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-plugin-props-sort": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0.tgz",
"integrity": "sha512-41mf22CImjwNdtOG3r+cdC8+RhwNm616sjHx5YlqTwtSJLyLFinbQC/a4PIFk8xqf1qpFH1kEAIw+yx9HaqZ3g==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.1.1.tgz",
"integrity": "sha512-g/joK3eTDZB4pkqpZB38257yD4LXB0X15jxtZAGbUzcKAVUHPl9Jb47Y7lYmiGsShiV4YmQRqG1p2DHMYoK91g==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0"
"jss": "10.1.1"
},
"dependencies": {
"jss": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-plugin-rule-value-function": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0.tgz",
"integrity": "sha512-Jw+BZ8JIw1f12V0SERqGlBT1JEPWax3vuZpMym54NAXpPb7R1LYHiCTIlaJUyqvIfEy3kiHMtgI+r2whGgRIxQ==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.1.1.tgz",
"integrity": "sha512-ClV1lvJ3laU9la1CUzaDugEcwnpjPTuJ0yGy2YtcU+gG/w9HMInD5vEv7xKAz53Bk4WiJm5uLOElSEshHyhKNw==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.0.0"
"jss": "10.1.1"
},
"dependencies": {
"jss": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jss-plugin-vendor-prefixer": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0.tgz",
"integrity": "sha512-qslqvL0MUbWuzXJWdUxpj6mdNUX8jr4FFTo3aZnAT65nmzWL7g8oTr9ZxmTXXgdp7ANhS1QWE7036/Q2isFBpw==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.1.1.tgz",
"integrity": "sha512-09MZpQ6onQrhaVSF6GHC4iYifQ7+4YC/tAP6D4ZWeZotvCMq1mHLqNKRIaqQ2lkgANjlEot2JnVi1ktu4+L4pw==",
"requires": {
"@babel/runtime": "^7.3.1",
"css-vendor": "^2.0.6",
"jss": "10.0.0"
"css-vendor": "^2.0.7",
"jss": "10.1.1"
},
"dependencies": {
"jss": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz",
"integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
}
}
},
"jsx-ast-utils": {
@ -16348,11 +16494,6 @@
"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
"dev": true
},
"normalize-scroll-left": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.2.0.tgz",
"integrity": "sha512-t5oCENZJl8TGusJKoCJm7+asaSsPuNmK6+iEjrZ5TyBj2f02brCRsd4c83hwtu+e5d4LCSBZ0uoDlMjBo+A8yA=="
},
"normalize-url": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
@ -22170,6 +22311,12 @@
"psl": "^1.1.24",
"punycode": "^1.4.1"
}
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
@ -23393,6 +23540,12 @@
"requires": {
"websocket-driver": ">=0.5.1"
}
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
@ -24956,10 +25109,9 @@
"dev": true
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz",
"integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw=="
},
"v8-compile-cache": {
"version": "2.1.0",
@ -25267,7 +25419,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -25295,6 +25448,7 @@
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -25309,7 +25463,8 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
@ -25320,7 +25475,8 @@
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -25437,7 +25593,8 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -25449,6 +25606,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -25463,6 +25621,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -25470,12 +25629,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -25494,6 +25655,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -25583,7 +25745,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -25595,6 +25758,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -25680,7 +25844,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -25716,6 +25881,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -25735,6 +25901,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -25778,12 +25945,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -25879,6 +26048,14 @@
"requires": {
"ansi-colors": "^3.0.0",
"uuid": "^3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
"webpack-manifest-plugin": {

View file

@ -4,8 +4,9 @@
"license": "unlicense",
"dependencies": {
"@apollo/react-hooks": "^3.1.3",
"@material-ui/core": "4.5.0",
"@material-ui/icons": "4.4.3",
"@material-ui/core": "4.9.8",
"@material-ui/icons": "4.9.1",
"@material-ui/lab": "^4.0.0-alpha.47",
"@use-hooks/axios": "1.3.0",
"apollo-boost": "^0.4.7",
"axios": "0.19.0",
@ -13,7 +14,7 @@
"classnames": "2.2.6",
"downshift": "3.3.4",
"file-saver": "2.0.2",
"formik": "2.0.3",
"formik": "2.1.4",
"fuse.js": "^3.4.6",
"graphql": "^14.5.8",
"jss-plugin-extend": "^10.0.0",
@ -27,6 +28,7 @@
"react-virtualized": "^9.21.2",
"sanctuary": "^2.0.1",
"slugify": "^1.3.6",
"uuid": "^7.0.2",
"yup": "0.27.0"
},
"devDependencies": {

View file

@ -1,3 +1,4 @@
import { ApolloProvider } from '@apollo/react-hooks'
import CssBaseline from '@material-ui/core/CssBaseline'
import {
StylesProvider,
@ -5,13 +6,12 @@ import {
MuiThemeProvider,
makeStyles
} from '@material-ui/core/styles'
import ApolloClient from 'apollo-boost'
import { setAutoFreeze } from 'immer'
import { create } from 'jss'
import extendJss from 'jss-plugin-extend'
import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import ApolloClient from 'apollo-boost'
import { ApolloProvider } from '@apollo/react-hooks'
import Header from './components/Header'
import { tree, Routes } from './routing/routes'
@ -19,8 +19,23 @@ import global from './styling/global'
import theme from './styling/theme'
import { backgroundColor, mainWidth } from './styling/variables'
const defaultOptions = {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'ignore'
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all'
},
mutate: {
errorPolicy: 'all'
}
}
const client = new ApolloClient({
credentials: 'include',
defaultOptions,
uri:
process.env.NODE_ENV === 'development'
? 'https://localhost:8070/graphql/'
@ -29,7 +44,7 @@ const client = new ApolloClient({
if (process.env.NODE_ENV !== 'production') {
const whyDidYouRender = require('@welldone-software/why-did-you-render')
whyDidYouRender(React, { include: [/Logs/] })
whyDidYouRender(React)
}
// disable immer autofreeze for performance

View file

@ -73,7 +73,7 @@ export const ConfirmDialog = memo(
autoFocus
id="confirm-input"
type="text"
large
size="lg"
fullWidth
value={value}
touched={{}}

View file

@ -1,14 +1,13 @@
import { makeStyles } from '@material-ui/core/styles'
import React, { useState, memo } from 'react'
import { H4 } from 'src/components/typography'
import { Link } from 'src/components/buttons'
import { RadioGroup } from 'src/components/inputs'
import { Table, TableBody, TableRow, TableCell } from 'src/components/table'
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
import BooleanCell from 'src/components/tables/BooleanCell'
import { H4 } from 'src/components/typography'
import { ReactComponent as EditIconDisabled } from 'src/styling/icons/action/edit/disabled.svg'
import { ReactComponent as TrueIcon } from 'src/styling/icons/table/true.svg'
import { ReactComponent as FalseIcon } from 'src/styling/icons/table/false.svg'
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
import { booleanPropertiesTableStyles } from './BooleanPropertiesTable.styles'
@ -82,7 +81,7 @@ const BooleanPropertiesTable = memo(
<TableRow key={idx} size="sm" className={classes.tableRow}>
<TableCell className={classes.tableCell}>
{element.display}
{editing ? (
{editing && (
<RadioGroup
options={radioButtonOptions}
value={element.value}
@ -94,11 +93,8 @@ const BooleanPropertiesTable = memo(
}
className={classes.radioButtons}
/>
) : element.value ? (
<TrueIcon />
) : (
<FalseIcon />
)}
{!editing && <BooleanCell value={element.value} />}
</TableCell>
</TableRow>
))}

View file

@ -2,9 +2,9 @@ import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames'
import React, { memo } from 'react'
import typographyStyles from 'src/components/typography/styles'
import { ReactComponent as AddIcon } from 'src/styling/icons/button/add/zodiac.svg'
import { zircon, zircon2, comet, fontColor, white } from 'src/styling/variables'
import typographyStyles from 'src/components/typography/styles'
const { p } = typographyStyles

View file

@ -0,0 +1,27 @@
import { makeStyles, IconButton as IconB, SvgIcon } from '@material-ui/core'
import React from 'react'
const styles = {
root: {
'&:hover': {
backgroundColor: 'inherit'
}
}
}
const useStyles = makeStyles(styles)
const IconButton = ({ children, onClick, ...props }) => {
const classes = useStyles()
return (
<IconB
{...props}
classes={{ root: classes.root }}
disableRipple
onClick={onClick}>
<SvgIcon>{children}</SvgIcon>
</IconB>
)
}
export default IconButton

View file

@ -6,7 +6,8 @@ import styles from './Link.styles'
const useStyles = makeStyles(styles)
const Link = memo(({ submit, className, children, color, size, ...props }) => {
const Link = memo(
({ submit, className, children, color = 'primary', ...props }) => {
const classes = useStyles()
const classNames = {
[classes.link]: true,
@ -23,6 +24,7 @@ const Link = memo(({ submit, className, children, color, size, ...props }) => {
{children}
</button>
)
})
}
)
export default Link

View file

@ -1,11 +1,11 @@
import { fade } from '@material-ui/core/styles/colorManipulator'
import typographyStyles from 'src/components/typography/styles'
import {
white,
linkPrimaryColor,
linkSecondaryColor
} from 'src/styling/variables'
import typographyStyles from 'src/components/typography/styles'
const { h4 } = typographyStyles
@ -24,7 +24,8 @@ export default {
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer',
padding: '0'
padding: '0',
height: '100%'
},
primary: {
extend: color(linkPrimaryColor)

View file

@ -3,6 +3,7 @@ import AddButton from './AddButton'
import Button from './Button'
import FeatureButton from './FeatureButton'
import IDButton from './IDButton'
import IconButton from './IconButton'
import Link from './Link'
import SimpleButton from './SimpleButton'
@ -12,6 +13,7 @@ export {
SimpleButton,
ActionButton,
FeatureButton,
IconButton,
IDButton,
AddButton
}

View file

@ -19,15 +19,18 @@ const DataTable = memo(({ elements, data }) => {
<>
<div>
<THead>
{elements.map(({ size, className, textAlign, header }, idx) => (
{elements.map(
({ width, size, className, textAlign, header }, idx) => (
<Th
key={idx}
size={size}
width={width}
className={className}
textAlign={textAlign}>
{header}
</Th>
))}
)
)}
</THead>
</div>
<div style={{ flex: '1 1 auto' }}>
@ -52,8 +55,8 @@ const DataTable = memo(({ elements, data }) => {
{elements.map(
(
{
header,
size,
width,
className,
textAlign,
view = it => it?.toString()
@ -63,6 +66,7 @@ const DataTable = memo(({ elements, data }) => {
<Td
key={idx}
size={size}
width={width}
className={className}
textAlign={textAlign}>
{view(data[index])}

View file

@ -0,0 +1,35 @@
import React from 'react'
import { Td, THead } from 'src/components/fake-table/Table'
import { startCase } from 'src/utils/string'
import { ACTION_COL_SIZE, DEFAULT_COL_SIZE } from './consts'
const Header = ({ elements, enableEdit, enableDelete }) => {
const actionColSize =
enableDelete && enableEdit ? ACTION_COL_SIZE / 2 : ACTION_COL_SIZE
return (
<THead>
{elements.map(
({ name, width = DEFAULT_COL_SIZE, header, textAlign }, idx) => (
<Td header key={idx} width={width} textAlign={textAlign}>
{header || startCase(name)}
</Td>
)
)}
{enableEdit && (
<Td header width={actionColSize} textAlign="right">
Edit
</Td>
)}
{enableDelete && (
<Td header width={actionColSize} textAlign="right">
Delete
</Td>
)}
</THead>
)
}
export default Header

View file

@ -1,250 +1,150 @@
import React, { memo } from 'react'
import * as R from 'ramda'
import classnames from 'classnames'
import { Form, Formik, Field, useFormikContext } from 'formik'
import { makeStyles } from '@material-ui/core'
import classnames from 'classnames'
import { Field, useFormikContext } from 'formik'
import React from 'react'
import { Link } from 'src/components/buttons'
import { Td, Tr, CellDoubleLevel } from 'src/components/fake-table/Table'
import { TextInputDisplay } from 'src/components/inputs/base/TextInput'
import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
import { Link, IconButton } from 'src/components/buttons'
import { Td, Tr } from 'src/components/fake-table/Table'
import { TL2 } from 'src/components/typography'
import { ReactComponent as DisabledDeleteIcon } from 'src/styling/icons/action/delete/disabled.svg'
// import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
// import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg'
import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg'
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
const styles = {
button: {
border: 'none',
backgroundColor: 'transparent',
outline: 0,
cursor: 'pointer'
},
actionCol: {
display: 'flex',
marginLeft: 'auto'
},
actionColDisplayMode: {
justifyContent: 'center'
},
actionColEditMode: {
justifyContent: 'flex-end',
'& > :first-child': {
marginRight: 16
}
},
textInput: {
'& > .MuiInputBase-input': {
width: 282
}
}
// doubleLevelRow: {
// '& > div': {
// marginRight: 72
// }
// }
}
import styles from './Row.styles'
import { ACTION_COL_SIZE } from './consts'
const useStyles = makeStyles(styles)
const ERow = memo(
({ elements, editing, setEditing, disableAction, action }) => {
const ActionCol = ({
editing,
setEditing,
enableEdit,
disabled,
onDelete,
enableDelete
}) => {
const classes = useStyles()
const { values, submitForm, resetForm } = useFormikContext()
const Cell = ({
const actionColSize =
enableDelete && enableEdit ? ACTION_COL_SIZE / 2 : ACTION_COL_SIZE
return (
<>
{editing && (
<Td textAlign="center" width={ACTION_COL_SIZE}>
<Link
className={classes.cancelButton}
color="secondary"
onClick={resetForm}>
Cancel
</Link>
<Link color="primary" onClick={submitForm}>
Save
</Link>
</Td>
)}
{!editing && enableEdit && (
<Td textAlign="right" width={actionColSize}>
<IconButton
disabled={disabled}
className={classes.editButton}
onClick={() => setEditing && setEditing(values.id)}>
{disabled ? <DisabledEditIcon /> : <EditIcon />}
</IconButton>
</Td>
)}
{!editing && enableDelete && (
<Td textAlign="right" width={actionColSize}>
<IconButton disabled={disabled} onClick={() => onDelete(values.id)}>
{disabled ? <DisabledDeleteIcon /> : <DeleteIcon />}
</IconButton>
</Td>
)}
</>
)
}
const ECol = ({ editing, config }) => {
const {
name,
input,
type,
display,
className,
size,
bold,
width,
textAlign,
inputProps,
editing
}) => {
suffix,
view = it => it?.toString(),
inputProps = {}
} = config
const { values } = useFormikContext()
const classes = useStyles({ textAlign, size })
const viewClasses = {
[classes.bold]: bold,
[classes.size]: true
}
const iProps = {
fullWidth: true,
size,
bold,
textAlign,
...inputProps
}
// Autocomplete
if (iProps.options && !iProps.getLabel) {
iProps.getLabel = view
}
return (
<Td size={size} textAlign={textAlign}>
{editing && (
<Field
id={name}
name={name}
component={input}
className={className}
{...inputProps}
/>
)}
{!editing && type === 'text' && (
<TextInputDisplay display={display} {...inputProps} />
<Td
className={{ [classes.withSuffix]: suffix }}
width={width}
size={size}
textAlign={textAlign}>
{editing && <Field name={name} component={input} {...iProps} />}
{!editing && values && (
<div className={classnames(viewClasses)}>{view(values[name])}</div>
)}
{suffix && <TL2 className={classes.suffix}>{suffix}</TL2>}
</Td>
)
}
}
const actionCol = R.last(elements)
const { values, errors } = useFormikContext()
const actionColClasses = {
[classes.actionCol]: true,
[classes.actionColDisplayMode]: !editing,
[classes.actionColEditMode]: editing
}
const icon = (action, disabled) => {
if (action === 'delete' && !disabled) return <DeleteIcon />
if (action === 'delete' && disabled) return <DisabledDeleteIcon />
}
const ERow = ({
elements,
enableEdit,
enableDelete,
onDelete,
editing,
setEditing,
disabled
}) => {
const { errors } = useFormikContext()
return (
<Tr
error={errors && errors.length}
errorMessage={errors && errors.toString()}>
{R.init(elements).map((element, idx) => {
const colClasses = {
[classes.textInput]: true
}
if (Array.isArray(element)) {
return (
<CellDoubleLevel key={idx} className={classes.doubleLevelRow}>
{R.map(
(
{
name,
input,
size,
textAlign,
type,
view = it => it?.toString(),
inputProps
},
idx
) => (
<Cell
key={name}
name={name}
input={input}
type={type}
display={view(values[name])}
className={classnames(colClasses)}
size={size}
textAlign={textAlign}
inputProps={inputProps}
{elements.map((it, idx) => (
<ECol key={idx} config={it} editing={editing} />
))}
{(enableEdit || enableDelete) && (
<ActionCol
disabled={disabled}
editing={editing}
setEditing={setEditing}
onDelete={onDelete}
enableEdit={enableEdit}
enableDelete={enableDelete}
/>
// <Td size={sizes.cashOut1} textAlign="right">
// <Field
// editing={editing}
// field={fields[CASSETTE_1_KEY]}
// displayValue={x => (x === '' ? '-' : x)}
// decoration="%"
// className={classes.eRowField}
// setError={setError}
// />
// </Td>
)
)(R.tail(element))}
</CellDoubleLevel>
)
}
const {
name,
input,
size,
textAlign,
type,
view = it => it?.toString(),
inputProps
} = element
return (
<Cell
key={idx}
name={name}
input={input}
type={type}
display={view(values[name])}
className={classnames(colClasses)}
size={size}
textAlign={textAlign}
inputProps={inputProps}
editing={editing}
/>
// <Td key={idx} size={size} textAlign={textAlign}>
// {editing && (
// <Field
// id={name}
// name={name}
// component={input}
// className={classnames(colClasses)}
// {...inputProps}
// />
// )}
// {!editing && type === 'text' && (
// <TextInputDisplay
// display={view(values[name])}
// {...inputProps}
// />
// )}
// </Td>
)
})}
<Td size={actionCol.size} className={classnames(actionColClasses)}>
{!editing && !disableAction && (
<button
type="button"
className={classes.button}
onClick={() => action(values)}>
{icon(actionCol.name, disableAction)}
</button>
)}
{!editing && disableAction && (
<div>{icon(actionCol.name, disableAction)}</div>
)}
{editing && (
<>
<Link color="secondary" type="reset">
Cancel
</Link>
<Link color="primary" type="submit">
Save
</Link>
</>
)}
</Td>
</Tr>
)
}
)
}
const ERowWithFormik = memo(
({
initialValues,
validationSchema,
save,
reset,
action,
elements,
editing,
disableAction
}) => {
return (
<Formik
enableReinitialize
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={save}
onReset={reset}>
<Form>
<ERow
elements={elements}
editing={editing}
disableAction={disableAction}
action={action}
/>
</Form>
</Formik>
)
}
)
export default ERowWithFormik
export default ERow

View file

@ -0,0 +1,17 @@
import { bySize, bold } from 'src/styling/helpers'
export default {
cancelButton: {
marginRight: 20
},
withSuffix: ({ textAlign }) => ({
display: 'flex',
alignItems: 'baseline',
justifyContent: textAlign === 'right' && 'end'
}),
suffix: {
marginLeft: 7
},
size: ({ size }) => bySize(size),
bold
}

View file

@ -1,124 +1,152 @@
import React, { memo } from 'react'
import { makeStyles } from '@material-ui/core'
import { Form, Formik } from 'formik'
import * as R from 'ramda'
import React, { useState } from 'react'
import { v4 } from 'uuid'
import {
Th,
ThDoubleLevel,
THead,
TBody,
Table,
TDoubleLevelHead
} from 'src/components/fake-table/Table'
import { startCase } from 'src/utils/string'
import Link from 'src/components/buttons/Link.js'
import { AddButton } from 'src/components/buttons/index.js'
import { TBody, Table } from 'src/components/fake-table/Table'
import { Info2 } from 'src/components/typography'
import Header from './Header'
import ERow from './Row'
import styles from './Table.styles'
import { DEFAULT_COL_SIZE, ACTION_COL_SIZE } from './consts'
const ETHead = memo(({ elements, className }) => {
const action = R.last(elements)
const useStyles = makeStyles(styles)
return (
<THead className={className?.root}>
{R.init(elements).map(({ name, size, display, textAlign }, idx) => (
<Th
id={name}
key={idx}
size={size}
textAlign={textAlign}
className={className?.cell}>
{display}
</Th>
))}
<Th size={action.size} action>
{startCase(action.name)}
</Th>
</THead>
)
})
const getWidth = R.compose(
R.reduce(R.add)(0),
R.map(it => it.width ?? DEFAULT_COL_SIZE)
)
const ETDoubleHead = memo(({ elements, className }) => {
const action = R.last(elements)
return (
<TDoubleLevelHead className={className?.root}>
{R.init(elements).map((element, idx) => {
if (Array.isArray(element)) {
return (
<ThDoubleLevel
key={idx}
title={element[0].display}
className={className?.cell}>
{R.map(({ name, size, display, textAlign }) => (
<Th key={name} id={name} size={size} textAlign={textAlign}>
{display}
</Th>
))(R.tail(element))}
</ThDoubleLevel>
)
}
const { name, size, display, textAlign } = element
return (
<Th id={idx} key={name} size={size} textAlign={textAlign}>
{display}
</Th>
)
})}
<Th size={action.size} action>
{startCase(action.name)}
</Th>
</TDoubleLevelHead>
)
})
const ETable = memo(
({
const ETable = ({
name,
title,
elements = [],
data = [],
save,
reset,
action,
initialValues,
validationSchema,
editing,
addingRow,
disableAction,
className,
double
}) => {
enableCreate,
forceDisable,
disableAdd,
enableDelete,
initialValues,
enableEdit,
setEditing,
createText = 'Add override'
}) => {
const [editingId, setEditingId] = useState(null)
const [adding, setAdding] = useState(false)
const innerSave = async it => {
const index = R.findIndex(R.propEq('id', it.id))(data)
const list = index !== -1 ? R.update(index, it, data) : R.prepend(it, data)
// no response means the save failed
const response = await save({ [name]: list })
if (!response) return
setAdding(false)
setEditingId(null)
}
const onDelete = id => {
const list = R.reject(it => it.id === id, data)
return save({ [name]: list })
}
const onReset = () => {
setAdding(false)
setEditingId(null)
setEditing && setEditing(false)
}
const onEdit = it => {
setEditingId(it)
setEditing && setEditing(it, true)
}
const addField = () => setAdding(true)
const actionSize = enableEdit || enableDelete ? ACTION_COL_SIZE : 0
const width = getWidth(elements) + actionSize
const classes = useStyles({ width })
const showButtonOnEmpty = !data.length && enableCreate && !adding
const canAdd = !forceDisable && !editingId && !disableAdd && !adding
const showTable = adding || data.length !== 0
return (
<Table className={className}>
{!double && <ETHead elements={elements} />}
{double && (
<ETDoubleHead elements={elements} className={className?.head} />
<div className={classes.wrapper}>
{showButtonOnEmpty && (
<AddButton disabled={!canAdd} onClick={addField}>
{createText}
</AddButton>
)}
<TBody>
{addingRow && (
<ERow
{showTable && (
<>
<div className={classes.outerHeader}>
{title && <Info2 className={classes.title}>{title}</Info2>}
{enableCreate && canAdd && (
<Link className={classes.addLink} onClick={addField}>
{createText}
</Link>
)}
</div>
<Table>
<Header
elements={elements}
initialValues={initialValues}
save={save}
reset={reset}
validationSchema={validationSchema}
editing
enableEdit={enableEdit}
enableDelete={enableDelete}
/>
<TBody>
{adding && (
<Formik
initialValues={{ id: v4(), ...initialValues }}
onReset={onReset}
validationSchema={validationSchema}
onSubmit={innerSave}>
<Form>
<ERow
editing={true}
disabled={forceDisable}
enableEdit={enableEdit}
enableDelete={enableDelete}
elements={elements}
/>
</Form>
</Formik>
)}
{data.map((it, idx) => (
<ERow
key={idx}
<Formik
key={it.id ?? idx}
enableReinitialize
initialValues={it}
elements={elements}
save={save}
reset={it => reset(it)}
action={action}
onReset={onReset}
validationSchema={validationSchema}
disableAction={disableAction}
editing={editing[idx]}
onSubmit={innerSave}>
<Form>
<ERow
editing={editingId === it.id}
disabled={
forceDisable || (editingId && editingId !== it.id)
}
setEditing={onEdit}
onDelete={onDelete}
enableEdit={enableEdit}
enableDelete={enableDelete}
elements={elements}
/>
</Form>
</Formik>
))}
</TBody>
</Table>
</>
)}
</div>
)
}
)
}
export default ETable

View file

@ -0,0 +1,21 @@
import { offColor } from 'src/styling/variables'
export default {
wrapper: ({ width }) => ({
width: width
}),
addLink: {
marginLeft: 'auto'
},
title: {
margin: 0,
color: offColor
},
outerHeader: {
minHeight: 16,
marginBottom: 24,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}
}

View file

@ -0,0 +1,4 @@
const ACTION_COL_SIZE = 175
const DEFAULT_COL_SIZE = 100
export { ACTION_COL_SIZE, DEFAULT_COL_SIZE }

View file

@ -8,10 +8,10 @@ import {
CellMeasurerCache
} from 'react-virtualized'
import { THead, Tr, Td, Th } from 'src/components/fake-table/Table'
import { ReactComponent as ExpandClosedIcon } from 'src/styling/icons/action/expand/closed.svg'
import { ReactComponent as ExpandOpenIcon } from 'src/styling/icons/action/expand/open.svg'
import { mainWidth } from 'src/styling/variables'
import { THead, Tr, Td, Th } from 'src/components/fake-table/Table'
const styles = {
expandButton: {
@ -48,25 +48,19 @@ const ExpRow = ({
.slice(0, -1)
.map(
(
{
header,
size,
className,
textAlign,
view = it => it?.toString()
},
{ width, className, textAlign, view = it => it?.toString() },
idx
) => (
<Td
key={idx}
size={size}
width={width}
className={className}
textAlign={textAlign}>
{view(data)}
</Td>
)
)}
<Td size={elements[elements.length - 1].size}>
<Td width={elements[elements.length - 1].width}>
<button
onClick={() => expandRow(id)}
className={classes.expandButton}>
@ -77,7 +71,7 @@ const ExpRow = ({
</Tr>
{expanded && (
<Tr className={classes.detailsRow}>
<Td size={mainWidth}>
<Td width={mainWidth}>
<Details it={data} />
</Td>
</Tr>
@ -86,8 +80,8 @@ const ExpRow = ({
)
}
/* rows = [{ columns = [{ name, value, className, textAlign, size }], details, className, error, errorMessage }]
* Don't forget to include the size of the last (expand button) column!
/* rows = [{ columns = [{ name, value, className, textAlign, width }], details, className, error, errorMessage }]
* Don't forget to include the width of the last (expand button) column!
*/
const ExpTable = ({
elements = [],
@ -133,10 +127,10 @@ const ExpTable = ({
<>
<div>
<THead>
{elements.map(({ size, className, textAlign, header }, idx) => (
{elements.map(({ width, className, textAlign, header }, idx) => (
<Th
key={idx}
size={size}
width={width}
className={className}
textAlign={textAlign}>
{header}

View file

@ -5,103 +5,10 @@ import classnames from 'classnames'
import React from 'react'
import { Link } from 'src/components/buttons'
import {
tableHeaderColor,
tableHeaderHeight,
tableErrorColor,
spacer,
white,
tableDoubleHeaderHeight,
offColor
} from 'src/styling/variables'
import typographyStyles from 'src/components/typography/styles'
const { tl2, p, label1 } = typographyStyles
import styles from './Table.styles'
const useStyles = makeStyles({
body: {
borderSpacing: [[0, 4]]
},
header: {
extend: tl2,
backgroundColor: tableHeaderColor,
height: tableHeaderHeight,
textAlign: 'left',
color: white,
display: 'flex',
alignItems: 'center'
},
doubleHeader: {
extend: tl2,
backgroundColor: tableHeaderColor,
height: tableDoubleHeaderHeight,
color: white,
display: 'table-row'
},
thDoubleLevel: {
padding: [[0, spacer * 2]],
display: 'table-cell',
'& > :first-child': {
extend: label1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: offColor,
color: white,
borderRadius: [[0, 0, 8, 8]],
height: 28
},
'& > :last-child': {
display: 'table-cell',
verticalAlign: 'middle',
height: tableDoubleHeaderHeight - 28,
'& > div': {
display: 'inline-block'
}
}
},
cellDoubleLevel: {
display: 'flex',
padding: [[0, spacer * 2]]
},
td: {
padding: [[0, spacer * 3]]
},
tdHeader: {
verticalAlign: 'middle',
display: 'table-cell',
padding: [[0, spacer * 3]]
},
trError: {
backgroundColor: tableErrorColor
},
mainContent: {
display: 'flex',
alignItems: 'center',
minHeight: 54
},
// mui-overrides
cardContentRoot: {
// display: 'flex',
margin: 0,
padding: 0,
'&:last-child': {
padding: 0
}
},
card: {
extend: p,
'&:before': {
height: 0
},
margin: [[4, 0]],
width: '100%',
boxShadow: [[0, 0, 4, 0, 'rgba(0, 0, 0, 0.08)']]
},
actionCol: {
marginLeft: 'auto'
}
})
const useStyles = makeStyles(styles)
const Table = ({ children, className, ...props }) => (
<div className={classnames(className)} {...props}>
@ -129,21 +36,25 @@ const TBody = ({ children, className }) => {
return <div className={classnames(className, classes.body)}>{children}</div>
}
const Td = ({ children, header, className, size = 100, textAlign, action }) => {
const classes = useStyles()
const Td = ({
children,
header,
className,
width = 100,
size,
textAlign,
action
}) => {
const classes = useStyles({ textAlign, width })
const classNames = {
[classes.td]: true,
[classes.tdHeader]: header,
[classes.actionCol]: action
[classes.actionCol]: action,
[classes.large]: size === 'lg' && !header,
[classes.md]: size === 'md' && !header
}
return (
<div
className={classnames(className, classNames)}
style={{ width: size, textAlign }}>
{children}
</div>
)
return <div className={classnames(className, classNames)}>{children}</div>
}
const Th = ({ children, ...props }) => {

View file

@ -0,0 +1,105 @@
import typographyStyles from 'src/components/typography/styles'
import {
tableHeaderColor,
tableHeaderHeight,
tableErrorColor,
spacer,
white,
tableDoubleHeaderHeight,
offColor
} from 'src/styling/variables'
const { tl1, info2, tl2, p, label1 } = typographyStyles
export default {
body: {
borderSpacing: [[0, 4]]
},
large: {
extend: tl1
},
md: {
extend: info2
},
header: {
extend: tl2,
backgroundColor: tableHeaderColor,
height: tableHeaderHeight,
textAlign: 'left',
color: white,
display: 'flex',
alignItems: 'center'
},
doubleHeader: {
extend: tl2,
backgroundColor: tableHeaderColor,
height: tableDoubleHeaderHeight,
color: white,
display: 'table-row'
},
thDoubleLevel: {
padding: [[0, spacer * 2]],
display: 'table-cell',
'& > :first-child': {
extend: label1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: offColor,
color: white,
borderRadius: [[0, 0, 8, 8]],
height: 28
},
'& > :last-child': {
display: 'table-cell',
verticalAlign: 'middle',
height: tableDoubleHeaderHeight - 28,
'& > div': {
display: 'inline-block'
}
}
},
cellDoubleLevel: {
display: 'flex',
padding: [[0, spacer * 2]]
},
td: ({ textAlign, width }) => ({
width,
padding: [[1, spacer * 3, 0, spacer * 3]],
textAlign
}),
tdHeader: {
verticalAlign: 'middle',
display: 'table-cell',
padding: [[0, spacer * 3]]
},
trError: {
backgroundColor: tableErrorColor
},
mainContent: {
display: 'flex',
alignItems: 'center',
minHeight: 54
},
// mui-overrides
cardContentRoot: {
// display: 'flex',
margin: 0,
padding: 0,
'&:last-child': {
padding: 0
}
},
card: {
extend: p,
'&:before': {
height: 0
},
margin: [[4, 0]],
width: '100%',
boxShadow: [[0, 0, 4, 0, 'rgba(0, 0, 0, 0.08)']]
},
actionCol: {
marginLeft: 'auto'
}
}

View file

@ -1,120 +0,0 @@
import Paper from '@material-ui/core/Paper'
import Popper from '@material-ui/core/Popper'
import { withStyles } from '@material-ui/core/styles'
import Downshift from 'downshift'
import * as R from 'ramda'
import React, { memo, useState } from 'react'
import {
renderInput,
renderSuggestion,
filterSuggestions,
styles
} from './commons'
const Autocomplete = memo(
({
suggestions,
classes,
placeholder,
label,
itemToString,
code = 'code',
display = 'display',
...props
}) => {
const { name, value, onBlur } = props.field
const { touched, errors, setFieldValue } = props.form
const [popperNode, setPopperNode] = useState(null)
return (
<Downshift
id={name}
itemToString={it => {
if (itemToString) return itemToString(it)
if (it) return it[display]
return undefined
}}
onChange={it => setFieldValue(name, it)}
defaultHighlightedIndex={0}
selectedItem={value}>
{({
getInputProps,
getItemProps,
getMenuProps,
isOpen,
inputValue: inputValue2,
selectedItem: selectedItem2,
highlightedIndex,
inputValue,
toggleMenu,
clearSelection
}) => (
<div className={classes.container}>
{renderInput({
name,
fullWidth: true,
error:
(touched[`${name}-input`] || touched[name]) && errors[name],
success:
(touched[`${name}-input`] || touched[name] || value) &&
!errors[name],
InputProps: getInputProps({
value: inputValue2 || '',
placeholder,
onBlur,
onClick: event => {
setPopperNode(event.currentTarget.parentElement)
toggleMenu()
},
onChange: it => {
if (it.target.value === '') {
clearSelection()
}
inputValue = it.target.value
}
}),
label
})}
<Popper
open={isOpen}
anchorEl={popperNode}
modifiers={{ flip: { enabled: true } }}>
<div
{...(isOpen
? getMenuProps({}, { suppressRefError: true })
: {})}>
<Paper
square
style={{
minWidth: popperNode ? popperNode.clientWidth + 2 : null
}}>
{filterSuggestions(
suggestions,
inputValue2,
value ? R.of(value) : [],
code,
display
).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion }),
highlightedIndex,
selectedItem: selectedItem2,
code,
display
})
)}
</Paper>
</div>
</Popper>
</div>
)}
</Downshift>
)
}
)
export default withStyles(styles)(Autocomplete)

View file

@ -1,123 +0,0 @@
import Paper from '@material-ui/core/Paper'
import Popper from '@material-ui/core/Popper'
import { withStyles } from '@material-ui/core/styles'
import Downshift from 'downshift'
import React, { useState, memo } from 'react'
import Chip from 'src/components/Chip'
import {
renderInput,
renderSuggestion,
filterSuggestions,
styles
} from './commons'
const AutocompleteMultiple = memo(
({ suggestions, classes, placeholder, label, ...props }) => {
const { name, value, onBlur } = props.field
const { touched, errors, setFieldValue } = props.form
const [inputValue, setInputValue] = useState('')
const [popperNode, setPopperNode] = useState(null)
const onDelete = item => {
const selectedItem = (value || []).slice()
const index = selectedItem.indexOf(item)
if (index === -1) return
selectedItem.splice(selectedItem.indexOf(item), 1)
setFieldValue(name, selectedItem)
}
const handleKeyDown = event => {
if (value.length && !inputValue.length && event.key === 'Backspace') {
onDelete(value[value.length - 1])
}
}
return (
<Downshift
id={name}
inputValue={inputValue}
itemToString={it => it && it.display}
defaultHighlightedIndex={0}
onChange={it => {
setInputValue('')
let selectedItem = (value || []).slice()
if (selectedItem.indexOf(it) === -1) {
selectedItem = [...selectedItem, it]
}
setFieldValue(name, selectedItem)
}}
selectedItem={value}>
{({
getInputProps,
getItemProps,
getMenuProps,
isOpen,
inputValue: inputValue2,
selectedItem: selectedItem2,
highlightedIndex,
toggleMenu
}) => (
<div className={classes.container}>
{renderInput({
id: name,
fullWidth: true,
classes,
error:
(touched[`${name}-input`] || touched[name]) && errors[name],
success:
(touched[`${name}-input`] || touched[name] || value) &&
!errors[name],
InputProps: getInputProps({
startAdornment: (value || []).map(item => (
<Chip key={item.code} tabIndex={-1} label={item.code} />
)),
onBlur,
onChange: it => setInputValue(it.target.value),
onClick: () => toggleMenu(),
onKeyDown: handleKeyDown,
placeholder
}),
ref: node => {
setPopperNode(node)
},
label
})}
<Popper open={isOpen} anchorEl={popperNode}>
<div
{...(isOpen
? getMenuProps({}, { suppressRefError: true })
: {})}>
<Paper
className={classes.paper}
square
style={{
marginTop: 8,
minWidth: popperNode ? popperNode.clientWidth : null
}}>
{filterSuggestions(suggestions, inputValue2, value).map(
(suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion }),
highlightedIndex,
selectedItem: selectedItem2
})
)}
</Paper>
</div>
</Popper>
</div>
)}
</Downshift>
)
}
)
export default withStyles(styles)(AutocompleteMultiple)

View file

@ -1,133 +0,0 @@
import MenuItem from '@material-ui/core/MenuItem'
import Fuse from 'fuse.js'
import React from 'react'
import slugify from 'slugify'
import { withStyles } from '@material-ui/core/styles'
import {
fontColor,
inputFontSize,
inputFontWeight,
zircon
} from 'src/styling/variables'
import S from 'src/utils/sanctuary'
import { TextInput } from '../base'
function renderInput({ InputProps, error, name, success, ...props }) {
const { onChange, onBlur, value } = InputProps
return (
<TextInput
name={name}
onChange={onChange}
onBlur={onBlur}
value={value}
error={!!error}
InputProps={InputProps}
{...props}
/>
)
}
function renderSuggestion({
suggestion,
index,
itemProps,
highlightedIndex,
selectedItem,
code,
display
}) {
const isHighlighted = highlightedIndex === index
const StyledMenuItem = withStyles(theme => ({
root: {
fontSize: 14,
fontWeight: 400,
color: fontColor
},
selected: {
'&.Mui-selected, &.Mui-selected:hover': {
fontWeight: 500,
backgroundColor: zircon
}
}
}))(MenuItem)
return (
<StyledMenuItem
{...itemProps}
key={suggestion[code]}
selected={isHighlighted}
component="div">
{suggestion[display]}
</StyledMenuItem>
)
}
function filterSuggestions(
suggestions = [],
value = '',
currentValues = [],
code,
display
) {
const options = {
shouldSort: true,
threshold: 0.2,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
code,
display
}
const fuse = new Fuse(suggestions, options)
const result = value ? fuse.search(slugify(value, ' ')) : suggestions
const currentCodes = S.map(S.prop(code))(currentValues)
const filtered = S.filter(it => !S.elem(it[code])(currentCodes))(result)
const amountToTake = S.min(filtered.length)(5)
return S.compose(S.fromMaybe([]))(S.take(amountToTake))(filtered)
}
const styles = theme => ({
root: {
flexGrow: 1,
height: 250
},
container: {
flexGrow: 1,
position: 'relative'
},
paper: {
// position: 'absolute',
zIndex: 1,
marginTop: theme.spacing(1),
left: 0,
right: 0
},
inputRoot: {
fontSize: inputFontSize,
color: fontColor,
fontWeight: inputFontWeight,
flexWrap: 'wrap'
},
inputInput: {
flex: 1
},
success: {
'&:after': {
transform: 'scaleX(1)'
}
},
divider: {
height: theme.spacing(2)
}
})
export { renderInput, renderSuggestion, filterSuggestions, styles }

View file

@ -0,0 +1,69 @@
import MAutocomplete, {
createFilterOptions
} from '@material-ui/lab/Autocomplete'
import * as R from 'ramda'
import React from 'react'
import TextInput from './TextInput'
const Autocomplete = ({
limit = 5,
options,
label,
shouldAdd,
getOptionSelected,
forceShowValue,
value,
onChange,
multiple,
getLabel,
error,
fullWidth,
textAlign,
size,
...props
}) => {
let iOptions = options
const compare = getOptionSelected || R.equals
const find = R.find(it => compare(value, it))
if (forceShowValue && !multiple && value && !find(options)) {
iOptions = R.concat(options, [value])
}
return (
<MAutocomplete
options={iOptions}
multiple={multiple}
value={value}
onChange={onChange}
getOptionLabel={getLabel}
forcePopupIcon={false}
filterOptions={createFilterOptions({ ignoreAccents: true, limit })}
openOnFocus
autoHighlight
disableClearable
ChipProps={{ onDelete: null }}
blurOnSelect
clearOnEscape
getOptionSelected={getOptionSelected}
{...props}
renderInput={params => {
return (
<TextInput
{...params}
label={label}
value={value}
error={error}
size={size}
fullWidth={fullWidth}
textAlign={textAlign}
/>
)
}}
/>
)
}
export default Autocomplete

View file

@ -19,8 +19,6 @@ const useStyles = makeStyles({
const CheckboxInput = ({ name, onChange, value, label, ...props }) => {
const classes = useStyles()
// const { name, onChange, value } = props.field
return (
<Checkbox
id={name}

View file

@ -1,105 +1,12 @@
import React, { memo } from 'react'
import classnames from 'classnames'
import { makeStyles } from '@material-ui/core'
import TextField from '@material-ui/core/TextField'
import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames'
import * as R from 'ramda'
import React, { memo } from 'react'
import {
fontColor,
offColor,
secondaryColor,
inputFontSize,
inputFontSizeLg,
inputFontWeight,
inputFontWeightLg
} from 'src/styling/variables'
import { TL2, Label2, Info1, Info2 } from 'src/components/typography'
import styles from './TextInput.styles'
const useStyles = makeStyles({
wrapper: {
display: 'inline-block',
maxWidth: '100%',
'& > span': {
display: 'flex',
alignItems: 'baseline',
'& > p:first-child': {
margin: [[0, 4, 5, 0]]
},
'&> p:last-child': {
margin: [[0, 0, 0, 3]]
}
}
},
inputRoot: {
fontSize: inputFontSize,
color: fontColor,
fontWeight: inputFontWeight,
paddingLeft: 4,
'& > .MuiInputBase-input': {
width: 282
}
},
inputRootLg: {
fontSize: inputFontSizeLg,
color: fontColor,
fontWeight: inputFontWeightLg,
'& > .MuiInputBase-input': {
width: 96
}
},
labelRoot: {
color: fontColor,
paddingLeft: 4
},
root: {
'& > .MuiInput-underline:before': {
borderBottom: [[2, 'solid', fontColor]]
},
'& .Mui-focused': {
color: fontColor
},
'& input': {
paddingTop: 4,
paddingBottom: 3
},
'& .MuiInputBase-inputMultiline': {
width: 500,
paddingRight: 20
}
},
empty: {
'& .MuiInputLabel-root:not(.MuiFormLabel-filled):not(.MuiInputLabel-shrink)': {
color: offColor
},
'& .MuiInputLabel-formControl:not(.MuiInputLabel-shrink)': {
top: -2
}
},
filled: {
'& .MuiInput-underline:before': {
borderBottomColor: secondaryColor
},
'& .MuiInput-underline:hover:not(.Mui-disabled)::before': {
borderBottomColor: secondaryColor
}
}
})
const TextInputDisplay = memo(({ display, suffix, large }) => {
const classes = useStyles()
return (
<div className={classes.wrapper}>
<span>
{large && !suffix && <span>{display}</span>}
{!large && !suffix && <span>{display}</span>}
{large && suffix && <Info1>{display}</Info1>}
{!large && suffix && <Info2>{display}</Info2>}
{suffix && large && <TL2>{suffix}</TL2>}
{suffix && !large && <Label2>{suffix}</Label2>}
</span>
</div>
)
})
const useStyles = makeStyles(styles)
const TextInput = memo(
({
@ -109,22 +16,23 @@ const TextInput = memo(
value,
error,
suffix,
large,
textAlign,
width,
// lg or sm
size,
bold,
className,
InputProps,
...props
}) => {
const classes = useStyles()
const classes = useStyles({ textAlign, width, size })
const filled = !error && value && !R.isEmpty(value)
const classNames = {
[className]: true,
[classes.filled]: !error && value,
[classes.empty]: !value || value === ''
const inputClasses = {
[classes.bold]: bold
}
return (
<div className={classes.wrapper}>
<span>
<TextField
id={name}
onChange={onChange}
@ -132,24 +40,19 @@ const TextInput = memo(
error={error}
value={value}
classes={{ root: classes.root }}
className={classnames(classNames)}
className={className}
InputProps={{
className: large ? classes.inputRootLg : classes.inputRoot,
className: classnames(inputClasses),
classes: {
root: classes.size,
underline: filled ? classes.underline : null
},
...InputProps
}}
InputLabelProps={{ className: classes.labelRoot }}
{...props}
/>
{suffix && large && (
<>
<TL2>{suffix}</TL2>
</>
)}
{suffix && !large && <Label2>{suffix}</Label2>}
</span>
</div>
)
}
)
export { TextInput, TextInputDisplay }
export default TextInput

View file

@ -0,0 +1,21 @@
import { bySize, bold } from 'src/styling/helpers'
import { secondaryColor } from 'src/styling/variables'
export default {
size: ({ size }) => bySize(size),
bold,
root: ({ width, textAlign }) => ({
width,
'& input': {
textAlign
}
}),
underline: {
'&:before': {
borderBottomColor: secondaryColor
},
'&:hover:not(.Mui-disabled)::before': {
borderBottomColor: secondaryColor
}
}
}

View file

@ -1,6 +1,7 @@
import Autocomplete from './Autocomplete'
import Checkbox from './Checkbox'
import { TextInput } from './TextInput'
import Switch from './Switch'
import RadioGroup from './RadioGroup'
import Switch from './Switch'
import TextInput from './TextInput'
export { Checkbox, TextInput, Switch, RadioGroup }
export { Checkbox, TextInput, Switch, RadioGroup, Autocomplete }

View file

@ -0,0 +1,22 @@
import React from 'react'
import { Autocomplete } from '../base'
const AutocompleteFormik = props => {
const { name, onBlur, value } = props.field
const { touched, errors, setFieldValue } = props.form
const error = !!(touched[name] && errors[name])
return (
<Autocomplete
name={name}
onChange={(event, item) => setFieldValue(name, item)}
onBlur={onBlur}
value={value}
error={error}
{...props}
/>
)
}
export default AutocompleteFormik

View file

@ -2,7 +2,7 @@ import React, { memo } from 'react'
import { Checkbox } from '../base'
const CheckboxInput = memo(({ label, ...props }) => {
const CheckboxInput = memo(({ label, textAlign, fullWidth, ...props }) => {
const { name, onChange, value } = props.field
return <Checkbox name={name} onChange={onChange} value={value} {...props} />

View file

@ -1,10 +1,10 @@
import typographyStyles from 'src/components/typography/styles'
import {
fontColor,
offColor,
inputFontSize,
inputFontWeight
} from 'src/styling/variables'
import typographyStyles from 'src/components/typography/styles'
const { info3 } = typographyStyles

View file

@ -1,19 +1,8 @@
import Autocomplete from './autocomplete/Autocomplete'
import AutocompleteMultiple from './autocomplete/AutocompleteMultiple'
import Checkbox from './base/Checkbox'
import Radio from './base/Radio'
import RadioGroup from './base/RadioGroup'
import Select from './base/Select'
import Switch from './base/Switch'
import { TextInput } from './base/TextInput'
import TextInput from './base/TextInput'
export {
Autocomplete,
AutocompleteMultiple,
TextInput,
Radio,
Checkbox,
Switch,
Select,
RadioGroup
}
export { TextInput, Radio, Checkbox, Switch, Select, RadioGroup }

View file

@ -0,0 +1,24 @@
import { makeStyles } from '@material-ui/core'
import React from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import { TL1 } from 'src/components/typography'
import styles from './Section.styles'
const useStyles = makeStyles(styles)
const Section = ({ error, children, title }) => {
const classes = useStyles()
return (
<div className={classes.section}>
<div className={classes.sectionHeader}>
<TL1 className={classes.sectionTitle}>{title}</TL1>
{error && <ErrorMessage>Failed to save changes</ErrorMessage>}
</div>
{children}
</div>
)
}
export default Section

View file

@ -0,0 +1,15 @@
import { offColor } from 'src/styling/variables'
export default {
section: {
marginBottom: 72
},
sectionHeader: {
display: 'flex',
alignItems: 'center'
},
sectionTitle: {
color: offColor,
margin: [[16, 20, 23, 0]]
}
}

View file

@ -0,0 +1,21 @@
import { makeStyles } from '@material-ui/core'
import React from 'react'
import Title from 'src/components/Title'
import styles from './TitleSection.styles'
const useStyles = makeStyles(styles)
const TitleSection = ({ title }) => {
const classes = useStyles()
return (
<div className={classes.titleWrapper}>
<div className={classes.titleAndButtonsContainer}>
<Title>{title}</Title>
</div>
</div>
)
}
export default TitleSection

View file

@ -1,9 +1,11 @@
import React from 'react'
import classnames from 'classnames'
import { makeStyles } from '@material-ui/core'
import classnames from 'classnames'
import React from 'react'
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/white.svg'
import { Table, THead, TBody, Td, Th } from 'src/components/fake-table/Table'
import typographyStyles from 'src/components/typography/styles'
import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/white.svg'
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg'
import {
offColor,
@ -11,8 +13,6 @@ import {
tableNewDisabledHeaderColor,
secondaryColorDarker
} from 'src/styling/variables'
import { Table, THead, TBody, Td, Th } from 'src/components/fake-table/Table'
import typographyStyles from 'src/components/typography/styles'
const { label1, p } = typographyStyles
@ -106,11 +106,11 @@ const SingleRowTable = ({
{items && (
<Table className={classnames(className, classes.wrapper)}>
<THead className={classnames(headerClasses)}>
<Th size={width - editButtonSize}>
<Th width={width - editButtonSize}>
{title}
{newService && <span className={classes.spanNew}>New</span>}
</Th>
<Th size={editButtonSize} className={classes.buttonTh}>
<Th width={editButtonSize} className={classes.buttonTh}>
{!disabled && (
<button className={classes.editButton} onClick={onEdit}>
<EditIcon />
@ -124,7 +124,7 @@ const SingleRowTable = ({
</Th>
</THead>
<TBody className={classnames(bodyClasses)}>
<Td size={width}>
<Td width={width}>
{!disabled && (
<>
{items[0] && (

View file

@ -0,0 +1,8 @@
import React from 'react'
import { ReactComponent as FalseIcon } from 'src/styling/icons/table/false.svg'
import { ReactComponent as TrueIcon } from 'src/styling/icons/table/true.svg'
const BooleanCell = ({ value }) => (value ? <TrueIcon /> : <FalseIcon />)
export default BooleanCell

View file

@ -1,17 +1,17 @@
import useAxios from '@use-hooks/axios'
import React from 'react'
import { useLocation, useHistory } from 'react-router-dom'
const useQuery = () => new URLSearchParams(useLocation().search)
const url =
process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : ''
const AuthRegister = () => {
const history = useHistory()
const query = useQuery()
useAxios({
url: `/api/register?otp=${query.get('otp')}`,
url: `${url}/api/register?otp=${query.get('otp')}`,
method: 'GET',
options: {
withCredentials: true

View file

@ -1,10 +1,10 @@
import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
import moment from 'moment'
import * as R from 'ramda'
import React from 'react'
import { useQuery } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
import Title from 'src/components/Title'
import { DataTable } from 'src/components/dataTable'
@ -38,34 +38,34 @@ const Customers = () => {
const elements = [
{
header: 'Name',
size: 277,
width: 277,
view: R.path(['name'])
},
{
header: 'Phone',
size: 166,
width: 166,
view: it => parsePhoneNumberFromString(it.phone).formatInternational()
},
{
header: 'Total TXs',
size: 174,
width: 174,
textAlign: 'right',
view: it => `${Number.parseInt(it.totalTxs)}`
},
{
header: 'Total spent',
size: 188,
width: 188,
textAlign: 'right',
view: it => `${Number.parseFloat(it.totalSpent)} ${it.lastTxFiatCode}`
},
{
header: 'Last active',
size: 197,
width: 197,
view: it => moment.utc(it.lastActive).format('YYYY-MM-D')
},
{
header: 'Last transaction',
size: 198,
width: 198,
textAlign: 'right',
view: it => (
<>

View file

@ -1,11 +1,11 @@
import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import BigNumber from 'bignumber.js'
import classnames from 'classnames'
import moment from 'moment'
import QRCode from 'qrcode.react'
import React, { useState } from 'react'
import { useQuery } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
import Sidebar from 'src/components/Sidebar'
import TableLabel from 'src/components/TableLabel'
@ -200,22 +200,22 @@ const Funding = () => {
/>
<Table className={classes.table}>
<THead>
<Td header size={sizes.big}>
<Td header width={sizes.big}>
Amount Entered
</Td>
<Td header size={sizes.big}>
<Td header width={sizes.big}>
Balance After
</Td>
<Td header size={sizes.big}>
<Td header width={sizes.big}>
Cash Value
</Td>
<Td header size={sizes.date}>
<Td header width={sizes.date}>
Date
</Td>
<Td header size={sizes.time}>
<Td header width={sizes.time}>
Time (h:m:s)
</Td>
<Td header size={sizes.big}>
<Td header width={sizes.big}>
Performed By
</Td>
</THead>
@ -224,22 +224,22 @@ const Funding = () => {
<Tr
key={idx}
className={classnames({ [classes.pending]: it.pending })}>
<Td size={sizes.big}>
<Td width={sizes.big}>
{it.cryptoAmount} {selected.cryptoCode}
</Td>
<Td size={sizes.big}>
<Td width={sizes.big}>
{it.balance} {selected.cryptoCode}
</Td>
<Td size={sizes.big}>
<Td width={sizes.big}>
{it.fiatValue} {selected.fiatCode}
</Td>
<Td size={sizes.date}>
<Td width={sizes.date}>
{moment(it.date).format('YYYY-MM-DD')}
</Td>
<Td size={sizes.time}>
<Td width={sizes.time}>
{moment(it.date).format('hh:mm:ss')}
</Td>
<Td size={sizes.big}>add</Td>
<Td width={sizes.big}>add</Td>
</Tr>
))}
</TBody>

View file

@ -1,7 +1,7 @@
import React, { memo } from 'react'
import * as Yup from 'yup'
import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
import React, { memo } from 'react'
import * as Yup from 'yup'
import Subtitle from 'src/components/Subtitle'
import Title from 'src/components/Title'

View file

@ -20,41 +20,48 @@ const MainForm = memo(({ value, save, auxData, validationSchema }) => {
return (
<EditableTable
name="locale"
title="Default Settings"
altTitleColor
initialValues={{ country: null }}
enableEdit
onDelete={() => {}}
setEditing={() => {}}
save={save}
validationSchema={validationSchema}
data={R.of(value)}
elements={[
{
name: 'country',
size: sizes.country,
width: sizes.country,
view: R.path(['display']),
input: Autocomplete,
inputProps: { suggestions: getData(['countries']) }
},
{
name: 'fiatCurrency',
size: sizes.fiatCurrency,
width: sizes.fiatCurrency,
view: R.path(['code']),
input: Autocomplete,
inputProps: { suggestions: getData(['currencies']) }
},
{
name: 'languages',
size: sizes.languages,
width: sizes.languages,
view: displayCodeArray,
input: AutocompleteMultiple,
inputProps: { suggestions: getData(['languages']) }
},
{
name: 'cryptoCurrencies',
size: sizes.cryptoCurrencies,
width: sizes.cryptoCurrencies,
view: displayCodeArray,
input: AutocompleteMultiple,
inputProps: { suggestions: getData(['cryptoCurrencies']) }
},
{
name: 'showRates',
size: sizes.showRates,
width: sizes.showRates,
textAlign: 'center',
view: it => (it ? 'true' : 'false'),
input: Checkbox

View file

@ -1,254 +0,0 @@
import React, { useState } from 'react'
import * as R from 'ramda'
import { gql } from 'apollo-boost'
import classnames from 'classnames'
import * as Yup from 'yup'
import { makeStyles } from '@material-ui/core'
import { useQuery } from '@apollo/react-hooks'
import { Info2 } from 'src/components/typography'
import commonStyles from 'src/pages/common.styles'
import { Table as EditableTable } from 'src/components/editableTable'
import Link from 'src/components/buttons/Link.js'
import { Autocomplete } from 'src/components/inputs/index.js'
import { AddButton } from 'src/components/buttons/index.js'
import TextInputFormik from 'src/components/inputs/formik/TextInput.js'
import {
isDisabled,
LOW_BALANCE_KEY,
HIGH_BALANCE_KEY,
OVERRIDES_KEY,
ADD_OVERRIDE_CBA_KEY
} from './aux.js'
import { BigNumericInput } from './Inputs'
import { localStyles, cryptoBalanceAlertsStyles } from './Notifications.styles'
const CRYPTOCURRENCY_KEY = 'cryptocurrency'
const styles = R.mergeAll([
commonStyles,
localStyles,
cryptoBalanceAlertsStyles
])
const GET_CRYPTOCURRENCIES = gql`
{
cryptoCurrencies {
code
display
}
}
`
const useStyles = makeStyles(styles)
const CryptoBalanceAlerts = ({
values: setupValues,
save,
editingState,
handleEditingClick,
setError
}) => {
const [cryptoCurrencies, setCryptoCurrencies] = useState(null)
useQuery(GET_CRYPTOCURRENCIES, {
onCompleted: data => {
setCryptoCurrencies(data.cryptoCurrencies)
},
onError: error => console.error(error)
})
const classes = useStyles()
const editingLowBalance = editingState[LOW_BALANCE_KEY]
const editingHighBalance = editingState[HIGH_BALANCE_KEY]
const addingOverride = editingState[ADD_OVERRIDE_CBA_KEY]
const overrideOpsDisabled = isDisabled(editingState, ADD_OVERRIDE_CBA_KEY)
const handleEdit = R.curry(handleEditingClick)
const handleSubmit = it => save(it)
const handleSubmitOverrides = it => {
const newOverrides = {
[OVERRIDES_KEY]: R.prepend(it, setupValues[OVERRIDES_KEY])
}
save(newOverrides)
}
const handleResetForm = () => {
handleEdit(ADD_OVERRIDE_CBA_KEY)(false)
setError(null)
}
const deleteOverride = it => {
const cryptocurrency = it[CRYPTOCURRENCY_KEY]
const idx = R.findIndex(
R.propEq([CRYPTOCURRENCY_KEY], cryptocurrency),
setupValues[OVERRIDES_KEY]
)
const newOverrides = R.remove(idx, 1, setupValues[OVERRIDES_KEY])
save({ [OVERRIDES_KEY]: newOverrides })
}
const defaultsFields = {
[LOW_BALANCE_KEY]: {
name: LOW_BALANCE_KEY,
label: 'Alert me under',
value: setupValues[LOW_BALANCE_KEY]
},
[HIGH_BALANCE_KEY]: {
name: HIGH_BALANCE_KEY,
label: 'Alert me over',
value: setupValues[HIGH_BALANCE_KEY]
}
}
const getSuggestions = () => {
const overridenCryptos = R.map(
override => override[CRYPTOCURRENCY_KEY],
setupValues[OVERRIDES_KEY]
)
return R.without(overridenCryptos, cryptoCurrencies ?? [])
}
const { [OVERRIDES_KEY]: overrides } = setupValues
const initialValues = {
[CRYPTOCURRENCY_KEY]: '',
[LOW_BALANCE_KEY]: '',
[HIGH_BALANCE_KEY]: ''
}
const validationSchema = Yup.object().shape({
[CRYPTOCURRENCY_KEY]: Yup.string().required(),
[LOW_BALANCE_KEY]: Yup.number()
.integer()
.min(0)
.max(99999999)
.required(),
[HIGH_BALANCE_KEY]: Yup.number()
.integer()
.min(0)
.max(99999999)
.required()
})
const elements = [
{
name: CRYPTOCURRENCY_KEY,
display: 'Cryptocurrency',
size: 166,
textAlign: 'left',
view: R.path(['display']),
type: 'text',
input: Autocomplete,
inputProps: {
suggestions: getSuggestions(),
onFocus: () => setError(null)
}
},
{
name: LOW_BALANCE_KEY,
display: 'Low Balance',
size: 140,
textAlign: 'right',
view: it => it,
type: 'text',
input: TextInputFormik,
inputProps: {
suffix: 'EUR', // TODO: Current currency?
className: classes.textInput,
onFocus: () => setError(null)
}
},
{
name: HIGH_BALANCE_KEY,
display: 'High Balance',
size: 140,
textAlign: 'right',
view: it => it,
type: 'text',
input: TextInputFormik,
inputProps: {
suffix: 'EUR', // TODO: Current currency?
className: classes.textInput,
onFocus: () => setError(null)
}
},
{
name: 'delete',
size: 91
}
]
if (!cryptoCurrencies) return null
return (
<>
<div>
<div className={classnames(classes.defaults, classes.cbaDefaults)}>
<BigNumericInput
title="Default (Low Balance)"
field={defaultsFields[LOW_BALANCE_KEY]}
editing={editingLowBalance}
disabled={isDisabled(editingState, LOW_BALANCE_KEY)}
setEditing={handleEdit(LOW_BALANCE_KEY)}
handleSubmit={handleSubmit}
className={classes.lowBalance}
setError={setError}
/>
<BigNumericInput
title="Default (High Balance)"
field={defaultsFields[HIGH_BALANCE_KEY]}
editing={editingHighBalance}
disabled={isDisabled(editingState, HIGH_BALANCE_KEY)}
setEditing={handleEdit(HIGH_BALANCE_KEY)}
handleSubmit={handleSubmit}
setError={setError}
/>
</div>
</div>
<div className={classes.overrides}>
<div className={classes.overridesTitle}>
<Info2>Overrides</Info2>
{!addingOverride && !overrideOpsDisabled && overrides.length > 0 && (
<Link
color="primary"
onClick={() => handleEdit(ADD_OVERRIDE_CBA_KEY)(true)}>
Add override
</Link>
)}
</div>
{!addingOverride && !overrideOpsDisabled && overrides.length === 0 && (
<AddButton onClick={() => handleEdit(ADD_OVERRIDE_CBA_KEY)(true)}>
Add overrides
</AddButton>
)}
{(addingOverride || overrides.length > 0) && (
<EditableTable
className={classes.overridesTable}
addingRow={addingOverride}
disableAction={overrideOpsDisabled || addingOverride}
editing={R.map(
() => false,
R.range(0, setupValues[OVERRIDES_KEY].length)
)}
save={handleSubmitOverrides}
reset={handleResetForm}
action={deleteOverride}
initialValues={initialValues}
validationSchema={validationSchema}
data={setupValues[OVERRIDES_KEY]}
elements={elements}
/>
)}
</div>
</>
)
}
export default CryptoBalanceAlerts

View file

@ -1,597 +0,0 @@
import React, { useState } from 'react'
import * as R from 'ramda'
import classnames from 'classnames'
import { gql } from 'apollo-boost'
import * as Yup from 'yup'
import { makeStyles } from '@material-ui/core'
import { useQuery } from '@apollo/react-hooks'
import { Info2 } from 'src/components/typography'
import commonStyles from 'src/pages/common.styles'
import { Table as EditableTable } from 'src/components/editableTable'
import { Link, AddButton } from 'src/components/buttons'
import { Autocomplete } from 'src/components/inputs'
import TextInputFormik from 'src/components/inputs/formik/TextInput.js'
import { BigPercentageAndNumericInput, MultiplePercentageInput } from './Inputs'
import { localStyles, fiatBalanceAlertsStyles } from './Notifications.styles'
import {
CASH_IN_FULL_KEY,
isDisabled,
CASH_OUT_EMPTY_KEY,
CASSETTE_1_KEY,
CASSETTE_2_KEY,
PERCENTAGE_KEY,
NUMERARY_KEY,
OVERRIDES_KEY,
ADD_OVERRIDE_FBA_KEY,
MACHINE_KEY
} from './aux'
const styles = R.mergeAll([commonStyles, localStyles, fiatBalanceAlertsStyles])
const useStyles = makeStyles(styles)
const GET_MACHINES = gql`
{
machines {
name
deviceId
}
}
`
// const OverridesRow = ({
// machine,
// handleSubmitOverrides,
// handleEdit,
// setError,
// sizes,
// editing,
// fields,
// disabled,
// getSuggestions,
// ...props
// }) => {
// const classes = useStyles()
// const baseInitialValues = {
// [fields[PERCENTAGE_KEY].name]: fields[PERCENTAGE_KEY].value ?? '',
// [fields[NUMERARY_KEY].name]: fields[NUMERARY_KEY].value ?? '',
// [fields[CASSETTE_1_KEY].name]: fields[CASSETTE_1_KEY].value ?? '',
// [fields[CASSETTE_2_KEY].name]: fields[CASSETTE_2_KEY].value ?? ''
// }
// const initialValues = machine
// ? baseInitialValues
// : R.assoc(fields[MACHINE_KEY].name, '', baseInitialValues)
// const baseValidationSchemaShape = {
// [fields[PERCENTAGE_KEY].name]: Yup.number()
// .integer()
// .min(0)
// .max(100)
// .required(),
// [fields[NUMERARY_KEY].name]: Yup.number()
// .integer()
// .min(0)
// .max(99999999)
// .required(),
// [fields[CASSETTE_1_KEY].name]: Yup.number()
// .integer()
// .min(0)
// .max(100)
// .required(),
// [fields[CASSETTE_2_KEY].name]: Yup.number()
// .integer()
// .min(0)
// .max(100)
// .required()
// }
// const validationSchemaShape = machine
// ? baseValidationSchemaShape
// : R.assoc(
// fields[MACHINE_KEY].name,
// Yup.string().required(),
// baseValidationSchemaShape
// )
// return (
// <Formik
// initialValues={initialValues}
// validationSchema={Yup.object().shape(validationSchemaShape)}
// onSubmit={values => {
// const machineName = machine
// ? machine.name
// : values[fields[MACHINE_KEY].name].name
// handleSubmitOverrides(machineName)(values)
// }}
// onReset={(values, bag) => {
// handleEdit(machine?.name ?? ADD_OVERRIDE_FBA_KEY)(false)
// setError(null)
// }}>
// <Form>
// <Tr>
// <Td size={sizes.machine}>
// {machine && machine.name}
// {!machine && (
// <FormikField
// id={fields[MACHINE_KEY].name}
// name={fields[MACHINE_KEY].name}
// component={Autocomplete}
// type="text"
// suggestions={getSuggestions()}
// code="deviceId"
// display="name"
// />
// )}
// </Td>
// <CellDoubleLevel className={classes.doubleLevelRow}>
// <Td size={sizes.percentage} textAlign="right">
// <Field
// editing={editing}
// field={fields[PERCENTAGE_KEY]}
// displayValue={x => (x === '' ? '-' : x)}
// decoration="%"
// className={classes.eRowField}
// setError={setError}
// />
// </Td>
// <Td size={sizes.amount} textAlign="right">
// <Field
// editing={editing}
// field={fields[NUMERARY_KEY]}
// displayValue={x => (x === '' ? '-' : x)}
// decoration="EUR"
// className={classes.eRowField}
// setError={setError}
// />
// </Td>
// </CellDoubleLevel>
// <CellDoubleLevel className={classes.doubleLevelRow}>
// <Td size={sizes.cashOut1} textAlign="right">
// <Field
// editing={editing}
// field={fields[CASSETTE_1_KEY]}
// displayValue={x => (x === '' ? '-' : x)}
// decoration="%"
// className={classes.eRowField}
// setError={setError}
// />
// </Td>
// <Td size={sizes.cashOut2} textAlign="right">
// <Field
// editing={editing}
// field={fields[CASSETTE_2_KEY]}
// displayValue={x => (x === '' ? '-' : x)}
// decoration="%"
// className={classes.eRowField}
// setError={setError}
// />
// </Td>
// </CellDoubleLevel>
// <Td
// size={sizes.edit}
// textAlign="center"
// className={editing && classes.edit}>
// {!editing && !disabled && (
// <button
// className={classes.button}
// onClick={() => handleEdit(machine.name)(true)}>
// <EditIcon />
// </button>
// )}
// {disabled && (
// <div>
// <DisabledEditIcon />
// </div>
// )}
// {editing && (
// <>
// <Link color="primary" type="submit">
// Save
// </Link>
// <Link color="secondary" type="reset">
// Cancel
// </Link>
// </>
// )}
// </Td>
// </Tr>
// </Form>
// </Formik>
// )
// }
const FiatBalanceAlerts = ({
values: setupValues,
save,
editingState,
handleEditingClick,
setError
}) => {
const [machines, setMachines] = useState(null)
useQuery(GET_MACHINES, {
onCompleted: data => {
setMachines(data.machines)
},
onError: error => console.error(error)
})
const classes = useStyles()
const getValue = R.path(R.__, setupValues)
const handleEdit = R.curry(handleEditingClick)
const handleSubmit = R.curry((key, it) => {
const setup = setupValues[key]
const pairs = R.mapObjIndexed((num, k, obj) => {
return [R.split('-', k)[1], num]
}, it)
const rightKeys = R.fromPairs(R.values(pairs))
const newItem = { [key]: R.merge(setup, rightKeys) }
save(newItem)
})
const handleSubmitOverrides = R.curry((key, it) => {
const setup = setupValues[OVERRIDES_KEY]
const pathMatches = R.pathEq(['machine', 'name'], key)
const pairs = R.values(
R.mapObjIndexed((num, k, obj) => {
const split = R.split('-', k)
if (split.length < 3) return { [split[1]]: num }
return { [split[1]]: { [split[2]]: num } }
}, it)
)
const old = R.find(pathMatches, setup)
if (!old) {
const newOverride = R.reduce(R.mergeDeepRight, {}, pairs)
const newOverrides = {
[OVERRIDES_KEY]: R.prepend(newOverride, setup)
}
save(newOverrides)
return
}
const machineIdx = R.findIndex(pathMatches, setup)
const newOverride = R.mergeDeepRight(
old,
R.reduce(R.mergeDeepRight, {}, pairs)
)
const newOverrides = {
[OVERRIDES_KEY]: R.update(machineIdx, newOverride, setup)
}
save(newOverrides)
})
const handleResetForm = it => {
const machine = it?.machine
handleEdit(machine?.name ?? ADD_OVERRIDE_FBA_KEY)(false)
setError(null)
}
const getSuggestions = () => {
const overridenMachines = R.map(
override => override.machine,
setupValues[OVERRIDES_KEY]
)
return R.without(overridenMachines, machines ?? [])
}
const cashInFields = {
percentage: {
name: CASH_IN_FULL_KEY + '-' + PERCENTAGE_KEY,
label: 'Alert me over',
value: getValue([CASH_IN_FULL_KEY, PERCENTAGE_KEY])
},
numeric: {
name: CASH_IN_FULL_KEY + '-' + NUMERARY_KEY,
label: 'Or',
value: getValue([CASH_IN_FULL_KEY, NUMERARY_KEY])
}
}
const cashOutFields = [
{
title: 'Cassette 1 (Top)',
name: CASH_OUT_EMPTY_KEY + '-' + CASSETTE_1_KEY,
label: 'Alert me at',
value: getValue([CASH_OUT_EMPTY_KEY, CASSETTE_1_KEY])
},
{
title: 'Cassette 2',
name: CASH_OUT_EMPTY_KEY + '-' + CASSETTE_2_KEY,
label: 'Alert me at',
value: getValue([CASH_OUT_EMPTY_KEY, CASSETTE_2_KEY])
}
]
const { overrides } = setupValues
const initialValues = {
[MACHINE_KEY]: '',
[PERCENTAGE_KEY]: '',
[NUMERARY_KEY]: '',
[CASSETTE_1_KEY]: '',
[CASSETTE_2_KEY]: ''
}
const validationSchema = Yup.object().shape({
[MACHINE_KEY]: Yup.string().required(),
[PERCENTAGE_KEY]: Yup.number()
.integer()
.min(0)
.max(100)
.required(),
[NUMERARY_KEY]: Yup.number()
.integer()
.min(0)
.max(99999999)
.required(),
[CASSETTE_1_KEY]: Yup.number()
.integer()
.min(0)
.max(100)
.required(),
[CASSETTE_2_KEY]: Yup.number()
.integer()
.min(0)
.max(100)
.required()
})
const elements = [
{
name: MACHINE_KEY,
display: 'Machine',
size: 238,
textAlign: 'left',
view: R.path(['display']),
type: 'text',
input: Autocomplete,
inputProps: {
suggestions: getSuggestions(),
onFocus: () => setError(null)
}
},
[
{ display: 'Cash-in (Cassette Full)' },
{
name: PERCENTAGE_KEY,
display: 'Percentage',
size: 128,
textAlign: 'right',
view: it => it,
type: 'text',
input: TextInputFormik,
inputProps: {
suffix: '%',
className: classes.textInput,
onFocus: () => setError(null)
}
},
{
name: NUMERARY_KEY,
display: 'Amount',
size: 128,
textAlign: 'right',
view: it => it,
type: 'text',
input: TextInputFormik,
inputProps: {
suffix: 'EUR', // TODO: Current currency?
className: classes.textInput,
onFocus: () => setError(null)
}
}
],
[
{ display: 'Cash-out (Cassette Empty)' },
{
name: CASSETTE_1_KEY,
display: 'Cash-out 1',
size: 128,
textAlign: 'right',
view: it => it,
type: 'text',
input: TextInputFormik,
inputProps: {
suffix: '%',
className: classes.textInput,
onFocus: () => setError(null)
}
},
{
name: CASSETTE_2_KEY,
display: 'Cash-out 2',
size: 128,
textAlign: 'right',
view: it => it,
type: 'text',
input: TextInputFormik,
inputProps: {
suffix: '%',
className: classes.textInput,
onFocus: () => setError(null)
}
}
],
{
name: 'edit',
size: 98
}
]
const cashInEditing = editingState[CASH_IN_FULL_KEY]
const cashOutEditing = editingState[CASH_OUT_EMPTY_KEY]
const overrideOpsDisabled = isDisabled(editingState, ADD_OVERRIDE_FBA_KEY)
const addingOverride = editingState[ADD_OVERRIDE_FBA_KEY]
if (!machines) return null
return (
<div>
<div className={classnames(classes.defaults, classes.fbaDefaults)}>
<BigPercentageAndNumericInput
title="Cash-in (Full)"
fields={cashInFields}
editing={cashInEditing}
disabled={isDisabled(editingState, CASH_IN_FULL_KEY)}
setEditing={handleEdit(CASH_IN_FULL_KEY)}
handleSubmit={handleSubmit(CASH_IN_FULL_KEY)}
className={classes.cashInWrapper}
setError={setError}
/>
<div>
<MultiplePercentageInput
title="Cash-out (Empty)"
fields={cashOutFields}
editing={cashOutEditing}
disabled={isDisabled(editingState, CASH_OUT_EMPTY_KEY)}
setEditing={handleEdit(CASH_OUT_EMPTY_KEY)}
handleSubmit={handleSubmit(CASH_OUT_EMPTY_KEY)}
setError={setError}
/>
</div>
</div>
<div className={classes.overrides}>
<div className={classes.overridesTitle}>
<Info2>Overrides</Info2>
{!addingOverride && !overrideOpsDisabled && overrides.length > 0 && (
<Link
color="primary"
onClick={() => handleEdit(ADD_OVERRIDE_FBA_KEY)(true)}>
Add override
</Link>
)}
</div>
{!addingOverride && !overrideOpsDisabled && overrides.length === 0 && (
<AddButton onClick={() => handleEdit(ADD_OVERRIDE_FBA_KEY)(true)}>
Add overrides
</AddButton>
)}
{(addingOverride || overrides.length > 0) && (
<EditableTable
className={{ head: { cell: classes.doubleLevelHead } }}
addingRow={addingOverride}
disableAction={overrideOpsDisabled || addingOverride}
editing={R.map(
() => false,
R.range(0, setupValues[OVERRIDES_KEY].length)
)}
save={handleSubmitOverrides}
reset={() => handleResetForm}
action={it => handleEdit(it.machine.name)(true)}
initialValues={initialValues}
validationSchema={validationSchema}
// data={setupValues[OVERRIDES_KEY]}
data={[]}
elements={elements}
double
/>
// <Table>
// <TDoubleLevelHead>
// <Th size={sizes.machine}>Machine</Th>
// <ThDoubleLevel
// title="Cash-in (Cassette Full)"
// className={classes.doubleLevelHead}>
// <Th size={sizes.percentage} textAlign="right">
// Percentage
// </Th>
// <Th size={sizes.amount} textAlign="right">
// Amount
// </Th>
// </ThDoubleLevel>
// <ThDoubleLevel
// title="Cash-out (Cassette Empty)"
// className={classes.doubleLevelHead}>
// <Th size={sizes.cashOut1} textAlign="right">
// Cash-out 1
// </Th>
// <Th size={sizes.cashOut2} textAlign="right">
// Cash-out 2
// </Th>
// </ThDoubleLevel>
// <Th size={sizes.edit} textAlign="center">
// Edit
// </Th>
// </TDoubleLevelHead>
// <TBody>
// {addingOverride && (
// <OverridesRow
// handleSubmitOverrides={handleSubmitOverrides}
// handleEdit={handleEdit}
// sizes={sizes}
// editing={editingState[ADD_OVERRIDE_FBA_KEY]}
// fields={{
// [MACHINE_KEY]: { name: `new-${MACHINE_KEY}` },
// [PERCENTAGE_KEY]: {
// name: `new-${CASH_IN_FULL_KEY}-${PERCENTAGE_KEY}`
// },
// [NUMERARY_KEY]: {
// name: `new-${CASH_IN_FULL_KEY}-${NUMERARY_KEY}`
// },
// [CASSETTE_1_KEY]: {
// name: `new-${CASH_OUT_EMPTY_KEY}-${CASSETTE_1_KEY}`
// },
// [CASSETTE_2_KEY]: {
// name: `new-${CASH_OUT_EMPTY_KEY}-${CASSETTE_2_KEY}`
// }
// }}
// disabled={isDisabled(ADD_OVERRIDE_FBA_KEY)}
// getSuggestions={getSuggestions}
// setError={setError}
// />
// )}
// {overrides.map((override, idx) => {
// const machine = override[MACHINE_KEY]
// const fields = {
// [PERCENTAGE_KEY]: {
// name: `${machine.name}-${CASH_IN_FULL_KEY}-${PERCENTAGE_KEY}`,
// value: override[CASH_IN_FULL_KEY][PERCENTAGE_KEY]
// },
// [NUMERARY_KEY]: {
// name: `${machine.name}-${CASH_IN_FULL_KEY}-${NUMERARY_KEY}`,
// value: override[CASH_IN_FULL_KEY][NUMERARY_KEY]
// },
// [CASSETTE_1_KEY]: {
// name: `${machine.name}-${CASH_OUT_EMPTY_KEY}-${CASSETTE_1_KEY}`,
// value: override[CASH_OUT_EMPTY_KEY][CASSETTE_1_KEY]
// },
// [CASSETTE_2_KEY]: {
// name: `${machine.name}-${CASH_OUT_EMPTY_KEY}-${CASSETTE_2_KEY}`,
// value: override[CASH_OUT_EMPTY_KEY][CASSETTE_2_KEY]
// }
// }
// const editing = editingState[machine.name]
// const disabled = isDisabled(editingState, machine.name)
// return (
// <OverridesRow
// key={idx}
// machine={machine}
// handleSubmitOverrides={handleSubmitOverrides}
// handleEdit={handleEdit}
// sizes={sizes}
// editing={editing}
// fields={fields}
// disabled={disabled}
// setError={setError}
// />
// )
// })}
// </TBody>
// </Table>
)}
</div>
</div>
)
}
export default FiatBalanceAlerts

View file

@ -1,340 +0,0 @@
import React from 'react'
import * as R from 'ramda'
import classnames from 'classnames'
import * as Yup from 'yup'
import { Form, Formik, Field as FormikField } from 'formik'
import { makeStyles } from '@material-ui/core'
import {
H4,
Label1,
Info1,
TL2,
Info2,
Label2
} from 'src/components/typography'
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg'
import { Link } from 'src/components/buttons'
import TextInputFormik from 'src/components/inputs/formik/TextInput'
import {
localStyles,
inputSectionStyles,
percentageAndNumericInputStyles,
multiplePercentageInputStyles,
fieldStyles
} from './Notifications.styles'
const fieldUseStyles = makeStyles(R.mergeAll([fieldStyles, localStyles]))
const Field = ({
editing,
field,
displayValue,
decoration,
className,
setError,
...props
}) => {
const classes = fieldUseStyles()
const classNames = {
[className]: true,
[classes.field]: true,
[classes.notEditing]: !editing,
[classes.percentageInput]: decoration === '%'
}
return (
<div className={classnames(classNames)}>
{field.label && <Label1 className={classes.label}>{field.label}</Label1>}
<div className={classes.displayValue}>
{!editing && props.large && (
<>
<Info1>{displayValue(field.value)}</Info1>
</>
)}
{!editing && !props.large && (
<>
<Info2>{displayValue(field.value)}</Info2>
</>
)}
{editing && (
<FormikField
id={field.name}
name={field.name}
component={TextInputFormik}
placeholder={field.placeholder}
type="text"
onFocus={() => setError(null)}
{...props}
/>
)}
{props.large && (
<>
<TL2>{decoration}</TL2>
</>
)}
{!props.large && (
<>
<Label2>{decoration}</Label2>
</>
)}
</div>
</div>
)
}
const useStyles = makeStyles(inputSectionStyles)
const Header = ({ title, editing, disabled, setEditing }) => {
const classes = useStyles()
return (
<div className={classes.header}>
<H4>{title}</H4>
{!editing && !disabled && (
<button onClick={() => setEditing(true)} className={classes.editButton}>
<EditIcon />
</button>
)}
{disabled && (
<div className={classes.disabledButton}>
<DisabledEditIcon />
</div>
)}
{editing && (
<div className={classes.editingButtons}>
<Link color="primary" type="submit">
Save
</Link>
<Link color="secondary" type="reset">
Cancel
</Link>
</div>
)}
</div>
)
}
const BigNumericInput = ({
title,
field,
editing,
disabled,
setEditing,
handleSubmit,
setError,
className
}) => {
const classes = useStyles()
const { name, value } = field
return (
<div className={className}>
<Formik
initialValues={{ [name]: value }}
validationSchema={Yup.object().shape({
[name]: Yup.number()
.integer()
.min(0)
.max(99999)
.required()
})}
onSubmit={values => {
handleSubmit(values)
}}
onReset={(values, bag) => {
setEditing(false)
setError(null)
}}>
<Form>
<Header
title={title}
editing={editing}
disabled={disabled}
setEditing={() => setEditing(true)}
/>
<div className={classes.body}>
<Field
editing={editing}
field={field}
displayValue={x => (x === '' ? '-' : x)}
decoration="EUR"
large
setError={setError}
/>
</div>
</Form>
</Formik>
</div>
)
}
const percentageAndNumericInputUseStyles = makeStyles(
R.merge(inputSectionStyles, percentageAndNumericInputStyles)
)
const BigPercentageAndNumericInput = ({
title,
fields,
editing,
disabled,
setEditing,
handleSubmit,
setError,
className
}) => {
const classes = percentageAndNumericInputUseStyles()
const { percentage, numeric } = fields
const { name: percentageName, value: percentageValue } = percentage
const { name: numericName, value: numericValue } = numeric
return (
<div className={className}>
<Formik
initialValues={{
[percentageName]: percentageValue,
[numericName]: numericValue
}}
validationSchema={Yup.object().shape({
[percentageName]: Yup.number()
.integer()
.min(0)
.max(100)
.required(),
[numericName]: Yup.number()
.integer()
.min(0)
.max(99999)
.required()
})}
onSubmit={values => {
handleSubmit(values)
}}
onReset={(values, bag) => {
setEditing(false)
setError(null)
}}>
<Form>
<Header
title={title}
editing={editing}
disabled={disabled}
setEditing={() => setEditing(true)}
/>
<div className={classes.body}>
<div className={classes.percentageDisplay}>
<div style={{ height: `${percentageValue}%` }}></div>
</div>
<div className={classes.inputColumn}>
<Field
editing={editing}
field={percentage}
displayValue={x => (x === '' ? '-' : x)}
decoration="%"
large
setError={setError}
/>
<Field
editing={editing}
field={numeric}
displayValue={x => (x === '' ? '-' : x)}
decoration="EUR"
large
setError={setError}
/>
</div>
</div>
</Form>
</Formik>
</div>
)
}
const multiplePercentageInputUseStyles = makeStyles(
R.merge(inputSectionStyles, multiplePercentageInputStyles)
)
const MultiplePercentageInput = ({
title,
fields,
editing,
disabled,
setEditing,
handleSubmit,
setError,
className
}) => {
const classes = multiplePercentageInputUseStyles()
const initialValues = R.fromPairs(R.map(f => [f.name, f.value], fields))
const validationSchemaShape = R.fromPairs(
R.map(
f => [
f.name,
Yup.number()
.integer()
.min(0)
.max(100)
.required()
],
fields
)
)
return (
<div className={className}>
<Formik
initialValues={initialValues}
validationSchema={Yup.object().shape(validationSchemaShape)}
onSubmit={values => {
handleSubmit(values)
}}
onReset={(values, bag) => {
setEditing(false)
setError(null)
}}>
<Form>
<Header
title={title}
editing={editing}
disabled={disabled}
setEditing={() => setEditing(true)}
/>
<div className={classes.body}>
{fields.map((field, idx) => (
<div key={idx}>
<div className={classes.percentageDisplay}>
<div style={{ height: `${field.value}%` }}></div>
</div>
<div className={classes.inputColumn}>
<TL2 className={classes.title}>{field.title}</TL2>
<Field
editing={editing}
field={field}
displayValue={x => (x === '' ? '-' : x)}
decoration="%"
className={classes.percentageInput}
large
setError={setError}
/>
</div>
</div>
))}
</div>
</Form>
</Formik>
</div>
)
}
export {
Field,
BigNumericInput,
BigPercentageAndNumericInput,
MultiplePercentageInput
}

View file

@ -1,103 +1,32 @@
import React, { useState } from 'react'
import * as R from 'ramda'
import { gql } from 'apollo-boost'
import { makeStyles } from '@material-ui/core'
import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
import * as R from 'ramda'
import React, { useState } from 'react'
import { TL1 } from 'src/components/typography'
import Title from 'src/components/Title'
import ErrorMessage from 'src/components/ErrorMessage'
import commonStyles from 'src/pages/common.styles'
import TitleSection from 'src/components/layout/TitleSection'
import { fromServer, toServer } from 'src/utils/config'
import { localStyles } from './Notifications.styles'
import Setup from './Setup'
import TransactionAlerts from './TransactionAlerts'
import {
SETUP_KEY,
TRANSACTION_ALERTS_KEY,
HIGH_VALUE_TRANSACTION_KEY,
CASH_IN_FULL_KEY,
FIAT_BALANCE_ALERTS_KEY,
CASH_OUT_EMPTY_KEY,
CASSETTE_1_KEY,
CASSETTE_2_KEY,
OVERRIDES_KEY,
PERCENTAGE_KEY,
NUMERARY_KEY,
CRYPTO_BALANCE_ALERTS_KEY,
LOW_BALANCE_KEY,
HIGH_BALANCE_KEY,
ADD_OVERRIDE_FBA_KEY,
ADD_OVERRIDE_CBA_KEY,
EMAIL_KEY,
BALANCE_KEY,
TRANSACTIONS_KEY,
COMPLIANCE_KEY,
SECURITY_KEY,
ERRORS_KEY,
ACTIVE_KEY,
SMS_KEY
} from './aux.js'
import FiatBalanceAlerts from './FiatBalanceAlerts'
import CryptoBalanceAlerts from './CryptoBalanceAlerts'
import Section from '../../components/layout/Section'
const fiatBalanceAlertsInitialValues = {
[CASH_IN_FULL_KEY]: {
[PERCENTAGE_KEY]: '',
[NUMERARY_KEY]: ''
},
[CASH_OUT_EMPTY_KEY]: {
[CASSETTE_1_KEY]: '',
[CASSETTE_2_KEY]: ''
}
}
const initialValues = {
[SETUP_KEY]: {
[EMAIL_KEY]: {
[BALANCE_KEY]: false,
[TRANSACTIONS_KEY]: false,
[COMPLIANCE_KEY]: false,
[SECURITY_KEY]: false,
[ERRORS_KEY]: false,
[ACTIVE_KEY]: false
},
[SMS_KEY]: {
[BALANCE_KEY]: false,
[TRANSACTIONS_KEY]: false,
[COMPLIANCE_KEY]: false,
[SECURITY_KEY]: false,
[ERRORS_KEY]: false,
[ACTIVE_KEY]: false
}
},
[TRANSACTION_ALERTS_KEY]: {
[HIGH_VALUE_TRANSACTION_KEY]: ''
},
[FIAT_BALANCE_ALERTS_KEY]: {
...fiatBalanceAlertsInitialValues,
[OVERRIDES_KEY]: []
},
[CRYPTO_BALANCE_ALERTS_KEY]: {
[LOW_BALANCE_KEY]: '',
[HIGH_BALANCE_KEY]: '',
[OVERRIDES_KEY]: []
}
}
const initialEditingState = {
[HIGH_VALUE_TRANSACTION_KEY]: false,
[CASH_IN_FULL_KEY]: false,
[CASH_OUT_EMPTY_KEY]: false,
[LOW_BALANCE_KEY]: false,
[HIGH_BALANCE_KEY]: false,
[ADD_OVERRIDE_FBA_KEY]: false,
[ADD_OVERRIDE_CBA_KEY]: false
}
import NotificationsCtx from './NotificationsContext'
import CryptoBalanceAlerts from './sections/CryptoBalanceAlerts'
import CryptoBalanceOverrides from './sections/CryptoBalanceOverrides'
import FiatBalanceAlerts from './sections/FiatBalanceAlerts'
import FiatBalanceOverrides from './sections/FiatBalanceOverrides'
import Setup from './sections/Setup'
import TransactionAlerts from './sections/TransactionAlerts'
const GET_INFO = gql`
{
query getData {
config
machines {
name
deviceId
}
cryptoCurrencies {
code
display
}
}
`
@ -107,125 +36,81 @@ const SAVE_CONFIG = gql`
}
`
const styles = R.merge(commonStyles, localStyles)
const useStyles = makeStyles(styles)
const SectionHeader = ({ error, children }) => {
const classes = useStyles()
return (
<div className={classes.sectionHeader}>
<TL1 className={classes.sectionTitle}>{children}</TL1>
{error && <ErrorMessage>Failed to save changes</ErrorMessage>}
</div>
)
}
const Notifications = () => {
const [state, setState] = useState(null)
const [editingState, setEditingState] = useState(initialEditingState)
const Notifications = ({ name: SCREEN_KEY }) => {
const [section, setSection] = useState(null)
const [error, setError] = useState(null)
const [tryingToSave, setTryingToSave] = useState(null)
const [editingKey, setEditingKey] = useState(null)
const { data } = useQuery(GET_INFO)
const [saveConfig] = useMutation(SAVE_CONFIG, {
onCompleted: data => {
const { notifications } = data.saveConfig
setState(notifications)
setEditingState(R.map(x => false, editingState))
setTryingToSave(null)
refetchQueries: ['getData'],
onCompleted: () => setEditingKey(null),
onError: error => setError({ error })
})
const config = data?.config && fromServer(SCREEN_KEY)(data.config)
const machines = data?.machines
const cryptoCurrencies = data?.cryptoCurrencies
// TODO check path when locales is finished
const currency = R.path(['locales_currency'])(data?.config ?? {})
const save = (section, rawConfig) => {
const config = toServer(SCREEN_KEY)(rawConfig)
setSection(section)
setError(null)
},
onError: e => {
setError({ section: tryingToSave, error: e })
}
})
const classes = useStyles()
useQuery(GET_INFO, {
onCompleted: data => {
const { notifications } = data.config
if (notifications) {
const { [OVERRIDES_KEY]: machines } = notifications[
FIAT_BALANCE_ALERTS_KEY
]
const editingFiatBalanceAlertsOverrides = R.fromPairs(
machines.map(machine => [machine.name, false])
)
setEditingState({
...editingState,
...editingFiatBalanceAlertsOverrides
})
}
setState(notifications ?? initialValues)
},
fetchPolicy: 'network-only'
})
const save = it => {
return saveConfig({ variables: { config: { notifications: it } } })
return saveConfig({ variables: { config } })
}
const handleEditingClick = (key, state) => {
setEditingState(R.merge(editingState, { [key]: state }))
const setEditing = (key, state) => {
if (!state) {
setError(null)
}
setEditingKey(state ? key : null)
}
const curriedSave = R.curry((key, values) => {
setTryingToSave(key)
save(R.mergeDeepRight(state)({ [key]: values }))
})
const isEditing = key => editingKey === key
const isDisabled = key => editingKey && editingKey !== key
if (!state) return null
const contextValue = {
save,
error,
editingKey,
data: config,
currency,
isEditing,
isDisabled,
setEditing,
setSection,
machines,
cryptoCurrencies
}
return (
<>
<div className={classes.titleWrapper}>
<div className={classes.titleAndButtonsContainer}>
<Title>Notifications</Title>
</div>
</div>
<div className={classes.section}>
<SectionHeader error={error?.section === SETUP_KEY}>
Setup
</SectionHeader>
<Setup values={state.setup} save={curriedSave(SETUP_KEY)} />
</div>
<div className={classes.section}>
<SectionHeader error={error?.section === TRANSACTION_ALERTS_KEY}>
Transaction alerts
</SectionHeader>
<TransactionAlerts
value={state[TRANSACTION_ALERTS_KEY]}
editingState={editingState}
handleEditingClick={handleEditingClick}
save={curriedSave(TRANSACTION_ALERTS_KEY)}
setError={setError}
/>
</div>
<div className={classes.section}>
<SectionHeader error={error?.section === FIAT_BALANCE_ALERTS_KEY}>
Fiat balance alerts
</SectionHeader>
<FiatBalanceAlerts
values={state[FIAT_BALANCE_ALERTS_KEY]}
editingState={editingState}
handleEditingClick={handleEditingClick}
save={curriedSave(FIAT_BALANCE_ALERTS_KEY)}
setError={setError}
/>
</div>
<div className={classes.section}>
<SectionHeader error={error?.section === CRYPTO_BALANCE_ALERTS_KEY}>
Crypto balance alerts
</SectionHeader>
<CryptoBalanceAlerts
values={state[CRYPTO_BALANCE_ALERTS_KEY]}
editingState={editingState}
handleEditingClick={handleEditingClick}
save={curriedSave(CRYPTO_BALANCE_ALERTS_KEY)}
setError={setError}
/>
</div>
</>
<NotificationsCtx.Provider value={contextValue}>
<TitleSection title="Notifications" />
<Section title="Setup" error={error && !section}>
<Setup />
</Section>
<Section title="Transaction alerts" error={error && section === 'tx'}>
<TransactionAlerts section="tx" />
</Section>
<Section title="Fiat balance alerts" error={error && section === 'fiat'}>
<FiatBalanceAlerts section="fiat" />
<FiatBalanceOverrides section="fiat" />
</Section>
<Section
title="Crypto balance alerts"
error={error && section === 'crypto'}>
<CryptoBalanceAlerts section="crypto" />
<CryptoBalanceOverrides section="crypto" />
</Section>
</NotificationsCtx.Provider>
)
}

View file

@ -1,252 +1,20 @@
import { offColor, primaryColor } from 'src/styling/variables'
import theme from 'src/styling/theme'
const localStyles = {
section: {
marginBottom: 72,
'&:last-child': {
marginBottom: 150
}
},
sectionHeader: {
display: 'flex',
alignItems: 'center',
'& > :first-child': {
marginRight: 20
}
},
sectionTitle: {
color: offColor,
margin: [[16, 0, 16, 0]]
},
button: {
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer',
height: '100%'
},
defaults: {
display: 'flex',
'& > div': {
export default {
cryptoBalanceAlerts: {
display: 'flex',
marginBottom: 36,
height: 135,
alignItems: 'center'
},
'& > div:first-child': {
borderRight: [['solid', 1, primaryColor]]
cryptoBalanceAlertsForm: {
width: 222,
marginRight: 32
},
'& > div:not(:first-child)': {
marginLeft: 56
}
cryptoBalanceAlertsSecondForm: {
marginLeft: 50
},
overrides: {
display: 'inline-block'
},
overridesTitle: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'baseline',
'& > :first-child': {
color: offColor,
margin: [[0, 0, 24, 0]]
},
'& > button': {
height: '100%'
}
},
displayValue: {
display: 'flex',
alignItems: 'baseline',
'& > p:first-child': {
margin: [[0, 4, 5, 0]]
},
'&> p:last-child': {
margin: 0
}
},
edit: {
display: 'flex',
justifyContent: 'flex-end',
'& > :first-child': {
marginRight: 16
}
},
eRowField: {
display: 'inline-block',
height: '100%'
},
textInput: {
'& .MuiInputBase-input': {
width: 80
}
vertSeparator: {
width: 1,
height: '100%',
borderRight: [[1, 'solid', 'black']]
}
}
const inputSectionStyles = {
header: {
display: 'flex',
alignItems: 'center',
marginBottom: 24,
height: 26,
'& > :first-child': {
flexShrink: 2,
margin: 0,
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis'
},
'& button': {
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer'
}
},
editButton: {
marginLeft: 16
},
disabledButton: {
padding: [[0, theme.spacing(1)]],
lineHeight: 'normal',
marginLeft: 16
},
editingButtons: {
display: 'flex',
marginLeft: 16,
'& > :not(:last-child)': {
marginRight: 20
}
},
percentageDisplay: {
position: 'relative',
width: 76,
height: 118,
border: [['solid', 4, primaryColor]],
marginRight: 12,
'& > div': {
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
backgroundColor: primaryColor,
transition: [['height', '0.5s']],
transitionTimingFunction: 'ease-out'
}
},
inputColumn: {
'& > div:not(:last-child)': {
marginBottom: 4
}
}
}
const fiatBalanceAlertsStyles = {
cashInWrapper: {
width: 254
},
doubleLevelHead: {
'& > div > div': {
marginRight: 72
}
},
doubleLevelRow: {
'& > div': {
marginRight: 72
}
},
fbaDefaults: {
'& > div': {
height: 185
},
marginBottom: 69
}
}
const cryptoBalanceAlertsStyles = {
lowBalance: {
width: 254,
'& form': {
width: 217
}
},
cbaDefaults: {
'& > div': {
height: 135
},
marginBottom: 36
},
overridesTable: {
width: 648
}
}
const percentageAndNumericInputStyles = {
body: {
display: 'flex',
alignItems: 'center'
}
}
const multiplePercentageInputStyles = {
body: {
display: 'flex',
'& > div': {
display: 'flex'
},
'& > div:not(:last-child)': {
marginRight: 43
}
},
title: {
margin: [[2, 0, 12, 0]]
}
}
const fieldStyles = {
field: {
position: 'relative',
display: 'flex',
flexDirection: 'column',
height: 53,
padding: 0,
'& > div': {
display: 'flex',
alignItems: 'baseline',
'& > p:first-child': {
margin: [[0, 4, 5, 0]]
},
'&> p:last-child': {
margin: [[0, 0, 0, 3]]
}
},
'& .MuiInputBase-input': {
width: 80
}
},
label: {
margin: 0
},
notEditing: {
'& > div': {
margin: [[5, 0, 0, 0]],
'& > p:first-child': {
height: 16
}
}
},
percentageInput: {
'& > div': {
'& .MuiInputBase-input': {
width: 30
}
}
}
}
export {
localStyles,
inputSectionStyles,
fiatBalanceAlertsStyles,
cryptoBalanceAlertsStyles,
percentageAndNumericInputStyles,
multiplePercentageInputStyles,
fieldStyles
}

View file

@ -0,0 +1,3 @@
import React from 'react'
export default React.createContext()

View file

@ -1,148 +0,0 @@
import React from 'react'
import * as R from 'ramda'
import { Switch } from 'src/components/inputs'
import {
Table as FakeTable,
THead,
TBody,
Tr,
Td,
Th
} from 'src/components/fake-table/Table'
import {
BALANCE_KEY,
TRANSACTIONS_KEY,
COMPLIANCE_KEY,
SECURITY_KEY,
ERRORS_KEY,
ACTIVE_KEY,
CHANNEL_KEY,
EMAIL_KEY,
SMS_KEY
} from './aux'
const elements = [
{
header: 'Channel',
name: CHANNEL_KEY,
size: 129,
textAlign: 'left'
},
{
header: 'Balance',
name: BALANCE_KEY,
size: 152,
textAlign: 'center'
},
{
header: 'Transactions',
name: TRANSACTIONS_KEY,
size: 184,
textAlign: 'center'
},
{
header: 'Compliance',
name: COMPLIANCE_KEY,
size: 178,
textAlign: 'center'
},
{
header: 'Security',
name: SECURITY_KEY,
size: 152,
textAlign: 'center'
},
{
header: 'Errors',
name: ERRORS_KEY,
size: 142,
textAlign: 'center'
},
{
header: 'Active',
name: ACTIVE_KEY,
size: 263,
textAlign: 'center'
}
]
const Row = ({ channel, columns, values, save }) => {
const { active } = values
const findField = name => R.find(R.propEq('name', name))(columns)
const findSize = name => findField(name).size
const findAlign = name => findField(name).textAlign
const Cell = ({ name, disabled }) => {
const handleChange = name => event => {
save(R.mergeDeepRight(values, { [name]: event.target.checked }))
}
return (
<Td size={findSize(name)} textAlign={findAlign(name)}>
<Switch
disabled={disabled}
checked={values[name]}
onChange={handleChange(name)}
value={name}
/>
</Td>
)
}
return (
<Tr>
<Td size={findSize(CHANNEL_KEY)} textAlign={findAlign(CHANNEL_KEY)}>
{channel}
</Td>
<Cell name={BALANCE_KEY} disabled={!active} />
<Cell name={TRANSACTIONS_KEY} disabled={!active} />
<Cell name={COMPLIANCE_KEY} disabled={!active} />
<Cell name={SECURITY_KEY} disabled={!active} />
<Cell name={ERRORS_KEY} disabled={!active} />
<Cell name={ACTIVE_KEY} />
</Tr>
)
}
const Setup = ({ values: setupValues, save }) => {
const saveSetup = R.curry((key, values) =>
save(R.mergeDeepRight(setupValues, { [key]: values }))
)
return (
<div>
<FakeTable>
<THead>
{elements.map(({ size, className, textAlign, header }, idx) => (
<Th
key={idx}
size={size}
className={className}
textAlign={textAlign}>
{header}
</Th>
))}
</THead>
<TBody>
<Row
channel="Email"
columns={elements}
values={setupValues[EMAIL_KEY]}
save={saveSetup(EMAIL_KEY)}
/>
<Row
channel="SMS"
columns={elements}
values={setupValues[SMS_KEY]}
save={saveSetup(SMS_KEY)}
/>
</TBody>
</FakeTable>
</div>
)
}
export default Setup

View file

@ -1,41 +0,0 @@
import React from 'react'
import * as R from 'ramda'
import { HIGH_VALUE_TRANSACTION_KEY, isDisabled } from './aux.js'
import { BigNumericInput } from './Inputs'
const TransactionAlerts = ({
value: setupValue,
save,
editingState,
handleEditingClick,
setError
}) => {
const editing = editingState[HIGH_VALUE_TRANSACTION_KEY]
const handleEdit = R.curry(handleEditingClick)
const handleSubmit = it => save(it)
const field = {
name: HIGH_VALUE_TRANSACTION_KEY,
label: 'Alert me over',
value: setupValue[HIGH_VALUE_TRANSACTION_KEY]
}
return (
<div>
<BigNumericInput
title="High value transaction"
field={field}
editing={editing}
disabled={isDisabled(editingState, HIGH_VALUE_TRANSACTION_KEY)}
setEditing={handleEdit(HIGH_VALUE_TRANSACTION_KEY)}
handleSubmit={handleSubmit}
setError={setError}
/>
</div>
)
}
export default TransactionAlerts

View file

@ -1,61 +0,0 @@
import * as R from 'ramda'
const EMAIL_KEY = 'email'
const SMS_KEY = 'sms'
const BALANCE_KEY = 'balance'
const TRANSACTIONS_KEY = 'transactions'
const COMPLIANCE_KEY = 'compliance'
const SECURITY_KEY = 'security'
const ERRORS_KEY = 'errors'
const ACTIVE_KEY = 'active'
const SETUP_KEY = 'setup'
const CHANNEL_KEY = 'channel'
const TRANSACTION_ALERTS_KEY = 'transactionAlerts'
const HIGH_VALUE_TRANSACTION_KEY = 'highValueTransaction'
const FIAT_BALANCE_ALERTS_KEY = 'fiatBalanceAlerts'
const CASH_IN_FULL_KEY = 'cashInFull'
const CASH_OUT_EMPTY_KEY = 'cashOutEmpty'
const MACHINE_KEY = 'machine'
const PERCENTAGE_KEY = 'percentage'
const NUMERARY_KEY = 'numerary'
const CASSETTE_1_KEY = 'cassete1'
const CASSETTE_2_KEY = 'cassete2'
const OVERRIDES_KEY = 'overrides'
const CRYPTO_BALANCE_ALERTS_KEY = 'cryptoBalanceAlerts'
const LOW_BALANCE_KEY = 'lowBalance'
const HIGH_BALANCE_KEY = 'highBalance'
const ADD_OVERRIDE_FBA_KEY = 'addOverrideFBA'
const ADD_OVERRIDE_CBA_KEY = 'addOverrideCBA'
const isDisabled = (state, self) =>
R.any(x => x === true, R.values(R.omit([self], state)))
export {
EMAIL_KEY,
SMS_KEY,
BALANCE_KEY,
TRANSACTIONS_KEY,
COMPLIANCE_KEY,
SECURITY_KEY,
ERRORS_KEY,
ACTIVE_KEY,
SETUP_KEY,
CHANNEL_KEY,
TRANSACTION_ALERTS_KEY,
HIGH_VALUE_TRANSACTION_KEY,
FIAT_BALANCE_ALERTS_KEY,
CASH_IN_FULL_KEY,
CASH_OUT_EMPTY_KEY,
MACHINE_KEY,
PERCENTAGE_KEY,
NUMERARY_KEY,
CASSETTE_1_KEY,
CASSETTE_2_KEY,
OVERRIDES_KEY,
CRYPTO_BALANCE_ALERTS_KEY,
LOW_BALANCE_KEY,
HIGH_BALANCE_KEY,
ADD_OVERRIDE_FBA_KEY,
ADD_OVERRIDE_CBA_KEY,
isDisabled
}

View file

@ -0,0 +1,41 @@
import { makeStyles } from '@material-ui/core'
import React from 'react'
import { Link } from 'src/components/buttons'
import { H4 } from 'src/components/typography'
import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg'
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
import styles from './EditHeader.styles'
const useStyles = makeStyles(styles)
const Header = ({ title, editing, disabled, setEditing }) => {
const classes = useStyles()
return (
<div className={classes.header}>
<H4 className={classes.title}>{title}</H4>
{!editing && (
<button
onClick={() => setEditing(true)}
className={classes.button}
disabled={disabled}>
{disabled ? <DisabledEditIcon /> : <EditIcon />}
</button>
)}
{editing && (
<div className={classes.editingButtons}>
<Link color="primary" type="submit">
Save
</Link>
<Link color="secondary" type="reset">
Cancel
</Link>
</div>
)}
</div>
)
}
export default Header

View file

@ -0,0 +1,29 @@
export default {
header: {
display: 'flex',
alignItems: 'center',
marginBottom: 16,
height: 26,
margin: 0
},
title: {
flexShrink: 2,
margin: 0,
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis'
},
button: {
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer',
marginLeft: 8
},
editingButtons: {
display: 'flex',
flexShrink: 0,
marginLeft: 16,
justifyContent: 'space-between',
width: 110
}
}

View file

@ -0,0 +1,55 @@
import { makeStyles } from '@material-ui/core'
import classnames from 'classnames'
import { useFormikContext, Field as FormikField } from 'formik'
import React from 'react'
import TextInput from 'src/components/inputs/formik/TextInput'
import { Label1, Info1, TL2 } from 'src/components/typography'
import styles from './EditableNumber.styles'
const useStyles = makeStyles(styles)
const EditableNumber = ({
label,
name,
editing,
displayValue,
decoration,
className,
width = 80
}) => {
const classes = useStyles({ width, editing })
const { values } = useFormikContext()
const classNames = {
[classes.fieldWrapper]: true,
className
}
return (
<div className={classnames(classNames)}>
{label && <Label1 className={classes.label}>{label}</Label1>}
<div className={classes.valueWrapper}>
{!editing && (
<Info1 className={classes.text}>{displayValue(values[name])}</Info1>
)}
{editing && (
<FormikField
id={name}
size="lg"
fullWidth
name={name}
component={TextInput}
textAlign="right"
type="text"
width={width}
/>
)}
<TL2 className={classes.decoration}>{decoration}</TL2>
</div>
</div>
)
}
export default EditableNumber

View file

@ -0,0 +1,18 @@
export default {
text: {
margin: [[7, 0, 7, 1]]
},
fieldWrapper: {
height: 53
},
valueWrapper: {
display: 'flex',
alignItems: 'baseline'
},
label: {
margin: 0
},
decoration: {
margin: [[0, 0, 0, 7]]
}
}

View file

@ -0,0 +1,63 @@
import { Form, Formik } from 'formik'
import React, { useContext } from 'react'
import * as Yup from 'yup'
import NotificationsCtx from '../NotificationsContext'
import Header from './EditHeader'
import EditableNumber from './EditableNumber'
const SingleFieldEditableNumber = ({
title,
label,
width = 80,
name,
section,
className
}) => {
const {
save,
data,
currency,
isEditing,
isDisabled,
setEditing
} = useContext(NotificationsCtx)
const schema = Yup.object().shape({
[name]: Yup.number()
.integer()
.min(0)
.required()
})
return (
<Formik
enableReinitialize
initialValues={{ [name]: (data && data[name]) ?? '' }}
validationSchema={schema}
onSubmit={it => save(section, it)}
onReset={() => {
setEditing(name, false)
}}>
<Form className={className}>
<Header
title={title}
editing={isEditing(name)}
disabled={isDisabled(name)}
setEditing={it => setEditing(name, it)}
/>
<EditableNumber
label={label}
name={name}
editing={isEditing(name)}
width={width}
displayValue={x => (x === '' ? '-' : x)}
decoration={currency}
/>
</Form>
</Formik>
)
}
export default SingleFieldEditableNumber

View file

@ -0,0 +1,61 @@
import { makeStyles } from '@material-ui/core'
import React, { useContext } from 'react'
import NotificationsCtx from '../NotificationsContext'
import SingleFieldEditableNumber from '../components/SingleFieldEditableNumber'
import styles from './CryptoBalanceAlerts.styles'
const LOW_BALANCE_KEY = 'cryptoLowBalance'
const HIGH_BALANCE_KEY = 'cryptoHighBalance'
const useStyles = makeStyles(styles)
const CryptoBalanceAlerts = ({ section }) => {
const classes = useStyles()
const {
data,
save,
currency,
setEditing,
isEditing,
isDisabled
} = useContext(NotificationsCtx)
return (
<div className={classes.cryptoBalanceAlerts}>
<SingleFieldEditableNumber
name={LOW_BALANCE_KEY}
data={data}
save={save}
section={section}
decoration={currency}
className={classes.cryptoBalanceAlertsForm}
title="Default (Low Balance)"
label="Alert me under"
editing={isEditing(LOW_BALANCE_KEY)}
disabled={isDisabled(LOW_BALANCE_KEY)}
setEditing={it => setEditing(LOW_BALANCE_KEY, it)}
/>
<div className={classes.vertSeparator} />
<SingleFieldEditableNumber
name={HIGH_BALANCE_KEY}
data={data}
section={section}
save={save}
decoration={currency}
className={classes.cryptoBalanceAlertsSecondForm}
title="Default (High Balance)"
label="Alert me over"
editing={isEditing(HIGH_BALANCE_KEY)}
disabled={isDisabled(HIGH_BALANCE_KEY)}
setEditing={it => setEditing(HIGH_BALANCE_KEY, it)}
/>
</div>
)
}
export default CryptoBalanceAlerts

View file

@ -0,0 +1,20 @@
export default {
cryptoBalanceAlerts: {
display: 'flex',
marginBottom: 36,
height: 135,
alignItems: 'center'
},
cryptoBalanceAlertsForm: {
width: 222,
marginRight: 32
},
cryptoBalanceAlertsSecondForm: {
marginLeft: 50
},
vertSeparator: {
width: 1,
height: '100%',
borderRight: [[1, 'solid', 'black']]
}
}

View file

@ -0,0 +1,118 @@
import * as R from 'ramda'
import React, { useContext } from 'react'
import * as Yup from 'yup'
import { Table as EditableTable } from 'src/components/editableTable'
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
import TextInputFormik from 'src/components/inputs/formik/TextInput.js'
import NotificationsCtx from '../NotificationsContext'
const HIGH_BALANCE_KEY = 'highBalance'
const LOW_BALANCE_KEY = 'lowBalance'
const CRYPTOCURRENCY_KEY = 'cryptoCurrency'
const NAME = 'cryptoBalanceOverrides'
const CryptoBalanceOverrides = ({ section }) => {
const {
cryptoCurrencies,
data,
save,
currency,
isDisabled,
setEditing
} = useContext(NotificationsCtx)
const setupValues = data?.cryptoBalanceOverrides ?? []
const innerSetEditing = it => setEditing(NAME, it)
const onDelete = id => {
const newOverrides = {
cryptoBalanceOverrides: R.reject(it => it.id === id, setupValues)
}
return save(newOverrides)
}
const getSuggestions = () => {
const overridenCryptos = R.map(
override => override[CRYPTOCURRENCY_KEY],
setupValues
)
return R.without(overridenCryptos, cryptoCurrencies ?? [])
}
const initialValues = {
[CRYPTOCURRENCY_KEY]: null,
[LOW_BALANCE_KEY]: '',
[HIGH_BALANCE_KEY]: ''
}
const validationSchema = Yup.object().shape({
[CRYPTOCURRENCY_KEY]: Yup.string().required(),
[LOW_BALANCE_KEY]: Yup.number()
.integer()
.min(0)
.max(99999999)
.required(),
[HIGH_BALANCE_KEY]: Yup.number()
.integer()
.min(0)
.max(99999999)
.required()
})
const suggestions = getSuggestions()
const elements = [
{
name: CRYPTOCURRENCY_KEY,
header: 'Cryptocurrency',
width: 166,
size: 'sm',
view: R.path(['display']),
input: Autocomplete,
inputProps: {
options: suggestions,
limit: null,
forceShowValue: true,
getOptionSelected: R.eqProps('display')
}
},
{
name: LOW_BALANCE_KEY,
width: 155,
textAlign: 'right',
bold: true,
input: TextInputFormik,
suffix: currency
},
{
name: HIGH_BALANCE_KEY,
width: 155,
textAlign: 'right',
bold: true,
input: TextInputFormik,
suffix: currency
}
]
return (
<EditableTable
name={NAME}
title="Overrides"
enableDelete
enableEdit
enableCreate
save={it => save(section, it)}
initialValues={initialValues}
validationSchema={validationSchema}
forceDisable={isDisabled(NAME) || !cryptoCurrencies}
data={setupValues}
elements={elements}
disableAdd={!suggestions?.length}
onDelete={onDelete}
setEditing={innerSetEditing}
/>
)
}
export default CryptoBalanceOverrides

View file

@ -0,0 +1,83 @@
import { makeStyles } from '@material-ui/core'
import { Form, Formik } from 'formik'
import React, { useContext } from 'react'
import * as Yup from 'yup'
import { TL2 } from 'src/components/typography'
import NotificationsCtx from '../NotificationsContext'
import Header from '../components/EditHeader'
import EditableNumber from '../components/EditableNumber'
import styles from './FiatBalanceAlerts.styles.js'
const useStyles = makeStyles(styles)
const schema = Yup.object().shape({
fiatBalanceCassette1: Yup.number()
.integer()
.min(0)
.required(),
fiatBalanceCassette2: Yup.number()
.integer()
.min(0)
.required()
})
const NAME = 'fiatBalanceAlerts'
const FiatBalance = ({ section }) => {
const { isEditing, isDisabled, setEditing, data, save } = useContext(
NotificationsCtx
)
const classes = useStyles()
const editing = isEditing(NAME)
return (
<Formik
enableReinitialize
initialValues={{
fiatBalanceCassette1: data?.fiatBalanceCassette1 ?? '',
fiatBalanceCassette2: data?.fiatBalanceCassette2 ?? ''
}}
validationSchema={schema}
onSubmit={it => save(section, it)}
onReset={() => {
setEditing(NAME, false)
}}>
<Form className={classes.form}>
<Header
title="Cash out (Empty)"
editing={editing}
disabled={isDisabled(NAME)}
setEditing={it => setEditing(NAME, it)}
/>
<div className={classes.wrapper}>
<div className={classes.first}>
<TL2 className={classes.title}>Cassette 1 (Top)</TL2>
<EditableNumber
label="Alert me under"
name="fiatBalanceCassette1"
editing={editing}
displayValue={x => (x === '' ? '-' : x)}
decoration="notes"
/>
</div>
<div>
<TL2 className={classes.title}>Cassette 2 (Bottom)</TL2>
<EditableNumber
label="Alert me under"
name="fiatBalanceCassette2"
editing={editing}
displayValue={x => (x === '' ? '-' : x)}
decoration="notes"
/>
</div>
</div>
</Form>
</Formik>
)
}
export default FiatBalance

View file

@ -0,0 +1,14 @@
export default {
wrapper: {
display: 'flex'
},
form: {
marginBottom: 36
},
first: {
width: 200
},
title: {
marginTop: 0
}
}

View file

@ -0,0 +1,102 @@
import * as R from 'ramda'
import React, { useContext } from 'react'
import * as Yup from 'yup'
import { Table as EditableTable } from 'src/components/editableTable'
import Autocomplete from 'src/components/inputs/formik/Autocomplete'
import TextInputFormik from 'src/components/inputs/formik/TextInput.js'
import NotificationsCtx from '../NotificationsContext'
const CASSETTE_1_KEY = 'cassette1'
const CASSETTE_2_KEY = 'cassette2'
const MACHINE_KEY = 'machine'
const NAME = 'fiatBalanceOverrides'
const FiatBalanceOverrides = ({ section }) => {
const { machines, data, save, isDisabled, setEditing } = useContext(
NotificationsCtx
)
const setupValues = data?.fiatBalanceOverrides ?? []
const innerSetEditing = it => setEditing(NAME, it)
const getSuggestions = () => {
const overridenMachines = R.map(override => override.machine, setupValues)
return R.without(overridenMachines, machines ?? [])
}
const initialValues = {
[MACHINE_KEY]: null,
[CASSETTE_1_KEY]: '',
[CASSETTE_2_KEY]: ''
}
const validationSchema = Yup.object().shape({
[MACHINE_KEY]: Yup.string().required(),
[CASSETTE_1_KEY]: Yup.number()
.integer()
.min(0)
.required(),
[CASSETTE_2_KEY]: Yup.number()
.integer()
.min(0)
.required()
})
const suggestions = getSuggestions()
const elements = [
{
name: MACHINE_KEY,
width: 238,
size: 'sm',
view: R.path(['name']),
input: Autocomplete,
inputProps: {
options: suggestions,
limit: null,
forceShowValue: true,
getOptionSelected: R.eqProps('display')
}
},
{
name: CASSETTE_1_KEY,
display: 'Cash-out 1',
width: 155,
textAlign: 'right',
bold: true,
input: TextInputFormik,
suffix: 'notes'
},
{
name: CASSETTE_2_KEY,
display: 'Cash-out 2',
width: 155,
textAlign: 'right',
bold: true,
input: TextInputFormik,
suffix: 'notes'
}
]
return (
<EditableTable
name={NAME}
title="Overrides"
enableDelete
enableEdit
enableCreate
save={it => save(section, it)}
initialValues={initialValues}
validationSchema={validationSchema}
forceDisable={isDisabled(NAME) || !machines}
data={setupValues}
elements={elements}
disableAdd={!suggestions?.length}
setEditing={innerSetEditing}
/>
)
}
export default FiatBalanceOverrides

View file

@ -0,0 +1,87 @@
import { makeStyles } from '@material-ui/core'
import * as R from 'ramda'
import React, { useContext } from 'react'
import {
Table,
THead,
TBody,
Tr,
Td,
Th
} from 'src/components/fake-table/Table'
import { Switch } from 'src/components/inputs'
import { startCase } from 'src/utils/string'
import NotificationsCtx from '../NotificationsContext'
const channelSize = 129
const sizes = {
balance: 152,
transactions: 184,
compliance: 178,
errors: 142,
active: 263
}
const width = R.sum(R.values(sizes)) + channelSize
const Row = ({ namespace }) => {
const { data, save } = useContext(NotificationsCtx)
const disabled = !data || !data[`${namespace}_active`]
const Cell = ({ name, disabled }) => {
const namespaced = `${namespace}_${name}`
const value = !!(data && data[namespaced])
return (
<Td width={sizes[name]} textAlign="center">
<Switch
disabled={disabled}
checked={value}
onChange={event => {
save(null, { [namespaced]: event.target.checked })
}}
value={value}
/>
</Td>
)
}
return (
<Tr>
<Td width={channelSize}>{startCase(namespace)}</Td>
<Cell name="balance" disabled={disabled} />
<Cell name="transactions" disabled={disabled} />
<Cell name="compliance" disabled={disabled} />
<Cell name="errors" disabled={disabled} />
<Cell name="active" />
</Tr>
)
}
const useStyles = makeStyles({
mainTable: {
width
}
})
const Setup = () => {
const classes = useStyles()
return (
<Table className={classes.mainTable}>
<THead>
<Th width={channelSize}>Channel</Th>
{Object.keys(sizes).map(it => (
<Th key={it} width={sizes[it]} textAlign="center">
{startCase(it)}
</Th>
))}
</THead>
<TBody>
<Row namespace="email" />
<Row namespace="sms" />
</TBody>
</Table>
)
}
export default Setup

View file

@ -0,0 +1,18 @@
import React from 'react'
import SingleFieldEditableNumber from '../components/SingleFieldEditableNumber'
const NAME = 'highValueTransaction'
const TransactionAlerts = ({ section }) => {
return (
<SingleFieldEditableNumber
section={section}
title="High value transaction"
label="Alert me over"
name={NAME}
/>
)
}
export default TransactionAlerts

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react'
import * as R from 'ramda'
import { makeStyles } from '@material-ui/core'
import * as R from 'ramda'
import React, { useState } from 'react'
import Sidebar from 'src/components/Sidebar'
import Title from 'src/components/Title'
@ -53,7 +53,6 @@ const OperatorInfo = () => {
{isSelected(CONTACT_INFORMATION) && <ContactInfo />}
{isSelected(TERMS_CONDITIONS) && <TermsConditions />}
{isSelected(COIN_ATM_RADAR) && <CoinAtmRadar />}
{isSelected(TERMS_CONDITIONS) && <TermsConditions />}
</div>
</div>
</>

View file

@ -1,11 +1,10 @@
import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost'
import moment from 'moment'
import * as R from 'ramda'
import React, { useState } from 'react'
import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
import typographyStyles from 'src/components/typography/styles'
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
import Title from 'src/components/Title'
import Uptime from 'src/components/Uptime'
@ -20,6 +19,7 @@ import {
TableCell
} from 'src/components/table'
import { Info3 } from 'src/components/typography'
import typographyStyles from 'src/components/typography/styles'
import { ReactComponent as DownloadActive } from 'src/styling/icons/button/download/white.svg'
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg'
import { offColor } from 'src/styling/variables'

View file

@ -70,29 +70,29 @@ const Transactions = () => {
const elements = [
{
header: '',
size: 62,
width: 62,
view: it => (it.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />)
},
{
header: 'Machine',
name: 'machineName',
size: 180,
width: 180,
view: R.path(['machineName'])
},
{
header: 'Customer',
size: 162,
width: 162,
view: getCustomerDisplayName
},
{
header: 'Cash',
size: 110,
width: 110,
textAlign: 'right',
view: it => `${Number.parseFloat(it.fiat)} ${it.fiatCode}`
},
{
header: 'Crypto',
size: 141,
width: 141,
textAlign: 'right',
view: it =>
`${toUnit(new BigNumber(it.cryptoAtoms), it.cryptoCode).toFormat(5)} ${
@ -103,27 +103,27 @@ const Transactions = () => {
header: 'Address',
view: R.path(['toAddress']),
className: classes.overflowTd,
size: 136
width: 136
},
{
header: 'Date (UTC)',
view: it => moment.utc(it.created).format('YYYY-MM-D'),
textAlign: 'right',
size: 124
width: 124
},
{
header: 'Time (UTC)',
view: it => moment.utc(it.created).format('HH:mm:ss'),
textAlign: 'right',
size: 124
width: 124
},
{
header: '', // Trade
view: () => {},
size: 90
width: 90
},
{
size: 71
width: 71
}
]

View file

@ -1,13 +1,13 @@
import { makeStyles } from '@material-ui/core'
import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost'
import moment from 'moment'
import * as R from 'ramda'
import React from 'react'
import ExpTable from '../../components/expandable-table/ExpTable'
import { MainStatus } from '../../components/Status'
import Title from '../../components/Title'
import ExpTable from '../../components/expandable-table/ExpTable'
import { ReactComponent as WarningIcon } from '../../styling/icons/status/pumpkin.svg'
import { ReactComponent as ErrorIcon } from '../../styling/icons/status/tomato.svg'
import { mainStyles } from '../Transactions/Transactions.styles'
@ -41,36 +41,36 @@ const MachineStatus = () => {
const elements = [
{
header: 'Machine Name',
size: 232,
width: 232,
textAlign: 'left',
view: m => m.name
},
{
header: 'Status',
size: 349,
width: 349,
textAlign: 'left',
view: m => <MainStatus statuses={m.statuses} />
},
{
header: 'Last ping',
size: 192,
width: 192,
textAlign: 'left',
view: m => moment(m.lastPing).fromNow()
},
{
header: 'Ping Time',
size: 155,
width: 155,
textAlign: 'left',
view: m => m.pingTime || 'unknown'
},
{
header: 'Software Version',
size: 201,
width: 201,
textAlign: 'left',
view: m => m.softwareVersion || 'unknown'
},
{
size: 71
width: 71
}
]

View file

@ -16,25 +16,43 @@ import Transactions from 'src/pages/Transactions/Transactions'
import MachineStatus from 'src/pages/maintenance/MachineStatus'
const tree = [
{ key: 'transactions', label: 'Transactions', route: '/transactions' },
// maintenence: { label: 'Maintenence', children: [{ label: 'Locale', route: '/locale' }] },
// analytics: { label: 'Analytics', children: [{ label: 'Locale', route: '/locale' }] },
{
key: 'transactions',
label: 'Transactions',
route: '/transactions',
component: Transactions
},
{
key: 'maintenance',
label: 'Maintenance',
route: '/maintenance',
get component() {
return () => <Redirect to={this.children[0].route} />
},
children: [
{ key: 'logs', label: 'Logs', route: '/maintenance/logs' },
{ key: 'fuding', label: 'Funding', route: '/maintenance/funding' },
{
key: 'logs',
label: 'Logs',
route: '/maintenance/logs',
component: MachineLogs
},
{
key: 'fuding',
label: 'Funding',
route: '/maintenance/funding',
component: Funding
},
{
key: 'server-logs',
label: 'Server',
route: '/maintenance/server-logs'
route: '/maintenance/server-logs',
component: ServerLogs
},
{
key: 'machine-status',
label: 'Machine Status',
route: '/maintenance/machine-status'
route: '/maintenance/machine-status',
component: MachineStatus
}
]
},
@ -42,87 +60,74 @@ const tree = [
key: 'settings',
label: 'Settings',
route: '/settings',
get component() {
return () => <Redirect to={this.children[0].route} />
},
children: [
{
key: 'commissions',
label: 'Commissions',
route: '/settings/commissions'
route: '/settings/commissions',
component: Commissions
},
{
key: 'locale',
label: 'Locale',
route: '/settings/locale',
component: Locales
},
{ key: 'locale', label: 'Locale', route: '/settings/locale' },
{
key: 'services',
label: '3rd party services',
route: '/settings/3rd-party-services'
route: '/settings/3rd-party-services',
component: Services
},
{
key: 'notifications',
label: 'Notifications',
route: '/settings/notifications'
route: '/settings/notifications',
component: Notifications
},
{ key: 'info', label: 'Operator Info', route: '/settings/operator-info' }
{
key: 'info',
label: 'Operator Info',
route: '/settings/operator-info',
component: OperatorInfo
}
]
},
{
key: 'compliance',
label: 'Compliance',
route: '/compliance',
get component() {
return () => <Redirect to={this.children[0].route} />
},
children: [
// {
// key: 'triggers',
// label: 'Triggers',
// route: '/compliance/triggers'
// },
{
key: 'customers',
label: 'Customers',
route: '/compliance/customers'
route: '/compliance/customers',
component: Customers
}
// {
// key: 'blacklist',
// label: 'Blacklist',
// route: '/compliance/blacklist'
// }
]
}
// compliance: { label: 'Compliance', children: [{ label: 'Locale', route: '/locale' }] }
]
const firstChild = key => {
const getRoute = R.path(['children', 0, 'route'])
const withKey = R.find(R.propEq('key', key))
return R.compose(getRoute, withKey)(tree)
}
const map = R.map(R.when(R.has('children'), R.prop('children')))
const leafRoutes = R.compose(R.flatten, map)(tree)
const parentRoutes = R.filter(R.has('children'))(tree)
const flattened = R.concat(leafRoutes, parentRoutes)
const Routes = () => (
<Switch>
<Route exact path="/" />
<Route
path="/settings"
exact
component={() => <Redirect to={firstChild('settings')} />}
/>
<Route
path="/maintenance"
exact
component={() => <Redirect to={firstChild('maintenance')} />}
/>
<Route
path="/compliance"
exact
component={() => <Redirect to={firstChild('compliance')} />}
/>
<Route path="/settings/commissions" component={Commissions} />
<Route path="/settings/locale" component={Locales} />
<Route path="/settings/3rd-party-services" component={Services} />
<Route path="/settings/notifications" component={Notifications} />
<Route path="/settings/operator-info" component={OperatorInfo} />
<Route path="/maintenance/logs" component={MachineLogs} />
<Route path="/maintenance/funding" component={Funding} />
<Route path="/maintenance/server-logs" component={ServerLogs} />
<Route path="/transactions" component={Transactions} />
<Route path="/register" component={AuthRegister} />
<Route path="/maintenance/machine-status" component={MachineStatus} />
<Route path="/compliance/customers" component={Customers} />
{flattened.map(({ route, component: Page, key }) => (
<Route path={route} key={key}>
<Page name={key} />
</Route>
))}
</Switch>
)

View file

@ -12,10 +12,10 @@ import { create } from 'jss'
import extendJss from 'jss-plugin-extend'
import React from 'react'
import { ReactComponent as AuthorizeIconReversed } from 'src/styling/icons/button/authorize/white.svg'
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg'
import { ActionButton, Button, Link } from 'src/components/buttons'
import { Radio, TextInput, Switch } from 'src/components/inputs'
import { ReactComponent as AuthorizeIconReversed } from 'src/styling/icons/button/authorize/white.svg'
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg'
import ConfirmDialog from '../components/ConfirmDialog'
import {
@ -29,7 +29,11 @@ import {
Info2,
Mono
} from '../components/typography'
import { inputFontFamily, secondaryColor } from '../styling/variables'
import {
inputFontFamily,
secondaryColor,
fontColor
} from '../styling/variables'
const jss = create({
plugins: [extendJss(), ...jssPreset().plugins]
@ -78,6 +82,19 @@ const theme = createMuiTheme({
dark: secondaryColor,
main: secondaryColor
}
},
overrides: {
MuiInputLabel: {
paddingLeft: 4
},
MuiFormLabel: {
root: {
color: fontColor,
'&$focused': {
color: 'blue'
}
}
}
}
})

View file

@ -1,4 +1,26 @@
import {
inputFontSize,
inputFontSizeLg,
inputFontSizeSm,
inputFontWeightBold
} from './variables'
const respondTo = breakpoint =>
`@media only screen and (max-width: ${breakpoint})`
export { respondTo }
const bySize = size => {
switch (size) {
case 'sm':
return { fontSize: inputFontSizeSm }
case 'lg':
return { fontSize: inputFontSizeLg, fontWeight: inputFontWeightBold }
default:
return { fontSize: inputFontSize }
}
}
const bold = {
fontWeight: inputFontWeightBold
}
export { respondTo, bySize, bold }

View file

@ -1,24 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>icon/action/delete/disabled</title>
<desc>Created with Sketch.</desc>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22"></rect>
</defs>
<g id="icon/action/delete/disabled" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
<svg
width="22"
height="22"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path id="a" d="M0 0h22v22H0z" />
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="b" fill="#fff">
<use xlink:href="#a" />
</mask>
<g id="Background"></g>
<line x1="9" y1="9" x2="9" y2="18" id="Stroke-1" stroke="#9B9B9B" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></line>
<line x1="13" y1="9" x2="13" y2="18" id="Stroke-2" stroke="#9B9B9B" stroke-width="2" stroke-linecap="round" mask="url(#mask-2)"></line>
<g id="Group-9" mask="url(#mask-2)" stroke="#9B9B9B" stroke-linecap="round" stroke-width="2">
<g transform="translate(1.000000, 1.000000)">
<polyline id="Stroke-3" stroke-linejoin="round" points="2 5 4 20 16 20 18 5"></polyline>
<line x1="0" y1="4" x2="20" y2="4" id="Stroke-5"></line>
<path d="M13,3 C13,1.343 11.657,0 10,0 C8.343,0 7,1.343 7,3" id="Stroke-7"></path>
</g>
</g>
<path
stroke="#9B9B9B"
stroke-width="2"
stroke-linecap="round"
mask="url(#b)"
d="M9 9v9M13 9v9"
/>
<g
mask="url(#b)"
stroke="#9B9B9B"
stroke-linecap="round"
stroke-width="2">
<path stroke-linejoin="round" d="M3 6l2 15h12l2-15" />
<path d="M1 5h20M14 4a3 3 0 10-6 0" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 611 B

Before After
Before After

View file

@ -1,6 +1,14 @@
import { createMuiTheme } from '@material-ui/core/styles'
import { backgroundColor, inputFontFamily, secondaryColor } from './variables'
import {
backgroundColor,
inputFontFamily,
secondaryColor,
fontColor,
offColor,
subheaderColor,
fontSize5
} from './variables'
export default createMuiTheme({
typography: {
@ -23,5 +31,69 @@ export default createMuiTheme({
background: {
default: backgroundColor
}
},
overrides: {
MuiAutocomplete: {
root: {
color: fontColor
},
noOptions: {
padding: [[6, 16]]
},
option: {
'&[data-focus="true"]': {
backgroundColor: subheaderColor
}
},
paper: {
color: fontColor,
margin: 0
},
listbox: {
padding: 0
},
tag: {
'&[data-tag-index="0"]': {
marginLeft: 0
},
backgroundColor: subheaderColor,
borderRadius: 4,
height: 18
}
},
MuiChip: {
label: {
paddingLeft: 4,
paddingRight: 4,
color: fontColor,
fontSize: fontSize5
}
},
MuiInput: {
root: {
color: fontColor
},
underline: {
'&:before': {
borderBottom: [[2, 'solid', fontColor]]
}
}
},
MuiInputLabel: {
root: {
font: 'inherit',
color: offColor
},
shrink: {
color: fontColor
}
},
MuiFormLabel: {
root: {
'&$focused': {
color: fontColor
}
}
}
}
})

View file

@ -78,10 +78,11 @@ if (version === 8) {
}
const smallestFontSize = fontSize5
const inputFontSizeSm = fontSize4
const inputFontSize = fontSize3
const inputFontSizeLg = fontSize1
const inputFontWeight = 500
const inputFontWeightLg = 700
const inputFontWeightBold = 700
const inputFontFamily = fontSecondary
// Breakpoints
@ -159,10 +160,11 @@ export {
// named font sizes
smallestFontSize,
inputFontSize,
inputFontSizeSm,
inputFontSizeLg,
inputFontFamily,
inputFontWeight,
inputFontWeightLg,
inputFontWeightBold,
// screen sizes
sm,
md,

View file

@ -0,0 +1,21 @@
import * as R from 'ramda'
const mapKeys = R.curry((fn, obj) =>
R.fromPairs(R.map(R.adjust(0, fn), R.toPairs(obj)))
)
const filterByKey = R.curry((fn, obj) =>
R.fromPairs(R.filter(it => fn(it[0]), R.toPairs(obj)))
)
const stripl = R.curry((q, str) =>
R.startsWith(q, str) ? str.slice(q.length) : str
)
const filtered = key => filterByKey(R.startsWith(`${key}_`))
const stripped = key => mapKeys(stripl(`${key}_`))
const fromServer = key => R.compose(stripped(key), filtered(key))
const toServer = key => mapKeys(it => `${key}_${it}`)
export { fromServer, toServer }