Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 94 additions & 37 deletions packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
Card
} from "nhsuk-react-components"

import "../styles/roleselectionpage.scss"

import {useAuth} from "@/context/AuthProvider"
import {RoleDetails} from "@cpt-ui-common/common-types"
import {Button} from "./ReactRouterButton"
Expand Down Expand Up @@ -96,6 +98,8 @@

const navigate = useNavigate()
const redirecting = useRef(false)
const [isSelectingRole, setIsSelectingRole] = useState(false)
const [selectedCardId, setSelectedCardId] = useState<string | null>(null)

const [roleComponentProps, setRoleComponentProps] = useState<RoleComponentProps>({
rolesWithAccess: [],
Expand All @@ -111,6 +115,15 @@
roleCardProps: RolesWithAccessProps
) => {
e.preventDefault()

// Prevent multiple submissions
if (isSelectingRole) {
return
}

setIsSelectingRole(true)
setSelectedCardId(roleCardProps.uuid)

Comment on lines +119 to +126
try {
await auth.updateSelectedRole(roleCardProps.role)
navigate(roleCardProps.link)
Expand All @@ -121,6 +134,8 @@
return
}
logger.error("Error selecting role:", err)
setIsSelectingRole(false) // Reset loading state on error
setSelectedCardId(null)
}
}

