diff --git a/client/angular.json b/client/angular.json index 7b1f113..31dac2f 100644 --- a/client/angular.json +++ b/client/angular.json @@ -25,6 +25,7 @@ "src/assets" ], "styles": [ + "@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], "scripts": [] @@ -87,6 +88,7 @@ "src/assets" ], "styles": [ + "@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], "scripts": [] diff --git a/client/package-lock.json b/client/package-lock.json index 30686e2..0553cad 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^16.0.0", + "@angular/cdk": "^16.0.0", "@angular/common": "^16.0.0", "@angular/compiler": "^16.0.0", "@angular/core": "^16.0.0", "@angular/forms": "^16.0.0", + "@angular/material": "^16.0.0", "@angular/platform-browser": "^16.0.0", "@angular/platform-browser-dynamic": "^16.0.0", "@angular/router": "^16.0.0", @@ -260,6 +262,46 @@ "@angular/core": "16.0.0" } }, + "node_modules/@angular/cdk": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.0.0.tgz", + "integrity": "sha512-3hiNRB5kBOYv+MJRlpLX++KzY96mTivE7BXM/pNoR4Hiq7aBnoCtp0zbTc9J7gPn8POX6t/4WfAogslbkvQXSg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cdk/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "optional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "optional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/@angular/cli": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.0.0.tgz", @@ -427,6 +469,70 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.0.0.tgz", + "integrity": "sha512-FIkBjcygdgN0vDnO+e3dWrjw/+U805zGC9dh7lwSkpbgQp89ZYVk4Wygk5+I6oSKn1oR9GJpIe/j8+kP6CMA4w==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/auto-init": "15.0.0-canary.51f9c0c28.0", + "@material/banner": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/button": "15.0.0-canary.51f9c0c28.0", + "@material/card": "15.0.0-canary.51f9c0c28.0", + "@material/checkbox": "15.0.0-canary.51f9c0c28.0", + "@material/chips": "15.0.0-canary.51f9c0c28.0", + "@material/circular-progress": "15.0.0-canary.51f9c0c28.0", + "@material/data-table": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dialog": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/drawer": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/fab": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/floating-label": "15.0.0-canary.51f9c0c28.0", + "@material/form-field": "15.0.0-canary.51f9c0c28.0", + "@material/icon-button": "15.0.0-canary.51f9c0c28.0", + "@material/image-list": "15.0.0-canary.51f9c0c28.0", + "@material/layout-grid": "15.0.0-canary.51f9c0c28.0", + "@material/line-ripple": "15.0.0-canary.51f9c0c28.0", + "@material/linear-progress": "15.0.0-canary.51f9c0c28.0", + "@material/list": "15.0.0-canary.51f9c0c28.0", + "@material/menu": "15.0.0-canary.51f9c0c28.0", + "@material/menu-surface": "15.0.0-canary.51f9c0c28.0", + "@material/notched-outline": "15.0.0-canary.51f9c0c28.0", + "@material/radio": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/segmented-button": "15.0.0-canary.51f9c0c28.0", + "@material/select": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/slider": "15.0.0-canary.51f9c0c28.0", + "@material/snackbar": "15.0.0-canary.51f9c0c28.0", + "@material/switch": "15.0.0-canary.51f9c0c28.0", + "@material/tab": "15.0.0-canary.51f9c0c28.0", + "@material/tab-bar": "15.0.0-canary.51f9c0c28.0", + "@material/tab-indicator": "15.0.0-canary.51f9c0c28.0", + "@material/tab-scroller": "15.0.0-canary.51f9c0c28.0", + "@material/textfield": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tooltip": "15.0.0-canary.51f9c0c28.0", + "@material/top-app-bar": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^16.0.0 || ^17.0.0", + "@angular/cdk": "16.0.0", + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "@angular/forms": "^16.0.0 || ^17.0.0", + "@angular/platform-browser": "^16.0.0 || ^17.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.0.0.tgz", @@ -2746,6 +2852,758 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@material/animation": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-YNCPsf7yoRmduOd+7H+PlQNci7gdvjkae92XQTY8cnYFy9XTbgFCmwCbJQn5AgxHtVuMssKl2sroszgUmF9aoQ==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/auto-init": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-U75Y3Pm0OfKi2b8UUsWn+X7s+LGutqisOtgFytPJ1Su4UcB8Mh3nr/4+MOdb3DT0Z1bNdq+43JhEjTi9oijibg==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/banner": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-y5Q0MpuiGpIIN9UldCEv2eliYq0pHLTHkqHnjAsImzHRT8vnCqIQeeROwOlrLwBl3u/kpeLhSZzJ1w9OPkZeRQ==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/button": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/base": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-nqFSLpkBRlTNS0Z2ydJQtghRH8Hz3gUexQG/mP+pBv8afqOc0teHwug3M5btdteoXmCHiRpDkP/CHbbZclClHg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/button": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-eLiLRm8uKERgpnnoUKaG98O5Kh5lQljaAWz/RBiKiwia+przJSikMtsBrBMV8BrwKg5wOIFUrUsJOP0q2YcYyw==", + "dependencies": { + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/focus-ring": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/card": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-scGd0XzNNarVk8S0InGbpX04ApYYoRND5udDx1vRtU2jzu97j39dd+cwusLWtrtR+FGrS7C4A7yA08NI9BoTEQ==", + "dependencies": { + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/checkbox": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-GEqx+GQcgslxRLsLpbj/ddd4rSgCQxtikkvnKUBd8nw2bvNm/6HUXGl8IaTU2nj36SS5zNLdiDJLRI+k+G4C6g==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/focus-ring": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/chips": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-a3lGEXw/Y1+KqMuvakzcb9rCis5R+eLQ7uHSrC0WjGgX1oU9gWpM0/p05/rTknHbIji5sOHr4SxC48cRCBCSSA==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/checkbox": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/focus-ring": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/circular-progress": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-ApIq1SK5B0aYqsyKc7byzWVYg0zk9uFziryBOAKfyd96caG7BfzEfheOWK2PPEQGo9cA/m08ZZJzz6JDmJNMLA==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/progress-indicator": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/data-table": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-5TajNrJqVqr49PeyHSXf3YzPpAF2xPz4/r2TaPbLd/gvw9/z7dN7QiiQkSQ15oYzjueRwh+JHMwLfY5gakWC2Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/checkbox": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/icon-button": "15.0.0-canary.51f9c0c28.0", + "@material/linear-progress": "15.0.0-canary.51f9c0c28.0", + "@material/list": "15.0.0-canary.51f9c0c28.0", + "@material/menu": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/select": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/density": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-7hc7vuh4RajlgNTtHYWZzUeMC6A7w4ePJGavslqAawPb84bo0M/3Idm6OmoW1c6dyDcQo59KOiHMsmnlgDWdjg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dialog": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-60o18+BmPLoGLLZeJVEwexYr89dsZGVCRC0N3zASQTNVz3XWtyjgeragAoly4NTUIICJ4PMRjMEH7YhJ/Qg/BA==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/button": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/icon-button": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dom": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-SiJRfAgfBBffWVuc5yvaYiRqjli7+4Mx2BYZ69AT8J5k6Uj0Lt8ppd3T3a0zhfF0EXwTlHdeunP+X0keQFcRdw==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/drawer": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-GXq6jHZHMX/6A0z1YEYkI9809sxC/r7g/uJxF3CWC9/8/d2TRYRATAccnllgsyK06XhrnFPf6DtXhyPr1icOzQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/list": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/elevation": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-bBsfYZBi/k380Cdxob+xnNhrmy6OZ/7k3q3xfT9gjWqE7TveAkOukLfMkRMyIltdl40jI7G9ewoW4oN8F0qOmg==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/fab": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-QRanDJSZDMxMXcIyXWfspSXb31iusve9fB6D4SgeJ1/bZsvy/I3t9wXyl06sJBYJhRzIXOiORyoVUjal/OPCpg==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/focus-ring": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/feature-targeting": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-ebWpsBhpOU3uwu46OsT4Gv7OUjPP9eQV17b5Ze5y8yYn3FcdKyZI3nBQH7gjlOpH0xhz6DNq/Dx1z7DVL5/3NQ==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/floating-label": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-4T1uKFXIUAg6lP2+kY6QcHdIl6exqsgDkOUlkPnAvXUZmGWnL9NlTSv3c6hdTcattM8XfcdPrjHozS1ltiIiFA==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/focus-ring": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-QGGMoJf38IHwHSWUxvcsiB3AOfZ7RtqF1NNwQOy7dTycXwzpf7gA+tYlbr4KFHg5rz0PBd6C7yAdPRK+RAO+Eg==", + "dependencies": { + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0" + } + }, + "node_modules/@material/form-field": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-sHn97Z9f9YUioeGve/EANIAqddH4YslybHbFTMFx9AA4CyFITEXuNI7GC7J7BzkI10rcjmatWUGx0JRal1kVGw==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/icon-button": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-/qJscrbn45yZN+yno0NHBdHGttJ/0kgOIY34OIb9LeGDnVe+snsWrA8DkiaPeYJTLge+ACtgfDdk/iN+6vvckg==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/focus-ring": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/image-list": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-4IhKYtrF7qA2e7qKt1B1XXg9mEJy7eyoWO0iQrqK8jtYyAaDMmsi24fAvlubczO+Qj02RFCqPCO2j23RCojYnA==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/layout-grid": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-ihb7N9hLB4CSbrGC2b70zaFMiJo8DJTZAy99AmAdtpo9CdGlkXDxlyEDYI+NkFHMeyoL85m9dEqGgLgiEGlO5Q==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/line-ripple": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-4LNf92KjQ+RnPv3qb9R4gwO0zWMbMfCYnuvA7mXysRChL8f/3uuvTLySRuobMeE8HndErAMylHdWdtgTQ+6GSw==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/linear-progress": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-Aoi03JbUspj+08fLx2Y72jguD3uYkXHE9RNk9R2cqcjtahzGDdL7tDCZEqzmepSvdnDezHsWIBvkiLHuJP7WGA==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/progress-indicator": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/list": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-hMxDwAgs3NMi5z6XZvBjiopmdkApWZlKt1EOurPyuMDAKu/f0Dpu4lttNXmC1Qhg/Z82inMxUgYVWv4eMok4cQ==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-Ua2KDrT+VW6a270QNXqHwC/WU0oO88THXV8XV50kZtFHipe70ZB8sBcqKYM3zntbhI0q05Dl+R+z1icJhpw2hA==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/list": "15.0.0-canary.51f9c0c28.0", + "@material/menu-surface": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu-surface": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-aDGmwcZ2nI5E+gJ7jn7QrihJ2p33nU5EuKs9cpBZSmYZKm1wcfFcb2CR07ui1WBT/zaZ9CNaXnIdcJsKY76mSg==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/notched-outline": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-5fXgCxrEjoV9WKN2+4ru1mA8OIbnrrK/Zq5RT6QJgAbW/YRyMjtMZKIIbGhlL62/q4pZQCbYZaMmDskTkhiylg==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/floating-label": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/progress-indicator": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-teQwuo+45CKDRT17Ly9DOYJBHfoIJhGYbRsTCjgm6yGfWER3htP77NseTD8w1iaVHM09D9hjhvji3BQGD5s6Pg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/radio": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-8zYl1iQz4E8l+iI9m2rnf1pO7wZJZdvXjxjk6Wx608Rac5ntPFn8ky8eBVRa3AnCLFJlLMPRfGPwCO8TJQutKg==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/focus-ring": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/ripple": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-q5Qk3a5kxlC3zvVRfDfpJBXMjPReey/xCFgov9NskbYyAWvGDUkizHxQqdBpZtaczMgFLG4locaM7lHCD6sGgw==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/rtl": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-osFXl265Tg0tMiLts31UBeV+hRogXdDclsMRDrIsfs1faSXkBANFf8pPBk9Iyo70zx3X4fhFDY0PnsIkex3kug==", + "dependencies": { + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/segmented-button": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-rYIyfIzf/Ev2d1/4OwkmzGvZ/jlQZDwR6i1FJA7pYbpYyVAI+8CxBL0ea02iUZfvTG8KQcx0fBW5HOlYSHayYQ==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/touch-target": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/select": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-DpfJ3ZgMEGfgRLDniGmkNlG/Wwrmd6wYI1bQiq2GhlkA3vsDCuCa/EyNkCDTCqhPDUzvWVJOH3kqUm4piSyDrA==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/floating-label": "15.0.0-canary.51f9c0c28.0", + "@material/line-ripple": "15.0.0-canary.51f9c0c28.0", + "@material/list": "15.0.0-canary.51f9c0c28.0", + "@material/menu": "15.0.0-canary.51f9c0c28.0", + "@material/menu-surface": "15.0.0-canary.51f9c0c28.0", + "@material/notched-outline": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/shape": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-SdJEmNYyhvlITE2sZJuX3yygl/3l6N1Md4mKT2tclkY/eYf+HnHZRJvUpE2KnWMnIAvVdAljhU+eYZfalAvH7A==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/slider": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-LxZRzXMRjc9mmMz3PYY6QOl85WHH9AoBox3+kNaB5J7Q9J/LaCRdqHHpOvh05n+MVZcecaV+UdADxQ8dJbR0Fg==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/snackbar": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-51MLSplWiVjzVoEEkvRFEqFu/q4ra6ai03/rG/em9kmr8VI0hpvyxLEZ4yIRue+7SSjt5Bsou3hUdL0Q8Q20iw==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/button": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/icon-button": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/switch": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-kRTmekNkKkiHgKuLb/GcnPvZ2WVvoS5U66NxGsRhs2ixKbWwa6qsBtXc5V7kNEsUDVgetUG5xDLye+ofQoyxEg==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/focus-ring": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-Z8tepFaE46qTjKi+NLb/tY6EXxFNxhx3VS4vGbC/ZktuQpPJx3POU1HHs7pQzhRIh6fDTreWAoXZo5WTj8miZg==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/focus-ring": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/tab-indicator": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-bar": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-C0lE3ye6QycNNTbzhgCe5lUj01Z5tZpm/BF/aw2z9N49cXVB0Ka59cn4jktOs7wTzoW0OQOilhs72/xf0PR7Cw==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/tab": "15.0.0-canary.51f9c0c28.0", + "@material/tab-indicator": "15.0.0-canary.51f9c0c28.0", + "@material/tab-scroller": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-indicator": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-C5DBeiVgR75+Du4MdSdfPmBJ/AYhMyvM2nG+2PBqR5h1recM5+ocypeEtmZI9foJaPRHypc3fNTW/mdxV7c3dA==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-scroller": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-00JZXIbnVvrRPoY6N4MNlS/L2sHZOCj8tzCLEszJZCg722onlN8YDyCh51t0mdcfzw5N1ULvGnsa5BpeWeW3vw==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/tab": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/textfield": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-e8HjmBiRFI7/4Sg6c8XsshYLZsLn7qwKrJZWR7gA9LcYCJZDN1zaOVC5/Cx6lwS+R/hShtJh85ob3HJhdKmKRw==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/density": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/floating-label": "15.0.0-canary.51f9c0c28.0", + "@material/line-ripple": "15.0.0-canary.51f9c0c28.0", + "@material/notched-outline": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/theme": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-E9W/SM3YAsz4qfYuNZby9bq4TvrgPEjsDitj842FmlTmHXZIoQqizvnX6mtzGq+aPmlSF+nDG+uSJTg37kVQWA==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tokens": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-rXjIFqfos9M60J9oYgbRA784I2JtTL7WvFHH6c4i3vIFnGwJiC57ovju5jF2OaAyrB5GuUNRVRaJw1CSn5bzvA==", + "dependencies": { + "@material/elevation": "15.0.0-canary.51f9c0c28.0" + } + }, + "node_modules/@material/tooltip": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-Lvh/Yjmh2wLSFp6p/S6PWrbIQbWFNqAq9K7YAnjYaWLUfBznz/GmgJbWzIhwtTwLzA6eyCk84KY7Ldm4y6QXnA==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/button": "15.0.0-canary.51f9c0c28.0", + "@material/dom": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/tokens": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/top-app-bar": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-ApTalnQcMMuoZ9r2wace2A3SUIfwOfHryFX/rzR00SG8VlXA+Hj4SFNeEQ7qQVPx0RL5supT3Ixz9MWaujdb3Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.51f9c0c28.0", + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/elevation": "15.0.0-canary.51f9c0c28.0", + "@material/ripple": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/shape": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "@material/typography": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/touch-target": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-0O6TSukUJSscOQYlel1MTR63cEeX3XV6SQ/X1FIV+JkYxMof/stiIbWYF55FrWuiV8o4t0JCYHHwB7MPWvufVA==", + "dependencies": { + "@material/base": "15.0.0-canary.51f9c0c28.0", + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/rtl": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/typography": { + "version": "15.0.0-canary.51f9c0c28.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.51f9c0c28.0.tgz", + "integrity": "sha512-54B9LQe1ueFwcjrh58C8FII/O9bflTWNMv3eZSaLwAFz4csNPXHkoLryFmMGbO99p04SsQ/xCjqLJedJCrpN3A==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.51f9c0c28.0", + "@material/theme": "15.0.0-canary.51f9c0c28.0", + "tslib": "^2.1.0" + } + }, "node_modules/@ngtools/webpack": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.0.0.tgz", @@ -9895,6 +10753,11 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/safevalues": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", + "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" + }, "node_modules/sass": { "version": "1.62.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", diff --git a/client/package.json b/client/package.json index 91efaad..ae24ad9 100644 --- a/client/package.json +++ b/client/package.json @@ -11,10 +11,12 @@ "private": true, "dependencies": { "@angular/animations": "^16.0.0", + "@angular/cdk": "^16.0.0", "@angular/common": "^16.0.0", "@angular/compiler": "^16.0.0", "@angular/core": "^16.0.0", "@angular/forms": "^16.0.0", + "@angular/material": "^16.0.0", "@angular/platform-browser": "^16.0.0", "@angular/platform-browser-dynamic": "^16.0.0", "@angular/router": "^16.0.0", @@ -35,4 +37,4 @@ "karma-jasmine-html-reporter": "~2.0.0", "typescript": "~5.0.2" } -} +} \ No newline at end of file diff --git a/client/src/app/app.component.css b/client/src/app/app.component.css index e69de29..7af9c72 100644 --- a/client/src/app/app.component.css +++ b/client/src/app/app.component.css @@ -0,0 +1,5 @@ +.loginbox:host { + display: flex; + flex-direction: column; + align-items: flex-start; +} diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 4246f2c..f6842f4 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -1,21 +1,21 @@ -
+
Üdv {{ userService.user.username }}
-
-
- Felhasználónév: - -
-
- Jelszó: - -
- -
- -
-
+
+
+ + Felhasználónév + + + + Jelszó + + + + +
+

