diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 5354f2bef2f..7eb4d1e6b84 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -73,7 +73,9 @@ "click-start-course": "Start the course", "click-start-project": "Start the project", "change-language": "Change Language", - "cancel-change": "Cancel Change" + "cancel-change": "Cancel Change", + "resume-project": "Resume project", + "start-project": "Start project" }, "landing": { "big-heading-1": "Learn to code — for free.", @@ -321,7 +323,8 @@ "sorry-keep-trying":"Sorry, your code does not pass. Keep trying.", "sorry-getting-there":"Sorry, your code does not pass. You're getting there.", "sorry-hang-in-there":"Sorry, your code does not pass. Hang in there.", - "sorry-dont-giveup":"Sorry, your code does not pass. Don't give up." + "sorry-dont-giveup":"Sorry, your code does not pass. Don't give up.", + "challenges-completed": "{{completedCount}} of {{totalChallenges}} challenges completed" }, "donate": { "title": "Support our nonprofit", @@ -482,7 +485,10 @@ "primary-nav": "primary", "breadcrumb-nav": "breadcrumb", "submit": "Use Ctrl + Enter to submit.", - "running-tests": "Running tests" + "running-tests": "Running tests", + "step": "Step", + "steps": "Steps", + "steps-for": "Steps for {{blockTitle}}" }, "flash": { "honest-first": "To claim a certification, you must first accept our academic honesty policy", diff --git a/client/src/templates/Introduction/components/block.tsx b/client/src/templates/Introduction/components/block.tsx index 59ea98910e2..84cbbf1066a 100644 --- a/client/src/templates/Introduction/components/block.tsx +++ b/client/src/templates/Introduction/components/block.tsx @@ -193,12 +193,23 @@ export class Block extends Component { }} > -

- {`${isExpanded ? collapseText : expandText}`} -

+
+ {`${isExpanded ? collapseText : expandText}`}{' '} + {blockTitle} +
{this.renderCheckMark(isBlockCompleted)} - {`${completedCount}/${challengesWithCompleted.length}`} + + + ,{' '} + {t('learn.challenges-completed', { + completedCount, + totalChallenges: challengesWithCompleted.length + })} +
{isExpanded && ( @@ -268,7 +279,7 @@ export class Block extends Component { {this.renderCheckMark(isBlockCompleted)} - {blockTitle}{' '} + {blockTitle} , {courseCompletionStatus()} @@ -293,13 +304,12 @@ export class Block extends Component { {isExpanded && this.renderBlockIntros(blockIntroArr)} {isExpanded && ( - <> - - + )} diff --git a/client/src/templates/Introduction/components/challenges.tsx b/client/src/templates/Introduction/components/challenges.tsx index 1f4726ec09d..f34d97b4c3f 100644 --- a/client/src/templates/Introduction/components/challenges.tsx +++ b/client/src/templates/Introduction/components/challenges.tsx @@ -1,6 +1,6 @@ import { Link } from 'gatsby'; import React from 'react'; -import { withTranslation } from 'react-i18next'; +import { withTranslation, useTranslation } from 'react-i18next'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import type { Dispatch } from 'redux'; @@ -21,6 +21,7 @@ interface Challenges { executeGA: (payload: ExecuteGaArg) => void; isProjectBlock: boolean; superBlock: SuperBlocks; + blockTitle?: string | null; } const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' }; @@ -29,8 +30,10 @@ function Challenges({ challengesWithCompleted, executeGA, isProjectBlock, - superBlock + superBlock, + blockTitle }: Challenges): JSX.Element { + const { t } = useTranslation(); const handleChallengeClick = (slug: string) => executeGA({ type: 'event', @@ -49,40 +52,78 @@ function Challenges({ const isGridMap = isNewRespCert(superBlock) || isNewJsCert(superBlock); + const firstIncompleteChallenge = challengesWithCompleted.find( + challenge => !challenge.isCompleted + ); + + const isChallengeStarted = !!challengesWithCompleted.find( + challenge => challenge.isCompleted + ); + return isGridMap ? ( -
    - {challengesWithCompleted.map((challenge, i) => ( -
  • - {!isProjectBlock ? ( - handleChallengeClick(challenge.fields.slug)} - to={challenge.fields.slug} - className={`map-grid-item ${ - challenge.isCompleted ? 'challenge-completed' : '' + <> + {firstIncompleteChallenge && ( +
    + + handleChallengeClick(firstIncompleteChallenge.fields.slug) + } + to={firstIncompleteChallenge.fields.slug} + > + {!isChallengeStarted + ? t('buttons.start-project') + : t('buttons.resume-project')}{' '} + {blockTitle && {blockTitle}} + +
    + )} +
  • + ))} +
+ + ) : (
    {challengesWithCompleted.map(challenge => ( diff --git a/client/src/templates/Introduction/intro.css b/client/src/templates/Introduction/intro.css index 413f5c2424c..059b855be57 100644 --- a/client/src/templates/Introduction/intro.css +++ b/client/src/templates/Introduction/intro.css @@ -44,11 +44,23 @@ a.cert-tag:active { } .block-grid-title { - font-size: 1.2rem; margin: 0; overflow-wrap: break-word; } +.block-grid .challenge-jump-link { + margin: 0 25px 25px; +} + +.block-grid .challenge-jump-link a:focus { + outline: 3px solid var(--blue-mid); + outline-offset: 0; +} + +.block-grid .challenge-jump-link a:focus:not(:focus-visible) { + outline: none; +} + .block-title-translation-cta { white-space: nowrap; padding: 0.2em 0.5em; @@ -234,11 +246,30 @@ button.map-title { .map-challenge-title a { width: 100%; padding: 10px 15px; + align-items: center; +} + +.map-challenges-grid .map-challenge-title a { + width: 3.4rem; + height: 2.75rem; + min-width: 24px; + min-height: 24px; + padding: 0; +} + +.map-challenges-grid .map-challenge-title a:focus { + outline: 3px solid var(--secondary-color); + outline-offset: -3px; +} + +.map-challenges-grid .map-challenge-title a:focus:not(:focus-visible) { + outline: none; } .map-challenges-grid { display: flex; flex-wrap: wrap; + margin-top: 1rem; } .map-challenge-title-grid { flex: 0 1 60px; @@ -261,6 +292,13 @@ button.map-title { .challenge-completed { background: var(--highlight-background); + position: relative; +} + +.challenge-completed span { + font-weight: 700; + font-size: 1.3rem; + margin-top: -0.05rem; } @media screen and (max-width: 500px) { @@ -346,7 +384,7 @@ button.map-title { } .block-grid { - border-bottom: 3px solid var(--secondary-background); + border-bottom: 4px solid var(--secondary-background); } .block-grid .block-header { @@ -366,7 +404,7 @@ button.map-title { } .block-grid .block-header[aria-expanded='true'] { - border-bottom: 3px solid var(--secondary-background); + border-bottom: 2px solid var(--secondary-background); } .block-header-button-text {