diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 202d02309a..c46d2127d6 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -5,6 +5,7 @@ import { ApplicationRoutes, routerPublicPageUrl, routerReactivation, + routerSummaryPageUrl, routerThirdPartySignInMatch, } from './constants' import { AuthenticatedGuard } from './guards/authenticated.guard' @@ -22,6 +23,13 @@ const routes: Routes = [ path: ApplicationRoutes.home, loadChildren: () => import('./home/home.module').then((m) => m.HomeModule), }, + { + matcher: routerSummaryPageUrl, + loadChildren: () => + import('./trusted-summary/trusted-summary.module').then( + (m) => m.TrustedSummaryModule + ), + }, { matcher: routerPublicPageUrl, loadChildren: () => diff --git a/src/app/app.component.html b/src/app/app.component.html index 8995896c64..ad608ff4d1 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -3,7 +3,11 @@ -
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0c362b5b34..6b1d819228 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -130,9 +130,10 @@ export class AppComponent { } setPlatformClasses(platformInfo: PlatformInfo, oauthSessionFound?: boolean) { - this.headlessMode = + const isOauth = (platformInfo.hasOauthParameters || oauthSessionFound) && this.currentRouteIsHeadlessOnOauthPage + this.headlessMode = isOauth || platformInfo.summaryScreen this.ie = platformInfo.ie this.edge = platformInfo.edge this.tabletOrHandset = platformInfo.tabletOrHandset diff --git a/src/app/cdk/platform-info/platform-info.service.ts b/src/app/cdk/platform-info/platform-info.service.ts index 8605f24ca5..01257d5343 100644 --- a/src/app/cdk/platform-info/platform-info.service.ts +++ b/src/app/cdk/platform-info/platform-info.service.ts @@ -36,6 +36,7 @@ export class PlatformInfoService { currentRoute: '', reactivation: false, reactivationCode: '', + summaryScreen: false, } platformSubject = new BehaviorSubject(this.platform) @@ -231,6 +232,7 @@ export class PlatformInfoService { this.window.location.pathname.toLowerCase().indexOf('reactivation') >= 0, reactivationCode: this.getReactivationCode(), + summaryScreen: this.window.location.pathname.endsWith('/summary'), } this.platformSubject.next(this.platform) return this.platformSubject.asObservable() diff --git a/src/app/cdk/platform-info/platform-info.type.ts b/src/app/cdk/platform-info/platform-info.type.ts index bc43f8f725..acb583110f 100644 --- a/src/app/cdk/platform-info/platform-info.type.ts +++ b/src/app/cdk/platform-info/platform-info.type.ts @@ -23,4 +23,5 @@ export interface PlatformInfo { currentRoute: string reactivation: boolean reactivationCode: string + summaryScreen: boolean } diff --git a/src/app/constants.ts b/src/app/constants.ts index b62df73c25..0ea5047377 100644 --- a/src/app/constants.ts +++ b/src/app/constants.ts @@ -221,7 +221,18 @@ export function routerPublicPageUrl(segments: UrlSegment[]) { if (segments[0] && isValidOrcidFormat(segments[0].path)) { return { consumed: [segments[0]] } } - if (segments[1] && isValidOrcidFormat(segments[1].path)) { + return { + consumed: [], + } +} + +export function routerSummaryPageUrl(segments: UrlSegment[]) { + if ( + segments[0] && + isValidOrcidFormat(segments[0].path) && + segments[1] && + segments[1].path === 'summary' + ) { return { consumed: [segments[0], segments[1]] } } return { diff --git a/src/app/core/trusted-summary/trusted-summary.service.spec.ts b/src/app/core/trusted-summary/trusted-summary.service.spec.ts new file mode 100644 index 0000000000..a87f5c8548 --- /dev/null +++ b/src/app/core/trusted-summary/trusted-summary.service.spec.ts @@ -0,0 +1,27 @@ +import { TestBed } from '@angular/core/testing' + +import { TrustedSummaryService } from './trusted-summary.service' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { ErrorHandlerService } from '../error-handler/error-handler.service' + +describe('TrustedSummaryService', () => { + let service: TrustedSummaryService + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: ErrorHandlerService, userValue: {} }, + { + provide: ErrorHandlerService, + useValue: {}, + }, + ], + }) + service = TestBed.inject(TrustedSummaryService) + }) + + it('should be created', () => { + expect(service).toBeTruthy() + }) +}) diff --git a/src/app/core/trusted-summary/trusted-summary.service.ts b/src/app/core/trusted-summary/trusted-summary.service.ts new file mode 100644 index 0000000000..ad717969dc --- /dev/null +++ b/src/app/core/trusted-summary/trusted-summary.service.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { ErrorHandlerService } from '../error-handler/error-handler.service' +import { Observable } from 'rxjs' +import { TrustedSummary } from 'src/app/types/trust-summary' +import { environment } from 'src/environments/environment' +import { catchError, retry } from 'rxjs/operators' +import { ERROR_REPORT } from 'src/app/errors' + +@Injectable({ + providedIn: 'root', +}) +export class TrustedSummaryService { + headers = new HttpHeaders({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + }) + + constructor( + private _http: HttpClient, + private _errorHandler: ErrorHandlerService + ) {} + + getSummary(orcid): Observable { + let url = orcid + '/summary.json' + return this._http + .get(environment.BASE_URL + url, { + headers: this.headers, + }) + .pipe( + retry(3), + catchError((error) => + this._errorHandler.handleError(error, ERROR_REPORT.STANDARD_VERBOSE) + ) + ) + } +} diff --git a/src/app/shared/pipes/month-day-year-date-to-string/month-day-year-date-to-string.pipe.ts b/src/app/shared/pipes/month-day-year-date-to-string/month-day-year-date-to-string.pipe.ts index 375a82c3c2..531fd5d546 100644 --- a/src/app/shared/pipes/month-day-year-date-to-string/month-day-year-date-to-string.pipe.ts +++ b/src/app/shared/pipes/month-day-year-date-to-string/month-day-year-date-to-string.pipe.ts @@ -5,8 +5,8 @@ import { MonthDayYearDate } from 'src/app/types' name: 'monthDayYearDateToString', }) export class MonthDayYearDateToStringPipe implements PipeTransform { - transform(value: MonthDayYearDate | number): string { - if (typeof value === 'number') { + transform(value: MonthDayYearDate | number | string): string { + if (typeof value === 'number' || typeof value === 'string') { const date = new Date(value) value = { day: date.getUTCDate() + '', diff --git a/src/app/trusted-summary/component/summary-panel/summary-panel.component.html b/src/app/trusted-summary/component/summary-panel/summary-panel.component.html new file mode 100644 index 0000000000..5c4e640479 --- /dev/null +++ b/src/app/trusted-summary/component/summary-panel/summary-panel.component.html @@ -0,0 +1,105 @@ +
+ +
+
+
+ check_circle + + +

+ {{ activity.organizationName }} +

+

+ {{ activity.organizationName }} +

+
+ + + + + + + + +
+
+ + + {{ activity.startDate | monthDayYearDateToString }} + + + to + {{ activity.endDate | monthDayYearDateToString }} + + +
{{ activity.role }}
+
{{ activity.type }}
+
+
+
+ +
diff --git a/src/app/trusted-summary/component/summary-panel/summary-panel.component.scss b/src/app/trusted-summary/component/summary-panel/summary-panel.component.scss new file mode 100644 index 0000000000..36be12373c --- /dev/null +++ b/src/app/trusted-summary/component/summary-panel/summary-panel.component.scss @@ -0,0 +1,78 @@ +.card-container { + display: flex; + padding: 16px; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 16px; + border: 2px solid; + border-radius: 4px; + background-color: white; +} + +.mobile { + :host { + .card-container { + max-width: 100%; + } + } +} + +.activity-container.padding-botton { + padding-bottom: 16px; +} + +.activity-container { + width: 100%; + border-bottom: 16px; + text-align: initial; + + .header { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + .header-title { + display: flex; + align-items: center; + + h3 { + font-weight: 400; + + margin: 0; + } + } + + display: flex; + padding-bottom: 4px; + } + + caption { + font-style: italic; + display: flex; + width: 100%; + text-align: left; + } +} + +.activity-container.with-border { + border-bottom: 2px solid; +} + +.body-wrapper { + margin-inline-start: 27px; +} + +mat-icon.margin-compensation { + margin-bottom: 3px; +} + +mat-icon { + min-width: 22px; + margin-inline-end: 3px; +} + +a { + font-weight: 400; +} diff --git a/src/app/trusted-summary/component/summary-panel/summary-panel.component.scss-theme.scss b/src/app/trusted-summary/component/summary-panel/summary-panel.component.scss-theme.scss new file mode 100644 index 0000000000..e50e10673e --- /dev/null +++ b/src/app/trusted-summary/component/summary-panel/summary-panel.component.scss-theme.scss @@ -0,0 +1,40 @@ +@use '@angular/material' as mat; +@import 'src/assets/scss/material.orcid-theme.scss'; + +@mixin theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .verified { + color: mat.get-color-from-palette($accent, 400); + } + + .not-verified { + color: mat.get-color-from-palette($primary, 200); + } + + .card-container { + border-color: map-get($background, 'ui-background-light'); + } + + .activity-container { + .header { + .header-title { + color: map-get($foreground, 'text-dark-high'); + } + } + } + + .activity-container.with-border { + border-color: map-get($background, 'ui-background-light'); + } + + .body-wrapper { + color: map-get($foreground, 'text-dark-mid'); + } +} + +@include theme($orcid-app-theme); diff --git a/src/app/trusted-summary/component/summary-panel/summary-panel.component.spec.ts b/src/app/trusted-summary/component/summary-panel/summary-panel.component.spec.ts new file mode 100644 index 0000000000..7c1361fbc8 --- /dev/null +++ b/src/app/trusted-summary/component/summary-panel/summary-panel.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { SummaryPanelComponent } from './summary-panel.component' + +describe('SummaryPanelComponent', () => { + let component: SummaryPanelComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SummaryPanelComponent], + providers: [], + }).compileComponents() + + fixture = TestBed.createComponent(SummaryPanelComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/trusted-summary/component/summary-panel/summary-panel.component.ts b/src/app/trusted-summary/component/summary-panel/summary-panel.component.ts new file mode 100644 index 0000000000..93e0381bb3 --- /dev/null +++ b/src/app/trusted-summary/component/summary-panel/summary-panel.component.ts @@ -0,0 +1,36 @@ +import { Component, Input, OnInit } from '@angular/core' +import { Subject } from 'rxjs' +import { ActivitySummary } from 'src/app/types/trust-summary' + +@Component({ + selector: 'app-summary-panel', + templateUrl: './summary-panel.component.html', + styleUrls: [ + './summary-panel.component.scss', + './summary-panel.component.scss-theme.scss', + ], +}) +export class SummaryPanelComponent implements OnInit { + validatedSourceAriaLabel = $localize`:@@summary.validatedSource:Validated source` + selftAssertedSource = $localize`:@@summary.selfAssertedSource:Self-asserted source` + @Input() activitySummary: ActivitySummary[] + @Input() url: string = '' + @Input() count: number = 0 + activitiesToDisplay: ActivitySummary[] + acitivityCountOverflow: boolean + unsubscribe = new Subject() + + ngOnDestroy(): void { + this.unsubscribe.next() + this.unsubscribe.complete() + } + + ngOnInit(): void { + if (this.activitySummary) { + this.activitySummary = [...this.activitySummary, ...this.activitySummary] + this.activitiesToDisplay = this.activitySummary.slice(0, 3) + + this.acitivityCountOverflow = this.count > 3 + } + } +} diff --git a/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.html b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.html new file mode 100644 index 0000000000..10b578f25c --- /dev/null +++ b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.html @@ -0,0 +1,97 @@ +
+ +
+
+
+ check_circle + + + +
+ + {{ element.url }} + + + {{ element.countA }} + + {{ + element.stringA + }} + + {{ element.countB }} + + {{ + element.stringB + }} + + +
+
+ + + + + + + +
+
+
+ +
diff --git a/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.scss b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.scss new file mode 100644 index 0000000000..607ba86870 --- /dev/null +++ b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.scss @@ -0,0 +1,67 @@ +.card-container { + display: flex; + padding: 16px; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 16px; + border: 2px solid; + border-radius: 4px; + background-color: white; +} + +.activity-container.padding-botton { + padding-bottom: 16px; +} + +.activity-container { + width: 100%; + border-bottom: 16px; + text-align: initial; + + .header { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + .header-title { + display: flex; + align-items: center; + + h3 { + margin: 0; + } + } + + display: flex; + padding-bottom: 4px; + } + + caption { + display: inline; + width: 100%; + text-align: left; + } +} + +.activity-container.with-border { + border-bottom: 2px solid; +} + +.body-wrapper { + margin-inline-start: 25px; +} + +mat-icon.margin-compensation { + margin-bottom: 3px; +} + +mat-icon { + min-width: 25px; + margin-inline-end: 8px; +} + +a { + font-weight: 400; +} diff --git a/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.scss-theme.scss b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.scss-theme.scss new file mode 100644 index 0000000000..e50e10673e --- /dev/null +++ b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.scss-theme.scss @@ -0,0 +1,40 @@ +@use '@angular/material' as mat; +@import 'src/assets/scss/material.orcid-theme.scss'; + +@mixin theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .verified { + color: mat.get-color-from-palette($accent, 400); + } + + .not-verified { + color: mat.get-color-from-palette($primary, 200); + } + + .card-container { + border-color: map-get($background, 'ui-background-light'); + } + + .activity-container { + .header { + .header-title { + color: map-get($foreground, 'text-dark-high'); + } + } + } + + .activity-container.with-border { + border-color: map-get($background, 'ui-background-light'); + } + + .body-wrapper { + color: map-get($foreground, 'text-dark-mid'); + } +} + +@include theme($orcid-app-theme); diff --git a/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.spec.ts b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.spec.ts new file mode 100644 index 0000000000..15255373ae --- /dev/null +++ b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { SummarySimplePanelComponent } from './summary-simple-panel.component' + +describe('SummarySimplePanelComponent', () => { + let component: SummarySimplePanelComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SummarySimplePanelComponent], + }).compileComponents() + + fixture = TestBed.createComponent(SummarySimplePanelComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.ts b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.ts new file mode 100644 index 0000000000..9cd5442836 --- /dev/null +++ b/src/app/trusted-summary/component/summary-simple-panel/summary-simple-panel.component.ts @@ -0,0 +1,38 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core' +import { Subject } from 'rxjs' +import { takeUntil } from 'rxjs/operators' +import { PlatformInfoService } from 'src/app/cdk/platform-info' + +export interface SimpleActivityModel { + verified?: boolean + url?: string + countA?: number + countB?: number + stringA?: string + stringB?: string +} +;[] + +@Component({ + selector: 'app-summary-simple-panel', + templateUrl: './summary-simple-panel.component.html', + styleUrls: [ + './summary-simple-panel.component.scss', + './summary-simple-panel.component.scss-theme.scss', + ], +}) +export class SummarySimplePanelComponent implements OnInit { + validatedSourceAriaLabel = $localize`:@@summary.validatedSource:Validated source` + selftAssertedSource = $localize`:@@summary.selfAssertedSource:Self-asserted source` + @Input() simpleActivities: SimpleActivityModel[] = [] + @Input() count: number = 0 + @Input() overflowUrl: string = '' + unsubscribe = new Subject() + mobile: boolean + acitivityCountOverflow = false + + ngOnInit(): void { + this.acitivityCountOverflow = this.simpleActivities.length > 3 + this.simpleActivities = this.simpleActivities.slice(0, 3) + } +} diff --git a/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.html b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.html new file mode 100644 index 0000000000..b8d4ce041d --- /dev/null +++ b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.html @@ -0,0 +1,219 @@ +
+
+ + + +
+
+
+ + +

+ AFFILIATIONS +

+
+ + +

+ KEY DATES +

+
+
+
    +
  • +
    + + Record created + + +
    +
  • +
  • +
    + + Last updated + + +
    +
  • +
+
+
+
+ + +
+
+
+ +
diff --git a/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.scss b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.scss new file mode 100644 index 0000000000..43ff490513 --- /dev/null +++ b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.scss @@ -0,0 +1,168 @@ +:host { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; +} + +.panel { + display: grid; + grid-gap: 16px; + border-radius: 8px 8px 0px 0px; + border: 2px solid; +} + +.panel-header { + height: 46px; + border-bottom: 2px solid; + color: #fff; + padding: 16px; + display: flex; + flex-direction: row; + + caption { + text-align: initial; + display: inline; + line-height: 1; + } + + img { + padding-inline-end: 10px; + } + + .header-name { + display: flex; + flex-direction: column; + + h1 { + margin: 0; + line-height: 1; + word-break: break-word; + } + } +} + +.column { + padding-left: 20px; + padding-right: 20px; +} + +.panel-body { + grid-column: 1 / -1; + display: grid; + padding-bottom: 16px; +} + +.panel-body-3-cols { + grid-template-columns: 1fr 1fr 1fr; +} + +.panel-body-2-cols { + grid-template-columns: 1fr 1fr; +} + +.panel-body-1-cols { + grid-template-columns: 1fr; +} + +.column-border { + border-right: 2px solid; +} + +h2 { + font-weight: 600; + letter-spacing: 2px; +} + +main { + max-width: 100%; + overflow: hidden; +} + +.date-item { + display: flex; + flex-direction: column; + padding-bottom: 16px; + + caption, + label { + display: block; + } +} + +.date-item.last-date { + padding-bottom: 0; +} + +ul { + margin: 0; +} + +ul li { + position: relative; + margin-bottom: 0; +} + +ul li:after { + content: url('/assets/vectors/bullet-point.svg'); + position: absolute; + left: -26px; + top: 0px; +} + +ul li:before { + content: ''; + position: absolute; + left: -16px; + border-left: 1px dashed; + height: 100%; + width: 1px; +} + +ul li:first-child:before { + top: 6px; +} + +ul li:last-child:before { + height: 6px; +} + +.card-container { + display: flex; + padding: 16px; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 16px; + border-radius: 4px; + border: 2px solid; + background: #fff; +} + +.cursor-pointer { + cursor: pointer; +} + +a { + .label-with-icon { + padding-top: 2px; + color: #fff; + text-decoration: underline; + + > svg > path { + fill: #fff; + } + + display: flex; + align-items: centerf; + gap: 8px; + } + + h1 { + color: #fff; + } + + h2 { + color: black; + } +} diff --git a/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.scss-theme.scss b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.scss-theme.scss new file mode 100644 index 0000000000..16c1a12257 --- /dev/null +++ b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.scss-theme.scss @@ -0,0 +1,44 @@ +@use '@angular/material' as mat; +@import 'src/assets/scss/material.orcid-theme.scss'; + +@mixin theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .verified { + color: mat.get-color-from-palette($accent, 400); + } + + .not-verified { + color: mat.get-color-from-palette($primary, 200); + } + + .panel, + .panel-header { + border-color: mat.get-color-from-palette( + $background, + ui-background-darkest + ); + } + + .panel-header { + background-color: mat.get-color-from-palette($primary, 900); + } + + ul li:before { + border-color: mat.get-color-from-palette($accent, 200) !important; + } + + .card-container { + border-color: map-get($background, 'ui-background-light'); + } + + .column-border { + border-color: map-get($background, 'ui-background-light'); + } +} + +@include theme($orcid-app-theme); diff --git a/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.spec.ts b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.spec.ts new file mode 100644 index 0000000000..f2ac458fcc --- /dev/null +++ b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.spec.ts @@ -0,0 +1,36 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { TrustedSummaryComponent } from './trusted-summary.component' +import { TrustedSummaryService } from 'src/app/core/trusted-summary/trusted-summary.service' +import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { PlatformInfoService } from 'src/app/cdk/platform-info' +import { of } from 'rxjs' + +describe('TrustedSummaryComponent', () => { + let component: TrustedSummaryComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [TrustedSummaryComponent], + providers: [ + { provide: ErrorHandlerService, userValue: {} }, + { + provide: TrustedSummaryService, + useValue: { getSummary: () => of() }, + }, + { provide: PlatformInfoService, useValue: { get: () => of() } }, + ], + imports: [HttpClientTestingModule], + }).compileComponents() + + fixture = TestBed.createComponent(TrustedSummaryComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.ts b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.ts new file mode 100644 index 0000000000..718f239eba --- /dev/null +++ b/src/app/trusted-summary/pages/trusted-summary/trusted-summary.component.ts @@ -0,0 +1,160 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { Router } from '@angular/router' +import { TrustedSummaryService } from 'src/app/core/trusted-summary/trusted-summary.service' +import { TrustedSummary } from 'src/app/types/trust-summary' +import { SimpleActivityModel } from '../../component/summary-simple-panel/summary-simple-panel.component' +import { PlatformInfoService } from 'src/app/cdk/platform-info' +import { takeUntil } from 'rxjs/operators' +import { Subject } from 'rxjs' + +@Component({ + selector: 'app-trusted-summary', + templateUrl: './trusted-summary.component.html', + styleUrls: [ + './trusted-summary.component.scss', + './trusted-summary.component.scss-theme.scss', + ], +}) +export class TrustedSummaryComponent implements OnInit, OnDestroy { + trustedSummary: TrustedSummary + currentLocation: string + orcid: string + works: SimpleActivityModel[] = [] + unsubscribe = new Subject() + labelValidatedWorks = $localize`:@@summary.validatedWorks:Validated works` + labelValidatedWork = $localize`:@@summary.validatedWork:Validated work` + labelSelfAssertedWorks = $localize`:@@summary.selfAssertedWorks:Self-asserted works` + labelSelfAssertedWork = $localize`:@@summary.selfAssertedWork:Self-asserted work` + labelValidatedFunding = $localize`:@@summary.validatedFunding:Validated funding` + labelValidatedFundings = $localize`:@@summary.validatedFundings:Validated fundings` + labelSelfAssertedFunding = $localize`:@@summary.selfAssertedFunding:Self-asserted funding` + labelSelfAssertedFundings = $localize`:@@summary.selfAssertedFundings:Self-asserted fundings` + labelReviesFor = $localize`:@@summary.reviewsFor:Reviews for` + labelReviewFor = $localize`:@@summary.reviewFor:Review for` + labelpublicationgrants = $localize`:@@summary.publicationgrantes:publication/grants` + labelpublicationgrant = $localize`:@@summary.publicationgrant:publication/grant` + + funds: SimpleActivityModel[] = [] + peerReviews: SimpleActivityModel[] = [] + mobile: boolean + externalIdentifiers: SimpleActivityModel[] + twoColumns: boolean = false + threeColumns: boolean = false + oneColumn: boolean + + constructor( + private _trustedSummary: TrustedSummaryService, + private _router: Router, + private _platform: PlatformInfoService + ) {} + ngOnDestroy(): void { + this.unsubscribe.next() + this.unsubscribe.complete() + } + + ngOnInit(): void { + this._platform + .get() + .pipe(takeUntil(this.unsubscribe)) + .subscribe((platform) => { + if (platform.columns4 || platform.columns8) { + this.mobile = true + } else { + this.mobile = false + } + }) + this.currentLocation = window.location.origin + this.orcid = this._router.url.split('/')[1] + this._trustedSummary.getSummary(this.orcid).subscribe((data) => { + this.trustedSummary = data + if (this.trustedSummary.selfAssertedWorks) { + this.works.push({ + verified: false, + countA: this.trustedSummary.selfAssertedWorks, + stringA: + this.trustedSummary.selfAssertedWorks > 1 + ? this.labelSelfAssertedWorks + : this.labelSelfAssertedWork, + }) + } + if (this.trustedSummary.validatedWorks) { + this.works.push({ + verified: true, + countA: this.trustedSummary.validatedWorks, + stringA: + this.trustedSummary.validatedWorks > 1 + ? this.labelValidatedWorks + : this.labelValidatedWork, + }) + } + if (this.trustedSummary.validatedFunds) { + this.funds.push({ + verified: true, + countA: this.trustedSummary.validatedFunds, + stringA: + this.trustedSummary.validatedFunds > 1 + ? this.labelValidatedFundings + : this.labelValidatedFunding, + }) + } + if (this.trustedSummary.selfAssertedFunds) { + this.funds.push({ + verified: false, + countA: this.trustedSummary.selfAssertedFunds, + stringA: + this.trustedSummary.selfAssertedFunds > 1 + ? this.labelSelfAssertedFundings + : this.labelSelfAssertedFunding, + }) + } + if ( + this.trustedSummary.reviews && + this.trustedSummary.peerReviewPublicationGrants + ) { + this.peerReviews.push({ + verified: true, + countA: this.trustedSummary.reviews, + stringA: + this.trustedSummary.reviews > 1 + ? this.labelReviesFor + : this.labelReviewFor, + countB: this.trustedSummary.peerReviewPublicationGrants, + stringB: + this.trustedSummary.peerReviewPublicationGrants > 1 + ? this.labelpublicationgrants + : this.labelpublicationgrant, + }) + } + this.externalIdentifiers = this.trustedSummary.externalIdentifiers.map( + (id) => { + return { + verified: id.verified, + url: id.url, + stringA: id.id, + } + } + ) + + if ( + (this.works.length > 0 || + this.funds.length > 0 || + this.peerReviews.length > 0) && + (this.externalIdentifiers.length > 0 || + this.trustedSummary.professionalActivitiesCount > 0 || + this.trustedSummary.externalIdentifiers.length > 0) + ) { + this.threeColumns = true + } else if ( + this.works.length > 0 || + this.funds.length > 0 || + this.peerReviews.length > 0 || + this.externalIdentifiers.length > 0 || + this.trustedSummary.professionalActivitiesCount > 0 + ) { + this.twoColumns = true + } else { + this.oneColumn = true + } + }) + } +} diff --git a/src/app/trusted-summary/trusted-summary-routing.module.ts b/src/app/trusted-summary/trusted-summary-routing.module.ts new file mode 100644 index 0000000000..3c49708eff --- /dev/null +++ b/src/app/trusted-summary/trusted-summary-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core' +import { Routes, RouterModule } from '@angular/router' +import { TrustedSummaryComponent } from './pages/trusted-summary/trusted-summary.component' + +const routes: Routes = [ + { + path: '', + component: TrustedSummaryComponent, + }, +] + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class TrustedSummaryRouting {} diff --git a/src/app/trusted-summary/trusted-summary.module.ts b/src/app/trusted-summary/trusted-summary.module.ts new file mode 100644 index 0000000000..d1ccdfdbf4 --- /dev/null +++ b/src/app/trusted-summary/trusted-summary.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core' +import { CommonModule } from '@angular/common' +import { SummaryPanelComponent } from './component/summary-panel/summary-panel.component' +import { SummarySimplePanelComponent } from './component/summary-simple-panel/summary-simple-panel.component' +import { TrustedSummaryComponent } from './pages/trusted-summary/trusted-summary.component' +import { TrustedSummaryRouting } from './trusted-summary-routing.module' +import { MatIconModule } from '@angular/material/icon' +import { + MatProgressSpinner, + MatProgressSpinnerModule, +} from '@angular/material/progress-spinner' +import { SharedModule } from '../shared/shared.module' + +@NgModule({ + declarations: [ + SummaryPanelComponent, + SummarySimplePanelComponent, + TrustedSummaryComponent, + ], + imports: [ + CommonModule, + TrustedSummaryRouting, + MatIconModule, + MatProgressSpinnerModule, + SharedModule, + ], +}) +export class TrustedSummaryModule {} diff --git a/src/app/types/trust-summary.ts b/src/app/types/trust-summary.ts new file mode 100644 index 0000000000..ba189e4edd --- /dev/null +++ b/src/app/types/trust-summary.ts @@ -0,0 +1,28 @@ +export interface TrustedSummary { + name: string + orcid: string + employmentAffiliations: ActivitySummary[] + employmentAffiliationsCount: number + creation: string + lastModified: string + validatedWorks: number + selfAssertedWorks: number + reviews: number + peerReviewPublicationGrants: number + validatedFunds: number + selfAssertedFunds: number + professionalActivities: ActivitySummary[] + professionalActivitiesCount: number + externalIdentifiers: any[] +} + +export interface ActivitySummary { + organizationName?: string + url?: string + startDate?: string + endDate?: string + role?: string + title: any + type: string + validatedOrSelfAsserted: boolean +} diff --git a/src/assets/vectors/bullet-point.svg b/src/assets/vectors/bullet-point.svg new file mode 100644 index 0000000000..5ab9f03859 --- /dev/null +++ b/src/assets/vectors/bullet-point.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/locale/properties/summary/summary.en.properties b/src/locale/properties/summary/summary.en.properties new file mode 100644 index 0000000000..749abd2f1a --- /dev/null +++ b/src/locale/properties/summary/summary.en.properties @@ -0,0 +1,25 @@ +summary.to=to +summary.moreAffiliation=more Affiliations +summary.validatedSource=Validated source +summary.selfAssertedSource=Self-asserted source +summary.affiliations=AFFILIATIONS +summary.keyDates=KEY DATES +summary.recordCreated=Record created +summary.lastUpdated=Last updated +summary.works=WORKS +summary.peerReviews=PEER REVIEWS +summary.funding=FUNDING +summary.professionalActivites=PROFESSIONAL ACTIVITIES +summary.otherids=OTHER IDENTIFIERS +summary.validatedWorks=Validated works +summary.validatedWork=Validated work +summary.selfAssertedWorks=Self-asserted works +summary.selfAssertedWork=Self-asserted work +summary.validatedFunding=Validated funding +summary.validatedFundings=Validated fundings +summary.selfAssertedFunding=Self-asserted funding +summary.selfAssertedFundings=Self-asserted fundings +summary.reviewsFor=Reviews for +summary.reviewFor=Review for +summary.publicationgrantes=publication/grants +summary.publicationgrant=publication/grant \ No newline at end of file