diff --git a/.eslintrc.json b/.eslintrc.json index 34bac92..f5e55b0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,9 +1,39 @@ { + "env": { + "browser": true, + "es6": true + }, "extends": [ "react-app", - "react-app/jest" + "react-app/jest", + "airbnb", + "airbnb/hooks" ], "rules": { - "no-trailing-spaces": "warn" + // disallow use of unary operators, ++ and -- + // http://eslint.org/docs/rules/no-plusplus + // retropilot: we allow them in the for loop + "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], + + // disallow use of the continue statement + // https://eslint.org/docs/rules/no-continue + // retropilot: we allow use of the continue statement + "no-continue": "off", + + // disallow use of variables before they are defined + // http://eslint.org/docs/rules/no-use-before-define + // retropilot: permit referencing functions before they're defined + "no-use-before-define": ["error", { "functions": false }], + + // specify the maximum length of a line in your program + // https://eslint.org/docs/rules/max-len + // retropilot: ignore comments + "max-len": ["error", 100, 2, { + "ignoreUrls": true, + "ignoreComments": true, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + }] } } diff --git a/package-lock.json b/package-lock.json index f2fc394..c93759b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "app", + "name": "retropilot-client", "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "app", + "name": "retropilot-client", "version": "0.1.0", "dependencies": { "@emotion/react": "^11.7.1", @@ -30,6 +30,9 @@ "react-scripts": "4.0.3", "styled-components": "^5.3.3", "web-vitals": "^1.1.2" + }, + "devDependencies": { + "eslint-config-airbnb": "^19.0.4" } }, "node_modules/@babel/code-frame": { @@ -5256,9 +5259,9 @@ "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" }, "node_modules/axe-core": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz", - "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.5.tgz", + "integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==", "engines": { "node": ">=4" } @@ -8374,6 +8377,55 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-config-react-app": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", @@ -8436,13 +8488,12 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", - "integrity": "sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz", + "integrity": "sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg==", "dependencies": { "debug": "^3.2.7", - "find-up": "^2.1.0", - "pkg-dir": "^2.0.0" + "find-up": "^2.1.0" }, "engines": { "node": ">=4" @@ -8517,17 +8568,6 @@ "node": ">=4" } }, - "node_modules/eslint-module-utils/node_modules/pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint-plugin-flowtype": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.10.0.tgz", @@ -8544,23 +8584,23 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz", - "integrity": "sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==", + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "dependencies": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.0", + "eslint-module-utils": "^2.7.2", "has": "^1.0.3", - "is-core-module": "^2.7.0", + "is-core-module": "^2.8.0", "is-glob": "^4.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.5", "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" + "tsconfig-paths": "^3.12.0" }, "engines": { "node": ">=4" @@ -8626,27 +8666,28 @@ } }, "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz", - "integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz", + "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==", "dependencies": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.16.3", "aria-query": "^4.2.2", - "array-includes": "^3.1.1", + "array-includes": "^3.1.4", "ast-types-flow": "^0.0.7", - "axe-core": "^4.0.2", + "axe-core": "^4.3.5", "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.6", - "emoji-regex": "^9.0.0", + "damerau-levenshtein": "^1.0.7", + "emoji-regex": "^9.2.2", "has": "^1.0.3", - "jsx-ast-utils": "^3.1.0", - "language-tags": "^1.0.5" + "jsx-ast-utils": "^3.2.1", + "language-tags": "^1.0.5", + "minimatch": "^3.0.4" }, "engines": { "node": ">=4.0" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { @@ -8655,41 +8696,41 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/eslint-plugin-react": { - "version": "7.26.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.26.1.tgz", - "integrity": "sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz", + "integrity": "sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw==", "dependencies": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", + "array-includes": "^3.1.4", + "array.prototype.flatmap": "^1.2.5", "doctrine": "^2.1.0", - "estraverse": "^5.2.0", + "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.hasown": "^1.0.0", - "object.values": "^1.1.4", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.0", + "object.values": "^1.1.5", "prop-types": "^15.7.2", "resolve": "^2.0.0-next.3", "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.5" + "string.prototype.matchall": "^4.0.6" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", - "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", + "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==", "engines": { "node": ">=10" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, "node_modules/eslint-plugin-react/node_modules/doctrine": { @@ -8704,9 +8745,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "engines": { "node": ">=4.0" } @@ -19939,9 +19980,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.1", @@ -25990,9 +26031,9 @@ } }, "axe-core": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz", - "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==" + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.5.tgz", + "integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==" }, "axios": { "version": "0.24.0", @@ -28635,6 +28676,37 @@ } } }, + "eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "eslint-config-react-app": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", @@ -28672,13 +28744,12 @@ } }, "eslint-module-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", - "integrity": "sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz", + "integrity": "sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg==", "requires": { "debug": "^3.2.7", - "find-up": "^2.1.0", - "pkg-dir": "^2.0.0" + "find-up": "^2.1.0" }, "dependencies": { "debug": { @@ -28731,14 +28802,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "requires": { - "find-up": "^2.1.0" - } } } }, @@ -28752,23 +28815,23 @@ } }, "eslint-plugin-import": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz", - "integrity": "sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==", + "version": "2.25.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", + "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "requires": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.0", + "eslint-module-utils": "^2.7.2", "has": "^1.0.3", - "is-core-module": "^2.7.0", + "is-core-module": "^2.8.0", "is-glob": "^4.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.5", "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" + "tsconfig-paths": "^3.12.0" }, "dependencies": { "debug": { @@ -28812,21 +28875,22 @@ } }, "eslint-plugin-jsx-a11y": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz", - "integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz", + "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==", "requires": { - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.16.3", "aria-query": "^4.2.2", - "array-includes": "^3.1.1", + "array-includes": "^3.1.4", "ast-types-flow": "^0.0.7", - "axe-core": "^4.0.2", + "axe-core": "^4.3.5", "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.6", - "emoji-regex": "^9.0.0", + "damerau-levenshtein": "^1.0.7", + "emoji-regex": "^9.2.2", "has": "^1.0.3", - "jsx-ast-utils": "^3.1.0", - "language-tags": "^1.0.5" + "jsx-ast-utils": "^3.2.1", + "language-tags": "^1.0.5", + "minimatch": "^3.0.4" }, "dependencies": { "emoji-regex": { @@ -28837,24 +28901,24 @@ } }, "eslint-plugin-react": { - "version": "7.26.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.26.1.tgz", - "integrity": "sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz", + "integrity": "sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw==", "requires": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", + "array-includes": "^3.1.4", + "array.prototype.flatmap": "^1.2.5", "doctrine": "^2.1.0", - "estraverse": "^5.2.0", + "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.hasown": "^1.0.0", - "object.values": "^1.1.4", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.0", + "object.values": "^1.1.5", "prop-types": "^15.7.2", "resolve": "^2.0.0-next.3", "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.5" + "string.prototype.matchall": "^4.0.6" }, "dependencies": { "doctrine": { @@ -28866,9 +28930,9 @@ } }, "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" }, "resolve": { "version": "2.0.0-next.3", @@ -28887,9 +28951,9 @@ } }, "eslint-plugin-react-hooks": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", - "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", + "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==", "requires": {} }, "eslint-plugin-testing-library": { @@ -37426,9 +37490,9 @@ "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" }, "tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.1", diff --git a/package.json b/package.json index 5268ce1..557b6f9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "lint": "eslint src --ext .js,.jsx", + "lint:fix": "npm run lint -- --fix" }, "dependencies": { "@emotion/react": "^11.7.1", @@ -32,6 +34,9 @@ "styled-components": "^5.3.3", "web-vitals": "^1.1.2" }, + "devDependencies": { + "eslint-config-airbnb": "^19.0.4" + }, "browserslist": { "production": [ ">0.2%", diff --git a/src/App.js b/src/App.js index 18078ae..00ab36e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,42 +1,29 @@ import CssBaseline from '@mui/material/CssBaseline'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import React, { useState } from 'react'; -import Login from "./components/views/login"; -import UserAdmin from "./components/views/useradmin"; +import Login from './components/views/login'; +import UserAdmin from './components/views/useradmin'; import GlobalSnack from './components/widgets/globalSnack'; -import DeviceStore from "./context/devices"; -import ToastStore from "./context/toast"; -import { UserProvider } from "./context/users"; -import * as authenticationController from "./controllers/authentication"; - - +import DeviceStore from './context/devices'; +import ToastStore from './context/toast'; +import { UserProvider } from './context/users'; +import * as authenticationController from './controllers/authentication'; // Connection opened - - - function App() { - - - const [session, setSession] = useState(false) + const [session, setSession] = useState(false); authenticationController.getSession().then((res) => { - setSession(res.data.authenticated) - - }) - - - - + setSession(res.data.authenticated); + }); const theme = React.useMemo( - () => - createTheme({ - palette: { - mode: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', - }, - }), + () => createTheme({ + palette: { + mode: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', + }, + }), [], ); diff --git a/src/App.test.js b/src/App.test.js index 02281a5..b1c30cc 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -2,5 +2,5 @@ import { render, screen } from '@testing-library/react'; import App from './App'; test('renders learn react link', () => { - render(

); + render(

); }); diff --git a/src/components/device/deviceData.jsx b/src/components/device/deviceData.jsx index 80a92e3..9af9194 100644 --- a/src/components/device/deviceData.jsx +++ b/src/components/device/deviceData.jsx @@ -6,123 +6,119 @@ import DrivesTable from './tabPane'; function DeviceLastSeenMap() { return ( -

+
- -
- ) + +
+ ); } - export default function SignIn(props) { - - - - return (
+ height: '100%', + width: '100%', + }} + > - + - + @@ -131,5 +127,3 @@ export default function SignIn(props) { ); } - - diff --git a/src/components/device/overview.jsx b/src/components/device/overview.jsx index 673878c..a257edf 100644 --- a/src/components/device/overview.jsx +++ b/src/components/device/overview.jsx @@ -5,35 +5,26 @@ import Grid from '@mui/material/Grid'; import Typography from '@mui/material/Typography'; import React, { useEffect } from 'react'; - - const stylezz = { margin: '0px 2px 0px 0px', -} +}; function timeSince(date) { - - - - var seconds = Math.floor((new Date() - date) / 1000); + const seconds = Math.floor((new Date() - date) / 1000); if (seconds / 86400 > 1) { - return Math.floor(seconds / 86400) + `d`; - } else if (seconds / 3600 > 1) { - return Math.floor(seconds / 3600) + `h`; - } else if (seconds / 60 > 1) { - return Math.floor(seconds / 60) + `m`; - } else { - return "just now"; + return `${Math.floor(seconds / 86400)}d`; + } if (seconds / 3600 > 1) { + return `${Math.floor(seconds / 3600)}h`; + } if (seconds / 60 > 1) { + return `${Math.floor(seconds / 60)}m`; } - + return 'just now'; } - export default function SignIn(props) { - const [state, setState] = React.useState({ count: 0, last_seen: 0 }); - const device = props.device; + const { device } = props; // Reloads component to update X time ago // TODO prevent X time ago from being refreshed when the device has been @@ -41,21 +32,18 @@ export default function SignIn(props) { useEffect(() => { setInterval(() => { - setState({ ...state, count: state.count + 1 }) - }, 60 * 1000) + setState({ ...state, count: state.count + 1 }); + }, 60 * 1000); }); - const deviceLastSeen = timeSince(new Date(device.last_seen)); - - return (
- device icon + device icon {/* */} - Dongle: {device.dongle_id} + /> */} + + Dongle: + {device.dongle_id} +
- {device.online ? - : - - } + {device.online + ? + : }
@@ -98,5 +87,3 @@ export default function SignIn(props) { ); } - - diff --git a/src/components/device/tabPane/boot.jsx b/src/components/device/tabPane/boot.jsx index 19be5a6..34eda3c 100644 --- a/src/components/device/tabPane/boot.jsx +++ b/src/components/device/tabPane/boot.jsx @@ -13,39 +13,36 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Tooltip from '@mui/material/Tooltip'; import React, { useContext, useEffect } from 'react'; -import { context as DeviceContext } from "./../../../context/devices"; -import { context as SnackbarContext } from "./../../../context/toast"; -import * as deviceController from "./../../../controllers/devices"; -import * as helpers from "./../../../controllers/helpers" - +import { context as DeviceContext } from '../../../context/devices'; +import { context as SnackbarContext } from '../../../context/toast'; +import * as deviceController from '../../../controllers/devices'; +import * as helpers from '../../../controllers/helpers'; function loading() { - return ( - - - - - - - ) - } - + return ( + + + + + + + ); +} export default function EnhancedTable(props) { - const [state, dispatch] = useContext(DeviceContext) + const [state, dispatch] = useContext(DeviceContext); - const [, notifDispatch] = useContext(SnackbarContext) + const [, notifDispatch] = useContext(SnackbarContext); useEffect(() => { deviceController.getBootlogs(props.dongleId).then((res) => { setTimeout(() => { - dispatch({ type: "update_dongle_bootlogs", dongle_id: props.dongleId, bootlogs: res.data }) - }, 1) + dispatch({ type: 'update_dongle_bootlogs', dongle_id: props.dongleId, bootlogs: res.data }); + }, 1); }).catch(() => { - notifDispatch({type: "NEW_TOAST", msg: 'Failed to load bootlogs'}) - }) - - }, [dispatch, notifDispatch, props.dongleId]) + notifDispatch({ type: 'NEW_TOAST', msg: 'Failed to load bootlogs' }); + }); + }, [dispatch, notifDispatch, props.dongleId]); return ( @@ -54,51 +51,48 @@ export default function EnhancedTable(props) { - Date - File - File size - Actions + Date + File + File size + Actions {/* if you don't need to support IE11, you can replace the `stableSort` call with: rows.slice().sort(getComparator(order, orderBy)) */} - {state.dongles[props.dongleId].boot ? state.dongles[props.dongleId].boot.map((row) => { - return ( - - {helpers.formatDate(row.date)} - {row.name} - {Math.round(row.size / 1024) + ' MiB'} - + {state.dongles[props.dongleId].boot ? state.dongles[props.dongleId].boot.map((row) => ( + + {helpers.formatDate(row.date)} + {row.name} + {`${Math.round(row.size / 1024)} MiB`} + - window.open(row.permalink, "_blank")}> - + window.open(row.permalink, '_blank')}> + - + - - + + - - - ) - - }) : [1, 1, 1, 1, 1].map(loading) } + + + )) : [1, 1, 1, 1, 1].map(loading) }
@@ -106,4 +100,3 @@ export default function EnhancedTable(props) {
); } - diff --git a/src/components/device/tabPane/crash.jsx b/src/components/device/tabPane/crash.jsx index 1028c07..8bc9539 100644 --- a/src/components/device/tabPane/crash.jsx +++ b/src/components/device/tabPane/crash.jsx @@ -1,4 +1,4 @@ - // eslint-disable-next-line react-hooks/exhaustive-deps +// eslint-disable-next-line react-hooks/exhaustive-deps import DeleteIcon from '@mui/icons-material/Delete'; import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; @@ -15,13 +15,9 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Tooltip from '@mui/material/Tooltip'; import React, { useContext, useEffect } from 'react'; -import { context as DeviceContext } from "./../../../context/devices"; -import * as deviceController from "./../../../controllers/devices"; -import * as helpers from "./../../../controllers/helpers" - - - - +import { context as DeviceContext } from '../../../context/devices'; +import * as deviceController from '../../../controllers/devices'; +import * as helpers from '../../../controllers/helpers'; function buildContent(row) { return ( @@ -29,15 +25,13 @@ function buildContent(row) { hover > - - {helpers.formatDate(row.date)} - {row.name} - {Math.round(row.size / 1024) + ' MiB'} - + {helpers.formatDate(row.date)} + {row.name} + {`${Math.round(row.size / 1024)} MiB`} - window.open(row.permalink, "_blank")}> + window.open(row.permalink, '_blank')}> @@ -56,31 +50,31 @@ function buildContent(row) { - ) + ); } function loading() { return ( - - - - + + + + - ) + ); } export default function EnhancedTable(props) { // eslint-disable-next-line no-unused-vars - const [state, dispatch] = useContext(DeviceContext) + const [state, dispatch] = useContext(DeviceContext); useEffect(() => { deviceController.getCrashlogs(props.dongleId).then((res) => { - dispatch({ type: "update_dongle_bootlogs", dongle_id: props.dongleId, bootlogs: res.data }) - }) + dispatch({ type: 'update_dongle_bootlogs', dongle_id: props.dongleId, bootlogs: res.data }); + }); }, [dispatch, props.dongleId]); - console.log("drives", state.dongles[props.dongleId]) - console.log("drives", typeof state.dongles[props.dongleId]) + console.log('drives', state.dongles[props.dongleId]); + console.log('drives', typeof state.dongles[props.dongleId]); return ( @@ -89,23 +83,21 @@ export default function EnhancedTable(props) { - Date - File - File size - Actions + Date + File + File size + Actions - {state.dongles[props.dongleId].crash ? - state.dongles[props.dongleId].crash.length > 0 ? state.dongles[props.dongleId].crash.map(buildContent) :

