Skip to content

Commit

Permalink
feat: migrate challenge modals (freeCodeCamp#54179)
Browse files Browse the repository at this point in the history
Co-authored-by: Huyen Nguyen <[email protected]>
  • Loading branch information
Sembauke and huyenltnguyen authored Apr 3, 2024
1 parent 5013ba0 commit a39e740
Show file tree
Hide file tree
Showing 22 changed files with 266 additions and 304 deletions.
4 changes: 3 additions & 1 deletion client/i18n/locales/english/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,9 @@
"task": "Task",
"dialogues-and-tasks-for": "Dialogues and tasks for {{blockTitle}}",
"code-example": "{{codeName}} code example",
"opens-new-window": "Opens in new window"
"opens-new-window": "Opens in new window",
"rsa-checkbox": "I have tried the Read-Search-Ask method",
"similar-questions-checkbox": "I have searched for similar questions that have already been answered on the forum"
},
"flash": {
"honest-first": "To claim a certification, you must first agree to our academic honesty policy",
Expand Down
35 changes: 0 additions & 35 deletions client/src/templates/Challenges/components/completion-modal.css
Original file line number Diff line number Diff line change
@@ -1,32 +1,3 @@
.completion-message {
text-align: center;
font-weight: 700;
font-size: 1.5rem;
}

.completion-modal-body {
min-height: 400px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
}

.video-modal-body {
min-height: 400px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding-inline-start: 30px;
}

.completion-challenge-details {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 15px;
}

.completion-success-icon {
width: 200px;
height: 200px;
Expand All @@ -35,11 +6,6 @@
animation: success-icon-animation 150ms linear 100ms forwards;
}

.challenge-success-modal .btn-cta-big {
max-width: 100%;
font-size: 1.3rem;
}

@keyframes success-icon-animation {
100% {
opacity: 1;
Expand All @@ -55,7 +21,6 @@
justify-content: space-between;
width: 100%;
gap: 0.5rem;
max-width: 500px;
}

.completion-block-name {
Expand Down
37 changes: 12 additions & 25 deletions client/src/templates/Challenges/components/completion-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { Modal } from '@freecodecamp/react-bootstrap';
import { noop } from 'lodash-es';
import React, { Component } from 'react';
import type { TFunction } from 'i18next';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Button } from '@freecodecamp/ui';
import { Button, Modal } from '@freecodecamp/ui';

import Login from '../../../components/Header/components/login';
import {
Expand Down Expand Up @@ -168,30 +166,19 @@ class CompletionModal extends Component<

return (
<Modal
data-cy='completion-modal'
animation={false}
bsSize='lg'
dialogClassName='challenge-success-modal'
keyboard={true}
onHide={close}
onClose={close}
open={!!isOpen}
size='large'
// eslint-disable-next-line @typescript-eslint/unbound-method
onKeyDown={isOpen ? this.handleKeypress : noop}
show={isOpen}
onKeyDown={isOpen ? this.handleKeypress : undefined}
>
<Modal.Header
className='challenge-list-header fcc-modal'
closeButton={true}
>
<Modal.Title className='completion-message'>{message}</Modal.Title>
</Modal.Header>
<Modal.Body className='completion-modal-body'>
<div className='completion-challenge-details'>
<GreenPass
className='completion-success-icon'
data-testid='fcc-completion-success-icon'
data-playwright-test-label='completion-success-icon'
/>
</div>
<Modal.Header closeButtonClassNames='close'>{message}</Modal.Header>
<Modal.Body>
<GreenPass
className='completion-success-icon'
data-testid='fcc-completion-success-icon'
data-playwright-test-label='completion-success-icon'
/>
<div className='completion-block-details'>
<Progress />
</div>
Expand Down
8 changes: 2 additions & 6 deletions client/src/templates/Challenges/components/help-modal.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,12 @@
font-size: 18px;
}

.checkbox {
.checkbox-container {
display: flex;
flex-direction: row;
width: 100%;
text-align: left;
}

.checkbox-text {
margin-inline-start: 10px;
}

@media screen and (max-width: 767px) {
.help-modal .btn-lg {
font-size: 16px;
Expand Down
79 changes: 40 additions & 39 deletions client/src/templates/Challenges/components/help-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Modal } from '@freecodecamp/react-bootstrap';
import { Button, FormControl } from '@freecodecamp/ui';
import { Button, FormControl, Modal } from '@freecodecamp/ui';
import React, { useMemo, useState, useRef, useEffect } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
Expand Down Expand Up @@ -47,34 +46,45 @@ const generateSearchLink = (title: string, block: string) => {

interface CheckboxProps {
name: string;
i18nkey: string;
i18nKey: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
value: boolean;
href: string;
label: string;
}

function Checkbox({ name, i18nkey, onChange, value, href }: CheckboxProps) {
function Checkbox({
name,
i18nKey,
onChange,
value,
href,
label
}: CheckboxProps) {
const { t } = useTranslation();

return (
<div className='checkbox'>
<label>
<input
name={name}
type='checkbox'
onChange={onChange}
checked={value}
required
/>
<span className='checkbox-text'>
<Trans i18nKey={i18nkey}>
<a href={href} rel='noopener noreferrer' target='_blank'>
placeholder
<span className='sr-only'>{t('aria.opens-new-window')}</span>
</a>
</Trans>
</span>
</label>
<div className='checkbox-container'>
<input
id={name}
name={name}
type='checkbox'
onChange={onChange}
checked={value}
required
// Instead of reusing the `i18nKey`, use a plain text version for label
// as input label should not contain interactive elements
aria-label={t(label)}
/>

<span>
<Trans i18nKey={i18nKey}>
<a href={href} rel='noopener noreferrer' target='_blank'>
placeholder
<span className='sr-only'>{t('aria.opens-new-window')}</span>
</a>
</Trans>
</span>
</div>
);
}
Expand Down Expand Up @@ -138,22 +148,11 @@ function HelpModal({
callGA({ event: 'pageview', pagePath: '/help-modal' });
}
return (
<Modal
dialogClassName='help-modal'
onHide={handleClose}
show={isOpen}
aria-labelledby='ask-for-help-modal'
>
<Modal.Header
className='help-modal-header fcc-modal'
closeButton={true}
closeLabel={t('buttons.close')}
>
<Modal.Title id='ask-for-help-modal' className='text-center'>
{t('buttons.ask-for-help')}
</Modal.Title>
<Modal onClose={handleClose} open={!!isOpen}>
<Modal.Header closeButtonClassNames='close'>
{t('buttons.ask-for-help')}
</Modal.Header>
<Modal.Body className='text-center'>
<Modal.Body>
{showHelpForm ? (
<form onSubmit={handleSubmit} ref={formRef}>
<fieldset>
Expand All @@ -163,7 +162,8 @@ function HelpModal({

<Checkbox
name='read-search-ask-checkbox'
i18nkey='learn.read-search-ask-checkbox'
i18nKey='learn.read-search-ask-checkbox'
label='aria.rsa-checkbox'
onChange={event => setReadSearchCheckbox(event.target.checked)}
value={readSearchCheckbox}
href={RSA}
Expand All @@ -173,7 +173,8 @@ function HelpModal({

<Checkbox
name='similar-questions-checkbox'
i18nkey='learn.similar-questions-checkbox'
i18nKey='learn.similar-questions-checkbox'
label='aria.similar-questions-checkbox'
onChange={event =>
setSimilarQuestionsCheckbox(event.target.checked)
}
Expand Down
58 changes: 46 additions & 12 deletions client/src/templates/Challenges/components/hotkeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,39 @@ import {
import {
canFocusEditorSelector,
challengeFilesSelector,
challengeTestsSelector
challengeTestsSelector,
isHelpModalOpenSelector,
isProjectPreviewModalOpenSelector,
isResetModalOpenSelector,
isShortcutsModalOpenSelector
} from '../redux/selectors';
import './hotkeys.css';
import { isProjectBased } from '../../../utils/curriculum-layout';
import type { EditorProps } from '../classic/editor';

const mapStateToProps = createSelector(
isHelpModalOpenSelector,
isResetModalOpenSelector,
isShortcutsModalOpenSelector,
isProjectPreviewModalOpenSelector,
canFocusEditorSelector,
challengeFilesSelector,
challengeTestsSelector,
userSelector,
(
isHelpModalOpen: boolean,
isResetModalOpen: boolean,
isShortcutsModalOpen: boolean,
isProjectPreviewModalOpen: boolean,
canFocusEditor: boolean,
challengeFiles: ChallengeFiles,
tests: Test[],
user: User
) => ({
isHelpModalOpen,
isResetModalOpen,
isShortcutsModalOpen,
isProjectPreviewModalOpen,
canFocusEditor,
challengeFiles,
tests,
Expand All @@ -51,16 +67,6 @@ const mapDispatchToProps = {
setIsAdvancing
};

const keyMap = {
navigationMode: 'escape',
executeChallenge: ['ctrl+enter', 'command+enter'],
focusEditor: 'e',
focusInstructionsPanel: 'r',
navigatePrev: ['p'],
navigateNext: ['n'],
showShortcuts: 'shift+/'
};

export type HotkeysProps = Pick<
ChallengeMeta,
'nextChallengePath' | 'prevChallengePath'
Expand All @@ -79,6 +85,10 @@ export type HotkeysProps = Pick<
| 'submitChallenge'
| 'setEditorFocusability'
> & {
isHelpModalOpen?: boolean;
isResetModalOpen?: boolean;
isShortcutsModalOpen?: boolean;
isProjectPreviewModalOpen?: boolean;
canFocusEditor: boolean;
children: React.ReactElement;
instructionsPanelRef?: React.RefObject<HTMLElement>;
Expand All @@ -104,8 +114,32 @@ function Hotkeys({
tests,
usesMultifileEditor,
openShortcutsModal,
user: { keyboardShortcuts }
user: { keyboardShortcuts },
isHelpModalOpen,
isResetModalOpen,
isShortcutsModalOpen,
isProjectPreviewModalOpen
}: HotkeysProps): JSX.Element {
const isModalOpen = [
isHelpModalOpen,
isResetModalOpen,
isShortcutsModalOpen,
isProjectPreviewModalOpen
].some(Boolean);

const keyMap = {
// The Modal component needs to listen to the 'Escape' keypress event
// in order to close itself when the key is press.
// Therefore, we don't want HotKeys to hijack the 'escape' event when a modal is open.
navigationMode: isModalOpen ? '' : 'escape',
executeChallenge: ['ctrl+enter', 'command+enter'],
focusEditor: 'e',
focusInstructionsPanel: 'r',
navigatePrev: ['p'],
navigateNext: ['n'],
showShortcuts: 'shift+/'
};

const handlers = {
executeChallenge: (keyEvent?: KeyboardEvent) => {
// the 'enter' part of 'ctrl+enter' stops HotKeys from listening, so it
Expand Down
Loading

0 comments on commit a39e740

Please sign in to comment.