diff --git a/src/app/core/login-interstitials-manager/login-main-interstitials-manager.service.ts b/src/app/core/login-interstitials-manager/login-main-interstitials-manager.service.ts
index ecc9325376..42d774b140 100644
--- a/src/app/core/login-interstitials-manager/login-main-interstitials-manager.service.ts
+++ b/src/app/core/login-interstitials-manager/login-main-interstitials-manager.service.ts
@@ -36,6 +36,11 @@ export class LoginMainInterstitialsManagerService {
LoginDomainInterstitialManagerService: LoginDomainInterstitialManagerService,
LoginAffiliationInterstitialManagerService: LoginAffiliationInterstitialManagerService
) {
+ // Delare here all the interstitial services.
+ // This are the entry points to add new interstitials.
+ // They should be added in the order they should be checked.
+ // The first one that returns a component or a dialog subscription will be used.
+ // The rest will be ignored.
this.interstitialServices = [
LoginDomainInterstitialManagerService,
LoginAffiliationInterstitialManagerService,
diff --git a/src/app/core/observability-events/observability-events.service.ts b/src/app/core/observability-events/observability-events.service.ts
index 163b5ea363..ed768cca71 100644
--- a/src/app/core/observability-events/observability-events.service.ts
+++ b/src/app/core/observability-events/observability-events.service.ts
@@ -1,7 +1,11 @@
import { Inject, Injectable } from '@angular/core'
import { WINDOW } from 'src/app/cdk/window'
-export type JourneyType = 'orcid_registration' | 'orcid_update_emails'
+export type JourneyType =
+ | 'orcid_registration'
+ | 'orcid_update_emails'
+ | 'orcid_with_notifications'
+ | 'orcid_without_notifications'
@Injectable({
providedIn: 'root',
})
@@ -26,7 +30,7 @@ export class CustomEventService {
if (runtimeEnvironment.debugger) {
console.debug(
- `-> Journey "${journeyType}" started at ${this.journeys[journeyType].startTime}`,
+ `[RUM][journey:${journeyType}] : start`,
attributes
)
}
@@ -58,14 +62,14 @@ export class CustomEventService {
eventName,
elapsedTime,
}
- if (typeof (this.window as any)?.addPageAction === 'function') {
+ if (typeof (this.window as any).newrelic?.addPageAction === 'function') {
;(this.window as any).newrelic.addPageAction(journeyType, eventAttributes)
}
// Send the custom event to New Relic
if (runtimeEnvironment.debugger) {
console.debug(
- `-> Event "${eventName}" recorded for journey "${journeyType}" with elapsed time ${elapsedTime}ms`,
+ `[RUM][journey:${journeyType}] : event ${eventName}`,
eventAttributes
)
}
@@ -94,15 +98,18 @@ export class CustomEventService {
}
// Send the final custom event to New Relic
- if (typeof (this.window as any)?.addPageAction === 'function') {
- ;(this.window as any).addPageAction(journeyType, finalAttributes)
+ if (typeof (this.window as any).newrelic?.addPageAction === 'function') {
+ ;(this.window as any).newrelic?.addPageAction(
+ journeyType,
+ finalAttributes
+ )
}
// Clean up the journey data
delete this.journeys[journeyType]
console.debug(
- `Journey "${journeyType}" finished with elapsed time ${elapsedTime}ms`,
+ `[RUM][journey:${journeyType}] : finished`,
finalAttributes
)
}
diff --git a/src/app/layout/header/header.component.scss b/src/app/layout/header/header.component.scss
index 53de7303d7..0c182c63eb 100644
--- a/src/app/layout/header/header.component.scss
+++ b/src/app/layout/header/header.component.scss
@@ -195,3 +195,7 @@ nav {
app-search {
overflow: hidden;
}
+
+app-user-menu {
+ display: flex;
+}
diff --git a/src/app/layout/layout.module.ts b/src/app/layout/layout.module.ts
index 56682f60b0..b3a80e3ccd 100644
--- a/src/app/layout/layout.module.ts
+++ b/src/app/layout/layout.module.ts
@@ -21,6 +21,7 @@ import { BannerModule } from '../cdk/banner/banner.module'
import { MaintenanceMessageComponent } from './maintenance-message/maintenance-message.component'
import { MatDividerModule } from '@angular/material/divider'
import { A11yLinkModule } from '../cdk/a11y-link/a11y-link.module'
+import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip'
@NgModule({
imports: [
@@ -37,6 +38,7 @@ import { A11yLinkModule } from '../cdk/a11y-link/a11y-link.module'
BannerModule,
MatDividerModule,
A11yLinkModule,
+ MatTooltipModule,
],
declarations: [
HeaderComponent,
diff --git a/src/app/layout/user-menu/user-menu.component.html b/src/app/layout/user-menu/user-menu.component.html
index 4c1d4cda75..fd48a2d258 100644
--- a/src/app/layout/user-menu/user-menu.component.html
+++ b/src/app/layout/user-menu/user-menu.component.html
@@ -29,6 +29,35 @@
Sign in / Register
+
+
diff --git a/src/app/layout/user-menu/user-menu.component.scss b/src/app/layout/user-menu/user-menu.component.scss
index a5192826ab..3e285730b0 100644
--- a/src/app/layout/user-menu/user-menu.component.scss
+++ b/src/app/layout/user-menu/user-menu.component.scss
@@ -1,3 +1,8 @@
+section {
+ display: flex;
+ flex-wrap: nowrap;
+}
+
.main-button {
height: auto;
.main-button-container {
@@ -37,7 +42,6 @@
:host {
.user-menu-button {
max-width: 100%;
- padding: 0 32px;
.columns-4 & {
padding: 0;
}
@@ -69,7 +73,14 @@
span.name-text-container {
overflow: hidden;
- width: calc(100% - 59px);
+ max-width: calc(102% - 55px);
display: inline-block;
text-overflow: ellipsis;
+ width: fit-content;
+}
+
+[mat-stroked-button] {
+ min-width: 40px;
+ padding: 0;
+ margin: 0 16px;
}
diff --git a/src/app/layout/user-menu/user-menu.component.ts b/src/app/layout/user-menu/user-menu.component.ts
index ff0c38ab96..11ad97536c 100644
--- a/src/app/layout/user-menu/user-menu.component.ts
+++ b/src/app/layout/user-menu/user-menu.component.ts
@@ -9,6 +9,7 @@ import { ApplicationRoutes } from 'src/app/constants'
import { TogglzService } from 'src/app/core/togglz/togglz.service'
import { InboxService } from '../../core/inbox/inbox.service'
import { first } from 'rxjs/operators'
+import { CustomEventService } from 'src/app/core/observability-events/observability-events.service'
@Component({
selector: 'app-user-menu',
@@ -25,8 +26,11 @@ export class UserMenuComponent implements OnInit {
platform: PlatformInfo
labelSigninRegister = $localize`:@@layout.ariaLabelSigninRegister:Sign in to ORCID or register for your ORCID iD`
labelUserMenu = $localize`:@@layout.ariaLabelUserMenu:User menu`
+ notificationTooltipActive = $localize`:@@layout.notificationTooltip:You have unread notifications`
+ notificationTooltip = $localize`:@@layout.notificationTooltipInactive:Notifications inbox`
isAccountDelegate: boolean
inboxUnread = 0
+ userJourney!: 'orcid_with_notifications' | 'orcid_without_notifications'
constructor(
private _router: Router,
@@ -34,7 +38,8 @@ export class UserMenuComponent implements OnInit {
@Inject(WINDOW) private window: Window,
_platform: PlatformInfoService,
private _inboxService: InboxService,
- private _togglz: TogglzService
+ private _togglz: TogglzService,
+ private observabilityEventService: CustomEventService
) {
_userInfo.getUserSession().subscribe((data) => {
if (data.loggedIn) {
@@ -56,15 +61,27 @@ export class UserMenuComponent implements OnInit {
this._inboxService
.retrieveUnreadCount()
.pipe(first())
- .subscribe((inboxUnread) => (this.inboxUnread = inboxUnread))
+ .subscribe((inboxUnread) => {
+ ;(this.userJourney =
+ inboxUnread > 0
+ ? 'orcid_with_notifications'
+ : 'orcid_without_notifications'),
+ this.observabilityEventService.startJourney(
+ this.userJourney,
+
+ { inboxUnread }
+ )
+ this.inboxUnread = inboxUnread
+ })
}
- goto(url) {
+ goto(url, from?: string) {
if (url === 'my-orcid') {
this._router.navigate([ApplicationRoutes.myOrcid])
} else if (url === 'signin') {
this._router.navigate([ApplicationRoutes.signin])
} else if (url === 'inbox') {
+ this.observabilityEventService.recordEvent(this.userJourney, from)
this._router.navigate([ApplicationRoutes.inbox])
} else if (url === 'account') {
this._router.navigate([ApplicationRoutes.account])
diff --git a/src/assets/vectors/notification-button-active.svg b/src/assets/vectors/notification-button-active.svg
new file mode 100644
index 0000000000..94d7c78a4b
--- /dev/null
+++ b/src/assets/vectors/notification-button-active.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/vectors/notification-button.svg b/src/assets/vectors/notification-button.svg
new file mode 100644
index 0000000000..23a55262e7
--- /dev/null
+++ b/src/assets/vectors/notification-button.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/environments/environment.local.4200.ts b/src/environments/environment.local.4200.ts
index aab0be1592..d0497860a8 100644
--- a/src/environments/environment.local.4200.ts
+++ b/src/environments/environment.local.4200.ts
@@ -7,7 +7,7 @@ export const environment: EnvironmentInterface = {
API_NEWS: 'https://www.mocky.io/v2/5dced45b3000007300931ce8',
API_PUB: `///v3.0`,
API_WEB: `///`,
- AUTH_SERVER: 'https://auth./',
+ AUTH_SERVER: '///auth/',
BASE_URL: '///',
INFO_SITE: 'https://info.orcid.org/',
GOOGLE_ANALYTICS_TESTING_MODE: true,
diff --git a/src/locale/properties/layout/layout.en.properties b/src/locale/properties/layout/layout.en.properties
index 3fd6552239..b5277fc84a 100644
--- a/src/locale/properties/layout/layout.en.properties
+++ b/src/locale/properties/layout/layout.en.properties
@@ -114,3 +114,5 @@ footer.cookieSettings=Cookie Settings
footer.ariaLabelLicense=license
layout.ariaLabelConnectingResearchers=Connecting research and researchers
layout.ariaLabelSearchRegistry=Search the ORCID registry...
+layout.notificationTooltip=You have unread notifications
+layout.notificationTooltipInactive=Notifications inbox
\ No newline at end of file
diff --git a/src/proxy.conf.qa.mjs b/src/proxy.conf.qa.mjs
index fc98db5caf..a5f99d9eeb 100644
--- a/src/proxy.conf.qa.mjs
+++ b/src/proxy.conf.qa.mjs
@@ -1,60 +1,115 @@
-export default {
- '/v3.0': {
- target: 'https://pub.qa.orcid.org',
- secure: false,
- logLevel: 'debug',
- changeOrigin: true,
- cookieDomainRewrite: 'localhost',
- bypass: function (req, res, proxyOptions) {
- /// PRINT REQUEST PATH
- if (req.headers.accept?.includes('html')) {
- return '/index.html'
- }
- req.headers['X-Dev-Header'] = 'local-host-proxy-call'
- },
- onProxyRes: responseOverights(),
- },
- '/': {
- target: 'https://qa.orcid.org',
- secure: false,
- logLevel: 'debug',
- changeOrigin: true,
- cookieDomainRewrite: 'localhost',
- onProxyRes: responseOverights(),
+// proxy.conf.qa.mjs (ESM)
- bypass: function (req, res, proxyOptions) {
- if (req.headers.accept?.includes('html') && req.path !== '/signout') {
- return '/index.html'
- }
- req.headers['X-Dev-Header'] = 'local-host-proxy-call'
- },
- },
+/**
+ * ──────────────────────────────────────────────────────────────────────────────
+ * Bypass hook: if the browser is requesting HTML, serve index.html;
+ * otherwise, inject a dev header so backend knows it’s from local dev.
+ * ──────────────────────────────────────────────────────────────────────────────
+ */
+function bypassHtmlOrJson(req, res) {
+ if (req.headers.accept?.includes('html')) {
+ return '/index.html'
+ }
+ req.headers['X-Dev-Header'] = 'local-host-proxy-call'
}
-function responseOverights() {
+
+/**
+ * ──────────────────────────────────────────────────────────────────────────────
+ * onProxyReq for /auth: before sending to auth.qa.orcid.org,
+ * force Origin/Referer to https://qa.orcid.org
+ * ──────────────────────────────────────────────────────────────────────────────
+ */
+function proxyReqOverrideHeaders(proxyReq /* http.ClientRequest */, req, res) {
+ proxyReq.setHeader('Origin', 'https://qa.orcid.org')
+ proxyReq.setHeader('Referer', 'https://qa.orcid.org')
+}
+
+/**
+ * ──────────────────────────────────────────────────────────────────────────────
+ * responseOverridesAuth: for /auth proxy responses.
+ * 1) Rewrite Set-Cookie domain from qa.orcid.org → localhost
+ * 2) If a 3xx redirect points at auth.qa.orcid.org/login → localhost:4200/auth/login
+ * ──────────────────────────────────────────────────────────────────────────────
+ */
+function responseOverridesAuth() {
return (proxyRes, req, res) => {
- // Grab the existing 'set-cookie' headers
const cookies = proxyRes.headers['set-cookie']
- if (cookies) {
- // Transform each cookie
- const newCookies = cookies.map((cookie) => {
- // Example: rewrite "Domain=qa.orcid.org" to "Domain=localhost"
- return cookie.replace(/Domain=\.?qa\.orcid\.org/i, 'Domain=localhost')
- })
-
- // Put the transformed cookies back into the response header
- proxyRes.headers['set-cookie'] = newCookies
+ if (Array.isArray(cookies)) {
+ proxyRes.headers['set-cookie'] = cookies.map((cookie) =>
+ cookie.replace(/Domain=\.?(qa\.)?orcid\.org/i, 'Domain=localhost')
+ )
}
+ if (proxyRes.statusCode >= 300 && proxyRes.statusCode < 400) {
+ let location = proxyRes.headers['location']
+ if (typeof location === 'string') {
+ proxyRes.headers['location'] = location.replace(
+ 'http://auth.qa.orcid.org/login',
+ 'http://localhost:4200/auth/login'
+ )
+ }
+ }
+ }
+}
- // Check for 3xx (especially 302) status codes:
+/**
+ * ──────────────────────────────────────────────────────────────────────────────
+ * responseOverridesGeneric: for root (/) proxies.
+ * 1) Rewrite Set-Cookie domain from qa.orcid.org → localhost
+ * 2) If a 3xx redirect points at qa.orcid.org/signin → /signin
+ * ──────────────────────────────────────────────────────────────────────────────
+ */
+function responseOverridesGeneric() {
+ return (proxyRes, req, res) => {
+ const cookies = proxyRes.headers['set-cookie']
+ if (Array.isArray(cookies)) {
+ proxyRes.headers['set-cookie'] = cookies.map((cookie) =>
+ cookie.replace(/Domain=\.?(qa\.)?orcid\.org/i, 'Domain=localhost')
+ )
+ }
if (proxyRes.statusCode >= 300 && proxyRes.statusCode < 400) {
let location = proxyRes.headers['location']
- if (location) {
- location = location.replace(
+ if (typeof location === 'string') {
+ proxyRes.headers['location'] = location.replace(
'https://qa.orcid.org/signin',
'http://localhost:4200/signin'
)
- proxyRes.headers['location'] = location
}
}
}
}
+
+/**
+ * ──────────────────────────────────────────────────────────────────────────────
+ * Finally, export the proxy table as an ES module default export.
+ * ──────────────────────────────────────────────────────────────────────────────
+ */
+export default {
+ '/v3.0': {
+ target: 'https://pub.qa.orcid.org',
+ secure: false,
+ changeOrigin: true,
+ logLevel: 'debug',
+ cookieDomainRewrite: 'localhost',
+ },
+
+ '/auth': {
+ target: 'https://auth.qa.orcid.org',
+ secure: false,
+ changeOrigin: true,
+ logLevel: 'debug',
+ cookieDomainRewrite: 'localhost',
+ pathRewrite: { '^/auth': '' },
+ onProxyReq: proxyReqOverrideHeaders,
+ onProxyRes: responseOverridesAuth(),
+ },
+
+ '/': {
+ target: 'https://qa.orcid.org',
+ secure: false,
+ changeOrigin: true,
+ logLevel: 'debug',
+ cookieDomainRewrite: 'localhost',
+ bypass: bypassHtmlOrJson,
+ onProxyRes: responseOverridesGeneric(),
+ },
+}