No drives

- : - [1, 1, 1, 1, 1].map(loading) - } + {state.dongles[props.dongleId].crash + ? state.dongles[props.dongleId].crash.length > 0 ? state.dongles[props.dongleId].crash.map(buildContent) :

No drives

+ : [1, 1, 1, 1, 1].map(loading)}
@@ -115,4 +107,3 @@ export default function EnhancedTable(props) {
); } - diff --git a/src/components/device/tabPane/device.jsx b/src/components/device/tabPane/device.jsx index e82bc07..251e7a8 100644 --- a/src/components/device/tabPane/device.jsx +++ b/src/components/device/tabPane/device.jsx @@ -1,47 +1,37 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import Grid from '@mui/material/Grid'; import IconButton from '@mui/material/IconButton'; -import Skeleton from "@mui/material/Skeleton"; +import Skeleton from '@mui/material/Skeleton'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import React, { useContext } from 'react'; -import { context as DeviceContext } from "./../../../context/devices"; -import { context as SnackbarContext } from "./../../../context/toast"; -import * as helpers from "./../../../controllers/helpers" - - - - - - - +import { context as DeviceContext } from '../../../context/devices'; +import { context as SnackbarContext } from '../../../context/toast'; +import * as helpers from '../../../controllers/helpers'; export default function SignIn(props) { - - const [state] = useContext(DeviceContext) - const [, notifDispatch] = useContext(SnackbarContext) + const [state] = useContext(DeviceContext); + const [, notifDispatch] = useContext(SnackbarContext); function pubKeyClipboard(newClip) { - navigator.clipboard.writeText(newClip).then(function () { + navigator.clipboard.writeText(newClip).then(() => { notifDispatch({ - type: "NEW_TOAST", + type: 'NEW_TOAST', open: true, - msg: "Successfully copied to clipboard!" - }) - }, function () { + msg: 'Successfully copied to clipboard!', + }); + }, () => { notifDispatch({ - type: "NEW_TOAST", + type: 'NEW_TOAST', open: true, - msg: "Failed to write to clipboard!" - }) + msg: 'Failed to write to clipboard!', + }); }); } - - if (!state.dongles[props.dongleId]) { return (

no

) } + if (!state.dongles[props.dongleId]) { return (

no

); } const dongle = state.dongles[props.dongleId]; - if (!dongle) { return ( @@ -53,7 +43,7 @@ export default function SignIn(props) { - ) + ); } return ( @@ -63,29 +53,47 @@ export default function SignIn(props) { - - Nickname: {dongle.nick_name ? dongle.nick_name : `My ${dongle.device_type}`}

- Type: {dongle.device_type}

- Serial: {dongle.serial}

- IMEI: {dongle.imei}

- Registered: {helpers.formatDate(dongle.created)}

- Last Ping: {helpers.formatDate(dongle.last_ping)}

- Public Key: -----BEGIN PUBLIC KEY----- + Nickname: + {' '} + {dongle.nick_name ? dongle.nick_name : `My ${dongle.device_type}`} +
+ Type: + {' '} + {dongle.device_type} +
+ Serial: + {' '} + {dongle.serial} +
+ IMEI: + {' '} + {dongle.imei} +
+ Registered: + {' '} + {helpers.formatDate(dongle.created)} +
+ Last Ping: + {' '} + {helpers.formatDate(dongle.last_ping)} +
+ Public Key: + {' '} + -----BEGIN PUBLIC KEY----- pubKeyClipboard(dongle.public_key)}> -

+
- - Quota Storage: {dongle.storage_used} MB / 200000 MB + Quota Storage: + {dongle.storage_used} + {' '} + MB / 200000 MB
-
); } - - diff --git a/src/components/device/tabPane/drives.jsx b/src/components/device/tabPane/drives.jsx index d762b72..dee1910 100644 --- a/src/components/device/tabPane/drives.jsx +++ b/src/components/device/tabPane/drives.jsx @@ -13,26 +13,26 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Tooltip from '@mui/material/Tooltip'; import React, { useContext, useEffect, useState } from 'react'; -import { context as DeviceContext } from "./../../../context/devices"; -import { context as SnackbarContext } from "./../../../context/toast"; -import * as deviceController from "./../../../controllers/devices"; -import * as helpers from "./../../../controllers/helpers" -import ViewDrive from "./view_drive" +import { context as DeviceContext } from '../../../context/devices'; +import { context as SnackbarContext } from '../../../context/toast'; +import * as deviceController from '../../../controllers/devices'; +import * as helpers from '../../../controllers/helpers'; +import ViewDrive from './view_drive'; export default function EnhancedTable(props) { - const [deviceState, dispatch] = useContext(DeviceContext) - const [, notifDispatch] = useContext(SnackbarContext) - const [state, setState] = useState({selectedSegment: null}) + const [deviceState, dispatch] = useContext(DeviceContext); + const [, notifDispatch] = useContext(SnackbarContext); + const [state, setState] = useState({ selectedSegment: null }); useEffect(() => { deviceController.getDrives(props.dongleId).then((res) => { setTimeout(() => { - dispatch({ type: "update_dongle_drive", dongle_id: props.dongleId, drives: res.data }) - }, 1) + dispatch({ type: 'update_dongle_drive', dongle_id: props.dongleId, drives: res.data }); + }, 1); }).catch(() => { - notifDispatch({type: "NEW_TOAST", msg: 'Failed to load drives'}) - }) - }, [dispatch, notifDispatch, props.dongleId]) + notifDispatch({ type: 'NEW_TOAST', msg: 'Failed to load drives' }); + }); + }, [dispatch, notifDispatch, props.dongleId]); return ( @@ -41,7 +41,7 @@ export default function EnhancedTable(props) { @@ -65,25 +65,27 @@ export default function EnhancedTable(props) { let metadata; try { - metadata = JSON.parse(row.metadata) - } catch (err) { metadata = {} } + metadata = JSON.parse(row.metadata); + } catch (err) { metadata = {}; } return ( {state.selectedSegment === index ? setState({...state, selectedSegment: null }) : setState({...state, selectedSegment: index })}} + onClick={() => { state.selectedSegment === index ? setState({ ...state, selectedSegment: null }) : setState({ ...state, selectedSegment: index }); }} > {row.identifier} + > + {row.identifier} + - {metadata.hasOwnProperty('CarParams1') ? metadata.CarParams['CarName'] : "Glorious Skoda"} - {metadata.hasOwnProperty('InitData1') ? metadata.InitData['Version'] : "Lemon boy"} - {Math.round(row.filesize / 1024) + ' MiB'} - {helpers.formatDuration(row.duration)} - {Math.round(row.distance_meters / 1000)} - {row.upload_complete.toString()} - {row.is_processed.toString()} - {helpers.formatDate(row.drive_date)} + {metadata.hasOwnProperty('CarParams1') ? metadata.CarParams.CarName : 'Glorious Skoda'} + {metadata.hasOwnProperty('InitData1') ? metadata.InitData.Version : 'Lemon boy'} + {`${Math.round(row.filesize / 1024)} MiB`} + {helpers.formatDuration(row.duration)} + {Math.round(row.distance_meters / 1000)} + {row.upload_complete.toString()} + {row.is_processed.toString()} + {helpers.formatDate(row.drive_date)} @@ -106,13 +108,11 @@ export default function EnhancedTable(props) { - ) + ); + }) - }) : - - [1, 1, 1, 1, 1].map((v) => ( - + : [1, 1, 1, 1, 1].map((v) => ( + @@ -122,19 +122,16 @@ export default function EnhancedTable(props) { - - - - - - - - + + + + + + + + - )) - - - } + ))}
@@ -145,4 +142,3 @@ export default function EnhancedTable(props) {
); } - diff --git a/src/components/device/tabPane/index.jsx b/src/components/device/tabPane/index.jsx index 26e9750..c874c1c 100644 --- a/src/components/device/tabPane/index.jsx +++ b/src/components/device/tabPane/index.jsx @@ -2,17 +2,16 @@ import Box from '@mui/material/Box'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; import React from 'react'; -import BootLogsTable from "./boot"; +import BootLogsTable from './boot'; import Console from './console'; -import CrashLogsTable from "./crash"; +import CrashLogsTable from './crash'; import DeviceInfo from './device'; -import DrivesLogTable from "./drives"; - - - +import DrivesLogTable from './drives'; function TabPanel(props) { - const { children, value, index, ...other } = props; + const { + children, value, index, ...other + } = props; return (
{ @@ -45,8 +41,13 @@ export default function SignIn(props) {
- + @@ -72,10 +73,7 @@ export default function SignIn(props) { { - }
); } - - diff --git a/src/components/device/tabPane/view_drive.jsx b/src/components/device/tabPane/view_drive.jsx index 8ccda54..924ca75 100644 --- a/src/components/device/tabPane/view_drive.jsx +++ b/src/components/device/tabPane/view_drive.jsx @@ -10,177 +10,178 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Tooltip from '@mui/material/Tooltip'; import React, { useContext, useState } from 'react'; -import { context as DeviceContext } from "./../../../context/devices"; -import * as deviceController from "./../../../controllers/devices"; -import Typography from "@mui/material/Typography" - - - +import Typography from '@mui/material/Typography'; +import { context as DeviceContext } from '../../../context/devices'; +import * as deviceController from '../../../controllers/devices'; export default function EnhancedTable(props) { - const [deviceState] = useContext(DeviceContext) + const [deviceState] = useContext(DeviceContext); - const [state, setState] = useState({ loading: true, firstReqSent: false, segment: null, drive: null }) + const [state, setState] = useState({ + loading: true, firstReqSent: false, segment: null, drive: null, + }); - if (state.drive === null) { - setState({ ...state, drive: props.drive }) + if (state.drive === null) { + setState({ ...state, drive: props.drive }); + } + + if (props.drive !== state.drive) { + setState({ + ...state, loading: true, firstReqSent: false, segment: null, drive: props.drive, + }); + } + + const dongle_id = props.dongleId; + const drive_id = props.drive; + const dongle = deviceState.dongles[dongle_id]; + console.log('view drive', dongle); + console.log('drives', dongle.drives); + if (!dongle || !dongle.drives) return (

loading

); + + if (state.segment === null) { + // TODO Make this not run multiple times + deviceController.getDriveSegments(dongle_id, dongle.drives[drive_id].identifier).then((res) => { + console.log('my res', res.data); + if (res.data === null) { + setState({ + ...state, loading: false, firstReqSent: true, segment: [], + }); + } else { + setState({ + ...state, loading: false, firstReqSent: true, segment: res.data, + }); + } + }); + } + + // test + + const drive = dongle.drives[drive_id]; + + let vehicle = ''; + let version = ''; + let gitRemote = ''; + let gitBranch = ''; + let gitCommit = ''; + let metadata = {}; + + try { + metadata = JSON.parse(drive.metadata); + + if (metadata.InitData) { + version = metadata.InitData.Version || 'Unknown'; + gitRemote = metadata.InitData.GitRemote || 'Unknown'; + gitBranch = metadata.InitData.GitBranch || 'Unknown'; + gitCommit = metadata.InitData.GitCommit || 'Unknown'; } - if (props.drive !== state.drive) { - setState({ ...state, loading: true, firstReqSent: false, segment: null, drive: props.drive }) + if (metadata.CarParams) { + if (metadata.CarParams.CarName !== undefined) vehicle += `${metadata.CarParams.CarName.toUpperCase()} `; + if (metadata.CarParams.CarFingerprint !== undefined) vehicle += (metadata.CarParams.CarFingerprint.toUpperCase()); + } + } catch (exception) { console.log(exception); } + + // const directoryTree = dirTree(config.storagePath + device.dongle_id + "/" + dongleIdHash + "/" + driveIdentifierHash + "/" + drive.identifier); + const directoryTree = state.segment; + const driveUrl = 'driveurl'; + const directorySegments = {}; + + if (directoryTree) { + for (const i in directoryTree.children) { + // skip any non-directory entries (for example m3u8 file in the drive directory) + if (directoryTree.children[i].type !== 'directory') continue; + + const segment = directoryTree.children[i].name; + + const logSegment = {}; + for (const c in directoryTree.children[i].children) { + logSegment[directoryTree.children[i].children[c].name] = { + url: `${driveUrl}/${segment}/${directoryTree.children[i].children[c].name}`, + name: directoryTree.children[i].children[c].name, + fileSize: directoryTree.children[i].children[c].size, + + }; + } + + directorySegments[segment] = logSegment; } - const dongle_id = props.dongleId; - const drive_id = props.drive; - const dongle = deviceState.dongles[dongle_id]; - console.log("view drive", dongle) - console.log("drives", dongle.drives) - if (!dongle || !dongle.drives) return (

loading

) + console.log('output is', directorySegments); + } - if (state.segment === null) { - // TODO Make this not run multiple times - deviceController.getDriveSegments(dongle_id, dongle.drives[drive_id].identifier).then((res) => { - console.log("my res", res.data) - if (res.data === null) { - setState({ ...state, loading: false, firstReqSent: true, segment: [] }) - } else { - setState({ ...state, loading: false, firstReqSent: true, segment: res.data }) - } + return ( + + - }) + + Vehicle: + {' '} + {vehicle} + + + Version: + {' '} + {version} + + + gitRemote: + {' '} + {gitRemote} + + + gitBranch: + {' '} + {gitBranch} + + + gitCommit: + {' '} + {gitCommit} + - } + Fingerprint: - // test + + + + + Segment ID + File + File size + Actions + + + + { + directorySegments ? Object.keys(directorySegments).map((key, index) => Object.keys(directorySegments[key]).map((key1, index1) => ( + + {key} + {directorySegments[key][key1].name} + {`${Math.round(directorySegments[key][key1].fileSize / 1024)} MiB`} + + + window.open(directorySegments[key][key1].url, '_blank')}> + + + + - - - const drive = dongle.drives[drive_id]; - - - - - - let vehicle = ""; - let version = ""; - let gitRemote = ""; - let gitBranch = ""; - let gitCommit = ""; - let metadata = {}; - - - try { - metadata = JSON.parse(drive.metadata); - - if (metadata['InitData']) { - version = metadata['InitData']['Version'] || "Unknown"; - gitRemote = metadata['InitData']['GitRemote'] || "Unknown"; - gitBranch = metadata['InitData']['GitBranch'] || "Unknown"; - gitCommit = metadata['InitData']['GitCommit'] || "Unknown"; - } - - - if (metadata['CarParams']) { - if (metadata['CarParams']['CarName'] !== undefined) vehicle += (metadata['CarParams']['CarName'].toUpperCase()) + " "; - if (metadata['CarParams']['CarFingerprint'] !== undefined) vehicle += (metadata['CarParams']['CarFingerprint'].toUpperCase()) - - } - } catch (exception) { console.log(exception) } - - //const directoryTree = dirTree(config.storagePath + device.dongle_id + "/" + dongleIdHash + "/" + driveIdentifierHash + "/" + drive.identifier); - const directoryTree = state.segment; - const driveUrl = "driveurl" - var directorySegments = {}; - - if (directoryTree) { - for (var i in directoryTree.children) { - // skip any non-directory entries (for example m3u8 file in the drive directory) - if (directoryTree.children[i].type !== 'directory') continue; - - var segment = directoryTree.children[i].name; - - let logSegment = {} - for (var c in directoryTree.children[i].children) { - logSegment[directoryTree.children[i].children[c].name] = { - url: `${driveUrl}/${segment}/${directoryTree.children[i].children[c].name}`, - name: directoryTree.children[i].children[c].name, - fileSize: directoryTree.children[i].children[c].size - - } - } - - directorySegments[segment] = logSegment; - } - - console.log("output is", directorySegments) - } - - - - - return ( - - - - Vehicle: {vehicle} - Version: {version} - gitRemote: {gitRemote} - gitBranch: {gitBranch} - gitCommit: {gitCommit} - - Fingerprint: - - - - -
- - - Segment ID - File - File size - Actions - - - - { - directorySegments ? Object.keys(directorySegments).map((key, index) => { - - - return Object.keys(directorySegments[key]).map((key1, index1) => ( - - {key} - {directorySegments[key][key1].name} - {Math.round(directorySegments[key][key1].fileSize / 1024) + ' MiB'} - - - - window.open(directorySegments[key][key1].url, "_blank")}> - - - - - - - - )) - - - }) : null + + ))) : null } - -
-
+ + + -
-
- ); + + + ); } /* @@ -195,8 +196,6 @@ var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(de cabanaUrl = config.cabanaUrl + '?retropilotIdentifier=' + device.dongle_id + '|' + dongleIdHash + '|' + drive.identifier + '|' + driveIdentifierHash + '&retropilotHost=' + encodeURIComponent(config.baseUrl) + '&demo=1"'; } - - var response = ` @@ -290,7 +289,6 @@ var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(de segmentqcameraqlogfcamerarlogdcameraprocessedstalled `; - var directorySegments = {}; for (var i in directoryTree.children) { // skip any non-directory entries (for example m3u8 file in the drive directory) @@ -298,7 +296,6 @@ var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(de var segment = directoryTree.children[i].name; - var qcamera = '--'; var fcamera = '--'; var dcamera = '--'; @@ -345,4 +342,4 @@ var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(de
Sign Out`; -*/ \ No newline at end of file +*/ diff --git a/src/components/views/home.jsx b/src/components/views/home.jsx index 512a2ea..8ba5b86 100644 --- a/src/components/views/home.jsx +++ b/src/components/views/home.jsx @@ -1,16 +1,16 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import React, { useContext, useState } from 'react'; -import { Link } from "react-router-dom"; -import { UserContext } from "./../../context/users"; +import { Link } from 'react-router-dom'; +import { UserContext } from '../../context/users'; -const theme = createTheme(); +const theme = createTheme(); export default function SignIn() { - const [loading, setLoading] = useState(false) - const [ state, dispatch ] = useContext(UserContext) - console.log("component", state) + const [loading, setLoading] = useState(false); + const [state, dispatch] = useContext(UserContext); + console.log('component', state); const handleSubmit = (event) => { - dispatch({ type: "toggle_button" }) + dispatch({ type: 'toggle_button' }); event.preventDefault(); const data = new FormData(event.currentTarget); @@ -20,19 +20,15 @@ export default function SignIn() { password: data.get('password'), }); - setLoading(true) - - + setLoading(true); }; return ( -

hello

- - {"login?"} - +

hello

+ + login? +
); } - - diff --git a/src/components/views/login.jsx b/src/components/views/login.jsx index eb05f01..146e309 100644 --- a/src/components/views/login.jsx +++ b/src/components/views/login.jsx @@ -7,14 +7,14 @@ import Paper from '@mui/material/Paper'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import React, { useContext, useState } from 'react'; -import { UserContext } from "./../../context/users"; +import { UserContext } from '../../context/users'; export default function SignIn() { - const [loading, setLoading] = useState(false) - const [state, dispatch] = useContext(UserContext) - console.log("component", state) + const [loading, setLoading] = useState(false); + const [state, dispatch] = useContext(UserContext); + console.log('component', state); const handleSubmit = (event) => { - dispatch({ type: "toggle_button" }) + dispatch({ type: 'toggle_button' }); event.preventDefault(); const data = new FormData(event.currentTarget); @@ -24,7 +24,7 @@ export default function SignIn() { password: data.get('password'), }); - setLoading(true) + setLoading(true); }; return ( @@ -41,13 +41,10 @@ export default function SignIn() { alignItems: 'center', }} style={{ - padding: '15px' + padding: '15px', }} > - - - Sign in @@ -85,20 +82,15 @@ export default function SignIn() { - {"New Here or Forgotten password?"} + New Here or Forgotten password? - - - ); } - - diff --git a/src/components/views/useradmin.jsx b/src/components/views/useradmin.jsx index 5dafbc6..4de440b 100644 --- a/src/components/views/useradmin.jsx +++ b/src/components/views/useradmin.jsx @@ -2,52 +2,41 @@ import Grid from '@mui/material/Grid'; import Paper from '@mui/material/Paper'; import { Scrollbars } from 'rc-scrollbars'; import React, { useContext } from 'react'; -import { context as DeviceContext } from "./../../context/devices"; -import DeviceData from './../device/deviceData'; -import DeviceOverview from "./../device/overview"; - - - - - +import { context as DeviceContext } from '../../context/devices'; +import DeviceData from '../device/deviceData'; +import DeviceOverview from '../device/overview'; export default function SignIn() { - - - const [deviceState] = useContext(DeviceContext); - - return (
- - - - + + + +
- {deviceState ? Object.keys(deviceState.dongles).map(key => ) :

no

} - + {deviceState ? Object.keys(deviceState.dongles).map((key) => ) :

no

}
- - -
{deviceState.dongles['53331425'] ? :

no

} -
); } - - diff --git a/src/components/widgets/globalSnack.jsx b/src/components/widgets/globalSnack.jsx index b13770a..47a2de1 100644 --- a/src/components/widgets/globalSnack.jsx +++ b/src/components/widgets/globalSnack.jsx @@ -1,19 +1,12 @@ import Snackbar from '@mui/material/Snackbar'; import React, { useContext } from 'react'; -import { context as DeviceContext } from "./../../context/toast"; - - - - - +import { context as DeviceContext } from '../../context/toast'; export default function Toast(props) { - const [state, dispatch] = useContext(DeviceContext) - - + const [state, dispatch] = useContext(DeviceContext); const handleClose = () => { - dispatch({ type: 'CLOSE_TOAST' }) + dispatch({ type: 'CLOSE_TOAST' }); }; return ( diff --git a/src/context/devices/index.js b/src/context/devices/index.js index 7ec1b83..463b6c3 100644 --- a/src/context/devices/index.js +++ b/src/context/devices/index.js @@ -1,131 +1,125 @@ -import React, { createContext, useEffect, useReducer } from "react"; -import * as deviceController from "./../../controllers/devices"; +import React, { createContext, useEffect, useReducer } from 'react'; +import * as deviceController from '../../controllers/devices'; function process(state, action) { - if (action.type !== "ADD_DATA") { return state } + if (action.type !== 'ADD_DATA') { return state; } - switch (action.data.command) { - - case "dongle_status": - return { - ...state, - dongles: { - ...state.dongles, - [action.data.data.dongle_id]: { - ...state.dongles[action.data.data.dongle_id], - online: action.data.data.online, - last_seen: action.data.data.time, - dongle_id: action.data.data.dongle_id - } - } - } - default: - return state; - } + switch (action.data.command) { + case 'dongle_status': + return { + ...state, + dongles: { + ...state.dongles, + [action.data.data.dongle_id]: { + ...state.dongles[action.data.data.dongle_id], + online: action.data.data.online, + last_seen: action.data.data.time, + dongle_id: action.data.data.dongle_id, + }, + }, + }; + default: + return state; + } } - export const Reducer = (state, action) => { - console.log("input", state, action) - switch (action.type) { - case 'ADD_DATA': - return process(state, action); - case "fetch_all_dongles": - console.log("fetch", action) - return { - ...state, - dongles: action.data - } + console.log('input', state, action); + switch (action.type) { + case 'ADD_DATA': + return process(state, action); + case 'fetch_all_dongles': + console.log('fetch', action); + return { + ...state, + dongles: action.data, + }; - case "update_dongle_drive": - return { - ...state, - dongles: { - ...state.dongles, - [action.dongle_id]: { - ...state.dongles[action.dongle_id], - drives: action.drives - } - } - } + case 'update_dongle_drive': + return { + ...state, + dongles: { + ...state.dongles, + [action.dongle_id]: { + ...state.dongles[action.dongle_id], + drives: action.drives, + }, + }, + }; - case "update_dongle_bootlogs": - return { - ...state, - dongles: { - ...state.dongles, - [action.dongle_id]: { - ...state.dongles[action.dongle_id], - boot: action.bootlogs - } - } - } + case 'update_dongle_bootlogs': + return { + ...state, + dongles: { + ...state.dongles, + [action.dongle_id]: { + ...state.dongles[action.dongle_id], + boot: action.bootlogs, + }, + }, + }; - case "update_dongle_crashlogs": - return { - ...state, - dongles: { - ...state.dongles, - [action.dongle_id]: { - ...state.dongles[action.dongle_id], - crash: action.crashlogs - } - } - } + case 'update_dongle_crashlogs': + return { + ...state, + dongles: { + ...state.dongles, + [action.dongle_id]: { + ...state.dongles[action.dongle_id], + crash: action.crashlogs, + }, + }, + }; - case "user_authentication": - return { - ...state, - user: action.user - } + case 'user_authentication': + return { + ...state, + user: action.user, + }; - default: - return state; - } + default: + return state; + } }; - - - const initialState = { - dongles: {} + dongles: {}, }; -const Store = ({ children }) => { - console.log("STORE HAS BEEN RERENDERED") - const [state, dispatch] = useReducer(Reducer, initialState); +function Store({ children }) { + console.log('STORE HAS BEEN RERENDERED'); + const [state, dispatch] = useReducer(Reducer, initialState); - useEffect(() => { - const ws = new WebSocket('ws://localhost:81'); + useEffect(() => { + const ws = new WebSocket('ws://localhost:81'); - ws.onmessage = ({ data }) => { - data = JSON.parse(data) - console.log("Message") - if (data.id) { - dispatch({ type: "ADD_DATA", id: data.id, data: data }) - } - }; + ws.onmessage = ({ data }) => { + data = JSON.parse(data); + console.log('Message'); + if (data.id) { + dispatch({ type: 'ADD_DATA', id: data.id, data }); + } + }; - deviceController.getAllDevices().then((devices) => { - console.log("store", devices) + deviceController.getAllDevices().then((devices) => { + console.log('store', devices); - dispatch({ type: "fetch_all_dongles", data: devices }) - }) + dispatch({ type: 'fetch_all_dongles', data: devices }); + }); - return () => { - try { - ws.close(); - } catch (e) { } - }; - }, []); + return () => { + try { + ws.close(); + } catch (e) { } + }; + }, []); - - return ( - - {children} - - ) -}; + return ( + + {children} + + ); +} export const context = createContext(initialState); -export default Store; \ No newline at end of file +export default Store; diff --git a/src/context/toast/index.js b/src/context/toast/index.js index 565cf3c..50d075c 100644 --- a/src/context/toast/index.js +++ b/src/context/toast/index.js @@ -1,20 +1,19 @@ -import React, {createContext, useReducer} from "react"; -import Reducer from './reducer' - +import React, { createContext, useReducer } from 'react'; +import Reducer from './reducer'; const initialState = { - open: false, - message: null + open: false, + message: null, }; -const Store = ({children}) => { - const [state, dispatch] = useReducer(Reducer, initialState); - return ( - - {children} - - ) -}; +function Store({ children }) { + const [state, dispatch] = useReducer(Reducer, initialState); + return ( + + {children} + + ); +} export const context = createContext(initialState); -export default Store; \ No newline at end of file +export default Store; diff --git a/src/context/toast/reducer.js b/src/context/toast/reducer.js index 8f35308..082a2ed 100644 --- a/src/context/toast/reducer.js +++ b/src/context/toast/reducer.js @@ -4,17 +4,17 @@ const Reducer = (state, action) => { return { ...state, open: action.open, - msg: action.message + msg: action.message, }; case 'CLOSE_TOAST': return { ...state, - open: false - } + open: false, + }; default: return state; } }; -export default Reducer; \ No newline at end of file +export default Reducer; diff --git a/src/context/users/index.js b/src/context/users/index.js index 5ce3745..56fe31e 100644 --- a/src/context/users/index.js +++ b/src/context/users/index.js @@ -1,17 +1,17 @@ -import React from "react" -import { reducer, initialState } from "./reducer" +import React from 'react'; +import { reducer, initialState } from './reducer'; export const UserContext = React.createContext({ state: initialState, - dispatch: () => null -}) + dispatch: () => null, +}); -export const UserProvider = ({ children }) => { - const [state, dispatch] = React.useReducer(reducer, initialState) +export function UserProvider({ children }) { + const [state, dispatch] = React.useReducer(reducer, initialState); return ( - - { children } + + { children } - ) -} \ No newline at end of file + ); +} diff --git a/src/context/users/reducer.js b/src/context/users/reducer.js index e465bce..eef5dd9 100644 --- a/src/context/users/reducer.js +++ b/src/context/users/reducer.js @@ -1,16 +1,16 @@ export const reducer = (state, action) => { switch (action.type) { - case "sign_out": + case 'sign_out': return { ...state, - active: !state.active - } + active: !state.active, + }; default: - return state + return state; } -} +}; export const initialState = { signedIn: false, @@ -19,4 +19,4 @@ export const initialState = { username: null, JWT: null, }, -} \ No newline at end of file +}; diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 11579d8..f1ffb55 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -1,9 +1,6 @@ -import axios from "axios"; - +import axios from 'axios'; export async function getSession() { - - const req = await axios.get(`http://localhost/retropilot/0/useradmin/session`, {withCredentials: true}); - return req.data - -} \ No newline at end of file + const req = await axios.get('http://localhost/retropilot/0/useradmin/session', { withCredentials: true }); + return req.data; +} diff --git a/src/controllers/devices.js b/src/controllers/devices.js index 7f54770..dd985fd 100644 --- a/src/controllers/devices.js +++ b/src/controllers/devices.js @@ -1,50 +1,44 @@ -import axios from "axios"; +import axios from 'axios'; export async function getDrives(dongleId) { - const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/drives/false`, { withCredentials: true }); - return req.data + const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/drives/false`, { withCredentials: true }); + return req.data; } export async function getBootlogs(dongleId) { - const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/bootlogs`, { withCredentials: true }); - return req.data + const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/bootlogs`, { withCredentials: true }); + return req.data; } export async function getCrashlogs(dongleId) { - const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/crashlogs`, { withCredentials: true }); - return req.data + const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/crashlogs`, { withCredentials: true }); + return req.data; } export async function getDriveSegments(dongleId, drive_identifier) { - const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/drives/${drive_identifier}/segment`, { withCredentials: true }); - return req.data + const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/drives/${drive_identifier}/segment`, { withCredentials: true }); + return req.data; } export async function getAllDevices() { - const req = await axios.get(`http://localhost/retropilot/0/devices`, { withCredentials: true }); - const responseData = req.data + const req = await axios.get('http://localhost/retropilot/0/devices', { withCredentials: true }); + const responseData = req.data; - let dongles = {} + let dongles = {}; - if (responseData.success === true) { + if (responseData.success === true) { + responseData.data.map((object) => dongles = { + ...dongles, + [object.dongle_id]: { + ...object, + online: false, + // Show when last connected to api instead Athena by default + last_seen: object.last_ping, + }, + }); - responseData.data.map((object) => { + return dongles; + } - return dongles = { - ...dongles, - [object.dongle_id]: { - ...object, - online: false, - // Show when last connected to api instead Athena by default - last_seen: object.last_ping, - } - } - - }) - - return dongles; - - } - - return null; -} \ No newline at end of file + return null; +} diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index b8b557d..a2da1b5 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -1,24 +1,20 @@ - - - export function formatDate(timestampMs) { return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, ''); } - export function formatDuration(durationSeconds) { durationSeconds = Math.round(durationSeconds); const secs = durationSeconds % 60; let mins = Math.floor(durationSeconds / 60); let hours = Math.floor(mins / 60); - mins = mins % 60; + mins %= 60; const days = Math.floor(hours / 24); - hours = hours % 24; + hours %= 24; let response = ''; - if (days > 0) response += days + 'd '; - if (hours > 0 || days > 0) response += hours + 'h '; - if (hours > 0 || days > 0 || mins > 0) response += mins + 'm '; - response += secs + 's'; + if (days > 0) response += `${days}d `; + if (hours > 0 || days > 0) response += `${hours}h `; + if (hours > 0 || days > 0 || mins > 0) response += `${mins}m `; + response += `${secs}s`; return response; -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index ef2edf8..b0a768f 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,7 @@ ReactDOM.render( , - document.getElementById('root') + document.getElementById('root'), ); // If you want to start measuring performance in your app, pass a function diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js index 5253d3a..bb47dbb 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.js @@ -1,6 +1,8 @@ -const reportWebVitals = onPerfEntry => { +const reportWebVitals = (onPerfEntry) => { if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + import('web-vitals').then(({ + getCLS, getFID, getFCP, getLCP, getTTFB, + }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); diff --git a/src/yeet.js b/src/yeet.js index 573a52a..029ea5f 100644 --- a/src/yeet.js +++ b/src/yeet.js @@ -1,4 +1,4 @@ -import React, {useState, useContext} from 'react'; +import React, { useState, useContext } from 'react'; import Avatar from '@mui/material/Avatar'; import LoadingButton from '@mui/lab/LoadingButton'; import CssBaseline from '@mui/material/CssBaseline'; @@ -14,20 +14,21 @@ import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import Typography from '@mui/material/Typography'; import Container from '@mui/material/Container'; import { createTheme, ThemeProvider } from '@mui/material/styles'; -import { UserContext } from "./context/users" -const theme = createTheme(); +import { UserContext } from './context/users'; + +const theme = createTheme(); export default function SignIn() { - const [ state, dispatch ] = useContext(UserContext) - + const [state, dispatch] = useContext(UserContext); return ( - {console.log("testing", state)} -

{JSON.stringify(state)}

- + {console.log('testing', state)} +

+ {' '} + {JSON.stringify(state)} +

+
); } - -