Skip to content

Commit a2b077a

Browse files
8laneChristianAMartinA-Ashiq
authored
feat: setup auth ui components (#598)
* feat: setup auth ui components * Render avatar in TopNav * Render sign out button at top of mega menu --------- Co-authored-by: Christian Martin <christian@christianmartin.co.uk> Co-authored-by: A-Ashiq <afaan.ashiq2@gmail.com>
1 parent 86d3be0 commit a2b077a

20 files changed

Lines changed: 529 additions & 71 deletions

File tree

config/i18n/i18nForTests.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import i18n from 'i18next'
22
import { initReactI18next } from 'react-i18next'
33

4+
import auth from '../../public/locales/en/auth.json'
45
import common from '../../public/locales/en/common.json'
56
import errors from '../../public/locales/en/errors.json'
67
import metrics from '../../public/locales/en/metrics.json'
@@ -11,6 +12,7 @@ import formatters from './formatters'
1112
export const defaultNS = 'common'
1213
export const resources = {
1314
en: {
15+
auth,
1416
common,
1517
whatsNew,
1618
weatherHealthAlerts,
@@ -21,7 +23,7 @@ export const resources = {
2123

2224
i18n.use(initReactI18next).init({
2325
lng: 'en',
24-
ns: ['common', 'topic', 'weatherHealthAlerts', 'whatsNew', 'metrics', 'errors'],
26+
ns: ['auth', 'common', 'topic', 'weatherHealthAlerts', 'whatsNew', 'metrics', 'errors'],
2527
resources,
2628
defaultNS,
2729
interpolation: {

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const customJestConfig = {
1616
'^@/config/(.*)$': '<rootDir>/src/config/$1',
1717
'^@/lib/(.*)$': '<rootDir>/src/lib/$1',
1818
'^@/app/(.*)$': '<rootDir>/src/app/$1',
19+
'^@/auth': '<rootDir>/src/auth',
1920
// Needed to get react markdown & dependencies to work with react-raw, see:
2021
// https://stackoverflow.com/questions/70916761/next-js-and-jest-syntaxerror-cannot-use-import-statement-outside-a-module
2122
'react-markdown': '<rootDir>/node_modules/react-markdown/react-markdown.min.js',

package-lock.json

Lines changed: 121 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"dependencies": {
2424
"@jsdevtools/rehype-toc": "^3.0.2",
2525
"@neshca/cache-handler": "^1.9.0",
26+
"@radix-ui/react-avatar": "^1.1.3",
2627
"@radix-ui/react-dialog": "^1.0.5",
2728
"@radix-ui/react-scroll-area": "^1.0.5",
2829
"@radix-ui/react-tabs": "^1.0.4",

public/locales/en/auth.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"signOutButton": "Sign out",
3+
"signInButton": "Start now",
4+
"startPage": {
5+
"heading": "Sign in to the UKHSA data dashboard",
6+
"intro": "The UKHSA Data Dashboard provides secure access to critical public health data and insights.",
7+
"accessInfo": "To view restricted datasets and reports, you need to sign in with an approved account.",
8+
"howToAccess": "How to Access:",
9+
"steps": {
10+
"signIn": "Click the button below to sign in using your credentials.",
11+
"contactAdmin": "If you don't have access but believe you should, please contact your administrator."
12+
},
13+
"signOutBannerTitle": "Success",
14+
"signOutBannerHeading": "You've been signed out",
15+
"signOutBannerDescription": "You have successfully signed out of the UKHSA Data Dashboard. If you need to access the data again, please sign in."
16+
}
17+
}

src/app/(auth)/start/layout.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { notFound } from 'next/navigation'
2+
import { ReactNode } from 'react'
3+
4+
import { BackToTop } from '@/app/components/ui/ukhsa'
5+
import LayoutBlackBanner from '@/app/components/ui/ukhsa/Layout/LayoutBlackBanner'
6+
import { authEnabled } from '@/config/constants'
7+
8+
export default async function Layout({ children }: { children: ReactNode }) {
9+
if (!authEnabled) return notFound()
10+
11+
return (
12+
<LayoutBlackBanner>
13+
<div className="govuk-!-padding-top-4 flex flex-col gap-0 xl:gap-7">
14+
<main className="govuk-main-wrapper govuk-!-padding-top-0" id="main-content">
15+
{children}
16+
</main>
17+
</div>
18+
<BackToTop className="govuk-!-margin-bottom-4" />
19+
</LayoutBlackBanner>
20+
)
21+
}

src/app/(auth)/start/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import StartPage from '@/app/components/cms/pages/Start'
2+
import { PageParams, SearchParams } from '@/app/types'
3+
4+
export default async function Page({ params, searchParams }: { params: PageParams; searchParams: SearchParams }) {
5+
const { slug = [] } = params
6+
7+
return <StartPage slug={slug} searchParams={searchParams} />
8+
}

src/app/(cms)/[[...slug]]/layout.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import HeroBanner from '@/app/components/ui/ukhsa/HeroBanner/HeroBanner'
88
import { MegaMenu } from '@/app/components/ui/ukhsa/MegaMenu/MegaMenu'
99
import { PhaseBanner } from '@/app/components/ui/ukhsa/PhaseBanner/PhaseBanner'
1010
import { TopNav } from '@/app/components/ui/ukhsa/TopNav/TopNav'
11+
import UserAvatar from '@/app/components/ui/ukhsa/UserAvatar/UserAvatar'
1112
import { getGlobalBanner } from '@/app/hooks/getGlobalBanner'
1213
import { getServerTranslation } from '@/app/i18n'
1314
import { getLandingPage } from '@/app/utils/cms'
15+
import { authEnabled } from '@/config/constants'
1416

1517
interface LayoutProps {
1618
children: ReactNode
@@ -57,7 +59,7 @@ export default async function Layout({ children, params }: LayoutProps) {
5759
</div>
5860
</header>
5961

60-
<TopNav>
62+
<TopNav avatar={authEnabled ? <UserAvatar /> : null}>
6163
<MegaMenu />
6264
</TopNav>
6365

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { redirect } from 'next/navigation'
2+
3+
import { View } from '@/app/components/ui/ukhsa'
4+
import UserSignIn from '@/app/components/ui/ukhsa/UserSignIn/UserSignIn'
5+
import { Heading } from '@/app/components/ui/ukhsa/View/Heading/Heading'
6+
import { getServerTranslation } from '@/app/i18n'
7+
import { PageComponentBaseProps } from '@/app/types'
8+
import { auth } from '@/auth'
9+
10+
export default async function StartPage({ searchParams: { logout } }: PageComponentBaseProps<{ logout?: 'success' }>) {
11+
const session = await auth()
12+
13+
if (session) redirect('/')
14+
15+
const { t } = await getServerTranslation('auth')
16+
17+
return (
18+
<View>
19+
<div className="govuk-grid-row">
20+
<div className="govuk-grid-column-three-quarters-from-desktop">
21+
{logout === 'success' ? (
22+
<div
23+
className="govuk-notification-banner govuk-notification-banner--success"
24+
role="alert"
25+
aria-labelledby="govuk-notification-banner-title"
26+
>
27+
<div className="govuk-notification-banner__header">
28+
<h2 className="govuk-notification-banner__title" id="govuk-notification-banner-title">
29+
{t('startPage.signOutBannerTitle')}
30+
</h2>
31+
</div>
32+
<div className="govuk-notification-banner__content">
33+
<h3 className="govuk-notification-banner__heading">{t('startPage.signOutBannerHeading')}</h3>
34+
<p className="govuk-body">{t('startPage.signOutBannerDescription')}</p>
35+
</div>
36+
</div>
37+
) : null}
38+
<Heading heading={t('startPage.heading')} />
39+
<p>{t('startPage.intro')}</p>
40+
<p>{t('startPage.accessInfo')}</p>
41+
<p>
42+
<strong>{t('startPage.howToAccess')}</strong>
43+
</p>
44+
<ul className="govuk-list govuk-list--bullet">
45+
<li>{t('startPage.steps.signIn')}</li>
46+
<li>{t('startPage.steps.contactAdmin')}</li>
47+
</ul>
48+
<UserSignIn />
49+
</div>
50+
</div>
51+
</View>
52+
)
53+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use client'
2+
3+
import * as AvatarPrimitive from '@radix-ui/react-avatar'
4+
import cn from 'clsx'
5+
import * as React from 'react'
6+
7+
const Avatar = React.forwardRef<
8+
React.ElementRef<typeof AvatarPrimitive.Root>,
9+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
10+
>(({ className, ...props }, ref) => (
11+
<AvatarPrimitive.Root
12+
ref={ref}
13+
className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
14+
{...props}
15+
/>
16+
))
17+
Avatar.displayName = AvatarPrimitive.Root.displayName
18+
19+
const AvatarImage = React.forwardRef<
20+
React.ElementRef<typeof AvatarPrimitive.Image>,
21+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
22+
>(({ className, ...props }, ref) => (
23+
<AvatarPrimitive.Image ref={ref} className={cn('aspect-square h-full w-full', className)} {...props} />
24+
))
25+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
26+
27+
const AvatarFallback = React.forwardRef<
28+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
29+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
30+
>(({ className, ...props }, ref) => (
31+
<AvatarPrimitive.Fallback
32+
ref={ref}
33+
className={cn('flex h-full w-full items-center justify-center rounded-full', className)}
34+
{...props}
35+
/>
36+
))
37+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
38+
39+
export { Avatar, AvatarFallback, AvatarImage }

0 commit comments

Comments
 (0)