feat(client): ts-migrate multiple files (#43262)
* feat(client): ts-migrate rename files * feat(client): ts-migrate client/src/templates/Introduction/* * feat(client): ts-migrate client/src/components/formHelpers/form* * fix: import * Update client/src/components/formHelpers/form-validators.tsx Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * Update client/src/components/formHelpers/form-fields.tsx Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * Update client/src/components/formHelpers/form-fields.tsx Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * fix: types in client/src/components/formHelpers/index.tsx * fix: types in client/src/templates/Introduction/super-block-intro.tsx * fix: types in client/src/components/formHelpers/* * fix: signInLoading and value types * Update client/src/templates/Introduction/super-block-intro.tsx Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * Update client/src/components/formHelpers/index.tsx Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * Update client/src/components/formHelpers/index.tsx Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * Update client/src/components/formHelpers/index.tsx Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * chore(deps): update dependency rollup to v2.58.1 * fix: rename variables and fix imports for ts-migrate * fix: remove `Type` suffix from the type definition. * Update client/src/components/formHelpers/form.tsx Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Renovate Bot <bot@renovateapp.com>
This commit is contained in:
committed by
GitHub
parent
001aa3ea9e
commit
9abc5f66ba
@ -40,7 +40,7 @@ const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) =>
|
||||
);
|
||||
|
||||
type DonateModalProps = {
|
||||
activeDonors: number;
|
||||
activeDonors?: number;
|
||||
closeDonationModal: typeof closeDonationModal;
|
||||
executeGA: typeof executeGA;
|
||||
location: WindowLocation | undefined;
|
||||
|
@ -1,75 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Form } from 'react-final-form';
|
||||
|
||||
import {
|
||||
FormFields,
|
||||
BlockSaveButton,
|
||||
BlockSaveWrapper,
|
||||
formatUrlValues
|
||||
} from './';
|
||||
|
||||
const propTypes = {
|
||||
buttonText: PropTypes.string,
|
||||
enableSubmit: PropTypes.bool,
|
||||
formFields: PropTypes.arrayOf(
|
||||
PropTypes.shape({ name: PropTypes.string, label: PropTypes.string })
|
||||
.isRequired
|
||||
).isRequired,
|
||||
hideButton: PropTypes.bool,
|
||||
id: PropTypes.string.isRequired,
|
||||
initialValues: PropTypes.object,
|
||||
options: PropTypes.shape({
|
||||
ignored: PropTypes.arrayOf(PropTypes.string),
|
||||
isEditorLinkAllowed: PropTypes.bool,
|
||||
required: PropTypes.arrayOf(PropTypes.string),
|
||||
types: PropTypes.objectOf(PropTypes.string),
|
||||
placeholders: PropTypes.shape({
|
||||
solution: PropTypes.string,
|
||||
githubLink: PropTypes.string
|
||||
})
|
||||
}),
|
||||
submit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
function DynamicForm({
|
||||
id,
|
||||
formFields,
|
||||
initialValues,
|
||||
options,
|
||||
submit,
|
||||
buttonText,
|
||||
enableSubmit,
|
||||
hideButton
|
||||
}) {
|
||||
return (
|
||||
<Form
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values, ...args) =>
|
||||
submit(formatUrlValues(values, options), ...args)
|
||||
}
|
||||
>
|
||||
{({ handleSubmit, pristine, error }) => (
|
||||
<form
|
||||
id={`dynamic-${id}`}
|
||||
onSubmit={handleSubmit}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<FormFields formFields={formFields} options={options} />
|
||||
<BlockSaveWrapper>
|
||||
{hideButton ? null : (
|
||||
<BlockSaveButton disabled={(pristine && !enableSubmit) || error}>
|
||||
{buttonText ? buttonText : null}
|
||||
</BlockSaveButton>
|
||||
)}
|
||||
</BlockSaveWrapper>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
DynamicForm.displayName = 'DynamicForm';
|
||||
DynamicForm.propTypes = propTypes;
|
||||
|
||||
export default DynamicForm;
|
@ -1,7 +1,7 @@
|
||||
import { render, fireEvent, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import Form from './Form';
|
||||
import Form from './form';
|
||||
|
||||
const defaultTestProps = {
|
||||
buttonText: 'Submit',
|
||||
|
@ -7,7 +7,7 @@ const style = {
|
||||
function BlockSaveWrapper({
|
||||
children
|
||||
}: {
|
||||
children?: React.ReactElement;
|
||||
children?: React.ReactElement | null;
|
||||
}): JSX.Element {
|
||||
return <div style={style}>{children}</div>;
|
||||
}
|
||||
|
@ -8,35 +8,26 @@ import {
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
import { kebabCase } from 'lodash-es';
|
||||
import normalizeUrl from 'normalize-url';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Field } from 'react-final-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FormOptions } from './form';
|
||||
import {
|
||||
editorValidator,
|
||||
localhostValidator,
|
||||
composeValidators,
|
||||
fCCValidator,
|
||||
httpValidator
|
||||
} from './FormValidators';
|
||||
} from './form-validators';
|
||||
|
||||
const propTypes = {
|
||||
formFields: PropTypes.arrayOf(
|
||||
PropTypes.shape({ name: PropTypes.string, label: PropTypes.string })
|
||||
.isRequired
|
||||
).isRequired,
|
||||
options: PropTypes.shape({
|
||||
ignored: PropTypes.arrayOf(PropTypes.string),
|
||||
isEditorLinkAllowed: PropTypes.bool,
|
||||
placeholders: PropTypes.objectOf(PropTypes.string),
|
||||
required: PropTypes.arrayOf(PropTypes.string),
|
||||
types: PropTypes.objectOf(PropTypes.string)
|
||||
})
|
||||
type FormFieldsProps = {
|
||||
formFields: { name: string; label: string }[];
|
||||
options: FormOptions;
|
||||
};
|
||||
|
||||
function FormFields(props) {
|
||||
function FormFields(props: FormFieldsProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const { formFields, options = {} } = props;
|
||||
const { formFields, options = {} }: FormFieldsProps = props;
|
||||
const {
|
||||
ignored = [],
|
||||
placeholders = {},
|
||||
@ -46,13 +37,18 @@ function FormFields(props) {
|
||||
isLocalLinkAllowed = false
|
||||
} = options;
|
||||
|
||||
const nullOrWarning = (value, error, isURL, name) => {
|
||||
let validationError;
|
||||
const nullOrWarning = (
|
||||
value: string,
|
||||
error: unknown,
|
||||
isURL: boolean,
|
||||
name: string
|
||||
) => {
|
||||
let validationError: string | undefined;
|
||||
if (value && isURL) {
|
||||
try {
|
||||
normalizeUrl(value, { stripWWW: false });
|
||||
} catch (err) {
|
||||
validationError = err.message;
|
||||
} catch (err: unknown) {
|
||||
validationError = (err as { message?: string })?.message;
|
||||
}
|
||||
}
|
||||
const validationWarning = composeValidators(
|
||||
@ -61,7 +57,9 @@ function FormFields(props) {
|
||||
httpValidator,
|
||||
isLocalLinkAllowed ? null : localhostValidator
|
||||
)(value);
|
||||
const message = error || validationError || validationWarning;
|
||||
const message: string = (error ||
|
||||
validationError ||
|
||||
validationWarning) as string;
|
||||
return message ? (
|
||||
<HelpBlock>
|
||||
<Alert
|
||||
@ -78,6 +76,7 @@ function FormFields(props) {
|
||||
{formFields
|
||||
.filter(formField => !ignored.includes(formField.name))
|
||||
.map(({ name, label }) => (
|
||||
// TODO: verify if the value is always a string
|
||||
<Field key={`${kebabCase(name)}-field`} name={name}>
|
||||
{({ input: { value, onChange }, meta: { pristine, error } }) => {
|
||||
const key = kebabCase(name);
|
||||
@ -100,7 +99,7 @@ function FormFields(props) {
|
||||
required={required.includes(name)}
|
||||
rows={4}
|
||||
type={type}
|
||||
value={value}
|
||||
value={value as string}
|
||||
/>
|
||||
{nullOrWarning(value, !pristine && error, isURL, name)}
|
||||
</FormGroup>
|
||||
@ -114,6 +113,5 @@ function FormFields(props) {
|
||||
}
|
||||
|
||||
FormFields.displayName = 'FormFields';
|
||||
FormFields.propTypes = propTypes;
|
||||
|
||||
export default FormFields;
|
@ -9,21 +9,26 @@ const fCCRegex =
|
||||
const localhostRegex = /localhost:/;
|
||||
const httpRegex = /http(?!s|([^s]+?localhost))/;
|
||||
|
||||
export const editorValidator = value =>
|
||||
export const editorValidator = (value: string): React.ReactElement | null =>
|
||||
editorRegex.test(value) ? <Trans>validation.editor-url</Trans> : null;
|
||||
|
||||
export const fCCValidator = value =>
|
||||
export const fCCValidator = (value: string): React.ReactElement | null =>
|
||||
fCCRegex.test(value) ? <Trans>validation.own-work-url</Trans> : null;
|
||||
|
||||
export const localhostValidator = value =>
|
||||
export const localhostValidator = (value: string): React.ReactElement | null =>
|
||||
localhostRegex.test(value) ? (
|
||||
<Trans>validation.publicly-visible-url</Trans>
|
||||
) : null;
|
||||
|
||||
export const httpValidator = value =>
|
||||
export const httpValidator = (value: string): React.ReactElement | null =>
|
||||
httpRegex.test(value) ? <Trans>validation.http-url</Trans> : null;
|
||||
|
||||
export const composeValidators =
|
||||
(...validators) =>
|
||||
value =>
|
||||
validators.reduce((error, validator) => error ?? validator?.(value), null);
|
||||
export type Validator = (value: string) => React.ReactElement | null;
|
||||
export function composeValidators(...validators: (Validator | null)[]) {
|
||||
return (value: string): ReturnType<Validator> | null =>
|
||||
validators.reduce(
|
||||
(error: ReturnType<Validator>, validator) =>
|
||||
error ?? (validator ? validator(value) : null),
|
||||
null
|
||||
);
|
||||
}
|
75
client/src/components/formHelpers/form.tsx
Normal file
75
client/src/components/formHelpers/form.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { Form } from 'react-final-form';
|
||||
|
||||
import {
|
||||
URLValues,
|
||||
ValidatedValues,
|
||||
FormFields,
|
||||
BlockSaveButton,
|
||||
BlockSaveWrapper,
|
||||
formatUrlValues
|
||||
} from '../formHelpers/index';
|
||||
|
||||
export type FormOptions = {
|
||||
ignored?: string[];
|
||||
isEditorLinkAllowed?: boolean;
|
||||
isLocalLinkAllowed?: boolean;
|
||||
required?: string[];
|
||||
types?: { [key: string]: string };
|
||||
placeholders?: { [key: string]: string };
|
||||
};
|
||||
|
||||
type FormProps = {
|
||||
buttonText?: string;
|
||||
enableSubmit?: boolean;
|
||||
formFields: { name: string; label: string }[];
|
||||
hideButton?: boolean;
|
||||
id?: string;
|
||||
initialValues?: Record<string, unknown>;
|
||||
options: FormOptions;
|
||||
submit: (values: ValidatedValues, ...args: unknown[]) => void;
|
||||
};
|
||||
|
||||
function DynamicForm({
|
||||
id,
|
||||
formFields,
|
||||
initialValues,
|
||||
options,
|
||||
submit,
|
||||
buttonText,
|
||||
enableSubmit,
|
||||
hideButton
|
||||
}: FormProps): JSX.Element {
|
||||
return (
|
||||
<Form
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values: URLValues, ...args: unknown[]) => {
|
||||
submit(formatUrlValues(values, options), ...args);
|
||||
}}
|
||||
>
|
||||
{({ handleSubmit, pristine, error }) => (
|
||||
<form
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
id={`dynamic-${id}`}
|
||||
onSubmit={handleSubmit}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<FormFields formFields={formFields} options={options} />
|
||||
<BlockSaveWrapper>
|
||||
{hideButton ? null : (
|
||||
<BlockSaveButton
|
||||
disabled={(pristine && !enableSubmit) || (error as boolean)}
|
||||
>
|
||||
{buttonText ? buttonText : null}
|
||||
</BlockSaveButton>
|
||||
)}
|
||||
</BlockSaveWrapper>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
DynamicForm.displayName = 'DynamicForm';
|
||||
|
||||
export default DynamicForm;
|
@ -1,45 +0,0 @@
|
||||
import normalizeUrl from 'normalize-url';
|
||||
import {
|
||||
localhostValidator,
|
||||
editorValidator,
|
||||
composeValidators,
|
||||
fCCValidator,
|
||||
httpValidator
|
||||
} from './FormValidators';
|
||||
|
||||
export { default as BlockSaveButton } from './block-save-button';
|
||||
export { default as BlockSaveWrapper } from './block-save-wrapper';
|
||||
export { default as Form } from './Form.js';
|
||||
export { default as FormFields } from './FormFields.js';
|
||||
|
||||
const normalizeOptions = {
|
||||
stripWWW: false
|
||||
};
|
||||
|
||||
export function formatUrlValues(values, options) {
|
||||
const { isEditorLinkAllowed, isLocalLinkAllowed, types } = options;
|
||||
const validatedValues = { values: {}, errors: [], invalidValues: [] };
|
||||
const urlValues = Object.keys(values).reduce((result, key) => {
|
||||
let value = values[key];
|
||||
const nullOrWarning = composeValidators(
|
||||
fCCValidator,
|
||||
httpValidator,
|
||||
isLocalLinkAllowed ? null : localhostValidator,
|
||||
key === 'githubLink' || isEditorLinkAllowed ? null : editorValidator
|
||||
)(value);
|
||||
if (nullOrWarning) {
|
||||
validatedValues.invalidValues.push(nullOrWarning);
|
||||
}
|
||||
if (value && types[key] === 'url') {
|
||||
try {
|
||||
value = normalizeUrl(value, normalizeOptions);
|
||||
} catch (err) {
|
||||
// Not a valid URL for testing or submission
|
||||
validatedValues.errors.push({ error: err, value });
|
||||
}
|
||||
}
|
||||
return { ...result, [key]: value };
|
||||
}, {});
|
||||
validatedValues.values = urlValues;
|
||||
return validatedValues;
|
||||
}
|
70
client/src/components/formHelpers/index.tsx
Normal file
70
client/src/components/formHelpers/index.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import normalizeUrl from 'normalize-url';
|
||||
import { FormOptions } from './form';
|
||||
import {
|
||||
localhostValidator,
|
||||
editorValidator,
|
||||
composeValidators,
|
||||
fCCValidator,
|
||||
httpValidator
|
||||
} from './form-validators';
|
||||
|
||||
export { default as BlockSaveButton } from './block-save-button';
|
||||
export { default as BlockSaveWrapper } from './block-save-wrapper';
|
||||
export { default as Form } from './form';
|
||||
export { default as FormFields } from './form-fields';
|
||||
|
||||
const normalizeOptions = {
|
||||
stripWWW: false
|
||||
};
|
||||
|
||||
export type URLValues = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type ValidationError = {
|
||||
error: { message?: string };
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ValidatedValues = {
|
||||
values: URLValues;
|
||||
errors: ValidationError[];
|
||||
invalidValues: (JSX.Element | null)[];
|
||||
};
|
||||
|
||||
export function formatUrlValues(
|
||||
values: URLValues,
|
||||
options: FormOptions
|
||||
): ValidatedValues {
|
||||
const { isEditorLinkAllowed, isLocalLinkAllowed, types } = options;
|
||||
const validatedValues: ValidatedValues = {
|
||||
values: {},
|
||||
errors: [],
|
||||
invalidValues: []
|
||||
};
|
||||
const urlValues = Object.keys(values).reduce((result, key: string) => {
|
||||
let value: string = values[key];
|
||||
const nullOrWarning: JSX.Element | null = composeValidators(
|
||||
fCCValidator,
|
||||
httpValidator,
|
||||
isLocalLinkAllowed ? null : localhostValidator,
|
||||
key === 'githubLink' || isEditorLinkAllowed ? null : editorValidator
|
||||
)(value);
|
||||
if (nullOrWarning) {
|
||||
validatedValues.invalidValues.push(nullOrWarning);
|
||||
}
|
||||
if (value && types && types[key] === 'url') {
|
||||
try {
|
||||
value = normalizeUrl(value, normalizeOptions);
|
||||
} catch (err: unknown) {
|
||||
validatedValues.errors.push({
|
||||
error: err as { message?: string },
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
||||
return { ...result, [key]: value };
|
||||
}, {});
|
||||
validatedValues.values = urlValues;
|
||||
return validatedValues;
|
||||
}
|
@ -8,7 +8,7 @@ import {
|
||||
frontEndProject,
|
||||
pythonProject
|
||||
} from '../../../../utils/challenge-types';
|
||||
import { Form } from '../../../components/formHelpers';
|
||||
import { Form, ValidatedValues } from '../../../components/formHelpers';
|
||||
|
||||
interface SubmitProps {
|
||||
showCompletionModal: boolean;
|
||||
@ -21,12 +21,6 @@ interface FormProps extends WithTranslation {
|
||||
updateSolutionForm: (arg0: Record<string, unknown>) => void;
|
||||
}
|
||||
|
||||
interface ValidatedValues {
|
||||
errors: string[];
|
||||
invalidValues: string[];
|
||||
values: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class SolutionForm extends Component<FormProps> {
|
||||
constructor(props: FormProps) {
|
||||
super(props);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Grid, ListGroup, ListGroupItem } from '@freecodecamp/react-bootstrap';
|
||||
import { Link, graphql } from 'gatsby';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -8,18 +7,19 @@ import { useTranslation } from 'react-i18next';
|
||||
import ButtonSpacer from '../../components/helpers/button-spacer';
|
||||
import FullWidthRow from '../../components/helpers/full-width-row';
|
||||
import LearnLayout from '../../components/layouts/learn';
|
||||
import { MarkdownRemark, AllChallengeNode } from '../../redux/prop-types';
|
||||
import {
|
||||
MarkdownRemarkType,
|
||||
AllChallengeNodeType,
|
||||
ChallengeNodeType
|
||||
} from '../../redux/prop-types';
|
||||
|
||||
import './intro.css';
|
||||
|
||||
const propTypes = {
|
||||
data: PropTypes.shape({
|
||||
markdownRemark: MarkdownRemark,
|
||||
allChallengeNode: AllChallengeNode
|
||||
})
|
||||
};
|
||||
|
||||
function renderMenuItems({ edges = [] }) {
|
||||
function renderMenuItems({
|
||||
edges = []
|
||||
}: {
|
||||
edges?: Array<{ node: ChallengeNodeType }>;
|
||||
}) {
|
||||
return edges
|
||||
.map(({ node }) => node)
|
||||
.map(({ title, fields: { slug } }) => (
|
||||
@ -29,7 +29,14 @@ function renderMenuItems({ edges = [] }) {
|
||||
));
|
||||
}
|
||||
|
||||
function IntroductionPage({ data: { markdownRemark, allChallengeNode } }) {
|
||||
function IntroductionPage({
|
||||
data: { markdownRemark, allChallengeNode }
|
||||
}: {
|
||||
data: {
|
||||
markdownRemark: MarkdownRemarkType;
|
||||
allChallengeNode: AllChallengeNodeType;
|
||||
};
|
||||
}): React.FunctionComponentElement<typeof LearnLayout> {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
html,
|
||||
@ -78,7 +85,6 @@ function IntroductionPage({ data: { markdownRemark, allChallengeNode } }) {
|
||||
}
|
||||
|
||||
IntroductionPage.displayName = 'IntroductionPage';
|
||||
IntroductionPage.propTypes = propTypes;
|
||||
|
||||
export default IntroductionPage;
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
|
||||
import { WindowLocation } from '@reach/router';
|
||||
import { graphql } from 'gatsby';
|
||||
import { uniq } from 'lodash-es';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment, useEffect, memo } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { TFunction, withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { configureAnchors } from 'react-scrollable-anchor';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import DonateModal from '../../../../client/src/components/Donation/DonationModal';
|
||||
import DonateModal from '../../components/Donation/DonationModal';
|
||||
import Login from '../../components/Header/components/Login';
|
||||
import Map from '../../components/Map';
|
||||
import { Spacer } from '../../components/helpers';
|
||||
@ -22,7 +22,11 @@ import {
|
||||
tryToShowDonationModal,
|
||||
userSelector
|
||||
} from '../../redux';
|
||||
import { MarkdownRemark, AllChallengeNode, User } from '../../redux/prop-types';
|
||||
import {
|
||||
MarkdownRemarkType,
|
||||
AllChallengeNodeType,
|
||||
UserType
|
||||
} from '../../redux/prop-types';
|
||||
import Block from './components/Block';
|
||||
import CertChallenge from './components/CertChallenge';
|
||||
import SuperBlockIntro from './components/SuperBlockIntro';
|
||||
@ -30,44 +34,48 @@ import { resetExpansion, toggleBlock } from './redux';
|
||||
|
||||
import './intro.css';
|
||||
|
||||
const propTypes = {
|
||||
currentChallengeId: PropTypes.string,
|
||||
data: PropTypes.shape({
|
||||
markdownRemark: MarkdownRemark,
|
||||
allChallengeNode: AllChallengeNode
|
||||
}),
|
||||
expandedState: PropTypes.object,
|
||||
fetchState: PropTypes.shape({
|
||||
pending: PropTypes.bool,
|
||||
complete: PropTypes.bool,
|
||||
errored: PropTypes.bool
|
||||
}),
|
||||
isSignedIn: PropTypes.bool,
|
||||
location: PropTypes.shape({
|
||||
hash: PropTypes.string,
|
||||
// TODO: state is sometimes a string
|
||||
state: PropTypes.shape({
|
||||
breadcrumbBlockClick: PropTypes.string
|
||||
})
|
||||
}),
|
||||
resetExpansion: PropTypes.func,
|
||||
signInLoading: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
toggleBlock: PropTypes.func,
|
||||
tryToShowDonationModal: PropTypes.func.isRequired,
|
||||
user: User
|
||||
type FetchState = {
|
||||
pending: boolean;
|
||||
complete: boolean;
|
||||
errored: boolean;
|
||||
};
|
||||
|
||||
type SuperBlockProp = {
|
||||
currentChallengeId: string;
|
||||
data: {
|
||||
markdownRemark: MarkdownRemarkType;
|
||||
allChallengeNode: AllChallengeNodeType;
|
||||
};
|
||||
expandedState: {
|
||||
[key: string]: boolean;
|
||||
};
|
||||
fetchState: FetchState;
|
||||
isSignedIn: boolean;
|
||||
signInLoading: boolean;
|
||||
location: WindowLocation<{ breadcrumbBlockClick: string }>;
|
||||
resetExpansion: () => void;
|
||||
t: TFunction;
|
||||
toggleBlock: (arg0: string) => void;
|
||||
tryToShowDonationModal: () => void;
|
||||
user: UserType;
|
||||
};
|
||||
|
||||
configureAnchors({ offset: -40, scrollDuration: 0 });
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state: unknown) => {
|
||||
return createSelector(
|
||||
currentChallengeIdSelector,
|
||||
isSignedInSelector,
|
||||
signInLoadingSelector,
|
||||
userFetchStateSelector,
|
||||
userSelector,
|
||||
(currentChallengeId, isSignedIn, signInLoading, fetchState, user) => ({
|
||||
(
|
||||
currentChallengeId: string,
|
||||
isSignedIn,
|
||||
signInLoading: boolean,
|
||||
fetchState: FetchState,
|
||||
user: UserType
|
||||
) => ({
|
||||
currentChallengeId,
|
||||
isSignedIn,
|
||||
signInLoading,
|
||||
@ -77,7 +85,7 @@ const mapStateToProps = state => {
|
||||
)(state);
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||
bindActionCreators(
|
||||
{
|
||||
tryToShowDonationModal,
|
||||
@ -87,7 +95,7 @@ const mapDispatchToProps = dispatch =>
|
||||
dispatch
|
||||
);
|
||||
|
||||
const SuperBlockIntroductionPage = props => {
|
||||
const SuperBlockIntroductionPage = (props: SuperBlockProp) => {
|
||||
useEffect(() => {
|
||||
initializeExpandedState();
|
||||
props.tryToShowDonationModal();
|
||||
@ -102,7 +110,7 @@ const SuperBlockIntroductionPage = props => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const getChosenBlock = () => {
|
||||
const getChosenBlock = (): string => {
|
||||
const {
|
||||
data: {
|
||||
allChallengeNode: { edges }
|
||||
@ -110,10 +118,14 @@ const SuperBlockIntroductionPage = props => {
|
||||
isSignedIn,
|
||||
currentChallengeId,
|
||||
location
|
||||
} = props;
|
||||
}: SuperBlockProp = props;
|
||||
|
||||
// if coming from breadcrumb click
|
||||
if (location.state && location.state.breadcrumbBlockClick) {
|
||||
if (
|
||||
location.state &&
|
||||
typeof location.state === 'object' &&
|
||||
location.state.hasOwnProperty('breadcrumbBlockClick')
|
||||
) {
|
||||
return location.state.breadcrumbBlockClick;
|
||||
}
|
||||
|
||||
@ -123,7 +135,7 @@ const SuperBlockIntroductionPage = props => {
|
||||
return dashedBlock;
|
||||
}
|
||||
|
||||
let edge = edges[0];
|
||||
const edge = edges[0];
|
||||
|
||||
if (isSignedIn) {
|
||||
// see if currentChallenge is in this superBlock
|
||||
@ -232,7 +244,6 @@ const SuperBlockIntroductionPage = props => {
|
||||
};
|
||||
|
||||
SuperBlockIntroductionPage.displayName = 'SuperBlockIntroductionPage';
|
||||
SuperBlockIntroductionPage.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
@ -21,11 +21,11 @@ const codeally = path.resolve(
|
||||
);
|
||||
const intro = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Introduction/Intro.js'
|
||||
'../../src/templates/Introduction/intro.tsx'
|
||||
);
|
||||
const superBlockIntro = path.resolve(
|
||||
__dirname,
|
||||
'../../src/templates/Introduction/SuperBlockIntro.js'
|
||||
'../../src/templates/Introduction/super-block-intro.tsx'
|
||||
);
|
||||
const video = path.resolve(
|
||||
__dirname,
|
||||
|
Reference in New Issue
Block a user