Expand Down Expand Up @@ -330,6 +345,7 @@
<Button
to={confirmButton.link}
data-testid="confirm-and-continue"
disabled={isSelectingRole}
>
{confirmButton.text}
</Button>
Expand All @@ -342,44 +358,85 @@
<Col width="two-thirds">
<div className="section">
{roleComponentProps.rolesWithAccess
.map((roleCardProps: RolesWithAccessProps) => (
<Card
key={roleCardProps.uuid}
data-testid="eps-card"
className="nhsuk-card nhsuk-card--primary nhsuk-u-margin-bottom-4"
tabIndex={0}
onKeyDown={(e) => handleCardKeyDown(e, roleCardProps)}
onClick={(e) => handleCardClick(e, roleCardProps)}
style={{cursor: "pointer"}}
>
<Card.Content>
<div className="eps-card__layout">
<div>
<Card.Heading className="nhsuk-heading-s eps-card__org-name">
{roleCardProps.role.org_name || noOrgName}
<br />
(ODS: {roleCardProps.role.org_code || noODSCode})
</Card.Heading>
<Card.Description className="nhsuk-u-margin-top-2">
{roleCardProps.role.role_name || noRoleName}
</Card.Description>
</div>
<div className="eps-card__address">
<Card.Description>
{(roleCardProps.role.site_address || contentText.noAddress)
.split("\n")
.map((line: string, index: number) => (
<span key={index}>
{line}
<br />
</span>
))}
</Card.Description>
.map((roleCardProps: RolesWithAccessProps) => {

Check failure on line 361 in packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 54 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-prescription-tracker-ui&issues=AZ0MOlgaKYsfbtn9tCFn&open=AZ0MOlgaKYsfbtn9tCFn&pullRequest=1921
const isThisCardSelected = selectedCardId === roleCardProps.uuid
const isOtherCardDisabled = isSelectingRole && !isThisCardSelected

return (
<Card
key={roleCardProps.uuid}
data-testid="eps-card"
className={`nhsuk-card nhsuk-card--primary nhsuk-u-margin-bottom-4 ${
isOtherCardDisabled ? "nhsuk-card--disabled" : ""
} ${
isThisCardSelected ? "nhsuk-card--selected" : ""
}`}
tabIndex={isOtherCardDisabled ? -1 : 0}
onKeyDown={isOtherCardDisabled ? undefined : (e) => handleCardKeyDown(e, roleCardProps)}
onClick={isOtherCardDisabled ? undefined : (e) => handleCardClick(e, roleCardProps)}
style={{
cursor: isOtherCardDisabled ? "not-allowed" : "pointer",
opacity: isOtherCardDisabled ? 0.5 : 1,
pointerEvents: isOtherCardDisabled ? "none" : "auto"
}}
role="button"
aria-disabled={isOtherCardDisabled}
>
<Card.Content>
<div className="eps-card__layout">
<div>
<Card.Heading className="nhsuk-heading-s eps-card__org-name">
<a
href="#"
onClick={isOtherCardDisabled ?
(e) => e.preventDefault() :
(e) => handleCardClick(e, roleCardProps)
}
onKeyDown={isOtherCardDisabled ?
undefined :
(e) => handleCardKeyDown(e, roleCardProps)
}
Comment on lines +391 to +398
style={{
textDecoration: "none",
color: "inherit",
pointerEvents: isOtherCardDisabled ? "none" : "auto",
cursor: isOtherCardDisabled ? "not-allowed" : "pointer"
}}
tabIndex={isOtherCardDisabled ? -1 : 0}
aria-disabled={isOtherCardDisabled}
role="button"
className={`${
Comment on lines +404 to +408
isOtherCardDisabled ? "disabled-card-link" : ""
} ${
isThisCardSelected ? "selected-card-link" : ""
}`}
>

Check warning on line 413 in packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use <input type="button">, <input type="image">, <input type="reset">, <input type="submit">, or <button> instead of the "button" role to ensure accessibility across all devices.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-prescription-tracker-ui&issues=AZ0MOlgaKYsfbtn9tCFp&open=AZ0MOlgaKYsfbtn9tCFp&pullRequest=1921

Check warning on line 413 in packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Anchor used as a button. Anchors are primarily expected to navigate. Use the button element instead.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-prescription-tracker-ui&issues=AZ0MOlgaKYsfbtn9tCFo&open=AZ0MOlgaKYsfbtn9tCFo&pullRequest=1921
{roleCardProps.role.org_name || noOrgName}
<br />
(ODS: {roleCardProps.role.org_code || noODSCode})
</a>
</Card.Heading>
<Card.Description className="nhsuk-u-margin-top-2">
{roleCardProps.role.role_name || noRoleName}
</Card.Description>
</div>
<div className="eps-card__address">
<Card.Description>
{(roleCardProps.role.site_address || contentText.noAddress)
.split("\n")
.map((line: string, index: number) => (
<span key={index}>
{line}
<br />
</span>
))}
</Card.Description>
</div>
</div>
</div>
</Card.Content>
</Card>
))}
</Card.Content>
</Card>
)
})}
</div>
</Col>
)}
Expand Down
7 changes: 6 additions & 1 deletion packages/cpt-ui/src/components/ReactRouterButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ interface ReactRouterButtonProps extends React.ButtonHTMLAttributes<HTMLButtonEl
export const Button: React.FC<ReactRouterButtonProps> = ({
children,
to,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
disabled = false,
onClick,
className = "",
Expand All @@ -28,6 +27,11 @@ export const Button: React.FC<ReactRouterButtonProps> = ({
const navigate = useNavigate()

const handleClick = (event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>) => {
if (disabled) {
event.preventDefault()
return
}

if (to) {
event.preventDefault()
const absolutePath = to.startsWith("/") ? to : `/${to}`
Expand All @@ -40,6 +44,7 @@ export const Button: React.FC<ReactRouterButtonProps> = ({

return (
<NHSButton
disabled={disabled}
onClick={handleClick}
className={`${className}`}
data-testid={testId}
Expand Down
32 changes: 32 additions & 0 deletions packages/cpt-ui/src/styles/roleselectionpage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.nhsuk-card--disabled:not(.nhsuk-card--selected),
.nhsuk-card--disabled:not(.nhsuk-card--selected):hover,
.nhsuk-card--disabled:not(.nhsuk-card--selected):focus {
pointer-events: none !important;
cursor: not-allowed !important;
}

.disabled-card-link:not(.selected-card-link),
.disabled-card-link:not(.selected-card-link):hover,
.disabled-card-link:not(.selected-card-link):focus {
color: inherit !important;
text-decoration: none !important;
pointer-events: none !important;
cursor: not-allowed !important;
}

// Maintain selected card styling
.nhsuk-card--selected .selected-card-link,
.nhsuk-card--selected .selected-card-link:hover,
.nhsuk-card--selected .selected-card-link:focus,
.nhsuk-card--selected .selected-card-link:active {
color: #7c2d12 !important;
text-decoration: underline !important;
text-underline-offset: .1578947368em;
text-decoration-thickness: max(.08em, 1px);
}

.nhsuk-card--selected .selected-card-link:focus {
background: #fd0 !important;
box-shadow: 0 -2px #fd0, 0 4px #212b32 !important;
outline: 4px solid transparent;
}
Loading