Skip to content

Commit a5c0389

Browse files
committed
1 parent 8564b35 commit a5c0389

File tree

4 files changed

+127
-90
lines changed

4 files changed

+127
-90
lines changed

frontend/src/components/molecules/AuthLink/AuthLink.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ export const AuthLink = ({ className }: { className?: string }) => {
1010
const { authStatus } = useAuthenticator((ctx) => [ctx.authStatus]);
1111

1212
let id = 'sign-in-link';
13-
let linkContent = content.components.header.links.signIn;
13+
let linkContent = content.components.header.accountInfo.links.signIn;
1414

1515
if (authStatus === 'authenticated') {
1616
id = 'sign-out-link';
17-
linkContent = content.components.header.links.signOut;
17+
linkContent = content.components.header.accountInfo.links.signOut;
1818
}
1919

2020
return (

frontend/src/components/molecules/HeaderWithAccount/HeaderWithAccount.tsx

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,31 @@
1+
'use client';
2+
13
import content from '@content/content';
24
import { AuthLink } from '@molecules/AuthLink/AuthLink';
35
import { truncate } from '@utils/truncate';
46
import Link from 'next/link';
57
import React from 'react';
68

79
interface NhsNotifyHeaderWithAccountProps {
8-
userName?: string;
9-
_accountUrl?: string;
10-
_signOutUrl?: string;
11-
dataTestId?: string;
10+
displayName?: string;
11+
clientName?: string;
1212
}
1313

14-
const NhsNotifyHeaderWithAccount: React.FC<NhsNotifyHeaderWithAccountProps> = ({
15-
userName,
16-
_accountUrl,
17-
_signOutUrl,
18-
dataTestId,
19-
}) => (
14+
const headerContent = content.components.header;
15+
16+
const NhsNotifyHeaderWithAccount = async ({ displayName, clientName }: NhsNotifyHeaderWithAccountProps) => (
2017
<header
2118
className='nhsuk-header'
2219
role='banner'
2320
data-module='nhsuk-header'
24-
data-testid={dataTestId ?? 'page-header'}
21+
data-testid='page-header'
2522
>
2623
<div className='nhsuk-header__container nhsuk-width-container'>
2724
<div className='nhsuk-header__service'>
2825
<Link
2926
className='nhsuk-header__service-logo'
30-
href='/message-templates'
31-
aria-label={content.components.header.links.logoLink.ariaLabel}
27+
href={headerContent.logoLink.href}
28+
aria-label={headerContent.logoLink.ariaLabel}
3229
>
3330
<svg
3431
className='nhsuk-header__logo'
@@ -40,70 +37,65 @@ const NhsNotifyHeaderWithAccount: React.FC<NhsNotifyHeaderWithAccountProps> = ({
4037
role='img'
4138
aria-label='NHS'
4239
>
43-
<title>{content.components.header.links.logoLink.logoTitle}</title>
40+
<title>{headerContent.logoLink.logoTitle}</title>
4441
<path
4542
fill='currentcolor'
4643
d='M200 0v80H0V0h200Zm-27.5 5.5c-14.5 0-29 5-29 22 0 10.2 7.7 13.5 14.7 16.3l.7.3c5.4 2 10.1 3.9 10.1 8.4 0 6.5-8.5 7.5-14 7.5s-12.5-1.5-16-3.5L135 70c5.5 2 13.5 3.5 20 3.5 15.5 0 32-4.5 32-22.5 0-19.5-25.5-16.5-25.5-25.5 0-5.5 5.5-6.5 12.5-6.5a35 35 0 0 1 14.5 3l4-13.5c-4.5-2-12-3-20-3Zm-131 2h-22l-14 65H22l9-45h.5l13.5 45h21.5l14-65H64l-9 45h-.5l-13-45Zm63 0h-18l-13 65h17l6-28H117l-5.5 28H129l13.5-65H125L119.5 32h-20l5-24.5Z'
4744
/>
4845
</svg>
4946
<span className='nhsuk-header__service-name'>
50-
{content.components.header.serviceName}
47+
{headerContent.serviceName}
5148
</span>
5249
</Link>
5350
</div>
54-
<nav className='nhsuk-header__account' aria-label='Account'>
51+
<nav
52+
className='nhsuk-header__account'
53+
aria-label={headerContent.accountInfo.ariaLabel}
54+
>
5555
<ul className='nhsuk-header__account-list'>
56-
<li className='nhsuk-header__account-item'>
57-
<svg
58-
className='nhsuk-icon nhsuk-icon__user'
59-
xmlns='http://www.w3.org/2000/svg'
60-
viewBox='0 0 24 24'
61-
aria-hidden='true'
62-
focusable='false'
63-
>
64-
<path d='M12 1a11 11 0 1 1 0 22 11 11 0 0 1 0-22Zm0 2a9 9 0 0 0-5 16.5V18a4 4 0 0 1 4-4h2a4 4 0 0 1 4 4v1.5A9 9 0 0 0 12 3Zm0 3a3.5 3.5 0 1 1-3.5 3.5A3.4 3.4 0 0 1 12 6Z'></path>
65-
</svg>
66-
{truncate('Florence Nightingale (Regional Manager)')}
67-
</li>
68-
<li className='nhsuk-header__account-item'>
69-
{truncate('Client name')}
70-
</li>
56+
{displayName && (
57+
<li className='nhsuk-header__account-item'>
58+
<svg
59+
className='nhsuk-icon nhsuk-icon__user'
60+
xmlns='http://www.w3.org/2000/svg'
61+
viewBox='0 0 24 24'
62+
aria-hidden='true'
63+
focusable='false'
64+
>
65+
<path d='M12 1a11 11 0 1 1 0 22 11 11 0 0 1 0-22Zm0 2a9 9 0 0 0-5 16.5V18a4 4 0 0 1 4-4h2a4 4 0 0 1 4 4v1.5A9 9 0 0 0 12 3Zm0 3a3.5 3.5 0 1 1-3.5 3.5A3.4 3.4 0 0 1 12 6Z'></path>
66+
</svg>
67+
{truncate(displayName)}
68+
</li>
69+
)}
70+
{clientName && (
71+
<li className='nhsuk-header__account-item'>
72+
{truncate(clientName)}
73+
</li>
74+
)}
7175
<li className='nhsuk-header__account-item'>
7276
<AuthLink className='nhsuk-header__account-link' />
7377
</li>
7478
</ul>
7579
</nav>
7680
</div>
77-
<nav className='nhsuk-header__navigation' aria-label='Menu'>
81+
<nav
82+
className='nhsuk-header__navigation'
83+
aria-label={headerContent.navigationMenu.ariaLabel}
84+
>
7885
<div className='nhsuk-header__navigation-container nhsuk-width-container'>
7986
<ul className='nhsuk-header__navigation-list'>
80-
{content.components.header.nav.map(({ text, href }, index) => {
87+
{headerContent.navigationMenu.links.map(({ text, href }, index) => {
8188
return (
82-
<li className='nhsuk-header__navigation-item'>
89+
<li
90+
className='nhsuk-header__navigation-item'
91+
key={`item-${index}`}
92+
>
8393
<Link className='nhsuk-header__navigation-link' href={href}>
8494
{text}
8595
</Link>
8696
</li>
8797
);
8898
})}
89-
<li className='nhsuk-header__menu' hidden>
90-
<button
91-
className='nhsuk-header__menu-toggle nhsuk-header__navigation-link'
92-
id='toggle-menu'
93-
aria-expanded='false'
94-
>
95-
<span className='nhsuk-u-visually-hidden'>Browse </span>More
96-
<svg
97-
className='nhsuk-icon nhsuk-icon__chevron-down'
98-
xmlns='http://www.w3.org/2000/svg'
99-
viewBox='0 0 24 24'
100-
aria-hidden='true'
101-
focusable='false'
102-
>
103-
<path d='M15.5 12a1 1 0 0 1-.29.71l-5 5a1 1 0 0 1-1.42-1.42l4.3-4.29-4.3-4.29a1 1 0 0 1 1.42-1.42l5 5a1 1 0 0 1 .29.71z'></path>
104-
</svg>
105-
</button>
106-
</li>
10799
</ul>
108100
</div>
109101
</nav>

frontend/src/content/content.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,39 @@ const selectAnOption = 'Select an option';
1414

1515
const header = {
1616
serviceName: 'Notify',
17-
links: {
18-
signIn: {
19-
text: 'Sign in',
20-
href: `/auth?redirect=${encodeURIComponent(
21-
`${getBasePath()}/create-and-submit-templates`
22-
)}`,
23-
},
24-
signOut: {
25-
text: 'Sign out',
26-
href: '/auth/signout',
27-
},
28-
logoLink: {
29-
ariaLabel: 'NHS Notify templates',
30-
logoTitle: 'NHS logo',
31-
},
17+
logoLink: {
18+
ariaLabel: 'NHS Notify templates',
19+
logoTitle: 'NHS logo',
20+
href: '/message-templates',
3221
},
33-
nav: [
34-
{
35-
text: 'Templates',
36-
href: '/message-templates'
22+
accountInfo: {
23+
ariaLabel: 'Account',
24+
links: {
25+
signIn: {
26+
text: 'Sign in',
27+
href: `/auth?redirect=${encodeURIComponent(
28+
`${getBasePath()}/create-and-submit-templates`
29+
)}`,
30+
},
31+
signOut: {
32+
text: 'Sign out',
33+
href: '/auth/signout',
34+
},
3735
},
38-
{
39-
text: 'Message plans',
40-
href: '/message-plans'
41-
}
42-
]
36+
},
37+
navigationMenu: {
38+
ariaLabel: 'Menu',
39+
links: [
40+
{
41+
text: 'Templates',
42+
href: '/message-templates',
43+
},
44+
{
45+
text: 'Message plans',
46+
href: '/message-plans',
47+
},
48+
],
49+
},
4350
};
4451

4552
const footer = {

frontend/src/utils/amplify-utils.ts

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ export const { runWithAmplifyServerContext } = createServerRunner({
1717
export async function getSessionServer(
1818
options: FetchAuthSessionOptions = {}
1919
): Promise<{
20-
accessToken: string | undefined;
21-
clientId: string | undefined;
20+
accessToken?: string;
21+
idToken?: string;
22+
clientId?: string;
23+
clientName?: string;
24+
displayName?: string;
2225
}> {
2326
const session = await runWithAmplifyServerContext({
2427
nextServerContext: { cookies },
@@ -28,20 +31,26 @@ export async function getSessionServer(
2831
});
2932

3033
const accessToken = session?.tokens?.accessToken?.toString();
31-
const clientId = accessToken && (await getClientId(accessToken));
34+
const clientId = accessToken && getClientId(accessToken);
35+
36+
const idToken = session?.tokens?.idToken?.toString();
37+
const idClaims = idToken ? getIdTokenClaims(idToken) : undefined;
3238

3339
return {
3440
accessToken,
41+
idToken,
3542
clientId,
43+
clientName: idClaims?.clientName,
44+
displayName: idClaims?.displayName,
3645
};
3746
}
3847

3948
export const getSessionId = async () => {
4049
return getAccessTokenParam('origin_jti');
4150
};
4251

43-
export const getClientId = async (accessToken: string) => {
44-
return getJwtPayload('nhs-notify:client-id', accessToken);
52+
export const getClientId = (accessToken: string) => {
53+
return getClaim(decodeJwt(accessToken), 'nhs-notify:client-id');
4554
};
4655

4756
const getAccessTokenParam = async (key: string) => {
@@ -50,17 +59,46 @@ const getAccessTokenParam = async (key: string) => {
5059

5160
if (!accessToken) return;
5261

53-
return getJwtPayload(key, accessToken);
62+
return getClaim(decodeJwt(accessToken), key);
5463
};
5564

56-
const getJwtPayload = (key: string, accessToken: string) => {
57-
const jwt = jwtDecode<JWT['payload']>(accessToken);
65+
const decodeJwt = (token: string): JWT['payload'] =>
66+
jwtDecode<JWT['payload']>(token);
67+
68+
const getClaim = (claims: JWT['payload'], key: string): string | undefined => {
69+
const value = claims[key];
70+
return value != null ? String(value) : undefined;
71+
};
72+
73+
const getIdTokenClaims = (
74+
idToken: string
75+
): {
76+
clientName?: string;
77+
displayName?: string;
78+
} => {
79+
const claims = decodeJwt(idToken);
80+
81+
const clientName = getClaim(claims, 'nhs-notify:client-name');
5882

59-
const value = jwt[key];
83+
let displayName;
6084

61-
if (!value) {
62-
return;
85+
const preferredUsername =
86+
getClaim(claims, 'preferred_username') || getClaim(claims, 'display_name');
87+
88+
if (preferredUsername) displayName = preferredUsername;
89+
else {
90+
const givenName = getClaim(claims, 'given_name');
91+
const familyName = getClaim(claims, 'family_name');
92+
93+
if (givenName && familyName) displayName = `${givenName} ${familyName}`;
94+
else {
95+
const email = getClaim(claims, 'email');
96+
if (email) displayName = email;
97+
}
6398
}
6499

65-
return value.toString();
100+
return {
101+
clientName,
102+
displayName,
103+
};
66104
};

0 commit comments

Comments
 (0)