From 8eb238a2087174d24455898a1af786b933596d12 Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Date: Tue, 28 Jul 2026 14:31:36 -0600 Subject: [PATCH 1/3] PD-5900 add backup email interstitial Shows an interstitial after standard sign in to users with exactly one email address on their record, prompting them to add a backup. It runs first in the interstitial chain (backup email, then public domains, then affiliations) and is gated by the LOGIN_BACKUP_EMAIL_INTERSTITIAL togglz. Sign in flow only. Panel follows the Figma: primary email with its Professional/Personal category (from email-domain/find-category), backup email field, and the three inline errors from the AC. The confirmation is a success notice at the top of the record, matching the other interstitials. Accessibility fixes for this dialog: it now has an accessible name and aria-modal, the decline control is a button so keyboard users can dismiss it (disableClose blocks Esc), and inline errors are announced. Panel padding moved to the dialog surface, which also had no padding at all on mobile. Adds the first analytics for the interstitial subsystem: an 'interstitial' RUM journey reporting shown, added, dismissed, validation error and save error, so add and dismiss rates are measurable. The shown event is emitted from the shared manager, so all three interstitials get a comparable denominator. Also makes the XSRF preload interceptor run on every browser instead of only Firefox. Without a cookie the first mutating request goes out with no x-xsrf-token header, 403s, and only sets the cookie as a side effect, which made addInterstitialFlag fail and local sign in take two attempts. The reload it falls back to is now guarded so it cannot loop. Recording an interstitial visit no longer gates the dialog opening either, so a failed flag POST cannot hide the interstitial while marking it as seen. --- src/app/app.module.ts | 8 +- .../backup-email.component.html | 146 +++++++ .../backup-email.component.scss | 127 ++++++ .../backup-email.component.scss-theme.scss | 35 ++ .../backup-email.component.spec.ts | 295 ++++++++++++++ .../backup-email.component.ts | 289 ++++++++++++++ .../backup-email-dialog.component.scss | 27 ++ .../backup-email-dialog.component.ts | 73 ++++ .../cdk/interstitials/interstitial.type.ts | 1 + .../cdk/interstitials/interstitials.module.ts | 6 + .../firefox-xsrf-preload.interceptor.ts | 116 ------ .../abstractions/dialog-interface.ts | 1 + ...n-abstract-interstitial-manager.service.ts | 23 +- ...kup-email-interstitials-manager.service.ts | 103 +++++ .../interstitial-observability.service.ts | 69 ++++ ...ogin-main-interstitials-manager.service.ts | 5 +- ...mail-interstitials-manager.service.spec.ts | 370 ++++++++++++++++++ ...main-interstitials-manager.service.spec.ts | 136 +++++++ src/app/core/qa-flag/qa-flags.enum.ts | 1 + .../xsrf-preload.interceptor.spec.ts} | 113 ++++-- src/app/core/xsrf/xsrf-preload.interceptor.ts | 155 ++++++++ .../components/top-bar/top-bar.component.html | 21 + .../components/top-bar/top-bar.component.ts | 1 + .../pages/my-orcid/my-orcid.component.html | 1 + .../pages/my-orcid/my-orcid.component.ts | 13 +- src/app/rum/app-event-names.ts | 12 + src/app/rum/interstitial-observability.md | 125 ++++++ src/app/rum/journeys/interstitial.ts | 20 + src/app/rum/journeys/types.ts | 11 + src/app/rum/terminating-rum-events.ts | 14 + src/app/types/config.endpoint.ts | 2 + src/locale/messages.lr.xlf | 270 +++++++++++-- src/locale/messages.rl.xlf | 270 +++++++++++-- src/locale/messages.xlf | 250 ++++++++++-- src/locale/messages.xx.xlf | 270 +++++++++++-- 35 files changed, 3077 insertions(+), 302 deletions(-) create mode 100644 src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.html create mode 100644 src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.scss create mode 100644 src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.scss-theme.scss create mode 100644 src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.spec.ts create mode 100644 src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.ts create mode 100644 src/app/cdk/interstitials/backup-email/interstitial-dialog-extend/backup-email-dialog.component.scss create mode 100644 src/app/cdk/interstitials/backup-email/interstitial-dialog-extend/backup-email-dialog.component.ts delete mode 100644 src/app/core/lang-preload/firefox-xsrf-preload.interceptor.ts create mode 100644 src/app/core/login-interstitials-manager/implementations/login-backup-email-interstitials-manager.service.ts create mode 100644 src/app/core/login-interstitials-manager/interstitial-observability.service.ts create mode 100644 src/app/core/login-interstitials-manager/test/login-backup-email-interstitials-manager.service.spec.ts rename src/app/core/{lang-preload/firefox-xsrf-preload.interceptor.spec.ts => xsrf/xsrf-preload.interceptor.spec.ts} (51%) create mode 100644 src/app/core/xsrf/xsrf-preload.interceptor.ts create mode 100644 src/app/rum/interstitial-observability.md create mode 100644 src/app/rum/journeys/interstitial.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f9c11888eb..dcd8462db2 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -12,7 +12,7 @@ import { PseudoModule } from 'src/locale/i18n.pseudo.component' import { TitleService } from './core/title-service/title.service' import { HttpContentTypeHeaderInterceptor } from './core/http-content-type-header-interceptor/http-content-type-header-interceptor' import { XsrfFallbackInterceptor } from './core/xsrf/xsrf-fallback.interceptor' -import { FirefoxXsrfPreloadInterceptor } from './core/lang-preload/firefox-xsrf-preload.interceptor' +import { XsrfPreloadInterceptor } from './core/xsrf/xsrf-preload.interceptor' import { RetryTransientInterceptor } from './core/http/retry-transient.interceptor' import { HTTP_INTERCEPTORS, @@ -47,10 +47,12 @@ import { FormsModule } from '@angular/forms' ], providers: [ TitleService, - // Firefox-only workaround to ensure XSRF cookie is established before backend calls + // Ensures the XSRF cookie is established before any backend call, on every + // browser: without it the first mutating request goes out with no + // x-xsrf-token header and gets a 403 { provide: HTTP_INTERCEPTORS, - useClass: FirefoxXsrfPreloadInterceptor, + useClass: XsrfPreloadInterceptor, multi: true, }, { diff --git a/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.html b/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.html new file mode 100644 index 0000000000..98e1cae000 --- /dev/null +++ b/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.html @@ -0,0 +1,146 @@ + + + orcid logo + +

+ Add a backup email address +

+
+
+ + + +
+ We’ve noticed you only have one email address associated with your ORCID + account. Adding a backup email will help you sign in to ORCID if you lose + access to your primary email address. +
+
+ +
+

+ Your primary email address +

+ +
+ {{ primaryEmail?.value }} + + +
+ +
+ +
+

+ Your backup email address +

+

+ Add another email to your ORCID account as a backup. A mix of personal and + professional email addresses works best. +

+ +
+
+ + Backup email + + + + + + Please enter your email + + + Please enter a valid email address, for example + joe@institution.edu + + + This email is already associated with an existing ORCID record. Please + use a different email address. + +
+
+
+ + + + +
+ + +
+
+ + diff --git a/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.scss b/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.scss new file mode 100644 index 0000000000..6107427d56 --- /dev/null +++ b/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.scss @@ -0,0 +1,127 @@ +@import '../../../../../assets/scss/orcid.spacing.scss'; + +// The orc-font-* classes set neither line-height nor font-weight, so the Figma +// metrics are spelled out here. + +section { + margin-top: $spacing-large; +} + +h2 { + margin: 0 0 $spacing-base 0; + font-weight: bold; + line-height: 24px; // Figma body-M 16/24 +} + +.primary-email-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: $spacing-small; + + strong { + font-weight: 700; + line-height: 21px; // Figma $body-s 14/21 bold + } +} + +.email-category { + display: flex; + align-items: center; + gap: $spacing-xsmall; + + span { + line-height: 18px; // Figma $body-xs 12/18 + } + + mat-icon { + height: 24px; + width: 24px; + font-size: 24px; + } +} + +.backup-email p { + margin: 0; +} + +.input-container, +mat-form-field { + width: 100%; +} + +.input-container { + margin-top: $spacing-base; +} + +mat-label.orc-font-small-print { + font-weight: bold; + display: block; + margin-bottom: $spacing-xsmall; +} + +button.authorize-button { + width: 100%; + margin-bottom: $spacing-base; +} + +// The decline action is a button so it is keyboard reachable, styled as a link +button.link-button { + background: none; + border: none; + padding: 0; + font: inherit; + cursor: pointer; + text-decoration: underline; +} + +mat-divider { + margin-top: $spacing-base; + margin-bottom: $spacing-base; +} + +mat-divider.footer-divider { + margin-top: $spacing-large; + margin-bottom: $spacing-large; +} + +mat-icon > img { + height: 20px; + margin-top: 2px; // Fix icon component with SVG image +} + +mat-spinner { + align-self: center; + margin: 60px; +} + +mat-card-header.authorize-header { + mat-card-title { + margin: 0 0 $spacing-large 0 !important; + } + + h1 { + margin-top: 0; + } + + .orc-font-heading-small { + font-style: normal; + font-weight: 500; + } + + mat-icon.logo-icon { + height: 80px; + width: 64px; + img { + height: 64px; + } + } +} + +mat-card-content.authorize-content { + margin: 0px !important; + + div.deny-button-wrapper { + justify-content: center; + } +} diff --git a/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.scss-theme.scss b/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.scss-theme.scss new file mode 100644 index 0000000000..a8c13379e7 --- /dev/null +++ b/src/app/cdk/interstitials/backup-email/interstitial-component/backup-email.component.scss-theme.scss @@ -0,0 +1,35 @@ +@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, accent); + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-mdc-raised-button.mat-primary:not(.mat-raised-button.mat-primary) { + background-color: mat.m2-get-color-from-palette($primary, 700); + } + + // The decline action is a