diff --git a/ui/Dockerfile b/ui/Dockerfile index d1ae9bfdc..8ee2d4399 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -16,14 +16,18 @@ COPY package*.json ./ # If you are building your code for production # RUN npm install --only=production -RUN npm install + +RUN \ + npm config list && \ + npm config delete proxy && \ + npm install --loglevel silly # Bundle app source COPY . . RUN npm run build -FROM nginx +FROM nginx:1.24.0-bullseye RUN \ apt-get update && \ diff --git a/ui/angular.json b/ui/angular.json index a07983c60..4efd8aec3 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -4,6 +4,51 @@ "newProjectRoot": "projects", "projects": { "ui": { + "i18n": { + "sourceLocale": { "code": "en", "baseHref": "en/../" }, + "locales": { + "cs": { + "translation": "src/i18n/messages.cs.xlf", + "baseHref": "cs/../" + }, + "es": { + "translation": "src/i18n/messages.es.xlf", + "baseHref": "es/../" + }, + "fr": { + "translation": "src/i18n/messages.fr.xlf", + "baseHref": "fr/../" + }, + "it": { + "translation": "src/i18n/messages.it.xlf", + "baseHref": "it/../" + }, + "ja": { + "translation": "src/i18n/messages.ja.xlf", + "baseHref": "ja/../" + }, + "ko": { + "translation": "src/i18n/messages.ko.xlf", + "baseHref": "ko/../" + }, + "pt": { + "translation": "src/i18n/messages.pt.xlf", + "baseHref": "pt/../" + }, + "ru": { + "translation": "src/i18n/messages.ru.xlf", + "baseHref": "ru/../" + }, + "zh-CN": { + "translation": "src/i18n/messages.zh-CN.xlf", + "baseHref": "zh-CN/../" + }, + "zh-TW": { + "translation": "src/i18n/messages.zh-TW.xlf", + "baseHref": "zh-TW/../" + } + } + }, "projectType": "application", "schematics": { "@schematics/angular:component": { @@ -17,13 +62,31 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { + "localize": [ + "en", + "fr", + "es", + "cs", + "it", + "ja", + "ko", + "pt", + "ru", + "zh-CN", + "zh-TW" + ], "outputPath": "dist/", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", - "assets": ["src/favicon.ico", "src/assets", "src/content/images"], + "assets": [ + "src/favicon.ico", + "src/assets", + "src/content/images", + "src/content/css" + ], "styles": ["src/content/scss/global.scss"], "scripts": [] }, @@ -50,6 +113,7 @@ "outputHashing": "all" }, "development": { + "localize": ["en"], "buildOptimizer": false, "optimization": false, "vendorChunk": true, @@ -77,9 +141,24 @@ "defaultConfiguration": "development" }, "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", + "builder": "ng-extract-i18n-merge:ng-extract-i18n-merge", "options": { - "browserTarget": "ui:build" + "includeContext": true, + "browserTarget": "ui:build", + "format": "xlf", + "outputPath": "src/i18n", + "targetFiles": [ + "messages.cs.xlf", + "messages.es.xlf", + "messages.fr.xlf", + "messages.it.xlf", + "messages.ja.xlf", + "messages.ko.xlf", + "messages.pt.xlf", + "messages.ru.xlf", + "messages.zh-CN.xlf", + "messages.zh-TW.xlf" + ] } }, "test": { diff --git a/ui/container-files/etc/nginx/conf.d/default.conf b/ui/container-files/etc/nginx/conf.d/default.conf index 226e97404..c46b77f33 100644 --- a/ui/container-files/etc/nginx/conf.d/default.conf +++ b/ui/container-files/etc/nginx/conf.d/default.conf @@ -4,22 +4,29 @@ server { #access_log /var/log/nginx/host.access.log main; + # Fallback to default language if no preference defined by browser + if ($accept_language ~ "^$") { + set $accept_language "en"; + } + location / { root /usr/share/nginx/html; index index.html index.htm; - rewrite ^/ui/(.*) /$1 break; - try_files $uri $uri/ /index.html; - } - # JT. Uncomment the error_page directive below to use the orcid 404 error page in /404 - #error_page 404 ../404; + location ~ ^/ui/(.*) { + # Redirect requests for `/ui/(.*)` to the Angular application in the preferred language of the browser + # For reference on multi language support see: https://angular.io/guide/i18n-common-deploy#configure-a-server + rewrite ^/ui/(.*)$ /$accept_language/$1 break; - # redirect server error pages to the static page /50x.html - # - #error_page 500 502 503 504 /50x.html; - #location = /50x.html { - # root /usr/share/nginx/html; - #} + # Configure a fallback route in the NGinX server to serve the index.html when requests arrive for `/ui/login` + # Requires static html content to have a `base-href=./` entry + try_files $uri $uri/ /$accept_language/index.html?$args; + + # Or use the following line instead + # auto-index on; + } + + } } diff --git a/ui/container-files/etc/nginx/nginx.conf b/ui/container-files/etc/nginx/nginx.conf index ee8c7fd6b..22027e11c 100644 --- a/ui/container-files/etc/nginx/nginx.conf +++ b/ui/container-files/etc/nginx/nginx.conf @@ -4,12 +4,10 @@ worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; - events { worker_connections 1024; } - http { include /etc/nginx/mime.types; default_type application/octet-stream; @@ -27,5 +25,24 @@ http { #gzip on; + # Browser preferred language detection (does NOT require AcceptLanguageModule) + # For reference on multi language support see: https://angular.io/guide/i18n-common-deploy#configure-a-server + map $http_accept_language $accept_language { + default en; + ~*^cs cs; + ~*^de de; + ~*^es es; + ~*^fr fr; + ~*^it it; + ~*^ja ja; + ~*^ko ko; + ~*^pt pt; + ~*^ru ru; + ~*^ui ui; + ~*^zh-CN zh-CN; + ~*^zh-TW zh-TW; + } + include /etc/nginx/conf.d/*.conf; -} \ No newline at end of file + +} diff --git a/ui/package-lock.json b/ui/package-lock.json index 3f05d5d24..ed12ef05f 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -23,6 +23,7 @@ "@ng-bootstrap/ng-bootstrap": "^15.1.1", "bootstrap": "^5.3.2", "moment": "^2.29.4", + "ngx-cookie-service": "^16.1.0", "ngx-webstorage": "^12.0.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", @@ -47,6 +48,7 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "ng-extract-i18n-merge": "^2.9.1", "prettier": "^3.0.3", "typescript": "~4.9.5" } @@ -5829,9 +5831,9 @@ } }, "node_modules/axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", + "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", "dev": true, "dependencies": { "follow-redirects": "^1.15.0", @@ -11065,6 +11067,100 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/ng-extract-i18n-merge": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/ng-extract-i18n-merge/-/ng-extract-i18n-merge-2.9.1.tgz", + "integrity": "sha512-EJAgJrV2ZSRoH1njMI9lLLtLJkwabkk41ZZyV+U+6h8e5vDCM4zPGjm0NNZFy+YP+/ST+nlvi2CxprDXnjS8BQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "^0.1301.0 || ^0.1401.0 || ^0.1501.0 || ^0.1601.0 || ^0.1700.0", + "@angular-devkit/core": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "@angular-devkit/schematics": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "@schematics/angular": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "xmldoc": "^1.1.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@angular-devkit/build-angular": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/ng-extract-i18n-merge/node_modules/@angular-devkit/architect": { + "version": "0.1700.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.9.tgz", + "integrity": "sha512-B8OeUrvJj5JsfOJIibpoVjvuZzthPFxf1LvuUXTyQcqDUscJAe/RJBc2woT6ss13Iv/HWt8mgaMPP4CccckdNg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "17.0.9", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/ng-extract-i18n-merge/node_modules/@angular-devkit/core": { + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.9.tgz", + "integrity": "sha512-r5jqwpWOgowqe9KSDqJ3iSbmsEt2XPjSvRG4DSI2T9s31bReoMtreo8b7wkRa2B3hbcDnstFbn8q27VvJDqRaQ==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "3.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/ng-extract-i18n-merge/node_modules/picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/ng-extract-i18n-merge/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/ngx-cookie-service": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-16.1.0.tgz", + "integrity": "sha512-FrzMjsGCHZCd2sEucigMaGyzImBL0l6gwWn6jmLBhcNVx0D7P8Yvtgk9aUptlqBrVKy4c2upglSa3Ogv3679bw==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": "^16.0.0", + "@angular/core": "^16.0.0" + } + }, "node_modules/ngx-webstorage": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ngx-webstorage/-/ngx-webstorage-12.0.0.tgz", @@ -13134,8 +13230,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", - "dev": true, - "optional": true + "dev": true }, "node_modules/saxes": { "version": "5.0.1", @@ -15152,6 +15247,15 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmldoc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz", + "integrity": "sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==", + "dev": true, + "dependencies": { + "sax": "^1.2.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -19470,9 +19574,9 @@ } }, "axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", + "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", "dev": true, "requires": { "follow-redirects": "^1.15.0", @@ -23415,6 +23519,68 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "ng-extract-i18n-merge": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/ng-extract-i18n-merge/-/ng-extract-i18n-merge-2.9.1.tgz", + "integrity": "sha512-EJAgJrV2ZSRoH1njMI9lLLtLJkwabkk41ZZyV+U+6h8e5vDCM4zPGjm0NNZFy+YP+/ST+nlvi2CxprDXnjS8BQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "^0.1301.0 || ^0.1401.0 || ^0.1501.0 || ^0.1601.0 || ^0.1700.0", + "@angular-devkit/core": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "@angular-devkit/schematics": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "@schematics/angular": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "xmldoc": "^1.1.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1700.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.9.tgz", + "integrity": "sha512-B8OeUrvJj5JsfOJIibpoVjvuZzthPFxf1LvuUXTyQcqDUscJAe/RJBc2woT6ss13Iv/HWt8mgaMPP4CccckdNg==", + "dev": true, + "requires": { + "@angular-devkit/core": "17.0.9", + "rxjs": "7.8.1" + } + }, + "@angular-devkit/core": { + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.9.tgz", + "integrity": "sha512-r5jqwpWOgowqe9KSDqJ3iSbmsEt2XPjSvRG4DSI2T9s31bReoMtreo8b7wkRa2B3hbcDnstFbn8q27VvJDqRaQ==", + "dev": true, + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "3.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + } + }, + "picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "dev": true + }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + } + } + }, + "ngx-cookie-service": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-16.1.0.tgz", + "integrity": "sha512-FrzMjsGCHZCd2sEucigMaGyzImBL0l6gwWn6jmLBhcNVx0D7P8Yvtgk9aUptlqBrVKy4c2upglSa3Ogv3679bw==", + "requires": { + "tslib": "^2.0.0" + } + }, "ngx-webstorage": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ngx-webstorage/-/ngx-webstorage-12.0.0.tgz", @@ -24901,8 +25067,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", - "dev": true, - "optional": true + "dev": true }, "saxes": { "version": "5.0.1", @@ -26368,6 +26533,15 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xmldoc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz", + "integrity": "sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==", + "dev": true, + "requires": { + "sax": "^1.2.4" + } + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/ui/package.json b/ui/package.json index 1aa605da5..69d9b1c16 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,6 +29,7 @@ "@ng-bootstrap/ng-bootstrap": "^15.1.1", "bootstrap": "^5.3.2", "moment": "^2.29.4", + "ngx-cookie-service": "^16.1.0", "ngx-webstorage": "^12.0.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", @@ -53,6 +54,7 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "ng-extract-i18n-merge": "^2.9.1", "prettier": "^3.0.3", "typescript": "~4.9.5" } diff --git a/ui/src/app/account/account.module.ts b/ui/src/app/account/account.module.ts index 59bdfd816..e4ee6c1e4 100644 --- a/ui/src/app/account/account.module.ts +++ b/ui/src/app/account/account.module.ts @@ -3,11 +3,21 @@ import { CommonModule } from '@angular/common' import { LoginComponent } from './login/login.component' import { RouterModule } from '@angular/router' import { ReactiveFormsModule } from '@angular/forms' -import { routes } from './account.route'; +import { routes } from './account.route' import { PasswordResetInitComponent } from './password/password-reset-init.component' +import { SettingsComponent } from './settings/settings.component' +import { SharedModule } from '../shared/shared.module' +import { PasswordComponent } from './password/password.component' +import { PasswordStrengthComponent } from './password/password-strength.component' @NgModule({ - declarations: [LoginComponent, PasswordResetInitComponent], - imports: [CommonModule, ReactiveFormsModule, RouterModule.forChild(routes)], + declarations: [ + LoginComponent, + PasswordResetInitComponent, + SettingsComponent, + PasswordComponent, + PasswordStrengthComponent, + ], + imports: [SharedModule, CommonModule, ReactiveFormsModule, RouterModule.forChild(routes)], }) export class AccountModule {} diff --git a/ui/src/app/account/account.route.ts b/ui/src/app/account/account.route.ts index 48f4f60fd..dac2ef290 100644 --- a/ui/src/app/account/account.route.ts +++ b/ui/src/app/account/account.route.ts @@ -1,6 +1,9 @@ import { Routes } from '@angular/router' import { LoginComponent } from './login/login.component' import { PasswordResetInitComponent } from './password/password-reset-init.component' +import { SettingsComponent } from './settings/settings.component' +import { AuthGuard } from './auth.guard' +import { PasswordComponent } from './password/password.component' export const routes: Routes = [ { @@ -15,4 +18,22 @@ export const routes: Routes = [ pageTitle: 'global.menu.account.password.string', }, }, + { + path: 'settings', + component: SettingsComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'global.menu.account.settings.string', + }, + canActivate: [AuthGuard], + }, + { + path: 'password', + component: PasswordComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'global.menu.account.password.string', + }, + canActivate: [AuthGuard], + }, ] diff --git a/ui/src/app/account/auth.guard.ts b/ui/src/app/account/auth.guard.ts index 4f49e9871..67718ff98 100644 --- a/ui/src/app/account/auth.guard.ts +++ b/ui/src/app/account/auth.guard.ts @@ -15,8 +15,6 @@ export const AuthGuard = (route: ActivatedRouteSnapshot, state: RouterStateSnaps return accountService.getAccountData().pipe( filter((account) => account !== undefined), map((account) => { - console.log(authorities, account) - if (account) { const hasAnyAuthority = accountService.hasAnyAuthority(authorities) if (hasAnyAuthority) { diff --git a/ui/src/app/account/login/login.component.html b/ui/src/app/account/login/login.component.html index 70dcfb5d4..c7b6e1b73 100644 --- a/ui/src/app/account/login/login.component.html +++ b/ui/src/app/account/login/login.component.html @@ -2,49 +2,45 @@