Просмотр исходного кода

Login page of "subscribe dialog", still WIP, but looking nice

Philipp Heckel 4 лет назад
Родитель
Сommit
6d343c0f1a

+ 2 - 1
server/server.go

@@ -860,8 +860,9 @@ func parseSince(r *http.Request, poll bool) (sinceTime, error) {
 }
 
 func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request) error {
-	w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
 	w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST")
+	w.Header().Set("Access-Control-Allow-Origin", "*")              // CORS, allow cross-origin requests
+	w.Header().Set("Access-Control-Allow-Headers", "Authorization") // CORS, allow auth
 	return nil
 }
 

+ 273 - 0
web/package-lock.json

@@ -11,6 +11,7 @@
         "@emotion/styled": "latest",
         "@mui/icons-material": "^5.4.2",
         "@mui/material": "latest",
+        "@mui/styles": "^5.4.2",
         "react": "latest",
         "react-dom": "latest",
         "react-router-dom": "^6.2.1",
@@ -2363,6 +2364,46 @@
         }
       }
     },
+    "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": {
       "version": "5.4.2",
       "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz",
@@ -5642,6 +5683,15 @@
         "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": {
       "version": "5.1.0",
       "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
@@ -8521,6 +8571,11 @@
       "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
       "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": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -9113,6 +9168,11 @@
         "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": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -10230,6 +10290,88 @@
         "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": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
@@ -16215,6 +16357,11 @@
       "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
       "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": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -19371,6 +19518,30 @@
         "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": {
       "version": "5.4.2",
       "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz",
@@ -21984,6 +22155,15 @@
         }
       }
     },
