From aa292884fe508d92c5de9644da2cebcbf97cb379 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 4 May 2026 16:18:44 +1000 Subject: [PATCH] PM-4393: Link project access support email What was broken Unauthorized Work project access messages displayed support@topcoder.com as plain text, so users could not click it to contact support. Root cause The shared Work ErrorMessage component rendered message text as plain content and did not link the support email used by PM-4393 denial copy. What was changed Updated ErrorMessage to convert support@topcoder.com in plain text messages into a standard mailto:support@topcoder.com link while preserving the existing message text and styling. Updated ProjectRouteAccessGuard expectations for the split text/link rendering. Any added/updated tests Added ErrorMessage coverage for the mailto support link and updated ProjectRouteAccessGuard denial tests to assert the support link href. --- .../ErrorMessage/ErrorMessage.module.scss | 5 +++ .../ErrorMessage/ErrorMessage.spec.tsx | 30 +++++++++++++++ .../components/ErrorMessage/ErrorMessage.tsx | 37 +++++++++++++++++-- .../ProjectRouteAccessGuard.spec.tsx | 16 ++++++-- 4 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.spec.tsx diff --git a/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.module.scss b/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.module.scss index 465ddd847..0012e5a2a 100644 --- a/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.module.scss +++ b/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.module.scss @@ -16,4 +16,9 @@ .message { font-size: 14px; margin: 0; + + a { + color: inherit; + text-decoration: underline; + } } diff --git a/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.spec.tsx b/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.spec.tsx new file mode 100644 index 000000000..4ffff7ea9 --- /dev/null +++ b/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.spec.tsx @@ -0,0 +1,30 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { + render, + screen, +} from '@testing-library/react' + +import { ErrorMessage } from './ErrorMessage' + +jest.mock('~/libs/ui', () => ({ + Button: (props: { label: string }) => ( + + ), +}), { + virtual: true, +}) + +describe('ErrorMessage', () => { + it('renders the Topcoder support email as a mailto link', () => { + const message = 'You don’t have access to this project. Please contact support@topcoder.com.' + + render() + + const supportLink = screen.getByRole('link', { name: 'support@topcoder.com' }) + + expect(supportLink.getAttribute('href')) + .toBe('mailto:support@topcoder.com') + expect(supportLink.closest('p')?.textContent) + .toBe(message) + }) +}) diff --git a/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.tsx b/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.tsx index ce554e97f..7453ff98e 100644 --- a/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.tsx +++ b/src/apps/work/src/lib/components/ErrorMessage/ErrorMessage.tsx @@ -1,17 +1,48 @@ -import { FC } from 'react' +import { + FC, + ReactNode, +} from 'react' import { Button } from '~/libs/ui' import styles from './ErrorMessage.module.scss' interface ErrorMessageProps { - message: string + message: ReactNode onRetry?: () => void } +const SUPPORT_EMAIL = 'support@topcoder.com' + +/** + * Renders the supplied error message with the Topcoder support email converted to a mailto link. + * + * @param message error message text or custom React content to display. + * @returns message content with the support email linked when the message is plain text. + * @remarks Used by ErrorMessage so project access denial messages can keep their configured copy while linking support. + * @throws Does not throw. + */ +function renderMessage(message: ReactNode): ReactNode { + if (typeof message !== 'string' || !message.includes(SUPPORT_EMAIL)) { + return message + } + + const emailIndex = message.indexOf(SUPPORT_EMAIL) + + return ( + <> + {message.slice(0, emailIndex)} + + {SUPPORT_EMAIL} + + {message.slice(emailIndex + SUPPORT_EMAIL.length)} + + ) +} + export const ErrorMessage: FC = (props: ErrorMessageProps) => (
-

{props.message}

+

{renderMessage(props.message)}

{props.onRetry ? (