chore(client): fix several type errors (#58500)

This commit is contained in:
Oliver Eyton-Williams
2025-01-31 17:25:43 +01:00
committed by GitHub
parent acfabb69de
commit 1738b1f05f
20 changed files with 147 additions and 99 deletions
@@ -1,11 +1,15 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { User } from '../../../redux/prop-types';
import { Link, AvatarRenderer } from '../../helpers'; import { Link, AvatarRenderer } from '../../helpers';
import Login from './login'; import Login from './login';
interface AuthOrProfileProps { interface AuthOrProfileProps {
user?: User; user?: {
isDonating: boolean;
username: string;
picture: string;
yearsTopContributor: string[];
};
} }
const AuthOrProfile = ({ user }: AuthOrProfileProps): JSX.Element => { const AuthOrProfile = ({ user }: AuthOrProfileProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -2,7 +2,6 @@ import React, { RefObject } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBars } from '@fortawesome/free-solid-svg-icons'; import { faBars } from '@fortawesome/free-solid-svg-icons';
import { User } from '../../../redux/prop-types';
interface MenuButtonProps { interface MenuButtonProps {
className?: string; className?: string;
@@ -10,7 +9,6 @@ interface MenuButtonProps {
innerRef?: RefObject<HTMLButtonElement>; innerRef?: RefObject<HTMLButtonElement>;
showMenu: () => void; showMenu: () => void;
hideMenu: () => void; hideMenu: () => void;
user?: User;
} }
const MenuButton = ({ const MenuButton = ({
@@ -13,14 +13,13 @@ import { openSignoutModal, toggleTheme } from '../../../redux/actions';
import { Link } from '../../helpers'; import { Link } from '../../helpers';
import { LocalStorageThemes } from '../../../redux/types'; import { LocalStorageThemes } from '../../../redux/types';
import { themeSelector } from '../../../redux/selectors'; import { themeSelector } from '../../../redux/selectors';
import { User } from '../../../redux/prop-types';
import SupporterBadge from '../../../assets/icons/supporter-badge'; import SupporterBadge from '../../../assets/icons/supporter-badge';
export interface NavLinksProps { export interface NavLinksProps {
displayMenu: boolean; displayMenu: boolean;
showMenu: () => void; showMenu: () => void;
hideMenu: () => void; hideMenu: () => void;
user?: User; user?: { isDonating: boolean; username: string };
menuButtonRef: React.RefObject<HTMLButtonElement>; menuButtonRef: React.RefObject<HTMLButtonElement>;
openSignoutModal: () => void; openSignoutModal: () => void;
theme: LocalStorageThemes; theme: LocalStorageThemes;
@@ -7,7 +7,7 @@ import { Link, SkeletonSprite } from '../../helpers';
import { SEARCH_EXPOSED_WIDTH } from '../../../../config/misc'; import { SEARCH_EXPOSED_WIDTH } from '../../../../config/misc';
import FreeCodeCampLogo from '../../../assets/icons/freecodecamp-logo'; import FreeCodeCampLogo from '../../../assets/icons/freecodecamp-logo';
import MenuButton from './menu-button'; import MenuButton from './menu-button';
import NavLinks, { type NavLinksProps } from './nav-links'; import NavLinks from './nav-links';
import AuthOrProfile from './auth-or-profile'; import AuthOrProfile from './auth-or-profile';
import LanguageList from './language-list'; import LanguageList from './language-list';
@@ -18,10 +18,17 @@ const SearchBarOptimized = Loadable(
() => import('../../search/searchBar/search-bar-optimized') () => import('../../search/searchBar/search-bar-optimized')
); );
type UniversalNavProps = Omit< type UniversalNavProps = {
NavLinksProps, displayMenu: boolean;
'toggleTheme' | 'openSignoutModal' showMenu: () => void;
> & { hideMenu: () => void;
menuButtonRef: React.RefObject<HTMLButtonElement>;
user: {
isDonating: boolean;
username: string;
picture: string;
yearsTopContributor: string[];
};
fetchState: { pending: boolean }; fetchState: { pending: boolean };
searchBarRef?: React.RefObject<HTMLDivElement>; searchBarRef?: React.RefObject<HTMLDivElement>;
pathname: string; pathname: string;
@@ -81,7 +88,6 @@ const UniversalNav = ({
hideMenu={hideMenu} hideMenu={hideMenu}
innerRef={menuButtonRef} innerRef={menuButtonRef}
showMenu={showMenu} showMenu={showMenu}
user={user}
/> />
{!isSearchExposedWidth && search} {!isSearchExposedWidth && search}
<NavLinks <NavLinks
+40 -47
View File
@@ -1,24 +1,46 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import React from 'react'; import React from 'react';
import { create, ReactTestRendererJSON } from 'react-test-renderer'; import { create, ReactTestRendererJSON } from 'react-test-renderer';
import AuthOrProfile from './components/auth-or-profile'; import AuthOrProfile from './components/auth-or-profile';
const defaultUserProps = {
user: {
username: 'test-user',
picture: 'https://freecodecamp.org/image.png',
isDonating: false,
yearsTopContributor: []
},
pending: false,
pathName: '/learn'
};
const donatingUserProps = {
...defaultUserProps,
user: {
...defaultUserProps.user,
isDonating: true
}
};
const topContributorUserProps = {
...defaultUserProps,
user: {
...defaultUserProps.user,
yearsTopContributor: ['2020']
}
};
const topDonatingContributorUserProps = {
...topContributorUserProps,
user: {
...topContributorUserProps.user,
isDonating: true
}
};
jest.mock('../../analytics'); jest.mock('../../analytics');
describe('<AuthOrProfile />', () => { describe('<AuthOrProfile />', () => {
it('has avatar with default border for default users', () => { it('has avatar with default border for default users', () => {
const defaultUserProps = {
user: {
username: 'test-user',
picture: 'https://freecodecamp.org/image.png'
},
pending: false,
pathName: '/learn'
};
const componentTree = create( const componentTree = create(
<AuthOrProfile {...defaultUserProps} /> <AuthOrProfile {...defaultUserProps} />
).toJSON(); ).toJSON();
@@ -26,16 +48,6 @@ describe('<AuthOrProfile />', () => {
}); });
it('has avatar with gold border for donating users', () => { it('has avatar with gold border for donating users', () => {
const donatingUserProps = {
user: {
username: 'test-user',
picture: 'https://freecodecamp.org/image.png',
isDonating: true
},
pending: false,
pathName: '/learn'
};
const componentTree = create( const componentTree = create(
<AuthOrProfile {...donatingUserProps} /> <AuthOrProfile {...donatingUserProps} />
).toJSON(); ).toJSON();
@@ -43,16 +55,6 @@ describe('<AuthOrProfile />', () => {
}); });
it('has avatar with blue border for top contributors', () => { it('has avatar with blue border for top contributors', () => {
const topContributorUserProps = {
user: {
username: 'test-user',
picture: 'https://freecodecamp.org/image.png',
yearsTopContributor: [2020]
},
pending: false,
pathName: '/learn'
};
const componentTree = create( const componentTree = create(
<AuthOrProfile {...topContributorUserProps} /> <AuthOrProfile {...topContributorUserProps} />
).toJSON(); ).toJSON();
@@ -60,17 +62,6 @@ describe('<AuthOrProfile />', () => {
}); });
it('has avatar with purple border for donating top contributors', () => { it('has avatar with purple border for donating top contributors', () => {
const topDonatingContributorUserProps = {
user: {
username: 'test-user',
picture: 'https://freecodecamp.org/image.png',
isDonating: true,
yearsTopContributor: [2020]
},
pending: false,
pathName: '/learn'
};
const componentTree = create( const componentTree = create(
<AuthOrProfile {...topDonatingContributorUserProps} /> <AuthOrProfile {...topDonatingContributorUserProps} />
).toJSON(); ).toJSON();
@@ -78,15 +69,17 @@ describe('<AuthOrProfile />', () => {
}); });
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any type Component = {
const profileNavItem = (component: any) => component.children[0]; children: { props: { className: string } }[];
};
const profileNavItem = (component: Component) => component.children[0];
const avatarHasClass = ( const avatarHasClass = (
componentTree: ReactTestRendererJSON | ReactTestRendererJSON[] | null, componentTree: ReactTestRendererJSON | ReactTestRendererJSON[] | null,
classes: string classes: string
) => { ) => {
return ( return (
profileNavItem(componentTree).props.className === profileNavItem(componentTree as unknown as Component).props.className ===
'avatar-container ' + classes 'avatar-container ' + classes
); );
}; };
+6 -2
View File
@@ -5,7 +5,6 @@
import React from 'react'; import React from 'react';
import { ConnectedProps, connect } from 'react-redux'; import { ConnectedProps, connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { User } from '../../redux/prop-types';
import { examInProgressSelector } from '../../redux/selectors'; import { examInProgressSelector } from '../../redux/selectors';
import UniversalNav from './components/universal-nav'; import UniversalNav from './components/universal-nav';
@@ -26,7 +25,12 @@ type PropsFromRedux = ConnectedProps<typeof connector>;
type Props = PropsFromRedux & { type Props = PropsFromRedux & {
fetchState: { pending: boolean }; fetchState: { pending: boolean };
user: User; user: {
isDonating: boolean;
username: string;
picture: string;
yearsTopContributor: string[];
};
skipButtonText: string; skipButtonText: string;
pathname: string; pathname: string;
}; };
-1
View File
@@ -57,7 +57,6 @@ const superBlockHeadings: { [key in SuperBlockStage]: string } = {
[SuperBlockStage.Professional]: 'landing.professional-certs-heading', [SuperBlockStage.Professional]: 'landing.professional-certs-heading',
[SuperBlockStage.Extra]: 'landing.interview-prep-heading', [SuperBlockStage.Extra]: 'landing.interview-prep-heading',
[SuperBlockStage.Legacy]: 'landing.legacy-curriculum-heading', [SuperBlockStage.Legacy]: 'landing.legacy-curriculum-heading',
[SuperBlockStage.New]: '', // TODO: add translation
[SuperBlockStage.Next]: 'landing.next-heading', [SuperBlockStage.Next]: 'landing.next-heading',
[SuperBlockStage.NextEnglish]: 'landing.next-english-heading', [SuperBlockStage.NextEnglish]: 'landing.next-english-heading',
[SuperBlockStage.Upcoming]: 'landing.upcoming-heading' [SuperBlockStage.Upcoming]: 'landing.upcoming-heading'
@@ -202,7 +202,10 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
}); });
} }
getUrlValidation(url: string) { getUrlValidation(url: string): {
state: 'success' | 'warning' | 'error';
message: string;
} {
const { t } = this.props; const { t } = this.props;
const len = url.length; const len = url.length;
@@ -1 +1 @@
export { parseDate, formatYears } from './utils'; export { parseDate } from './utils';
@@ -90,6 +90,11 @@ function renderWithRedux(ui: JSX.Element) {
} }
describe('<Profile/>', () => { describe('<Profile/>', () => {
it('renders the report button on another persons profile', () => { it('renders the report button on another persons profile', () => {
// TODO: Profile is a mess, it shouldn't depend on the entire user. Each
// component Camper, Stats, HeatMap etc should be get the relevant data from
// the store themselves.
// @ts-expect-error - quick hack to mollify TS.
renderWithRedux(<Profile {...notMyProfileProps} />); renderWithRedux(<Profile {...notMyProfileProps} />);
const reportButton: HTMLElement = screen.getByText('buttons.flag-user'); const reportButton: HTMLElement = screen.getByText('buttons.flag-user');
@@ -97,6 +102,7 @@ describe('<Profile/>', () => {
}); });
it('renders correctly', () => { it('renders correctly', () => {
// @ts-expect-error - quick hack to mollify TS.
const { container } = renderWithRedux(<Profile {...notMyProfileProps} />); const { container } = renderWithRedux(<Profile {...notMyProfileProps} />);
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
@@ -68,6 +68,7 @@ function DeleteModal(props: DeleteModalProps): JSX.Element {
/> />
</FormGroup> </FormGroup>
<Spacer size='xs' /> <Spacer size='xs' />
{/* @ts-expect-error The UI lib's types don't allow this: https://github.com/freeCodeCamp/ui/issues/473 */}
<Button <Button
block={true} block={true}
size='large' size='large'
@@ -66,6 +66,7 @@ function ResetModal(props: ResetModalProps): JSX.Element {
/> />
</FormGroup> </FormGroup>
<Spacer size='xs' /> <Spacer size='xs' />
{/* @ts-expect-error freecodecamp/ui doesn't allow disable to be false: https://github.com/freeCodeCamp/ui/issues/473 */}
<Button <Button
block={true} block={true}
size='large' size='large'
-15
View File
@@ -152,21 +152,6 @@ export interface PrerequisiteChallenge {
slug?: string; slug?: string;
} }
export type ExtendedChallenge = {
block: string;
challengeType: number;
dashedName: string;
fields: {
slug: string;
};
id: string;
isCompleted: boolean;
order: number;
superBlock: SuperBlocks;
stepNumber: number;
title: string;
};
export type ChallengeNode = { export type ChallengeNode = {
challenge: { challenge: {
block: string; block: string;
@@ -14,7 +14,6 @@ import { isAuditedSuperBlock } from '../../../../../shared/utils/is-audited';
import Caret from '../../../assets/icons/caret'; import Caret from '../../../assets/icons/caret';
import { Link } from '../../../components/helpers'; import { Link } from '../../../components/helpers';
import { completedChallengesSelector } from '../../../redux/selectors'; import { completedChallengesSelector } from '../../../redux/selectors';
import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types';
import { playTone } from '../../../utils/tone'; import { playTone } from '../../../utils/tone';
import { makeExpandedBlockSelector, toggleBlock } from '../redux'; import { makeExpandedBlockSelector, toggleBlock } from '../redux';
import { isGridBased, isProjectBased } from '../../../utils/curriculum-layout'; import { isGridBased, isProjectBased } from '../../../utils/curriculum-layout';
@@ -30,15 +29,13 @@ import './block.css';
const { curriculumLocale } = envData; const { curriculumLocale } = envData;
type Challenge = ChallengeNode['challenge'];
const mapStateToProps = (state: unknown, ownProps: { block: string }) => { const mapStateToProps = (state: unknown, ownProps: { block: string }) => {
const expandedSelector = makeExpandedBlockSelector(ownProps.block); const expandedSelector = makeExpandedBlockSelector(ownProps.block);
return createSelector( return createSelector(
expandedSelector, expandedSelector,
completedChallengesSelector, completedChallengesSelector,
(isExpanded: boolean, completedChallenges: CompletedChallenge[]) => ({ (isExpanded: boolean, completedChallenges: { id: string }[]) => ({
isExpanded, isExpanded,
completedChallengeIds: completedChallenges.map(({ id }) => id) completedChallengeIds: completedChallenges.map(({ id }) => id)
}) })
@@ -48,10 +45,20 @@ const mapStateToProps = (state: unknown, ownProps: { block: string }) => {
const mapDispatchToProps = (dispatch: Dispatch) => const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators({ toggleBlock }, dispatch); bindActionCreators({ toggleBlock }, dispatch);
interface ChallengeInfo {
id: string;
title: string;
fields: { slug: string };
dashedName: string;
challengeType: number;
blockLayout: BlockLayouts;
superBlock: SuperBlocks;
}
interface BlockProps { interface BlockProps {
block: string; block: string;
blockType: BlockTypes | null; blockType: BlockTypes | null;
challenges: Challenge[]; challenges: ChallengeInfo[];
completedChallengeIds: string[]; completedChallengeIds: string[];
isExpanded: boolean; isExpanded: boolean;
superBlock: SuperBlocks; superBlock: SuperBlocks;
@@ -124,10 +131,10 @@ class Block extends Component<BlockProps> {
const expandText = t('intro:misc-text.expand'); const expandText = t('intro:misc-text.expand');
const collapseText = t('intro:misc-text.collapse'); const collapseText = t('intro:misc-text.collapse');
const isBlockCompleted = completedCount === extendedChallenges.length; const isBlockCompleted = completedCount === challenges.length;
const percentageCompleted = Math.floor( const percentageCompleted = Math.floor(
(completedCount / extendedChallenges.length) * 100 (completedCount / challenges.length) * 100
); );
// since the Blocks are not components, we need link to exist even if it's // since the Blocks are not components, we need link to exist even if it's
@@ -142,7 +149,7 @@ class Block extends Component<BlockProps> {
if (completedCount === 0) { if (completedCount === 0) {
return t('learn.not-started'); return t('learn.not-started');
} }
if (completedCount === extendedChallenges.length) { if (isBlockCompleted) {
return t('learn.completed'); return t('learn.completed');
} }
return `${percentageCompleted}% ${t('learn.completed')}`; return `${percentageCompleted}% ${t('learn.completed')}`;
@@ -188,12 +195,12 @@ class Block extends Component<BlockProps> {
<span <span
aria-hidden='true' aria-hidden='true'
className='map-completed-count' className='map-completed-count'
>{`${completedCount}/${extendedChallenges.length}`}</span> >{`${completedCount}/${challenges.length}`}</span>
<span className='sr-only'> <span className='sr-only'>
,{' '} ,{' '}
{t('learn.challenges-completed', { {t('learn.challenges-completed', {
completedCount, completedCount,
totalChallenges: extendedChallenges.length totalChallenges: challenges.length
})} })}
</span> </span>
</div> </div>
@@ -3,14 +3,23 @@ import { withTranslation, useTranslation } from 'react-i18next';
import GreenNotCompleted from '../../../assets/icons/green-not-completed'; import GreenNotCompleted from '../../../assets/icons/green-not-completed';
import GreenPass from '../../../assets/icons/green-pass'; import GreenPass from '../../../assets/icons/green-pass';
import { ExtendedChallenge } from '../../../redux/prop-types';
import { SuperBlocks } from '../../../../../shared/config/curriculum'; import { SuperBlocks } from '../../../../../shared/config/curriculum';
import { challengeTypes } from '../../../../../shared/config/challenge-types'; import { challengeTypes } from '../../../../../shared/config/challenge-types';
import { Link } from '../../../components/helpers'; import { Link } from '../../../components/helpers';
import { ButtonLink } from '../../../components/helpers/button-link'; import { ButtonLink } from '../../../components/helpers/button-link';
interface ChallengeInfo {
isCompleted: boolean;
fields: { slug: string };
dashedName: string;
title: string;
stepNumber: number;
superBlock: SuperBlocks;
challengeType: number;
}
interface Challenges { interface Challenges {
challenges: ExtendedChallenge[]; challenges: ChallengeInfo[];
isProjectBlock: boolean; isProjectBlock: boolean;
isGridMap?: boolean; isGridMap?: boolean;
blockTitle?: string | null; blockTitle?: string | null;
@@ -19,7 +28,7 @@ interface Challenges {
const CheckMark = ({ isCompleted }: { isCompleted: boolean }) => const CheckMark = ({ isCompleted }: { isCompleted: boolean }) =>
isCompleted ? <GreenPass /> : <GreenNotCompleted />; isCompleted ? <GreenPass /> : <GreenNotCompleted />;
const ListChallenge = ({ challenge }: { challenge: ExtendedChallenge }) => ( const ListChallenge = ({ challenge }: { challenge: ChallengeInfo }) => (
<Link to={challenge.fields.slug}> <Link to={challenge.fields.slug}>
<span className='map-badge'> <span className='map-badge'>
<CheckMark isCompleted={challenge.isCompleted} /> <CheckMark isCompleted={challenge.isCompleted} />
@@ -28,7 +37,7 @@ const ListChallenge = ({ challenge }: { challenge: ExtendedChallenge }) => (
</Link> </Link>
); );
const CertChallenge = ({ challenge }: { challenge: ExtendedChallenge }) => ( const CertChallenge = ({ challenge }: { challenge: ChallengeInfo }) => (
<Link to={challenge.fields.slug}> <Link to={challenge.fields.slug}>
{challenge.title} {challenge.title}
<span className='map-badge map-project-checkmark'> <span className='map-badge map-project-checkmark'>
@@ -38,7 +47,7 @@ const CertChallenge = ({ challenge }: { challenge: ExtendedChallenge }) => (
); );
// Step or Task challenge // Step or Task challenge
const GridChallenge = ({ challenge }: { challenge: ExtendedChallenge }) => { const GridChallenge = ({ challenge }: { challenge: ChallengeInfo }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@@ -3,13 +3,13 @@ import { useTranslation } from 'react-i18next';
// TODO: Add this component to freecodecamp/ui and remove this dependency // TODO: Add this component to freecodecamp/ui and remove this dependency
import { Disclosure } from '@headlessui/react'; import { Disclosure } from '@headlessui/react';
import { ChallengeNode } from '../../../redux/prop-types';
import { SuperBlocks } from '../../../../../shared/config/curriculum'; import { SuperBlocks } from '../../../../../shared/config/curriculum';
import DropDown from '../../../assets/icons/dropdown'; import DropDown from '../../../assets/icons/dropdown';
// TODO: See if there's a nice way to incorporate the structure into data Gatsby // TODO: See if there's a nice way to incorporate the structure into data Gatsby
// sources from the curriculum, rather than importing it directly. // sources from the curriculum, rather than importing it directly.
import superBlockStructure from '../../../../../curriculum/superblock-structure/full-stack.json'; import superBlockStructure from '../../../../../curriculum/superblock-structure/full-stack.json';
import { ChapterIcon } from '../../../assets/chapter-icon'; import { ChapterIcon } from '../../../assets/chapter-icon';
import { BlockLayouts, BlockTypes } from '../../../../../shared/config/blocks';
import { FsdChapters } from '../../../../../shared/config/chapters'; import { FsdChapters } from '../../../../../shared/config/chapters';
import envData from '../../../../config/env.json'; import envData from '../../../../config/env.json';
import Block from './block'; import Block from './block';
@@ -34,8 +34,21 @@ interface ModuleProps {
totalSteps: number; totalSteps: number;
completedSteps: number; completedSteps: number;
} }
interface Challenge {
id: string;
block: string;
blockType: BlockTypes;
title: string;
fields: { slug: string };
dashedName: string;
challengeType: number;
blockLayout: BlockLayouts;
superBlock: SuperBlocks;
}
interface SuperBlockTreeViewProps { interface SuperBlockTreeViewProps {
challenges: ChallengeNode['challenge'][]; challenges: Challenge[];
superBlock: SuperBlocks; superBlock: SuperBlocks;
chosenBlock: string; chosenBlock: string;
completedChallengeIds: string[]; completedChallengeIds: string[];
@@ -191,7 +204,7 @@ const LinkBlock = ({
challenges challenges
}: { }: {
superBlock: SuperBlocks; superBlock: SuperBlocks;
challenges?: ChallengeNode['challenge'][]; challenges?: Challenge[];
}) => }) =>
challenges?.length ? ( challenges?.length ? (
<li className='link-block'> <li className='link-block'>
@@ -24,9 +24,10 @@ import {
userFetchStateSelector, userFetchStateSelector,
signInLoadingSelector signInLoadingSelector
} from '../../redux/selectors'; } from '../../redux/selectors';
import type { ChallengeNode, User } from '../../redux/prop-types'; import type { User } from '../../redux/prop-types';
import { CertTitle, liveCerts } from '../../../config/cert-and-project-map'; import { CertTitle, liveCerts } from '../../../config/cert-and-project-map';
import { superBlockToCertMap } from '../../../../shared/config/certification-settings'; import { superBlockToCertMap } from '../../../../shared/config/certification-settings';
import { BlockLayouts, BlockTypes } from '../../../../shared/config/blocks';
import Block from './components/block'; import Block from './components/block';
import CertChallenge from './components/cert-challenge'; import CertChallenge from './components/cert-challenge';
import LegacyLinks from './components/legacy-links'; import LegacyLinks from './components/legacy-links';
@@ -43,6 +44,23 @@ type FetchState = {
errored: boolean; errored: boolean;
}; };
type ChallengeNode = {
challenge: {
fields: { slug: string; blockName: string };
id: string;
block: string;
blockType: BlockTypes;
challengeType: number;
title: string;
order: number;
superBlock: SuperBlocks;
dashedName: string;
blockLayout: BlockLayouts;
chapter: string;
module: string;
};
};
type SuperBlockProps = { type SuperBlockProps = {
currentChallengeId: string; currentChallengeId: string;
data: { data: {
+1
View File
@@ -3,6 +3,7 @@ declare global {
// This is a feature Gatsby adds to the `window` object. // This is a feature Gatsby adds to the `window` object.
// https://github.com/gatsbyjs/gatsby/blob/deb41cdfefbefe0c170b5dd7c10a19ba2b338f6e/packages/gatsby/cache-dir/production-app.js#L28 // https://github.com/gatsbyjs/gatsby/blob/deb41cdfefbefe0c170b5dd7c10a19ba2b338f6e/packages/gatsby/cache-dir/production-app.js#L28
___loader: { ___loader: {
enqueue: () => void;
hovering: (path: string | null) => void; hovering: (path: string | null) => void;
}; };
} }
@@ -31,6 +31,7 @@ function getComponentNameAndProps(
const LayoutReactComponent = layoutSelector({ const LayoutReactComponent = layoutSelector({
element: { type: elementType, props: {}, key: '' }, element: { type: elementType, props: {}, key: '' },
props: { props: {
data: {},
location: { location: {
pathname pathname
}, },
+1 -1
View File
@@ -112,7 +112,7 @@ export const hiddenLangs = [Languages.Korean];
/** /**
* This array contains languages that use the RTL layouts. * This array contains languages that use the RTL layouts.
*/ */
export const rtlLangs = []; export const rtlLangs: Languages[] = [];
// locale is sourced from a JSON file, so we use getLangCode to // locale is sourced from a JSON file, so we use getLangCode to
// find the associated enum values // find the associated enum values