chore(client): add and apply naming convention (#44110)

* refactor: remove unused types

* chore: add naming-convention lint rule

* refactor: rename redux proptypes

* chore: remove Type suffix from prop-types

* chore: apply conventions to ajax

* chore: apply convention to create-types

* chore: apply convention to show-project-links

* chore: search-bar

* chore: Hotkeys

* chore: privacy

* chore: portfolio

* chore: search-page-hits

* chore: search-suggestion

* chore: search-hits

* chore: no-hits-suggestion

* chore: timeline-pagination

* chore: various profile files

* chore: heat-map

* chore: portfolio

* chore: certifications

* chore: landing-top

* chore: certifications

* chore: campers-image

* chore: big-call-to-action

* chore: paypal related files

* chore: show-user

* chore: show-settings

* chore: show-certification

* test: rename profile snap

* fix: ignore snake case for stripe card form

* refactor: remove duplicate type declarations

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>

* fix: handle null solutions in Timeline

* test: add remaining Profile props

* refactor: revert accidental rename

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
Oliver Eyton-Williams
2021-11-11 19:09:50 +01:00
committed by GitHub
parent 85359ed00a
commit 89c94e54e7
41 changed files with 246 additions and 314 deletions

View File

@ -54,7 +54,46 @@
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking" "plugin:@typescript-eslint/recommended-requiring-type-checking"
], ],
"plugins": ["@typescript-eslint"] "plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "default",
"format": ["camelCase"],
"leadingUnderscore": "allow",
"trailingUnderscore": "allow"
},
{
"selector": "variable",
"format": ["camelCase", "UPPER_CASE", "PascalCase"],
"leadingUnderscore": "allowSingleOrDouble",
"trailingUnderscore": "allow"
},
{
"selector": "typeLike",
"format": ["PascalCase"],
"custom": {
"regex": "^I[A-Z]|[a-zA-Z]Type$",
"match": false
}
},
{
"selector": "typeProperty",
"format": ["camelCase", "PascalCase"]
},
{
"selector": "objectLiteralProperty",
"format": ["camelCase", "PascalCase"],
"leadingUnderscore": "allowSingleOrDouble"
},
{
"selector": "function",
"format": ["camelCase", "PascalCase"]
}
]
}
}, },
{ {
"files": [ "files": [

View File

@ -36,7 +36,7 @@ import ShowProjectLinks from './show-project-links';
const { clientLocale } = envData as { clientLocale: keyof typeof langCodes }; const { clientLocale } = envData as { clientLocale: keyof typeof langCodes };
const localeCode = langCodes[clientLocale]; const localeCode = langCodes[clientLocale];
type CertType = { type Cert = {
username: string; username: string;
name: string; name: string;
certName: string; certName: string;
@ -44,8 +44,8 @@ type CertType = {
completionTime: number; completionTime: number;
date: number; date: number;
}; };
interface IShowCertificationProps { interface ShowCertificationProps {
cert: CertType; cert: Cert;
certDashedName: string; certDashedName: string;
certSlug: string; certSlug: string;
createFlashMessage: typeof createFlashMessage; createFlashMessage: typeof createFlashMessage;
@ -82,7 +82,7 @@ const requestedUserSelector = (state: unknown, { username = '' }) =>
const validCertSlugs = certMap.map(cert => cert.certSlug); const validCertSlugs = certMap.map(cert => cert.certSlug);
const mapStateToProps = (state: unknown, props: IShowCertificationProps) => { const mapStateToProps = (state: unknown, props: ShowCertificationProps) => {
const isValidCert = validCertSlugs.some(slug => slug === props.certSlug); const isValidCert = validCertSlugs.some(slug => slug === props.certSlug);
return createSelector( return createSelector(
showCertSelector, showCertSelector,
@ -92,10 +92,10 @@ const mapStateToProps = (state: unknown, props: IShowCertificationProps) => {
isDonatingSelector, isDonatingSelector,
requestedUserSelector, requestedUserSelector,
( (
cert: CertType, cert: Cert,
fetchState: IShowCertificationProps['fetchState'], fetchState: ShowCertificationProps['fetchState'],
signedInUserName: string, signedInUserName: string,
userFetchState: IShowCertificationProps['userFetchState'], userFetchState: ShowCertificationProps['userFetchState'],
isDonating: boolean, isDonating: boolean,
user user
) => ({ ) => ({
@ -116,7 +116,7 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
dispatch dispatch
); );
const ShowCertification = (props: IShowCertificationProps): JSX.Element => { const ShowCertification = (props: ShowCertificationProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isDonationSubmitted, setIsDonationSubmitted] = useState(false); const [isDonationSubmitted, setIsDonationSubmitted] = useState(false);
const [isDonationDisplayed, setIsDonationDisplayed] = useState(false); const [isDonationDisplayed, setIsDonationDisplayed] = useState(false);

View File

@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { isBrowser } from '../../utils/index'; import { isBrowser } from '../../utils/index';
import FourOhFour from '../components/FourOhFour'; import FourOhFour from '../components/FourOhFour';
import Loader from '../components/helpers/loader'; import Loader from '../components/helpers/loader';
import Profile from '../components/profile/Profile'; import Profile from '../components/profile/profile';
import { import {
userByNameSelector, userByNameSelector,
userProfileFetchStateSelector, userProfileFetchStateSelector,
@ -14,7 +14,7 @@ import {
} from '../redux'; } from '../redux';
import { User } from '../redux/prop-types'; import { User } from '../redux/prop-types';
interface IShowProfileOrFourOhFourProps { interface ShowProfileOrFourOhFourProps {
fetchProfileForUser: (username: string) => void; fetchProfileForUser: (username: string) => void;
fetchState: { fetchState: {
pending: boolean; pending: boolean;
@ -37,12 +37,12 @@ const createIsSessionUserSelector =
maybeUser.toLowerCase() === usernameSelector(state); maybeUser.toLowerCase() === usernameSelector(state);
const makeMapStateToProps = const makeMapStateToProps =
() => (state: unknown, props: IShowProfileOrFourOhFourProps) => { () => (state: unknown, props: ShowProfileOrFourOhFourProps) => {
const requestedUserSelector = createRequestedUserSelector(); const requestedUserSelector = createRequestedUserSelector();
const isSessionUserSelector = createIsSessionUserSelector(); const isSessionUserSelector = createIsSessionUserSelector();
const fetchState = userProfileFetchStateSelector( const fetchState = userProfileFetchStateSelector(
state state
) as IShowProfileOrFourOhFourProps['fetchState']; ) as ShowProfileOrFourOhFourProps['fetchState'];
return { return {
requestedUser: requestedUserSelector(state, props), requestedUser: requestedUserSelector(state, props),
isSessionUser: isSessionUserSelector(state, props), isSessionUser: isSessionUserSelector(state, props),
@ -52,12 +52,12 @@ const makeMapStateToProps =
}; };
const mapDispatchToProps: { const mapDispatchToProps: {
fetchProfileForUser: IShowProfileOrFourOhFourProps['fetchProfileForUser']; fetchProfileForUser: ShowProfileOrFourOhFourProps['fetchProfileForUser'];
} = { } = {
fetchProfileForUser fetchProfileForUser
}; };
class ShowProfileOrFourOhFour extends Component<IShowProfileOrFourOhFourProps> { class ShowProfileOrFourOhFour extends Component<ShowProfileOrFourOhFourProps> {
componentDidMount() { componentDidMount() {
const { requestedUser, maybeUser, fetchProfileForUser } = this.props; const { requestedUser, maybeUser, fetchProfileForUser } = this.props;
if (isEmpty(requestedUser)) { if (isEmpty(requestedUser)) {

View File

@ -12,27 +12,27 @@ import {
import { maybeUrlRE } from '../utils'; import { maybeUrlRE } from '../utils';
interface IShowProjectLinksProps { interface ShowProjectLinksProps {
certName: string; certName: string;
name: string; name: string;
user: User; user: User;
} }
type SolutionStateType = { type SolutionState = {
projectTitle: string; projectTitle: string;
challengeFiles: ChallengeFiles; challengeFiles: ChallengeFiles;
solution: CompletedChallenge['solution']; solution: CompletedChallenge['solution'];
isOpen: boolean; isOpen: boolean;
}; };
const initSolutionState: SolutionStateType = { const initSolutionState: SolutionState = {
projectTitle: '', projectTitle: '',
challengeFiles: null, challengeFiles: null,
solution: '', solution: '',
isOpen: false isOpen: false
}; };
const ShowProjectLinks = (props: IShowProjectLinksProps): JSX.Element => { const ShowProjectLinks = (props: ShowProjectLinksProps): JSX.Element => {
const [solutionState, setSolutionState] = useState(initSolutionState); const [solutionState, setSolutionState] = useState(initSolutionState);
const handleSolutionModalHide = () => setSolutionState(initSolutionState); const handleSolutionModalHide = () => setSolutionState(initSolutionState);

View File

@ -28,7 +28,7 @@ import { submitNewAbout, updateUserFlag, verifyCert } from '../redux/settings';
const { apiLocation } = envData; const { apiLocation } = envData;
// TODO: update types for actions // TODO: update types for actions
interface IShowSettingsProps { interface ShowSettingsProps {
createFlashMessage: typeof createFlashMessage; createFlashMessage: typeof createFlashMessage;
isSignedIn: boolean; isSignedIn: boolean;
navigate: (location: string) => void; navigate: (location: string) => void;
@ -70,7 +70,7 @@ const mapDispatchToProps = {
verifyCert verifyCert
}; };
export function ShowSettings(props: IShowSettingsProps): JSX.Element { export function ShowSettings(props: ShowSettingsProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
createFlashMessage, createFlashMessage,

View File

@ -23,7 +23,7 @@ import {
reportUser reportUser
} from '../redux'; } from '../redux';
interface IShowUserProps { interface ShowUserProps {
email: string; email: string;
isSignedIn: boolean; isSignedIn: boolean;
reportUser: (payload: { reportUser: (payload: {
@ -45,7 +45,7 @@ const mapStateToProps = createSelector(
userSelector, userSelector,
( (
isSignedIn, isSignedIn,
userFetchState: IShowUserProps['userFetchState'], userFetchState: ShowUserProps['userFetchState'],
{ email }: { email: string } { email }: { email: string }
) => ({ ) => ({
isSignedIn, isSignedIn,
@ -65,7 +65,7 @@ function ShowUser({
t, t,
userFetchState, userFetchState,
username username
}: IShowUserProps) { }: ShowUserProps) {
const [textarea, setTextarea] = useState(''); const [textarea, setTextarea] = useState('');
function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) { function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {

View File

@ -27,9 +27,9 @@ import {
} from '../../redux'; } from '../../redux';
import Spacer from '../helpers/spacer'; import Spacer from '../helpers/spacer';
import DonateCompletion from './DonateCompletion'; import DonateCompletion from './DonateCompletion';
import type { AddDonationData } from './PaypalButton';
import PaypalButton from './PaypalButton';
import PatreonButton from './patreon-button'; import PatreonButton from './patreon-button';
import type { AddDonationData } from './paypal-button';
import PaypalButton from './paypal-button';
import StripeCardForm, { HandleAuthentication } from './stripe-card-form'; import StripeCardForm, { HandleAuthentication } from './stripe-card-form';
import WalletsWrapper from './walletsButton'; import WalletsWrapper from './walletsButton';

View File

@ -4,8 +4,9 @@ import ReactDOM from 'react-dom';
import { scriptLoader, scriptRemover } from '../../utils/script-loaders'; import { scriptLoader, scriptRemover } from '../../utils/script-loaders';
import type { AddDonationData } from './PaypalButton'; import type { AddDonationData } from './paypal-button';
/* eslint-disable @typescript-eslint/naming-convention */
type PayPalButtonScriptLoaderProps = { type PayPalButtonScriptLoaderProps = {
isMinimalForm: boolean | undefined; isMinimalForm: boolean | undefined;
clientId: string; clientId: string;
@ -43,6 +44,7 @@ type PayPalButtonScriptLoaderProps = {
}; };
planId: string | null; planId: string | null;
}; };
/* eslint-enable @typescript-eslint/naming-convention */
type PayPalButtonScriptLoaderState = { type PayPalButtonScriptLoaderState = {
isSdkLoaded: boolean; isSdkLoaded: boolean;

View File

@ -1,7 +1,7 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { PaypalButton } from './PaypalButton'; import { PaypalButton } from './paypal-button';
const commonProps = { const commonProps = {
donationAmount: 500, donationAmount: 500,

View File

@ -13,7 +13,7 @@ import {
} from '../../../../config/donation-settings'; } from '../../../../config/donation-settings';
import envData from '../../../../config/env.json'; import envData from '../../../../config/env.json';
import { signInLoadingSelector, userSelector } from '../../redux'; import { signInLoadingSelector, userSelector } from '../../redux';
import PayPalButtonScriptLoader from './PayPalButtonScriptLoader'; import PayPalButtonScriptLoader from './paypal-button-script-loader';
type PaypalButtonProps = { type PaypalButtonProps = {
addDonation: (data: AddDonationData) => void; addDonation: (data: AddDonationData) => void;
@ -138,7 +138,7 @@ export class PaypalButton extends Component<
return ( return (
<div className={'paypal-buttons-container'}> <div className={'paypal-buttons-container'}>
{/* help needed */} {/* eslint-disable @typescript-eslint/naming-convention */}
<PayPalButtonScriptLoader <PayPalButtonScriptLoader
clientId={paypalClientId} clientId={paypalClientId}
createOrder={( createOrder={(
@ -207,6 +207,7 @@ export class PaypalButton extends Component<
color: buttonColor color: buttonColor
}} }}
/> />
{/* eslint-enable @typescript-eslint/naming-convention */}
</div> </div>
); );
} }

View File

@ -16,7 +16,7 @@ import type {
import React, { useState } from 'react'; import React, { useState } from 'react';
import envData from '../../../../config/env.json'; import envData from '../../../../config/env.json';
import { AddDonationData } from './PaypalButton'; import { AddDonationData } from './paypal-button';
const { stripePublicKey }: { stripePublicKey: string | null } = envData; const { stripePublicKey }: { stripePublicKey: string | null } = envData;
@ -132,7 +132,7 @@ const StripeCardForm = ({
) => { ) => {
if (stripe) { if (stripe) {
return stripe.confirmCardPayment(clientSecret, { return stripe.confirmCardPayment(clientSecret, {
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase, @typescript-eslint/naming-convention
payment_method: paymentMethod payment_method: paymentMethod
}); });
} }

View File

@ -7,7 +7,7 @@ import { Stripe, loadStripe } from '@stripe/stripe-js';
import type { Token, PaymentRequest } from '@stripe/stripe-js'; import type { Token, PaymentRequest } from '@stripe/stripe-js';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import envData from '../../../../config/env.json'; import envData from '../../../../config/env.json';
import { AddDonationData } from './PaypalButton'; import { AddDonationData } from './paypal-button';
const { stripePublicKey }: { stripePublicKey: string | null } = envData; const { stripePublicKey }: { stripePublicKey: string | null } = envData;

View File

@ -2,11 +2,11 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Login from '../../Header/components/Login'; import Login from '../../Header/components/Login';
interface bigCallToActionProps { interface BigCallToActionProps {
pageName: string; pageName: string;
} }
const BigCallToAction = ({ pageName }: bigCallToActionProps): JSX.Element => { const BigCallToAction = ({ pageName }: BigCallToActionProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (

View File

@ -6,28 +6,22 @@ import { Spacer, ImageLoader } from '../../helpers';
const LARGE_SCREEN_SIZE = 1200; const LARGE_SCREEN_SIZE = 1200;
interface campersImageProps { interface CampersImageProps {
pageName: string; pageName: string;
} }
interface imageSizeProps { const donateImageSize = {
spacerSize: number;
height: number;
width: number;
}
const donateImageSize: imageSizeProps = {
spacerSize: 0, spacerSize: 0,
height: 345, height: 345,
width: 585 width: 585
}; };
const landingImageSize: imageSizeProps = { const landingImageSize = {
spacerSize: 2, spacerSize: 2,
height: 442, height: 442,
width: 750 width: 750
}; };
function CampersImage({ pageName }: campersImageProps): JSX.Element { function CampersImage({ pageName }: CampersImageProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const { spacerSize, height, width } = const { spacerSize, height, width } =

View File

@ -5,13 +5,13 @@ import Map from '../../Map/index';
import { Spacer } from '../../helpers'; import { Spacer } from '../../helpers';
import BigCallToAction from './big-call-to-action'; import BigCallToAction from './big-call-to-action';
interface certificationProps { interface CertificationProps {
pageName: string; pageName: string;
} }
const Certifications = ({ const Certifications = ({
pageName = 'landing' pageName = 'landing'
}: certificationProps): JSX.Element => { }: CertificationProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (

View File

@ -15,12 +15,12 @@ import { Spacer } from '../../helpers';
import BigCallToAction from './big-call-to-action'; import BigCallToAction from './big-call-to-action';
import CampersImage from './campers-image'; import CampersImage from './campers-image';
interface landingTopProps { interface LandingTopProps {
pageName: string; pageName: string;
} }
const { clientLocale } = envData; const { clientLocale } = envData;
function LandingTop({ pageName }: landingTopProps): JSX.Element { function LandingTop({ pageName }: LandingTopProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const showChineseLogos = ['chinese', 'chinese-tradition'].includes( const showChineseLogos = ['chinese', 'chinese-tradition'].includes(
clientLocale clientLocale

View File

@ -20,10 +20,10 @@ import {
getTitleFromId getTitleFromId
} from '../../../../../utils'; } from '../../../../../utils';
import CertificationIcon from '../../../assets/icons/certification-icon'; import CertificationIcon from '../../../assets/icons/certification-icon';
import { ChallengeFiles } from '../../../redux/prop-types'; import { ChallengeFiles, CompletedChallenge } from '../../../redux/prop-types';
import { maybeUrlRE } from '../../../utils'; import { maybeUrlRE } from '../../../utils';
import { FullWidthRow, Link } from '../../helpers'; import { FullWidthRow, Link } from '../../helpers';
import TimelinePagination from './TimelinePagination'; import TimelinePagination from './timeline-pagination';
import './timeline.css'; import './timeline.css';
@ -37,32 +37,15 @@ const localeCode = langCodes[clientLocale];
// Items per page in timeline. // Items per page in timeline.
const ITEMS_PER_PAGE = 15; const ITEMS_PER_PAGE = 15;
interface CompletedMap {
id: string;
completedDate: number;
challengeType: number;
solution: string;
challengeFiles: ChallengeFiles;
githubLink: string;
}
interface TimelineProps { interface TimelineProps {
completedMap: CompletedMap[]; completedMap: CompletedChallenge[];
t: TFunction; t: TFunction;
username: string; username: string;
} }
interface SortedTimeline {
id: string;
completedDate: number;
challengeFiles: ChallengeFiles;
githubLink: string;
solution: string;
}
interface TimelineInnerProps extends TimelineProps { interface TimelineInnerProps extends TimelineProps {
idToNameMap: Map<string, string>; idToNameMap: Map<string, string>;
sortedTimeline: SortedTimeline[]; sortedTimeline: CompletedChallenge[];
totalPages: number; totalPages: number;
} }
@ -83,12 +66,12 @@ function TimelineInner({
function viewSolution( function viewSolution(
id: string, id: string,
solution_: string, solution_: string | undefined | null,
challengeFiles_: ChallengeFiles challengeFiles_: ChallengeFiles
): void { ): void {
setSolutionToView(id); setSolutionToView(id);
setSolutionOpen(true); setSolutionOpen(true);
setSolution(solution_); setSolution(solution_ ?? '');
setChallengeFiles(challengeFiles_); setChallengeFiles(challengeFiles_);
} }
@ -115,8 +98,8 @@ function TimelineInner({
function renderViewButton( function renderViewButton(
id: string, id: string,
challengeFiles: ChallengeFiles, challengeFiles: ChallengeFiles,
githubLink: string, githubLink?: string,
solution: string solution?: string | null
): React.ReactNode { ): React.ReactNode {
if (challengeFiles?.length) { if (challengeFiles?.length) {
return ( return (
@ -159,7 +142,7 @@ function TimelineInner({
</DropdownButton> </DropdownButton>
</div> </div>
); );
} else if (maybeUrlRE.test(solution)) { } else if (solution && maybeUrlRE.test(solution)) {
return ( return (
<Button <Button
block={true} block={true}
@ -178,7 +161,7 @@ function TimelineInner({
} }
} }
function renderCompletion(completed: SortedTimeline): JSX.Element { function renderCompletion(completed: CompletedChallenge): JSX.Element {
const { id, challengeFiles, githubLink, solution } = completed; const { id, challengeFiles, githubLink, solution } = completed;
const completedDate = new Date(completed.completedDate); const completedDate = new Date(completed.completedDate);
// @ts-expect-error idToNameMap is not a <string, string> Map... // @ts-expect-error idToNameMap is not a <string, string> Map...

View File

@ -12,7 +12,7 @@ import envData from '../../../../../config/env.json';
import { langCodes } from '../../../../../config/i18n/all-langs'; import { langCodes } from '../../../../../config/i18n/all-langs';
import { AvatarRenderer } from '../../helpers'; import { AvatarRenderer } from '../../helpers';
import Link from '../../helpers/link'; import Link from '../../helpers/link';
import SocialIcons from './SocialIcons'; import SocialIcons from './social-icons';
import './camper.css'; import './camper.css';
@ -22,7 +22,7 @@ const { clientLocale } = envData;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const localeCode = langCodes[clientLocale]; const localeCode = langCodes[clientLocale];
interface ICamperProps { interface CamperProps {
about: string; about: string;
githubProfile: string; githubProfile: string;
isDonating: boolean; isDonating: boolean;
@ -83,7 +83,7 @@ function Camper({
linkedin, linkedin,
twitter, twitter,
website website
}: ICamperProps): JSX.Element { }: CamperProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (

View File

@ -6,11 +6,12 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { certificatesByNameSelector } from '../../../redux'; import { certificatesByNameSelector } from '../../../redux';
import type { CurrentCert } from '../../../redux/prop-types';
import { ButtonSpacer, FullWidthRow, Link, Spacer } from '../../helpers'; import { ButtonSpacer, FullWidthRow, Link, Spacer } from '../../helpers';
import './certifications.css'; import './certifications.css';
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const mapStateToProps = (state: any, props: ICertificationProps) => const mapStateToProps = (state: any, props: CertificationProps) =>
createSelector( createSelector(
certificatesByNameSelector(props.username), certificatesByNameSelector(props.username),
({ ({
@ -19,7 +20,7 @@ const mapStateToProps = (state: any, props: ICertificationProps) =>
currentCerts, currentCerts,
legacyCerts legacyCerts
}: Pick< }: Pick<
ICertificationProps, CertificationProps,
'hasModernCert' | 'hasLegacyCert' | 'currentCerts' | 'legacyCerts' 'hasModernCert' | 'hasLegacyCert' | 'currentCerts' | 'legacyCerts'
>) => ({ >) => ({
hasModernCert, hasModernCert,
@ -32,21 +33,15 @@ const mapStateToProps = (state: any, props: ICertificationProps) =>
// @ts-ignore // @ts-ignore
)(state, props); )(state, props);
interface ICert { interface CertificationProps {
show: boolean; currentCerts?: CurrentCert[];
title: string;
certSlug: string;
}
interface ICertificationProps {
currentCerts?: ICert[];
hasLegacyCert?: boolean; hasLegacyCert?: boolean;
hasModernCert?: boolean; hasModernCert?: boolean;
legacyCerts?: ICert[]; legacyCerts?: CurrentCert[];
username: string; username: string;
} }
function renderCertShow(username: string, cert: ICert): React.ReactNode { function renderCertShow(username: string, cert: CurrentCert): React.ReactNode {
return cert.show ? ( return cert.show ? (
<Fragment key={cert.title}> <Fragment key={cert.title}>
<Row> <Row>
@ -70,7 +65,7 @@ function Certificates({
hasLegacyCert, hasLegacyCert,
hasModernCert, hasModernCert,
username username
}: ICertificationProps): JSX.Element { }: CertificationProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const renderCertShowWithUsername = curry(renderCertShow)(username); const renderCertShowWithUsername = curry(renderCertShow)(username);
return ( return (

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
import HeatMap from './HeatMap'; import HeatMap from './heat-map';
// offset is used to shift the dates so that the calendar renders (for testing // offset is used to shift the dates so that the calendar renders (for testing
// purposes only) the same way in each timezone. // purposes only) the same way in each timezone.

View File

@ -28,36 +28,36 @@ const { clientLocale } = envData;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
const localeCode = langCodes[clientLocale]; const localeCode = langCodes[clientLocale];
interface IHeatMapProps { interface HeatMapProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
calendar: any; calendar: any;
} }
interface IPageData { interface PageData {
startOfCalendar: Date; startOfCalendar: Date;
endOfCalendar: Date; endOfCalendar: Date;
} }
interface ICalendarData { interface CalendarData {
date: Date; date: Date;
count: number; count: number;
} }
interface IHeatMapInnerProps { interface HeatMapInnerProps {
calendarData: ICalendarData[]; calendarData: CalendarData[];
currentStreak: number; currentStreak: number;
longestStreak: number; longestStreak: number;
pages: IPageData[]; pages: PageData[];
points?: number; points?: number;
t: TFunction; t: TFunction;
} }
interface IHeatMapInnerState { interface HeatMapInnerState {
pageIndex: number; pageIndex: number;
} }
class HeatMapInner extends Component<IHeatMapInnerProps, IHeatMapInnerState> { class HeatMapInner extends Component<HeatMapInnerProps, HeatMapInnerState> {
constructor(props: IHeatMapInnerProps) { constructor(props: HeatMapInnerProps) {
super(props); super(props);
this.state = { this.state = {
@ -184,7 +184,7 @@ class HeatMapInner extends Component<IHeatMapInnerProps, IHeatMapInnerState> {
} }
} }
const HeatMap = (props: IHeatMapProps): JSX.Element => { const HeatMap = (props: HeatMapProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { calendar } = props; const { calendar } = props;
@ -202,7 +202,7 @@ const HeatMap = (props: IHeatMapProps): JSX.Element => {
let startOfCalendar; let startOfCalendar;
// creates pages for heatmap // creates pages for heatmap
const pages: IPageData[] = []; const pages: PageData[] = [];
do { do {
startOfCalendar = addDays(addMonths(endOfCalendar, -6), 1); startOfCalendar = addDays(addMonths(endOfCalendar, -6), 1);
@ -219,7 +219,7 @@ const HeatMap = (props: IHeatMapProps): JSX.Element => {
pages.reverse(); pages.reverse();
const calendarData: ICalendarData[] = []; const calendarData: CalendarData[] = [];
let dayCounter = pages[0].startOfCalendar; let dayCounter = pages[0].startOfCalendar;
// create an object for each day of the calendar period // create an object for each day of the calendar period

View File

@ -2,23 +2,16 @@ import { Media } from '@freecodecamp/react-bootstrap';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { Portfolio as PortfolioData } from '../../../redux/prop-types';
import { FullWidthRow } from '../../helpers'; import { FullWidthRow } from '../../helpers';
import './portfolio.css'; import './portfolio.css';
interface IPortfolioData { interface PortfolioProps {
description: string; portfolio: PortfolioData[];
id: string;
image: string;
title: string;
url: string;
} }
interface IPortfolioProps { function Portfolio({ portfolio = [] }: PortfolioProps): JSX.Element | null {
portfolio: IPortfolioData[];
}
function Portfolio({ portfolio = [] }: IPortfolioProps): JSX.Element | null {
const { t } = useTranslation(); const { t } = useTranslation();
if (!portfolio.length) { if (!portfolio.length) {
return null; return null;

View File

@ -10,7 +10,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import './social-icons.css'; import './social-icons.css';
interface ISocialIconsProps { interface SocialIconsProps {
email?: string; email?: string;
githubProfile: string; githubProfile: string;
isGithub: boolean; isGithub: boolean;
@ -80,7 +80,7 @@ function TwitterIcon(handle: string, username: string): JSX.Element {
); );
} }
function SocialIcons(props: ISocialIconsProps): JSX.Element | null { function SocialIcons(props: SocialIconsProps): JSX.Element | null {
const { const {
githubProfile, githubProfile,
isLinkedIn, isLinkedIn,

View File

@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
interface ITimelinePaginationProps { interface TimelinePaginationProps {
firstPage: () => void; firstPage: () => void;
lastPage: () => void; lastPage: () => void;
nextPage: () => void; nextPage: () => void;
@ -11,7 +11,7 @@ interface ITimelinePaginationProps {
totalPages: number; totalPages: number;
} }
const TimelinePagination = (props: ITimelinePaginationProps): JSX.Element => { const TimelinePagination = (props: TimelinePaginationProps): JSX.Element => {
const { pageNo, totalPages, firstPage, prevPage, nextPage, lastPage } = props; const { pageNo, totalPages, firstPage, prevPage, nextPage, lastPage } = props;
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -1,12 +1,16 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
import Profile from './Profile'; import Profile from './profile';
jest.mock('../../analytics'); jest.mock('../../analytics');
const userProps = { const userProps = {
user: { user: {
acceptedPrivacyTerms: true,
currentChallengeId: 'string',
email: 'string',
emailVerified: true,
profileUI: { profileUI: {
isLocked: false, isLocked: false,
showAbout: false, showAbout: false,
@ -26,8 +30,12 @@ const userProps = {
}, },
completedChallenges: [], completedChallenges: [],
portfolio: [], portfolio: [],
progressTimestamps: [],
about: 'string', about: 'string',
githubProfile: 'string', githubProfile: 'string',
isBanned: false,
isCheater: true,
isHonest: true,
isGithub: true, isGithub: true,
isLinkedIn: true, isLinkedIn: true,
isTwitter: true, isTwitter: true,
@ -38,11 +46,30 @@ const userProps = {
name: 'string', name: 'string',
picture: 'string', picture: 'string',
points: 1, points: 1,
sendQuincyEmail: true,
sound: true,
theme: 'string',
twitter: 'string', twitter: 'string',
username: 'string', username: 'string',
website: 'string', website: 'string',
yearsTopContributor: [], yearsTopContributor: [],
isDonating: false isDonating: false,
is2018DataVisCert: true,
isApisMicroservicesCert: true,
isBackEndCert: true,
isDataVisCert: true,
isEmailVerified: true,
isFrontEndCert: true,
isFrontEndLibsCert: true,
isFullStackCert: true,
isInfosecQaCert: true,
isQaCertV7: true,
isInfosecCertV7: true,
isJsAlgoDataStructCert: true,
isRespWebDesignCert: true,
isSciCompPyCertV7: true,
isDataAnalysisPyCertV7: true,
isMachineLearningPyCertV7: true
}, },
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
navigate: () => {} navigate: () => {}

View File

@ -4,52 +4,16 @@ import Helmet from 'react-helmet';
import { TFunction, useTranslation } from 'react-i18next'; import { TFunction, useTranslation } from 'react-i18next';
import { CurrentChallengeLink, FullWidthRow, Link, Spacer } from '../helpers'; import { CurrentChallengeLink, FullWidthRow, Link, Spacer } from '../helpers';
import Camper from './components/Camper'; import { User } from './../../redux/prop-types';
import Certifications from './components/Certifications';
import HeatMap from './components/HeatMap';
import Portfolio from './components/Portfolio';
import Timeline from './components/TimeLine'; import Timeline from './components/TimeLine';
import Camper from './components/camper';
import Certifications from './components/certifications';
import HeatMap from './components/heat-map';
import Portfolio from './components/portfolio';
interface IProfileProps { interface ProfileProps {
isSessionUser: boolean; isSessionUser: boolean;
user: { user: User;
profileUI: {
isLocked: boolean;
showAbout: boolean;
showCerts: boolean;
showDonation: boolean;
showHeatMap: boolean;
showLocation: boolean;
showName: boolean;
showPoints: boolean;
showPortfolio: boolean;
showTimeLine: boolean;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
calendar: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
completedChallenges: any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
portfolio: any[];
about: string;
githubProfile: string;
isGithub: boolean;
isLinkedIn: boolean;
isTwitter: boolean;
isWebsite: boolean;
joinDate: string;
linkedin: string;
location: string;
name: string;
picture: string;
points: number;
twitter: string;
username: string;
website: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
yearsTopContributor: any[];
isDonating: boolean;
};
} }
function renderMessage( function renderMessage(
@ -88,7 +52,7 @@ function renderMessage(
); );
} }
function renderProfile(user: IProfileProps['user']): JSX.Element { function renderProfile(user: ProfileProps['user']): JSX.Element {
const { const {
profileUI: { profileUI: {
showAbout = false, showAbout = false,
@ -156,7 +120,7 @@ function renderProfile(user: IProfileProps['user']): JSX.Element {
); );
} }
function Profile({ user, isSessionUser }: IProfileProps): JSX.Element { function Profile({ user, isSessionUser }: ProfileProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
profileUI: { isLocked = true }, profileUI: { isLocked = true },

View File

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
interface noHitsSuggestionPropType { interface NoHitsSuggestionProps {
handleMouseEnter: (e: React.ChangeEvent<HTMLElement>) => void; handleMouseEnter: (e: React.ChangeEvent<HTMLElement>) => void;
handleMouseLeave: (e: React.ChangeEvent<HTMLElement>) => void; handleMouseLeave: (e: React.ChangeEvent<HTMLElement>) => void;
title: string; title: string;
} }
const NoHitsSuggestion = ({ title }: noHitsSuggestionPropType): JSX.Element => { const NoHitsSuggestion = ({ title }: NoHitsSuggestionProps): JSX.Element => {
return ( return (
<div className={'no-hits-footer fcc_suggestion_item'} role='region'> <div className={'no-hits-footer fcc_suggestion_item'} role='region'>
<span className='hit-name'>{title}</span> <span className='hit-name'>{title}</span>

View File

@ -1,7 +1,7 @@
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { HotKeys, ObserveKeys } from 'react-hotkeys'; import { HotKeys, ObserveKeys } from 'react-hotkeys';
import { withTranslation } from 'react-i18next'; import { TFunction, withTranslation } from 'react-i18next';
import { Hit } from 'react-instantsearch-core'; import { Hit } from 'react-instantsearch-core';
import { SearchBox } from 'react-instantsearch-dom'; import { SearchBox } from 'react-instantsearch-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -39,23 +39,23 @@ const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) =>
dispatch dispatch
); );
type searchBarPropType = { type SearchBarProps = {
innerRef?: React.RefObject<HTMLDivElement>; innerRef?: React.RefObject<HTMLDivElement>;
toggleSearchDropdown: typeof toggleSearchDropdown; toggleSearchDropdown: typeof toggleSearchDropdown;
toggleSearchFocused: typeof toggleSearchFocused; toggleSearchFocused: typeof toggleSearchFocused;
updateSearchQuery: typeof updateSearchQuery; updateSearchQuery: typeof updateSearchQuery;
isDropdownEnabled?: boolean; isDropdownEnabled?: boolean;
isSearchFocused?: boolean; isSearchFocused?: boolean;
t?: (label: string) => string; t?: TFunction;
}; };
type classState = { type SearchBarState = {
index: number; index: number;
hits: Array<Hit>; hits: Array<Hit>;
}; };
export class SearchBar extends Component<searchBarPropType, classState> { export class SearchBar extends Component<SearchBarProps, SearchBarState> {
static displayName: string; static displayName: string;
constructor(props: searchBarPropType) { constructor(props: SearchBarProps) {
super(props); super(props);
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
@ -165,18 +165,18 @@ export class SearchBar extends Component<searchBarPropType, classState> {
}; };
keyMap = { keyMap = {
INDEX_UP: ['up'], indexUp: ['up'],
INDEX_DOWN: ['down'] indexDown: ['down']
}; };
keyHandlers = { keyHandlers = {
INDEX_UP: (e: KeyboardEvent | undefined): void => { indexUp: (e: KeyboardEvent | undefined): void => {
e?.preventDefault(); e?.preventDefault();
this.setState(({ index, hits }) => ({ this.setState(({ index, hits }) => ({
index: index === -1 ? hits.length - 1 : index - 1 index: index === -1 ? hits.length - 1 : index - 1
})); }));
}, },
INDEX_DOWN: (e: KeyboardEvent | undefined): void => { indexDown: (e: KeyboardEvent | undefined): void => {
e?.preventDefault(); e?.preventDefault();
this.setState(({ index, hits }) => ({ this.setState(({ index, hits }) => ({
index: index === hits.length - 1 ? -1 : index + 1 index: index === hits.length - 1 ? -1 : index + 1

View File

@ -8,7 +8,7 @@ import NoHitsSuggestion from './no-hits-suggestion';
import Suggestion from './search-suggestion'; import Suggestion from './search-suggestion';
const searchUrl = searchPageUrl; const searchUrl = searchPageUrl;
interface customHitsPropTypes { interface CustomHitsProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
hits: Array<any>; hits: Array<any>;
searchQuery: string; searchQuery: string;
@ -17,7 +17,7 @@ interface customHitsPropTypes {
selectedIndex: number; selectedIndex: number;
handleHits: (currHits: Array<Hit>) => void; handleHits: (currHits: Array<Hit>) => void;
} }
interface searchHitsPropTypes { interface SearchHitsProps {
searchState: SearchState; searchState: SearchState;
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void; handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void; handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
@ -32,7 +32,7 @@ const CustomHits = connectHits(
handleMouseLeave, handleMouseLeave,
selectedIndex, selectedIndex,
handleHits handleHits
}: customHitsPropTypes) => { }: CustomHitsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const noHits = isEmpty(hits); const noHits = isEmpty(hits);
const noHitsTitle = t('search.no-tutorials'); const noHitsTitle = t('search.no-tutorials');
@ -101,7 +101,7 @@ const SearchHits = connectStateResults(
handleMouseLeave, handleMouseLeave,
selectedIndex, selectedIndex,
handleHits handleHits
}: searchHitsPropTypes) => { }: SearchHitsProps) => {
return isEmpty(searchState) || !searchState.query ? null : ( return isEmpty(searchState) || !searchState.query ? null : (
<CustomHits <CustomHits
handleHits={handleHits} handleHits={handleHits}

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Hit } from 'react-instantsearch-core'; import { Hit } from 'react-instantsearch-core';
import { Highlight } from 'react-instantsearch-dom'; import { Highlight } from 'react-instantsearch-dom';
interface suggestionPropTypes { interface SuggestionProps {
hit: Hit; hit: Hit;
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void; handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void; handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
@ -12,7 +12,7 @@ const Suggestion = ({
hit, hit,
handleMouseEnter, handleMouseEnter,
handleMouseLeave handleMouseLeave
}: suggestionPropTypes): JSX.Element => { }: SuggestionProps): JSX.Element => {
const dropdownFooter = hit.objectID.includes('footer-'); const dropdownFooter = hit.objectID.includes('footer-');
return ( return (
<a <a

View File

@ -11,12 +11,11 @@ import NoResults from './no-results';
import './search-page-hits.css'; import './search-page-hits.css';
type allHitType = { type AllHitsProps = {
handleClick?: EventHandler<SyntheticEvent>; handleClick?: EventHandler<SyntheticEvent>;
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any const AllHits: React.ComponentClass<AutocompleteExposed & AllHitsProps> =
const AllHits: React.ComponentClass<AutocompleteExposed & allHitType, any> =
connectAutoComplete(({ hits, currentRefinement }) => { connectAutoComplete(({ hits, currentRefinement }) => {
const isHitsEmpty = !hits.length; const isHitsEmpty = !hits.length;

View File

@ -280,10 +280,10 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
!title || !title ||
!isURL(url, { !isURL(url, {
protocols: ['http', 'https'], protocols: ['http', 'https'],
/* eslint-disable camelcase */ /* eslint-disable camelcase, @typescript-eslint/naming-convention */
require_tld: true, require_tld: true,
require_protocol: true require_protocol: true
/* eslint-enable camelcase */ /* eslint-enable camelcase, @typescript-eslint/naming-convention */
}) })
} }
> >

View File

@ -7,6 +7,7 @@ import type { Dispatch } from 'redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { userSelector } from '../../redux'; import { userSelector } from '../../redux';
import type { ProfileUI } from '../../redux/prop-types';
import { submitProfileUI } from '../../redux/settings'; import { submitProfileUI } from '../../redux/settings';
import FullWidthRow from '../helpers/full-width-row'; import FullWidthRow from '../helpers/full-width-row';
@ -22,24 +23,11 @@ const mapStateToProps = createSelector(userSelector, user => ({
const mapDispatchToProps = (dispatch: Dispatch) => const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators({ submitProfileUI }, dispatch); bindActionCreators({ submitProfileUI }, dispatch);
type ProfileUIType = {
isLocked: boolean;
showAbout: boolean;
showCerts: boolean;
showDonation: boolean;
showHeatMap: boolean;
showLocation: boolean;
showName: boolean;
showPoints: boolean;
showPortfolio: boolean;
showTimeLine: boolean;
};
type PrivacyProps = { type PrivacyProps = {
submitProfileUI: (profileUI: ProfileUIType) => void; submitProfileUI: (profileUI: ProfileUI) => void;
t: TFunction; t: TFunction;
user: { user: {
profileUI: ProfileUIType; profileUI: ProfileUI;
username: string; username: string;
}; };
}; };
@ -56,8 +44,8 @@ function PrivacySettings({
function toggleFlag(flag: string): () => void { function toggleFlag(flag: string): () => void {
return () => { return () => {
const privacyValues = { ...user.profileUI }; const privacyValues = { ...user.profileUI };
privacyValues[flag as keyof ProfileUIType] = privacyValues[flag as keyof ProfileUI] =
!privacyValues[flag as keyof ProfileUIType]; !privacyValues[flag as keyof ProfileUI];
submitProfileUI(privacyValues); submitProfileUI(privacyValues);
}; };
} }

View File

@ -1,32 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { HandlerProps } from 'react-reflex'; import { HandlerProps } from 'react-reflex';
export const FilePropType = PropTypes.shape({
key: PropTypes.string,
ext: PropTypes.string,
name: PropTypes.string,
contents: PropTypes.string,
head: PropTypes.string,
tail: PropTypes.string
});
export const MarkdownRemarkPropType = PropTypes.shape({
html: PropTypes.string,
frontmatter: PropTypes.shape({
title: PropTypes.string,
block: PropTypes.string,
superBlock: PropTypes.string
})
});
export const AllMarkdownRemarkPropType = PropTypes.shape({
edges: PropTypes.arrayOf(
PropTypes.shape({
node: MarkdownRemarkPropType
})
)
});
export const UserPropType = PropTypes.shape({ export const UserPropType = PropTypes.shape({
about: PropTypes.string, about: PropTypes.string,
completedChallenges: PropTypes.arrayOf( completedChallenges: PropTypes.arrayOf(
@ -95,8 +69,6 @@ export const StepsPropType = PropTypes.shape({
isShowProfile: PropTypes.bool isShowProfile: PropTypes.bool
}); });
// TYPESCRIPT TYPES
export type CurrentCert = { export type CurrentCert = {
show: boolean; show: boolean;
title: string; title: string;
@ -244,6 +216,7 @@ export type CertTest = {
}; };
export type User = { export type User = {
calendar: unknown;
about: string; about: string;
acceptedPrivacyTerms: boolean; acceptedPrivacyTerms: boolean;
completedChallenges: CompletedChallenge[]; completedChallenges: CompletedChallenge[];
@ -253,18 +226,20 @@ export type User = {
githubProfile: string; githubProfile: string;
isBanned: boolean; isBanned: boolean;
isCheater: boolean; isCheater: boolean;
isDonating: boolean;
isHonest: boolean; isHonest: boolean;
isGithub: boolean;
isLinkedIn: boolean;
isTwitter: boolean;
isWebsite: boolean;
joinDate: string;
linkedin: string; linkedin: string;
location: string; location: string;
name: string; name: string;
picture: string; picture: string;
points: number; points: number;
portfolio: Portfolio[]; portfolio: Portfolio[];
profileUI: { profileUI: ProfileUI;
isLocked: boolean;
showCerts: boolean;
showName: boolean;
};
progressTimestamps: Array<unknown>; progressTimestamps: Array<unknown>;
sendQuincyEmail: boolean; sendQuincyEmail: boolean;
sound: boolean; sound: boolean;
@ -272,9 +247,24 @@ export type User = {
twitter: string; twitter: string;
username: string; username: string;
website: string; website: string;
} & isCertified; // eslint-disable-next-line @typescript-eslint/no-explicit-any
yearsTopContributor: any[];
} & ClaimedCertifications;
export type isCertified = { export type ProfileUI = {
isLocked: boolean;
showAbout: boolean;
showCerts: boolean;
showDonation: boolean;
showHeatMap: boolean;
showLocation: boolean;
showName: boolean;
showPoints: boolean;
showPortfolio: boolean;
showTimeLine: boolean;
};
export type ClaimedCertifications = {
is2018DataVisCert: boolean; is2018DataVisCert: boolean;
isApisMicroservicesCert: boolean; isApisMicroservicesCert: boolean;
isBackEndCert: boolean; isBackEndCert: boolean;
@ -336,8 +326,6 @@ export type FileKeyChallenge = {
tail: string; tail: string;
}; };
// Extra types built from challengeSchema
export type ChallengeFile = { export type ChallengeFile = {
fileKey: string; fileKey: string;
ext: Ext; ext: Ext;
@ -355,42 +343,3 @@ export type ChallengeFile = {
}; };
export type ChallengeFiles = ChallengeFile[] | null; export type ChallengeFiles = ChallengeFile[] | null;
export interface ChallengeSchema {
block: string;
blockId: string;
challengeOrder: number;
removeComments: boolean;
// TODO: should be typed with possible values
challengeType: number;
checksum: number;
__commentCounts: Record<string, unknown>;
dashedName: string;
description: string;
challengeFiles: ChallengeFiles;
guideUrl: string;
// TODO: should be typed with possible values
helpCategory: string;
videoUrl: string;
forumTopicId: number;
id: string;
instructions: string;
isComingSoon: boolean;
// TODO: Do we still use this
isLocked: boolean;
isPrivate: boolean;
order: number;
videoId?: string;
question: Question;
required: Required[];
solutions: ChallengeFile[][];
superBlock: string;
superOrder: number;
suborder: number;
tests: Test[];
template: string;
time: string;
title: string;
translationPending: boolean;
url?: string;
}

View File

@ -30,12 +30,12 @@ const mapStateToProps = createSelector(
const mapDispatchToProps = { setEditorFocusability, submitChallenge }; const mapDispatchToProps = { setEditorFocusability, submitChallenge };
const keyMap = { const keyMap = {
NAVIGATION_MODE: 'escape', navigationMode: 'escape',
EXECUTE_CHALLENGE: ['ctrl+enter', 'command+enter'], executeChallenge: ['ctrl+enter', 'command+enter'],
FOCUS_EDITOR: 'e', focusEditor: 'e',
FOCUS_INSTRUCTIONS_PANEL: 'r', focusInstructionsPanel: 'r',
NAVIGATE_PREV: ['p'], navigatePrev: ['p'],
NAVIGATE_NEXT: ['n'] navigateNext: ['n']
}; };
interface HotkeysProps { interface HotkeysProps {
@ -70,7 +70,7 @@ function Hotkeys({
usesMultifileEditor usesMultifileEditor
}: HotkeysProps): JSX.Element { }: HotkeysProps): JSX.Element {
const handlers = { const handlers = {
EXECUTE_CHALLENGE: (e: React.KeyboardEvent<HTMLButtonElement>) => { executeChallenge: (e: React.KeyboardEvent<HTMLButtonElement>) => {
// the 'enter' part of 'ctrl+enter' stops HotKeys from listening, so it // the 'enter' part of 'ctrl+enter' stops HotKeys from listening, so it
// needs to be prevented. // needs to be prevented.
// TODO: 'enter' on its own also disables HotKeys, but default behaviour // TODO: 'enter' on its own also disables HotKeys, but default behaviour
@ -91,22 +91,22 @@ function Hotkeys({
executeChallenge({ showCompletionModal: true }); executeChallenge({ showCompletionModal: true });
} }
}, },
FOCUS_EDITOR: (e: React.KeyboardEvent) => { focusEditor: (e: React.KeyboardEvent) => {
e.preventDefault(); e.preventDefault();
if (editorRef && editorRef.current) { if (editorRef && editorRef.current) {
editorRef.current.focus(); editorRef.current.focus();
} }
}, },
FOCUS_INSTRUCTIONS_PANEL: () => { focusInstructionsPanel: () => {
if (instructionsPanelRef && instructionsPanelRef.current) { if (instructionsPanelRef && instructionsPanelRef.current) {
instructionsPanelRef.current.focus(); instructionsPanelRef.current.focus();
} }
}, },
NAVIGATION_MODE: () => setEditorFocusability(false), navigationMode: () => setEditorFocusability(false),
NAVIGATE_PREV: () => { navigatePrev: () => {
if (!canFocusEditor) void navigate(prevChallengePath); if (!canFocusEditor) void navigate(prevChallengePath);
}, },
NAVIGATE_NEXT: () => { navigateNext: () => {
if (!canFocusEditor) void navigate(nextChallengePath); if (!canFocusEditor) void navigate(nextChallengePath);
} }
}; };

View File

@ -16,7 +16,7 @@ import {
isSignedInSelector isSignedInSelector
} from '../../../redux'; } from '../../../redux';
import { StepsType, User } from '../../../redux/prop-types'; import { StepsPropType, UserPropType } from '../../../redux/prop-types';
import { verifyCert } from '../../../redux/settings'; import { verifyCert } from '../../../redux/settings';
import { certMap } from '../../../resources/cert-and-project-map'; import { certMap } from '../../../resources/cert-and-project-map';
@ -31,11 +31,11 @@ const propTypes = {
errored: PropTypes.bool errored: PropTypes.bool
}), }),
isSignedIn: PropTypes.bool, isSignedIn: PropTypes.bool,
steps: StepsType, steps: StepsPropType,
superBlock: PropTypes.string, superBlock: PropTypes.string,
t: PropTypes.func, t: PropTypes.func,
title: PropTypes.string, title: PropTypes.string,
user: User, user: UserPropType,
verifyCert: PropTypes.func.isRequired verifyCert: PropTypes.func.isRequired
}; };

View File

@ -7,7 +7,7 @@ import Caret from '../../../assets/icons/caret';
import GreenNotCompleted from '../../../assets/icons/green-not-completed'; import GreenNotCompleted from '../../../assets/icons/green-not-completed';
// import { navigate } from 'gatsby'; // import { navigate } from 'gatsby';
import GreenPass from '../../../assets/icons/green-pass'; import GreenPass from '../../../assets/icons/green-pass';
import { StepsType } from '../../../redux/prop-types'; import { StepsPropType } from '../../../redux/prop-types';
import ClaimCertSteps from './ClaimCertSteps'; import ClaimCertSteps from './ClaimCertSteps';
const propTypes = { const propTypes = {
@ -17,7 +17,7 @@ const propTypes = {
numberOfSteps: PropTypes.number, numberOfSteps: PropTypes.number,
completedCount: PropTypes.number completedCount: PropTypes.number
}), }),
steps: StepsType, steps: StepsPropType,
superBlock: PropTypes.string superBlock: PropTypes.string
}; };

View File

@ -4,14 +4,14 @@ import React from 'react';
import { withTranslation, useTranslation } from 'react-i18next'; 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 { StepsType } from '../../../redux/prop-types'; import { StepsPropType } from '../../../redux/prop-types';
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' }; const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
const propTypes = { const propTypes = {
i18nCertText: PropTypes.string, i18nCertText: PropTypes.string,
isProjectsCompleted: PropTypes.bool, isProjectsCompleted: PropTypes.bool,
steps: StepsType, steps: StepsPropType,
superBlock: PropTypes.string superBlock: PropTypes.string
}; };

View File

@ -59,34 +59,32 @@ interface SessionUser {
sessionMeta: { activeDonations: number }; sessionMeta: { activeDonations: number };
} }
type challengeFilesForFiles = { type ChallengeFilesForFiles = {
files: Array<Omit<ChallengeFile, 'fileKey'> & { key: string }>; files: Array<Omit<ChallengeFile, 'fileKey'> & { key: string }>;
} & Omit<CompletedChallenge, 'challengeFiles'>; } & Omit<CompletedChallenge, 'challengeFiles'>;
type ApiSessionResponse = Omit<SessionUser, 'user'>; type ApiSessionResponse = Omit<SessionUser, 'user'>;
type ApiUser = { type ApiUser = {
user: { user: {
[username: string]: ApiUserType; [username: string]: Omit<User, 'completedChallenges'> & {
completedChallenges?: ChallengeFilesForFiles[];
};
}; };
result?: string; result?: string;
}; };
type ApiUserType = Omit<User, 'completedChallenges'> & { type UserResponse = {
completedChallenges?: challengeFilesForFiles[];
};
type UserResponseType = {
user: { [username: string]: User } | Record<string, never>; user: { [username: string]: User } | Record<string, never>;
result: string | undefined; result: string | undefined;
}; };
function parseApiResponseToClientUser(data: ApiUser): UserResponseType { function parseApiResponseToClientUser(data: ApiUser): UserResponse {
const userData = data.user?.[data?.result ?? '']; const userData = data.user?.[data?.result ?? ''];
let completedChallenges: CompletedChallenge[] = []; let completedChallenges: CompletedChallenge[] = [];
if (userData) { if (userData) {
completedChallenges = completedChallenges =
userData.completedChallenges?.reduce( userData.completedChallenges?.reduce(
(acc: CompletedChallenge[], curr: challengeFilesForFiles) => { (acc: CompletedChallenge[], curr: ChallengeFilesForFiles) => {
return [ return [
...acc, ...acc,
{ {
@ -123,7 +121,7 @@ export function getSessionUser(): Promise<SessionUser> {
} }
type UserProfileResponse = { type UserProfileResponse = {
entities: Omit<UserResponseType, 'result'>; entities: Omit<UserResponse, 'result'>;
result: string | undefined; result: string | undefined;
}; };
export function getUserProfile(username: string): Promise<UserProfileResponse> { export function getUserProfile(username: string): Promise<UserProfileResponse> {

View File

@ -1,4 +1,4 @@
type CreateTypesType = { type ActionTypes = {
[action: string]: string; [action: string]: string;
}; };
@ -13,7 +13,7 @@ type CreateTypesType = {
* @param {string} ns Name of the namespace. * @param {string} ns Name of the namespace.
* @returns {object} Object with action types. * @returns {object} Object with action types.
*/ */
export function createTypes(types: string[], ns: string): CreateTypesType { export function createTypes(types: string[], ns: string): ActionTypes {
return types.reduce( return types.reduce(
(types, action: string) => ({ (types, action: string) => ({
...types, ...types,