diff --git a/src/app/authorize/authorize.module.ts b/src/app/authorize/authorize.module.ts index 4dbdc64f2..518cf9d81 100644 --- a/src/app/authorize/authorize.module.ts +++ b/src/app/authorize/authorize.module.ts @@ -15,8 +15,8 @@ import { AuthorizeRoutingModule } from './authorize-routing.module' import { OauthErrorComponent } from './components/oauth-error/oauth-error.component' import { AuthorizeComponent } from './pages/authorize/authorize.component' import { FormAuthorizeComponent } from './components/form-authorize/form-authorize.component' -import { ShareEmailsDomainsComponent } from '../cdk/interstitials/share-emails-domains/share-emails-domains.component' import { InterstitialsModule } from '../cdk/interstitials/interstitials.module' +import { MatLegacyDialogModule } from '@angular/material/legacy-dialog' @NgModule({ declarations: [ @@ -38,6 +38,7 @@ import { InterstitialsModule } from '../cdk/interstitials/interstitials.module' InfoDropDownModule, MatProgressBarModule, InterstitialsModule, + MatLegacyDialogModule, ], }) export class AuthorizeModule {} diff --git a/src/app/cdk/info-panel/info-panel/info-panel.component.scss b/src/app/cdk/info-panel/info-panel/info-panel.component.scss index a846418b5..102cc0434 100644 --- a/src/app/cdk/info-panel/info-panel/info-panel.component.scss +++ b/src/app/cdk/info-panel/info-panel/info-panel.component.scss @@ -5,6 +5,7 @@ border: solid 2px; border-radius: 4px; display: flex; + gap: 16px; p { margin: 0; diff --git a/src/app/cdk/interstitials/interstitials.module.ts b/src/app/cdk/interstitials/interstitials.module.ts index 969ef55ae..ab806a190 100644 --- a/src/app/cdk/interstitials/interstitials.module.ts +++ b/src/app/cdk/interstitials/interstitials.module.ts @@ -12,9 +12,13 @@ import { MatLegacyCheckboxModule } from '@angular/material/legacy-checkbox' import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button' import { InfoPanelModule } from '../info-panel/info-panel.module' import { A11yLinkModule } from '../a11y-link/a11y-link.module' +import { ShareEmailsDomainsDialogComponent } from './share-emails-domains/share-emails-domains-dialog.component' @NgModule({ - declarations: [ShareEmailsDomainsComponent], + declarations: [ + ShareEmailsDomainsComponent, + ShareEmailsDomainsDialogComponent, + ], imports: [ CommonModule, MatLegacyCardModule, @@ -29,6 +33,6 @@ import { A11yLinkModule } from '../a11y-link/a11y-link.module' InfoPanelModule, A11yLinkModule, ], - exports: [ShareEmailsDomainsComponent], + exports: [ShareEmailsDomainsComponent, ShareEmailsDomainsDialogComponent], }) export class InterstitialsModule {} diff --git a/src/app/cdk/interstitials/share-emails-domains/share-emails-domains-dialog.component.scss b/src/app/cdk/interstitials/share-emails-domains/share-emails-domains-dialog.component.scss new file mode 100644 index 000000000..d4fd26a48 --- /dev/null +++ b/src/app/cdk/interstitials/share-emails-domains/share-emails-domains-dialog.component.scss @@ -0,0 +1,12 @@ +mat-card-header.authorize-header { + mat-card-title { + margin: 0 0 32px 0 !important; + display: flex; + flex-direction: column; + align-items: center; + } +} +:host { + padding: 48px; + display: block; +} diff --git a/src/app/cdk/interstitials/share-emails-domains/share-emails-domains-dialog.component.ts b/src/app/cdk/interstitials/share-emails-domains/share-emails-domains-dialog.component.ts new file mode 100644 index 000000000..78c131f08 --- /dev/null +++ b/src/app/cdk/interstitials/share-emails-domains/share-emails-domains-dialog.component.ts @@ -0,0 +1,52 @@ +import { Component, EventEmitter, Inject, Input, Output } from '@angular/core' +import { PlatformInfoService } from '../../platform-info' +import { AssertionVisibilityString, EmailsEndpoint } from 'src/app/types' +import { FormBuilder, FormControl, FormGroup } from '@angular/forms' +import { RecordEmailsService } from 'src/app/core/record-emails/record-emails.service' +import { error } from 'console' +import { WINDOW } from '../../window' +import { + MAT_LEGACY_DIALOG_DATA, + MatLegacyDialogRef, + MatLegacyDialogState, +} from '@angular/material/legacy-dialog' +import { ShareEmailsDomainsComponent } from './share-emails-domains.component' + +export type ShareEmailsDomainsComponentDialogInput = { + userEmailsJson: EmailsEndpoint + organizationName?: string +} + +@Component({ + templateUrl: './share-emails-domains.component.html', + styleUrls: [ + './share-emails-domains.component.scss', + './share-emails-domains.component.scss-theme.scss', + './share-emails-domains-dialog.component.scss', + ], +}) +export class ShareEmailsDomainsDialogComponent extends ShareEmailsDomainsComponent { + constructor( + platformInfo: PlatformInfoService, + fb: FormBuilder, + recordEmailsService: RecordEmailsService, + @Inject(WINDOW) window: Window, + @Inject(MAT_LEGACY_DIALOG_DATA) + public data: ShareEmailsDomainsComponentDialogInput, + public dialogRef: MatLegacyDialogRef + ) { + super(platformInfo, fb, recordEmailsService, window) + if (this.data) { + this.userEmailsJson = this.data.userEmailsJson + this.organizationName = this.data.organizationName + this.dialogMode = true + } + } + + override finishIntertsitial(emails?: string[]) { + this.finish.emit() + if (this.dialogRef.getState() === MatLegacyDialogState.OPEN) { + this.dialogRef.close(emails) + } + } +} diff --git a/src/app/cdk/interstitials/share-emails-domains/share-emails-domains.component.ts b/src/app/cdk/interstitials/share-emails-domains/share-emails-domains.component.ts index be75d51a8..1114e2ea3 100644 --- a/src/app/cdk/interstitials/share-emails-domains/share-emails-domains.component.ts +++ b/src/app/cdk/interstitials/share-emails-domains/share-emails-domains.component.ts @@ -5,6 +5,16 @@ import { FormBuilder, FormControl, FormGroup } from '@angular/forms' import { RecordEmailsService } from 'src/app/core/record-emails/record-emails.service' import { error } from 'console' import { WINDOW } from '../../window' +import { + MAT_LEGACY_DIALOG_DATA, + MatLegacyDialogRef, + MatLegacyDialogState, +} from '@angular/material/legacy-dialog' + +export type ShareEmailsDomainsComponentDialogInput = { + userEmailsJson: EmailsEndpoint + organizationName?: string +} @Component({ selector: 'app-share-emails-domains', @@ -21,6 +31,7 @@ export class ShareEmailsDomainsComponent { @Input() userEmailsJson: EmailsEndpoint @Input() organizationName: string form: any + dialogMode: boolean constructor( public platformInfo: PlatformInfoService, private fb: FormBuilder, @@ -78,16 +89,22 @@ export class ShareEmailsDomainsComponent { this.recordEmailsService.postEmails(this.userEmailsJson).subscribe( (response) => { - this.afterSummit = true - this.beforeSummit = false - setTimeout(() => { - this.finish.emit() - }, 10000) + if (!this.dialogMode) { + this.afterSummit = true + this.beforeSummit = false + } else { + this.finishIntertsitial(this.domainToMakePublic) + } + setTimeout(() => {}, 10000) }, - (error) => this.finish.emit() + (error) => this.finishIntertsitial() ) } else { - this.finish.emit() + this.finishIntertsitial() } } + + finishIntertsitial(emails?: string[]) { + this.finish.emit() + } } diff --git a/src/app/cdk/platform-info/browserlist.regexp.ts b/src/app/cdk/platform-info/browserlist.regexp.ts index 545a90290..bf8c4aa5c 100644 --- a/src/app/cdk/platform-info/browserlist.regexp.ts +++ b/src/app/cdk/platform-info/browserlist.regexp.ts @@ -1,3 +1,2 @@ // tslint:disable-next-line: max-line-length -export const BROWSERLIST_REGEXP = - /((CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS)[ +]+(13[_.]4|13[_.]([5-9]|\d{2,})|13[_.]7|13[_.]([8-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})[_.]\d+|14[_.]0|14[_.]([1-9]|\d{2,})|14[_.]4|14[_.]([5-9]|\d{2,})|14[_.]8|14[_.](9|\d{2,})|(1[5-9]|[2-9]\d|\d{3,})[_.]\d+|15[_.]0|15[_.]([1-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})[_.]\d+|16[_.]0|16[_.]([1-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})[_.]\d+|17[_.]0|17[_.]([1-9]|\d{2,})|(1[8-9]|[2-9]\d|\d{3,})[_.]\d+)(?:[_.]\d+)?)|((?:Chrome).*OPR\/(74|(7[5-9]|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Edge\/(80|(8[1-9]|9\d|\d{3,})|83|(8[4-9]|9\d|\d{3,}))(?:\.\d+)?)|((Chromium|Chrome)\/(80|(8[1-9]|9\d|\d{3,})|83|(8[4-9]|9\d|\d{3,}))\.\d+(?:\.\d+)?)|(Version\/(13\.1|13\.([2-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})\.\d+|14\.0|14\.([1-9]|\d{2,})|(1[5-9]|[2-9]\d|\d{3,})\.\d+|15\.0|15\.([1-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})\.\d+|16\.0|16\.([1-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})\.\d+|17\.0|17\.([1-9]|\d{2,})|(1[8-9]|[2-9]\d|\d{3,})\.\d+)(?:\.\d+)? Safari\/)|(Firefox\/(78|(79|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Firefox\/(78|(79|[8-9]\d|\d{3,}))\.\d+(pre|[ab]\d+[a-z]*)?)/ +export const BROWSERLIST_REGEXP = /((CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS)[ +]+(13[_.]4|13[_.]([5-9]|\d{2,})|13[_.]7|13[_.]([8-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})[_.]\d+|14[_.]0|14[_.]([1-9]|\d{2,})|14[_.]4|14[_.]([5-9]|\d{2,})|14[_.]8|14[_.](9|\d{2,})|(1[5-9]|[2-9]\d|\d{3,})[_.]\d+|15[_.]0|15[_.]([1-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})[_.]\d+|16[_.]0|16[_.]([1-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})[_.]\d+|17[_.]0|17[_.]([1-9]|\d{2,})|(1[8-9]|[2-9]\d|\d{3,})[_.]\d+)(?:[_.]\d+)?)|((?:Chrome).*OPR\/(74|(7[5-9]|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Edge\/(80|(8[1-9]|9\d|\d{3,})|83|(8[4-9]|9\d|\d{3,}))(?:\.\d+)?)|((Chromium|Chrome)\/(80|(8[1-9]|9\d|\d{3,})|83|(8[4-9]|9\d|\d{3,}))\.\d+(?:\.\d+)?)|(Version\/(13\.1|13\.([2-9]|\d{2,})|(1[4-9]|[2-9]\d|\d{3,})\.\d+|14\.0|14\.([1-9]|\d{2,})|(1[5-9]|[2-9]\d|\d{3,})\.\d+|15\.0|15\.([1-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})\.\d+|16\.0|16\.([1-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})\.\d+|17\.0|17\.([1-9]|\d{2,})|(1[8-9]|[2-9]\d|\d{3,})\.\d+)(?:\.\d+)? Safari\/)|(Firefox\/(78|(79|[8-9]\d|\d{3,}))\.\d+\.\d+)|(Firefox\/(78|(79|[8-9]\d|\d{3,}))\.\d+(pre|[ab]\d+[a-z]*)?)/ diff --git a/src/app/cdk/warning-message/warning-message/warning-message.component.html b/src/app/cdk/warning-message/warning-message/warning-message.component.html index bc8c53f23..c62892e1d 100644 --- a/src/app/cdk/warning-message/warning-message/warning-message.component.html +++ b/src/app/cdk/warning-message/warning-message/warning-message.component.html @@ -1,19 +1,33 @@
-
- {{ icon }} -
-
- - - -

- -

+
+
+ + {{ icon }} +
+
+ + + +

+ +

+
diff --git a/src/app/cdk/warning-message/warning-message/warning-message.component.scss b/src/app/cdk/warning-message/warning-message/warning-message.component.scss index 854c6a3fd..946dc5dac 100644 --- a/src/app/cdk/warning-message/warning-message/warning-message.component.scss +++ b/src/app/cdk/warning-message/warning-message/warning-message.component.scss @@ -10,8 +10,12 @@ } } } -:host { - border: 1px solid; +.wrapper { + border: 2px solid; padding: 14px; - width: 100%; } + +:host { + display: block; + width: 100%; +} \ No newline at end of file diff --git a/src/app/cdk/warning-message/warning-message/warning-message.component.scss-theme.scss b/src/app/cdk/warning-message/warning-message/warning-message.component.scss-theme.scss index 692fdc130..95353602e 100644 --- a/src/app/cdk/warning-message/warning-message/warning-message.component.scss-theme.scss +++ b/src/app/cdk/warning-message/warning-message/warning-message.component.scss-theme.scss @@ -7,18 +7,29 @@ $warn: map-get($theme, accent); $foreground: map-get($theme, foreground); $background: map-get($theme, background); - :host { + + .wrapper.warning-message { border-color: mat.get-color-from-palette( $foreground, 'state-notice-dark' ) !important; + .container-text-and-icon { + mat-icon { + color: mat.get-color-from-palette( + $foreground, + 'state-notice-dark' + ) !important; + } + } } - .container-text-and-icon { - mat-icon { - color: mat.get-color-from-palette( - $foreground, - 'state-notice-dark' - ) !important; + + .wrapper.success-message { + border-color: $brand-primary-dark; + + .container-text-and-icon { + mat-icon { + color: $brand-primary-dark; + } } } } diff --git a/src/app/cdk/warning-message/warning-message/warning-message.component.ts b/src/app/cdk/warning-message/warning-message/warning-message.component.ts index 08e432ac0..0e085754d 100644 --- a/src/app/cdk/warning-message/warning-message/warning-message.component.ts +++ b/src/app/cdk/warning-message/warning-message/warning-message.component.ts @@ -13,7 +13,17 @@ import { PlatformInfoService } from '../../platform-info' preserveWhitespaces: false, }) export class WarningMessageComponent implements OnInit, OnDestroy { - @Input() icon = 'info' + private _type: 'warning' | 'success' | 'info' = 'warning' + icon = 'info' + + @Input() set type(value: 'warning' | 'success' | 'info') { + this._type = value + this.icon = value === 'warning' ? 'info' : value === 'success' ? 'thumb_up' : 'info' + } + get type(): 'warning' | 'success' | 'info' { + return this._type + } + isMobile: boolean $destroy: Subject = new Subject() diff --git a/src/app/core/login-interstitials/login-interstitials.service.spec.ts b/src/app/core/login-interstitials/login-interstitials.service.spec.ts new file mode 100644 index 000000000..a1c58e95b --- /dev/null +++ b/src/app/core/login-interstitials/login-interstitials.service.spec.ts @@ -0,0 +1,29 @@ +import { TestBed } from '@angular/core/testing' + +import { LoginInterstitialsService } from './login-interstitials.service' +import { MatLegacyDialog } from '@angular/material/legacy-dialog' +import { InterstitialsService } from 'src/app/cdk/interstitials/interstitials.service' +import { TogglzService } from '../togglz/togglz.service' +import { NEVER } from 'rxjs' + +describe('LoginInterstitialsService', () => { + let service: LoginInterstitialsService + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { provide: MatLegacyDialog, useValue: {} }, + { + provide: InterstitialsService, + useValue: { getInterstitialsViewed: () => NEVER }, + }, + { provide: TogglzService, useValue: { getStateOf: () => NEVER } }, + ], + }) + service = TestBed.inject(LoginInterstitialsService) + }) + + it('should be created', () => { + expect(service).toBeTruthy() + }) +}) diff --git a/src/app/core/login-interstitials/login-interstitials.service.ts b/src/app/core/login-interstitials/login-interstitials.service.ts new file mode 100644 index 000000000..11e9ed946 --- /dev/null +++ b/src/app/core/login-interstitials/login-interstitials.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@angular/core' +import { + MatLegacyDialog as MatDialog, + MatLegacyDialogState, +} from '@angular/material/legacy-dialog' +import { ShareEmailsDomainsComponent } from 'src/app/cdk/interstitials/share-emails-domains/share-emails-domains.component' +import { EmailsEndpoint } from 'src/app/types' +import { UserRecord } from 'src/app/types/record.local' +import { ShareEmailsDomainsComponentDialogInput } from 'src/app/cdk/interstitials/share-emails-domains/share-emails-domains.component' +import { InterstitialsService } from 'src/app/cdk/interstitials/interstitials.service' +import { Observable } from 'rxjs' +import { ShareEmailsDomainsDialogComponent } from 'src/app/cdk/interstitials/share-emails-domains/share-emails-domains-dialog.component' +import { TogglzService } from '../togglz/togglz.service' + +@Injectable({ + providedIn: 'root', +}) +export class LoginInterstitialsService { + alreadySawSignDomainInterstitial: boolean + loginDomainsInterstitialEnabled: boolean + constructor( + private _matDialog: MatDialog, + private interstitialService: InterstitialsService, + private toggleService: TogglzService + ) { + this.interstitialService + .getInterstitialsViewed('SIGN_IN_DOMAIN_INTERSTITIAL') + .subscribe((viewed) => { + this.alreadySawSignDomainInterstitial = viewed + }) + this.toggleService + .getStateOf('LOGIN_DOMAINS_INTERSTITIAL') + .subscribe((state) => { + this.loginDomainsInterstitialEnabled = state + }) + } + + checkLoginInterstitials(userRecord: UserRecord): Observable | void { + let isNotImpersonating + if (userRecord.userInfo) { + isNotImpersonating = + userRecord.userInfo.REAL_USER_ORCID === + userRecord.userInfo.EFFECTIVE_USER_ORCID + } + if ( + isNotImpersonating && + userRecord.emails && + userRecord.emails.emailDomains && + !this.userHasPublicDomains(userRecord.emails) && + this.userHasPrivateDomains(userRecord.emails) && + this.loginDomainsInterstitialEnabled && + !this.alreadySawSignDomainInterstitial + ) { + this.alreadySawSignDomainInterstitial = true + this.interstitialService + .setInterstitialsViewed('SIGN_IN_DOMAIN_INTERSTITIAL') + .subscribe() + const data: ShareEmailsDomainsComponentDialogInput = { + userEmailsJson: userRecord.emails, + } + + const dialog = this._matDialog.open(ShareEmailsDomainsDialogComponent, { + data, + width: '580px', + disableClose: true, + autoFocus: false, + restoreFocus: false, + }) + return dialog.afterClosed() + } + } + + userHasPublicDomains(value: EmailsEndpoint): any { + return !!value.emailDomains.find((domain) => domain.visibility === 'PUBLIC') + } + + userHasPrivateDomains(value: EmailsEndpoint): boolean { + return !!value.emailDomains.find((domain) => domain.visibility !== 'PUBLIC') + } +} diff --git a/src/app/record/components/top-bar/top-bar.component.html b/src/app/record/components/top-bar/top-bar.component.html index a2bd997ac..7c7ab3775 100644 --- a/src/app/record/components/top-bar/top-bar.component.html +++ b/src/app/record/components/top-bar/top-bar.component.html @@ -33,6 +33,64 @@ + + Email domains shared + The email domains + The email domain + + + {{ email }}, + + + + and + + {{ email }} + + + are now visible on your public ORCID record. Manage your email addresses + and domains in the + + + is now visible on your public ORCID record. Manage your email addresses + and domains in the + + + Emails & domains section. + + +