diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json
index db761fa6607..7694fb5e639 100644
--- a/client/i18n/locales/english/translations.json
+++ b/client/i18n/locales/english/translations.json
@@ -510,7 +510,8 @@
"step": "Step",
"steps": "Steps",
"steps-for": "Steps for {{blockTitle}}",
- "code-example": "{{codeName}} code example"
+ "code-example": "{{codeName}} code example",
+ "opens-new-window": "Opens in new window"
},
"flash": {
"honest-first": "To claim a certification, you must first accept our academic honesty policy",
diff --git a/client/src/client-only-routes/show-project-links.tsx b/client/src/client-only-routes/show-project-links.tsx
index 30df99a8843..94d8a603759 100644
--- a/client/src/client-only-routes/show-project-links.tsx
+++ b/client/src/client-only-routes/show-project-links.tsx
@@ -83,6 +83,7 @@ const ShowProjectLinks = (props: ShowProjectLinksProps): JSX.Element => {
', () => {
it('Render button when only solution is present', () => {
// @ts-expect-error
render(, store);
- const showViewButton = screen.getByRole('link', { name: 'buttons.view' });
+ const showViewButton = screen.getByRole('link', {
+ name: 'buttons.view settings.labels.solution-for (aria.opens-new-window)'
+ });
expect(showViewButton).toHaveAttribute(
'href',
'https://github.com/freeCodeCamp/freeCodeCamp'
@@ -84,7 +86,9 @@ describe('', () => {
// @ts-expect-error
render(, store);
- const viewButtons = screen.getAllByRole('button', { name: 'buttons.view' });
+ const viewButtons = screen.getAllByRole('button', {
+ name: 'buttons.view settings.labels.solution-for'
+ });
viewButtons.forEach(button => {
expect(button).toBeInTheDocument();
});
diff --git a/client/src/components/profile/components/time-line.tsx b/client/src/components/profile/components/time-line.tsx
index 9dcdb48934b..b9c19a0d426 100644
--- a/client/src/components/profile/components/time-line.tsx
+++ b/client/src/components/profile/components/time-line.tsx
@@ -103,12 +103,15 @@ function TimelineInner({
function renderViewButton(
completedChallenge: CompletedChallenge
): React.ReactNode {
+ const { id } = completedChallenge;
+ const projectTitle = idToNameMap.get(id)?.challengeTitle || '';
return (
viewSolution(completedChallenge)}
showProjectPreview={() => viewProject(completedChallenge)}
- displayContext={'timeline'}
+ displayContext='timeline'
>
);
}
diff --git a/client/src/components/settings/certification.js b/client/src/components/settings/certification.js
index a44724bdf09..2e81f4cf4be 100644
--- a/client/src/components/settings/certification.js
+++ b/client/src/components/settings/certification.js
@@ -213,9 +213,10 @@ export class CertificationSettings extends Component {
);
};
@@ -285,7 +286,8 @@ export class CertificationSettings extends Component {
data-cy={`btn-for-${certSlug}`}
onClick={createClickHandler(certSlug)}
>
- {isCert ? t('buttons.show-cert') : t('buttons.claim-cert')}
+ {isCert ? t('buttons.show-cert') : t('buttons.claim-cert')}{' '}
+ {certName}
diff --git a/client/src/components/settings/certification.test.tsx b/client/src/components/settings/certification.test.tsx
index de7969d34a6..195b64dd920 100644
--- a/client/src/components/settings/certification.test.tsx
+++ b/client/src/components/settings/certification.test.tsx
@@ -20,7 +20,7 @@ describe('', () => {
expect(
screen.getByRole('link', {
- name: 'buttons.show-cert'
+ name: /^buttons.show-cert\s+\S+/
})
).toHaveAttribute(
'href',
@@ -33,7 +33,7 @@ describe('', () => {
renderWithRedux();
const allClaimedCerts = screen.getAllByRole('link', {
- name: 'buttons.show-cert'
+ name: /^buttons.show-cert\s+\S+/
});
allClaimedCerts.forEach(cert => {
@@ -49,7 +49,7 @@ describe('', () => {
renderWithRedux();
const allClaimedCerts = screen.getAllByRole('link', {
- name: 'buttons.show-cert'
+ name: /^buttons.show-cert\s+\S+/
});
allClaimedCerts.forEach(cert => {
@@ -65,7 +65,7 @@ describe('', () => {
expect(
screen.getByRole('link', {
- name: 'buttons.view'
+ name: 'buttons.view settings.labels.solution-for (aria.opens-new-window)'
})
).toHaveAttribute('href', 'https://github.com/freeCodeCamp/freeCodeCamp');
});
diff --git a/client/src/components/solution-display-widget/index.tsx b/client/src/components/solution-display-widget/index.tsx
index 4ba1a5aa581..50694807034 100644
--- a/client/src/components/solution-display-widget/index.tsx
+++ b/client/src/components/solution-display-widget/index.tsx
@@ -1,17 +1,15 @@
import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import {
- Button,
- DropdownButton,
- MenuItem
-} from '@freecodecamp/react-bootstrap';
+import { Button, Dropdown, MenuItem } from '@freecodecamp/react-bootstrap';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { CompletedChallenge } from '../../redux/prop-types';
import { getSolutionDisplayType } from '../../utils/solution-display-type';
+import './solution-display-widget.css';
interface Props {
completedChallenge: CompletedChallenge;
dataCy?: string;
+ projectTitle: string;
showUserCode: () => void;
showProjectPreview?: () => void;
displayContext: 'timeline' | 'settings' | 'certification';
@@ -20,6 +18,7 @@ interface Props {
export function SolutionDisplayWidget({
completedChallenge,
dataCy,
+ projectTitle,
showUserCode,
showProjectPreview,
displayContext
@@ -29,37 +28,48 @@ export function SolutionDisplayWidget({
const viewText = t('buttons.view');
const viewCode = t('buttons.view-code');
const viewProject = t('buttons.view-project');
-
+ // We need to add a random number for dropdown button id's since there may be
+ // two dropdowns for the same project on the page.
+ const randomIdSuffix = Math.floor(Math.random() * 1_000_000);
const ShowFilesSolutionForCertification = (
);
const ShowProjectAndGithubLinkForCertification = (
-
-
-
-
+
+
+ {viewText}{' '}
+
+ {t('settings.labels.solution-for', { projectTitle })}
+
+
+
+
+
+
+
);
const ShowProjectLinkForCertification = (
);
const MissingSolutionComponentForCertification = (
@@ -81,57 +96,67 @@ export function SolutionDisplayWidget({
bsStyle='primary'
className='btn-invert'
data-cy={dataCy}
- id={`btn-for-${id}`}
onClick={showUserCode}
>
- {viewText}
+ {viewText}{' '}
+
+ {t('settings.labels.solution-for', { projectTitle })}
+
);
const ShowMultifileProjectSolution = (
-
-
-
-
+
+
+ {viewText}{' '}
+
+ {t('settings.labels.solution-for', { projectTitle })}
+
+
+
+
+
+
+
);
const ShowProjectAndGithubLinks = (
-
-
-
-
+
+
+ {viewText}{' '}
+
+ {t('settings.labels.solution-for', { projectTitle })}
+
+
+
+
+
+
+
);
const ShowProjectLink = (
@@ -140,11 +165,15 @@ export function SolutionDisplayWidget({
bsStyle='primary'
className='btn-invert'
href={solution}
- id={`btn-for-${id}`}
rel='noopener noreferrer'
target='_blank'
>
- {viewText}
+ {viewText}{' '}
+
+ {t('settings.labels.solution-for', { projectTitle })} (
+ {t('aria.opens-new-window')})
+
+
);
const MissingSolutionComponent =
diff --git a/client/src/components/solution-display-widget/solution-display-widget.css b/client/src/components/solution-display-widget/solution-display-widget.css
new file mode 100644
index 00000000000..4bff674e523
--- /dev/null
+++ b/client/src/components/solution-display-widget/solution-display-widget.css
@@ -0,0 +1,3 @@
+.solutions-dropdown a[role='menuitem'] {
+ text-decoration: none;
+}
diff --git a/client/src/templates/Introduction/components/cert-challenge.tsx b/client/src/templates/Introduction/components/cert-challenge.tsx
index 58f57e949ef..f193e852d3e 100644
--- a/client/src/templates/Introduction/components/cert-challenge.tsx
+++ b/client/src/templates/Introduction/components/cert-challenge.tsx
@@ -130,7 +130,8 @@ const CertChallenge = ({
>
{isCertified && userLoaded
? t('buttons.show-cert')
- : t('buttons.go-to-settings')}
+ : t('buttons.go-to-settings')}{' '}
+ {title}
)}