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 @@
+
+
+
+
+
+
+
+ {{ 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 @@
+
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 @@
+
+
+
+
+
+
+
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