Termékek

diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index df31288..a1bea9e 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -1,12 +1,34 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { UserService } from './services/user.service'; +import { FormControl, FormGroup } from '@angular/forms'; +import { Router } from '@angular/router'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent { - constructor(public userService: UserService) { +export class AppComponent implements OnInit { + loginForm = new FormGroup({ + username: new FormControl(), + password: new FormControl() + }); + + constructor(public userService: UserService, private router: Router) { + } + + ngOnInit() { + if (!this.userService.user) { + this.userService.getCurrentUser(); + } + } + + async doLogin() { + try { + await this.userService.login(this.loginForm.value.username, this.loginForm.value.password); + } catch (e: any) { + console.log("Hiba:", e); + alert("Hiba: " + (e?.error?.error ?? JSON.stringify(e))); + } } } diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 8dfc1d6..93db176 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -2,13 +2,33 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonModule } from '@angular/material/button'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ProductListComponent } from './product-list/product-list.component'; +import { MatTableModule } from '@angular/material/table'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { HttpClientModule } from '@angular/common/http'; @NgModule({ declarations: [ - AppComponent + AppComponent, + ProductListComponent ], imports: [ - BrowserModule + BrowserModule, + MatFormFieldModule, + MatButtonModule, + ReactiveFormsModule, + MatInputModule, + BrowserAnimationsModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + HttpClientModule ], providers: [], bootstrap: [AppComponent] diff --git a/client/src/app/product-list/product-list-datasource.ts b/client/src/app/product-list/product-list-datasource.ts new file mode 100644 index 0000000..79e2eb8 --- /dev/null +++ b/client/src/app/product-list/product-list-datasource.ts @@ -0,0 +1,111 @@ +import { DataSource } from '@angular/cdk/collections'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { map } from 'rxjs/operators'; +import { Observable, of as observableOf, merge } from 'rxjs'; + +// TODO: Replace this with your own data model type +export interface ProductListItem { + name: string; + id: number; +} + +// TODO: replace this with real data from your application +const EXAMPLE_DATA: ProductListItem[] = [ + {id: 1, name: 'Hydrogen'}, + {id: 2, name: 'Helium'}, + {id: 3, name: 'Lithium'}, + {id: 4, name: 'Beryllium'}, + {id: 5, name: 'Boron'}, + {id: 6, name: 'Carbon'}, + {id: 7, name: 'Nitrogen'}, + {id: 8, name: 'Oxygen'}, + {id: 9, name: 'Fluorine'}, + {id: 10, name: 'Neon'}, + {id: 11, name: 'Sodium'}, + {id: 12, name: 'Magnesium'}, + {id: 13, name: 'Aluminum'}, + {id: 14, name: 'Silicon'}, + {id: 15, name: 'Phosphorus'}, + {id: 16, name: 'Sulfur'}, + {id: 17, name: 'Chlorine'}, + {id: 18, name: 'Argon'}, + {id: 19, name: 'Potassium'}, + {id: 20, name: 'Calcium'}, +]; + +/** + * Data source for the ProductList view. This class should + * encapsulate all logic for fetching and manipulating the displayed data + * (including sorting, pagination, and filtering). + */ +export class ProductListDataSource extends DataSource { + data: ProductListItem[] = EXAMPLE_DATA; + paginator: MatPaginator | undefined; + sort: MatSort | undefined; + + constructor() { + super(); + } + + /** + * Connect this data source to the table. The table will only update when + * the returned stream emits new items. + * @returns A stream of the items to be rendered. + */ + connect(): Observable { + if (this.paginator && this.sort) { + // Combine everything that affects the rendered data into one update + // stream for the data-table to consume. + return merge(observableOf(this.data), this.paginator.page, this.sort.sortChange) + .pipe(map(() => { + return this.getPagedData(this.getSortedData([...this.data ])); + })); + } else { + throw Error('Please set the paginator and sort on the data source before connecting.'); + } + } + + /** + * Called when the table is being destroyed. Use this function, to clean up + * any open connections or free any held resources that were set up during connect. + */ + disconnect(): void {} + + /** + * Paginate the data (client-side). If you're using server-side pagination, + * this would be replaced by requesting the appropriate data from the server. + */ + private getPagedData(data: ProductListItem[]): ProductListItem[] { + if (this.paginator) { + const startIndex = this.paginator.pageIndex * this.paginator.pageSize; + return data.splice(startIndex, this.paginator.pageSize); + } else { + return data; + } + } + + /** + * Sort the data (client-side). If you're using server-side sorting, + * this would be replaced by requesting the appropriate data from the server. + */ + private getSortedData(data: ProductListItem[]): ProductListItem[] { + if (!this.sort || !this.sort.active || this.sort.direction === '') { + return data; + } + + return data.sort((a, b) => { + const isAsc = this.sort?.direction === 'asc'; + switch (this.sort?.active) { + case 'name': return compare(a.name, b.name, isAsc); + case 'id': return compare(+a.id, +b.id, isAsc); + default: return 0; + } + }); + } +} + +/** Simple sort comparator for example ID/Name columns (for client-side sorting). */ +function compare(a: string | number, b: string | number, isAsc: boolean): number { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); +} diff --git a/client/src/app/product-list/product-list.component.css b/client/src/app/product-list/product-list.component.css new file mode 100644 index 0000000..5050fb6 --- /dev/null +++ b/client/src/app/product-list/product-list.component.css @@ -0,0 +1,3 @@ +.full-width-table { + width: 100%; +} diff --git a/client/src/app/product-list/product-list.component.html b/client/src/app/product-list/product-list.component.html new file mode 100644 index 0000000..de4cced --- /dev/null +++ b/client/src/app/product-list/product-list.component.html @@ -0,0 +1,26 @@ +
+ + + + + + + + + + + + + + + +
Id{{row.id}}Name{{row.name}}
+ + + +
diff --git a/client/src/app/product-list/product-list.component.spec.ts b/client/src/app/product-list/product-list.component.spec.ts new file mode 100644 index 0000000..80be887 --- /dev/null +++ b/client/src/app/product-list/product-list.component.spec.ts @@ -0,0 +1,34 @@ +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; + +import { ProductListComponent } from './product-list.component'; + +describe('ProductListComponent', () => { + let component: ProductListComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ProductListComponent], + imports: [ + NoopAnimationsModule, + MatPaginatorModule, + MatSortModule, + MatTableModule, + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProductListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should compile', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/product-list/product-list.component.ts b/client/src/app/product-list/product-list.component.ts new file mode 100644 index 0000000..2147e4b --- /dev/null +++ b/client/src/app/product-list/product-list.component.ts @@ -0,0 +1,30 @@ +import { AfterViewInit, Component, ViewChild } from '@angular/core'; +import { MatTable } from '@angular/material/table'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { ProductListDataSource, ProductListItem } from './product-list-datasource'; + +@Component({ + selector: 'app-product-list', + templateUrl: './product-list.component.html', + styleUrls: ['./product-list.component.css'] +}) +export class ProductListComponent implements AfterViewInit { + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatSort) sort!: MatSort; + @ViewChild(MatTable) table!: MatTable; + dataSource: ProductListDataSource; + + /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ + displayedColumns = ['id', 'name']; + + constructor() { + this.dataSource = new ProductListDataSource(); + } + + ngAfterViewInit(): void { + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + this.table.dataSource = this.dataSource; + } +} diff --git a/client/src/app/services/user.service.ts b/client/src/app/services/user.service.ts index 8c0a115..d2d67d2 100644 --- a/client/src/app/services/user.service.ts +++ b/client/src/app/services/user.service.ts @@ -1,5 +1,8 @@ import { Injectable } from '@angular/core'; import { User } from '../model/user.model'; +import { FormControl, ɵFormGroupValue, ɵTypedOrUntyped } from '@angular/forms'; +import { HttpClient } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; @Injectable({ providedIn: 'root' @@ -7,13 +10,26 @@ import { User } from '../model/user.model'; export class UserService { private _user: User | undefined; - get user() { + get user(): User | undefined { return this._user; } - constructor() { } + constructor(private http: HttpClient) { } - login() { + async login(username: string, password: string) { + const user = await firstValueFrom(this.http.post('/api/users/login', {username, password})); + this._user = user as User; + } + async getCurrentUser() { + try { + const user = await firstValueFrom(this.http.get('/api/users/status')); + this._user = user as User; + } catch (e: any) { + if (e?.error?.error?.status !== 'NOTOK') { + console.log("Failed to get user:", e); + } + this._user = undefined; + } } } diff --git a/client/src/index.html b/client/src/index.html index cef9802..b338225 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -6,8 +6,11 @@ + + + - + diff --git a/client/src/styles.css b/client/src/styles.css index 90d4ee0..7e7239a 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -1 +1,4 @@ /* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } diff --git a/server/db/bootstrapper.js b/server/db/bootstrapper.js index 2c497c0..bb2bccf 100644 --- a/server/db/bootstrapper.js +++ b/server/db/bootstrapper.js @@ -1,5 +1,6 @@ const mongoose = require('mongoose'); const User = mongoose.model('user'); +const Product = require('./productSchema') async function ensureAdminExists() { try { @@ -22,4 +23,21 @@ async function ensureAdminExists() { } } -module.exports = ensureAdminExists; +async function boot() { + await ensureAdminExists(); + if (await Product.exists({})) { + console.log("A termékek már léteznek"); + return; + } + for (let i = 0; i < 20; i++) { + const product = new Product({ + name: `Teszt termék${i}`, + price: Math.floor(500+Math.random()*500), + description: `Teszt leirás${i}` + }); + await product.save(); + } + console.log("Termékek sikeresen létrehozva!"); +} + +module.exports = boot; diff --git a/server/db/productSchema.js b/server/db/productSchema.js index cc9a198..3cf5cf2 100644 --- a/server/db/productSchema.js +++ b/server/db/productSchema.js @@ -1,7 +1,7 @@ const mongoose = require('mongoose'); const productSchema = new mongoose.Schema({ - username: { + name: { type: String, /* támogatott típusok: String, Number, Date, Buffer, Boolean, Mixed, ObjectId, Array, Decimal128, Map, Schema - az utolsóval valósítható meg az egymásba ágyazás, tehát hogy az egyik dokumentum @@ -9,49 +9,28 @@ const productSchema = new mongoose.Schema({ required: true, unique : true }, - password: { + price: { + type: Number, + required: true, + default: 1000, + }, + description: { type: String, required: true, }, - accessLevel: { - type: Number, - required: true, - default: 1, - }, - birthdate: { - type: Date, - required: true, - }, -}); - -userSchema.pre('save', function(next) { - const user = this; - if(user.isModified('password')) { - bcrypt.genSalt(10, function(err, salt) { - if(err) { - console.log('hiba a salt generalasa soran'); - return next(err); - } - bcrypt.hash(user.password, salt, function(error, hash) { - if(error) { - console.log('hiba a hasheles soran'); - return next(error); - } - user.password = hash; - return next(); - }) - }) - } else { - return next(); + id: { + type: String, } }); -userSchema.methods.comparePasswords = function(password, nx) { - bcrypt.compare(password, this.password, function(err, isMatch) { - nx(err, isMatch); - }); -}; +productSchema.pre('save', function(next) { + const product = this; + if(product.isModified('name')) { + product.name = product.name.replaceAll(/[ !?$.,-]/g, ''); + } + return next(); +}); -const User = mongoose.model('user', userSchema); +const Product = mongoose.model('product', productSchema); -module.exports = User; +module.exports = Product; diff --git a/server/index.js b/server/index.js index 5aa7e7f..2dac319 100644 --- a/server/index.js +++ b/server/index.js @@ -30,39 +30,33 @@ app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); -passport.use('local', new LocalStrategy({}, function (username, password, done) { - User.findOne({ username: username }, function (err, user) { - if (err) return done('Hiba lekeres soran', null); - if (!user) return done('Nincs ilyen felhasználónév', null); - user.comparePasswords(password, function (error, isMatch) { - if (error) return done(error, false); - if (!isMatch) return done('Hibas jelszo', false); - return done(null, user); - }); +passport.use('local', new LocalStrategy({}, async function (username, password, done) { + const user = await User.findOne({ username: username }).catch(() => done('Hiba lekeres soran', null)); + if (!user) return done('Nincs ilyen felhasználónév', null); + user.comparePasswords(password, function (error, isMatch) { + if (error) return done(error, false); + if (!isMatch) return done('Hibás jelszó', false); + return done(null, user); }); })); passport.serializeUser(function (user, done) { - if (!user) return done('nincs megadva beléptethető felhasználó', null); + if (!user) return done('nincs megadva szerializálható felhasználó', null); return done(null, user); }); passport.deserializeUser(function (user, done) { - if (!user) return done("nincs user akit kiléptethetnénk", null); + if (!user) return done("nincs megadva deszerializálható felhasználó", null); return done(null, user); }); -app.use(expressSession({ secret: 'prf2021lassananodejsvegereerunk', resave: true, saveUninitialized: false })); +app.use(expressSession({ secret: 'prfcheesecrescentmc', resave: true, saveUninitialized: false })); app.use(passport.initialize({})); app.use(passport.session({})); -app.use((req, res, next) => { - console.log('A middleware futott!') - next() -}); - app.use('/api/users', require('./usersRouter')) +app.use('/api/products', require('./productsRouter')) app.use('', express.static('public')) diff --git a/server/package.json b/server/package.json index 12cd92d..b2871b7 100644 --- a/server/package.json +++ b/server/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "start": "node .", + "start": "node --watch .", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], diff --git a/server/productsRouter.js b/server/productsRouter.js new file mode 100644 index 0000000..d59366b --- /dev/null +++ b/server/productsRouter.js @@ -0,0 +1,105 @@ +const express = require('express'); +const router = express.Router(); +const mongoose = require('mongoose'); +const Product = mongoose.model('product'); + +const passport = require('passport'); + +function getProductData(product) { + return {name: product.name, price: product.price, description: product.description, id: product.id}; +} + +async function getProduct(req, res, next) { + try { + const product = await Product.findById(req.params.id); + if (product == null) { + return res.status(404).json({ message: 'A termék nem található' }); + } + res.product = product; + next(); + } catch (error) { + return res.status(500).json({ message: error.message }); + } +} + +// GET /products - összes termék lekérdezése +router.get('/', async (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } + try { + const products = await Product.find(); + res.status(200).json(products); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}); + +// GET /products/:id - egy termék lekérdezése az id alapján +router.get('/:id', getProduct, (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } + res.json(res.product); +}); + +// POST /products - új termék létrehozása +router.post('/', async (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } + const product = new Product({ + name: req.body.name, + price: req.body.price, + description: req.body.description, + id: req.body.id, + }); + + try { + const newProduct = await product.save(); + res.status(201).json(newProduct); + } catch (error) { + res.status(400).json({ message: error.message }); + } +}); + +// PATCH /products/:id - egy termék frissítése az id alapján +router.patch('/:id', getProduct, async (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } + if (req.body.name != null) { + res.product.name = req.body.name; + } + if (req.body.price != null) { + res.product.price = req.body.price; + } + if (req.body.description != null) { + res.product.description = req.body.description; + } + if (req.body.id != null) { + res.product.id = req.body.id; + } + + try { + const updatedProduct = await res.product.save(); + res.json(updatedProduct); + } catch (error) { + res.status(400).json({ message: error.message }); + } +}); + +// DELETE /products/:id - egy termék törlése az id alapján +router.delete('/:id', getProduct, async (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } + try { + await res.product.remove(); + res.json({ message: 'A termék sikeresen törölve!' }); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}); + +module.exports = router diff --git a/server/usersRouter.js b/server/usersRouter.js index a11f662..da82f61 100644 --- a/server/usersRouter.js +++ b/server/usersRouter.js @@ -5,13 +5,18 @@ const User = mongoose.model('user'); const passport = require('passport'); +function getUserData(user) { + return {username: user.username, accessLevel: user.accessLevel, birthdate: user.birthdate}; +} + router.route('/login').post((req, res) => { + console.log("Req body:", req.body); if (req.body.username && req.body.password) { passport.authenticate('local', {}, function (error, user) { - if (error) return res.status(500).send(error); + if (error) return res.status(500).send({error: error}); req.login(user, function (error) { if (error) return res.status(500).send(error); - return res.status(200).send('Bejelentkezes sikeres'); + return res.status(200).send(getUserData(user)); }) })(req, res); } else { @@ -36,9 +41,9 @@ router.route('/logout').post((req, res) => { router.route('/status').get((req, res) => { if (req.isAuthenticated()) { console.log(req.user) - return res.status(200).send(req.user); + return res.status(200).send(getUserData(req.user)); } else { - return res.status(403).send('Nem is volt bejelentkezve'); + return res.status(403).send({status: "NOTOK"}); } }) @@ -57,6 +62,9 @@ async function getUser(req, res, next) { // GET /users - összes felhasználó lekérdezése router.get('/', async (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } try { const users = await User.find(); res.status(200).json(users); @@ -67,11 +75,17 @@ router.get('/', async (req, res) => { // GET /users/:id - egy felhasználó lekérdezése az id alapján router.get('/:id', getUser, (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } res.json(res.user); }); // POST /users - új felhasználó létrehozása router.post('/', async (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } const user = new User({ username: req.body.username, password: req.body.password, @@ -89,6 +103,9 @@ router.post('/', async (req, res) => { // PATCH /users/:id - egy felhasználó frissítése az id alapján router.patch('/:id', getUser, async (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } if (req.body.username != null) { res.user.username = req.body.username; } @@ -112,6 +129,9 @@ router.patch('/:id', getUser, async (req, res) => { // DELETE /users/:id - egy felhasználó törlése az id alapján router.delete('/:id', getUser, async (req, res) => { + if (!req.isAuthenticated()) { + return res.status(403).json({ message: "Unauthenticated" }); + } try { await res.user.remove(); res.json({ message: 'A felhasználó sikeresen törölve!' });