Add Dexie for persistence; user management with dexie; this is the way
This commit is contained in:
		
							parent
							
								
									8036aa2942
								
							
						
					
					
						commit
						23d275acec
					
				
					 16 changed files with 285 additions and 494 deletions
				
			
		
							
								
								
									
										362
									
								
								web/package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										362
									
								
								web/package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -11,10 +11,10 @@
 | 
				
			||||||
        "@emotion/styled": "latest",
 | 
					        "@emotion/styled": "latest",
 | 
				
			||||||
        "@mui/icons-material": "^5.4.2",
 | 
					        "@mui/icons-material": "^5.4.2",
 | 
				
			||||||
        "@mui/material": "latest",
 | 
					        "@mui/material": "latest",
 | 
				
			||||||
        "@mui/styles": "^5.4.2",
 | 
					        "dexie": "^3.2.1",
 | 
				
			||||||
 | 
					        "dexie-react-hooks": "^1.1.1",
 | 
				
			||||||
        "react": "latest",
 | 
					        "react": "latest",
 | 
				
			||||||
        "react-dom": "latest",
 | 
					        "react-dom": "latest",
 | 
				
			||||||
        "react-router-dom": "^6.2.1",
 | 
					 | 
				
			||||||
        "react-scripts": "^3.0.1"
 | 
					        "react-scripts": "^3.0.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					@ -2364,46 +2364,6 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@mui/styles": {
 | 
					 | 
				
			||||||
      "version": "5.4.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.4.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-BX75fNHmRF51yove9dBkH28gpSFjClOPDEnUwLTghPYN913OsqViS/iuCd61dxzygtEEmmeYuWfQjxu/F6vF5g==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.17.0",
 | 
					 | 
				
			||||||
        "@emotion/hash": "^0.8.0",
 | 
					 | 
				
			||||||
        "@mui/private-theming": "^5.4.2",
 | 
					 | 
				
			||||||
        "@mui/types": "^7.1.2",
 | 
					 | 
				
			||||||
        "@mui/utils": "^5.4.2",
 | 
					 | 
				
			||||||
        "clsx": "^1.1.1",
 | 
					 | 
				
			||||||
        "csstype": "^3.0.10",
 | 
					 | 
				
			||||||
        "hoist-non-react-statics": "^3.3.2",
 | 
					 | 
				
			||||||
        "jss": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-camel-case": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-default-unit": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-global": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-nested": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-props-sort": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-rule-value-function": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-vendor-prefixer": "^10.8.2",
 | 
					 | 
				
			||||||
        "prop-types": "^15.7.2"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=12.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "type": "opencollective",
 | 
					 | 
				
			||||||
        "url": "https://opencollective.com/mui"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "@types/react": "^16.8.6 || ^17.0.0",
 | 
					 | 
				
			||||||
        "react": "^17.0.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependenciesMeta": {
 | 
					 | 
				
			||||||
        "@types/react": {
 | 
					 | 
				
			||||||
          "optional": true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/@mui/system": {
 | 
					    "node_modules/@mui/system": {
 | 
				
			||||||
      "version": "5.4.2",
 | 
					      "version": "5.4.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz",
 | 
				
			||||||
| 
						 | 
					@ -5683,15 +5643,6 @@
 | 
				
			||||||
        "node": ">=0.10.0"
 | 
					        "node": ">=0.10.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/css-vendor": {
 | 
					 | 
				
			||||||
      "version": "2.0.8",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.8.3",
 | 
					 | 
				
			||||||
        "is-in-browser": "^1.0.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/css-what": {
 | 
					    "node_modules/css-what": {
 | 
				
			||||||
      "version": "5.1.0",
 | 
					      "version": "5.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
 | 
				
			||||||
| 
						 | 
					@ -6174,6 +6125,24 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
 | 
					      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/dexie": {
 | 
				
			||||||
 | 
					      "version": "3.2.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Y8oz3t2XC9hvjkP35B5I8rUkKKwM36GGRjWQCMjzIYScg7W+GHKDXobSYswkisW7CxL1/tKQtggMDsiWqDUc1g==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=6.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/dexie-react-hooks": {
 | 
				
			||||||
 | 
					      "version": "1.1.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Cam5JP6PxHN564RvWEoe8cqLhosW0O4CAZ9XEVYeGHJBa6KEJlOpd9CUpV3kmU9dm2MrW97/lk7qkf1xpij7gA==",
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@types/react": ">=16",
 | 
				
			||||||
 | 
					        "dexie": ">=3.1.0-alpha.1 <5.0.0",
 | 
				
			||||||
 | 
					        "react": ">=16"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/diff-sequences": {
 | 
					    "node_modules/diff-sequences": {
 | 
				
			||||||
      "version": "24.9.0",
 | 
					      "version": "24.9.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
 | 
				
			||||||
| 
						 | 
					@ -8364,14 +8333,6 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
 | 
					      "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/history": {
 | 
					 | 
				
			||||||
      "version": "5.3.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.7.6"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/hmac-drbg": {
 | 
					    "node_modules/hmac-drbg": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
 | 
				
			||||||
| 
						 | 
					@ -8571,11 +8532,6 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
 | 
					      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/hyphenate-style-name": {
 | 
					 | 
				
			||||||
      "version": "1.0.4",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/iconv-lite": {
 | 
					    "node_modules/iconv-lite": {
 | 
				
			||||||
      "version": "0.4.24",
 | 
					      "version": "0.4.24",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
				
			||||||
| 
						 | 
					@ -9168,11 +9124,6 @@
 | 
				
			||||||
        "node": ">=0.10.0"
 | 
					        "node": ">=0.10.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/is-in-browser": {
 | 
					 | 
				
			||||||
      "version": "1.1.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/is-negative-zero": {
 | 
					    "node_modules/is-negative-zero": {
 | 
				
			||||||
      "version": "2.0.2",
 | 
					      "version": "2.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
 | 
				
			||||||
| 
						 | 
					@ -10290,88 +10241,6 @@
 | 
				
			||||||
        "node": ">=0.6.0"
 | 
					        "node": ">=0.6.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/jss": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "csstype": "^3.0.2",
 | 
					 | 
				
			||||||
        "is-in-browser": "^1.1.3",
 | 
					 | 
				
			||||||
        "tiny-warning": "^1.0.2"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "funding": {
 | 
					 | 
				
			||||||
        "type": "opencollective",
 | 
					 | 
				
			||||||
        "url": "https://opencollective.com/jss"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jss-plugin-camel-case": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "hyphenate-style-name": "^1.0.3",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jss-plugin-default-unit": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jss-plugin-global": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jss-plugin-nested": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0",
 | 
					 | 
				
			||||||
        "tiny-warning": "^1.0.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jss-plugin-props-sort": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jss-plugin-rule-value-function": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0",
 | 
					 | 
				
			||||||
        "tiny-warning": "^1.0.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jss-plugin-vendor-prefixer": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "css-vendor": "^2.0.8",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jsx-ast-utils": {
 | 
					    "node_modules/jsx-ast-utils": {
 | 
				
			||||||
      "version": "2.4.1",
 | 
					      "version": "2.4.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
 | 
				
			||||||
| 
						 | 
					@ -13824,30 +13693,6 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 | 
					      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/react-router": {
 | 
					 | 
				
			||||||
      "version": "6.2.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "history": "^5.2.0"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "react": ">=16.8"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/react-router-dom": {
 | 
					 | 
				
			||||||
      "version": "6.2.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					 | 
				
			||||||
        "history": "^5.2.0",
 | 
					 | 
				
			||||||
        "react-router": "6.2.1"
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "peerDependencies": {
 | 
					 | 
				
			||||||
        "react": ">=16.8",
 | 
					 | 
				
			||||||
        "react-dom": ">=16.8"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/react-scripts": {
 | 
					    "node_modules/react-scripts": {
 | 
				
			||||||
      "version": "3.4.4",
 | 
					      "version": "3.4.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
				
			||||||
| 
						 | 
					@ -16357,11 +16202,6 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
 | 
				
			||||||
      "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
 | 
					      "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/tiny-warning": {
 | 
					 | 
				
			||||||
      "version": "1.0.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/tmp": {
 | 
					    "node_modules/tmp": {
 | 
				
			||||||
      "version": "0.0.33",
 | 
					      "version": "0.0.33",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
 | 
				
			||||||
| 
						 | 
					@ -19518,30 +19358,6 @@
 | 
				
			||||||
        "prop-types": "^15.7.2"
 | 
					        "prop-types": "^15.7.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "@mui/styles": {
 | 
					 | 
				
			||||||
      "version": "5.4.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.4.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-BX75fNHmRF51yove9dBkH28gpSFjClOPDEnUwLTghPYN913OsqViS/iuCd61dxzygtEEmmeYuWfQjxu/F6vF5g==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.17.0",
 | 
					 | 
				
			||||||
        "@emotion/hash": "^0.8.0",
 | 
					 | 
				
			||||||
        "@mui/private-theming": "^5.4.2",
 | 
					 | 
				
			||||||
        "@mui/types": "^7.1.2",
 | 
					 | 
				
			||||||
        "@mui/utils": "^5.4.2",
 | 
					 | 
				
			||||||
        "clsx": "^1.1.1",
 | 
					 | 
				
			||||||
        "csstype": "^3.0.10",
 | 
					 | 
				
			||||||
        "hoist-non-react-statics": "^3.3.2",
 | 
					 | 
				
			||||||
        "jss": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-camel-case": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-default-unit": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-global": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-nested": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-props-sort": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-rule-value-function": "^10.8.2",
 | 
					 | 
				
			||||||
        "jss-plugin-vendor-prefixer": "^10.8.2",
 | 
					 | 
				
			||||||
        "prop-types": "^15.7.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "@mui/system": {
 | 
					    "@mui/system": {
 | 
				
			||||||
      "version": "5.4.2",
 | 
					      "version": "5.4.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz",
 | 
				
			||||||
| 
						 | 
					@ -22155,15 +21971,6 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "css-vendor": {
 | 
					 | 
				
			||||||
      "version": "2.0.8",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.8.3",
 | 
					 | 
				
			||||||
        "is-in-browser": "^1.0.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "css-what": {
 | 
					    "css-what": {
 | 
				
			||||||
      "version": "5.1.0",
 | 
					      "version": "5.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
 | 
				
			||||||
| 
						 | 
					@ -22542,6 +22349,17 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "dexie": {
 | 
				
			||||||
 | 
					      "version": "3.2.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Y8oz3t2XC9hvjkP35B5I8rUkKKwM36GGRjWQCMjzIYScg7W+GHKDXobSYswkisW7CxL1/tKQtggMDsiWqDUc1g=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "dexie-react-hooks": {
 | 
				
			||||||
 | 
					      "version": "1.1.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Cam5JP6PxHN564RvWEoe8cqLhosW0O4CAZ9XEVYeGHJBa6KEJlOpd9CUpV3kmU9dm2MrW97/lk7qkf1xpij7gA==",
 | 
				
			||||||
 | 
					      "requires": {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "diff-sequences": {
 | 
					    "diff-sequences": {
 | 
				
			||||||
      "version": "24.9.0",
 | 
					      "version": "24.9.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz",
 | 
				
			||||||
| 
						 | 
					@ -24243,14 +24061,6 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
 | 
					      "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "history": {
 | 
					 | 
				
			||||||
      "version": "5.3.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.7.6"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "hmac-drbg": {
 | 
					    "hmac-drbg": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
 | 
				
			||||||
| 
						 | 
					@ -24418,11 +24228,6 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
 | 
					      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "hyphenate-style-name": {
 | 
					 | 
				
			||||||
      "version": "1.0.4",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "iconv-lite": {
 | 
					    "iconv-lite": {
 | 
				
			||||||
      "version": "0.4.24",
 | 
					      "version": "0.4.24",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
				
			||||||
| 
						 | 
					@ -24846,11 +24651,6 @@
 | 
				
			||||||
        "is-extglob": "^2.1.1"
 | 
					        "is-extglob": "^2.1.1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "is-in-browser": {
 | 
					 | 
				
			||||||
      "version": "1.1.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "is-negative-zero": {
 | 
					    "is-negative-zero": {
 | 
				
			||||||
      "version": "2.0.2",
 | 
					      "version": "2.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
 | 
				
			||||||
| 
						 | 
					@ -25729,84 +25529,6 @@
 | 
				
			||||||
        "verror": "1.10.0"
 | 
					        "verror": "1.10.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "jss": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "csstype": "^3.0.2",
 | 
					 | 
				
			||||||
        "is-in-browser": "^1.1.3",
 | 
					 | 
				
			||||||
        "tiny-warning": "^1.0.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jss-plugin-camel-case": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "hyphenate-style-name": "^1.0.3",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jss-plugin-default-unit": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jss-plugin-global": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jss-plugin-nested": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0",
 | 
					 | 
				
			||||||
        "tiny-warning": "^1.0.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jss-plugin-props-sort": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jss-plugin-rule-value-function": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "jss": "10.9.0",
 | 
					 | 
				
			||||||
        "tiny-warning": "^1.0.2"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jss-plugin-vendor-prefixer": {
 | 
					 | 
				
			||||||
      "version": "10.9.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "@babel/runtime": "^7.3.1",
 | 
					 | 
				
			||||||
        "css-vendor": "^2.0.8",
 | 
					 | 
				
			||||||
        "jss": "10.9.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "jsx-ast-utils": {
 | 
					    "jsx-ast-utils": {
 | 
				
			||||||
      "version": "2.4.1",
 | 
					      "version": "2.4.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
 | 
				
			||||||
| 
						 | 
					@ -28599,23 +28321,6 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 | 
					      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "react-router": {
 | 
					 | 
				
			||||||
      "version": "6.2.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "history": "^5.2.0"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "react-router-dom": {
 | 
					 | 
				
			||||||
      "version": "6.2.1",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
 | 
					 | 
				
			||||||
      "requires": {
 | 
					 | 
				
			||||||
        "history": "^5.2.0",
 | 
					 | 
				
			||||||
        "react-router": "6.2.1"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "react-scripts": {
 | 
					    "react-scripts": {
 | 
				
			||||||
      "version": "3.4.4",
 | 
					      "version": "3.4.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz",
 | 
				
			||||||
| 
						 | 
					@ -30620,11 +30325,6 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
 | 
				
			||||||
      "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
 | 
					      "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "tiny-warning": {
 | 
					 | 
				
			||||||
      "version": "1.0.3",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "tmp": {
 | 
					    "tmp": {
 | 
				
			||||||
      "version": "0.0.33",
 | 
					      "version": "0.0.33",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,8 @@
 | 
				
			||||||
    "@emotion/styled": "latest",
 | 
					    "@emotion/styled": "latest",
 | 
				
			||||||
    "@mui/icons-material": "^5.4.2",
 | 
					    "@mui/icons-material": "^5.4.2",
 | 
				
			||||||
    "@mui/material": "latest",
 | 
					    "@mui/material": "latest",
 | 
				
			||||||
 | 
					    "dexie": "^3.2.1",
 | 
				
			||||||
 | 
					    "dexie-react-hooks": "^1.1.1",
 | 
				
			||||||
    "react": "latest",
 | 
					    "react": "latest",
 | 
				
			||||||
    "react-dom": "latest",
 | 
					    "react-dom": "latest",
 | 
				
			||||||
    "react-scripts": "^3.0.1"
 | 
					    "react-scripts": "^3.0.1"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,9 +7,11 @@ import {
 | 
				
			||||||
    topicShortUrl,
 | 
					    topicShortUrl,
 | 
				
			||||||
    topicUrlJsonPollWithSince
 | 
					    topicUrlJsonPollWithSince
 | 
				
			||||||
} from "./utils";
 | 
					} from "./utils";
 | 
				
			||||||
 | 
					import db from "./db";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Api {
 | 
					class Api {
 | 
				
			||||||
    async poll(baseUrl, topic, since, user) {
 | 
					    async poll(baseUrl, topic, since) {
 | 
				
			||||||
 | 
					        const user = await db.users.get(baseUrl);
 | 
				
			||||||
        const shortUrl = topicShortUrl(baseUrl, topic);
 | 
					        const shortUrl = topicShortUrl(baseUrl, topic);
 | 
				
			||||||
        const url = (since)
 | 
					        const url = (since)
 | 
				
			||||||
            ? topicUrlJsonPollWithSince(baseUrl, topic, since)
 | 
					            ? topicUrlJsonPollWithSince(baseUrl, topic, since)
 | 
				
			||||||
| 
						 | 
					@ -24,7 +26,8 @@ class Api {
 | 
				
			||||||
        return messages;
 | 
					        return messages;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async publish(baseUrl, topic, user, message) {
 | 
					    async publish(baseUrl, topic, message) {
 | 
				
			||||||
 | 
					        const user = await db.users.get(baseUrl);
 | 
				
			||||||
        const url = topicUrl(baseUrl, topic);
 | 
					        const url = topicUrl(baseUrl, topic);
 | 
				
			||||||
        console.log(`[Api] Publishing message to ${url}`);
 | 
					        console.log(`[Api] Publishing message to ${url}`);
 | 
				
			||||||
        await fetch(url, {
 | 
					        await fetch(url, {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,7 +85,7 @@ class Connection {
 | 
				
			||||||
        if (this.since) {
 | 
					        if (this.since) {
 | 
				
			||||||
            params.push(`since=${this.since}`);
 | 
					            params.push(`since=${this.since}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (this.user !== null) {
 | 
					        if (this.user) {
 | 
				
			||||||
            const auth = encodeBase64Url(basicAuth(this.user.username, this.user.password));
 | 
					            const auth = encodeBase64Url(basicAuth(this.user.username, this.user.password));
 | 
				
			||||||
            params.push(`auth=${auth}`);
 | 
					            params.push(`auth=${auth}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,11 @@ class ConnectionManager {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    refresh(subscriptions, users, onNotification) {
 | 
					    refresh(subscriptions, users, onNotification) {
 | 
				
			||||||
 | 
					        if (!subscriptions || !users) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        console.log(`[ConnectionManager] Refreshing connections`);
 | 
					        console.log(`[ConnectionManager] Refreshing connections`);
 | 
				
			||||||
 | 
					        console.log(users);
 | 
				
			||||||
        const subscriptionIds = subscriptions.ids();
 | 
					        const subscriptionIds = subscriptions.ids();
 | 
				
			||||||
        const deletedIds = Array.from(this.connections.keys()).filter(id => !subscriptionIds.includes(id));
 | 
					        const deletedIds = Array.from(this.connections.keys()).filter(id => !subscriptionIds.includes(id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +20,7 @@ class ConnectionManager {
 | 
				
			||||||
            if (added) {
 | 
					            if (added) {
 | 
				
			||||||
                const baseUrl = subscription.baseUrl;
 | 
					                const baseUrl = subscription.baseUrl;
 | 
				
			||||||
                const topic = subscription.topic;
 | 
					                const topic = subscription.topic;
 | 
				
			||||||
                const user = users.get(baseUrl);
 | 
					                const [user] = users.filter(user => user.baseUrl === baseUrl);
 | 
				
			||||||
                const since = subscription.last;
 | 
					                const since = subscription.last;
 | 
				
			||||||
                const connection = new Connection(id, baseUrl, topic, user, since, onNotification);
 | 
					                const connection = new Connection(id, baseUrl, topic, user, since, onNotification);
 | 
				
			||||||
                this.connections.set(id, connection);
 | 
					                this.connections.set(id, connection);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,5 @@
 | 
				
			||||||
import Subscription from "./Subscription";
 | 
					import Subscription from "./Subscription";
 | 
				
			||||||
import Subscriptions from "./Subscriptions";
 | 
					import Subscriptions from "./Subscriptions";
 | 
				
			||||||
import Users from "./Users";
 | 
					 | 
				
			||||||
import User from "./User";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Repository {
 | 
					class Repository {
 | 
				
			||||||
    loadSubscriptions() {
 | 
					    loadSubscriptions() {
 | 
				
			||||||
| 
						 | 
					@ -43,40 +41,6 @@ class Repository {
 | 
				
			||||||
        localStorage.setItem('subscriptions', serialized);
 | 
					        localStorage.setItem('subscriptions', serialized);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loadUsers() {
 | 
					 | 
				
			||||||
        console.log(`[Repository] Loading users from localStorage`);
 | 
					 | 
				
			||||||
        const users = new Users();
 | 
					 | 
				
			||||||
        users.loaded = true;
 | 
					 | 
				
			||||||
        const serialized = localStorage.getItem('users');
 | 
					 | 
				
			||||||
        if (serialized === null) {
 | 
					 | 
				
			||||||
            return users;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            JSON.parse(serialized).forEach(u => {
 | 
					 | 
				
			||||||
                users.add(new User(u.baseUrl, u.username, u.password));
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return users;
 | 
					 | 
				
			||||||
        } catch (e) {
 | 
					 | 
				
			||||||
            console.log(`[Repository] Unable to deserialize users: ${e.message}`);
 | 
					 | 
				
			||||||
            return users;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    saveUsers(users) {
 | 
					 | 
				
			||||||
        if (!users.loaded) {
 | 
					 | 
				
			||||||
            return; // Avoid saving invalid state, triggered by initial useEffect hook
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        console.log(`[Repository] Saving users to localStorage`);
 | 
					 | 
				
			||||||
        const serialized = JSON.stringify(users.map(user => {
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                baseUrl: user.baseUrl,
 | 
					 | 
				
			||||||
                username: user.username,
 | 
					 | 
				
			||||||
                password: user.password
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
        localStorage.setItem('users', serialized);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    loadSelectedSubscriptionId() {
 | 
					    loadSelectedSubscriptionId() {
 | 
				
			||||||
        console.log(`[Repository] Loading selected subscription ID from localStorage`);
 | 
					        console.log(`[Repository] Loading selected subscription ID from localStorage`);
 | 
				
			||||||
        const selectedSubscriptionId = localStorage.getItem('selectedSubscriptionId');
 | 
					        const selectedSubscriptionId = localStorage.getItem('selectedSubscriptionId');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +0,0 @@
 | 
				
			||||||
class User {
 | 
					 | 
				
			||||||
    constructor(baseUrl, username, password) {
 | 
					 | 
				
			||||||
        this.baseUrl = baseUrl;
 | 
					 | 
				
			||||||
        this.username = username;
 | 
					 | 
				
			||||||
        this.password = password;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default User;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,38 +0,0 @@
 | 
				
			||||||
class Users {
 | 
					 | 
				
			||||||
    constructor() {
 | 
					 | 
				
			||||||
        this.loaded = false; // FIXME I hate this
 | 
					 | 
				
			||||||
        this.users = new Map();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    add(user) {
 | 
					 | 
				
			||||||
        this.users.set(user.baseUrl, user);
 | 
					 | 
				
			||||||
        return this;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    get(baseUrl) {
 | 
					 | 
				
			||||||
        const user = this.users.get(baseUrl);
 | 
					 | 
				
			||||||
        return (user) ? user : null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    update(user) {
 | 
					 | 
				
			||||||
        return this.add(user);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    remove(baseUrl) {
 | 
					 | 
				
			||||||
        this.users.delete(baseUrl);
 | 
					 | 
				
			||||||
        return this;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    map(cb) {
 | 
					 | 
				
			||||||
        return Array.from(this.users.values()).map(cb);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    clone() {
 | 
					 | 
				
			||||||
        const c = new Users();
 | 
					 | 
				
			||||||
        c.loaded = this.loaded;
 | 
					 | 
				
			||||||
        c.users = new Map(this.users);
 | 
					 | 
				
			||||||
        return c;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Users;
 | 
					 | 
				
			||||||
							
								
								
									
										15
									
								
								web/src/app/db.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								web/src/app/db.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					import Dexie from 'dexie';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Uses Dexie.js
 | 
				
			||||||
 | 
					// https://dexie.org/docs/API-Reference#quick-reference
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Notes:
 | 
				
			||||||
 | 
					// - As per docs, we only declare the indexable columns, not all columns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const db = new Dexie('ntfy');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					db.version(1).stores({
 | 
				
			||||||
 | 
					    users: '&baseUrl, username',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default db;
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,6 @@ const ActionBar = (props) => {
 | 
				
			||||||
                </Typography>
 | 
					                </Typography>
 | 
				
			||||||
                {props.selectedSubscription !== null && <IconSubscribeSettings
 | 
					                {props.selectedSubscription !== null && <IconSubscribeSettings
 | 
				
			||||||
                    subscription={props.selectedSubscription}
 | 
					                    subscription={props.selectedSubscription}
 | 
				
			||||||
                    users={props.users}
 | 
					 | 
				
			||||||
                    onClearAll={props.onClearAll}
 | 
					                    onClearAll={props.onClearAll}
 | 
				
			||||||
                    onUnsubscribe={props.onUnsubscribe}
 | 
					                    onUnsubscribe={props.onUnsubscribe}
 | 
				
			||||||
                />}
 | 
					                />}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,19 +12,20 @@ import connectionManager from "../app/ConnectionManager";
 | 
				
			||||||
import Subscriptions from "../app/Subscriptions";
 | 
					import Subscriptions from "../app/Subscriptions";
 | 
				
			||||||
import Navigation from "./Navigation";
 | 
					import Navigation from "./Navigation";
 | 
				
			||||||
import ActionBar from "./ActionBar";
 | 
					import ActionBar from "./ActionBar";
 | 
				
			||||||
import Users from "../app/Users";
 | 
					 | 
				
			||||||
import notificationManager from "../app/NotificationManager";
 | 
					import notificationManager from "../app/NotificationManager";
 | 
				
			||||||
import NoTopics from "./NoTopics";
 | 
					import NoTopics from "./NoTopics";
 | 
				
			||||||
import Preferences from "./Preferences";
 | 
					import Preferences from "./Preferences";
 | 
				
			||||||
 | 
					import db from "../app/db";
 | 
				
			||||||
 | 
					import {useLiveQuery} from "dexie-react-hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO subscribe dialog:
 | 
					// TODO subscribe dialog:
 | 
				
			||||||
//  - check/use existing user
 | 
					//  - check/use existing user
 | 
				
			||||||
//  - add baseUrl
 | 
					//  - add baseUrl
 | 
				
			||||||
// TODO user management
 | 
					 | 
				
			||||||
// TODO embed into ntfy server
 | 
					// TODO embed into ntfy server
 | 
				
			||||||
// TODO make default server functional
 | 
					// TODO make default server functional
 | 
				
			||||||
// TODO indexeddb for notifications + subscriptions
 | 
					// TODO indexeddb for notifications + subscriptions
 | 
				
			||||||
// TODO business logic with callbacks
 | 
					// TODO business logic with callbacks
 | 
				
			||||||
 | 
					// TODO connection indicator in subscription list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const App = () => {
 | 
					const App = () => {
 | 
				
			||||||
    console.log(`[App] Rendering main view`);
 | 
					    console.log(`[App] Rendering main view`);
 | 
				
			||||||
| 
						 | 
					@ -32,21 +33,18 @@ const App = () => {
 | 
				
			||||||
    const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
 | 
					    const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
 | 
				
			||||||
    const [prefsOpen, setPrefsOpen] = useState(false);
 | 
					    const [prefsOpen, setPrefsOpen] = useState(false);
 | 
				
			||||||
    const [subscriptions, setSubscriptions] = useState(new Subscriptions());
 | 
					    const [subscriptions, setSubscriptions] = useState(new Subscriptions());
 | 
				
			||||||
    const [users, setUsers] = useState(new Users());
 | 
					 | 
				
			||||||
    const [selectedSubscription, setSelectedSubscription] = useState(null);
 | 
					    const [selectedSubscription, setSelectedSubscription] = useState(null);
 | 
				
			||||||
    const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted());
 | 
					    const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted());
 | 
				
			||||||
 | 
					    const users = useLiveQuery(() => db.users.toArray());
 | 
				
			||||||
    const handleSubscriptionClick = (subscriptionId) => {
 | 
					    const handleSubscriptionClick = (subscriptionId) => {
 | 
				
			||||||
        setSelectedSubscription(subscriptions.get(subscriptionId));
 | 
					        setSelectedSubscription(subscriptions.get(subscriptionId));
 | 
				
			||||||
        setPrefsOpen(false);
 | 
					        setPrefsOpen(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const handleSubscribeSubmit = (subscription, user) => {
 | 
					    const handleSubscribeSubmit = (subscription) => {
 | 
				
			||||||
        console.log(`[App] New subscription: ${subscription.id}`);
 | 
					        console.log(`[App] New subscription: ${subscription.id}`);
 | 
				
			||||||
        if (user !== null) {
 | 
					 | 
				
			||||||
            setUsers(prev => prev.add(user).clone());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        setSubscriptions(prev => prev.add(subscription).clone());
 | 
					        setSubscriptions(prev => prev.add(subscription).clone());
 | 
				
			||||||
        setSelectedSubscription(subscription);
 | 
					        setSelectedSubscription(subscription);
 | 
				
			||||||
        poll(subscription, user);
 | 
					        poll(subscription);
 | 
				
			||||||
        handleRequestPermission();
 | 
					        handleRequestPermission();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const handleDeleteNotification = (subscriptionId, notificationId) => {
 | 
					    const handleDeleteNotification = (subscriptionId, notificationId) => {
 | 
				
			||||||
| 
						 | 
					@ -80,9 +78,9 @@ const App = () => {
 | 
				
			||||||
        setPrefsOpen(true);
 | 
					        setPrefsOpen(true);
 | 
				
			||||||
        setSelectedSubscription(null);
 | 
					        setSelectedSubscription(null);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const poll = (subscription, user) => {
 | 
					    const poll = (subscription) => {
 | 
				
			||||||
        const since = subscription.last;
 | 
					        const since = subscription.last;
 | 
				
			||||||
        api.poll(subscription.baseUrl, subscription.topic, since, user)
 | 
					        api.poll(subscription.baseUrl, subscription.topic, since)
 | 
				
			||||||
            .then(notifications => {
 | 
					            .then(notifications => {
 | 
				
			||||||
                setSubscriptions(prev => {
 | 
					                setSubscriptions(prev => {
 | 
				
			||||||
                    subscription.addNotifications(notifications);
 | 
					                    subscription.addNotifications(notifications);
 | 
				
			||||||
| 
						 | 
					@ -94,12 +92,10 @@ const App = () => {
 | 
				
			||||||
    // Define hooks: Note that the order of the hooks is important. The "loading" hooks
 | 
					    // Define hooks: Note that the order of the hooks is important. The "loading" hooks
 | 
				
			||||||
    // must be before the "saving" hooks.
 | 
					    // must be before the "saving" hooks.
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        // Load subscriptions and users
 | 
					        // Load subscriptions
 | 
				
			||||||
        const subscriptions = repository.loadSubscriptions();
 | 
					        const subscriptions = repository.loadSubscriptions();
 | 
				
			||||||
        const selectedSubscriptionId = repository.loadSelectedSubscriptionId();
 | 
					        const selectedSubscriptionId = repository.loadSelectedSubscriptionId();
 | 
				
			||||||
        const users = repository.loadUsers();
 | 
					 | 
				
			||||||
        setSubscriptions(subscriptions);
 | 
					        setSubscriptions(subscriptions);
 | 
				
			||||||
        setUsers(users);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Set selected subscription
 | 
					        // Set selected subscription
 | 
				
			||||||
        const maybeSelectedSubscription = subscriptions.get(selectedSubscriptionId);
 | 
					        const maybeSelectedSubscription = subscriptions.get(selectedSubscriptionId);
 | 
				
			||||||
| 
						 | 
					@ -109,8 +105,7 @@ const App = () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Poll all subscriptions
 | 
					        // Poll all subscriptions
 | 
				
			||||||
        subscriptions.forEach((subscriptionId, subscription) => {
 | 
					        subscriptions.forEach((subscriptionId, subscription) => {
 | 
				
			||||||
            const user = users.get(subscription.baseUrl); // May be null
 | 
					            poll(subscription);
 | 
				
			||||||
            poll(subscription, user);
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }, [/* initial render */]);
 | 
					    }, [/* initial render */]);
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
| 
						 | 
					@ -127,7 +122,6 @@ const App = () => {
 | 
				
			||||||
        connectionManager.refresh(subscriptions, users, handleNotification);
 | 
					        connectionManager.refresh(subscriptions, users, handleNotification);
 | 
				
			||||||
    }, [subscriptions, users]);
 | 
					    }, [subscriptions, users]);
 | 
				
			||||||
    useEffect(() => repository.saveSubscriptions(subscriptions), [subscriptions]);
 | 
					    useEffect(() => repository.saveSubscriptions(subscriptions), [subscriptions]);
 | 
				
			||||||
    useEffect(() => repository.saveUsers(users), [users]);
 | 
					 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        const subscriptionId = (selectedSubscription) ? selectedSubscription.id : "";
 | 
					        const subscriptionId = (selectedSubscription) ? selectedSubscription.id : "";
 | 
				
			||||||
        repository.saveSelectedSubscriptionId(subscriptionId)
 | 
					        repository.saveSelectedSubscriptionId(subscriptionId)
 | 
				
			||||||
| 
						 | 
					@ -140,7 +134,6 @@ const App = () => {
 | 
				
			||||||
                <CssBaseline/>
 | 
					                <CssBaseline/>
 | 
				
			||||||
                <ActionBar
 | 
					                <ActionBar
 | 
				
			||||||
                    selectedSubscription={selectedSubscription}
 | 
					                    selectedSubscription={selectedSubscription}
 | 
				
			||||||
                    users={users}
 | 
					 | 
				
			||||||
                    onClearAll={handleDeleteAllNotifications}
 | 
					                    onClearAll={handleDeleteAllNotifications}
 | 
				
			||||||
                    onUnsubscribe={handleUnsubscribe}
 | 
					                    onUnsubscribe={handleUnsubscribe}
 | 
				
			||||||
                    onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
 | 
					                    onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,6 @@ import api from "../app/Api";
 | 
				
			||||||
const IconSubscribeSettings = (props) => {
 | 
					const IconSubscribeSettings = (props) => {
 | 
				
			||||||
    const [open, setOpen] = useState(false);
 | 
					    const [open, setOpen] = useState(false);
 | 
				
			||||||
    const anchorRef = useRef(null);
 | 
					    const anchorRef = useRef(null);
 | 
				
			||||||
    const users = props.users;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleToggle = () => {
 | 
					    const handleToggle = () => {
 | 
				
			||||||
        setOpen((prevOpen) => !prevOpen);
 | 
					        setOpen((prevOpen) => !prevOpen);
 | 
				
			||||||
| 
						 | 
					@ -40,8 +39,7 @@ const IconSubscribeSettings = (props) => {
 | 
				
			||||||
    const handleSendTestMessage = () => {
 | 
					    const handleSendTestMessage = () => {
 | 
				
			||||||
        const baseUrl = props.subscription.baseUrl;
 | 
					        const baseUrl = props.subscription.baseUrl;
 | 
				
			||||||
        const topic = props.subscription.topic;
 | 
					        const topic = props.subscription.topic;
 | 
				
			||||||
        const user = users.get(baseUrl); // May be null
 | 
					        api.publish(baseUrl, topic,
 | 
				
			||||||
        api.publish(baseUrl, topic, user,
 | 
					 | 
				
			||||||
            `This is a test notification sent by the ntfy Web UI at ${new Date().toString()}.`); // FIXME result ignored
 | 
					            `This is a test notification sent by the ntfy Web UI at ${new Date().toString()}.`); // FIXME result ignored
 | 
				
			||||||
        setOpen(false);
 | 
					        setOpen(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,9 +57,9 @@ const NavList = (props) => {
 | 
				
			||||||
        setSubscribeDialogOpen(false);
 | 
					        setSubscribeDialogOpen(false);
 | 
				
			||||||
        setSubscribeDialogKey(prev => prev+1);
 | 
					        setSubscribeDialogKey(prev => prev+1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const handleSubscribeSubmit = (subscription, user) => {
 | 
					    const handleSubscribeSubmit = (subscription) => {
 | 
				
			||||||
        handleSubscribeReset();
 | 
					        handleSubscribeReset();
 | 
				
			||||||
        props.onSubscribeSubmit(subscription, user);
 | 
					        props.onSubscribeSubmit(subscription);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const showSubscriptionsList = props.subscriptions.size() > 0;
 | 
					    const showSubscriptionsList = props.subscriptions.size() > 0;
 | 
				
			||||||
    const showGrantPermissionsBox = props.subscriptions.size() > 0 && !props.notificationsGranted;
 | 
					    const showGrantPermissionsBox = props.subscriptions.size() > 0 && !props.notificationsGranted;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,18 @@
 | 
				
			||||||
import * as React from 'react';
 | 
					import * as React from 'react';
 | 
				
			||||||
import {useState} from 'react';
 | 
					import {useEffect, useState} from 'react';
 | 
				
			||||||
import {FormControl, Select, Stack, Table, TableBody, TableCell, TableHead, TableRow} from "@mui/material";
 | 
					import {
 | 
				
			||||||
 | 
					    CardActions,
 | 
				
			||||||
 | 
					    CardContent,
 | 
				
			||||||
 | 
					    FormControl,
 | 
				
			||||||
 | 
					    Select,
 | 
				
			||||||
 | 
					    Stack,
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    TableBody,
 | 
				
			||||||
 | 
					    TableCell,
 | 
				
			||||||
 | 
					    TableHead,
 | 
				
			||||||
 | 
					    TableRow,
 | 
				
			||||||
 | 
					    useMediaQuery
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
import Typography from "@mui/material/Typography";
 | 
					import Typography from "@mui/material/Typography";
 | 
				
			||||||
import Paper from "@mui/material/Paper";
 | 
					import Paper from "@mui/material/Paper";
 | 
				
			||||||
import repository from "../app/Repository";
 | 
					import repository from "../app/Repository";
 | 
				
			||||||
| 
						 | 
					@ -11,6 +23,15 @@ import IconButton from "@mui/material/IconButton";
 | 
				
			||||||
import Container from "@mui/material/Container";
 | 
					import Container from "@mui/material/Container";
 | 
				
			||||||
import TextField from "@mui/material/TextField";
 | 
					import TextField from "@mui/material/TextField";
 | 
				
			||||||
import MenuItem from "@mui/material/MenuItem";
 | 
					import MenuItem from "@mui/material/MenuItem";
 | 
				
			||||||
 | 
					import Card from "@mui/material/Card";
 | 
				
			||||||
 | 
					import Button from "@mui/material/Button";
 | 
				
			||||||
 | 
					import db from "../app/db";
 | 
				
			||||||
 | 
					import {useLiveQuery} from "dexie-react-hooks";
 | 
				
			||||||
 | 
					import theme from "./theme";
 | 
				
			||||||
 | 
					import Dialog from "@mui/material/Dialog";
 | 
				
			||||||
 | 
					import DialogTitle from "@mui/material/DialogTitle";
 | 
				
			||||||
 | 
					import DialogContent from "@mui/material/DialogContent";
 | 
				
			||||||
 | 
					import DialogActions from "@mui/material/DialogActions";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Preferences = (props) => {
 | 
					const Preferences = (props) => {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
| 
						 | 
					@ -26,7 +47,7 @@ const Preferences = (props) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Notifications = (props) => {
 | 
					const Notifications = (props) => {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Paper sx={{p: 3}}>
 | 
					        <Card sx={{p: 3}}>
 | 
				
			||||||
            <Typography variant="h5">
 | 
					            <Typography variant="h5">
 | 
				
			||||||
                Notifications
 | 
					                Notifications
 | 
				
			||||||
            </Typography>
 | 
					            </Typography>
 | 
				
			||||||
| 
						 | 
					@ -34,7 +55,7 @@ const Notifications = (props) => {
 | 
				
			||||||
                <MinPriority/>
 | 
					                <MinPriority/>
 | 
				
			||||||
                <DeleteAfter/>
 | 
					                <DeleteAfter/>
 | 
				
			||||||
            </PrefGroup>
 | 
					            </PrefGroup>
 | 
				
			||||||
        </Paper>
 | 
					        </Card>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,7 +87,7 @@ const DeleteAfter = () => {
 | 
				
			||||||
        repository.setDeleteAfter(ev.target.value);
 | 
					        repository.setDeleteAfter(ev.target.value);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Pref title="Minimum priority">
 | 
					        <Pref title="Delete notifications">
 | 
				
			||||||
            <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
 | 
					            <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
 | 
				
			||||||
                <Select value={deleteAfter} onChange={handleChange}>
 | 
					                <Select value={deleteAfter} onChange={handleChange}>
 | 
				
			||||||
                    <MenuItem value={0}>Never</MenuItem>
 | 
					                    <MenuItem value={0}>Never</MenuItem>
 | 
				
			||||||
| 
						 | 
					@ -139,22 +160,81 @@ const DefaultServer = (props) => {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Users = (props) => {
 | 
					const Users = (props) => {
 | 
				
			||||||
 | 
					    const [dialogKey, setDialogKey] = useState(0);
 | 
				
			||||||
 | 
					    const [dialogOpen, setDialogOpen] = useState(false);
 | 
				
			||||||
 | 
					    const users = useLiveQuery(() => db.users.toArray());
 | 
				
			||||||
 | 
					    const handleAddClick = () => {
 | 
				
			||||||
 | 
					        setDialogKey(prev => prev+1);
 | 
				
			||||||
 | 
					        setDialogOpen(true);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const handleDialogCancel = () => {
 | 
				
			||||||
 | 
					        setDialogOpen(false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const handleDialogSubmit = async (user) => {
 | 
				
			||||||
 | 
					        setDialogOpen(false);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await db.users.add(user);
 | 
				
			||||||
 | 
					            console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} added`);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            console.log(`[Preferences] Error adding user.`, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Paper sx={{p: 3}}>
 | 
					        <Card sx={{p: 3}}>
 | 
				
			||||||
 | 
					            <CardContent>
 | 
				
			||||||
                <Typography variant="h5">
 | 
					                <Typography variant="h5">
 | 
				
			||||||
                    Manage users
 | 
					                    Manage users
 | 
				
			||||||
                </Typography>
 | 
					                </Typography>
 | 
				
			||||||
                <Paragraph>
 | 
					                <Paragraph>
 | 
				
			||||||
                You may manage users for your protected topics here. Please note that since this is a client
 | 
					                    Add/remove users for your protected topics here. Please note that username and password are
 | 
				
			||||||
                application only, username and password are stored in the browser's local storage.
 | 
					                    stored in the browser's local storage.
 | 
				
			||||||
                </Paragraph>
 | 
					                </Paragraph>
 | 
				
			||||||
            <UserTable/>
 | 
					                {users?.length > 0 && <UserTable users={users}/>}
 | 
				
			||||||
        </Paper>
 | 
					            </CardContent>
 | 
				
			||||||
 | 
					            <CardActions>
 | 
				
			||||||
 | 
					                <Button onClick={handleAddClick}>Add user</Button>
 | 
				
			||||||
 | 
					                <UserDialog
 | 
				
			||||||
 | 
					                    key={`userAddDialog${dialogKey}`}
 | 
				
			||||||
 | 
					                    open={dialogOpen}
 | 
				
			||||||
 | 
					                    user={null}
 | 
				
			||||||
 | 
					                    users={users}
 | 
				
			||||||
 | 
					                    onCancel={handleDialogCancel}
 | 
				
			||||||
 | 
					                    onSubmit={handleDialogSubmit}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </CardActions>
 | 
				
			||||||
 | 
					        </Card>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const UserTable = () => {
 | 
					const UserTable = (props) => {
 | 
				
			||||||
    const users = repository.loadUsers();
 | 
					    const [dialogKey, setDialogKey] = useState(0);
 | 
				
			||||||
 | 
					    const [dialogOpen, setDialogOpen] = useState(false);
 | 
				
			||||||
 | 
					    const [dialogUser, setDialogUser] = useState(null);
 | 
				
			||||||
 | 
					    const handleEditClick = (user) => {
 | 
				
			||||||
 | 
					        setDialogKey(prev => prev+1);
 | 
				
			||||||
 | 
					        setDialogUser(user);
 | 
				
			||||||
 | 
					        setDialogOpen(true);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const handleDialogCancel = () => {
 | 
				
			||||||
 | 
					        setDialogOpen(false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const handleDialogSubmit = async (user) => {
 | 
				
			||||||
 | 
					        setDialogOpen(false);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await db.users.put(user); // put() is an upsert
 | 
				
			||||||
 | 
					            console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            console.log(`[Preferences] Error updating user.`, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const handleDeleteClick = async (user) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await db.users.delete(user.baseUrl);
 | 
				
			||||||
 | 
					            console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Table size="small">
 | 
					        <Table size="small">
 | 
				
			||||||
            <TableHead>
 | 
					            <TableHead>
 | 
				
			||||||
| 
						 | 
					@ -165,27 +245,106 @@ const UserTable = () => {
 | 
				
			||||||
                </TableRow>
 | 
					                </TableRow>
 | 
				
			||||||
            </TableHead>
 | 
					            </TableHead>
 | 
				
			||||||
            <TableBody>
 | 
					            <TableBody>
 | 
				
			||||||
                    {users.map((user, i) => (
 | 
					                {props.users?.map(user => (
 | 
				
			||||||
                    <TableRow
 | 
					                    <TableRow
 | 
				
			||||||
                            key={i}
 | 
					                        key={user.baseUrl}
 | 
				
			||||||
                        sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
 | 
					                        sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <TableCell component="th" scope="row">{user.username}</TableCell>
 | 
					                        <TableCell component="th" scope="row">{user.username}</TableCell>
 | 
				
			||||||
                        <TableCell>{user.baseUrl}</TableCell>
 | 
					                        <TableCell>{user.baseUrl}</TableCell>
 | 
				
			||||||
                        <TableCell align="right">
 | 
					                        <TableCell align="right">
 | 
				
			||||||
                                <IconButton>
 | 
					                            <IconButton onClick={() => handleEditClick(user)}>
 | 
				
			||||||
                                <EditIcon/>
 | 
					                                <EditIcon/>
 | 
				
			||||||
                            </IconButton>
 | 
					                            </IconButton>
 | 
				
			||||||
                                <IconButton>
 | 
					                            <IconButton onClick={() => handleDeleteClick(user)}>
 | 
				
			||||||
                                <CloseIcon />
 | 
					                                <CloseIcon />
 | 
				
			||||||
                            </IconButton>
 | 
					                            </IconButton>
 | 
				
			||||||
                        </TableCell>
 | 
					                        </TableCell>
 | 
				
			||||||
                    </TableRow>
 | 
					                    </TableRow>
 | 
				
			||||||
                ))}
 | 
					                ))}
 | 
				
			||||||
            </TableBody>
 | 
					            </TableBody>
 | 
				
			||||||
 | 
					            <UserDialog
 | 
				
			||||||
 | 
					                key={`userEditDialog${dialogKey}`}
 | 
				
			||||||
 | 
					                open={dialogOpen}
 | 
				
			||||||
 | 
					                user={dialogUser}
 | 
				
			||||||
 | 
					                users={props.users}
 | 
				
			||||||
 | 
					                onCancel={handleDialogCancel}
 | 
				
			||||||
 | 
					                onSubmit={handleDialogSubmit}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
        </Table>
 | 
					        </Table>
 | 
				
			||||||
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const UserDialog = (props) => {
 | 
				
			||||||
 | 
					    const [baseUrl, setBaseUrl] = useState("");
 | 
				
			||||||
 | 
					    const [username, setUsername] = useState("");
 | 
				
			||||||
 | 
					    const [password, setPassword] = useState("");
 | 
				
			||||||
 | 
					    const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
 | 
				
			||||||
 | 
					    const editMode = props.user !== null;
 | 
				
			||||||
 | 
					    const addButtonEnabled = (() => {
 | 
				
			||||||
 | 
					        if (editMode) {
 | 
				
			||||||
 | 
					            return username.length > 0 && password.length > 0;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        const baseUrlExists = props.users?.map(user => user.baseUrl).includes(baseUrl);
 | 
				
			||||||
 | 
					        return !baseUrlExists && username.length > 0 && password.length > 0;
 | 
				
			||||||
 | 
					    })();
 | 
				
			||||||
 | 
					    const handleSubmit = async () => {
 | 
				
			||||||
 | 
					        props.onSubmit({
 | 
				
			||||||
 | 
					            baseUrl: baseUrl,
 | 
				
			||||||
 | 
					            username: username,
 | 
				
			||||||
 | 
					            password: password
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (editMode) {
 | 
				
			||||||
 | 
					            setBaseUrl(props.user.baseUrl);
 | 
				
			||||||
 | 
					            setUsername(props.user.username);
 | 
				
			||||||
 | 
					            setPassword(props.user.password);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, []);
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
 | 
				
			||||||
 | 
					            <DialogTitle>{editMode ? "Edit user" : "Add user"}</DialogTitle>
 | 
				
			||||||
 | 
					            <DialogContent>
 | 
				
			||||||
 | 
					                {!editMode && <TextField
 | 
				
			||||||
 | 
					                    autoFocus
 | 
				
			||||||
 | 
					                    margin="dense"
 | 
				
			||||||
 | 
					                    id="baseUrl"
 | 
				
			||||||
 | 
					                    label="Service URL, e.g. https://ntfy.sh"
 | 
				
			||||||
 | 
					                    value={baseUrl}
 | 
				
			||||||
 | 
					                    onChange={ev => setBaseUrl(ev.target.value)}
 | 
				
			||||||
 | 
					                    type="url"
 | 
				
			||||||
 | 
					                    fullWidth
 | 
				
			||||||
 | 
					                    variant="standard"
 | 
				
			||||||
 | 
					                />}
 | 
				
			||||||
 | 
					                <TextField
 | 
				
			||||||
 | 
					                    autoFocus={editMode}
 | 
				
			||||||
 | 
					                    margin="dense"
 | 
				
			||||||
 | 
					                    id="username"
 | 
				
			||||||
 | 
					                    label="Username, e.g. phil"
 | 
				
			||||||
 | 
					                    value={username}
 | 
				
			||||||
 | 
					                    onChange={ev => setUsername(ev.target.value)}
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    fullWidth
 | 
				
			||||||
 | 
					                    variant="standard"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <TextField
 | 
				
			||||||
 | 
					                    margin="dense"
 | 
				
			||||||
 | 
					                    id="password"
 | 
				
			||||||
 | 
					                    label="Password"
 | 
				
			||||||
 | 
					                    type="password"
 | 
				
			||||||
 | 
					                    value={password}
 | 
				
			||||||
 | 
					                    onChange={ev => setPassword(ev.target.value)}
 | 
				
			||||||
 | 
					                    fullWidth
 | 
				
			||||||
 | 
					                    variant="standard"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </DialogContent>
 | 
				
			||||||
 | 
					            <DialogActions>
 | 
				
			||||||
 | 
					                <Button onClick={props.onCancel}>Cancel</Button>
 | 
				
			||||||
 | 
					                <Button onClick={handleSubmit} disabled={!addButtonEnabled}>{editMode ? "Save" : "Add"}</Button>
 | 
				
			||||||
 | 
					            </DialogActions>
 | 
				
			||||||
 | 
					        </Dialog>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Preferences;
 | 
					export default Preferences;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,8 +12,8 @@ import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/mate
 | 
				
			||||||
import theme from "./theme";
 | 
					import theme from "./theme";
 | 
				
			||||||
import api from "../app/Api";
 | 
					import api from "../app/Api";
 | 
				
			||||||
import {topicUrl, validTopic, validUrl} from "../app/utils";
 | 
					import {topicUrl, validTopic, validUrl} from "../app/utils";
 | 
				
			||||||
import User from "../app/User";
 | 
					 | 
				
			||||||
import Box from "@mui/material/Box";
 | 
					import Box from "@mui/material/Box";
 | 
				
			||||||
 | 
					import db from "../app/db";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultBaseUrl = "http://127.0.0.1"
 | 
					const defaultBaseUrl = "http://127.0.0.1"
 | 
				
			||||||
//const defaultBaseUrl = "https://ntfy.sh"
 | 
					//const defaultBaseUrl = "https://ntfy.sh"
 | 
				
			||||||
| 
						 | 
					@ -23,10 +23,10 @@ const SubscribeDialog = (props) => {
 | 
				
			||||||
    const [topic, setTopic] = useState("");
 | 
					    const [topic, setTopic] = useState("");
 | 
				
			||||||
    const [showLoginPage, setShowLoginPage] = useState(false);
 | 
					    const [showLoginPage, setShowLoginPage] = useState(false);
 | 
				
			||||||
    const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
 | 
					    const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
 | 
				
			||||||
    const handleSuccess = (user) => {
 | 
					    const handleSuccess = () => {
 | 
				
			||||||
        const actualBaseUrl = (baseUrl) ? baseUrl : defaultBaseUrl; // FIXME
 | 
					        const actualBaseUrl = (baseUrl) ? baseUrl : defaultBaseUrl; // FIXME
 | 
				
			||||||
        const subscription = new Subscription(actualBaseUrl, topic);
 | 
					        const subscription = new Subscription(actualBaseUrl, topic);
 | 
				
			||||||
        props.onSuccess(subscription, user);
 | 
					        props.onSuccess(subscription);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
 | 
					        <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
 | 
				
			||||||
| 
						 | 
					@ -65,7 +65,7 @@ const SubscribePage = (props) => {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for anonymous user`);
 | 
					        console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for anonymous user`);
 | 
				
			||||||
        props.onSuccess(null);
 | 
					        props.onSuccess();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const handleUseAnotherChanged = (e) => {
 | 
					    const handleUseAnotherChanged = (e) => {
 | 
				
			||||||
        props.setBaseUrl("");
 | 
					        props.setBaseUrl("");
 | 
				
			||||||
| 
						 | 
					@ -129,7 +129,7 @@ const LoginPage = (props) => {
 | 
				
			||||||
    const baseUrl = (props.baseUrl) ? props.baseUrl : defaultBaseUrl;
 | 
					    const baseUrl = (props.baseUrl) ? props.baseUrl : defaultBaseUrl;
 | 
				
			||||||
    const topic = props.topic;
 | 
					    const topic = props.topic;
 | 
				
			||||||
    const handleLogin = async () => {
 | 
					    const handleLogin = async () => {
 | 
				
			||||||
        const user = new User(baseUrl, username, password);
 | 
					        const user = {baseUrl, username, password};
 | 
				
			||||||
        const success = await api.auth(baseUrl, topic, user);
 | 
					        const success = await api.auth(baseUrl, topic, user);
 | 
				
			||||||
        if (!success) {
 | 
					        if (!success) {
 | 
				
			||||||
            console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`);
 | 
					            console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`);
 | 
				
			||||||
| 
						 | 
					@ -137,7 +137,8 @@ const LoginPage = (props) => {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`);
 | 
					        console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`);
 | 
				
			||||||
        props.onSuccess(user);
 | 
					        db.users.put(user);
 | 
				
			||||||
 | 
					        props.onSuccess();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import {styled} from "@mui/styles";
 | 
					 | 
				
			||||||
import Typography from "@mui/material/Typography";
 | 
					import Typography from "@mui/material/Typography";
 | 
				
			||||||
import theme from "./theme";
 | 
					import theme from "./theme";
 | 
				
			||||||
import Container from "@mui/material/Container";
 | 
					import Container from "@mui/material/Container";
 | 
				
			||||||
 | 
					import {styled} from "@mui/material";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Paragraph = styled(Typography)({
 | 
					export const Paragraph = styled(Typography)({
 | 
				
			||||||
  paddingTop: 8,
 | 
					  paddingTop: 8,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue