From 1adf825c3ffad32a5a36fbde2999a7199ee4d9a1 Mon Sep 17 00:00:00 2001 From: andrej romanov <50377758+auumgn@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:41:59 +0300 Subject: [PATCH 1/3] add member info edit component --- .../app/entities/member/member.service.ts | 4 +- ui/angular.json | 5 +- ui/package-lock.json | 697 ++++++++++++++++-- ui/package.json | 3 + .../app/affiliation/affiliations.component.ts | 1 + ui/src/app/home/home.component.scss | 1 + ui/src/app/home/home.module.ts | 7 +- ui/src/app/home/home.route.ts | 19 + .../member-info-edit.component.html | 440 +++++++++++ .../member-info-edit.component.scss | 130 ++++ .../member-info-edit.component.spec.ts | 21 + .../member-info/member-info-edit.component.ts | 227 ++++++ .../home/member-info/member-info.component.ts | 1 - ui/src/app/member/service/member.service.ts | 39 +- ui/src/index.html | 2 - 15 files changed, 1538 insertions(+), 59 deletions(-) create mode 100644 ui/src/app/home/member-info/member-info-edit.component.html create mode 100644 ui/src/app/home/member-info/member-info-edit.component.scss create mode 100644 ui/src/app/home/member-info/member-info-edit.component.spec.ts create mode 100644 ui/src/app/home/member-info/member-info-edit.component.ts diff --git a/gateway/src/main/webapp/app/entities/member/member.service.ts b/gateway/src/main/webapp/app/entities/member/member.service.ts index 8ba2d4ef3..3f8632cea 100644 --- a/gateway/src/main/webapp/app/entities/member/member.service.ts +++ b/gateway/src/main/webapp/app/entities/member/member.service.ts @@ -165,8 +165,8 @@ export class MSMemberService { return this.http.delete<any>(`${this.resourceUrl}/members/${id}`, { observe: 'response' }); } - updateMemberDetails(memberDetails: ISFMemberUpdate, salesforceId: string): Observable<HttpResponse<any>> { - return this.http.put(`${this.resourceUrl}/members/${salesforceId}/member-details`, memberDetails, { observe: 'response' }); + updateMemberDetails(memberDetails: ISFMemberUpdate, salesforceId: string): Observable<ISFMemberUpdate> { + return this.http.put(`${this.resourceUrl}/members/${salesforceId}/member-details`, memberDetails); } getConsortiaLeadName(consortiaLeadId: string): Observable<EntityResponseType> { diff --git a/ui/angular.json b/ui/angular.json index 07a8d92ca..281a30a19 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -87,7 +87,10 @@ "src/content/images", "src/content/css" ], - "styles": ["src/content/scss/global.scss"], + "styles": [ + "src/content/scss/global.scss", + "node_modules/quill/dist/quill.snow.css" + ], "scripts": [] }, "configurations": { diff --git a/ui/package-lock.json b/ui/package-lock.json index 744e83d18..01cf3ec80 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -26,9 +26,11 @@ "jsrsasign-util": "^1.0.5", "moment": "^2.29.4", "ngx-clipboard": "^16.0.0", + "ngx-quill": "^23.0.2", "ngx-webstorage": "^12.0.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", + "uninstall": "^0.0.0", "zone.js": "~0.13.3" }, "devDependencies": { @@ -42,6 +44,7 @@ "@angular/compiler-cli": "^16.2.9", "@types/jasmine": "~4.0.0", "@types/jsrsasign": "^10.5.13", + "@types/quill": "^1.3.10", "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", "eslint": "^8.49.0", @@ -4891,6 +4894,15 @@ "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", "dev": true }, + "node_modules/@types/quill": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz", + "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", + "dev": true, + "dependencies": { + "parchment": "^1.1.2" + } + }, "node_modules/@types/range-parser": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", @@ -6245,13 +6257,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7016,6 +7033,26 @@ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, + "node_modules/deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "peer": true, + "dependencies": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -7046,6 +7083,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -7055,6 +7108,23 @@ "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7452,6 +7522,25 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", @@ -8181,8 +8270,7 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "node_modules/external-editor": { "version": "3.1.0", @@ -8204,6 +8292,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", + "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", + "peer": true + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -8574,10 +8668,21 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gauge": { "version": "4.0.4", @@ -8615,14 +8720,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8715,6 +8824,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -8765,11 +8885,47 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -8783,6 +8939,17 @@ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hdr-histogram-js": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", @@ -9305,6 +9472,22 @@ "node": ">= 10" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -9334,6 +9517,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -9438,6 +9636,22 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -11187,6 +11401,22 @@ "@angular/core": ">=13.0.0" } }, + "node_modules/ngx-quill": { + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/ngx-quill/-/ngx-quill-23.0.2.tgz", + "integrity": "sha512-6NoO/J7JWMdUdFRHwLVzyYWvihilrl9zEYUp8rRBm0bNdgMPTBUmoHnMbto8ln/eNu+APOfVq+DboTx8vO1PeA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@angular/core": "^16.0.0", + "quill": "^1.3.7", + "rxjs": "^7.0.0" + } + }, "node_modules/ngx-webstorage": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ngx-webstorage/-/ngx-webstorage-12.0.0.tgz", @@ -11853,6 +12083,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object-path": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", @@ -12160,6 +12415,11 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "node_modules/parchment": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12759,6 +13019,49 @@ } ] }, + "node_modules/quill": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz", + "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==", + "peer": true, + "dependencies": { + "clone": "^2.1.1", + "deep-equal": "^1.0.1", + "eventemitter3": "^2.0.3", + "extend": "^3.0.2", + "parchment": "^1.1.4", + "quill-delta": "^3.6.2" + } + }, + "node_modules/quill-delta": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz", + "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", + "peer": true, + "dependencies": { + "deep-equal": "^1.0.1", + "extend": "^3.0.2", + "fast-diff": "1.1.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/quill/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "peer": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/quill/node_modules/eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", + "peer": true + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -12907,6 +13210,24 @@ "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", "dev": true }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -13512,6 +13833,37 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -14038,9 +14390,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -14526,6 +14878,11 @@ "node": ">=4" } }, + "node_modules/uninstall": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/uninstall/-/uninstall-0.0.0.tgz", + "integrity": "sha512-pjP/0+A4gsbDVa8XH/S2GZdT9NPJW8NFMy3GI7HnsWG+NAmFSSj3QidNosXBI9cPtxxNExEDdhKFO6sli8K3mA==" + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", @@ -18904,6 +19261,15 @@ "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", "dev": true }, + "@types/quill": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz", + "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", + "dev": true, + "requires": { + "parchment": "^1.1.2" + } + }, "@types/range-parser": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", @@ -19921,13 +20287,15 @@ } }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -20500,6 +20868,20 @@ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, + "deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "peer": true, + "requires": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + } + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -20524,12 +20906,33 @@ "clone": "^1.0.2" } }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "peer": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -20843,6 +21246,19 @@ "is-arrayish": "^0.2.1" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-module-lexer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", @@ -21384,8 +21800,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "external-editor": { "version": "3.1.0", @@ -21404,6 +21819,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-diff": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", + "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", + "peer": true + }, "fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -21682,10 +22103,15 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "peer": true }, "gauge": { "version": "4.0.4", @@ -21714,14 +22140,15 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-package-type": { @@ -21781,6 +22208,14 @@ "slash": "^4.0.0" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -21822,11 +22257,32 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "peer": true, + "requires": { + "has-symbols": "^1.0.3" + } }, "has-unicode": { "version": "2.0.1", @@ -21834,6 +22290,14 @@ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "hdr-histogram-js": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", @@ -22232,6 +22696,16 @@ "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", "dev": true }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -22255,6 +22729,15 @@ "has": "^1.0.3" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "peer": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -22323,6 +22806,16 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -23642,6 +24135,14 @@ "tslib": "^2.0.0" } }, + "ngx-quill": { + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/ngx-quill/-/ngx-quill-23.0.2.tgz", + "integrity": "sha512-6NoO/J7JWMdUdFRHwLVzyYWvihilrl9zEYUp8rRBm0bNdgMPTBUmoHnMbto8ln/eNu+APOfVq+DboTx8vO1PeA==", + "requires": { + "tslib": "^2.3.0" + } + }, "ngx-webstorage": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ngx-webstorage/-/ngx-webstorage-12.0.0.tgz", @@ -24137,6 +24638,22 @@ "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", "dev": true }, + "object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "peer": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "peer": true + }, "object-path": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", @@ -24364,6 +24881,11 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "parchment": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -24771,6 +25293,45 @@ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, + "quill": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz", + "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==", + "peer": true, + "requires": { + "clone": "^2.1.1", + "deep-equal": "^1.0.1", + "eventemitter3": "^2.0.3", + "extend": "^3.0.2", + "parchment": "^1.1.4", + "quill-delta": "^3.6.2" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "peer": true + }, + "eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", + "peer": true + } + } + }, + "quill-delta": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz", + "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", + "peer": true, + "requires": { + "deep-equal": "^1.0.1", + "extend": "^3.0.2", + "fast-diff": "1.1.2" + } + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -24896,6 +25457,18 @@ "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", "dev": true }, + "regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "peer": true, + "requires": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + } + }, "regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -25339,6 +25912,31 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "peer": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -25743,9 +26341,9 @@ "dev": true }, "tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -26095,6 +26693,11 @@ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true }, + "uninstall": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/uninstall/-/uninstall-0.0.0.tgz", + "integrity": "sha512-pjP/0+A4gsbDVa8XH/S2GZdT9NPJW8NFMy3GI7HnsWG+NAmFSSj3QidNosXBI9cPtxxNExEDdhKFO6sli8K3mA==" + }, "unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", diff --git a/ui/package.json b/ui/package.json index a3ddf84ac..4fa77790e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -32,9 +32,11 @@ "jsrsasign-util": "^1.0.5", "moment": "^2.29.4", "ngx-clipboard": "^16.0.0", + "ngx-quill": "^23.0.2", "ngx-webstorage": "^12.0.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", + "uninstall": "^0.0.0", "zone.js": "~0.13.3" }, "devDependencies": { @@ -48,6 +50,7 @@ "@angular/compiler-cli": "^16.2.9", "@types/jasmine": "~4.0.0", "@types/jsrsasign": "^10.5.13", + "@types/quill": "^1.3.10", "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", "eslint": "^8.49.0", diff --git a/ui/src/app/affiliation/affiliations.component.ts b/ui/src/app/affiliation/affiliations.component.ts index 510699b6c..5eddaee12 100644 --- a/ui/src/app/affiliation/affiliations.component.ts +++ b/ui/src/app/affiliation/affiliations.component.ts @@ -87,6 +87,7 @@ export class AffiliationsComponent implements OnInit, OnDestroy { this.searchTerm = '' this.submittedSearchTerm = '' this.loadAll() + console.log('test') }) this.importEventSubscriber = this.eventService.on(EventType.IMPORT_AFFILIATIONS).subscribe(() => { this.loadAll() diff --git a/ui/src/app/home/home.component.scss b/ui/src/app/home/home.component.scss index b47a816ce..759fef2d0 100644 --- a/ui/src/app/home/home.component.scss +++ b/ui/src/app/home/home.component.scss @@ -1,3 +1,4 @@ +@import '~quill/dist/quill.snow.css'; :host { max-width: 1250px; display: block; diff --git a/ui/src/app/home/home.module.ts b/ui/src/app/home/home.module.ts index b8d49c2b6..db7828823 100644 --- a/ui/src/app/home/home.module.ts +++ b/ui/src/app/home/home.module.ts @@ -5,9 +5,12 @@ import { routes } from './home.route' import { HomeComponent } from './home.component' import { MemberInfoComponent } from './member-info/member-info.component' import { FontAwesomeModule } from '@fortawesome/angular-fontawesome' +import { MemberInfoEditComponent } from './member-info/member-info-edit.component' +import { QuillModule } from 'ngx-quill' +import { ReactiveFormsModule } from '@angular/forms' @NgModule({ - imports: [CommonModule, RouterModule.forChild(routes), FontAwesomeModule], - declarations: [HomeComponent, MemberInfoComponent], + imports: [CommonModule, RouterModule.forChild(routes), FontAwesomeModule, QuillModule.forRoot(), ReactiveFormsModule], + declarations: [HomeComponent, MemberInfoComponent, MemberInfoEditComponent], }) export class HomeModule {} diff --git a/ui/src/app/home/home.route.ts b/ui/src/app/home/home.route.ts index ff6c70412..93bb025da 100644 --- a/ui/src/app/home/home.route.ts +++ b/ui/src/app/home/home.route.ts @@ -5,6 +5,7 @@ import { MemberInfoComponent } from './member-info/member-info.component' import { Injectable, inject } from '@angular/core' import { MemberService } from '../member/service/member.service' import { Observable, map } from 'rxjs' +import { MemberInfoEditComponent } from './member-info/member-info-edit.component' export const ManageMemberGuard = (route: ActivatedRouteSnapshot): Observable<boolean> | boolean => { const router = inject(Router) @@ -63,6 +64,24 @@ export const routes: Routes = [ }, canActivate: [AuthGuard], }, + { + path: 'edit', + component: MemberInfoEditComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'home.title.string', + }, + canActivate: [AuthGuard, ManageMemberGuard], + }, + { + path: 'manage/:id/edit', + component: MemberInfoEditComponent, + data: { + authorities: ['ROLE_USER'], + pageTitle: 'home.title.string', + }, + canActivate: [AuthGuard], + }, ], }, ] diff --git a/ui/src/app/home/member-info/member-info-edit.component.html b/ui/src/app/home/member-info/member-info-edit.component.html new file mode 100644 index 000000000..d0a5e56b0 --- /dev/null +++ b/ui/src/app/home/member-info/member-info-edit.component.html @@ -0,0 +1,440 @@ +<div *ngIf="memberData" class="h-100"> + <div class="p-3 mb-3"> + <div class="font-size-14 line-height-150"> + Back to + <a routerLink="" class="font-weight-normal text-decoration-underline">{{ memberData.publicDisplayName }}</a> + </div> + <hr class="mb-4" /> + <div class="edit-org-label mb-10 font-size-18 color-gray line-height-150">Edit organization</div> + <h1 class="font-weight-bold mb-3 wide-text">{{ memberData.publicDisplayName }}</h1> + <div class="line-height-150">Manage public and private information for this organization.</div> + </div> + <div class="d-flex"> + <div class="side-bar"> + <div class="logo-container mb-20"> + <img + src="{{ memberData.logoUrl }}" + onerror="this.src='../../content/images/member-logo-placeholder.svg'" + alt="Member logo" + /> + </div> + <div class="text-center"> + <img src="./../../../../content/images/lockpad.svg" alt="Locked" /> + </div> + <div class="text-center m-1"> + <em class="wide-text font-size-12 line-height-150">Logo upload coming soon</em> + </div> + </div> + <div class="main-section"> + <form (ngSubmit)="save()" name="editForm" role="form" [formGroup]="editForm"> + <div *ngIf="invalidForm" class="warning d-flex w-75 p-3 mb-3"> + <img src="./../../../../content/images/error-sign.svg" alt="Warning sign" class="p-2" /> + <div> + <div class="mb-2 font-size-12 wide-text font-weight-bold line-height-150">Your changes cannot be saved</div> + <div class="font-size-12 wide-text line-height-150"> + Please fix the issues with the public details before trying to save again + </div> + </div> + </div> + <h2 class="mb-30 wide-text font-size-24">Organization details</h2> + + <!-- Organization name --> + + <h3 class="mb-20 font-size-16 font-weight-bold">Organization name</h3> + <div class="form-group mb-30"> + <input + type="text" + class="form-control wide-text-25 org-name-input-field" + name="orgName" + formControlName="orgName" + (input)="editForm.get('orgName')?.markAsUntouched()" + [ngClass]="{ + 'text-danger': + editForm.get('orgName')?.invalid && editForm.get('orgName')?.touched && editForm.get('orgName')?.dirty, + 'input-field-default-border': !editForm.get('orgName')?.dirty || !editForm.get('orgName')?.touched + }" + /> + <ng-template #validOrgName> + <small class="wide-text font-size-12 form-text color-gray" + >The legal or official name for this organization. Max 41 characters.</small + > + </ng-template> + <div + *ngIf=" + editForm.get('orgName')?.invalid && editForm.get('orgName')?.touched && editForm.get('orgName')?.dirty; + else validOrgName + " + > + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('orgName')?.errors)!['required']" + > + Organization name cannot be empty + </small> + <div> + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('orgName')?.errors)!['maxlength']" + > + Organization name is too long. Please use 41 characters or less. + </small> + </div> + </div> + </div> + + <!-- Billing address --> + + <h3 class="mb-20 font-size-16 font-weight-bold">Billing address</h3> + <div class="form-group mb-20"> + <label + class="wide-text font-size-12 font-weight-bold" + [ngClass]="{ 'text-danger': editForm.get('street')?.invalid && editForm.get('street')?.touched }" + >Street</label + > + <input + type="text" + class="form-control wide-text-25 w-75" + name="street" + formControlName="street" + (input)="editForm.get('street')?.markAsUntouched()" + [ngClass]="{ + 'text-danger': editForm.get('street')?.invalid && editForm.get('street')?.touched, + 'input-field-default-border': !editForm.get('street')?.dirty || !editForm.get('street')?.touched + }" + /> + <div *ngIf="editForm.get('street')?.invalid && editForm.get('street')?.touched"> + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('street')?.errors)!['maxlength']" + > + Street name is too long. Please use 255 characters or less. + </small> + </div> + </div> + <div class="form-group mb-20"> + <label + class="wide-text font-size-12 font-weight-bold" + [ngClass]="{ 'text-danger': editForm.get('city')?.invalid && editForm.get('city')?.touched }" + >City</label + > + <input + type="text" + class="form-control wide-text-25 w-75" + name="city" + formControlName="city" + (input)="editForm.get('city')?.markAsUntouched()" + [ngClass]="{ + 'text-danger': editForm.get('city')?.invalid && editForm.get('city')?.touched, + 'input-field-default-border': !editForm.get('city')?.dirty || !editForm.get('city')?.touched + }" + /> + <div *ngIf="editForm.get('city')?.invalid && editForm.get('city')?.touched"> + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('city')?.errors)!['maxlength']" + > + City name is too long. Please use 40 characters or less. + </small> + </div> + </div> + <div class="form-group mb-20" *ngIf="states"> + <label + class="wide-text font-size-12 font-weight-bold" + [ngClass]="{ + 'text-danger': editForm.get('state')?.invalid && editForm.get('state')?.touched + }" + >State/Province</label + > + <select + class="form-control font-size-14 wide-text-25 w-75" + name="state" + formControlName="state" + [ngClass]="{ + 'text-danger': editForm.get('state')?.invalid && editForm.get('state')?.dirty, + 'input-field-default-border': !editForm.get('state')?.dirty + }" + > + <option selected [value]="null">-- No state or province --</option> + <option *ngFor="let state of states" [value]="state.name" class="form-field-text-color-default"> + {{ state.name }} + </option> + </select> + </div> + <div class="form-group mb-20"> + <label + class="wide-text font-size-12 font-weight-bold" + [ngClass]="{ 'text-danger': editForm.get('country')?.invalid && editForm.get('country')?.dirty }" + >Country</label + > + <input + readonly + class="form-control font-size-14 wide-text-25 w-75" + name="country" + formControlName="country" + /> + </div> + <div class="form-group mb-30"> + <label + class="wide-text font-size-12 font-weight-bold" + [ngClass]="{ 'text-danger': editForm.get('postcode')?.invalid && editForm.get('postcode')?.touched }" + >ZIP/Postcode</label + > + <input + type="text" + class="form-control wide-text-25 postcode-input-field" + name="postcode" + formControlName="postcode" + (input)="editForm.get('postcode')?.markAsUntouched()" + [ngClass]="{ + 'text-danger': editForm.get('postcode')?.invalid && editForm.get('postcode')?.touched, + 'input-field-default-border': !editForm.get('postcode')?.dirty || !editForm.get('postcode')?.touched + }" + /> + <div *ngIf="editForm.get('postcode')?.invalid && editForm.get('postcode')?.touched"> + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('postcode')?.errors)!['maxlength']" + > + ZIP/Postcode is too long. Please use 20 characters or less. + </small> + </div> + </div> + + <!-- Trademark license --> + + <h3 class="font-weight-bold font-size-16 mb-10">Trademark license</h3> + <div class="font-size-14 wide-text-25 mb-20 line-height-150"> + Can ORCID use this organization's trademarked name and logos? + </div> + <div class="mb-40"> + <div class="form-group d-flex"> + <input + type="radio" + id="trademarkLicenseYes" + class="form-control radio mr-8" + name="trademarkLicense" + value="Yes" + formControlName="trademarkLicense" + [ngClass]="{ + 'outline-danger': editForm.get('trademarkLicense')?.invalid && editForm.get('trademarkLicense')?.dirty + }" + /> + <label for="trademarkLicenseYes" class="wide-text-25 font-size-14 font-weight-normal" + ><strong>YES</strong> - ORCID can use this organization's trademarked name and logos</label + > + </div> + <div class="form-group d-flex"> + <input + type="radio" + id="trademarkLicenseNo" + class="form-control radio mr-8" + name="trademarkLicense" + value="No" + formControlName="trademarkLicense" + [ngClass]="{ + 'outline-danger': editForm.get('trademarkLicense')?.invalid && editForm.get('trademarkLicense')?.dirty + }" + /> + <label for="trademarkLicenseNo" class="wide-text-25 font-size-14 font-weight-normal" + ><strong>NO</strong> - ORCID cannot use this organization's trademarked name and logos</label + > + </div> + <div *ngIf="editForm.get('trademarkLicense')?.invalid && editForm.get('trademarkLicense')?.dirty"> + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('trademarkLicense')?.errors)!['requred']" + > + Please select a trademark license option + </small> + </div> + </div> + + <!-- PUBLIC DETAILS --> + + <h2 class="wide-text font-size-24">Public details</h2> + <div class="wide-text-25 mb-20 font-size-14 line-height-150"> + Organization information to be displayed publicly, such as on the + <a href="{{ MEMBER_LIST_URL }} " target="_blank">ORCID member list</a> + </div> + <div class="mb-5"> + <div class="form-group w-75"> + <label + class="wide-text font-size-12" + [ngClass]="{ + 'text-danger': + editForm.get('publicName')?.invalid && + editForm.get('publicName')?.touched && + editForm.get('publicName')?.dirty + }" + >Public display name</label + > + <input + type="text" + class="form-control" + name="publicName" + (input)="editForm.get('publicName')?.markAsUntouched()" + formControlName="publicName" + [ngClass]="{ + 'text-danger': + editForm.get('publicName')?.invalid && + editForm.get('publicName')?.touched && + editForm.get('publicName')?.dirty, + 'input-field-default-border': !editForm.get('publicName')?.touched || !editForm.get('publicName')?.dirty + }" + /> + <div *ngIf="editForm.get('publicName')?.invalid"> + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('publicName')?.errors)!['requred']" + > + Public organization name cannot be empty + </small> + <div> + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('publicName')?.errors)!['maxlength']" + > + Public organization name is too long. Please use 255 characters or less. + </small> + </div> + </div> + </div> + <div class="form-group"> + <label class="wide-text font-size-12">Organization description</label> + <quill-editor + class="d-block w-75 description" + formControlName="description" + format="html" + [styles]="quillStyles" + [modules]="quillConfig" + [placeholder]="'A brief description of the organization'" + > + </quill-editor> + <div *ngIf="editForm.get('description')?.invalid"> + <small + class="wide-text font-size-12 form-text text-danger" + *ngIf="(editForm.get('description')?.errors)!['maxlength']" + > + Organization description is too long. Please use 5000 characters or less. + </small> + </div> + </div> + <div class="form-group w-75"> + <label + class="wide-text font-size-12" + [ngClass]="{ + 'text-danger': + editForm.get('website')?.invalid && editForm.get('website')?.touched && editForm.get('website')?.dirty + }" + >Website</label + > + <input + type="text" + class="form-control" + name="website" + (input)="editForm.get('website')?.markAsUntouched()" + formControlName="website" + [ngClass]="{ + 'text-danger': + editForm.get('website')?.invalid && + editForm.get('website')?.touched && + editForm.get('website')?.dirty, + 'input-field-default-border': !editForm.get('website')?.touched || !editForm.get('website')?.dirty + }" + /> + <ng-template #validWebsite> + <small class="wide-text font-size-12 color-gray" + >Links should be in the full URL format e.g. http://www.website.com</small + > + </ng-template> + <div *ngIf="editForm.get('website')?.invalid && editForm.get('website')?.touched; else validWebsite"> + <small class="wide-text font-size-12 text-danger" *ngIf="(editForm.get('website')?.errors)!['pattern']"> + Please enter a valid website URL, for example http://www.website.com + </small> + <div> + <small + class="wide-text font-size-12 text-danger" + *ngIf="(editForm.get('website')?.errors)!['maxlength']" + > + Website is too long. Please use 255 characters or less. + </small> + </div> + </div> + </div> + <div class="form-group mb-40 w-75"> + <label + [ngClass]="{ 'text-danger': editForm.get('email')?.invalid && editForm.get('email')?.touched }" + class="wide-text font-size-12" + >Email</label + > + <input + type="text" + class="form-control" + name="email" + (input)="editForm.get('email')?.markAsUntouched()" + formControlName="email" + [ngClass]="{ + 'text-danger': editForm.get('email')?.invalid && editForm.get('email')?.touched, + 'input-field-default-border': !editForm.get('email')?.touched + }" + /> + <ng-template #validEmail> + <small class="wide-text font-size-12 color-gray" + >Emails should be in the standard format e.g. contactus@website.com</small + > + </ng-template> + <div *ngIf="editForm.get('email')?.invalid && editForm.get('email')?.touched; else validEmail"> + <small class="wide-text font-size-12 text-danger" *ngIf="(editForm.get('email')?.errors)!['pattern']"> + Please enter a valid email address, for example contactus@website.com + </small> + <div> + <small class="wide-text font-size-12 text-danger" *ngIf="(editForm.get('email')?.errors)!['maxlength']"> + Email is too long. Please use 80 characters or less. + </small> + </div> + </div> + </div> + <button type="submit" [disabled]="isSaving" class="btn btn-primary">Save changes</button> + <button type="button" class="btn btn-outline-primary" routerLink="">Cancel</button> + </div> + </form> + <div *ngIf="memberData.orgIds" class="mb-40"> + <div class="d-flex mb-2"> + <h3 class="mb-0 mr-2">Identifiers</h3> + </div> + <div class="wide-text font-size-12 coming-soon line-height-150"> + <em + >We did our best to register the right organization identifiers in our systems when you became an ORCID + member. If you think any are missing (ROR, GRID, RINGGOLD or Funder Registry ID) + <a href="mailto:membership@orcid.org">contact your engagement support lead</a> to register additional + IDs</em + > + </div> + <div class="row ml-0 d-flex justify-content-between contact"> + <h4 class="w-66 font-size-14">ID</h4> + <h4 class="w-33 font-size-14">Type</h4> + </div> + <hr class="green-hr mb-0" /> + <ul class="ml-0 pl-0 mb-0" *ngFor="let orgId of orgIdsTransformed | keyvalue; let i = index"> + <li class="row ml-0 pt-16 pb-16 d-flex justify-content-between contact"> + <div class="w-66 line-height-150">{{ orgId.value }}</div> + <div class="w-33 line-height-150">{{ orgId.key }}</div> + </li> + <hr *ngIf="!(i == objectKeys(memberData.orgIds).length - 1)" class="mb-0 mt-0" /> + </ul> + </div> + <div *ngIf="memberData.isConsortiumLead"> + <h3 class="mb-4 font-weight-bold"> + Consortium Members + <span class="font-weight-normal">({{ memberData.consortiumMembers?.length }})</span> + </h3> + <h4 class="font-size-14">Member name</h4> + <hr class="green-hr" /> + <div *ngFor="let consortiumMember of memberData.consortiumMembers; let i = index"> + <div>{{ consortiumMember.orgName }}</div> + <hr *ngIf="memberData.consortiumMembers && i + 1 < memberData.consortiumMembers!.length" /> + </div> + </div> + </div> + </div> +</div> diff --git a/ui/src/app/home/member-info/member-info-edit.component.scss b/ui/src/app/home/member-info/member-info-edit.component.scss new file mode 100644 index 000000000..bc8539101 --- /dev/null +++ b/ui/src/app/home/member-info/member-info-edit.component.scss @@ -0,0 +1,130 @@ +@use '../../../content/scss/bootstrap-variables' as global; + +button, +textarea, +input, +span, +h1, +h2, +h3, +h4, +label, +small { + line-height: 150%; +} + +button { + font-size: 14px; +} + +.side-bar { + max-width: 300px; + height: 100%; + padding: 1.25rem; + flex: 1; +} + +.logo-container { + img { + width: 200px; + height: auto; + align-self: center; + } + width: 208px; + height: 208px; + display: flex; + justify-content: center; + margin-left: auto; + margin-right: auto; +} + +.contact { + > div, + h4 { + width: 10%; + text-align: left; + flex-grow: 1; + max-width: 200px; + font-size: 14px; + } + word-wrap: break-word; +} + +.main-section { + label { + font-weight: bold; + margin-bottom: 4px; + } + flex: 1; + padding: 1.25rem; +} + +.input-prompt { + color: global.$black-transparent; +} + +textarea { + min-height: 9rem; + font-size: 14px; + letter-spacing: 0.25px; +} + +input { + font-size: 14px; + letter-spacing: 0.25px; +} + +input, +select, +option { + height: 40px; +} + +.coming-soon { + margin-bottom: 24px; +} + +.lockpad { + margin-top: -4px; +} + +.ng-invalid:not(form) { + border: 1px solid #d32f2f; + border-radius: 2px; +} + +.warning { + img { + padding: 0 16px 0 8px !important; + margin-top: -1.75rem; + } + border: 2px solid #b71c1c; + border-radius: 4px; +} + +.radio { + height: 1.125rem; + width: 1.125rem; + accent-color: global.$info; +} + +.postcode-input-field { + max-width: 180px; +} + +.org-name-input-field { + max-width: 400px; +} + +:host ::ng-deep .ql-editor a { + color: global.$info; + text-decoration: none; +} + +:host ::ng-deep .ql-toolbar button:hover .ql-stroke { + stroke: global.$info !important; +} + +:host ::ng-deep .ql-active .ql-stroke { + stroke: global.$info !important; +} diff --git a/ui/src/app/home/member-info/member-info-edit.component.spec.ts b/ui/src/app/home/member-info/member-info-edit.component.spec.ts new file mode 100644 index 000000000..447862b83 --- /dev/null +++ b/ui/src/app/home/member-info/member-info-edit.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MemberInfoEditComponent } from './member-info-edit.component'; + +describe('MemberInfoEditComponent', () => { + let component: MemberInfoEditComponent; + let fixture: ComponentFixture<MemberInfoEditComponent>; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [MemberInfoEditComponent] + }); + fixture = TestBed.createComponent(MemberInfoEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/home/member-info/member-info-edit.component.ts b/ui/src/app/home/member-info/member-info-edit.component.ts new file mode 100644 index 000000000..08f95af49 --- /dev/null +++ b/ui/src/app/home/member-info/member-info-edit.component.ts @@ -0,0 +1,227 @@ +import { KeyValue } from '@angular/common' +import { Component, OnDestroy, OnInit } from '@angular/core' +import { FormBuilder, FormControl, Validators } from '@angular/forms' +import { ActivatedRoute, Router } from '@angular/router' +import { EMPTY, Subject, combineLatest } from 'rxjs' +import { switchMap, take, takeUntil } from 'rxjs/operators' +import { AccountService } from 'src/app/account' +import { IAccount } from 'src/app/account/model/account.model' +import { EMAIL_REGEXP, URL_REGEXP } from 'src/app/app.constants' +import { ISFAddress } from 'src/app/member/model/salesforce-address.model' +import { ISFCountry } from 'src/app/member/model/salesforce-country.model' +import { ISFState } from 'src/app/member/model/salesforce-country.model copy' +import { ISFMemberData } from 'src/app/member/model/salesforce-member-data.model' +import { ISFMemberUpdate, SFMemberUpdate } from 'src/app/member/model/salesforce-member-update.model' +import { MemberService } from 'src/app/member/service/member.service' + +@Component({ + selector: 'app-member-info-edit', + templateUrl: './member-info-edit.component.html', + styleUrls: ['./member-info-edit.component.scss'], +}) +export class MemberInfoEditComponent implements OnInit, OnDestroy { + countries: ISFCountry[] | undefined + country: ISFCountry | undefined + states: ISFState[] | undefined + account: IAccount | undefined | null + memberData: ISFMemberData | undefined | null + objectKeys = Object.keys + orgIdsTransformed: KeyValue<string, string[]>[] = [] + // TODO move to constants + MEMBER_LIST_URL: string = 'https://orcid.org/members' + isSaving = false + invalidForm: boolean | undefined + managedMember: string | undefined + destroy$ = new Subject() + quillConfig = { + toolbar: [['bold', 'italic'], [{ list: 'ordered' }, { list: 'bullet' }], ['link']], + } + quillStyles = { + fontFamily: 'inherit', + fontSize: '14px', + letterSpacing: '0.25px', + marginRight: '0', + } + + editForm = this.fb.group({ + orgName: new FormControl<null | string>(null, [Validators.required, Validators.maxLength(41)]), + street: new FormControl<null | string>(null, [Validators.maxLength(255)]), + city: new FormControl<null | string>(null, [Validators.maxLength(40)]), + state: new FormControl<null | string>(null, [Validators.maxLength(80)]), + country: new FormControl<null | string>(null, [Validators.required]), + postcode: new FormControl<null | string>(null, [Validators.maxLength(20)]), + trademarkLicense: new FormControl<null | string>(null, [Validators.required]), + publicName: new FormControl<null | string>(null, [Validators.required, Validators.maxLength(255)]), + description: new FormControl<null | string>(null, [Validators.maxLength(5000)]), + website: new FormControl<null | string>(null, [Validators.pattern(URL_REGEXP), Validators.maxLength(255)]), + email: new FormControl<null | string>(null, [Validators.pattern(EMAIL_REGEXP), Validators.maxLength(80)]), + }) + + constructor( + private memberService: MemberService, + private accountService: AccountService, + private fb: FormBuilder, + protected activatedRoute: ActivatedRoute, + private router: Router + ) {} + + ngOnInit() { + combineLatest([this.activatedRoute.params, this.accountService.getAccountData()]) + .pipe( + switchMap(([params, account]) => { + if (params['id']) { + this.managedMember = params['id'] + } + if (account) { + this.account = account + if (this.managedMember) { + this.memberService.setManagedMember(params['id']) + return this.memberService.getMemberData(this.managedMember) + } else { + return this.memberService.getMemberData(account?.salesforceId) + } + } else { + return EMPTY + } + }), + takeUntil(this.destroy$) + ) + .subscribe((data) => { + this.memberData = data + this.orgIdsTransformed = Object.entries(this.memberData?.orgIds || {}).map(([key, value]) => ({ key, value })) + this.validateUrl() + if (data) { + this.updateForm(data) + } + }) + this.memberService + .getCountries() + .pipe(take(1)) + .subscribe((countries) => { + this.countries = countries + if (this.memberData) { + this.updateForm(this.memberData) + } + }) + + this.editForm.valueChanges.subscribe(() => { + if (this.editForm.status === 'VALID') { + this.invalidForm = false + } + }) + } + + validateUrl() { + if (this.memberData?.website && !/(http(s?)):\/\//i.test(this.memberData.website)) { + this.memberData.website = 'http://' + this.memberData.website + } + } + + updateForm(data: ISFMemberData) { + if (data && data.id) { + this.editForm.patchValue({ + orgName: data.name || null, + trademarkLicense: data.trademarkLicense ? data.trademarkLicense : 'No', + publicName: data.publicDisplayName, + description: data.publicDisplayDescriptionHtml, + website: data.website, + email: data.publicDisplayEmail, + }) + if (data.billingAddress) { + if (this.countries) { + this.country = this.countries.find((country) => country.name === data.billingAddress?.country) + if (this.country) { + this.states = this.country.states + } else { + console.error('Unable to find country: ', data.billingAddress.country) + } + } + this.editForm.patchValue({ + street: data.billingAddress.street, + city: data.billingAddress.city, + state: data.billingAddress.state, + country: data.billingAddress.country, + postcode: data.billingAddress.postalCode, + }) + } + } + } + + filterCRFID(id: string) { + return id.replace(/^.*dx.doi.org\//g, '') + } + + createDetailsFromForm(): ISFMemberUpdate { + const address: ISFAddress = { + street: this.editForm.get(['street'])?.value, + city: this.editForm.get(['city'])?.value, + state: + this.editForm.get(['state'])?.value == '-- No state or province --' + ? undefined + : this.editForm.get(['state'])?.value, + country: this.editForm.get(['country'])?.value, + countryCode: this.country?.code, + postalCode: this.editForm.get(['postcode'])?.value, + } + return { + ...new SFMemberUpdate(), + orgName: this.editForm.get(['orgName'])?.value, + billingAddress: address, + trademarkLicense: this.editForm.get(['trademarkLicense'])?.value, + publicName: this.editForm.get(['publicName'])?.value, + description: this.editForm.get(['description'])?.value, + website: this.editForm.get(['website'])?.value, + email: this.editForm.get(['email'])?.value, + } + } + + save() { + if (this.editForm.status === 'INVALID') { + this.invalidForm = true + this.editForm.markAllAsTouched() + Object.keys(this.editForm.controls).forEach((key) => { + this.editForm.get(key)?.markAsDirty() + }) + } else { + this.invalidForm = false + this.isSaving = true + const details: ISFMemberUpdate = this.createDetailsFromForm() + + if (this.memberData?.id) { + this.memberService.updateMemberDetails(details, this.memberData?.id).subscribe({ + next: () => { + this.memberService.setMemberData({ + ...this.memberData, + publicDisplayDescriptionHtml: details.description, + publicDisplayName: details.publicName, + name: details.orgName, + billingAddress: details.billingAddress, + trademarkLicense: details.trademarkLicense, + publicDisplayEmail: details.email, + website: details.website, + }) + this.onSaveSuccess() + }, + error: (err: any) => { + console.error(err) + this.onSaveError() + }, + }) + } + } + } + + ngOnDestroy() { + this.destroy$.next(true) + this.destroy$.complete() + } + + onSaveSuccess() { + this.isSaving = false + this.router.navigate(['']) + } + + onSaveError() { + this.isSaving = false + } +} diff --git a/ui/src/app/home/member-info/member-info.component.ts b/ui/src/app/home/member-info/member-info.component.ts index 52d1f9152..3b36d3929 100644 --- a/ui/src/app/home/member-info/member-info.component.ts +++ b/ui/src/app/home/member-info/member-info.component.ts @@ -49,7 +49,6 @@ export class MemberInfoComponent implements OnInit, OnDestroy { if (params['id']) { this.managedMember = params['id'] } - if (account) { this.account = account if (this.managedMember) { diff --git a/ui/src/app/member/service/member.service.ts b/ui/src/app/member/service/member.service.ts index 87571aeb3..3dfd5e06f 100644 --- a/ui/src/app/member/service/member.service.ts +++ b/ui/src/app/member/service/member.service.ts @@ -29,6 +29,7 @@ import { import { ISFCountry } from '../model/salesforce-country.model' import { ISFRawMemberContact, ISFRawMemberContacts, SFMemberContact } from '../model/salesforce-member-contact.model' import { ISFRawMemberOrgIds, SFMemberOrgIds } from '../model/salesforce-member-org-id.model' +import { ISFMemberUpdate } from '../model/salesforce-member-update.model' @Injectable({ providedIn: 'root' }) export class MemberService { @@ -145,14 +146,18 @@ export class MemberService { return this.memberData.asObservable() } + setMemberData(memberData: ISFMemberData | undefined | null) { + this.memberData.next(memberData) + } + private fetchMemberData(salesforceId: string) { this.fetchingMemberDataState = true - this.getSFMemberData(salesforceId) + this.fetchSFMemberData(salesforceId) .pipe( switchMap((res) => { this.memberData.next(res) return combineLatest([ - this.getMemberContacts(salesforceId), + this.fetchMemberContacts(salesforceId), this.getMemberOrgIds(salesforceId), this.getConsortiaLeadName(res.consortiaLeadId!), this.getIsConsortiumLead(salesforceId), @@ -170,7 +175,33 @@ export class MemberService { .subscribe() } - getMemberContacts(salesforceId: string): Observable<SFMemberContact[]> { + getCountries(): Observable<ISFCountry[] | undefined> { + if (!this.countries.value) { + return this.fetchCountries() + } + return this.countries.asObservable() + } + + fetchCountries(): Observable<ISFCountry[]> { + return this.http.get<ISFCountry[]>(`${this.resourceUrl}/countries`).pipe( + catchError((error) => { + return of('An error occurred:', error) + }), + tap((res: ISFCountry[]) => { + if (res) { + this.countries.next(res) + } else { + console.error('Request failed:', res) + } + }) + ) + } + + updateMemberDetails(memberDetails: ISFMemberUpdate, salesforceId: string): Observable<ISFMemberUpdate> { + return this.http.put(`${this.resourceUrl}/members/${salesforceId}/member-details`, memberDetails) + } + + private fetchMemberContacts(salesforceId: string): Observable<SFMemberContact[]> { return this.http .get<ISFRawMemberContacts>(`${this.resourceUrl}/members/${salesforceId}/member-contacts`, { observe: 'response' }) .pipe( @@ -183,7 +214,7 @@ export class MemberService { ) } - getSFMemberData(salesforceId: string): Observable<SFMemberData> { + private fetchSFMemberData(salesforceId: string): Observable<SFMemberData> { return this.http.get<ISFRawMemberData>(`${this.resourceUrl}/members/${salesforceId}/member-details`).pipe( catchError((err) => { return of(err) diff --git a/ui/src/index.html b/ui/src/index.html index bee93e114..d37623d15 100644 --- a/ui/src/index.html +++ b/ui/src/index.html @@ -12,8 +12,6 @@ <link rel="shortcut icon" href="favicon.ico" /> <link rel="manifest" href="manifest.webapp" /> <link rel="stylesheet" href="content/css/loading.css" /> - <link rel="stylesheet" href="content/css/quill.snow.css" /> - <link rel="stylesheet" href="content/css/quill.core.css" /> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" From 8365a0269d2e6e145222fedd138d51dd88c28f5d Mon Sep 17 00:00:00 2001 From: andrej romanov <50377758+auumgn@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:57:14 +0300 Subject: [PATCH 2/3] lint --- .../member-info-edit.component.html | 223 +++++++++--------- .../member-info/member-info-edit.component.ts | 6 +- ui/src/app/user/users.component.ts | 5 +- 3 files changed, 118 insertions(+), 116 deletions(-) diff --git a/ui/src/app/home/member-info/member-info-edit.component.html b/ui/src/app/home/member-info/member-info-edit.component.html index d0a5e56b0..d922d3d08 100644 --- a/ui/src/app/home/member-info/member-info-edit.component.html +++ b/ui/src/app/home/member-info/member-info-edit.component.html @@ -89,19 +89,19 @@ <h3 class="mb-20 font-size-16 font-weight-bold">Billing address</h3> <label class="wide-text font-size-12 font-weight-bold" [ngClass]="{ 'text-danger': editForm.get('street')?.invalid && editForm.get('street')?.touched }" - >Street</label + ><input + type="text" + class="form-control wide-text-25 w-75" + name="street" + formControlName="street" + (input)="editForm.get('street')?.markAsUntouched()" + [ngClass]="{ + 'text-danger': editForm.get('street')?.invalid && editForm.get('street')?.touched, + 'input-field-default-border': !editForm.get('street')?.dirty || !editForm.get('street')?.touched + }" + />Street</label > - <input - type="text" - class="form-control wide-text-25 w-75" - name="street" - formControlName="street" - (input)="editForm.get('street')?.markAsUntouched()" - [ngClass]="{ - 'text-danger': editForm.get('street')?.invalid && editForm.get('street')?.touched, - 'input-field-default-border': !editForm.get('street')?.dirty || !editForm.get('street')?.touched - }" - /> + <div *ngIf="editForm.get('street')?.invalid && editForm.get('street')?.touched"> <small class="wide-text font-size-12 form-text text-danger" @@ -115,19 +115,19 @@ <h3 class="mb-20 font-size-16 font-weight-bold">Billing address</h3> <label class="wide-text font-size-12 font-weight-bold" [ngClass]="{ 'text-danger': editForm.get('city')?.invalid && editForm.get('city')?.touched }" - >City</label + ><input + type="text" + class="form-control wide-text-25 w-75" + name="city" + formControlName="city" + (input)="editForm.get('city')?.markAsUntouched()" + [ngClass]="{ + 'text-danger': editForm.get('city')?.invalid && editForm.get('city')?.touched, + 'input-field-default-border': !editForm.get('city')?.dirty || !editForm.get('city')?.touched + }" + />City</label > - <input - type="text" - class="form-control wide-text-25 w-75" - name="city" - formControlName="city" - (input)="editForm.get('city')?.markAsUntouched()" - [ngClass]="{ - 'text-danger': editForm.get('city')?.invalid && editForm.get('city')?.touched, - 'input-field-default-border': !editForm.get('city')?.dirty || !editForm.get('city')?.touched - }" - /> + <div *ngIf="editForm.get('city')?.invalid && editForm.get('city')?.touched"> <small class="wide-text font-size-12 form-text text-danger" @@ -143,53 +143,51 @@ <h3 class="mb-20 font-size-16 font-weight-bold">Billing address</h3> [ngClass]="{ 'text-danger': editForm.get('state')?.invalid && editForm.get('state')?.touched }" + ><select + class="form-control font-size-14 wide-text-25 w-75" + name="state" + formControlName="state" + [ngClass]="{ + 'text-danger': editForm.get('state')?.invalid && editForm.get('state')?.dirty, + 'input-field-default-border': !editForm.get('state')?.dirty + }" + > + <option selected [value]="null">-- No state or province --</option> + <option *ngFor="let state of states" [value]="state.name" class="form-field-text-color-default"> + {{ state.name }} + </option></select >State/Province</label > - <select - class="form-control font-size-14 wide-text-25 w-75" - name="state" - formControlName="state" - [ngClass]="{ - 'text-danger': editForm.get('state')?.invalid && editForm.get('state')?.dirty, - 'input-field-default-border': !editForm.get('state')?.dirty - }" - > - <option selected [value]="null">-- No state or province --</option> - <option *ngFor="let state of states" [value]="state.name" class="form-field-text-color-default"> - {{ state.name }} - </option> - </select> </div> <div class="form-group mb-20"> <label class="wide-text font-size-12 font-weight-bold" [ngClass]="{ 'text-danger': editForm.get('country')?.invalid && editForm.get('country')?.dirty }" - >Country</label + ><input + readonly + class="form-control font-size-14 wide-text-25 w-75" + name="country" + formControlName="country" + />Country</label > - <input - readonly - class="form-control font-size-14 wide-text-25 w-75" - name="country" - formControlName="country" - /> </div> <div class="form-group mb-30"> <label class="wide-text font-size-12 font-weight-bold" [ngClass]="{ 'text-danger': editForm.get('postcode')?.invalid && editForm.get('postcode')?.touched }" - >ZIP/Postcode</label + ><input + type="text" + class="form-control wide-text-25 postcode-input-field" + name="postcode" + formControlName="postcode" + (input)="editForm.get('postcode')?.markAsUntouched()" + [ngClass]="{ + 'text-danger': editForm.get('postcode')?.invalid && editForm.get('postcode')?.touched, + 'input-field-default-border': !editForm.get('postcode')?.dirty || !editForm.get('postcode')?.touched + }" + />ZIP/Postcode</label > - <input - type="text" - class="form-control wide-text-25 postcode-input-field" - name="postcode" - formControlName="postcode" - (input)="editForm.get('postcode')?.markAsUntouched()" - [ngClass]="{ - 'text-danger': editForm.get('postcode')?.invalid && editForm.get('postcode')?.touched, - 'input-field-default-border': !editForm.get('postcode')?.dirty || !editForm.get('postcode')?.touched - }" - /> + <div *ngIf="editForm.get('postcode')?.invalid && editForm.get('postcode')?.touched"> <small class="wide-text font-size-12 form-text text-danger" @@ -254,7 +252,7 @@ <h3 class="font-weight-bold font-size-16 mb-10">Trademark license</h3> <h2 class="wide-text font-size-24">Public details</h2> <div class="wide-text-25 mb-20 font-size-14 line-height-150"> Organization information to be displayed publicly, such as on the - <a href="{{ MEMBER_LIST_URL }} " target="_blank">ORCID member list</a> + <a href="{{ ORCID_BASE_URL }}/members" target="_blank">ORCID member list</a> </div> <div class="mb-5"> <div class="form-group w-75"> @@ -266,22 +264,23 @@ <h2 class="wide-text font-size-24">Public details</h2> editForm.get('publicName')?.touched && editForm.get('publicName')?.dirty }" - >Public display name</label + ><input + type="text" + class="form-control" + name="publicName" + (input)="editForm.get('publicName')?.markAsUntouched()" + formControlName="publicName" + [ngClass]="{ + 'text-danger': + editForm.get('publicName')?.invalid && + editForm.get('publicName')?.touched && + editForm.get('publicName')?.dirty, + 'input-field-default-border': + !editForm.get('publicName')?.touched || !editForm.get('publicName')?.dirty + }" + />Public display name</label > - <input - type="text" - class="form-control" - name="publicName" - (input)="editForm.get('publicName')?.markAsUntouched()" - formControlName="publicName" - [ngClass]="{ - 'text-danger': - editForm.get('publicName')?.invalid && - editForm.get('publicName')?.touched && - editForm.get('publicName')?.dirty, - 'input-field-default-border': !editForm.get('publicName')?.touched || !editForm.get('publicName')?.dirty - }" - /> + <div *ngIf="editForm.get('publicName')?.invalid"> <small class="wide-text font-size-12 form-text text-danger" @@ -300,16 +299,20 @@ <h2 class="wide-text font-size-24">Public details</h2> </div> </div> <div class="form-group"> - <label class="wide-text font-size-12">Organization description</label> - <quill-editor - class="d-block w-75 description" - formControlName="description" - format="html" - [styles]="quillStyles" - [modules]="quillConfig" - [placeholder]="'A brief description of the organization'" + <!-- eslint-disable-next-line --> + <label class="wide-text font-size-12" + ><quill-editor + class="d-block w-75 description" + formControlName="description" + format="html" + [styles]="quillStyles" + [modules]="quillConfig" + [placeholder]="'A brief description of the organization'" + > + </quill-editor + >Organization description</label > - </quill-editor> + <div *ngIf="editForm.get('description')?.invalid"> <small class="wide-text font-size-12 form-text text-danger" @@ -326,22 +329,22 @@ <h2 class="wide-text font-size-24">Public details</h2> 'text-danger': editForm.get('website')?.invalid && editForm.get('website')?.touched && editForm.get('website')?.dirty }" - >Website</label + ><input + type="text" + class="form-control" + name="website" + (input)="editForm.get('website')?.markAsUntouched()" + formControlName="website" + [ngClass]="{ + 'text-danger': + editForm.get('website')?.invalid && + editForm.get('website')?.touched && + editForm.get('website')?.dirty, + 'input-field-default-border': !editForm.get('website')?.touched || !editForm.get('website')?.dirty + }" + />Website</label > - <input - type="text" - class="form-control" - name="website" - (input)="editForm.get('website')?.markAsUntouched()" - formControlName="website" - [ngClass]="{ - 'text-danger': - editForm.get('website')?.invalid && - editForm.get('website')?.touched && - editForm.get('website')?.dirty, - 'input-field-default-border': !editForm.get('website')?.touched || !editForm.get('website')?.dirty - }" - /> + <ng-template #validWebsite> <small class="wide-text font-size-12 color-gray" >Links should be in the full URL format e.g. http://www.website.com</small @@ -365,19 +368,19 @@ <h2 class="wide-text font-size-24">Public details</h2> <label [ngClass]="{ 'text-danger': editForm.get('email')?.invalid && editForm.get('email')?.touched }" class="wide-text font-size-12" - >Email</label + ><input + type="text" + class="form-control" + name="email" + (input)="editForm.get('email')?.markAsUntouched()" + formControlName="email" + [ngClass]="{ + 'text-danger': editForm.get('email')?.invalid && editForm.get('email')?.touched, + 'input-field-default-border': !editForm.get('email')?.touched + }" + />Email</label > - <input - type="text" - class="form-control" - name="email" - (input)="editForm.get('email')?.markAsUntouched()" - formControlName="email" - [ngClass]="{ - 'text-danger': editForm.get('email')?.invalid && editForm.get('email')?.touched, - 'input-field-default-border': !editForm.get('email')?.touched - }" - /> + <ng-template #validEmail> <small class="wide-text font-size-12 color-gray" >Emails should be in the standard format e.g. contactus@website.com</small @@ -420,7 +423,7 @@ <h4 class="w-33 font-size-14">Type</h4> <div class="w-66 line-height-150">{{ orgId.value }}</div> <div class="w-33 line-height-150">{{ orgId.key }}</div> </li> - <hr *ngIf="!(i == objectKeys(memberData.orgIds).length - 1)" class="mb-0 mt-0" /> + <hr *ngIf="!(i === objectKeys(memberData.orgIds).length - 1)" class="mb-0 mt-0" /> </ul> </div> <div *ngIf="memberData.isConsortiumLead"> diff --git a/ui/src/app/home/member-info/member-info-edit.component.ts b/ui/src/app/home/member-info/member-info-edit.component.ts index 08f95af49..71ef9c27e 100644 --- a/ui/src/app/home/member-info/member-info-edit.component.ts +++ b/ui/src/app/home/member-info/member-info-edit.component.ts @@ -6,7 +6,7 @@ import { EMPTY, Subject, combineLatest } from 'rxjs' import { switchMap, take, takeUntil } from 'rxjs/operators' import { AccountService } from 'src/app/account' import { IAccount } from 'src/app/account/model/account.model' -import { EMAIL_REGEXP, URL_REGEXP } from 'src/app/app.constants' +import { EMAIL_REGEXP, URL_REGEXP, ORCID_BASE_URL } from 'src/app/app.constants' import { ISFAddress } from 'src/app/member/model/salesforce-address.model' import { ISFCountry } from 'src/app/member/model/salesforce-country.model' import { ISFState } from 'src/app/member/model/salesforce-country.model copy' @@ -27,8 +27,8 @@ export class MemberInfoEditComponent implements OnInit, OnDestroy { memberData: ISFMemberData | undefined | null objectKeys = Object.keys orgIdsTransformed: KeyValue<string, string[]>[] = [] - // TODO move to constants - MEMBER_LIST_URL: string = 'https://orcid.org/members' + ORCID_BASE_URL = ORCID_BASE_URL + isSaving = false invalidForm: boolean | undefined managedMember: string | undefined diff --git a/ui/src/app/user/users.component.ts b/ui/src/app/user/users.component.ts index 41ba39a0f..1b747927c 100644 --- a/ui/src/app/user/users.component.ts +++ b/ui/src/app/user/users.component.ts @@ -1,9 +1,8 @@ import { Component, NgZone, OnDestroy, OnInit } from '@angular/core' import { IUser } from './model/user.model' -import { Subscription, filter } from 'rxjs' +import { Subscription } from 'rxjs' import { UserService } from './service/user.service' -import { HttpErrorResponse } from '@angular/common/http' -import { IUserPage, UserPage } from './model/user-page.model' +import { IUserPage } from './model/user-page.model' import { faCheckCircle, faPencilAlt, From fd94af1a87fe5ee0c7b6de525a7ba97b537beba5 Mon Sep 17 00:00:00 2001 From: andrej romanov <50377758+auumgn@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:59:29 +0300 Subject: [PATCH 3/3] stub test --- .../member-info-edit.component.spec.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/ui/src/app/home/member-info/member-info-edit.component.spec.ts b/ui/src/app/home/member-info/member-info-edit.component.spec.ts index 447862b83..aa8bed363 100644 --- a/ui/src/app/home/member-info/member-info-edit.component.spec.ts +++ b/ui/src/app/home/member-info/member-info-edit.component.spec.ts @@ -1,21 +1,23 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing' -import { MemberInfoEditComponent } from './member-info-edit.component'; +import { MemberInfoEditComponent } from './member-info-edit.component' +import { AppModule } from 'src/app/app.module' describe('MemberInfoEditComponent', () => { - let component: MemberInfoEditComponent; - let fixture: ComponentFixture<MemberInfoEditComponent>; + let component: MemberInfoEditComponent + let fixture: ComponentFixture<MemberInfoEditComponent> beforeEach(() => { TestBed.configureTestingModule({ - declarations: [MemberInfoEditComponent] - }); - fixture = TestBed.createComponent(MemberInfoEditComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); + imports: [AppModule], + declarations: [MemberInfoEditComponent], + }) + fixture = TestBed.createComponent(MemberInfoEditComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) it('should create', () => { - expect(component).toBeTruthy(); - }); -}); + expect(component).toBeTruthy() + }) +})