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) { function saveConfig (config) {
const currentState = db.getState() const currentState = db.getState()
// TODO this should be _.assign
// change after flattening of schema
const newState = _.mergeWith((objValue, srcValue) => { const newState = _.mergeWith((objValue, srcValue) => {
if (_.isArray(objValue)) { if (_.isArray(objValue)) {
return srcValue return srcValue

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,9 +2,9 @@ import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames' import classnames from 'classnames'
import React, { memo } from 'react' 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 { ReactComponent as AddIcon } from 'src/styling/icons/button/add/zodiac.svg'
import { zircon, zircon2, comet, fontColor, white } from 'src/styling/variables' import { zircon, zircon2, comet, fontColor, white } from 'src/styling/variables'
import typographyStyles from 'src/components/typography/styles'
const { p } = typographyStyles 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 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 classes = useStyles()
const classNames = { const classNames = {
[classes.link]: true, [classes.link]: true,
@ -23,6 +24,7 @@ const Link = memo(({ submit, className, children, color, size, ...props }) => {
{children} {children}
</button> </button>
) )
}) }
)
export default Link export default Link

View file

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

View file

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

View file

@ -19,15 +19,18 @@ const DataTable = memo(({ elements, data }) => {
<> <>
<div> <div>
<THead> <THead>
{elements.map(({ size, className, textAlign, header }, idx) => ( {elements.map(
({ width, size, className, textAlign, header }, idx) => (
<Th <Th
key={idx} key={idx}
size={size} size={size}
width={width}
className={className} className={className}
textAlign={textAlign}> textAlign={textAlign}>
{header} {header}
</Th> </Th>
))} )
)}
</THead> </THead>
</div> </div>
<div style={{ flex: '1 1 auto' }}> <div style={{ flex: '1 1 auto' }}>
@ -52,8 +55,8 @@ const DataTable = memo(({ elements, data }) => {
{elements.map( {elements.map(
( (
{ {
header,
size, size,
width,
className, className,
textAlign, textAlign,
view = it => it?.toString() view = it => it?.toString()
@ -63,6 +66,7 @@ const DataTable = memo(({ elements, data }) => {
<Td <Td
key={idx} key={idx}
size={size} size={size}
width={width}
className={className} className={className}
textAlign={textAlign}> textAlign={textAlign}>
{view(data[index])} {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 { 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 { Link, IconButton } from 'src/components/buttons'
import { Td, Tr, CellDoubleLevel } from 'src/components/fake-table/Table' import { Td, Tr } from 'src/components/fake-table/Table'
import { TextInputDisplay } from 'src/components/inputs/base/TextInput' import { TL2 } from 'src/components/typography'
import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
import { ReactComponent as DisabledDeleteIcon } from 'src/styling/icons/action/delete/disabled.svg' 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 DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
// import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.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 = { import styles from './Row.styles'
button: { import { ACTION_COL_SIZE } from './consts'
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
// }
// }
}
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const ERow = memo( const ActionCol = ({
({ elements, editing, setEditing, disableAction, action }) => { editing,
setEditing,
enableEdit,
disabled,
onDelete,
enableDelete
}) => {
const classes = useStyles() 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, name,
input, input,
type,
display,
className,
size, size,
bold,
width,
textAlign, textAlign,
inputProps, suffix,
editing 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 ( return (
<Td size={size} textAlign={textAlign}> <Td
{editing && ( className={{ [classes.withSuffix]: suffix }}
<Field width={width}
id={name} size={size}
name={name} textAlign={textAlign}>
component={input} {editing && <Field name={name} component={input} {...iProps} />}
className={className} {!editing && values && (
{...inputProps} <div className={classnames(viewClasses)}>{view(values[name])}</div>
/>
)}
{!editing && type === 'text' && (
<TextInputDisplay display={display} {...inputProps} />
)} )}
{suffix && <TL2 className={classes.suffix}>{suffix}</TL2>}
</Td> </Td>
) )
} }
const actionCol = R.last(elements) const ERow = ({
const { values, errors } = useFormikContext() elements,
enableEdit,
const actionColClasses = { enableDelete,
[classes.actionCol]: true, onDelete,
[classes.actionColDisplayMode]: !editing, editing,
[classes.actionColEditMode]: editing setEditing,
} disabled
}) => {
const icon = (action, disabled) => { const { errors } = useFormikContext()
if (action === 'delete' && !disabled) return <DeleteIcon />
if (action === 'delete' && disabled) return <DisabledDeleteIcon />
}
return ( return (
<Tr <Tr
error={errors && errors.length} error={errors && errors.length}
errorMessage={errors && errors.toString()}> errorMessage={errors && errors.toString()}>
{R.init(elements).map((element, idx) => { {elements.map((it, idx) => (
const colClasses = { <ECol key={idx} config={it} editing={editing} />
[classes.textInput]: true ))}
} {(enableEdit || enableDelete) && (
<ActionCol
if (Array.isArray(element)) { disabled={disabled}
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}
editing={editing} 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> </Tr>
) )
} }
)
const ERowWithFormik = memo( export default ERow
({
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

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 * as R from 'ramda'
import React, { useState } from 'react'
import { v4 } from 'uuid'
import { import Link from 'src/components/buttons/Link.js'
Th, import { AddButton } from 'src/components/buttons/index.js'
ThDoubleLevel, import { TBody, Table } from 'src/components/fake-table/Table'
THead, import { Info2 } from 'src/components/typography'
TBody,
Table,
TDoubleLevelHead
} from 'src/components/fake-table/Table'
import { startCase } from 'src/utils/string'
import Header from './Header'
import ERow from './Row' import ERow from './Row'
import styles from './Table.styles'
import { DEFAULT_COL_SIZE, ACTION_COL_SIZE } from './consts'
const ETHead = memo(({ elements, className }) => { const useStyles = makeStyles(styles)
const action = R.last(elements)
return ( const getWidth = R.compose(
<THead className={className?.root}> R.reduce(R.add)(0),
{R.init(elements).map(({ name, size, display, textAlign }, idx) => ( R.map(it => it.width ?? DEFAULT_COL_SIZE)
<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 ETDoubleHead = memo(({ elements, className }) => { const ETable = ({
const action = R.last(elements) name,
title,
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(
({
elements = [], elements = [],
data = [], data = [],
save, save,
reset,
action,
initialValues,
validationSchema, validationSchema,
editing, enableCreate,
addingRow, forceDisable,
disableAction, disableAdd,
className, enableDelete,
double 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 ( return (
<Table className={className}> <div className={classes.wrapper}>
{!double && <ETHead elements={elements} />} {showButtonOnEmpty && (
{double && ( <AddButton disabled={!canAdd} onClick={addField}>
<ETDoubleHead elements={elements} className={className?.head} /> {createText}
</AddButton>
)} )}
<TBody> {showTable && (
{addingRow && ( <>
<ERow <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} elements={elements}
initialValues={initialValues} enableEdit={enableEdit}
save={save} enableDelete={enableDelete}
reset={reset}
validationSchema={validationSchema}
editing
/> />
<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) => ( {data.map((it, idx) => (
<ERow <Formik
key={idx} key={it.id ?? idx}
enableReinitialize
initialValues={it} initialValues={it}
elements={elements} onReset={onReset}
save={save}
reset={it => reset(it)}
action={action}
validationSchema={validationSchema} validationSchema={validationSchema}
disableAction={disableAction} onSubmit={innerSave}>
editing={editing[idx]} <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> </TBody>
</Table> </Table>
</>
)}
</div>
) )
} }
)
export default ETable 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 CellMeasurerCache
} from 'react-virtualized' } 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 ExpandClosedIcon } from 'src/styling/icons/action/expand/closed.svg'
import { ReactComponent as ExpandOpenIcon } from 'src/styling/icons/action/expand/open.svg' import { ReactComponent as ExpandOpenIcon } from 'src/styling/icons/action/expand/open.svg'
import { mainWidth } from 'src/styling/variables' import { mainWidth } from 'src/styling/variables'
import { THead, Tr, Td, Th } from 'src/components/fake-table/Table'
const styles = { const styles = {
expandButton: { expandButton: {
@ -48,25 +48,19 @@ const ExpRow = ({
.slice(0, -1) .slice(0, -1)
.map( .map(
( (
{ { width, className, textAlign, view = it => it?.toString() },
header,
size,
className,
textAlign,
view = it => it?.toString()
},
idx idx
) => ( ) => (
<Td <Td
key={idx} key={idx}
size={size} width={width}
className={className} className={className}
textAlign={textAlign}> textAlign={textAlign}>
{view(data)} {view(data)}
</Td> </Td>
) )
)} )}
<Td size={elements[elements.length - 1].size}> <Td width={elements[elements.length - 1].width}>
<button <button
onClick={() => expandRow(id)} onClick={() => expandRow(id)}
className={classes.expandButton}> className={classes.expandButton}>
@ -77,7 +71,7 @@ const ExpRow = ({
</Tr> </Tr>
{expanded && ( {expanded && (
<Tr className={classes.detailsRow}> <Tr className={classes.detailsRow}>
<Td size={mainWidth}> <Td width={mainWidth}>
<Details it={data} /> <Details it={data} />
</Td> </Td>
</Tr> </Tr>
@ -86,8 +80,8 @@ const ExpRow = ({
) )
} }
/* rows = [{ columns = [{ name, value, className, textAlign, size }], details, className, error, errorMessage }] /* rows = [{ columns = [{ name, value, className, textAlign, width }], details, className, error, errorMessage }]
* Don't forget to include the size of the last (expand button) column! * Don't forget to include the width of the last (expand button) column!
*/ */
const ExpTable = ({ const ExpTable = ({
elements = [], elements = [],
@ -133,10 +127,10 @@ const ExpTable = ({
<> <>
<div> <div>
<THead> <THead>
{elements.map(({ size, className, textAlign, header }, idx) => ( {elements.map(({ width, className, textAlign, header }, idx) => (
<Th <Th
key={idx} key={idx}
size={size} width={width}
className={className} className={className}
textAlign={textAlign}> textAlign={textAlign}>
{header} {header}

View file

@ -5,103 +5,10 @@ import classnames from 'classnames'
import React from 'react' import React from 'react'
import { Link } from 'src/components/buttons' 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({ const useStyles = makeStyles(styles)
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 Table = ({ children, className, ...props }) => ( const Table = ({ children, className, ...props }) => (
<div className={classnames(className)} {...props}> <div className={classnames(className)} {...props}>
@ -129,21 +36,25 @@ const TBody = ({ children, className }) => {
return <div className={classnames(className, classes.body)}>{children}</div> return <div className={classnames(className, classes.body)}>{children}</div>
} }
const Td = ({ children, header, className, size = 100, textAlign, action }) => { const Td = ({
const classes = useStyles() children,
header,
className,
width = 100,
size,
textAlign,
action
}) => {
const classes = useStyles({ textAlign, width })
const classNames = { const classNames = {
[classes.td]: true, [classes.td]: true,
[classes.tdHeader]: header, [classes.tdHeader]: header,
[classes.actionCol]: action [classes.actionCol]: action,
[classes.large]: size === 'lg' && !header,
[classes.md]: size === 'md' && !header
} }
return ( return <div className={classnames(className, classNames)}>{children}</div>
<div
className={classnames(className, classNames)}
style={{ width: size, textAlign }}>
{children}
</div>
)
} }
const Th = ({ children, ...props }) => { 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 CheckboxInput = ({ name, onChange, value, label, ...props }) => {
const classes = useStyles() const classes = useStyles()
// const { name, onChange, value } = props.field
return ( return (
<Checkbox <Checkbox
id={name} id={name}

View file

@ -1,105 +1,12 @@
import React, { memo } from 'react' import { makeStyles } from '@material-ui/core'
import classnames from 'classnames'
import TextField from '@material-ui/core/TextField' 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 { import styles from './TextInput.styles'
fontColor,
offColor,
secondaryColor,
inputFontSize,
inputFontSizeLg,
inputFontWeight,
inputFontWeightLg
} from 'src/styling/variables'
import { TL2, Label2, Info1, Info2 } from 'src/components/typography'
const useStyles = makeStyles({ const useStyles = makeStyles(styles)
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 TextInput = memo( const TextInput = memo(
({ ({
@ -109,22 +16,23 @@ const TextInput = memo(
value, value,
error, error,
suffix, suffix,
large, textAlign,
width,
// lg or sm
size,
bold,
className, className,
InputProps, InputProps,
...props ...props
}) => { }) => {
const classes = useStyles() const classes = useStyles({ textAlign, width, size })
const filled = !error && value && !R.isEmpty(value)
const classNames = { const inputClasses = {
[className]: true, [classes.bold]: bold
[classes.filled]: !error && value,
[classes.empty]: !value || value === ''
} }
return ( return (
<div className={classes.wrapper}>
<span>
<TextField <TextField
id={name} id={name}
onChange={onChange} onChange={onChange}
@ -132,24 +40,19 @@ const TextInput = memo(
error={error} error={error}
value={value} value={value}
classes={{ root: classes.root }} classes={{ root: classes.root }}
className={classnames(classNames)} className={className}
InputProps={{ InputProps={{
className: large ? classes.inputRootLg : classes.inputRoot, className: classnames(inputClasses),
classes: {
root: classes.size,
underline: filled ? classes.underline : null
},
...InputProps ...InputProps
}} }}
InputLabelProps={{ className: classes.labelRoot }}
{...props} {...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 Checkbox from './Checkbox'
import { TextInput } from './TextInput'
import Switch from './Switch'
import RadioGroup from './RadioGroup' 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' import { Checkbox } from '../base'
const CheckboxInput = memo(({ label, ...props }) => { const CheckboxInput = memo(({ label, textAlign, fullWidth, ...props }) => {
const { name, onChange, value } = props.field const { name, onChange, value } = props.field
return <Checkbox name={name} onChange={onChange} value={value} {...props} /> return <Checkbox name={name} onChange={onChange} value={value} {...props} />

View file

@ -1,10 +1,10 @@
import typographyStyles from 'src/components/typography/styles'
import { import {
fontColor, fontColor,
offColor, offColor,
inputFontSize, inputFontSize,
inputFontWeight inputFontWeight
} from 'src/styling/variables' } from 'src/styling/variables'
import typographyStyles from 'src/components/typography/styles'
const { info3 } = typographyStyles 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 Checkbox from './base/Checkbox'
import Radio from './base/Radio' import Radio from './base/Radio'
import RadioGroup from './base/RadioGroup' import RadioGroup from './base/RadioGroup'
import Select from './base/Select' import Select from './base/Select'
import Switch from './base/Switch' import Switch from './base/Switch'
import { TextInput } from './base/TextInput' import TextInput from './base/TextInput'
export { export { TextInput, Radio, Checkbox, Switch, Select, RadioGroup }
Autocomplete,
AutocompleteMultiple,
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 { 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 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 { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg'
import { import {
offColor, offColor,
@ -11,8 +13,6 @@ import {
tableNewDisabledHeaderColor, tableNewDisabledHeaderColor,
secondaryColorDarker secondaryColorDarker
} from 'src/styling/variables' } 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 const { label1, p } = typographyStyles
@ -106,11 +106,11 @@ const SingleRowTable = ({
{items && ( {items && (
<Table className={classnames(className, classes.wrapper)}> <Table className={classnames(className, classes.wrapper)}>
<THead className={classnames(headerClasses)}> <THead className={classnames(headerClasses)}>
<Th size={width - editButtonSize}> <Th width={width - editButtonSize}>
{title} {title}
{newService && <span className={classes.spanNew}>New</span>} {newService && <span className={classes.spanNew}>New</span>}
</Th> </Th>
<Th size={editButtonSize} className={classes.buttonTh}> <Th width={editButtonSize} className={classes.buttonTh}>
{!disabled && ( {!disabled && (
<button className={classes.editButton} onClick={onEdit}> <button className={classes.editButton} onClick={onEdit}>
<EditIcon /> <EditIcon />
@ -124,7 +124,7 @@ const SingleRowTable = ({
</Th> </Th>
</THead> </THead>
<TBody className={classnames(bodyClasses)}> <TBody className={classnames(bodyClasses)}>
<Td size={width}> <Td width={width}>
{!disabled && ( {!disabled && (
<> <>
{items[0] && ( {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 useAxios from '@use-hooks/axios'
import React from 'react' import React from 'react'
import { useLocation, useHistory } from 'react-router-dom' import { useLocation, useHistory } from 'react-router-dom'
const useQuery = () => new URLSearchParams(useLocation().search) const useQuery = () => new URLSearchParams(useLocation().search)
const url =
process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : ''
const AuthRegister = () => { const AuthRegister = () => {
const history = useHistory() const history = useHistory()
const query = useQuery() const query = useQuery()
useAxios({ useAxios({
url: `/api/register?otp=${query.get('otp')}`, url: `${url}/api/register?otp=${query.get('otp')}`,
method: 'GET', method: 'GET',
options: { options: {
withCredentials: true withCredentials: true

View file

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

View file

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

View file

@ -20,41 +20,48 @@ const MainForm = memo(({ value, save, auxData, validationSchema }) => {
return ( return (
<EditableTable <EditableTable
name="locale"
title="Default Settings"
altTitleColor
initialValues={{ country: null }}
enableEdit
onDelete={() => {}}
setEditing={() => {}}
save={save} save={save}
validationSchema={validationSchema} validationSchema={validationSchema}
data={R.of(value)} data={R.of(value)}
elements={[ elements={[
{ {
name: 'country', name: 'country',
size: sizes.country, width: sizes.country,
view: R.path(['display']), view: R.path(['display']),
input: Autocomplete, input: Autocomplete,
inputProps: { suggestions: getData(['countries']) } inputProps: { suggestions: getData(['countries']) }
}, },
{ {
name: 'fiatCurrency', name: 'fiatCurrency',
size: sizes.fiatCurrency, width: sizes.fiatCurrency,
view: R.path(['code']), view: R.path(['code']),
input: Autocomplete, input: Autocomplete,
inputProps: { suggestions: getData(['currencies']) } inputProps: { suggestions: getData(['currencies']) }
}, },
{ {
name: 'languages', name: 'languages',
size: sizes.languages, width: sizes.languages,
view: displayCodeArray, view: displayCodeArray,
input: AutocompleteMultiple, input: AutocompleteMultiple,
inputProps: { suggestions: getData(['languages']) } inputProps: { suggestions: getData(['languages']) }
}, },
{ {
name: 'cryptoCurrencies', name: 'cryptoCurrencies',
size: sizes.cryptoCurrencies, width: sizes.cryptoCurrencies,
view: displayCodeArray, view: displayCodeArray,
input: AutocompleteMultiple, input: AutocompleteMultiple,
inputProps: { suggestions: getData(['cryptoCurrencies']) } inputProps: { suggestions: getData(['cryptoCurrencies']) }
}, },
{ {
name: 'showRates', name: 'showRates',
size: sizes.showRates, width: sizes.showRates,
textAlign: 'center', textAlign: 'center',
view: it => (it ? 'true' : 'false'), view: it => (it ? 'true' : 'false'),
input: Checkbox 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 { 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 TitleSection from 'src/components/layout/TitleSection'
import Title from 'src/components/Title' import { fromServer, toServer } from 'src/utils/config'
import ErrorMessage from 'src/components/ErrorMessage'
import commonStyles from 'src/pages/common.styles'
import { localStyles } from './Notifications.styles' import Section from '../../components/layout/Section'
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'
const fiatBalanceAlertsInitialValues = { import NotificationsCtx from './NotificationsContext'
[CASH_IN_FULL_KEY]: { import CryptoBalanceAlerts from './sections/CryptoBalanceAlerts'
[PERCENTAGE_KEY]: '', import CryptoBalanceOverrides from './sections/CryptoBalanceOverrides'
[NUMERARY_KEY]: '' import FiatBalanceAlerts from './sections/FiatBalanceAlerts'
}, import FiatBalanceOverrides from './sections/FiatBalanceOverrides'
[CASH_OUT_EMPTY_KEY]: { import Setup from './sections/Setup'
[CASSETTE_1_KEY]: '', import TransactionAlerts from './sections/TransactionAlerts'
[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
}
const GET_INFO = gql` const GET_INFO = gql`
{ query getData {
config config
machines {
name
deviceId
}
cryptoCurrencies {
code
display
}
} }
` `
@ -107,125 +36,81 @@ const SAVE_CONFIG = gql`
} }
` `
const styles = R.merge(commonStyles, localStyles) const Notifications = ({ name: SCREEN_KEY }) => {
const [section, setSection] = useState(null)
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 [error, setError] = 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, { const [saveConfig] = useMutation(SAVE_CONFIG, {
onCompleted: data => { refetchQueries: ['getData'],
const { notifications } = data.saveConfig onCompleted: () => setEditingKey(null),
setState(notifications) onError: error => setError({ error })
setEditingState(R.map(x => false, editingState)) })
setTryingToSave(null)
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) setError(null)
}, return saveConfig({ variables: { config } })
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 } } })
} }
const handleEditingClick = (key, state) => { const setEditing = (key, state) => {
setEditingState(R.merge(editingState, { [key]: state })) if (!state) {
setError(null)
}
setEditingKey(state ? key : null)
} }
const curriedSave = R.curry((key, values) => { const isEditing = key => editingKey === key
setTryingToSave(key) const isDisabled = key => editingKey && editingKey !== key
save(R.mergeDeepRight(state)({ [key]: values }))
})
if (!state) return null const contextValue = {
save,
error,
editingKey,
data: config,
currency,
isEditing,
isDisabled,
setEditing,
setSection,
machines,
cryptoCurrencies
}
return ( return (
<> <NotificationsCtx.Provider value={contextValue}>
<div className={classes.titleWrapper}> <TitleSection title="Notifications" />
<div className={classes.titleAndButtonsContainer}>
<Title>Notifications</Title> <Section title="Setup" error={error && !section}>
</div> <Setup />
</div> </Section>
<div className={classes.section}>
<SectionHeader error={error?.section === SETUP_KEY}> <Section title="Transaction alerts" error={error && section === 'tx'}>
Setup <TransactionAlerts section="tx" />
</SectionHeader> </Section>
<Setup values={state.setup} save={curriedSave(SETUP_KEY)} />
</div> <Section title="Fiat balance alerts" error={error && section === 'fiat'}>
<div className={classes.section}> <FiatBalanceAlerts section="fiat" />
<SectionHeader error={error?.section === TRANSACTION_ALERTS_KEY}> <FiatBalanceOverrides section="fiat" />
Transaction alerts </Section>
</SectionHeader>
<TransactionAlerts <Section
value={state[TRANSACTION_ALERTS_KEY]} title="Crypto balance alerts"
editingState={editingState} error={error && section === 'crypto'}>
handleEditingClick={handleEditingClick} <CryptoBalanceAlerts section="crypto" />
save={curriedSave(TRANSACTION_ALERTS_KEY)} <CryptoBalanceOverrides section="crypto" />
setError={setError} </Section>
/> </NotificationsCtx.Provider>
</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>
</>
) )
} }

View file

@ -1,252 +1,20 @@
import { offColor, primaryColor } from 'src/styling/variables' export default {
import theme from 'src/styling/theme' cryptoBalanceAlerts: {
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': {
display: 'flex', display: 'flex',
marginBottom: 36,
height: 135,
alignItems: 'center' alignItems: 'center'
}, },
'& > div:first-child': { cryptoBalanceAlertsForm: {
borderRight: [['solid', 1, primaryColor]] width: 222,
marginRight: 32
}, },
'& > div:not(:first-child)': { cryptoBalanceAlertsSecondForm: {
marginLeft: 56 marginLeft: 50
}
}, },
overrides: { vertSeparator: {
display: 'inline-block' width: 1,
}, height: '100%',
overridesTitle: { borderRight: [[1, 'solid', 'black']]
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
}
} }
} }
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 { makeStyles } from '@material-ui/core'
import * as R from 'ramda'
import React, { useState } from 'react'
import Sidebar from 'src/components/Sidebar' import Sidebar from 'src/components/Sidebar'
import Title from 'src/components/Title' import Title from 'src/components/Title'
@ -53,7 +53,6 @@ const OperatorInfo = () => {
{isSelected(CONTACT_INFORMATION) && <ContactInfo />} {isSelected(CONTACT_INFORMATION) && <ContactInfo />}
{isSelected(TERMS_CONDITIONS) && <TermsConditions />} {isSelected(TERMS_CONDITIONS) && <TermsConditions />}
{isSelected(COIN_ATM_RADAR) && <CoinAtmRadar />} {isSelected(COIN_ATM_RADAR) && <CoinAtmRadar />}
{isSelected(TERMS_CONDITIONS) && <TermsConditions />}
</div> </div>
</div> </div>
</> </>

View file

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

View file

@ -70,29 +70,29 @@ const Transactions = () => {
const elements = [ const elements = [
{ {
header: '', header: '',
size: 62, width: 62,
view: it => (it.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />) view: it => (it.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />)
}, },
{ {
header: 'Machine', header: 'Machine',
name: 'machineName', name: 'machineName',
size: 180, width: 180,
view: R.path(['machineName']) view: R.path(['machineName'])
}, },
{ {
header: 'Customer', header: 'Customer',
size: 162, width: 162,
view: getCustomerDisplayName view: getCustomerDisplayName
}, },
{ {
header: 'Cash', header: 'Cash',
size: 110, width: 110,
textAlign: 'right', textAlign: 'right',
view: it => `${Number.parseFloat(it.fiat)} ${it.fiatCode}` view: it => `${Number.parseFloat(it.fiat)} ${it.fiatCode}`
}, },
{ {
header: 'Crypto', header: 'Crypto',
size: 141, width: 141,
textAlign: 'right', textAlign: 'right',
view: it => view: it =>
`${toUnit(new BigNumber(it.cryptoAtoms), it.cryptoCode).toFormat(5)} ${ `${toUnit(new BigNumber(it.cryptoAtoms), it.cryptoCode).toFormat(5)} ${
@ -103,27 +103,27 @@ const Transactions = () => {
header: 'Address', header: 'Address',
view: R.path(['toAddress']), view: R.path(['toAddress']),
className: classes.overflowTd, className: classes.overflowTd,
size: 136 width: 136
}, },
{ {
header: 'Date (UTC)', header: 'Date (UTC)',
view: it => moment.utc(it.created).format('YYYY-MM-D'), view: it => moment.utc(it.created).format('YYYY-MM-D'),
textAlign: 'right', textAlign: 'right',
size: 124 width: 124
}, },
{ {
header: 'Time (UTC)', header: 'Time (UTC)',
view: it => moment.utc(it.created).format('HH:mm:ss'), view: it => moment.utc(it.created).format('HH:mm:ss'),
textAlign: 'right', textAlign: 'right',
size: 124 width: 124
}, },
{ {
header: '', // Trade header: '', // Trade
view: () => {}, 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 { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost' import { gql } from 'apollo-boost'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React from 'react' import React from 'react'
import ExpTable from '../../components/expandable-table/ExpTable'
import { MainStatus } from '../../components/Status' import { MainStatus } from '../../components/Status'
import Title from '../../components/Title' 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 WarningIcon } from '../../styling/icons/status/pumpkin.svg'
import { ReactComponent as ErrorIcon } from '../../styling/icons/status/tomato.svg' import { ReactComponent as ErrorIcon } from '../../styling/icons/status/tomato.svg'
import { mainStyles } from '../Transactions/Transactions.styles' import { mainStyles } from '../Transactions/Transactions.styles'
@ -41,36 +41,36 @@ const MachineStatus = () => {
const elements = [ const elements = [
{ {
header: 'Machine Name', header: 'Machine Name',
size: 232, width: 232,
textAlign: 'left', textAlign: 'left',
view: m => m.name view: m => m.name
}, },
{ {
header: 'Status', header: 'Status',
size: 349, width: 349,
textAlign: 'left', textAlign: 'left',
view: m => <MainStatus statuses={m.statuses} /> view: m => <MainStatus statuses={m.statuses} />
}, },
{ {
header: 'Last ping', header: 'Last ping',
size: 192, width: 192,
textAlign: 'left', textAlign: 'left',
view: m => moment(m.lastPing).fromNow() view: m => moment(m.lastPing).fromNow()
}, },
{ {
header: 'Ping Time', header: 'Ping Time',
size: 155, width: 155,
textAlign: 'left', textAlign: 'left',
view: m => m.pingTime || 'unknown' view: m => m.pingTime || 'unknown'
}, },
{ {
header: 'Software Version', header: 'Software Version',
size: 201, width: 201,
textAlign: 'left', textAlign: 'left',
view: m => m.softwareVersion || 'unknown' 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' import MachineStatus from 'src/pages/maintenance/MachineStatus'
const tree = [ const tree = [
{ key: 'transactions', label: 'Transactions', route: '/transactions' }, {
// maintenence: { label: 'Maintenence', children: [{ label: 'Locale', route: '/locale' }] }, key: 'transactions',
// analytics: { label: 'Analytics', children: [{ label: 'Locale', route: '/locale' }] }, label: 'Transactions',
route: '/transactions',
component: Transactions
},
{ {
key: 'maintenance', key: 'maintenance',
label: 'Maintenance', label: 'Maintenance',
route: '/maintenance', route: '/maintenance',
get component() {
return () => <Redirect to={this.children[0].route} />
},
children: [ 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', key: 'server-logs',
label: 'Server', label: 'Server',
route: '/maintenance/server-logs' route: '/maintenance/server-logs',
component: ServerLogs
}, },
{ {
key: 'machine-status', key: 'machine-status',
label: 'Machine Status', label: 'Machine Status',
route: '/maintenance/machine-status' route: '/maintenance/machine-status',
component: MachineStatus
} }
] ]
}, },
@ -42,87 +60,74 @@ const tree = [
key: 'settings', key: 'settings',
label: 'Settings', label: 'Settings',
route: '/settings', route: '/settings',
get component() {
return () => <Redirect to={this.children[0].route} />
},
children: [ children: [
{ {
key: 'commissions', key: 'commissions',
label: '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', key: 'services',
label: '3rd party services', label: '3rd party services',
route: '/settings/3rd-party-services' route: '/settings/3rd-party-services',
component: Services
}, },
{ {
key: 'notifications', key: 'notifications',
label: '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', key: 'compliance',
label: 'Compliance', label: 'Compliance',
route: '/compliance', route: '/compliance',
get component() {
return () => <Redirect to={this.children[0].route} />
},
children: [ children: [
// {
// key: 'triggers',
// label: 'Triggers',
// route: '/compliance/triggers'
// },
{ {
key: 'customers', key: 'customers',
label: '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 map = R.map(R.when(R.has('children'), R.prop('children')))
const getRoute = R.path(['children', 0, 'route']) const leafRoutes = R.compose(R.flatten, map)(tree)
const withKey = R.find(R.propEq('key', key)) const parentRoutes = R.filter(R.has('children'))(tree)
return R.compose(getRoute, withKey)(tree) const flattened = R.concat(leafRoutes, parentRoutes)
}
const Routes = () => ( const Routes = () => (
<Switch> <Switch>
<Route exact path="/" /> <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="/register" component={AuthRegister} />
<Route path="/maintenance/machine-status" component={MachineStatus} /> {flattened.map(({ route, component: Page, key }) => (
<Route path="/compliance/customers" component={Customers} /> <Route path={route} key={key}>
<Page name={key} />
</Route>
))}
</Switch> </Switch>
) )

View file

@ -12,10 +12,10 @@ import { create } from 'jss'
import extendJss from 'jss-plugin-extend' import extendJss from 'jss-plugin-extend'
import React from 'react' 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 { ActionButton, Button, Link } from 'src/components/buttons'
import { Radio, TextInput, Switch } from 'src/components/inputs' 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 ConfirmDialog from '../components/ConfirmDialog'
import { import {
@ -29,7 +29,11 @@ import {
Info2, Info2,
Mono Mono
} from '../components/typography' } from '../components/typography'
import { inputFontFamily, secondaryColor } from '../styling/variables' import {
inputFontFamily,
secondaryColor,
fontColor
} from '../styling/variables'
const jss = create({ const jss = create({
plugins: [extendJss(), ...jssPreset().plugins] plugins: [extendJss(), ...jssPreset().plugins]
@ -78,6 +82,19 @@ const theme = createMuiTheme({
dark: secondaryColor, dark: secondaryColor,
main: 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 => const respondTo = breakpoint =>
`@media only screen and (max-width: ${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
<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"> width="22"
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com --> height="22"
<title>icon/action/delete/disabled</title> xmlns="http://www.w3.org/2000/svg"
<desc>Created with Sketch.</desc> xmlns:xlink="http://www.w3.org/1999/xlink">
<defs> <defs>
<rect id="path-1" x="0" y="0" width="22" height="22"></rect> <path id="a" d="M0 0h22v22H0z" />
</defs> </defs>
<g id="icon/action/delete/disabled" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white"> <mask id="b" fill="#fff">
<use xlink:href="#path-1"></use> <use xlink:href="#a" />
</mask> </mask>
<g id="Background"></g> <path
<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> stroke="#9B9B9B"
<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> stroke-width="2"
<g id="Group-9" mask="url(#mask-2)" stroke="#9B9B9B" stroke-linecap="round" stroke-width="2"> stroke-linecap="round"
<g transform="translate(1.000000, 1.000000)"> mask="url(#b)"
<polyline id="Stroke-3" stroke-linejoin="round" points="2 5 4 20 16 20 18 5"></polyline> d="M9 9v9M13 9v9"
<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> mask="url(#b)"
</g> 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>
</g>
</svg> </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 { 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({ export default createMuiTheme({
typography: { typography: {
@ -23,5 +31,69 @@ export default createMuiTheme({
background: { background: {
default: backgroundColor 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 smallestFontSize = fontSize5
const inputFontSizeSm = fontSize4
const inputFontSize = fontSize3 const inputFontSize = fontSize3
const inputFontSizeLg = fontSize1 const inputFontSizeLg = fontSize1
const inputFontWeight = 500 const inputFontWeight = 500
const inputFontWeightLg = 700 const inputFontWeightBold = 700
const inputFontFamily = fontSecondary const inputFontFamily = fontSecondary
// Breakpoints // Breakpoints
@ -159,10 +160,11 @@ export {
// named font sizes // named font sizes
smallestFontSize, smallestFontSize,
inputFontSize, inputFontSize,
inputFontSizeSm,
inputFontSizeLg, inputFontSizeLg,
inputFontFamily, inputFontFamily,
inputFontWeight, inputFontWeight,
inputFontWeightLg, inputFontWeightBold,
// screen sizes // screen sizes
sm, sm,
md, 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 }