From eee932800796507c978980a8a431aacd7a7db8bb Mon Sep 17 00:00:00 2001 From: Jonno Date: Thu, 19 Mar 2026 15:24:54 +0000 Subject: [PATCH 1/4] first steps --- .../src/components/EpsRoleSelectionPage.tsx | 51 ++++++++++++++++--- .../src/components/ReactRouterButton.tsx | 7 ++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx b/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx index 57a5ca3392..d0b81d05b2 100644 --- a/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx +++ b/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx @@ -96,6 +96,7 @@ export default function RoleSelectionPage({ const navigate = useNavigate() const redirecting = useRef(false) + const [isSelectingRole, setIsSelectingRole] = useState(false) const [roleComponentProps, setRoleComponentProps] = useState({ rolesWithAccess: [], @@ -111,6 +112,14 @@ export default function RoleSelectionPage({ roleCardProps: RolesWithAccessProps ) => { e.preventDefault() + + // Prevent multiple submissions + if (isSelectingRole) { + return + } + + setIsSelectingRole(true) + try { await auth.updateSelectedRole(roleCardProps.role) navigate(roleCardProps.link) @@ -121,6 +130,7 @@ export default function RoleSelectionPage({ return } logger.error("Error selecting role:", err) + setIsSelectingRole(false) // Reset loading state on error } } @@ -321,6 +331,7 @@ export default function RoleSelectionPage({ @@ -337,19 +348,43 @@ export default function RoleSelectionPage({ handleCardKeyDown(e, roleCardProps)} - onClick={(e) => handleCardClick(e, roleCardProps)} - style={{cursor: "pointer"}} + className={`nhsuk-card nhsuk-card--primary nhsuk-u-margin-bottom-4 ${ + isSelectingRole ? "nhsuk-card--disabled" : "" + }`} + tabIndex={isSelectingRole ? -1 : 0} + onKeyDown={isSelectingRole ? undefined : (e) => handleCardKeyDown(e, roleCardProps)} + onClick={isSelectingRole ? undefined : (e) => handleCardClick(e, roleCardProps)} + style={{ + cursor: isSelectingRole ? "not-allowed" : "pointer", + opacity: isSelectingRole ? 0.5 : 1 + }} + role="button" + aria-disabled={isSelectingRole} >
- {roleCardProps.role.org_name || noOrgName} -
- (ODS: {roleCardProps.role.org_code || noODSCode}) + e.preventDefault() : + (e) => handleCardClick(e, roleCardProps) + } + onKeyDown={isSelectingRole ? undefined : (e) => handleCardKeyDown(e, roleCardProps)} + style={{ + textDecoration: "none", + color: "inherit", + pointerEvents: isSelectingRole ? "none" : "auto" + }} + tabIndex={isSelectingRole ? -1 : 0} + aria-disabled={isSelectingRole} + role="button" + > + {roleCardProps.role.org_name || noOrgName} +
+ (ODS: {roleCardProps.role.org_code || noODSCode}) +
{roleCardProps.role.role_name || noRoleName} diff --git a/packages/cpt-ui/src/components/ReactRouterButton.tsx b/packages/cpt-ui/src/components/ReactRouterButton.tsx index 8e19ef4983..b44d0e5534 100644 --- a/packages/cpt-ui/src/components/ReactRouterButton.tsx +++ b/packages/cpt-ui/src/components/ReactRouterButton.tsx @@ -18,7 +18,6 @@ interface ReactRouterButtonProps extends React.ButtonHTMLAttributes = ({ children, to, - // eslint-disable-next-line @typescript-eslint/no-unused-vars disabled = false, onClick, className = "", @@ -28,6 +27,11 @@ export const Button: React.FC = ({ const navigate = useNavigate() const handleClick = (event: React.MouseEvent | React.KeyboardEvent) => { + if (disabled) { + event.preventDefault() + return + } + if (to) { event.preventDefault() const absolutePath = to.startsWith("/") ? to : `/${to}` @@ -40,6 +44,7 @@ export const Button: React.FC = ({ return ( Date: Thu, 19 Mar 2026 16:03:48 +0000 Subject: [PATCH 2/4] further styling improvements --- .../src/components/EpsRoleSelectionPage.tsx | 142 ++++++++++-------- .../ui-strings/YourSelectedRoleStrings.ts | 2 +- .../cpt-ui/src/styles/roleselectionpage.scss | 15 ++ 3 files changed, 98 insertions(+), 61 deletions(-) create mode 100644 packages/cpt-ui/src/styles/roleselectionpage.scss diff --git a/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx b/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx index d0b81d05b2..659169e911 100644 --- a/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx +++ b/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx @@ -11,6 +11,8 @@ import { 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" @@ -97,6 +99,7 @@ export default function RoleSelectionPage({ const navigate = useNavigate() const redirecting = useRef(false) const [isSelectingRole, setIsSelectingRole] = useState(false) + const [selectedCardId, setSelectedCardId] = useState(null) const [roleComponentProps, setRoleComponentProps] = useState({ rolesWithAccess: [], @@ -119,6 +122,7 @@ export default function RoleSelectionPage({ } setIsSelectingRole(true) + setSelectedCardId(roleCardProps.uuid) try { await auth.updateSelectedRole(roleCardProps.role) @@ -131,6 +135,7 @@ export default function RoleSelectionPage({ } logger.error("Error selecting role:", err) setIsSelectingRole(false) // Reset loading state on error + setSelectedCardId(null) } } @@ -344,68 +349,85 @@ export default function RoleSelectionPage({
{roleComponentProps.rolesWithAccess - .map((roleCardProps: RolesWithAccessProps) => ( - handleCardKeyDown(e, roleCardProps)} - onClick={isSelectingRole ? undefined : (e) => handleCardClick(e, roleCardProps)} - style={{ - cursor: isSelectingRole ? "not-allowed" : "pointer", - opacity: isSelectingRole ? 0.5 : 1 - }} - role="button" - aria-disabled={isSelectingRole} - > - -
-
- - e.preventDefault() : - (e) => handleCardClick(e, roleCardProps) - } - onKeyDown={isSelectingRole ? undefined : (e) => handleCardKeyDown(e, roleCardProps)} - style={{ - textDecoration: "none", - color: "inherit", - pointerEvents: isSelectingRole ? "none" : "auto" - }} - tabIndex={isSelectingRole ? -1 : 0} - aria-disabled={isSelectingRole} - role="button" - > - {roleCardProps.role.org_name || noOrgName} -
+ .map((roleCardProps: RolesWithAccessProps) => { + const isThisCardSelected = selectedCardId === roleCardProps.uuid + const isOtherCardDisabled = isSelectingRole && !isThisCardSelected + + return ( + 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} + > + +
+ -
- - {(roleCardProps.role.site_address || contentText.noAddress) - .split("\n") - .map((line: string, index: number) => ( - - {line} -
-
- ))} -
+ + + + {roleCardProps.role.role_name || noRoleName} + +
+
+ + {(roleCardProps.role.site_address || contentText.noAddress) + .split("\n") + .map((line: string, index: number) => ( + + {line} +
+
+ ))} +
+
-
- - - ))} + + + ) + })}
)} diff --git a/packages/cpt-ui/src/constants/ui-strings/YourSelectedRoleStrings.ts b/packages/cpt-ui/src/constants/ui-strings/YourSelectedRoleStrings.ts index 09d9a4c167..b6cf55c56d 100644 --- a/packages/cpt-ui/src/constants/ui-strings/YourSelectedRoleStrings.ts +++ b/packages/cpt-ui/src/constants/ui-strings/YourSelectedRoleStrings.ts @@ -2,7 +2,7 @@ export const YOUR_SELECTED_ROLE_STRINGS = { pageTitle: "Role confirmed - Prescription Tracker", heading: "Your selected role", subheading: "The following role is now active.", - tableTitle: "Current role Details", + tableTitle: "Current role details", roleLabel: "Role", orgLabel: "Organisation", changeLinkText: "Change", diff --git a/packages/cpt-ui/src/styles/roleselectionpage.scss b/packages/cpt-ui/src/styles/roleselectionpage.scss new file mode 100644 index 0000000000..5887f1f2f9 --- /dev/null +++ b/packages/cpt-ui/src/styles/roleselectionpage.scss @@ -0,0 +1,15 @@ +.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; +} From 7c7f44001cc9cd8d188251d1176aec6dea948377 Mon Sep 17 00:00:00 2001 From: Jonno Date: Thu, 19 Mar 2026 16:12:03 +0000 Subject: [PATCH 3/4] selected card click styling --- .../cpt-ui/src/styles/roleselectionpage.scss | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/cpt-ui/src/styles/roleselectionpage.scss b/packages/cpt-ui/src/styles/roleselectionpage.scss index 5887f1f2f9..71151566d7 100644 --- a/packages/cpt-ui/src/styles/roleselectionpage.scss +++ b/packages/cpt-ui/src/styles/roleselectionpage.scss @@ -13,3 +13,20 @@ 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; +} From 2b15528e9e7175fac8a5b9b4020fe33698a36f2f Mon Sep 17 00:00:00 2001 From: Jonno Date: Fri, 20 Mar 2026 17:00:12 +0000 Subject: [PATCH 4/4] changed file import --- packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx b/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx index 659169e911..7c10a0a72d 100644 --- a/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx +++ b/packages/cpt-ui/src/components/EpsRoleSelectionPage.tsx @@ -11,7 +11,7 @@ import { Card } from "nhsuk-react-components" -import "@/styles/roleselectionpage.scss" +import "../styles/roleselectionpage.scss" import {useAuth} from "@/context/AuthProvider" import {RoleDetails} from "@cpt-ui-common/common-types"