+    "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": {
       "version": "5.1.0",
       "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
@@ -24238,6 +24418,11 @@
       "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
       "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": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -24661,6 +24846,11 @@
         "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": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -25539,6 +25729,84 @@
         "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": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
@@ -30352,6 +30620,11 @@
       "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
       "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": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

+ 1 - 0
web/package.json

@@ -12,6 +12,7 @@
     "@emotion/styled": "latest",
     "@mui/icons-material": "^5.4.2",
     "@mui/material": "latest",
+    "@mui/styles": "^5.4.2",
     "react": "latest",
     "react-dom": "latest",
     "react-router-dom": "^6.2.1",

+ 12 - 1
web/src/app/Api.js

@@ -23,7 +23,10 @@ class Api {
     async auth(baseUrl, topic, user) {
         const url = topicUrlAuth(baseUrl, topic);
         console.log(`[Api] Checking auth for ${url}`);
-        const response = await fetch(url);
+        const headers = this.maybeAddAuthorization({}, user);
+        const response = await fetch(url, {
+            headers: headers
+        });
         if (response.status >= 200 && response.status <= 299) {
             return true;
         } else if (!user && response.status === 404) {
@@ -33,6 +36,14 @@ class Api {
         }
         throw new Error(`Unexpected server response ${response.status}`);
     }
+
+    maybeAddAuthorization(headers, user) {
+        if (user) {
+            const encoded = new Buffer(`${user.username}:${user.password}`).toString('base64');
+            headers['Authorization'] = `Basic ${encoded}`;
+        }
+        return headers;
+    }
 }
 
 const api = new Api();

+ 27 - 4
web/src/app/Repository.js

@@ -4,11 +4,11 @@ import Subscriptions from "./Subscriptions";
 export class Repository {
     loadSubscriptions() {
         console.log(`[Repository] Loading subscriptions from localStorage`);
-
         const subscriptions = new Subscriptions();
         const serialized = localStorage.getItem('subscriptions');
-        if (serialized === null) return subscriptions;
-
+        if (serialized === null) {
+            return subscriptions;
+        }
         try {
             const serializedSubscriptions = JSON.parse(serialized);
             serializedSubscriptions.forEach(s => {
@@ -26,7 +26,6 @@ export class Repository {
 
     saveSubscriptions(subscriptions) {
         console.log(`[Repository] Saving ${subscriptions.size()} subscription(s) to localStorage`);
-
         const serialized = JSON.stringify(subscriptions.map( (id, subscription) => {
             return {
                 baseUrl: subscription.baseUrl,
@@ -37,6 +36,30 @@ export class Repository {
         }));
         localStorage.setItem('subscriptions', serialized);
     }
+
+    loadUsers() {
+        console.log(`[Repository] Loading users from localStorage`);
+        const serialized = localStorage.getItem('users');
+        if (serialized === null) {
+            return {};
+        }
+        try {
+            return JSON.parse(serialized);
+        } catch (e) {
+            console.log(`[Repository] Unable to deserialize users: ${e.message}`);
+            return {};
+        }
+    }
+
+    saveUser(baseUrl, username, password) {
+        console.log(`[Repository] Saving users to localStorage`);
+        const users = this.loadUsers();
+        users[baseUrl] = {
+            username: username,
+            password: password
+        };
+        localStorage.setItem('users', users);
+    }
 }
 
 const repository = new Repository();

+ 33 - 4
web/src/components/SubscribeDialog.js

@@ -12,6 +12,7 @@ import {useMediaQuery} from "@mui/material";
 import theme from "./theme";
 import api from "../app/Api";
 import {topicUrl} from "../app/utils";
+import useStyles from "./styles";
 
 const defaultBaseUrl = "http://127.0.0.1"
 //const defaultBaseUrl = "https://ntfy.sh"
@@ -19,6 +20,7 @@ const defaultBaseUrl = "http://127.0.0.1"
 const SubscribeDialog = (props) => {
     const [baseUrl, setBaseUrl] = useState(defaultBaseUrl); // FIXME
     const [topic, setTopic] = useState("");
+    const [user, setUser] = useState(null);
     const [showLoginPage, setShowLoginPage] = useState(false);
     const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
     const handleCancel = () => {
@@ -45,8 +47,10 @@ const SubscribeDialog = (props) => {
                 onSubmit={handleSubmit}
             />}
             {showLoginPage && <LoginPage
+                baseUrl={baseUrl}
                 topic={topic}
                 onBack={() => setShowLoginPage(false)}
+                onSubmit={handleSubmit}
             />}
         </Dialog>
     );
@@ -82,6 +86,22 @@ const SubscribePage = (props) => {
 };
 
 const LoginPage = (props) => {
+    const styles = useStyles();
+    const [username, setUsername] = useState("");
+    const [password, setPassword] = useState("");
+    const [errorText, setErrorText] = useState("");
+    const baseUrl = props.baseUrl;
+    const topic = props.topic;
+    const handleLogin = async () => {
+        const user = {username: username, password: password};
+        const success = await api.auth(baseUrl, topic, user);
+        if (!success) {
+            console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`);
+            setErrorText(`User ${username} not authorized`);
+            return;
+        }
+        console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} successful for user ${username}`);
+    };
     return (
         <>
             <DialogTitle>Login required</DialogTitle>
@@ -95,6 +115,8 @@ const LoginPage = (props) => {
                     margin="dense"
                     id="username"
                     label="Username, e.g. phil"
+                    value={username}
+                    onChange={ev => setUsername(ev.target.value)}
                     type="text"
                     fullWidth
                     variant="standard"
@@ -104,14 +126,21 @@ const LoginPage = (props) => {
                     id="password"
                     label="Password"
                     type="password"
+                    value={password}
+                    onChange={ev => setPassword(ev.target.value)}
                     fullWidth
                     variant="standard"
                 />
             </DialogContent>
-            <DialogActions>
-                <Button onClick={props.onBack}>Back</Button>
-                <Button>Login</Button>
-            </DialogActions>
+            <div className={styles.bottomBar}>
+                <DialogContentText className={styles.statusText}>
+                    {errorText}
+                </DialogContentText>
+                <DialogActions>
+                    <Button onClick={props.onBack}>Back</Button>
+                    <Button onClick={handleLogin}>Login</Button>
+                </DialogActions>
+            </div>
         </>
     );
 };

+ 18 - 0
web/src/components/styles.js

@@ -0,0 +1,18 @@
+import {makeStyles} from "@mui/styles";
+
+const useStyles = makeStyles(theme => ({
+  bottomBar: {
+    display: 'flex',
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    paddingLeft: '24px',
+    paddingTop: '8px 24px',
+    paddingBottom: '8px 24px',
+  },
+  statusText: {
+    margin: '0px',
+    paddingTop: '8px',
+  }
+}));
+
+export default useStyles;

+ 0 - 1
web/src/components/theme.js

@@ -1,7 +1,6 @@
 import { red } from '@mui/material/colors';
 import { createTheme } from '@mui/material/styles';
 
-// A custom theme for this app
 const theme = createTheme({
   palette: {
     primary: {