diff --git a/package-lock.json b/package-lock.json index 1dffb63..5d79a01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -368,9 +368,9 @@ } }, "@angular/common": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-10.2.1.tgz", - "integrity": "sha512-aJtgokgWxibd7wGmktHm0uYkR/lOrbcStrn6Qisj/PIJf9xTGXYFB0yusnk103aiuBfCIKq+Wl0ZGc1s81Okaw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-10.2.4.tgz", + "integrity": "sha512-bBfsLJNDQaC2OI1mReDJuSZ/uBb7Pf3HVpRmlQKNIPllIxqX1hLH8I3Plodrns9m32JMJ6FMsQthcP0KMdRCJA==", "requires": { "tslib": "^2.0.0" } @@ -572,9 +572,9 @@ } }, "@angular/core": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-10.2.1.tgz", - "integrity": "sha512-zt9G5Ei1nxB6yVJqpiH7K6npaiEUrPWlDCq6vwXeJbmO3tbw2WWiqD55Wkx5hRfysY43swC5j7VveNytHidkkQ==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-10.2.4.tgz", + "integrity": "sha512-5xpAvmZwD9nZ8eWx10urjibqEeePGEiFXVMEn3IaJWgfdOcMmeSoioW9JUllT3w85+DlNVWbRbhz0YfE9a4jyw==", "requires": { "tslib": "^2.0.0" } @@ -619,6 +619,16 @@ "tslib": "^2.0.0" } }, + "@apidevtools/json-schema-ref-parser": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.7.tgz", + "integrity": "sha512-QdwOGF1+eeyFh+17v2Tz626WX0nucd1iKOm6JUTUvCZdbolblCOOQCxGrQPY0f7jEhn36PiAWqZnsC2r5vmUWg==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.13.1" + } + }, "@babel/code-frame": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", @@ -1729,28 +1739,60 @@ } }, "@ethereumjs/common": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.1.0.tgz", - "integrity": "sha512-sY2yBKwZjlIM5Z+sBd9ZEEH5YDB5OsvSVCi5hzTqeMfv4307JfLH0MjM09whi4InLmqWRdJp/fmt/rQyP4qxKg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.2.0.tgz", + "integrity": "sha512-PyQiTG00MJtBRkJmv46ChZL8u2XWxNBeAthznAUIUiefxPAXjbkuiCZOuncgJS34/XkMbNc9zMt/PlgKRBElig==", "requires": { - "crc-32": "^1.2.0" + "crc-32": "^1.2.0", + "ethereumjs-util": "^7.0.9" + }, + "dependencies": { + "@types/bn.js": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", + "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", + "requires": { + "@types/node": "*" + } + }, + "ethereumjs-util": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz", + "integrity": "sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw==", + "requires": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.4" + } + } } }, "@ethereumjs/tx": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.0.2.tgz", - "integrity": "sha512-zmFCosjOdj1WoYEiQBdC4sCOAllBEwxdKuY85L9FgZ4zVDfZUVsQ4S9paczt4hVt65A7N8sJwgVEzDaQmrRaqw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.1.4.tgz", + "integrity": "sha512-6cJpmmjCpG5ZVN9NJYtWvmrEQcevw9DIR8hj2ca2PszD2fxbIFXky3Z37gpf8S6u0Npv09kG8It+G4xjydZVLg==", "requires": { - "@ethereumjs/common": "^2.0.0", - "ethereumjs-util": "^7.0.8" + "@ethereumjs/common": "^2.2.0", + "ethereumjs-util": "^7.0.10" }, "dependencies": { - "ethereumjs-util": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.8.tgz", - "integrity": "sha512-JJt7tDpCAmDPw/sGoFYeq0guOVqT3pTE9xlEbBmc/nlCij3JRCoS2c96SQ6kXVHOT3xWUNLDm5QCJLQaUnVAtQ==", + "@types/bn.js": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", + "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", "requires": { - "@types/bn.js": "^4.11.3", + "@types/node": "*" + } + }, + "ethereumjs-util": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz", + "integrity": "sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw==", + "requires": { + "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", "create-hash": "^1.1.2", "ethereum-cryptography": "^0.1.3", @@ -2201,6 +2243,11 @@ "schema-utils": "^2.7.0" } }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "@ngtools/webpack": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-10.2.0.tgz", @@ -2392,9 +2439,9 @@ } }, "@types/eslint": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz", - "integrity": "sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw==", + "version": "7.2.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.10.tgz", + "integrity": "sha512-kUEPnMKrqbtpCq/KTaGFFKAcz6Ethm2EjCoKIDaCmfRBWLbFuTcOJfTlorwbnboXBzahqWLgUp1BQeKHiJzPUQ==", "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -2410,9 +2457,9 @@ } }, "@types/estree": { - "version": "0.0.46", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", - "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==" + "version": "0.0.47", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz", + "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==" }, "@types/glob": { "version": "7.1.3", @@ -4162,6 +4209,11 @@ "get-intrinsic": "^1.0.2" } }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -4298,9 +4350,9 @@ } }, "cic-client": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/cic-client/-/cic-client-0.1.1.tgz", - "integrity": "sha512-4eywf1VT8YaA4a+ap2gTsjmep/vmjWDMNoADOztOhua7uh/Jh0AR4446npmT3c7S3Hp1aailR2EJABbzpkePjA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/cic-client/-/cic-client-0.1.4.tgz", + "integrity": "sha512-4dYgVyDQlZ7uQyLKAeapXmtd7PZZkB6QIxPpyVv8ctjHHfj8N8KlAXOE+OugN0CxTGnfCPKmlbe+GhRSPcSe4Q==", "requires": { "bn": "^1.0.5", "lru-cache": "^6.0.0", @@ -4428,9 +4480,9 @@ } }, "acorn": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz", - "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==" + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.1.tgz", + "integrity": "sha512-z716cpm5TX4uzOzILx8PavOE6C6DKshHDw1aQN52M/yNSqE9s5O8SMfyhCCfCJ3HmTL0NkVOi+8a/55T7YB3bg==" }, "ajv": { "version": "6.12.6", @@ -4444,9 +4496,9 @@ } }, "enhanced-resolve": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz", - "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.0.tgz", + "integrity": "sha512-Sl3KRpJA8OpprrtaIswVki3cWPiPKxXuFxJXBp+zNb6s6VwNWwFRUdtmzd2ReUut8n+sCPx7QCtQ7w5wfJhSgQ==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -4531,9 +4583,9 @@ "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==" }, "terser": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.0.tgz", - "integrity": "sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.1.tgz", + "integrity": "sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==", "requires": { "commander": "^2.20.0", "source-map": "~0.7.2", @@ -4583,19 +4635,19 @@ } }, "webpack": { - "version": "5.24.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.24.2.tgz", - "integrity": "sha512-uxxKYEY4kMNjP+D2Y+8aw5Vd7ar4pMuKCNemxV26ysr1nk0YDiQTylg9U3VZIdkmI0YHa0uC8ABxL+uGxGWWJg==", + "version": "5.35.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.35.1.tgz", + "integrity": "sha512-uWKYStqJ23+N6/EnMEwUjPSSKUG1tFmcuKhALEh/QXoUxwN8eb3ATNIZB38A+fO6QZ0xfc7Cu7KNV9LXNhDCsw==", "requires": { "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.46", + "@types/estree": "^0.0.47", "@webassemblyjs/ast": "1.11.0", "@webassemblyjs/wasm-edit": "1.11.0", "@webassemblyjs/wasm-parser": "1.11.0", "acorn": "^8.0.4", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.7.0", + "enhanced-resolve": "^5.8.0", "es-module-lexer": "^0.4.0", "eslint-scope": "^5.1.1", "events": "^3.2.0", @@ -4624,9 +4676,9 @@ } }, "cic-client-meta": { - "version": "0.0.7-alpha.3", - "resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.3.tgz", - "integrity": "sha512-4lnzx8Bz6iiIi0wuJL0vSnR+umvrTyDok3efQfafkUM4stNR4kBh6vpqZJiA0FE+oTOr1raiVvm2QyekyUx24A==", + "version": "0.0.7-alpha.6", + "resolved": "https://registry.npmjs.org/cic-client-meta/-/cic-client-meta-0.0.7-alpha.6.tgz", + "integrity": "sha512-oIN1aHkPHfsxJKDV6k4f1kX2tcppw3Q+D1b4BoPh0hYjNKNb7gImBMWnGsy8uiD9W6SNYE4sIXyrtct8mvrhsw==", "requires": { "@ethereumjs/tx": "^3.0.0-beta.1", "automerge": "^0.14.1", @@ -4685,9 +4737,9 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4713,9 +4765,9 @@ } }, "y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yargs": { "version": "16.2.0", @@ -4732,9 +4784,36 @@ } }, "yargs-parser": { - "version": "20.2.6", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.6.tgz", - "integrity": "sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==" + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==" + } + } + }, + "cic-schemas-data-validator": { + "version": "1.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/cic-schemas-data-validator/-/cic-schemas-data-validator-1.0.0-alpha.3.tgz", + "integrity": "sha512-0Cgl6oyPnXfXGX03bAmBr0xzVeq2z2OuD4aw7akYmTYqjCdt2Zt7Y+JWUmIZCyP1F5pii4UK6SNw4dF6AVTXgg==", + "requires": { + "@apidevtools/json-schema-ref-parser": "^9.0.7", + "ajv": "^8.1.0" + }, + "dependencies": { + "ajv": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz", + "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" } } }, @@ -6342,9 +6421,9 @@ } }, "es-module-lexer": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.0.tgz", - "integrity": "sha512-iuEGihqqhKWFgh72Q/Jtch7V2t/ft8w8IPP2aEN8ArYKO+IWyo6hsi96hCdgyeEDQIV3InhYQ9BlwUFPGXrbEQ==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz", + "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==" }, "es-to-primitive": { "version": "1.2.1", @@ -6585,12 +6664,20 @@ "uuid": "^3.3.2" }, "dependencies": { - "ethereumjs-util": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.8.tgz", - "integrity": "sha512-JJt7tDpCAmDPw/sGoFYeq0guOVqT3pTE9xlEbBmc/nlCij3JRCoS2c96SQ6kXVHOT3xWUNLDm5QCJLQaUnVAtQ==", + "@types/bn.js": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", + "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", "requires": { - "@types/bn.js": "^4.11.3", + "@types/node": "*" + } + }, + "ethereumjs-util": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz", + "integrity": "sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw==", + "requires": { + "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", "create-hash": "^1.1.2", "ethereum-cryptography": "^0.1.3", @@ -8882,7 +8969,6 @@ "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -9617,9 +9703,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" } @@ -11609,9 +11695,9 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" } @@ -11786,23 +11872,23 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.5.1.tgz", - "integrity": "sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz", + "integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", - "pg-connection-string": "^2.4.0", - "pg-pool": "^3.2.2", - "pg-protocol": "^1.4.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.3.0", + "pg-protocol": "^1.5.0", "pg-types": "^2.1.0", "pgpass": "1.x" } }, "pg-connection-string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz", - "integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, "pg-int8": { "version": "1.0.1", @@ -11810,14 +11896,14 @@ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" }, "pg-pool": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz", - "integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz", + "integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg==" }, "pg-protocol": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz", - "integrity": "sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" }, "pg-types": { "version": "2.2.0", @@ -13406,6 +13492,11 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -14656,9 +14747,9 @@ } }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "dev": true, "requires": { "minipass": "^3.1.1" @@ -17420,9 +17511,9 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" }, "yaeti": { "version": "0.0.6", diff --git a/package.json b/package.json index ff0c292..a9d9fe3 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,9 @@ "block-syncer": "^0.2.4", "bootstrap": "^4.5.3", "chart.js": "^2.9.4", - "cic-client": "^0.1.1", - "cic-client-meta": "0.0.7-alpha.3", + "cic-client": "0.1.4", + "cic-client-meta": "0.0.7-alpha.6", + "cic-schemas-data-validator": "^1.0.0-alpha.3", "datatables.net": "^1.10.22", "datatables.net-dt": "^1.10.22", "ethers": "^5.0.31", diff --git a/src/app/_eth/accountIndex.ts b/src/app/_eth/accountIndex.ts index 0000757..5519340 100644 --- a/src/app/_eth/accountIndex.ts +++ b/src/app/_eth/accountIndex.ts @@ -1,10 +1,8 @@ -// @ts-ignore -import * as accountIndex from '@src/assets/js/block-sync/data/AccountRegistry.json'; import {environment} from '@src/environments/environment'; -const Web3 = require('web3'); +import Web3 from 'web3'; -const web3 = new Web3(environment.web3Provider); -const abi = accountIndex.default; +const abi: Array = require('@src/assets/js/block-sync/data/AccountRegistry.json'); +const web3: Web3 = new Web3(environment.web3Provider); export class AccountIndex { contractAddress: string; @@ -30,14 +28,14 @@ export class AccountIndex { } public async last(numberOfAccounts: number): Promise> { - const count = await this.totalAccounts(); - let lowest = count - numberOfAccounts - 1; + const count: number = await this.totalAccounts(); + let lowest: number = count - numberOfAccounts - 1; if (lowest < 0) { lowest = 0; } - let accounts = []; + const accounts: Array = []; for (let i = count - 1; i > lowest; i--) { - const account = await this.contract.methods.accounts(i).call(); + const account: string = await this.contract.methods.accounts(i).call(); accounts.push(account); } return accounts; @@ -46,8 +44,7 @@ export class AccountIndex { public async addToAccountRegistry(address: string): Promise { if (!await this.haveAccount(address)) { return await this.contract.methods.add(address).send({from: this.signerAddress}); - } else { - return await this.haveAccount(address); } + return true; } } diff --git a/src/app/_eth/index.ts b/src/app/_eth/index.ts index aed7851..23abfa9 100644 --- a/src/app/_eth/index.ts +++ b/src/app/_eth/index.ts @@ -1,3 +1,2 @@ export * from '@app/_eth/accountIndex'; -export * from '@app/_eth/registry'; export * from '@app/_eth/token-registry'; diff --git a/src/app/_eth/registry.spec.ts b/src/app/_eth/registry.spec.ts deleted file mode 100644 index ffc4a39..0000000 --- a/src/app/_eth/registry.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Registry } from '@app/_eth/registry'; -import {environment} from '@src/environments/environment'; - -describe('Registry', () => { - it('should create an instance', () => { - expect(new Registry(environment.registryAddress)).toBeTruthy(); - }); -}); diff --git a/src/app/_eth/registry.ts b/src/app/_eth/registry.ts deleted file mode 100644 index 3ee136a..0000000 --- a/src/app/_eth/registry.ts +++ /dev/null @@ -1,39 +0,0 @@ -// @ts-ignore -import * as registry from '@src/assets/js/block-sync/data/Registry.json'; -import {environment} from '@src/environments/environment'; -import {LoggingService} from '@app/_services/logging.service'; -const Web3 = require('web3'); - -const web3 = new Web3(environment.web3Provider); -const abi = registry.default; - -export class Registry { - contractAddress: string; - signerAddress: string; - contract: any; - - constructor(contractAddress: string, signerAddress?: string) { - this.contractAddress = contractAddress; - this.contract = new web3.eth.Contract(abi, contractAddress); - if (signerAddress) { - this.signerAddress = signerAddress; - } else { - this.signerAddress = web3.eth.accounts[0]; - } - } - - public async owner(): Promise { - return await this.contract.methods.owner().call(); - } - - public async addressOf(identifier: string): Promise { - const id = '0x' + web3.utils.padRight(new Buffer(identifier).toString('hex'), 64); - try { - return await this.contract.methods.addressOf(id).call(); - } catch (error) { - // TODO logger service - // this.loggingService.sendInfoLevelMessage - console.log('Unable to fetch addressOf', error) - } - } -} diff --git a/src/app/_eth/token-registry.ts b/src/app/_eth/token-registry.ts index 7b1807e..f4dc384 100644 --- a/src/app/_eth/token-registry.ts +++ b/src/app/_eth/token-registry.ts @@ -1,10 +1,8 @@ -// @ts-ignore -import * as registryClient from '@src/assets/js/block-sync/data/RegistryClient.json'; import Web3 from 'web3'; import {environment} from '@src/environments/environment'; -const web3 = new Web3(environment.web3Provider); -const abi = registryClient.default; +const abi: Array = require('@src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json'); +const web3: Web3 = new Web3(environment.web3Provider); export class TokenRegistry { contractAddress: string; @@ -22,7 +20,7 @@ export class TokenRegistry { } public async totalTokens(): Promise { - return await this.contract.methods.registryCount().call(); + return await this.contract.methods.entryCount().call(); } public async entry(serial: number): Promise { @@ -30,7 +28,7 @@ export class TokenRegistry { } public async addressOf(identifier: string): Promise { - const id = '0x' + web3.utils.padRight(new Buffer(identifier).toString('hex'), 64); + const id: string = web3.eth.abi.encodeParameter('bytes32', web3.utils.toHex(identifier)); return await this.contract.methods.addressOf(id).call(); } } diff --git a/src/app/_helpers/array-sum.ts b/src/app/_helpers/array-sum.ts index 44b6856..67cf305 100644 --- a/src/app/_helpers/array-sum.ts +++ b/src/app/_helpers/array-sum.ts @@ -1,5 +1,7 @@ -export class ArraySum { - static arraySum(arr: any[]): number { - return arr.reduce((accumulator, current) => accumulator + current, 0); - } +function arraySum(arr: Array): number { + return arr.reduce((accumulator, current) => accumulator + current, 0); } + +export { + arraySum +}; diff --git a/src/app/_helpers/clipboard-copy.ts b/src/app/_helpers/clipboard-copy.ts new file mode 100644 index 0000000..59d9f18 --- /dev/null +++ b/src/app/_helpers/clipboard-copy.ts @@ -0,0 +1,53 @@ +function copyToClipboard(text: any): boolean { + // create our hidden div element + const hiddenCopy: HTMLDivElement = document.createElement('div'); + // set the innerHTML of the div + hiddenCopy.innerHTML = text; + // set the position to be absolute and off the screen + hiddenCopy.classList.add('clipboard'); + + // check and see if the user had a text selection range + let currentRange: Range | boolean; + if (document.getSelection().rangeCount > 0) { + // the user has a text selection range, store it + currentRange = document.getSelection().getRangeAt(0); + // remove the current selection + window.getSelection().removeRange(currentRange); + } else { + // they didn't have anything selected + currentRange = false; + } + + // append the div to the body + document.body.appendChild(hiddenCopy); + // create a selection range + const copyRange: Range = document.createRange(); + // set the copy range to be the hidden div + copyRange.selectNode(hiddenCopy); + // add the copy range + window.getSelection().addRange(copyRange); + + // since not all browsers support this, use a try block + try { + // copy the text + document.execCommand('copy'); + } catch (err) { + window.alert('Your Browser Doesn\'t support this! Error : ' + err); + return false; + } + // remove the selection range (Chrome throws a warning if we don't.) + window.getSelection().removeRange(copyRange); + // remove the hidden div + document.body.removeChild(hiddenCopy); + + // return the old selection range + if (currentRange) { + window.getSelection().addRange(currentRange); + } + + return true; +} + +export { + copyToClipboard +}; diff --git a/src/app/_helpers/custom-error-state-matcher.ts b/src/app/_helpers/custom-error-state-matcher.ts index 8728100..b33c367 100644 --- a/src/app/_helpers/custom-error-state-matcher.ts +++ b/src/app/_helpers/custom-error-state-matcher.ts @@ -3,7 +3,7 @@ import {FormControl, FormGroupDirective, NgForm} from '@angular/forms'; export class CustomErrorStateMatcher implements ErrorStateMatcher{ isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { - const isSubmitted = form && form.submitted; + const isSubmitted: boolean = form && form.submitted; return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted)); } } diff --git a/src/app/_helpers/custom.validator.ts b/src/app/_helpers/custom.validator.ts index 25aa06a..a5a3b59 100644 --- a/src/app/_helpers/custom.validator.ts +++ b/src/app/_helpers/custom.validator.ts @@ -15,7 +15,7 @@ export class CustomValidator { return null; } - const valid = regex.test(control.value); + const valid: boolean = regex.test(control.value); return valid ? null : error; }; } diff --git a/src/app/_helpers/export-csv.ts b/src/app/_helpers/export-csv.ts index 6962ae0..46b5262 100644 --- a/src/app/_helpers/export-csv.ts +++ b/src/app/_helpers/export-csv.ts @@ -1,9 +1,11 @@ -function exportCsv(arrayData: any[], filename: string, delimiter = ','): void { - if (arrayData === undefined) { return; } - const header = Object.keys(arrayData[0]).join(delimiter) + '\n'; - let csv = header; +function exportCsv(arrayData: Array, filename: string, delimiter: string = ','): void { + if (arrayData === undefined || arrayData.length === 0) { + alert('No data to be exported!'); + return; + } + let csv: string = Object.keys(arrayData[0]).join(delimiter) + '\n'; arrayData.forEach(obj => { - let row = []; + const row: Array = []; for (const key in obj) { if (obj.hasOwnProperty(key)) { row.push(obj[key]); @@ -12,11 +14,10 @@ function exportCsv(arrayData: any[], filename: string, delimiter = ','): void { csv += row.join(delimiter) + '\n'; }); - const csvData = new Blob([csv], {type: 'text/csv'}); - const csvUrl = URL.createObjectURL(csvData); - // csvUrl = 'data:text/csv;charset=utf-8,' + encodeURI(csv); + const csvData: Blob = new Blob([csv], {type: 'text/csv'}); + const csvUrl: string = URL.createObjectURL(csvData); - let downloadLink = document.createElement('a'); + const downloadLink: HTMLAnchorElement = document.createElement('a'); downloadLink.href = csvUrl; downloadLink.target = '_blank'; downloadLink.download = filename + '.csv'; diff --git a/src/app/_helpers/global-error-handler.ts b/src/app/_helpers/global-error-handler.ts index 52d808e..fd708b2 100644 --- a/src/app/_helpers/global-error-handler.ts +++ b/src/app/_helpers/global-error-handler.ts @@ -3,10 +3,10 @@ import {LoggingService} from '@app/_services/logging.service'; import {HttpErrorResponse} from '@angular/common/http'; import {Router} from '@angular/router'; -// A generalized http repsonse error +// A generalized http response error export class HttpError extends Error { - public status: number - constructor(message, status) { + public status: number; + constructor(message: string, status: number) { super(message); this.status = status; this.name = 'HttpError'; @@ -15,7 +15,7 @@ export class HttpError extends Error { @Injectable() export class GlobalErrorHandler extends ErrorHandler { - private sentencesForWarningLogging: string[] = []; + private sentencesForWarningLogging: Array = []; constructor( private loggingService: LoggingService, @@ -26,15 +26,15 @@ export class GlobalErrorHandler extends ErrorHandler { handleError(error: Error): void { this.logError(error); - const message = error.message ? error.message : error.toString(); + const message: string = error.message ? error.message : error.toString(); // if (error.status) { // error = new Error(message); // } - const errorTraceString = `Error message:\n${message}.\nStack trace: ${error.stack}`; + const errorTraceString: string = `Error message:\n${message}.\nStack trace: ${error.stack}`; - const isWarning = this.isWarning(errorTraceString); + const isWarning: boolean = this.isWarning(errorTraceString); if (isWarning) { this.loggingService.sendWarnLevelMessage(errorTraceString, {error}); } else { @@ -45,7 +45,7 @@ export class GlobalErrorHandler extends ErrorHandler { } logError(error: any): void { - const route = this.router.url; + const route: string = this.router.url; if (error instanceof HttpErrorResponse) { this.loggingService.sendErrorLevelMessage( `There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${(error as HttpErrorResponse).status}`, @@ -60,12 +60,12 @@ export class GlobalErrorHandler extends ErrorHandler { } private isWarning(errorTraceString: string): boolean { - let isWarning = true; + let isWarning: boolean = true; if (errorTraceString.includes('/src/app/')) { isWarning = false; } - this.sentencesForWarningLogging.forEach((whiteListSentence) => { + this.sentencesForWarningLogging.forEach((whiteListSentence: string) => { if (errorTraceString.includes(whiteListSentence)) { isWarning = true; } diff --git a/src/app/_helpers/http-getter.ts b/src/app/_helpers/http-getter.ts index f5c9f65..dd59444 100644 --- a/src/app/_helpers/http-getter.ts +++ b/src/app/_helpers/http-getter.ts @@ -1,13 +1,13 @@ function HttpGetter(): void {} -HttpGetter.prototype.get = filename => new Promise((whohoo, doh) => { - const xhr = new XMLHttpRequest(); +HttpGetter.prototype.get = filename => new Promise((resolve, reject) => { + const xhr: XMLHttpRequest = new XMLHttpRequest(); xhr.addEventListener('load', (e) => { if (xhr.status === 200) { - whohoo(xhr.responseText); + resolve(xhr.responseText); return; } - doh('failed with status ' + xhr.status + ': ' + xhr.statusText); + reject('failed with status ' + xhr.status + ': ' + xhr.statusText); }); xhr.open('GET', filename); xhr.send(); diff --git a/src/app/_helpers/index.ts b/src/app/_helpers/index.ts index 0294677..28fb61f 100644 --- a/src/app/_helpers/index.ts +++ b/src/app/_helpers/index.ts @@ -6,3 +6,5 @@ export * from '@app/_helpers/http-getter'; export * from '@app/_helpers/global-error-handler'; export * from '@app/_helpers/export-csv'; export * from '@app/_helpers/read-csv'; +export * from '@app/_helpers/clipboard-copy'; +export * from '@app/_helpers/schema-validation'; diff --git a/src/app/_helpers/mock-backend.ts b/src/app/_helpers/mock-backend.ts index 55f2d85..c1a1c25 100644 --- a/src/app/_helpers/mock-backend.ts +++ b/src/app/_helpers/mock-backend.ts @@ -2,16 +2,9 @@ import {HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, import {Injectable} from '@angular/core'; import {Observable, of, throwError} from 'rxjs'; import {delay, dematerialize, materialize, mergeMap} from 'rxjs/operators'; +import {Action, AreaName, AreaType, Category, Token} from '@app/_models'; -const accounts = [ - {id: 1, name: 'John Doe', phone: '+25412345678', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', age: 43, created: '08/16/2020', balance: '12987', failedPinAttempts: 1, status: 'active', bio: 'Bodaboda', category: 'transport', gender: 'male', location: 'Bofu', locationType: 'Rural', token: 'RSV', referrer: '+25412341234'}, - {id: 2, name: 'Jane Buck', phone: '+25412341234', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9866', type: 'vendor', age: 25, created: '04/02/2020', balance: '56281', failedPinAttempts: 0, status: 'active', bio: 'Groceries', category: 'food/water', gender: 'female', location: 'Lindi', locationType: 'Urban', token: 'ERN', referrer: ''}, - {id: 3, name: 'Mc Donald', phone: '+25498765432', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9867', type: 'group', age: 31, created: '11/16/2020', balance: '450', failedPinAttempts: 2, status: 'blocked', bio: 'Food', category: 'food/water', gender: 'male', location: 'Miyani', locationType: 'Rural', token: 'RSV', referrer: '+25498769876'}, - {id: 4, name: 'Hera Cles', phone: '+25498769876', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9868', type: 'user', age: 38, created: '05/28/2020', balance: '5621', failedPinAttempts: 3, status: 'active', bio: 'Shop', category: 'shop', gender: 'female', location: 'Kayaba', locationType: 'Urban', token: 'BRT', referrer: '+25412341234'}, - {id: 5, name: 'Silver Fia', phone: '+25462518374', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9869', type: 'tokenAgent', age: 19, created: '10/10/2020', balance: '817', failedPinAttempts: 0, status: 'blocked', bio: 'Electronics', category: 'shop', gender: 'male', location: 'Mkanyeni', locationType: 'Rural', token: 'RSV', referrer: '+25412345678'}, -]; - -const actions = [ +const actions: Array = [ { id: 1, user: 'Tom', role: 'enroller', action: 'Disburse RSV 100', approval: false }, { id: 2, user: 'Christine', role: 'admin', action: 'Change user phone number', approval: true }, { id: 3, user: 'Will', role: 'superadmin', action: 'Reclaim RSV 1000', approval: true }, @@ -20,61 +13,228 @@ const actions = [ { id: 6, user: 'Patience', role: 'enroller', action: 'Change user information', approval: false } ]; -const histories = [ - { id: 1, userId: 3, userName: 'Mc Donald', action: 'Receive RSV 100', staff: 'Tom', timestamp: Date.now() }, - { id: 2, userId: 5, userName: 'Silver Fia', action: 'Change phone number from +25412345678 to +25498765432', staff: 'Christine', timestamp: Date.now()}, - { id: 3, userId: 4, userName: 'Hera Cles', action: 'Completed user profile', staff: 'Vivian', timestamp: Date.now() }, -]; - -const locations: any = [ - { name: 'Kwale', - districts: [ - { name: 'Kinango', - locations: [ - { name: 'Bofu', villages: ['Bofu', 'Chidzuvini', 'Mkanyeni']}, - { name: 'Mnyenzeni', villages: ['Miloeni', 'Miyani', 'Mnyenzeni', 'Vikolani', 'Vitangani']} - ] - } - ] +const tokens: Array = [ + { + name: 'Giftable Reserve', symbol: 'GRZ', address: '0xa686005CE37Dce7738436256982C3903f2E4ea8E', supply: '1000000001000000000000000000', + decimals: '18', reserves: {} }, - { name: 'Nairobi', - districts: [ - { name: 'Dagorreti', - locations: [ - { name: 'Kawangware', villages: ['Congo']}, - ] - }, - { name: 'Ngong', - locations: [ - { name: 'Kibera', villages: ['Kibera', 'Lindi']}, - ] - }, - { name: 'South B', - locations: [ - { name: 'Mukuru', villages: ['Kayaba']}, - { name: 'South B', villages: ['South B']}, - ] - }, - ] + { + name: 'Demo Token', symbol: 'DEMO', address: '0xc80D6aFF8194114c52AEcD84c9f15fd5c8abb187', supply: '99999999999999998976', + decimals: '18', reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '99999999999999998976'}}, + reserveRatio: '1000000', owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a' + }, + { + name: 'Foo Token', symbol: 'FOO', address: '0x9ceD86089f7aBB5A97B40eb0E7521e7aa308d354', supply: '1000000000000000001014', + decimals: '18', reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '1000000000000000001014'}}, + reserveRatio: '1000000', owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a' + }, + { + name: 'testb', symbol: 'tstb', address: '0xC63cFA91A3BFf41cE31Ff436f67D3ACBC977DB95', supply: '99000', decimals: '18', + reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '99000'}}, reserveRatio: '1000000', + owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a' + }, + { + name: 'testa', symbol: 'tsta', address: '0x8fA4101ef19D0a078239d035659e92b278bD083C', supply: '9981', decimals: '18', + reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '9981'}}, reserveRatio: '1000000', + owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a' + }, + { + name: 'testc', symbol: 'tstc', address: '0x4A6fA6bc3BfE4C9661bC692D9798425350C9e3D4', supply: '100990', decimals: '18', + reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '100990'}}, reserveRatio: '1000000', + owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a' } ]; -const staffMembers = [ - { id: 1, name: 'admin@acme.org', accountType: 'Admin', created: '17/11/2020', status: 'activated'}, - { id: 2, name: 'will@grassecon.org', accountType: 'SuperAdmin', created: '17/11/2020', status: 'activated'}, - { id: 3, name: 'spence@grassecon.org', accountType: 'Enroller', created: '17/11/2020', status: 'activated'}, - { id: 4, name: 'admin@redcross.org', accountType: 'View', created: '17/11/2020', status: 'activated'} +const categories: Array = [ + { + name: 'system', + products: ['system', 'office main', 'office main phone'] + }, + { + name: 'education', + products: ['book', 'coach', 'teacher', 'sch', 'school', 'pry', 'education', 'student', 'mwalimu', 'maalim', 'consultant', 'consult', + 'college', 'university', 'lecturer', 'primary', 'secondary', 'daycare', 'babycare', 'baby care', 'elim', 'eimu', 'nursery', + 'red cross', 'volunteer', 'instructor', 'journalist', 'lesson', 'academy', 'headmistress', 'headteacher', 'cyber', 'researcher', + 'professor', 'demo', 'expert', 'tution', 'tuition', 'children', 'headmaster', 'educator', 'Marital counsellor', 'counsellor', + 'trainer', 'vijana', 'youth', 'intern', 'redcross', 'KRCS', 'danish', 'science', 'data', 'facilitator', 'vitabu', 'kitabu'] + }, + { + name: 'faith', + products: ['pastor', 'imam', 'madrasa', 'religous', 'religious', 'ustadh', 'ustadhi', 'Marital counsellor', 'counsellor', 'church', + 'kanisa', 'mksiti', 'donor'] + }, + { + name: 'government', + products: ['elder', 'chief', 'police', 'government', 'country', 'county', 'soldier', 'village admin', 'ward', 'leader', 'kra', + 'mailman', 'immagration', 'immigration'] + }, + { + name: 'environment', + products: ['conservation', 'toilet', 'choo', 'garbage', 'fagio', 'waste', 'tree', 'taka', 'scrap', 'cleaning', 'gardener', 'rubbish', + 'usafi', 'mazingira', 'miti', 'trash', 'cleaner', 'plastic', 'collection', 'seedling', 'seedlings', 'recycling'] + }, + { + name: 'farming', + products: ['farm', 'farmer', 'farming', 'mkulima', 'kulima', 'ukulima', 'wakulima', 'jembe', 'shamba'] + }, + { + name: 'labour', + products: ['artist', 'agent', 'guard', 'askari', 'accountant', 'baker', 'beadwork', 'beauty', 'business', 'barber', 'casual', + 'electrian', 'caretaker', 'car wash', 'capenter', 'construction', 'chef', 'catering', 'cobler', 'cobbler', 'carwash', 'dhobi', + 'landlord', 'design', 'carpenter', 'fundi', 'hawking', 'hawker', 'househelp', 'hsehelp', 'house help', 'help', 'housegirl', 'kushona', + 'juakali', 'jualikali', 'juacali', 'jua kali', 'shepherd', 'makuti', 'kujenga', 'kinyozi', 'kazi', 'knitting', 'kufua', 'fua', + 'hustler', 'biashara', 'labour', 'labor', 'laundry', 'repair', 'hair', 'posho', 'mill', 'mtambo', 'uvuvi', 'engineer', 'manager', + 'tailor', 'nguo', 'mason', 'mtumba', 'garage', 'mechanic', 'mjenzi', 'mfugaji', 'painter', 'receptionist', 'printing', 'programming', + 'plumb', 'charging', 'salon', 'mpishi', 'msusi', 'mgema', 'footballer', 'photocopy', 'peddler', 'staff', 'sales', 'service', 'saloon', + 'seremala', 'security', 'insurance', 'secretary', 'shoe', 'shepard', 'shephard', 'tout', 'tv', 'mvuvi', 'mawe', 'majani', 'maembe', + 'freelance', 'mjengo', 'electronics', 'photographer', 'programmer', 'electrician', 'washing', 'bricks', 'welder', 'welding', + 'working', 'worker', 'watchman', 'waiter', 'waitress', 'viatu', 'yoga', 'guitarist', 'house', 'artisan', 'musician', 'trade', + 'makonge', 'ujenzi', 'vendor', 'watchlady', 'marketing', 'beautician', 'photo', 'metal work', 'supplier', 'law firm', 'brewer'] + }, + { + name: 'food', + products: ['avocado', 'bhajia', 'bajia', 'mbonga', 'bofu', 'beans', 'biscuits', 'biringanya', 'banana', 'bananas', 'crisps', 'chakula', + 'coconut', 'chapati', 'cereal', 'chipo', 'chapo', 'chai', 'chips', 'cassava', 'cake', 'cereals', 'cook', 'corn', 'coffee', 'chicken', + 'dagaa', 'donut', 'dough', 'groundnuts', 'hotel', 'holel', 'hoteli', 'butcher', 'butchery', 'fruit', 'food', 'fruits', 'fish', + 'githeri', 'grocery', 'grocer', 'pojo', 'papa', 'goats', 'mabenda', 'mbenda', 'poultry', 'soda', 'peanuts', 'potatoes', 'samosa', + 'soko', 'samaki', 'tomato', 'tomatoes', 'mchele', 'matunda', 'mango', 'melon', 'mellon', 'nyanya', 'nyama', 'omena', 'umena', 'ndizi', + 'njugu', 'kamba kamba', 'khaimati', 'kaimati', 'kunde', 'kuku', 'kahawa', 'keki', 'muguka', 'miraa', 'milk', 'choma', 'maziwa', + 'mboga', 'mbog', 'busaa', 'chumvi', 'cabbages', 'mabuyu', 'machungwa', 'mbuzi', 'mnazi', 'mchicha', 'ngombe', 'ngano', 'nazi', + 'oranges', 'peanuts', 'mkate', 'bread', 'mikate', 'vitungu', 'sausages', 'maize', 'mbata', 'mchuzi', 'mchuuzi', 'mandazi', 'mbaazi', + 'mahindi', 'maandazi', 'mogoka', 'meat', 'mhogo', 'mihogo', 'muhogo', 'maharagwe', 'miwa', 'mahamri', 'mitumba', 'simsim', 'porridge', + 'pilau', 'vegetable', 'egg', 'mayai', 'mifugo', 'unga', 'good', 'sima', 'sweet', 'sweats', 'sambusa', 'snacks', 'sugar', 'suger', + 'ugoro', 'sukari', 'soup', 'spinach', 'smokie', 'smokies', 'sukuma', 'tea', 'uji', 'ugali', 'uchuzi', 'uchuuzi', 'viazi', 'yoghurt', + 'yogurt', 'wine', 'marondo', 'maandzi', 'matoke', 'omeno', 'onions', 'nzugu', 'korosho', 'barafu', 'juice'] + }, + { + name: 'water', + products: ['maji', 'water'] + }, + { + name: 'health', + products: ['agrovet', 'dispensary', 'barakoa', 'chemist', 'Chemicals', 'chv', 'doctor', 'daktari', 'dawa', 'hospital', 'herbalist', + 'mganga', 'sabuni', 'soap', 'nurse', 'heath', 'community health worker', 'clinic', 'clinical', 'mask', 'medicine', 'lab technician', + 'pharmacy', 'cosmetics', 'veterinary', 'vet', 'sickly', 'emergency response', 'emergency'] + }, + { + name: 'savings', + products: ['chama', 'group', 'savings', 'loan', 'silc', 'vsla', 'credit', 'finance'] + }, + { + name: 'shop', + products: ['bag', 'bead', 'belt', 'bedding', 'jik', 'bed', 'cement', 'botique', 'boutique', 'lines', 'kibanda', 'kiosk', 'spareparts', + 'candy', 'cloth', 'electricals', 'mutumba', 'cafe', 'leso', 'lesso', 'duka', 'spare parts', 'socks', 'malimali', 'mitungi', + 'mali mali', 'hardware', 'detergent', 'detergents', 'dera', 'retail', 'kamba', 'pombe', 'pampers', 'pool', 'phone', 'simu', 'mangwe', + 'mikeka', 'movie', 'shop', 'acces', 'mchanga', 'uto', 'airtime', 'matress', 'mattress', 'mattresses', 'mpsea', 'mpesa', 'shirt', + 'wholesaler', 'perfume', 'playstation', 'tissue', 'vikapu', 'uniform', 'flowers', 'vitenge', 'utencils', 'utensils', 'station', + 'jewel', 'pool table', 'club', 'pub', 'bar', 'furniture', 'm-pesa', 'vyombo'] + }, + { + name: 'transport', + products: ['kebeba', 'beba', 'bebabeba', 'bike', 'bicycle', 'matatu', 'boda', 'bodaboda', 'cart', 'carrier', 'tour', 'travel', 'driver', + 'dereva', 'tout', 'conductor', 'kubeba', 'tuktuk', 'taxi', 'piki', 'pikipiki', 'manamba', 'trasportion', 'mkokoteni', 'mover', + 'motorist', 'motorbike', 'transport', 'transpoter', 'gari', 'magari', 'makanga', 'car'] + }, + { + name: 'fuel/energy', + products: ['timber', 'timberyard', 'biogas', 'charcol', 'charcoal', 'kuni', 'mbao', 'fuel', 'makaa', 'mafuta', 'moto', 'solar', 'stima', + 'fire', 'firewood', 'wood', 'oil', 'taa', 'gas', 'paraffin', 'parrafin', 'parafin', 'petrol', 'petro', 'kerosine', 'kerosene', + 'diesel'] + }, + { + name: 'other', + products: ['other', 'none', 'unknown', 'none'] + } ]; -const tokens = [ - {name: 'Giftable Reserve', symbol: 'GRZ', address: '0xa686005CE37Dce7738436256982C3903f2E4ea8E', supply: '1000000001000000000000000000', decimals: '18', reserves: {}}, - {name: 'Demo Token', symbol: 'DEMO', address: '0xc80D6aFF8194114c52AEcD84c9f15fd5c8abb187', supply: '99999999999999998976', decimals: '18', reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '99999999999999998976'}}, reserveRatio: '1000000', owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a'}, - {name: 'Foo Token', symbol: 'FOO', address: '0x9ceD86089f7aBB5A97B40eb0E7521e7aa308d354', supply: '1000000000000000001014', decimals: '18', reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '1000000000000000001014'}}, reserveRatio: '1000000', owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a'}, - {name: 'testb', symbol: 'tstb', address: '0xC63cFA91A3BFf41cE31Ff436f67D3ACBC977DB95', supply: '99000', decimals: '18', reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '99000'}}, reserveRatio: '1000000', owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a'}, - {name: 'testa', symbol: 'tsta', address: '0x8fA4101ef19D0a078239d035659e92b278bD083C', supply: '9981', decimals: '18', reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '9981'}}, reserveRatio: '1000000', owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a'}, - {name: 'testc', symbol: 'tstc', address: '0x4A6fA6bc3BfE4C9661bC692D9798425350C9e3D4', supply: '100990', decimals: '18', reserves: {'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {weight: '1000000', balance: '100990'}}, reserveRatio: '1000000', owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a'} +const areaNames: Array = [ + { + name: 'Mukuru Nairobi', + locations: ['kayaba', 'kayba', 'kambi', 'mukuru', 'masai', 'hazina', 'south', 'tetra', 'tetrapak', 'ruben', 'rueben', 'kingston', + 'korokocho', 'kingstone', 'kamongo', 'lungalunga', 'sinai', 'sigei', 'lungu', 'lunga lunga', 'owino road', 'seigei'] + }, + { + name: 'Kinango Kwale', + locations: ['amani', 'bofu', 'chibuga', 'chikomani', 'chilongoni', 'chigojoni', 'chinguluni', 'chigato', 'chigale', 'chikole', + 'chilongoni', 'chilumani', 'chigojoni', 'chikomani', 'chizini', 'chikomeni', 'chidzuvini', 'chidzivuni', 'chikuyu', 'chizingo', + 'doti', 'dzugwe', 'dzivani', 'dzovuni', 'hanje', 'kasemeni', 'katundani', 'kibandaogo', 'kibandaongo', 'kwale', 'kinango', + 'kidzuvini', 'kalalani', 'kafuduni', 'kaloleni', 'kilibole', 'lutsangani', 'peku', 'gona', 'guro', 'gandini', 'mkanyeni', 'myenzeni', + 'miyenzeni', 'miatsiani', 'mienzeni', 'mnyenzeni', 'minyenzeni', 'miyani', 'mioleni', 'makuluni', 'mariakani', 'makobeni', 'madewani', + 'mwangaraba', 'mwashanga', 'miloeni', 'mabesheni', 'mazeras', 'mazera', 'mlola', 'muugano', 'mulunguni', 'mabesheni', 'miatsani', + 'miatsiani', 'mwache', 'mwangani', 'mwehavikonje', 'miguneni', 'nzora', 'nzovuni', 'vikinduni', 'vikolani', 'vitangani', 'viogato', + 'vyogato', 'vistangani', 'yapha', 'yava', 'yowani', 'ziwani', 'majengo', 'matuga', 'vigungani', 'vidziweni', 'vinyunduni', 'ukunda', + 'kokotoni', 'mikindani'] + }, + { + name: 'Misc Nairobi', + locations: ['nairobi', 'west', 'lindi', 'kibera', 'kibira', 'kibra', 'makina', 'soweto', 'olympic', 'kangemi', 'ruiru', 'congo', + 'kawangware', 'kwangware', 'donholm', 'dagoreti', 'dandora', 'kabete', 'sinai', 'donhom', 'donholm', 'huruma', 'kitengela', + 'makadara', ',mlolongo', 'kenyatta', 'mlolongo', 'tassia', 'tasia', 'gatina', '56', 'industrial', 'kariobangi', 'kasarani', 'kayole', + 'mathare', 'pipe', 'juja', 'uchumi', 'jogoo', 'umoja', 'thika', 'kikuyu', 'stadium', 'buru buru', 'ngong', 'starehe', 'mwiki', + 'fuata', 'kware', 'kabiro', 'embakassi', 'embakasi', 'kmoja', 'east', 'githurai', 'landi', 'langata', 'limuru', 'mathere', + 'dagoretti', 'kirembe', 'muugano', 'mwiki', 'toi market'] + }, + { + name: 'Misc Mombasa', + locations: ['mombasa', 'likoni', 'bangla', 'bangladesh', 'kizingo', 'old town', 'makupa', 'mvita', 'ngombeni', 'ngómbeni', 'ombeni', + 'magongo', 'miritini', 'changamwe', 'jomvu', 'ohuru', 'tudor', 'diani'] + }, + { + name: 'Kisauni', + locations: ['bamburi', 'kisauni', 'mworoni', 'nyali', 'shanzu', 'bombolulu', 'mtopanga', 'mjambere', 'majaoni', 'manyani', 'magogoni', + 'junda', 'mwakirunge', 'mshomoroni'] + }, + { + name: 'Kilifi', + locations: ['kilfi', 'kilifi', 'mtwapa', 'takaungu', 'makongeni', 'mnarani', 'mnarani', 'office', 'g.e', 'ge', 'raibai', 'ribe'] + }, + { + name: 'Kakuma', + locations: ['kakuma'] + }, + { + name: 'Kitui', + locations: ['kitui', 'mwingi'] + }, + { + name: 'Nyanza', + locations: ['busia', 'nyalgunga', 'mbita', 'siaya', 'kisumu', 'nyalenda', 'hawinga', 'rangala', 'uyoma', 'mumias', 'homabay', 'homaboy', + 'migori', 'kusumu'] + }, + { + name: 'Misc Rural Counties', + locations: ['makueni', 'meru', 'kisii', 'bomet', 'machakos', 'bungoma', 'eldoret', 'kakamega', 'kericho', 'kajiado', 'nandi', 'nyeri', + 'wote', 'kiambu', 'mwea', 'nakuru', 'narok'] + }, + { + name: 'other', + locations: ['other', 'none', 'unknown'] + } ]; +const areaTypes: Array = [ + { + name: 'urban', + area: ['urban', 'nairobi', 'mombasa'] + }, + { + name: 'rural', + area: ['rural', 'kakuma', 'kwale', 'kinango', 'kitui', 'nyanza'] + }, + { + name: 'periurban', + area: ['kilifi', 'periurban'] + }, + { + name: 'other', + area: ['other'] + } +]; + +const accountTypes: Array = ['user', 'cashier', 'vendor', 'tokenagent', 'group']; +const transactionTypes: Array = ['transactions', 'conversions', 'disbursements', 'rewards', 'reclamation']; +const genders: Array = ['male', 'female', 'other']; + @Injectable() export class MockBackendInterceptor implements HttpInterceptor { intercept(request: HttpRequest, next: HttpHandler): Observable> { @@ -90,32 +250,34 @@ export class MockBackendInterceptor implements HttpInterceptor { function handleRoute(): Observable { switch (true) { - case url.endsWith('/accounts') && method === 'GET': - return getAccounts(); - case url.match(/\/accounts\/\d+$/) && method === 'GET': - return getAccountById(); case url.endsWith('/actions') && method === 'GET': return getActions(); case url.match(/\/actions\/\d+$/) && method === 'GET': return getActionById(); case url.match(/\/actions\/\d+$/) && method === 'POST': return approveAction(); - case url.match(/\/history\/\d+$/) && method === 'GET': - return getHistoryByUser(); - case url.endsWith('/locations') && method === 'GET': - return getLocations(); - case url.endsWith('/staff') && method === 'GET': - return getStaff(); - case url.match(/\/staff\/\d+$/) && method === 'GET': - return getStaffById(); - case url.match(/\/staff\/\d+$/) && method === 'POST' && body.status !== undefined: - return changeStaffStatus(); - case url.match(/\/staff\/\d+$/) && method === 'POST' && body.accountType !== undefined: - return changeStaffType(); case url.endsWith('/tokens') && method === 'GET': return getTokens(); case url.match(/\/tokens\/\w+$/) && method === 'GET': return getTokenBySymbol(); + case url.endsWith('/categories') && method === 'GET': + return getCategories(); + case url.match(/\/categories\/\w+$/) && method === 'GET': + return getCategoryByProduct(); + case url.endsWith('/areanames') && method === 'GET': + return getAreaNames(); + case url.match(/\/areanames\/\w+$/) && method === 'GET': + return getAreaNameByLocation(); + case url.endsWith('/areatypes') && method === 'GET': + return getAreaTypes(); + case url.match(/\/areatypes\/\w+$/) && method === 'GET': + return getAreaTypeByArea(); + case url.endsWith('/accounttypes') && method === 'GET': + return getAccountTypes(); + case url.endsWith('/transactiontypes') && method === 'GET': + return getTransactionTypes(); + case url.endsWith('/genders') && method === 'GET': + return getGenders(); default: // pass through any requests not handled above return next.handle(request); @@ -124,76 +286,77 @@ export class MockBackendInterceptor implements HttpInterceptor { // route functions - function getAccounts(): Observable { - return ok(accounts); - } - - function getAccountById(): Observable { - const queriedAccount = accounts.find(account => account.id === idFromUrl()); - return ok(queriedAccount); - } - - function getActions(): Observable { + function getActions(): Observable> { return ok(actions); } - function getActionById(): Observable { - const queriedAction = actions.find(action => action.id === idFromUrl()); + function getActionById(): Observable> { + const queriedAction: Action = actions.find(action => action.id === idFromUrl()); return ok(queriedAction); } - function approveAction(): Observable { - const queriedAction = actions.find(action => action.id === idFromUrl()); + function approveAction(): Observable> { + const queriedAction: Action = actions.find(action => action.id === idFromUrl()); queriedAction.approval = body.approval; - const message = `Action approval status set to ${body.approval} successfully!`; + const message: string = `Action approval status set to ${body.approval} successfully!`; return ok(message); } - function getHistoryByUser(): Observable { - const queriedUserHistory = histories.filter(history => history.userId === idFromUrl()); - return ok(queriedUserHistory); - } - - function getLocations(): Observable { - return ok(locations); - } - - function getStaff(): Observable { - return ok(staffMembers); - } - - function getStaffById(): Observable { - const queriedStaff = staffMembers.find(staff => staff.id === idFromUrl()); - return ok(queriedStaff); - } - - function changeStaffStatus(): Observable { - const queriedStaff = staffMembers.find(staff => staff.id === idFromUrl()); - queriedStaff.status = body.status; - const message = `Staff account status changed to ${body.status} successfully!`; - return ok(message); - } - - function changeStaffType(): Observable { - const queriedStaff = staffMembers.find(staff => staff.id === idFromUrl()); - queriedStaff.accountType = body.accountType; - const message = `Staff account type changed to ${body.accountType} successfully!`; - return ok(message); - } - - function getTokens(): Observable { + function getTokens(): Observable> { return ok(tokens); } - function getTokenBySymbol(): Observable { - const queriedToken = tokens.find(token => token.symbol === stringFromUrl()); + function getTokenBySymbol(): Observable> { + const queriedToken: Token = tokens.find(token => token.symbol === stringFromUrl()); return ok(queriedToken); } + function getCategories(): Observable> { + const categoryList: Array = categories.map(category => category.name); + return ok(categoryList); + } + + function getCategoryByProduct(): Observable> { + const queriedCategory: Category = categories.find(category => category.products.includes(stringFromUrl())); + return ok(queriedCategory.name); + } + + function getAreaNames(): Observable> { + const areaNameList: Array = areaNames.map(areaName => areaName.name); + return ok(areaNameList); + } + + function getAreaNameByLocation(): Observable> { + const queriedAreaName: AreaName = areaNames.find(areaName => areaName.locations.includes(stringFromUrl())); + return ok(queriedAreaName.name); + } + + function getAreaTypes(): Observable> { + const areaTypeList: Array = areaTypes.map(areaType => areaType.name); + return ok(areaTypeList); + } + + function getAreaTypeByArea(): Observable> { + const queriedAreaType: AreaType = areaTypes.find(areaType => areaType.area.includes(stringFromUrl())); + return ok(queriedAreaType.name); + } + + function getAccountTypes(): Observable> { + return ok(accountTypes); + } + + function getTransactionTypes(): Observable> { + return ok(transactionTypes); + } + + function getGenders(): Observable> { + return ok(genders); + } + // helper functions - function ok(body): Observable { - return of(new HttpResponse({ status: 200, body })); + function ok(responseBody: any): Observable> { + return of(new HttpResponse({ status: 200, body: responseBody })); } function error(message): Observable { @@ -201,12 +364,12 @@ export class MockBackendInterceptor implements HttpInterceptor { } function idFromUrl(): number { - const urlParts = url.split('/'); + const urlParts: Array = url.split('/'); return parseInt(urlParts[urlParts.length - 1], 10); } function stringFromUrl(): string { - const urlParts = url.split('/'); + const urlParts: Array = url.split('/'); return urlParts[urlParts.length - 1]; } } diff --git a/src/app/_helpers/read-csv.ts b/src/app/_helpers/read-csv.ts index 8ba24af..0e81715 100644 --- a/src/app/_helpers/read-csv.ts +++ b/src/app/_helpers/read-csv.ts @@ -1,24 +1,23 @@ -let objCsv = { +const objCsv: { size: number, dataFile: any } = { size: 0, dataFile: [] }; -function readCsv(input: any): any { +function readCsv(input: any): Array | void { if (input.files && input.files[0]) { - let reader = new FileReader(); + const reader: FileReader = new FileReader(); reader.readAsBinaryString(input.files[0]); reader.onload = event => { objCsv.size = event.total; - // @ts-ignore objCsv.dataFile = event.target.result; return parseData(objCsv.dataFile); }; } } -function parseData(data: any): any { - let csvData = []; - const lineBreak = data.split('\n'); +function parseData(data: any): Array { + const csvData: Array = []; + const lineBreak: Array = data.split('\n'); lineBreak.forEach(res => { csvData.push(res.split(',')); }); diff --git a/src/app/_helpers/schema-validation.ts b/src/app/_helpers/schema-validation.ts new file mode 100644 index 0000000..113202e --- /dev/null +++ b/src/app/_helpers/schema-validation.ts @@ -0,0 +1,22 @@ +import { validatePerson, validateVcard } from 'cic-schemas-data-validator'; + +async function personValidation(person: any): Promise { + const personValidationErrors: any = await validatePerson(person); + + if (personValidationErrors) { + personValidationErrors.map(error => console.error(`${error.message}`)); + } +} + +async function vcardValidation(vcard: any): Promise { + const vcardValidationErrors: any = await validateVcard(vcard); + + if (vcardValidationErrors) { + vcardValidationErrors.map(error => console.error(`${error.message}`)); + } +} + +export { + personValidation, + vcardValidation, +}; diff --git a/src/app/_interceptors/error.interceptor.ts b/src/app/_interceptors/error.interceptor.ts index 69819fd..480a734 100644 --- a/src/app/_interceptors/error.interceptor.ts +++ b/src/app/_interceptors/error.interceptor.ts @@ -1,4 +1,4 @@ -import {Injectable, isDevMode} from '@angular/core'; +import {Injectable} from '@angular/core'; import { HttpRequest, HttpHandler, @@ -6,7 +6,7 @@ import { HttpInterceptor, HttpErrorResponse } from '@angular/common/http'; import {Observable, throwError} from 'rxjs'; -import {catchError, retry} from 'rxjs/operators'; +import {catchError} from 'rxjs/operators'; import {ErrorDialogService, LoggingService} from '@app/_services'; import {Router} from '@angular/router'; @@ -20,30 +20,29 @@ export class ErrorInterceptor implements HttpInterceptor { ) {} intercept(request: HttpRequest, next: HttpHandler): Observable> { - return next.handle(request).pipe( - catchError((err: HttpErrorResponse) => { - let errorMessage; - if (err.error instanceof ErrorEvent) { - // A client-side or network error occurred. Handle it accordingly. - errorMessage = `An error occurred: ${err.error.message}`; - } else { - // The backend returned an unsuccessful response code. - // The response body may contain clues as to what went wrong. - errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify(err.error)}`; - } - this.loggingService.sendErrorLevelMessage(errorMessage, this, {error: err}); - switch (err.status) { - case 401: // unauthorized - this.router.navigateByUrl('/auth').then(); - break; - case 403: // forbidden - location.reload(true); - break; - } - // Return an observable with a user-facing error message. - return throwError(err); - }) - ); - return next.handle(request); + return next.handle(request).pipe( + catchError((err: HttpErrorResponse) => { + let errorMessage: string; + if (err.error instanceof ErrorEvent) { + // A client-side or network error occurred. Handle it accordingly. + errorMessage = `An error occurred: ${err.error.message}`; + } else { + // The backend returned an unsuccessful response code. + // The response body may contain clues as to what went wrong. + errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify(err.error)}`; + } + this.loggingService.sendErrorLevelMessage(errorMessage, this, {error: err}); + switch (err.status) { + case 401: // unauthorized + this.router.navigateByUrl('/auth').then(); + break; + case 403: // forbidden + alert('Access to resource is not allowed!'); + break; + } + // Return an observable with a user-facing error message. + return throwError(err); + }) + ); } } diff --git a/src/app/_interceptors/http-config.interceptor.ts b/src/app/_interceptors/http-config.interceptor.ts index 865b8f2..58a2067 100644 --- a/src/app/_interceptors/http-config.interceptor.ts +++ b/src/app/_interceptors/http-config.interceptor.ts @@ -13,11 +13,11 @@ export class HttpConfigInterceptor implements HttpInterceptor { constructor() {} intercept(request: HttpRequest, next: HttpHandler): Observable> { - const token = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN')); + // const token: string = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN')); - if (token) { - request = request.clone({headers: request.headers.set('Authorization', 'Bearer ' + token)}); - } + // if (token) { + // request = request.clone({headers: request.headers.set('Authorization', 'Bearer ' + token)}); + // } return next.handle(request); } diff --git a/src/app/_interceptors/logging.interceptor.ts b/src/app/_interceptors/logging.interceptor.ts index d992e73..d617590 100644 --- a/src/app/_interceptors/logging.interceptor.ts +++ b/src/app/_interceptors/logging.interceptor.ts @@ -19,20 +19,20 @@ export class LoggingInterceptor implements HttpInterceptor { intercept(request: HttpRequest, next: HttpHandler): Observable> { return next.handle(request); - // this.loggingService.sendInfoLevelMessage(request); - // const startTime = Date.now(); - // let status: string; - - // return next.handle(request).pipe(tap(event => { - // status = ''; - // if (event instanceof HttpResponse) { - // status = 'succeeded'; - // } - // }, error => status = 'failed'), - // finalize(() => { - // const elapsedTime = Date.now() - startTime; - // const message = `${request.method} request for ${request.urlWithParams} ${status} in ${elapsedTime} ms`; - // this.loggingService.sendInfoLevelMessage(message); - // })); + // this.loggingService.sendInfoLevelMessage(request); + // const startTime: number = Date.now(); + // let status: string; + // + // return next.handle(request).pipe(tap(event => { + // status = ''; + // if (event instanceof HttpResponse) { + // status = 'succeeded'; + // } + // }, error => status = 'failed'), + // finalize(() => { + // const elapsedTime: number = Date.now() - startTime; + // const message: string = `${request.method} request for ${request.urlWithParams} ${status} in ${elapsedTime} ms`; + // this.loggingService.sendInfoLevelMessage(message); + // })); } } diff --git a/src/app/_models/account.ts b/src/app/_models/account.ts index 7209861..c295e2d 100644 --- a/src/app/_models/account.ts +++ b/src/app/_models/account.ts @@ -1,8 +1,9 @@ -export interface AccountDetails { +interface AccountDetails { date_registered: number; gender: string; age?: string; type?: string; + balance?: number; identities: { evm: { 'bloxberg:8996': string[]; @@ -40,25 +41,25 @@ export interface AccountDetails { }; } -export interface Signature { +interface Signature { algo: string; data: string; digest: string; engine: string; } -export interface Meta { +interface Meta { data: AccountDetails; id: string; signature: Signature; } -export interface MetaResponse { +interface MetaResponse { id: string; m: Meta; } -export const defaultAccount: AccountDetails = { +const defaultAccount: AccountDetails = { date_registered: Date.now(), gender: 'other', identities: { @@ -94,3 +95,11 @@ export const defaultAccount: AccountDetails = { }], }, }; + +export { + AccountDetails, + Signature, + Meta, + MetaResponse, + defaultAccount +}; diff --git a/src/app/_models/index.ts b/src/app/_models/index.ts index fb2270a..a6dfb15 100644 --- a/src/app/_models/index.ts +++ b/src/app/_models/index.ts @@ -1,4 +1,6 @@ export * from '@app/_models/transaction'; export * from '@app/_models/settings'; -export * from '@app/_models/user'; export * from '@app/_models/account'; +export * from '@app/_models/staff'; +export * from '@app/_models/token'; +export * from '@app/_models/mappings'; diff --git a/src/app/_models/mappings.ts b/src/app/_models/mappings.ts new file mode 100644 index 0000000..e5abbec --- /dev/null +++ b/src/app/_models/mappings.ts @@ -0,0 +1,29 @@ +interface Action { + id: number; + user: string; + role: string; + action: string; + approval: boolean; +} + +interface Category { + name: string; + products: Array; +} + +interface AreaName { + name: string; + locations: Array; +} + +interface AreaType { + name: string; + area: Array; +} + +export { + Action, + Category, + AreaName, + AreaType +}; diff --git a/src/app/_models/settings.ts b/src/app/_models/settings.ts index 6cb5547..23f75b2 100644 --- a/src/app/_models/settings.ts +++ b/src/app/_models/settings.ts @@ -1,4 +1,4 @@ -export class Settings { +class Settings { w3: W3 = { engine: undefined, provider: undefined, @@ -12,7 +12,12 @@ export class Settings { } } -export class W3 { +class W3 { engine: any; provider: any; } + +export { + Settings, + W3 +}; diff --git a/src/app/_models/staff.ts b/src/app/_models/staff.ts index 1dfdb07..0fea485 100644 --- a/src/app/_models/staff.ts +++ b/src/app/_models/staff.ts @@ -1,7 +1,11 @@ -export interface Staff { +interface Staff { comment: string; email: string; name: string; tag: number; userid: string; } + +export { + Staff +}; diff --git a/src/app/_models/token.ts b/src/app/_models/token.ts index ab11ca3..580e3bc 100644 --- a/src/app/_models/token.ts +++ b/src/app/_models/token.ts @@ -1,4 +1,4 @@ -export interface Token { +interface Token { name: string; symbol: string; address: string; @@ -13,3 +13,7 @@ export interface Token { reserveRatio?: string; owner?: string; } + +export { + Token +}; diff --git a/src/app/_models/transaction.ts b/src/app/_models/transaction.ts index 203ae6e..2a8b27e 100644 --- a/src/app/_models/transaction.ts +++ b/src/app/_models/transaction.ts @@ -1,6 +1,6 @@ -import {User} from '@app/_models/user'; +import {AccountDetails} from '@app/_models/account'; -export class BlocksBloom { +class BlocksBloom { low: number; blockFilter: string; blocktxFilter: string; @@ -8,13 +8,13 @@ export class BlocksBloom { filterRounds: number; } -export class Token { +class TxToken { address: string; name: string; symbol: string; } -export class Tx { +class Tx { block: number; success: boolean; timestamp: number; @@ -22,22 +22,31 @@ export class Tx { txIndex: number; } -export class Transaction { +class Transaction { from: string; - sender: User; + sender: AccountDetails; to: string; - recipient: User; - token: Token; + recipient: AccountDetails; + token: TxToken; tx: Tx; value: number; + type?: string; } -export class Conversion { - destinationToken: Token; +class Conversion { + destinationToken: TxToken; fromValue: number; - sourceToken: Token; + sourceToken: TxToken; toValue: number; trader: string; - user: User; + user: AccountDetails; tx: Tx; } + +export { + BlocksBloom, + TxToken, + Tx, + Transaction, + Conversion +}; diff --git a/src/app/_models/user.ts b/src/app/_models/user.ts deleted file mode 100644 index 957ebc3..0000000 --- a/src/app/_models/user.ts +++ /dev/null @@ -1,22 +0,0 @@ -export class User { - dateRegistered: number; - vcard: { - fn: string; - version: string; - tel: [{ - meta: { - TYP: string; - }; - value: string[]; - }]; - }; - key: { - ethereum: string[]; - }; - location: { - latitude: string; - longitude: string; - external: {}; - }; - selling: string[]; -} diff --git a/src/app/_pgp/pgp-key-store.ts b/src/app/_pgp/pgp-key-store.ts index 0b27f9f..ddce20c 100644 --- a/src/app/_pgp/pgp-key-store.ts +++ b/src/app/_pgp/pgp-key-store.ts @@ -1,5 +1,5 @@ import { KeyStore } from 'cic-client-meta'; -// TODO should we put this on the mutalble key store object +// TODO should we put this on the mutable key store object import * as openpgp from 'openpgp'; const keyring = new openpgp.Keyring(); @@ -76,15 +76,14 @@ class MutablePgpKeyStore implements MutableKeyStore{ } async isValidKey(key): Promise { - // There is supposed to be an opengpg.readKey() method but I can't find it? + // There is supposed to be an openpgp.readKey() method but I can't find it? const _key = await openpgp.key.readArmored(key); return !_key.err; } async isEncryptedPrivateKey(privateKey: any): Promise { const imported = await openpgp.key.readArmored(privateKey); - for (let i = 0; i < imported.keys.length; i++) { - const key = imported.keys[i]; + for (const key of imported.keys) { if (key.isDecrypted()) { return false; } diff --git a/src/app/_services/auth.service.ts b/src/app/_services/auth.service.ts index 731602a..0761476 100644 --- a/src/app/_services/auth.service.ts +++ b/src/app/_services/auth.service.ts @@ -1,20 +1,19 @@ -import { Injectable } from '@angular/core'; -import { hobaParseChallengeHeader } from '@src/assets/js/hoba.js'; -import { signChallenge } from '@src/assets/js/hoba-pgp.js'; -import { environment } from '@src/environments/environment'; -import { LoggingService } from '@app/_services/logging.service'; -import { MutableKeyStore, MutablePgpKeyStore } from '@app/_pgp'; -import { ErrorDialogService } from '@app/_services/error-dialog.service'; -import { HttpClient } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { HttpError } from '@app/_helpers/global-error-handler'; +import {Injectable} from '@angular/core'; +import {hobaParseChallengeHeader} from '@src/assets/js/hoba.js'; +import {signChallenge} from '@src/assets/js/hoba-pgp.js'; +import {environment} from '@src/environments/environment'; +import {LoggingService} from '@app/_services/logging.service'; +import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp'; +import {ErrorDialogService} from '@app/_services/error-dialog.service'; +import {HttpClient} from '@angular/common/http'; +import {HttpError} from '@app/_helpers/global-error-handler'; @Injectable({ providedIn: 'root' }) export class AuthService { sessionToken: any; - sessionLoginCount = 0; + sessionLoginCount: number = 0; mutableKeyStore: MutableKeyStore; constructor( @@ -22,18 +21,17 @@ export class AuthService { private loggingService: LoggingService, private errorDialogService: ErrorDialogService ) { - this.mutableKeyStore = new MutablePgpKeyStore() + this.mutableKeyStore = new MutablePgpKeyStore(); } async init(): Promise { - this.mutableKeyStore.loadKeyring(); - // TODO setting these together shoulds be atomic + await this.mutableKeyStore.loadKeyring(); + // TODO setting these together should be atomic if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) { this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN')); } if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) { - this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) - // this.privateKey = localStorage.getItem(btoa('CICADA_PRIVATE_KEY')); + await this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))); } } @@ -42,7 +40,7 @@ export class AuthService { } getWithToken(): void { - const xhr = new XMLHttpRequest(); + const xhr: XMLHttpRequest = new XMLHttpRequest(); xhr.responseType = 'text'; xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1)); xhr.setRequestHeader('Authorization', 'Bearer ' + this.sessionToken); @@ -59,10 +57,10 @@ export class AuthService { xhr.send(); } - //TODO renmae to send signed challenge and set session. Also seperate these responsibilities - sendResponse(hobaResponseEncoded): Promise { + // TODO rename to send signed challenge and set session. Also separate these responsibilities + sendResponse(hobaResponseEncoded: any): Promise { return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); + const xhr: XMLHttpRequest = new XMLHttpRequest(); xhr.responseType = 'text'; xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1)); xhr.setRequestHeader('Authorization', 'HOBA ' + hobaResponseEncoded); @@ -80,11 +78,11 @@ export class AuthService { return resolve(true); }); xhr.send(); - }) + }); } getChallenge(): void { - const xhr = new XMLHttpRequest(); + const xhr: XMLHttpRequest = new XMLHttpRequest(); xhr.responseType = 'arraybuffer'; xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1)); xhr.onload = async (e) => { @@ -97,25 +95,46 @@ export class AuthService { xhr.send(); } - async loginResponse(o): Promise { - return new Promise(async(resolve, reject) => { + + login(): boolean { + if (this.sessionToken !== undefined) { try { - const r = await signChallenge(o.challenge, - o.realm, - environment.cicMetaUrl, + this.getWithToken(); + return true; + } catch (e) { + this.loggingService.sendErrorLevelMessage('Login token failed', this, {error: e}); + } + } else { + try { + this.getChallenge(); + } catch (e) { + this.loggingService.sendErrorLevelMessage('Login challenge failed', this, {error: e}); + } + } + return false; + } + + + async loginResponse(o: { challenge: string, realm: any }): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await signChallenge(o.challenge, + o.realm, + environment.cicMetaUrl, this.mutableKeyStore); - const sessionTokenResult = await this.sendResponse(r); + const sessionTokenResult: boolean = await this.sendResponse(r); } catch (error) { if (error instanceof HttpError) { if (error.status === 403) { - this.errorDialogService.openDialog({ message: 'You are not authorized to use this system' }) + this.errorDialogService.openDialog({ message: 'You are not authorized to use this system' }); } if (error.status === 401) { - this.errorDialogService.openDialog({ message: 'Unable to authenticate with the service. ' + - 'Please speak with the staff at Grassroots ' + - 'Economics for requesting access ' + - 'staff@grassrootseconomics.net.' }) - + this.errorDialogService.openDialog({ + message: 'Unable to authenticate with the service. ' + + 'Please speak with the staff at Grassroots ' + + 'Economics for requesting access ' + + 'staff@grassrootseconomics.net.' + }); } } // TODO define this error @@ -138,10 +157,10 @@ export class AuthService { throw Error('The private key is invalid'); } // TODO leaving this out for now. - //const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(privateKeyArmored); - //if (!isEncryptedKeyCheck) { - // throw Error('The private key doesn\'t have a password!'); - //} + // const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(privateKeyArmored); + // if (!isEncryptedKeyCheck) { + // throw Error('The private key doesn\'t have a password!'); + // } const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored); localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored); } catch (err) { @@ -162,21 +181,20 @@ export class AuthService { } getTrustedUsers(): any { - let trustedUsers = []; + const trustedUsers: Array = []; this.mutableKeyStore.getPublicKeys().forEach(key => trustedUsers.push(key.users[0].userId)); return trustedUsers; } async getPublicKeys(): Promise { - const data = await fetch(environment.publicKeysUrl) + return await fetch(environment.publicKeysUrl) .then(res => { if (!res.ok) { - //TODO does angular recommend an error interface? + // TODO does angular recommend an error interface? throw Error(`${res.statusText} - ${res.status}`); } return res.text(); - }) - return data; + }); } getPrivateKey(): any { diff --git a/src/app/_services/block-sync.service.ts b/src/app/_services/block-sync.service.ts index d96d28f..2c98e99 100644 --- a/src/app/_services/block-sync.service.ts +++ b/src/app/_services/block-sync.service.ts @@ -1,12 +1,11 @@ import {Injectable} from '@angular/core'; import {Settings} from '@app/_models'; -import Web3 from 'web3'; -import {CICRegistry, TransactionHelper} from 'cic-client'; +import {TransactionHelper} from 'cic-client'; import {first} from 'rxjs/operators'; import {TransactionService} from '@app/_services/transaction.service'; import {environment} from '@src/environments/environment'; -import {HttpGetter} from '@app/_helpers'; import {LoggingService} from '@app/_services/logging.service'; +import {RegistryService} from '@app/_services/registry.service'; @Injectable({ providedIn: 'root' @@ -14,23 +13,20 @@ import {LoggingService} from '@app/_services/logging.service'; export class BlockSyncService { readyStateTarget: number = 2; readyState: number = 0; - fileGetter = new HttpGetter(); constructor( private transactionService: TransactionService, - private loggingService: LoggingService + private loggingService: LoggingService, + private registryService: RegistryService, ) { } - blockSync(address: string = null, offset: number = 0, limit: number = 100): any { + blockSync(address: string = null, offset: number = 0, limit: number = 100): void { this.transactionService.resetTransactionsList(); - const settings = new Settings(this.scan); - const provider = environment.web3Provider; - const readyStateElements = { network: 2 }; - settings.w3.provider = provider; - settings.w3.engine = new Web3(provider); - settings.registry = new CICRegistry(settings.w3.engine, environment.registryAddress, this.fileGetter, - ['../../assets/js/block-sync/data']); - settings.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress); + const settings: Settings = new Settings(this.scan); + const readyStateElements: { network: number } = { network: 2 }; + settings.w3.provider = environment.web3Provider; + settings.w3.engine = this.registryService.getWeb3(); + settings.registry = this.registryService.getRegistry(); settings.txHelper = new TransactionHelper(settings.w3.engine, settings.registry); settings.txHelper.ontransfer = async (transaction: any): Promise => { @@ -50,7 +46,7 @@ export class BlockSyncService { readyStateProcessor(settings: Settings, bit: number, address: string, offset: number, limit: number): void { this.readyState |= bit; if (this.readyStateTarget === this.readyState && this.readyStateTarget) { - const wHeadSync = new Worker('./../assets/js/block-sync/head.js'); + const wHeadSync: Worker = new Worker('./../assets/js/block-sync/head.js'); wHeadSync.onmessage = (m) => { settings.txHelper.processReceipt(m.data); }; @@ -69,7 +65,7 @@ export class BlockSyncService { } } - newTransferEvent(tx): any { + newTransferEvent(tx: any): any { return new CustomEvent('cic_transfer', { detail: { tx, @@ -77,7 +73,7 @@ export class BlockSyncService { }); } - newConversionEvent(tx): any { + newConversionEvent(tx: any): any { return new CustomEvent('cic_convert', { detail: { tx, @@ -85,8 +81,8 @@ export class BlockSyncService { }); } - async scan(settings, lo, hi, bloomBlockBytes, bloomBlocktxBytes, bloomRounds): Promise { - const w = new Worker('./../assets/js/block-sync/ondemand.js'); + async scan(settings: Settings, lo: number, hi: number, bloomBlockBytes: Uint8Array, bloomBlocktxBytes: Uint8Array, bloomRounds: any): Promise { + const w: Worker = new Worker('./../assets/js/block-sync/ondemand.js'); w.onmessage = (m) => { settings.txHelper.processReceipt(m.data); }; @@ -103,12 +99,12 @@ export class BlockSyncService { } fetcher(settings: Settings, transactionsInfo: any): void { - const blockFilterBinstr = window.atob(transactionsInfo.block_filter); - const bOne = new Uint8Array(blockFilterBinstr.length); + const blockFilterBinstr: string = window.atob(transactionsInfo.block_filter); + const bOne: Uint8Array = new Uint8Array(blockFilterBinstr.length); bOne.map((e, i, v) => v[i] = blockFilterBinstr.charCodeAt(i)); - const blocktxFilterBinstr = window.atob(transactionsInfo.blocktx_filter); - const bTwo = new Uint8Array(blocktxFilterBinstr.length); + const blocktxFilterBinstr: string = window.atob(transactionsInfo.blocktx_filter); + const bTwo: Uint8Array = new Uint8Array(blocktxFilterBinstr.length); bTwo.map((e, i, v) => v[i] = blocktxFilterBinstr.charCodeAt(i)); settings.scanFilter(settings, transactionsInfo.low, transactionsInfo.high, bOne, bTwo, transactionsInfo.filter_rounds); diff --git a/src/app/_services/error-dialog.service.ts b/src/app/_services/error-dialog.service.ts index 37db8cd..8a68a7f 100644 --- a/src/app/_services/error-dialog.service.ts +++ b/src/app/_services/error-dialog.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import {MatDialog} from '@angular/material/dialog'; +import {MatDialog, MatDialogRef} from '@angular/material/dialog'; import {ErrorDialogComponent} from '@app/shared/error-dialog/error-dialog.component'; @Injectable({ @@ -17,7 +17,7 @@ export class ErrorDialogService { return false; } this.isDialogOpen = true; - const dialogRef = this.dialog.open(ErrorDialogComponent, { + const dialogRef: MatDialogRef = this.dialog.open(ErrorDialogComponent, { width: '300px', data }); diff --git a/src/app/_services/location.service.ts b/src/app/_services/location.service.ts index 834d02a..911ba26 100644 --- a/src/app/_services/location.service.ts +++ b/src/app/_services/location.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import {BehaviorSubject} from 'rxjs'; +import {Observable} from 'rxjs'; import {environment} from '@src/environments/environment'; import {first} from 'rxjs/operators'; import {HttpClient} from '@angular/common/http'; @@ -8,15 +8,24 @@ import {HttpClient} from '@angular/common/http'; providedIn: 'root' }) export class LocationService { - locations: any = ''; - private locationsList = new BehaviorSubject(this.locations); - locationsSubject = this.locationsList.asObservable(); constructor( private httpClient: HttpClient, ) { } - getLocations(): void { - this.httpClient.get(`${environment.cicCacheUrl}/locations`).pipe(first()).subscribe(res => this.locationsList.next(res)); + getAreaNames(): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/areanames`); + } + + getAreaNameByLocation(location: string): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/areanames/${location.toLowerCase()}`); + } + + getAreaTypes(): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/areatypes`).pipe(first()); + } + + getAreaTypeByArea(area: string): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/areatypes/${area.toLowerCase()}`).pipe(first()); } } diff --git a/src/app/_services/logging.service.ts b/src/app/_services/logging.service.ts index f08264b..fef6c81 100644 --- a/src/app/_services/logging.service.ts +++ b/src/app/_services/logging.service.ts @@ -15,31 +15,31 @@ export class LoggingService { } } - sendTraceLevelMessage(message, source, error): void { + sendTraceLevelMessage(message: any, source: any, error: any): void { this.logger.trace(message, source, error); } - sendDebugLevelMessage(message, source, error): void { + sendDebugLevelMessage(message: any, source: any, error: any): void { this.logger.debug(message, source, error); } - sendInfoLevelMessage(message): void { + sendInfoLevelMessage(message: any): void { this.logger.info(message); } - sendLogLevelMessage(message, source, error): void { + sendLogLevelMessage(message: any, source: any, error: any): void { this.logger.log(message, source, error); } - sendWarnLevelMessage(message, error): void { + sendWarnLevelMessage(message: any, error: any): void { this.logger.warn(message, error); } - sendErrorLevelMessage(message, source, error): void { + sendErrorLevelMessage(message: any, source: any, error: any): void { this.logger.error(message, source, error); } - sendFatalLevelMessage(message, source, error): void { + sendFatalLevelMessage(message: any, source: any, error: any): void { this.logger.fatal(message, source, error); } } diff --git a/src/app/_services/registry.service.spec.ts b/src/app/_services/registry.service.spec.ts new file mode 100644 index 0000000..fa9d231 --- /dev/null +++ b/src/app/_services/registry.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { RegistryService } from './registry.service'; + +describe('RegistryService', () => { + let service: RegistryService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(RegistryService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/_services/registry.service.ts b/src/app/_services/registry.service.ts new file mode 100644 index 0000000..8fc8018 --- /dev/null +++ b/src/app/_services/registry.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import Web3 from 'web3'; +import {environment} from '@src/environments/environment'; +import {CICRegistry, FileGetter} from 'cic-client'; +import {HttpGetter} from '@app/_helpers'; + +@Injectable({ + providedIn: 'root' +}) +export class RegistryService { + web3: Web3 = new Web3(environment.web3Provider); + fileGetter: FileGetter = new HttpGetter(); + registry: CICRegistry = new CICRegistry(this.web3, environment.registryAddress, 'CICRegistry', this.fileGetter, + ['../../assets/js/block-sync/data']); + + constructor() { + this.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress); + this.registry.load(); + } + + getRegistry(): any { + return this.registry; + } + + getWeb3(): any { + return this.web3; + } +} diff --git a/src/app/_services/token.service.ts b/src/app/_services/token.service.ts index 2924f53..71d787d 100644 --- a/src/app/_services/token.service.ts +++ b/src/app/_services/token.service.ts @@ -1,32 +1,34 @@ -import { Injectable } from '@angular/core'; +import { EventEmitter, Injectable } from '@angular/core'; import {environment} from '@src/environments/environment'; import {BehaviorSubject, Observable} from 'rxjs'; -import {HttpGetter} from '@app/_helpers'; import {CICRegistry} from 'cic-client'; -import Web3 from 'web3'; -import {Registry, TokenRegistry} from '@app/_eth'; +import {TokenRegistry} from '@app/_eth'; import {HttpClient} from '@angular/common/http'; +import {RegistryService} from '@app/_services/registry.service'; @Injectable({ providedIn: 'root' }) export class TokenService { - web3 = new Web3(environment.web3Provider); - fileGetter = new HttpGetter(); - registry = new Registry(environment.registryAddress); - cicRegistry = new CICRegistry(this.web3, environment.registryAddress, this.fileGetter, ['../../assets/js/block-sync/data']); - tokens: any = ''; - private tokensList = new BehaviorSubject(this.tokens); - tokensSubject = this.tokensList.asObservable(); + registry: CICRegistry; + tokenRegistry: TokenRegistry; + LoadEvent: EventEmitter = new EventEmitter(); constructor( private httpClient: HttpClient, - ) { } + private registryService: RegistryService, + ) { + this.registry = registryService.getRegistry(); + this.registry.load(); + this.registry.onload = async (address: string): Promise => { + this.tokenRegistry = new TokenRegistry(await this.registry.getContractAddressByName('TokenRegistry')); + this.LoadEvent.next(Date.now()); + }; + } - async getTokens(): Promise { - const tokenRegistryQuery = new TokenRegistry(await this.registry.addressOf('TokenRegistry')); - const count = await tokenRegistryQuery.totalTokens(); - return Array.from({length: count}, async (v, i) => await tokenRegistryQuery.entry(i)); + async getTokens(): Promise>> { + const count: number = await this.tokenRegistry.totalTokens(); + return Array.from({length: count}, async (v, i) => await this.tokenRegistry.entry(i)); } getTokenBySymbol(symbol: string): Observable { @@ -34,8 +36,7 @@ export class TokenService { } async getTokenBalance(address: string): Promise { - const tokenRegistryQuery = new TokenRegistry(await this.registry.addressOf('TokenRegistry')); - const sarafuToken = await this.cicRegistry.addToken(await tokenRegistryQuery.entry(0)); + const sarafuToken = await this.registry.addToken(await this.tokenRegistry.entry(0)); return await sarafuToken.methods.balanceOf(address).call(); } } diff --git a/src/app/_services/transaction.service.ts b/src/app/_services/transaction.service.ts index c1f9ba9..3638bd3 100644 --- a/src/app/_services/transaction.service.ts +++ b/src/app/_services/transaction.service.ts @@ -13,9 +13,10 @@ import * as secp256k1 from 'secp256k1'; import {AuthService} from '@app/_services/auth.service'; import {defaultAccount} from '@app/_models'; import {LoggingService} from '@app/_services/logging.service'; -import {Registry} from '@app/_eth'; import {HttpClient} from '@angular/common/http'; -const Web3 = require('web3'); +import {CICRegistry} from 'cic-client'; +import {RegistryService} from '@app/_services/registry.service'; +import Web3 from 'web3'; const vCard = require('vcard-parser'); @Injectable({ @@ -26,15 +27,20 @@ export class TransactionService { private transactionList = new BehaviorSubject(this.transactions); transactionsSubject = this.transactionList.asObservable(); userInfo: any; - web3 = new Web3(environment.web3Provider); - registry = new Registry(environment.registryAddress); + web3: Web3; + registry: CICRegistry; constructor( private httpClient: HttpClient, private authService: AuthService, private userService: UserService, - private loggingService: LoggingService - ) { } + private loggingService: LoggingService, + private registryService: RegistryService, + ) { + this.web3 = this.registryService.getWeb3(); + this.registry = registryService.getRegistry(); + this.registry.load(); + } getAllTransactions(offset: number, limit: number): Observable { return this.httpClient.get(`${environment.cicCacheUrl}/tx/${offset}/${limit}`); @@ -100,7 +106,7 @@ export class TransactionService { } async transferRequest(tokenAddress: string, senderAddress: string, recipientAddress: string, value: number): Promise { - const transferAuthAddress = await this.registry.addressOf('TransferAuthorization'); + const transferAuthAddress = await this.registry.getContractAddressByName('TransferAuthorization'); const hashFunction = new Keccak(256); hashFunction.update('createRequest(address,address,address,uint256)'); const hash = hashFunction.digest(); @@ -110,7 +116,7 @@ export class TransactionService { const data = fromHex(methodSignature + strip0x(abi)); const tx = new Tx(environment.bloxbergChainId); tx.nonce = await this.web3.eth.getTransactionCount(senderAddress); - tx.gasPrice = await this.web3.eth.getGasPrice(); + tx.gasPrice = Number(await this.web3.eth.getGasPrice()); tx.gasLimit = 8000000; tx.to = fromHex(strip0x(transferAuthAddress)); tx.value = toValue(value); diff --git a/src/app/_services/user.service.ts b/src/app/_services/user.service.ts index b803775..839f7cf 100644 --- a/src/app/_services/user.service.ts +++ b/src/app/_services/user.service.ts @@ -1,14 +1,19 @@ import {Injectable} from '@angular/core'; -import {BehaviorSubject, Observable} from 'rxjs'; +import {BehaviorSubject, Observable, Subject} from 'rxjs'; import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; import {environment} from '@src/environments/environment'; import {first} from 'rxjs/operators'; -import {ArgPair, Envelope, Syncable, User} from 'cic-client-meta'; -import {MetaResponse} from '@app/_models'; +import {ArgPair, Envelope, Phone, Syncable, User} from 'cic-client-meta'; +import {AccountDetails} from '@app/_models'; import {LoggingService} from '@app/_services/logging.service'; import {TokenService} from '@app/_services/token.service'; -import {AccountIndex, Registry} from '@app/_eth'; -import {MutableKeyStore, MutablePgpKeyStore, PGPSigner, Signer} from '@app/_pgp'; +import {AccountIndex} from '@app/_eth'; +import {MutableKeyStore, PGPSigner, Signer} from '@app/_pgp'; +import {RegistryService} from '@app/_services/registry.service'; +import {CICRegistry} from 'cic-client'; +import {AuthService} from '@app/_services/auth.service'; +import {personValidation, vcardValidation} from '@app/_helpers'; +import {add0x} from '@src/assets/js/ethtx/dist/hex'; const vCard = require('vcard-parser'); @Injectable({ @@ -16,49 +21,58 @@ const vCard = require('vcard-parser'); }) export class UserService { headers: HttpHeaders = new HttpHeaders({'x-cic-automerge': 'client'}); - keystore: MutableKeyStore = new MutablePgpKeyStore(); - signer: Signer = new PGPSigner(this.keystore); - registry = new Registry(environment.registryAddress); + keystore: MutableKeyStore; + signer: Signer; + registry: CICRegistry; - accountsMeta = []; - accounts: any = []; - private accountsList = new BehaviorSubject(this.accounts); - accountsSubject = this.accountsList.asObservable(); + accounts: Array = []; + private accountsList: BehaviorSubject> = new BehaviorSubject>(this.accounts); + accountsSubject: Observable> = this.accountsList.asObservable(); - actions: any = ''; - private actionsList = new BehaviorSubject(this.actions); - actionsSubject = this.actionsList.asObservable(); - - staff: any = ''; - private staffList = new BehaviorSubject(this.staff); - staffSubject = this.staffList.asObservable(); + actions: Array = []; + private actionsList: BehaviorSubject = new BehaviorSubject(this.actions); + actionsSubject: Observable> = this.actionsList.asObservable(); constructor( private httpClient: HttpClient, private loggingService: LoggingService, - private tokenService: TokenService + private tokenService: TokenService, + private registryService: RegistryService, + private authService: AuthService, ) { + this.authService.init().then(() => { + this.keystore = authService.mutableKeyStore; + this.signer = new PGPSigner(this.keystore); + }); + this.registry = registryService.getRegistry(); + this.registry.load(); } resetPin(phone: string): Observable { - const params = new HttpParams().set('phoneNumber', phone); + const params: HttpParams = new HttpParams().set('phoneNumber', phone); return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params}); } - getAccountStatus(phone: string): any { - const params = new HttpParams().set('phoneNumber', phone); + getAccountStatus(phone: string): Observable { + const params: HttpParams = new HttpParams().set('phoneNumber', phone); return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params}); } - getLockedAccounts(offset: number, limit: number): any { + getLockedAccounts(offset: number, limit: number): Observable { return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`); } async changeAccountInfo(address: string, name: string, phoneNumber: string, age: string, type: string, bio: string, gender: string, - businessCategory: string, userLocation: string, location: string, locationType: string, metaAccount: MetaResponse + businessCategory: string, userLocation: string, location: string, locationType: string ): Promise { - let reqBody = metaAccount; - let accountInfo = reqBody.m.data; + const accountInfo: any = { + vcard: { + fn: [{}], + n: [{}], + tel: [{}] + }, + location: {} + }; accountInfo.vcard.fn[0].value = name; accountInfo.vcard.n[0].value = name.split(' '); accountInfo.vcard.tel[0].value = phoneNumber; @@ -70,16 +84,17 @@ export class UserService { accountInfo.location.area = location; accountInfo.location.area_name = userLocation; accountInfo.location.area_type = locationType; - accountInfo.vcard = vCard.generate(accountInfo.vcard); - reqBody.m.data = accountInfo; - const accountKey = await User.toKey(address); - this.httpClient.get(`${environment.cicMetaUrl}/${accountKey}`, { headers: this.headers }).pipe(first()).subscribe(async res => { + await vcardValidation(accountInfo.vcard); + accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard)); + const accountKey: string = await User.toKey(address); + this.getAccountDetailsFromMeta(accountKey).pipe(first()).subscribe(async res => { const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); - let update = []; - for (const prop in reqBody) { - update.push(new ArgPair(prop, reqBody[prop])); + const update: Array = []; + for (const prop in accountInfo) { + update.push(new ArgPair(prop, accountInfo[prop])); } syncableAccount.update(update, 'client-branch'); + await personValidation(syncableAccount.m.data); await this.updateMeta(syncableAccount, accountKey, this.headers); }, async error => { this.loggingService.sendErrorLevelMessage('Can\'t find account info in meta service', this, {error}); @@ -90,26 +105,18 @@ export class UserService { } async updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders): Promise { - const envelope = await this.wrap(syncableAccount , this.signer); - const reqBody = envelope.toJSON(); + const envelope: Envelope = await this.wrap(syncableAccount , this.signer); + const reqBody: string = envelope.toJSON(); this.httpClient.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => { this.loggingService.sendInfoLevelMessage(`Response: ${res}`); }); } - getAccounts(): void { - this.httpClient.get(`${environment.cicCacheUrl}/accounts`).pipe(first()).subscribe(res => this.accountsList.next(res)); - } - - getAccountById(id: number): Observable { - return this.httpClient.get(`${environment.cicCacheUrl}/accounts/${id}`); - } - getActions(): void { this.httpClient.get(`${environment.cicCacheUrl}/actions`).pipe(first()).subscribe(res => this.actionsList.next(res)); } - getActionById(id: string): any { + getActionById(id: string): Observable { return this.httpClient.get(`${environment.cicCacheUrl}/actions/${id}`); } @@ -121,30 +128,19 @@ export class UserService { return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: false }); } - getHistoryByUser(id: string): Observable { - return this.httpClient.get(`${environment.cicCacheUrl}/history/${id}`); - } - getAccountDetailsFromMeta(userKey: string): Observable { return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers }); } - getUser(userKey: string): any { - return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers }) - .pipe(first()).subscribe(async res => { - return Envelope.fromJSON(JSON.stringify(res)).unwrap(); - }); - } - wrap(syncable: Syncable, signer: Signer): Promise { - return new Promise(async (whohoo, doh) => { + return new Promise(async (resolve, reject) => { syncable.setSigner(signer); syncable.onwrap = async (env) => { if (env === undefined) { - doh(); + reject(); return; } - whohoo(env); + resolve(env); }; await syncable.sign(); }); @@ -152,28 +148,71 @@ export class UserService { async loadAccounts(limit: number = 100, offset: number = 0): Promise { this.resetAccountsList(); - const accountIndexAddress = await this.registry.addressOf('AccountRegistry'); + const accountIndexAddress: string = await this.registry.getContractAddressByName('AccountRegistry'); const accountIndexQuery = new AccountIndex(accountIndexAddress); - const accountAddresses = await accountIndexQuery.last(await accountIndexQuery.totalAccounts()); + const accountAddresses: Array = await accountIndexQuery.last(await accountIndexQuery.totalAccounts()); this.loggingService.sendInfoLevelMessage(accountAddresses); for (const accountAddress of accountAddresses.slice(offset, offset + limit)) { - this.getAccountDetailsFromMeta(await User.toKey(accountAddress)).pipe(first()).subscribe(async res => { - const account = Envelope.fromJSON(JSON.stringify(res)).unwrap(); - this.accountsMeta.push(account); - const accountInfo = account.m.data; - accountInfo.balance = await this.tokenService.getTokenBalance(accountInfo.identities.evm['bloxberg:8996'][0]); - accountInfo.vcard = vCard.parse(atob(accountInfo.vcard)); - this.accounts.unshift(accountInfo); - if (this.accounts.length > limit) { - this.accounts.length = limit; - } - this.accountsList.next(this.accounts); - }); + await this.getAccountByAddress(accountAddress, limit); } } + async getAccountByAddress(accountAddress: string, limit: number = 100): Promise> { + let accountSubject: Subject = new Subject(); + this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress))).pipe(first()).subscribe(async res => { + const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); + const accountInfo = account.m.data; + await personValidation(accountInfo); + accountInfo.balance = await this.tokenService.getTokenBalance(accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]); + accountInfo.vcard = vCard.parse(atob(accountInfo.vcard)); + await vcardValidation(accountInfo.vcard); + this.accounts.unshift(accountInfo); + if (this.accounts.length > limit) { + this.accounts.length = limit; + } + this.accountsList.next(this.accounts); + accountSubject.next(accountInfo); + }); + return accountSubject.asObservable(); + } + + async getAccountByPhone(phoneNumber: string, limit: number = 100): Promise> { + let accountSubject: Subject = new Subject(); + this.getAccountDetailsFromMeta(await Phone.toKey(phoneNumber)).pipe(first()).subscribe(async res => { + const response: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); + const address: string = response.m.data; + const account: Observable = await this.getAccountByAddress(address, limit); + account.subscribe(result => { + accountSubject.next(result); + }); + }); + return accountSubject.asObservable(); + } + resetAccountsList(): void { this.accounts = []; this.accountsList.next(this.accounts); } + + searchAccountByName(name: string): any { return; } + + getCategories(): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/categories`); + } + + getCategoryByProduct(product: string): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/categories/${product.toLowerCase()}`); + } + + getAccountTypes(): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/accounttypes`); + } + + getTransactionTypes(): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/transactiontypes`); + } + + getGenders(): Observable { + return this.httpClient.get(`${environment.cicMetaUrl}/genders`); + } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2cc5c1b..cecd933 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,7 +12,7 @@ export class AppComponent { title = 'CICADA'; readyStateTarget: number = 3; readyState: number = 0; - mediaQuery = window.matchMedia('(max-width: 768px)'); + mediaQuery: MediaQueryList = window.matchMedia('(max-width: 768px)'); constructor( private authService: AuthService, @@ -22,19 +22,18 @@ export class AppComponent { ) { (async () => { try { - await this.authService.mutableKeyStore.loadKeyring(); + await this.authService.init(); // this.authService.getPublicKeys() // .pipe(catchError(async (error) => { // this.loggingService.sendErrorLevelMessage('Unable to load trusted public keys.', this, {error}); // this.errorDialogService.openDialog({message: 'Trusted keys endpoint can\'t be reached. Please try again later.'}); // })).subscribe(this.authService.mutableKeyStore.importPublicKey); - const publicKeys = await this.authService.getPublicKeys() + const publicKeys = await this.authService.getPublicKeys(); await this.authService.mutableKeyStore.importPublicKey(publicKeys); - } catch(error) { + } catch (error) { this.errorDialogService.openDialog({message: 'Trusted keys endpoint can\'t be reached. Please try again later.'}); // TODO do something to halt user progress...show a sad cicada page 🦗? } - })(); this.mediaQuery.addListener(this.onResize); this.onResize(this.mediaQuery); @@ -42,9 +41,9 @@ export class AppComponent { // Load resize onResize(e): void { - const sidebar = document.getElementById('sidebar'); - const content = document.getElementById('content'); - const sidebarCollapse = document.getElementById('sidebarCollapse'); + const sidebar: HTMLElement = document.getElementById('sidebar'); + const content: HTMLElement = document.getElementById('content'); + const sidebarCollapse: HTMLElement = document.getElementById('sidebarCollapse'); if (sidebarCollapse?.classList.contains('active')) { sidebarCollapse?.classList.remove('active'); } @@ -67,13 +66,13 @@ export class AppComponent { @HostListener('window:cic_transfer', ['$event']) async cicTransfer(event: CustomEvent): Promise { - const transaction = event.detail.tx; + const transaction: any = event.detail.tx; await this.transactionService.setTransaction(transaction, 100); } @HostListener('window:cic_convert', ['$event']) async cicConvert(event: CustomEvent): Promise { - const conversion = event.detail.tx; + const conversion: any = event.detail.tx; await this.transactionService.setConversion(conversion, 100); } } diff --git a/src/app/auth/_directives/password-toggle.directive.ts b/src/app/auth/_directives/password-toggle.directive.ts index c6b998b..5c2b8fb 100644 --- a/src/app/auth/_directives/password-toggle.directive.ts +++ b/src/app/auth/_directives/password-toggle.directive.ts @@ -20,8 +20,8 @@ export class PasswordToggleDirective { } togglePasswordVisibility(): void { - const password = document.getElementById(this.id); - const icon = document.getElementById(this.iconId); + const password: HTMLElement = document.getElementById(this.id); + const icon: HTMLElement = document.getElementById(this.iconId); // @ts-ignore if (password.type === 'password') { // @ts-ignore diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index 19419db..dff3cbe 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -14,7 +14,7 @@ export class AuthComponent implements OnInit { keyForm: FormGroup; submitted: boolean = false; loading: boolean = false; - matcher = new CustomErrorStateMatcher(); + matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher(); constructor( private authService: AuthService, @@ -27,10 +27,10 @@ export class AuthComponent implements OnInit { key: ['', Validators.required], }); await this.authService.init(); - //if (this.authService.privateKey !== undefined) { - // const setKey = await this.authService.setKey(this.authService.privateKey); - // } - //} + // if (this.authService.privateKey !== undefined) { + // const setKey = await this.authService.setKey(this.authService.privateKey); + // } + // } } get keyFormStub(): any { return this.keyForm.controls; } @@ -46,7 +46,7 @@ export class AuthComponent implements OnInit { } login(): void { - // TODO check if we have privatekey + // TODO check if we have privatekey // Send us to home if we have a private key // talk to meta somehow // in the error interceptor if 401/403 handle it @@ -58,14 +58,14 @@ export class AuthComponent implements OnInit { switchWindows(): void { this.authService.sessionToken = undefined; - const divOne = document.getElementById('one'); - const divTwo = document.getElementById('two'); + const divOne: HTMLElement = document.getElementById('one'); + const divTwo: HTMLElement = document.getElementById('two'); this.toggleDisplay(divOne); this.toggleDisplay(divTwo); } toggleDisplay(element: any): void { - const style = window.getComputedStyle(element).display; + const style: string = window.getComputedStyle(element).display; if (style === 'block') { element.style.display = 'none'; } else { diff --git a/src/app/pages/accounts/account-details/account-details.component.html b/src/app/pages/accounts/account-details/account-details.component.html index c1ee838..4cdec33 100644 --- a/src/app/pages/accounts/account-details/account-details.component.html +++ b/src/app/pages/accounts/account-details/account-details.component.html @@ -14,7 +14,7 @@
@@ -35,7 +35,10 @@ Balance: {{account?.balance | tokenRatio}} SRF Created: {{account?.date_registered | date}} - Address: {{account?.identities.evm['bloxberg:8996']}} + Address: + {{accountAddress}} + Copy +
@@ -75,11 +78,9 @@ ACCOUNT TYPE: - USER - CASHIER - VENDOR - TOKENAGENT - GROUPACCOUNT + + {{accountType | uppercase}} + Type is required. @@ -99,9 +100,9 @@ GENDER: - MALE - FEMALE - OTHER + + {{gender | uppercase}} + Gender is required. @@ -112,16 +113,9 @@ BUSINESS CATEGORY: - Food/Water - Fuel/Energy - Education - Health - Shop - Environment - Transport - Farming/Labour - Savings Group - Savings Group + + {{category | titlecase}} + Category is required. @@ -146,16 +140,9 @@ LOCATION: -
-
- - - {{village}} - - -
-
+ + {{area | uppercase}} +
Location is required. @@ -166,23 +153,22 @@ LOCATION TYPE: - URBAN - PERIURBAN - RURAL - OTHER + + {{type | uppercase}} + Location Type is required.
-
- @@ -240,7 +226,7 @@
- +
@@ -250,11 +236,9 @@ TRANSACTION TYPE ALL TRANSFERS - PAYMENTS - CONVERSION - DISBURSEMENTS - REWARDS - RECLAMATION + + {{transactionType | uppercase}} + @@ -324,11 +308,9 @@ ACCOUNT TYPE ALL - USER - CASHIER - VENDOR - TOKENAGENT - GROUPACCOUNT + + {{accountType | uppercase}} + diff --git a/src/app/pages/accounts/account-details/account-details.component.spec.ts b/src/app/pages/accounts/account-details/account-details.component.spec.ts index 3b9b288..6af8f5b 100644 --- a/src/app/pages/accounts/account-details/account-details.component.spec.ts +++ b/src/app/pages/accounts/account-details/account-details.component.spec.ts @@ -50,12 +50,4 @@ describe('AccountDetailsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); - - it('#addTransfer() should toggle #isDisbursing', () => { - expect(component.isDisbursing).toBe(false, 'off at first'); - component.addTransfer(); - expect(component.isDisbursing).toBe(true, 'on after click'); - component.addTransfer(); - expect(component.isDisbursing).toBe(false, 'off after second click'); - }); }); diff --git a/src/app/pages/accounts/account-details/account-details.component.ts b/src/app/pages/accounts/account-details/account-details.component.ts index 8695592..d9cd130 100644 --- a/src/app/pages/accounts/account-details/account-details.component.ts +++ b/src/app/pages/accounts/account-details/account-details.component.ts @@ -1,4 +1,4 @@ -import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core'; import {MatTableDataSource} from '@angular/material/table'; import {MatPaginator} from '@angular/material/paginator'; import {MatSort} from '@angular/material/sort'; @@ -6,9 +6,11 @@ import {BlockSyncService, LocationService, LoggingService, TokenService, Transac import {ActivatedRoute, Params, Router} from '@angular/router'; import {first} from 'rxjs/operators'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {CustomErrorStateMatcher, exportCsv} from '@app/_helpers'; -import {Envelope, User} from 'cic-client-meta'; -const vCard = require('vcard-parser'); +import {copyToClipboard, CustomErrorStateMatcher, exportCsv} from '@app/_helpers'; +import {MatSnackBar} from '@angular/material/snack-bar'; +import {add0x, strip0x} from '@src/assets/js/ethtx/dist/hex'; +import {environment} from '@src/environments/environment'; +import {AccountDetails, AreaName, AreaType, Category, Transaction} from '@app/_models'; @Component({ selector: 'app-account-details', @@ -18,32 +20,35 @@ const vCard = require('vcard-parser'); }) export class AccountDetailsComponent implements OnInit { transactionsDataSource: MatTableDataSource; - transactionsDisplayedColumns = ['sender', 'recipient', 'value', 'created', 'type']; - transactionsDefaultPageSize = 10; - transactionsPageSizeOptions = [10, 20, 50, 100]; + transactionsDisplayedColumns: Array = ['sender', 'recipient', 'value', 'created', 'type']; + transactionsDefaultPageSize: number = 10; + transactionsPageSizeOptions: Array = [10, 20, 50, 100]; @ViewChild('TransactionTablePaginator', {static: true}) transactionTablePaginator: MatPaginator; @ViewChild('TransactionTableSort', {static: true}) transactionTableSort: MatSort; userDataSource: MatTableDataSource; - userDisplayedColumns = ['name', 'phone', 'created', 'balance', 'location']; - usersDefaultPageSize = 10; - usersPageSizeOptions = [10, 20, 50, 100]; + userDisplayedColumns: Array = ['name', 'phone', 'created', 'balance', 'location']; + usersDefaultPageSize: number = 10; + usersPageSizeOptions: Array = [10, 20, 50, 100]; @ViewChild('UserTablePaginator', {static: true}) userTablePaginator: MatPaginator; @ViewChild('UserTableSort', {static: true}) userTableSort: MatSort; accountInfoForm: FormGroup; - account: any; + account: AccountDetails; accountAddress: string; - accountBalance: number; accountStatus: any; - metaAccount: any; - accounts: any[] = []; - accountsType = 'all'; - locations: any; + accounts: Array = []; + accountsType: string = 'all'; + categories: Array; + areaNames: Array; + areaTypes: Array; transaction: any; - transactions: any[]; - transactionsType = 'all'; - matcher = new CustomErrorStateMatcher(); + transactions: Array; + transactionsType: string = 'all'; + accountTypes: Array; + transactionsTypes: Array; + genders: Array; + matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher(); submitted: boolean = false; bloxbergLink: string; @@ -56,7 +61,9 @@ export class AccountDetailsComponent implements OnInit { private router: Router, private tokenService: TokenService, private loggingService: LoggingService, - private blockSyncService: BlockSyncService + private blockSyncService: BlockSyncService, + private cdr: ChangeDetectorRef, + private snackBar: MatSnackBar, ) { this.accountInfoForm = this.formBuilder.group({ name: ['', Validators.required], @@ -71,35 +78,39 @@ export class AccountDetailsComponent implements OnInit { locationType: ['', Validators.required], }); this.route.paramMap.subscribe(async (params: Params) => { - this.accountAddress = params.get('id'); + this.accountAddress = add0x(params.get('id')); this.bloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions'; - this.userService.getAccountDetailsFromMeta(await User.toKey(this.accountAddress)).pipe(first()).subscribe(async res => { - this.metaAccount = Envelope.fromJSON(JSON.stringify(res.body)).unwrap(); - this.account = this.metaAccount.m.data; - this.loggingService.sendInfoLevelMessage(this.account); - this.accountBalance = await this.tokenService.getTokenBalance(this.accountAddress); - this.account.vcard = vCard.parse(atob(this.account.vcard)); - this.userService.getAccountStatus(this.account.vcard?.tel[0].value).pipe(first()).subscribe(response => this.accountStatus = response); - this.accountInfoForm.patchValue({ - name: this.account.vcard?.fn[0].value, - phoneNumber: this.account.vcard?.tel[0].value, - age: this.account.age, - type: this.account.type, - bio: this.account.products, - gender: this.account.gender, - businessCategory: this.account.category, - userLocation: this.account.location.area_name, - location: this.account.location.area, - locationType: this.account.location.area_type, - }); + (await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(async res => { + if (res !== undefined) { + this.account = res; + this.cdr.detectChanges(); + this.loggingService.sendInfoLevelMessage(this.account); + // this.userService.getAccountStatus(this.account.vcard?.tel[0].value).pipe(first()) + // .subscribe(response => this.accountStatus = response); + this.accountInfoForm.patchValue({ + name: this.account.vcard?.fn[0].value, + phoneNumber: this.account.vcard?.tel[0].value, + age: this.account.age, + type: this.account.type, + bio: this.account.products, + gender: this.account.gender, + businessCategory: this.account.category, + userLocation: this.account.location.area_name, + location: this.account.location.area, + locationType: this.account.location.area_type, + }); + } else { + alert('Account not found!'); + } }); this.blockSyncService.blockSync(this.accountAddress); }); - this.userService.getAccounts(); - this.locationService.getLocations(); - this.locationService.locationsSubject.subscribe(locations => { - this.locations = locations; - }); + this.userService.getCategories().pipe(first()).subscribe(res => this.categories = res); + this.locationService.getAreaNames().pipe(first()).subscribe(res => this.areaNames = res); + this.locationService.getAreaTypes().pipe(first()).subscribe(res => this.areaTypes = res); + this.userService.getAccountTypes().pipe(first()).subscribe(res => this.accountTypes = res); + this.userService.getTransactionTypes().pipe(first()).subscribe(res => this.transactionsTypes = res); + this.userService.getGenders().pipe(first()).subscribe(res => this.genders = res); } ngOnInit(): void { @@ -131,7 +142,7 @@ export class AccountDetailsComponent implements OnInit { } viewAccount(account): void { - this.router.navigateByUrl(`/accounts/${account.id}`); + this.router.navigateByUrl(`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`); } get accountInfoFormStub(): any { return this.accountInfoForm.controls; } @@ -140,7 +151,7 @@ export class AccountDetailsComponent implements OnInit { this.submitted = true; if (this.accountInfoForm.invalid || !confirm('Change user\'s profile information?')) { return; } const accountKey = await this.userService.changeAccountInfo( - this.account.address, + this.accountAddress, this.accountInfoFormStub.name.value, this.accountInfoFormStub.phoneNumber.value, this.accountInfoFormStub.age.value, @@ -150,10 +161,8 @@ export class AccountDetailsComponent implements OnInit { this.accountInfoFormStub.businessCategory.value, this.accountInfoFormStub.userLocation.value, this.accountInfoFormStub.location.value, - this.accountInfoFormStub.locationType.value, - this.metaAccount + this.accountInfoFormStub.locationType.value ); - this.loggingService.sendInfoLevelMessage(`Response: ${accountKey}`); this.submitted = false; } @@ -181,16 +190,18 @@ export class AccountDetailsComponent implements OnInit { resetPin(): void { if (!confirm('Reset user\'s pin?')) { return; } - this.userService.resetPin(this.account.phone).pipe(first()).subscribe(res => { + this.userService.resetPin(this.account.vcard.tel[0].value).pipe(first()).subscribe(res => { this.loggingService.sendInfoLevelMessage(`Response: ${res}`); }); } - public trackByName(index, item): string { - return item.name; - } - downloadCsv(data: any, filename: string): void { exportCsv(data, filename); } + + copyAddress(): void { + if (copyToClipboard(this.accountAddress)) { + this.snackBar.open(this.accountAddress + ' copied successfully!', 'Close', { duration: 3000 }); + } + } } diff --git a/src/app/pages/accounts/account-search/account-search.component.html b/src/app/pages/accounts/account-search/account-search.component.html new file mode 100644 index 0000000..56a17cc --- /dev/null +++ b/src/app/pages/accounts/account-search/account-search.component.html @@ -0,0 +1,59 @@ + +
+ + + + + + +
+ + +
+ +
+ + Accounts + +
+ + +
+ + Search + + Phone Number is required. + phone + Phone Number + + +
+
+ +
+ + Search + + Account Address is required. + view_in_ar + Account Address + + +
+
+
+
+
+
+ +
+ + + +
diff --git a/src/app/pages/accounts/disbursement/disbursement.component.scss b/src/app/pages/accounts/account-search/account-search.component.scss similarity index 100% rename from src/app/pages/accounts/disbursement/disbursement.component.scss rename to src/app/pages/accounts/account-search/account-search.component.scss diff --git a/src/app/pages/accounts/account-search/account-search.component.spec.ts b/src/app/pages/accounts/account-search/account-search.component.spec.ts new file mode 100644 index 0000000..8c38dbf --- /dev/null +++ b/src/app/pages/accounts/account-search/account-search.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AccountSearchComponent } from './account-search.component'; + +describe('AccountSearchComponent', () => { + let component: AccountSearchComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AccountSearchComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AccountSearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/accounts/account-search/account-search.component.ts b/src/app/pages/accounts/account-search/account-search.component.ts new file mode 100644 index 0000000..0d94aa0 --- /dev/null +++ b/src/app/pages/accounts/account-search/account-search.component.ts @@ -0,0 +1,84 @@ +import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {CustomErrorStateMatcher} from '@app/_helpers'; +import {UserService} from '@app/_services'; +import {Router} from '@angular/router'; +import {strip0x} from '@src/assets/js/ethtx/dist/hex'; +import {environment} from '@src/environments/environment'; + +@Component({ + selector: 'app-account-search', + templateUrl: './account-search.component.html', + styleUrls: ['./account-search.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AccountSearchComponent implements OnInit { + nameSearchForm: FormGroup; + nameSearchSubmitted: boolean = false; + nameSearchLoading: boolean = false; + phoneSearchForm: FormGroup; + phoneSearchSubmitted: boolean = false; + phoneSearchLoading: boolean = false; + addressSearchForm: FormGroup; + addressSearchSubmitted: boolean = false; + addressSearchLoading: boolean = false; + matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher(); + + constructor( + private formBuilder: FormBuilder, + private userService: UserService, + private router: Router, + ) { } + + ngOnInit(): void { + this.nameSearchForm = this.formBuilder.group({ + name: ['', Validators.required], + }); + this.phoneSearchForm = this.formBuilder.group({ + phoneNumber: ['', Validators.required], + }); + this.addressSearchForm = this.formBuilder.group({ + address: ['', Validators.required], + }); + } + + get nameSearchFormStub(): any { return this.nameSearchForm.controls; } + get phoneSearchFormStub(): any { return this.phoneSearchForm.controls; } + get addressSearchFormStub(): any { return this.addressSearchForm.controls; } + + onNameSearch(): void { + this.nameSearchSubmitted = true; + if (this.nameSearchForm.invalid) { return; } + this.nameSearchLoading = true; + this.userService.searchAccountByName(this.nameSearchFormStub.name.value); + this.nameSearchLoading = false; + } + + async onPhoneSearch(): Promise { + this.phoneSearchSubmitted = true; + if (this.phoneSearchForm.invalid) { return; } + this.phoneSearchLoading = true; + (await this.userService.getAccountByPhone(this.phoneSearchFormStub.phoneNumber.value, 100)).subscribe(async res => { + if (res !== undefined) { + await this.router.navigateByUrl(`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`); + } else { + alert('Account not found!'); + } + }); + this.phoneSearchLoading = false; + } + + async onAddressSearch(): Promise { + this.addressSearchSubmitted = true; + if (this.addressSearchForm.invalid) { return; } + this.addressSearchLoading = true; + (await this.userService.getAccountByAddress(this.addressSearchFormStub.address.value, 100)).subscribe(async res => { + if (res !== undefined) { + await this.router.navigateByUrl(`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`); + } else { + alert('Account not found!'); + } + }); + this.addressSearchLoading = false; + } +} diff --git a/src/app/pages/accounts/accounts-routing.module.ts b/src/app/pages/accounts/accounts-routing.module.ts index 1ef5e21..25ef7d1 100644 --- a/src/app/pages/accounts/accounts-routing.module.ts +++ b/src/app/pages/accounts/accounts-routing.module.ts @@ -3,13 +3,13 @@ import { Routes, RouterModule } from '@angular/router'; import { AccountsComponent } from '@pages/accounts/accounts.component'; import {CreateAccountComponent} from '@pages/accounts/create-account/create-account.component'; -import {ExportAccountsComponent} from '@pages/accounts/export-accounts/export-accounts.component'; import {AccountDetailsComponent} from '@pages/accounts/account-details/account-details.component'; +import {AccountSearchComponent} from '@pages/accounts/account-search/account-search.component'; const routes: Routes = [ { path: '', component: AccountsComponent }, + { path: 'search', component: AccountSearchComponent }, // { path: 'create', component: CreateAccountComponent }, - { path: 'export', component: ExportAccountsComponent }, { path: ':id', component: AccountDetailsComponent }, { path: '**', redirectTo: '', pathMatch: 'full' } ]; diff --git a/src/app/pages/accounts/accounts.component.html b/src/app/pages/accounts/accounts.component.html index 18f045e..7252fcf 100644 --- a/src/app/pages/accounts/accounts.component.html +++ b/src/app/pages/accounts/accounts.component.html @@ -26,14 +26,13 @@ ACCOUNT TYPE ALL - USER - CASHIER - VENDOR - TOKENAGENT - GROUPACCOUNT + + {{accountType | uppercase}} + - + +
diff --git a/src/app/pages/accounts/accounts.component.ts b/src/app/pages/accounts/accounts.component.ts index 1b112f0..c84e50e 100644 --- a/src/app/pages/accounts/accounts.component.ts +++ b/src/app/pages/accounts/accounts.component.ts @@ -5,6 +5,10 @@ import {MatSort} from '@angular/material/sort'; import {LoggingService, UserService} from '@app/_services'; import {Router} from '@angular/router'; import {exportCsv} from '@app/_helpers'; +import {strip0x} from '@src/assets/js/ethtx/dist/hex'; +import {first} from 'rxjs/operators'; +import {environment} from '@src/environments/environment'; +import {AccountDetails} from '@app/_models'; @Component({ selector: 'app-accounts', @@ -14,11 +18,12 @@ import {exportCsv} from '@app/_helpers'; }) export class AccountsComponent implements OnInit { dataSource: MatTableDataSource; - accounts: any[] = []; - displayedColumns = ['name', 'phone', 'created', 'balance', 'location']; - defaultPageSize = 10; - pageSizeOptions = [10, 20, 50, 100]; - accountsType = 'all'; + accounts: Array = []; + displayedColumns: Array = ['name', 'phone', 'created', 'balance', 'location']; + defaultPageSize: number = 10; + pageSizeOptions: Array = [10, 20, 50, 100]; + accountsType: string = 'all'; + accountTypes: Array; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -27,16 +32,17 @@ export class AccountsComponent implements OnInit { private userService: UserService, private loggingService: LoggingService, private router: Router - ) + ) { (async () => { try { - // TODO it feels like this shuold be in the onInit handler + // TODO it feels like this should be in the onInit handler await this.userService.loadAccounts(100); } catch (error) { this.loggingService.sendErrorLevelMessage('Failed to load accounts', this, {error}); } })(); + this.userService.getAccountTypes().pipe(first()).subscribe(res => this.accountTypes = res); } ngOnInit(): void { @@ -53,7 +59,7 @@ export class AccountsComponent implements OnInit { } async viewAccount(account): Promise { - await this.router.navigateByUrl(`/accounts/${account.identities.evm['bloxberg:8996']}`); + await this.router.navigateByUrl(`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`); } filterAccounts(): void { diff --git a/src/app/pages/accounts/accounts.module.ts b/src/app/pages/accounts/accounts.module.ts index a347767..0fa3d31 100644 --- a/src/app/pages/accounts/accounts.module.ts +++ b/src/app/pages/accounts/accounts.module.ts @@ -7,8 +7,6 @@ import {SharedModule} from '@app/shared/shared.module'; import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component'; import {DataTablesModule} from 'angular-datatables'; import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component'; -import { DisbursementComponent } from '@pages/accounts/disbursement/disbursement.component'; -import { ExportAccountsComponent } from '@pages/accounts/export-accounts/export-accounts.component'; import {MatTableModule} from '@angular/material/table'; import {MatSortModule} from '@angular/material/sort'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -24,10 +22,17 @@ import {MatTabsModule} from '@angular/material/tabs'; import {MatRippleModule} from '@angular/material/core'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {ReactiveFormsModule} from '@angular/forms'; +import { AccountSearchComponent } from './account-search/account-search.component'; +import {MatSnackBarModule} from '@angular/material/snack-bar'; @NgModule({ - declarations: [AccountsComponent, AccountDetailsComponent, CreateAccountComponent, DisbursementComponent, ExportAccountsComponent], + declarations: [ + AccountsComponent, + AccountDetailsComponent, + CreateAccountComponent, + AccountSearchComponent + ], imports: [ CommonModule, AccountsRoutingModule, @@ -47,7 +52,8 @@ import {ReactiveFormsModule} from '@angular/forms'; MatTabsModule, MatRippleModule, MatProgressSpinnerModule, - ReactiveFormsModule + ReactiveFormsModule, + MatSnackBarModule, ] }) export class AccountsModule { } diff --git a/src/app/pages/accounts/create-account/create-account.component.html b/src/app/pages/accounts/create-account/create-account.component.html index ca75dec..41a82a9 100644 --- a/src/app/pages/accounts/create-account/create-account.component.html +++ b/src/app/pages/accounts/create-account/create-account.component.html @@ -27,11 +27,9 @@ Account Type: - USER - CASHIER - VENDOR - TOKENAGENT - GROUPACCOUNT + + {{accountType | uppercase}} + Account type is required.
@@ -81,15 +79,9 @@ Location: -
-
- - - {{village}} - - -
-
+ + {{area | uppercase}} +
Location is required.

@@ -99,9 +91,9 @@ Gender: - FEMALE - MALE - OTHER + + {{gender | uppercase}} + Gender is required.
@@ -119,16 +111,9 @@ Business Category: - Food/Water - Fuel/Energy - Education - Health - Shop - Environment - Transport - Farming/Labour - Savings Group - Other + + {{category | titlecase}} + Business Category is required. diff --git a/src/app/pages/accounts/create-account/create-account.component.ts b/src/app/pages/accounts/create-account/create-account.component.ts index 81b5ff8..5443f15 100644 --- a/src/app/pages/accounts/create-account/create-account.component.ts +++ b/src/app/pages/accounts/create-account/create-account.component.ts @@ -1,7 +1,9 @@ import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {LocationService} from '@app/_services'; +import {LocationService, UserService} from '@app/_services'; import {CustomErrorStateMatcher} from '@app/_helpers'; +import {first} from 'rxjs/operators'; +import {AreaName, Category} from '@app/_models'; @Component({ selector: 'app-create-account', @@ -11,13 +13,17 @@ import {CustomErrorStateMatcher} from '@app/_helpers'; }) export class CreateAccountComponent implements OnInit { createForm: FormGroup; - matcher = new CustomErrorStateMatcher(); + matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher(); submitted: boolean = false; - locations: any; + categories: Array; + areaNames: Array; + accountTypes: Array; + genders: Array; constructor( private formBuilder: FormBuilder, - private locationService: LocationService + private locationService: LocationService, + private userService: UserService ) { } ngOnInit(): void { @@ -33,10 +39,10 @@ export class CreateAccountComponent implements OnInit { referrer: ['', Validators.required], businessCategory: ['', Validators.required] }); - this.locationService.getLocations(); - this.locationService.locationsSubject.subscribe(locations => { - this.locations = locations; - }); + this.userService.getCategories().pipe(first()).subscribe(res => this.categories = res); + this.locationService.getAreaNames().pipe(first()).subscribe(res => this.areaNames = res); + this.userService.getAccountTypes().pipe(first()).subscribe(res => this.accountTypes = res); + this.userService.getGenders().pipe(first()).subscribe(res => this.genders = res); } get createFormStub(): any { return this.createForm.controls; } @@ -46,8 +52,4 @@ export class CreateAccountComponent implements OnInit { if (this.createForm.invalid || !confirm('Create account?')) { return; } this.submitted = false; } - - public trackByName(index, item): string { - return item.name; - } } diff --git a/src/app/pages/accounts/disbursement/disbursement.component.html b/src/app/pages/accounts/disbursement/disbursement.component.html deleted file mode 100644 index 137a153..0000000 --- a/src/app/pages/accounts/disbursement/disbursement.component.html +++ /dev/null @@ -1,36 +0,0 @@ -
- -
- NEW TRANSFER - -
-
-
-
-
- - TRANSACTION TYPE - - DISBURSEMENT - TRANSFER - DEPOSIT - RECLAMATION - - Transaction type is required. - - - Enter Recipient: - - - - Enter Amount: - - Amount is required. - - -
-
-
-
diff --git a/src/app/pages/accounts/disbursement/disbursement.component.spec.ts b/src/app/pages/accounts/disbursement/disbursement.component.spec.ts deleted file mode 100644 index e22fee6..0000000 --- a/src/app/pages/accounts/disbursement/disbursement.component.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DisbursementComponent } from '@pages/accounts/disbursement/disbursement.component'; -import {AccountsModule} from '@pages/accounts/accounts.module'; -import {AppModule} from '@app/app.module'; -import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing'; - -describe('DisbursementComponent', () => { - let component: DisbursementComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - DisbursementComponent, - FooterStubComponent, - SidebarStubComponent, - TopbarStubComponent - ], - imports: [ - AccountsModule, - AppModule - ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(DisbursementComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/pages/accounts/disbursement/disbursement.component.ts b/src/app/pages/accounts/disbursement/disbursement.component.ts deleted file mode 100644 index 4c6820a..0000000 --- a/src/app/pages/accounts/disbursement/disbursement.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -import {Component, OnInit, EventEmitter, Output, Input, ChangeDetectionStrategy} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {CustomErrorStateMatcher} from '@app/_helpers'; -import {TransactionService} from '@app/_services'; - -@Component({ - selector: 'app-disbursement', - templateUrl: './disbursement.component.html', - styleUrls: ['./disbursement.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DisbursementComponent implements OnInit { - @Input() account; - @Output() cancelDisbursmentEvent = new EventEmitter(); - disbursementForm: FormGroup; - matcher = new CustomErrorStateMatcher(); - submitted: boolean = false; - - constructor( - private formBuilder: FormBuilder, - private transactionService: TransactionService - ) { } - - ngOnInit(): void { - this.disbursementForm = this.formBuilder.group({ - transactionType: ['', Validators.required], - recipient: '', - amount: ['', Validators.required] - }); - } - - get disbursementFormStub(): any { return this.disbursementForm.controls; } - - async createTransfer(): Promise { - this.submitted = true; - if (this.disbursementForm.invalid || !confirm('Make transfer?')) { return; } - if (this.disbursementFormStub.transactionType.value === 'transfer') { - await this.transactionService.transferRequest( - this.account.token, - this.account.address, - this.disbursementFormStub.recipient.value, - this.disbursementFormStub.amount.value - ); - } - this.submitted = false; - } - - cancel(): void { - this.cancelDisbursmentEvent.emit(); - } -} diff --git a/src/app/pages/accounts/export-accounts/export-accounts.component.html b/src/app/pages/accounts/export-accounts/export-accounts.component.html deleted file mode 100644 index 4f98e6c..0000000 --- a/src/app/pages/accounts/export-accounts/export-accounts.component.html +++ /dev/null @@ -1,53 +0,0 @@ - -
- - - - - - -
- - -
- -
- - EXPORT ACCOUNTS - -
-
-
- - Export : - - VENDORS - PARTNERS - SELECTED - - Account Type is required. - -
-
-
- - -
-
- -
-
-
-
- -
- - - -
diff --git a/src/app/pages/accounts/export-accounts/export-accounts.component.scss b/src/app/pages/accounts/export-accounts/export-accounts.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/pages/accounts/export-accounts/export-accounts.component.spec.ts b/src/app/pages/accounts/export-accounts/export-accounts.component.spec.ts deleted file mode 100644 index f78e9d4..0000000 --- a/src/app/pages/accounts/export-accounts/export-accounts.component.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ExportAccountsComponent } from '@pages/accounts/export-accounts/export-accounts.component'; -import {AccountsModule} from '@pages/accounts/accounts.module'; -import {AppModule} from '@app/app.module'; -import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing'; - -describe('ExportAccountsComponent', () => { - let component: ExportAccountsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - ExportAccountsComponent, - FooterStubComponent, - SidebarStubComponent, - TopbarStubComponent - ], - imports: [ - AccountsModule, - AppModule - ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ExportAccountsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/pages/accounts/export-accounts/export-accounts.component.ts b/src/app/pages/accounts/export-accounts/export-accounts.component.ts deleted file mode 100644 index b0fa3cc..0000000 --- a/src/app/pages/accounts/export-accounts/export-accounts.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {CustomErrorStateMatcher} from '@app/_helpers'; - -@Component({ - selector: 'app-export-accounts', - templateUrl: './export-accounts.component.html', - styleUrls: ['./export-accounts.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ExportAccountsComponent implements OnInit { - exportForm: FormGroup; - matcher = new CustomErrorStateMatcher(); - submitted: boolean = false; - - constructor( - private formBuilder: FormBuilder - ) { } - - ngOnInit(): void { - this.exportForm = this.formBuilder.group({ - accountType: ['', Validators.required], - transfers: [''] - }); - } - - get exportFormStub(): any { return this.exportForm.controls; } - - export(): void { - this.submitted = true; - if (this.exportForm.invalid || !confirm('Export accounts?')) { return; } - this.submitted = false; - } -} diff --git a/src/app/pages/admin/admin.component.ts b/src/app/pages/admin/admin.component.ts index 6e133f4..a28fc44 100644 --- a/src/app/pages/admin/admin.component.ts +++ b/src/app/pages/admin/admin.component.ts @@ -6,6 +6,7 @@ import {LoggingService, UserService} from '@app/_services'; import {animate, state, style, transition, trigger} from '@angular/animations'; import {first} from 'rxjs/operators'; import {exportCsv} from '@app/_helpers'; +import {Action} from '../../_models'; @Component({ selector: 'app-admin', @@ -22,9 +23,9 @@ import {exportCsv} from '@app/_helpers'; }) export class AdminComponent implements OnInit { dataSource: MatTableDataSource; - displayedColumns = ['expand', 'user', 'role', 'action', 'status', 'approve']; - action: any; - actions: any; + displayedColumns: Array = ['expand', 'user', 'role', 'action', 'status', 'approve']; + action: Action; + actions: Array; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -39,7 +40,6 @@ export class AdminComponent implements OnInit { this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; this.actions = actions; - console.log(this.actions); }); } diff --git a/src/app/pages/pages.component.html b/src/app/pages/pages.component.html index b596f15..5e60109 100644 --- a/src/app/pages/pages.component.html +++ b/src/app/pages/pages.component.html @@ -15,97 +15,13 @@ -
- - CICADA DASHBOARD - -
-
- - Filter by location : - -
-
- - - {{village}} - - -
-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

MASTER WALLET BALANCE

-

{{10000000 | number}} RCU

-
-
-

TOTAL DISTRIBUTED

-

{{disbursements * 1000 | number}} RCU

-
-
-

TOTAL SPENT

-

{{transactions * 100000 | number}} RCU

-
-
-

TOTAL USERS

-

{{users * 10 | number}} users

-
-
-
-
-
- - -
-
-
-
-
-

TRANSFER USAGES

-
- - -
-
-
-
-

PARTNER LIVE FEED

- -
-
-
-
+
+
diff --git a/src/app/pages/pages.component.ts b/src/app/pages/pages.component.ts index 14ba106..576c61b 100644 --- a/src/app/pages/pages.component.ts +++ b/src/app/pages/pages.component.ts @@ -1,8 +1,4 @@ -import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; -import {Color, Label} from 'ng2-charts'; -import {ChartDataSets, ChartOptions, ChartType} from 'chart.js'; -import {LocationService, LoggingService} from '@app/_services'; -import {ArraySum} from '@app/_helpers'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ selector: 'app-pages', @@ -10,181 +6,8 @@ import {ArraySum} from '@app/_helpers'; styleUrls: ['./pages.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class PagesComponent implements OnInit { - disbursements: number = 0; - users: any; - locations: any; - transactions: number = 0; - public lineChartData: ChartDataSets[] = [ - { data: [65, 59, 80, 81, 56, 55, 40], label: 'User Registration'}, - { data: [28, 48, 40, 19, 86, 27, 90], label: 'Transaction Volumes'}, - { data: [180, 480, 770, 90, 1000, 270, 400], label: 'Token Disbursements', yAxisID: 'y-axis-1'} - ]; +export class PagesComponent { + url: string = 'https://dashboard.sarafu.network/'; - public lineChartLabels: Label[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; - - public lineChartOptions: (ChartOptions & { annotation: any }) = { - responsive: true, - scales: { - // We use this empty structure as a placeholder for dynamic theming. - xAxes: [{}], - yAxes: [ - { - id: 'y-axis-0', - position: 'left', - }, - { - id: 'y-axis-1', - position: 'right', - gridLines: { - color: 'rgba(255,0,0,0.3)', - }, - ticks: { - fontColor: 'red', - } - } - ] - }, - annotation: { - annotations: [ - { - type: 'line', - mode: 'vertical', - scaleID: 'x-axis-0', - value: 'March', - borderColor: 'orange', - borderWidth: 2, - label: { - enabled: true, - fontColor: 'orange', - content: 'LineAnno' - } - }, - ], - }, - }; - - public lineChartColors: Color[] = [ - { // grey - backgroundColor: 'rgba(148,159,177,0.2)', - borderColor: 'rgba(148,159,177,1)', - pointBackgroundColor: 'rgba(148,159,177,1)', - pointBorderColor: '#fff', - pointHoverBackgroundColor: '#fff', - pointHoverBorderColor: 'rgba(148,159,177,0.8)' - }, - { // dark grey - backgroundColor: 'rgba(77,83,96,0.2)', - borderColor: 'rgba(77,83,96,1)', - pointBackgroundColor: 'rgba(77,83,96,1)', - pointBorderColor: '#fff', - pointHoverBackgroundColor: '#fff', - pointHoverBorderColor: 'rgba(77,83,96,1)' - }, - { // red - backgroundColor: 'rgba(255,0,0,0.3)', - borderColor: 'red', - pointBackgroundColor: 'rgba(148,159,177,1)', - pointBorderColor: '#fff', - pointHoverBackgroundColor: '#fff', - pointHoverBorderColor: 'rgba(148,159,177,0.8)' - } - ]; - - public lineChartLegend = true; - public lineChartType = 'line'; - - public barChartOptions: ChartOptions = { - responsive: true, - // We use these empty structures as placeholders for dynamic theming. - scales: { xAxes: [{}], yAxes: [{}] }, - plugins: { - datalabels: { - anchor: 'end', - align: 'end', - } - } - }; - public barChartLabels: Label[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; - public barChartType: ChartType = 'horizontalBar'; - public barChartLegend = true; - public barChartData: ChartDataSets[] = [ - { data: [65, 59, 80, 81, 56, 55, 40], label: 'New Users'}, - { data: [28, 48, 40, 19, 86, 27, 90], label: 'Recurrent Users'} - ]; - - public transferUsagesChartLabels: Label[] = ['Food/Water', 'Fuel/Energy', 'Education', 'Health', 'Shop', 'Environment', 'Transport', - 'Farming/Labour', 'Savings Group', 'Savings Group']; - public transferUsagesChartData: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - public transferUsagesChartType: ChartType = 'pie'; - public transferUsagesChartOptions: ChartOptions = { - responsive: true, - legend: { - position: 'top', - }, - plugins: { - datalabels: { - formatter: (value, ctx) => { - const label = ctx.chart.data.labels[ctx.dataIndex]; - return label; - }, - }, - } - }; - public transferUsagesChartLegend = true; - public transferUsagesChartColors = [ - { - backgroundColor: [ - 'rgba(0,0,255,0.3)', - 'rgba(255,0,0,0.3)', - 'rgba(0,255,0,0.3)', - 'rgba(255,0,255,0.3)', - 'rgba(255,255,0,0.3)', - 'rgba(0,255,255,0.3)', - 'rgba(255,255,255,0.3)', - 'rgba(255,100,0,0.3)', - 'rgba(0,255,100,0.3)', - 'rgba(100,0,255,0.3)'], - }, - ]; - - constructor( - private locationService: LocationService, - private loggingService: LoggingService - ) { - this.locationService.getLocations(); - this.locationService.locationsSubject.subscribe(locations => { - this.locations = locations; - }); - } - - ngOnInit(): void { - this.newDataPoint([50, 80], 'August'); - this.transferUsagesChartData = [18, 12, 10, 8, 6, 5, 5, 3, 2, 1]; - this.disbursements = ArraySum.arraySum(this.lineChartData.find(data => data.label === 'Token Disbursements').data); - this.users = ArraySum.arraySum(this.barChartData.find(data => data.label === 'New Users').data); - this.transactions = ArraySum.arraySum(this.lineChartData.find(data => data.label === 'Transaction Volumes').data); - } - - newDataPoint(dataArr: any[], label: string): void { - this.barChartData.forEach((dataset, index) => { - this.barChartData[index] = Object.assign({}, this.barChartData[index], { - data: [...this.barChartData[index].data, dataArr[index]] - }); - }); - - this.barChartLabels = [...this.barChartLabels, label]; - } - - public chartClicked({ event, active}: { event: MouseEvent, active: {}[] }): void { - this.loggingService.sendInfoLevelMessage(`Event: ${event}, ${active}`); - } - - public chartHovered({ event, active }: { event: MouseEvent, active: {}[] }): void { - this.loggingService.sendInfoLevelMessage(`Event: ${event}, ${active}`); - } - - public trackByName(index, item): string { - return item.name; - } + constructor() { } } diff --git a/src/app/pages/settings/invite/invite.component.html b/src/app/pages/settings/invite/invite.component.html deleted file mode 100644 index 3f3d458..0000000 --- a/src/app/pages/settings/invite/invite.component.html +++ /dev/null @@ -1,51 +0,0 @@ - -
- - - - - - -
- - -
- -
-
- - INVITE NEW USERS - -
-
- - Email Address: - - Email is required. -
- - Superadmin
- Admin
- Subadmin
- View
-
- Role is required. - -
-
-
-
-
- -
- - - -
diff --git a/src/app/pages/settings/invite/invite.component.scss b/src/app/pages/settings/invite/invite.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/pages/settings/invite/invite.component.spec.ts b/src/app/pages/settings/invite/invite.component.spec.ts deleted file mode 100644 index 45cd81e..0000000 --- a/src/app/pages/settings/invite/invite.component.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { InviteComponent } from '@pages/settings/invite/invite.component'; -import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing'; -import {SettingsModule} from '@pages/settings/settings.module'; -import {AppModule} from '@app/app.module'; - -describe('InviteComponent', () => { - let component: InviteComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - InviteComponent, - FooterStubComponent, - SidebarStubComponent, - TopbarStubComponent - ], - imports: [ - AppModule, - SettingsModule, - ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(InviteComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/pages/settings/invite/invite.component.ts b/src/app/pages/settings/invite/invite.component.ts deleted file mode 100644 index cb8dc2f..0000000 --- a/src/app/pages/settings/invite/invite.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {CustomErrorStateMatcher} from '@app/_helpers'; - -@Component({ - selector: 'app-invite', - templateUrl: './invite.component.html', - styleUrls: ['./invite.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class InviteComponent implements OnInit { - inviteForm: FormGroup; - submitted: boolean = false; - matcher = new CustomErrorStateMatcher(); - - constructor( - private formBuilder: FormBuilder - ) { } - - ngOnInit(): void { - this.inviteForm = this.formBuilder.group({ - email: ['', Validators.required], - role: ['', Validators.required] - }); - } - - get inviteFormStub(): any { return this.inviteForm.controls; } - - invite(): void { - this.submitted = true; - if (this.inviteForm.invalid || !confirm('Invite user?')) { return; } - this.submitted = false; - } -} diff --git a/src/app/pages/settings/organization/organization.component.ts b/src/app/pages/settings/organization/organization.component.ts index e8b6126..2e9b966 100644 --- a/src/app/pages/settings/organization/organization.component.ts +++ b/src/app/pages/settings/organization/organization.component.ts @@ -11,7 +11,7 @@ import {CustomErrorStateMatcher} from '@app/_helpers'; export class OrganizationComponent implements OnInit { organizationForm: FormGroup; submitted: boolean = false; - matcher = new CustomErrorStateMatcher(); + matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher(); constructor( private formBuilder: FormBuilder diff --git a/src/app/pages/settings/settings-routing.module.ts b/src/app/pages/settings/settings-routing.module.ts index 9e9597a..22d32ef 100644 --- a/src/app/pages/settings/settings-routing.module.ts +++ b/src/app/pages/settings/settings-routing.module.ts @@ -2,12 +2,10 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { SettingsComponent } from '@pages/settings/settings.component'; -import {InviteComponent} from '@pages/settings/invite/invite.component'; import {OrganizationComponent} from '@pages/settings/organization/organization.component'; const routes: Routes = [ { path: '', component: SettingsComponent }, - { path: 'invite', component: InviteComponent }, { path: 'organization', component: OrganizationComponent }, { path: '**', redirectTo: '', pathMatch: 'full' } ]; diff --git a/src/app/pages/settings/settings.component.ts b/src/app/pages/settings/settings.component.ts index 5d733bc..7195ee3 100644 --- a/src/app/pages/settings/settings.component.ts +++ b/src/app/pages/settings/settings.component.ts @@ -15,8 +15,8 @@ import {exportCsv} from '@app/_helpers'; export class SettingsComponent implements OnInit { date: string; dataSource: MatTableDataSource; - displayedColumns = ['name', 'email', 'userId']; - trustedUsers: Staff[]; + displayedColumns: Array = ['name', 'email', 'userId']; + trustedUsers: Array; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; diff --git a/src/app/pages/settings/settings.module.ts b/src/app/pages/settings/settings.module.ts index 9e4da32..fe35fd6 100644 --- a/src/app/pages/settings/settings.module.ts +++ b/src/app/pages/settings/settings.module.ts @@ -4,7 +4,6 @@ import { CommonModule } from '@angular/common'; import { SettingsRoutingModule } from '@pages/settings/settings-routing.module'; import { SettingsComponent } from '@pages/settings/settings.component'; import {SharedModule} from '@app/shared/shared.module'; -import { InviteComponent } from '@pages/settings/invite/invite.component'; import { OrganizationComponent } from '@pages/settings/organization/organization.component'; import {MatTableModule} from '@angular/material/table'; import {MatSortModule} from '@angular/material/sort'; @@ -22,7 +21,7 @@ import {ReactiveFormsModule} from '@angular/forms'; @NgModule({ - declarations: [SettingsComponent, InviteComponent, OrganizationComponent], + declarations: [SettingsComponent, OrganizationComponent], imports: [ CommonModule, SettingsRoutingModule, diff --git a/src/app/pages/tokens/token-details/token-details.component.ts b/src/app/pages/tokens/token-details/token-details.component.ts index 46277fe..042800f 100644 --- a/src/app/pages/tokens/token-details/token-details.component.ts +++ b/src/app/pages/tokens/token-details/token-details.component.ts @@ -2,6 +2,7 @@ import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; import {ActivatedRoute, Params} from '@angular/router'; import {TokenService} from '@app/_services'; import {first} from 'rxjs/operators'; +import {Token} from '../../../_models'; @Component({ selector: 'app-token-details', @@ -10,7 +11,7 @@ import {first} from 'rxjs/operators'; changeDetection: ChangeDetectionStrategy.OnPush }) export class TokenDetailsComponent implements OnInit { - token: any; + token: Token; constructor( private route: ActivatedRoute, diff --git a/src/app/pages/tokens/tokens.component.ts b/src/app/pages/tokens/tokens.component.ts index 8676f4f..44bf7a6 100644 --- a/src/app/pages/tokens/tokens.component.ts +++ b/src/app/pages/tokens/tokens.component.ts @@ -5,6 +5,8 @@ import {LoggingService, TokenService} from '@app/_services'; import {MatTableDataSource} from '@angular/material/table'; import {Router} from '@angular/router'; import {exportCsv} from '@app/_helpers'; +import {TokenRegistry} from '../../_eth'; +import {Token} from '../../_models'; @Component({ selector: 'app-tokens', @@ -14,10 +16,10 @@ import {exportCsv} from '@app/_helpers'; }) export class TokensComponent implements OnInit { dataSource: MatTableDataSource; - columnsToDisplay = ['name', 'symbol', 'address', 'supply']; + columnsToDisplay: Array = ['name', 'symbol', 'address', 'supply']; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; - tokens: any; + tokens: Array>; constructor( private tokenService: TokenService, @@ -26,13 +28,14 @@ export class TokensComponent implements OnInit { ) { } async ngOnInit(): Promise { + this.tokenService.LoadEvent.subscribe(async () => { + this.tokens = await this.tokenService.getTokens(); + }); this.tokens = await this.tokenService.getTokens(); this.loggingService.sendInfoLevelMessage(this.tokens); - this.tokenService.tokensSubject.subscribe(tokens => { - this.dataSource = new MatTableDataSource(tokens); - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; - }); + this.dataSource = new MatTableDataSource(this.tokens); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; } doFilter(value: string): void { diff --git a/src/app/pages/transactions/transaction-details/transaction-details.component.html b/src/app/pages/transactions/transaction-details/transaction-details.component.html index 038b837..4789857 100644 --- a/src/app/pages/transactions/transaction-details/transaction-details.component.html +++ b/src/app/pages/transactions/transaction-details/transaction-details.component.html @@ -13,12 +13,20 @@
  • Sender: {{transaction.sender?.vcard.fn[0].value}}

    - Sender Address: {{transaction.from}}

    + + Sender Address: + {{transaction.from}} + Copy +

  • Recipient: {{transaction.recipient?.vcard.fn[0].value}}

    - Recipient Address: {{transaction.to}}

    + + Recipient Address: + {{transaction.to}} + Copy +

  • @@ -28,7 +36,11 @@

    Token:

    • - Address: {{transaction.token._address}} + + Address: + {{transaction.token._address}} + Copy +
    • Name: Sarafu Token @@ -73,17 +85,25 @@ Trader: {{transaction.sender?.vcard.fn[0].value}}
    • - Trader Address: {{transaction.trader}} + + Trader Address: + {{transaction.trader}} + Copy +
    - +

    Source Token:

    • - Address: {{transaction.sourceToken.address}} + + Address: + {{transaction.sourceToken.address}} + Copy +
    • Name: {{transaction.sourceToken.name}} @@ -100,7 +120,11 @@

      Destination Token:

      • - Address: {{transaction.destinationToken.address}} + + Address: + {{transaction.destinationToken.address}} + Copy +
      • Name: {{transaction.destinationToken.name}} diff --git a/src/app/pages/transactions/transaction-details/transaction-details.component.ts b/src/app/pages/transactions/transaction-details/transaction-details.component.ts index 40389d3..dd4f23a 100644 --- a/src/app/pages/transactions/transaction-details/transaction-details.component.ts +++ b/src/app/pages/transactions/transaction-details/transaction-details.component.ts @@ -1,6 +1,9 @@ import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core'; import {Router} from '@angular/router'; import {TransactionService} from '@app/_services'; +import {copyToClipboard} from '@app/_helpers'; +import {MatSnackBar} from '@angular/material/snack-bar'; +import {strip0x} from '@src/assets/js/ethtx/dist/hex'; @Component({ selector: 'app-transaction-details', @@ -12,23 +15,33 @@ export class TransactionDetailsComponent implements OnInit { @Input() transaction; senderBloxbergLink: string; recipientBloxbergLink: string; + traderBloxbergLink: string; constructor( private router: Router, - private transactionService: TransactionService + private transactionService: TransactionService, + private snackBar: MatSnackBar, ) { } ngOnInit(): void { - this.senderBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.from + '/transactions'; - this.recipientBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.to + '/transactions'; + if (this.transaction?.type === 'conversion') { + this.traderBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.trader + '/transactions'; + } else { + this.senderBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.from + '/transactions'; + this.recipientBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.to + '/transactions'; + } } async viewSender(): Promise { - await this.router.navigateByUrl(`/accounts/${this.transaction.from}`); + await this.router.navigateByUrl(`/accounts/${strip0x(this.transaction.from)}`); } async viewRecipient(): Promise { - await this.router.navigateByUrl(`/accounts/${this.transaction.to}`); + await this.router.navigateByUrl(`/accounts/${strip0x(this.transaction.to)}`); + } + + async viewTrader(): Promise { + await this.router.navigateByUrl(`/accounts/${strip0x(this.transaction.trader)}`); } async reverseTransaction(): Promise { @@ -39,4 +52,10 @@ export class TransactionDetailsComponent implements OnInit { this.transaction.value ); } + + copyAddress(address: string): void { + if (copyToClipboard(address)) { + this.snackBar.open(address + ' copied successfully!', 'Close', { duration: 3000 }); + } + } } diff --git a/src/app/pages/transactions/transactions.component.html b/src/app/pages/transactions/transactions.component.html index fe178b2..7491a36 100644 --- a/src/app/pages/transactions/transactions.component.html +++ b/src/app/pages/transactions/transactions.component.html @@ -29,11 +29,9 @@ TRANSFER TYPE ALL TRANSFERS - PAYMENTS - CONVERSION - DISBURSEMENTS - REWARDS - RECLAMATION + + {{transactionType | uppercase}} + diff --git a/src/app/pages/transactions/transactions.component.ts b/src/app/pages/transactions/transactions.component.ts index d9a7ce7..0444fce 100644 --- a/src/app/pages/transactions/transactions.component.ts +++ b/src/app/pages/transactions/transactions.component.ts @@ -1,9 +1,11 @@ import {AfterViewInit, ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core'; -import {BlockSyncService, TransactionService} from '@app/_services'; +import {BlockSyncService, TransactionService, UserService} from '@app/_services'; import {MatTableDataSource} from '@angular/material/table'; import {MatPaginator} from '@angular/material/paginator'; import {MatSort} from '@angular/material/sort'; import {exportCsv} from '@app/_helpers'; +import {first} from 'rxjs/operators'; +import {Transaction} from '@app/_models'; @Component({ selector: 'app-transactions', @@ -13,12 +15,13 @@ import {exportCsv} from '@app/_helpers'; }) export class TransactionsComponent implements OnInit, AfterViewInit { transactionDataSource: MatTableDataSource; - transactionDisplayedColumns = ['sender', 'recipient', 'value', 'created', 'type']; - defaultPageSize = 10; - pageSizeOptions = [10, 20, 50, 100]; - transactions: any[]; - transaction: any; - transactionsType = 'all'; + transactionDisplayedColumns: Array = ['sender', 'recipient', 'value', 'created', 'type']; + defaultPageSize: number = 10; + pageSizeOptions: Array = [10, 20, 50, 100]; + transactions: Array; + transaction: Transaction; + transactionsType: string = 'all'; + transactionsTypes: Array; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -26,6 +29,7 @@ export class TransactionsComponent implements OnInit, AfterViewInit { constructor( private blockSyncService: BlockSyncService, private transactionService: TransactionService, + private userService: UserService ) { this.blockSyncService.blockSync(); } @@ -37,6 +41,7 @@ export class TransactionsComponent implements OnInit, AfterViewInit { this.transactionDataSource.sort = this.sort; this.transactions = transactions; }); + this.userService.getTransactionTypes().pipe(first()).subscribe(res => this.transactionsTypes = res); } viewTransaction(transaction): void { diff --git a/src/app/pages/transactions/transactions.module.ts b/src/app/pages/transactions/transactions.module.ts index 55dd2f9..fc9273d 100644 --- a/src/app/pages/transactions/transactions.module.ts +++ b/src/app/pages/transactions/transactions.module.ts @@ -17,6 +17,7 @@ import {MatIconModule} from '@angular/material/icon'; import {MatSelectModule} from '@angular/material/select'; import {MatCardModule} from '@angular/material/card'; import {MatRippleModule} from '@angular/material/core'; +import {MatSnackBarModule} from '@angular/material/snack-bar'; @NgModule({ @@ -39,7 +40,8 @@ import {MatRippleModule} from '@angular/material/core'; MatIconModule, MatSelectModule, MatCardModule, - MatRippleModule + MatRippleModule, + MatSnackBarModule, ] }) export class TransactionsModule { } diff --git a/src/app/shared/_directives/menu-selection.directive.ts b/src/app/shared/_directives/menu-selection.directive.ts index 1c7534a..fcc3de9 100644 --- a/src/app/shared/_directives/menu-selection.directive.ts +++ b/src/app/shared/_directives/menu-selection.directive.ts @@ -18,15 +18,15 @@ export class MenuSelectionDirective { } onMenuSelect(): void { - const sidebar = document.getElementById('sidebar'); + const sidebar: HTMLElement = document.getElementById('sidebar'); if (!sidebar?.classList.contains('active')) { sidebar?.classList.add('active'); } - const content = document.getElementById('content'); + const content: HTMLElement = document.getElementById('content'); if (!content?.classList.contains('active')) { content?.classList.add('active'); } - const sidebarCollapse = document.getElementById('sidebarCollapse'); + const sidebarCollapse: HTMLElement = document.getElementById('sidebarCollapse'); if (sidebarCollapse?.classList.contains('active')) { sidebarCollapse?.classList.remove('active'); } diff --git a/src/app/shared/_directives/menu-toggle.directive.ts b/src/app/shared/_directives/menu-toggle.directive.ts index 5e7c873..9d43941 100644 --- a/src/app/shared/_directives/menu-toggle.directive.ts +++ b/src/app/shared/_directives/menu-toggle.directive.ts @@ -16,11 +16,11 @@ export class MenuToggleDirective { // Menu Trigger onMenuToggle(): void { - const sidebar = document.getElementById('sidebar'); + const sidebar: HTMLElement = document.getElementById('sidebar'); sidebar?.classList.toggle('active'); - const content = document.getElementById('content'); + const content: HTMLElement = document.getElementById('content'); content?.classList.toggle('active'); - const sidebarCollapse = document.getElementById('sidebarCollapse'); + const sidebarCollapse: HTMLElement = document.getElementById('sidebarCollapse'); sidebarCollapse?.classList.toggle('active'); } } diff --git a/src/app/shared/_pipes/safe.pipe.spec.ts b/src/app/shared/_pipes/safe.pipe.spec.ts new file mode 100644 index 0000000..49ee0ad --- /dev/null +++ b/src/app/shared/_pipes/safe.pipe.spec.ts @@ -0,0 +1,8 @@ +import { SafePipe } from './safe.pipe'; + +describe('SafePipe', () => { + it('create an instance', () => { + const pipe = new SafePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/shared/_pipes/safe.pipe.ts b/src/app/shared/_pipes/safe.pipe.ts new file mode 100644 index 0000000..73ffce6 --- /dev/null +++ b/src/app/shared/_pipes/safe.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import {DomSanitizer} from '@angular/platform-browser'; + +@Pipe({ + name: 'safe' +}) +export class SafePipe implements PipeTransform { + + constructor(private sanitizer: DomSanitizer) {} + + transform(url: string, ...args: unknown[]): unknown { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index b559ab4..81a60b3 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -8,8 +8,9 @@ import { MenuToggleDirective } from '@app/shared/_directives/menu-toggle.directi import {RouterModule} from '@angular/router'; import {MatIconModule} from '@angular/material/icon'; import {TokenRatioPipe} from '@app/shared/_pipes/token-ratio.pipe'; -import { ErrorDialogComponent } from './error-dialog/error-dialog.component'; +import { ErrorDialogComponent } from '@app/shared/error-dialog/error-dialog.component'; import {MatDialogModule} from '@angular/material/dialog'; +import { SafePipe } from '@app/shared/_pipes/safe.pipe'; @@ -21,14 +22,16 @@ import {MatDialogModule} from '@angular/material/dialog'; MenuSelectionDirective, MenuToggleDirective, TokenRatioPipe, - ErrorDialogComponent + ErrorDialogComponent, + SafePipe ], exports: [ TopbarComponent, FooterComponent, SidebarComponent, MenuSelectionDirective, - TokenRatioPipe + TokenRatioPipe, + SafePipe ], imports: [ CommonModule, diff --git a/src/assets/images/checklist.svg b/src/assets/images/checklist.svg new file mode 100644 index 0000000..c4d88a6 --- /dev/null +++ b/src/assets/images/checklist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json b/src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json new file mode 100644 index 0000000..c97eaa6 --- /dev/null +++ b/src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes32","name":"_key","type":"bytes32"}],"name":"addressOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_idx","type":"uint256"}],"name":"entry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"entryCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"name":"register","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"registry","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_interfaceCode","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}] diff --git a/src/styles.scss b/src/styles.scss index c0375e2..9696d1b 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,9 +1,20 @@ /* You can add global styles to this file, and also import other style files */ +.declaration-order { + /* Positioning */ + /* Box-model */ + /* Typography */ + /* Visual */ + /* Misc */ +} @import "~bootstrap/dist/css/bootstrap.css"; @import "https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"; +html, +body { height: 100%; } + body { - font-family: 'Roboto', sans-serif; + margin: 0; + font-family: Roboto, 'Roboto', sans-serif; background: #fafafa; } @@ -12,35 +23,53 @@ p { font-size: 1.1em; font-weight: 300; line-height: 1.7em; - color: #000000; + color: #000; } -a, a:hover, a:focus { - color: inherit; +a, +a:hover, +a:focus { text-decoration: none; - transition: all 0.3s; + color: inherit; + transition: all .3s; } .wrapper { + perspective: 1500px; + align-items: stretch; display: flex; width: 100%; - align-items: stretch; height: 100vh; - perspective: 1500px; +} + +ul ul a { + padding-left: 30px; + font-size: .9em; + background: #6d7fcc; +} + +.full-width, +table { width: 100%; } + +li.breadcrumb-item.active, +footer.footer { color: black; } + +.clipboard { + position: absolute; + left: -9999px; } #sidebar { - min-width: 250px; - max-width: 250px; - height: 100vh; - position: -webkit-sticky; position: sticky; + position: -webkit-sticky; top: 0; left: 0; z-index: 9999; + min-width: 250px; + max-width: 250px; + height: 100vh; background: #313a46; color: #fff; - transition: all 0.3s cubic-bezier(0.945, 0.020, 0.270, 0.665); transform-origin: center left; } @@ -48,7 +77,6 @@ a, a:hover, a:focus { min-width: 100px; max-width: 100px; text-align: center; - transition: all 0.3s cubic-bezier(0.945, 0.020, 0.270, 0.665); } #sidebar .sidebar-header { @@ -56,55 +84,44 @@ a, a:hover, a:focus { background: #313a46; } -#sidebar .sidebar-header strong { - display: none; -} +#sidebar .sidebar-header strong, +#sidebar.active .sidebar-header h3 { display: none; } -#sidebar.active .sidebar-header h3 { - display: none; -} - -#sidebar.active .sidebar-header strong { - display: block; -} +#sidebar.active .sidebar-header strong { display: block; } #sidebar ul li a { - color: #ffffff; + display: block; + padding: 10px; text-align: left; + font-size: 1.1em; + color: #fff; } -#sidebar ul li a.active { - background: #000000; -} +#sidebar ul li a.active, +#sidebar.active ul li a.active { background: #000; } #sidebar.active ul li a { padding: 20px 10px; + font-size: .85em; text-align: center; - font-size: 0.85em; -} - -#sidebar.active ul li a.active { - background: #000000; } #sidebar.active ul li a i { - margin-right: 0; display: block; - font-size: 1.8em; + margin-right: 0; margin-bottom: 5px; + font-size: 1.8em; } -#sidebar.active ul li a { - padding: 10px !important; -} +#sidebar.active ul li a { padding: 10px; } #sidebar.active .dropdown-toggle::after { top: auto; bottom: 10px; right: 50%; - -webkit-transform: translateX(50%); - -ms-transform: translateX(50%); transform: translateX(50%); + -ms-transform: translateX(50%); + -webkit-transform: translateX(50%); } #sidebar ul.components { @@ -113,14 +130,8 @@ a, a:hover, a:focus { } #sidebar ul p { + padding: 10px; color: #fff; - padding: 10px; -} - -#sidebar ul li a { - padding: 10px; - font-size: 1.1em; - display: block; } #sidebar ul li a:hover { @@ -128,40 +139,32 @@ a, a:hover, a:focus { background: #fff; } -#sidebar ul li.active > a, a[aria-expanded="true"] { +#sidebar ul li.active > a, +a[aria-expanded="true"] { color: #fff; background: #313a46; } -ul ul a { - font-size: 0.9em !important; - padding-left: 30px !important; - background: #6d7fcc; -} - -a[data-toggle="collapse"] { - position: relative; -} +a[data-toggle="collapse"] { position: relative; } .dropdown-toggle::after { - display: block; position: absolute; top: 50%; right: 20%; + display: block; transform: translateY(-50%); } #content { - width: 100%; position: relative; overflow: auto; + width: 100%; min-height: 100vh; - transition: all 0.3s cubic-bezier(0.945, 0.020, 0.270, 0.665); -} - -#content.active { - transition: all 0.3s cubic-bezier(0.945, 0.020, 0.270, 0.665); } +#sidebar, +#sidebar.active, +#content, +#content.active { transition: all .3s cubic-bezier(.945, .020, .270, .665); } #sidebarCollapse { width: 40px; @@ -170,84 +173,57 @@ a[data-toggle="collapse"] { } #sidebarCollapse span { + display: block; width: 80%; height: 2px; margin: 0 auto; - display: block; background: #555; - transition: all 0.8s cubic-bezier(0.810, -0.330, 0.345, 1.375); + transition: all .8s cubic-bezier(.810, -.330, .345, 1.375); } -#sidebarCollapse span:first-of-type { - /* rotate first one */ - transform: rotate(45deg) translate(2px, 2px); -} +#sidebarCollapse span:first-of-type { transform: rotate(45deg) translate(2px, 2px); } -#sidebarCollapse span:nth-of-type(2) { - /* second one is not visible */ - opacity: 0; -} +#sidebarCollapse span:nth-of-type(2) { opacity: 0; } -#sidebarCollapse span:last-of-type { - /* rotate third one */ - transform: rotate(-45deg) translate(1px, -1px); -} +#sidebarCollapse span:last-of-type { transform: rotate(-45deg) translate(1px, -1px); } #sidebarCollapse.active span { - /* no rotation */ - transform: none; - /* all bars are visible */ - opacity: 1; margin: 5px auto; + transform: none; + opacity: 1; } .footer { - color: #98a6ad; padding: 10px; text-align: center; + color: #98a6ad; } -.mat-column-select { - overflow: initial; -} +.mat-column-select { overflow: initial; } -button { - height: 2.5rem; -} +button { height: 2.5rem; } -.badge-pill { - width: 5rem; -} +.badge-pill { width: 5rem; } .mat-column { - word-wrap: break-word !important; - white-space: unset !important; + word-wrap: break-word; + white-space: unset; overflow-wrap: break-word; word-break: break-word; + hyphens: auto; -ms-hyphens: auto; -moz-hyphens: auto; -webkit-hyphens: auto; - hyphens: auto; } .mat-column-address { - flex: 0 0 30% !important; - width: 30% !important; + flex: 0 0 30%; + width: 30%; } .mat-column-supply { - flex: 0 0 25% !important; - width: 25% !important; -} - -.mat-column-select { - flex: 0 0 10% !important; - width: 10% !important; -} - -.mat-column-view { - flex: 0 0 5% !important; - width: 5% !important; + flex: 0 0 25%; + width: 25%; } .center-body { @@ -256,103 +232,70 @@ button { } @media (max-width: 768px) { - #sidebar { - margin-left: 0; - } - #sidebar.active { min-width: 100px; max-width: 100px; + margin-left: -100px; text-align: center; - margin-left: -100px !important; } - #sidebar .sidebar-header strong { - display: none; - } + #sidebar .sidebar-header strong, + #sidebar.active .sidebar-header h3 { display: none; } - #sidebar.active .sidebar-header h3 { - display: none; - } - - #sidebar.active .sidebar-header strong { - display: block; - } + #sidebar.active .sidebar-header strong, + #sidebar.active ul li a i { display: block; } #sidebar.active ul li a { padding: 20px 10px; - font-size: 0.85em; + font-size: .85em; } #sidebar.active ul li a i { margin-right: 0; - display: block; - font-size: 1.8em; margin-bottom: 5px; + font-size: 1.8em; } - #sidebar.active ul ul a { - padding: 10px !important; - } + #sidebar.active ul ul a { padding: 10px; } .dropdown-toggle::after { top: auto; bottom: 10px; right: 50%; - -webkit-transform: translateX(50%); - -ms-transform: translateX(50%); transform: translateX(50%); + -ms-transform: translateX(50%); + -webkit-transform: translateX(50%); } - #content, #content.active { - height: 100vh; + #content, + #content.active { position: fixed; - margin-left: 0; + height: 100vh; } - #content .menutoggle { - margin-left: 250px; - transition: all 0.3s cubic-bezier(0.945, 0.020, 0.270, 0.665); - } + #content .menutoggle { margin-left: 250px; } - #content.active .menutoggle { - margin-left: 0; - transition: all 0.3s cubic-bezier(0.945, 0.020, 0.270, 0.665); - } + #sidebar, + #content, + #content.active, + #content.active .menutoggle { margin-left: 0; } + + #content .menutoggle, + #content.active .menutoggle { transition: all .3s cubic-bezier(.945, .020, .270, .665); } #sidebarCollapse span:first-of-type, #sidebarCollapse span:nth-of-type(2), #sidebarCollapse span:last-of-type { + margin: 5px auto; transform: none; opacity: 1; - margin: 5px auto; } - #sidebarCollapse.active span { - margin: 0 auto; - } + #sidebarCollapse.active span { margin: 0 auto; } - #sidebarCollapse.active span:first-of-type { - transform: rotate(45deg) translate(2px, 2px); - } + #sidebarCollapse.active span:first-of-type { transform: rotate(45deg) translate(2px, 2px); } - #sidebarCollapse.active span:nth-of-type(2) { - opacity: 0; - } - - #sidebarCollapse.active span:last-of-type { - transform: rotate(-45deg) translate(1px, -1px); - } + #sidebarCollapse.active span:nth-of-type(2) { opacity: 0; } -} - -html, body { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } - -.full-width, table { - width: 100%; -} - -li.breadcrumb-item.active, footer.footer { - color: black; + #sidebarCollapse.active span:last-of-type { transform: rotate(-45deg) translate(1px, -1px); } }